266 lines
10 KiB
PHP
Executable File
266 lines
10 KiB
PHP
Executable File
<?php
|
|
|
|
class DiscordSender
|
|
{
|
|
private const API_BASE_URL = 'https://discord.com/api/v10';
|
|
private const MESSAGE_CHAR_LIMIT = 1990;
|
|
private const LOG_FILE = __DIR__ . '/../logs/discord_api.log';
|
|
|
|
private string $token;
|
|
|
|
public function __construct(string $token)
|
|
{
|
|
custom_log('[DiscordSender] Initializing...');
|
|
$this->token = $token;
|
|
}
|
|
|
|
public function sendMessage(string $discordId, string $content, string $recipientType = 'channel') {
|
|
custom_log("[DiscordSender] sendMessage: Called for ID {$discordId} and recipient type {$recipientType}.");
|
|
|
|
try {
|
|
if (empty(trim($content))) {
|
|
$this->logMessage("Error: No se puede enviar un mensaje vacío");
|
|
return false;
|
|
}
|
|
|
|
$targetChannelId = $this->getTargetChannelId($discordId, $recipientType);
|
|
custom_log("[DiscordSender] sendMessage: Target channel ID is {$targetChannelId}.");
|
|
|
|
$parts = [];
|
|
|
|
preg_match_all('/<img[^>]+src=[\'"]([^\'"]+)[\'"][^>]*>/i', $content, $imageMatches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
|
|
|
$lastPos = 0;
|
|
|
|
foreach ($imageMatches as $match) {
|
|
$imageTag = $match[0][0];
|
|
$imageUrl = $match[1][0];
|
|
$imagePos = $match[0][1];
|
|
|
|
$textBefore = trim(substr($content, $lastPos, $imagePos - $lastPos));
|
|
if (!empty($textBefore)) {
|
|
$textWithNewlines = str_ireplace(['<p>', '</p>', '<br>', '<br />'], ["", "\n", "\n", "\n"], $textBefore);
|
|
$text = trim(strip_tags($textWithNewlines));
|
|
if (!empty($text)) {
|
|
$parts[] = ['type' => 'text', 'content' => $text];
|
|
}
|
|
}
|
|
|
|
if (!empty($imageUrl)) {
|
|
$absoluteImageUrl = $imageUrl;
|
|
if (strpos($imageUrl, 'http') !== 0 && strpos($imageUrl, '//') !== 0) {
|
|
$base = rtrim(BOT_BASE_URL, '/');
|
|
$path = ltrim($imageUrl, '/');
|
|
$absoluteImageUrl = "{$base}/{$path}";
|
|
}
|
|
$parts[] = ['type' => 'image', 'url' => $absoluteImageUrl];
|
|
}
|
|
|
|
$lastPos = $imagePos + strlen($imageTag);
|
|
}
|
|
|
|
$textAfter = trim(substr($content, $lastPos));
|
|
if (!empty($textAfter)) {
|
|
$textWithNewlines = str_ireplace(['<p>', '</p>', '<br>', '<br />'], ["", "\n", "\n", "\n"], $textAfter);
|
|
$text = trim(strip_tags($textWithNewlines));
|
|
if (!empty($text)) {
|
|
$parts[] = ['type' => 'text', 'content' => $text];
|
|
}
|
|
}
|
|
|
|
if (empty($parts)) {
|
|
$textWithNewlines = str_ireplace(['<p>', '</p>', '<br>', '<br />'], ["", "\n", "\n", "\n"], $content);
|
|
$text = trim(strip_tags($textWithNewlines));
|
|
if (!empty($text)) {
|
|
$parts[] = ['type' => 'text', 'content' => $text];
|
|
}
|
|
}
|
|
|
|
if (empty($parts)) {
|
|
return false;
|
|
}
|
|
|
|
$messageIds = [];
|
|
$allPartsSentSuccessfully = true;
|
|
|
|
foreach ($parts as $part) {
|
|
if ($part['type'] === 'text') {
|
|
$chunks = $this->splitMessage($part['content']);
|
|
foreach ($chunks as $chunk) {
|
|
$trimmedChunk = trim($chunk);
|
|
if ($trimmedChunk === '') continue;
|
|
|
|
try {
|
|
$response = $this->sendDiscordMessage($targetChannelId, ['content' => $trimmedChunk]);
|
|
if (isset($response['id'])) {
|
|
$messageIds[] = $response['id'];
|
|
} else {
|
|
$allPartsSentSuccessfully = false; break;
|
|
}
|
|
} catch (Exception $e) {
|
|
$this->logMessage("Error al enviar texto: " . $e->getMessage());
|
|
$allPartsSentSuccessfully = false; break;
|
|
}
|
|
usleep(250000);
|
|
}
|
|
} elseif ($part['type'] === 'image') {
|
|
try {
|
|
$response = $this->sendDiscordMessage($targetChannelId, ['content' => $part['url']]);
|
|
if (isset($response['id'])) {
|
|
$messageIds[] = $response['id'];
|
|
} else {
|
|
$allPartsSentSuccessfully = false; break;
|
|
}
|
|
} catch (Exception $e) {
|
|
$this->logMessage("Error al enviar imagen como URL: " . $e->getMessage());
|
|
$allPartsSentSuccessfully = false; break;
|
|
}
|
|
}
|
|
if (!$allPartsSentSuccessfully) break;
|
|
usleep(500000);
|
|
}
|
|
|
|
return $allPartsSentSuccessfully ? $messageIds : false;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logMessage("Error in sendMessage: " . $e->getMessage());
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
private function getTargetChannelId(string $discordId, string $recipientType): string {
|
|
if ($recipientType === 'user') {
|
|
return $this->createDMChannel($discordId);
|
|
}
|
|
return $discordId;
|
|
}
|
|
|
|
private function createDMChannel(string $userId): string {
|
|
$url = self::API_BASE_URL . '/users/@me/channels';
|
|
$data = json_encode(['recipient_id' => $userId]);
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_HTTPHEADER => [
|
|
'Authorization: Bot ' . $this->token,
|
|
'Content-Type: application/json',
|
|
'Content-Length: ' . strlen($data)
|
|
],
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => $data
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
if (curl_errno($ch)) {
|
|
throw new Exception('cURL error: ' . curl_error($ch));
|
|
}
|
|
|
|
curl_close($ch);
|
|
|
|
if ($httpCode !== 200) {
|
|
throw new Exception("Failed to create DM channel. HTTP code: $httpCode, Response: $response");
|
|
}
|
|
|
|
$responseData = json_decode($response, true);
|
|
return $responseData['id'];
|
|
}
|
|
|
|
private function sendDiscordMessage(string $channelId, array $payload, array $files = []) {
|
|
$url = self::API_BASE_URL . "/channels/{$channelId}/messages";
|
|
|
|
if (isset($payload['content'])) {
|
|
$payload['content'] = trim($payload['content']);
|
|
if ($payload['content'] === '') unset($payload['content']);
|
|
}
|
|
|
|
if (empty($payload['content'] ?? '') && empty($payload['embeds'] ?? '') && empty($files)) {
|
|
throw new Exception("No se puede enviar un mensaje vacío");
|
|
}
|
|
|
|
$ch = curl_init($url);
|
|
$headers = ['Authorization: Bot ' . $this->token, 'User-Agent: DiscordBot (v1.0)'] ;
|
|
|
|
if (empty($files)) {
|
|
$headers[] = 'Content-Type: application/json';
|
|
$postData = json_encode($payload);
|
|
} else {
|
|
// Multipart logic for files would go here if needed
|
|
}
|
|
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_HTTPHEADER => $headers,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => $postData
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
$responseData = json_decode($response, true);
|
|
|
|
if ($httpCode >= 400) {
|
|
throw new Exception("Discord API error ({$httpCode}): " . ($responseData['message'] ?? 'Unknown error'));
|
|
}
|
|
|
|
return $responseData;
|
|
}
|
|
|
|
private function splitMessage(string $text, int $maxLength = self::MESSAGE_CHAR_LIMIT): array
|
|
{
|
|
$chunks = [];
|
|
$text = str_replace(["\r\n", "\r"], "\n", $text);
|
|
$lines = explode("\n", $text);
|
|
$currentChunk = '';
|
|
|
|
foreach ($lines as $line) {
|
|
if (mb_strlen($currentChunk, 'UTF-8') + mb_strlen($line, 'UTF-8') + 1 > $maxLength) {
|
|
$chunks[] = $currentChunk;
|
|
$currentChunk = $line;
|
|
} else {
|
|
$currentChunk .= (empty($currentChunk) ? '' : "\n") . $line;
|
|
}
|
|
}
|
|
if (!empty($currentChunk)) $chunks[] = $currentChunk;
|
|
|
|
return $chunks;
|
|
}
|
|
|
|
private function logMessage(string $message): void {
|
|
$logMessage = date('[Y-m-d H:i:s] ') . $message . "\n";
|
|
file_put_contents(self::LOG_FILE, $logMessage, FILE_APPEND | LOCK_EX);
|
|
}
|
|
|
|
public function sendRawMessage(string $channelId, string $content): ?array
|
|
{
|
|
custom_log("[DiscordSender] sendRawMessage: Called for channel ID {$channelId}.");
|
|
try {
|
|
return $this->sendDiscordMessage($channelId, ['content' => $content]);
|
|
} catch (Exception $e) {
|
|
$this->logMessage("Error in sendRawMessage: " . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Envía un Embed (construido como un array) a un canal de Discord.
|
|
*
|
|
* @param string $channelId El ID del canal de destino.
|
|
* @param array $embedData El array que representa el embed.
|
|
* @return array|null La respuesta de la API de Discord o null si hay un error.
|
|
*/
|
|
public function sendEmbedData(string $channelId, array $embedData): ?array
|
|
{
|
|
custom_log("[DiscordSender] sendEmbedData: Called for channel ID {$channelId}.");
|
|
try {
|
|
return $this->sendDiscordMessage($channelId, ['embeds' => [$embedData]]);
|
|
} catch (Exception $e) {
|
|
$this->logMessage("Error in sendEmbedData: " . $e->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
} |