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) + ); + } +} +?> + + + +
= h($successMessage) ?>
+ + + ++ Diese Seite sendet eine Nachricht an alle aktiven Benutzerkonten mit hinterlegter Telegram-Chat-ID. +
+ +| Verknuepfte Telegram-Nutzer | = count($recipients) ?> |
| Bot | = h(($telegramConfig['bot_username'] ?? '') !== '' ? '@' . (string) $telegramConfig['bot_username'] : 'nicht konfiguriert') ?> |
Noch keine verknuepften Telegram-Konten vorhanden.
+ +| Benutzer | +Telegram | +Chat-ID | +
|---|---|---|
|
+ = h($recipient['display_name'] !== '' ? $recipient['display_name'] : $recipient['username']) ?> + = h($recipient['username']) ?> + |
+ = h(($recipient['telegram_username'] ?? '') !== '' ? '@' . ltrim((string) $recipient['telegram_username'], '@') : '—') ?> | += h((string) $recipient['telegram_chat_id']) ?> | +
| Benutzer | +Chat-ID | +Status | +Rueckmeldung | +
|---|---|---|---|
| = h($row['display_name'] !== '' ? $row['display_name'] : $row['username']) ?> | += h($row['chat_id']) ?> | += $row['ok'] ? 'OK' : 'Fehler' ?> | += h($row['description'] !== '' ? $row['description'] : 'Nachricht gesendet.') ?> | +