luckytri 1.1.1 → 1.2.0

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 (96) hide show
  1. package/README.en.md +14 -12
  2. package/README.md +4 -2
  3. package/docs/en/CHANGELOG.md +28 -0
  4. package/docs/en/architecture.md +12 -10
  5. package/docs/en/chat-style.md +13 -13
  6. package/docs/en/cognition.md +12 -4
  7. package/docs/en/evolution.md +35 -0
  8. package/docs/en/her-life.md +5 -2
  9. package/docs/en/vision.md +50 -0
  10. package/docs/zh/CHANGELOG.md +28 -0
  11. package/docs/zh/architecture.md +12 -10
  12. package/docs/zh/chat-style.md +17 -17
  13. package/docs/zh/cognition.md +12 -4
  14. package/docs/zh/evolution.md +35 -0
  15. package/docs/zh/her-life.md +5 -2
  16. package/docs/zh/vision.md +50 -0
  17. package/package.json +4 -1
  18. package/public/app/assets/{ChatsPage-OQgvV8YR.js → ChatsPage-eDqFIg97.js} +1 -1
  19. package/public/app/assets/{Empty-DjvoHpAi.js → Empty-CAGOJsfD.js} +1 -1
  20. package/public/app/assets/{Field-BECf8Ykc.js → Field-CRz-JmN1.js} +1 -1
  21. package/public/app/assets/{HeartPage-6s6prdTE.js → HeartPage-BGWoeMrC.js} +1 -1
  22. package/public/app/assets/{LifePage-g-8hiKC6.js → LifePage-iao3o3wt.js} +1 -1
  23. package/public/app/assets/{MemoryPage-efIhloIp.js → MemoryPage-Dwi2yi7E.js} +1 -1
  24. package/public/app/assets/NaturePage-DniImo9U.js +3 -0
  25. package/public/app/assets/{NaturePage-CHkpb5NK.css → NaturePage-zguDpGWY.css} +1 -1
  26. package/public/app/assets/{PeoplePage-DQZ0uevM.js → PeoplePage-D-MC3JEt.js} +1 -1
  27. package/public/app/assets/{PluginsPage-DZQFEyxq.js → PluginsPage-D7dJTf96.js} +1 -1
  28. package/public/app/assets/SystemPage-C5CUHjGh.js +1 -0
  29. package/public/app/assets/{SystemPage-BHHcZb1h.css → SystemPage-C_Y5M7JU.css} +1 -1
  30. package/public/app/assets/{TimePage-B3oncGqb.js → TimePage-Dwhr3-92.js} +1 -1
  31. package/public/app/assets/{index-CqRx6L9a.js → index-L0bHax8C.js} +3 -3
  32. package/public/app/assets/{knowledge-Dj6hHClj.js → knowledge-BTDNafzl.js} +1 -1
  33. package/public/app/assets/{media-B1i_W5FC.js → media-BBzOAfNG.js} +1 -1
  34. package/public/app/assets/{mind-BY8WSI7v.js → mind-CBTkFje2.js} +1 -1
  35. package/public/app/index.html +3 -3
  36. package/public/guide.en.html +3 -3
  37. package/public/guide.html +3 -3
  38. package/scripts/migrate-instance.js +27 -0
  39. package/scripts/update-instance.js +21 -2
  40. package/server/channels/contract.js +5 -1
  41. package/server/channels/hub.js +2 -2
  42. package/server/channels/onebot/adapter.js +291 -288
  43. package/server/channels/onebot/index.js +2 -2
  44. package/server/channels/qqbot/api.js +194 -199
  45. package/server/core/context-builder.js +20 -3
  46. package/server/core/conversation-cues.js +5 -4
  47. package/server/core/conversation-grounding.js +20 -17
  48. package/server/core/conversation-needs.js +32 -0
  49. package/server/core/dialogue-context.js +2 -5
  50. package/server/core/dialogue-contract.js +23 -16
  51. package/server/core/message-scheduler.js +85 -78
  52. package/server/core/model-manager.js +113 -16
  53. package/server/core/model-presets.js +1 -1
  54. package/server/core/orchestrator.js +18 -2
  55. package/server/core/persona-manager.js +14 -5
  56. package/server/core/reader-check.js +47 -42
  57. package/server/core/reply-delivery.js +125 -34
  58. package/server/core/response-validator.js +28 -1
  59. package/server/core/retired-prompts.js +5 -0
  60. package/server/core/speech-length.js +43 -0
  61. package/server/core/turn-processor.js +38 -5
  62. package/server/core/turn.js +70 -16
  63. package/server/i18n/errors.js +30 -0
  64. package/server/i18n/labels.js +5 -0
  65. package/server/index.js +3 -0
  66. package/server/knowledge/api.js +2 -1
  67. package/server/knowledge/embedding-profile.js +127 -29
  68. package/server/knowledge/manager.js +103 -34
  69. package/server/knowledge/schema.js +197 -158
  70. package/server/mind/affect.js +267 -265
  71. package/server/mind/aftermath.js +169 -0
  72. package/server/mind/anticipations.js +2 -0
  73. package/server/mind/clock.js +91 -83
  74. package/server/mind/discretion.js +20 -0
  75. package/server/mind/episodes.js +2 -1
  76. package/server/mind/index.js +17 -2
  77. package/server/mind/initiative.js +29 -14
  78. package/server/mind/memory.js +47 -5
  79. package/server/mind/recall/cue.js +67 -0
  80. package/server/mind/recall/index.js +183 -29
  81. package/server/mind/recall/roots.js +231 -0
  82. package/server/mind/recall/schema.js +2 -0
  83. package/server/mind/recall/store.js +113 -30
  84. package/server/mind/salience.js +22 -1
  85. package/server/mind/schema.js +5 -0
  86. package/server/mind/thoughts.js +276 -274
  87. package/server/mind/view.js +100 -60
  88. package/server/plugins/install.js +1 -6
  89. package/server/plugins/zip.js +18 -4
  90. package/server/runtime/data-migration.js +193 -0
  91. package/server/runtime/update-execution.js +43 -4
  92. package/server/runtime/update-manager.js +4 -1
  93. package/server/storage/archive.js +1 -1
  94. package/server/storage/backup-scheduler.js +15 -3
  95. package/public/app/assets/NaturePage-cjgIe6wY.js +0 -3
  96. package/public/app/assets/SystemPage-DjYS33Sv.js +0 -1
@@ -11,7 +11,11 @@
11
11
  // start() begin accepting or opening the connection for the
12
12
  // current settings; safe to call again after a change
13
13
  // stop() drop the connection and stop timers; safe to repeat
14
- // send(message, text) deliver one bubble; resolves { message_id }
14
+ // send(message, text, options) deliver one bubble; resolves { message_id }.
15
+ // options.replyTo, when present, is the platform id of
16
+ // the message this bubble answers; a channel with the
17
+ // quote capability may show it as a quote, any other
18
+ // channel ignores it
15
19
  // fetchQuoted(message) the message that `message.replyId` points to, or null
16
20
  // fetchImage(file) a fresh URL or bytes for an expired image, or null
17
21
  // fetchMedia(file, type) optional: a voice or file the same way, or null
@@ -103,9 +103,9 @@ export function createChannelHub(store, channels, { env = process.env } = {}) {
103
103
  return true;
104
104
  }
105
105
 
106
- const send = async (message, text) => {
106
+ const send = async (message, text, options) => {
107
107
  if (message?.simulated) return { message_id: "sim" };
108
- return resolve(message.sessionId).send(message, text);
108
+ return resolve(message.sessionId).send(message, text, options);
109
109
  };
110
110
 
111
111
  function canReach(sessionId) {
@@ -1,288 +1,291 @@
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, options) {
130
+ const parsed = parseSessionKey(message.sessionId);
131
+ const reply = /^\d+$/.test(String(options?.replyTo || ""))
132
+ ? [{ type: "reply", data: { id: String(options.replyTo) } }]
133
+ : [];
134
+ return {
135
+ message: [...reply, { type: "text", data: { text } }],
136
+ [parsed.kind === "group" ? "group_id" : "user_id"]:
137
+ parsed.kind === "group"
138
+ ? Number(message.raw?.group_id ?? parsed.nativeId)
139
+ : Number(message.raw?.user_id ?? message.userId ?? parsed.nativeId),
140
+ };
141
+ },
142
+ nativeChatId(sessionId) {
143
+ return Number(parseSessionKey(sessionId).nativeId);
144
+ },
145
+ quotedMessage(data, current) {
146
+ if (!data || typeof data !== "object" || !Array.isArray(data.message))
147
+ return null;
148
+ try {
149
+ const parsed = parseSessionKey(current.sessionId);
150
+ if (data.group_id && String(data.group_id) !== parsed.nativeId)
151
+ return null;
152
+ } catch {
153
+ return null;
154
+ }
155
+ const sender = String(data.sender?.user_id || data.user_id || "unknown");
156
+ return {
157
+ sessionId: current.sessionId,
158
+ kind: current.kind,
159
+ userId: sender === current.accountId ? "bot" : sender,
160
+ name: senderLabel(data.sender, sender),
161
+ text: data.message
162
+ .filter((s) => s.type === "text")
163
+ .map((s) => s.data?.text || "")
164
+ .join(""),
165
+ role: sender === current.accountId ? "assistant" : "user",
166
+ raw: {
167
+ ...data,
168
+ self_id: current.accountId,
169
+ message_id: current.replyId,
170
+ },
171
+ };
172
+ },
173
+ };
174
+
175
+ export function normalize(event, { botMessageIds } = {}) {
176
+ if (
177
+ !event ||
178
+ typeof event !== "object" ||
179
+ event.post_type !== "message" ||
180
+ !["group", "private"].includes(event.message_type) ||
181
+ String(event.user_id) === String(event.self_id)
182
+ )
183
+ return null;
184
+ if (
185
+ !/^\d+$/.test(String(event.user_id)) ||
186
+ !/^\d+$/.test(String(event.self_id)) ||
187
+ event.message_id == null ||
188
+ (event.message_type === "group" && !/^\d+$/.test(String(event.group_id)))
189
+ )
190
+ return null;
191
+ const segments = Array.isArray(event.message)
192
+ ? event.message
193
+ : parseCqMessage(event.raw_message || event.message || "");
194
+ const safeSegments = segments.filter(
195
+ (s) => s && typeof s === "object" && s.data && typeof s.data === "object",
196
+ );
197
+ const atId = (segment) => {
198
+ const data = segment?.data || {};
199
+ const id = data.qq ?? data.user_id;
200
+ return id == null || id === "" ? "" : String(id);
201
+ };
202
+ const atSelf = safeSegments.some(
203
+ (s) =>
204
+ String(s.type || "").toLowerCase() === "at" &&
205
+ atId(s) === String(event.self_id),
206
+ ),
207
+ replyToBot = replyTarget(event, safeSegments, botMessageIds),
208
+ media = {
209
+ images: safeSegments.filter(
210
+ (s) => String(s.type).toLowerCase() === "image" && !isSticker(s),
211
+ ).length,
212
+ stickers: safeSegments.filter(isSticker).length,
213
+ other: safeSegments.filter((s) =>
214
+ ["record", "video", "file", "json", "xml"].includes(
215
+ String(s.type).toLowerCase(),
216
+ ),
217
+ ).length,
218
+ },
219
+ textParts = safeSegments.map((s) => {
220
+ const type = String(s.type || "").toLowerCase();
221
+ if (type === "text" && typeof s.data.text === "string")
222
+ return s.data.text;
223
+ if (type === "at")
224
+ return atId(s) === String(event.self_id) ? "@我" : "[提及成员]";
225
+ if (type === "image" && !isSticker(s)) return "[图片]";
226
+ if (isSticker(s)) return "[表情]";
227
+ if (["record", "video", "file", "json", "xml"].includes(type))
228
+ return "[媒体]";
229
+ return "";
230
+ }),
231
+ plainText = safeSegments
232
+ .filter((s) => s.type === "text" && typeof s.data.text === "string")
233
+ .map((s) => s.data.text)
234
+ .join("")
235
+ .trim(),
236
+ visibleText = textParts.join("").trim(),
237
+ text =
238
+ visibleText ||
239
+ (atSelf || replyToBot ? (replyToBot ? "[回复你]" : "[叫了你一声]") : "");
240
+ if (!text) return null;
241
+ const mediaCount = media.images + media.stickers + media.other;
242
+ const kind = event.message_type;
243
+ const nativeId = String(event.group_id || event.user_id);
244
+ const facts = segmentFacts(safeSegments);
245
+ return {
246
+ sessionId: formatSessionKey({
247
+ channel: "onebot",
248
+ accountId: String(event.self_id),
249
+ kind,
250
+ nativeId,
251
+ }),
252
+ channel: "onebot",
253
+ accountId: String(event.self_id),
254
+ nativeId,
255
+ segments: safeSegments,
256
+ kind,
257
+ userId: String(event.user_id),
258
+ name: senderLabel(event.sender, event.user_id),
259
+ accountName: senderLabel(
260
+ { nickname: event.sender?.nickname, nick: event.sender?.nick },
261
+ event.user_id,
262
+ ),
263
+ text: text.slice(0, 4000),
264
+ mentioned: atSelf || replyToBot,
265
+ replyToBot,
266
+ platformId: String(event.message_id),
267
+ time: event.time ? event.time * 1000 : Date.now(),
268
+ mentions: facts.mentions,
269
+ replyId: facts.replyId,
270
+ attachments: facts.attachments,
271
+ media: {
272
+ ...media,
273
+ count: mediaCount,
274
+ only: mediaCount > 0 && !plainText && !atSelf && !replyToBot,
275
+ imageOnly:
276
+ media.images > 0 &&
277
+ mediaCount === media.images &&
278
+ !plainText &&
279
+ !atSelf &&
280
+ !replyToBot,
281
+ stickerOnly:
282
+ media.stickers > 0 &&
283
+ mediaCount === media.stickers &&
284
+ !plainText &&
285
+ !atSelf &&
286
+ !replyToBot,
287
+ },
288
+ eventId: `${event.self_id}:${event.message_type}:${event.group_id || event.user_id}:${event.message_id}`,
289
+ raw: event,
290
+ };
291
+ }
@@ -129,11 +129,11 @@ export function createOneBotChannel(
129
129
  });
130
130
  };
131
131
 
132
- const send = async (m, text) => {
132
+ const send = async (m, text, options) => {
133
133
  if (m.simulated) return { message_id: "sim" };
134
134
  const result = await rpc(
135
135
  onebot.sendAction(m),
136
- onebot.sendParams(m, text),
136
+ onebot.sendParams(m, text, options),
137
137
  10000,
138
138
  );
139
139
  if (result?.message_id != null) {