checkSecret($request); $body = json_decode((string)$request->getBody(), true); if (json_last_error() !== JSON_ERROR_NONE) { throw new JsonException(json_last_error_msg()); } $this->bot = new BotApi(config('bot.token')); $this->update = Update::fromResponse($body); } /** * Запсукает обработку команды * * @return bool * @throws InvalidArgumentException */ public function process(): bool { $commandText = $this->getBotCommandText(); return match ($commandText) { '/list', '/list@iptv_aggregator_bot' => $this->processListCommand(), '/info', '/info@iptv_aggregator_bot' => $this->processInfoCommand(), default => true, }; } /** * Обрабатывает команду /list * * @return bool * @throws InvalidArgumentException */ protected function processListCommand(): bool { $playlists = ini()->getPlaylists(); if (empty($playlists)) { $replyText = 'Плейлистов нет'; } else { $replyText = []; foreach ($playlists as $code => $pls) { $statusEmoji = match ($pls['isOnline']) { true => '🟢', false => '🔴', default => '⚪', }; in_array('adult', $pls['tags'] ?? []) === true && $statusEmoji .= "🔞"; $replyText[] = "$statusEmoji \[$code\]"; } $replyText = "Полный список плейлистов:\n\n**>" . implode("\n>", $replyText) . "||\n"; } return $this->reply($replyText, InlineKeyboardMarkup::fromResponse([ 'inline_keyboard' => [ [ [ 'text' => 'Список на сайте', 'url' => "https://iptv.axenov.dev/", ], [ 'text' => 'FAQ', 'url' => 'https://iptv.axenov.dev/faq', ] ] ] ])); } /** * Обрабатывает команду /info * * @return bool * @throws InvalidArgumentException */ protected function processInfoCommand(): bool { $message = $this->update->getMessage(); $text = $message->getText(); $command = $this->getBotCommand(); $code = mb_substr($text, $command->getLength() + 1 , mb_strlen($text)); if (empty($code)) { return $this->reply('Укажите код плейлиста с сайта, чтобы получить результаты его проверки, ' . 'например, `/info ru` или `/info@iptv_aggregator_bot ru`'); } try { $pls = ini()->getPlaylist($code); } catch (PlaylistNotFoundException) { return $this->reply("Плейлист `$code` не найден"); } $statusEmoji = match ($pls['isOnline']) { true => '🟢', false => '🔴', default => '⚪', }; in_array('adult', $pls['tags']) && $statusEmoji .= "🔞"; $replyText[] = $statusEmoji . ' [' . $this->escape($pls['name']) . "](https://m3u.su/$code/details)\n"; empty($pls['description']) || $replyText[] = $this->escape($pls['description']) . "\n"; if ($pls['isOnline'] === null) { $replyText[] = "⏲️ *Проверка:* в очереди"; } else { $now = DateTimeImmutable::createFromTimestamp(time()); $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 (count($pls['tags'] ?? []) > 0) { $replyText[] = "\n🏷️ *Теги* \(" . count($pls['tags']) . "\):\n**>" . $this->escape(trim(implode(' ', $pls['tags']))) . "||\n"; } else { $replyText[] = "🏷️ *Теги:* нет"; } } $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"; return $this->reply( implode("\n", $replyText), InlineKeyboardMarkup::fromResponse([ 'inline_keyboard' => [ [ [ 'text' => is_null($pls['isOnline']) ? 'Подробности' : 'Список каналов', 'url' => "https://iptv.axenov.dev/$code/details", ], [ 'text' => 'FAQ', 'url' => 'https://iptv.axenov.dev/faq', ] ] ] ]), ); } /** * Сверяет секретный заголовок с заданным в конфиге * * @param ServerRequestInterface $request * @return void * @throws InvalidTelegramSecretException */ protected function checkSecret(ServerRequestInterface $request): void { $secret = config('bot.secret'); if (empty($secret)) { return; } $header = $request->getHeaderLine('X-Telegram-Bot-Api-Secret-Token'); if (empty($header) || $header !== $secret) { throw new InvalidTelegramSecretException(); } } /** * Возвращает объект, описывающий команду бота * * @return MessageEntity|null */ protected function getBotCommand(): ?MessageEntity { return array_filter( $this->update->getMessage()->getEntities(), static fn (MessageEntity $entity) => $entity->getType() === 'bot_command', )[0] ?? null; } /** * Возвращает текст команды бота * * @return string|null */ protected function getBotCommandText(): ?string { $text = $this->update->getMessage()->getText(); $command = $this->getBotCommand(); return $command ? mb_substr($text, 0, $command->getLength()) : null; } /** * Отправляет текстовое сообщение в ответ на команду, отправленную пользователем * * @param string $text * @param InlineKeyboardMarkup|ReplyKeyboardMarkup|ReplyKeyboardRemove|ForceReply|null $keyboard * @return bool */ protected function reply( string $text, InlineKeyboardMarkup|ReplyKeyboardMarkup|ReplyKeyboardRemove|ForceReply|null $keyboard = null, ): bool { try { $this->bot->sendMessage( chatId: $this->update->getMessage()->getChat()->getId(), text: $text, parseMode: 'MarkdownV2', replyToMessageId: $this->update->getMessage()->getMessageId(), replyMarkup: $keyboard, ); } catch (Throwable $e) { error_log($e->getMessage()); return false; } return true; } /** * Экранирует служебные символы в строке * * @param string $string * @return string */ protected function escape(string $string): string { return str_replace( ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'], ['\_', '\*', '\[', '\]', '\(', '\)', '\~', '\`', '\>', '\#', '\+', '\-', '\=', '\|', '\{', '\}', '\.', '\!'], $string, ); } }