From b5d3b6035656a2a94527cbeb7557fe712328a5e6 Mon Sep 17 00:00:00 2001 From: AnthonyAxenov Date: Sat, 1 Nov 2025 00:58:25 +0800 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=80=D0=BE?= =?UTF-8?q?=D1=83=D1=82=D1=8B=20API=20=D0=B4=D0=BB=D1=8F=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B8=20=D0=B8=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=BD=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/version - /api/health - /api/stats --- app/Controllers/ApiController.php | 105 ++++++++++++++++++++++++ app/Controllers/BasicController.php | 3 +- app/Core/Bot.php | 120 +++++++++------------------- config/routes.php | 18 +++++ 4 files changed, 162 insertions(+), 84 deletions(-) diff --git a/app/Controllers/ApiController.php b/app/Controllers/ApiController.php index 0ff6275..5728cd0 100644 --- a/app/Controllers/ApiController.php +++ b/app/Controllers/ApiController.php @@ -9,6 +9,9 @@ declare(strict_types=1); namespace App\Controllers; +use App\Core\Bot; +use App\Core\Kernel; +use App\Core\StatisticsService; use App\Errors\PlaylistNotFoundException; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; @@ -81,4 +84,106 @@ class ApiController extends BasicController return $response->withStatus(200) ->withHeader('Content-Type', $mime); } + + /** + * Возвращает информацию о плейлисте + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return ResponseInterface + * @throws Exception + */ + public function version(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + return $this->responseJson($response, 200, [ + 'web' => Kernel::VERSION, + 'php' => PHP_VERSION, + 'keydb' => redis()->info('server')['redis_version'], + 'checker' => 'todo', + ]); + } + + /** + * Возвращает информацию о плейлисте + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return ResponseInterface + * @throws Exception + */ + public function health(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + function getSize(string $directory): int + { + $size = 0; + foreach (glob($directory . '/*') as $path){ + is_file($path) && $size += filesize($path); + is_dir($path) && $size += getSize($path); + } + return $size; + } + + $tgBotInfo = config('bot.token') ? new Bot()->api->getMe() : null; + $redisInfoServer = redis()->info('server'); // General information about the Redis server + $redisInfoClients = redis()->info('clients'); // Client connections section + $redisInfoMemory = redis()->info('memory'); // Memory consumption related information + $redisInfoPers = redis()->info('persistence'); // RDB and AOF related information + $redisInfoStats = redis()->info('stats'); // General statistics + $redisInfoCpu = redis()->info('cpu'); // CPU consumption statistics + $redisInfoCmd = redis()->info('commandstats'); // Redis command statistics + $redisInfoKeysp = redis()->info('keyspace'); // Database related statistics + $redisInfoErr = redis()->info('errorstats'); // Redis error statistics + + $health = [ + 'fileCache' => [ + 'tv-logos' => [ + 'sizeB' => $size = getSize(cache_path('tv-logos')), + 'sizeMiB' => round($size / 1024 / 1024, 3), + 'count' => count(glob(cache_path('tv-logos') . '/*')), + ], + 'qr-codes' => [ + 'sizeB' => $size = getSize(cache_path('qr-codes')), + 'sizeMiB' => round($size / 1024 / 1024, 3), + 'count' => count(glob(cache_path('qr-codes') . '/*')), + ], + ], + 'telegram' => [ + 'id' => $tgBotInfo->getId(), + 'first_name' => $tgBotInfo->getFirstName(), + 'username' => $tgBotInfo->getUsername(), + ], + 'redis' => [ + 'isConnected' => redis()->isConnected(), + 'info' => [ + 'server' => [ + 'uptime_in_seconds' => $redisInfoServer['uptime_in_seconds'], + 'uptime_in_days' => $redisInfoServer['uptime_in_days'], + ], + 'clients' => ['connected_clients' => $redisInfoClients['connected_clients']], + 'memory' => $redisInfoMemory, + 'persistence' => $redisInfoPers, + 'stats' => $redisInfoStats, + 'cpu' => $redisInfoCpu, + 'commandstats' => $redisInfoCmd, + 'keyspace' => $redisInfoKeysp, + 'errorstats' => $redisInfoErr, + ], + ], + ]; + + return $this->responseJson($response, 200, $health); + } + + /** + * Возвращает информацию о плейлисте + * + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return ResponseInterface + * @throws Exception + */ + public function stats(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface + { + return $this->responseJson($response, 200, new StatisticsService()->get()); + } } diff --git a/app/Controllers/BasicController.php b/app/Controllers/BasicController.php index 148f576..c16fc51 100644 --- a/app/Controllers/BasicController.php +++ b/app/Controllers/BasicController.php @@ -49,8 +49,9 @@ class BasicController * @param array $data * @return ResponseInterface */ - protected function responseJson(ResponseInterface $response, int $status, array $data): ResponseInterface + protected function responseJson(ResponseInterface $response, int $status, mixed $data): ResponseInterface { + is_scalar($data) && $data = [$data]; $data = array_merge(['timestamp' => time()], $data); $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); diff --git a/app/Core/Bot.php b/app/Core/Bot.php index b8f57f2..78f5099 100644 --- a/app/Core/Bot.php +++ b/app/Core/Bot.php @@ -33,37 +33,35 @@ class Bot /** * @var BotApi Объект Telegram Bot API */ - protected BotApi $bot; + public readonly BotApi $api; /** * @var Update Объект обновления бота */ protected Update $update; + /** + * @var ServerRequestInterface Пришедший от Telegram запрос + */ + protected ServerRequestInterface $request; + /** * Конструктор * - * @param ServerRequestInterface $request + * @param ServerRequestInterface|null $request * @throws InvalidTelegramSecretException - * @throws JsonException - * @throws InvalidArgumentException - * @throws Exception */ - public function __construct(ServerRequestInterface $request) + public function __construct(?ServerRequestInterface $request = null) { - $this->checkSecret($request); - - $body = json_decode((string)$request->getBody(), true); - if (json_last_error() !== JSON_ERROR_NONE) { - throw new JsonException(json_last_error_msg()); + $this->api = new BotApi(config('bot.token')); + if ($request) { + $this->checkSecret($request); + $this->request = $request; } - - $this->bot = new BotApi(config('bot.token')); - $this->update = Update::fromResponse($body); } /** - * Запсукает обработку команды + * Запускает обработку команды * * @return bool * @throws InvalidArgumentException @@ -72,7 +70,9 @@ class Bot */ public function process(): bool { + $this->parseRequestBody(); $commandText = $this->getBotCommandText(); + return match (true) { str_starts_with($commandText, '/start') => $this->processHelpCommand(), str_starts_with($commandText, '/list') => $this->processListCommand(), @@ -84,6 +84,22 @@ class Bot }; } + /** + * Подготавливает объект события бота + * + * @return void + * @throws InvalidArgumentException + * @throws JsonException + */ + protected function parseRequestBody(): void + { + $body = json_decode((string)$this->request->getBody(), true); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new JsonException(json_last_error_msg()); + } + $this->update = Update::fromResponse($body); + } + /** * Обрабатывает команду /list * @@ -94,7 +110,7 @@ class Bot */ protected function processListCommand(): bool { - $this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); + $this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); $playlists = ini()->getPlaylists(); if (empty($playlists)) { @@ -139,7 +155,7 @@ class Bot */ protected function processInfoCommand(): bool { - $this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); + $this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); $message = $this->update->getMessage(); $text = $message->getText(); @@ -235,7 +251,7 @@ class Bot */ protected function processHelpCommand(): bool { - $this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); + $this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); $replyText[] = 'Бот предоставляет короткую сводку о плейлистах, которые видны на сайте ' . $this->escape(base_url()) . '\.'; @@ -265,7 +281,7 @@ class Bot */ protected function processLinksCommand(): bool { - $this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); + $this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); $replyText[] = '*Ресурсы и страницы*'; $replyText[] = ''; @@ -288,69 +304,7 @@ class Bot */ protected function processStatsCommand(): bool { - $this->bot->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); - - $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); - } + $this->api->sendChatAction($this->update->getMessage()->getChat()->getId(), 'typing'); $stats = new StatisticsService()->get(); $replyText[] = '📊 *Статистика*'; @@ -437,7 +391,7 @@ class Bot InlineKeyboardMarkup|ReplyKeyboardMarkup|ReplyKeyboardRemove|ForceReply|null $keyboard = null, ): bool { try { - $this->bot->sendMessage( + $this->api->sendMessage( chatId: $this->update->getMessage()->getChat()->getId(), text: $text, parseMode: 'MarkdownV2', diff --git a/config/routes.php b/config/routes.php index 711872e..f6a958f 100644 --- a/config/routes.php +++ b/config/routes.php @@ -67,6 +67,24 @@ return [ 'handler' => [ApiController::class, 'makeQrCode'], 'name' => 'api::makeQrCode', ], + [ + 'method' => 'GET', + 'path' => '/api/version', + 'handler' => [ApiController::class, 'version'], + 'name' => 'api::version', + ], + [ + 'method' => 'GET', + 'path' => '/api/health', + 'handler' => [ApiController::class, 'health'], + 'name' => 'api::health', + ], + [ + 'method' => 'GET', + 'path' => '/api/stats', + 'handler' => [ApiController::class, 'stats'], + 'name' => 'api::stats', + ], /* |--------------------------------------------------------------------------