luckytri 1.0.4 → 1.0.6

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 (156) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +2 -0
  3. package/README.md +2 -0
  4. package/docs/en/CHANGELOG.md +20 -13
  5. package/docs/en/guide.md +7 -5
  6. package/docs/en/her-life.md +2 -2
  7. package/docs/en/webui.md +1 -1
  8. package/docs/zh/CHANGELOG.md +20 -13
  9. package/docs/zh/guide.md +7 -5
  10. package/docs/zh/her-life.md +2 -2
  11. package/docs/zh/webui.md +1 -1
  12. package/package.json +2 -1
  13. package/public/app/assets/ChatsPage-CFMVozoG.css +1 -0
  14. package/public/app/assets/{ChatsPage-CshdtTzV.js → ChatsPage-DCYMkJ91.js} +2 -2
  15. package/public/app/assets/{Empty-C7yzOBzy.js → Empty-CLyZkxR5.js} +1 -1
  16. package/public/app/assets/{Empty-DmUB2-Bk.css → Empty-DIobYG3x.css} +1 -1
  17. package/public/app/assets/{Field-BjFv4Rfn.js → Field-BpaRwLI_.js} +1 -1
  18. package/public/app/assets/{HeartPage-eqo6ru16.js → HeartPage-BIOMTiwr.js} +1 -1
  19. package/public/app/assets/HeartPage-C-2jvgzF.css +1 -0
  20. package/public/app/assets/{LifePage-CN8GE5NK.js → LifePage-BCgM4JkR.js} +1 -1
  21. package/public/app/assets/LifePage-CG1OHbRK.css +1 -0
  22. package/public/app/assets/MemoryPage-C_Bu4rfb.css +1 -0
  23. package/public/app/assets/{MemoryPage-CCpMN4Wg.js → MemoryPage-vShvRqMt.js} +1 -1
  24. package/public/app/assets/NaturePage-CPZlImT0.css +1 -0
  25. package/public/app/assets/{NaturePage-C0JqdBTR.js → NaturePage-Cy9ptr4G.js} +2 -2
  26. package/public/app/assets/PeoplePage-3RRUITDz.css +1 -0
  27. package/public/app/assets/{PeoplePage-BXGBbO4u.js → PeoplePage-CUZekB2E.js} +1 -1
  28. package/public/app/assets/{PluginsPage-Dksp5AV5.js → PluginsPage-1RM4rmJP.js} +1 -1
  29. package/public/app/assets/{SystemPage-BenIyTvk.css → SystemPage-BI2CB5Ig.css} +1 -1
  30. package/public/app/assets/{SystemPage-Cd7aa8eE.js → SystemPage-BeT5TmIV.js} +1 -1
  31. package/public/app/assets/TimePage-C-guwPk2.css +1 -0
  32. package/public/app/assets/{TimePage-Bs_zImb4.js → TimePage-taTmPMM8.js} +2 -2
  33. package/public/app/assets/{index-BHZcHUMh.css → index-DY5AM0Hz.css} +1 -1
  34. package/public/app/assets/{index-CyRtYxP3.js → index-DdYwC5ZU.js} +3 -3
  35. package/public/app/assets/{knowledge-BJOIyuJo.js → knowledge-DJmfu2KN.js} +1 -1
  36. package/public/app/assets/{media-Bwtx8blz.js → media-BEtnE0Gv.js} +1 -1
  37. package/public/app/assets/{mind-DtTVrW6Z.js → mind-Dtiw0-QK.js} +1 -1
  38. package/public/app/index.html +6 -5
  39. package/public/guide.en.html +11 -3
  40. package/public/guide.html +6 -3
  41. package/public/tutorial.en.md +2 -0
  42. package/public/tutorial.md +2 -0
  43. package/scripts/import-data.js +128 -128
  44. package/scripts/proxy-config.js +167 -167
  45. package/scripts/service-supervisor.js +50 -0
  46. package/scripts/start-server.js +71 -16
  47. package/scripts/update-instance.js +201 -201
  48. package/server/channels/adapters.js +27 -27
  49. package/server/channels/onebot/adapter.js +288 -288
  50. package/server/channels/qqbot/adapter.js +253 -253
  51. package/server/channels/qqbot/api.js +199 -199
  52. package/server/channels/qqbot/credentials.js +11 -11
  53. package/server/channels/qqbot/gateway.js +274 -274
  54. package/server/channels/qqbot/index.js +389 -389
  55. package/server/channels/segments.js +40 -40
  56. package/server/channels/session-key.js +153 -153
  57. package/server/channels/state.js +42 -42
  58. package/server/core/api.js +564 -564
  59. package/server/core/context-builder.js +288 -288
  60. package/server/core/context-compactor.js +454 -454
  61. package/server/core/conversation-cues.js +325 -325
  62. package/server/core/conversation-manager.js +87 -87
  63. package/server/core/feedback.js +8 -8
  64. package/server/core/group-style.js +72 -72
  65. package/server/core/initiative-context.js +66 -66
  66. package/server/core/input-budget.js +32 -32
  67. package/server/core/local-demo.js +98 -98
  68. package/server/core/message-manager.js +118 -118
  69. package/server/core/message-scheduler.js +78 -78
  70. package/server/core/model-json.js +190 -190
  71. package/server/core/model-presets.js +900 -900
  72. package/server/core/name-call.js +29 -29
  73. package/server/core/network.js +157 -157
  74. package/server/core/person-name.js +22 -22
  75. package/server/core/persona-manager.js +2 -1
  76. package/server/core/reply-delivery.js +404 -403
  77. package/server/core/reply-target-resolver.js +50 -50
  78. package/server/core/response-generator.js +50 -50
  79. package/server/core/response-validator.js +14 -2
  80. package/server/core/retired-prompts.js +51 -51
  81. package/server/core/sessions.js +109 -109
  82. package/server/core/speaker-names.js +92 -92
  83. package/server/core/topic-tracker.js +65 -65
  84. package/server/http.js +18 -18
  85. package/server/i18n/decisions.js +209 -206
  86. package/server/i18n/errors.js +4 -0
  87. package/server/i18n/index.js +144 -144
  88. package/server/i18n/models.js +111 -111
  89. package/server/knowledge/api.js +342 -342
  90. package/server/knowledge/embedding-profile.js +114 -114
  91. package/server/knowledge/index.js +13 -13
  92. package/server/knowledge/retrieval.js +93 -93
  93. package/server/knowledge/schema.js +158 -158
  94. package/server/knowledge/vector-index.js +90 -90
  95. package/server/mind/affect.js +265 -265
  96. package/server/mind/attention.js +200 -200
  97. package/server/mind/bonds.js +565 -565
  98. package/server/mind/clock.js +83 -83
  99. package/server/mind/continuity.js +562 -562
  100. package/server/mind/conversation-origin.js +175 -175
  101. package/server/mind/days.js +210 -210
  102. package/server/mind/evidence-read.js +45 -45
  103. package/server/mind/faces.js +373 -373
  104. package/server/mind/guard.js +60 -60
  105. package/server/mind/initiative.js +338 -338
  106. package/server/mind/life/activities.js +21 -21
  107. package/server/mind/life/context.js +330 -323
  108. package/server/mind/life/diary.js +20 -0
  109. package/server/mind/life/growth.js +261 -254
  110. package/server/mind/life/index.js +10 -2
  111. package/server/mind/life/presence.js +305 -305
  112. package/server/mind/meetings.js +1069 -1069
  113. package/server/mind/memory-policy.js +48 -48
  114. package/server/mind/memory.js +888 -888
  115. package/server/mind/nature.js +358 -358
  116. package/server/mind/novelty.js +19 -19
  117. package/server/mind/periods.js +118 -118
  118. package/server/mind/reading.js +120 -120
  119. package/server/mind/relationship-context.js +71 -0
  120. package/server/mind/relationships.js +390 -279
  121. package/server/mind/salience.js +201 -201
  122. package/server/mind/schema.js +567 -567
  123. package/server/mind/self.js +484 -484
  124. package/server/mind/thoughts.js +274 -274
  125. package/server/mind/time/agenda.js +242 -242
  126. package/server/mind/time/api.js +200 -200
  127. package/server/mind/time/attention.js +34 -34
  128. package/server/mind/time/delivery-links.js +130 -130
  129. package/server/mind/time/model-material.js +77 -77
  130. package/server/mind/time/presentation.js +51 -51
  131. package/server/mind/time/schedule.js +83 -83
  132. package/server/mind/time/schema.js +84 -84
  133. package/server/mind/time/search.js +383 -383
  134. package/server/mind/time/sharing.js +472 -472
  135. package/server/mind/time/task-links.js +311 -311
  136. package/server/mind/time/works.js +432 -432
  137. package/server/mind/traits.js +191 -191
  138. package/server/mind/view.js +9 -1
  139. package/server/runtime/installation.js +116 -116
  140. package/server/runtime/update-execution.js +2 -1
  141. package/server/runtime/update-manager.js +140 -140
  142. package/server/storage/data-package.js +298 -298
  143. package/server/storage/data-transfer-api.js +50 -50
  144. package/server/storage/data-transfer-worker.js +18 -18
  145. package/server/storage/data-transfer.js +416 -416
  146. package/server/studio/display-names.js +139 -139
  147. package/server/studio/management.js +102 -102
  148. package/server/studio/updates.js +144 -144
  149. package/server/version.js +10 -10
  150. package/public/app/assets/ChatsPage-DfyPCS9N.css +0 -1
  151. package/public/app/assets/HeartPage-Bft9wZp5.css +0 -1
  152. package/public/app/assets/LifePage-BUi5euBF.css +0 -1
  153. package/public/app/assets/MemoryPage-cZvXfkXb.css +0 -1
  154. package/public/app/assets/NaturePage-CHkpb5NK.css +0 -1
  155. package/public/app/assets/PeoplePage-BTU0H1pM.css +0 -1
  156. package/public/app/assets/TimePage-BOms_n4I.css +0 -1
@@ -1,288 +1,288 @@
1
- import { formatSessionKey, parseSessionKey } from "../session-key.js";
2
- import { segmentFacts } from "../segments.js";
3
-
4
- const CQ_ESCAPES = {
5
- ",": ",",
6
- "[": "[",
7
- "]": "]",
8
- "&": "&",
9
- };
10
- const decodeCq = (value) =>
11
- String(value).replace(
12
- /,|[|]|&/g,
13
- (token) => CQ_ESCAPES[token],
14
- );
15
-
16
- function parseCqMessage(raw) {
17
- const source = String(raw || ""),
18
- segments = [],
19
- pattern = /\[CQ:([^,\]]+)((?:,[^\]]*)?)\]/g;
20
- let cursor = 0,
21
- match;
22
- while ((match = pattern.exec(source))) {
23
- if (match.index > cursor)
24
- segments.push({
25
- type: "text",
26
- data: { text: source.slice(cursor, match.index) },
27
- });
28
- const data = {};
29
- for (const item of match[2].replace(/^,/, "").split(",")) {
30
- if (!item) continue;
31
- const [key, ...parts] = item.split("=");
32
- if (key) data[key] = decodeCq(parts.join("="));
33
- }
34
- segments.push({ type: match[1], data });
35
- cursor = pattern.lastIndex;
36
- }
37
- if (cursor < source.length)
38
- segments.push({ type: "text", data: { text: source.slice(cursor) } });
39
- return segments.length
40
- ? segments
41
- : [{ type: "text", data: { text: source } }];
42
- }
43
-
44
- const stickerTypes = new Set([
45
- "face",
46
- "mface",
47
- "market_face",
48
- "bface",
49
- "sface",
50
- "sticker",
51
- "dice",
52
- "rps",
53
- "poke",
54
- ]);
55
- export function senderLabel(sender, userId) {
56
- const id = String(userId ?? "");
57
- const values = [sender?.card, sender?.nickname, sender?.nick]
58
- .map((value) => String(value || "").trim())
59
- .filter(Boolean);
60
- const readable = values.find(
61
- (value) => value !== id && !/^\d{4,20}$/.test(value),
62
- );
63
- return (readable || values.find((value) => value !== id) || id).slice(0, 100);
64
- }
65
-
66
- export const isSticker = (segment) =>
67
- stickerTypes.has(String(segment?.type || "").toLowerCase()) ||
68
- [segment?.data?.sub_type, segment?.data?.type]
69
- .map((value) => String(value || "").toLowerCase())
70
- .includes("sticker") ||
71
- (String(segment?.type || "").toLowerCase() === "image" &&
72
- Boolean(
73
- segment?.data?.emoji_id ||
74
- segment?.data?.emoji_package_id ||
75
- (segment?.data?.summary && segment?.data?.key),
76
- ));
77
-
78
- function replyTarget(event, safeSegments, botMessageIds) {
79
- const replies = safeSegments.filter((segment) => segment.type === "reply"),
80
- ids = replies
81
- .map((segment) => segment.data.id)
82
- .filter((id) => id != null)
83
- .map(String),
84
- senderIds = [
85
- event.reply?.user_id,
86
- event.reply?.sender?.user_id,
87
- event.reply?.sender?.id,
88
- ...replies.map((segment) => segment.data.qq),
89
- ...replies.map((segment) => segment.data.user_id),
90
- ...replies.map((segment) => segment.data.sender?.user_id),
91
- ...replies.map((segment) => segment.data.sender?.id),
92
- ]
93
- .filter((id) => id != null)
94
- .map(String),
95
- known =
96
- typeof botMessageIds?.has === "function" &&
97
- ids.some((id) => botMessageIds.has(id));
98
- return known || senderIds.includes(String(event.self_id));
99
- }
100
-
101
- export const onebot = {
102
- type: "onebot",
103
- capabilities: {
104
- mention: true,
105
- quote: true,
106
- image: true,
107
- thread: false,
108
- sticker: true,
109
- },
110
- mediaHosts: /(^|\.)(qpic\.cn|gtimg\.cn|qq\.com\.cn|qq\.com|myqcloud\.com)$/i,
111
- usableMediaUrl(url) {
112
- try {
113
- const u = new URL(url);
114
- return (
115
- u.protocol === "https:" &&
116
- !u.username &&
117
- !u.password &&
118
- this.mediaHosts.test(u.hostname)
119
- );
120
- } catch {
121
- return false;
122
- }
123
- },
124
- sendAction(message) {
125
- return parseSessionKey(message.sessionId).kind === "group"
126
- ? "send_group_msg"
127
- : "send_private_msg";
128
- },
129
- sendParams(message, text) {
130
- const parsed = parseSessionKey(message.sessionId);
131
- return {
132
- message: [{ type: "text", data: { text } }],
133
- [parsed.kind === "group" ? "group_id" : "user_id"]:
134
- parsed.kind === "group"
135
- ? Number(message.raw?.group_id ?? parsed.nativeId)
136
- : Number(message.raw?.user_id ?? message.userId ?? parsed.nativeId),
137
- };
138
- },
139
- nativeChatId(sessionId) {
140
- return Number(parseSessionKey(sessionId).nativeId);
141
- },
142
- quotedMessage(data, current) {
143
- if (!data || typeof data !== "object" || !Array.isArray(data.message))
144
- return null;
145
- try {
146
- const parsed = parseSessionKey(current.sessionId);
147
- if (data.group_id && String(data.group_id) !== parsed.nativeId)
148
- return null;
149
- } catch {
150
- return null;
151
- }
152
- const sender = String(data.sender?.user_id || data.user_id || "unknown");
153
- return {
154
- sessionId: current.sessionId,
155
- kind: current.kind,
156
- userId: sender === current.accountId ? "bot" : sender,
157
- name: senderLabel(data.sender, sender),
158
- text: data.message
159
- .filter((s) => s.type === "text")
160
- .map((s) => s.data?.text || "")
161
- .join(""),
162
- role: sender === current.accountId ? "assistant" : "user",
163
- raw: {
164
- ...data,
165
- self_id: current.accountId,
166
- message_id: current.replyId,
167
- },
168
- };
169
- },
170
- };
171
-
172
- export function normalize(event, { botMessageIds } = {}) {
173
- if (
174
- !event ||
175
- typeof event !== "object" ||
176
- event.post_type !== "message" ||
177
- !["group", "private"].includes(event.message_type) ||
178
- String(event.user_id) === String(event.self_id)
179
- )
180
- return null;
181
- if (
182
- !/^\d+$/.test(String(event.user_id)) ||
183
- !/^\d+$/.test(String(event.self_id)) ||
184
- event.message_id == null ||
185
- (event.message_type === "group" && !/^\d+$/.test(String(event.group_id)))
186
- )
187
- return null;
188
- const segments = Array.isArray(event.message)
189
- ? event.message
190
- : parseCqMessage(event.raw_message || event.message || "");
191
- const safeSegments = segments.filter(
192
- (s) => s && typeof s === "object" && s.data && typeof s.data === "object",
193
- );
194
- const atId = (segment) => {
195
- const data = segment?.data || {};
196
- const id = data.qq ?? data.user_id;
197
- return id == null || id === "" ? "" : String(id);
198
- };
199
- const atSelf = safeSegments.some(
200
- (s) =>
201
- String(s.type || "").toLowerCase() === "at" &&
202
- atId(s) === String(event.self_id),
203
- ),
204
- replyToBot = replyTarget(event, safeSegments, botMessageIds),
205
- media = {
206
- images: safeSegments.filter(
207
- (s) => String(s.type).toLowerCase() === "image" && !isSticker(s),
208
- ).length,
209
- stickers: safeSegments.filter(isSticker).length,
210
- other: safeSegments.filter((s) =>
211
- ["record", "video", "file", "json", "xml"].includes(
212
- String(s.type).toLowerCase(),
213
- ),
214
- ).length,
215
- },
216
- textParts = safeSegments.map((s) => {
217
- const type = String(s.type || "").toLowerCase();
218
- if (type === "text" && typeof s.data.text === "string")
219
- return s.data.text;
220
- if (type === "at")
221
- return atId(s) === String(event.self_id) ? "@我" : "[提及成员]";
222
- if (type === "image" && !isSticker(s)) return "[图片]";
223
- if (isSticker(s)) return "[表情]";
224
- if (["record", "video", "file", "json", "xml"].includes(type))
225
- return "[媒体]";
226
- return "";
227
- }),
228
- plainText = safeSegments
229
- .filter((s) => s.type === "text" && typeof s.data.text === "string")
230
- .map((s) => s.data.text)
231
- .join("")
232
- .trim(),
233
- visibleText = textParts.join("").trim(),
234
- text =
235
- visibleText ||
236
- (atSelf || replyToBot ? (replyToBot ? "[回复你]" : "[叫了你一声]") : "");
237
- if (!text) return null;
238
- const mediaCount = media.images + media.stickers + media.other;
239
- const kind = event.message_type;
240
- const nativeId = String(event.group_id || event.user_id);
241
- const facts = segmentFacts(safeSegments);
242
- return {
243
- sessionId: formatSessionKey({
244
- channel: "onebot",
245
- accountId: String(event.self_id),
246
- kind,
247
- nativeId,
248
- }),
249
- channel: "onebot",
250
- accountId: String(event.self_id),
251
- nativeId,
252
- segments: safeSegments,
253
- kind,
254
- userId: String(event.user_id),
255
- name: senderLabel(event.sender, event.user_id),
256
- accountName: senderLabel(
257
- { nickname: event.sender?.nickname, nick: event.sender?.nick },
258
- event.user_id,
259
- ),
260
- text: text.slice(0, 4000),
261
- mentioned: atSelf || replyToBot,
262
- replyToBot,
263
- platformId: String(event.message_id),
264
- time: event.time ? event.time * 1000 : Date.now(),
265
- mentions: facts.mentions,
266
- replyId: facts.replyId,
267
- attachments: facts.attachments,
268
- media: {
269
- ...media,
270
- count: mediaCount,
271
- only: mediaCount > 0 && !plainText && !atSelf && !replyToBot,
272
- imageOnly:
273
- media.images > 0 &&
274
- mediaCount === media.images &&
275
- !plainText &&
276
- !atSelf &&
277
- !replyToBot,
278
- stickerOnly:
279
- media.stickers > 0 &&
280
- mediaCount === media.stickers &&
281
- !plainText &&
282
- !atSelf &&
283
- !replyToBot,
284
- },
285
- eventId: `${event.self_id}:${event.message_type}:${event.group_id || event.user_id}:${event.message_id}`,
286
- raw: event,
287
- };
288
- }
1
+ import { formatSessionKey, parseSessionKey } from "../session-key.js";
2
+ import { segmentFacts } from "../segments.js";
3
+
4
+ const CQ_ESCAPES = {
5
+ "&#44;": ",",
6
+ "&#91;": "[",
7
+ "&#93;": "]",
8
+ "&amp;": "&",
9
+ };
10
+ const decodeCq = (value) =>
11
+ String(value).replace(
12
+ /&#44;|&#91;|&#93;|&amp;/g,
13
+ (token) => CQ_ESCAPES[token],
14
+ );
15
+
16
+ function parseCqMessage(raw) {
17
+ const source = String(raw || ""),
18
+ segments = [],
19
+ pattern = /\[CQ:([^,\]]+)((?:,[^\]]*)?)\]/g;
20
+ let cursor = 0,
21
+ match;
22
+ while ((match = pattern.exec(source))) {
23
+ if (match.index > cursor)
24
+ segments.push({
25
+ type: "text",
26
+ data: { text: source.slice(cursor, match.index) },
27
+ });
28
+ const data = {};
29
+ for (const item of match[2].replace(/^,/, "").split(",")) {
30
+ if (!item) continue;
31
+ const [key, ...parts] = item.split("=");
32
+ if (key) data[key] = decodeCq(parts.join("="));
33
+ }
34
+ segments.push({ type: match[1], data });
35
+ cursor = pattern.lastIndex;
36
+ }
37
+ if (cursor < source.length)
38
+ segments.push({ type: "text", data: { text: source.slice(cursor) } });
39
+ return segments.length
40
+ ? segments
41
+ : [{ type: "text", data: { text: source } }];
42
+ }
43
+
44
+ const stickerTypes = new Set([
45
+ "face",
46
+ "mface",
47
+ "market_face",
48
+ "bface",
49
+ "sface",
50
+ "sticker",
51
+ "dice",
52
+ "rps",
53
+ "poke",
54
+ ]);
55
+ export function senderLabel(sender, userId) {
56
+ const id = String(userId ?? "");
57
+ const values = [sender?.card, sender?.nickname, sender?.nick]
58
+ .map((value) => String(value || "").trim())
59
+ .filter(Boolean);
60
+ const readable = values.find(
61
+ (value) => value !== id && !/^\d{4,20}$/.test(value),
62
+ );
63
+ return (readable || values.find((value) => value !== id) || id).slice(0, 100);
64
+ }
65
+
66
+ export const isSticker = (segment) =>
67
+ stickerTypes.has(String(segment?.type || "").toLowerCase()) ||
68
+ [segment?.data?.sub_type, segment?.data?.type]
69
+ .map((value) => String(value || "").toLowerCase())
70
+ .includes("sticker") ||
71
+ (String(segment?.type || "").toLowerCase() === "image" &&
72
+ Boolean(
73
+ segment?.data?.emoji_id ||
74
+ segment?.data?.emoji_package_id ||
75
+ (segment?.data?.summary && segment?.data?.key),
76
+ ));
77
+
78
+ function replyTarget(event, safeSegments, botMessageIds) {
79
+ const replies = safeSegments.filter((segment) => segment.type === "reply"),
80
+ ids = replies
81
+ .map((segment) => segment.data.id)
82
+ .filter((id) => id != null)
83
+ .map(String),
84
+ senderIds = [
85
+ event.reply?.user_id,
86
+ event.reply?.sender?.user_id,
87
+ event.reply?.sender?.id,
88
+ ...replies.map((segment) => segment.data.qq),
89
+ ...replies.map((segment) => segment.data.user_id),
90
+ ...replies.map((segment) => segment.data.sender?.user_id),
91
+ ...replies.map((segment) => segment.data.sender?.id),
92
+ ]
93
+ .filter((id) => id != null)
94
+ .map(String),
95
+ known =
96
+ typeof botMessageIds?.has === "function" &&
97
+ ids.some((id) => botMessageIds.has(id));
98
+ return known || senderIds.includes(String(event.self_id));
99
+ }
100
+
101
+ export const onebot = {
102
+ type: "onebot",
103
+ capabilities: {
104
+ mention: true,
105
+ quote: true,
106
+ image: true,
107
+ thread: false,
108
+ sticker: true,
109
+ },
110
+ mediaHosts: /(^|\.)(qpic\.cn|gtimg\.cn|qq\.com\.cn|qq\.com|myqcloud\.com)$/i,
111
+ usableMediaUrl(url) {
112
+ try {
113
+ const u = new URL(url);
114
+ return (
115
+ u.protocol === "https:" &&
116
+ !u.username &&
117
+ !u.password &&
118
+ this.mediaHosts.test(u.hostname)
119
+ );
120
+ } catch {
121
+ return false;
122
+ }
123
+ },
124
+ sendAction(message) {
125
+ return parseSessionKey(message.sessionId).kind === "group"
126
+ ? "send_group_msg"
127
+ : "send_private_msg";
128
+ },
129
+ sendParams(message, text) {
130
+ const parsed = parseSessionKey(message.sessionId);
131
+ return {
132
+ message: [{ type: "text", data: { text } }],
133
+ [parsed.kind === "group" ? "group_id" : "user_id"]:
134
+ parsed.kind === "group"
135
+ ? Number(message.raw?.group_id ?? parsed.nativeId)
136
+ : Number(message.raw?.user_id ?? message.userId ?? parsed.nativeId),
137
+ };
138
+ },
139
+ nativeChatId(sessionId) {
140
+ return Number(parseSessionKey(sessionId).nativeId);
141
+ },
142
+ quotedMessage(data, current) {
143
+ if (!data || typeof data !== "object" || !Array.isArray(data.message))
144
+ return null;
145
+ try {
146
+ const parsed = parseSessionKey(current.sessionId);
147
+ if (data.group_id && String(data.group_id) !== parsed.nativeId)
148
+ return null;
149
+ } catch {
150
+ return null;
151
+ }
152
+ const sender = String(data.sender?.user_id || data.user_id || "unknown");
153
+ return {
154
+ sessionId: current.sessionId,
155
+ kind: current.kind,
156
+ userId: sender === current.accountId ? "bot" : sender,
157
+ name: senderLabel(data.sender, sender),
158
+ text: data.message
159
+ .filter((s) => s.type === "text")
160
+ .map((s) => s.data?.text || "")
161
+ .join(""),
162
+ role: sender === current.accountId ? "assistant" : "user",
163
+ raw: {
164
+ ...data,
165
+ self_id: current.accountId,
166
+ message_id: current.replyId,
167
+ },
168
+ };
169
+ },
170
+ };
171
+
172
+ export function normalize(event, { botMessageIds } = {}) {
173
+ if (
174
+ !event ||
175
+ typeof event !== "object" ||
176
+ event.post_type !== "message" ||
177
+ !["group", "private"].includes(event.message_type) ||
178
+ String(event.user_id) === String(event.self_id)
179
+ )
180
+ return null;
181
+ if (
182
+ !/^\d+$/.test(String(event.user_id)) ||
183
+ !/^\d+$/.test(String(event.self_id)) ||
184
+ event.message_id == null ||
185
+ (event.message_type === "group" && !/^\d+$/.test(String(event.group_id)))
186
+ )
187
+ return null;
188
+ const segments = Array.isArray(event.message)
189
+ ? event.message
190
+ : parseCqMessage(event.raw_message || event.message || "");
191
+ const safeSegments = segments.filter(
192
+ (s) => s && typeof s === "object" && s.data && typeof s.data === "object",
193
+ );
194
+ const atId = (segment) => {
195
+ const data = segment?.data || {};
196
+ const id = data.qq ?? data.user_id;
197
+ return id == null || id === "" ? "" : String(id);
198
+ };
199
+ const atSelf = safeSegments.some(
200
+ (s) =>
201
+ String(s.type || "").toLowerCase() === "at" &&
202
+ atId(s) === String(event.self_id),
203
+ ),
204
+ replyToBot = replyTarget(event, safeSegments, botMessageIds),
205
+ media = {
206
+ images: safeSegments.filter(
207
+ (s) => String(s.type).toLowerCase() === "image" && !isSticker(s),
208
+ ).length,
209
+ stickers: safeSegments.filter(isSticker).length,
210
+ other: safeSegments.filter((s) =>
211
+ ["record", "video", "file", "json", "xml"].includes(
212
+ String(s.type).toLowerCase(),
213
+ ),
214
+ ).length,
215
+ },
216
+ textParts = safeSegments.map((s) => {
217
+ const type = String(s.type || "").toLowerCase();
218
+ if (type === "text" && typeof s.data.text === "string")
219
+ return s.data.text;
220
+ if (type === "at")
221
+ return atId(s) === String(event.self_id) ? "@我" : "[提及成员]";
222
+ if (type === "image" && !isSticker(s)) return "[图片]";
223
+ if (isSticker(s)) return "[表情]";
224
+ if (["record", "video", "file", "json", "xml"].includes(type))
225
+ return "[媒体]";
226
+ return "";
227
+ }),
228
+ plainText = safeSegments
229
+ .filter((s) => s.type === "text" && typeof s.data.text === "string")
230
+ .map((s) => s.data.text)
231
+ .join("")
232
+ .trim(),
233
+ visibleText = textParts.join("").trim(),
234
+ text =
235
+ visibleText ||
236
+ (atSelf || replyToBot ? (replyToBot ? "[回复你]" : "[叫了你一声]") : "");
237
+ if (!text) return null;
238
+ const mediaCount = media.images + media.stickers + media.other;
239
+ const kind = event.message_type;
240
+ const nativeId = String(event.group_id || event.user_id);
241
+ const facts = segmentFacts(safeSegments);
242
+ return {
243
+ sessionId: formatSessionKey({
244
+ channel: "onebot",
245
+ accountId: String(event.self_id),
246
+ kind,
247
+ nativeId,
248
+ }),
249
+ channel: "onebot",
250
+ accountId: String(event.self_id),
251
+ nativeId,
252
+ segments: safeSegments,
253
+ kind,
254
+ userId: String(event.user_id),
255
+ name: senderLabel(event.sender, event.user_id),
256
+ accountName: senderLabel(
257
+ { nickname: event.sender?.nickname, nick: event.sender?.nick },
258
+ event.user_id,
259
+ ),
260
+ text: text.slice(0, 4000),
261
+ mentioned: atSelf || replyToBot,
262
+ replyToBot,
263
+ platformId: String(event.message_id),
264
+ time: event.time ? event.time * 1000 : Date.now(),
265
+ mentions: facts.mentions,
266
+ replyId: facts.replyId,
267
+ attachments: facts.attachments,
268
+ media: {
269
+ ...media,
270
+ count: mediaCount,
271
+ only: mediaCount > 0 && !plainText && !atSelf && !replyToBot,
272
+ imageOnly:
273
+ media.images > 0 &&
274
+ mediaCount === media.images &&
275
+ !plainText &&
276
+ !atSelf &&
277
+ !replyToBot,
278
+ stickerOnly:
279
+ media.stickers > 0 &&
280
+ mediaCount === media.stickers &&
281
+ !plainText &&
282
+ !atSelf &&
283
+ !replyToBot,
284
+ },
285
+ eventId: `${event.self_id}:${event.message_type}:${event.group_id || event.user_id}:${event.message_id}`,
286
+ raw: event,
287
+ };
288
+ }