Files
skyview.astronomiemuseum.de/public/telegram/broadcast_test.php
T

535 lines
18 KiB
PHP

<?php
declare(strict_types=1);
ini_set('display_errors', '1');
error_reporting(E_ALL);
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
require_once __DIR__ . '/../auth_helpers.php';
$config = require __DIR__ . '/../../config/database.php';
$telegramConfig = require __DIR__ . '/../../config/telegram.php';
require_once __DIR__ . '/../../config/telegram_message_footer.php';
$dsn = sprintf(
'mysql:host=%s;dbname=%s;charset=%s',
$config['host'],
$config['dbname'],
$config['charset'] ?? 'utf8mb4'
);
$pdo = new PDO(
$dsn,
$config['user'],
$config['pass'],
[
PDO::ATTR_ERRMODE => 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 = 'Testnachrichten nur an verknuepfte Master-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;
$responseHeaders = function_exists('http_get_last_response_headers')
? http_get_last_response_headers()
: ($http_response_header ?? null);
if (is_array($responseHeaders)) {
foreach ($responseHeaders 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' => appendTelegramServiceFooter($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 gültige 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 ||
!isMasterRole((string) $currentUser['role']) ||
(int) $currentUser['is_active'] !== 1
) {
$_SESSION = [];
session_destroy();
header('Location: ../login.php');
exit;
}
$stmtRecipients = $pdo->query("
SELECT
`id`,
`username`,
`display_name`,
`role`,
`telegram_username`,
`telegram_chat_id`,
`telegram_connected_at`
FROM `app_users`
WHERE `is_active` = 1
AND `role` = 'master'
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 ungültig.';
}
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 Master-Konten mit Telegram.';
}
$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 Empfänger auswählen.';
} else {
foreach ($selectedRecipientIds as $recipientId) {
if (isset($recipientIndex[$recipientId])) {
$targetRecipients[] = $recipientIndex[$recipientId];
}
}
if (empty($targetRecipients)) {
$errors[] = 'Die ausgewählten Empfänger konnten nicht geladen werden.';
}
}
} elseif ($sendMode === 'all') {
$targetRecipients = $recipients;
} else {
$errors[] = 'Der Versandmodus ist ungültig.';
}
}
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 Master-Konten erfolgreich erreicht.',
$successCount,
count($targetRecipients)
);
}
}
?>
<?php require __DIR__ . '/../header.php'; ?>
<?php if ($successMessage !== ''): ?>
<p class="msg-success"><?= h($successMessage) ?></p>
<?php endif; ?>
<?php if (!empty($errors)): ?>
<ul class="msg-error">
<?php foreach ($errors as $error): ?>
<li><?= h($error) ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<div class="grid-2">
<div class="card">
<h2>Testnachricht senden</h2>
<p class="hint">
Diese Seite sendet Testnachrichten ausschliesslich an aktive Master-Konten mit hinterlegter Telegram-Chat-ID.
</p>
<table class="data-table mb-lg">
<tr><td>Verknüpfte Master-Konten</td><td><?= count($recipients) ?></td></tr>
<tr><td>Bot</td><td><?= h(($telegramConfig['bot_username'] ?? '') !== '' ? '@' . (string) $telegramConfig['bot_username'] : 'nicht konfiguriert') ?></td></tr>
</table>
<form method="post" action="" class="form-stack">
<input type="hidden" name="action" value="send_broadcast">
<input type="hidden" name="csrf_token" value="<?= h($_SESSION['telegram_broadcast_csrf']) ?>">
<div class="form-group">
<label>Empfänger</label>
<label>
<input type="radio" name="send_mode" value="self" <?= $sendMode === 'self' ? 'checked' : '' ?>>
Nur an mich senden
</label>
<label>
<input type="radio" name="send_mode" value="selected" <?= $sendMode === 'selected' ? 'checked' : '' ?>>
An ausgewählte Nutzer senden
</label>
<label>
<input type="radio" name="send_mode" value="all" <?= $sendMode === 'all' ? 'checked' : '' ?>>
An alle verknuepften Master senden
</label>
</div>
<div class="form-group">
<label for="recipient_ids">Auswahl für "ausgewählte Nutzer"</label>
<select id="recipient_ids" name="recipient_ids[]" multiple size="8">
<?php foreach ($recipients as $recipient): ?>
<?php $recipientId = (int) $recipient['id']; ?>
<?php $recipientLabel = ($recipient['display_name'] !== '' ? $recipient['display_name'] : $recipient['username']) . ' (' . $recipient['username'] . ')'; ?>
<option value="<?= $recipientId ?>" <?= in_array($recipientId, $selectedRecipientIds, true) ? 'selected' : '' ?>>
<?= h($recipientLabel) ?>
</option>
<?php endforeach; ?>
</select>
<p class="hint">Mit gedrueckter Strg-Taste lassen sich mehrere Benutzer markieren.</p>
</div>
<div class="form-group">
<label for="message_text">Nachricht</label>
<textarea
id="message_text"
name="message_text"
rows="8"
maxlength="4000"
required
><?= h($messageText) ?></textarea>
</div>
<button type="submit" class="btn btn-primary">Testnachricht an Master senden</button>
</form>
</div>
<div class="card">
<h2>Empfänger</h2>
<?php if (empty($recipients)): ?>
<p class="hint">Noch keine verknuepften Master-Konten vorhanden.</p>
<?php else: ?>
<table class="data-table">
<thead>
<tr>
<th>Benutzer</th>
<th>Telegram</th>
<th>Chat-ID</th>
</tr>
</thead>
<tbody>
<?php foreach ($recipients as $recipient): ?>
<tr>
<td>
<strong><?= h($recipient['display_name'] !== '' ? $recipient['display_name'] : $recipient['username']) ?></strong><br>
<span class="hint"><?= h($recipient['username']) ?></span>
</td>
<td><?= h(($recipient['telegram_username'] ?? '') !== '' ? '@' . ltrim((string) $recipient['telegram_username'], '@') : '—') ?></td>
<td><?= h((string) $recipient['telegram_chat_id']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<?php if (!empty($resultRows)): ?>
<div class="card">
<h2>Versandprotokoll</h2>
<table class="data-table">
<thead>
<tr>
<th>Benutzer</th>
<th>Chat-ID</th>
<th>Status</th>
<th>Rueckmeldung</th>
</tr>
</thead>
<tbody>
<?php foreach ($resultRows as $row): ?>
<tr>
<td><?= h($row['display_name'] !== '' ? $row['display_name'] : $row['username']) ?></td>
<td><?= h($row['chat_id']) ?></td>
<td><?= $row['ok'] ? 'OK' : 'Fehler' ?></td>
<td><?= h($row['description'] !== '' ? $row['description'] : 'Nachricht gesendet.') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php require __DIR__ . '/../footer.php'; ?>