From 7f10930b7b9d1972d780bb439b3373dfb853e4ab Mon Sep 17 00:00:00 2001 From: AnthonyAxenov Date: Mon, 9 Jun 2025 23:59:19 +0800 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D1=8B=20/h?= =?UTF-8?q?elp,=20/stats=20=D0=B8=20/links=20+=20=D0=BA=D0=BE=D0=BD=D1=84?= =?UTF-8?q?=D0=B8=D0=B3=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B5=D1=80=D0=BA?= =?UTF-8?q?=D0=B0=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + README.md | 1 + app/Core/Bot.php | 206 ++++++++++++++++++++++++++++++++++++++++------- app/helpers.php | 13 ++- config/app.php | 3 +- 5 files changed, 195 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index f52e69c..55790ee 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,7 @@ # config/app.php APP_URL="http://localhost:8080" +APP_URL_MIRROR="https://m3u.su/" APP_DEBUG=false APP_ENV="prod" APP_TITLE="IPTV Плейлисты" diff --git a/README.md b/README.md index 460956f..096e993 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ * [Bootstrap v5.2](https://getbootstrap.com/docs/5.2/getting-started/introduction/) * [Ionicons](https://ionic.io/ionicons) * [List.js](https://listjs.com) +* [telegram-bot/api](https://packagist.org/packages/telegram-bot/api) ## Лицензия diff --git a/app/Core/Bot.php b/app/Core/Bot.php index 0f5d104..3d9b2df 100644 --- a/app/Core/Bot.php +++ b/app/Core/Bot.php @@ -67,13 +67,19 @@ class Bot * * @return bool * @throws InvalidArgumentException + * @throws \TelegramBot\Api\Exception + * @throws Exception */ public function process(): bool { + $this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); $commandText = $this->getBotCommandText(); - return match ($commandText) { - '/list', '/list@iptv_aggregator_bot' => $this->processListCommand(), - '/info', '/info@iptv_aggregator_bot' => $this->processInfoCommand(), + return match (true) { + str_starts_with($commandText, '/list') => $this->processListCommand(), + str_starts_with($commandText, '/info') => $this->processInfoCommand(), + str_starts_with($commandText, '/help') => $this->processHelpCommand(), + str_starts_with($commandText, '/links') => $this->processLinksCommand(), + str_starts_with($commandText, '/stats') => $this->processStatsCommand(), default => true, }; } @@ -109,11 +115,11 @@ class Bot [ [ 'text' => 'Список на сайте', - 'url' => "https://iptv.axenov.dev/", + 'url' => base_url(), ], [ 'text' => 'FAQ', - 'url' => 'https://iptv.axenov.dev/faq', + 'url' => base_url('faq'), ] ] ] @@ -151,7 +157,7 @@ class Bot }; in_array('adult', $pls['tags']) && $statusEmoji .= "🔞"; - $replyText[] = $statusEmoji . ' [' . $this->escape($pls['name']) . "](https://m3u.su/$code/details)\n"; + $replyText[] = $statusEmoji . ' [' . $this->escape($pls['name']) . '](' . base_url("$code/details") . ")\n"; empty($pls['description']) || $replyText[] = $this->escape($pls['description']) . "\n"; if ($pls['isOnline'] === null) { @@ -161,32 +167,39 @@ class Bot $checkedAt = DateTimeImmutable::createFromTimestamp($pls['checkedAt']); $minutes = $checkedAt->diff($now)->i; $replyText[] = "⏲️ *Проверка:* " . ($minutes > 1 ? "$minutes мин\. назад" : "только что"); - $replyText[] = "📺 *Каналов:* " . count($pls['channels']) . - ' \(онлайн ' . $pls['onlineCount'] . ', оффлайн ' . $pls['offlineCount'] . "\)"; - $replyText[] = "⏪ *Перемотка:* " . ($pls['hasCatchup'] === true ? '*есть*' : 'нет'); - $replyText[] = "🗞️ *Телепрограмма:* " . ($pls['hasTvg'] === true ? '*есть*' : 'нет'); - if (count($pls['groups'] ?? []) > 0) { - $groups = array_map(fn (array $group) => $this->escape($group['name']), $pls['groups']); - $replyText[] = "\n🗂️ *Группы* \(" . count($pls['groups']) . "\):\n**>\- " . - implode("\n>\- ", $groups) . '||'; - } else { - $replyText[] = "🗂️ *Группы:* нет"; - } + if ($pls['isOnline'] === true) { + $replyText[] = "📺 *Каналов:* " . count($pls['channels'] ?? []) . + ' \(онлайн ' . $pls['onlineCount'] . ', оффлайн ' . $pls['offlineCount'] . "\)"; + $replyText[] = "⏪ *Перемотка:* " . ($pls['hasCatchup'] === true ? '*есть*' : 'нет'); + $replyText[] = "🗞️ *Телепрограмма:* " . ($pls['hasTvg'] === true ? '*есть*' : 'нет'); - if (count($pls['tags'] ?? []) > 0) { - $replyText[] = "\n🏷️ *Теги* \(" . count($pls['tags']) . "\):\n**>" . - $this->escape(trim(implode(' ', $pls['tags']))) . "||\n"; + if (count($pls['groups'] ?? []) > 0) { + $groups = array_map(fn (array $group) => $this->escape($group['name']), $pls['groups']); + $replyText[] = "\n🗂️ *Группы* \(" . count($pls['groups'] ?? []) . "\):\n**>\- " . + implode("\n>\- ", $groups) . '||'; + } else { + $replyText[] = "🗂️ *Группы:* нет"; + } + + if (count($pls['tags'] ?? []) > 0) { + $replyText[] = "\n🏷️ *Теги* \(" . count($pls['tags']) . "\):\n**>" . + $this->escape(trim(implode(' ', $pls['tags']))) . "||\n"; + } else { + $replyText[] = "🏷️ *Теги:* нет"; + } } else { - $replyText[] = "🏷️ *Теги:* нет"; + $replyText[] = "❌ *Ошибка проверки:*\n**>" . $this->escape($pls['content']) . "||\n"; } } $replyText[] = "🔗 *Ссылка для ТВ:* \(скопируй подходящую\)"; - $replyText[] = "\- `https://m3u.su/$code`"; - $replyText[] = "\- `https://m3u.su/$code.m3u`"; - $replyText[] = "\- `https://iptv.axenov.dev/$code`"; - $replyText[] = "\- `https://iptv.axenov.dev/$code.m3u`\n"; + if (config('app.mirror_url')) { + $replyText[] = '\- `' . mirror_url("$code") . '`'; + $replyText[] = '\- `' . mirror_url("$code.m3u") . '`'; + } + $replyText[] = '\- `' . base_url("$code") . '`'; + $replyText[] = '\- `' . base_url("$code.m3u") . "`\n"; return $this->reply( implode("\n", $replyText), @@ -195,11 +208,11 @@ class Bot [ [ 'text' => is_null($pls['isOnline']) ? 'Подробности' : 'Список каналов', - 'url' => "https://iptv.axenov.dev/$code/details", + 'url' => base_url("$code/details"), ], [ 'text' => 'FAQ', - 'url' => 'https://iptv.axenov.dev/faq', + 'url' => base_url('faq'), ] ] ] @@ -207,6 +220,145 @@ class Bot ); } + /** + * Обрабатывает команду /help + * + * @return bool + */ + protected function processHelpCommand(): bool + { + $replyText[] = 'Бот предоставляет короткую сводку о плейлистах, которые видны на сайте ' . + $this->escape(base_url()) . '\.'; + $replyText[] = 'Плейлисты проверяются сервером автоматически\.'; + $replyText[] = ''; + $replyText[] = 'Команды бота:'; + $replyText[] = '`/list` \- список кодов всех плейлистов;'; + $replyText[] = '`/info <код>` \- информация о плейлисте с указанным кодом;'; + $replyText[] = '`/help` \- данная справка\.'; + $replyText[] = ''; + $replyText[] = 'Статусы плейлистов:'; + $replyText[] = '🟢 \- онлайн'; + $replyText[] = '🔴 \- оффлайн'; + $replyText[] = '⚪ \- не проверялось'; + $replyText[] = '🔞 \- есть каналы для взрослых'; + + return $this->reply(implode("\n", $replyText)); + } + + /** + * Обрабатывает команду /links + * + * @return bool + */ + protected function processLinksCommand(): bool + { + $replyText[] = '*Ресурсы и страницы*'; + $replyText[] = ''; + $replyText[] = '🌏 Сайт: ' . $this->escape(base_url()); + config('app.mirror_url') && $replyText[] = '🪞 Зеркало: ' . $this->escape(mirror_url()); + $replyText[] = '👩‍💻 Исходный код: ' . $this->escape('https://git.axenov.dev/IPTV'); + $replyText[] = '✈️ Telegram\-канал: @iptv\_aggregator'; + $replyText[] = '✈️ Обсуждение: @iptv\_aggregator\_chat'; + $replyText[] = '📚 Доп\. сведения:'; + $replyText[] = '\- ' . $this->escape('https://git.axenov.dev/IPTV/.profile'); + $replyText[] = '\- ' . $this->escape(base_url('faq')); + + return $this->reply(implode("\n", $replyText)); + } + + /** + * Обрабатывает команду /stats + * + * @return bool + * @throws Exception + */ + protected function processStatsCommand(): bool + { + $allChannels = []; + foreach (ini()->getPlaylists() as $pls) { + $allChannels = array_merge($allChannels, $pls['channels'] ?? []); + } + + $onlinePls = array_filter( + ini()->getPlaylists(), + static fn (array $pls) => $pls['isOnline'] === true, + ); + + $offlinePls = array_filter( + ini()->getPlaylists(), + static fn (array $pls) => $pls['isOnline'] === false, + ); + + $unknownPls = array_filter( + ini()->getPlaylists(), + static fn (array $pls) => $pls['isOnline'] === null, + ); + + $adultPls = array_filter( + $onlinePls, + static fn (array $pls) => in_array('adult', $pls['tags']), + ); + + $catchupPls = array_filter( + $onlinePls, + static fn (array $pls) => $pls['hasCatchup'] === true, + ); + + $tvgPls = array_filter( + $onlinePls, + static fn (array $pls) => $pls['hasTvg'] === true, + ); + + $grouppedPls = array_filter( + $onlinePls, + static fn (array $pls) => count($pls['groups'] ?? []) > 0 + ); + + $onlineCh = $offlineCh = $adultCh = []; + foreach ($onlinePls as $pls) { + $tmpOnline = array_filter( + $pls['channels'] ?? [], + static fn (array $ch) => $ch['isOnline'] === true, + ); + + $tmpOffline = array_filter( + $pls['channels'] ?? [], + static fn (array $ch) => $ch['isOnline'] === false, + ); + + $tmpAdult = array_filter( + $pls['channels'] ?? [], + static fn (array $ch) => in_array('adult', $ch['tags']), + ); + + $onlineCh = array_merge($onlineCh, $tmpOnline); + $offlineCh = array_merge($offlineCh, $tmpOffline); + $adultCh = array_merge($adultCh, $tmpAdult); + } + + $replyText[] = '📊 *Статистика*'; + $replyText[] = ''; + $replyText[] = '*Список изменён:* ' . $this->escape(ini()->updatedAt()); + $replyText[] = ''; + $replyText[] = '*Плейлистов:* ' . count(ini()->getPlaylists()); + $replyText[] = '🟢 Онлайн \- ' . count($onlinePls); + $replyText[] = '🔴 Оффлайн \- ' . count($offlinePls); + $replyText[] = '⚪ В очереди \- ' . count($unknownPls); + $replyText[] = '🔞 Для взрослых \- ' . count($adultPls); + $replyText[] = '⏪ С перемоткой \- ' . count($catchupPls); + $replyText[] = '🗞️ С телепрограммой \- ' . count($tvgPls); + $replyText[] = '🗂️ С группировкой каналов \- ' . count($grouppedPls); + $replyText[] = ''; + $replyText[] = '*Каналов:* ' . count($allChannels); + $replyText[] = '🟢 Онлайн \- ' . count($onlineCh); + $replyText[] = '🔴 Оффлайн \- ' . count($offlineCh); + $replyText[] = '🔞 Для взрослых \- ' . count($adultCh); + $replyText[] = ''; + $replyText[] = ''; + + return $this->reply(implode("\n", $replyText)); + } + /** * Сверяет секретный заголовок с заданным в конфиге * diff --git a/app/helpers.php b/app/helpers.php index c3d2573..b59bea0 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -52,7 +52,18 @@ function cache_path(string $path = ''): string */ function base_url(string $route = ''): string { - return rtrim(sprintf('%s/%s', env('APP_URL'), $route), '/'); + return rtrim(sprintf('%s/%s', config('app.base_url'), $route), '/'); +} + +/** + * Returns mirror URL + * + * @param string $route + * @return string + */ +function mirror_url(string $route = ''): string +{ + return rtrim(sprintf('%s/%s', config('app.mirror_url'), $route), '/'); } /** diff --git a/config/app.php b/config/app.php index 224c299..f221762 100644 --- a/config/app.php +++ b/config/app.php @@ -8,7 +8,8 @@ declare(strict_types=1); return [ - 'base_url' => env('APP_URL', 'http://localhost:8080'), + 'base_url' => rtrim(trim(env('APP_URL', 'http://localhost:8080')), '/'), + 'mirror_url' => rtrim(trim(env('APP_URL_MIRROR') ?? '', '/')), 'debug' => bool(env('APP_DEBUG', false)), 'env' => env('APP_ENV', env('IPTV_ENV', 'prod')), 'title' => 'IPTV Плейлисты',