dsh-qq-rooms
来源与致谢:本项目衍生自 dsh-qq-bridge
(作者 Yuuyuko-uu,MIT)。
src/transport.js 中的消息段解析、标记剥离、卡片抽取、正文图、转发展开等函数移植自原项目并做了修改;
房间模型、白/黑名单、群策略、插件装配为本项目新写。
本项目是独立的第三方项目,不是原项目的官方分支;插件 id(qq-rooms)与配置格式均不同,
不能与原版同时启用。原项目版权声明见 LICENSE。
DSH 的 QQ 房间插件:聊天即房间,不用再手动把 QQ 会话绑到 DSH 会话。
- 私聊一人一间,群一群一间,第一条消息自动建房,之后复用(进程重启后
resume)
- 白名单直接聊;陌生人先问主人;主人拒绝后进黑名单,以后不再询问
- 群默认不接;接入后只回「@ 我 / 引用我」,主人在群里免 @
- 收图、发图、合并转发、卡片解析、语音转写(可选外部命令)
- 多 TTS 引擎:MiniMax / edge-tts / Windows 朗读 / 自定义;选引擎后音色列表跟着变,命令由插件生成
- 语音回复(TTS):把回复念成 QQ 语音条;支持随机、由她判断、
qq_voice 现发,以及房间级「都用语音 / 都用文字」记忆
- 生图:主人私聊里说「画一张…」,走 MiniMax
image-01 出图,画好直接发到 QQ(默认关)
- 身份上下文:首次进房注入对方 QQ、昵称和关系;QQ 私聊每条消息也带
昵称(QQ号)
- 工具档:访客 / 群 / 单房三级设置;访客和群房默认全禁,只有主人私聊不受限,改完保存即时生效
- 房间、白名单、审批消息都显示昵称,群房显示群名;启动时自动补全
- 设置页「通到 QQ」:状态徽章、分段页签、滑动开关、粘性保存条;日志落盘,一键打开目录
消息走 OneBot 11 HTTP(NapCat / SnowLuma 都行),入站以 WebSocket 上报为主、HTTP 轮询补漏。
安装
前提:本机已经跑着一个 OneBot 11 后端,并且开了
- HTTP 服务(默认
http://127.0.0.1:3000)
- WebSocket 上报(默认
ws://127.0.0.1:3001,陌生私聊只能靠它发现)
dsh plugin --profile desktop add github:Yukolike/dsh-qq-rooms
装完重启一次 DSH,然后 设置 → 通到 QQ。
不需要构建:仓库就是可运行源码(main 指向 src/host.js),没有依赖、没有编译步骤。
dsh plugin add 会直接 clone 并用源码运行;GitHub Release 里的 Source code 包就是完整安装包。
与 dsh-qq-bridge 不兼容:两个都启用会双发。请只留一个。
配置
| 项 | 默认 | 说明 |
|---|
| 总开关 | 关 | 关 = 一句都不发 |
| 试运行 | 开 | 开 = 只记账,不真发 |
| 主人 | 空 | 管理员的 QQ 号;审批、加群、重置只认这个号 |
| NapCat 地址 | http://127.0.0.1:3000 | OneBot HTTP |
| 接口令牌 | 空 | 后端开了鉴权才填 |
| 事件地址 | ws://127.0.0.1:3001 | 留空 = 不连事件源(只剩轮询) |
| 事件令牌 | 空 | 可空 |
| 房间工作区 | 空 | 新房间的 cwd;空 = ~/.dsh/qq-rooms |
| 模型 | deepseek-account/deepseek-flash | provider/model |
| 访客工具 | none | 访客房间的默认工具档:none / safe / standard |
| 群工具 | none | 群房间的默认工具档;群成员不可信,默认全禁;单群可在「房间」页覆盖 |
| 房间工具 | 空 | 按房间覆盖上一条:「设置 → 房间」里每一间访客私聊和群房都有一个下拉;保存即生效,不用重启、不用等下一句 |
| 只发 [QQ] 块 | 关 | 开 = 只发正文里 [QQ]…[/QQ] 中间那段 |
| 语音模式 | off | off / random / mark / all;random = 按概率抽 |
| 语音概率 | 20 | random 模式下每条回复发语音的几率(0–100) |
| 语音引擎 | minimax | minimax / edge / sapi / custom;命令由插件生成 |
| 引擎配置 | 默认 | 每个引擎的模型 / 语速 / 音高 / python 路径 |
| 引擎音色 | 默认 | 每个引擎上次选的音色 |
| 语音合成命令 | 空 | 高级覆盖:非空时优先,普通用户不用填;自定义引擎必须填 |
| 音色 | 空 | 当前引擎的音色;下拉按引擎变,克隆音色直接填 ID |
| 语音情绪 | 空 | 空 = 模型自动判断;也可选 happy / calm / sad / whisper 等;命令里用 |
配置文件在 ~/.dsh/qq-rooms-config.json,状态在 ~/.dsh/qq-rooms-state.json。
日志不再显示在设置页里,直接落盘到 ~/.dsh/qq-rooms-logs/plugin.log(超过 2MB 轮转成 plugin.1.log);设置页「操作」里点 打开日志目录 就能在资源管理器里打开。
设置页
「设置 → 通到 QQ」分四个页签:
- 总览:开关、主人、连接(地址 / 令牌 / 轮询)、房间默认(模型、工作区、访客工具 / 群工具)、语音回复(TTS)
- 访客:等你拍板(同意 / 拒绝)、白名单(可直接输入 QQ 号加入,自动查昵称)、黑名单(解除)
- 群:接群、每个群的模式(只回点名 / 听所有人)与静音
- 房间:每间房的昵称 / 群名、会话状态(活着 / 睡着 / 已归档)、单房工具档、重置上下文
有改动时底部浮出保存条;「操作」里有保存、测试一句 / 测试语音(都绕过总开关与试运行)、打开日志目录、刷新。令牌显示为 ********,原样保存不会覆盖它。
昵称和群名在启动后自动补全(问 get_stranger_info / get_group_info),收到新消息时也会更新。
「重置上下文」= 归档旧会话 + 删映射:历史还在,但侧栏不再显示;下条消息会新建会话,并自动登记到「房间工作区」对应的 workspace。
主人指令(只在自己的私聊里生效)
同意 [号] 进白名单(以后直接聊)
拒绝 [号] 进黑名单(以后不再问你)
解除 10001 移出黑名单(下次会重新问)
放行 10001 直接进白名单
接群 123456789 [mention|all]
退群 123456789
静音 123456789 2h
取消静音 123456789
房间 列出所有房间
重置 群:123456789 归档会话,下次消息重建
指令必须整条消息匹配,日常聊天不会被误吃。
房间身份
- 第一次进房会注入一段身份上下文:QQ 私聊还是群聊、对方 QQ、昵称、和主人的关系(主人 / 已批准访客 / 群白名单成员 / 普通成员)。
- QQ 私聊每条消息前面也带
昵称(QQ号):,和群聊一致。
- 身份只在首条或昵称/关系变化时注入一次,不会每轮都塞;hash 存在房间行里,重启不丢。
- 老房间第一次收到新的 QQ 消息时会补一次身份注入。直接在 DSH 里打开旧会话、没有新 QQ 消息时,不会凭空补。
群策略
- 默认:一个群都不接,未加入的群消息直接丢弃,也不会来问你
mention(默认):只处理 @ 我 / 引用我的消息;at all 不算
all:所有消息都进,交给模型判断
- 主人在群里说话:无视模式直接进
- 成员白/黑名单:
members.allow 为空表示不限制,deny 优先
- 群房间的工具默认全禁(
群工具 = none);要让某个群能跑工具,在「房间」页给那一行单独选档,保存即生效。群房是共享的,限制作用于整个群的 agent,不区分发言者。
语音回复(TTS)
把回复念成 QQ 语音条。开关在 设置 → 通到 QQ → 总览 → 语音回复(TTS)。
| 模式 | 什么时候念 |
|---|
off | 不念(默认) |
random | 按「随机概率」抽;[语音] 仍然强制触发 |
all | 每条发到 QQ 的回复都念 |
mark | 由她判断:想发时在回复里写 [语音];标记本身会被删掉,不会露到 QQ |
random 模式用「随机概率」(0–100,默认 20)控制每条回复抽中的几率;抽中了才合成语音,没抽中照常发文字。用 qq_voice 主动发过语音的轮次,不会在同一轮再自动发一遍。
-
Windows 朗读:引擎选 Windows 朗读,用系统 System.Speech(tools/tts_sapi.ps1),离线、不用装东西;只支持 Windows。
-
换引擎:在「语音引擎」里选 MiniMax / edge-tts / Windows 朗读 / 自定义命令。命令由插件生成,普通配置不用碰 语音合成命令。
-
自定义 / 排错:展开「高级」,命令覆盖 模板拿到四个占位:
{text}:插件写好的 UTF-8 文字文件路径
{out}:要生成的 wav 路径
{voice}:设置里的「音色」
{emotion}:设置里的「情绪」(空 = 不传,交给模型自动判断)
自定义命令里的路径记得自己加引号(... "{text}" ...),不然带空格会裂。
-
念之前会先洗掉代码块、markdown、链接和 URL;超过「最长字」截断。合成失败 / 超时会写日志,并且这一条自动退回文字,不会哑掉。
-
语音条走 OneBot 的 record 消息段(file:/// 本地路径),SnowLuma / NapCat 会自己转 silk。第一次用先点「操作 → 测试语音(发给主人)」。
-
主人私聊里也可以直接说「发条语音」,模型会调 qq_voice 工具现合成一条发过来;不用写 [语音]。这个工具只挂在主人私聊。
-
记住这间房的偏好:主人私聊说「接下来都用语音 / 都用语音回复」,模型会调 qq_voice_mode 把这间房设成「都用语音」,之后每轮自动发语音;说「用文字 / 别语音」切回文字;说「恢复自动」回到全局设置。插件也会直接识别「接下来都用语音」「语音回复」「用文字」「恢复自动」这类明确说法,不依赖模型一定调工具。偏好存在 ~/.dsh/qq-rooms-state.json,重启不丢。
-
发送顺序:先语音,再(如果开了「语音附带文字」)文字,最后图片。
-
MiniMax 现成接法:tools/tts_minimax.py 已经带在包里,key 放 ~/.dsh/minimax-api-key.txt(或环境变量 MINIMAX_API_KEY),命令填:
"<python.exe>" "<插件目录>/tools/tts_minimax.py" --text "{text}" --out "{out}" --voice "{voice}" --model speech-2.8-hd --speed 1.0 --pitch 0 --emotion "{emotion}"
音色可以填 Chinese (Mandarin)_Warm_Bestie、Chinese (Mandarin)_Crisp_Girl、Chinese (Mandarin)_Warm_Girl 等;key 不会进设置页或命令模板。
-
2.8 的语气标签:文字里可以少量加 (laughs)、(sighs)、(breath)、(emm),这些会被模型当提示,不会念出来。AGENTS.md 里已经写了让她只在语音里偶尔用。
-
情绪:命令模板里的 {emotion} 来自设置页「情绪」;留空 = 自动,happy / calm / sad / angry / whisper 等可强制。
-
生成的 wav 在 ~/.dsh/qq-rooms-voice/,一天前的会自动清掉。
多 TTS 引擎
设置页现在先选「语音引擎」,再选这个引擎的音色;原始 shell 命令收在「高级」里,普通配置不用碰。
| 引擎 | 前置 | 音色 |
|---|
| MiniMax | ~/.dsh/minimax-api-key.txt 或 MINIMAX_API_KEY | 常用中文女声 + 手填克隆音色 |
| edge-tts | pip install edge-tts,系统有 python | Xiaoxiao / Xiaoyi / 方言 / 男声 |
| Windows 朗读 | Windows | 本机 SAPI 音色(默认 Huihui) |
| 自定义命令 | 自己写一个读 {text}、写 {out} 的命令 | 手填 |
切换引擎时,插件会清掉上一个引擎的命令覆盖,并按新引擎的默认音色带出。每个引擎的模型、语速、音高存在 引擎配置 里;音色存在 引擎音色 里,切回去还在。
原来的 语音合成命令 现在只作为高级覆盖;MiniMax 的 wrapper、edge 的命令、SAPI 的内置脚本都由适配器生成。旧配置里如果还是原来的 MiniMax 命令,会自动识别成 minimax 引擎。
生图(MiniMax)
开 设置 → 通到 QQ → 总览 → 生图(MiniMax) 后,主人的私聊里会多一个 qq_draw 工具。
- 在 QQ 里直接说「画一张蓝发女孩在吃大白饭」「来张海边日落的图」就行,模型会调工具。
- 工具是异步的:先回「画着呢」,MiniMax 出图后直接发到当前聊天,不用等整轮回复。
- 只给主人私聊;访客和群房拿不到这个工具,避免拿你的 key 刷图。
- 用的 key 和 TTS 同一个:
~/.dsh/minimax-api-key.txt(或 MINIMAX_API_KEY)。
- 模型
image-01;1024×1024 实测约 80 秒,可以调小尺寸或换 image-01-live 试。
- 出图按张计费,默认每天 10 张;
生图每日上限 = 0 才不限制。
- 图片存在
~/.dsh/qq-rooms-images/,7 天前的会自动清掉。
- 提示词最长 1500 字;被 MiniMax 审核拦了会直接回一句原因。
发表情包
在 [QQ] 块里写 ,插件会读本地文件、以 base64 发出去,markdown 不会泄露到 QQ。
相对路径按 图片根目录 → 房间工作区 → 进程 cwd → ~/.dsh 依次查找;也可以直接用绝对路径或 http(s) 链接。
安全
- 访客 / 群房间默认不能调工具:建房后
agent.ctx.tools.restrict({ allow: ['<占位工具>'] }) 只放行一个占位工具,none 再用 agent.ctx.tools.guard() 把执行全拒掉。只有「主人的私聊」不受限;群房按 群工具(默认 none)走。
- 为什么不直接「零工具」:DeepSeek 要求请求里至少有一个非 deferred 工具,而中途新增的工具是 deferred 的。别的插件注册在 agent 自己 scope 上的工具(例如
dsh-fish-memory 的 remember)restrict 遮不住(官方语义:masks intersect, scoped registrations stay visible)。零工具时第二轮起整个请求会 400(tools: at least one tool must have defer_loading=false)。所以留一个占位工具让请求合法,执行由 guard 拒绝。
访客工具 可设 none(默认:占位工具也不许执行)/ safe(只留 todo_write,可以真的记账)/ standard(不限制,不推荐)。
- 可以实时换档:每一间访客私聊或群房在「房间」页单独选档,或改全局默认,点保存就当场生效——不需要重启 DSH,也不需要对方再发一条。换档时先 dispose 掉旧限制再装新的(
restrict 的多次调用是相交的,只加不撤会越限越死,standard 也放不开)。
- 这是 agent 级运行时掩码,不是 profile 隔离;
房间工作区 仍建议指向空目录。
- 默认总开关关、试运行开,装完不会立刻发消息。
- 设置页接口只认「本人」:插件挂在 webServer 上的地址不会自动带 DSH 的登录校验,所以每个请求先过宿主的信任检查;拿不到检查、或检查本身出错一律放行,只有宿主明确回 401/403 才拒。
- 令牌只回遮罩:
/state 里的 令牌 / 事件令牌 是 ********;设置页把它原样存回来不会覆盖真令牌,清空才会删掉。
/save 只收设置项:房间(运行时建立)与 访客 / 黑名单(走审批接口)不接受前端回传,免得拿着旧页面一保存就把新建的房间冲掉。
开发
node --test tests/*.test.mjs
测试使用假 ctx / 假 fetch,不需要 DSH 或 QQ 后端。当前覆盖 105 项。
来源
src/transport.js 的部分纯函数(标记剥离、卡片抽取、正文图、消息段解析)移植自
dsh-qq-bridge(MIT)。房间、策略、装配、白/黑名单为本项目实现。
License
MIT