Краткое содержание предыдущих серий (с):

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

Важная новость для всех махоботоводов!

На официальной странице MAX Bot API на днях появилась информация о необходимости до 19 июля перенаправить запросы к Bot API на сервер platform-api2.max.ru вместо platform-api.max.ru и установить корневые и промежуточные (в терминологии портала госуслуг "выпускающие") сертификаты Минцифры в хранилище доверенных. Новый API endpoint работает именно с сертификатом от Минцифры (на старом работает сертификат от Let's Encrypt). Надо полагать, старый эндпоинт ввиду санкционных (или каких-то других, не суть) ограничений не сможет получить новые сертификаты в глобальных УЦ, и MAX решил среагировать превентивно: пусть доменный сертификат Let's Encrypt на домен max.ru действует до 4 сентября, но УЦ может отозвать его досрочно, как раньше сделал GlobalSign.

Порядок установки сертификатов Минцифры для разных ОС со ссылками для их скачивания расписан на портале госуслуг по ссылке https://www.gosuslugi.ru/crt . Но для Linux почему-то они описали только процедуру установки на Red Hat Enterprise. Не проверял, но надо полагать, что она полностью или с небольшими изменениями подойдёт для других RedHat-based дистрибутивов. Здесь распишу процедуру для Debian, которым пользуюсь сам, в том числе на машинке, где пишу и отлаживаю учебные примеры; она немного отличается от красношапочной и уже проверена на той же машинке. Надо полагать, для производных дистрибутивов (Ubuntu, Mint и т. п.) процедура будет если не такой же, то очень схожей.

Как ставить сертификаты Минцифры на Debian

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

Исходно предполагаем, что мы находимся в домашнем каталоге пользователя, под которым работаем и у которого есть полные права при работе через sudo. Чтобы не засорять домашний каталог файлами, создадим отдельный каталог и перейдём в него:

mkdir russian_ca
cd russian_ca

Скачиваем zip-архивы с корневым и промежуточными («выпускающими») сертификатами (ссылки при необходимости взять актуальные на портале госуслуг):

wget https://gu-st.ru/content/lending/linux_russian_trusted_root_ca_pem.zip
wget https://gu-st.ru/content/lending/russian_trusted_sub_ca_pem.zip

Распаковываем архивы:

unzip linux_russian_trusted_root_ca_pem.zip
unzip russian_trusted_sub_ca_pem.zip

Копируем их с правами root в каталог сертификатов

sudo cp *.crt /usr/local/share/ca-certificates/

И также с правами root пересчитываем хэши в хранилище сертификатов.

sudo update-ca-certificates

Сертификаты установлены.

Также в скриптах или конфигурации ботов (у кого как), конечно, потребуется поменять URL API-эндпоинта MAX. Сам уже проверял новый endpoint в процессе работы с учебным примером на учебной машине; запросы отправляются, ответы принимаются. В случае с моей либой для этого библиотеку нужно обновить. Делается это git pull-ом из каталога библиотеки, как описывалось в прошлой публикации. Сделаете вы это до или после установки сертификатов Минцифры, в случае с этой библиотекой значения не имеет: в коде пока что прописано автоматическое переключение эндпоинта с 19 июля (дата проверяется при создании модуля), а до этого работа идёт со старым. После 19 июля, конечно, этот костыль будет убран, и URL окончательно будет заменён на новый.

Наконец, учебная часть по библиотеке

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

cd /var/maxbot/vendor/lubezniy/Yii2-max
git pull
cd /var/maxbot
nano commands/MaxLearnController.php

Как мы, надеюсь, увидели в прошлый раз, на заливке файлов входных параметров получается сравнительно немного, но расписаны они в довольно большом количестве строк кода. А заливок в нашем примере будет много. Посмотрев на это всё внимательнее, я решил вынести заливку в отдельный статический метод класса вложений библиотеки. Можно делать и по-старому, но в текущей версии для загрузки файла предпочтительнее использовать вызов статического метода \lubezniy\yii2max\entity\AttachmentRequest::uploadFile. Туда подаются объект модуля для отправки запросов, тип вложения, MIME-тип и полное имя файла; в ответ вернётся его токен.

Аналогично получается с созданием объекта файла вложения. В том же классе для этого появился статический метод fileAttach, куда параметрами сдаются тип и токен, а в ответ возвращается созданный объект AttachmentRequest (ну или null при неверном типе). Возможно, что-то такое появится и для других вложений (например, кнопок), но пока не обещаю. Всё новое мы сегодня задействуем в работе.

Сначала попробуем создать и передать сообщение, в котором будут две картинки, видео и одна кнопка клавиатуры. В конце файла, как водится, между последними двумя закрывающими фигурными скобками, пишем новый action. Их сегодня будет довольно много, но все относятся к одному и тому же предмету, поэтому пусть его имя у всех сегодняшних action-ов будет начинаться на step3, а дальше поставим цифру, соответствующую номеру action-а в публикации. Итак:

    /**
     * Шаг 3. Загрузка файлов и добавление к сообщениям в качестве вложений
     * Подшаг 1. Две картинки, видео и одна кнопка клавиатуры
     * @return int
     */
    public function actionStep31(): int
    {
        // получаем модуль
        /** @var \lubezniy\yii2max\MaxModule $module */
        $module = Yii::$app->getModule('maxbot', true);

        // загружаем первую картинку и получаем её токен
        /** @var string $image1Token */
        $image1Token = \lubezniy\yii2max\entity\AttachmentRequest::uploadFile(
            $module,
            'image',
            'image/jpeg',
            __DIR__ . '/../vendor/lubezniy/Yii2-max/examples/tutorial/files/img.jpg'
        );
        $this->stdout('Image 1 token is ' . $image1Token . "\n");

        // загружаем вторую картинку и получаем её токен
        /** @var string $image2Token */
        $image2Token = \lubezniy\yii2max\entity\AttachmentRequest::uploadFile(
            $module,
            'image',
            'image/png',
            __DIR__ . '/../vendor/lubezniy/Yii2-max/examples/tutorial/files/lubezniy.png'
        );
        $this->stdout('Image 2 token is ' . $image2Token . "\n");

        // загружаем видео и тоже получаем токен
        /** @var string $videoToken */
        $videoToken = \lubezniy\yii2max\entity\AttachmentRequest::uploadFile(
            $module,
            'video',
            'video/mp4',
            __DIR__ . '/../vendor/lubezniy/Yii2-max/examples/tutorial/files/video.mp4'
        );
        $this->stdout('Video token is ' . $videoToken . "\n");

        // Скомпонуем тело сообщения в отдельной переменной для понятности
        $body = new \lubezniy\yii2max\entity\NewMessageBody([
            'text' => 'Сообщение с двумя картинками и видео',
            'format' => 'html',
            'attachments' => [ // вложения к сообщению
                // картинка 1
                \lubezniy\yii2max\entity\AttachmentRequest::fileAttach(
                    'image', // тип вложения
                    $image1Token // токен файла вложения
                ),
                // картинка 2
                \lubezniy\yii2max\entity\AttachmentRequest::fileAttach(
                    'image',
                    $image2Token
                ),
                // видео
                \lubezniy\yii2max\entity\AttachmentRequest::fileAttach(
                    'video',
                    $videoToken
                ),
                // inline-клавиатура с одной кнопкой
                // естественно, в одну строку
                new \lubezniy\yii2max\entity\AttachmentRequest([
                    'type' => 'inline_keyboard',
                    'payload' => new \lubezniy\yii2max\entity\InlineKeyboardAttachmentRequestPayload([
                        'buttons' => [
                            // первая строка клавиатуры
                            [
                                new \lubezniy\yii2max\entity\KeyboardButton([
                                    'type' => 'link',
                                    'text' => 'Перейти по ссылке',
                                    'url' => 'https://www.max.ru/',
                                ]),
                            ],
                        ],
                    ]),
                ]),
            ],
        ]);

        // Теперь компонуем сообщение путём создания запроса
        $request = $module->createRequest([
            'class' => \lubezniy\yii2max\request\SendMessage::class,
            // Заполним нужные нам свойства класса.
            'userId' => ваш_id_пользователя-получателя,
            'messageBody' => $body,
        ]);

        // Отправляем сообщение
        $res = $request->send();
        // id сообщения выведем на экран
        $this->stdout('Sent message id is ' . $res->message->body->mid . "\n");
        return ExitCode::OK;
    }

В свойство userId параметров запроса отправки сообщения не забываем подставить id Вашего пользователя, полученный по методике из первой серии этой мыльной оперы про махоботоводство. Затем сохраняем файл, выходим из редактора и запускаем созданный action:

php yii max-learn/step31

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

В доработанной версии библиотеки мы загрузили файл вложения уже не в два приёма, а в один, не заморачиваясь с внутренними нюансами вроде разных типов объектов. Аналогичным образом создание объекта вложения тоже было сделано вызовом одного и того же метода, тогда как в классическом варианте взаимодействия с MAX Bot API разные типы вложений подразумевают разные вызовы. В этом плане стало проще компоновать сложные сообщения.

Ещё мы увидели, что к сообщениям в MAX можно относительно произвольно прикладывать видео, картинки и кнопки. В этом action-е я не показал полный результат выполнения запроса на отправку сообщения, но в целом в MAX Bot API упрощён парсинг сообщений по сравнению с Telegram: у телеги такого рода сообщения делятся на части по одному медиафайлу в каждом, плюс в чисто текстовом сообщении текст хранится в свойстве text, а у медиа - в свойстве caption (если мне память не изменяет, он ещё и есть только в одном сообщении, а с остальными его связывает свойство mediaGroupId). В сообщении MAX отдаётся единый объект сообщения, где в теле прописаны свойств text и вложения.

Теперь поработаем с нюансами: комбинации вложений в сообщения MAX не являются совсем уж произвольными. Попробуем объединить в одном сообщении картинку и аудио, а результат отправки сообщения выведем полностью. Пишем новый action:

    /**
     * Шаг 3. Загрузка файлов и добавление к сообщениям в качестве вложений
     * Подшаг 2. Картинка и аудио
     * @return int
     */
    public function actionStep32(): int
    {
        // получаем модуль
        /** @var \lubezniy\yii2max\MaxModule $module */
        $module = Yii::$app->getModule('maxbot', true);

        // загружаем картинку и получаем её токен
        /** @var string $imageToken */
        $imageToken = \lubezniy\yii2max\entity\AttachmentRequest::uploadFile(
            $module,
            'image',
            'image/jpeg',
            __DIR__ . '/../vendor/lubezniy/Yii2-max/examples/tutorial/files/img.jpg'
        );

        // загружаем аудио
        /** @var string $audioToken */
        $audioToken = \lubezniy\yii2max\entity\AttachmentRequest::uploadFile(
            $module,
            'audio',
            'audio/mp3',
            __DIR__ . '/../vendor/lubezniy/Yii2-max/examples/tutorial/files/audio.mp3'
        );

        // Скомпонуем тело сообщения в отдельной переменной для понятности
        $body = new \lubezniy\yii2max\entity\NewMessageBody([
            'text' => 'Сообщение с картинкой и аудио',
            'format' => 'html',
            'attachments' => [ // вложения к сообщению
                // картинка
                \lubezniy\yii2max\entity\AttachmentRequest::fileAttach(
                    'image', // тип вложения
                    $imageToken // токен файла вложения
                ),
                // аудио
                \lubezniy\yii2max\entity\AttachmentRequest::fileAttach(
                    'audio',
                    $audioToken
                ),
            ],
        ]);

        // Компонуем сообщение
        $request = $module->createRequest([
            'class' => \lubezniy\yii2max\request\SendMessage::class,
            // Заполним нужные нам свойства класса.
            'userId' => ваш_id_пользователя-получателя,
            'messageBody' => $body,
        ]);

        // Отправляем сообщение и выдаём сразу результат
        print_r($request->send());
        return ExitCode::OK;
    }

Запускаем:

php yii max-learn/step32

И получаем в ответ ошибку; в мессенджере сообщения, конечно, нет:

Exception 'yii\base\Exception' with message 'HTTP-error: 400
{"code":"proto.payload","message":"Must be only one audio attachment in message"}'

in /var/maxbot/vendor/lubezniy/Yii2-max/src/MaxRequest.php:171

Stack trace:
#0 /var/maxbot/vendor/lubezniy/Yii2-max/src/request/SendMessage.php(85): lubezniy\yii2max\MaxRequest->send()
#1 /var/maxbot/commands/MaxLearnController.php(261): lubezniy\yii2max\request\SendMessage->send()
#2 [internal function]: app\commands\MaxLearnController->actionStep32()
#3 /var/maxbot/vendor/yiisoft/yii2/base/InlineAction.php(60): call_user_func_array()
#4 /var/maxbot/vendor/yiisoft/yii2/base/Controller.php(182): yii\base\InlineAction->runWithParams()
#5 /var/maxbot/vendor/yiisoft/yii2/console/Controller.php(187): yii\base\Controller->runAction()
#6 /var/maxbot/vendor/yiisoft/yii2/base/Module.php(547): yii\console\Controller->runAction()
#7 /var/maxbot/vendor/yiisoft/yii2/console/Application.php(180): yii\base\Module->runAction()
#8 /var/maxbot/vendor/yiisoft/yii2/console/Application.php(147): yii\console\Application->runAction()
#9 /var/maxbot/vendor/yiisoft/yii2/base/Application.php(382): yii\console\Application->handleRequest()
#10 /var/maxbot/yii(20): yii\base\Application->run()
#11 {main}

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

Exception 'yii\base\Exception' with message 'HTTP-error: 400
{"code":"attachment.not.ready","message":"Key: errors.process.attachment.video.not.processed"}'

Это сообщение говорит о том, что обработка вложения ещё не завершена, и отправить сообщение с ним пока невозможно. Для видео и аудио такое встречается, и это описано в документации. Способов проверить готовность вложения нет, поэтому единственное, что мы можем сделать — это добавить задержку. Поставив строку

sleep(3);

между загрузкой вложения и компоновкой сообщения, я хоть и замедлил отправку сообщения на 3 секунды, но получил успешный вывод. И теперь можно подробно рассмотреть, что представляет собой сообщение, о чём я писал несколько абзацев назад. Но разберём мы это в другой публикации, когда будем рассматривать обработку апдейтов с сообщениями; пока же просто приведу вывод в чуть урезанном виде.

lubezniy\yii2max\response\SendMessageResponse Object
(    
    [message] => lubezniy\yii2max\entity\Message Object
        (
             [sender] => lubezniy\yii2max\entity\User Object
                (
                    [userId] => id_пользователя_бота
                    [firstName] => название_бота
                    [lastName] =>
                    [username] => публичное_имя_бота
                    [isBot] => 1
                    [lastActivityTime] => 1782855738636
                    [name] => старое_свойство_названия_бота
                )

            [recipient] => lubezniy\yii2max\entity\Recipient Object
                (
                    [chatId] => id_чата
                    [chatType] => dialog
                    [userId] => id_моего_пользователя
                )

            [timestamp] => 1782855738606
            [link] =>
            [body] => lubezniy\yii2max\entity\MessageBody Object
                (
                    [mid] => mid.000000000a059655019f1a7ba4ee2a8d
                    [seq] => 116841233685293709
                    [text] => Сообщение с картинкой и аудио
                    [attachments] => Array
                        (
                            [0] => lubezniy\yii2max\entity\Attachment Object
                                (
                                    [type] => audio
                                    [payload] => lubezniy\yii2max\entity\AttachmentPayload Object
                                            [photoId] =>
                                            [id] => 15025807646895
                                            [token] => токен_файла
                                            [url] => ссылка_на_файл
                                            [vcfInfo] =>
                                            [maxInfo] =>
                                            [buttons] =>
                                            [title] =>
                                            [description] =>
                                            [imageUrl] =>
                                            [latitude] =>
                                            [longitude] =>
                                        )

                                    [thumbnail] =>
                                    [width] =>
                                    [height] =>
                                    [duration] =>
                                    [transcription] =>
                                    [filename] =>
                                    [size] =>
                                )

                        )

                    [markup] =>
                )

            [stat] =>
            [url] =>
        )

)

В мессенджере при этом появилось сообщение, по виду голосовое; текст сообщения отображался сверху. Вывод: отправка сообщения с вложением audio (с текстом или без) есть отправка голосовухи; другие файлы вложений к audio цеплять нельзя. Ну и ещё из результата отправки можно извлечь пользовательский id бота (message.sender.userId).

Ещё по типам медиафайлов и вообще файлов есть свои ограничения по загрузке, описанные в официальной документации MAX. Их тоже надо учитывать при компоновке сообщения с вложениями.

Последнее, что мы сегодня опробуем — это отправка медиа как файла, а также отправка нескольких файлов вложениями к одному сообщению. Когда я пробовал отправлять несколько файлов через мессенджер, они почему-то автоматически разбивались на несколько сообщений, по одному файлу в каждом. Посмотрим, что получится с ботом. Action будет аналогичен предыдущему, только в качестве одного файла будет картинка, а в качестве второго — docx. Поехали:

    /**
     * Шаг 3. Загрузка файлов и добавление к сообщениям в качестве вложений
     * Подшаг 3. Картинка как файл + файл docx
     * @return int
     */
    public function actionStep33(): int
    {
        // получаем модуль
        /** @var \lubezniy\yii2max\MaxModule $module */
        $module = Yii::$app->getModule('maxbot', true);

        // загружаем картинку и получаем её токен
        /** @var string $imageToken */
        $imageToken = \lubezniy\yii2max\entity\AttachmentRequest::uploadFile(
            $module,
            'file',
            'image/jpeg',
            __DIR__ . '/../vendor/lubezniy/Yii2-max/examples/tutorial/files/img.jpg'
        );

        // загружаем docx
        /** @var string $docxToken */
        $docxToken = \lubezniy\yii2max\entity\AttachmentRequest::uploadFile(
            $module,
            'file',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            __DIR__ . '/../vendor/lubezniy/Yii2-max/examples/tutorial/files/docxfile.docx'
        );

        // Скомпонуем тело сообщения в отдельной переменной для понятности
        $body = new \lubezniy\yii2max\entity\NewMessageBody([
            'text' => 'Сообщение с несколькими файлами, один из них - картинка',
            'format' => 'html',
            'attachments' => [ // вложения к сообщению
                // картинка как файл
                \lubezniy\yii2max\entity\AttachmentRequest::fileAttach(
                    'file', // тип вложения
                    $imageToken // токен файла вложения
                ),
                // docx
                \lubezniy\yii2max\entity\AttachmentRequest::fileAttach(
                    'file',
                    $docxToken
                ),
            ],
        ]);

        // Компонуем сообщение
        $request = $module->createRequest([
            'class' => \lubezniy\yii2max\request\SendMessage::class,
            // Заполним нужные нам свойства класса.
            'userId' => ваш_id_пользователя-получателя,
            'messageBody' => $body,
        ]);

        // Отправляем сообщение и выдаём сразу результат
        print_r($request->send());
        return ExitCode::OK;
    }

Подставляем свой id, сохраняем, запускаем:

php yii max-learn/step33

И, увы, получаем ошибку:

Exception 'yii\base\Exception' with message 'HTTP-error: 400
{"code":"proto.payload","message":"Must be only one file attachment in message"}'

in /var/maxbot/vendor/lubezniy/Yii2-max/src/MaxRequest.php:171

Вывод: ни ботом, ни мессенджером (пока?) нельзя отправить в одном сообщении несколько файлов как файл.

Стоит попытаться закомментить добавление docx в сообщение и таки отправить картинку как файл. Попробуйте сделать это самостоятельно. У меня получилось; картинка нормально отправилась как файл, и текст сообщения отобразился нормально.

В каталоге examples/tutorial/files свежей библиотеки есть несколько небольших файлов разных типов. Можно на них отработать самостоятельно любые другие комбинации файлов; базы для этого расписано достаточно. На этом сегодняшнее занятие мы закончим; следующей темой предварительно возьмём редактирование сообщений. Всем желающим удачи в освоении и использовании.

Комментарии (7)


  1. fraks
    01.07.2026 01:51

    У меня нет кармы поставить плюс, но говорю в каменте - спасибо что публикуете актуальные для РФ решения, несмотря на то что вас активно минусуют.


    1. mmMike
      01.07.2026 01:51

      С нетерпением жду на Хабре статей от гордых разработчиков ПО/железа для ТСПУ. Со актуальной информацией и статьями “какую пользу я принес гражданам России”.

      Очень хотелось бы почитать и познакомится с авторами. Можно и лично. Страна должна знать своих героев.

      Впрочем, с учетом уголовного наказания за фотографии табличек на кладбище, то возможно и не нужно это. Герои они такие… анонимные.


  1. alexs963
    01.07.2026 01:51

    Заминусить реально полезный для жителей РФ пост

    Такая-то борьба.


  1. Feargin
    01.07.2026 01:51

    Полезная статья, перечитал дважды, но не очень понятно, почему пост заминусили, для рф более чем актуально


    1. mmMike
      01.07.2026 01:51

      Откуда: Армения

      Самые большие патриоты российской власти (не России. не надо путать) Почему то живут не в Россси


  1. gevals
    01.07.2026 01:51

    С одной стороны полезная информация, с другой стороны - мыши плакали кололись, ели кактус, с третьей, вижу новости про max, на автомате ставлю минус, и кто в этом виноват?


  1. fire64
    01.07.2026 01:51

    С учётом "дружелюбности" Макса по отношению к разработчикам и "открытости" системы...

    С их политикой думаю ещё и ручная модерация присутствует, странно, что пока не додумались создавать боты по бумажному заявлению со сроком рассмотрения 1 месяц.

    Да и пользователи далеко не все стремятся его к себе ставить.

    Самозанятый может создать не более 2 ботов, ООО или ИП не более 5.

    Боты исключительно корпоративные и должны соответствовать направлению компании. Никаких пет. проектов или развлекательных сервисов... Ник задаётся в виде ИНН.

    п.с.

    С точки зрения бизнеса вопросов нет, если есть аудитория с потребностью создания им корпоративных ботов в Максе, то почему нет. С точки зрения уважения к разработчикам ботов со стороны Макса конечно все печально.