简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

Спецификация методов отправки 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 адаптер должен следовать следующему порядку:

  1. bytes — загрузить напрямую
  2. Строка начинается с http:// / https:// — обработать как URL (использовать напрямую или загрузить, в зависимости от возможностей платформы)
  3. Строка начинается с file:// — удалить префикс и обработать как локальный путь
  4. Остальные строки — обработать как локальный путь (если существует, прочитать и загрузить; если нет, вернуть стандартный формат ошибки)
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 не указан):

  1. Явный параметр filename (наивысший приоритет)
  2. basename из URL (например, https://host/a/b/report.pdf → report.pdf, с удалением query string)
  3. basename из локального пути (например, /tmp/data/backup.zip → backup.zip)
  4. Генерация платформой (например, file_{timestamp}; должно сохранять расширение — оно влияет на тип и поведение предпросмотра)

Image / Voice / Video также могут принимать filename (через data.filename), но только File гарантирует кроссплатформенную семантику имени файла.

2.1.4 Объявление ограничений платформы

Разные платформы имеют ограничения на размер, формат (MIME), длительность (аудио/видео) и т.д. Адаптер должен:

2.1.5 Ступенчатое понижение возможностей

Если платформа не поддерживает определенный тип медиа, следует использовать следующую иерархию понижения (в соответствии с принципом "понижение возможностей не должно вызывать ошибку"):

Сценарий Поведение при понижении
Voice не поддерживает голосовые сообщения Должен отправлять как File (или ближайшую платформенную форму); если невозможно — вернуть retcode=10002
Video не поддерживает видео То же
Тип медиа полностью не поддерживается (нет поддержки файлов) Вернуть retcode=10002, указав в message не поддерживаемый тип
Форма не поддерживается (например, не может обрабатывать base64) Вернуть retcode=10002, можно в message указать, что модуль может использовать URL/bytes

Запрещенные действия: молчаливое отбрасывание (без возврата), выброс исключения, требование от модуля обработки платформы.

2.2 Спецификация параметра @пользователя

Метод: At (модификатор)

Параметр: user_id (str)

Требования:

Пример:

# Одно упоминание пользователя
Send.To("group", "g123").At("123456").Text("Привет")

# Несколько упоминаний пользователя (цепочка вызовов)
send.To("group", "g123").At("123456").At("789012").Text("Всем привет")

2.3 Спецификация параметра ответа на сообщение

Метод: Reply (модификатор)

Параметр: message_id (str)

Требования:

Пример:

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

Требования к расширению методов:

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. Спецификация возвращаемых значений


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 вернет стандартный формат ответа
    """

Требования реализации:

  1. Должен обрабатывать все стандартные типы сообщений: поддерживает text, image, audio, video, file, mention, reply
  2. Должен обрабатывать расширенные сообщения платформы: для сообщений с префиксом {platform}_xxx — преобразует в соответствующие вызовы платформы
  3. Должен возвращать стандартный формат ответа: в соответствии с спецификацией ответа API
  4. Неподдерживаемые сообщения должны пропускаться и записываться в лог, не должны вызывать исключение, приводящее к сбою всей отправки

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}}])

Преимущества:

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. Чек-лист реализации адаптера

Методы отправки

Протокол отправки медиа

Обратное преобразование


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. Связанные документы