Спецификация методов отправки ErisPulse
Документ определяет стандартные именования, параметры и требования по обратному преобразованию для методов отправки в адаптере ErisPulse.
0. Определение ключевых терминов
В данном документе должно (MUST), должен (SHOULD), может (MAY) трактоваться следующим образом (см. RFC 2119):
| Ключевое слово | Значение | Последствия нарушения |
|---|---|---|
| Должно | Обязательное требование, от которого зависит поведение фреймворка и согласованность между платформами | Адаптер считается не соответствующим стандарту, код модуля может не работать |
| Должен | Сильно рекомендуется; если есть веские причины, следует описывать отклонения в документации адаптера | При отклонении необходимо объяснить причины и альтернативное поведение в документации адаптера |
| Может | Необязательный выбор; следует определять в зависимости от возможностей платформы | Нет |
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: базовый класс по умолчанию записывает ошибку и возвращает стандартный формат ответа (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 не поддерживает ссылки), адаптер должен самостоятельно загрузить/прочитать и загрузить, не требуя от модуля повторной попытки с другой формой.
2.1.2 Порядок определения формы
При реализации обработки параметра file адаптер должен следовать следующему порядку:
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) # Если платформа не поддерживает ссылки, загрузить
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должен быть строковым идентификатором сообщения- Должен быть идентификатором ранее полученного сообщения
- Некоторые платформы могут не поддерживать функцию ответа, адаптер должен корректно обрабатывать это
Пример:
send.To("group", "g123").Reply("msg_123456").Text("Получено")
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 |
Идентификатор пользователя | str / int |
group_id |
Идентификатор группы | str / int |
message_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 стандарта (с {platform}_raw) Вызовы 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 обрабатывается по протоколу медиа (три обязательные формы + порядок определения) |
audio |
Аналогично image |
video |
Аналогично image |
file |
Аналогично image; имя файла по порядку вывода 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. Примечания для разработки адаптера
О том, как правильно переопределять BaseAdapter、Send、Request в __init__, см. Руководство по разработке адаптера - Примечания по __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, без исключений и молчаливого отбрасывания
Обратное преобразование
-
Raw_ob12реализован (обязательно, нельзя пропускать) -
Raw_ob12обрабатывает все стандартные типы сообщений (text,image,audio,video,file,mention,reply) -
Raw_ob12обрабатывает расширенные сообщения платформы ({platform}_xxxтипы) - Стандартные методы (
Text,Imageи т.д.) делегируют вызовRaw_ob12, а не реализуют собственную логику преобразования - Неподдерживаемые сообщения пропускаются и записываются в лог, без выброса исключений
- Составные сообщения обрабатываются корректно (объединение или последовательная отправка)
11. MessageBuilder
MessageBuilder — утилита для построения OneBot12-сообщений, поставляемая ErisPulse, совместимая с Raw_ob12.
11.1 Импорт
from ErisPulse.Core import MessageBuilder
# или
from ErisPulse.Core.Event import MessageBuilder
11.2 Цепочечное построение
# Построение сообщения с текстом, изображением и упоминанием
segments = (
MessageBuilder()
.mention("123456")
.text("Привет, посмотри на эту картинку")
.image("https://example.com/img.jpg")
.reply("msg_789")
.build()
)
# Отправка
await adapter.Send.To("group", "456").Raw_ob12(segments)
11.3 Быстрое построение одного сегмента
# Быстрое построение одного сегмента (возвращает list[dict], можно передать в Raw_ob12)
await adapter.Send.To("user", "123").Raw_ob12(MessageBuilder.text("Hello"))
await adapter.Send.To("group", "456").Raw_ob12(MessageBuilder.image("https://..."))
await adapter.Send.To("group", "456").Raw_ob12(MessageBuilder.mention("123"))
await adapter.Send.To("group", "456").Raw_ob12(MessageBuilder.reply("msg_id"))
await adapter.Send.To("group", "456").Raw_ob12(MessageBuilder.at_all())
11.4 Использование вместе с Event.reply_ob12
from ErisPulse.Core import MessageBuilder
@message()
async def handle(event: Event):
await event.reply_ob12(
MessageBuilder()
.mention(event.get_user_id())
.text("Получено твое сообщение")
.build()
)
11.5 Поддерживаемые методы сегментов
| Метод | Описание | Параметры data |
|---|---|---|
text(text) |
Текст | text |
image(file) |
Изображение | file |
audio(file) |
Аудио | file |
video(file) |
Видео | file |
file(file, filename=None) |
Файл | file, filename(необязателен) |
mention(user_id, user_name=None) |
Упоминание | user_id, user_name(необязателен) |
at(user_id, user_name=None) |
Упоминание (mention синоним) |
То же, что и mention |
reply(message_id) |
Ответ | message_id |
at_all() |
Упоминание всех | {} |
custom(type, data) |
Пользовательский/расширенный | Пользовательские |
11.6 Вспомогательные методы
builder = MessageBuilder().text("Базовое содержимое")
# Копирование (глубокая копия)
msg1 = builder.copy().image("img1").build()
msg2 = builder.copy().image("img2").build()
# Очистка
builder.clear().text("Новое содержимое").build()
# Проверка на пустоту
if builder:
print(f"Содержит {len(builder)} сегментов")
12. Связанные документы
- Спецификация преобразования событий — полная спецификация преобразования событий, расширений идентификаторов и стандартов сегментов
- Спецификация формата ответа API — стандартный формат ответа API адаптера
- Спецификация типов сессий — определение и сопоставление типов сессий
- Спецификация действий запроса — требования к полям запроса, DSL HandleRequest и требования к реализации адаптера