dsh-napcat-bot
DeepSeek Harness (DSH) 插件:内置 NapCat(随 tgz 一起分发),启用即自动启动、监听 6099,并且通过 NapCat 官方 WebUI API 自动开启 OneBot 正向 HTTP + 正向 WS,WS 断线自动重连、进程异常自动拉起,让 AI 通过 QQ 私聊 / 群聊与真人对话。
开箱即用:安装并启用插件 → 重启 DSH → 打开侧边【设置 → 集成 → NapCat QQ 机器人】→ 在「NapCat WebUI」标签页扫码登录 QQ → 完事。 接口地址、端口、OneBot access_token、WebUI 管理 Token 全部由插件自动生成与写入,不用手填、不用手抄、不用调试连接。 OneBot WS 固定
ws://127.0.0.1:3001;如果历史配置被写成了 6099,插件会自动纠正并落盘。零外部 npm 依赖、可离线运行;不带
pnpm-lock.yaml,不会触发 pnpm 供应链校验。
安装
- 推荐:从 Releases 下载
dsh-napcat-bot-x.y.z.tgz,在 DSH 插件市场「导入本地包」里选择它。 - 源码:本仓库不含
vendor/(内置 NapCat 本体约 111MB 的第三方二进制,用 Release 分发更合适)。 clone 后可直接读改lib/;要打包成可安装 tgz,请把 NapCat 解压到vendor/napcat/再tar -czf。
装好必须完全退出 DSH 再启动:服务端
lib/index.js只在启动时加载一次。
2.8.0:联网搜索(手动 + 不懂自动查)
搜索后端直接用 DSH 自带的 ctx.web.search()(dsh-base 已挂 @deepseek-ai/dsh-web +
dsh-web-search-deepseek,走 DeepSeek 原生搜索),所以不需要另填搜索接口和 Key;面板也支持填 SearXNG 等自定义接口。
两类触发
| 类型 | 条件 | 例子 |
|---|---|---|
| 手动(原有) | 命中关键词:帮我搜 / 搜一下 / 查一下 / 上网查 / 搜索 |
搜一下今天金价 |
| 自动(新增) | 本地规则先判:时效信息(新闻/价格/时刻/表/发售/比分)、现实实体(地点/参数/规格/教程/怎么设置)、带疑问但拿不准;拿不准时再让模型回一个词确认 | 今天金价多少?(没说搜,也去查) |
不搜的情况:纯闲聊、情绪陪伴(想你了/抱抱/晚安)、玩梗(哈哈哈/笑死/这梗)、主观感受、人设对话(你叫什么)、 以及去掉标点后不足 4 个字的语气词。
优先级
强制语音 > 手动搜索 > 自动搜索 > 识图解读 > 普通闲聊
所以「发个语音说你好」永远直接朗读、不会去搜;发图 + 「搜一下这是啥」会先让主模型用一句短话点出图里是什么,再拿这句话去搜。
兜底与容错
- 搜索失败 / 无结果 → 回
呜,鱼鱼的小鳍划不动搜索引擎,暂时查不到这个哦(手动、自动同一套)。 - 没有可用搜索后端时(宿主没挂 web 且没填自定义接口)→ 直接跳过搜索、照常回答,不会突然回「查不到」。
- 所有搜索都 try/catch,超时 20 秒、同类查询 10 分钟内走缓存,插件不崩不刷屏。
配置 / 规则 / 日志
- 面板「联网搜索」:总开关、不懂自动搜索(默认开)、自动搜索前模型二次确认、自定义接口地址、Key、每次取几条。
- 管理 → 规则:可对单个私聊/群单独「不联网」或「只手动搜」。
- 日志:
[SEARCH] manual:<关键词>/auto:<原因>/ok provider=… n=… ms/fail …/skip <原因>/image-hint <提词>。
2.7.0:识图默认「读梗接话」,下指令才描述画面
以前默认提示词是「简短描述这张图片内容」——本质是在让模型看图汇报,发张表情包回来一段画面说明,很出戏。 2.7.0 把两种模式分开,优先级由你这句话决定:
| 你说的话 | 模式 | 模型被要求做什么 |
|---|---|---|
| 只发图 / 「哈哈哈哈」/「你看这个」 | 接梗模式(默认) | 禁止描述物体、角色、文字、构图;只判断你发图的意图,读懂梗和情绪,直接接梗互动(例:呜!你好坏,居然喊一车肥鱼来欺负我🥺) |
| 「描述一下这张图」「说说图片里面有什么」「图里长啥样」「详细讲讲画面」 | 描述模式 | 客观描述画面:元素、角色、文字(有就念出来)、构图 |
- 命中词表在
lib/vision.js的DESCRIBE_KEYWORDS,只收「明确针对图片内容」的说法,所以「你长啥样呀」不会误触发。 - 描述模式的提示词是硬编码的
DESCRIBE_PROMPT,不受面板提示词影响;面板里的「识图提示词(默认模式)」只管接梗模式。 - 兜底与容错:默认模式若模型仍在「图片里有…」式汇报,会自动带更硬指令重试一次;重试仍汇报就照发(不冷场、不死循环)。
- 历史默认提示词(「简短描述这张图片内容」等)在
__schemaVersion=10迁移时自动换成新默认,用户自己改过的提示词保留。
其余链路一律没动:图片仍走 ctx.attachments 附件引用 + V4.1-Flash 原生视觉(同一条 user 消息里 text + image),识图开关、规则面板按会话覆盖、TTS 四种模式与强制语音、随缘主动发言、记忆、NapCat、风控全部原样。
新增日志:[VISION] mode: meme(默认接梗,不报画面) / describe:<命中词> / report-retry。
2.6.0:主动发言改成「随缘」(去掉倒计时与次数上限,只留冷却)
2.5.0 是「安静满 N 秒 → 说一条 → 你必须回话才会有下一条」,本质还是个定时器,而且被「一轮一条」卡住。 2.6.0 换成随机心情判定:
| 维度 | 现在的行为 |
|---|---|
| 触发方式 | 安静下来后,每隔 20~45 秒(带抖动)做一次心情判定,没有固定倒计时 |
| 次数 | 完全取消上限(没有每日/每轮封顶),冷却过完、心情到了就来找你 |
| 概率 | 每次判定先抽「心情档位」(12% 忽然很想你 p=0.45 / 43% 一般 p=0.18 / 45% 懒得开口 p=0.07),再掷一次是否命中 —— 所以节奏天然不均匀,可能一晚上两三次,也可能一整天不吭声 |
| 冷却 | 发出一条后强制冷却(默认 180 秒,面板滑块 60~1800),冷却期内绝不再发 |
| 对话中 | 正在生成回复、或刚说过话(冷却内)→ 一律不插话 |
| 告别 | 收到「晚安 / 先睡了 / 先不聊了 / 晚点再说 / 拜拜…」→ 暂停随机触发,直到你重新发消息才恢复 |
| 群聊 | 默认关闭,只在私聊;规则面板可给某个群单独打开 |
| 内容 | 主模型 + 面板人设 + 最近 12 条记忆生成,短句优先;方向含「想念 / 关心 / 接旧话题 / 小鱼碎碎念」,不是固定句子 |
| 语音 | 主动那句照样走「想发就发」随机判定 |
| 失败 | 模型空回复 / 报错 → 一个字都不发 |
面板
AI 配置 → 主动发言:主动找我(随缘)开关(默认开)、冷却间隔滑块(60~1800 秒,默认 180)、群聊也允许。
状态行:主动发言:开启(随缘)· 冷却 180 秒 · 待机 1 个会话 · 已主动 3 · 没心情 12 · 收尾暂停 1。
日志
| 日志 | 内容 |
|---|---|
主动发言 待机 |
冷却多少秒后开始随缘判定 |
主动发言 触发 |
触发时间、空闲多久、p=命中概率 roll=骰子、进入多少秒冷却 |
主动发言 跳过 / 顺延 / 停表 |
没心情、正在回复、收尾语句 |
[PROACTIVE] |
主动发出的那句话原文 |
2.5.0:空闲主动发言(会接话,不会刷屏)
旧版是「每 N 分钟给最近活跃私聊群发一句问候」——那套已经删掉,生硬、会打断人、还容易刷屏。 新版走空闲触发 + 上下文生成:
| 维度 | 行为 |
|---|---|
| 触发 | 最后一条消息之后静默满 N 秒才考虑开口(默认 120 秒,面板滑块 30~600) |
| 说什么 | 把「刚刚聊了什么 + 对方最后一句」交给主模型,按面板人设写成一句短话;不是固定句子 |
| 频率 | 同一段空闲最多一条;你回话后重新计时,进入新一轮 |
| 边界 | 正在生成回复时到点 → 顺延,绝不插话;你说「晚安 / 先不聊了 / 拜拜」→ 直接停表 |
| 群聊 | 默认关闭,只在私聊生效;规则面板可对某个群单独打开 |
| 语音 | 主动那句照样进「想发就发」随机判定,有心情就出声 |
| 失败 | 模型没给出内容 / 报错 → 一个字都不发(宁可不吭声,也不发套话) |
面板
AI 配置 → 主动发言:开关(默认开)、空闲等待时长(滑块 30~600 秒,默认 120)、群聊也允许。
状态行会显示:主动发言:开启 · 空闲 120 秒触发 · 计时中 1 个会话(最近 98 秒后)· 已主动 3。
管理 → 规则 新增「主动发言(本会话覆盖)」:跟随全局 / 允许 / 本会话不主动 —— 可以给某个群开、给某个私聊关。
日志
| 前缀 | 内容 |
|---|---|
[napcat] 主动发言 计时中 |
什么时候排的计时、多久后检查、最后一句是什么 |
[napcat] 主动发言 已发送 |
空闲了多久才开口、之后重置计时 |
[napcat] 主动发言 跳过 / 顺延 / 停表 |
为什么没说话(未开启、已说过一条、正在回复、收尾语句) |
[PROACTIVE] |
主动发出的那句话原文(在「消息收发记录」里能看到) |
2.4.0:识图改用主模型原生视觉(不再配两套模型)
为什么能这么干:DSH 的模型目录里 deepseek-flash(= DeepSeek-V4.1-Flash)本身就声明了
inputModalities: ["text","image"],视觉是主模型自带能力,没必要再挂一个独立的识图接口。
做法
| 旧(≤2.3.1) | 新(2.4.0) |
|---|---|
| 面板填「识图接口地址 + 识图 API Key + 识图模型名」 | 三个输入框全部删除,复用 DSH 里已填好的主模型与密钥 |
| 先调识图接口拿一句描述,再把描述拼进 prompt | 图片和文本同一次请求进主模型:[{type:'text'}, {type:'image', attachment}] |
| 两套模型、两套密钥、两份计费 | 一套配置,文字和看图同一个模型 |
技术要点:QQ 图片先下载成字节 → ctx.attachments.saveImage() 存进 DSH 附件库拿
ImageAttachmentRef → 以 {type:'image', attachment} 进 user 消息;宿主的 deepseek 适配器
会自己 readImageRequest() 转成 base64 图片块发出去。直接塞 url 是不行的(适配器会判为
不支持的块,整条请求失败),所以必须走附件引用。
保留的东西
- 识图总开关(默认开)、识图提示词(默认「简短描述这张图片内容」,可改、持久化)。
- 规则面板仍可按群/私聊单独关识图、单独调语音概率。
- 识图回复照常进 TTS 判定(全部 / 关键词 / 性格随机 / 强制语音)。
- 强制语音仍是绕过 LLM 的预处理拦截:
发个语音说你好直接抠出「你好」合成发送。 - 识图关闭 / 图片取不到 / 类型不支持 / 附件服务不可用 / 主模型报错 → 一律走人设兜底台词。
日志
| 前缀 | 内容 |
|---|---|
[FORCE] |
命中哪个强制语音词、抠出的朗读文本(或「交给模型起稿」) |
[VISION] |
原生多模态 · 图 N 张 或 fallback(原因);原因含 download / unsupported-type / no-url / no-attachment-service |
[MODEL] |
multimodal · 带图 N 张请求主模型 · 返回 M 字 |
[TTS] |
synth-ok(引擎 + 音色 + 耗时)/ fail / skip |
同类报错 30 秒只写一条,插件不崩、不刷屏。
验收
- 发「发个语音说你好」→ 直接发 QQ 语音,不经过模型,不会出现「鱼鱼不会按按钮」。
- 发「一车肥鱼」表情包 → 图片随对话进 V4.1-Flash,小鱼语气回复(日志有
[VISION] 原生多模态 · 图 1 张+[MODEL] multimodal)。 - 不用填任何识图地址 / 识图 Key —— 用的就是 DSH 当前选择的主模型。
2.3.1:专项修复(强制语音只回文字 / 发图不识图)
真因(看日志就能确认)
- 强制语音只回文字:两个原因叠加 ——
ttsUrl是空的,plan()判定「无引擎」→ 只发文字;- 指令走的是「先让模型说话,再决定发不发语音」,模型先回了「鱼鱼这小短鳍按不来按钮啦」,推脱话术检查里没覆盖「按不来」,于是这条推脱被当正文发出去了。
- 发图不识图:
visionEndpoint是空的 → 直接no-endpoint兜底。
修法
① 强制语音改成「消息底层预处理拦截」,绕过 LLM
消息进来先解析指令,命中 发语音 / 发句语音 / 语音说xxx / 说出来 … 就把要念的正文抠出来,直接合成语音发送,完全不调模型(从根上不可能再推脱)。
- 「发个语音说你好」→ 朗读文本 =
你好→ 直接发语音; - 「语音说你今天怎么样」→ 朗读文本 =
你今天怎么样; - 只说「发语音」没给内容 → 才让模型起稿,但仍强制语音,并注入「禁止说不会发语音/按不了按钮」+ 回复里出现推脱就重试/清洗;
- 带图 + 强制语音 → 不走抠文本,走识图 + 人设起稿 + 强制语音(否则会把「这是啥」念出来)。
- 优先级最高:
ttsMode=off、随机 0% 也照发。
② 内置离线语音引擎(真开箱即用) 没填 TTS 接口地址时,自动用 Windows 自带语音(SAPI)合成 WAV → 交 NapCat 发语音。零依赖、无需联网、无需 Key;本机实测 3.16 秒语音合成耗时 ~2.4 秒。填了接口则以接口为准。可在面板关闭(「接口没填时用系统内置语音」)或指定音色(Huihui / Xiaoxiao …)。
③ 识图接口自动预置
默认 visionEndpoint = zhipu(智谱,glm-4v-flash 有免费额度),模型名自动补,提示词默认「简短描述图片内容,不要冗长」,开关默认开 —— 只需要补一个 API Key 就能识图(面板会显示「缺 API Key → 补上即可识图」)。地址/提示词都能在界面改,保存即持久化。
④ 日志(面板「消息收发记录」直接看)
| 前缀 | 内容 |
|---|---|
[FORCE] |
是否命中强制语音、命中词、抠出的朗读文本 |
[VISION] |
是否触发识图、ok/fallback、原因(no-endpoint / auth / http-500 / no-url …)、识别到的内容 |
[TTS] |
synth-ok(引擎 http/local + 音色 + 耗时)/ fail / skip(含 no-engine 原因) |
同类报错 30 秒内只写一条,不刷屏;所有异常都降级为文字,插件不崩。
升级到 2.3.1 后必须完全退出 DSH 再启动。
2.3.0:强制语音指令 + 识图默认开箱即用
一、TTS 强制语音(内置关键词库,不用配)
只要这句话里命中下列任一说法,优先级最高:跳过模式(哪怕模式是「关闭」)、跳过随机概率,直接合成语音发出去。
发语音 发句语音 发条语音 发个语音 发一段语音 来句语音 来条语音 来段语音 语音说 用语音 语音回复 语音回答 语音念 语音读 说出来 念出来 读出来 念一下 读一下 说给我听 念给我听 唱一个
例:发「发个语音说你好」→ 直接发语音。
防推脱三层保险:
- 生成前给模型注入定向提示:你能直接发语音,禁止回答「不会发语音 / 按不了按钮 / 没法发」;
- 回复里仍出现推脱话术 → 带更硬指令自动重试一次;
- 还在推脱 → 清洗掉推脱句,语音照发(
messages.log记一行TTS retry)。
二、识图默认配置
- 识图开关默认开启;识图提示词默认「简短描述图片内容,不要冗长」(老配置里的旧默认值会自动迁移)。
- 接口地址支持预设短名,只填一个词就行,模型名会自动补:
| 填这个 | 实际地址 | 自动模型 |
|---|---|---|
zhipu |
open.bigmodel.cn/api/paas/v4/chat/completions | glm-4v-flash(有免费额度) |
dashscope |
dashscope.aliyuncs.com/compatible-mode/v1/chat/completions | qwen-vl-max |
openai |
api.openai.com/v1/chat/completions | gpt-4o-mini |
siliconflow |
api.siliconflow.cn/v1/chat/completions | Qwen/Qwen2.5-VL-72B-Instruct |
moonshot |
api.moonshot.cn/v1/chat/completions | moonshot-v1-8k-vision-preview |
openrouter |
openrouter.ai/api/v1/chat/completions | google/gemini-2.0-flash-exp:free |
ollama |
127.0.0.1:11434/v1/chat/completions | llava |
也可以填任意完整地址(.../chat/completions)或只填 base 地址(https://api.openai.com/v1,会自动补 /chat/completions),自定义完整地址不会被乱拼。
API Key 只能你自己填(这个没法内置)。
三、联动
- 识图产出的回复若没有命中强制语音,正常走「心情随机语音」掷骰子。
- 强制语音与随机模式互不干扰:随机 0% 时普通消息不发语音,但「发语音」指令照样发。
- 规则面板可对单个群/私聊单独关闭识图、单独设语音概率(规则 > 全局)。
- 面板「人设已生效」下方能看到:语音 已发/跳过/强制/失败、识图 成功/失败 + 上一张图内容。
四、容错
TTS / 识图 超时、401、5xx、返回空 → 一律降级为文字(或人设兜底台词),只写日志,插件不崩;同类报错 30 秒内只记一条,不刷屏。所有配置保存后立即生效,重启自动加载。
升级到 2.3.0 后必须完全退出 DSH 再启动。
2.2.0:识图(看图 → 用人设说话)
面板 AI 配置 → 识图(多模态):
| 配置项 | 说明 |
|---|---|
| 识图功能总开关 | 默认开启。关闭后收到图片只回一句人设兜底台词 |
| 多模态模型接口地址 | OpenAI 兼容的 /chat/completions,如 https://api.openai.com/v1/chat/completions |
| 识图 API Key | 以 Bearer 发送;留空则不带鉴权头 |
| 多模态模型名 | 如 gpt-4o-mini / qwen-vl-max / glm-4v-flash(必须支持图片输入) |
| 识图提示词 | 默认「描述这张图片,简短一点」 |
链路:QQ 图片 → 从 NapCat 消息里拿直链(只有文件名时用 OneBot get_image 换)→ 多模态接口得到一句描述 → 描述作为上下文交给主模型 → 按面板人设的语气重新组织语言(不是冷冰冰的客观描述)。
例:识图得到「一整车肥鱼表情包」→ 大肥鱼会说「哇!一整车都是肥鱼小伙伴,浩浩荡荡的,也太热闹啦🥳」。
- 兜底:识图关闭 / 没填接口 / 超时 / 密钥错 / 请求报错 / 返回空 → 直接回人设台词: 「呜…有点糊糊的,鱼鱼眼睛小,得凑很近才看得清一点点。要不你讲讲图里是啥?我慢慢听~」 (这条台词整句一次发出,不拆段、不刷屏;同类报错 30 秒内只写一条日志。)
- 和随机语音联动:识图回复与兜底台词都走同一条投递管道,所以照样按 TTS 模式掷骰子,可能变成语音。
- 按会话覆盖:管理 → 规则可对某个私聊/群聊单独「关闭识图」。
- 纯图片消息也能收到:以前没有文字的消息会被当作空消息丢掉,现在只要带图片就放行。
- 同一张图 10 分钟内复用识别结果(有缓存,不重复烧 token)。
- 面板「人设已生效」下方会显示识图状态、成功/失败次数和上一张图识别到的内容。
升级到 2.2.0 后必须完全退出 DSH 再启动。
2.1.0:TTS 语音「性格随机模式(想发就发)」
面板 AI 配置 → 语音(TTS) 区域,模式四选一:
| 模式 | 行为 |
|---|---|
关闭 |
只发文字 |
全部消息转语音 |
每条回复都发语音(回复超过「单条语音最长字数」时自动补发文字,信息不丢) |
关键词触发 |
用户或回复里出现触发词(默认「语音 / 发语音 / 说给我听 / 念一下」)才发语音 |
性格随机模式(想发就发) |
每轮先掷骰子,命中发语音,没命中只发文字 |
随机模式细节
⚠️ 语音要真的出声,必须填「TTS 接口地址」。接口只需要
POST {text, voice}返回音频二进制或{"audio_base64":"..."};OpenAI 兼容的/v1/audio/speech也能直接用(此时再填「TTS 模型名」)。没填接口时面板会显示「未填接口地址」,行为等同只发文字。
- 概率滑动条 0~100%,默认 40%;0 = 永不发,100 = 次次发。是每轮独立掷骰,不是固定间隔,所以出现「有时出声、有时懒得开口」的随性节奏。
- 人设智能权重(默认开):短句(≤12 字)权重 ×1.3、活泼语气再往上抬;长段解释(>120 字)×0.72、emotion 低落词(难过/累/emo/哭…)再压;人设里带「懒 / 瘫 / 佛系 / 慢悠悠」整体 ×0.9。边界仍然严格:0 还是 0,100 还是 100。
- 随机音色(默认开):每次从音色池(默认
软萌少女,甜妹,奶音,软糯小助手,可改)里随机挑,每轮都可能不一样;关掉则固定用「固定音色」。 - 兜底:TTS 接口超时/报错/返回空/发送失败 → 自动降级只发文字,写
messages.log,插件不崩;如果是「只发语音」模式且发送失败,会补发文字保证用户收到内容。 - 缓存:同文本+同音色命中内存缓存(TTL 10 分钟、上限 60 条,自动淘汰)。音频全程 base64 直发 NapCat,不落盘,所以没有残留文件需要清理。
- 按会话覆盖:管理 → 规则里可以对某个群/某个私聊单独设
语音模式 / 概率 / 随机音色(留空 = 跟随全局)。 - 面板「人设已生效」下方会显示:语音模式、概率、已发 / 跳过 / 失败次数、上一句用的音色 —— 用来确认「确实是想发就发」。
升级到 2.1.0 后必须完全退出 DSH 再启动。旧配置里
ttsEnabled=true会自动迁移成ttsMode='all',行为不变。
2.0.1(紧急修复):人格不生效 / 固定输出套话
现象:面板里填了「大肥鱼」,问“你叫什么名字”却固定回一句
我觉得这个得看具体情况~你先说说细节,我再给你个靠谱的答复。
两个真实原因(都已修):
- 代码里有一段硬编码兜底话术(离线降级时输出)。模型调用一失败就会吐出这句, 看起来就像“人格完全没生效”。现在这句连同整套硬编码兜底已彻底删除: 模型不可用时就用人设推导一句身份回答(如「我是大肥鱼呀~…」),推导不出来就不发消息, 绝不敷衍一句模板话。
- system 提示词用错了传参方式。以前把人格塞在
llm.stream({ system })这个字段里, 而 DSH 的模型协议要求 system 作为role:'system'的消息进入messages;字段被忽略, 等于人格根本没送到模型。现在 system 以标准消息发送,面板文本一字不改地成为唯一 system。 - 顺带修掉「模型返回空内容」→ 之前
maxTokens=800时,模型的思维链会把预算吃光, 可见文本为空,于是又触发第 1 条的兜底话术。现在maxTokens=2000,并且空内容会自动 以reasoningEffort:'off'重试一次。
现在的规则:
- 面板填了人格 → 它就是唯一的 system(不加前缀、不追加任何“最高优先级”之类的说明文字,也不放风格模板);
- 面板留空 → 完全不发 system,不自动补任何文字;
- 问身份(你叫什么 / 你是谁 / 你会做什么)→ 额外给一条定向指令:直接按人设第一人称回答, 禁止“看具体情况、你先说说细节”式回避;万一模型还是回避,会自动带硬指令重试一次;
- 上下文(历史 / 摘要 / 知识库)和排版要求全部挪到 user 消息里,不再污染 system。
排障:面板「人设已生效」下方会显示回复引擎、上次生成成功/失败以及具体报错;
明细写入 ~/.dsh/napcat-bot/engine.log,也可通过 GET /napcat-bot/api/napcat/log?source=engine 查看。
升级到 2.0.1 后必须完全退出 DSH 再启动(服务端代码只在启动时加载)。
三步验收
- 导入
dsh-napcat-bot-2.8.0.tgz→ 完全退出并重启 DSH(服务端代码只在启动时加载)→ 启用插件 → 日志出现「内置 NapCat 已就绪,WebUI 监听 127.0.0.1:6099」。 - 设置页左侧【集成】里点「NapCat QQ 机器人」→ 第二个标签页「NapCat WebUI」已自动带上管理 Token,直接扫码登录。
- 三个状态灯全绿(NapCat 进程 ✓ / WS 已连接 ✓ / 可以收发消息 ✓)后,私聊发消息、群里 @ 机器人即可收到 AI 回复。
端口分工(1.5.0 起强制纠正,不再可能连错)
| 用途 | 地址 | 说明 |
|---|---|---|
| NapCat WebUI(管理面) | http://127.0.0.1:6099/webui |
只用来扫码登录 QQ、看 NapCat 自己的面板。它不是 OneBot 端口 |
| OneBot 正向 WS(收消息) | ws://127.0.0.1:3001 |
插件主通道。连上后 NapCat 的「WS 客户端数」显示 1 |
| OneBot HTTP(发消息) | http://127.0.0.1:3000 |
能开就用;没开也不影响,插件会自动改走 WS 通道发送 |
历史配置里如果 napcatWsUrl 被写成了 6099(把管理面当成了 OneBot 端口),插件启动时会
自动改回 3001 并落盘,日志里会有一行 已修正 OneBot 端点:WS ws://127.0.0.1:6099 → ws://127.0.0.1:3001。
面板顶部也会把这三个地址并排列出来,排障时一眼能确认连的是哪个口。
⚠️ 关键:QQ 登录 ≠ WS 自动连上
这是本插件踩过的最深的坑,1.4.0 修了配置生效问题,1.5.0 又补了两个:
坑 0:WS 连到 6099 上(1.5.0 修复)
6099 是 NapCat 的管理面(HTTP 页面 + WebUI API),不在上面跑 OneBot 协议。
连它只会被拒或无响应,表现就是「插件 WS 状态未知/断开、NapCat 里 WS 客户端数一直是 0」。
现在 normalizeWsUrl() 会把「空值 / 等于 6099 / 等于 HTTP 端口」的地址一律纠正成 ws://127.0.0.1:3001,
配置层、WS 客户端层、面板展示层三处都过一遍,不可能再漏。
坑 1:改配置文件不生效(1.4.0 修复)
直接改 config/onebot11_<QQ>.json 并不能保证生效。 实测 NapCat 4.18.41 经常不热加载该文件,
甚至在自身保存配置时把 websocketServers 条目丢掉——结果就是「QQ 明明登录了,3001 却没在监听」。
所以插件改用 NapCat 自己的 WebUI HTTP API 来配置 OneBot 网络:
POST /api/auth/login { hash = sha256(webuiToken + ".napcat") } → data.Credential
POST /api/OB11Config/SetConfig { config: "<json 文本>" } // NapCat 内部 save + reloadNetwork
SetConfig 会让 NapCat 真正重启网络适配器,3001 立刻开始监听,不需要重启 NapCat、不需要重新扫码。
插件在「WebUI 就绪」「QQ 登录成功」「发现 3001 未监听」三个时机自动调用它,失败则每 6 秒重试。
坑 2:OneBot HTTP 必须打到具名路径(1.4.0 修复)
NapCat 的 HTTP 适配器不按 body 里的 action 字段分发。往根路径 POST http://127.0.0.1:3000
只会得到一个桩响应:
{"status":"ok","retcode":0,"data":{},"message":"NapCat4 Is Running"}
于是 get_login_info 永远拿不到 QQ 号(面板显示「未登录」),send_private_msg / send_group_msg
变成空操作(消息永远发不出去却也不报错)。
正确写法是 POST 到 /<action>。1.5.0 更进一步:HTTP 不通时 action 会自动走正向 WS(同一连接支持 echo 应答),
所以「NapCat 只开了 WS、没开 HTTP」也能正常收发。
两处 Token(插件已全自动,这里只是说明区别)
| Token | 用途 | 谁生成 |
|---|---|---|
| WebUI 管理 Token | 登录 http://127.0.0.1:6099/webui 这个管理页面 |
插件启动前写入 config/webui.json,随后从 NapCat 启动日志([WebUi] WebUi Token: xxx)自动读取校验;内嵌页面自动附加,无需手输 |
| OneBot access_token | 插件 ↔ NapCat 通信用(HTTP Bearer / WS query),真正收发 QQ 消息 | 插件首次启用时随机生成,自动写进插件配置与 NapCat 的 onebot11_<QQ>.json |
两者完全独立:WebUI Token 只影响能否打开管理页面;OneBot token 只影响插件能否调 NapCat 的 API。
自动写入的 NapCat 配置
插件启动内置 NapCat 前会主动写配置文件(作为兜底),并在运行期用官方 API 校正:
config/webui.json:host=127.0.0.1、port=6099、带 token、不禁用 WebUI。config/onebot11_<QQ>.json(对历史登录过的 QQ 号全部预置):- 正向 HTTP(默认
3000)—— 插件用它发消息、查登录态; - 正向 WS(默认
3001)—— 插件用它收消息; - 两者使用同一个自动生成的 token(与插件配置一致,不一致会直接 403 → 全链路静默);
reportSelfMessage=false,避免机器人收到自己发的消息形成自问自答。
- 正向 HTTP(默认
状态灯与自动重连(1.5.0)
面板顶部固定三个灯,语义与 NapCat 侧对得上:
| 灯 | 含义 |
|---|---|
| 🟢 NapCat 进程已启动 | 内置 NapCat 在跑(按 6099 端口实际监听判定,不是看状态文件) |
| 🟡 WS 正在尝试连接 | 插件正在重连 ws://127.0.0.1:3001;旁边显示第几次重试、下次重试倒计时 |
| 🔴 WS 连接失败 | 重连次数用尽(wsMaxRetries>0 时)或环境不支持 WebSocket |
| 🟢✓ 可以收发消息 | WS 已连接 且 3001 在监听 且 QQ 已登录 |
灯下面一行会直接显示 WS 目标:ws://127.0.0.1:3001(端口 3001),并标注 客户端数 1,
可以和 NapCat WebUI 里的「WS 客户端数」对照。
重连策略(面板可调):
- 首次重连间隔
wsRetryInterval(默认 2000ms),之后指数退避 2→4→8→15s(上限wsRetryMaxInterval)。 wsMaxRetries默认 0 = 一直重试;填 N 则连续失败 N 次后停止(按钮可手动重连)。- 连接超时 8s 未建立即判失败换下一次(不依赖 onclose——实测 Node 的 WebSocket 在连接被拒时可能只触发 error)。
- 心跳看门狗:连上后
wsHeartbeatTimeout(默认 100s)内收不到任何数据(含心跳)→ 判定半死连接,主动重连。 - NapCat 重启 / QQ 掉线重连后,插件检测到 3001 恢复监听会立即重连,不用手动操作。
- 进程守护:NapCat 进程异常退出(6099 不再监听)→ 状态如实回落成「未运行」,并在 12s 后自动重新拉起; 10 分钟内最多自动拉起 3 次,超过则停在「反复退出」提示,避免把 QQ 拉成重启风暴。
消息应答逻辑(默认即正确)
- 私聊自动回复;群聊默认仅在被 @ 时回复(面板可关)。
- 只处理真人聊天消息:
post_type=message且message_type=private|group; 自动丢弃点赞 / 群成员变动等 notice、心跳 meta_event、加好友请求、机器人自己发的消息(含sub_type=self)、以及纯图片语音等无文字消息。 - 回复时把人设 Prompt + 最近 10 条对话记忆一起喂给模型,输出限制为口语化短句(不用 Markdown、不自称 AI)。
- 回复引擎默认
dsh:走ctx.get('llm')+ctx.get('agentDefaultModel').currentSelection(),用你在 DSH 里选中的模型与凭据,不需要另填 API Key。 - 发送失败会被捕获并显示在面板(
发送失败:...),同时计入「失败」计数。 - 面板顶部有连接指示灯:
未连接 / 连接中 / 已就绪,并显示 QQ 号、收发计数、最近收到时间、错误信息。
面板结构(固定两个标签页)
- AI 配置:
AI 配置内含内置 NapCat 状态卡与启停、连接(全部自动维护,一般不用改)、人格与回复、行为与风控(记忆开关、群内仅 @ 回复、主动发言、冷却与每分钟上限)。 - NapCat WebUI:内嵌 WebUI(自动免 Token 登录,这里扫码登录 QQ)、两类 Token 卡片、NapCat 控制台日志预览(实时刷新 / 暂停 / 只看 Token 行 / 复制日志)。
插件详情页里的配置表单同样保留,两个入口共存。
进程管理
- 启动前先检测:自己的 pid 文件 + 日志里的
Main Process ID+ 6099 端口探测 + 短期互斥锁,绝不重复多开 NapCat / QQ。 - 关闭插件或退出 DSH 时自动停掉由本插件拉起的 NapCat 进程树(面板可关
stopNapCatOnDisable)。 - 面板提供「启动内置 NapCat / 重新启动 / 停止」。
关于 QQ(唯一的外部前提)
NapCat 的工作原理是注入已安装的官方 QQ NT 客户端(NapCatWinBootMain.exe + NapCatWinBootHook.dll)。
系统上有官方 QQ 即可,不需要再单独安装或启动 NapCat。插件按下列顺序探测 QQ:
- 面板里填写的
qqPath; %ProgramFiles%/%ProgramFiles(x86)%/%LOCALAPPDATA%\Programs下的Tencent\QQNT\QQ.exe;C:–F:盘下的Program Files\Tencent\QQNT\QQ.exe、Tencent\QQNT\QQ.exe。
探测不到时面板会明确提示「未检测到 QQ NT」并给出填写入口。
HTTP 面板接口
注意:宿主
webServer的 prefix 匹配是pathname === prefix || pathname.startsWith(prefix + '/'), 因此 prefix 路由的 path 结尾不能带斜杠(早期版本/napcat-bot/api/napcat/就是因为这个永远 404)。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /napcat-bot/api/status |
连接三态、OneBot 登录态、内置进程态、WebUI Token、运行计数、当前回复引擎 |
| GET/POST | /napcat-bot/api/config |
读取 / 保存配置(保存即热重载) |
| GET | /napcat-bot/api/webui |
WebUI 地址 + 自动读取的管理 Token + OneBot token |
| POST | /napcat-bot/api/webui/token |
重新生成 / 指定 WebUI 管理 Token(并重启生效) |
| POST | /napcat-bot/api/napcat/{start|stop|restart} |
内置 NapCat 启停 |
| POST | /napcat-bot/api/napcat/reconnect |
只重连插件侧 WS(不动 NapCat、不踢 QQ) |
| POST | /napcat-bot/api/napcat/apply-onebot |
立即通过 WebUI API 重新应用 OneBot 配置 |
| GET | /napcat-bot/api/napcat/log?tail=N |
控制台日志尾部(含 Token 行) |
| POST | /napcat-bot/api/client-report |
客户端 UI 注册自检回执(排障用) |
排障
| 现象 | 处理 |
|---|---|
| 面板还是旧样子(没有三个灯 / 没有端点行) | 服务端代码只在 DSH 启动时加载。装完新版必须完全退出 DSH 再启动,否则跑的还是内存里的旧版本 |
| 侧边【集成】没有入口 | 看 ~/.dsh/napcat-bot/client-report.json,其中 registered 应含 settings.section(1.5.0 已验证会出现) |
| WS 状态未知 / 断开,NapCat 里客户端数 0 | 1.5.0 已自动纠正到 3001;确认面板「WS 目标」这一行显示的是 ws://127.0.0.1:3001 |
| 状态灯一直「WS 正在尝试连接」 | 点「重新应用 OneBot 配置」;若仍不行看日志里有没有 WebSocket服务: ... 已启动 |
| 显示「未登录」但 QQ 确实在线 | 1.4.0 已修:OneBot HTTP 必须 POST 到 /<action>,打到根路径只会拿到桩响应 |
| 日志面板 404 | 1.3.0 起已修(路由 path 去掉尾斜杠),确认版本 ≥ 1.5.0 |
| 收到消息但发不出去 | 1.5.0 会自动改走 WS 通道发送,不再依赖 3000;若仍失败看面板 发送失败:... 的具体报错 |
| NapCat 进程显示「未运行」 | 1.5.0 按端口真实存活判定,会 12s 后自动拉起(10 分钟内最多 3 次);也可点「启动内置 NapCat」 |
| 回复走离线兜底 | 面板连接卡显示「回复引擎:离线兜底应答」→ DSH 未配置模型,或 llm 服务不可用 |
| 内嵌 WebUI 空白 | 个别版本禁止 iframe 嵌套,点「在浏览器打开」,链接已带 Token |
v2.0.0:AstrBot 风格消息管道
三个标签页
| 标签 | 内容 |
|---|---|
| AI 配置 | 三个状态灯、端点明细、人设生效提示、消息收发记录、全部开关与字段 |
| NapCat WebUI | 内嵌管理页(自动带 token),扫码登录 QQ |
| 管理 | 人格 / 会话 / 规则 / 知识库 / 任务 五个子页 |
消息处理顺序
- 组件解析(文本/图片/@/语音/文件/卡片)→ 过滤(自身、系统、进退群、红包闪照、空消息、纯表情)
- 黑白名单(用户 + 群)→
message_id去重(跨实例共享,2 分钟窗口;另加 1.5s 内容指纹兜底) - 私聊直达;群聊「@ 唤醒 → 免@ 超时(默认 120s,滑动续期)→ 超时静默」
- 会话等待器(多轮流程接管,例如
/idiom成语接龙,接管期间不过模型) - 指令层(见下)→ 上下文(历史 + 摘要压缩 + 知识库检索)→ 模型流式 → 分段发送
会话隔离
- 私聊:
private:${uid},强制独立,不可关闭。 - 群聊:开关
groupUniqueSession(面板「会话策略」里)- 开:
group:${gid}:${uid},每人独立上下文 - 关:
group:${gid},全群共用一套
- 开:
指令
基础:/help /reset /new /stop /stats /sid /history /ls /rename /del /switch /idiom
管理员:/op /deop /ban /unban /model /provider /groupnew
人格优先级
规则里指定的人格(按会话) > 全局人格(人格库) > 面板单人格字段。
全部走 system prompt 最前面,插件内不再内置任何固定人设(旧版的“小深”会在升级时自动清除)。
人格文本支持变量:{{bot_name}} {{group_id}} {{user_id}} {{user_name}} {{time}}。
知识库
本地 BM25 + 字符 bigram 哈希向量余弦的混合检索(0.65 / 0.35 加权),中文优化; 按规则把知识库绑定到会话,命中后自动加进 system 并在回复末尾标注来源。 支持粘贴文本片段(PDF/DOCX 请先转成文本)。
定时任务
target 形如 group:123456 / private:10001,prompt 以 gen: 开头表示先用模型生成文案再发。
任务持久化在 state.json,支持立即执行/删除,重启后继续按间隔触发。
目录结构
lib/index.js 服务端主入口(配置 schema、路由、生命周期、单例守卫)
lib/client.js 面板 UI(AI 配置 / NapCat WebUI / 管理 三个标签)
lib/pipeline.js 消息流水线:解析 → 过滤 → 去重 → 指令 → 识图 → 搜索 → 推理 → 分段/语音
lib/responder.js DSH 模型调用(system 只来自面板人格,三级重试,绝不输出套话)
lib/napcat.js OneBot 连接:HTTP 动作 + 正向 WS + 重连/心跳
lib/napcat-embedded.js 内置 NapCat 进程管理(启动/停止/探活/自动拉起)
lib/vision.js 原生多模态:图片 → 附件引用;两种识图模式
lib/search.js 联网搜索:手动指令 + 自动查证 + 断路器
lib/tts.js TTS 四种模式 + 强制语音 + 音色池 + 缓存
lib/tts-local.js 内置离线语音(Windows SAPI)
lib/proactive.js 主动发言调度(随缘随机、仅冷却)
lib/state.js 人格/规则/定时任务持久化
lib/sessions.js 会话与上下文
lib/kb.js 知识库混合检索
lib/risk.js 限流 / 黑白名单
tools/ 开发期自测脚本(Node 原生跑,无第三方依赖)
No comments yet. Be the first to write one.