Краткое содержание предыдущих серий (с):
Скрытый текст
Важная новость для всех махоботоводов!
На официальной странице 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)

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

fire64
01.07.2026 01:51С учётом "дружелюбности" Макса по отношению к разработчикам и "открытости" системы...
С их политикой думаю ещё и ручная модерация присутствует, странно, что пока не додумались создавать боты по бумажному заявлению со сроком рассмотрения 1 месяц.
Да и пользователи далеко не все стремятся его к себе ставить.
Самозанятый может создать не более 2 ботов, ООО или ИП не более 5.
Боты исключительно корпоративные и должны соответствовать направлению компании. Никаких пет. проектов или развлекательных сервисов... Ник задаётся в виде ИНН.
п.с.
С точки зрения бизнеса вопросов нет, если есть аудитория с потребностью создания им корпоративных ботов в Максе, то почему нет. С точки зрения уважения к разработчикам ботов со стороны Макса конечно все печально.
fraks
У меня нет кармы поставить плюс, но говорю в каменте - спасибо что публикуете актуальные для РФ решения, несмотря на то что вас активно минусуют.
mmMike
С нетерпением жду на Хабре статей от гордых разработчиков ПО/железа для ТСПУ. Со актуальной информацией и статьями “какую пользу я принес гражданам России”.
Очень хотелось бы почитать и познакомится с авторами. Можно и лично. Страна должна знать своих героев.
Впрочем, с учетом уголовного наказания за фотографии табличек на кладбище, то возможно и не нужно это. Герои они такие… анонимные.