From cbeced3a2aa5b890088f573ab5c6f290da9a839a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 4 Apr 2026 20:21:45 +0200 Subject: [PATCH] Userverbindung Telegram ist drin --- public/header.php | 1 + public/telegram/broadcast_test.php | 526 +++++++++++++++++++++++++++++ public/telegram/webhook.php | 72 ++++ 3 files changed, 599 insertions(+) create mode 100644 public/telegram/broadcast_test.php diff --git a/public/header.php b/public/header.php index 4581fd7..555a055 100644 --- a/public/header.php +++ b/public/header.php @@ -78,6 +78,7 @@ $menuGroups = [ $adminLinks = [ ['href' => 'admin_users.php', 'label' => 'Benutzerverwaltung'], ['href' => 'monatsvorhersage.php', 'label' => 'Monatsvorhersage'], + ['href' => 'telegram/broadcast_test.php', 'label' => 'Telegram-Test'], ]; ?> diff --git a/public/telegram/broadcast_test.php b/public/telegram/broadcast_test.php new file mode 100644 index 0000000..1ec9dbf --- /dev/null +++ b/public/telegram/broadcast_test.php @@ -0,0 +1,526 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ] +); + +$currentUserId = (int) $_SESSION['user_id']; +$loggedIn = true; +$publicBasePath = '../'; +$pageTitle = 'Telegram-Testversand - AstroTools'; +$headerIntroPre = 'Administration'; +$headerIntroTitle = 'Telegram-Testversand'; +$headerIntroSub = 'Testnachricht an alle verknuepften Telegram-Konten senden.'; +$errors = []; +$successMessage = ''; +$resultRows = []; +$messageText = "Testnachricht von SkyView\n\nDer Telegram-Bot ist jetzt erfolgreich verbunden."; +$sendMode = 'self'; +$selectedRecipientIds = []; + +function h(?string $value): string +{ + return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); +} + +function textLength(string $value): int +{ + if (function_exists('mb_strlen')) { + return mb_strlen($value); + } + + return strlen($value); +} + +function telegramHttpPostJson(string $url, array $payload): array +{ + $jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + if ($jsonPayload === false) { + return [ + 'ok' => false, + 'status_code' => 0, + 'body' => null, + 'error' => 'Die Telegram-Anfrage konnte nicht als JSON kodiert werden.', + ]; + } + + if (DIRECTORY_SEPARATOR === '\\' && function_exists('shell_exec')) { + $urlBase64 = base64_encode($url); + $payloadBase64 = base64_encode($jsonPayload); + $powerShellScript = str_replace( + ['__URL_BASE64__', '__PAYLOAD_BASE64__'], + [$urlBase64, $payloadBase64], + <<<'PS' +$ErrorActionPreference = 'Stop' +$url = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('__URL_BASE64__')) +$body = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('__PAYLOAD_BASE64__')) +try { + $response = Invoke-RestMethod -Method Post -Uri $url -ContentType 'application/json' -Body $body + [PSCustomObject]@{ + ok = $true + body = ($response | ConvertTo-Json -Depth 20 -Compress) + error = '' + } | ConvertTo-Json -Compress +} catch { + $stream = $null + $responseBody = '' + if ($_.Exception.Response) { + try { + $stream = $_.Exception.Response.GetResponseStream() + if ($stream) { + $reader = New-Object System.IO.StreamReader($stream) + $responseBody = $reader.ReadToEnd() + } + } catch { + } finally { + if ($stream) { + $stream.Dispose() + } + } + } + [PSCustomObject]@{ + ok = $false + body = $responseBody + error = $_.Exception.Message + } | ConvertTo-Json -Compress +} +PS + ); + $encodedCommandBinary = function_exists('iconv') + ? iconv('UTF-8', 'UTF-16LE', $powerShellScript) + : $powerShellScript; + $encodedCommand = base64_encode($encodedCommandBinary === false ? $powerShellScript : $encodedCommandBinary); + $command = 'powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand ' . $encodedCommand; + $rawOutput = shell_exec($command); + + if ($rawOutput !== null && trim($rawOutput) !== '') { + $decodedShell = json_decode(trim($rawOutput), true); + if (is_array($decodedShell)) { + return [ + 'ok' => (bool) ($decodedShell['ok'] ?? false), + 'status_code' => 0, + 'body' => $decodedShell['body'] ?? null, + 'error' => $decodedShell['error'] ?? null, + ]; + } + } + } + + if (function_exists('curl_init')) { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($jsonPayload), + ], + CURLOPT_POSTFIELDS => $jsonPayload, + CURLOPT_TIMEOUT => 20, + ]); + + $body = curl_exec($ch); + $curlError = curl_error($ch); + $statusCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE); + curl_close($ch); + + if ($body === false) { + return [ + 'ok' => false, + 'status_code' => $statusCode, + 'body' => null, + 'error' => $curlError !== '' ? $curlError : 'Die Telegram-Anfrage ist fehlgeschlagen.', + ]; + } + + return [ + 'ok' => true, + 'status_code' => $statusCode, + 'body' => $body, + 'error' => null, + ]; + } + + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => implode("\r\n", [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($jsonPayload), + ]), + 'content' => $jsonPayload, + 'timeout' => 20, + 'ignore_errors' => true, + ], + ]); + + $body = @file_get_contents($url, false, $context); + $statusCode = 0; + + if (isset($http_response_header) && is_array($http_response_header)) { + foreach ($http_response_header as $headerLine) { + if (preg_match('/^HTTP\/\S+\s+(\d{3})\b/', $headerLine, $matches) === 1) { + $statusCode = (int) $matches[1]; + break; + } + } + } + + if ($body === false) { + return [ + 'ok' => false, + 'status_code' => $statusCode, + 'body' => null, + 'error' => 'Die Telegram-Anfrage ist fehlgeschlagen. Auf diesem Server ist moeglicherweise weder cURL noch der HTTPS-Stream-Wrapper verfuegbar.', + ]; + } + + return [ + 'ok' => true, + 'status_code' => $statusCode, + 'body' => $body, + 'error' => null, + ]; +} + +function sendTelegramMessage(string $botToken, string $chatId, string $messageText): array +{ + $response = telegramHttpPostJson( + 'https://api.telegram.org/bot' . $botToken . '/sendMessage', + [ + 'chat_id' => $chatId, + 'text' => $messageText, + ] + ); + + if (!$response['ok']) { + return [ + 'ok' => false, + 'description' => (string) ($response['error'] ?? 'Telegram konnte nicht erreicht werden.'), + ]; + } + + $decoded = json_decode((string) $response['body'], true); + if (!is_array($decoded)) { + return [ + 'ok' => false, + 'description' => 'Telegram hat keine gueltige JSON-Antwort geliefert.', + ]; + } + + return [ + 'ok' => (bool) ($decoded['ok'] ?? false), + 'description' => (string) ($decoded['description'] ?? ''), + ]; +} + +if (empty($_SESSION['telegram_broadcast_csrf'])) { + $_SESSION['telegram_broadcast_csrf'] = bin2hex(random_bytes(16)); +} + +$stmtCurrentUser = $pdo->prepare(" + SELECT `id`, `username`, `display_name`, `role`, `is_active`, `telegram_chat_id` + FROM `app_users` + WHERE `id` = :id + LIMIT 1 +"); +$stmtCurrentUser->execute([':id' => $currentUserId]); +$currentUser = $stmtCurrentUser->fetch(); + +if ( + !$currentUser || + $currentUser['role'] !== 'admin' || + (int) $currentUser['is_active'] !== 1 +) { + $_SESSION = []; + session_destroy(); + header('Location: ../login.php'); + exit; +} + +$stmtRecipients = $pdo->query(" + SELECT + `id`, + `username`, + `display_name`, + `telegram_username`, + `telegram_chat_id`, + `telegram_connected_at` + FROM `app_users` + WHERE `is_active` = 1 + AND `telegram_chat_id` IS NOT NULL + AND `telegram_chat_id` <> '' + ORDER BY COALESCE(`display_name`, `username`) ASC, `id` ASC +"); +$recipients = $stmtRecipients->fetchAll(); +$recipientIndex = []; +foreach ($recipients as $recipient) { + $recipientIndex[(int) $recipient['id']] = $recipient; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $action = (string) ($_POST['action'] ?? ''); + $csrfToken = (string) ($_POST['csrf_token'] ?? ''); + $messageText = trim((string) ($_POST['message_text'] ?? '')); + $sendMode = (string) ($_POST['send_mode'] ?? 'self'); + $selectedRecipientIds = array_values(array_unique(array_map( + static fn ($value): int => (int) $value, + is_array($_POST['recipient_ids'] ?? null) ? $_POST['recipient_ids'] : [] + ))); + + if (!hash_equals($_SESSION['telegram_broadcast_csrf'], $csrfToken)) { + $errors[] = 'Die Sitzung ist abgelaufen. Bitte die Seite neu laden.'; + } + + if ($action !== 'send_broadcast') { + $errors[] = 'Die angeforderte Aktion ist ungueltig.'; + } + + if ($messageText === '') { + $errors[] = 'Bitte eine Nachricht eingeben.'; + } elseif (textLength($messageText) > 4000) { + $errors[] = 'Die Nachricht ist zu lang. Bitte auf maximal 4000 Zeichen kuerzen.'; + } + + $botToken = trim((string) ($telegramConfig['bot_token'] ?? '')); + if ($botToken === '') { + $errors[] = 'In config/telegram.php ist noch kein Bot-Token hinterlegt.'; + } + + if (empty($recipients)) { + $errors[] = 'Es gibt derzeit keine verknuepften Telegram-Nutzer.'; + } + + $targetRecipients = []; + if (empty($errors)) { + if ($sendMode === 'self') { + if (empty($currentUser['telegram_chat_id'])) { + $errors[] = 'Dein Administrationskonto ist noch nicht mit Telegram verknuepft.'; + } else { + foreach ($recipients as $recipient) { + if ((int) $recipient['id'] === $currentUserId) { + $targetRecipients[] = $recipient; + break; + } + } + + if (empty($targetRecipients)) { + $targetRecipients[] = [ + 'id' => $currentUserId, + 'username' => (string) $currentUser['username'], + 'display_name' => (string) ($currentUser['display_name'] ?? ''), + 'telegram_username' => '', + 'telegram_chat_id' => (string) $currentUser['telegram_chat_id'], + 'telegram_connected_at' => null, + ]; + } + } + } elseif ($sendMode === 'selected') { + if (empty($selectedRecipientIds)) { + $errors[] = 'Bitte mindestens einen Empfaenger auswaehlen.'; + } else { + foreach ($selectedRecipientIds as $recipientId) { + if (isset($recipientIndex[$recipientId])) { + $targetRecipients[] = $recipientIndex[$recipientId]; + } + } + + if (empty($targetRecipients)) { + $errors[] = 'Die ausgewaehlten Empfaenger konnten nicht geladen werden.'; + } + } + } elseif ($sendMode === 'all') { + $targetRecipients = $recipients; + } else { + $errors[] = 'Der Versandmodus ist ungueltig.'; + } + } + + if (empty($errors)) { + $successCount = 0; + + foreach ($targetRecipients as $recipient) { + $chatId = trim((string) ($recipient['telegram_chat_id'] ?? '')); + $sendResult = sendTelegramMessage($botToken, $chatId, $messageText); + $wasSuccessful = (bool) ($sendResult['ok'] ?? false); + + if ($wasSuccessful) { + $successCount++; + } + + $resultRows[] = [ + 'username' => (string) ($recipient['username'] ?? ''), + 'display_name' => (string) ($recipient['display_name'] ?? ''), + 'telegram_username' => (string) ($recipient['telegram_username'] ?? ''), + 'chat_id' => $chatId, + 'ok' => $wasSuccessful, + 'description' => (string) ($sendResult['description'] ?? ''), + ]; + } + + $successMessage = sprintf( + 'Versand abgeschlossen: %d von %d Telegram-Nutzern erfolgreich erreicht.', + $successCount, + count($targetRecipients) + ); + } +} +?> + + + +

+ + + + + + +
+
+

Testnachricht senden

+

+ Diese Seite sendet eine Nachricht an alle aktiven Benutzerkonten mit hinterlegter Telegram-Chat-ID. +

+ + + + +
Verknuepfte Telegram-Nutzer
Bot
+ +
+ + + +
+ + + + +
+ +
+ + +

Mit gedrueckter Strg-Taste lassen sich mehrere Benutzer markieren.

+
+ +
+ + +
+ + +
+
+ +
+

Empfaenger

+ +

Noch keine verknuepften Telegram-Konten vorhanden.

+ + + + + + + + + + + + + + + + + + +
BenutzerTelegramChat-ID
+
+ +
+ +
+
+ + +
+

Versandprotokoll

+ + + + + + + + + + + + + + + + + + + +
BenutzerChat-IDStatusRueckmeldung
+
+ + + diff --git a/public/telegram/webhook.php b/public/telegram/webhook.php index b72a354..2998314 100644 --- a/public/telegram/webhook.php +++ b/public/telegram/webhook.php @@ -32,6 +32,66 @@ function telegramWebhookRespond(array $payload, int $statusCode = 200): never exit; } +function telegramHttpPostJson(string $url, array $payload): bool +{ + $jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + if ($jsonPayload === false) { + return false; + } + + if (function_exists('curl_init')) { + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($jsonPayload), + ], + CURLOPT_POSTFIELDS => $jsonPayload, + CURLOPT_TIMEOUT => 15, + ]); + + $body = curl_exec($ch); + curl_close($ch); + + return $body !== false; + } + + $context = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => implode("\r\n", [ + 'Content-Type: application/json', + 'Content-Length: ' . strlen($jsonPayload), + ]), + 'content' => $jsonPayload, + 'timeout' => 15, + 'ignore_errors' => true, + ], + ]); + + $body = @file_get_contents($url, false, $context); + + return $body !== false; +} + +function telegramSendBotMessage(array $telegramConfig, string $chatId, string $messageText): void +{ + $botToken = trim((string) ($telegramConfig['bot_token'] ?? '')); + if ($botToken === '' || $chatId === '' || $messageText === '') { + return; + } + + telegramHttpPostJson( + 'https://api.telegram.org/bot' . $botToken . '/sendMessage', + [ + 'chat_id' => $chatId, + 'text' => $messageText, + ] + ); +} + $expectedSecret = trim((string) ($telegramConfig['webhook_secret'] ?? '')); if ($expectedSecret !== '') { $providedSecret = (string) ($_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? ''); @@ -102,6 +162,18 @@ $stmtUpdate->execute([ ':id' => (int) $user['id'], ]); +$welcomeName = $telegramUsername !== '' ? '@' . ltrim($telegramUsername, '@') : 'bei Telegram'; +$welcomeMessage = implode("\n", [ + 'Willkommen bei SkyView.', + '', + 'Dein Telegram-Konto wurde erfolgreich mit deinem SkyView-Benutzer verbunden.', + 'Du kannst jetzt spaeter Hinweise und Inhalte direkt vom Bot erhalten.', + '', + 'Verbunden als: ' . $welcomeName, +]); + +telegramSendBotMessage($telegramConfig, $chatId, $welcomeMessage); + telegramWebhookRespond([ 'ok' => true, 'status' => 'connected',