451 lines
14 KiB
PHP
451 lines
14 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_is_forum TINYINT(1) NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (cl_scwebhook_id)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
|
|
);
|
|
|
|
$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(
|
|
"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,
|
|
];
|
|
}
|
|
|