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

В прошлой заметке мы начали разработку нашего нового проекта на 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)
Сергей Лелеко
01.03.2022 15:16

Получается под каждую команду свой скрипт и соотвественно своя логика отдельно, ну в целом наверное это и правильно

bezumkinВасилий Наумкин
01.03.2022 15:21

Да, учитывая, что каждая команда расширяет общий класс, остатётся только прописывать логику и параметры.

Точно так же как в контроллерах Vesp - лично мне такой подход очень нравится.

Сергей Лелеко
01.03.2022 15:26

Да и мне! И кстати библиотека конечно очень хорошая, которую ты тут используешь при создании бота. Я их кучу перелопатил и эта лучшая на мой взгляд из тех что есть под PHP.

bezumkinВасилий Наумкин
01.03.2022 15:32

Я делал одного бота на botman/botman, но из-за своей универсальности конкретно с Телеграм на нём работать мне не понравилось.

Поэтому искал другую либу, и вот эта пока хорошая, посмотрим как будет дальше.

bezumkin
Василий Наумкин
04.07.2022 23:34
Что-то странное у тебя произошло: миграция есть, и вроде как выполнена, но таблицы при этом отсутств...
inetlover
Александр Наумов
03.07.2022 20:36
Василий, спасибо! Все понятно!
bezumkin
Василий Наумкин
02.07.2022 20:28
Спасибо, поправил!
bezumkin
Василий Наумкин
30.06.2022 03:58
Есть ли возможность формировать &quot;friendly URL aliases&quot;, используя аналог translit MODx? ...
bezumkin
Василий Наумкин
27.06.2022 03:32
Спасибо за исправления, очень выручаешь =) Но учитывая количество не описаных в заметке дополнительн...
bezumkin
Василий Наумкин
27.06.2022 03:10
что будет использоваться для вывода многоуровневого меню Посмотри как работают комментарии на этом ...
bezumkin
Василий Наумкин
25.06.2022 11:56
Поправил, спасибо!
bezumkin
Василий Наумкин
21.06.2022 01:58
onLoad(data) { this.total = data.total }, и onLoad({total}) { this.total = total }, В нашем случ...
bezumkin
Василий Наумкин
20.06.2022 14:01
Прекрасно тебя понимаю, я когда сам в этом разбирался - голова дымилась. Но зато теперь прямо-таки п...
bezumkin
Василий Наумкин
20.06.2022 09:30
Не надо, оно по умолчанию так - я просто чуть более подробно написал.