codeksei 0.1.0 → 0.1.1
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/LICENSE +661 -661
- package/README.en.md +109 -47
- package/README.md +79 -58
- package/bin/cyberboss.js +1 -1
- package/package.json +86 -86
- package/scripts/open_shared_wechat_thread.sh +77 -77
- package/scripts/open_wechat_thread.sh +108 -108
- package/scripts/shared-common.js +144 -144
- package/scripts/shared-open.js +14 -14
- package/scripts/shared-start.js +5 -5
- package/scripts/shared-status.js +27 -27
- package/scripts/show_shared_status.sh +45 -45
- package/scripts/start_shared_app_server.sh +52 -52
- package/scripts/start_shared_wechat.sh +94 -94
- package/scripts/timeline-screenshot.sh +14 -14
- package/src/adapters/channel/weixin/account-store.js +99 -99
- package/src/adapters/channel/weixin/api-v2.js +50 -50
- package/src/adapters/channel/weixin/api.js +169 -169
- package/src/adapters/channel/weixin/context-token-store.js +84 -84
- package/src/adapters/channel/weixin/index.js +618 -604
- package/src/adapters/channel/weixin/legacy.js +579 -566
- package/src/adapters/channel/weixin/media-mime.js +22 -22
- package/src/adapters/channel/weixin/media-receive.js +370 -370
- package/src/adapters/channel/weixin/media-send.js +102 -102
- package/src/adapters/channel/weixin/message-utils-v2.js +282 -282
- package/src/adapters/channel/weixin/message-utils.js +199 -199
- package/src/adapters/channel/weixin/redact.js +41 -41
- package/src/adapters/channel/weixin/reminder-queue-store.js +101 -101
- package/src/adapters/channel/weixin/sync-buffer-store.js +35 -35
- package/src/adapters/runtime/codex/events.js +215 -215
- package/src/adapters/runtime/codex/index.js +109 -104
- package/src/adapters/runtime/codex/message-utils.js +95 -95
- package/src/adapters/runtime/codex/model-catalog.js +106 -106
- package/src/adapters/runtime/codex/protocol-leak-monitor.js +75 -75
- package/src/adapters/runtime/codex/rpc-client.js +339 -339
- package/src/adapters/runtime/codex/session-store.js +286 -286
- package/src/app/channel-send-file-cli.js +57 -57
- package/src/app/diary-write-cli.js +236 -88
- package/src/app/note-sync-cli.js +2 -2
- package/src/app/reminder-write-cli.js +215 -210
- package/src/app/review-cli.js +7 -5
- package/src/app/system-checkin-poller.js +64 -64
- package/src/app/system-send-cli.js +129 -129
- package/src/app/timeline-event-cli.js +28 -25
- package/src/app/timeline-screenshot-cli.js +103 -100
- package/src/core/app.js +1763 -1763
- package/src/core/branding.js +2 -1
- package/src/core/command-registry.js +381 -369
- package/src/core/config.js +30 -14
- package/src/core/default-targets.js +163 -163
- package/src/core/durable-note-schema.js +9 -8
- package/src/core/instructions-template.js +17 -16
- package/src/core/note-sync.js +8 -7
- package/src/core/path-utils.js +54 -0
- package/src/core/project-radar.js +11 -10
- package/src/core/review.js +48 -50
- package/src/core/stream-delivery.js +1162 -983
- package/src/core/system-message-dispatcher.js +68 -68
- package/src/core/system-message-queue-store.js +128 -128
- package/src/core/thread-state-store.js +96 -96
- package/src/core/timeline-screenshot-queue-store.js +134 -134
- package/src/core/timezone.js +436 -0
- package/src/core/workspace-bootstrap.js +9 -1
- package/src/index.js +148 -146
- package/src/integrations/timeline/index.js +130 -74
- package/src/integrations/timeline/state-sync.js +240 -0
- package/templates/weixin-instructions.md +12 -38
- package/templates/weixin-operations.md +29 -31
|
@@ -1,282 +1,282 @@
|
|
|
1
|
-
const MESSAGE_TYPE_USER = 1;
|
|
2
|
-
const MESSAGE_TYPE_BOT = 2;
|
|
3
|
-
const MESSAGE_ITEM_TEXT = 1;
|
|
4
|
-
const MESSAGE_ITEM_IMAGE = 2;
|
|
5
|
-
const MESSAGE_ITEM_VOICE = 3;
|
|
6
|
-
const MESSAGE_ITEM_FILE = 4;
|
|
7
|
-
const MESSAGE_ITEM_VIDEO = 5;
|
|
8
|
-
const DEDUP_TTL_MS = 5 * 60_000;
|
|
9
|
-
|
|
10
|
-
function createInboundFilter() {
|
|
11
|
-
const seen = new Map();
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
normalize(message, config, accountId) {
|
|
15
|
-
if (!message || typeof message !== "object") {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
const messageType = Number(message.message_type);
|
|
19
|
-
if (messageType === MESSAGE_TYPE_BOT) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
if (messageType !== 0 && messageType !== MESSAGE_TYPE_USER) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const senderId = normalizeText(message.from_user_id);
|
|
27
|
-
if (!senderId) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const createdAtMs = normalizeMessageTimestampMs(message);
|
|
32
|
-
|
|
33
|
-
const dedupKey = buildDedupKey(message, senderId, createdAtMs);
|
|
34
|
-
pruneSeen(seen);
|
|
35
|
-
if (dedupKey && seen.has(dedupKey)) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
if (dedupKey) {
|
|
39
|
-
seen.set(dedupKey, Date.now());
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const itemList = Array.isArray(message.item_list) ? message.item_list : [];
|
|
43
|
-
const text = bodyFromItemList(itemList);
|
|
44
|
-
const attachments = extractAttachmentItems(itemList);
|
|
45
|
-
if (!text && !attachments.length) {
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
provider: "weixin",
|
|
51
|
-
accountId,
|
|
52
|
-
workspaceId: config.workspaceId,
|
|
53
|
-
senderId,
|
|
54
|
-
chatId: senderId,
|
|
55
|
-
messageId: normalizeMessageId(message),
|
|
56
|
-
threadKey: normalizeText(message.session_id),
|
|
57
|
-
text,
|
|
58
|
-
attachments,
|
|
59
|
-
contextToken: normalizeText(message.context_token),
|
|
60
|
-
receivedAt: createdAtMs > 0 ? new Date(createdAtMs).toISOString() : new Date().toISOString(),
|
|
61
|
-
};
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function bodyFromItemList(items) {
|
|
67
|
-
if (!Array.isArray(items) || !items.length) {
|
|
68
|
-
return "";
|
|
69
|
-
}
|
|
70
|
-
for (const item of items) {
|
|
71
|
-
const itemType = Number(item?.type);
|
|
72
|
-
if (itemType === MESSAGE_ITEM_TEXT) {
|
|
73
|
-
const text = normalizeText(item?.text_item?.text);
|
|
74
|
-
if (!text) {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const ref = item?.ref_msg;
|
|
78
|
-
if (!ref || !ref.message_item || isMediaItemType(Number(ref.message_item.type))) {
|
|
79
|
-
return text;
|
|
80
|
-
}
|
|
81
|
-
const parts = [];
|
|
82
|
-
const refTitle = normalizeText(ref.title);
|
|
83
|
-
if (refTitle) {
|
|
84
|
-
parts.push(refTitle);
|
|
85
|
-
}
|
|
86
|
-
const refBody = bodyFromItemList([ref.message_item]);
|
|
87
|
-
if (refBody) {
|
|
88
|
-
parts.push(refBody);
|
|
89
|
-
}
|
|
90
|
-
if (!parts.length) {
|
|
91
|
-
return text;
|
|
92
|
-
}
|
|
93
|
-
return `[引用: ${parts.join(" | ")}]\n${text}`;
|
|
94
|
-
}
|
|
95
|
-
if (itemType === MESSAGE_ITEM_VOICE) {
|
|
96
|
-
const voiceText = normalizeText(item?.voice_item?.text);
|
|
97
|
-
if (voiceText) {
|
|
98
|
-
return voiceText;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return "";
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function isMediaItemType(type) {
|
|
106
|
-
return type === MESSAGE_ITEM_IMAGE || type === MESSAGE_ITEM_VOICE || type === MESSAGE_ITEM_FILE || type === MESSAGE_ITEM_VIDEO;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function extractAttachmentItems(itemList) {
|
|
110
|
-
if (!Array.isArray(itemList) || !itemList.length) {
|
|
111
|
-
return [];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const attachments = [];
|
|
115
|
-
for (let index = 0; index < itemList.length; index += 1) {
|
|
116
|
-
const normalized = normalizeAttachmentItem(itemList[index], index);
|
|
117
|
-
if (normalized) {
|
|
118
|
-
attachments.push(normalized);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return attachments;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function normalizeAttachmentItem(item, index) {
|
|
125
|
-
const itemType = Number(item?.type);
|
|
126
|
-
const payload = resolveAttachmentPayload(itemType, item);
|
|
127
|
-
if (!payload) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const media = payload.media && typeof payload.media === "object"
|
|
132
|
-
? payload.media
|
|
133
|
-
: {};
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
kind: payload.kind,
|
|
137
|
-
itemType,
|
|
138
|
-
index,
|
|
139
|
-
fileName: normalizeText(
|
|
140
|
-
payload.body?.file_name
|
|
141
|
-
|| payload.body?.filename
|
|
142
|
-
|| item?.file_name
|
|
143
|
-
|| item?.filename
|
|
144
|
-
),
|
|
145
|
-
sizeBytes: parseOptionalInt(
|
|
146
|
-
payload.body?.len
|
|
147
|
-
|| payload.body?.file_size
|
|
148
|
-
|| payload.body?.size
|
|
149
|
-
|| payload.body?.video_size
|
|
150
|
-
|| item?.len
|
|
151
|
-
),
|
|
152
|
-
directUrls: collectStringValues([
|
|
153
|
-
payload.body?.url,
|
|
154
|
-
payload.body?.download_url,
|
|
155
|
-
payload.body?.cdn_url,
|
|
156
|
-
media?.url,
|
|
157
|
-
media?.download_url,
|
|
158
|
-
media?.cdn_url,
|
|
159
|
-
]),
|
|
160
|
-
mediaRef: {
|
|
161
|
-
encryptQueryParam: normalizeText(
|
|
162
|
-
media?.encrypt_query_param
|
|
163
|
-
|| media?.encrypted_query_param
|
|
164
|
-
|| payload.body?.encrypt_query_param
|
|
165
|
-
|| payload.body?.encrypted_query_param
|
|
166
|
-
|| item?.encrypt_query_param
|
|
167
|
-
|| item?.encrypted_query_param
|
|
168
|
-
),
|
|
169
|
-
aesKey: normalizeText(
|
|
170
|
-
media?.aes_key
|
|
171
|
-
|| payload.body?.aes_key
|
|
172
|
-
|| item?.aes_key
|
|
173
|
-
),
|
|
174
|
-
aesKeyHex: normalizeText(
|
|
175
|
-
payload.body?.aeskey
|
|
176
|
-
|| payload.body?.aes_key_hex
|
|
177
|
-
|| item?.aeskey
|
|
178
|
-
),
|
|
179
|
-
encryptType: Number(
|
|
180
|
-
media?.encrypt_type
|
|
181
|
-
?? payload.body?.encrypt_type
|
|
182
|
-
?? item?.encrypt_type
|
|
183
|
-
?? 1
|
|
184
|
-
),
|
|
185
|
-
fileKey: normalizeText(
|
|
186
|
-
media?.filekey
|
|
187
|
-
|| payload.body?.filekey
|
|
188
|
-
|| item?.filekey
|
|
189
|
-
),
|
|
190
|
-
},
|
|
191
|
-
rawItem: item,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function resolveAttachmentPayload(itemType, item) {
|
|
196
|
-
if (itemType === MESSAGE_ITEM_IMAGE && item?.image_item && typeof item.image_item === "object") {
|
|
197
|
-
return { kind: "image", body: item.image_item, media: item.image_item.media };
|
|
198
|
-
}
|
|
199
|
-
if (itemType === MESSAGE_ITEM_FILE && item?.file_item && typeof item.file_item === "object") {
|
|
200
|
-
return { kind: "file", body: item.file_item, media: item.file_item.media };
|
|
201
|
-
}
|
|
202
|
-
if (itemType === MESSAGE_ITEM_VIDEO && item?.video_item && typeof item.video_item === "object") {
|
|
203
|
-
return { kind: "video", body: item.video_item, media: item.video_item.media };
|
|
204
|
-
}
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function collectStringValues(values) {
|
|
209
|
-
const seen = new Set();
|
|
210
|
-
const result = [];
|
|
211
|
-
for (const value of values) {
|
|
212
|
-
const normalized = normalizeText(value);
|
|
213
|
-
if (!normalized || seen.has(normalized)) {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
seen.add(normalized);
|
|
217
|
-
result.push(normalized);
|
|
218
|
-
}
|
|
219
|
-
return result;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function parseOptionalInt(value) {
|
|
223
|
-
if (value == null || value === "") {
|
|
224
|
-
return 0;
|
|
225
|
-
}
|
|
226
|
-
const parsed = Number.parseInt(String(value), 10);
|
|
227
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function normalizeMessageId(message) {
|
|
231
|
-
const raw = message?.message_id;
|
|
232
|
-
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
233
|
-
return String(raw);
|
|
234
|
-
}
|
|
235
|
-
if (typeof raw === "string") {
|
|
236
|
-
return raw.trim();
|
|
237
|
-
}
|
|
238
|
-
return "";
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function normalizeMessageTimestampMs(message) {
|
|
242
|
-
const rawMs = Number(message?.create_time_ms);
|
|
243
|
-
if (Number.isFinite(rawMs) && rawMs > 0) {
|
|
244
|
-
return rawMs;
|
|
245
|
-
}
|
|
246
|
-
const rawSeconds = Number(message?.create_time);
|
|
247
|
-
if (Number.isFinite(rawSeconds) && rawSeconds > 0) {
|
|
248
|
-
return rawSeconds * 1000;
|
|
249
|
-
}
|
|
250
|
-
return 0;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function buildDedupKey(message, senderId, createdAtMs) {
|
|
254
|
-
const seq = normalizeNumeric(message?.seq);
|
|
255
|
-
const messageId = normalizeNumeric(message?.message_id);
|
|
256
|
-
const clientId = normalizeText(message?.client_id);
|
|
257
|
-
const parts = [senderId, messageId, seq, createdAtMs || 0, clientId];
|
|
258
|
-
return parts.join("|");
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function normalizeNumeric(value) {
|
|
262
|
-
const num = Number(value);
|
|
263
|
-
return Number.isFinite(num) ? String(num) : "0";
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function pruneSeen(seen) {
|
|
267
|
-
const now = Date.now();
|
|
268
|
-
for (const [key, timestamp] of seen.entries()) {
|
|
269
|
-
if (now - timestamp > DEDUP_TTL_MS) {
|
|
270
|
-
seen.delete(key);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function normalizeText(value) {
|
|
276
|
-
return typeof value === "string" ? value.trim() : "";
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
module.exports = {
|
|
280
|
-
createInboundFilter,
|
|
281
|
-
bodyFromItemList,
|
|
282
|
-
};
|
|
1
|
+
const MESSAGE_TYPE_USER = 1;
|
|
2
|
+
const MESSAGE_TYPE_BOT = 2;
|
|
3
|
+
const MESSAGE_ITEM_TEXT = 1;
|
|
4
|
+
const MESSAGE_ITEM_IMAGE = 2;
|
|
5
|
+
const MESSAGE_ITEM_VOICE = 3;
|
|
6
|
+
const MESSAGE_ITEM_FILE = 4;
|
|
7
|
+
const MESSAGE_ITEM_VIDEO = 5;
|
|
8
|
+
const DEDUP_TTL_MS = 5 * 60_000;
|
|
9
|
+
|
|
10
|
+
function createInboundFilter() {
|
|
11
|
+
const seen = new Map();
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
normalize(message, config, accountId) {
|
|
15
|
+
if (!message || typeof message !== "object") {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const messageType = Number(message.message_type);
|
|
19
|
+
if (messageType === MESSAGE_TYPE_BOT) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (messageType !== 0 && messageType !== MESSAGE_TYPE_USER) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const senderId = normalizeText(message.from_user_id);
|
|
27
|
+
if (!senderId) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const createdAtMs = normalizeMessageTimestampMs(message);
|
|
32
|
+
|
|
33
|
+
const dedupKey = buildDedupKey(message, senderId, createdAtMs);
|
|
34
|
+
pruneSeen(seen);
|
|
35
|
+
if (dedupKey && seen.has(dedupKey)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
if (dedupKey) {
|
|
39
|
+
seen.set(dedupKey, Date.now());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const itemList = Array.isArray(message.item_list) ? message.item_list : [];
|
|
43
|
+
const text = bodyFromItemList(itemList);
|
|
44
|
+
const attachments = extractAttachmentItems(itemList);
|
|
45
|
+
if (!text && !attachments.length) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
provider: "weixin",
|
|
51
|
+
accountId,
|
|
52
|
+
workspaceId: config.workspaceId,
|
|
53
|
+
senderId,
|
|
54
|
+
chatId: senderId,
|
|
55
|
+
messageId: normalizeMessageId(message),
|
|
56
|
+
threadKey: normalizeText(message.session_id),
|
|
57
|
+
text,
|
|
58
|
+
attachments,
|
|
59
|
+
contextToken: normalizeText(message.context_token),
|
|
60
|
+
receivedAt: createdAtMs > 0 ? new Date(createdAtMs).toISOString() : new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function bodyFromItemList(items) {
|
|
67
|
+
if (!Array.isArray(items) || !items.length) {
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
const itemType = Number(item?.type);
|
|
72
|
+
if (itemType === MESSAGE_ITEM_TEXT) {
|
|
73
|
+
const text = normalizeText(item?.text_item?.text);
|
|
74
|
+
if (!text) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const ref = item?.ref_msg;
|
|
78
|
+
if (!ref || !ref.message_item || isMediaItemType(Number(ref.message_item.type))) {
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
81
|
+
const parts = [];
|
|
82
|
+
const refTitle = normalizeText(ref.title);
|
|
83
|
+
if (refTitle) {
|
|
84
|
+
parts.push(refTitle);
|
|
85
|
+
}
|
|
86
|
+
const refBody = bodyFromItemList([ref.message_item]);
|
|
87
|
+
if (refBody) {
|
|
88
|
+
parts.push(refBody);
|
|
89
|
+
}
|
|
90
|
+
if (!parts.length) {
|
|
91
|
+
return text;
|
|
92
|
+
}
|
|
93
|
+
return `[引用: ${parts.join(" | ")}]\n${text}`;
|
|
94
|
+
}
|
|
95
|
+
if (itemType === MESSAGE_ITEM_VOICE) {
|
|
96
|
+
const voiceText = normalizeText(item?.voice_item?.text);
|
|
97
|
+
if (voiceText) {
|
|
98
|
+
return voiceText;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return "";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isMediaItemType(type) {
|
|
106
|
+
return type === MESSAGE_ITEM_IMAGE || type === MESSAGE_ITEM_VOICE || type === MESSAGE_ITEM_FILE || type === MESSAGE_ITEM_VIDEO;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function extractAttachmentItems(itemList) {
|
|
110
|
+
if (!Array.isArray(itemList) || !itemList.length) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const attachments = [];
|
|
115
|
+
for (let index = 0; index < itemList.length; index += 1) {
|
|
116
|
+
const normalized = normalizeAttachmentItem(itemList[index], index);
|
|
117
|
+
if (normalized) {
|
|
118
|
+
attachments.push(normalized);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return attachments;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeAttachmentItem(item, index) {
|
|
125
|
+
const itemType = Number(item?.type);
|
|
126
|
+
const payload = resolveAttachmentPayload(itemType, item);
|
|
127
|
+
if (!payload) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const media = payload.media && typeof payload.media === "object"
|
|
132
|
+
? payload.media
|
|
133
|
+
: {};
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
kind: payload.kind,
|
|
137
|
+
itemType,
|
|
138
|
+
index,
|
|
139
|
+
fileName: normalizeText(
|
|
140
|
+
payload.body?.file_name
|
|
141
|
+
|| payload.body?.filename
|
|
142
|
+
|| item?.file_name
|
|
143
|
+
|| item?.filename
|
|
144
|
+
),
|
|
145
|
+
sizeBytes: parseOptionalInt(
|
|
146
|
+
payload.body?.len
|
|
147
|
+
|| payload.body?.file_size
|
|
148
|
+
|| payload.body?.size
|
|
149
|
+
|| payload.body?.video_size
|
|
150
|
+
|| item?.len
|
|
151
|
+
),
|
|
152
|
+
directUrls: collectStringValues([
|
|
153
|
+
payload.body?.url,
|
|
154
|
+
payload.body?.download_url,
|
|
155
|
+
payload.body?.cdn_url,
|
|
156
|
+
media?.url,
|
|
157
|
+
media?.download_url,
|
|
158
|
+
media?.cdn_url,
|
|
159
|
+
]),
|
|
160
|
+
mediaRef: {
|
|
161
|
+
encryptQueryParam: normalizeText(
|
|
162
|
+
media?.encrypt_query_param
|
|
163
|
+
|| media?.encrypted_query_param
|
|
164
|
+
|| payload.body?.encrypt_query_param
|
|
165
|
+
|| payload.body?.encrypted_query_param
|
|
166
|
+
|| item?.encrypt_query_param
|
|
167
|
+
|| item?.encrypted_query_param
|
|
168
|
+
),
|
|
169
|
+
aesKey: normalizeText(
|
|
170
|
+
media?.aes_key
|
|
171
|
+
|| payload.body?.aes_key
|
|
172
|
+
|| item?.aes_key
|
|
173
|
+
),
|
|
174
|
+
aesKeyHex: normalizeText(
|
|
175
|
+
payload.body?.aeskey
|
|
176
|
+
|| payload.body?.aes_key_hex
|
|
177
|
+
|| item?.aeskey
|
|
178
|
+
),
|
|
179
|
+
encryptType: Number(
|
|
180
|
+
media?.encrypt_type
|
|
181
|
+
?? payload.body?.encrypt_type
|
|
182
|
+
?? item?.encrypt_type
|
|
183
|
+
?? 1
|
|
184
|
+
),
|
|
185
|
+
fileKey: normalizeText(
|
|
186
|
+
media?.filekey
|
|
187
|
+
|| payload.body?.filekey
|
|
188
|
+
|| item?.filekey
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
rawItem: item,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function resolveAttachmentPayload(itemType, item) {
|
|
196
|
+
if (itemType === MESSAGE_ITEM_IMAGE && item?.image_item && typeof item.image_item === "object") {
|
|
197
|
+
return { kind: "image", body: item.image_item, media: item.image_item.media };
|
|
198
|
+
}
|
|
199
|
+
if (itemType === MESSAGE_ITEM_FILE && item?.file_item && typeof item.file_item === "object") {
|
|
200
|
+
return { kind: "file", body: item.file_item, media: item.file_item.media };
|
|
201
|
+
}
|
|
202
|
+
if (itemType === MESSAGE_ITEM_VIDEO && item?.video_item && typeof item.video_item === "object") {
|
|
203
|
+
return { kind: "video", body: item.video_item, media: item.video_item.media };
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function collectStringValues(values) {
|
|
209
|
+
const seen = new Set();
|
|
210
|
+
const result = [];
|
|
211
|
+
for (const value of values) {
|
|
212
|
+
const normalized = normalizeText(value);
|
|
213
|
+
if (!normalized || seen.has(normalized)) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
seen.add(normalized);
|
|
217
|
+
result.push(normalized);
|
|
218
|
+
}
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseOptionalInt(value) {
|
|
223
|
+
if (value == null || value === "") {
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
227
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function normalizeMessageId(message) {
|
|
231
|
+
const raw = message?.message_id;
|
|
232
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
233
|
+
return String(raw);
|
|
234
|
+
}
|
|
235
|
+
if (typeof raw === "string") {
|
|
236
|
+
return raw.trim();
|
|
237
|
+
}
|
|
238
|
+
return "";
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function normalizeMessageTimestampMs(message) {
|
|
242
|
+
const rawMs = Number(message?.create_time_ms);
|
|
243
|
+
if (Number.isFinite(rawMs) && rawMs > 0) {
|
|
244
|
+
return rawMs;
|
|
245
|
+
}
|
|
246
|
+
const rawSeconds = Number(message?.create_time);
|
|
247
|
+
if (Number.isFinite(rawSeconds) && rawSeconds > 0) {
|
|
248
|
+
return rawSeconds * 1000;
|
|
249
|
+
}
|
|
250
|
+
return 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function buildDedupKey(message, senderId, createdAtMs) {
|
|
254
|
+
const seq = normalizeNumeric(message?.seq);
|
|
255
|
+
const messageId = normalizeNumeric(message?.message_id);
|
|
256
|
+
const clientId = normalizeText(message?.client_id);
|
|
257
|
+
const parts = [senderId, messageId, seq, createdAtMs || 0, clientId];
|
|
258
|
+
return parts.join("|");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function normalizeNumeric(value) {
|
|
262
|
+
const num = Number(value);
|
|
263
|
+
return Number.isFinite(num) ? String(num) : "0";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function pruneSeen(seen) {
|
|
267
|
+
const now = Date.now();
|
|
268
|
+
for (const [key, timestamp] of seen.entries()) {
|
|
269
|
+
if (now - timestamp > DEDUP_TTL_MS) {
|
|
270
|
+
seen.delete(key);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function normalizeText(value) {
|
|
276
|
+
return typeof value === "string" ? value.trim() : "";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
module.exports = {
|
|
280
|
+
createInboundFilter,
|
|
281
|
+
bodyFromItemList,
|
|
282
|
+
};
|