name: erispulse-adapter-standards
description: "适用于:实现/评审适配器时需要对照的硬性技术规范。覆盖场景:когда вы разрабатываете новый адаптер, добавляете новую возможность для какой-либо платформы, вам нужно решить «включать в стандарт или оформлять как расширение {platform}_», как придумывать имена, как обрабатывать различия между платформами — сначала прочитайте этот документ. Если нужно лишь уточнить конкретную спецификацию по какой-то области, можно сразу перейти к соответствующему документу из карты стандартов в §2.。Когда нужно читать эту статью: когда вы адаптируете платформу, сопоставляя нативные сессии с платформы с типами (private/group/channel/guild/thread), или в модуле, если не уверены, какой тип цели нужно передавать в Send.To(), или хотите понять, как происходит автоматическое преобразование event.reply(). Если не нужно, можете пропустить.。Когда нужно читать эту статью: когда вы конвертируете нативные события платформы в стандартные события OneBot12 в адаптере — проверьте обязательные поля, структуру сегментов сообщений, именование расширенных полей (..."
ErisPulse 适配器技术标准
This skill is auto-built from the ErisPulse documentation. Repository: https://github.com/ErisPulse/ErisPulse Source paths are annotated per section; see docs/ in the repository for the latest content.
内容大纲
AI 按需读取:只读需要的章节即可,不必全文加载。
- 标准化总纲: 89-104 行
- Принципы стандартизации: 106-119 行
- Карта стандартов: 121-131 行
- Рабочий процесс стандартизации (как новые возможности попадают в стандарт): 133-170 行
- Паттерны обработки различий (уровень адаптера): 172-185 行
- Стандарт интерактивных компонентов: 187-309 行
- Будущие кандидаты (Roadmap): 311-324 行
- Стандартный чек-лист (Checklist) для разработчиков новых адаптеров: 326-345 行
- Связанные документы: 347-350 行
- 会话类型: 352-361 行
- Основные понятия: 363-389 行
- Стандартные типы сессий: 391-437 行
- Сопоставление типов платформ: 439-478 行
- Расширение с помощью пользовательских типов: 480-522 行
- Автоматическое определение типа: 524-559 行
- Примеры использования API: 561-600 行
- Справочник основных API: 602-650 行
- Вспомогательные методы: 652-674 行
- Рекомендуемые практики: 676-696 行
- Часто задаваемые вопросы: 698-718 行
- Связанные документы: 720-728 行
- 事件转换: 730-737 行
- Основные принципы: 739-744 行
- Требования к стандартным полям: 746-792 行
- Примеры форматов событий: 794-869 行
- Стандартные сегменты сообщений: 871-970 行
- Обработка неизвестных событий: 972-988 行
- Стандарты именования расширений: 990-1083 行
- Расширение типов сессий: 1085-1119 行
- Руководство для разработчиков модулей: 1121-1234 行
- Вывод типа сессии для событий notice / request: 1236-1301 行
- Связанные документы: 1303-1314 行
- 发送方法规范: 1316-1325 行
- Соглашения о ключевых словах: 1327-1335 行
- Стандартное именование методов: 1337-1389 行
- Подробная спецификация параметров: 1391-1559 行
- Именование платформенно-специфичных методов: 1561-1590 行
- Соглашения об именовании параметров: 1592-1602 行
- Спецификация возвращаемых значений: 1604-1609 行
- Спецификация обратного преобразования (OneBot12 → платформа): 1611-1793 行
- Обнаружение методов: 1795-1816 行
- Замечания по разработке адаптеров: 1818-1824 行
- Чек-лист реализации адаптера: 1826-1856 行
- MessageBuilder (конструктор сообщений) — что нужно знать стороне адаптера: 1858-1866 行
- Связанные документы: 1868-1876 行
- API 动作规范: 1878-1889 行
- Предпосылки проектирования: 1891-1903 行
- Параллельная структура DSL на трех уровнях: 1905-1920 行
- Список стандартных действий: 1922-2041 行
- Способы использования: 2043-2096 行
- Реализация адаптера: 2098-2171 行
- Формат ответа: 2173-2188 行
- Отношения с SendDSL / RequestDSL: 2190-2198 行
- Контрольный список реализации адаптера: 2200-2213 行
- Связанные документы: 2215-2225 行
- API 响应规范: 2227-2234 行
- Описание: 2236-2243 行
- Базовая структура возвращаемого значения: 2245-2260 行
- Полная спецификация полей: 2262-2331 行
- Требования к реализации: 2333-2337 行
- Расширения спецификации: 2339-2414 行
- Примечания: 2416-2422 行
- Что дальше: 2424-2427 行
- 请求动作规范: 2429-2438 行
- Обзор: 2440-2473 行
- Требования к полям события запроса: 2475-2519 行
- DSL запросов: 2521-2586 行
- Удобные методы Event: 2588-2622 行
- Требования к реализации адаптера: 2624-2769 行
- Проверочный список реализации адаптера: 2771-2788 行
- Расширение кодов ошибок: 2790-2803 行
- Связанные документы: 2805-2814 行
标准化总纲
来源:
docs/ru/standards/standardization-guide.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/ru/standards/standardization-guide.md)
Руководство по стандартизации адаптеров (генеральное руководство)
Tip
Когда читать этот документ: когда вы разрабатываете новый адаптер, добавляете новую возможность для какой-либо платформы, вам нужно решить «включать в стандарт или оформлять как расширение {platform}_», как придумывать имена, как обрабатывать различия между платформами — сначала прочитайте этот документ. Если нужно лишь уточнить конкретную спецификацию по какой-то области, можно сразу перейти к соответствующему документу из карты стандартов в §2.
Настоящий документ является генеральным руководством по стандартизации адаптеров ErisPulse: он устанавливает принципы кроссплатформенной стандартизации, карту стандартов, паттерны обработки различий, процесс включения новых возможностей в стандарт, а также даёт полные определения стандарта интерактивных компонентов (кнопки/клавиатуры, выпадающие списки и т.д.) и интерактивных событий обратного вызова. Любой разработчик адаптеров при реализации новой функциональности должен в первую очередь использовать стандартные определения, чтобы разработчики модулей получали одинаковый опыт на любой платформе с одним и тем же кодом — одинаковые имена, одинаковые параметры, одинаковые возвращаемые значения.
1. Принципы стандартизации
- Единообразие имён: одно и то же понятие на всех платформах использует одно и то же имя (например, отзыв сообщения единообразно
delete_message, сегмент кнопок единообразноkeyboard), независимо от платформы - Единообразие параметров: структура параметров стандартных действий/сегментов/методов одинакова на всех платформах; платформенно-специфичные параметры предоставляются как необязательные расширяемые параметры или поля расширения, не загрязняя стандартную сигнатуру
- Единообразие возвращаемых значений: все вызовы API/отправки возвращают стандартную структуру ответа (
status/retcode/data/message_id/message), стандартные поля внутриdata(такие какuser_id/user_name) имеют одинаковую семантику; исходные данные платформы помещаются в{platform}_raw - Обоснованные расширения: платформенно-специфичные возможности именуются с префиксом
{platform}_(сегменты:yunhu_form; действия:yunhu.board; поля событий:qqbot_button_id), явно обозначая их некроссплатформенность - Различия поглощаются на уровне адаптера: код модулей программируется ориентированно на стандарт, различия платформ преобразуются адаптером (маппинг параметров, нормализация полей, деградация возможностей), от модулей не требуется писать платформенные ветвления
- Деградация возможностей без ошибок: когда платформа не поддерживает некоторую стандартную возможность, выполняется грациозная деградация
(возврат
retcode=10002, текстовизация компонента вalt_message), без выбрасывания исключений, прерывающих логику модуля
2. Карта стандартов
| Область | Документ стандарта | Охватываемое содержание |
|---|---|---|
| Преобразование событий | Стандарт преобразования событий | Структура событий, стандартные сегменты сообщений (text/image/mention/reply/keyboard и др.), спецификации платформенных сегментов-расширений |
| Интерактивные компоненты | §5 настоящего документа | Кнопки/клавиатуры, выпадающие списки, карточки, стандартные поля интерактивных событий обратного вызова, соглашения о декораторах, маппинг для каждой платформы |
| API-действия | Стандарт API-действий | Унифицированный интерфейс стандартных действий OneBot12 (пользователи/группы/каналы/управление сообщениями/мета-действия) и ApiDSL |
| Операции с запросами | Спецификация операций с запросами | Поля событий запросов (request_id) и Request DSL (approve/reject) |
| Методы отправки | Спецификация методов отправки | Именование методов класса Send, параметры, декораторы, обратное преобразование (OB12→платформа) |
| Типы сессий | Стандарт типов сессий | Определения и маппинг типов сессий user/group/channel/guild/dms и др. |
| Ответы API | Стандарт ответов API | Стандартная структура ответа и соглашения о retcode |
3. Рабочий процесс стандартизации (как новые возможности попадают в стандарт)
Платформенно-специфичная возможность (префикс {platform}_)
│ одна и та же возможность появилась на ≥2 платформах
▼
Выявление общего (извлечение универсальных понятий и подмножества параметров)
│
▼
Черновик стандарта (именование + параметры + возвращаемые значения + таблицы маппинга для каждой платформы)
│ ревью
▼
Внесение в настоящее генеральное руководство / документы стандартов по областям + реализация слоя совместимости в базовом классе фреймворка/адаптерах
│
▼
Стандартный сегмент/действие (без префикса) — доступно модулям кроссплатформенно
Пример: кнопки изначально были реализованы независимо на каждой платформе (telegram_inline_keyboard / кнопки Yunhu / QQBot keyboard) → появились на ≥3 платформах → извлечена общая структура (label/type/data + rows) → опубликован стандартный сегмент keyboard → каждый адаптер реализует слой совместимости (декораторы принимают общую структуру + преобразование стандартного сегмента + прозрачная передача нативных сегментов).
3.1 Правила именования
| Объект | Правило | Пример |
|---|---|---|
| Стандартные сегменты сообщений | нижний регистр, общие понятия без префикса | keyboard, select, mention |
| Платформенные сегменты-расширения | префикс {platform}_ |
telegram_sticker, yunhu_form |
| Стандартные API-действия | стандартные имена OB12 (snake_case) | get_group_info, delete_message |
| Платформенные действия-расширения | префикс {platform}. или общее имя протокола |
yunhu.board, send_poke (расширение OB11) |
| Декораторы | PascalCase, общие возможности входят в базовый класс фреймворка | .Keyboard(rows), .At(uid) |
| Стандартные поля событий | общие понятия без префикса | interaction_id, button_data, request_id |
| Платформенные поля событий | префикс {platform}_ |
qqbot_event_id, telegram_chat_id |
3.2 Правила параметров и возвращаемых значений
- Стандартные параметры на всех платформах имеют одинаковые имена и одинаковую семантику; единицы/формат явно указываются в документе стандарта (например, метки времени в секундах, строковые ID)
- Обязательные параметры берутся как общее подмножество возможностей всех платформ; платформенные расширенные возможности — необязательные параметры
- Жёсткие ограничения платформ (например, rich media QQBot нельзя отправлять вместе с event_id) автоматически обрабатываются/деградируют на уровне адаптера и не раскрываются модулям
- Стандартные поля возвращаемого
dataодинаковы на всех платформах; дополнительная платформенная информация помещается в поля с платформенными именами внутриdataили в{platform}_raw
4. Паттерны обработки различий (уровень адаптера)
| Паттерн | Описание | Пример |
|---|---|---|
| Маппинг параметров | стандартные параметры → нативные параметры платформы | delete_message(message_id) → TG deleteMessage(chat_id, message_id) (chat_id дополняется из реестра) |
| Преобразование структур | стандартный сегмент → нативная структура платформы | сегмент keyboard → inline_keyboard / buttons / QQBot keyboard |
| Маппинг действий | имя стандартного действия → имя действия платформы | get_self_info → get_login_info (OB11) |
| Нормализация полей | ответ платформы → стандартные поля | getMe() → {user_id, user_name, user_displayname} |
| Синтез идентификаторов | генерация детерминированного ID, когда платформа не имеет нативного идентификатора | TG join request без ID → tjr_{chat}_{user}_{date} |
| Деградация возможностей | при отсутствии поддержки — текстовизация/возврат 10002 | Kook без keyboard → текстовизация в alt_message; get_friend_list → 10002 |
| Нормализация | «грязные» данные платформы → стандартный формат | @-метка QQBot openid → bot_id (нормализация имён) |
| Двухрежимная совместимость | одновременное принятие стандартной структуры и нативной структуры платформы | .Keyboard() принимает общую структуру rows или нативную структуру |
5. Стандарт интерактивных компонентов
5.1 Перечень компонентов и их состояние
| Компонент | type стандартного сегмента | Статус | Поддерживаемые платформы |
|---|---|---|---|
| Кнопки/клавиатура (keyboard) | keyboard |
✅ стандартизировано | Telegram / Yunhu / QQBot |
| Выпадающий список (select) | select |
📋 зарезервировано (структура см. §5.4) | Discord / Telegram(bot) |
| Карточка (card) | card |
📋 зарезервировано (см. §5.5) | Kook / Yunhu(html) |
| Модальное окно (modal) | modal |
📋 зарезервировано | Discord |
Соглашение об уровнях декораторов: Send-декораторы универсальных интерактивных компонентов реализуются классом Send адаптера (не встроены в базовый класс фреймворка), именование следует соглашениям §5.2, параметры следуют стандартной структуре настоящего документа.
5.2 keyboard — кнопки/инлайн-клавиатура
Структура сегмента сообщения (направление отправки)
Стандартный сегмент keyboard = двумерный массив rows, каждая кнопка имеет вид
{"label": "отображаемый текст", "type": "callback" | "link", "data": "данные обратного вызова или адрес перехода"},
также допускаются платформенно-специфичные необязательные поля (адаптер маппит по возможностям или игнорирует).
Эталонное определение структуры: полный JSON сегмента сообщения, таблица полей и семантика полей приведены в Стандарте преобразования событий §4.1.1 — сегмент keyboard (кнопки/инлайн-клавиатура), в настоящем разделе не повторяются.
Таблица маппинга по платформам
| Платформа | Стандартный сегмент → нативная структура | Справочник нативной структуры |
|---|---|---|
| Telegram | reply_markup.inline_keyboard: [{text, callback_data | url}] |
callback→callback_data (≤64 байт), link→url |
| Yunhu | content.buttons: [{label, action_type}] |
callback→action_type:2 + action + value, link→action_type:1 + url |
| QQBot | keyboard.content.rows: [{label, type, data}] |
callback→type:2 + data, link→type:0 + data (сообщение должно быть типа markdown) |
| Kook | модуль action-group карточки: [{type, text, value, click}] |
callback→click:return + value, link→click:link + url |
| Discord | components[].components: [{label, style, custom_id | url}] |
callback→style:1 + custom_id, link→style:5 + url |
Send-декораторы (реализуются классом Send каждого адаптера)
Универсальные декораторы реализуются каждым адаптером в собственном классе Send (без изменения базового класса фреймворка):
.Keyboard(rows): стандартное имя, принимает универсальную структуру rows из §5.2, внутри генерирует стандартный сегмент сообщенияkeyboard(или напрямую преобразует в нативную структуру платформы), обрабатывается единообразно черезRaw_ob12.Buttons(rows): необязательный семантический псевдоним, поведение идентично- Обратная совместимость: при обнаружении нативной структуры платформы она передаётся напрямую (без ошибок, без преобразования)
- Платформенные нативные сегменты-расширения (например,
telegram_inline_keyboard) продолжают передаваться прозрачно, без изменений
# Один и тот же код — любая платформа (декораторы и преобразования предоставляются каждым адаптером)
rows = [[{"label": "Нравится", "type": "callback", "data": "like:1"},
{"label": "Профиль", "type": "link", "data": "https://example.com"}]]
await adapter.Send.To("group", gid).Keyboard(rows).Text("Выберите")
Список совместимости адаптеров: QQBot / Telegram / Yunhu уже реализовано (декораторы + преобразование стандартного сегмента); новые адаптеры могут просто реализовать согласно настоящему документу (см. §7 Checklist).
5.3 Интерактивные события обратного вызова (после нажатия кнопки)
После нажатия кнопки пользователем событие, отправляемое платформой, обязано предоставлять следующие стандартные поля (detail_type может сохранять платформенное именование):
| Стандартное поле | Тип | Обязательно | Описание |
|---|---|---|---|
interaction_id |
str | да | ID данного взаимодействия (можно использовать для ответа, например, спиннер/подсказка) |
button_data |
str | да | Данные, возвращаемые кнопкой (то есть data из §5.2) |
button_label |
str | нет | Отображаемый текст кнопки |
user_id |
str | да | Нажавший пользователь |
message_id |
str | нет | Сообщение, содержащее кнопку |
group_id / channel_id |
str | нет | Сессия-источник |
Соглашение о detail_type: сохраняются существующие имена каждой платформы (qqbot_interaction / telegram_callback_query /
yunhu_a2ui_button и др.), но стандартные поля должны присутствовать полностью — модуль может получать значения кроссплатформенно с помощью event.get("button_data").
Рекомендуемые расширения Event (предоставляются EventMixin адаптера):
def get_button_data(self) -> str: ... # button_data
def get_interaction_id(self) -> str: ... # interaction_id
Ответ на взаимодействие (в зависимости от возможностей платформы): adapter.reply_interaction(interaction_id, code=0) (QQBot) /
answerCallbackQuery (Telegram) и т.д.; именование следует семейству методов платформы, принудительная унификация не применяется.
Маппинг событий обратного вызова по платформам
| Платформа | Нативное событие | detail_type | Источник interaction_id | Источник button_data |
|---|---|---|---|---|
| Telegram | callback_query |
telegram_callback_query (notice) |
callback_query.id |
callback_query.data |
| Yunhu | событие нажатия кнопки | yunhu_button_click / yunhu_a2ui_button |
buttonId / sourceComponentId |
value / actionName |
| QQBot | INTERACTION_CREATE |
qqbot_interaction |
interaction.id |
data.resolved.button_data |
| Kook | событие нажатия кнопки | kook_button_click |
msg_id+value |
value |
| Discord | INTERACTION_CREATE |
discord_interaction |
interaction.id |
data.custom_id |
5.4 select — выпадающий список (резерв)
{
"type": "select",
"data": {
"placeholder": "Выберите",
"options": [
{"label": "Вариант A", "data": "opt:A"},
{"label": "Вариант B", "data": "opt:B"}
],
"min_values": 1,
"max_values": 1
}
}
События обратного вызова повторно используют поля §5.3 (button_data = выбранный data, при множественном выборе — JSON-массив).
Платформы первой волны реализации: Discord (select menu), Telegram (переключение keyboard). Платформы без реализации, получив данный сегмент,
должны деградировать его до текстового списка в alt_message.
5.5 card — карточки (резерв, стандартизация отложена)
Структуры карточек различаются чрезвычайно сильно (полнофункциональные карточные модули Kook vs html Yunhu vs QQ markdown+keyboard), пока не вводим жёсткий стандарт. Рекомендации:
- Богатые текстовые карточки выражать комбинацией
text+keyboard(для большинства сценариев достаточно) - Полнофункциональные карточки платформ продолжать выражать сегментом-расширением
{platform}_card(например,kook_card) - Пересмотреть и повысить статус после появления изоморфной возможности карточек на ≥2 платформах
6. Будущие кандидаты (Roadmap)
Следующие возможности уже появились или ожидаются на ≥2 платформах; стандартизация продвигается в порядке приоритета:
| Кандидат | Затронутые платформы | Приоритет | Примечания |
|---|---|---|---|
| select — выпадающий список | Discord / Telegram | высокий | Черновик структуры см. §5.4 |
| Реакции (reactions) | QQBot / Telegram / Discord / Kook | высокий | Стандартизация обеих сторон: действие + событие |
| Действия управления группой (мут/кик/одобрение) | QQBot / Yunhu / OB11 | высокий | Большинство уже реализовано как платформенные действия, предстоит извлечь стандартные сигнатуры |
| Объявления/доски | Yunhu / Telegram / Discord | средний | Действия типа set_announcement |
| Модель файловых ресурсов (двухсегментный file_id) | все платформы | низкий (отложено) | Прямая отправка SendDSL.File(file, filename) уже является стандартным путём (прямая передача URL/пути/bytes, см. Спецификацию методов отправки §2.1); модель file_id передаётся прозрачно только если бэкенд изначально её поддерживает, см. Стандарт API-действий §3.5 |
| Карточки card | Kook / Yunhu | низкий | Структуры сильно различаются, см. §5.5 |
| Формы form | Yunhu | низкий | Платформенно-специфично, сохраняется префикс {platform}_ |
| Транскодирование медиа/определение размера | все платформы | низкий | Реализуется внутри адаптера, наружу не стандартизируется |
7. Стандартный чек-лист (Checklist) для разработчиков новых адаптеров
При разработке нового адаптера сверяйтесь с этим списком при реализации (★ — обязательно, остальное — рекомендуется):
- ★ Преобразование событий в стандартную структуру OneBot12, наследование
BaseConverter - ★ Поддержка приёма/отправки стандартных сегментов сообщений (text/image/mention/reply/keyboard…; медиа-сегменты обрабатываются согласно градации форм и лестнице деградации из Спецификации методов отправки §2.1)
- ★ Реализация
Raw_ob12(включая преобразование стандартных сегментов → структур платформы; стандартный сегментkeyboardобязателен) - ★ Возврат стандартной структуры ответа (
make_response/make_error) - ★ Мультиаккаунтность:
AccountConfigClass(BotAccountConfig)+_resolve_account - ★ Класс Send наследует
BaseAdapter.Send, использует_apply_modifiers/send_context - ☆ Api DSL: маппинг стандартных действий на API платформы (см. Стандарт API-действий)
- ☆ Request DSL: события запросов содержат
request_id+accept/reject - ☆ Интерактивные компоненты: преобразование сегмента
keyboard+ стандартные поля интерактивного обратного вызова + декораторы.Keyboard()/.Buttons()(реализуются классом Send адаптера) - ☆ EventMixin:
get_raw_event()/get_button_data()и другие платформенные расширенные методы - ☆ Фоновые задачи жизненного цикла через
runtime.spawn_background - ☆ Чтение конфигурации через
self.cfg - ☆ Мягкая зависимость от фреймворка: не объявлять жёсткую зависимость от ErisPulse + проверка версии в рантайме
- ☆ i18n: многоязычность полей конфигурации и логов
- ☆ Документация платформы platform-guide + platform-features.md в репозитории адаптера
8. Связанные документы
- Стандарты по областям см. в карте стандартов §2
- Встроенные в фреймворк адаптеры могут служить эталонными реализациями: QQBot (полный набор парадигмы v5), OneBot11 (маппинг Api DSL), Yunhu (BaseConverter + расширение Web API)
会话类型
来源:
docs/ru/standards/session-types.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/ru/standards/session-types.md)
Стандарт типов сессий ErisPulse
Tip
Когда нужно читать эту статью: когда вы адаптируете платформу, сопоставляя нативные сессии с платформы с типами (private/group/channel/guild/thread), или в модуле, если не уверены, какой тип цели нужно передавать в Send.To(), или хотите понять, как происходит автоматическое преобразование event.reply(). Если не нужно, можете пропустить.
В этом документе определены стандартные типы сессий, поддерживаемые ErisPulse, включая типы событий получения и типы целей отправки.
1. Основные понятия
1.1 Типы получения и отправки
ErisPulse различает два типа сессий:
- Тип получения (Receive Type): поле
detail_typeсобытия, используемое для получения - Тип отправки (Send Type): тип цели, используемый в методе
Send.To()при отправке сообщений
1.2 Отношения между типами
Тип получения (detail_type) Тип отправки (Send.To)
───────────────── ────────────────
private → user
group → group
channel → channel
guild → guild
thread → thread
user → user
Ключевые моменты:
private— это тип получения, при отправке стандартный тип —usergroup,channel,guild,thread— одинаковые типы при получении и отправке- Рекомендуемый путь: использовать
event.reply()— автоматически выполняет преобразование типов и вывод цели, без необходимости ручного управления (автоматическое преобразованиеprivate→userпроисходит здесь) - При ручной отправке: передавайте тип отправки (например,
Send.To("user", ...)); передача типа получения, например,Send.To("private", ...)тоже может быть автоматически преобразована, но не рекомендуется (см. §6.1)
2. Стандартные типы сессий
2.1 Стандартные типы OneBot12
private
- Тип получения:
private - Тип отправки:
user - Описание: сообщения в личной переписке
- Поле ID:
user_id - Применимые платформы: все платформы, поддерживающие личную переписку
group
- Тип получения:
group - Тип отправки:
group - Описание: сообщения в группе, включая различные формы групп (например, supergroup в Telegram)
- Поле ID:
group_id - Применимые платформы: все платформы, поддерживающие групповые чаты
user
- Тип получения:
user - Тип отправки:
user - Описание: тип пользователя, некоторые платформы (например, Telegram) представляют личные чаты как
user, а неprivate - Поле ID:
user_id - Применимые платформы: Telegram и другие платформы
2.2 Расширенные типы ErisPulse
channel
- Тип получения:
channel - Тип отправки:
channel - Описание: сообщения в канале, поддерживают широковещательные сообщения нескольким пользователям
- Поле ID:
channel_id - Применимые платформы: Discord, Telegram, Line и др.
guild
- Тип получения:
guild - Тип отправки:
guild - Описание: сообщения в сервере/сообществе, обычно используются для событий уровня сервера Discord
- Поле ID:
guild_id - Применимые платформы: Discord и др.
thread
- Тип получения:
thread - Тип отправки:
thread - Описание: сообщения в теме/подканале, используются для подобсуждений в сообществах
- Поле ID:
thread_id - Применимые платформы: Discord Threads, Telegram Topics и др.
3. Сопоставление типов платформ
3.1 Принцип сопоставления
Адаптер отвечает за сопоставление нативных типов платформы со стандартными типами ErisPulse:
Нативный тип платформы → Стандартный тип ErisPulse → Тип отправки
3.2 Примеры сопоставления популярных платформ
Telegram
Тип Telegram Тип получения ErisPulse Тип отправки
───────────────── ──────────────── ───────────
private private user
group group group
supergroup group group # Сопоставляется с group
channel channel channel
Discord
Тип Discord Тип получения ErisPulse Тип отправки
───────────────── ──────────────── ───────────
Direct Message private user
Text Channel channel channel
Guild guild guild
Thread thread thread
OneBot11
Тип OneBot11 Тип получения ErisPulse Тип отправки
───────────────── ──────────────── ───────────
private private user
group group group
discuss group group # Сопоставляется с group
4. Расширение с помощью пользовательских типов
4.1 Регистрация пользовательских типов
Адаптер может зарегистрировать пользовательские типы сессий:
from ErisPulse.Core.Event import register_custom_type
# Регистрация пользовательского типа
register_custom_type(
receive_type="my_custom_type",
send_type="custom",
id_field="custom_id",
platform="MyPlatform"
)
4.2 Использование пользовательских типов
После регистрации система автоматически обрабатывает преобразование и вывод типа:
# Автоматический вывод
receive_type = infer_receive_type(event, platform="MyPlatform")
# Возвращает: "my_custom_type"
# Преобразование в тип отправки
send_type = convert_to_send_type(receive_type, platform="MyPlatform")
# Возвращает: "custom"
# Получение соответствующего ID
target_id = get_target_id(event, platform="MyPlatform")
# Возвращает: event["custom_id"]
4.3 Удаление пользовательских типов
from ErisPulse.Core.Event import unregister_custom_type
unregister_custom_type("my_custom_type", platform="MyPlatform")
5. Автоматическое определение типа
Если у события отсутствует явное поле detail_type, система автоматически определяет тип по доступным полям ID:
Note
Изменение поведения в 2.7.0+: detail_type используется только если это известный тип сессии (стандартный или пользовательский). У событий notice/request (например, group_member_increase, friend_increase) detail_type является семантическим подтипом, а не типом сессии, и система будет определять правильный тип сессии по полям ID.
5.1 Приоритет определения
Приоритет (от высокого к низкому):
1. group_id → group
2. channel_id → channel
3. guild_id → guild
4. thread_id → thread
5. user_id → private
5.2 Примеры использования
# У события только group_id
event = {"group_id": "123", "user_id": "456"}
receive_type = infer_receive_type(event)
# Возвращает: "group" (используется приоритетный group_id)
# У события только user_id
event = {"user_id": "123"}
receive_type = infer_receive_type(event)
# Возвращает: "private"
# У события notice detail_type — семантический подтип, в 2.7.0+ определяется по полю ID
event = {"type": "notice", "detail_type": "group_member_increase", "group_id": "123"}
receive_type = infer_receive_type(event)
# Возвращает: "group" (а не "group_member_increase")
6. Примеры использования API
6.1 Отправка сообщений
from ErisPulse import adapter
# Отправка пользователю
await adapter.myplatform.Send.To("user", "123").Text("Hello")
# Отправка в группу
await adapter.myplatform.Send.To("group", "456").Text("Hello")
# Автоматическое преобразование private → user: использование приемлемого, но не рекомендуемого способа (может быть несовместимость)
await adapter.myplatform.Send.To("private", "789").Text("Hello")
# Внутри автоматически преобразуется в: Send.To("user", "789")
# Рекомендуемый способ: при ручной отправке используйте тип отправки user, или используйте event.reply() для автоматического преобразования (см. 6.2)
6.2 Ответ на событие
from ErisPulse.Core.Event import Event
# Event.reply() автоматически обрабатывает преобразование типов (рекомендуемый путь)
await event.reply("Сообщение ответа")
# Внутри автоматически используется правильный тип отправки (автоматическое преобразование private → user происходит здесь)
6.3 Обработка команд
from ErisPulse.Core.Event import command
@command(name="test")
async def handle_test(event):
# Система автоматически обрабатывает тип сессии
# Не нужно вручную проверять group_id или user_id
await event.reply("Команда выполнена успешно")
7. Справочник основных API
7.1 Преобразование типов
from ErisPulse.Core.Event import convert_to_send_type, convert_to_receive_type
# Преобразование типа получения в тип отправки
convert_to_send_type("private") # → "user"
convert_to_send_type("group") # → "group"
# Преобразование типа отправки в тип получения
convert_to_receive_type("user") # → "private"
convert_to_receive_type("group") # → "group"
7.2 Получение полей ID
from ErisPulse.Core.Event import get_id_field, get_receive_type
get_id_field("group") # → "group_id"
get_id_field("private") # → "user_id"
get_receive_type("group_id") # → "group"
get_receive_type("user_id") # → "private"
7.3 Получение информации для отправки за один вызов
from ErisPulse.Core.Event import get_send_type_and_target_id
event = {"detail_type": "private", "user_id": "123"}
send_type, target_id = get_send_type_and_target_id(event)
# send_type = "user", target_id = "123"
# Непосредственно для использования в Send.To()
await adapter.Send.To(send_type, target_id).Text("Hello")
7.4 Получение целевого ID
from ErisPulse.Core.Event import get_target_id
event = {"detail_type": "group", "group_id": "456"}
get_target_id(event) # → "456"
8. Вспомогательные методы
from ErisPulse.Core.Event import (
is_standard_type,
is_valid_send_type,
get_standard_types,
get_send_types,
clear_custom_types,
)
is_standard_type("private") # True
is_standard_type("custom_type") # False
is_valid_send_type("user") # True
is_valid_send_type("invalid") # False
get_standard_types() # {"private", "group", "channel", "guild", "thread", "user"}
get_send_types() # {"user", "group", "channel", "guild", "thread"}
clear_custom_types() # Очистка всех пользовательских типов
clear_custom_types(platform="discord") # Очистка пользовательских типов только для указанной платформы
9. Рекомендуемые практики
7.1 Разработчики адаптеров
- Используйте стандартные сопоставления: по возможности сопоставляйте с типами стандарта, а не создавайте новые типы
- Правильно преобразуйте: убедитесь, что сопоставление типов получения и отправки корректно
- Сохраняйте исходные данные: сохраняйте оригинальный тип события в
{platform}_raw - Документируйте: в документации адаптера укажите сопоставления типов
7.2 Разработчики модулей
- Используйте вспомогательные методы: используйте методы, такие как
get_send_type_and_target_id() - Избегайте жесткого кодирования: не пишите код вида
if group_id else "private" - Учитывайте все типы: код должен поддерживать все стандартные типы, а не только private/group
- Гибко проектируйте: используйте методы обертки событий, а не прямой доступ к полям
7.3 Определение типов
- Предпочитайте detail_type: если есть явное поле, не используйте определение
- Рационально используйте определение: только когда нет явного типа
- Учитывайте приоритеты: знайте приоритеты определения, чтобы избежать неожиданных результатов
10. Часто задаваемые вопросы
В1: Почему при отправке private нужно преобразовывать в user?
О: Это требование стандарта OneBot12. private — это понятие получения, при отправке лучше использовать user, что соответствует семантике.
В2: Как поддержать новые типы сессий?
О: Зарегистрируйте пользовательский тип с помощью register_custom_type(), или используйте стандартные типы, такие как channel, guild.
В3: Что делать, если у события нет detail_type?
О: Система автоматически определит тип по доступным полям ID. Приоритет: group > channel > guild > thread > user.
В4: Как адаптер должен сопоставлять Telegram supergroup?
О: В логике преобразования адаптера сопоставьте supergroup со стандартным типом group.
В5: Как обрабатывать специальные платформы, такие как почта?
О: Для неуниверсальных или платформенных специфических типов используйте {platform}_raw и {platform}_raw_type для сохранения исходных данных, адаптер должен обрабатывать их самостоятельно.
11. Связанные документы
Со стороны адаптера (сопоставление и преобразование):
- Стандарт преобразования событий - полное описание стандарта преобразования событий
- Спецификация методов отправки - спецификация методов и параметров класса Send
- Руководство по разработке адаптеров - полное руководство по разработке адаптеров
Со стороны модуля (если не хотите вручную управлять преобразованием типов):
- Используйте
event.reply(), чтобы автоматически выполнить преобразование типов и вывод цели, подробнее см. Документация по Event Wrapper
事件转换
来源:
docs/ru/standards/event-conversion.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/ru/standards/event-conversion.md)
Стандартизированные правила преобразования адаптеров
Tip
Когда нужно читать эту статью: когда вы конвертируете нативные события платформы в стандартные события OneBot12 в адаптере — проверьте обязательные поля, структуру сегментов сообщений, именование расширенных полей ({platform}_ префикс), вывод типа сессии, сопоставляя с этой статьей. Если не нужно, пропустите.
1. Основные принципы
- Строгое соответствие: все стандартные поля должны полностью соответствовать спецификации OneBot12
- Ясное расширение: функции, специфичные для платформы, должны иметь префикс
{platform}_(например, yunhu_form) - Полная целостность данных: исходные данные события должны сохраняться в поле
{platform}_raw, тип исходного события — в поле{platform}_raw_type - Единое время: все временные метки должны быть преобразованы в 10-значные Unix-временные метки (в секундах)
- Единое имя платформы: поле
platformдолжно соответствовать названию или псевдониму, зарегистрированному в ErisPulse
2. Требования к стандартным полям
2.1 Обязательные поля
| Поле | Тип | Описание |
|---|---|---|
| id | string | Уникальный идентификатор события |
| time | integer | Unix-временная метка (в секундах) |
| type | string | Тип события |
| detail_type | string | Подтип события (см. Стандарт типов сессий) |
| platform | string | Название платформы |
| self | object | Информация о боте |
| self.platform | string | Название платформы |
| self.user_id | string | ID пользователя бота |
Правила для detail_type:
- Должен использовать стандартные типы сессий ErisPulse (см. Стандарт типов сессий)
- Поддерживаемые типы:
private,group,user,channel,guild,thread - Адаптер отвечает за сопоставление типов платформы со стандартными типами
2.2 Поля для событий сообщений
| Поле | Тип | Описание |
|---|---|---|
| message | array | Массив сегментов сообщения |
| alt_message | string | Запасной текст сегментов сообщения |
| user_id | string | ID пользователя |
| user_nickname | string | Имя пользователя (необязательно) |
2.3 Поля для уведомлений
| Поле | Тип | Описание |
|---|---|---|
| user_id | string | ID пользователя |
| user_nickname | string | Имя пользователя (необязательно) |
| operator_id | string | ID оператора (необязательно) |
2.4 Поля для запросов
| Поле | Тип | Описание |
|---|---|---|
| user_id | string | ID пользователя |
| user_nickname | string | Имя пользователя (необязательно) |
| comment | string | Комментарий к запросу (необязательно) |
| request_id | string | Идентификатор запроса (рекомендуется для согласования/отклонения запроса) |
Описание поля request_id:
request_id— уникальный идентификатор запроса, используемый для согласования/отклонения запроса через DSLHandleRequest- Адаптер должен сопоставлять идентификатор запроса платформы с этим полем
- Если платформа не предоставляет идентификатор запроса, адаптер должен сгенерировать уникальный идентификатор (например, хэш из временной метки и ID пользователя)
- При отсутствии
request_idвызовevent.approve()/event.reject()будет генерироватьValueError
3. Примеры форматов событий
3.1 Событие сообщения (message)
{
"id": "1234567890",
"time": 1752241223,
"type": "message",
"detail_type": "group",
"platform": "yunhu",
"self": {
"platform": "yunhu",
"user_id": "bot_123"
},
"message": [
{
"type": "text",
"data": {
"text": "Розыгрыш суперприза"
}
}
],
"alt_message": "Розыгрыш суперприза",
"user_id": "user_456",
"user_nickname": "YingXinche",
"group_id": "group_789",
"yunhu_raw": {...},
"yunhu_raw_type": "message.receive.normal",
"yunhu_command": {
"name": "Розыгрыш",
"args": "Суперприз"
}
}
3.2 Событие уведомления (notice)
{
"id": "1234567891",
"time": 1752241224,
"type": "notice",
"detail_type": "group_member_increase",
"platform": "yunhu",
"self": {
"platform": "yunhu",
"user_id": "bot_123"
},
"user_id": "user_456",
"user_nickname": "YingXinche",
"group_id": "group_789",
"operator_id": "",
"yunhu_raw": {...},
"yunhu_raw_type": "bot.followed"
}
3.3 Событие запроса (request)
{
"id": "1234567892",
"time": 1752241225,
"type": "request",
"detail_type": "friend",
"platform": "onebot11",
"self": {
"platform": "onebot11",
"user_id": "bot_123"
},
"user_id": "user_456",
"user_nickname": "YingXinche",
"comment": "Пожалуйста, добавьте в друзья",
"request_id": "req_abc123",
"onebot11_raw": {...},
"onebot11_raw_type": "request"
}
4. Стандартные сегменты сообщений
4.1 Стандартные сегменты сообщений
Стандартные сегменты сообщений не требуют префикса платформы.
| Тип | Описание | Поля data |
|---|---|---|
text |
Текст | text: str |
image |
Изображение | file, url: str |
audio |
Аудио | file, url: str |
video |
Видео | file, url: str |
file |
Файл | file, url: str, filename: str |
mention |
Упоминание пользователя | user_id: str, user_name: str |
reply |
Ответ | message_id: str |
face |
Эмодзи | id: str |
location |
Геолокация | latitude: float, longitude: float |
keyboard |
Кнопки/внутренняя клавиатура | rows: list[list[button]] (см. 4.1.1) |
Формат поля file для медиа-сегментов (направление отправки, общий для image / audio / video / file):
| Формат | Пример | Требования адаптера |
|---|---|---|
| HTTP(S) URL | https://example.com/a.png |
Обязательно принимать |
| Локальный путь к файлу | /tmp/a.png, C:\tmp\a.png |
Обязательно принимать |
| Бинарные данные | bytes |
Обязательно принимать |
URI file:// / Base64 / Data URI |
file:///tmp/a.png, data:image/png;base64,... |
Рекомендуется принимать |
Полный протокол отправки медиа (порядок определения формата, вывод имени файла, иерархия возможностей) см. в Спецификации методов отправки §2.1.
Семантика направления полей:
file: Направление отправки — источник содержимого (указанные форматы); Направление получения — заполняется адаптером в виде формата, доступного для скачивания (обычно это URL, или идентификатор ресурса, доступного черезget_file)url: URL-ссылка, возвращаемая платформой (адаптер должен заполнять, если возможно, для прямого использования модулями); направление отправки может быть не указаноfilename: Имя файла для сегментаfile(направление отправки — необязательно, если не указано, адаптер генерирует имя по Спецификации методов отправки §2.1.3 иначе); направление получения должно содержать оригинальное имя файла платформы
{
"type": "text",
"data": {
"text": "Привет, мир"
}
}
4.1.1 Сегмент кнопок/внутренней клавиатуры keyboard (универсальный для платформ)
Кнопки/внутренняя клавиатура есть на нескольких платформах (Telegram / Yunhu / QQBot / Kook / Discord и др.), и являются универсальной концепцией, поэтому они являются стандартным сегментом (без префикса платформы). Адаптер должен преобразовывать стандартный сегмент в платформенный формат; платформенные расширенные сегменты (например, telegram_inline_keyboard) должны сохраняться и передаваться без изменений.
{
"type": "keyboard",
"data": {
"rows": [
[
{"label": "Вариант A", "type": "callback", "data": "vote:A"},
{"label": "Официальный сайт", "type": "link", "data": "https://example.com"}
]
]
}
}
Описание полей:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
rows |
Двумерный массив | Да | Каждый подмассив — строка кнопок |
rows[][].label |
str | Да | Текст кнопки |
rows[][].type |
str | Да | callback (отправка данных при нажатии) / link (переход по URL) |
rows[][].data |
str | Да | Данные для callback (type=callback) или адрес для перехода (type=link) |
rows[][].* |
Any | Нет | Платформенные необязательные поля (например, web_app, menus), адаптер отображает по возможностям или игнорирует |
Примеры преобразования адаптера (полное отображение и стандарт для событий взаимодействия см. в Стандарте кросс-платформенных компонентов взаимодействия):
| Платформа | Стандартный сегмент → Платформенный формат |
|---|---|
| Telegram | inline_keyboard: [{text, callback_data | url}] |
| Yunhu | buttons: [{label, action_type: 2=callback | 1=link, ...}] |
| QQBot | keyboard.content.rows: [{label, type: 2=callback | 0=link, data}] (требуется сообщение в формате markdown) |
| Kook | Модуль action-group в карточке |
| Discord | components: action_row + buttons (custom_id/url) |
4.2 Платформенные расширенные сегменты сообщений
Сегменты сообщений, специфичные для платформы, должны иметь платформенный префикс:
// Yunhu - форма
{"type": "yunhu_form", "data": {"form_id": "123456", "form_name": "Форма регистрации"}}
// Telegram - стикер
{"type": "telegram_sticker", "data": {"file_id": "CAACAgIAAxkBAA...", "emoji": "😂"}}
Требования к расширенным сегментам сообщений:
- Поля внутри
dataне должны иметь префикса:{"type": "yunhu_form", "data": {"form_id": "..."}}, а не{"type": "yunhu_form", "data": {"yunhu_form_id": "..."}} - Обеспечьте механизм понижения: модуль может не распознавать расширенные сегменты сообщений, адаптер должен предоставить текстовое замещение в
alt_message - Документация: каждый расширенный сегмент сообщения должен быть подробно описан в документации адаптера:
type, структураdataи сценарии использования
5. Обработка неизвестных событий
Для неопознанных типов событий следует генерировать событие предупреждения:
{
"id": "1234567893",
"time": 1752241223,
"type": "unknown",
"platform": "yunhu",
"yunhu_raw": {...},
"yunhu_raw_type": "unknown",
"warning": "Неизвестный тип события: special_event",
"alt_message": "Этот тип события не поддерживается системой."
}
6. Стандарты именования расширений
6.1 Именование полей
Правило: {platform}_{field_name}
Платформенный префикс Имя поля Полное имя поля
──────── ─────── ──────────
yunhu command yunhu_command
telegram sticker_file_id telegram_sticker_file_id
onebot11 anonymous onebot11_anonymous
email subject email_subject
Требования:
platformдолжен полностью соответствовать названию платформы, зарегистрированному в адаптере (регистрозависимо)field_nameдолжно использовать стильsnake_case- Запрещено использовать двойное подчёркивание
__в начале (зарезервировано Python) - Запрещено использовать имя, совпадающее со стандартным полем (например,
type,time,messageи т.д.)
6.2 Именование типов сегментов сообщений
Правило: {platform}_{segment_type}
Стандартные типы сегментов сообщений (text, image, audio, video, mention, reply и т.д.) не должны иметь платформенный префикс. Только платформенные специфичные типы сегментов сообщений должны иметь префикс.
6.3 Именование полей исходных данных
Следующие имена полей являются зарезервированными, все адаптеры должны соблюдать:
| Зарезервированное поле | Тип | Описание |
|---|---|---|
{platform}_raw |
any |
Полная копия исходных данных события платформы |
{platform}_raw_type |
string |
Идентификатор типа исходного события платформы |
Требования:
{platform}_rawдолжен быть глубокой копией исходных данных, а не ссылкой{platform}_raw_typeдолжен быть строкой, даже если платформа использует числовой тип, его нужно преобразовать в строку- Эти два поля должны присутствовать во всех событиях (если данные недоступны, то
nullи пустая строка"")
6.4 Примеры платформенных специфичных полей
{
"yunhu_command": {
"name": "Розыгрыш",
"args": "Суперприз"
},
"yunhu_form": {
"form_id": "123456"
},
"telegram_sticker": {
"file_id": "CAACAgIAAxkBAA..."
}
}
6.5 Вложенные расширенные поля
Расширенные поля могут быть простыми значениями, а также вложенными объектами:
{
"telegram_chat": {
"id": 123456,
"type": "supergroup",
"title": "Моя группа"
},
"telegram_forward_from": {
"user_id": "789",
"user_name": "ForwardUser"
}
}
Требования к вложенным полям:
- Ключи верхнего уровня должны иметь платформенный префикс
- Вложенные поля не должны иметь платформенный префикс
- Рекомендуемая глубина вложенности не превышает 3 уровней
6.6 Расширение поля self
Стандартные обязательные поля self (см. §2.1): platform, user_id, а также расширения ErisPulse:
| Поле | Тип | Описание |
|---|---|---|
self.user_name |
string |
Имя бота |
self.avatar |
string |
URL аватара бота |
self.account_id |
string |
Идентификатор аккаунта в режиме множественных аккаунтов |
Отслеживание состояния бота: адаптер сообщает фреймворку о состоянии подключения бота, отправляя событие
type: "meta". Поддерживаемыеdetail_type:connect(онлайн),heartbeat(пинг),disconnect(офлайн). Система автоматически извлекает информацию о боте из поляselfдля отслеживания состояния. Кроме того, полеselfв обычных событиях также автоматически обнаруживается. Подробнее см. в API адаптера - Управление состоянием бота.
7. Расширение типов сессий
ErisPulse расширяет стандартные типы сессий OneBot12 (private, group) следующими типами:
| Тип | Стандарт OneBot12 | Расширение ErisPulse | Описание |
|---|---|---|---|
private |
✅ | — | Личный чат |
group |
✅ | — | Групповой чат |
user |
— | ✅ | Тип пользователя (Telegram и др.) |
channel |
— | ✅ | Канал (трансляционный) |
guild |
— | ✅ | Сервер/сообщество |
thread |
— | ✅ | Тема/подканал |
Расширение типов сессий адаптером:
from ErisPulse.Core.Event.session_type import register_custom_type
# Регистрация в начале работы адаптера
register_custom_type(
receive_type="email", # detail_type для входящих событий
send_type="email", # тип цели при отправке
id_field="email_id", # соответствующее имя поля ID
platform="email" # идентификатор платформы
)
Требования к пользовательским типам:
- Должны быть зарегистрированы в
start()адаптера и отменены вshutdown() receive_typeне должен совпадать со стандартными типамиid_fieldдолжен соответствовать шаблону{цель}_id
Полное определение и сопоставление типов сессий см. в Стандарте типов сессий.
8. Руководство для разработчиков модулей
8.1 Доступ к расширенным полям
from ErisPulse.Core.Event import message
@message()
async def handle_message(event):
# Доступ к стандартным полям
text = event.get_text()
user_id = event.get_user_id()
# Доступ к платформенным расширенным полям - способ 1: прямой get
yunhu_command = event.get("yunhu_command")
# Доступ к платформенным расширенным полям - способ 2: точечный доступ (обёртка Event)
# event.yunhu_command
# Доступ к исходным данным
raw_data = event.get("yunhu_raw")
raw_type = event.get_raw_type()
# Определение платформы
platform = event.get_platform()
if platform == "yunhu":
pass
elif platform == "telegram":
pass
8.2 Обработка расширенных сегментов сообщений
@message()
async def handle_message(event):
message_segments = event.get("message", [])
for segment in message_segments:
seg_type = segment.get("type")
seg_data = segment.get("data", {})
if seg_type == "text":
text = seg_data["text"]
elif seg_type.startswith("yunhu_"):
if seg_type == "yunhu_form":
form_id = seg_data["form_id"]
elif seg_type.startswith("telegram_"):
if seg_type == "telegram_sticker":
file_id = seg_data["file_id"]
8.3 Рекомендуемые практики
- Приоритет стандартных полей: не предполагайте, что расширенные поля всегда существуют
- Определение платформы: используйте
event.get_platform()для определения платформы, а не по наличию расширенных полей - Плавное понижение: при невозможности обработки расширенных сегментов сообщений используйте
alt_messageкак резервный вариант - Не жёстко кодируйте префиксы: используйте переменную
platformдля динамического составления
# ✅ Рекомендуется
platform = event.get_platform()
raw_data = event.get(f"{platform}_raw")
# ❌ Не рекомендуется
raw_data = event.get("yunhu_raw")
8.4 Обработка запросов
Разработчики модулей могут использовать event.approve() и event.reject() для обработки запросов:
from ErisPulse.Core.Event import request
# Автоматическое согласование запроса на добавление в друзья
@request.on_friend_request()
async def handle_friend_request(event):
user_name = event.get_user_nickname() or event.get_user_id()
comment = event.get_comment()
# Согласование запроса
result = await event.approve()
if result.get("status") == "ok":
print(f"Согласован запрос на добавление в друзья от {user_name}")
else:
print(f"Ошибка согласования запроса: {result.get('message')}")
# Запрос на вступление в группу: решение по условиям
@request.on_group_request()
async def handle_group_request(event):
comment = event.get_comment()
# Отклонение запроса
result = await event.reject(comment="Временно не принимаю новые группы")
Прямое управление через адаптер (для сценариев, не связанных с обработчиками событий):
from ErisPulse import adapter
# Прямое управление по request_id
await adapter.myplatform.Request("req_abc123").accept()
await adapter.myplatform.Request("req_abc123").reject()
# Указание аккаунта бота
await adapter.myplatform.Request("req_abc123").Using("bot1").accept()
# С комментарием
await adapter.myplatform.Request("req_abc123").accept(comment="Добро пожаловать")
9. Вывод типа сессии для событий notice / request
9.1 Проблемный фон
Типы detail_type событий notice и request — это семантические подтипы (например, group_member_increase, friend_increase), а не типы сессии (например, group, private).
type detail_type Смысл Тип сессии
──── ─────────── ──── ────────
message group Сообщение в группе group (detail_type — тип сессии)
message private Личное сообщение private (detail_type — тип сессии)
notice group_member_increase Увеличение участников группы group (выводится по group_id)
notice friend_increase Увеличение друзей private (выводится по user_id)
request friend Запрос на добавление в друзья private (выводится по user_id)
request group Запрос на вступление в группу group (detail_type — тип сессии)
9.2 Правила вывода
Порядок вывода infer_receive_type():
- Если
detail_type— известный тип сессии (private/group/channel/guild/thread/user), используйте его - Если
detail_type— пользовательский тип сессии, используйте его - В противном случае (семантические подтипы
notice/request), выводите по ID-полю:- Если есть
group_id→"group" - Если есть
channel_id→"channel" - Если есть
guild_id→"guild" - Если есть
thread_id→"thread" - Если есть
user_id→"private"
- Если есть
9.3 Вывод цели event.reply()
Цель отправки event.reply() в событиях notice/request определяется выводом типа сессии:
- События уведомления о группе (с
group_id) → отправка в группу - События уведомления о друзьях (только
user_id) → отправка в личный чат
from ErisPulse.Core.Event import notice
@notice.on_group_increase()
async def handle_welcome(event):
group_id = event.get("group_id") # "group_789"
user_id = event.get("user_id") # "user_456"
# event.reply() отправляется в группу (group/group_789)
await event.reply("Добро пожаловать в группу!")
# Если нужно уведомить администратора (личный чат), явно укажите цель:
await adapter.Send.To("user", "admin_id").Text(f"Новый участник {user_id} присоединился к {group_id}")
9.4 Рекомендации для разработчиков адаптеров
Убедитесь, что события notice/request содержат правильные ID-поля:
| detail_type | Обязательные ID-поля | Выводимый тип сессии |
|---|---|---|
group_member_increase |
group_id + user_id |
group |
group_member_decrease |
group_id + user_id |
group |
friend_increase |
user_id |
private |
friend_decrease |
user_id |
private |
friend (запрос) |
user_id |
private |
group (запрос) |
group_id |
group |
10. Связанные документы
Для сопоставления при реализации конвертера:
- Стандарт типов сессий — определения и сопоставления типов сессий
- Стандарт ответов API — стандарт форматов ответов API адаптера
Для отправки сообщений в рамках одного адаптера:
- Спецификация методов отправки — именование методов класса Send, стандарт параметров и требования к обратному преобразованию
- Стандарт действий API — единый интерфейс стандартных действий OneBot12
Для изучения известных расширенных событий и сегментов сообщений каждой платформы:
- Документация по особенностям платформ — вы можете посетить этот документ, чтобы узнать особенности каждой платформы, а также известные расширенные события и сегменты сообщений.
发送方法规范
来源:
docs/ru/standards/send-method-spec.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/ru/standards/send-method-spec.md)
Спецификация методов отправки ErisPulse
Tip
Когда стоит читать этот документ: когда вы реализуете класс Send в адаптере — именование методов, поддержку форматов медиапараметров (URL/путь/bytes), обратное преобразование Raw_ob12, возможности объединённой пересылки — сверяйтесь с этим документом. Если это не нужно — пропустите.
Данный документ определяет соглашения об именовании, спецификации параметров и требования к обратному преобразованию для методов отправки класса Send в адаптерах ErisPulse.
0. Соглашения о ключевых словах
Ключевые слова Обязательно (MUST), Рекомендуется (SHOULD), Можно (MAY) в этом документе интерпретируются в следующей семантике (со ссылкой на RFC 2119):
| Ключевое слово | Семантика | Последствия нарушения |
|---|---|---|
| Обязательно (MUST) | Императивное требование; от него зависят поведение фреймворка и кроссплатформенная согласованность | Адаптер считается несоответствующим стандарту, код модулей может не работать |
| Рекомендуется (SHOULD) | Настоятельная рекомендация; следовать, если нет веских причин поступить иначе | При отклонении необходимо указать в документации адаптера причину и альтернативное поведение |
| Можно (MAY) | Необязательный пункт; решается самостоятельно в зависимости от возможностей платформы | Отсутствуют |
1. Стандартное именование методов
Все методы отправки используют PascalCase (стиль ВерхнегоВерблюжьегоРегистра), первая буква заглавная.
1.1 Стандартные методы отправки
| Имя метода | Описание | Тип параметра | Требование к реализации |
|---|---|---|---|
Text |
Отправка текстового сообщения | str |
Обязательно |
Image |
Отправка изображения | str | bytes |
Обязательно (встроено в базовый класс, см. §6.4) |
Voice |
Отправка голосового сообщения | str | bytes |
Обязательно (встроено в базовый класс; при отсутствии поддержки голоса платформой — деградация согласно §2.1.5) |
Video |
Отправка видео | str | bytes |
Обязательно (встроено в базовый класс; при отсутствии поддержки видео платформой — деградация согласно §2.1.5) |
File |
Отправка файла | str | bytes, filename: str | None = None |
Обязательно (встроено в базовый класс) |
At |
Упоминание пользователя/группы | str (user_id) |
Модифицирующий метод, по необходимости |
Face |
Отправка эмодзи | str (emoji) |
Необязательно |
Reply |
Ответ на сообщение | str (message_id) |
Модифицирующий метод, по необходимости |
Forward |
Пересылка сообщения | str (message_id) |
Необязательно |
Markdown |
Отправка сообщения Markdown | str |
Необязательно |
HTML |
Отправка сообщения HTML | str |
Необязательно |
Card |
Отправка карточного сообщения | dict |
Необязательно |
Стандартные методы (
Text/Image/Voice/Video/File) встроены в базовый классSendDSLи по умолчанию делегируютRaw_ob12; адаптер получает сигнатуры типов без повторной реализации. Переопределять отдельные методы следует только тогда, когда платформе требуется особая логика (см. §6.4).
1.2 Модифицирующие методы для цепочек
| Имя метода | Описание | Тип параметра |
|---|---|---|
At |
Упоминание пользователя (можно вызывать несколько раз) | str (user_id) |
AtAll |
Упоминание всех участников | Нет |
Reply |
Ответ на сообщение | str (message_id) |
1.3 Методы протокола
| Имя метода | Описание | Обязателен ли |
|---|---|---|
Raw_ob12 |
Отправка сегментов сообщений в формате OneBot12 | Обязательно |
Raw_ob12 — метод, реализация которого обязательна. Это одна из ключевых обязанностей адаптера: принимать стандартные сегменты сообщений OneBot12 и преобразовывать их в вызовы нативного API платформы. Raw_ob12 — единая точка входа обратного преобразования (OneBot12 → платформа), гарантирующая, что модули могут отправлять сообщения стандартными сегментами, не завися от платформенно-специфичных методов.
Поведение при отсутствии переопределения Raw_ob12: реализация по умолчанию в базовом классе записывает лог уровня error и возвращает стандартный формат ответа об ошибке (status: "failed", retcode: 10002), напоминая разработчику адаптера о необходимости реализовать этот метод.
1.4 Рекомендуемые соглашения об именовании расширений
Если адаптеру требуется поддержка отправки сырых данных в формате, отличном от OneBot12 (например, специфичный для платформы JSON, XML и т.д.), рекомендуется использовать следующие соглашения об именовании:
| Рекомендуемое имя метода | Описание |
|---|---|
Raw_json |
Отправка произвольных данных JSON |
Raw_xml |
Отправка произвольных данных XML |
Примечание: эти методы не являются предоставляемыми базовым классом методами по умолчанию, и их реализация не обязательна. Они служат лишь соглашением об именовании; адаптер может определять их самостоятельно по необходимости. Если адаптер не поддерживает эти форматы, определять их не нужно.
MessageBuilder (конструктор сообщений): ErisPulse предоставляет утилитарный класс MessageBuilder для удобного построения списков сегментов сообщений OneBot12, используемых совместно с Raw_ob12. Подробнее см. Подробно о MessageBuilder.
2. Подробная спецификация параметров
2.1 Протокол отправки медиасообщений (Image / Voice / Video / File)
Этот раздел — единый стандарт протокола отправки медиа: модуль вызывает четыре медиаметода одним и тем же кодом, а адаптер отвечает за преобразование различных форм параметра file в нативное для платформы поведение загрузки/отправки.
2.1.1 Допустимые формы параметра file
| Форма | Пример | Требования к адаптеру |
|---|---|---|
| HTTP(S) URL | https://example.com/image.jpg |
Обязательно принимать |
| Локальный путь к файлу | /path/to/file.jpg, C:\path\to\file.jpg |
Обязательно принимать |
| Бинарные данные | b"\x89PNG..." |
Обязательно принимать |
URI file:// |
file:///path/to/file.jpg |
Рекомендуется принимать (можно преобразовать в локальный путь для обработки) |
| Строка Base64 / Data URI | iVBORw0KGgo=..., data:image/png;base64,... |
Рекомендуется принимать (совместимо с практиками экосистемы OneBot12) |
Адаптер обязан вести себя единообразно на трёх обязательных формах — независимо от того, передаёт модуль URL, путь или bytes, полученное сообщение будет одним и тем же. Если платформа не может напрямую использовать какую-то форму (например, API платформы не поддерживает ссылки на внешние URL), адаптер самостоятельно скачивает/читает и загружает данные; недопустимо требовать от модуля повторить попытку с другой формой.
2.1.2 Порядок определения формы
При реализации обработки медиапараметров адаптер должен определять форму в следующем порядке:
- Тип
bytes→ прямая загрузка - Строка начинается с
http:///https://→ обрабатывать как URL (прямая ссылка или скачивание с последующей загрузкой — по возможностям платформы) - Строка начинается с
file://→ отрезать префикс и обрабатывать как локальный путь - Остальные строки → обрабатывать как локальный путь (если существует — прочитать и загрузить; если нет — вернуть стандартный ответ об ошибке)
def _resolve_media(self, file: "str | bytes") -> bytes:
"""Определение формы и нормализация (пример)"""
if isinstance(file, (bytes, bytearray)):
return bytes(file)
if file.startswith(("http://", "https://")):
return self._download(file) # скачивать, когда платформа не может ссылаться на URL
if file.startswith("file://"):
file = file[len("file://"):]
with open(file, "rb") as f: # локальный путь
return f.read()
2.1.3 Семантика имени файла для File
Сигнатура метода File: File(file, filename=None) (filename — необязательный параметр, встроен в базовый класс).
Порядок определения имени файла (адаптер генерирует его по этой схеме, когда filename не предоставлен явно):
- Явный параметр
filename(наивысший приоритет) - basename из URL (например,
https://host/a/b/report.pdf→report.pdf; необходимо отрезать query string) - basename из локального пути (например,
/tmp/data/backup.zip→backup.zip) - Генерация по умолчанию платформы (например,
file_{timestamp}; следует сохранять реальное расширение — расширение влияет на распознавание типа и поведение предпросмотра на стороне платформы)
Image/Voice/Videoтакже могут приниматьfilename(передаётся черезdata.filenameсегмента сообщения), но кроссплатформенная семантика имени файла гарантируется только дляFile.
2.1.4 Обязанность декларирования ограничений платформы
Ограничения платформ на максимальный размер медиа, форматы (MIME), длительность (аудио/видео) и т.д. различаются. Адаптер должен:
- декларировать в документации адаптера поддерживаемые типы медиа и диапазоны ограничений;
- при превышении лимитов или неподдерживаемом вводе возвращать стандартный ответ об ошибке (
status: "failed";retcode—10002или семантический код ошибки платформы,messageпоясняет причину); недопустимо выбрасывать исключения, прерывая логику модуля.
2.1.5 Лестница деградации возможностей
Когда платформа не поддерживает какой-либо тип медиа, деградация выполняется по следующей лестнице (согласно общему принципу «деградация возможностей без ошибок»):
| Сценарий | Поведение при деградации |
|---|---|
Voice не поддерживает голосовые сообщения |
Следует отправлять как File (или ближайшую платформенную форму); при невозможности выразить — вернуть retcode=10002 |
Video не поддерживает видеосообщения |
То же |
| Тип медиа полностью не поддерживается (нет файловых возможностей) | Вернуть retcode=10002, в message указать неподдерживаемый тип данных |
| Форма не поддерживается (например, невозможно обработать base64) | Вернуть retcode=10002, можно указать в message подсказку модулю использовать URL/bytes |
Запрещённое поведение: молчаливое отбрасывание (без ответа), выбрасывание исключений, требование к модулю писать платформенные ветки обработки.
2.2 Спецификация параметра упоминания пользователя
Метод: At (модифицирующий метод)
Параметр: user_id (str)
Требования:
user_idдолжен быть строковым идентификатором пользователя- Формат
user_idможет отличаться на разных платформах (число, UUID, строка и т.д.) - Адаптер отвечает за преобразование
user_idв платформенно-специфичный формат - Обратите внимание: вызов фактического метода отправки должен находиться на последней позиции
Пример:
# Одиночное упоминание пользователя
Send.To("group", "g123").At("123456").Text("Привет")
# Несколько упоминаний (цепной вызов)
send.To("group", "g123").At("123456").At("789012").Text("Всем привет")
2.3 Спецификация параметра ответа на сообщение
Метод: Reply (модифицирующий метод)
Параметр: message_id (str)
Требования:
message_idдолжен быть строковым идентификатором сообщения- Должен быть ID ранее полученного сообщения
- Некоторые платформы могут не поддерживать функцию ответа; адаптер должен корректно деградировать
Пример:
send.To("group", "g123").Reply("msg_123456").Text("Принято")
2.4 Возможность объединённой пересылки (MergeForward, необязательно)
MergeForward(nodes) — необязательный метод возможности: упаковывает несколько фрагментов содержимого для отправки в форме «объединённой пересылки / свёрнутого форматированного текста»; адаптер определяет оптимальное представление в зависимости от возможностей платформы. Семантика отличается от Forward(from_chat_id, message_id) из §1.1 (пересылка существующего сообщения); оба сосуществуют.
Контекст стандарта: ядро стандарта OneBot12 не содержит сегмента node и API объединённой пересылки — «узлы сообщений» являются концепцией уровня реализации экосистемы OneBot v11 (расширение go-cqhttp); v12 передаёт подобные платформенно-специфические возможности механизму расширений. Реализация в ErisPulse — эквивалент механизма расширений: без добавления новых стандартных сегментов сообщений — через необязательные методы возможностей + обнаружение в рантайме через
list_sends— адаптеры с реализованнымMergeForwardавтоматически получают возможность; вызывающая сторона при отсутствии реализации автоматически откатывается к обычному тексту, модули не требуют никаких изменений.
Сигнатура метода:
def MergeForward(self, nodes: List[Dict]) -> asyncio.Task:
"""
Отправка объединённой пересылки / свёрнутого форматированного текста
:param nodes: массив узлов объединённой пересылки (формат узлов OneBot v11 из расширения go-cqhttp):
[
{
"type": "node",
"data": {
"user_id": "10000", # id отправителя для отображения
"nickname": "MyBot", # ник отправителя для отображения
"content": [ # массив сегментов сообщения, сейчас сегменты text
{"type": "text", "data": {"text": "[1/3] Заголовок\nОсновной текст…"}}
],
},
},
]
:return: asyncio.Task, после await возвращает стандартный формат ответа
"""
Требования к реализации:
- Платформа имеет нативную объединённую пересылку (семейство OneBot11): обязательно вызывать специализированный эндпоинт протокола (
send_group_forward_msg/send_private_forward_msg) — реализации вроде NapCat отклоняют сегменты node, передаваемые черезsend_msg(retcode 1400); при отказе специализированного эндпоинта можно откатиться к прямой отправке сегментов node черезsend_msg - Платформа не имеет нативной пересылки, но имеет форматированный текст (Yunhu / Telegram / Kook / Discord / Matrix и др.): следует рендерить узлы в свёрнутую форму форматированного текста платформы (сворачиваемая HTML-карточка / Card / Embed / список blockquote); справочные стили и список задач по каждому адаптеру см. в
SDK/Adapter/ADAPTER_FORWARD_GUIDE.md - Нет ни того, ни другого: недопустимо реализовывать этот метод (тем более возвращать успех, отправляя словари node как чистый текст) — без реализации
list_sendsне содержитMergeForward, и вызывающая сторона автоматически откатывается к обычному тексту - Семантика сбоя: допустимо вернуть стандартный ответ об ошибке (
status: "failed") или выбросить исключение; вызывающая сторона откатится к отправке чистого текста, содержимое не теряется - Недопустимо внутри
MergeForwardснова вызыватьRaw_ob12для отправки тех же nodes (вRaw_ob12OneBot11 уже встроена автоматическая маршрутизация чистых сегментов node — образуется цикл) - Парсинг исходящих сегментов node (соглашение для всех адаптеров): диспетчеризация сегментов
Raw_ob12каждого адаптера должна регистрировать сегментnode— чистый список node автоматически маршрутизируется в представление объединённой пересылки этого адаптера (на платформах сMergeForwardэто эквивалентно прямому вызову; на платформах без этого метода извлекается текст каждого узла и выводится построчно как есть в формате «ник: текст»); текст сегментов node в смешанном списке включается в основной текст. Любой адаптер, получивший сегмент node, не должен молча его отбрасывать.
Формы реализации на платформах (подробнее в SDK/Adapter/ADAPTER_FORWARD_GUIDE.md):
- Нативные эндпоинты: OneBot11, SnowLuma (трёхуровневый откат: специализированный эндпоинт
send_group/private_forward_msg→upload_forward_msgс получением resid и отправкой сегмента forward → прямая отправка сегментов node; API SnowLuma полностью выровнено с NapCat, адаптер может подключаться напрямую к бэкенду NapCat) - Сворачиваемая HTML-карточка
<details>: Yunhu (протоколы bot/пользователя), Email, Sandbox, Allons(Ideaura) - HTML по белому списку / цитатные блоки: Telegram (
<b>+<blockquote>), Matrix (вариант blockquote, удаление details/инлайн-стилей по белому списку) - Платформенные карточки: Kook (Card header+section), Discord (Embed fields)
- Официальный Markdown-список цитат: QQBot (в официальном md нет синтаксиса сворачивания, серверная валидация отклоняет недопустимые теги с ошибкой 40034011)
- Не реализуется (деградация к выводу сегментов node как есть): OneBot12 (в стандарте нет сегмента node), WeCom, Terminal, Webhook, WechatMp
Пример обнаружения на стороне вызывающего:
sends = adapter.list_sends(platform)
if "MergeForward" in sends:
await adapter.Send.To("group", "123").MergeForward(nodes)
else:
await adapter.Send.To("group", "123").Text(plain_text)
Референсная реализация:
ErisPulse-OneBot11Adapter(специализированный эндпоинт → откат кsend_msg); шаблоны рендеринга и список задач по каждому адаптеру см. вSDK/Adapter/ADAPTER_FORWARD_GUIDE.md.
3. Именование платформенно-специфичных методов
Не рекомендуется напрямую добавлять методы с платформенным префиксом в класс Send. Рекомендуется использовать универсальные имена методов или методы Raw_{протокол}.
Не рекомендуется:
def YunhuForm(self, form_id: str): # ❌ Не рекомендуется
pass
def TelegramSticker(self, sticker_id: str): # ❌ Не рекомендуется
pass
Рекомендуется:
def Form(self, form_id: str): # ✅ Универсальное имя метода
pass
def Sticker(self, sticker_id: str): # ✅ Универсальное имя метода
pass
def Raw_ob12(self, message): # ✅ Отправка в формате OneBot12
pass
Требования к методам расширений:
- имя метода в PascalCase, без платформенного префикса;
- должен возвращать объект
asyncio.Task; - должны предоставляться полные аннотации типов и строки документации;
- дизайн параметров должен по возможности соответствовать стилю стандартных методов.
4. Соглашения об именовании параметров
| Имя параметра | Описание | Тип |
|---|---|---|
text |
Текстовое содержимое | str |
file |
Медиа-содержимое (URL / путь / бинарные данные, см. §2.1.1) | str / bytes |
filename |
Имя файла (необязательно для File, см. §2.1.3) |
str / None |
user_id |
ID пользователя | str / int |
group_id |
ID группы | str / int |
message_id |
ID сообщения | str |
data |
Объект данных (например, данные карточки) | dict |
5. Спецификация возвращаемых значений
- Методы отправки (например,
Text,Image): должны возвращать объектasyncio.Task - Модифицирующие методы (например,
At,Reply,AtAll): должны возвращатьselfдля поддержки цепных вызовов
6. Спецификация обратного преобразования (OneBot12 → платформа)
Адаптер не только должен преобразовывать нативные события платформы в формат OneBot12 (прямое преобразование), но и обязан предоставлять возможность преобразования сегментов сообщений OneBot12 обратно в вызовы нативного API платформы (обратное преобразование). Единая точка входа обратного преобразования — метод Raw_ob12.
6.1 Модель преобразования
Прямое преобразование (направление приёма) Обратное преобразование (направление отправки)
───────────────── ─────────────────
Нативное событие платформы Список сегментов сообщений OneBot12
│ │
▼ ▼
Converter.convert() Send.Raw_ob12()
│ │
▼ ▼
Стандартное событие OneBot12 Вызов нативного API платформы
(содержит {platform}_raw) (возвращает стандартный формат ответа)
Ключевая симметрия: прямое преобразование сохраняет исходные данные в {platform}_raw, обратное преобразование принимает стандартный формат OneBot12 и восстанавливает его в вызовы платформы.
6.2 Спецификация реализации Raw_ob12
Raw_ob12 принимает список стандартных сегментов сообщений OneBot12 и обязан преобразовывать их в вызовы нативного API платформы.
Сигнатура метода:
def Raw_ob12(self, message_segments: List[Dict]) -> asyncio.Task:
"""
Отправка стандартных сегментов сообщений OneBot12
:param message_segments: список сегментов сообщений OneBot12
[
{"type": "text", "data": {"text": "Hello"}},
{"type": "image", "data": {"file": "https://..."}},
{"type": "mention", "data": {"user_id": "123"}},
]
:return: asyncio.Task, после await возвращает стандартный формат ответа
"""
Требования к реализации:
- Должны обрабатываться все стандартные типы сегментов сообщений: как минимум поддержка
text,image,audio,video,file,mention,reply - Должны обрабатываться платформенные сегменты расширений: сегменты типа
{platform}_xxxпреобразовывать в соответствующие нативные вызовы платформы - Должен возвращаться стандартный формат ответа: согласно Стандарту API-ответов
- Неподдерживаемые сегменты должны пропускаться с записью предупреждения; не следует выбрасывать исключение, приводящее к сбою отправки всего сообщения
6.3 Правила преобразования сегментов сообщений
6.3.1 Преобразование стандартных сегментов сообщений
Адаптер обязан реализовать преобразование следующих стандартных сегментов сообщений:
| Сегмент OneBot12 | Требования к преобразованию |
|---|---|
text |
Напрямую использовать data.text |
image |
data.file обрабатывается согласно медиапротоколу §2.1 (три обязательные формы + порядок определения) |
audio |
Та же логика обработки, что и у image |
video |
Та же логика обработки, что и у image |
file |
Та же логика обработки, что и у image; имя файла обрабатывается по порядку определения из §2.1.3 через data.filename |
mention |
Преобразуется в механизм упоминаний платформы (например, entities в Telegram, at_uid в Yunhu) |
reply |
Преобразуется в механизм цитирования/ответа платформы |
face |
Преобразуется в механизм отправки эмодзи платформы; при отсутствии поддержки — пропустить |
location |
Преобразуется в механизм отправки геопозиции платформы; при отсутствии поддержки — пропустить |
6.3.2 Преобразование платформенных сегментов расширений
Для сегментов с платформенным префиксом адаптер должен распознавать и преобразовывать их:
def _convert_ob12_segments(self, segments: List[Dict]) -> Any:
"""Преобразование сегментов сообщений OneBot12 в нативный формат платформы"""
platform_prefix = f"{self._platform_name}_"
for segment in segments:
seg_type = segment["type"]
seg_data = segment["data"]
if seg_type.startswith(platform_prefix):
# Сегмент расширения платформы → нативный вызов платформы
self._handle_platform_segment(seg_type, seg_data)
elif seg_type in self._standard_segment_handlers:
# Стандартный сегмент → эквивалентная операция платформы
self._standard_segment_handlers[seg_type](seg_data)
else:
# Неизвестный сегмент → записать предупреждение и пропустить
logger.warning(f"Неподдерживаемый тип сегмента сообщения: {seg_type}")
6.3.3 Обработка составных сегментов сообщений
Одно сообщение может содержать несколько сегментов; адаптер должен корректно обрабатывать составные сообщения:
# Модуль отправляет сообщение с текстом + изображением + упоминанием пользователя
await send.Raw_ob12([
{"type": "mention", "data": {"user_id": "123"}},
{"type": "text", "data": {"text": "Привет"}},
{"type": "image", "data": {"file": "https://example.com/img.jpg"}}
])
Стратегия обработки:
- Приоритет слиянию: если платформа поддерживает текст, изображения, @ и т.д. в одном сообщении, следует отправлять слитно
- Откат к разделению: если платформа не поддерживает слияние, разделить на несколько сообщений и отправить по порядку
- Сохранение порядка: порядок отправки сегментов должен соответствовать порядку в списке
6.4 Отношение Raw_ob12 к стандартным методам
Стандартные методы отправки адаптера (Text, Image и т.д.) уже встроены в базовый класс SendDSL и по умолчанию делегируются Raw_ob12; подклассу адаптера не нужно реализовывать их повторно:
class Send(SendDSL):
def Raw_ob12(self, message_segments: List[Dict]) -> asyncio.Task:
"""Ключевая реализация: сегменты сообщений OneBot12 → API платформы (обязательна)"""
return asyncio.create_task(self._send_ob12(message_segments))
# Text/Image/Voice/Video/File унаследованы от базового класса, автоматически делегируют Raw_ob12
# Если нужна платформенно-специфичная логика, можно переопределить отдельный метод:
# def Text(self, text: str) -> asyncio.Task:
# return self.Raw_ob12([{"type": "text", "data": {"text": text}}])
Преимущества:
- логика преобразования сосредоточена в одном месте —
Raw_ob12, меньше дублирования кода - поведение стандартных методов и
Raw_ob12полностью совпадает - модуль получает одинаковый результат независимо от того, используется
Text()илиRaw_ob12() - базовый класс предоставляет сигнатуры типов, IDE может дополнять стандартные методы
6.5 Пример реализации
class YunhuSend(SendDSL):
"""Реализация Send для платформы Yunhu"""
def Raw_ob12(self, message_segments: list) -> asyncio.Task:
"""Сегменты сообщений OneBot12 → вызов API Yunhu"""
return asyncio.create_task(self._do_send(message_segments))
async def _do_send(self, segments: list) -> dict:
"""Фактическая логика отправки"""
# 1. Разбор состояния модификаторов
at_users = self._at_users or []
reply_to = self._reply_to
at_all = self._at_all
# 2. Преобразование сегментов сообщений
yunhu_elements = []
for seg in segments:
seg_type = seg["type"]
seg_data = seg["data"]
if seg_type == "text":
yunhu_elements.append({"type": "text", "content": seg_data["text"]})
elif seg_type == "image":
yunhu_elements.append({"type": "image", "url": seg_data["file"]})
elif seg_type == "mention":
at_users.append(seg_data["user_id"])
elif seg_type == "reply":
reply_to = seg_data["message_id"]
elif seg_type == "yunhu_form":
# Сегмент расширения платформы
yunhu_elements.append({"type": "form", "form_id": seg_data["form_id"]})
else:
logger.warning(f"Сегмент сообщения, не поддерживаемый Yunhu: {seg_type}")
# 3. Вызов API Yunhu
response = await self._call_yunhu_api(yunhu_elements, at_users, reply_to, at_all)
# 4. Возврат стандартного формата ответа
return {
"status": "ok" if response["code"] == 0 else "failed",
"retcode": response["code"],
"data": {"message_id": response.get("msg_id", ""), "time": int(time.time())},
"message_id": response.get("msg_id", ""),
"message": "",
"yunhu_raw": response
}
7. Обнаружение методов
Разработчики модулей могут через API запрашивать методы отправки, поддерживаемые адаптером (не следует хардкодить в модулях список методов какой-либо платформы — методы расширений адаптеров эволюционируют вместе с версиями, ориентир — обнаружение в рантайме):
from ErisPulse import adapter
# Перечислить все методы отправки
methods = adapter.list_sends("myplatform")
# ["Batch", "Form", "Image", "Recall", "Sticker", "Text", ...]
# Посмотреть детали метода
info = adapter.send_info("myplatform", "Form")
# {
# "name": "Form",
# "parameters": [{"name": "form_id", "type": "str", ...}],
# "return_type": "Awaitable[Any]",
# "docstring": "Отправка формы Yunhu"
# }
9. Замечания по разработке адаптеров
О том, как правильно переопределять __init__ у BaseAdapter, Send, Request, см. Введение в разработку адаптеров — замечания по __init__.
10. Чек-лист реализации адаптера
Методы отправки
- Стандартные методы (
Text,Imageи т.д.) реализованы - Все возвращаемые значения —
asyncio.Task - Модифицирующие методы (
At,Reply,AtAll) возвращаютself - Методы расширений платформы используют PascalCase, без платформенного префикса
- Все методы имеют полные аннотации типов и строки документации
Протокол отправки медиа
- Все обязательные формы параметра
fileподдерживаются: HTTP(S) URL / локальный путь /bytes(см. §2.1.1) - Порядок определения формы соответствует §2.1.2 (bytes → URL →
file://→ путь) - Порядок определения имени файла
Fileсоответствует §2.1.3 (явныйfilename> basename URL > basename пути > значение по умолчанию платформы) - Ограничения платформы на медиа (размер / MIME / длительность) задекларированы в документации адаптера (§2.1.4)
- Неподдерживаемые типы медиа обрабатываются по лестнице деградации §2.1.5: деградация к близкому типу или возврат
retcode=10002, без исключений и без молчаливого отбрасывания
Объединённая пересылка (необязательно, см. §2.4)
- При наличии у платформы нативной объединённой пересылки
MergeForwardиспользует специализированный эндпоинт протокола, при сбое — откат кsend_msg(семейство OneBot11) - При отсутствии нативной пересылки, но наличии форматированного текста,
MergeForwardрендерится в свёрнутую форму платформы (HTML-карточка / Card / Embed / blockquote) - При отсутствии обоих
MergeForwardне реализуется; не возвращается успех с отправкой словарей node как чистого текста - Внутри
MergeForwardбольше не вызываетсяRaw_ob12для пересылки тех же nodes
Обратное преобразование
-
Raw_ob12реализован (обязательно, пропускать нельзя) -
Raw_ob12обрабатывает все стандартные сегменты сообщений (text,image,audio,video,file,mention,reply) -
Raw_ob12обрабатывает платформенные сегменты расширений (типы{platform}_xxx) - Стандартные методы отправки (
Text,Imageи т.д.) внутренне делегируютRaw_ob12, а не реализуют логику преобразования независимо - Неподдерживаемые сегменты пропускаются с записью предупреждения, без выбрасывания исключений
- Составные сегменты сообщений обрабатываются корректно (слияние или разделение по порядку)
11. MessageBuilder (конструктор сообщений) — что нужно знать стороне адаптера
MessageBuilder — предоставляемый ErisPulse инструмент построения сегментов сообщений OneBot12 для построения сообщений на стороне модулей: цепные вызовы (или статическое быстрое построение) в итоге выдают стандартный список сегментов сообщений [{"type": ..., "data": ...}], который вызывающая сторона передаёт напрямую в Raw_ob12.
Стороне адаптера нужно знать только одно: то, что получает Raw_ob12, — это и есть результат работы этого инструмента — полностью изоморфный рукописному списку сегментов сообщений; обрабатывайте его согласно логике диспетчеризации сегментов из §6.2, без необходимости определять, откуда он — из MessageBuilder или из литерала.
Полное использование (механизм двух режимов, все методы сегментов сообщений, утилитарные методы
copy()/clear()и примеры) см. в Подробно о MessageBuilder; в этом документе повторяться не будем.
12. Связанные документы
Для сопоставления при реализации возвращаемых значений отправки:
- Стандарт API-ответов - стандарт формата API-ответов адаптера
Другие стороны того же адаптера:
- Стандарт преобразования событий - полная спецификация преобразования событий, именования расширений и стандарт сегментов сообщений
- Стандарт типов сессий - определение типов сессий и отношения их отображения
- Спецификация операций запросов - требования к полям событий запросов, HandleRequest DSL и требования к реализации адаптера
API 动作规范
来源:
docs/ru/standards/api-action-spec.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/ru/standards/api-action-spec.md)
Стандарт действий API ErisPulse
Tip
Когда это нужно читать: когда вы реализуете стандартные действия DSL-Api (get_user_info / delete_message и т.д.) в адаптере или в модуле вызываете эти стандартные действия между платформами и хотите убедиться в параметрах и структуре данных, обратитесь к этой статье. Если не нужно, пропустите.
В этом документе определяется единый интерфейс для стандартных действий API OneBot12 в адаптере ErisPulse, чтобы разработчики модулей могли программировать по стандартному интерфейсу, а адаптер отвечал за сопоставление с оригинальными API платформы.
Охват: В стандартных действиях OneBot12,
ApiDSLпредоставляет строго типизированные методы для пользователей / групп / каналов (Guild) / управления сообщениями / мета-обычные интерфейсы (send_message реализуется черезSendDSL.Raw_ob12). Действия с файловыми ресурсами (upload_file / get_file / фрагментация) сохраняются только для понижения уровня, см. §3.5. Расширенные действия платформы вызываются черезApi.call("prefix.action", ...). Параметры и структура возврата действий соответствуют спецификации OneBot12 (в хранилищеonebot/specs/interface/).
1. Предпосылки проектирования
В ErisPulse форматы сообщений (отправка и получение сообщений) и событий полностью соответствуют стандарту OneBot12, но вызов действий API (например, получение информации о пользователе, получение списка групп, отмена сообщения и т.д.) ранее не был унифицирован — разработчикам модулей приходилось писать разные вызовы call_api для каждой платформы.
ApiDSL решает эту проблему, предоставляя строго типизированные стандартные методы действий:
Код модуля (унифицированный для всех платформ) Реализация адаптера (платформа специфична)
───────────────── ──────────────────
adapter.Api.get_user_info("123") → вызов адаптера call_api / переопределение
adapter.Api.get_group_list() → вызов адаптера call_api / переопределение
adapter.Api.delete_message("id") → вызов адаптера call_api / переопределение
2. Параллельная структура DSL на трех уровнях
В адаптере ErisPulse есть три параллельных внутренних класса DSL, каждый со своей задачей:
BaseAdapter
├── Send(SendDSL) ← отправка сообщений (Text/Image/Raw_ob12)
├── Request(RequestDSL) ← обработка действий запроса (accept/reject)
└── Api(ApiDSL) ← стандартные действия API (пользователи/группы/каналы/управление сообщениями/файлами/мета) ★
| DSL | Задача | Стиль методов | Возвращаемое значение |
|---|---|---|---|
Send |
Отправка сообщений | Цепочка + asyncio.Task |
Стандартный ответ |
Request |
Обработка событий запроса | asyncio.Task |
Стандартный ответ |
Api |
Запросы/управление | async методы |
Стандартный ответ |
3. Список стандартных действий
3.1 Действия, связанные с пользователями
| Метод | OB12 действие | Параметры | Возвращаемое значение |
|---|---|---|---|
get_self_info() |
get_self_info |
Нет | user_id, user_name, user_displayname |
get_user_info(user_id) |
get_user_info |
user_id: str |
user_id, user_name, user_displayname, user_remark |
get_friend_list() |
get_friend_list |
Нет | list[get_user_info ответ] |
3.2 Действия, связанные с группами
| Метод | OB12 действие | Параметры | Возвращаемое значение |
|---|---|---|---|
get_group_info(group_id) |
get_group_info |
group_id: str |
group_id, group_name |
get_group_list() |
get_group_list |
Нет | list[get_group_info ответ] |
get_group_member_info(group_id, user_id) |
get_group_member_info |
group_id: str, user_id: str |
user_id, user_name, user_displayname |
get_group_member_list(group_id) |
get_group_member_list |
group_id: str |
list[get_group_member_info ответ] |
set_group_name(group_id, group_name) |
set_group_name |
group_id: str, group_name: str |
Нет |
leave_group(group_id) |
leave_group |
group_id: str |
Нет |
3.3 Управление сообщениями
| Метод | OB12 действие | Параметры | Примечание |
|---|---|---|---|
delete_message(message_id) |
delete_message |
message_id: str |
Отмена/удаление сообщения |
Отправка сообщений (
send_message) обрабатывается черезSendDSLвRaw_ob12, не повторяется вApiDSL.
3.4 Действия, связанные с каналами (Guild)
Система каналов OneBot12 разделена на два уровня: канал (guild) и подканал (channel).
| Метод | OB12 действие | Параметры | Возвращаемое значение |
|---|---|---|---|
get_guild_info(guild_id) |
get_guild_info |
guild_id: str |
guild_id, guild_name |
get_guild_list() |
get_guild_list |
Нет | list[get_guild_info ответ] |
set_guild_name(guild_id, guild_name) |
set_guild_name |
guild_id: str, guild_name: str |
Нет |
get_guild_member_info(guild_id, user_id) |
get_guild_member_info |
guild_id: str, user_id: str |
user_id, user_name, user_displayname |
get_guild_member_list(guild_id) |
get_guild_member_list |
guild_id: str |
list[get_guild_member_info ответ] |
leave_guild(guild_id) |
leave_guild |
guild_id: str |
Нет |
get_channel_info(guild_id, channel_id) |
get_channel_info |
guild_id: str, channel_id: str |
channel_id, channel_name |
get_channel_list(guild_id, *, joined_only) |
get_channel_list |
guild_id: str, joined_only: bool=false |
list[get_channel_info ответ] |
set_channel_name(guild_id, channel_id, channel_name) |
set_channel_name |
guild_id, channel_id, channel_name |
Нет |
get_channel_member_info(guild_id, channel_id, user_id) |
get_channel_member_info |
guild_id, channel_id, user_id |
user_id, user_name, user_displayname |
get_channel_member_list(guild_id, channel_id) |
get_channel_member_list |
guild_id, channel_id |
list[get_channel_member_info ответ] |
leave_channel(guild_id, channel_id) |
leave_channel |
guild_id, channel_id |
Нет |
Система каналов и группы (group) независимы: платформы Discord / QQ каналы / Kook реализуют интерфейс канала, традиционные QQ / WeChat реализуют интерфейс группы, оба могут существовать одновременно или только один.
3.5 Действия с файловыми ресурсами
Warning
Модель файловых ресурсов (двухчастный file_id) в ErisPulse считается "пониженного уровня":
ErisPulse не использует модель "сначала загрузить, получить file_id, затем ссылаться" для отправки файлов — модули отправляют файлы с помощью SendDSL.File(file, filename) (URL / путь / байты отправляются напрямую во время отправки, см. спецификацию метода отправки).
Действия upload_file / get_file / фрагментация зависят от специфичных для платформы возможностей файловых ресурсов file_id, имеют недостаточную универсальность; только когда у адаптера есть такая возможность, они могут быть переданы, встроенные адаптеры не реализуют и не рекомендуют реализовывать; при вызове обычно возвращается retcode=10002.
Если модулю нужно отправлять файлы между платформами, используйте SendDSL.File, не полагайтесь на file_id.
Перспектива: стандартизация модели ресурсов file_id на уровне фреймворка — будущее направление, в текущей версии не предоставляется.
Отправка целого файла (маленький файл):
| Метод | OB12 действие | Параметры | Возвращаемое значение |
|---|---|---|---|
upload_file(*, type, name, ...) |
upload_file |
type, name, url/path/data, headers?, sha256? |
file_id |
get_file(file_id, type) |
get_file |
file_id: str, type: str |
name, url/path/data |
Параметр type метода upload_file:
"url": загрузка по URL (требуется предоставитьurl)"path": загрузка по локальному пути (требуется предоставитьpath)"data": загрузка по двоичным данным (требуется предоставитьdata)
3.5.1 Фрагментированная передача (большие файлы, входит в вышеупомянутый пониженный уровень)
Действия OneBot12 по фрагментации по stage различаются этапами. ApiDSL разделяет этапы одного действия на отдельные методы (offset — смещение байт, data в JSON — Base64); таблица предназначена для справки, адаптер не должен и не должен реализовывать:
Три этапа фрагментированной загрузки: prepare → transfer (циклически по частям) → finish
| Метод | Этап | Параметры | Возвращаемое значение |
|---|---|---|---|
upload_file_fragmented_prepare(name, total_size) |
prepare |
name: str, total_size: int |
file_id (используется во время передачи) |
upload_file_fragmented_transfer(file_id, offset, data) |
transfer |
file_id, offset: int, data: bytes |
Нет |
upload_file_fragmented_finish(file_id, sha256) |
finish |
file_id, sha256: str (проверка целостности файла) |
file_id |
total = os.path.getsize(path)
r = await adapter.Api.upload_file_fragmented_prepare(os.path.basename(path), total)
fid = r["data"]["file_id"]
offset = 0
with open(path, "rb") as f:
while chunk := f.read(65536):
await adapter.Api.upload_file_fragmented_transfer(fid, offset, chunk)
offset += len(chunk)
sha256 = hashlib.sha256(open(path, "rb").read()).hexdigest()
await adapter.Api.upload_file_fragmented_finish(fid, sha256)
Два этапа фрагментированной загрузки: prepare → transfer (циклически получать части)
| Метод | Этап | Параметры | Возвращаемое значение |
|---|---|---|---|
get_file_fragmented_prepare(file_id) |
prepare |
file_id |
name, total_size, sha256 |
get_file_fragmented_transfer(file_id, offset, size) |
transfer |
file_id, offset: int, size: int |
data (байты текущей части) |
3.6 Мета-действия
Мета-действия не связаны с конкретным аккаунтом, не требуют указания Using() для бота.
| Метод | OB12 действие | Параметры | Возвращаемое значение |
|---|---|---|---|
get_latest_events(limit, timeout) |
get_latest_events |
limit: int=0, timeout: int=0 |
Массив объектов событий (без мета-событий) |
get_supported_actions() |
get_supported_actions |
Нет | list[str] поддерживаемые имена действий |
get_status() |
get_status |
Нет | good: bool, bots: list[{self, online, ...}] |
get_version() |
get_version |
Нет | impl, version, onebot_version |
3.7 Общие расширенные действия
| Метод | Описание |
|---|---|
call(action, **params) |
Эвакуационный люк для расширенных действий платформы, соответствует правилу именования расширения OB12 {prefix}.{action} |
4. Способы использования
4.1 Базовый вызов
from ErisPulse import adapter
# Получение информации о пользователе (унифицированный для всех платформ)
result = await adapter.myplatform.Api.get_user_info("123456")
if result["status"] == "ok":
user_name = result["data"]["user_name"]
print(f"Имя пользователя: {user_name}")
# Получение списка групп
result = await adapter.myplatform.Api.get_group_list()
groups = result["data"]
# Отмена сообщения
await adapter.myplatform.Api.delete_message("msg_123456")
4.2 Указание аккаунта бота (режим нескольких учетных записей)
# Выполнение операций с указанным аккаунтом бота
info = await adapter.myplatform.Api.Using("bot1").get_self_info()
4.3 Расширенные действия платформы
# Вызов специфичных для платформы расширенных действий (рекомендуется использовать имя {prefix}.{action})
result = await adapter.telegram.Api.call(
"telegram.send_sticker",
sticker_id="CAACAgIAAxkBAA...",
)
4.4 Использование в обработчике событий
from ErisPulse.Core.Event import message
@message()
async def handle(event):
# Получение подробной информации об отправителе
user_id = event.get_user_id()
platform = event.get_platform()
result = await getattr(adapter, platform).Api.get_user_info(user_id)
if result["status"] == "ok":
user_name = result["data"]["user_name"]
await event.reply(f"Привет, {user_name}!")
5. Реализация адаптера
5.1 Стандартное поведение (без настройки)
Стандартная реализация ApiDSL передает имя стандартного действия как endpoint напрямую в adapter.call_api():
# Стандартная реализация ApiDSL эквивалентна:
async def get_user_info(self, user_id: str) -> dict:
return await self._adapter.call_api("get_user_info", user_id=user_id, account_id=self._account_id)
Сценарий применения: когда базовый API адаптера сам следует стандартному протоколу OneBot12, call_api нативно поддерживает имена стандартных действий (например, напрямую подключается к сервису, соответствующему этому протоколу).
5.2 Переопределение стандартных методов (сопоставление с оригинальными API платформы)
Адаптер может переопределить отдельные стандартные методы, сопоставляя их с оригинальными API платформы:
class MyAdapter(BaseAdapter):
class Api(BaseAdapter.Api):
"""Реализация стандартных действий API для MyPlatform"""
async def get_user_info(self, user_id: str) -> dict:
# Сопоставление с оригинальным API платформы
raw = await self._adapter._request("GET", f"/users/{user_id}")
if raw.get("code") != 0:
# Ошибка на стороне платформы: используйте сегмент 341xx~345xx для трех низших цифр (346xx зарезервирован фреймворком, не используйте)
return self._adapter.make_error(retcode=34400, message="Пользователь не существует")
user = raw["data"]
return self._adapter.make_response(
data={
"user_id": str(user["id"]),
"user_name": user.get("nick", ""),
"user_displayname": user.get("display_name", ""),
"user_remark": user.get("remark", ""),
},
raw=raw,
)
async def get_friend_list(self) -> dict:
raw = await self._adapter._request("GET", "/friends")
friends = [
{
"user_id": str(u["id"]),
"user_name": u.get("nick", ""),
"user_displayname": u.get("display_name", ""),
"user_remark": u.get("remark", ""),
}
for u in raw.get("data", [])
]
return self._adapter.make_response(data=friends, raw=raw)
5.3 Не поддерживаемые действия
Методы, которые адаптер не переопределил, используют стандартную реализацию (передаются в call_api). Если call_api также не поддерживает это действие, следует вернуть стандартный ответ об ошибке:
async def call_api(self, endpoint: str, **params):
if endpoint not in self._supported_endpoints:
return self.make_error(retcode=10002, message=f"Действие не поддерживается: {endpoint}")
# ... вызов API платформы
Разработчики модулей могут определить поддержку по значению retcode в возвращаемом значении:
result = await adapter.myplatform.Api.get_friend_list()
if result["retcode"] == 10002:
print("Эта платформа не поддерживает получение списка друзей")
6. Формат ответа
Все методы ApiDSL возвращают стандартный формат ответа API (см. стандарт ответа API):
{
"status": "ok",
"retcode": 0,
"data": { ... },
"message_id": "",
"message": "",
"myplatform_raw": { ... }
}
Важно: для действий получения информации
message_id— пустая строка (только для действий отправки сообщенийmessage_idзаполняется).
7. Отношения с SendDSL / RequestDSL
| Сценарий | Используемый DSL | Пример |
|---|---|---|
| Отправка сообщений | Send |
adapter.Send.To("group", "123").Text("hi") |
| Подтверждение/отклонение запроса | Request |
adapter.Request("req_id").accept() |
| Получение информации о пользователе/группе | Api |
adapter.Api.get_user_info("123") |
| Отмена сообщения | Api |
adapter.Api.delete_message("msg_id") |
| Выход из группы | Api |
adapter.Api.leave_group("group_id") |
8. Контрольный список реализации адаптера
Стандартные действия
-
call_apiможет обрабатывать имена стандартных действий (или переопределить соответствующий методApiDSL) - Не поддерживаемые действия возвращают
retcode=10002 - Возвращаемое значение соответствует стандартному формату ответа API
- Поле
dataсодержит поля, определенные в стандарте OB12 - Платформа с каналами должна реализовать
get_guild_*/get_channel_*/leave_guild/leave_channel - Рекомендуется реализовать мета-действия (
get_status/get_version/get_supported_actions) - Отправка файлов с помощью
SendDSL.File(прямая передача); действия с файловыми ресурсами (upload_file/get_file/фрагментация) не являются обязательными, реализуются только при наличии у бэкенда возможностейfile_id
Расширенные действия
- Расширенные действия платформы используют именование
{prefix}.{action} - Параметры и ответы расширенных действий по-прежнему соответствуют структуре запроса/ответа OneBot12
9. Связанные документы
Сопутствующие документы при реализации возвратов API DSL:
- Стандарт ответа API - стандартный формат ответа API адаптера
Другие DSL в одном адаптере:
- Спецификация метода отправки - стандарт именования и параметров методов Send
- Спецификация действий запроса - использование DSL Request
Сопутствующие документы для стороны событий:
- Стандарт преобразования событий - стандарт формата событий и сегментов сообщений
API 响应规范
来源:
docs/ru/standards/api-response.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/ru/standards/api-response.md)
Стандарт спецификации возвращаемых значений адаптеров ErisPulse
Tip
Когда читать этот документ: когда вы реализуете возвращаемые значения call_api / Send / Request в адаптере и хотите убедиться, что структура ответа и коды возврата соответствуют стандарту ErisPulse, или вам нужно выяснить значение конкретного retcode — сверяйтесь с этим документом. Если в этом нет необходимости — пропустите.
1. Описание
Откуда взялась эта спецификация?
Для обеспечения единообразия возвращаемых значений интерфейсов отправки на различных платформах и совместимости с OneBot12 адаптеры ErisPulse для формата API-ответов используют стандарт структуры возвращаемых значений отправки сообщений, определённый в OneBot12.
Однако протокол ErisPulse имеет некоторые особые определения:
- Среди базовых полей
message_idявляется обязательным, но в стандарте OneBot12 такого поля нет
- Среди базовых полей
- В возвращаемое содержимое необходимо добавить поле
{platform_name}_rawдля хранения исходных данных ответа
- В возвращаемое содержимое необходимо добавить поле
2. Базовая структура возвращаемого значения
Все ответы на действия должны содержать следующие базовые поля:
| Имя поля | Тип данных | Обязательное | Описание |
|---|---|---|---|
| status | string | Да | Статус выполнения, должен быть "ok" или "failed" |
| retcode | int64 | Да | Код возврата, следует правилам кодов возврата OneBot12 |
| data | any | Да | Данные ответа; при успехе содержит результат запроса, при неудаче — null |
| message_id | string | Да | Идентификатор сообщения; если отсутствует — пустая строка |
| message | string | Да | Сообщение об ошибке; при успехе — пустая строка |
| {platform_name}_raw | any | Нет | Исходные данные ответа |
Необязательные поля:
| Имя поля | Тип данных | Обязательное | Описание |
|---|---|---|---|
| echo | string | Нет | Если запрос содержит поле echo, оно возвращается в неизменном виде |
3. Полная спецификация полей
3.1 Общие поля
Пример успешного ответа
{
"status": "ok",
"retcode": 0,
"data": {
"message_id": "1234",
"time": 1632847927.599013
},
"message_id": "1234",
"message": "",
"echo": "1234",
"telegram_raw": {...}
}
Пример ответа с ошибкой
{
"status": "failed",
"retcode": 10003,
"data": null,
"message_id": "",
"message": "Отсутствует обязательный параметр: user_id",
"echo": "1234",
"telegram_raw": {...}
}
3.2 Спецификация кодов возврата
0 Успех (OK)
- 0: Успех (OK)
1xxxx Ошибка запроса действия (Request Error)
| Код ошибки | Название ошибки | Описание |
|---|---|---|
| 10001 | Bad Request | Недействительный запрос действия |
| 10002 | Unsupported Action | Неподдерживаемый запрос действия |
| 10003 | Bad Param | Недействительные параметры запроса действия |
| 10004 | Unsupported Param | Неподдерживаемые параметры запроса действия |
| 10005 | Unsupported Segment | Неподдерживаемый тип сегмента сообщения |
| 10006 | Bad Segment Data | Недействительные данные сегмента сообщения |
| 10007 | Unsupported Segment Data | Неподдерживаемые данные сегмента сообщения |
| 10101 | Who Am I | Не указан аккаунт бота |
| 10102 | Unknown Self | Неизвестный аккаунт бота |
2xxxx Ошибка обработчика действия (Handler Error)
| Код ошибки | Название ошибки | Описание |
|---|---|---|
| 20001 | Bad Handler | Ошибка реализации обработчика действия |
| 20002 | Internal Handler Error | Исключение во время выполнения обработчика действия |
3xxxx Ошибка выполнения действия (Execution Error)
| Диапазон кодов | Тип ошибки | Описание |
|---|---|---|
| 31xxx | Database Error | Ошибка базы данных |
| 32xxx | Filesystem Error | Ошибка файловой системы |
| 33xxx | Network Error | Сетевая ошибка |
| 34xxx | Platform Error | Ошибка платформы бота |
| 35xxx | Logic Error | Ошибка логики действия |
| 36xxx | I Am Tired | Реализация решила забастовать |
Зарезервированные сегменты ошибок
- 4xxxx, 5xxxx: зарезервированные сегменты, использовать не следует
- 6xxxx–9xxxx: прочие сегменты ошибок, для использования по усмотрению реализации
4. Требования к реализации
- Все ответы должны содержать поля status, retcode, data и message
- Если запрос содержит непустое поле echo, ответ должен содержать поле echo с тем же значением
- Коды возврата должны строго следовать спецификации OneBot12
- Сообщения об ошибках (message) должны быть понятны человеку
5. Расширения спецификации
ErisPulse сделал следующие расширения поверх стандартной структуры возвращаемых значений OneBot12:
5.1 Обязательное поле message_id
В стандарте OneBot12 message_id находится внутри объекта data и не является обязательным. ErisPulse поднял его до обязательного поля верхнего уровня:
- Если
message_idнедоступен, следует установить пустую строку"" message_idприсутствует всегда, модулям не нужно выполнять проверку на null
5.2 Поле исходного ответа {platform}_raw
Возвращаемое значение должно содержать поле {platform}_raw, в котором хранится полная копия исходных данных ответа платформы:
{
"status": "ok",
"retcode": 0,
"data": {"message_id": "1234", "time": 1632847927},
"message_id": "1234",
"message": "",
"telegram_raw": {
"ok": true,
"result": {"message_id": 1234, "date": 1632847927, ...}
}
}
Требования:
{platform}_rawдолжен быть глубокой копией исходного ответа, а не ссылкойplatformдолжен полностью совпадать с именем платформы, указанным при регистрации адаптера (с учётом регистра)- Информация об ошибках в исходном ответе также должна сохраняться для удобства отладки
5.3 Расширенные коды возврата фреймворка (пользовательские три младших разряда сегмента ошибок платформы 34xxx)
Спецификация OneBot12 позволяет реализациям определять собственные значения трёх младших разрядов 3xxxx. Семантика 34xxx — Platform Error
(ошибка платформы бота, например сбой из-за ограничений платформы). Внутри 34xxx используется послойно по зонам ответственности:
| Сегмент (младшие три разряда) | Принадлежность | Назначение |
|---|---|---|
340xx |
Реализация адаптера | Семейство операций запросов (Request Not Found / Already Handled / Not Supported / Permission Denied, см. request-action-spec §7) |
341xx–345xx |
Реализация адаптера | Ошибки на стороне платформы: права доступа / антифрод / ограничения аккаунта и т. п. (младшие три разряда определяются реализацией, исходная ошибка помещается в {platform}_raw) |
346xx |
Фреймворк ErisPulse (зарезервировано) | Перехваты самим фреймворком и общие сбои; адаптерам/модулям не занимать |
347xx–349xx |
Реализация адаптера | Прочие ошибки выполнения на стороне платформы |
Коды 346xx, используемые в настоящее время фреймворком ErisPulse:
| Код ошибки | Название ошибки | Описание |
|---|---|---|
| 34600 | SDK Failure | Общий сбой фреймворка (код возврата по умолчанию для make_error()) |
| 34601 | Action Denied | Исходящее действие отключено скоупом (scope.actions); вызов не инициируется, сразу возвращается этот ответ |
Разграничение ответственности:
34601— это перехват фреймворком до вызова (модуль вообще не имеет права инициировать действие); коды платформы34004/34xxx— это действие отправлено, но отклонено платформой (например, у бота нет прав, сработал антифрод). При диагностике проблем с правами модули должны проверять оба случая: сначала34601(свой модуль отключён скоупом), затем34xxx(ограничения на стороне платформы).
Структура возврата — стандартный ответ с ошибкой из §2:
{
"status": "failed",
"retcode": 34601,
"data": null,
"message_id": "",
"message": "action 'send' denied by scope.actions"
}
5.4 Контрольный список для реализации адаптера
- Содержит поля
status,retcode,data,message_id,message - Коды возврата следуют спецификации OneBot12 (подробнее см. §3.2)
-
message_idприсутствует всегда (пустая строка, если недоступен) -
{platform}_rawсодержит исходные данные ответа платформы
6. Примечания
- Для кодов ошибок 3xxxx три младших разряда могут определяться реализацией самостоятельно
- Избегайте использования зарезервированных сегментов ошибок (4xxxx, 5xxxx)
34600/34601зарезервированы фреймворком ErisPulse (см. §5.3), адаптерам/модулям следует их не использовать- Сообщения об ошибках должны быть краткими и понятными для удобства отладки
Что дальше
- Справочные материалы при реализации возвращаемых значений каждого DSL: Стандарт API-действий (поля data стандартных действий), Спецификация операций запросов (расширения 34001–34004 для операций запросов), Спецификация методов отправки (семантика деградации при сбое отправки)
- Диагностика сбоев, связанных с правами: сначала проверьте
34601(модуль отключён скоупом, фреймворк перехватывает до вызова, см. §5.3), затем34xxx(действие отправлено, но отклонено на стороне платформы)
请求动作规范
来源:
docs/ru/standards/request-action-spec.md(https://github.com/ErisPulse/ErisPulse/blob/main/docs/ru/standards/request-action-spec.md)
Стандарты операций запросов ErisPulse
Tip
Когда читать эту статью: Когда ваш адаптер должен обрабатывать запросы от друзей/приглашения в группы (преобразование событий запросов, реализация accept/reject), или когда модуль использует event.approve() / event.reject() для одобрения запросов, обратитесь к данной статье. Если не нужно, пропустите.
Данный документ определяет стандартизированные стандарты операций запросов в адаптере ErisPulse, включая требования к полям событий запроса, использование DSL запросов и требования к реализации адаптера.
1. Обзор
Событие запроса (type: "request") — это специальный тип события, определенный в OneBot12, представляющий запрос, требующий принятия решения ботом (например, запрос на добавление в друзья, приглашение в группу и т.д.).
В отличие от событий сообщений, события запроса требуют двустороннего взаимодействия:
- Получение: адаптер преобразует оригинальные запросы платформы в стандартные события запроса
- Ответ: модуль выполняет операции с помощью DSL запроса или
Event.approve()/Event.reject()
Оригинальное событие запроса платформы
│
▼
Converter.convert() ← Реализация адаптера (прямое преобразование)
│
▼
Стандартное событие запроса (с request_id)
│
├─→ Обработчик модуля @request.on_friend_request()
│ │
│ ├─→ event.approve() ← Одобрение запроса
│ └─→ event.reject() ← Отклонение запроса
│ │
│ ▼
│ adapter.Request(request_id).accept()
│ │
│ ▼
│ BaseAdapter.Request.accept() ← Переопределение адаптером
│ │
│ ▼
│ Вызов API платформы
│
└─→ Или напрямую через операции адаптера
await adapter.Request("req_id").accept()
2. Требования к полям события запроса
2.1 Стандартные поля
Событие запроса должно содержать обязательные поля стандарта OneBot12, а также следующие поля:
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
request_id |
string | Рекомендуется | Идентификатор запроса, используется для одобрения/отклонения |
user_id |
string | Да | ID пользователя, отправившего запрос |
user_nickname |
string | Нет | Никнейм пользователя, отправившего запрос |
comment |
string | Нет | Комментарий к запросу |
2.2 Поле request_id
request_id — это ключевой идентификатор запроса:
- Назначение: идентифицирует запрос, который можно обработать, используется в DSL запроса
- Правила генерации:
- Предпочтительно использовать оригинальный идентификатор запроса платформы (например, поле
flagOneBot11,chat_invite_linkTelegram и т.д.) - Если платформа не предоставляет оригинального идентификатора запроса, адаптер должен сгенерировать уникальный идентификатор (рекомендуемый формат:
{platform}_{timestamp}_{user_id})
- Предпочтительно использовать оригинальный идентификатор запроса платформы (например, поле
- Уникальность: должен быть уникален в пределах одной платформы
- Поведение при отсутствии: если
request_idотсутствует,event.approve()/event.reject()выбросятValueError
2.3 Пример события запроса
{
"id": "evt_123456",
"time": 1752241225,
"type": "request",
"detail_type": "friend",
"platform": "onebot11",
"self": {
"platform": "onebot11",
"user_id": "bot_123"
},
"user_id": "user_456",
"user_nickname": "YingXinche",
"comment": "Пожалуйста, добавьте в друзья",
"request_id": "flag_abc123",
"onebot11_raw": {...},
"onebot11_raw_type": "request"
}
3. DSL запросов
3.1 Цепочечные вызовы
Request предоставляет интерфейс цепочечных вызовов, аналогичный Send:
# Основное использование
await adapter.Request("req_id").accept()
await adapter.Request("req_id").reject()
# Указание учетной записи бота
await adapter.Request("req_id").Using("bot1").accept()
# С комментарием (через kwargs)
await adapter.Request("req_id").accept(comment="Добро пожаловать")
await adapter.Request("req_id").reject(comment="Пока не добавляю в друзья")
# Комбинированное использование
await adapter.Request("req_id").Using("bot1").accept(comment="Добро пожаловать")
3.2 Список методов
| Метод | Описание | Возвращаемое значение |
|---|---|---|
Using(account_id) |
Указание учетной записи бота для выполнения операции | RequestDSL (поддерживает цепочечные вызовы) |
accept(**kwargs) |
Одобрение запроса | asyncio.Task (await возвращает стандартный ответ) |
reject(**kwargs) |
Отклонение запроса | asyncio.Task (await возвращает стандартный ответ) |
3.3 Формат возвращаемых значений
Операция возвращает стандартный формат ответа API:
Успешно:
{
"status": "ok",
"retcode": 0,
"data": null,
"message_id": "",
"message": ""
}
Неудачно:
{
"status": "failed",
"retcode": 34001,
"data": null,
"message_id": "",
"message": "Запрос просрочен или не существует"
}
Не реализовано (адаптер не переопределил accept/reject):
{
"status": "failed",
"retcode": 10002,
"data": null,
"message_id": "",
"message": "Платформа MyAdapter не реализовала операции запроса (accept)"
}
4. Удобные методы Event
Обёртка Event предоставляет удобные методы, подходящие для использования в обработчиках событий запросов:
from ErisPulse.Core.Event import request
@request.on_friend_request()
async def handle_friend_request(event):
# Проверка идентификатора запроса
request_id = event.get_request_id()
if not request_id:
print("Предупреждение: событие запроса не содержит request_id")
return
# Одобрение запроса
result = await event.approve()
# Или отклонение запроса
# result = await event.reject(comment="Пока не добавляю в друзья")
# Проверка результата
if result.get("status") == "ok":
print("Операция выполнена успешно")
else:
print(f"Операция не удалась: {result.get('message')}")
4.1 Список методов Event
| Метод | Описание | Возвращаемое значение |
|---|---|---|
get_request_id() |
Получение идентификатора запроса | str |
approve(comment=None) |
Одобрение текущего события запроса | Стандартный формат ответа |
reject(comment=None) |
Отклонение текущего события запроса | Стандартный формат ответа |
5. Требования к реализации адаптера
5.1 Требования к конвертеру
Конвертер адаптера при преобразовании событий запроса должен правильно устанавливать поле request_id:
def convert_request_event(self, raw_event: dict) -> dict:
"""Преобразование оригинального события запроса платформы"""
return {
"id": self._generate_event_id(raw_event),
"time": int(time.time()),
"type": "request",
"detail_type": self._map_request_type(raw_event), # "friend" или "group"
"platform": self._platform_name,
"self": {
"platform": self._platform_name,
"user_id": str(self._bot_id),
},
"user_id": str(raw_event.get("user_id", "")),
"user_nickname": raw_event.get("nickname", ""),
"comment": raw_event.get("message", ""),
"request_id": self._extract_request_id(raw_event), # ← Ключевое поле
f"{self._platform_name}_raw": raw_event,
f"{self._platform_name}_raw_type": raw_event.get("type", ""),
}
def _extract_request_id(self, raw_event: dict) -> str:
"""
Извлечение идентификатора запроса из оригинального события платформы
Предпочтительно использовать оригинальный идентификатор платформы, если нет, генерируем уникальный ID
"""
# Предпочтительно использовать оригинальный идентификатор платформы
if flag := raw_event.get("flag"):
return str(flag)
if request_key := raw_event.get("request_key"):
return str(request_key)
# Резервное: генерируем уникальный ID
import hashlib
raw = f"{self._platform_name}_{raw_event.get('user_id')}_{raw_event.get('timestamp')}"
return hashlib.md5(raw.encode()).hexdigest()
5.2 Реализация внутреннего класса Request
Адаптер переопределяет accept и reject в внутреннем классе Request:
from ErisPulse.Core import BaseAdapter, RequestDSL
class MyAdapter(BaseAdapter):
class Request(RequestDSL):
"""Реализация операций запроса для MyPlatform"""
def accept(self, **kwargs):
"""
Одобрение запроса
:param kwargs: Дополнительные параметры, например comment="Комментарий"
:return: asyncio.Task
"""
async def _do():
try:
result = await self._adapter.call_api(
endpoint="/set_request",
request_id=self._request_id,
approve=True,
**kwargs,
)
return {
"status": "ok" if result.get("code") == 0 else "failed",
"retcode": result.get("code", 0),
"data": None,
"message_id": "",
"message": result.get("message", ""),
}
except Exception as e:
return {
"status": "failed",
"retcode": 34001,
"data": None,
"message_id": "",
"message": f"Операция запроса не удалась: {e}",
}
return self._create_task(_do())
def reject(self, **kwargs):
"""Отклонение запроса"""
async def _do():
try:
result = await self._adapter.call_api(
endpoint="/set_request",
request_id=self._request_id,
approve=False,
**kwargs,
)
return {
"status": "ok" if result.get("code") == 0 else "failed",
"retcode": result.get("code", 0),
"data": None,
"message_id": "",
"message": result.get("message", ""),
}
except Exception as e:
return {
"status": "failed",
"retcode": 34001,
"data": None,
"message_id": "",
"message": f"Операция запроса не удалась: {e}",
}
return self._create_task(_do())
5.3 Платформа не поддерживает операции запроса
Если платформа не поддерживает запросы на добавление в друзья/приглашения в группы (например, некоторые платформы обрабатывают запросы автоматически), адаптер может:
- Не переопределять внутренний класс
Request: использовать реализацию по умолчанию базового класса, вызовaccept()/reject()вернётretcode=10002 - Не генерировать
request_idпри преобразовании: не генерироватьrequest_id, чтобыevent.approve()выбросилValueError - Записывать логи: в
accept/rejectзаписывать предупреждения и возвращать соответствующий код ошибки
5.4 Итог: Send и Request параллельны
Адаптер имеет два параллельных внутренних класса DSL, каждый с собственной функцией:
BaseAdapter
├── Send(SendDSL) ← Отправка сообщений
│ ├── Raw_ob12() ← Обязательно реализовать
│ ├── Text() ← Рекомендуется реализовать
│ └── Image() ← Реализовать по необходимости
│
└── Request(RequestDSL) ← Операции запроса
├── accept() ← Реализовать по необходимости
└── reject() ← Реализовать по необходимости
5.5 Примечания к __init__ адаптера
При переопределении __init__ внутреннего класса Request необходимо передавать параметры и вызывать super().__init__(), подробнее см. Введение в разработку адаптеров - Примечания к __init__ (Request аналогично, параметры: adapter, request_id, account_id).
6. Проверочный список реализации адаптера
Основные требования
- Если переопределяется
__init__, вызванsuper().__init__()(убедитесь, что инициализированы фабрики Send / Request)
Преобразование запроса
- Событие запроса содержит поле
request_id(рекомендуется) -
detail_typeкорректно отображается в"friend"или"group" - Сохранены оригинальные данные платформы в поле
{platform}_raw - Правила генерации
request_idописаны в документации
Операции запроса
- Внутренний класс
Requestреализован (если платформа поддерживает операции запроса) - Метод
accept()реализован - Метод
reject()реализован - Операции возвращают стандартный формат ответа API
- Операции, которые платформа не поддерживает, возвращают
retcode=10002 - Ошибки сети возвращают
retcode=33xxx(следуя стандарту ответов API)
7. Расширение кодов ошибок
Рекомендуемые коды ошибок адаптера/платформы, связанные с операциями запроса (следуя стандарту ответов API §3.2, в диапазоне 34xxx с самыми низкими тремя цифрами для пользовательских определений):
| Код ошибки | Название ошибки | Описание |
|---|---|---|
| 34001 | Request Not Found | Запрос не существует или просрочен |
| 34002 | Request Already Handled | Запрос уже обработан |
| 34003 | Request Not Supported | Платформа не поддерживает данную операцию запроса |
| 34004 | Permission Denied | Бот не имеет прав обрабатывать этот запрос (возвращается платформой) |
Граница с кодами фреймворка: вышеуказанные
340xx— это ошибки, возвращаемые платформой/адаптером при обработке запроса; фреймворк ErisPulse, отключая действиеrequestдля модуля вscope.actions, до вызова адаптера напрямую возвращает34601(Action Denied, см. стандарт ответов API §5.3), эти два типа ошибок не заменяют друг друга: сначала проходит фреймворк34601, затем платформа340xx.
8. Связанные документы
Документы, сопутствующие реализации операций запроса:
- Стандарт преобразования событий - Полный стандарт преобразования событий (включая требования к полю
request_id) - Стандарт ответов API - Стандарт формата ответов API адаптера (возвращаемые значения операций и коды ошибок)
Другие DSL в одном адаптере:
- Спецификация методов отправки - Стандарт именования и параметров методов класса Send
- Стандарт типов сессий - Определение и сопоставление типов сессий