连接Astrbot&Mfuns生态 | 赛博喵友已上线
———— Mfuns OneBot v11 Bridge
喵御宅(Mfuns)社区通知 → OneBot v11 事件桥接服务,将 Mfuns 的私信 / 评论 / @提及 / 点赞通知转换为标准 OneBot v11 事件,通过 OneBot v11 Reverse WebSocket 推送给 AstrBot,使 AstrBot 能把 Mfuns 通知识别为标准 OneBot 事件并触发 Agent。
- 技术栈:Python 3.10+ / httpx / pydantic / websockets
- Mfuns 接入:轮询
/v1/notify/*与/v1/message/list(Mfuns 无 Webhook/SSE,与 Mfuns MCP 项目实测一致) - 推送方式:OneBot v11 Reverse WebSocket(客户端主动连接 AstrBot)
- 可靠性:事件去重(JSON 持久化,重启不重发)、断线自动重连(指数退避)、失败下轮重试(at-least-once)
架构
Mfuns
│ /v1/notify/count · /v1/notify/get · /v1/message/list(轮询)
▼
NotifyReceiver ──► NotificationDispatcher
│ Parser → NotifyEvent
│ SeenStore 去重
▼
NotificationMapper(纯数据转换层)
▼
OneBot v11 Event(message / notice)
▼
OneBotWebSocket(Reverse WebSocket 客户端,自动重连)
▼
ws://127.0.0.1:6199/ws
▼
AstrBot(OneBot v11 Reverse WS 服务器)
▼
Agent 被触发
Mfuns Notification ──► OneBot v11 Event
支持事件(2026-08-27 真实 API 实测确认)
Mfuns 通知OneBot v11 Event映射规则私信message(message_type=private, sub_type=friend)会话 last_msg 发送者 → user_id;user.name → 昵称;内容 → message 段评论 / 回复message(message_type=group, sub_type=normal)content_id(帖子/动态/视频 ID)→ group_id;comment_id → message_id;sender_user_id → user_id;user.name → 昵称;回复时以 回复「被回复内容」 文本引用(API 未提供被回复评论 ID)@提及message(message_type=group)同评论;被 @ 用户无独立 ID 字段,@名字 保留在文本中点赞notice(notice_type=notify, sub_type=poke)sender_user_id → user_id;content_id → target_id / group_id;notify_params.count 为连赞数实测数据要点:
- 通知条目含稳定
id(去重键)、content_id/content_type(0 文章 / 1 视频 / 4 动态)、发送者完整信息user {id, name} - 回复型评论:
comment_is_reply=true+reply_text(被回复内容),无 reply_id → 以文本引用形式保留 - 提及通知无 at 用户 ID 字段 → 不生成
at段,@名字保留在文本中 - 私信无公开聊天记录接口(实测所有参数均返回会话列表)→ 轮询各会话
last_msg,msg_id(如1787666906211-0)为稳定去重键,自动跳过自己发出的消息 - 文本中内嵌表情代码(
[s-2]、[simplevip-3]等)原样保留 - 平台图片为
/static/...相对路径,自动补全为https://cdn2.mfuns.net/...(与 Mfuns_Flutter 一致:图片 CDN 为 cdn2.mfuns.net,同路径放 api.mfuns.net 会返回 JSON) - 必须使用真实浏览器 UA(WAF 拦截无浏览器 UA 的请求,实测返回 403),项目已内置
评论类事件示例(实际推送到 AstrBot 的报文):
{
"time": 1787760064,
"self_id": 10001,
"post_type": "message",
"message_type": "group",
"sub_type": "normal",
"message_id": 1216957,
"group_id": 122777,
"user_id": 32278,
"message": [
{ "type": "text", "data": { "text": "回复「AI生成的」 Mfuns Flutter v1.2.x 新增了……" } }
],
"raw_message": "回复「AI生成的」 Mfuns Flutter v1.2.x 新增了……",
"sender": { "user_id": 32278, "nickname": "小蓝TheBlueFire" }
}
Message Segment 转换
支持 text / image / at / reply 四类标准段(at / reply 段仅在 API 提供对应 ID 时生成),内容来源自动识别:
- Quill JSON(私信消息,图片 insert 转 image 段)
- 服务端渲染 HTML(含
<img>,转 image 段) - 纯文本(识别 Markdown 图片
、[图片: url]、裸图片链接)
评论回复(AstrBot → Mfuns)
AstrBot 回复评论通知(message_type=group)时经 send_group_msg 回传,桥接按 Mfuns_Flutter 的评论模型处理:
- 纯文本:优先使用 AstrBot 回传
reply段中的评论 ID 调/v1/comment/create_reply(二级回复,评论者收到回复通知)
带图:create_reply 仅支持文本(工具接口限制,与 Flutter 一致),故带图时改发楼层评论(/v1/comment/create),图片支持:
http(s)外链:自动下载后经/v1/media/upload_image转用户媒体库/static/...路径base64://数据 / 本地文件路径(桥接与 AstrBot 同机时):直接读字节上传- 本地路径不可读(跨机部署)时忽略该图并记日志
images字段为 JSON 字符串(jsonEncode),与正文content(Quill JSON)分离- 仅图片无文本也可发表楼层评论
- 原评论已删除或
create_reply失败时,同样回退为楼层评论 - 本地
file://图片路径不会被拼进评论文本
私信发送(AstrBot → Mfuns)
send_private_msg / send_msg(private) 支持文本 + 图片:
- 图片同样先上传转
/static/...路径,再以 Quill JSON 发送(msg字段 form 编码,图片为{"insert": {"image": "/static/..."}},与收信格式一致) - 仅图片无文本也可发送;图片上传失败(如跨机本地路径不可读)时忽略该图并记日志
默认启动行为:
dedup.prime_on_start=true时,首次启动会先拉取全部存量通知并只记录去重键、不推送,之后仅推送新增通知(实测该账号存量 1500+ 条也不会刷屏)。如需把历史存量也推送一遍,将prime_on_start设为false后启动。
配置
复制配置模板并填写:
Copy-Item config.json.example config.json
{
"base_url": "https://api.mfuns.net",
"account": {
"account": "手机号或用户名",
"password": "密码(可选,配置了 token 可不填)",
"token": "登录 token(可选,留空自动登录并回写)",
"user_id": null,
"user_name": ""
},
"onebot": {
"url": "ws://127.0.0.1:6199/ws",
"access_token": "",
"self_id": 10001,
"reconnect_interval": 5000,
"reconnect_max_interval": 60000
},
"poll": {
"interval": 5.0,
"page_size": 20
},
"dedup": {
"seen_file": "logs/seen_notifications.json",
"max_seen": 10000
},
"log_level": "INFO"
}
配置项说明accountMfuns 账号。token 为空且配置了账密时自动登录并回写;401 自动重登onebot.urlAstrBot 的 OneBot v11 Reverse WS 地址onebot.access_token非空时通过 Authorization: Bearer <token> 鉴权onebot.self_idOneBot 事件 self_id 字段onebot.reconnect_interval断线重连初始间隔(毫秒),失败后指数退避至 reconnect_max_intervalpoll.interval通知轮询间隔(秒)dedup.seen_file已处理事件 ID 持久化文件(重启不重发)dedup.prime_on_start默认 true:启动时初始化去重存储,只记录存量通知,之后仅推送新增配置文件路径支持环境变量 MFUNS_ONEOT_CONFIG 或 --config 参数指定。
AstrBot 配置
- AstrBot 启用 OneBot v11 反向 WebSocket 适配器。
- 记下 AstrBot 的 WS 监听地址与端口(默认
ws://127.0.0.1:6199/ws),填入本桥的onebot.url。 - 若 AstrBot 配置了 access token,同步填入本桥
onebot.access_token。
启动
uv sync # 安装依赖 uv run python -m mfuns_onebot # 正常启动 uv run mfuns-onebot # 等价(console script) uv run python -m mfuns_onebot --config config.json # 指定配置 uv run python -m mfuns_onebot --dry-run --once # 不连接 AstrBot,轮询一次并打印事件 uv run python -m mfuns_onebot --once # 轮询一次后退出(调试)
优雅退出:Ctrl+C 或 SIGTERM。
可靠性设计
- 事件去重:Notify 条目
id(或类型+发送者+时间+内容哈希)、私信msg_id为去重键,成功推送后写入dedup.seen_file,重启后已处理事件不重发。 - at-least-once:推送失败的事件不计入去重,下轮轮询自动重试。
- 自动重连:AstrBot 重启、网络异常、连接被主动关闭时自动重连,指数退避。
- 轮询优化:整页均为已处理事件时提前停止翻页;私信轮询各会话
last_msg(跳过自己发出的消息)。
项目结构
mfuns_onebot/
├── __init__.py
├── config.py # JSON 配置读写(环境变量 / --config 覆盖)
├── client.py # Mfuns API 客户端:信封解包 / 自动登录 / 401 重试 / 5 QPS 限速
├── app.py # BridgeApp 组装 + CLI(--once / --dry-run / --config)
├── __main__.py # python -m mfuns_onebot
│
├── notify/
│ ├── models.py # Notification / Conversation / 统一 NotifyEvent(Pydantic)
│ ├── parser.py # 原始数据 → NotifyEvent(Quill / HTML / 文本)
│ ├── receiver.py # NotifyReceiver:/v1/notify/* 轮询循环
│ ├── dispatcher.py # 解析 → 去重 → 映射 → 推送
│ └── store.py # SeenStore:JSON 持久化去重
│
└── onebot/
├── models.py # OneBot v11 MessageEvent / NoticeEvent / Segment
├── message.py # 内容 → text / image / at / reply 段
├── mapper.py # NotificationMapper:NotifyEvent → OneBot Event
└── websocket.py # OneBotWebSocket:Reverse WS 客户端(自动重连 / Access Token)
tests/ # 单元 + 集成测试(本地 Mock Mfuns API + 真实 WS 服务器)
测试
uv run pytest
覆盖:Parser(私信/评论/提及/点赞/未知/无效载荷)、Mapper(四类映射)、Message Segment(text/image/at/reply/mixed)、WebSocket(连接/发送/重连/失败恢复)、事件去重持久化、以及 Notify → Parser → Mapper → WebSocket 全链路集成。
参考
- Mfuns MCP 项目
- Mfuns 官方开放平台 API 文档
- OneBot v11 规范
