dsh-meme-qq
Verifieddsh-meme-qq · v0.2.1 · MIT
DSH 插件:把表情包图库接到 QQ,给一句描述就自己找图并发裸图(不套气泡、不出现 [表情: ...] 标记)
Install
dsh plugin add dsh-meme-qq Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-meme-qq
DSH 的表情包图库 + QQ 机器人接口。给一句描述或情绪,插件自己找图,直接把裸图发到 QQ(不套文字气泡、正文里不出现 [表情: ...] 这类标记)。
为什么需要它:
dsh-meme是在回复里写[表情: 描述],由网页端渲染成图。QQ 通道没有渲染层,那边永远只收到一行字。这个插件补上最后一步。
更新日志
- 0.2.1 — 修掉 0.2.0 的致命 bug:成功返回值里带了一个
undefined字段,宿主把它判成value is not lossless JSON,于是每次调用都失败(图可能已经发出去了,模型却收到报错)。现在所有返回值出门前都过一遍无损 JSON 安检,并补了回归测试。 - 0.2.0 — 全面配置化(图库/凭证/目标)、多目标与群聊、token 缓存、超时重试、错误归一、
reindex/stats动作。
装上
npm i dsh-meme-qq
或者在 DSH 里:设置 → 插件 → 添加插件,填 dsh-meme-qq(也支持 GitHub 地址、本地目录)。装完必须重启 DSH——插件只在进程启动时装载。重启后 meme_qq 会出现在工具列表里。
自己改代码调试时,直接把目录挂进 profile 也行:
{
"dependencies": { "dsh-meme-qq": "file:D:/path/to/dsh-meme-qq" },
"dsh": { "profile": { "bundles": ["...", "dsh-meme-qq"] } }
}
图库从哪来
插件只认 <packsDir>/index.json 这一个扁平索引,它由各个图库包的 index.db(sqlite)汇总而来。三种拿到方式:
- 已经装了 dsh-meme —— 它自带的图库包会被自动扫进来(
includeBundledPack: true)。 - 自己的图 —— 按
packsDir/<pack>/memes/<tag>/*.gif+packsDir/<pack>/index.db放好,然后调meme_qq { "action": "reindex" }。 - 别人的
index.json—— 直接放到packsDir下也能用(但图得在对应路径上)。
index.db 表结构:
create table memes(path text, tag text, file_name text, file_hash text,
caption text, keywords text, mtime integer, captioned_at integer);
-- path 是相对 pack 根的路径,用 / 分隔,例如 memes/happy/笑.gif
需要什么
- Node 22.5+(重建索引用的
node:sqlite;不重建索引也能正常发图) - 一个自己的 QQ 机器人(QQ 开放平台的 appId / appSecret),并且它跟目标说过话(否则拿不到 openid / group_openid)。插件不提供机器人,也不带任何人的凭证。
配置
配置写在 cordis patch 里那条 entry 上,全部可省。默认值假设 DSH 标准目录($DSH_HOME,默认 ~/.dsh)。
| 配置项 | 默认 | 说明 |
|---|---|---|
packsDir |
$DSH_HOME/meme-packs |
图库根目录(每个子目录是一个 pack) |
indexPath |
<packsDir>/index.json |
扁平索引文件,可用 action=reindex 重建 |
usageFile |
<packsDir>/usage.json |
发图账本(发得多往后排) |
credentialsFile |
$DSH_HOME/.credentials.yaml |
从这里读 appId / appSecret / userOpenid |
appId / appSecret |
空 | 直接填就覆盖凭证文件里的值 |
targets |
空 = 用凭证里的 userOpenid 发私聊 |
目标列表,见下 |
defaultTarget |
空 | 不指定目标时发给谁(name / id / 序号) |
extraPackDirs |
[] |
额外图库根目录(多个库不同位置时用) |
includeBundledPack |
true |
是否把 dsh-meme 自带图库也算进来 |
tagAliases |
{} |
自定义中文 → tag 映射,叠加在内置别名上 |
timeoutMs |
15000 |
单次请求超时 |
retries |
2 |
网络/429/5xx 重试次数 |
maxFileBytes |
8388608 |
图片上限,超了直接给可读错误 |
usageDecay |
6 |
每发过一次扣多少匹配分 |
jitter |
12 |
随机扰动幅度(让相近的图轮着出) |
debug |
false |
打开后往 DSH 日志里写每一步 |
targets
targets:
- { type: c2c, openid: "USER_OPENID", name: 我 }
- { type: group, groupOpenid: "GROUP_OPENID", name: 群聊 }
也接受字符串:"c2c:OPENID"、"group:GROUP_OPENID"、直接写 "OPENID"。
工具
meme_qq,三个动作:
{ "description": "累瘫", "tag": "sad" } // 默认 action=send
{ "action": "reindex" } // 扫 index.db 重建 index.json
{ "action": "stats" } // 一共多少张、各情绪多少、哪几张发得最多
tag不填也行,会按描述文字匹配;给错了不崩,退回同情绪或全库。- 发过的图会降权,所以同一句话连发两次大概率换图。
- 返回值是
{ ok, sent, caption, tag, target };失败是{ ok: false, error: "一句话" }。
收新图
# 1. 图放进 packsDir/<pack>/memes/<tag>/
# 2. 往该 pack 的 index.db 插一行(表结构见上)
# 3. 调 meme_qq { "action": "reindex" }
出错时
meme_qq根本不在工具列表:多半是package.json少了engines.dsh(宿主会静默跳过),或者没重启 DSH。没读到 QQ 机器人凭证:检查credentialsFile,或直接在配置里填appId/appSecret。QQ 接口 401:appId / appSecret 不配套,或 token 失效(插件会自动重取一次)。QQ 接口 403:机器人没开通对应权限(C2C 私聊 / 群消息)。QQ 接口 404:openid 不对,或机器人还没跟这个人/群说过话。索引里的图文件都不在了:图库搬过家,跑一次action=reindex。
开发
node --test test/meme-qq.test.mjs
测试不碰真网络(fetch 全被 stub),也不需要真图库:临时目录里现造 sqlite 和假图片。覆盖配置归一、凭证解析、tag 映射、选图排序、目标选择、reindex、send 全链路、错误归一。
设计取舍
- 零运行时依赖:不出于省事,是因为插件在宿主进程里跑,多一个依赖就多一个装不上的理由。
srv_send_msg=true直接发:先上传再发消息那条路走过四轮,QQ 要求文本消息 content 非空,气泡里必定带一行字或一个零宽空格,最后都会在图上套个大泡泡。- 不导出 Config schema:配置写错时降级,比让整条插件不激活要好。
- 错误只回一句话:模型不需要看 JSON 堆栈。