Telegram drin
This commit is contained in:
@@ -26,6 +26,11 @@ CREATE TABLE IF NOT EXISTS `app_users` (
|
||||
`email` varchar(190) NOT NULL,
|
||||
`password_hash` varchar(255) NOT NULL,
|
||||
`display_name` varchar(100) DEFAULT NULL,
|
||||
`telegram_username` varchar(32) DEFAULT NULL,
|
||||
`telegram_chat_id` varchar(20) DEFAULT NULL,
|
||||
`telegram_link_token` varchar(64) DEFAULT NULL,
|
||||
`telegram_link_token_expires_at` datetime DEFAULT NULL,
|
||||
`telegram_connected_at` datetime DEFAULT NULL,
|
||||
`role` varchar(32) NOT NULL DEFAULT 'user',
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`last_login_at` datetime DEFAULT NULL,
|
||||
@@ -34,6 +39,7 @@ CREATE TABLE IF NOT EXISTS `app_users` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uq_app_users_username` (`username`),
|
||||
UNIQUE KEY `uq_app_users_email` (`email`),
|
||||
UNIQUE KEY `uq_app_users_telegram_link_token` (`telegram_link_token`),
|
||||
KEY `idx_app_users_role` (`role`),
|
||||
KEY `idx_app_users_is_active` (`is_active`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
// Telegram-Bot-Token aus BotFather, z. B. '123456:ABCDEF...'
|
||||
'bot_token' => '7852963086:AAHltMndpPCk7cJSYwnmpesHpeu98a-pxBg',
|
||||
|
||||
// Telegram-Bot-Username ohne @, z. B. 'skyview_connect_bot'
|
||||
'bot_username' => 'skvwam_bot',
|
||||
|
||||
// Optionales Secret fuer den Telegram-Webhook-Header X-Telegram-Bot-Api-Secret-Token
|
||||
'webhook_secret' => '3f2d6e7a5bc14b32915d6a0fdc4a8e11',
|
||||
];
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -32,18 +32,29 @@ $pdo = new PDO(
|
||||
$userId = (int) $_SESSION['user_id'];
|
||||
$errors = [];
|
||||
$successMessage = '';
|
||||
$telegramConfig = require __DIR__ . '/../config/telegram.php';
|
||||
|
||||
function h(?string $value): string
|
||||
{
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function generateTelegramLinkToken(int $length = 32): string
|
||||
{
|
||||
return bin2hex(random_bytes(max(16, (int) ceil($length / 2))));
|
||||
}
|
||||
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT
|
||||
`id`,
|
||||
`username`,
|
||||
`email`,
|
||||
`display_name`,
|
||||
`telegram_username`,
|
||||
`telegram_chat_id`,
|
||||
`telegram_link_token`,
|
||||
`telegram_link_token_expires_at`,
|
||||
`telegram_connected_at`,
|
||||
`role`,
|
||||
`is_active`
|
||||
FROM `app_users`
|
||||
@@ -113,6 +124,46 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
}
|
||||
|
||||
if ($action === 'generate_telegram_link') {
|
||||
$token = generateTelegramLinkToken(32);
|
||||
$expiresAt = (new DateTimeImmutable('now'))->modify('+30 minutes')->format('Y-m-d H:i:s');
|
||||
|
||||
$stmtLink = $pdo->prepare("
|
||||
UPDATE `app_users`
|
||||
SET
|
||||
`telegram_link_token` = :token,
|
||||
`telegram_link_token_expires_at` = :expires_at,
|
||||
`updated_at` = NOW()
|
||||
WHERE `id` = :id
|
||||
");
|
||||
$stmtLink->execute([
|
||||
':token' => $token,
|
||||
':expires_at' => $expiresAt,
|
||||
':id' => $userId,
|
||||
]);
|
||||
|
||||
$successMessage = 'Ein neuer Telegram-Verbindungscode wurde erzeugt.';
|
||||
}
|
||||
|
||||
if ($action === 'disconnect_telegram') {
|
||||
$stmtDisconnect = $pdo->prepare("
|
||||
UPDATE `app_users`
|
||||
SET
|
||||
`telegram_username` = NULL,
|
||||
`telegram_chat_id` = NULL,
|
||||
`telegram_link_token` = NULL,
|
||||
`telegram_link_token_expires_at` = NULL,
|
||||
`telegram_connected_at` = NULL,
|
||||
`updated_at` = NOW()
|
||||
WHERE `id` = :id
|
||||
");
|
||||
$stmtDisconnect->execute([
|
||||
':id' => $userId,
|
||||
]);
|
||||
|
||||
$successMessage = 'Die Telegram-Verknuepfung wurde entfernt.';
|
||||
}
|
||||
|
||||
if ($action === 'change_password') {
|
||||
$currentPassword = $_POST['current_password'] ?? '';
|
||||
$newPassword = $_POST['new_password'] ?? '';
|
||||
@@ -165,6 +216,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
|
||||
$loggedIn = true;
|
||||
$publicBasePath = './';
|
||||
$telegramBotUsername = trim((string) ($telegramConfig['bot_username'] ?? ''));
|
||||
$telegramHasConnection = !empty($user['telegram_chat_id']);
|
||||
$telegramLinkToken = trim((string) ($user['telegram_link_token'] ?? ''));
|
||||
$telegramLinkExpiresAt = !empty($user['telegram_link_token_expires_at'])
|
||||
? new DateTimeImmutable((string) $user['telegram_link_token_expires_at'])
|
||||
: null;
|
||||
$telegramLinkIsActive = $telegramLinkToken !== ''
|
||||
&& $telegramLinkExpiresAt instanceof DateTimeImmutable
|
||||
&& $telegramLinkExpiresAt > new DateTimeImmutable('now');
|
||||
$telegramDeepLink = $telegramLinkIsActive && $telegramBotUsername !== ''
|
||||
? 'https://t.me/' . $telegramBotUsername . '?start=connect_' . $telegramLinkToken
|
||||
: null;
|
||||
?>
|
||||
<?php require __DIR__ . '/header.php'; ?>
|
||||
|
||||
@@ -214,6 +278,60 @@ $loggedIn = true;
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Telegram verbinden</h2>
|
||||
<div class="form-stack">
|
||||
<p class="hint">
|
||||
Spaetere Bot-Nachrichten funktionieren nur mit einer verknuepften Chat-ID. Der Telegram-Username allein reicht Telegram fuer aktive Nachrichten an einen Nutzer nicht aus.
|
||||
</p>
|
||||
<table class="data-table mb-lg">
|
||||
<tr><td>Status</td><td><?= $telegramHasConnection ? 'Verbunden' : 'Nicht verbunden' ?></td></tr>
|
||||
<tr><td>Telegram-Username</td><td><?= h(($user['telegram_username'] ?? '') !== '' ? '@' . ltrim((string) $user['telegram_username'], '@') : '—') ?></td></tr>
|
||||
<tr><td>Chat-ID</td><td><?= h($user['telegram_chat_id'] ?? '—') ?></td></tr>
|
||||
<tr><td>Verbunden seit</td><td><?= h($user['telegram_connected_at'] ?? '—') ?></td></tr>
|
||||
</table>
|
||||
|
||||
<?php if ($telegramLinkIsActive): ?>
|
||||
<p class="hint">
|
||||
Aktiver Verbindungscode bis <?= h($telegramLinkExpiresAt?->format('d.m.Y H:i')) ?>:
|
||||
<strong><?= h($telegramLinkToken) ?></strong>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="input-row">
|
||||
<form method="post" action="">
|
||||
<input type="hidden" name="action" value="generate_telegram_link">
|
||||
<button type="submit" class="btn btn-primary"><?= $telegramLinkIsActive ? 'Neuen Verbindungscode erzeugen' : 'Telegram verbinden' ?></button>
|
||||
</form>
|
||||
|
||||
<?php if ($telegramHasConnection): ?>
|
||||
<form method="post" action="" onsubmit="return confirm('Telegram-Verknuepfung wirklich entfernen?');">
|
||||
<input type="hidden" name="action" value="disconnect_telegram">
|
||||
<button type="submit" class="btn btn-danger">Verknuepfung loesen</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($telegramLinkIsActive): ?>
|
||||
<?php if ($telegramDeepLink !== null): ?>
|
||||
<div class="telegram-qr-block">
|
||||
<div class="telegram-qr-image" id="telegramConnectQr" data-telegram-link="<?= h($telegramDeepLink) ?>"></div>
|
||||
<p class="hint">QR-Code mit dem Handy in Telegram scannen und dann im Bot nur noch auf <strong>Start</strong> tippen.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<p class="hint">
|
||||
Verbindung starten: <a href="<?= h($publicBasePath . 'telegram/connect.php') ?>">Telegram-Verknuepfung oeffnen</a>
|
||||
<?php if ($telegramDeepLink !== null): ?>
|
||||
oder direkt im Bot:
|
||||
<a href="<?= h($telegramDeepLink) ?>" target="_blank" rel="noopener">@<?= h($telegramBotUsername) ?></a>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h2>Passwort aendern</h2>
|
||||
<form method="post" action="" class="form-stack">
|
||||
@@ -235,4 +353,28 @@ $loggedIn = true;
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($telegramDeepLink !== null): ?>
|
||||
<script src="<?= h($publicBasePath . 'js/qrcode.min.js') ?>"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const qrEl = document.getElementById('telegramConnectQr');
|
||||
if (!qrEl || typeof QRCode === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const qrLink = qrEl.getAttribute('data-telegram-link');
|
||||
if (!qrLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
new QRCode(qrEl, {
|
||||
text: qrLink,
|
||||
width: 220,
|
||||
height: 220,
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require __DIR__ . '/footer.php'; ?>
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
$config = require __DIR__ . '/../../config/database.php';
|
||||
$telegramConfig = require __DIR__ . '/../../config/telegram.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,
|
||||
]
|
||||
);
|
||||
|
||||
$userId = (int) $_SESSION['user_id'];
|
||||
$loggedIn = true;
|
||||
$publicBasePath = '../';
|
||||
$pageTitle = 'Telegram verbinden - AstroTools';
|
||||
$headerIntroPre = 'Konto';
|
||||
$headerIntroTitle = 'Telegram verbinden';
|
||||
$headerIntroSub = 'Bot-Verknuepfung fuer spaetere Benachrichtigungen.';
|
||||
|
||||
function h(?string $value): string
|
||||
{
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
function generateTelegramLinkToken(int $length = 32): string
|
||||
{
|
||||
return bin2hex(random_bytes(max(16, (int) ceil($length / 2))));
|
||||
}
|
||||
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT
|
||||
`id`,
|
||||
`username`,
|
||||
`telegram_username`,
|
||||
`telegram_chat_id`,
|
||||
`telegram_link_token`,
|
||||
`telegram_link_token_expires_at`,
|
||||
`telegram_connected_at`
|
||||
FROM `app_users`
|
||||
WHERE `id` = :id
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([':id' => $userId]);
|
||||
$user = $stmtUser->fetch();
|
||||
|
||||
if (!$user) {
|
||||
$_SESSION = [];
|
||||
session_destroy();
|
||||
header('Location: ../login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$now = new DateTimeImmutable('now');
|
||||
$token = trim((string) ($user['telegram_link_token'] ?? ''));
|
||||
$expiresAt = !empty($user['telegram_link_token_expires_at'])
|
||||
? new DateTimeImmutable((string) $user['telegram_link_token_expires_at'])
|
||||
: null;
|
||||
|
||||
if ($token === '' || !$expiresAt instanceof DateTimeImmutable || $expiresAt <= $now) {
|
||||
$token = generateTelegramLinkToken(32);
|
||||
$expiresAt = $now->modify('+30 minutes');
|
||||
|
||||
$stmtRefresh = $pdo->prepare("
|
||||
UPDATE `app_users`
|
||||
SET
|
||||
`telegram_link_token` = :token,
|
||||
`telegram_link_token_expires_at` = :expires_at,
|
||||
`updated_at` = NOW()
|
||||
WHERE `id` = :id
|
||||
");
|
||||
$stmtRefresh->execute([
|
||||
':token' => $token,
|
||||
':expires_at' => $expiresAt->format('Y-m-d H:i:s'),
|
||||
':id' => $userId,
|
||||
]);
|
||||
|
||||
$stmtUser->execute([':id' => $userId]);
|
||||
$user = $stmtUser->fetch();
|
||||
}
|
||||
|
||||
$botUsername = trim((string) ($telegramConfig['bot_username'] ?? ''));
|
||||
$deepLink = $botUsername !== ''
|
||||
? 'https://t.me/' . $botUsername . '?start=connect_' . $token
|
||||
: null;
|
||||
?>
|
||||
<?php require __DIR__ . '/../header.php'; ?>
|
||||
|
||||
<div class="card">
|
||||
<h2>Telegram-Verknuepfung</h2>
|
||||
<p class="hint">
|
||||
Diese Verbindung merkt sich die Chat-ID deines Telegram-Kontos, damit spaeter ein Bot dir Inhalte direkt schicken kann.
|
||||
</p>
|
||||
|
||||
<table class="data-table mb-lg">
|
||||
<tr><td>Status</td><td><?= !empty($user['telegram_chat_id']) ? 'Verbunden' : 'Noch nicht verbunden' ?></td></tr>
|
||||
<tr><td>Aktiver Code</td><td><strong><?= h($token) ?></strong></td></tr>
|
||||
<tr><td>Gueltig bis</td><td><?= h($expiresAt?->format('d.m.Y H:i')) ?></td></tr>
|
||||
<tr><td>Aktueller Telegram-Username</td><td><?= h(!empty($user['telegram_username']) ? '@' . ltrim((string) $user['telegram_username'], '@') : '—') ?></td></tr>
|
||||
<tr><td>Aktuelle Chat-ID</td><td><?= h($user['telegram_chat_id'] ?? '—') ?></td></tr>
|
||||
</table>
|
||||
|
||||
<?php if ($deepLink !== null): ?>
|
||||
<p>
|
||||
<a class="btn btn-primary" href="<?= h($deepLink) ?>" target="_blank" rel="noopener">Bot in Telegram oeffnen</a>
|
||||
</p>
|
||||
<div class="telegram-qr-block">
|
||||
<div class="telegram-qr-image" id="telegramConnectQrLarge" data-telegram-link="<?= h($deepLink) ?>"></div>
|
||||
<p class="hint">QR-Code mit dem Handy scannen. Telegram oeffnet dann direkt den richtigen Bot mit deinem Verknuepfungslink.</p>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<p class="hint">
|
||||
In `config/telegram.php` ist noch kein Bot-Username hinterlegt. Du kannst den Code trotzdem schon verwenden und spaeter an den Bot senden.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="form-stack">
|
||||
<p>So funktioniert es:</p>
|
||||
<ol>
|
||||
<li>Oeffne den Telegram-Bot.</li>
|
||||
<li>Sende den Startbefehl <code>/start connect_<?= h($token) ?></code>.</li>
|
||||
<li>Der Bot verknuepft deinen Chat mit deinem Benutzerkonto.</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<p class="hint">
|
||||
Nach erfolgreicher Verknuepfung kannst du zurueck zu <a href="<?= h($publicBasePath . 'settings.php') ?>">Einstellungen</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<?php if ($deepLink !== null): ?>
|
||||
<script src="<?= h($publicBasePath . 'js/qrcode.min.js') ?>"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const qrEl = document.getElementById('telegramConnectQrLarge');
|
||||
if (!qrEl || typeof QRCode === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const qrLink = qrEl.getAttribute('data-telegram-link');
|
||||
if (!qrLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
new QRCode(qrEl, {
|
||||
text: qrLink,
|
||||
width: 280,
|
||||
height: 280,
|
||||
correctLevel: QRCode.CorrectLevel.M
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php require __DIR__ . '/../footer.php'; ?>
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
$config = require __DIR__ . '/../../config/database.php';
|
||||
$telegramConfig = require __DIR__ . '/../../config/telegram.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,
|
||||
]
|
||||
);
|
||||
|
||||
function telegramWebhookRespond(array $payload, int $statusCode = 200): never
|
||||
{
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
$expectedSecret = trim((string) ($telegramConfig['webhook_secret'] ?? ''));
|
||||
if ($expectedSecret !== '') {
|
||||
$providedSecret = (string) ($_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '');
|
||||
if (!hash_equals($expectedSecret, $providedSecret)) {
|
||||
telegramWebhookRespond(['ok' => false, 'error' => 'invalid_secret'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
$rawBody = file_get_contents('php://input');
|
||||
$update = json_decode((string) $rawBody, true);
|
||||
if (!is_array($update)) {
|
||||
telegramWebhookRespond(['ok' => false, 'error' => 'invalid_json'], 400);
|
||||
}
|
||||
|
||||
$message = $update['message'] ?? $update['edited_message'] ?? null;
|
||||
if (!is_array($message)) {
|
||||
telegramWebhookRespond(['ok' => true, 'status' => 'ignored_no_message']);
|
||||
}
|
||||
|
||||
$chatId = isset($message['chat']['id']) ? (string) $message['chat']['id'] : '';
|
||||
$telegramUsername = trim((string) ($message['from']['username'] ?? ''));
|
||||
$text = trim((string) ($message['text'] ?? ''));
|
||||
|
||||
if ($chatId === '' || $text === '') {
|
||||
telegramWebhookRespond(['ok' => true, 'status' => 'ignored_missing_data']);
|
||||
}
|
||||
|
||||
$token = null;
|
||||
if (preg_match('/^\/start\s+connect_([A-Fa-f0-9]{32,128})$/', $text, $matches)) {
|
||||
$token = strtolower($matches[1]);
|
||||
} elseif (preg_match('/^connect_([A-Fa-f0-9]{32,128})$/', $text, $matches)) {
|
||||
$token = strtolower($matches[1]);
|
||||
}
|
||||
|
||||
if ($token === null) {
|
||||
telegramWebhookRespond(['ok' => true, 'status' => 'ignored_no_connect_token']);
|
||||
}
|
||||
|
||||
$stmtUser = $pdo->prepare("
|
||||
SELECT `id`
|
||||
FROM `app_users`
|
||||
WHERE `telegram_link_token` = :token
|
||||
AND `telegram_link_token_expires_at` IS NOT NULL
|
||||
AND `telegram_link_token_expires_at` > NOW()
|
||||
LIMIT 1
|
||||
");
|
||||
$stmtUser->execute([':token' => $token]);
|
||||
$user = $stmtUser->fetch();
|
||||
|
||||
if (!$user) {
|
||||
telegramWebhookRespond(['ok' => true, 'status' => 'token_not_found_or_expired']);
|
||||
}
|
||||
|
||||
$stmtUpdate = $pdo->prepare("
|
||||
UPDATE `app_users`
|
||||
SET
|
||||
`telegram_username` = :telegram_username,
|
||||
`telegram_chat_id` = :telegram_chat_id,
|
||||
`telegram_connected_at` = NOW(),
|
||||
`telegram_link_token` = NULL,
|
||||
`telegram_link_token_expires_at` = NULL,
|
||||
`updated_at` = NOW()
|
||||
WHERE `id` = :id
|
||||
");
|
||||
$stmtUpdate->execute([
|
||||
':telegram_username' => $telegramUsername !== '' ? $telegramUsername : null,
|
||||
':telegram_chat_id' => $chatId,
|
||||
':id' => (int) $user['id'],
|
||||
]);
|
||||
|
||||
telegramWebhookRespond([
|
||||
'ok' => true,
|
||||
'status' => 'connected',
|
||||
'user_id' => (int) $user['id'],
|
||||
]);
|
||||
Reference in New Issue
Block a user