nodejs-insta-private-api-mqtt 1.2.10 → 1.3.11
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.
|
@@ -6,6 +6,22 @@ const constants_1 = require("../../constants");
|
|
|
6
6
|
const shared_1 = require("../../shared");
|
|
7
7
|
const mqtts_1 = require("mqtts");
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* MessageSyncMixin - patched for 2026 (robust parsing + safe username fetch + tolerant timestamp handling)
|
|
11
|
+
*
|
|
12
|
+
* Changes applied:
|
|
13
|
+
* - tolerant parsing for e.value (string / object / already-parsed)
|
|
14
|
+
* - support for several path shapes when extracting thread id
|
|
15
|
+
* - safer timestamp parsing (accepts seconds or milliseconds)
|
|
16
|
+
* - username fetch uses a pending map + small backoff to reduce rush/rate-limit risk
|
|
17
|
+
* - defensive try/catch around JSON.parse and all external calls
|
|
18
|
+
* - keeps original API: apply(client) registers post-connect hook and emits same events
|
|
19
|
+
*
|
|
20
|
+
* Additional change requested:
|
|
21
|
+
* - set message status to 'received' for incoming messages and 'sent' for messages authored by the logged-in account,
|
|
22
|
+
* instead of the previous 'good'.
|
|
23
|
+
*/
|
|
24
|
+
|
|
9
25
|
class MessageSyncMixin extends mixin_1.Mixin {
|
|
10
26
|
constructor() {
|
|
11
27
|
super();
|
|
@@ -36,10 +52,17 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
36
52
|
client.mqtt.listen({
|
|
37
53
|
topic: constants_1.Topics.MESSAGE_SYNC.id,
|
|
38
54
|
transformer: async ({ payload }) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
try {
|
|
56
|
+
const unzipped = await (0, shared_1.tryUnzipAsync)(payload);
|
|
57
|
+
const parsed = constants_1.Topics.MESSAGE_SYNC.parser
|
|
58
|
+
.parseMessage(constants_1.Topics.MESSAGE_SYNC, unzipped)
|
|
59
|
+
.map(msg => msg.data);
|
|
60
|
+
return parsed;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// If transformer fails, return empty array so handler is tolerant
|
|
63
|
+
console.warn('[MESSAGE_SYNC] transformer parse failed:', err?.message || err);
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
43
66
|
},
|
|
44
67
|
}, data => {
|
|
45
68
|
this.handleMessageSync(client, data);
|
|
@@ -47,9 +70,13 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
47
70
|
} else {
|
|
48
71
|
console.log(`[MESSAGE_SYNC] mqtt.listen() NOT FOUND - using fallback 'receive' event`);
|
|
49
72
|
client.on('receive', (topic, messages) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
try {
|
|
74
|
+
if (topic.id === constants_1.Topics.MESSAGE_SYNC.id) {
|
|
75
|
+
const data = messages.map(m => m.data);
|
|
76
|
+
this.handleMessageSync(client, data);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.warn('[MESSAGE_SYNC] receive fallback handler error:', err?.message || err);
|
|
53
80
|
}
|
|
54
81
|
});
|
|
55
82
|
}
|
|
@@ -67,16 +94,26 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
67
94
|
}
|
|
68
95
|
|
|
69
96
|
if (this.pendingUserFetches.has(userIdStr)) {
|
|
70
|
-
|
|
97
|
+
try {
|
|
98
|
+
return await this.pendingUserFetches.get(userIdStr);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
// if pending fetch failed, continue to fresh attempt
|
|
101
|
+
}
|
|
71
102
|
}
|
|
72
103
|
|
|
73
104
|
const fetchPromise = (async () => {
|
|
74
105
|
try {
|
|
106
|
+
// small backoff to avoid immediate burst of parallel requests
|
|
107
|
+
await new Promise(r => setTimeout(r, 120));
|
|
75
108
|
if (client.ig && client.ig.user && client.ig.user.info) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
109
|
+
try {
|
|
110
|
+
const userInfo = await client.ig.user.info(userIdStr);
|
|
111
|
+
if (userInfo && userInfo.username) {
|
|
112
|
+
this.userCache.set(userIdStr, userInfo.username);
|
|
113
|
+
return userInfo.username;
|
|
114
|
+
}
|
|
115
|
+
} catch (innerErr) {
|
|
116
|
+
// rate-limited or not found - swallow
|
|
80
117
|
}
|
|
81
118
|
}
|
|
82
119
|
} catch (err) {
|
|
@@ -96,158 +133,171 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
96
133
|
let content = '';
|
|
97
134
|
let mediaInfo = '';
|
|
98
135
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
136
|
+
try {
|
|
137
|
+
switch (itemType) {
|
|
138
|
+
case 'text':
|
|
139
|
+
content = msgValue.text || msgValue.body || '';
|
|
140
|
+
break;
|
|
141
|
+
|
|
142
|
+
case 'media':
|
|
143
|
+
case 'raven_media':
|
|
144
|
+
content = '[PHOTO/VIDEO]';
|
|
145
|
+
if (msgValue.media) {
|
|
146
|
+
const media = msgValue.media;
|
|
147
|
+
if (media.image_versions2) {
|
|
148
|
+
content = '[PHOTO]';
|
|
149
|
+
mediaInfo = ` URL: ${media.image_versions2?.candidates?.[0]?.url || 'N/A'}`;
|
|
150
|
+
} else if (media.video_versions) {
|
|
151
|
+
content = '[VIDEO]';
|
|
152
|
+
mediaInfo = ` Duration: ${media.video_duration || 'N/A'}s`;
|
|
153
|
+
}
|
|
115
154
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
content = '[DISAPPEARING MEDIA]';
|
|
119
|
-
}
|
|
120
|
-
break;
|
|
121
|
-
|
|
122
|
-
case 'voice_media':
|
|
123
|
-
content = '[VOICE MESSAGE]';
|
|
124
|
-
if (msgValue.voice_media?.media?.audio) {
|
|
125
|
-
const duration = msgValue.voice_media.media.audio.duration || 0;
|
|
126
|
-
content = `[VOICE MESSAGE] Duration: ${duration}ms`;
|
|
127
|
-
}
|
|
128
|
-
break;
|
|
129
|
-
|
|
130
|
-
case 'animated_media':
|
|
131
|
-
content = '[GIF]';
|
|
132
|
-
if (msgValue.animated_media?.images?.fixed_height?.url) {
|
|
133
|
-
mediaInfo = ` URL: ${msgValue.animated_media.images.fixed_height.url}`;
|
|
134
|
-
}
|
|
135
|
-
break;
|
|
136
|
-
|
|
137
|
-
case 'media_share':
|
|
138
|
-
content = '[SHARED POST]';
|
|
139
|
-
if (msgValue.media_share) {
|
|
140
|
-
const share = msgValue.media_share;
|
|
141
|
-
content = `[SHARED POST] From: @${share.user?.username || 'unknown'}`;
|
|
142
|
-
if (share.caption?.text) {
|
|
143
|
-
content += ` Caption: "${share.caption.text.substring(0, 50)}..."`;
|
|
155
|
+
if (msgValue.visual_media) {
|
|
156
|
+
content = '[DISAPPEARING MEDIA]';
|
|
144
157
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
content = `[SHARED REEL] From: @${reel.media?.user?.username || 'unknown'}`;
|
|
153
|
-
if (reel.text) {
|
|
154
|
-
content += ` Text: "${reel.text}"`;
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case 'voice_media':
|
|
161
|
+
content = '[VOICE MESSAGE]';
|
|
162
|
+
if (msgValue.voice_media?.media?.audio) {
|
|
163
|
+
const duration = msgValue.voice_media.media.audio.duration || 0;
|
|
164
|
+
content = `[VOICE MESSAGE] Duration: ${duration}ms`;
|
|
155
165
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const story = msgValue.story_share;
|
|
163
|
-
content = `[SHARED STORY] From: @${story.media?.user?.username || 'unknown'}`;
|
|
164
|
-
if (story.message) {
|
|
165
|
-
content += ` Message: "${story.message}"`;
|
|
166
|
+
break;
|
|
167
|
+
|
|
168
|
+
case 'animated_media':
|
|
169
|
+
content = '[GIF]';
|
|
170
|
+
if (msgValue.animated_media?.images?.fixed_height?.url) {
|
|
171
|
+
mediaInfo = ` URL: ${msgValue.animated_media.images.fixed_height.url}`;
|
|
166
172
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
173
|
+
break;
|
|
174
|
+
|
|
175
|
+
case 'media_share':
|
|
176
|
+
content = '[SHARED POST]';
|
|
177
|
+
if (msgValue.media_share) {
|
|
178
|
+
const share = msgValue.media_share;
|
|
179
|
+
content = `[SHARED POST] From: @${share.user?.username || 'unknown'}`;
|
|
180
|
+
if (share.caption?.text) {
|
|
181
|
+
content += ` Caption: "${String(share.caption.text).substring(0, 50)}..."`;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
|
|
186
|
+
case 'reel_share':
|
|
187
|
+
content = '[SHARED REEL]';
|
|
188
|
+
if (msgValue.reel_share) {
|
|
189
|
+
const reel = msgValue.reel_share;
|
|
190
|
+
content = `[SHARED REEL] From: @${reel.media?.user?.username || 'unknown'}`;
|
|
191
|
+
if (reel.text) {
|
|
192
|
+
content += ` Text: "${reel.text}"`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
|
|
197
|
+
case 'story_share':
|
|
198
|
+
content = '[SHARED STORY]';
|
|
199
|
+
if (msgValue.story_share) {
|
|
200
|
+
const story = msgValue.story_share;
|
|
201
|
+
content = `[SHARED STORY] From: @${story.media?.user?.username || 'unknown'}`;
|
|
202
|
+
if (story.message) {
|
|
203
|
+
content += ` Message: "${story.message}"`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
case 'felix_share':
|
|
209
|
+
content = '[SHARED IGTV/VIDEO]';
|
|
210
|
+
if (msgValue.felix_share?.video) {
|
|
211
|
+
content = `[SHARED IGTV] Title: "${msgValue.felix_share.video.title || 'N/A'}"`;
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
|
|
215
|
+
case 'clip':
|
|
216
|
+
content = '[SHARED CLIP]';
|
|
217
|
+
if (msgValue.clip?.clip) {
|
|
218
|
+
content = `[SHARED CLIP] From: @${msgValue.clip.clip.user?.username || 'unknown'}`;
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
case 'profile':
|
|
223
|
+
content = '[SHARED PROFILE]';
|
|
224
|
+
if (msgValue.profile) {
|
|
225
|
+
content = `[SHARED PROFILE] @${msgValue.profile.username || 'unknown'}`;
|
|
226
|
+
}
|
|
227
|
+
break;
|
|
228
|
+
|
|
229
|
+
case 'location':
|
|
230
|
+
content = '[LOCATION]';
|
|
231
|
+
if (msgValue.location) {
|
|
232
|
+
content = `[LOCATION] ${msgValue.location.name || msgValue.location.address || 'Unknown location'}`;
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case 'hashtag':
|
|
237
|
+
content = '[HASHTAG]';
|
|
238
|
+
if (msgValue.hashtag) {
|
|
239
|
+
content = `[HASHTAG] #${msgValue.hashtag.name || 'unknown'}`;
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case 'like':
|
|
244
|
+
content = '[LIKE]';
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
case 'link':
|
|
248
|
+
content = '[LINK]';
|
|
249
|
+
if (msgValue.link) {
|
|
250
|
+
content = `[LINK] ${msgValue.link.text || msgValue.link.link_url || 'N/A'}`;
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
|
|
254
|
+
case 'action_log':
|
|
255
|
+
content = '[ACTION]';
|
|
256
|
+
if (msgValue.action_log) {
|
|
257
|
+
content = `[ACTION] ${msgValue.action_log.description || 'N/A'}`;
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
|
|
261
|
+
case 'placeholder':
|
|
262
|
+
content = '[PLACEHOLDER]';
|
|
263
|
+
if (msgValue.placeholder?.message) {
|
|
264
|
+
content = `[PLACEHOLDER] ${msgValue.placeholder.message}`;
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
|
|
268
|
+
case 'xma':
|
|
269
|
+
case 'xma_media_share':
|
|
270
|
+
content = '[XMA SHARE]';
|
|
271
|
+
if (msgValue.xma_link_url) {
|
|
272
|
+
content = `[XMA SHARE] ${msgValue.xma_link_url}`;
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
|
|
276
|
+
case 'video_call_event':
|
|
277
|
+
content = '[VIDEO CALL EVENT]';
|
|
278
|
+
if (msgValue.video_call_event) {
|
|
279
|
+
content = `[VIDEO CALL] ${msgValue.video_call_event.action || 'event'}`;
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
|
|
283
|
+
default:
|
|
284
|
+
if (msgValue && (msgValue.text || msgValue.body)) {
|
|
285
|
+
content = msgValue.text || msgValue.body;
|
|
286
|
+
} else {
|
|
287
|
+
content = `[${(itemType || 'UNKNOWN').toUpperCase()}]`;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
} catch (e) {
|
|
291
|
+
// defensive fallback
|
|
292
|
+
try {
|
|
293
|
+
if (msgValue && (msgValue.text || msgValue.body)) {
|
|
294
|
+
content = msgValue.text || msgValue.body;
|
|
248
295
|
} else {
|
|
249
296
|
content = `[${(itemType || 'UNKNOWN').toUpperCase()}]`;
|
|
250
297
|
}
|
|
298
|
+
} catch (e2) {
|
|
299
|
+
content = `[${(itemType || 'UNKNOWN').toUpperCase()}]`;
|
|
300
|
+
}
|
|
251
301
|
}
|
|
252
302
|
|
|
253
303
|
return content + mediaInfo;
|
|
@@ -255,6 +305,16 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
255
305
|
|
|
256
306
|
formatMessageForConsole(msgData) {
|
|
257
307
|
const separator = '----------------------------------------';
|
|
308
|
+
// robust timestamp formatting
|
|
309
|
+
let ts = 'N/A';
|
|
310
|
+
try {
|
|
311
|
+
if (msgData.timestamp) {
|
|
312
|
+
const t = this.parseTimestamp(msgData.timestamp);
|
|
313
|
+
if (t) ts = new Date(t).toISOString();
|
|
314
|
+
}
|
|
315
|
+
} catch (e) {
|
|
316
|
+
ts = 'N/A';
|
|
317
|
+
}
|
|
258
318
|
const lines = [
|
|
259
319
|
'',
|
|
260
320
|
separator,
|
|
@@ -266,14 +326,31 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
266
326
|
`Type: ${msgData.itemType || 'text'}`,
|
|
267
327
|
`Thread: ${msgData.threadId || 'unknown'}`,
|
|
268
328
|
`Message ID: ${msgData.messageId || 'unknown'}`,
|
|
269
|
-
`Timestamp: ${
|
|
270
|
-
`Status: ${msgData.status || '
|
|
329
|
+
`Timestamp: ${ts}`,
|
|
330
|
+
`Status: ${msgData.status || 'unknown'}`,
|
|
271
331
|
separator,
|
|
272
332
|
''
|
|
273
333
|
];
|
|
274
334
|
return lines.join('\n');
|
|
275
335
|
}
|
|
276
336
|
|
|
337
|
+
parseTimestamp(ts) {
|
|
338
|
+
// Accept numeric seconds or milliseconds, or numeric-like string
|
|
339
|
+
try {
|
|
340
|
+
if (ts === undefined || ts === null) return null;
|
|
341
|
+
const n = Number(ts);
|
|
342
|
+
if (isNaN(n)) return null;
|
|
343
|
+
// heuristics: if > 10^12 -> already ms; if between 10^9 .. 10^12 -> probably seconds -> convert
|
|
344
|
+
if (n > 1e12) return n; // ms
|
|
345
|
+
if (n > 1e9) return n * 1000; // sec -> ms
|
|
346
|
+
if (n > 1e6) return n * 1000; // fallback seconds-ish
|
|
347
|
+
// otherwise treat as ms fallback
|
|
348
|
+
return n;
|
|
349
|
+
} catch (e) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
277
354
|
async handleMessageSync(client, syncData) {
|
|
278
355
|
if (!syncData || !Array.isArray(syncData)) {
|
|
279
356
|
console.log(`[MESSAGE_SYNC] No sync data received`);
|
|
@@ -281,96 +358,184 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
281
358
|
}
|
|
282
359
|
|
|
283
360
|
for (const element of syncData) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
delete element.data;
|
|
292
|
-
|
|
293
|
-
for (const e of data) {
|
|
294
|
-
if (!e.path) {
|
|
295
|
-
client.emit('iris', { ...element, ...e });
|
|
361
|
+
try {
|
|
362
|
+
const data = element.data;
|
|
363
|
+
|
|
364
|
+
if (!data) {
|
|
365
|
+
// fallback: emit iris with original element
|
|
366
|
+
client.emit('iris', element);
|
|
296
367
|
continue;
|
|
297
368
|
}
|
|
298
369
|
|
|
299
|
-
|
|
370
|
+
// ensure element.data removed in downstream parsed message (keeps original behavior)
|
|
371
|
+
delete element.data;
|
|
372
|
+
|
|
373
|
+
for (const e of data) {
|
|
300
374
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
375
|
+
// tolerant handling: e.value may be string, object, null, or already parsed
|
|
376
|
+
let parsedValue = {};
|
|
377
|
+
if (e.value === undefined || e.value === null) {
|
|
378
|
+
parsedValue = {};
|
|
379
|
+
} else if (typeof e.value === 'string') {
|
|
380
|
+
const str = e.value.trim();
|
|
381
|
+
if (str.length === 0) {
|
|
382
|
+
parsedValue = {};
|
|
383
|
+
} else {
|
|
384
|
+
try {
|
|
385
|
+
parsedValue = JSON.parse(str);
|
|
386
|
+
} catch (errJson) {
|
|
387
|
+
// If not JSON, attempt basic fallback (sometimes server sends plain key=value or quoted)
|
|
388
|
+
try {
|
|
389
|
+
// try to safe-evaluate limited forms like a bare object without quotes (rare)
|
|
390
|
+
parsedValue = {};
|
|
391
|
+
} catch (err2) {
|
|
392
|
+
parsedValue = {};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} else if (typeof e.value === 'object') {
|
|
397
|
+
parsedValue = e.value;
|
|
398
|
+
} else {
|
|
399
|
+
parsedValue = {};
|
|
310
400
|
}
|
|
311
|
-
|
|
312
|
-
|
|
401
|
+
|
|
402
|
+
// Sometimes the message payload is nested under 'message' or similar
|
|
403
|
+
const msgValue = parsedValue.message || parsedValue.data || parsedValue || {};
|
|
404
|
+
|
|
405
|
+
if (!e.path) {
|
|
406
|
+
// no path means iris-like delta; merge element + e
|
|
407
|
+
client.emit('iris', { ...element, ...e, value: msgValue });
|
|
408
|
+
continue;
|
|
313
409
|
}
|
|
314
410
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
411
|
+
// normalize path check for thread messages
|
|
412
|
+
if ((e.path && e.path.startsWith('/direct_v2/threads')) ||
|
|
413
|
+
(e.path && e.path.startsWith('/direct_v2/inbox/threads')) ||
|
|
414
|
+
(e.path && e.path.indexOf('/direct_v2/threads/') !== -1) ) {
|
|
415
|
+
|
|
416
|
+
if (msgValue && (msgValue.item_type || msgValue.itemType || msgValue.type || msgValue.msg_type)) {
|
|
417
|
+
// determine item type as robustly as possible
|
|
418
|
+
const itemType = msgValue.item_type || msgValue.itemType || msgValue.type || msgValue.msg_type || 'text';
|
|
419
|
+
|
|
420
|
+
// thread id extraction
|
|
421
|
+
const threadId = MessageSyncMixin.getThreadIdFromPath(e.path);
|
|
422
|
+
|
|
423
|
+
// user id resolution: try many possible fields
|
|
424
|
+
const userId = msgValue.user_id || msgValue.from_user_id || msgValue.sender_id || msgValue.userId || msgValue.senderId || null;
|
|
425
|
+
|
|
426
|
+
// username resolution: prefer embedded username, otherwise fetch
|
|
427
|
+
let username = msgValue.username || msgValue.from_username || null;
|
|
428
|
+
if (!username && userId) {
|
|
429
|
+
try {
|
|
430
|
+
username = await this.getUsernameFromId(client, userId);
|
|
431
|
+
} catch (ux) {
|
|
432
|
+
username = null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!username) {
|
|
436
|
+
username = `user_${userId || 'unknown'}`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const textContent = this.extractMessageContent(msgValue, itemType);
|
|
440
|
+
|
|
441
|
+
const messageId = msgValue.item_id || msgValue.id || msgValue.client_context || msgValue.client_context_id || msgValue.message_id || msgValue.messageId || null;
|
|
442
|
+
const timestamp = msgValue.timestamp || msgValue.ts || msgValue.client_time || null;
|
|
443
|
+
|
|
444
|
+
// determine status based on whether message author is the logged-in account
|
|
445
|
+
let status = 'received';
|
|
446
|
+
try {
|
|
447
|
+
const ownId = client?.ig?.state?.cookieUserId || client?.ig?.state?.userId || null;
|
|
448
|
+
if (ownId && userId && String(userId) === String(ownId)) {
|
|
449
|
+
status = 'sent';
|
|
450
|
+
} else {
|
|
451
|
+
status = 'received';
|
|
452
|
+
}
|
|
453
|
+
} catch (stErr) {
|
|
454
|
+
status = 'received';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const msgData = {
|
|
458
|
+
username: username,
|
|
459
|
+
userId: userId,
|
|
460
|
+
text: textContent,
|
|
461
|
+
itemType: itemType,
|
|
462
|
+
threadId: threadId,
|
|
463
|
+
messageId: messageId,
|
|
464
|
+
timestamp: timestamp,
|
|
465
|
+
status: status,
|
|
466
|
+
rawData: msgValue
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// console output (keeps original formatted block)
|
|
470
|
+
try {
|
|
471
|
+
console.log(this.formatMessageForConsole(msgData));
|
|
472
|
+
} catch (eLog) {
|
|
473
|
+
// don't let logging break processing
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const parsedMessage = {
|
|
477
|
+
...element,
|
|
478
|
+
message: {
|
|
479
|
+
path: e.path,
|
|
480
|
+
op: e.op,
|
|
481
|
+
thread_id: threadId,
|
|
482
|
+
...msgValue,
|
|
483
|
+
},
|
|
484
|
+
parsed: msgData
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
client.emit('message', parsedMessage);
|
|
488
|
+
continue;
|
|
489
|
+
} // end if msgValue has item_type
|
|
490
|
+
} // end if path matches threads
|
|
343
491
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
|
|
492
|
+
// If not a thread message, emit as threadUpdate or iris depending on payload
|
|
493
|
+
try {
|
|
494
|
+
const updateValue = e.value ? (typeof e.value === 'string' ? (() => {
|
|
495
|
+
try { return JSON.parse(e.value); } catch { return e.value; }
|
|
496
|
+
})() : e.value) : {};
|
|
497
|
+
client.emit('threadUpdate', {
|
|
498
|
+
...element,
|
|
499
|
+
meta: {
|
|
500
|
+
path: e.path,
|
|
501
|
+
op: e.op,
|
|
502
|
+
thread_id: MessageSyncMixin.getThreadIdFromPath(e.path),
|
|
503
|
+
},
|
|
504
|
+
update: updateValue,
|
|
505
|
+
});
|
|
506
|
+
} catch (errUpdate) {
|
|
507
|
+
client.emit('iris', { ...element, ...e, value: parsedValue });
|
|
508
|
+
}
|
|
509
|
+
} catch (inner) {
|
|
510
|
+
console.log(`[MESSAGE_SYNC] element handling error: ${inner?.message || inner}`);
|
|
361
511
|
}
|
|
362
512
|
}
|
|
513
|
+
} catch (outer) {
|
|
514
|
+
console.log(`[MESSAGE_SYNC] item error: ${outer?.message || outer}`);
|
|
363
515
|
}
|
|
364
516
|
}
|
|
365
517
|
}
|
|
366
518
|
|
|
367
519
|
static getThreadIdFromPath(path) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
520
|
+
if (!path) return undefined;
|
|
521
|
+
// Common patterns:
|
|
522
|
+
// /direct_v2/threads/<thread_id>/...
|
|
523
|
+
// /direct_v2/inbox/threads/<thread_id>/...
|
|
524
|
+
// /direct_v2/threads/<thread_id>
|
|
525
|
+
// possibly with trailing segments
|
|
526
|
+
try {
|
|
527
|
+
let m = path.match(/\/direct_v2\/threads\/(\d+)/);
|
|
528
|
+
if (m && m[1]) return m[1];
|
|
529
|
+
m = path.match(/\/direct_v2\/inbox\/threads\/(\d+)/);
|
|
530
|
+
if (m && m[1]) return m[1];
|
|
531
|
+
m = path.match(/\/direct_v2\/inbox\/(\d+)/);
|
|
532
|
+
if (m && m[1]) return m[1];
|
|
533
|
+
// last resort: look for any long numeric id in path
|
|
534
|
+
const anyId = path.match(/(\d{6,})/);
|
|
535
|
+
if (anyId && anyId[1]) return anyId[1];
|
|
536
|
+
} catch (e) {
|
|
537
|
+
// ignore
|
|
538
|
+
}
|
|
374
539
|
return undefined;
|
|
375
540
|
}
|
|
376
541
|
|