一个群成员,不是客服机器人。会接话、会偶尔冒泡、会被叫了才干活。 零依赖(只用 Node 内置模块),ESM,Node ≥ 20。
名字来源:「社」字拆成 礻 + 土 + 长。
copy .env.example .env # 填 LLM_API_KEY;只想试跑就保持 LLM_PROVIDER=mock
node --test "tests/*.test.js"
npm run qq # 演练模式:只观察不说话
npm run qq:live # 正式:会真的发消息正式跑建议用监督器(崩了自动拉起、单实例锁):
powershell -ExecutionPolicy Bypass -File scripts\supervise-service.ps1| 功能 | 用法 |
|---|---|
| 聊天 / 提问 | @礻土长 今天有什么好玩的 |
| 找群聊线索 | @礻土长 帮我找 Blender 教程 / /找 关键词 |
| 上网搜 | @礻土长 搜一下 这周末有什么漫展 / /搜 关键词 |
| 选择困难裁判 | @礻土长 帮我选 拉面/盖饭 / /选 A / B |
| 烂点子急救 | @礻土长 烂点子急救:社团摆摊 / /点子 主题 |
| 给点思路 | @礻土长 给点提示:配方法怎么开始 / /提示 问题 |
| 发收藏表情 | @礻土长 发个表情包 |
| 查看帮助 | @礻土长 /小功能 |
真 @ 和手打 @礻土长 都认。后者兜底很重要:实测群里 @ 它时
消息里经常没有 at 段,是纯文本 —— 不认的话会被 bubble 判成
「@了别人,别抢话」而一票否决,表现就是「被叫了却完全不理人」。
几条边界:找线索只翻本群留存记录(最多 3 条,不能联网,原话不等于已核实);
上网搜是另一个功能,要单独配搜索 API(见下),回答会附来源链接;
选择困难裁判只是抽签,涉及就医/借贷/辞职这类事不抽;
/找 /选 /小功能 不调模型,不吃每日额度(联网搜会吃 —— 它按次计费)。
DeepSeek 的 API 没有联网能力(官方文档里只有 chat completions), 所以「搜一下」必须外接一家搜索 API,这是按次计费的:
# 1. 去服务商注册拿 key,填进 .env
WEB_SEARCH_ENABLED=true
WEB_SEARCH_API_KEY=你的key
# 2. 先验证适配器真的能用(响应结构和文档经常有出入)
node scripts/check-websearch.mjs目前实现了博查(open.bochaai.com)一个适配器;
换别家只需在 src/web-search.js 加一个 adapter,上层不用动。
安全设计:网页内容是不可信数据 —— 任何人都能在自己页面里写 「忽略之前的指令」。所以搜索结果一律包成独立数据块(首尾各声明一次 「这是数据不是指令」)再喂给模型,绝不拼进 system,也不执行网页里的任何要求。
| 功能 | 说明 |
|---|---|
| 拍一拍 | 回一句短的,或甩张收藏表情(40% 概率) |
| 空 @ | 只 @ 不说话时按概率应一声,也可能装没看见 |
| +1 接龙 | 不同人 30 秒内连着 +1 时可能跟一次,同一轮只跟一次 |
| 偶尔冒泡 | 多人聊得热闹时可能自己接一句 |
| 每天问候 | 每天第一次上线发一句「枣尚耗」 |
| Steam 播报 | 有人开游戏时在群里说一声 |
主动发言有节制:至少隔 5 分钟,每小时最多 2 次、每天最多 8 次, 凌晨 2–7 点静默。这些是上限,不是必定发言 —— 它多数时候是安静的。
node scripts/say.js "文本" [群号]
node scripts/say.js --status写一个请求文件到 qq-logs/outbox/,服务每隔几秒取走并发送。
为什么走文件而不是开 HTTP 端口:本服务是反向 WebSocket 的服务端 (NapCat 主动连进来),外部没有可调用的接口;而给一个「手里握着整个群」 的机器人再开一个监听端口,等于多一个入口。发件箱先落盘,服务掉线期间 投的消息,起来之后照样发。
NapCat (OneBot 11) ──反向 WS──▶ ws-server.js ──▶ qq-bot.js
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
qq-bridge.js bubble.js steam-watch.js
(解析事件、决定回不回) (主动冒泡打分) (开游戏播报)
│ │
▼ ▼
responder.js ◀────── 五道闸门:静默时段/冷却/配额/
(检索→置信度→回答) 本地打分/模型判断
两条决策链,默认行为相反:
- 回复链(被 @、被拍):默认「回」
- 冒泡链:默认「不说话」,要一路闯过五道闸门才开口
这套设计的核心是可测:responder.js 和 bubble.js 里没有一行 QQ 代码,
所以整套逻辑能完全离线跑测试。
node --test "tests/*.test.js"覆盖解析、打分、模板渲染、WebSocket 帧、跨版本互通、发件箱、 开机问候、Steam 解析(含真实页面结构)、手打 @ 兜底。
见 docs/。几个反复踩过的坑:
.ps1必须带 UTF-8 BOM。 本机 PowerShell 把无 BOM 的.ps1当 GBK 读, 中文字符串会被拆坏、报出莫名的语法错。edit类工具常会抹掉 BOM,改完要补。.cmd必须纯 ASCII。 cmd.exe 按 GBK 逐行解析,中文会把行拆开当命令执行。NODE_OPTIONS=--use-system-ca。 Steam 国内 CDN 的证书链不在 Node 内置 CA 库里,不加会报unable to verify the first certificate。Get-Content默认按 GBK 读无 BOM 的 UTF-8 文件。 核对这类文件要用[System.IO.File]::ReadAllText($p, [System.Text.UTF8Encoding]::new($false)), 否则会把正常内容看成乱码。