dsh-sticker-hub
给 DeepSeek Harness(DSH)装一套表情包库:本地打标、语义检索、自动配图、以及「表情包大战」模式——你发一张,它回一张,一个字都不说。
插件本身不带任何图片,也不带任何密钥。图从哪来、用哪个识图模型、花谁的钱,全在你自己机器上决定。
MIT · 纯 JS 无运行时依赖 · Node ≥ 20 · 在 Windows 11 + DSH 桌面版 + QQ 官方机器人通道上实测
最短路径:装插件 → 设置页填图库目录 →
node bin/sticker-hub.mjs pull→tag→ 在聊天里说「发个摸鱼的表情包」。 没有 QQ 也能用(工具 + GUI 内联图照常);想玩大战才需要图片收发通道。
0. 能干什么
| 能力 | 说明 |
|---|---|
| 本地图库 | 一个目录 + 一份 index-all.md 表格,人可读、可 git 管 |
| 打标 | 用你配的识图模型给新图生成「名字 / 情绪 / 标签 / 什么时候用」 |
| 检索 | 按想表达的意思找图(无语、庆祝、摸鱼),带防复读降权 |
| 发送 | 交给任意「发文件」工具(默认 QQ 的 qqbot_send_file),不回文字 |
| 大战 | 开关 / 触发词 / 连发三张 任一条件进入,之后只回图 |
| GUI 内联图 | 聊天窗口里直接显示表情包(不需要 QQ / IM 通道) |
| 设置页 | DSH 界面「设置 → 表情包库」直接改,不用重启 |
0.1 GUI 里直接发图
插件注册一条只读图片路由:
/sticker-hub/img?p=gallery/xxx.gif ← 查询式
/sticker-hub/img/gallery/xxx.gif ← 前缀式备胎
sticker_show 会把地址拼成同源绝对地址再返回 markdown(),聊天窗口里直接出图,不走 IM 的文件通道。
路由只接受回环地址(127.0.0.1 / ::1),路径锁死在图库目录内。
别在正文里手写
/sticker-hub/img?p=…这种相对地址:前端会把它当成文件附件,显示成一个 md 文件链接而不是图。要绝对地址就用sticker_show(它现读宿主端口,端口漂移也跟着变),或者给它传origin。
0.2 仓库里的 resources/(表情包与标签数据)
Git 仓库比 npm 包多一个 resources/ 目录,给不想从零攒图的人。结构和图库一样:图在一个目录里,索引只有一份。
| 文件 | 内容 |
|---|---|
resources/gallery/ |
18 张现成表情包(我们自己投喂的 + 从 dsh-pet 动作转的透明 GIF),22 MB |
resources/index-all.md |
614 张(已去重)的标签表:文件 / 名字 / 情绪 / 标签 / 什么时候发,人可读 |
resources/_all.json |
同上 JSON 版,每条多带 origin(合并前的路径),脚本和插件读这个 |
resources/tags-synonyms.json |
标签同义词表,74 组,「女仆装 / 女仆 / 女仆头饰」这类合成一个词 |
resources/sources.yaml |
上游图库清单,bin/sticker-hub.mjs pull 用 |
resources/ATTRIBUTION.md |
每个来源的许可(哪些能商用、哪些只能自己 pull) |
它不随 npm 包发布(files 里没列),想用就 clone 仓库取。
只有 resources/gallery/ 那 18 张随仓库分发;另外 596 张只留标签,图靠 pull 从上游拉——上游许可见 ATTRIBUTION.md,其中大肥鱼系 265 张是 CC BY-NC 4.0,别商用。
图库只有一层:library/gallery/。pull 拉下来的图也按内容哈希去重后落进这个目录,不分子目录。
重新生成这个目录:node tools/build-resources.mjs(读 ../stickers,可用 STICKER_LIB 覆盖)。
1. 三个前提
1.1 DSH 装好并且能跑
- 桌面版或 CLI 都行,Node ≥ 20。
- 能打开设置、能装插件(
~/.dsh/profiles/<name>/)。
1.2 接一个能收发图片的 IM 通道(推荐 QQ)
想要「大战」必须有能看图、能发图的通道。QQ 的做法:
打开 https://q.qq.com,注册开发者 → 创建机器人应用(个人开发者可选「沙箱」)。
记下 AppID 与 AppSecret。
「开发管理 → 沙箱配置 → 添加群聊」,把你的测试群号填进去(不填的话机器人进不了群,也搜不到)。
在 DSH 里装通道插件并填凭据:
dsh plugin --profile desktop add @tencent-connect/dsh-qqbot重启 DSH,用手机 QQ 扫码绑定,或直接在
cordis.patch.yml里填appId/appSecret。私聊机器人发一句话,能回话就通了。
没有 QQ 也能用插件:工具和检索照常,只是发不出去(
sticker_send会把文件路径还给你)。
1.3 配一个识图模型
只有「给新图自动打标」需要它。任意 OpenAI 兼容的 vision endpoint 都行:
// 例子:OpenAI
{ "baseUrl": "https://api.openai.com", "model": "gpt-4o-mini" }
// 例子:DMXAPI / 各类中转(价低,注意别把 key 写进仓库)
{ "baseUrl": "https://www.dmxapi.cn", "model": "qwen3.5-flash" }
// 例子:本地 ollama(免费,慢一点)
{ "baseUrl": "http://127.0.0.1:11434", "model": "qwen2.5vl:7b" }
密钥不要写在插件配置里,写一个一行文本文件(例:C:/Users/you/.dsh/vision-key.txt),把路径填给 keyFile。插件只读那一行,日志里也不会打印。
2. 装插件
# 方式 A:直接从 GitHub 装
dsh plugin --profile desktop add github:lukcyspring/dsh-sticker-hub
# 方式 B:从本地目录(开发中,改完代码立刻见效)
cd ~/.dsh/profiles/desktop
npm install /path/to/dsh-sticker-hub --no-save
# 方式 C:发布到 npm 之后
dsh plugin --profile desktop add dsh-sticker-hub
装完重启 DSH。设置里会出现「表情包库」一页。
装不上/装上了但不显示:DSH 的插件要同时出现在 profile
package.json的dependencies和dsh.profile.bundles里,只加依赖不写 bundles,宿主不会去加载它。
3. 拉图库
插件仓库里的 sources.yaml 只记来源,不存图:
sources:
- id: my-source
name: 我的来源(人看的名字)
repo: https://github.com/<owner>/<repo>
subdir: stickers # 仓库里图片所在目录;不填就不拉
license: 见仓库 README # 会原样写进入库条目
note: 备注(可选)
node bin/sticker-hub.mjs pull # 全部拉
node bin/sticker-hub.mjs pull dsh-meme # 只拉一个
node bin/sticker-hub.mjs stats # 看看有多少张
pull 走 GitHub 的 tarball(api.github.com/repos/<owner>/<repo>/tarball),不用装 git;解包用系统自带的 tar。
subdir 没填的条目不拉(图不在仓库里,比如 nekowhale 走自建镜像分发)。
图片落在 ~/.dsh/sticker-hub/library/gallery/(只有这一层,按内容哈希去重),索引是 library/index-all.md。
自己的图:丢进 library/gallery/,再跑一次打标即可;在 QQ 里直接发给它,sticker_learn 也会自动入库。
4. 打标
node bin/sticker-hub.mjs tag --limit 20 # 先试 20 张,看看质量
node bin/sticker-hub.mjs tag # 全量
或者在聊天里让 agent 做:
把
~/Downloads/猫猫搓手.gif存进表情包库
agent 会用 sticker_learn 调识图模型,自动补名字、情绪、标签和「什么时候用」,然后写进 index-all.md。
打标质量的两个经验:
- GIF 要抽三帧拼图再送模型。只给中间一帧,模型会把「捏脸」读成「吹气」、「搓手」读成「加油」。
- 标签写「在做什么」不写「像谁」。角色名可以留一两个,但动作才是检索时真正会命中的东西。
5. 用起来
5.1 工具
| 工具 | 干什么 |
|---|---|
sticker_search |
按意思找候选,返回名字/情绪/标签/什么时候用 |
sticker_send |
挑一张(query 自动挑、或 path 指定);默认只回路径,由调用方自己用发文件工具发 |
sticker_learn |
把一张图入库并打标 |
sticker_battle |
大战开 / 关 / 查状态 / 记一次「收到图」 |
sticker_stats |
库有多大、标签多密、大战开没开 |
sticker_show |
在 GUI 聊天窗里内联显示一张(返回 markdown 图,不走 IM 通道) |
5.2 表情包大战
三种任一条件进入:
- 设置页把「表情包大战」打开;
- 聊天里发一句触发词(默认
表情包的大战/表情包大战/开战); - 连续发三张表情包(默认阈值 3,可在设置页改)。
进入之后:只回图,不写字。收到你的图 → 识图 → 在库里找最贴的那张 → 发回来。
什么时候结束:你一开始打字,这一局就算完了——通道补丁看到文字就自动关战、连胜清零(触发词除外,那是要开不是要收);也可以在设置页或 sticker_battle 里手动关。
它怎么知道你在发图?通道插件会把下行图片存成本地文件并把路径交给 agent;agent 调 sticker_learn(可选入库)或 sticker_search + sticker_send。
想让它更快、更狠(连 agent 输出文字都掐掉),见
docs/HARD-MODE.md里的出站拦截补丁——改的是通道插件的 dist,升级会覆盖,自己权衡。
5.3 正文标记 [sticker:名字](推荐)
在回复正文里写 [sticker:变身],通道层把它拆成「文字段 + 图片段」按顺序发出去:图的位置由文字决定、QQ 和 GUI 一个写法、省一次工具往返。名字找不到就用情绪词同类随机;都对不上就丢掉标记、绝不硬凑。
两个通道各走各的,都是这一句标记:
- QQ:改通道插件的
dist/transport/outbound.js(与 HARD-MODE 同一个位置),装法与注意事项见docs/MARKER.md。 - GUI:插件自带客户端渲染——盯 DOM 把标记换成
<img>(走/sticker-hub/api/pick换地址)。设置页「界面渲染标记」可以关;被前端重渲染覆盖时最多退回成文字,不影响聊天。
6. 设置页
「设置 → 表情包库」:
- 大战开关、触发词、连发阈值
- 图库目录(默认
~/.dsh/sticker-hub/library) - 识图 Base URL / 模型 / 密钥文件路径
- 实时显示库大小与各来源张数
改完立刻生效,不用重启(服务端每次调用都重读 state.json)。
7. 常见问题
| 症状 | 原因 / 处理 |
|---|---|
图库是空的 |
没跑 pull,或 libraryDir 指错了 |
识图未配置 |
设置页没填模型,或 keyFile 路径不对;也可临时用环境变量 STICKER_VISION_KEY |
识图 HTTP 401/403 |
密钥不对,或这个中转不支持 image_url 那种格式 |
识图 HTTP 400 |
图片太大(默认上限 8 MB)或 base64 后超限;GIF 先用 Pillow 抽帧 |
| 发不出图 | 通道没有「发文件」工具,或文件不在允许目录内(QQ 默认只允许 media + 工作目录) |
| 同一张图反复发 | 用了旧版本;现在有「最近用过的降权」,可把 avoidWindow 调大 |
| 大战模式不退出 | 设置页关掉,或让 agent 调 sticker_battle {action:"off"} |
8. 版权与开源边界
- 插件仓库不含图片,只含
sources.yaml(仓库地址 + 许可说明)和代码。 - 用户自己跑
pull,图片就留在用户自己的机器上,许可归属原仓库。 - 插件不含任何 key / token / 内网地址。
~/.dsh/sticker-hub/state.json只在你本机。 - 代码 MIT;图片按各自来源的许可,转载图库前先看人家 README。
9. 卸载
dsh plugin --profile desktop remove dsh-sticker-hub
rm -rf ~/.dsh/sticker-hub # 图库与状态,自己确认后再删
10. 开发
npm test # 冒烟测试:假 ctx + 临时图库,不碰你本机数据
| 文件 | 干什么 |
|---|---|
index.js |
服务端:配置、图库解析、检索、识图、路由、6 个工具、createBridge() |
client.js |
设置页 + [sticker:] 的界面渲染器(MutationObserver 换 <img>) |
bin/sticker-hub.mjs |
pull / tag / stats 三个 CLI 子命令 |
sources.yaml |
图库来源清单(仓库地址 + 许可),只有元数据 |
docs/HARD-MODE.md |
出站拦截的硬模式补丁(连文字都不发) |
docs/MARKER.md |
[sticker:名字] 两个通道的接法 |
状态全在 ~/.dsh/sticker-hub/state.json(可用 DSH_HOME 改根目录),服务端每次调用都重读,所以改配置不用重启;改 client.js 要重启宿主才会重新加载。
No comments yet. Be the first to write one.