npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-qqbot-lite

v1.2.1

Published

DSH 的轻量 QQ 机器人插件(零运行时依赖):WebSocket 收群聊/私聊消息接入 dsh 会话,并提供 qq_message / qq_group / qq_bot 三个工具供 AI 主动调用(发消息、发文件、读消息、按需下载附件、群管理、查状态)。自带「设置 → QQ 机器人」页面,支持手机 QQ 扫码绑定,无需手抄凭据。

Readme

dsh-qqbot-lite

QQ 机器人插件(自研轻量实现,零运行时依赖)。

让 AI 能主动调用工具收发 QQ 消息:WebSocket 收消息接入 dsh 会话, 并提供一组 qq_* 工具供模型自主调用。自带「设置 → QQ 机器人」页面, 手机 QQ 扫码即可绑定,不用手抄凭据。


为什么自研

| 方案 | 依赖体积 | 问题 | |---|---|---| | @zaofan/dsh-qqbot | ~150MB | 带 onnxruntime-node(113MB)、sharp、transformers,安装时踩过 ERR_PNPM_IGNORED_BUILDS | | dsh-qqbot-ui(官方 0.5.0 重打包) | 4 个依赖 | 可用,但仍依赖腾讯 SDK | | 本插件 | 0 个依赖 | 只用 Node 内置能力 |

零依赖是怎么做到的:

  • HTTP → Node 内置 fetch(>= 18)
  • WebSocket → Node 内置 WebSocket(>= 22,已实测可用)
  • 加密/压缩 → node:crypto / node:zlib
  • 不 import 任何 @deepseek-ai/* 包(link: 安装时解析不到宿主的 peerDependencies, 会直接 ERR_MODULE_NOT_FOUND;本插件改用 ctx.get() 拿服务 + 动态 import 兜底)

提供的工具

工具按领域合并,用 action 参数分派具体操作——每个工具的描述都要进系统提示, 工具越多上下文越贵,所以只暴露 3 个:

| 工具 | action | 作用 | |---|---|---| | qq_message | send | 向指定群/私聊发文本(被动优先,超限自动转主动;长文自动分条) | | | send_file | 上传并发送本地文件(图片/视频/语音/普通文件) | | | read | 主动拉取消息,返回【新消息】+【历史消息】两区,供 AI 自行判断要不要回 | | | download | 按需下载超过闸门的附件(参数:message_id、index) | | | recall | 撤回自己发的消息(QQ 限制:2 分钟内) | | qq_group | info | 群信息(群号、成员数、群主) | | | members | 群成员列表(⚠️ 多数机器人无此权限,返回 11253) | | | restrict_get / restrict_set | 读/写禁言设置 | | | join_requests | 待审批入群申请列表 | | | approve_join | 批准/拒绝入群申请 | | | remove_members | 批量踢人(可选拉黑) | | qq_bot | info | 机器人自身身份(名字、id、AppID、已知会话数) | | | targets | 列出已知会话,供 AI 寻址 | | | status | 网关连接状态(是否正在收消息) |

关键设计:回复当前会话不需要工具——AI 的正文输出由出站层 (监听 session/event 的 assistant/message)自动转成 QQ 消息。 工具只补「正文做不到的事」:发到别的会话、发媒体、读群聊、群管理、查状态。

为什么要 read

插件的唤醒门控只在被 @ 时叫醒 AI(避免刷屏),但这样 AI 被叫醒时 看不到群里刚才还聊了什么。所以每条消息(含没 @ 机器人的闲聊)都写进 $DSH_HOME/qqbot/messages.json,AI 可以主动调 qq_message + action=read 来读:

【新消息】2 条
08:19 しい: 肥鱼[表情: 呲牙]
08:20 恸: 可以的
      ↩ 引用 某人:这个软件那可以刷视频吗?

【历史消息】1 条
08:19 Elyisum: [图片: flower.jpg 800x600] 康康我的花

读取即推进游标——下次再读,这批「新消息」就变成「历史消息」。 mark_read: false 可只预览不消费。AI 自己发的消息也会记进去(显示为 我:), 因为 QQ 不会把机器人自己的消息推回来。

文本解析:为什么不能直接把 content 喂给 AI

QQ 的 content 里塞着 AI 读不懂的原始标记,直接喂会造成理解偏差。 lib/text-parsing.js 负责把它们变成人话(规则对齐官方 SDK 的 protocol/utils/text-parsing.js):

| 原始形态 | 解析后 | 说明 | |---|---|---| | <faceType=1,faceId="13",ext="eyJ0ZXh0Ijoi5ZGy54mZIn0="> | [表情: 呲牙] | ext 是 base64 的 JSON,取 text 字段 | | <faceType=6,faceId="0",ext="...">(ext 无 text) | [表情包] | 自定义大表情,真实画面在图片附件里 | | [图片] | [图片: a.jpg 1239x1391] | 带文件名和尺寸 | | 48×48 的小图 | [表情包: x.jpg 48x48] | 按尺寸判定为表情包 | | attachments[].asr_refer_text | [语音: 你好啊] | QQ 自带语音识别 | | msg_elements[0] | ↩ 引用 某人:原文 | 被引用的消息 |

引用解析的两个来源(优先级从高到低):

  1. msg_elements[0] —— QQ 直接把被引用内容推过来,最可靠
  2. 本地流水按 ref_msg_idx 查找 —— QQ 没推时回退到自己的缓存

两个都拿不到时会留下 ↩ 引用了一条消息(原文未能取到) 这个明确标记, 而不是静默丢弃 —— 让 AI 知道「这是条引用」,好过让它误以为这是句独立的话。

附件存储

群里每条消息的附件都会下载(不只被 @ 的)—— AI 主动回看群聊时, 图片和文件得已经在本地,否则它只看到一句 [图片],不知道画的是什么。

两道闸门:

| 闸门 | 默认值 | 行为 | |---|---|---| | 单文件 | 10MB | 超过就不下,流水里留 URL,标 [图片未下载: 超过 10MB 闸门;可按需下载] | | 总量 | 500MB | media 目录超限时,按修改时间删最旧的,删到 90% 水位 |

按需下载:超过单文件闸门的附件,AI 可以用 qq_message + action=download(参数 message_id、index)单独拉取 —— 只在真要看的图上花代价。

⚠️ QQ 的附件 URL 带 rkey,会过期。过期后按需下载会失败, 只能让用户重发。所以默认策略是"能下就下",而不是"用时再下"。

清理策略的细节:

  • 删到 90% 水位而非刚好等于上限,避免每下载一张图都触发一次全目录扫描
  • 至少保留 1 个文件,防止单个超大文件导致目录被清空

寻址方式

AI 记不住 32 位 openid,所以 target 支持:

  • 群名 / 用户名(模糊匹配,同名时返回候选让 AI 选,不猜)
  • 完整 openid
  • group:<openid> / c2c:<openid> 显式前缀

地址簿在收到消息时自动登记,持久化到 $DSH_HOME/qqbot/targets.json。


被动回复窗口(最关键的坑)

| 场景 | 被动回复有效期 | 单条消息可回次数 | |---|---|---| | 群聊 | 5 分钟 | 5 次 | | 单聊 | 60 分钟 | 4 次 |

  • 带 msg_id = 被动回复(不耗主动配额,但受时效/次数限制)
  • 不带 msg_id = 主动推送(无时效,但用户可关闭 + 频控 + 每日 1000 条上限)

本插件策略:被动优先;窗口过期或次数用尽自动降级为主动推送, 并在工具返回值里如实标注走了哪种模式。同一 msg_id 的 msg_seq 自动递增。


安装

dsh plugin --profile web add dsh-qqbot-lite

装完重启 dsh 才会加载(dsh plugin add 只改磁盘,不影响已运行的进程)。

重启后打开 设置 → QQ 机器人,点「扫码绑定」,用手机 QQ 扫二维码即可 ——不用去 QQ 开放平台手抄凭据。凭据会自动写进 profile 的 cordis.patch.yml,再重启一次生效(插件在启动时读配置)。

也可以完全跳过界面,手动填配置(见下节)。


扫码配对

插件自带一个「设置 → QQ 机器人」页面,走的是 QQ 官方的扫码绑定流程:

本地生成 32 字节随机 key
  ↓
POST q.qq.com/lite/create_bind_task  { key }        → task_id
  ↓
二维码内容 = q.qq.com/qqbot/openclaw/connect.html?task_id=...
  ↓
手机 QQ 扫码确认
  ↓
轮询 POST q.qq.com/lite/poll_bind_result { task_id }
  → status=2(COMPLETED) 时返回 bot_appid + bot_encrypt_secret
  ↓
appSecret = AES-256-GCM 解密(key 就是本地那个随机 key)
  ↓
凭据写入 profile 的 cordis.patch.yml

这套协议只用 Node 内置能力复现(node:crypto + fetch),不依赖腾讯的 @tencent-connect/qqbot-connector。二维码编码算法内置在 lib/vendor/qrcode/(MIT,见各文件头部版权声明),因此本插件对外仍是零依赖。

宿主侧只暴露三个接口,绝不下发 appSecret:

| 接口 | 作用 | |---|---| | GET /qqbot-lite/status | 配置状态、网关连接状态、扫码进度 | | POST /qqbot-lite/qr/start | 开一个扫码会话(未过期则复用),返回二维码 SVG | | POST /qqbot-lite/qr/cancel | 中止当前扫码会话 |


配置

- id: dsh-qqbot-lite
  config:
    appId: "你的AppID"
    appSecret: "你的AppSecret"
    preset: qqbot        # 可选:收到消息时创建会话所用的 agent preset
    botOpenid: "..."     # 开了全量消息后必填:机器人自己的 openid(用于识别被 @)
    maxAttachmentBytes: 10485760   # 可选:单附件下载上限,默认 10MB
    maxMediaBytes: 524288000       # 可选:media 目录总量上限,默认 500MB
    debug: false         # 可选:调试日志
    intents: 33554432    # 可选:覆盖 intents(默认 1<<25 群+单聊)
    baseUrl: ""          # 可选:覆盖 API 基址
    dshHome: ""          # 可选:覆盖 $DSH_HOME

凭据解析顺序:config > 环境变量 QQBOT_APPID / QQBOT_SECRET。

缺凭据不会阻塞启动:插件照常激活(配置树里可见),只是不连网关。


架构

lib/
  index.js        插件入口:串起各层,注册工具,绑定生命周期
  api.js          REST 层:token 管理(提前 5 分钟刷新)+ 网关地址缓存 + 错误人话化
  gateway.js      WebSocket:HELLO/IDENTIFY/RESUME/心跳/重连退避/关闭码处置
  inbound.js      入站:QQ 事件 → dsh agent 会话(sessionId 确定性派生,可 resume)
  sender.js       发送层:被动优先,超限自动降级主动
  reply-window.js 被动回复窗口:按官方规则计数与过期
  store.js        地址簿:openid ↔ 人类可读名称
  message-log.js  消息流水:每条消息都存(含未 @ 的),带已读游标供 AI 回看
  text-parsing.js 入站文本解析:表情 base64 解码 / 引用还原 / 附件摘要
  attachment.js   附件存储:全部下载 + 大小闸门 + 空间配额 + SSRF 防护
  tools.js        AI 可调用的工具定义(3 个工具,action 分派)

会话映射

sessionKey = `qqbot:${appId}:${scope}:${peerId}`
sessionId  = SHA-256(sessionKey) 派生的 UUID

确定性派生 → 重启后能自动 resume 回同一个会话,历史不丢。

消息体组装(对齐真实运行记录)

群聊:[昵称 (openid)] 正文 (@you)
单聊:正文(不加前缀)
附件:- Image: /path/to/x.jpg  (在发送者标签之前)
语音:- ASR: <QQ 自带的识别文本>   ← 不用自己跑 STT

安全

  • 附件下载有 SSRF 防护:只允许 HTTPS,阻断指向私有网段 (10/8、172.16/12、192.168/16、127/8、169.254/16、224/4)的 URL。 附件 URL 来自外部消息,不校验就可能被用来探测内网。
  • 附件单文件上限 10MB,超时 120 秒;media 目录上限 500MB(可配)。
  • 工具名只用 [a-zA-Z0-9_-](出现 @ 等字符会让 LLM API 整轮拒绝)。

已知限制

  • 未实现:互动按钮、定时任务、表情包库、单聊流式
  • 群成员列表 / 批量踢人接口对本机器人多数无权限(11253),需平台授权
  • 群全量消息(GROUP_MESSAGE_CREATE)需在 QQ 开放平台开启「接收所有消息」
  • 开启全量消息后,@ 消息也走 GROUP_MESSAGE_CREATE,必须配 botOpenid 才能识别被 @(否则会被唤醒门控拦掉)
  • intents 带无权限位会被网关直接拒连,默认只用 1<<25

参考文档

调研成果在 /root/dsh/qqbot-api-docs/:

  • README.md — 索引与关键结论
  • 01-收消息链路调研.md — 时序、op 码表、事件类型、消息体、踩坑清单
  • 02-API接口清单.md — REST 端点(带实测状态)、SDK API、错误码映射
  • 03-真实运行记录还原.md — 从本机历史日志还原的真实行为与工具 schema