ErisPulse Send Method Specification
This document defines the naming conventions, parameter specifications, and reverse conversion requirements for the Send class methods in the ErisPulse adapter.
0. Keyword Conventions
The keywords MUST, SHOULD, and MAY in this document are interpreted as follows (referencing RFC 2119):
| Keyword | Meaning | Consequence of Violation |
|---|---|---|
| MUST | Mandatory requirement; framework behavior or cross-platform consistency depends on it | Adapter is considered non-compliant; module code may not work |
| SHOULD | Strongly recommended; unless there is sufficient reason, follow it | Deviation must be explained in adapter documentation with reasons and alternative behavior |
| MAY | Optional; decide based on platform capability | None |
1. Standard Method Naming
All send methods use PascalCase naming, with the first letter capitalized.
1.1 Standard Send Methods
| Method Name | Description | Parameter Type | Implementation Requirement |
|---|---|---|---|
Text |
Send text message | str |
MUST |
Image |
Send image | str | bytes |
MUST (built-in in base class, see §6.4) |
Voice |
Send voice | str | bytes |
MUST (built-in in base class; downgrade per §2.1.5 if platform does not support voice) |
Video |
Send video | str | bytes |
MUST (built-in in base class; downgrade per §2.1.5 if platform does not support video) |
File |
Send file | str | bytes, filename: str | None = None |
MUST (built-in in base class) |
At |
@ User/Group | str (user_id) |
Modifier method, optional |
Face |
Send emoji | str (emoji) |
MAY |
Reply |
Reply to message | str (message_id) |
Modifier method, optional |
Forward |
Forward message | str (message_id) |
MAY |
Markdown |
Send Markdown message | str |
MAY |
HTML |
Send HTML message | str |
MAY |
Card |
Send card message | dict |
MAY |
Standard methods (
Text/Image/Voice/Video/File) are built-in in the base classSendDSLand default to delegating toRaw_ob12. Adapters do not need to re-implement these methods to obtain type signatures; only override individual methods when platform-specific logic is required (see §6.4).
1.2 Chained Modifier Methods
| Method Name | Description | Parameter Type |
|---|---|---|
At |
@ User (can be called multiple times) | str (user_id) |
AtAll |
@ All Members | None |
Reply |
Reply to message | str (message_id) |
1.3 Protocol Methods
| Method Name | Description | Required? |
|---|---|---|
Raw_ob12 |
Send OneBot12 formatted message segment | MUST |
Raw_ob12 is a required method. This is one of the adapter's core responsibilities: receiving OneBot12 standard message segments and converting them into native platform API calls. Raw_ob12 serves as the unified entry point for reverse conversion (OneBot12 → Platform), ensuring modules can send messages directly using standard message segments without relying on platform-specific methods.
Behavior when Raw_ob12 is not overridden: The default base class implementation logs an error-level message and returns a standard error response format (status: "failed", retcode: 10002), prompting adapter developers to implement this method.
1.4 Recommended Extension Naming Convention
If adapters need to support sending non-OneBot12 formatted raw data (such as platform-specific JSON, XML, etc.), the following naming convention is recommended:
| Recommended Method Name | Description |
|---|---|
Raw_json |
Send arbitrary JSON data |
Raw_xml |
Send arbitrary XML data |
Note: These methods are not provided by the base class, nor are they mandatory. They are only recommended naming conventions, and adapters can define them as needed. If an adapter does not support these formats, there is no need to define them.
Message Builder (MessageBuilder): ErisPulse provides the MessageBuilder utility class for convenient construction of OneBot12 message segment lists, used in conjunction with Raw_ob12. See the MessageBuilder section.
2. Detailed Parameter Specification
2.1 Media Message Sending Protocol (Image / Voice / Video / File)
This section defines the unified protocol standard for media sending: modules use the same code to call the four media methods, and adapters are responsible for converting the various forms of the file parameter into native platform upload/send behavior.
2.1.1 Valid Forms of the file Parameter
| Form | Example | Adapter Requirement |
|---|---|---|
| HTTP(S) URL | https://example.com/image.jpg |
MUST accept |
| Local file path | /path/to/file.jpg, C:\path\to\file.jpg |
MUST accept |
| Binary data | b"\x89PNG..." |
MUST accept |
file:// URI |
file:///path/to/file.jpg |
SHOULD accept (can be forwarded as local path) |
| Base64 string / Data URI | iVBORw0KGgo=..., data:image/png;base64,... |
SHOULD accept (compatible with OneBot12 ecosystem conventions) |
Adapters must behave consistently across the three required forms—regardless of whether modules pass a URL, path, or bytes, the received message should be the same. If the platform cannot directly use a certain form (e.g., the platform API does not support referencing external URLs), the adapter should download/read and upload it, not require the module to retry with a different form.
2.1.2 Form Determination Order
When implementing media parameter handling, adapters should determine the form in the following order:
bytestype → Upload directly- String starts with
http:///https://→ Handle as URL (reference directly or download and upload, depending on platform capability) - String starts with
file://→ Strip prefix and handle as local path - Other strings → Handle as local path (read and upload if exists; return standard error response if not)
def _resolve_media(self, file: "str | bytes") -> bytes:
"""Form determination and normalization (example)"""
if isinstance(file, (bytes, bytearray)):
return bytes(file)
if file.startswith(("http://", "https://")):
return self._download(file) # Download if platform cannot reference URLs
if file.startswith("file://"):
file = file[len("file://"):]
with open(file, "rb") as f: # Local path
return f.read()
2.1.3 File Name Semantics for File
File method signature: File(file, filename=None) (filename is optional, built-in in base class).
File name derivation order (adapter generates this when filename is not explicitly provided):
- Explicit
filenameparameter (highest priority) - URL's basename (e.g.,
https://host/a/b/report.pdf→report.pdf, query string stripped) - Local path's basename (e.g.,
/tmp/data/backup.zip→backup.zip) - Platform default generation (e.g.,
file_{timestamp}; should retain real extension—extension affects platform-side type recognition and preview behavior)
Image/Voice/Videocan also acceptfilename(passed via message segmentdata.filename), but onlyFile's file name has cross-platform semantic guarantee.
2.1.4 Declaration of Platform Limitations
Platforms have different constraints on media size, format (MIME), duration (audio/video), etc. Adapters should:
- Declare supported media types and limitations in the adapter documentation
- Return standard error response for over-limit or unsupported input (
status: "failed";retcodeuses10002or platform-specific error code,messageexplains the reason), not throw exceptions to interrupt module logic
2.1.5 Capability Degradation Hierarchy
When a platform does not support a certain media type, the following degradation hierarchy applies (following the general principle of "capability degradation without error"):
| Scenario | Degradation Behavior |
|---|---|
Voice does not support voice messages |
Should send as File (or platform-adjacent form); return retcode=10002 if unable to express |
Video does not support video messages |
Same as above |
| Media type completely unsupported (no file capability) | Return retcode=10002, message indicates unsupported data type |
| Form not supported (e.g., cannot handle base64) | Return retcode=10002, can include a message suggesting the module switch to URL/bytes |
Prohibited actions: Silent drop (no return), throw exceptions, require the module to write platform-specific handling.
2.2 @User Parameter Specification
Method: At (modifier method)
Parameter: user_id (str)
Requirements:
user_idshould be a string-type user identifier- Different platforms may have different
user_idformats (numbers, UUIDs, strings, etc.) - The adapter is responsible for converting
user_idinto platform-specific format - Note that the actual send method call should be placed at the last position
Example:
# Single @ user
Send.To("group", "g123").At("123456").Text("Hello")
# Multiple @ users (chained call)
send.To("group", "g123").At("123456").At("789012").Text("Hello everyone")
2.3 Reply Message Parameter Specification
Method: Reply (modifier method)
Parameter: message_id (str)
Requirements:
message_idshould be a string-type message identifier- Should be the ID of a previously received message
- Some platforms may not support reply functionality; adapters should degrade gracefully
Example:
send.To("group", "g123").Reply("msg_123456").Text("Received")
3. Platform-Specific Method Naming
Do not directly add platform-prefixed methods in the Send class. It is recommended to use generic method names or Raw_{protocol} methods.
Not recommended:
def YunhuForm(self, form_id: str): # ❌ Not recommended
pass
def TelegramSticker(self, sticker_id: str): # ❌ Not recommended
pass
Recommended:
def Form(self, form_id: str): # ✅ Generic method name
pass
def Sticker(self, sticker_id: str): # ✅ Generic method name
pass
def Raw_ob12(self, message): # ✅ Send OneBot12 format
pass
Extension method requirements:
- Method names use PascalCase, without platform prefix
- Must return an
asyncio.Taskobject - Must provide complete type annotations and docstrings
- Parameter design should be as consistent as possible with standard method styles
4. Parameter Naming Convention
| Parameter Name | Description | Type |
|---|---|---|
text |
Text content | str |
file |
Media content (URL/path/bytes, see §2.1.1) | str / bytes |
filename |
File name (optional for File, see §2.1.3) |
str / None |
user_id |
User ID | str / int |
group_id |
Group ID | str / int |
message_id |
Message ID | str |
data |
Data object (e.g., card data) | dict |
5. Return Value Specification
- Send methods (e.g.,
Text,Image): Must return anasyncio.Taskobject - Modifier methods (e.g.,
At,Reply,AtAll): Must returnselfto support method chaining
6. Reverse Conversion Specification (OneBot12 → Platform)
Adapters must not only convert platform-native events into OneBot12 format (forward conversion), but also must provide the ability to convert OneBot12 message segments back into platform-native API calls (reverse conversion). The unified entry point for reverse conversion is the Raw_ob12 method.
6.1 Conversion Model
Forward Conversion (Receiving Direction) Reverse Conversion (Sending Direction)
───────────────── ─────────────────
Platform-native event OneBot12 message segment list
│ │
▼ ▼
Converter.convert() Send.Raw_ob12()
│ │
▼ ▼
OneBot12 Standard Event Platform-native API call
(containing {platform}_raw) (returns standard response format)
Core Symmetry: Forward conversion retains original data in {platform}_raw, and reverse conversion accepts OneBot12 standard format and restores it into platform calls.
6.2 Raw_ob12 Implementation Specification
Raw_ob12 receives a list of OneBot12 standard message segments and must convert them into platform-native API calls.
Method Signature:
def Raw_ob12(self, message_segments: List[Dict]) -> asyncio.Task:
"""
Send OneBot12 standard message segments
:param message_segments: List of OneBot12 message segments
[
{"type": "text", "data": {"text": "Hello"}},
{"type": "image", "data": {"file": "https://..."}},
{"type": "mention", "data": {"user_id": "123"}},
]
:return: asyncio.Task, await returns standard response format
"""
Implementation Requirements:
- Must handle all standard message segment types: At least support
text,image,audio,video,file,mention,reply - Must handle platform extension message segments: For message segments of type
{platform}_xxx, convert them into corresponding platform-native calls - Must return standard response format: Follow API Response Standard
- Unsupported message segments should be skipped and a warning logged, not throw exceptions causing the entire message to fail
6.3 Message Segment Conversion Rules
6.3.1 Standard Message Segment Conversion
Adapters must implement the following standard message segment conversions:
| OneBot12 Message Segment | Conversion Requirement |
|---|---|
text |
Use data.text directly |
image |
data.file processed per §2.1 media protocol (three must-accept forms + determination order) |
audio |
Same processing logic as image |
video |
Same processing logic as image |
file |
Same processing logic as image; file name processed per §2.1.3 derivation order for data.filename |
mention |
Convert to platform @user mechanism (e.g., Telegram's entities, Yunhu's at_uid) |
reply |
Convert to platform reply reference mechanism |
face |
Convert to platform emoji sending mechanism; skip if not supported |
location |
Convert to platform location sending mechanism; skip if not supported |
6.3.2 Platform Extension Message Segment Conversion
For message segments with platform prefixes, adapters should recognize and convert them:
def _convert_ob12_segments(self, segments: List[Dict]) -> Any:
"""Convert OneBot12 message segments to platform-native format"""
platform_prefix = f"{self._platform_name}_"
for segment in segments:
seg_type = segment["type"]
seg_data = segment["data"]
if seg_type.startswith(platform_prefix):
# Platform extension message segment → Platform-native call
self._handle_platform_segment(seg_type, seg_data)
elif seg_type in self._standard_segment_handlers:
# Standard message segment → Platform equivalent operation
self._standard_segment_handlers[seg_type](seg_data)
else:
# Unknown message segment → Log warning and skip
logger.warning(f"Unsupported message segment type: {seg_type}")
6.3.3 Handling Composite Message Segments
A message may contain multiple message segments, and adapters need to correctly handle composite messages:
# Module sends a message containing text + image + @user
await send.Raw_ob12([
{"type": "mention", "data": {"user_id": "123"}},
{"type": "text", "data": {"text": "Hello"}},
{"type": "image", "data": {"file": "https://example.com/img.jpg"}}
])
Handling Strategy:
- Prioritize merging: If the platform supports combining text, image, and @ in a single message, merge and send
- Fallback to splitting: If the platform does not support merging, send as multiple messages in sequence
- Maintain order: Message segments should be sent in the same order as in the list
6.4 Relationship Between Raw_ob12 and Standard Methods
Adapter's standard send methods (Text, Image, etc.) are already implemented and default-delegated to Raw_ob12 by the SendDSL base class; adapter subclasses do not need to re-implement them:
class Send(SendDSL):
def Raw_ob12(self, message_segments: List[Dict]) -> asyncio.Task:
"""Core implementation: OneBot12 message segments → Platform API (must implement)"""
return asyncio.create_task(self._send_ob12(message_segments))
# Text/Image/Voice/Video/File inherited from base class, automatically delegate to Raw_ob12
# Override individual methods if platform-specific logic is needed:
# def Text(self, text: str) -> asyncio.Task:
# return self.Raw_ob12([{"type": "text", "data": {"text": text}}])
Benefits:
- Conversion logic is centralized in
Raw_ob12, reducing code duplication - Standard methods and
Raw_ob12behave identically - Modules get the same result whether using
Text()orRaw_ob12() - Base class provides type signatures, IDE can auto-complete standard methods
6.5 Implementation Example
class YunhuSend(SendDSL):
"""Yunhu platform Send implementation"""
def Raw_ob12(self, message_segments: list) -> asyncio.Task:
"""OneBot12 message segments → Yunhu API call"""
return asyncio.create_task(self._do_send(message_segments))
async def _do_send(self, segments: list) -> dict:
"""Actual sending logic"""
# 1. Parse modifier state
at_users = self._at_users or []
reply_to = self._reply_to
at_all = self._at_all
# 2. Convert message segments
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":
# Platform extension message segment
yunhu_elements.append({"type": "form", "form_id": seg_data["form_id"]})
else:
logger.warning(f"Yunhu does not support message segment: {seg_type}")
# 3. Call Yunhu API
response = await self._call_yunhu_api(yunhu_elements, at_users, reply_to, at_all)
# 4. Return standard response format
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. Method Discovery
Module developers can query the supported send methods of an adapter via API (do not hardcode a list of methods for a specific platform in the module—each adapter's extension methods evolve with versions, and runtime discovery should be used):
from ErisPulse import adapter
# List all send methods
methods = adapter.list_sends("myplatform")
# ["Batch", "Form", "Image", "Recall", "Sticker", "Text", ...]
# View method details
info = adapter.send_info("myplatform", "Form")
# {
# "name": "Form",
# "parameters": [{"name": "form_id", "type": "str", ...}],
# "return_type": "Awaitable[Any]",
# "docstring": "Send Yunhu form"
# }
9. Adapter Development Notes
For how to correctly override BaseAdapter, Send, Request's __init__, see Adapter Development Guide - __init__ Notes.
10. Adapter Implementation Checklist
Send Methods
- Standard methods (
Text,Image, etc.) are implemented - Return values are all
asyncio.Task - Modifier methods (
At,Reply,AtAll) returnself - Platform extension methods use PascalCase, no platform prefix
- All methods have complete type annotations and docstrings
Media Sending Protocol
-
fileparameter must support all forms: HTTP(S) URL / local path /bytes(see §2.1.1) - Form determination order follows §2.1.2 (bytes → URL →
file://→ path) -
File's file name derivation order follows §2.1.3 (explicitfilename> URL basename > path basename > platform default) - Platform's media limitations (size / MIME / duration) are declared in adapter documentation (§2.1.4)
- Unsupported media types follow §2.1.5 degradation hierarchy: use adjacent type or return
retcode=10002, do not throw exceptions or silently drop
Reverse Conversion
-
Raw_ob12is implemented (must, not skipped) -
Raw_ob12can handle all standard message segments (text,image,audio,video,file,mention,reply) -
Raw_ob12can handle platform extension message segments ({platform}_xxxtype) - Standard send methods (
Text,Image, etc.) internally delegate toRaw_ob12, not implement conversion logic independently - Unsupported message segments are skipped and a warning is logged, no exception is thrown
- Composite message segments are handled correctly (merge or split in order)
11. Message Builder (MessageBuilder)
MessageBuilder is a message segment builder tool provided by ErisPulse, used in conjunction with Raw_ob12 to simplify the construction of OneBot12 message segments.
11.1 Import
from ErisPulse.Core import MessageBuilder
# or
from ErisPulse.Core.Event import MessageBuilder
11.2 Chainable Construction
# Build a message containing text, image, and @user
segments = (
MessageBuilder()
.mention("123456")
.text("Hello, look at this picture")
.image("https://example.com/img.jpg")
.reply("msg_789")
.build()
)
# Send
await adapter.Send.To("group", "456").Raw_ob12(segments)
11.3 Quick Single Segment Construction
# Quickly build a single message segment (returns list[dict], can be directly passed to 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 Use with 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("Received your message")
.build()
)
11.5 Supported Message Segment Methods
| Method | Description | data fields |
|---|---|---|
text(text) |
Text | text |
image(file) |
Image | file |
audio(file) |
Audio | file |
video(file) |
Video | file |
file(file, filename=None) |
File | file, filename (optional) |
mention(user_id, user_name=None) |
@User | user_id, user_name (optional) |
at(user_id, user_name=None) |
@User (alias of mention) |
Same as mention |
reply(message_id) |
Reply | message_id |
at_all() |
@All Members | {} |
custom(type, data) |
Custom/Platform Extension | Custom |
11.6 Utility Methods
builder = MessageBuilder().text("Base content")
# Copy (deep copy)
msg1 = builder.copy().image("img1").build()
msg2 = builder.copy().image("img2").build()
# Clear
builder.clear().text("New content").build()
# Check if empty
if builder:
print(f"Contains {len(builder)} message segments")
12. Related Documents
- Event Conversion Standard - Complete event conversion specification, extension naming, and message segment standard
- API Response Standard - Adapter API response format standard
- Session Type Standard - Session type definition and mapping relationship
- Request Operation Specification - Request event field requirements, HandleRequest DSL, and adapter implementation requirements