max-messenger-bot / max-bot-sender-php
PHP library for sending messages via the Max Messenger Bot API
Package info
github.com/max-messenger-bot/max-bot-sender-php
pkg:composer/max-messenger-bot/max-bot-sender-php
Requires
- php: ^7.4 || ^8.0
- ext-json: *
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^9.6 || ^10.5 || ^11.5
- psalm/plugin-phpunit: ^0.19
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
- vimeo/psalm: ^6.16
Suggests
- ext-curl: To send messages with the bundled CurlTransport; not needed with a PSR-18 client
- psr/http-client: To send messages with PsrTransport through any PSR-18 client
- psr/http-factory: To build PSR-7 requests in PsrTransport
Provides
None
Conflicts
None
Replaces
None
README
Лёгкая библиотека для отправки сообщений от имени бота в мессенджер МАКС. Умеет только одно — отправлять
сообщения (метод API POST /messages), зато подключается
одной строкой, работает на PHP 7.4+ и не тянет за собой зависимостей.
Подходит для уведомлений: заявки с сайта, алерты мониторинга, отчёты cron-задач.
Если нужно больше — получение событий, команды, обработка нажатий кнопок, загрузка файлов — используйте полный SDK max-messenger-bot/max-bot-api-php.
Содержание
- Требования
- Установка
- Быстрый старт
- Сборка сообщения
- Ответ метода
- Обработка ошибок
- Повторы при ошибках
- Выполнение запросов
- Справочник методов
Требования
- PHP 7.4 или новее;
ext-json;ext-curl— для встроенного транспортаCurlTransport. Не нужен, если запросы выполняет клиент PSR-18.
Установка
composer require max-messenger-bot/max-bot-sender-php
Быстрый старт
<?php require __DIR__ . '/vendor/autoload.php'; use MaxMessenger\Sender\MaxSender; $sender = new MaxSender('your-access-token'); $sender->sendToUser(12345678, 'Привет!'); $sender->sendToChat(-71234567890123, '<b>Новая заявка</b> с сайта', MaxSender::FORMAT_HTML);
Токен бота выдаётся при создании бота на платформе МАКС для партнёров.
Сборка сообщения
Сообщение собирается цепочкой методов set* и add*, а отправляется методами sendToChat() и sendToUser():
use MaxMessenger\Sender\MaxSender; $sender = new MaxSender('your-access-token'); $sender ->setText('**Сервер недоступен**: api.example.com') ->setFormat(MaxSender::FORMAT_MARKDOWN) ->addImageByUrl('https://example.com/graph.png') ->sendToChat($chatId);
Текст и форматирование
Текст задаётся методом setText() или параметром $message методов отправки. Длина текста — до 4000 символов.
По умолчанию текст пустой ('').
Разметка задаётся константами:
| Константа | Значение | Разметка |
|---|---|---|
MaxSender::FORMAT_HTML |
'html' |
HTML |
MaxSender::FORMAT_MARKDOWN |
'markdown' |
Markdown |
Без разметки (null) текст отправляется как есть. Неизвестный формат отклоняется с InvalidArgumentException.
Параметры $message и $format методов sendToChat() и sendToUser() действуют только на одну отправку
и не меняют значения, заданные через setText() и setFormat():
$sender->setText('Текст по умолчанию'); $sender->sendToUser($userId); // «Текст по умолчанию» $sender->sendToUser($userId, '<i>Другой текст</i>', MaxSender::FORMAT_HTML); $sender->sendToUser($userId); // снова «Текст по умолчанию»
Вложения
| Метод | Вложение |
|---|---|
addAudio($token) |
Аудио по токену. Должно быть единственным вложением |
addContact($contactId, $vcfInfo) |
Карточка контакта: ID пользователя МАКС и/или данные в формате VCF |
addFile($token) |
Файл по токену. Должен быть единственным вложением |
addImage($token) |
Изображение по токену |
addImageByUrl($url) |
Изображение по внешнему URL |
addLocation($latitude, $longitude) |
Геолокация |
addShare($url, $token) |
Предпросмотр контента по внешнему URL |
addSticker($code) |
Стикер по коду. Должен быть единственным вложением |
addVideo($token) |
Видео по токену |
Токены аудио, видео и файлов выдаются при загрузке файла на сервер МАКС. Эта библиотека загрузку не выполняет — для неё используйте полный SDK. Изображение проще всего отправить по внешнему URL:
$sender ->setText('Отчёт за день') ->addImageByUrl('https://example.com/report.png') ->addLocation(55.751244, 37.618423) ->sendToChat($chatId);
Сообщение без текста допустимо, если в нём есть вложения: текст по умолчанию пустой, поэтому для стикера
или вложения его задавать не нужно. Сообщение без текста и без вложений не отправляется: метод выбросит
LogicException.
$sender->addSticker('sticker-code')->sendToChat($chatId);
Клавиатура
Под сообщением можно разместить встроенную клавиатуру. Кнопки добавляются в текущий ряд,
addKeyboardNewRow() переносит следующую кнопку в новый ряд:
$sender ->setText('Новая заявка №1024') ->addCallbackButton('Принять', 'order:1024:accept') ->addCallbackButton('Отклонить', 'order:1024:reject') ->addKeyboardNewRow() ->addLinkButton('Открыть в CRM', 'https://crm.example.com/orders/1024') ->sendToChat($chatId);
| Метод | Кнопка |
|---|---|
addCallbackButton($text, $payload) |
Отправляет боту событие message_callback с $payload |
addClipboardButton($text, $payload) |
Копирует $payload в буфер обмена |
addLinkButton($text, $url) |
Открывает ссылку |
addMessageButton($text) |
Отправляет текст кнопки в чат от имени пользователя |
addOpenAppButton($text, $webApp, $contactId, $payload) |
Запускает мини-приложение бота, заданного $webApp или $contactId |
addRequestContactButton($text) |
Запрашивает контакт пользователя |
addRequestGeoLocationButton($text, $quick) |
Запрашивает геолокацию; $quick = true — без подтверждения |
Клавиатура содержит до 30 рядов, в ряду — до 7 кнопок (до 3, если это кнопки link, open_app,
request_contact или request_geo_location). Лишний ряд или лишняя кнопка в ряду отклоняются с LogicException.
Клавиатура отправляется как последнее вложение сообщения, поэтому сообщение из одной клавиатуры тоже допустимо. Обрабатывать нажатия на Callback-кнопки эта библиотека не умеет — для этого нужен полный SDK.
Настройки отправки
| Метод | Действие |
|---|---|
setDisableLinkPreview(bool $disableLinkPreview) |
true — не генерировать превью для ссылок в тексте. По умолчанию превью создаётся |
setNotify(bool $notify) |
false — участники чата не получат push-уведомление. Для каналов оставляйте true |
Повторная отправка и очистка
После отправки сообщение не очищается, поэтому одно и то же сообщение можно отправить нескольким получателям:
$sender->setText('Плановые работы с 02:00 до 03:00'); foreach ($chatIds as $chatId) { $sender->sendToChat($chatId); }
Чтобы собрать новое сообщение, вызовите reset(): он очищает текст, формат, вложения, клавиатуру и настройки отправки.
Токен, транспорт, базовый адрес и задержки повторов сохраняются.
Ответ метода
sendToChat() и sendToUser() возвращают созданное сообщение в виде массива — объект
Message API МАКС:
$message = $sender->sendToChat($chatId, 'Привет'); $mid = $message['body']['mid']; // ID сообщения
| Поле | Тип | Описание |
|---|---|---|
body.attachments |
list<array> |
Вложения сообщения. Может отсутствовать |
body.markup |
list<array> |
Разметка текста. Может отсутствовать |
body.mid |
string |
ID сообщения |
body.seq |
int |
Порядковый номер сообщения в чате |
body.text |
string |
Текст сообщения |
recipient.chat_id |
int |
ID чата или канала |
recipient.chat_type |
'channel', 'chat', 'dialog' |
Тип чата |
recipient.user_id |
int |
ID получателя в диалоге. Только для диалогов |
sender.first_name |
string |
Имя бота |
sender.is_bot |
bool |
true для бота |
sender.last_activity_time |
int |
Время последней активности (Unix-время в миллисекундах) |
sender.last_name |
string |
Фамилия. Для ботов не возвращается |
sender.user_id |
int |
ID бота |
sender.username |
string |
Никнейм бота |
timestamp |
int |
Время создания сообщения (Unix-время в миллисекундах) |
url |
string |
Публичная ссылка на пост. Только для каналов |
Объекта sender нет, если сообщение отправлено от имени канала.
Полная форма массива описана в PHPDoc методов, поэтому IDE и Psalm подсказывают поля и их типы.
Обработка ошибок
Все исключения отправки наследуют MaxMessenger\Sender\Exception\SenderException:
| Исключение | Когда возникает |
|---|---|
ApiException |
API МАКС вернул ошибку (код HTTP вне 2xx) |
SenderException |
Сервер ответил успешно, но в неожиданном формате |
TransportException |
Ответ не получен: сетевой сбой, истекло время ожидания, ошибка TLS |
ApiException содержит код ответа HTTP и код ошибки API:
use MaxMessenger\Sender\Exception\ApiException; use MaxMessenger\Sender\Exception\SenderException; try { $sender->sendToUser($userId, 'Привет'); } catch (ApiException $e) { $e->getHttpCode(); // 401 $e->getErrorCode(); // 'verify.token' $e->getMessage(); // 'Invalid access_token' } catch (SenderException $e) { // Сетевая ошибка или неожиданный ответ }
Ошибки использования — пустой токен, неизвестный формат, контакт без данных — выбрасывают InvalidArgumentException,
а попытка отправить пустое сообщение или переполнить клавиатуру — LogicException.
Исключение выбрасывается, только когда исчерпаны повторы.
Повторы при ошибках
При временной ошибке отправка повторяется автоматически, как в полном SDK:
| Ошибка | Задержки повторов |
|---|---|
TransportException: сетевой сбой, истекло время ожидания |
setRetryAttempts() |
| HTTP 429, 500, 502, 503, 504 | setRetryAttempts() |
HTTP 400 с кодом attachment.not.ready — вложение ещё обрабатывается |
setAttachmentRetryAttempts() |
Задержки задаются списком в миллисекундах: сколько элементов, столько повторов. По умолчанию —
[1000, 2000, 4000, 8000, 15000], то есть до пяти повторов за 30 секунд. Пустой список отключает повторы.
setAttachmentRetryAttempts(null) (по умолчанию) использует задержки из setRetryAttempts():
$sender ->setRetryAttempts([500, 1000, 2000]) // три повтора при временной ошибке ->setAttachmentRetryAttempts([]); // не повторять при attachment.not.ready
Обе очереди задержек расходуются независимо, и при каждой отправке начинаются заново.
Повтор после истечения времени ожидания может привести к дублю: сервер мог принять сообщение, но ответ не успел дойти. Если дубль недопустим, отключите повторы (
setRetryAttempts([])) и обрабатывайтеTransportExceptionсами.
Выполнение запросов
MaxSender выполняет запросы через транспорт — объект с интерфейсом
MaxMessenger\Sender\Transport\TransportInterface. Транспорт передаётся вторым параметром конструктора.
Если он не задан, создаётся CurlTransport с настройками по умолчанию.
Третий параметр конструктора — базовый адрес API (по умолчанию MaxSender::BASE_URL, https://platform-api2.max.ru).
Если указан собственный HTTPS-адрес, передайте транспорт с подходящими сертификатами:
по умолчанию CurlTransport использует только корневые сертификаты Минцифры.
Для сертификата из системного хранилища можно передать new CurlTransport(10000, 5000, false);
для собственного CA используйте setCaCertificatePath() или setCaCertificateDir().
curl
use MaxMessenger\Sender\MaxSender; use MaxMessenger\Sender\Transport\CurlTransport; $transport = new CurlTransport( 30000, // $timeout: сколько миллисекунд может выполняться запрос, 0 — без ограничения 5000, // $connectTimeout: сколько миллисекунд ждать подключения, 0 — сколько угодно true, // $useRussianTrustedCaCertificates: доверять сертификатам Минцифры из пакета ); $sender = new MaxSender('your-access-token', $transport);
По умолчанию: $timeout — 10 000 мс, $connectTimeout — 5000 мс, $useRussianTrustedCaCertificates — true.
Сертификаты. Сертификат API МАКС выпущен удостоверяющим центром Минцифры, которого нет в большинстве
системных хранилищ доверия. Поэтому CurlTransport по умолчанию проверяет сервер по корневым сертификатам
Минцифры, поставляемым с пакетом (resources/certs/russian_trusted_ca_bundle.pem). Если сертификаты Минцифры уже
установлены в системе, передайте false третьим параметром — будут использованы системные сертификаты.
Включить пакетные сертификаты позже можно методом useRussianTrustedCaCertificates().
Собственные корневые сертификаты задаются файлом или каталогом. null возвращает системные сертификаты:
$transport->setCaCertificatePath('/etc/ssl/custom/ca-bundle.pem'); // файл PEM (CURLOPT_CAINFO) $transport->setCaCertificateDir('/etc/ssl/custom/certs'); // каталог после openssl rehash (CURLOPT_CAPATH)
setCaCertificatePath() заменяет пакетный набор Минцифры, если он был включён.
Прокси. Метод setProxy() задаёт прокси-сервер:
$transport->setProxy('http://user:password@proxy.local:3128'); // HTTP-прокси $transport->setProxy('socks5.local:1080', true); // SOCKS5 $transport->setProxy(); // отключить прокси
Прочие параметры curl задаются методом setOption(). Значение null удаляет параметр:
$transport->setOption(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
Клиент PSR-18
Если в проекте уже есть HTTP-клиент PSR-18, используйте PsrTransport.
Нужны также фабрики PSR-17 для запросов и потоков; если клиент сам их реализует, фабрики можно не передавать.
Таймауты, прокси и сертификаты в этом случае настраиваются в самом клиенте.
Guzzle:
use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; use MaxMessenger\Sender\MaxSender; use MaxMessenger\Sender\Transport\PsrTransport; $factory = new HttpFactory(); $sender = new MaxSender('your-access-token', new PsrTransport(new Client(['timeout' => 10]), $factory, $factory));
Symfony HttpClient (Psr18Client сам реализует фабрики):
use MaxMessenger\Sender\MaxSender; use MaxMessenger\Sender\Transport\PsrTransport; use Symfony\Component\HttpClient\Psr18Client; $sender = new MaxSender('your-access-token', new PsrTransport(new Psr18Client()));
Сетевые ошибки клиента PSR-18 (NetworkExceptionInterface) превращаются в TransportException и повторяются,
исходное исключение доступно через getPrevious().
Прочие ошибки клиента (ClientExceptionInterface) не повторяются и пробрасываются как есть.
Свой транспорт
Достаточно реализовать один метод интерфейса TransportInterface:
use MaxMessenger\Sender\Exception\TransportException; use MaxMessenger\Sender\Transport\TransportInterface; final class MyTransport implements TransportInterface { public function post(string $url, array $headers, string $body): array { // Выполнить POST-запрос; при сетевой ошибке выбросить TransportException — запрос будет повторён. return [$httpCode, $responseBody]; } }
Справочник методов
| Метод | Описание |
|---|---|
__construct(string $token, ?TransportInterface $transport, string $baseUrl) |
Создаёт отправителя |
addAudio(string $token) |
Прикрепляет аудио |
addCallbackButton(string $text, string $payload) |
Добавляет Callback-кнопку |
addClipboardButton(string $text, string $payload) |
Добавляет кнопку копирования в буфер обмена |
addContact(?int $contactId, ?string $vcfInfo) |
Прикрепляет карточку контакта |
addFile(string $token) |
Прикрепляет файл |
addImage(string $token) |
Прикрепляет изображение по токену |
addImageByUrl(string $url) |
Прикрепляет изображение по URL |
addKeyboardNewRow() |
Переносит следующую кнопку в новый ряд |
addLinkButton(string $text, string $url) |
Добавляет кнопку-ссылку |
addLocation(float $latitude, float $longitude) |
Прикрепляет геолокацию |
addMessageButton(string $text) |
Добавляет кнопку сообщения |
addOpenAppButton(string $text, ?string $webApp, ?int $contactId, ?string $payload) |
Добавляет кнопку запуска мини-приложения |
addRequestContactButton(string $text) |
Добавляет кнопку запроса контакта |
addRequestGeoLocationButton(string $text, bool $quick) |
Добавляет кнопку запроса геолокации |
addShare(string $url, ?string $token) |
Прикрепляет предпросмотр контента по URL |
addSticker(string $code) |
Прикрепляет стикер |
addVideo(string $token) |
Прикрепляет видео |
reset() |
Очищает сообщение |
sendToChat(int $chatId, ?string $message, ?string $format) |
Отправляет сообщение в чат |
sendToUser(int $userId, ?string $message, ?string $format) |
Отправляет сообщение пользователю |
setAttachmentRetryAttempts(?array $attachmentRetryAttempts) |
Задаёт задержки повторов при attachment.not.ready |
setDisableLinkPreview(bool $disableLinkPreview) |
Отключает превью ссылок |
setFormat(?string $format) |
Задаёт разметку текста |
setNotify(bool $notify) |
Включает или отключает push-уведомление |
setRetryAttempts(array $retryAttempts) |
Задаёт задержки повторов при временной ошибке |
setText(string $text) |
Задаёт текст сообщения |