Консольная команда Yii2 для обслуживания Telegram-бота: webhook, getMe и ротация логов

В процессе разработки и поддержки Telegram-ботов на фреймворке Yii2 рутинные административные задачи быстро становятся узким горлом. Переключение бота со среды локальной разработки (где чаще используется Long Polling) на рабочий сервер (Webhook), проверка статуса подключения, отладка API и очистка логов входящих обновлений — все это требует автоматизации. Вызов методов Bot API вручную через браузер или сторонние клиенты небезопасен и неудобен.

Оптимальное решение — вынос этих операций в консольную команду Yii2 (yii telegram/*). Это позволяет автоматизировать развертывание (deploy pipelines), быстро проводить диагностику прямо на сервере и настроить регулярные задачи обслуживания через Cron.

Архитектура консольной команды и безопасный cURL-клиент

Для интеграции с Telegram Bot API мы напишем собственный легковесный cURL-клиент внутри консольного контроллера. Использовать стандартный file_get_contents в продакшене крайне не рекомендуется: он не позволяет гибко настраивать таймауты, не обрабатывает HTTP-коды ошибок корректно и уязвим к блокировкам при сетевых задержках.

Создадим файл commands/TelegramController.php. Токен бота мы будем получать из переменных окружения или конфигурационного файла Yii::$app->params, чтобы избежать хардкода конфиденциальных данных в репозитории.

<?php

namespace app\commands;

use Yii;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\Console;

class TelegramController extends Controller
{
private ?string $token = null;

/**
* Инициализация контроллера и валидация токена
*/
public function init()
{
parent::init();
// Загружаем токен из env или параметров Yii2
$this->token = getenv('TELEGRAM_BOT_TOKEN') ?: (Yii::$app->params['telegramBotToken'] ?? null);

if (empty($this->token)) {
$this->stderr("Ошибка: Токен Telegram-бота не сконфигурирован.\n", Console::FG_RED);
exit(ExitCode::CONFIG);
}
}

/**
* Универсальный метод отправки запросов к Telegram Bot API через cURL
*/
protected function sendRequest(string $method, array $params = []): array
{
$url = "https://api.telegram.org/bot{$this->token}/{$method}";
$ch = curl_init();

curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true,
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);

if ($response === false) {
throw new \RuntimeException("Ошибка cURL: " . $curlError);
}

$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException("Ошибка декодирования JSON: " . json_last_error_msg());
}

if ($httpCode !== 200 || !isset($data['ok']) || $data['ok'] !== true) {
$description = $data['description'] ?? 'Неизвестная ошибка';
$errorCode = $data['error_code'] ?? $httpCode;
throw new \RuntimeException("Telegram API Error [{$errorCode}]: {$description}");
}

return $data['result'];
}
}

Health-Check бота: getMe и getWebhookInfo

Первое, что необходимо при диагностике бота на сервере, — узнать его текущий статус. Команда actionStatus будет выполнять два последовательных запроса: getMe для верификации токена и getWebhookInfo для проверки текущего состояния веб-перехватчика.

Добавим в наш контроллер следующий код:

    /**
* Проверка состояния бота и текущих настроек Webhook
*/
public function actionStatus()
{
try {
$this->stdout("Запрос getMe... ", Console::FG_YELLOW);
$me = $this->sendRequest('getMe');
$this->stdout("OK\n", Console::FG_GREEN);
$this->stdout("Бот: @{$me['username']} (ID: {$me['id']})\n", Console::FG_BOLD);

$this->stdout("Запрос getWebhookInfo... ", Console::FG_YELLOW);
$webhook = $this->sendRequest('getWebhookInfo');
$this->stdout("OK\n", Console::FG_GREEN);

if (empty($webhook['url'])) {
$this->stdout("Статус Webhook: НЕ УСТАНОВЛЕН (бот работает в режиме Long Polling)\n", Console::FG_CYAN);
} else {
$this->stdout("URL вебхука: {$webhook['url']}\n", Console::FG_GREEN);
$this->stdout("Ожидающие апдейты (pending): " . ($webhook['pending_update_count'] ?? 0) . "\n");
$this->stdout("Макс. соединений: " . ($webhook['max_connections'] ?? 'по умолчанию') . "\n");
if (!empty($webhook['last_error_date'])) {
$errorTime = date('Y-m-d H:i:s', $webhook['last_error_date']);
$this->stdout("Последняя ошибка ({$errorTime}): {$webhook['last_error_message']}\n", Console::FG_RED);
}
}
return ExitCode::OK;
} catch (\Exception $e) {
$this->stderr("Ошибка диагностики: " . $e->getMessage() . "\n", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
}

Управление вебхуками: безопасный setWebhook с secret_token и удаление

При установке вебхука критически важно защитить конечную точку (endpoint) вашего контроллера от несанкционированных запросов. Начиная с версии API 6.1, Telegram поддерживает параметр secret_token. Этот токен передается в заголовке X-Telegram-Bot-Api-Secret-Token при каждом входящем вебхуке. Если заголовок не совпадает со значением, сгенерированным при установке, ваш контроллер должен сразу возвращать HTTP-ответ 403 Forbidden.

Реализуем методы установки и удаления вебхука в консольной команде. При установке мы будем автоматически генерировать криптографически безопасный токен.

    /**
* Установка Webhook для бота с генерацией secret_token
* @param string $url Полный HTTPS URL вашего вебхука
*/
public function actionSet(string $url)
{
if (!filter_var($url, FILTER_VALIDATE_URL) || !str_starts_with($url, 'https://')) {
$this->stderr("Ошибка: URL должен использовать безопасный протокол HTTPS.\n", Console::FG_RED);
return ExitCode::DATAERR;
}

// Генерируем случайный secret_token (16-32 символа)
$secretToken = bin2hex(random_bytes(16));

try {
$this->stdout("Установка вебхука на URL: {$url}... ", Console::FG_YELLOW);
$this->sendRequest('setWebhook', [
'url' => $url,
'secret_token' => $secretToken,
'max_connections' => 40,
'allowed_updates' => ['message', 'callback_query', 'my_chat_member']
]);
$this->stdout("Успешно!\n", Console::FG_GREEN);

$this->stdout("\nВНИМАНИЕ! Сохраните сгенерированный secret_token в .env или params.local.php:\n", Console::FG_YELLOW);
$this->stdout("TELEGRAM_SECRET_TOKEN={$secretToken}\n\n", Console::FG_BOLD);

return ExitCode::OK;
} catch (\Exception $e) {
$this->stderr("Ошибка установки вебхука: " . $e->getMessage() . "\n", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
}

/**
* Удаление вебхука и перевод бота в режим Long Polling
* @param bool $dropUpdates Сбросить ли все накопившиеся обновления в очереди Telegram
*/
public function actionDelete(bool $dropUpdates = false)
{
try {
$this->stdout("Удаление вебхука... ", Console::FG_YELLOW);
$this->sendRequest('deleteWebhook', [
'drop_pending_updates' => $dropUpdates
]);
$this->stdout("Успешно удален!\n", Console::FG_GREEN);
return ExitCode::OK;
} catch (\Exception $e) {
$this->stderr("Ошибка удаления: " . $e->getMessage() . "\n\

Новые статьи — в Telegram

Разбираем, что автоматизировать в бизнесе и как это работает на практике. Без спама.