cortico-world-forward-reader 0.1.0 → 0.1.2

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.
package/describe.json CHANGED
@@ -5,13 +5,13 @@
5
5
  "tags": [
6
6
  "read"
7
7
  ],
8
- "description": "拆开 QQ 的「合并转发 / 聊天记录」卡片,包括转发里套着的转发。\n什么时候用:收到 `qq.forward` 事件、或者 QQ 消息里写着「[合并转发]」但只摊开了一层、里面还有「又一层」时;想看清某段聊天记录到底是谁说了什么,也用它。\n怎么给编号:QQ 消息事件行首的 `#编号`(如 `#265277450`)就是 message_id,原样填进 ref;带不带 `#`、前后有空格都不要紧。\n它只读,只走本机的 SnowLuma OneBot HTTP 接口(默认 127.0.0.1:3000),不发消息、不改任何东西;接口没开或 token 不对会直接说清楚哪一步不对。输出过长会截断。",
8
+ "description": "拆开 QQ 的「合并转发 / 聊天记录」卡片,包括转发里套着的转发。\n什么时候用:收到 `qq.forward` 事件、或者 QQ 消息里写着「[合并转发]」但只摊开了一层、里面还有「又一层」时;想看清某段聊天记录到底是谁说了什么,也用它。\n怎么给编号:QQ 消息事件行首的 `#编号`(如 `#265277450`)就是 message_id,原样填进 ref;带不带 `#`、前后有空格都不要紧。编号可能是负数(如 `#-1175570559`),负号是编号的一部分,要一起填,别去掉。\n它只读,只走本机的 SnowLuma OneBot HTTP 接口(默认 127.0.0.1:3000),不发消息、不改任何东西;接口没开或 token 不对会直接说清楚哪一步不对。输出过长会截断。",
9
9
  "parameters": {
10
10
  "type": "object",
11
11
  "properties": {
12
12
  "ref": {
13
13
  "type": "string",
14
- "description": "消息编号,即 QQ 消息事件行首的 #后面的数字,如 \"265277450\"。"
14
+ "description": "消息编号,即 QQ 消息事件行首的 #后面的数字,如 \"265277450\";负数就带负号一起填,如 \"-1175570559\"。"
15
15
  },
16
16
  "depth": {
17
17
  "type": "integer",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-forward-reader",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "转发拆解 World:把 QQ 的「合并转发」一层层拆到底,嵌套在里面的转发也展开,输出带缩进的人话。只读,不发消息。",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -40,13 +40,21 @@ function senderName(sender) {
40
40
  return sender.card || sender.nickname || (sender.user_id ? String(sender.user_id) : '?');
41
41
  }
42
42
 
43
+ /** 今天的时间只给 HH:MM;跨天的带上日期,免得整份记录分不清哪天。 */
43
44
  function clockOf(time) {
44
45
  if (!time) return '';
45
46
  const d = new Date(Number(time) * 1000);
46
47
  if (Number.isNaN(d.getTime())) return '';
47
48
  const hh = String(d.getHours()).padStart(2, '0');
48
49
  const mm = String(d.getMinutes()).padStart(2, '0');
49
- return `${hh}:${mm}`;
50
+ const now = new Date();
51
+ const sameDay =
52
+ d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
53
+ if (sameDay) return `${hh}:${mm}`;
54
+ const mo = String(d.getMonth() + 1).padStart(2, '0');
55
+ const dd = String(d.getDate()).padStart(2, '0');
56
+ const year = d.getFullYear() === now.getFullYear() ? '' : `${d.getFullYear()}-`;
57
+ return `${year}${mo}-${dd} ${hh}:${mm}`;
50
58
  }
51
59
 
52
60
  /** 把一条消息的段合成一行,并挑出里面嵌套的转发段。 */
@@ -126,46 +134,58 @@ export class ForwardReaderWorld {
126
134
  const cfg = this.cfg;
127
135
  const rawRef = String(args.ref ?? '')
128
136
  .trim()
129
- .replace(/^#/, '');
137
+ .replace(/^#/, '')
138
+ .trim();
130
139
  if (!rawRef) return fail('要给消息编号(QQ 消息行首的 #后面那串数字)。');
131
- if (!/^\d+$/.test(rawRef)) return fail(`"${rawRef}" 不像消息编号,应该是纯数字,比如 265277450。`);
140
+ // QQ 的 message_id 有负数的(合并转发卡片就常见):负号是 id 的一部分,不能丢。
141
+ if (!/^-?\d+$/.test(rawRef)) return fail(`"${rawRef}" 不像消息编号,应该是数字(前面可能带一个负号),比如 265277450 或 -1175570559。`);
132
142
 
133
143
  const maxDepth = clampInt(args.depth ?? cfg.maxDepth, 8, 1, 20);
134
144
  const limit = clampInt(args.maxChars ?? cfg.maxChars, MAX_OUTPUT_CHARS, 1000, 60000);
135
145
 
146
+ let data;
147
+ try {
148
+ data = await callOneBot(cfg, 'get_msg', { message_id: rawRef });
149
+ } catch (err) {
150
+ const why = String(err?.message || err);
151
+ const hint = /not found|不存在|找不到/i.test(why)
152
+ ? '取不到这条,常见原因:消息太老、SnowLuma 那边没缓存了;或者编号抄错——负号是编号的一部分,除了行首的「#」别的字符一个都不能少。'
153
+ : '';
154
+ return fail(`取消息 ${rawRef} 失败:${why}${hint ? '\n' + hint : ''}\n可以先跑 qq_forward_status 看看接口和 token 怎么样。`);
155
+ }
156
+
136
157
  const out = [];
137
158
  const state = { items: 0, levels: 0, truncated: false };
159
+ const segments = data?.message || [];
160
+ const whole = foldMessage(segments);
161
+ const who = senderName(data?.sender);
162
+ const when = clockOf(data?.time);
163
+
164
+ if (!whole.nested.length) {
165
+ // 不是合并转发:直接把这条消息本身给人看。
166
+ out.push(`# 消息 ${rawRef}${who !== '?' ? ` · ${who}` : ''}${when ? ` · ${when}` : ''}`);
167
+ out.push('');
168
+ out.push(whole.text || '(没有文字内容)');
169
+ out.push('');
170
+ out.push('这条不是合并转发卡片,所以没有可拆的层。');
171
+ return cap(out.join('\n'), limit);
172
+ }
138
173
 
139
- try {
140
- const data = await callOneBot(cfg, 'get_msg', { message_id: rawRef });
141
- const segments = data?.message || [];
142
- const whole = foldMessage(segments);
143
-
144
- if (!whole.nested.length) {
145
- // 不是合并转发:直接把这条消息本身给人看。
146
- const who = senderName(data?.sender);
147
- const when = clockOf(data?.time);
148
- out.push(`# 消息 ${rawRef}${who !== '?' ? ` · ${who}` : ''}${when ? ` · ${when}` : ''}`);
149
- out.push('');
150
- out.push(whole.text || '(没有文字内容)');
151
- out.push('');
152
- out.push('这条不是合并转发卡片,所以没有可拆的层。');
153
- return cap(out.join('\n'), limit);
154
- }
174
+ out.push(`# 转发拆解 · 消息 ${rawRef}${who !== '?' ? ` · ${who}` : ''}${when ? ` · ${when}` : ''}`);
175
+ out.push('');
155
176
 
156
- const time = clockOf(data?.time);
157
- out.push(`# 转发拆解 · 消息 ${rawRef}${time ? ` · ${time}` : ''}`);
177
+ // 卡片本身带的文字也要留下,不然只列转发内容就把原话吞掉了。
178
+ if (whole.text) {
179
+ out.push(`(这张卡片上还写着)${whole.text}`);
158
180
  out.push('');
181
+ }
159
182
 
160
- for (const seg of whole.nested) {
161
- await this.walk(seg.data?.id, 0, out, state, maxDepth);
162
- }
163
-
164
- out.unshift(`共 ${state.levels} 层 / ${state.items} 条${state.truncated ? '(已达条数上限,后面没再往下翻)' : ''}`);
165
- return cap(out.join('\n'), limit);
166
- } catch (err) {
167
- return fail(`拆不开:${err.message}\n可以先跑 qq_forward_status 看看接口和 token 怎么样。`);
183
+ for (const seg of whole.nested) {
184
+ await this.walk(seg.data?.id, 0, out, state, maxDepth);
168
185
  }
186
+
187
+ out.unshift(`共 ${state.levels} 层 / ${state.items} 条${state.truncated ? '(已达条数上限,后面没再往下翻)' : ''}`);
188
+ return cap(out.join('\n'), limit);
169
189
  }
170
190
 
171
191
  async walk(forwardId, depth, out, state, maxDepth) {
package/src/tools.js CHANGED
@@ -15,7 +15,8 @@ export const TOOL_DECLS = [
15
15
  '拆开 QQ 的「合并转发 / 聊天记录」卡片,包括转发里套着的转发。\n' +
16
16
  '什么时候用:收到 `qq.forward` 事件、或者 QQ 消息里写着「[合并转发]」但只摊开了一层、里面还有「又一层」时;' +
17
17
  '想看清某段聊天记录到底是谁说了什么,也用它。\n' +
18
- '怎么给编号:QQ 消息事件行首的 `#编号`(如 `#265277450`)就是 message_id,原样填进 ref;带不带 `#`、前后有空格都不要紧。\n' +
18
+ '怎么给编号:QQ 消息事件行首的 `#编号`(如 `#265277450`)就是 message_id,原样填进 ref;带不带 `#`、前后有空格都不要紧。' +
19
+ '编号可能是负数(如 `#-1175570559`),负号是编号的一部分,要一起填,别去掉。\n' +
19
20
  '它只读,只走本机的 SnowLuma OneBot HTTP 接口(默认 127.0.0.1:3000),不发消息、不改任何东西;' +
20
21
  '接口没开或 token 不对会直接说清楚哪一步不对。输出过长会截断。',
21
22
  parameters: {
@@ -23,7 +24,7 @@ export const TOOL_DECLS = [
23
24
  properties: {
24
25
  ref: {
25
26
  type: 'string',
26
- description: '消息编号,即 QQ 消息事件行首的 #后面的数字,如 "265277450"。',
27
+ description: '消息编号,即 QQ 消息事件行首的 #后面的数字,如 "265277450";负数就带负号一起填,如 "-1175570559"。',
27
28
  },
28
29
  depth: {
29
30
  type: 'integer',