Практическое махоботоводство в 2026 году. Часть 2: библиотека для yii2

от автора

Это моя вторая публикация на тему практики махоботоводства. В первой были рассмотрены общие вопросы создания ботов в MAX, некоторые отличия Bot API от аналогичного у Telegram и Hello, world на чистом PHP. Здесь начнём освоение обещанной в первой части библиотеки. Сейчас она уже частично опубликована (кроме работы с вебхуками), но «причёсывание» (документирование, правка багов и т. п.) продолжается. Описывать получается ещё довольно много. Чтобы не перегружать сегодняшний материал, рассмотрим установку библиотеки, сформулируем учебную задачу по отправке запросов через Bot API и разберём выполнение её первой части. Остальное оставим на последующие публикации; пока не знаю, сколько даже их всего будет (если, конечно, вообще успею всё здесь опубликовать, пока всякие злобные не заминусовали карму).

Скрытый текст

Но сначала новость. 16 июня MAX открыл возможность создавать ботов российским самозанятым лицам (официально называемых налогоплательщиками налога на профессиональный доход). Сам таковым не являюсь, так что проверить не смогу. Но, вероятно, круг ботоводов постепенно будет расширяться и дальше. Пока этот процесс идёт очень небыстро.

Часть 2. Библиотека для yii2. Отправка запросов к MAX Bot API

Credits

За основу для разработки была взята, на мой взгляд, очень красиво спроектированная, но, к сожалению, давно заброшенная (последний коммит был в январе 2022 года) библиотека Игоря Тарасова dicr\yii2-telegram, присутствующая в каталоге сторонних расширений yii2. Оттуда были позаимствованы структура проекта и часть кода обработки запросов и ответов. Остальное в силу значительных отличий вызовов API было написано с нуля. Игорю громадное спасибо за то, что показал, как правильно проектировать такие вещи, и вообще за всё сделанное им для данного проекта.

Disclaimer

В процессе публикации библиотека активно дорабатывалась на предмет избавления от зависимостей и дополнения видами запросов, не использованными ранее в текущих задачах. После этой работы по хорошему счёту нужно перетестить все запросы и обработку вебхуков, а что-то, с чем ранее не сталкивался, (например, загрузку файлов) и вообще освоить с нуля; этим занимаюсь, но времени сейчас очень мало. Пока что не рекомендую слепо тащить её в production: сначала стоит всё, как минимум, самостоятельно проверить.

Общее описание

Библиотека представляет собой самостоятельный модуль, подключаемый к yii2. В ней реализованы вызовы всех запросов к MAX Bot API, описанных в официальной документации на текущий момент. Надеюсь, так будет и дальше, но не потому, что не появится ничего нового. Обработка вебхуков в релиз пойдёт позже и описана будет в одной из следующих публикаций.

Объекты библиотеки, описывающие API, относятся к одному из трёх классов, в зависимости от которого прицеплены к конкретному пространству имён:

  • запрос к API (пространство имён lubezniy\yii2max\request);

  • ответ на запрос (пространство имён lubezniy\yii2max\response);

  • прочие сущности (пространство имён lubezniy\yii2max\entity).

Файлы библиотеки располагаются по пути от каталога yii-приложения в vendor/lubezniy/Yii2-max/src. На этот путь в файле vendor/yiisoft/extensions.conf должен будет указывать подмассив описания; в разделе установки опишу подробнее. В composer этой библиотеки нет, и размещение там пока что не планируется.

Модуль библиотеки добавляется в элемент modules массива конфигурации yii-приложения. В его элементах нужно в зависимости от способа использования задать параметры — id, маршрут (путь, определяющий URI для приёма вебхуков), токен бота, secret для вебхуков.

В подкаталоге request названия для объектов придумывал самостоятельно; документация по Bot API MAX их не предлагает вообще. Надеюсь, получилось достаточно просто для понимания.

Примеров использования и документации в текущем релизе нет; их буду доделывать в рабочем порядке. Пока что можно пользоваться комментариями в исходном коде, подсказками IDE (при разработке старался расписывать всё, что только можно, в формате phpdoc) и примерами из данного материала.

Общий порядок отправки запросов

Чтобы отправить какой-то запрос через библиотеку, нужно сначала вызвать метод модуля createRequest. Его единственным параметром выступает key-value массив конфигурации с обязательным свойством class, значение которого содержит пространство имён и имя класса запроса, и заполняемыми значениями атрибутов создаваемого объекта запроса, описанными в комментариях к создаваемому объекту. Конструкция может быть довольно сложной, т. к. значения могут содержать другие объекты со своими свойствами, но уже создаваемые через обычную PHP-конструкцию new. Это будет продемонстрировано в учебном примере.

Метод createRequest вернёт созданный объект запроса с заполненными атрибутами, автоматически подставив из конфига токен бота. Дальше у созданного объекта можно при необходимости поменять какие-то свойства (например, тот же токен бота, если ботов много) и затем для собственно отправки вызвать метод send() без параметров.

В ответ метод send(), если не будет вызывающей исключение ошибки, возвращает объект ответа, один из имеющихся в подкаталоге response. Разумеется, с заполненными свойствами. Какой конкретно тип ответа возвращает тот или иной вид запроса, можно подсмотреть в исходниках запросов.

Учебная задача по установке библиотеки и отправке через неё запросов

В первой части в качестве возможных примеров для разработки задачи я рассматривал варианты отправки ботом сообщения с кнопками и загрузки файлов. План учебной задачи будет такой: сначала создадим сообщение с кнопками (шаг 1), затем загрузим файл (шаг 2), после чего отредактируем сообщение, добавив туда загруженный файл (шаг 3). В этой публикации сделаем первый из них, плюс подготовительные работы. Делать будем на основе классического yii2 basic application.

Требования к машинке под задачу

  • ОС — Linux посвежее с доступом к консоли (можно по ssh) от ограниченного пользователя с sudo;

  • системные требования — соответствуют работе с yii2;

  • PHP восьмой версии (лучше всего пока 8.5 — на нём идут сейчас проверки) с расширениями json и curl для библиотеки и другими расширениями из требований yii2 (mbstring, dom или xml) в варианте cli;

  • composer (для установки yii2);

  • git;

  • текстовый редактор для редактирования конфигов и работы с примерами (nano, vim, mcedit – по вкусу пробующего; в задаче будет nano). Но лучше всего IDE с поддержкой phpdoc; заодно поможете мне протестить работу phpdoc в коде (сам IDE пользую редко).

Также, хоть это и не относится к машинке, нужен будет зарегистрированный в MAX бот с токеном. Сразу говорю: указание при регистрации названия бота в стиле «Бот для освоения API» у меня прокатило, на модерации не завернули. Надо полагать, не я был такой один, и модераторы уже видели, как разработчики учатся на реальных ботах. Подробности о том, что нужно и как делать, приведены в первой публикации.

Поскольку работа в чатботах связана с сообщениями, для освоения также нужен будет действующий аккаунт в MAX. Или два, если речь о работе с каналами и чатами — один админ канала и чата, который будет вытаскивать второго (рядовой пользователь) из различных ситуаций (смотря, конечно, какие задачи отрабатывать).

Как я описывал в первой части, для отправки сообщения нам нужен id пользователя-получателя (или чата-канала, где у бота есть право постинга; разница в том, что в первом случае надо подавать значение в поле user_id, во втором — в chat_id). Мы будем делать задачу с пользователем. И, прежде чем идти дальше, просьба получить и записать id пользователя описанным в первой части методом, ну и не забыть оставить пользователя подписанным на бот.

Небольшое допущение

Ставить приложение в данной задаче и работать с ним будем в каталоге /var/maxbot. Можете для себя определить свой, только не забудьте поменять в командах.

Начинаем процесс

Первое. Создаём каталог приложения и даём на него права не root, чтобы composer не ругался. Только не надо нигде вписывать номера строк; они здесь подставляются автоматически. )

sudo mkdir /var/maxbotsudo chmod 777 /var/maxbot

Второе. По каталогам переходим на уровень выше.

cd /var

Третье. Устанавливаем composer-ом basic-приложение yii

composer create-project --prefer-dist yiisoft/yii2-app-basic maxbot

Четвёртое. Создаём подкаталог автора библиотеки в подкаталоге vendor приложения и переходим в него

mkdir maxbot/vendor/lubezniycd maxbot/vendor/lubezniy

Пятое. Тащим библиотеку из репозитория

git clone https://hub.mos.ru/lubezniy/Yii2-max.git

Шестое. Возвращаемся в каталог приложения и запускаем редактирование файла расширений yii

cd ../../nano vendor/yiisoft/extensions.php

Седьмое. Последним элементом возвращаемого файлом массива (на момент работы — строка 52) вписываем библиотеку:

  'lubezniy/yii2max' =>  array (    'name' => 'lubezniy/Yii2-max',    'version' => '1.0.0.0',    'alias' =>    array (      '@lubezniy/yii2max' => $vendorDir . '/lubezniy/Yii2-max/src',    ),  ),

Восьмое. Сохраняем файл и выходим из nano. Дальше запускаем редактирование файла конфигурации yii для консольного приложения:

nano config/console.php

Девятое. В key-value массив $config добавляем элемент modules со следующим содержанием:

'modules' => [    'maxbot' => [        'class' => \lubezniy\yii2max\MaxModule::class,        'id' => 'maxbot',        'defaultRoute' => 'maxbot/callback/index',        'botToken' => 'токен вашего бота',        'webhookSecret' => 'secret для вебхука',        'handler' => static function(              \lubezniy\yii2max\entity\Update $update,              \lubezniy\yii2max\MaxModule $module        ) {            if (               empty($module->webhookSecret)               || !\Yii::$app->request->headers?->has('X-Max-Bot-Api-Secret')               || (\Yii::$app->request->headers?->get('X-Max-Bot-Api-Secret') != $module->webhookSecret)            ) {              throw new \yii\web\ForbiddenHttpException('Access denied');            }            // \app\components\MaxUpdateProcessor::process($update, $module);        },    ],],

Токен бота, разумеется, подставляем свой. С secret-ом для вебхука пока можно не торопиться; сегодня мы его не затронем.

Немного поясню. Конфиг модуля в принципе является общим как для консольного, так и для web-приложения. Поэтому набор элементов там полный.

В качестве id модуля в этой задаче выбран maxbot; он будет использоваться по ходу задачи. Он прописан и в элементе id массива конфигурации, и как ключ элемента массива modules. Если удобно поставить какой-то свой, не вопрос, только не забудьте проставить его далее в скриптах.

Элемент defaultRoute определяет URI вебхука в конфигурации web-приложения. В консольной конфигурации он не обязателен, если только не делать подписку на вебхуки и отписку от оных через модуль.

Элемент handler (функция обработки поступившего вебхука), разумеется, применим только в конфигурации web-приложения. В этой части я его оставил только потому, что часто модули консольного и web-приложения имеют одинаковую конфигурацию. Как видно, здесь уже прописана проверка заголовка входящего запроса на наличие и правильность secret-а; остальной код нужно будет дописать в обработчике. А элемент webhookSecret может потребоваться и из консоли, если понадобится из командной строки сделать подписку на вебхук — чтобы значение подставилось. Но до освоения в должной мере работы с библиотекой и настройки сервера (лучше всего с участием знающего сисадмина) не рекомендую включать подписку на вебхуки в принципе. Тем более, что код обработки вебхуков и подписки на них пока что не опубликован.

Сохраняем конфиг и выходим из редактора. Библиотека установлена. Переходим к собственно кодингу.

Создадим контроллер консольного приложения сразу под всю задачу и в нём поработаем над action-ом первого шага. Запустим редактор с файлом контроллера:

nano commands/MaxLearnController.php

И пишем обычный консольный контроллер с пока пустым action-ом. Здесь и далее по задаче в каждом фрагменте кода будет оставлен комментарий с указанием в стиле «здесь писать дальше». Понимаю, что не все читающие это разработчики пишут на PHP; так, полагаю, им будет проще.

<?phpnamespace app\commands;use Yii;use yii\console\Controller;use yii\console\ExitCode;class MaxLearnController extends Controller{    /**     * Шаг 1. Отправка сообщения с inline-клавиатурой     * @return int    */    public function actionStep1(): int    {        // Здесь пишем код дальше по заданию        return ExitCode::OK;    }}

Вспоминаем, что первый шаг задачи у нас — это формирование и отправка сообщения с кнопками.

Объект запроса у нас создаётся методом createRequest модуля, который мы прописывали в конфиге. И, чтобы создать объект, нам нужно первым делом получить этот модуль, а затем вызвать метод с параметрами, после чего на возвращённом объекте дёрнуть метод send()

Вписываем вместо коммента-указателя алгоритм получения модуля, создания и отправки запроса класса SendMessage с объектом класса NewMessageBody внутри, а также текстом (точнее, контентом) и его форматом в этом объекте:

/** @var \lubezniy\yii2max\Module $module модуль бота */$module = Yii::$app->getModule('maxbot', true);$request = $module->createRequest([    'class' => \lubezniy\yii2max\request\SendMessage::class,    // Заполним нужные нам свойства класса.    'userId' => ваш_id_пользователя-получателя,    'messageBody' => new \lubezniy\yii2max\entity\NewMessageBody([    // Здесь напишем код следующего этапа выполнения шага.]),]);// Объект результата отправки выведем сразу на экранprint_r($request->send());

id пользователя-получателя, конечно, подставляем свой, подписанный на бот.

В библиотечном исходнике src/entity/NewMessageBody.php подсматриваем, в какие свойства и как нам нужно подставить контент сообщения, формат контента (markdown или html – мы будем использовать html) и вложения, которыми у нас сначала будут только кнопки. Там видим, что для контента у нас есть строковое свойство text, для формата — также строковое свойство format, а вложения — это массив объектов класса lubezniy\yii2max\entity\AttachmentRequest, который присваивается свойству attachments. Придумываем (конечно, для нашей учебной задачи) текст и добавляем его в элементы массива; подставить эти элементы нужно на место комментария-указателя в коде:

            'text' => 'Наконец-то принялось сообщение',            'format' => 'html',            'attachments' => [                // Здесь далее будем описывать элементы массива вложений — пока что только inline-клавиатуру.            ],

Следующая подзадача — добавить inline-клавиатуру первым (и пока единственным) элементом в массив attachments. Чтобы посмотреть, как это делается, идём в вышеуказанный исходник и видим там два свойства: type (тип вложения) с значением inline_keyboard и объект соответствующего типа вложения \lubezniy\yii2max\entity\InlineKeyboardAttachmentRequestPayload (типов там прописано много, но нам сейчас нужен именно этот) в свойстве payload. Добавляем создание объекта вложения и определение его содержания в код (опять же, вместо комментария-указателя):

                new \lubezniy\yii2max\entity\AttachmentRequest([                    'type' => 'inline_keyboard',                    'payload' => new \lubezniy\yii2max\entity\InlineKeyboardAttachmentRequestPayload([                        // Здесь будем писать содержимое inline-клавиатуры                    ]),                ]),

Чтобы посмотреть, что нам надо подавать в клавиатуру, открываем соответствующий исходник и видим там единственное свойство buttons, представляющее собой двумерный (!) массив. Другими словами, каждый элемент массива этого свойства у нас представляет собой отдельный массив, описывающий строку клавиатуры. А внутри каждого массива-строки расположены элементы-кнопки типа lubezniy\yii2max\entity\KeyboardButton .

Теперь открываем исходник объекта KeyboardButton . Там у нас расписаны семь типов кнопок, которые можно указать в свойстве type:

  • callback — отправить боту апдейт с callback-ом;

  • link — открыть ссылку в браузере;

  • request_geo_location — запросить геолокацию;

  • request_contact — запросить контакт;

  • open_app – открыть мини-приложение;

  • message — отправить сообщение с названием кнопки;

  • clipboard — скопировать заданный текст в буфер обмена.

Колбэки и открытие мини-приложений мы рассматривать здесь не будем; это темы отдельные. А кнопки остальных типов сделаем.

Для каждого типа кнопки нужно заполнить определённый набор свойств. Какое свойство для какого типа нужно и что означает, расписано в исходнике в комментариях к свойствам. Чтобы не перегружать и без того объёмный текст объяснениями, просто попрошу вставить вместо коммента-указателя нижеприведённый код, а со свойствами дальше разберётесь самостоятельно. Пока просто скажу, что у нас 5 кнопок: по две в первых двух строках, ещё одна в третьей.

                        'buttons' => [                            // первая строка клавиатуры                            [                                new \lubezniy\yii2max\entity\KeyboardButton([                                    'type' => 'link',                                    'text' => 'Перейти по ссылке',                                    'url' => 'https://www.max.ru/',                                ]),                                new \lubezniy\yii2max\entity\KeyboardButton([                                    'type' => 'message',                                    'text' => 'Отправить сообщение',                                ]),                            ],                            // вторая строка                            [                                new \lubezniy\yii2max\entity\KeyboardButton([                                    'type' => 'request_geo_location',                                    'text' => 'Запрос гео',                                    'quick' => false,                                ]),                                new \lubezniy\yii2max\entity\KeyboardButton([                                    'type' => 'request_contact',                                    'text' => 'Запрос контакта',                                ]),                            ],                            // третья строка                            [                                new \lubezniy\yii2max\entity\KeyboardButton([                                    'type' => 'clipboard',                                    'text' => 'Копировать текст',                                    'payload' => 'Текст, скопированный в буфер',                                ]),                            ],                        ],

Всё, кодить закончили… Сохраняем файл, выходим из редактора. Остаётся проверить, что получилось. Для этого даём команду:

php yii max-learn/step1

И смотрим, что выдала консоль и что бот прислал в мессенджер. Если пошла какая-то ругань, разбираем ошибки, пока не получится. Эталонный экземпляр решения можно взять в репозитории библиотеки (путь — examples/tutorial/MaxLearnController.php) или прямо в каталоге vendor. Не получается — пишите в комментах; по желанию на какие-то вещи можно сделать и pull request. )

За сим эту часть заканчиваю. План на следующую — шаг 2 задачи; если будет немного, то и побольше. Написанное рекомендую сохранить, чтобы не делать заново с нуля. Удачи.

ссылка на оригинал статьи https://habr.com/ru/articles/1048882/