MatrixAdapter 是基于 Matrix协议 构建的适配器,整合了Matrix协议的所有核心功能模块,提供统一的事件处理和消息操作接口。
- 对应模块版本: 1.0.0
- 维护者: ErisPulse
- 平台简介:Matrix是一个开放的去中心化通信协议,支持私聊、群组等多种场景
- 适配器名称:MatrixAdapter
- 多账户支持:支持同时配置多个 Matrix 账户
- 连接方式:Long Polling(通过 Matrix Sync API
/sync) - 认证方式:基于 access_token 或 user_id + password 登录获取 token
- 链式修饰支持:支持
.Reply()、.At()、.AtAll()等链式修饰方法 - OneBot12兼容:支持发送 OneBot12 格式消息
MatrixAdapter 支持多账户配置,每个账户独立配置 homeserver 和认证信息。
# config.toml
# 账户1
[Matrix_Adapter.accounts.default]
homeserver = "https://matrix.org" # Matrix服务器地址(必填)
access_token = "YOUR_ACCESS_TOKEN" # 访问令牌(与 user_id+password 二选一)
user_id = "" # Matrix用户ID(如 @bot:matrix.org)
password = "" # Matrix用户密码
auto_accept_invites = true # 是否自动接受房间邀请(可选,默认为true)
enabled = true # 是否启用(可选,默认为true)
# 账户2
[Matrix_Adapter.accounts.bot2]
homeserver = "https://matrix.example.com"
access_token = "ANOTHER_TOKEN"
enabled = true兼容旧配置:若检测到旧的单账户
[Matrix_Adapter]配置(含 access_token),会自动迁移为accounts.default。
配置项说明(每个账户):
homeserver:Matrix服务器地址(必填),默认为https://matrix.orgaccess_token:访问令牌,可从Matrix客户端获取。如果已有 token,直接填写即可user_id:Matrix用户ID(如@bot:matrix.org),与password配合使用进行登录password:Matrix用户密码,用于自动登录获取 access_tokenauto_accept_invites:是否自动接受房间邀请,默认为trueenabled:是否启用该账户(可选,默认为true)
认证方式:
- 方式一(推荐):直接提供
access_token - 方式二:提供
user_id和password,适配器会自动调用登录接口获取 token
所有发送方法均通过链式语法实现,例如:
from ErisPulse.Core import adapter
matrix = adapter.get("matrix")
await matrix.Send.To("group", room_id).Text("Hello World!")支持的发送类型包括:
.Text(text: str):发送纯文本消息。.Image(file: bytes | str):发送图片消息,支持文件路径、URL、MXC URI、二进制数据。.Voice(file: bytes | str):发送语音消息,支持文件路径、URL、MXC URI、二进制数据。.Video(file: bytes | str):发送视频消息,支持文件路径、URL、MXC URI、二进制数据。.File(file: bytes | str, filename: str = ""):发送文件消息,支持文件路径、URL、MXC URI、二进制数据。.Notice(text: str):发送通知消息(Matrix的 m.notice 类型)。.Html(html: str, fallback: str = ""):发送HTML格式消息,支持富文本内容。.Raw_ob12(message: List[Dict], **kwargs):发送 OneBot12 格式消息。
链式修饰方法返回 self,支持链式调用,必须在最终发送方法前调用:
.Reply(message_id: str):回复指定消息(通过 Matrixm.in_reply_to关系)。.At(user_id: str):@指定用户(通过 Matrixm.mentions字段实现)。.AtAll():@房间内所有人(通过 Matrix@room提及实现)。
# 基础发送
await matrix.Send.To("user", dm_room_id).Text("Hello")
# 回复消息
await matrix.Send.To("group", room_id).Reply("$event_id").Text("回复消息")
# @用户
await matrix.Send.To("group", room_id).At("@user:matrix.org").Text("你好")
# @所有人
await matrix.Send.To("group", room_id).AtAll().Text("公告通知")
# 组合使用:回复 + @
await matrix.Send.To("group", room_id).Reply("$event_id").At("@user:matrix.org").Text("复合消息")
# 发送HTML消息
await matrix.Send.To("group", room_id).Html("<h1>标题</h1><p>内容</p>", fallback="标题\n内容")
# 发送通知消息
await matrix.Send.To("group", room_id).Notice("系统通知")适配器支持发送 OneBot12 格式的消息,便于跨平台消息兼容:
# 发送 OneBot12 格式消息
ob12_msg = [{"type": "text", "data": {"text": "Hello"}}]
await matrix.Send.To("user", dm_room_id).Raw_ob12(ob12_msg)
# 配合链式修饰
ob12_msg = [{"type": "text", "data": {"text": "回复消息"}}]
await matrix.Send.To("group", room_id).Reply("$event_id").Raw_ob12(ob12_msg)
# 复杂消息
ob12_msg = [
{"type": "text", "data": {"text": "看这张图片:"}},
{"type": "image", "data": {"file": "https://example.com/image.png"}},
{"type": "text", "data": {"text": "不错吧?"}}
]
await matrix.Send.To("group", room_id).Raw_ob12(ob12_msg)所有发送方法均返回一个 Task 对象,可以直接 await 获取发送结果。返回结果遵循 ErisPulse 适配器标准化返回规范:
{
"status": "ok", // 执行状态: "ok" 或 "failed"
"retcode": 0, // 返回码
"data": {...}, // 响应数据
"message_id": "$event_id", // Matrix事件ID
"message": "", // 错误信息
"matrix_raw": {...} // 原始响应数据
}| retcode | 说明 |
|---|---|
| 0 | 成功 |
| 32000 | 请求超时或媒体上传失败 |
| 33000 | API调用异常 |
| 34000 | API返回了意外格式或业务错误 |
需要 platform=="matrix" 检测再使用本平台特性
- 去中心化架构:Matrix 是一个去中心化的通信协议,用户ID格式为
@user:server.domain,房间ID格式为!room_id:server.domain - 房间概念:Matrix 不区分群聊和私聊,所有会话都是"房间"。适配器通过 DM(Direct Message)账户数据自动识别私聊房间
- Long Polling 同步:使用
/syncAPI 进行长轮询获取新事件,而非 WebSocket - MXC URI:媒体文件通过
mxc://server.domain/media_id格式引用 - HTML 富文本:支持通过
formatted_body发送 HTML 格式消息 - 表情回应:支持消息级别的表情回应(Reaction),区别于传统的回复消息
- 消息编辑:支持通过
m.replace关系编辑已发送的消息 - 消息撤回:支持通过
m.room.redaction撤回/删除消息
- 所有特有字段均以
matrix_前缀标识 - 保留原始数据在
matrix_raw字段 matrix_raw_type标识原始Matrix事件类型(如m.room.message、m.room.member)
# 群组消息
{
"type": "message",
"detail_type": "group",
"user_id": "@user:matrix.org",
"group_id": "!room_id:matrix.org",
"matrix_room_id": "!room_id:matrix.org"
}
# 私聊消息
{
"type": "message",
"detail_type": "private",
"user_id": "@user:matrix.org",
"matrix_room_id": "!dm_room_id:matrix.org"
}
# 表情回应
{
"type": "notice",
"detail_type": "matrix_reaction",
"matrix_reaction_event_id": "$reacted_msg_id",
"matrix_reaction_key": "👍"
}
# 消息撤回
{
"type": "notice",
"detail_type": "matrix_redaction",
"matrix_redacted_event_id": "$deleted_msg_id"
}
# 消息编辑
{
"type": "message",
"detail_type": "group",
"matrix_edit": true,
"matrix_original_event_id": "$original_event_id"
}
# 线程消息
{
"type": "message",
"detail_type": "group",
"thread_id": "$thread_root_id"
}Matrix消息根据 msgtype 自动转换为对应的消息段:
| msgtype | 转换类型 | 说明 |
|---|---|---|
| m.text | text |
文本消息 |
| m.notice | text |
通知消息 |
| m.emote | text |
动作消息 |
| m.image | image |
图片消息 |
| m.audio | voice |
音频消息 |
| m.video | video |
视频消息 |
| m.file | file |
文件消息 |
| m.location | location |
位置消息 |
消息段结构示例:
// 文本消息(带HTML)
{
"type": "text",
"data": {
"text": "纯文本内容",
"html": "<b>HTML内容</b>"
}
}
// 图片消息
{
"type": "image",
"data": {
"url": "mxc://matrix.org/abc123",
"filename": "photo.png",
"matrix_mxc": "mxc://matrix.org/abc123",
"info": {
"mimetype": "image/png",
"w": 800,
"h": 600,
"size": 123456
}
}
}
// 位置消息
{
"type": "location",
"data": {
"latitude": 0.0,
"longitude": 0.0,
"matrix_geo_uri": "geo:39.9,116.4",
"text": "北京市"
}
}MatrixAdapter 注册了以下事件混入方法,可在事件处理中直接调用:
| 方法 | 返回类型 | 说明 |
|---|---|---|
get_room_id() |
str |
获取房间ID |
get_matrix_event_type() |
str |
获取原始Matrix事件类型 |
get_matrix_sender() |
str |
获取原始发送者ID |
get_reaction_key() |
str |
获取回应表情 |
is_edited() |
bool |
判断消息是否为编辑消息 |
is_notice() |
bool |
判断消息是否为 m.notice 类型 |
@message.on_message()
async def handle_message(event):
if event.get("platform") != "matrix":
return
room_id = event.get_room_id()
event_type = event.get_matrix_event_type()
sender = event.get_matrix_sender()
is_edited = event.is_edited()
is_notice = event.is_notice()- 使用 access_token 或 user_id + password 进行认证
- 调用
/_matrix/client/v3/account/whoami获取 bot_user_id - 发出 connect 元事件
- 执行初始同步(
/_matrix/client/v3/sync?timeout=0)获取next_batchtoken - 发现 DM 房间(
/_matrix/client/v3/user/{user_id}/account_data/m.direct) - 开始 Long Polling 同步循环(
/_matrix/client/v3/sync?since={next_batch}&timeout=30000) - 处理每次同步返回的新事件并转换发出
- 适配器每 30 秒发出一次
heartbeat元事件 - 连接成功时发出
connect元事件 - 关闭时发出
disconnect元事件
- 收到房间邀请(
invite状态的房间)时,如果auto_accept_invites配置为true(默认),适配器会自动加入房间 - 加入房间调用
/_matrix/client/v3/join/{room_id}接口
from ErisPulse.Core.Event import message
from ErisPulse import sdk
matrix = sdk.adapter.get("matrix")
@message.on_message()
async def handle_group_msg(event):
if event.get("platform") != "matrix":
return
if event.get("detail_type") != "group":
return
text = event.get_text()
room_id = event.get("group_id")
if text == "hello":
await matrix.Send.To("group", room_id).Reply(
event.get("message_id")
).Text("Hello!")from ErisPulse.Core.Event import notice
@notice.on_notice()
async def handle_reaction(event):
if event.get("platform") != "matrix":
return
if event.get("detail_type") == "matrix_reaction":
reaction_key = event.get("matrix_reaction_key")
reacted_event_id = event.get("matrix_reaction_event_id")
room_id = event.get_room_id()
# 处理表情回应...# 发送图片(URL)
await matrix.Send.To("group", room_id).Image("https://example.com/image.png")
# 发送图片(MXC URI)
await matrix.Send.To("group", room_id).Image("mxc://matrix.org/abc123")
# 发送图片(二进制数据)
with open("image.png", "rb") as f:
image_bytes = f.read()
await matrix.Send.To("group", room_id).Image(image_bytes)
# 发送图片(本地文件路径)
await matrix.Send.To("group", room_id).Image("/path/to/image.png")
# 发送文件(带文件名)
await matrix.Send.To("group", room_id).File("/path/to/document.pdf", filename="文档.pdf")@message.on_message()
async def handle_edited_message(event):
if event.get("platform") != "matrix":
return
if event.is_edited():
original_id = event.get("matrix_original_event_id")
# 处理编辑消息...@notice.on_notice()
async def handle_member_change(event):
if event.get("platform") != "matrix":
return
detail_type = event.get("detail_type")
if detail_type == "group_member_increase":
user_id = event.get("user_id")
nickname = event.get("user_nickname")
print(f"用户 {nickname} ({user_id}) 加入了房间")
elif detail_type == "group_member_decrease":
user_id = event.get("user_id")
operator_id = event.get("operator_id")
print(f"用户 {user_id} 被移除,操作者: {operator_id}")