Files
sistema_funcionando_lastwar/discord/DiscordSender.php

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;
}
}
}