Discord Platform Feature Documentation
DiscordAdapter is an adapter built on the Discord Gateway (WebSocket) and REST API v10 protocol, integrating the core functionalities of Discord Bots and providing unified event handling and message operation interfaces.
Documentation Information
- Corresponding Module Version: 4.2.0
- Maintainer: ErisPulse
- Discord API Version: v10
Basic Information
- Platform Overview: Discord is a widely popular community communication platform that supports various conversation formats such as servers, channels, and direct messages, and provides a comprehensive Bot development interface.
- Adapter Name: DiscordAdapter
- Multi-account Support: Supports configuring multiple Discord bots simultaneously.
- Connection Method: Gateway WebSocket (for receiving events) + REST API (for sending messages / invoking APIs)
- Authentication Method: Bot Token (HTTP header
Authorization: Bot {token}, token included in the Gateway IDENTIFY payload) - Chained Modifiers Support: Supports chained modifier methods such as
.Reply(),.At(), and.AtAll() - OneBot12 Compatibility: Supports sending OneBot12 formatted messages
Configuration
DiscordAdapter supports multi-account configuration, with each account corresponding to an independent Discord Bot.
# config.toml
# Account 1
[DiscordAdapter.accounts.default]
token = "YOUR_BOT_TOKEN" # Discord Bot Token (required)
intents = 33281 # Gateway Intents (optional, default: 33281)
enabled = true # Whether to enable (optional, default: true)
# Account 2
[DiscordAdapter.accounts.bot2]
token = "ANOTHER_BOT_TOKEN"
intents = 33281
enabled = true
Configuration Item Description (per account):
token: Discord Bot Token (required), obtained from Discord Developer Portalintents: Gateway Intents bitmask (optional, default:33281), determines the types of events the Bot subscribes tobot_id: Bot's user ID (optional, automatically retrieved at runtime from the READY event, no need to manually fill)enabled: Whether to enable this account (optional, default:true)
Gateway Intents
Intents use a bitmask, calculated as the bitwise OR (|) of each Intent value:
| Intent | Bit | Value | Description | Privileged |
|---|---|---|---|---|
| GUILDS | 1 << 0 |
1 | Server creation/deletion/update, channel, role changes | No |
| GUILD_MEMBERS | 1 << 1 |
2 | Member join/leave/update | Yes |
| GUILD_MESSAGES | 1 << 9 |
512 | Server message sending/receiving | No |
| MESSAGE_CONTENT | 1 << 15 |
32768 | Message content (empty if this Intent is not present) | Yes |
Default value 33281 = GUILDS(1) | GUILD_MESSAGES(512) | MESSAGE_CONTENT(32768).
Note: Privileged Intents must be enabled in Discord Developer Portal → Bot → Privileged Gateway Intents. If the Bot is in more than 100 servers, Discord approval is also required.
API Environment:
- Discord REST API base URL:
https://discord.com/api/v10 - Gateway WebSocket URL: Dynamically retrieved via
GET /gateway/bot, typicallywss://gateway.discord.gg/?v=10&encoding=json
v5 Paradigm Update (4.2.0)
This adapter has completed alignment with the v5 paradigm (incremental upgrade, API compatible):
- BaseConverter Inheritance: Common fields of the converter are built by the framework
build_base_event - Api DSL: Standard API action mapping (see below)
- Standard keyboard segment: Converted to Discord components (action row + buttons); .Keyboard(rows) decorator accepts a generic structure
- Standard interaction callback fields: The INTERACTION_CREATE event includes interaction_id / button_data
- spawn_background task ownership: Connection tasks now use runtime.spawn_background
- Framework soft dependency: Runtime detection of ErisPulse>=2.7.1 with prompts; version logs output on startup
Standard API Actions
from ErisPulse import sdk
discord = sdk.adapter.get("discord")
result = await discord.Api.get_self_info() # GET /users/@me
result = await discord.Api.get_user_info(user_id) # GET /users/{id}
result = await discord.Api.get_guild_info(guild_id) # GET /guilds/{id}
result = await discord.Api.get_guild_list() # GET /users/@me/guilds
result = await discord.Api.get_channel_list(guild_id) # GET /guilds/{id}/channels
result = await discord.Api.get_guild_member_info(gid, uid)
await discord.Api.delete_message(message_id) # channel_id auto-completed in the registration table
await discord.Api.leave_guild(guild_id)
result = await discord.Api.Using("main").get_self_info()
Buttons (keyboard / components)
rows = [[{"label": "Click", "type": "callback", "data": "btn:1"},
{"label": "Website", "type": "link", "data": "https://example.com"}]]
await discord.Send.To("channel", channel_id).Keyboard(rows).Text("Please Select")
# Automatically converts components: callback → custom_id / link → url
Supported Message Sending Types
All sending methods are implemented through a fluent interface, for example:
from ErisPulse.Core import adapter
discord = adapter.get("discord")
await discord.Send.To("group", channel_id).Text("Hello World!")
The supported sending types include:
.Text(text: str)- Sends plain text messages..Embed(embed: dict | list)- Sends Embed messages, supporting single or multiple Embeds..Image(file: bytes | str, filename: str = "image.png")- Sends images, supporting binary data or URLs..File(file: bytes | str, filename: str = None)- Sends files, supporting binary data or URLs..Reply(content: str, message_id: str)- Replies to a specified message (convenience terminal method)..Raw_ob12(message: List[Dict], **kwargs)- Sends OneBot12 formatted messages..Raw_json(json_str: str)- Sends arbitrary Discord API request JSON.
Fluent Modifier Methods (Can Be Combined)
Fluent modifier methods return self and support fluent chaining, must be called before the final sending method:
.Reply(message_id: str)- Replies (references) to a specified message, setsmessage_reference..At(user_id: str)- Mentions a specified user, converts to<@user_id>, can be called multiple times..AtAll()- Mentions everyone, converts to@everyone.
Fluent Chaining Examples
# Basic sending
await discord.Send.To("group", channel_id).Text("Hello")
# Reply to message
await discord.Send.To("group", channel_id).Reply(msg_id).Text("Reply message")
# Convenient reply (one-step)
await discord.Send.To("group", channel_id).Reply("Reply content", msg_id)
# Mention user
await discord.Send.To("group", channel_id).At("user_id").Text("Hello")
# Mention multiple users
await discord.Send.To("group", channel_id).At("user1").At("user2").Text("Multiple @")
# Mention everyone
await discord.Send.To("group", channel_id).AtAll().Text("Announcement")
# Combine methods
await discord.Send.To("group", channel_id).Reply(msg_id).At("user_id").Text("Composite message")
# Embed message
embed = {
"title": "Notice",
"description": "This is an embedded message",
"color": 5814783,
"fields": [{"name": "Field", "value": "Value", "inline": True}],
}
await discord.Send.To("group", channel_id).Embed(embed)
# Send image
await discord.Send.To("group", channel_id).Image("https://example.com/image.png")
Private Message Sending
When sending private messages, the adapter will automatically create a DM channel:
# Send private message
await discord.Send.To("user", user_id).Text("Private message content")
await discord.Send.To("user", user_id).Embed(embed)
Message Operations
# Recall message
await discord.Send.To("group", channel_id).Recall(msg_id)
# OneBot12 format
ob12_msg = [
{"type": "text", "data": {"text": "Hello "}},
{"type": "mention", "data": {"user_id": "user_id"}},
]
await discord.Send.To("group", channel_id).Raw_ob12(ob12_msg)
Send Method Return Values
All send methods return a Task object, which can be awaited directly to obtain the send result. The returned result follows the ErisPulse adapter's standardized return specification:
{
"status": "ok", // Execution status: "ok" or "failed"
"retcode": 0, // Return code (0 indicates success)
"data": {...}, // Original Discord API response
"message_id": "xxx", // Message ID (when sending a message)
"message": "", // Error message
"discord_raw": {...} // Raw response data
}
Error Code Description
| retcode | Description |
|---|---|
| 0 | Success |
| 33001 | Network error (connection failed, timeout, etc.) |
| 34000 | Discord API returned error (insufficient permissions, invalid parameters, etc.) |
Unique Event Types
The platform-specific features require platform == "discord" detection before use.
Core Differences
- Server/Channel System: Discord uses a two-tier structure of servers (Guild) and channels (Channel), with channels being the basic targets for message sending.
- Gateway Events: All events are received via the WebSocket Gateway using the Opcode + Dispatch mechanism.
- Intents Subscription: Event types are subscribed via bitmask, and
MESSAGE_CONTENTrequires Privileged permissions. - Message Segment Types: Supports text, images, files, videos, audio, Embed, Sticker, and other message segments.
- Mention Format: Discord uses the
<@user_id>format for user mentions.
Extended Fields
All unique fields are prefixed with discord_:
discord_raw: The raw Discord event data.discord_raw_type: The raw event type name (e.g.,MESSAGE_CREATE).discord_guild_id: The server ID.discord_channel_id: The channel ID.
detail_type Mapping
| Discord Scenario | detail_type | Description |
|---|---|---|
| Channel Message | channel |
ErisPulse extension type |
| Direct Message (DM) | private |
OneBot12 standard type |
Event Type Mapping
| Discord Event | OneBot12 type | detail_type | Description |
|---|---|---|---|
| MESSAGE_CREATE | message | channel/private | Message creation |
| MESSAGE_UPDATE | message | channel/private | Message edit |
| MESSAGE_DELETE | notice | group_message_delete / private_message_delete | Message deletion |
| GUILD_MEMBER_ADD | notice | group_member_increase | Member join |
| GUILD_MEMBER_REMOVE | notice | group_member_decrease | Member leave |
| GUILD_MEMBER_UPDATE | notice | group_member_update | Member information update |
| GUILD_ROLE_CREATE | notice | group_role_create | Role creation |
| GUILD_ROLE_DELETE | notice | group_role_delete | Role deletion |
| CHANNEL_CREATE | notice | channel_create | Channel creation |
| CHANNEL_DELETE | notice | channel_delete | Channel deletion |
| INTERACTION_CREATE | request | interaction | Interaction (button, command, etc.) |
Special Field Examples
# Channel text message
{
"type": "message",
"detail_type": "channel",
"user_id": "sender ID",
"user_nickname": "username",
"group_id": "channel ID",
"message_id": "message ID",
"discord_raw": {...},
"discord_raw_type": "MESSAGE_CREATE",
"discord_guild_id": "server ID",
"discord_channel_id": "channel ID",
"message": [
{"type": "text", "data": {"text": "Hello"}}
],
"alt_message": "Hello"
}
# Direct message
{
"type": "message",
"detail_type": "private",
"user_id": "sender ID",
"user_nickname": "username",
"message_id": "message ID",
"discord_raw": {...},
"discord_raw_type": "MESSAGE_CREATE",
"discord_channel_id": "DM channel ID",
"message": [
{"type": "text", "data": {"text": "private message content"}}
],
"alt_message": "private message content"
}
# Message with Embed
{
"type": "message",
"detail_type": "channel",
"message": [
{"type": "discord_embed", "data": {"embed": {...}}}
],
"alt_message": "[embedded message]"
}
# Message with attachment
{
"type": "message",
"detail_type": "channel",
"message": [
{"type": "text", "data": {"text": "Look at this image"}},
{"type": "image", "data": {"file": "image URL", "url": "image URL", "file_name": "image.png"}}
],
"alt_message": "Look at this image[image]"
}
Message Segment Types
Discord message content is automatically converted into corresponding message segments based on the content, attachments, and embeds fields:
| Source | Conversion Type | Description |
|---|---|---|
| content text | text |
Plain text content |
content <@id> |
mention |
User mention |
content <@&id> |
discord_role_mention |
Role mention |
content <#id> |
discord_channel_mention |
Channel mention |
| attachments (image/*) | image |
Image attachment |
| attachments (video/*) | video |
Video attachment |
| attachments (audio/*) | audio |
Audio attachment |
| attachments (other) | file |
File attachment |
| embeds | discord_embed |
Embedded message |
| sticker_items | discord_sticker |
Sticker |
discord_embed Message Segment
{
"type": "discord_embed",
"data": {
"embed": {
"title": "Title",
"description": "Description",
"color": 12345,
"fields": [...],
"image": {"url": "..."},
"thumbnail": {"url": "..."},
"footer": {"text": "..."}
}
}
}
Gateway Connection
Connection Flow
- Call
GET /gateway/botto get the WebSocket gateway URL - Connect to
wss://gateway.discord.gg/?v=10&encoding=json - Receive opcode 10 HELLO: contains
heartbeat_interval - Send opcode 2 IDENTIFY: includes token, intents, and properties
- Start heartbeat loop: send opcode 1 Heartbeat at intervals of
heartbeat_interval - Receive opcode 0 Dispatch: event dispatch (
t=event name,s=sequence number,d=data) - Receive opcode 11 Heartbeat ACK: heartbeat acknowledgment
Opcode Reference
| Opcode | Name | Direction | Description |
|---|---|---|---|
| 0 | Dispatch | Receive | Event dispatch (includes t, s, d fields) |
| 1 | Heartbeat | Send/Receive | Heartbeat (includes last seq) |
| 2 | Identify | Send | Identity authentication |
| 6 | Resume | Send | Resume session |
| 7 | Reconnect | Receive | Server requests reconnection |
| 9 | Invalid Session | Receive | Invalid session |
| 10 | Hello | Receive | Connection handshake (includes heartbeat_interval) |
| 11 | Heartbeat ACK | Receive | Heartbeat acknowledgment |
Reconnection and RESUME
- After a connection is disconnected, the adapter automatically retries the connection
- If a previous
session_idexists, attempt to RESUME (opcode 6) the session first - RESUME includes
token,session_id, and the lastseq, restoring missed events after resumption - When opcode 7 (Reconnect) is received, maintain session state and reconnect
- When opcode 9 (Invalid Session) is received and
d=false, clear the session and re-authenticate with IDENTIFY
Heartbeat Mechanism
- After receiving HELLO, wait
heartbeat_interval * random()milliseconds before sending the first heartbeat - Subsequently, send a heartbeat every
heartbeat_intervalmilliseconds - The heartbeat includes the last
seqvalue (opcode 1,d: seq) - If an ACK (opcode 11) is not received within
heartbeat_intervalmilliseconds after sending a heartbeat, consider the connection abnormal and reconnect
Usage Examples
Handling Channel Messages
from ErisPulse.Core.Event import message
from ErisPulse import sdk
discord = sdk.adapter.get("discord")
@message.on_message()
async def handle_group_msg(event):
if event.get("platform") != "discord":
return
text = event.get_text()
channel_id = event.get("group_id")
if text == "hello":
await discord.Send.To("group", channel_id).Text("Hello!")
Handling Private Messages
@message.on_message()
async def handle_private_msg(event):
if event.get("platform") != "discord":
return
if not event.is_dm():
return
text = event.get_text()
user_id = event.get("user_id")
await discord.Send.To("user", user_id).Text(f"You said: {text}")
Sending Embed Messages
embed = {
"title": "Server Announcement",
"description": "Welcome to use ErisPulse Discord adapter",
"color": 3447003,
"fields": [
{"name": "Version", "value": "4.0.0", "inline": True},
{"name": "Framework", "value": "ErisPulse", "inline": True},
],
"footer": {"text": "Powered by ErisPulse"},
"timestamp": "2025-01-01T00:00:00.000Z",
}
await discord.Send.To("group", channel_id).Embed(embed)
Using Discord-Specific Methods
@message.on_message()
async def handle(event):
if event.get("platform") != "discord":
return
channel_id = event.get_channel_id()
guild_id = event.get_guild_id()
is_dm = event.is_dm()
embeds = event.get_embeds()
attachments = event.get_attachments()
if embeds:
await discord.Send.To("group", channel_id).Text(
f"Received {len(embeds)} embeds"
)
Handling Interaction Events
from ErisPulse.Core.Event import request
@request.on_request()
async def handle_interaction(event):
if event.get("platform") != "discord":
return
interaction = event.get_interaction_data()
if interaction.get("type") == 3: # MESSAGE_COMPONENT
await event.reply("Button clicked!")