← QQ猫包Bot项目

MaiBot × QQ 机器人 × MC 服务器远程控制 —— 开发文档(公开版)

本文档为去敏公开版,可对外分享。内部完整归档(含凭据位置与逐日取证)见团队私有仓库。 涉及的值一律以占位符 <...> 表示;带真实配置的版本只存在于部署服务器与私有备份。


1. 项目概览

在单台云服务器上搭建一套「QQ 机器人 + AI 大脑 + Minecraft 服务器监控/控制」的完整链路:

  • QQ 端:Linux 版 QQ 挂小号(协议层),SnowLuma 做 OneBot 适配(HTTP/WS),消息进 MaiBot
  • 大脑:MaiBot(MaiSaka 系),负责理解自然语言、决定要不要回、怎么回
  • MC 监控:主站独立 TCP 探活 + SRV 域名解析,读真实在线人数(快、轻、只读)
  • MC 控制:麦块(MineKuai)面板官方 API,可开/关/重启/发命令(重、有风险、仅命令触发)

核心设计原则(本项目最重要的一条经验)

用「说话方式」区分两套 MC 系统,而不是靠模型自己猜

用户输入 系统 理由
自然语言:"服务器几个人 / 冒险服在线吗" 查人数(SRV 探活,只读) 问询类,误伤零风险
# 前缀命令:"#重启 养老服" 麦块面板 API(可控) 显式命令 = 明确意图,才允许破坏性操作

教训来源:早期让模型自己判断"服务器相关就调控制 API",结果一句"服务器当前状态"被误判成要查面板,还叠加了回复通道故障,群里半天没人应。危险操作的触发必须语法显式化,不能交给自然语言猜测。


2. 架构拓扑

QQ 群/私聊 ──► SnowLuma(OneBot) ──► MaiBot (AI 大脑)
                                      │
                     ┌────────────────┼──────────────────┐
                     ▼                ▼                  ▼
                网站后台/WebUI    MC 人数监控(SRV+TCP)  麦块面板 API(仅 # 命令)
                 (8001→nginx)     (主站 chatmiao)      (api.minekuai.cn/panel/client-api)

服务清单(systemd,全部 enabled,重启自动拉起)

服务 端口 职责
qq + SnowLuma 3000/3001 LinuxQQ 小号 → OneBot 协议层
maibot 8000(WS)/8001(WebUI) AI 大脑、插件宿主、聊天推理
主站 chatmiao 8010 MC 监控、网站、记忆/情绪存储
bge-embed 8011 记忆向量化服务
nginx(宝塔) 80/443/8080 反代:主站、WebUI(域名 + IP:8080 双入口)

MaiBot 插件

插件 职责 关键机制
chatmiao-bridge 网站情绪/人设注入;query_mc_online 查人数 三层闸门(见 §4)
mc-remote-control # 命令 → 麦块面板控制;mc_control 工具 # 前缀触发 + 白名单 + 三层闸门

3. 部署要点(浓缩版)

  1. QQ 链路:Xvfb(:99) 起 QQ → SnowLuma attach 该 QQ 进程 → SnowLuma 以 OneBot WS 主动反连 MaiBot 适配器。
  2. MaiBot:uv venv 安装;WebUI 会话密码在 bot_config.toml(生产必须改默认值并定期轮换)。
  3. 插件目录/plugins/<id>/ 下放 plugin.py + config.toml + _manifest.jsonconfig.toml 即配置真源,改动后重启宿主生效。
  4. 网页聊天:主站通过 WebSocket 直连 MaiBot 8000 端口(平台标识 webchat),实现网页端"直接问 AI"。

4. 三层闸门(本项目最可复用的工程经验)

LLM 的 prompt 规则不可靠——即使把数据塞进上下文,模型仍可能说"咱不知道/好像很挤"。所以对"服务器类必须给真实数据"的硬规矩,用三层代码硬闸门:

  1. 规则注入maisaka.planner.before_request hook):每轮向 planner 上下文注入一条【规则】。 - ⚠️ 宿主会持久化注入的 UserMessageItem → 必须先按前缀剔掉上一轮的旧注入再重注入,否则规则越堆越多。
  2. 动作闸门maisaka.planner.after_response hook):检查模型本轮输出,若"该调工具却打算直接回话",把首个 FunctionCallItem 强制改写为目标工具调用(动作/参数由代码按问题预推断,如 #查状态 冒险服mc_control(action=status, server_id=冒险服))。
  3. 发送闸门send_service.before_send hook):发送前核对回复文本必须包含工具返回的真实数字(如含 %/GB),缺失则整体替换为工具原文,杜绝"机器有点挤"这类凭感觉的话。

配套机制: - 插件工具必须 visibility="visible" 直接暴露给 planner,否则被藏进 tool_search 导致多轮空转。 - 群聊里工具鉴权拿不到 kwargs.user_id → 用 chat.receive.after_process hook 记录每会话最近入站发送者 QQ,工具执行时查这张表。


5. 可用命令(# 前缀,自然语言动作词)

服务器:养老服 / 冒险服(中文名自动映射到面板实例;也支持直接传实例 ID)。

命令示例 动作 权限
#查状态 冒险服 / #状态 养老服 面板实时状态(状态/CPU/内存/磁盘/网络/运行时长) 任何人(只读)
#重启 养老服 正常重启 仅站长
#强制重启 冒险服 stop→kill→start 强启(默认关闭,需 config 开启) 仅站长
#启动 养老服 / #开服 冒险服 开机 仅站长
#关闭 冒险服 / #停止 养老服 关机 仅站长
#强杀 冒险服 直接 kill 仅站长
#发命令 冒险服 say 大家好 给服务器控制台发指令 仅站长
#列表 列出账号下全部服务器 任何人(只读)
#切换目标 <实例ID> 改变持久默认控制目标 仅站长
  • 动作词解析顺序:强制重启/强启 > 重启/重开 > 强杀/强停 > 启动/开服 > 停止/关服 > 发命令 > 列表 > 切换 > 默认 查状态
  • 只读动作(查状态/列表)任何人有权限;破坏性动作仅站长 QQ(config admin_qqs)放行。
  • # 开头的任何自然语言都不触碰面板 API

6. 麦块(MineKuai) API 集成要点(血泪换来的)

  1. 用官方新地址 https://api.minekuai.cn/panel/client-api。 - 旧文档地址 minekuai.com/api/client 被创盛 WAF 的 JS 质询挡住,服务器出口任何 User-Agent 组合都返回挑战页,不可用
  2. 返回结构文档没写全,实测: - 列表 GET /servers{"data":[{attributes:{identifier,name,is_suspended,...}}]} - 详情 GET /servers/{id}顶层就是对象(无 attributes 包装);真实状态在 stats.attributes.current_state,资源在同级 stats.attributes.resources(无独立 /resources 端点);顶层 status 字段恒为 null,别读它。 - 实例名带 § 颜色码 → 必须清洗(去 § 及下一字符)。
  3. 控制:POST /servers/{id}/power {"signal": start|stop|restart|kill}{"success":true}POST /servers/{id}/command {"command":"..."}
  4. 鉴权:Authorization: Bearer <密钥>(放插件 config.toml,勿入代码/版本库);限流 300 次/分。
  5. 中文别名:config server_aliases="养老服=<实例A>;冒险服=<实例B>" + 实例名模糊匹配,用户不用记 ID。
  6. 插件运行环境禁止第三方 import → 全部用标准库 urllib + asyncio.to_thread

7. 踩坑与解决总表(按域分类)

QQ / 协议链

重启后 SnowLuma 不再注入新起的 QQ,链路静默失联 SnowLuma 配置文件开 hookAutoLoad:true 并重启
QQ 起不来 /tmp/qq.logDISPLAY=:99;进程带 --no-sandbox
SnowLuma 是 attach 模式 QQ 重启后必须重连;用 systemd 依赖顺序 + Restart=always

MaiBot 宿主

插件 on_load 里 pip 装重依赖 → 开机卡死 >10 分钟 py-spy dump --pid <bot.py> 定位阻塞线程,kill 掉 pip 子进程;重依赖先离线备好再启
重启后 8000 端口 75~100s 才就绪 轮询 ss -tln | grep :8000,别硬等固定秒数
webchat 平台单连接:第二个连接会把第一个踢下线 自动化测试串行执行,间隔 sleep
webchat 入站消息在 planner items 里包成 <message msg_id=...>\n正文 插件读取时剥壳取正文;纯壳(无正文)当指令行跳过
真人群里 @ 后 reply 报"未找到要回复的目标消息"死循环 宿主 reply 工具必须解析到可回复 msg_id;此适配器无纯文本 send 兜底。需宿主层改造(加兜底 send / 放宽窗口),非插件可救

插件 / planner 行为

注入的 UserMessageItem 被宿主持久化,规则越堆越多 每轮先剔旧前缀再注入
prompt 规则约束不可靠 三层代码闸门(§4)
工具默认藏在 tool_search visibility="visible"
群聊工具鉴权拿不到 user_id 入站 hook 记录会话→发送者 QQ 映射表
插件间关键词抢问题 明确分界:人数/在线→bridge;#控制/负载→mcremote;bridge 的 _is_server_question 加排除词
子代理"验证通过"的报告不可全信 关键结论自己探针复测;本地↔服务器 md5 核对;有并发覆盖立即 interrupt

网络 / 环境 / 工具

uv sync 国外 CDN 卡死 UV_DEFAULT_INDEX 指到阿里云 PyPI 镜像
EULA 文件带尾随换行 → 死循环 printf '%s' 写 + systemd Environment 双保险
tomlkit 增量改配置会粘连段落 全量读、全量重写、回读自校验
凭据从聊天记录转写复制变省略号 401 重连风暴;凭据永远从原文件读
pwsh→ssh heredoc 带 CRLF 管道前 ($scr -replace "\r","")
WebUI 直连 502 本机代理软件干扰 → hosts 指 IP + 代理加 DIRECT 规则,或走独立端口
新端口外网不通但本机/ufw 都通 两层防火墙都要放行:服务器 ufw + 云厂商安全组

8. 运维速查

# 重启 MaiBot 并等 8000 就绪
systemctl restart maibot
for i in $(seq 1 40); do ss -tln | grep -q :8000 && break; sleep 5; done

# 宿主/插件日志
tail -f /var/log/maibot.log

# 每轮推理取证(闸门问题排查神器)
ls /www/wwwroot/MaiBot/logs/maisaka_prompt/planner/<session>/

# 自测探针(webchat 通道,串行跑)
cd /www/wwwroot/MaiBot && timeout 150 .venv/bin/python tools/probe_mc.py '你的测试问题'

# 开机卡死定位
/www/wwwroot/MaiBot/.venv/bin/py-spy dump --pid <bot.py的PID>

9. 安全模型与建议

  • 破坏性动作白名单(admin_qqs)只放站长本人;白名单为空 = 不限制(生产勿空)。
  • 强制重启(kill 链路)默认关闭,需要时 config 显式开启。
  • 所有密钥/密码放插件或宿主 config.toml;代码与公开文档一律不含真实凭据
  • WebUI 密码曾在多份日志/配置中出现过 → 上线前必须轮换,并定期更换。
  • 云端安全组遵循最小放行;面板/WebUI 建议套 HTTPS 或至少限制来源。

← 返回