39514-vm/db/scdiscord.php
Flatlogic Bot 37e7f940e8 V1.4.4
2026-04-29 07:11:28 +00:00

479 lines
16 KiB
PHP

<?php
require_once __DIR__ . '/config.php';
function scdiscord_bootstrap(): void
{
static $bootstrapped = false;
if ($bootstrapped) {
return;
}
$db = db();
$db->exec(
"CREATE TABLE IF NOT EXISTS tbl_scwebhooks (
cl_scwebhook_id INT(11) NOT NULL AUTO_INCREMENT,
cl_scwebhook_name VARCHAR(255) NOT NULL,
cl_scwebhook_url TEXT NOT NULL,
cl_scwebhook_image_url TEXT NOT NULL,
cl_scwebhook_border_color VARCHAR(20) NOT NULL DEFAULT '#ffae00',
cl_scwebhook_is_forum TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (cl_scwebhook_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
);
$columns_stmt = $db->query("SHOW COLUMNS FROM tbl_scwebhooks LIKE 'cl_scwebhook_image_url'");
$has_webhook_image = (bool) $columns_stmt->fetch();
if (!$has_webhook_image) {
$db->exec("ALTER TABLE tbl_scwebhooks ADD COLUMN cl_scwebhook_image_url TEXT NOT NULL AFTER cl_scwebhook_url");
}
$columns_stmt = $db->query("SHOW COLUMNS FROM tbl_scwebhooks LIKE 'cl_scwebhook_border_color'");
$has_webhook_border_color = (bool) $columns_stmt->fetch();
if (!$has_webhook_border_color) {
$db->exec("ALTER TABLE tbl_scwebhooks ADD COLUMN cl_scwebhook_border_color VARCHAR(20) NOT NULL DEFAULT '#ffae00' AFTER cl_scwebhook_image_url");
}
$db->exec(
"CREATE TABLE IF NOT EXISTS tbl_scbanners (
cl_scbanner_id INT(11) NOT NULL AUTO_INCREMENT,
cl_scbanner_name VARCHAR(255) NOT NULL,
cl_scbanner_url TEXT NOT NULL,
cl_scbanner_border_color VARCHAR(20) NOT NULL DEFAULT '#ffae00',
PRIMARY KEY (cl_scbanner_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
);
$columns_stmt = $db->query("SHOW COLUMNS FROM tbl_scbanners LIKE 'cl_scbanner_border_color'");
$has_border_color = (bool) $columns_stmt->fetch();
if (!$has_border_color) {
$db->exec("ALTER TABLE tbl_scbanners ADD COLUMN cl_scbanner_border_color VARCHAR(20) NOT NULL DEFAULT '#ffae00' AFTER cl_scbanner_url");
}
$db->exec(
"UPDATE tbl_scwebhooks w
LEFT JOIN tbl_scbanners b ON b.cl_scbanner_name = w.cl_scwebhook_name
SET
w.cl_scwebhook_image_url = CASE
WHEN (w.cl_scwebhook_image_url IS NULL OR w.cl_scwebhook_image_url = '') AND b.cl_scbanner_url IS NOT NULL THEN b.cl_scbanner_url
ELSE w.cl_scwebhook_image_url
END,
w.cl_scwebhook_border_color = CASE
WHEN (w.cl_scwebhook_border_color IS NULL OR w.cl_scwebhook_border_color = '' OR w.cl_scwebhook_border_color = '#ffae00') AND b.cl_scbanner_border_color IS NOT NULL THEN b.cl_scbanner_border_color
ELSE w.cl_scwebhook_border_color
END"
);
$db->exec(
"CREATE TABLE IF NOT EXISTS tbl_scnotifications (
cl_scnotification_id INT(11) NOT NULL AUTO_INCREMENT,
cl_scnotification_webhook_id INT(11) NOT NULL,
cl_scnotification_banner_id INT(11) DEFAULT NULL,
cl_scnotification_title VARCHAR(255) NOT NULL DEFAULT '',
cl_scnotification_message TEXT NOT NULL,
cl_scnotification_payload LONGTEXT NOT NULL,
cl_scnotification_response LONGTEXT DEFAULT NULL,
cl_scnotification_success TINYINT(1) NOT NULL DEFAULT 0,
cl_scnotification_created_by VARCHAR(190) NOT NULL,
cl_scnotification_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (cl_scnotification_id),
KEY idx_scnotification_webhook (cl_scnotification_webhook_id),
KEY idx_scnotification_banner (cl_scnotification_banner_id),
CONSTRAINT fk_scnotification_webhook FOREIGN KEY (cl_scnotification_webhook_id)
REFERENCES tbl_scwebhooks (cl_scwebhook_id)
ON UPDATE CASCADE
ON DELETE RESTRICT,
CONSTRAINT fk_scnotification_banner FOREIGN KEY (cl_scnotification_banner_id)
REFERENCES tbl_scbanners (cl_scbanner_id)
ON UPDATE CASCADE
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
);
$stmt_existing_red = $db->prepare('SELECT cl_scbanner_id FROM tbl_scbanners WHERE cl_scbanner_name = :name LIMIT 1');
$stmt_existing_red->execute(['name' => 'Alerte Rouge']);
$existing_red_banner = $stmt_existing_red->fetch(PDO::FETCH_ASSOC);
if ($existing_red_banner) {
$stmt_update_red = $db->prepare(
'UPDATE tbl_scbanners
SET cl_scbanner_border_color = CASE
WHEN cl_scbanner_border_color IS NULL OR cl_scbanner_border_color = "" OR cl_scbanner_border_color = "#ffae00"
THEN :border_color
ELSE cl_scbanner_border_color
END
WHERE cl_scbanner_id = :id'
);
$stmt_update_red->execute([
'border_color' => '#ff3b30',
'id' => $existing_red_banner['cl_scbanner_id'],
]);
}
$bootstrapped = true;
}
function scdiscord_mask_webhook_url(string $url): string
{
$trimmed = trim($url);
if ($trimmed === '') {
return '';
}
$length = strlen($trimmed);
if ($length <= 24) {
return str_repeat('•', max(8, $length));
}
return substr($trimmed, 0, 32) . str_repeat('•', 18) . substr($trimmed, -10);
}
function scdiscord_normalize_hex_color(string $color): string
{
$candidate = strtoupper(trim($color));
if ($candidate === '') {
return '#FFAE00';
}
if ($candidate[0] !== '#') {
$candidate = '#' . $candidate;
}
if (!preg_match('/^#[0-9A-F]{6}$/', $candidate)) {
return '#FFAE00';
}
return $candidate;
}
function scdiscord_hex_to_decimal(string $color): int
{
return hexdec(ltrim(scdiscord_normalize_hex_color($color), '#'));
}
function scdiscord_build_mentions(bool $notify_here, bool $notify_everyone): array
{
$parts = [];
if ($notify_here) {
$parts[] = '@here';
}
if ($notify_everyone) {
$parts[] = '@everyone';
}
return $parts;
}
function scdiscord_build_thread_name(string $title, string $location, string $start_date): string
{
$parts = [];
if ($title !== '') {
$parts[] = $title;
}
if ($location !== '') {
$parts[] = $location;
}
if ($start_date !== '') {
$parts[] = $start_date;
}
$thread_name = trim(implode(' • ', $parts));
if ($thread_name === '') {
$thread_name = 'Notification Discord';
}
return mb_substr($thread_name, 0, 100);
}
function scdiscord_post_webhook(string $webhook_url, array $payload): array
{
$target_url = $webhook_url;
$separator = (str_contains($target_url, '?')) ? '&' : '?';
$target_url .= $separator . 'wait=true';
$json_payload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json_payload === false) {
return [
'success' => false,
'http_code' => 0,
'response' => 'Erreur d\'encodage JSON.',
];
}
if (function_exists('curl_init')) {
$ch = curl_init($target_url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $json_payload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Content-Length: ' . strlen($json_payload),
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
]);
$response = curl_exec($ch);
$http_code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($response === false) {
return [
'success' => false,
'http_code' => $http_code,
'response' => $curl_error !== '' ? $curl_error : 'Erreur CURL inconnue.',
];
}
return [
'success' => $http_code >= 200 && $http_code < 300,
'http_code' => $http_code,
'response' => $response,
];
}
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => $json_payload,
'timeout' => 20,
'ignore_errors' => true,
],
]);
$response = @file_get_contents($target_url, false, $context);
$http_code = 0;
if (isset($http_response_header) && is_array($http_response_header)) {
foreach ($http_response_header as $header_line) {
if (preg_match('#^HTTP/\S+\s+(\d{3})#', $header_line, $matches)) {
$http_code = (int) $matches[1];
break;
}
}
}
return [
'success' => $response !== false && $http_code >= 200 && $http_code < 300,
'http_code' => $http_code,
'response' => $response === false ? 'Erreur lors de la requête HTTP.' : $response,
];
}
function scdiscord_get_bot_token(): string
{
$candidates = [];
if (defined('DISCORD_BOT_TOKEN') && is_string(DISCORD_BOT_TOKEN)) {
$candidates[] = DISCORD_BOT_TOKEN;
}
foreach (['DISCORD_BOT_TOKEN', 'SC_DISCORD_BOT_TOKEN', 'BOT_TOKEN'] as $env_key) {
$value = getenv($env_key);
if ($value !== false) {
$candidates[] = $value;
}
}
foreach ($candidates as $candidate) {
$token = trim((string) $candidate);
if ($token !== '') {
return $token;
}
}
return '';
}
function scdiscord_decode_json_response(?string $response): array
{
if (!is_string($response) || trim($response) === '') {
return [];
}
$decoded = json_decode($response, true);
return is_array($decoded) ? $decoded : [];
}
function scdiscord_bot_request(string $method, string $url, string $bot_token, ?array $payload = null): array
{
$headers = [
'Authorization: Bot ' . $bot_token,
];
$json_payload = null;
if ($payload !== null) {
$json_payload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json_payload === false) {
return [
'success' => false,
'http_code' => 0,
'response' => 'Erreur d\'encodage JSON.',
];
}
$headers[] = 'Content-Type: application/json';
$headers[] = 'Content-Length: ' . strlen($json_payload);
}
if (function_exists('curl_init')) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => strtoupper($method),
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
]);
if ($json_payload !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $json_payload);
}
$response = curl_exec($ch);
$http_code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($response === false) {
return [
'success' => false,
'http_code' => $http_code,
'response' => $curl_error !== '' ? $curl_error : 'Erreur CURL inconnue.',
];
}
return [
'success' => $http_code >= 200 && $http_code < 300,
'http_code' => $http_code,
'response' => $response,
];
}
$header_lines = implode("\r\n", $headers) . "\r\n";
$context = stream_context_create([
'http' => [
'method' => strtoupper($method),
'header' => $header_lines,
'content' => $json_payload ?? '',
'timeout' => 20,
'ignore_errors' => true,
],
]);
$response = @file_get_contents($url, false, $context);
$http_code = 0;
if (isset($http_response_header) && is_array($http_response_header)) {
foreach ($http_response_header as $header_line) {
if (preg_match('#^HTTP/\S+\s+(\d{3})#', $header_line, $matches)) {
$http_code = (int) $matches[1];
break;
}
}
}
return [
'success' => $response !== false && $http_code >= 200 && $http_code < 300,
'http_code' => $http_code,
'response' => $response === false ? 'Erreur lors de la requête HTTP.' : $response,
];
}
function scdiscord_apply_bot_actions(array $message_data, bool $use_reactions, bool $use_publicthread, string $thread_name): array
{
if (!$use_reactions && !$use_publicthread) {
return [
'success' => true,
'http_code' => 200,
'response' => 'Aucune action bot demandée.',
'details' => [],
];
}
$message_id = trim((string) ($message_data['id'] ?? ''));
$channel_id = trim((string) ($message_data['channel_id'] ?? ''));
if ($message_id === '' || $channel_id === '') {
return [
'success' => false,
'http_code' => 0,
'response' => 'Réponse Discord invalide : id de message ou channel_id manquant.',
'details' => [],
];
}
$bot_token = scdiscord_get_bot_token();
if ($bot_token === '') {
return [
'success' => false,
'http_code' => 0,
'response' => 'Token bot Discord manquant. Définis DISCORD_BOT_TOKEN côté serveur.',
'details' => [],
];
}
$details = [];
$last_http_code = 200;
$failed = false;
if ($use_reactions) {
foreach (['👍', '⌛', '❔', '👎'] as $emoji) {
$url = 'https://discord.com/api/v10/channels/' . rawurlencode($channel_id) . '/messages/' . rawurlencode($message_id) . '/reactions/' . rawurlencode($emoji) . '/@me';
$result = scdiscord_bot_request('PUT', $url, $bot_token);
$action_success = !empty($result['success']);
$last_http_code = (int) ($result['http_code'] ?? $last_http_code);
$details[] = [
'action' => 'reaction',
'emoji' => $emoji,
'success' => $action_success,
'http_code' => $last_http_code,
'response' => (string) ($result['response'] ?? ''),
];
if (!$action_success) {
$failed = true;
}
sleep(1);
}
}
if ($use_publicthread) {
$thread_payload = [
'name' => $thread_name !== '' ? $thread_name : 'Discussion - Opération',
'auto_archive_duration' => 1440,
'type' => 11,
];
$url = 'https://discord.com/api/v10/channels/' . rawurlencode($channel_id) . '/messages/' . rawurlencode($message_id) . '/threads';
$result = scdiscord_bot_request('POST', $url, $bot_token, $thread_payload);
$action_success = !empty($result['success']);
$last_http_code = (int) ($result['http_code'] ?? $last_http_code);
$details[] = [
'action' => 'thread',
'success' => $action_success,
'http_code' => $last_http_code,
'response' => (string) ($result['response'] ?? ''),
];
if (!$action_success) {
$failed = true;
}
}
return [
'success' => !$failed,
'http_code' => $failed ? $last_http_code : 200,
'response' => json_encode($details, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'details' => $details,
];
}