@zaofan/dsh-qqbot 1.5.28 → 1.5.31

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 (42) hide show
  1. package/README_EN.md +24 -24
  2. package/client/qqbot-settings.js +142 -26
  3. package/client/qqbot-settings.js.bak-inc-1790865292905 +4967 -0
  4. package/client/qqbot-settings.js.bak-inc-1790865309979 +4967 -0
  5. package/client/qqbot-settings.js.bak-newer-1790865544006 +5023 -0
  6. package/client/qqbot-settings.js.bak-stick-1790865591295 +5033 -0
  7. package/dist/channel-tools.d.ts.map +1 -1
  8. package/dist/channel-tools.js +10 -0
  9. package/dist/channel-tools.js.map +1 -1
  10. package/dist/features/chat-ledger.d.ts +13 -0
  11. package/dist/features/chat-ledger.d.ts.map +1 -1
  12. package/dist/features/chat-ledger.js +52 -0
  13. package/dist/features/chat-ledger.js.map +1 -1
  14. package/dist/gateway/debounce.d.ts.map +1 -1
  15. package/dist/gateway/debounce.js +32 -2
  16. package/dist/gateway/debounce.js.map +1 -1
  17. package/dist/gateway/middleware-setup.d.ts.map +1 -1
  18. package/dist/gateway/middleware-setup.js +25 -5
  19. package/dist/gateway/middleware-setup.js.map +1 -1
  20. package/dist/middleware/media-history.d.ts +27 -0
  21. package/dist/middleware/media-history.d.ts.map +1 -1
  22. package/dist/middleware/media-history.js +19 -5
  23. package/dist/middleware/media-history.js.map +1 -1
  24. package/dist/transport/inbound.d.ts +33 -0
  25. package/dist/transport/inbound.d.ts.map +1 -1
  26. package/dist/transport/inbound.js +232 -28
  27. package/dist/transport/inbound.js.map +1 -1
  28. package/dist/transport/msg-content-cache.d.ts +15 -0
  29. package/dist/transport/msg-content-cache.d.ts.map +1 -0
  30. package/dist/transport/msg-content-cache.js +105 -0
  31. package/dist/transport/msg-content-cache.js.map +1 -0
  32. package/dist/transport/quote-images.d.ts +17 -0
  33. package/dist/transport/quote-images.d.ts.map +1 -0
  34. package/dist/transport/quote-images.js +91 -0
  35. package/dist/transport/quote-images.js.map +1 -0
  36. package/dist/transport/quote-text.d.ts +30 -0
  37. package/dist/transport/quote-text.d.ts.map +1 -0
  38. package/dist/transport/quote-text.js +119 -0
  39. package/dist/transport/quote-text.js.map +1 -0
  40. package/docs/USER-GUIDE.md +118 -115
  41. package/package.json +1 -1
  42. package/settings-host.js +116 -16
@@ -0,0 +1,105 @@
1
+ /**
2
+ * 被引用消息的内容台账(2026-10-01 主人实测:**引用纯图片**看不到是哪张图)。
3
+ *
4
+ * 为什么要自己记:
5
+ * QQ 引用一条**纯图片**消息时,`msg_elements` 里往往只有个文件名(`[image: x.jpg]`)甚至
6
+ * 什么都没有 —— 拿不到 URL / 本地路径,AI 就不知道被引用的是哪张图(实测:群里引用表情包回话,她答非所问)。
7
+ * 而这条消息**入站那一刻**我们手里是有图片本地路径的
8
+ * (表情包采集中间件已落盘 + image-path-cache 已缓存 URL→本地路径)。
9
+ * → 入站时把「QQ 的 msg_idx → { 文本, 图片本地路径 }」记进本地台账;解析引用时按 `refKey` 回查补齐。
10
+ *
11
+ * 落盘:`{dataRoot}/.qqbot/msg-content.jsonl`(一行一条;超过 {@link MAX_LINES} 行时只留尾部)。
12
+ * ⚠️ 只存消息索引与**本机路径**,不存任何凭据;与本机图库同生命周期。
13
+ */
14
+ import { appendFileSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
15
+ import { dirname, join } from 'node:path';
16
+ /** 单个 peer 保留的台账行数上限(超出只留尾部,防文件无限膨胀) */
17
+ const MAX_LINES = 2000;
18
+ /** 文件路径 → { mtimeKey, index, lines } */
19
+ const cache = new Map();
20
+ function fileOf(dataRoot) {
21
+ return join(dataRoot, '.qqbot', 'msg-content.jsonl');
22
+ }
23
+ function indexKey(peerKey, msgIdx) {
24
+ return peerKey + '\u0000' + msgIdx;
25
+ }
26
+ function load(dataRoot) {
27
+ const file = fileOf(dataRoot);
28
+ let key = 'missing';
29
+ try {
30
+ const st = statSync(file);
31
+ key = st.mtimeMs + ':' + st.size;
32
+ }
33
+ catch { /* 还没有台账文件 */ }
34
+ const hit = cache.get(file);
35
+ // 'dirty' = 本进程刚写过,内存比文件新 → 直接用内存,别重读整个文件
36
+ if (hit && (hit.key === key || hit.key === 'dirty'))
37
+ return { index: hit.index, lines: hit.lines };
38
+ const index = new Map();
39
+ let lines = 0;
40
+ try {
41
+ for (const line of readFileSync(file, 'utf8').split('\n')) {
42
+ const s = line.trim();
43
+ if (!s)
44
+ continue;
45
+ lines++;
46
+ try {
47
+ const r = JSON.parse(s);
48
+ if (!r || !r.p || !r.i)
49
+ continue;
50
+ index.set(indexKey(r.p, r.i), {
51
+ ...(r.t ? { t: r.t } : {}),
52
+ ...(Array.isArray(r.imgs) && r.imgs.length ? { imgs: r.imgs } : {}),
53
+ });
54
+ }
55
+ catch { /* 坏行跳过 */ }
56
+ }
57
+ }
58
+ catch { /* 没有文件 */ }
59
+ cache.set(file, { key, index, lines });
60
+ return { index, lines };
61
+ }
62
+ /**
63
+ * 记一条:这条消息(msgIdx)的文本与图片本地路径。
64
+ * 之后有人**引用它**时,就能靠 `refKey = msgIdx` 回查到"被引用的到底是哪张图"。
65
+ */
66
+ export function rememberMsgContent(dataRoot, peerKey, msgIdx, rec) {
67
+ const idx = String(msgIdx ?? '').trim();
68
+ if (!dataRoot || !peerKey || !idx)
69
+ return;
70
+ if (!rec.t && !(rec.imgs && rec.imgs.length))
71
+ return; // 没内容可记(纯表情符号之类),跳过
72
+ const { index, lines } = load(dataRoot);
73
+ index.set(indexKey(peerKey, idx), rec);
74
+ const file = fileOf(dataRoot);
75
+ try {
76
+ mkdirSync(dirname(file), { recursive: true });
77
+ appendFileSync(file, JSON.stringify({ p: peerKey, i: idx, ...rec, at: Date.now() }) + '\n');
78
+ const nextLines = lines + 1;
79
+ if (nextLines > MAX_LINES) {
80
+ // 超上限 → 重写成尾部 MAX_LINES 条(内存里已有的直接重建)
81
+ const tail = [];
82
+ for (const [k, v] of index) {
83
+ const pos = k.indexOf('\u0000');
84
+ tail.push(JSON.stringify({ p: k.slice(0, pos), i: k.slice(pos + 1), ...v, at: 0 }));
85
+ }
86
+ writeFileSync(file, tail.slice(-MAX_LINES).join('\n') + '\n', 'utf8');
87
+ cache.set(file, { key: 'dirty', index, lines: Math.min(nextLines, MAX_LINES) });
88
+ return;
89
+ }
90
+ cache.set(file, { key: 'dirty', index, lines: nextLines });
91
+ }
92
+ catch { /* 落盘失败不影响主链(内存索引已经更新) */ }
93
+ }
94
+ /** 回查:这条被引用消息(msgIdx)我们记过什么 */
95
+ export function lookupMsgContent(dataRoot, peerKey, msgIdx) {
96
+ if (!dataRoot || !peerKey || !msgIdx)
97
+ return undefined;
98
+ try {
99
+ return load(dataRoot).index.get(indexKey(peerKey, msgIdx));
100
+ }
101
+ catch {
102
+ return undefined;
103
+ }
104
+ }
105
+ //# sourceMappingURL=msg-content-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"msg-content-cache.js","sourceRoot":"","sources":["../../src/transport/msg-content-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /** 引用里能拿到的附件形状(SDK 的 QuotedAttachment) */
2
+ export interface QuoteImageAttachment {
3
+ contentType?: string;
4
+ url?: string;
5
+ filename?: string;
6
+ }
7
+ /**
8
+ * 把被引用的图片预取到本地(幂等;已缓存/非图片/非 http 的直接跳过)。
9
+ * @returns 成功预取的张数
10
+ */
11
+ export declare function prefetchQuoteImages(quote: {
12
+ attachments?: QuoteImageAttachment[];
13
+ } | undefined, dataRoot: string, logger?: {
14
+ debug?: (m: string) => void;
15
+ warn?: (m: string) => void;
16
+ }): Promise<number>;
17
+ //# sourceMappingURL=quote-images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-images.d.ts","sourceRoot":"","sources":["../../src/transport/quote-images.ts"],"names":[],"mappings":"AAwBA,0CAA0C;AAC1C,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAsBD;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE;IAAE,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAA;CAAE,GAAG,SAAS,EAC3D,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GACnE,OAAO,CAAC,MAAM,CAAC,CA4BjB"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * 被引用消息里的**图片预取**(2026-10-01 主人要求:「为什么没尽可能地转换成本地路径」)。
3
+ *
4
+ * 场景:有人引用一张图问"这是啥" —— 那张图我们**可能从没收藏过**(别人发的、闸门外、采集关过),
5
+ * image-path-cache 与图库台账都查不到 → 引用块里只能回退 QQ 的长链接
6
+ * (`multimedia.nt.qq.com.cn/download?…&rkey=…`,又长又会过期,AI 读不了图)。
7
+ *
8
+ * 做法:入站时(引用解析之后、**拼上下文之前**)异步把被引用的图片**下载到本地**
9
+ * `{dataRoot}/.qqbot/referenced/<sha1>.<ext>`,并 `rememberImagePath(url → 本地路径)` ——
10
+ * 之后同步的 buildQuotePart / media-history 就能给出本地路径,AI 直接读图。
11
+ *
12
+ * 边界:只处理 http(s) 的**图片**附件;最多 {@link MAX_IMAGES} 张、每张 {@link TIMEOUT_MS} 超时;
13
+ * 已缓存过的直接跳过;任何失败都静默(引用块照旧回退 URL,不影响主链)。
14
+ */
15
+ import { createHash } from 'node:crypto';
16
+ import { mkdirSync, writeFileSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { lookupImagePath, rememberImagePath } from '../features/image-path-cache.js';
19
+ /** 一次最多预取几张(防有人引用一堆图拖慢入站) */
20
+ const MAX_IMAGES = 3;
21
+ /** 单张下载超时 */
22
+ const TIMEOUT_MS = 8000;
23
+ function isImageAtt(a) {
24
+ const ct = String(a?.contentType ?? '').toLowerCase();
25
+ const url = String(a?.url ?? '');
26
+ if (ct.startsWith('image/'))
27
+ return true;
28
+ // contentType 不可信(QQ 群聊图片实测会报 'file')→ 再用文件名 / URL 扩展名兜底
29
+ const name = String(a?.filename ?? url).split(/[?#]/)[0] ?? '';
30
+ return /\.(jpe?g|png|gif|webp|bmp)$/i.test(name);
31
+ }
32
+ function extOf(a) {
33
+ const ct = String(a?.contentType ?? '').toLowerCase();
34
+ if (ct.includes('png'))
35
+ return '.png';
36
+ if (ct.includes('gif'))
37
+ return '.gif';
38
+ if (ct.includes('webp'))
39
+ return '.webp';
40
+ if (ct.includes('bmp'))
41
+ return '.bmp';
42
+ const name = String(a?.filename ?? a?.url ?? '').split(/[?#]/)[0] ?? '';
43
+ const m = name.match(/\.(jpe?g|png|gif|webp|bmp)$/i);
44
+ return m && m[1] ? '.' + m[1].toLowerCase() : '.jpg';
45
+ }
46
+ /**
47
+ * 把被引用的图片预取到本地(幂等;已缓存/非图片/非 http 的直接跳过)。
48
+ * @returns 成功预取的张数
49
+ */
50
+ export async function prefetchQuoteImages(quote, dataRoot, logger) {
51
+ const atts = Array.isArray(quote?.attachments) ? quote.attachments : [];
52
+ if (!atts.length || !dataRoot)
53
+ return 0;
54
+ let done = 0;
55
+ for (const a of atts) {
56
+ if (done >= MAX_IMAGES)
57
+ break;
58
+ const url = String(a?.url ?? '').trim();
59
+ if (!/^https?:\/\//i.test(url))
60
+ continue; // 已经是本机路径 / 非 http → 无需预取
61
+ if (!isImageAtt(a))
62
+ continue;
63
+ if (lookupImagePath(url))
64
+ continue; // 缓存已有 → 同步侧本来就能命中
65
+ try {
66
+ const res = await fetch(url, { signal: AbortSignal.timeout(TIMEOUT_MS) });
67
+ if (!res.ok) {
68
+ logger?.debug?.(`[引用图片预取] HTTP ${res.status}: ${url.slice(0, 80)}`);
69
+ continue;
70
+ }
71
+ const buf = Buffer.from(await res.arrayBuffer());
72
+ if (buf.length === 0 || buf.length > 12 * 1024 * 1024) {
73
+ logger?.debug?.(`[引用图片预取] 体积异常(${buf.length}B), 跳过`);
74
+ continue;
75
+ }
76
+ const dir = join(dataRoot, '.qqbot', 'referenced');
77
+ mkdirSync(dir, { recursive: true });
78
+ const sha1 = createHash('sha1').update(buf).digest('hex').slice(0, 12);
79
+ const file = join(dir, sha1 + extOf(a));
80
+ writeFileSync(file, buf);
81
+ rememberImagePath(url, file);
82
+ done++;
83
+ logger?.debug?.(`[引用图片预取] 已落盘 ${file} (${buf.length}B)`);
84
+ }
85
+ catch (e) {
86
+ logger?.debug?.(`[引用图片预取] 失败(忽略): ${e instanceof Error ? e.message : String(e)}`);
87
+ }
88
+ }
89
+ return done;
90
+ }
91
+ //# sourceMappingURL=quote-images.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-images.js","sourceRoot":"","sources":["../../src/transport/quote-images.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 剥掉 QQ 引用模板的壳,返回各层正文(**最外层在前**,即"最近被引用的那句话"排第一)。
3
+ * 不是模板串时,按行返回原文(保留原样,只 trim)。
4
+ */
5
+ export declare function stripQqQuoteTemplate(s: string): string[];
6
+ /**
7
+ * 从 QQ 表情标签里挖**图片线索**。
8
+ *
9
+ * 背景(2026-10-01 主人实测:「图片收藏进了表情里,依然是图片」):
10
+ * QQ 的「图片表情 / 收藏表情」在消息里只给一个 `<faceType=…,ext="base64">` 标签。
11
+ * 实测那条 ext = `eyJ0ZXh0IjoiIn0=` → `{"text":""}` —— **里面没有图**(QQ 官方文档也没定义 faceType,
12
+ * SDK 同样只解 ext.text),所以光靠标签解析不出图;真正的出路是"那条消息入站时的附件"
13
+ * (走本地台账回查,见 msg-content-cache.ts / quote-images.ts 的预取)。
14
+ * 但不同端/版本的 ext 里**可能**带 url 之类字段 —— 这里全部扫一遍,挖到就当真图片附件用。
15
+ */
16
+ export declare function extractFaceImageUrls(text: string): string[];
17
+ /**
18
+ * 引用块正文瘦身:剥模板壳 → **只取最外层那一句** → 表情标签转可读 → 压平空白 → 限长。
19
+ *
20
+ * ⚠️ 只取最外层 —— 那才是"他引用的内容"。QQ 给的嵌套链(`[关联消息] --- 第1条 --- [消息内容] …`)
21
+ * 全是"那句话又引用了谁",又长又乱(主人:「不止是排版乱的问题,这是内容就乱了」)。
22
+ * @param max 最大字数(默认 160;传 0 = 不限长)
23
+ */
24
+ export declare function slimQuoteText(s: string, max?: number): string;
25
+ /**
26
+ * 把整个 `[引]…[/引]` 块压成**一行**并限长(历史行用)。
27
+ * 附件行不参与截断(否则路径被切掉 = 白记)。
28
+ */
29
+ export declare function slimQuoteBlockOneLine(block: string, max?: number): string;
30
+ //# sourceMappingURL=quote-text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-text.d.ts","sourceRoot":"","sources":["../../src/transport/quote-text.ts"],"names":[],"mappings":"AAmBA;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB3D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,MAAM,CAM1D;AAMD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,MAAM,CAsBtE"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * 引用文本清洗(2026-10-01 主人实测:「历史里包含引用的排版太乱了」+「引用的是表情包?」)。
3
+ *
4
+ * 两个数据源问题都在这里修:
5
+ * ① QQ 的引用在 `msg_elements` 里是一段**模板串**:
6
+ * `=== 消息 1 === [消息内容] A [消息类型] 引用消息 [关联消息] --- 第1条 --- [消息内容] B …`
7
+ * 嵌套几层就重复几段,夹着一堆纯噪声 —— 原样喂给 AI 又长又乱。
8
+ * ② 引用一条**表情消息**时,内容是一个 QQ 表情标签:
9
+ * `<faceType=6,faceId="0",ext="eyJ0ZXh0IjoiIn0=">`(ext 是 base64 的 `{"text":""}`)。
10
+ * 原样显示谁也看不懂 —— 这里转成 `【表情: 微笑】`;并顺手把 ext 里**可能藏着**的图片 URL
11
+ * (图片表情 / 收藏表情的其它端形态)挖出来当附件用。
12
+ */
13
+ import { resolveFaceTags } from '../features/face-tags.js';
14
+ /** 判断是不是 QQ 的引用模板串 */
15
+ function looksLikeQqTemplate(s) {
16
+ return /===+\s*消息|\[消息内容\]|\[消息类型\]|\[关联消息\]|引用消息|第\s*\d+\s*条/.test(s);
17
+ }
18
+ /**
19
+ * 剥掉 QQ 引用模板的壳,返回各层正文(**最外层在前**,即"最近被引用的那句话"排第一)。
20
+ * 不是模板串时,按行返回原文(保留原样,只 trim)。
21
+ */
22
+ export function stripQqQuoteTemplate(s) {
23
+ const raw = String(s ?? '');
24
+ if (!looksLikeQqTemplate(raw)) {
25
+ return raw.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
26
+ }
27
+ let t = raw;
28
+ // 每层的正文起点:`[消息内容]` 和 `--- 第N条 ---` 都切开
29
+ t = t.replace(/\[消息内容\]/g, '\n');
30
+ t = t.replace(/-{2,}\s*第\s*\d+\s*条\s*-{2,}/g, '\n');
31
+ // 剩余的壳当空白
32
+ t = t.replace(/\[(?:消息类型|关联消息|消息来源|消息序号)\]/g, ' ');
33
+ t = t.replace(/[=]{2,}\s*消息\s*\d+\s*[=]{2,}/g, ' ');
34
+ t = t.replace(/引用消息/g, ' ');
35
+ return t
36
+ .split(/\r?\n/)
37
+ .map((l) => l.replace(/\s+/g, ' ').trim())
38
+ .filter(Boolean);
39
+ }
40
+ /**
41
+ * 从 QQ 表情标签里挖**图片线索**。
42
+ *
43
+ * 背景(2026-10-01 主人实测:「图片收藏进了表情里,依然是图片」):
44
+ * QQ 的「图片表情 / 收藏表情」在消息里只给一个 `<faceType=…,ext="base64">` 标签。
45
+ * 实测那条 ext = `eyJ0ZXh0IjoiIn0=` → `{"text":""}` —— **里面没有图**(QQ 官方文档也没定义 faceType,
46
+ * SDK 同样只解 ext.text),所以光靠标签解析不出图;真正的出路是"那条消息入站时的附件"
47
+ * (走本地台账回查,见 msg-content-cache.ts / quote-images.ts 的预取)。
48
+ * 但不同端/版本的 ext 里**可能**带 url 之类字段 —— 这里全部扫一遍,挖到就当真图片附件用。
49
+ */
50
+ export function extractFaceImageUrls(text) {
51
+ const src = String(text ?? '');
52
+ if (src.indexOf('<faceType=') < 0)
53
+ return [];
54
+ const out = [];
55
+ const re = /<faceType=\d+,faceId="[^"]*",ext="([^"]*)">/g;
56
+ let m;
57
+ while ((m = re.exec(src)) !== null) {
58
+ const b64 = m[1] ?? '';
59
+ if (!b64)
60
+ continue;
61
+ try {
62
+ const obj = JSON.parse(Buffer.from(b64, 'base64').toString('utf-8'));
63
+ for (const v of Object.values(obj ?? {})) {
64
+ if (typeof v === 'string' && /^https?:\/\//i.test(v) && !out.includes(v))
65
+ out.push(v);
66
+ }
67
+ }
68
+ catch { /* ext 不是 JSON / 解不开 → 跳过 */ }
69
+ }
70
+ return out;
71
+ }
72
+ /**
73
+ * 引用块正文瘦身:剥模板壳 → **只取最外层那一句** → 表情标签转可读 → 压平空白 → 限长。
74
+ *
75
+ * ⚠️ 只取最外层 —— 那才是"他引用的内容"。QQ 给的嵌套链(`[关联消息] --- 第1条 --- [消息内容] …`)
76
+ * 全是"那句话又引用了谁",又长又乱(主人:「不止是排版乱的问题,这是内容就乱了」)。
77
+ * @param max 最大字数(默认 160;传 0 = 不限长)
78
+ */
79
+ export function slimQuoteText(s, max = 160) {
80
+ const lines = stripQqQuoteTemplate(s);
81
+ // 表情标签 → `【表情: 微笑】`(原来是 `<faceType=6,faceId="0",ext="…">` 这种谁也看不懂的串)
82
+ let out = resolveFaceTags(lines[0] ?? '').replace(/\s+/g, ' ').trim();
83
+ if (max > 0 && out.length > max)
84
+ out = out.slice(0, max) + '…';
85
+ return out;
86
+ }
87
+ /** 附件行(`📷 被引用的图片: <本地路径>` 等)—— 截断时必须豁免:那是"引用的是哪张图"的唯一线索
88
+ * ⚠️ 不能用字符类 `[📷🎵🎬📎]` —— emoji 是代理对,字符类里会被拆成单个 UTF-16 码元,**匹配不上**。 */
89
+ const ATT_LINE_RE = /^\s*(?:📷|🎵|🎬|📎)\s*被引用的/;
90
+ /**
91
+ * 把整个 `[引]…[/引]` 块压成**一行**并限长(历史行用)。
92
+ * 附件行不参与截断(否则路径被切掉 = 白记)。
93
+ */
94
+ export function slimQuoteBlockOneLine(block, max = 140) {
95
+ const raw = String(block ?? '').trim();
96
+ if (!raw)
97
+ return '';
98
+ const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
99
+ const isBegin = (l) => /^\[(?:引|Quoted message begins)\]$/i.test(l);
100
+ const isEnd = (l) => /^\[\/(?:引|Quoted message ends)\]$/i.test(l);
101
+ const hasBegin = lines.some(isBegin);
102
+ const hasEnd = lines.some(isEnd);
103
+ // ⚠️ 2026-10-01 主人实测抓到:附件行**必须留在 `[引]…[/引]` 里面**。
104
+ // 原实现把「正文」和「附件」分成两段各自拼,附件被甩到 `[引] [/引]` **之后** →
105
+ // 引用块看着是空的、图却挂在块外(`[引] [/引] 📷 被引用的图片: …`)。
106
+ const att = lines.filter((l) => ATT_LINE_RE.test(l));
107
+ const body = lines
108
+ .filter((l) => !isBegin(l) && !isEnd(l) && !ATT_LINE_RE.test(l))
109
+ .join(' ')
110
+ .replace(/\s+/g, ' ')
111
+ .trim();
112
+ const clipped = max > 0 && body.length > max ? body.slice(0, max) + '…' : body;
113
+ const inner = [clipped, ...att].filter(Boolean).join(' ').replace(/\s+/g, ' ').trim();
114
+ if (!hasBegin && !hasEnd)
115
+ return inner;
116
+ // 老英文标记([Quoted message begins]…)也归一成短标记
117
+ return `[引] ${inner} [/引]`.replace(/\s+/g, ' ').trim();
118
+ }
119
+ //# sourceMappingURL=quote-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quote-text.js","sourceRoot":"","sources":["../../src/transport/quote-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,sBAAsB;AACtB,SAAS,mBAAmB,CAAC,CAAS;IACpC,OAAO,uDAAuD,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,CAAS;IAC5C,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5B,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,CAAC,GAAG,GAAG,CAAC;IACZ,uCAAuC;IACvC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,8BAA8B,EAAE,IAAI,CAAC,CAAC;IACpD,UAAU;IACV,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,8BAA8B,EAAE,GAAG,CAAC,CAAC;IACnD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;IACpD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC5B,OAAO,CAAC;SACL,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;SACzC,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC/B,IAAI,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAC7C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,EAAE,GAAG,8CAA8C,CAAC;IAC1D,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA4B,CAAC;YAChG,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,CAAC;gBACzC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACxF,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,4BAA4B,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,CAAS,EAAE,GAAG,GAAG,GAAG;IAChD,MAAM,KAAK,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAAC;IACtC,qEAAqE;IACrE,IAAI,GAAG,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACtE,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG;QAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;IAC/D,OAAO,GAAG,CAAC;AACb,CAAC;AAED;0EAC0E;AAC1E,MAAM,WAAW,GAAG,4BAA4B,CAAC;AAEjD;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa,EAAE,GAAG,GAAG,GAAG;IAC5D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,CAAC,CAAS,EAAW,EAAE,CAAC,oCAAoC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACrF,MAAM,KAAK,GAAG,CAAC,CAAS,EAAW,EAAE,CAAC,oCAAoC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACnF,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjC,kDAAkD;IAClD,mDAAmD;IACnD,8CAA8C;IAC9C,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,MAAM,IAAI,GAAG,KAAK;SACf,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SAC/D,IAAI,CAAC,GAAG,CAAC;SACT,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;IACV,MAAM,OAAO,GAAG,GAAG,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/E,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACtF,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACvC,yCAAyC;IACzC,OAAO,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACzD,CAAC"}