9178159/dsh-napcat-bot

dsh-napcat-bot

DeepSeek Harness 插件:内置 NapCat 的 QQ 机器人(识图 / 智能语音 / 随缘主动发言 / 联网搜索)

Uncategorizeddeepseek-harnessdshdsh-market

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 秒只写一条,插件不崩、不刷屏。

验收

  1. 发「发个语音说你好」→ 直接发 QQ 语音,不经过模型,不会出现「鱼鱼不会按按钮」。
  2. 发「一车肥鱼」表情包 → 图片随对话进 V4.1-Flash,小鱼语气回复(日志有 [VISION] 原生多模态 · 图 1 张 + [MODEL] multimodal)。
  3. 不用填任何识图地址 / 识图 Key —— 用的就是 DSH 当前选择的主模型。

2.3.1:专项修复(强制语音只回文字 / 发图不识图)

真因(看日志就能确认)

  1. 强制语音只回文字:两个原因叠加 ——
  • ttsUrl 是空的,plan() 判定「无引擎」→ 只发文字;
  • 指令走的是「先让模型说话,再决定发不发语音」,模型先回了「鱼鱼这小短鳍按不来按钮啦」,推脱话术检查里没覆盖「按不来」,于是这条推脱被当正文发出去了。
  1. 发图不识图: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 强制语音(内置关键词库,不用配)

只要这句话里命中下列任一说法,优先级最高:跳过模式(哪怕模式是「关闭」)、跳过随机概率,直接合成语音发出去。

发语音 发句语音 发条语音 发个语音 发一段语音 来句语音 来条语音 来段语音 语音说 用语音 语音回复 语音回答 语音念 语音读 说出来 念出来 读出来 念一下 读一下 说给我听 念给我听 唱一个

例:发「发个语音说你好」→ 直接发语音。

防推脱三层保险:

  1. 生成前给模型注入定向提示:你能直接发语音,禁止回答「不会发语音 / 按不了按钮 / 没法发」;
  2. 回复里仍出现推脱话术 → 带更硬指令自动重试一次;
  3. 还在推脱 → 清洗掉推脱句,语音照发(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 只能你自己填(这个没法内置)。