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('/]+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(['

', '

', '
', '
'], ["", "\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(['

', '

', '
', '
'], ["", "\n", "\n", "\n"], $textAfter); $text = trim(strip_tags($textWithNewlines)); if (!empty($text)) { $parts[] = ['type' => 'text', 'content' => $text]; } } if (empty($parts)) { $textWithNewlines = str_ireplace(['

', '

', '
', '
'], ["", "\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; } } }