У процесі розробки та підтримки 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\