Запускаем бота

В прошлой заметке мы начали разработку нашего нового проекта на Vesp и создали тестового бота.
Сегодня мы этого бота запустим с простейшими командами /start и /help.
Напоминаю, что мы используем библиотеку longman/telegram-bot, которая предлагает очень удобный метод для написания собственных команд боту.
Команда - это файл, имя которого заканчивается на Command и расширяет абстрактный класс Longman\TelegramBot\Commands\Command - ровно также, как контроллеры Vesp расширяют основной абстрактный контроллер.
Создаём директорию /core/src/Commands и в ней файл StartCommand.php:
<?php

namespace App\Commands;

use Longman\TelegramBot\Commands\UserCommand;
use Longman\TelegramBot\Entities\ServerResponse;

class StartCommand extends UserCommand
{
    protected $name = 'start';
    protected $description = 'Запуск бота';
    protected $usage = '/start';

    public function execute(): ServerResponse
    {
        $user = $this->getMessage()->getFrom();
        $data = [
            'Привет, ' . ($user->getFirstName()) . '!',
            'Это тренировочный бот, написанный в целях обучения на https://bezumkin.ru/sections/vesp-telegram.',
            'На данный момент бот отвечает раз в минуту. Используй /help, чтобы увидеть все доступные команды.',
        ];

        return $this->replyToChat(implode(PHP_EOL . PHP_EOL, $data));
    }
}
Теперь нам нужно указать нашему боту место с новыми командами. Делаем это в предусмотрительно заданном сервисе core/src/Services/Telegram.php прямо в конструкторе:
    public function __construct()
    {
        parent::__construct(getenv('BOT_API_KEY'), getenv('BOT_USERNAME'));
        // Наши команды
        $this->addCommandsPath(BASE_DIR . 'core/src/Commands');
    }
Константа BASE_DIR объявляется в файле core/bootstrap.php и означает корень всего проекта, так что можно смело её везде использовать.
Тут нужно небольшое лирическое отступление.

Как работают боты в Телеграм

Когда вы пишете что-то боту, ваше сообщение уходит на сервер Телеграма, и дальше он уже смотрит в настройки бота. Если у него есть назначенный адрес приёма сообщение (webhook), то сервис пытается переслать ваше сообщение на этот адрес.
Если ваш сервер с ботом не отвечает, или выдаёт ошибку, Телеграм пытается доставить сообщение в течение кого-то времени, со всё более увеличивающимся промежутком попыток, чтобы вас не заспамить запросами.
Если ваш сервер ответит кодом 200, значит всё ок, вы получили сообщение. Телеграм больше ничего от вас не ждёт, и дальше ваш бот должен отправить своё сообщение серверу Телеграм, а тот его перешлёт вам в чат.
То есть, вся работа с ботом идёт не напрямую, а через посредника, в лице серверов телеги. Но вы этого не замечаете, и вам кажется, что бот очень быстро напрямую вам отвечает.
Такая скорость возможно только при наличии webhook, то есть адреса на сервере вашего бота, куда Телеграм может обратиться в любой момент. Нам же заморачиваться с хостингам пока не нужно, и мы будем забирать сообщения для бота в ручном режиме, через cron.

Обработка сообщений

Для консольных скриптов, котороми обычно и являются команды cron, у Vesp предусмотрена директория cli, то бишь Command Line Interface.
Создаём core/cli/get-updates.php
<?php

require dirname(__DIR__) . '/bootstrap.php';

try {
    $telegram = new \App\Services\Telegram();
    $telegram->useGetUpdatesWithoutDatabase();
    $telegram->handleGetUpdates();
} catch (Throwable $e) {
    echo $e->getMessage();
}
Базу данных мы использовать не будем, поэтому здесь только загрузка нашего сервиса Telegram, без Eloquent.
Команда handleGetUpdates получит все отправленные боту сообщения, сопоставит их с командами, которые в нём прописаны, и отправит ответы юзерам на сервер Телеграм.
Этот файл можно запускать в консоли вручную или добавить в менджер задач проекта.
Для добавления есть готовый файл core/cli/cron.php, куда мы пишем:
$scheduler->php(__DIR__ . '/get-updates.php', null, [], 'get_updates')
    ->everyMinute()
    ->inForeground()
    ->onlyOne();
как видно, запуск будет каждуюу минуту и только в одном экземпляре.
Осталось только добавить менеджер в crontab. У себя на MacOS я делаю это через EDITOR=nano crontab -e в консоле.
Внутри пишем что-то вроде этого, образая внимание на пути к файлам:
* * * * *   /opt/homebrew/bin/php ~/ВашиПроекты/ExampleBot/core/cli/cron.php
Теперь команда /start уже должна работать.
Но при использовании /help, увы, ничего не будет, потому что мы такой команды не добавляли.
Исправляемся в файле core/src/Commands/HelpCommand.php:
<?php

namespace App\Commands;

use Longman\TelegramBot\Commands\UserCommand;
use Longman\TelegramBot\Entities\ServerResponse;

class HelpCommand extends UserCommand
{
    protected $name = 'help';
    protected $description = 'Вывод сообщения со списком команд';
    protected $usage = '/help';

    public function execute(): ServerResponse
    {
        $data = [
            'Вот все доступные команды:',
            '',
        ];

        /** @var UserCommand[] $commands */
        $commands = $this->telegram->getCommandsList();
        foreach ($commands as $command) {
            if ($command->showInHelp() && $command->getUsage()) {
                $data[] = $command->getUsage() . ' ' . $command->getDescription();
            }
        }

        return $this->replyToChat(implode(PHP_EOL, $data));
    }
}
Как видно из кода, здесь мы получаем все доступные команды и выводим их описание пользователю.

Заключение

Вот и всё, бот @VespExampleBot уже работает, можно подключаться к нему в Телеграме и проверять.
Только учтите, что на данный момент он отвечает только раз в минуту, потому что выполняется на моём домашнем компьютере через cron.
Текущий исходный код можно посмотреть вот здесь. Продолжение скоро!

4 комментария

Сергей Лелеко
Получается под каждую команду свой скрипт и соотвественно своя логика отдельно, ну в целом наверное это и правильно
Василий Наумкин
Да, учитывая, что каждая команда расширяет общий класс, остатётся только прописывать логику и параметры.
Точно так же как в контроллерах Vesp - лично мне такой подход очень нравится.
Сергей Лелеко
Да и мне! И кстати библиотека конечно очень хорошая, которую ты тут используешь при создании бота. Я их кучу перелопатил и эта лучшая на мой взгляд из тех что есть под PHP.
Василий Наумкин
Я делал одного бота на botman/botman, но из-за своей универсальности конкретно с Телеграм на нём работать мне не понравилось.
Поэтому искал другую либу, и вот эта пока хорошая, посмотрим как будет дальше.
bezumkin.ru
Personal website of Vasily Naumkin
Прямой эфир
Василий Наумкин
01.07.2024, 11:56:41
Да, верно, именно так. А в контроллере, скорее всего, ловить данные методом post.
Василий Наумкин
26.06.2024, 09:38:15
О, точно, вылезает если не залогинен. Спасибо, исправил!
Василий Наумкин
09.04.2024, 04:45:01
> Ошибка 500 Это не похоже на ошибку Nginx, это скорее всего ошибка PHP - надо смотреть его логи. ...
Futuris
04.04.2024, 08:56:12
Я просто немного запутался. Когда в абзаце "Vesp/Core" ты пишешь про "новый trait FileModel", я поду...
Василий Наумкин
20.03.2024, 21:21:52
Volledig!
Андрей
14.03.2024, 13:47:10
Василий! Как всегда очень круто! Моё почтение!
russel gal
09.03.2024, 20:17:18
> А этот стоило написать хотя бы затем, чтобы получить комментарий от юзера, который ничего не писал...
Александр Наумов
27.01.2024, 03:06:18
Василий, спасибо! Извини, тупанул.
Василий Наумкин
22.01.2024, 07:43:20
Давай-давай!
Василий Наумкин
24.12.2023, 14:26:13
Спасибо!