Commit inicial con archivos existentes
This commit is contained in:
266
discord/DiscordSender.php
Executable file
266
discord/DiscordSender.php
Executable file
@@ -0,0 +1,266 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user