OptionalcreateApiСоздаёт API-фасад платформы для controller.api.
Фасад даёт бизнес-логике доступ к исходящим возможностям платформы (отправить медиа, ответить на callback-кнопку) без ручного конструирования платформенных Request-классов. Ядро вызывает этот метод само — знает о платформах только через данный контракт, поэтому добавить фасад новой платформе можно, не трогая ядро.
Опционален: BasePlatform даёт реализацию по умолчанию (возвращает
null — фасад недоступен), поэтому переопределять его нужно только
платформам с исходящими API-вызовами. Голосовым платформам (Алиса,
Маруся, SmartApp) фасад не нужен: их ответ формируется телом webhook.
Контроллер текущего запроса
Фасад либо null, если для платформы он недоступен
Отправка текста пользователю Этот метод используется для активных рассылок — когда навык или бот инициирует диалог первым (например, уведомление). В данном методе необходимо поддержать отправку результата пользователю. Это необходимо для того, чтобы само приложение смогло продолжить диалог.
Если ваша платформа не поддерживает отправку сообщений без входящего запроса (как Алиса), оставьте реализацию пустой или верните заглушку.
Ид пользователя, которому нужно отправить сообщение
Контроллер приложения или текст. Если необходимо отправить просто текст, можно передать строку, в случае, если необходимо передать картинку звук и тд, то необходимо корректно заполнить контроллер.
Метод, который вызывается при уничтожении плагина. В данном методе можно добавить отписку, либо выполнить другие действия.
Формирует тело ответа для отправки пользователю.
Возвращает платформо-специфичный ответ (например, JSON для Алисы).
Для платформ, которые отправляют ответ напрямую (например, Telegram через sendMessage),
метод может возвращать { ok: true } или аналог.
контроллер с готовым ответом
OptionalstateData: Record<string, unknown> | nullданные для локального хранилища
ответ в формате, понятном платформе
OptionalgetDeliveryIdУникальный ID доставки вебхука — для дедупликации повторов.
Мессенджеры повторяют доставку, если не получили 2xx вовремя. Ядро помнит ID
принятых доставок (в памяти процесса, 1 час) и на повтор отвечает 200 ok,
не запуская логику повторно. Повтор, пришедший во время обработки исходного
запроса, ждёт его исхода и обрабатывается заново, если исходный упал с 500.
Ключ дополняется хэшем тела запроса, поэтому поддельный запрос с угаданным ID
не заблокирует настоящий апдейт.
Реализуйте, только если ответ платформе не несёт содержимого (ответ уходит
через API): повтор получит тело ok. Голосовым платформам метод не нужен.
Получает данные из локального хранилища платформы.
контроллер приложения
данные, сохранённые ранее
Возвращает время обработки запроса в миллисекундах.
Основано на разнице между updateTimeStart и текущим временем.
контроллер приложения
время обработки в мс
Генерирует пример входящего запроса для локального тестирования навыка/бота.
Используется в инструментах отладки и авто-тестах.
текст запроса
идентификатор пользователя
счётчик для уникальности
состояние сессии
объект, имитирующий входящий запрос платформы
Формирует контекст для отправки рейтинга (если поддерживается платформой).
Используется только на платформах с поддержкой рейтинга (например, Сбер SmartApp).
Метод обязателен, но BasePlatform уже даёт реализацию по умолчанию
(делегирует getContent), поэтому переопределять его нужно только
для спец-формата рейтинга.
контроллер приложения
данные для отправки рейтинга
OptionalgetResponseTimeoutСколько платформа ждёт ответа на запрос, мс.
Ядро выполняет запросы одного пользователя по очереди. Если платформа ждёт ответ
ограниченное время (Алиса, Маруся, SmartApp), запрос ждёт предыдущий не дольше
половины оставшегося времени и затем выполняется параллельно — иначе ответ опоздал бы.
BasePlatform отвечает null: мессенджеры получают ответ через API, и жёсткого
срока у них нет.
Срок ответа в мс или null, если срока нет
OptionalgetUpdatesLong polling: один запрос за новыми обновлениями платформы (Telegram getUpdates,
VK Bots Long Poll, MAX GET /updates). Реализуйте, если платформа умеет отдавать
обновления по запросу, — тогда бота можно запустить bot.startPolling() без
публичного HTTPS-адреса.
Ядро вызывает метод в цикле и обрабатывает каждое обновление как запрос вебхука,
но без проверки подписи: обновление получено от API платформы по токену бота.
Позицию чтения (offset, marker, ts) хранит адаптер: следующий вызов должен вернуть
обновления после уже отданных. Запрос должен завершаться по signal — так
bot.stopPolling() и bot.close() не ждут окончания долгого запроса. Передавайте
сигнал через Request.signal: AbortSignal.any() с этим сигналом в Node 20 копит
память, ведь сигнал живёт весь сеанс polling.
Временную ошибку (сеть, 5xx) бросайте исключением — ядро повторит вызов с растущей
паузой. Если polling невозможен (неверный токен, у бота активен вебхук), запишите
причину в лог и верните null: ядро остановит цикл этой платформы.
Сигнал остановки polling
Обновления в формате тела вебхука платформы, [] — новых нет, null — остановить polling
class MyAdapter extends BasePlatform {
#offset = 0;
async getUpdates(signal: AbortSignal): Promise<unknown[] | null> {
const request = new Request(this.appContext as AppContext);
request.maxTimeQuery = 35_000; // дольше, чем платформа держит запрос (25 с)
request.signal = signal;
const res = await request.send<{ id: number }[]>(
`https://api.example.com/updates?offset=${this.#offset}&timeout=25`,
);
if (res.httpStatus === 401) {
this.appContext?.logError('MyAdapter: неверный токен, polling остановлен.');
return null;
}
if (!res.status || !res.data) {
throw new Error(`HTTP ${res.httpStatus ?? 'нет ответа'}`);
}
const updates = res.data;
if (updates.length) {
this.#offset = updates[updates.length - 1].id + 1;
}
return updates;
}
}
Метод инициализации плагина.
Вызывается один раз при подключении через bot.use().
Контекст приложения
Основной класс приложения
Проверяет полученный запрос от платформы на корректность. Реализация зависит от адаптера, как правило, в чувствительных платформах есть токен, который приходит с запросом, и желательно проверять, что пришедший токен соответствует тому, который сохранён в настройках.
Запрос от платформы. В webhookHandle — сырое тело строкой
(от него считается HMAC-подпись)
Optionalheaders: Record<string, unknown>HTTP-заголовки запроса
OptionalparsedQuery: unknownТо же тело, уже разобранное из JSON фреймворком.
Передаётся в webhookHandle/webhookEvent: адаптеру, которому для проверки нужен
объект (секрет в теле, как у VK), не нужно разбирать JSON второй раз.
true, если запрос прошёл проверку
isCorrectQuery(query, headers, parsedQuery) {
const body = (parsedQuery ?? (typeof query === 'string' ? JSON.parse(query) : query)) as { secret?: string };
const got = Buffer.from(body.secret ?? '');
const expected = Buffer.from(this.secret);
// timingSafeEqual из node:crypto — сравнение секрета за постоянное время
return got.length === expected.length && timingSafeEqual(got, expected);
}
Указывает, поддерживает ли платформа локальное хранилище.
контроллер приложения
true, если локальное хранилище доступно
Определяет, принадлежит ли входящий запрос данной платформе.
Метод проверяет заголовки или структуру тела запроса. Используется для маршрутизации входящих запросов между адаптерами.
OptionalisSignatureCheckEnabledВозвращает, включена ли проверка подписи вебхука для этой платформы с текущей конфигурацией (секрет задан).
Используется ядром в bot.start() для предупреждения о вебхуке, который
принимает запросы платформы без проверки подлинности. Адаптеры платформ
с подписью переопределяют метод: базовая реализация считает проверку
включённой при заданных tokens[platform].token и signatureName
(схема HMAC, например Viber).
OptionalisSignatureSupportedУмеет ли платформа подписывать вебхук (секрет в заголовке или в теле).
Ядро предупреждает при старте о вебхуке без проверки подписи только для
таких платформ: у Алисы, Маруси и SmartApp подписи нет, и советовать задать
секрет бессмысленно. BasePlatform отвечает true, если задан signatureName
или переопределён isSignatureCheckEnabled. Без метода (прямая реализация
интерфейса) ядро судит по тем же признакам.
true, если у платформы есть механизм подписи вебхука
Флаг, указывающий, что платформа голосовая (например, Алиса, Маруся).
Определяет лимит платформы. В значение указывается количество запросов, которое можно отправить платформе за 1 секунду. В случае, если у платформы нет ограничений, можно указать 0 или null. По умолчанию null
Уникальное имя платформы (например, 'telegram', 'alisa').
Сохраняет данные в локальное хранилище платформы.
данные для сохранения
контроллер приложения
Инициализирует данные запроса в контроллере приложения.
Парсит входящий запрос и заполняет controller.userCommand, controller.payload и другие поля.
Вызывается после подтверждения, что запрос принадлежит этой платформе.
входящий запрос
контроллер приложения для текущего запроса
false, если запрос повреждён или не может быть обработан; иначе true
OptionalsignatureNameИмя http-заголовка, в котором платформа передаёт подпись/секрет вебхука.
Заполняется адаптером для платформ с подписью в http-заголовке
(Telegram, Viber, MAX). VK проверяет секрет в теле запроса и
задаёт только isSignatureCheckEnabled. Отсутствие поля означает, что проверка
подписи для платформы недоступна по построению (Alisa, Marusia, SmartApp).
Используется ядром для предупреждения при старте о вебхуке без защиты.
OptionalsupportedEventsУниверсальные события (TEventType), которые адаптер может выставить
в controller.eventType. Источник знания для валидации bot.addEvent(...):
таблица принадлежит адаптеру, поэтому кастомные платформы участвуют
в проверке автоматически.
Опционально: BasePlatform уже объявляет дефолт ['message'], поэтому
наследникам достаточно переопределить поле при поддержке других событий.
Отсутствие поля (прямая реализация интерфейса) приравнивается к ['message'].
Устанавливает время начала обработки запроса.
Используется для измерения времени отклика (processingTime).
контроллер приложения
Интерфейс для адаптеров платформы (Алиса, SmartApp, Telegram, VK и др.).
Обеспечивает унификацию обработки запросов от разных платформ. Реализуется как плагин (
IPlugin) и регистрируется в приложении.