dsh-qq-link 1.5.2 → 1.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/lib/host.js +35 -12
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 1.5.4 — 2026-11-07
2
+
3
+ **宿主 0.2.2 起,QQ 空间的四个工具每 2 秒报一次 already registered,日志被刷爆**
4
+
5
+ - 🐞 issue #36:`空间工具心跳()` 注册失败时**不写 map**,于是每跳(2 秒)重试一次;
6
+ 而 0.2.2 起同一作用域不允许重名注册,第二次就抛 `already registered` ——
7
+ 一个中午 521 条,把宿主日志淹了。工具其实一直是挂着的,用户只看见「桥在报错」。
8
+ - 现在:`already registered` 不算失败,按「会话 + agent 实例」记下来,**实例换了才重挂**;
9
+ 真的挂不上时也只记一次,不再每 2 秒刷。
10
+
11
+ ## 1.5.3 — 2026-11-07
12
+
13
+ **别人发来的文件,以前一行都看不见**
14
+
15
+ - 🐞 issue #34:发文件是支持的(回复里写 `[名字](路径)`),但**收**文件不行 ——
16
+ `file` 段在解析里没有分支,整段被静默丢掉,会话里什么都不会出现。
17
+ - 现在会留一行 `[文件:xxx.zip(1.2MB)]`。链接有就带上,但不自动下载 ——
18
+ 先让它看得见,别一上来就往本地写文件。
19
+
1
20
  ## 1.5.2 — 2026-11-06
2
21
 
3
22
  **`[QQ]` 忘了写结尾,后面的整段都会发到手机上**
package/lib/host.js CHANGED
@@ -77,7 +77,7 @@ const 默认配置 = {
77
77
  // 得你自己在 NapCat / SnowLuma 里把「HTTP 上报」指到本插件,开之前先看清 README 那一节。
78
78
  收通知: false,
79
79
  通知口令: '', // 非空时,上报地址必须带 ?token=xxx(防止局域网里别人乱推)
80
- // ⭐ 2026-10-25(QQ 桥用户反馈「一次回一堆消息」):长回复按空行拆成几条连发,以前是写死的。
80
+ // ⭐ 2026-10-25(QQlink 用户反馈「一次回一堆消息」):长回复按空行拆成几条连发,以前是写死的。
81
81
  长回复拆段: true, // 关掉 = 一整段只发一条消息(不再刷屏)
82
82
  拆段间隔毫秒: 450, // 每段之间隔多久(太短像刷屏,太长像卡住)
83
83
  // ⭐ 1.4.3:按**句子**拆(不再是「按空行」)。默认关 —— 老用户升级后行为不变。
@@ -2177,7 +2177,7 @@ export function apply(ctx) {
2177
2177
  // attachments;模型能看图就直接看,纯文本模型拿到的是一句占位文字 —— 所以文字里也写上本地路径。
2178
2178
  // ── QQ 里的「分享卡片」和「合并转发的聊天记录」也读成文字(2026-10-25)──────────
2179
2179
  // 以前只认 text / image / mface ⇒ 她一发 B站 卡片、或者转发一段聊天记录,
2180
- // 段里没有文字、也没有图,整条被当成「空消息」丢掉 —— 看着就是「QQ 桥读不了」。
2180
+ // 段里没有文字、也没有图,整条被当成「空消息」丢掉 —— 看着就是「QQlink 读不了」。
2181
2181
  const 掐短 = (s2, n = 120) => {
2182
2182
  const t = String(s2 == null ? '' : s2).replace(/\s+/g, ' ').trim()
2183
2183
  return t.length > n ? t.slice(0, n) + '…' : t
@@ -2315,6 +2315,16 @@ export function apply(ctx) {
2315
2315
  }
2316
2316
  if (型 === 'face') { 文本块.push('[表情]'); continue }
2317
2317
  if (型 === 'record') { 文本块.push('[语音]'); continue }
2318
+ // ⭐ 1.5.3(issue #34):别人发来的**文件**以前整段被丢掉 —— 一行都看不见。
2319
+ // 现在至少留下名字和大小;链接能取到就一起带上(模型想读再让它读)。
2320
+ if (型 === 'file') {
2321
+ const dd = (x.data || {})
2322
+ const 名 = 掐短(String(dd.name || dd.file || '文件'), 60)
2323
+ const 字节 = Number(dd.file_size || dd.size || 0)
2324
+ const 大小 = 字节 > 0 ? '(' + (字节 >= 1048576 ? (字节 / 1048576).toFixed(1) + 'MB' : Math.max(1, Math.round(字节 / 1024)) + 'KB') + ')' : ''
2325
+ 文本块.push('[文件:' + 名 + 大小 + ']')
2326
+ continue
2327
+ }
2318
2328
  if (型 === 'video') { 文本块.push('[视频]'); continue }
2319
2329
  if (型 === 'json' || 型 === 'xml') { 文本块.push('[卡片]'); continue }
2320
2330
  if (型 === 'at') { 文本块.push('@' + String((x.data && (x.data.name || x.data.qq)) || '')); continue }
@@ -3110,27 +3120,40 @@ export function apply(ctx) {
3110
3120
  // 宿主没给 tools 服务(或者那间会话没开)就跳过 —— 下一跳再看
3111
3121
  if (!agent || !agent.ctx || !agent.ctx.tools || typeof agent.ctx.tools.register !== 'function') continue
3112
3122
  const sid = 会话键((agent.session && agent.session.id) || id)
3113
- if (空间挂着.has(sid)) continue
3123
+ // ⭐ 1.5.4(issue #36):按「sid + agent 实例」记 —— 实例换了才重挂(会话重开、插件重载)。
3124
+ // 以前只按 sid 记,而且注册全失败时不写 map ⇒ 每 2 秒重试一次,
3125
+ // 0.2.2 起同一作用域不许重名注册,于是每次都抛 already registered,日志被刷爆。
3126
+ const 已挂 = 空间挂着.get(sid)
3127
+ if (已挂 && 已挂.agent === agent) continue
3114
3128
  const 卸们 = []
3129
+ let 已经有过 = 0
3115
3130
  for (const 工具 of 做空间工具()) {
3116
3131
  try {
3117
3132
  const off = agent.ctx.tools.register(工具)
3118
3133
  if (typeof off === 'function') 卸们.push(off)
3119
3134
  } catch (error) {
3120
- 记账('QQ 空间的工具挂不上(' + 短id(sid) + ',' + 工具.name + '):' + String((error && error.message) || error))
3135
+ const 因 = String((error && error.message) || error)
3136
+ // 已经注册过 = 上一次其实挂上了(只是我们手里没留 off)—— 记下来,别再试
3137
+ if (/already registered/i.test(因)) { 已经有过 += 1; continue }
3138
+ if (!已挂 || !已挂.报过) {
3139
+ 记账('QQ 空间的工具挂不上(' + 短id(sid) + ',' + 工具.name + '):' + 因)
3140
+ }
3121
3141
  }
3122
3142
  }
3123
- if (!卸们.length) continue
3124
- 空间挂着.set(sid, () => {
3125
- for (const off of 卸们) {
3126
- try { off() } catch (error) {}
3127
- }
3143
+ if (!卸们.length && !已经有过) {
3144
+ // 一个都没成:记一笔「报过」,下一跳别再刷同样的错
3145
+ 空间挂着.set(sid, { agent, off: null, 报过: true })
3146
+ continue
3147
+ }
3148
+ 空间挂着.set(sid, {
3149
+ agent,
3150
+ off: () => { for (const off of 卸们) { try { off() } catch (error) {} } },
3128
3151
  })
3129
- 记账('QQ 空间的工具挂上了(' + 短id(sid) + ')')
3152
+ 记账('QQ 空间的工具挂上了(' + 短id(sid) + (卸们.length < 4 ? ',其中 ' + 已经有过 + ' 个本来就在' : '') + ')')
3130
3153
  }
3131
- for (const [sid, off] of 空间挂着) {
3154
+ for (const [sid, 记] of 空间挂着) {
3132
3155
  if ([...该挂].some((id) => 会话键(id) === sid)) continue
3133
- try { off() } catch (error) {}
3156
+ try { if (记 && typeof 记.off === 'function') 记.off() } catch (error) {}
3134
3157
  空间挂着.delete(sid)
3135
3158
  记账('QQ 空间的工具卸下了(' + 短id(sid) + ')')
3136
3159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-qq-link",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "通到 QQ:DSH 里的 AI 伙伴和 QQ 双向互通 —— 它说完话自动发到 QQ,你在 QQ 里说的话自动转进 DSH。走 OneBot 11 HTTP(SnowLuma / NapCat 都行)。",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "engines": {