nodejs-insta-private-api-mqtt 1.3.10 → 1.3.12
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,24 @@ 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, milliseconds, microseconds, nanoseconds)
|
|
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
|
+
* Note: No rate-limiting code is included.
|
|
25
|
+
*/
|
|
26
|
+
|
|
9
27
|
class MessageSyncMixin extends mixin_1.Mixin {
|
|
10
28
|
constructor() {
|
|
11
29
|
super();
|
|
@@ -36,10 +54,17 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
36
54
|
client.mqtt.listen({
|
|
37
55
|
topic: constants_1.Topics.MESSAGE_SYNC.id,
|
|
38
56
|
transformer: async ({ payload }) => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
try {
|
|
58
|
+
const unzipped = await (0, shared_1.tryUnzipAsync)(payload);
|
|
59
|
+
const parsed = constants_1.Topics.MESSAGE_SYNC.parser
|
|
60
|
+
.parseMessage(constants_1.Topics.MESSAGE_SYNC, unzipped)
|
|
61
|
+
.map(msg => msg.data);
|
|
62
|
+
return parsed;
|
|
63
|
+
} catch (err) {
|
|
64
|
+
// If transformer fails, return empty array so handler is tolerant
|
|
65
|
+
console.warn('[MESSAGE_SYNC] transformer parse failed:', err?.message || err);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
43
68
|
},
|
|
44
69
|
}, data => {
|
|
45
70
|
this.handleMessageSync(client, data);
|
|
@@ -47,9 +72,13 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
47
72
|
} else {
|
|
48
73
|
console.log(`[MESSAGE_SYNC] mqtt.listen() NOT FOUND - using fallback 'receive' event`);
|
|
49
74
|
client.on('receive', (topic, messages) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
75
|
+
try {
|
|
76
|
+
if (topic.id === constants_1.Topics.MESSAGE_SYNC.id) {
|
|
77
|
+
const data = messages.map(m => m.data);
|
|
78
|
+
this.handleMessageSync(client, data);
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.warn('[MESSAGE_SYNC] receive fallback handler error:', err?.message || err);
|
|
53
82
|
}
|
|
54
83
|
});
|
|
55
84
|
}
|
|
@@ -67,16 +96,26 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
67
96
|
}
|
|
68
97
|
|
|
69
98
|
if (this.pendingUserFetches.has(userIdStr)) {
|
|
70
|
-
|
|
99
|
+
try {
|
|
100
|
+
return await this.pendingUserFetches.get(userIdStr);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// if pending fetch failed, continue to fresh attempt
|
|
103
|
+
}
|
|
71
104
|
}
|
|
72
105
|
|
|
73
106
|
const fetchPromise = (async () => {
|
|
74
107
|
try {
|
|
108
|
+
// small backoff to avoid immediate burst of parallel requests
|
|
109
|
+
await new Promise(r => setTimeout(r, 120));
|
|
75
110
|
if (client.ig && client.ig.user && client.ig.user.info) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
try {
|
|
112
|
+
const userInfo = await client.ig.user.info(userIdStr);
|
|
113
|
+
if (userInfo && userInfo.username) {
|
|
114
|
+
this.userCache.set(userIdStr, userInfo.username);
|
|
115
|
+
return userInfo.username;
|
|
116
|
+
}
|
|
117
|
+
} catch (innerErr) {
|
|
118
|
+
// rate-limited or not found - swallow
|
|
80
119
|
}
|
|
81
120
|
}
|
|
82
121
|
} catch (err) {
|
|
@@ -96,158 +135,171 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
96
135
|
let content = '';
|
|
97
136
|
let mediaInfo = '';
|
|
98
137
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
try {
|
|
139
|
+
switch (itemType) {
|
|
140
|
+
case 'text':
|
|
141
|
+
content = msgValue.text || msgValue.body || '';
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
case 'media':
|
|
145
|
+
case 'raven_media':
|
|
146
|
+
content = '[PHOTO/VIDEO]';
|
|
147
|
+
if (msgValue.media) {
|
|
148
|
+
const media = msgValue.media;
|
|
149
|
+
if (media.image_versions2) {
|
|
150
|
+
content = '[PHOTO]';
|
|
151
|
+
mediaInfo = ` URL: ${media.image_versions2?.candidates?.[0]?.url || 'N/A'}`;
|
|
152
|
+
} else if (media.video_versions) {
|
|
153
|
+
content = '[VIDEO]';
|
|
154
|
+
mediaInfo = ` Duration: ${media.video_duration || 'N/A'}s`;
|
|
155
|
+
}
|
|
115
156
|
}
|
|
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)}..."`;
|
|
157
|
+
if (msgValue.visual_media) {
|
|
158
|
+
content = '[DISAPPEARING MEDIA]';
|
|
144
159
|
}
|
|
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}"`;
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case 'voice_media':
|
|
163
|
+
content = '[VOICE MESSAGE]';
|
|
164
|
+
if (msgValue.voice_media?.media?.audio) {
|
|
165
|
+
const duration = msgValue.voice_media.media.audio.duration || 0;
|
|
166
|
+
content = `[VOICE MESSAGE] Duration: ${duration}ms`;
|
|
155
167
|
}
|
|
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}"`;
|
|
168
|
+
break;
|
|
169
|
+
|
|
170
|
+
case 'animated_media':
|
|
171
|
+
content = '[GIF]';
|
|
172
|
+
if (msgValue.animated_media?.images?.fixed_height?.url) {
|
|
173
|
+
mediaInfo = ` URL: ${msgValue.animated_media.images.fixed_height.url}`;
|
|
166
174
|
}
|
|
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
|
-
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
case 'media_share':
|
|
178
|
+
content = '[SHARED POST]';
|
|
179
|
+
if (msgValue.media_share) {
|
|
180
|
+
const share = msgValue.media_share;
|
|
181
|
+
content = `[SHARED POST] From: @${share.user?.username || 'unknown'}`;
|
|
182
|
+
if (share.caption?.text) {
|
|
183
|
+
content += ` Caption: "${String(share.caption.text).substring(0, 50)}..."`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case 'reel_share':
|
|
189
|
+
content = '[SHARED REEL]';
|
|
190
|
+
if (msgValue.reel_share) {
|
|
191
|
+
const reel = msgValue.reel_share;
|
|
192
|
+
content = `[SHARED REEL] From: @${reel.media?.user?.username || 'unknown'}`;
|
|
193
|
+
if (reel.text) {
|
|
194
|
+
content += ` Text: "${reel.text}"`;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
|
|
199
|
+
case 'story_share':
|
|
200
|
+
content = '[SHARED STORY]';
|
|
201
|
+
if (msgValue.story_share) {
|
|
202
|
+
const story = msgValue.story_share;
|
|
203
|
+
content = `[SHARED STORY] From: @${story.media?.user?.username || 'unknown'}`;
|
|
204
|
+
if (story.message) {
|
|
205
|
+
content += ` Message: "${story.message}"`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
|
|
210
|
+
case 'felix_share':
|
|
211
|
+
content = '[SHARED IGTV/VIDEO]';
|
|
212
|
+
if (msgValue.felix_share?.video) {
|
|
213
|
+
content = `[SHARED IGTV] Title: "${msgValue.felix_share.video.title || 'N/A'}"`;
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
|
|
217
|
+
case 'clip':
|
|
218
|
+
content = '[SHARED CLIP]';
|
|
219
|
+
if (msgValue.clip?.clip) {
|
|
220
|
+
content = `[SHARED CLIP] From: @${msgValue.clip.clip.user?.username || 'unknown'}`;
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
|
|
224
|
+
case 'profile':
|
|
225
|
+
content = '[SHARED PROFILE]';
|
|
226
|
+
if (msgValue.profile) {
|
|
227
|
+
content = `[SHARED PROFILE] @${msgValue.profile.username || 'unknown'}`;
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
|
|
231
|
+
case 'location':
|
|
232
|
+
content = '[LOCATION]';
|
|
233
|
+
if (msgValue.location) {
|
|
234
|
+
content = `[LOCATION] ${msgValue.location.name || msgValue.location.address || 'Unknown location'}`;
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
|
|
238
|
+
case 'hashtag':
|
|
239
|
+
content = '[HASHTAG]';
|
|
240
|
+
if (msgValue.hashtag) {
|
|
241
|
+
content = `[HASHTAG] #${msgValue.hashtag.name || 'unknown'}`;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
|
|
245
|
+
case 'like':
|
|
246
|
+
content = '[LIKE]';
|
|
247
|
+
break;
|
|
248
|
+
|
|
249
|
+
case 'link':
|
|
250
|
+
content = '[LINK]';
|
|
251
|
+
if (msgValue.link) {
|
|
252
|
+
content = `[LINK] ${msgValue.link.text || msgValue.link.link_url || 'N/A'}`;
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
|
|
256
|
+
case 'action_log':
|
|
257
|
+
content = '[ACTION]';
|
|
258
|
+
if (msgValue.action_log) {
|
|
259
|
+
content = `[ACTION] ${msgValue.action_log.description || 'N/A'}`;
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
|
|
263
|
+
case 'placeholder':
|
|
264
|
+
content = '[PLACEHOLDER]';
|
|
265
|
+
if (msgValue.placeholder?.message) {
|
|
266
|
+
content = `[PLACEHOLDER] ${msgValue.placeholder.message}`;
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
case 'xma':
|
|
271
|
+
case 'xma_media_share':
|
|
272
|
+
content = '[XMA SHARE]';
|
|
273
|
+
if (msgValue.xma_link_url) {
|
|
274
|
+
content = `[XMA SHARE] ${msgValue.xma_link_url}`;
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
|
|
278
|
+
case 'video_call_event':
|
|
279
|
+
content = '[VIDEO CALL EVENT]';
|
|
280
|
+
if (msgValue.video_call_event) {
|
|
281
|
+
content = `[VIDEO CALL] ${msgValue.video_call_event.action || 'event'}`;
|
|
282
|
+
}
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
default:
|
|
286
|
+
if (msgValue && (msgValue.text || msgValue.body)) {
|
|
287
|
+
content = msgValue.text || msgValue.body;
|
|
288
|
+
} else {
|
|
289
|
+
content = `[${(itemType || 'UNKNOWN').toUpperCase()}]`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
} catch (e) {
|
|
293
|
+
// defensive fallback
|
|
294
|
+
try {
|
|
295
|
+
if (msgValue && (msgValue.text || msgValue.body)) {
|
|
296
|
+
content = msgValue.text || msgValue.body;
|
|
248
297
|
} else {
|
|
249
298
|
content = `[${(itemType || 'UNKNOWN').toUpperCase()}]`;
|
|
250
299
|
}
|
|
300
|
+
} catch (e2) {
|
|
301
|
+
content = `[${(itemType || 'UNKNOWN').toUpperCase()}]`;
|
|
302
|
+
}
|
|
251
303
|
}
|
|
252
304
|
|
|
253
305
|
return content + mediaInfo;
|
|
@@ -255,6 +307,26 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
255
307
|
|
|
256
308
|
formatMessageForConsole(msgData) {
|
|
257
309
|
const separator = '----------------------------------------';
|
|
310
|
+
// robust timestamp formatting into readable date+time in Europe/Bucharest
|
|
311
|
+
let ts = 'N/A';
|
|
312
|
+
try {
|
|
313
|
+
const parsed = this.parseTimestamp(msgData.timestamp);
|
|
314
|
+
if (parsed) {
|
|
315
|
+
const d = new Date(parsed);
|
|
316
|
+
ts = d.toLocaleString('ro-RO', {
|
|
317
|
+
year: 'numeric',
|
|
318
|
+
month: '2-digit',
|
|
319
|
+
day: '2-digit',
|
|
320
|
+
hour: '2-digit',
|
|
321
|
+
minute: '2-digit',
|
|
322
|
+
second: '2-digit',
|
|
323
|
+
hour12: false,
|
|
324
|
+
timeZone: 'Europe/Bucharest'
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
} catch (e) {
|
|
328
|
+
ts = 'N/A';
|
|
329
|
+
}
|
|
258
330
|
const lines = [
|
|
259
331
|
'',
|
|
260
332
|
separator,
|
|
@@ -266,14 +338,69 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
266
338
|
`Type: ${msgData.itemType || 'text'}`,
|
|
267
339
|
`Thread: ${msgData.threadId || 'unknown'}`,
|
|
268
340
|
`Message ID: ${msgData.messageId || 'unknown'}`,
|
|
269
|
-
`Timestamp: ${
|
|
270
|
-
`Status: ${msgData.status || '
|
|
341
|
+
`Timestamp: ${ts}`,
|
|
342
|
+
`Status: ${msgData.status || 'unknown'}`,
|
|
271
343
|
separator,
|
|
272
344
|
''
|
|
273
345
|
];
|
|
274
346
|
return lines.join('\n');
|
|
275
347
|
}
|
|
276
348
|
|
|
349
|
+
/**
|
|
350
|
+
* parseTimestamp
|
|
351
|
+
* - accepts numeric strings or numbers in seconds, milliseconds, microseconds, nanoseconds
|
|
352
|
+
* - normalizes to milliseconds
|
|
353
|
+
* - sanity-checks to avoid absurd future dates; returns Date.now() fallback if out of range
|
|
354
|
+
*/
|
|
355
|
+
parseTimestamp(ts) {
|
|
356
|
+
try {
|
|
357
|
+
if (ts === undefined || ts === null) return null;
|
|
358
|
+
// if object with .ms or similar, try common fields
|
|
359
|
+
if (typeof ts === 'object') {
|
|
360
|
+
if (ts.ms) return Number(ts.ms);
|
|
361
|
+
if (ts.seconds) return Number(ts.seconds) * 1000;
|
|
362
|
+
if (ts.nano) return Math.floor(Number(ts.nano) / 1e6);
|
|
363
|
+
// fallback to toString
|
|
364
|
+
ts = String(ts);
|
|
365
|
+
}
|
|
366
|
+
let n = Number(ts);
|
|
367
|
+
if (!Number.isFinite(n)) return null;
|
|
368
|
+
|
|
369
|
+
// Heuristics:
|
|
370
|
+
// nanoseconds ~ 1e18+, microseconds ~ 1e15+, milliseconds ~ 1e12, seconds ~ 1e9
|
|
371
|
+
if (n > 1e17) {
|
|
372
|
+
// nanoseconds -> ms
|
|
373
|
+
n = Math.floor(n / 1e6);
|
|
374
|
+
} else if (n > 1e14) {
|
|
375
|
+
// microseconds -> ms
|
|
376
|
+
n = Math.floor(n / 1e3);
|
|
377
|
+
} else if (n > 1e12) {
|
|
378
|
+
// likely already ms (leave)
|
|
379
|
+
n = Math.floor(n);
|
|
380
|
+
} else if (n > 1e9) {
|
|
381
|
+
// seconds -> ms
|
|
382
|
+
n = Math.floor(n * 1000);
|
|
383
|
+
} else if (n > 1e6) {
|
|
384
|
+
// ambiguous (older formats) -> treat as seconds -> ms
|
|
385
|
+
n = Math.floor(n * 1000);
|
|
386
|
+
} else {
|
|
387
|
+
// too small -> invalid
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// sanity range: allow roughly 2010-2036 (ms)
|
|
392
|
+
const min = 1262304000000; // 2010-01-01
|
|
393
|
+
const max = 2114380800000; // 2037-01-01 (safe future upper bound)
|
|
394
|
+
if (!Number.isFinite(n) || n < min || n > max) {
|
|
395
|
+
// fallback to now to avoid huge future years displayed
|
|
396
|
+
return Date.now();
|
|
397
|
+
}
|
|
398
|
+
return n;
|
|
399
|
+
} catch (e) {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
277
404
|
async handleMessageSync(client, syncData) {
|
|
278
405
|
if (!syncData || !Array.isArray(syncData)) {
|
|
279
406
|
console.log(`[MESSAGE_SYNC] No sync data received`);
|
|
@@ -281,96 +408,184 @@ class MessageSyncMixin extends mixin_1.Mixin {
|
|
|
281
408
|
}
|
|
282
409
|
|
|
283
410
|
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 });
|
|
411
|
+
try {
|
|
412
|
+
const data = element.data;
|
|
413
|
+
|
|
414
|
+
if (!data) {
|
|
415
|
+
// fallback: emit iris with original element
|
|
416
|
+
client.emit('iris', element);
|
|
296
417
|
continue;
|
|
297
418
|
}
|
|
298
419
|
|
|
299
|
-
|
|
420
|
+
// ensure element.data removed in downstream parsed message (keeps original behavior)
|
|
421
|
+
delete element.data;
|
|
422
|
+
|
|
423
|
+
for (const e of data) {
|
|
300
424
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
425
|
+
// tolerant handling: e.value may be string, object, null, or already parsed
|
|
426
|
+
let parsedValue = {};
|
|
427
|
+
if (e.value === undefined || e.value === null) {
|
|
428
|
+
parsedValue = {};
|
|
429
|
+
} else if (typeof e.value === 'string') {
|
|
430
|
+
const str = e.value.trim();
|
|
431
|
+
if (str.length === 0) {
|
|
432
|
+
parsedValue = {};
|
|
433
|
+
} else {
|
|
434
|
+
try {
|
|
435
|
+
parsedValue = JSON.parse(str);
|
|
436
|
+
} catch (errJson) {
|
|
437
|
+
// If not JSON, attempt basic fallback (sometimes server sends plain key=value or quoted)
|
|
438
|
+
try {
|
|
439
|
+
// try to safe-evaluate limited forms like a bare object without quotes (rare)
|
|
440
|
+
parsedValue = {};
|
|
441
|
+
} catch (err2) {
|
|
442
|
+
parsedValue = {};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} else if (typeof e.value === 'object') {
|
|
447
|
+
parsedValue = e.value;
|
|
448
|
+
} else {
|
|
449
|
+
parsedValue = {};
|
|
310
450
|
}
|
|
311
|
-
|
|
312
|
-
|
|
451
|
+
|
|
452
|
+
// Sometimes the message payload is nested under 'message' or similar
|
|
453
|
+
const msgValue = parsedValue.message || parsedValue.data || parsedValue || {};
|
|
454
|
+
|
|
455
|
+
if (!e.path) {
|
|
456
|
+
// no path means iris-like delta; merge element + e
|
|
457
|
+
client.emit('iris', { ...element, ...e, value: msgValue });
|
|
458
|
+
continue;
|
|
313
459
|
}
|
|
314
460
|
|
|
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
|
-
|
|
461
|
+
// normalize path check for thread messages
|
|
462
|
+
if ((e.path && e.path.startsWith('/direct_v2/threads')) ||
|
|
463
|
+
(e.path && e.path.startsWith('/direct_v2/inbox/threads')) ||
|
|
464
|
+
(e.path && e.path.indexOf('/direct_v2/threads/') !== -1) ) {
|
|
465
|
+
|
|
466
|
+
if (msgValue && (msgValue.item_type || msgValue.itemType || msgValue.type || msgValue.msg_type)) {
|
|
467
|
+
// determine item type as robustly as possible
|
|
468
|
+
const itemType = msgValue.item_type || msgValue.itemType || msgValue.type || msgValue.msg_type || 'text';
|
|
469
|
+
|
|
470
|
+
// thread id extraction
|
|
471
|
+
const threadId = MessageSyncMixin.getThreadIdFromPath(e.path);
|
|
472
|
+
|
|
473
|
+
// user id resolution: try many possible fields
|
|
474
|
+
const userId = msgValue.user_id || msgValue.from_user_id || msgValue.sender_id || msgValue.userId || msgValue.senderId || null;
|
|
475
|
+
|
|
476
|
+
// username resolution: prefer embedded username, otherwise fetch
|
|
477
|
+
let username = msgValue.username || msgValue.from_username || null;
|
|
478
|
+
if (!username && userId) {
|
|
479
|
+
try {
|
|
480
|
+
username = await this.getUsernameFromId(client, userId);
|
|
481
|
+
} catch (ux) {
|
|
482
|
+
username = null;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (!username) {
|
|
486
|
+
username = `user_${userId || 'unknown'}`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const textContent = this.extractMessageContent(msgValue, itemType);
|
|
490
|
+
|
|
491
|
+
const messageId = msgValue.item_id || msgValue.id || msgValue.client_context || msgValue.client_context_id || msgValue.message_id || msgValue.messageId || null;
|
|
492
|
+
const timestamp = msgValue.timestamp || msgValue.ts || msgValue.client_time || null;
|
|
493
|
+
|
|
494
|
+
// determine status based on whether message author is the logged-in account
|
|
495
|
+
let status = 'received';
|
|
496
|
+
try {
|
|
497
|
+
const ownId = client?.ig?.state?.cookieUserId || client?.ig?.state?.userId || null;
|
|
498
|
+
if (ownId && userId && String(userId) === String(ownId)) {
|
|
499
|
+
status = 'sent';
|
|
500
|
+
} else {
|
|
501
|
+
status = 'received';
|
|
502
|
+
}
|
|
503
|
+
} catch (stErr) {
|
|
504
|
+
status = 'received';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const msgData = {
|
|
508
|
+
username: username,
|
|
509
|
+
userId: userId,
|
|
510
|
+
text: textContent,
|
|
511
|
+
itemType: itemType,
|
|
512
|
+
threadId: threadId,
|
|
513
|
+
messageId: messageId,
|
|
514
|
+
timestamp: timestamp,
|
|
515
|
+
status: status,
|
|
516
|
+
rawData: msgValue
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
// console output (keeps original formatted block)
|
|
520
|
+
try {
|
|
521
|
+
console.log(this.formatMessageForConsole(msgData));
|
|
522
|
+
} catch (eLog) {
|
|
523
|
+
// don't let logging break processing
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const parsedMessage = {
|
|
527
|
+
...element,
|
|
528
|
+
message: {
|
|
529
|
+
path: e.path,
|
|
530
|
+
op: e.op,
|
|
531
|
+
thread_id: threadId,
|
|
532
|
+
...msgValue,
|
|
533
|
+
},
|
|
534
|
+
parsed: msgData
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
client.emit('message', parsedMessage);
|
|
538
|
+
continue;
|
|
539
|
+
} // end if msgValue has item_type
|
|
540
|
+
} // end if path matches threads
|
|
343
541
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
|
|
542
|
+
// If not a thread message, emit as threadUpdate or iris depending on payload
|
|
543
|
+
try {
|
|
544
|
+
const updateValue = e.value ? (typeof e.value === 'string' ? (() => {
|
|
545
|
+
try { return JSON.parse(e.value); } catch { return e.value; }
|
|
546
|
+
})() : e.value) : {};
|
|
547
|
+
client.emit('threadUpdate', {
|
|
548
|
+
...element,
|
|
549
|
+
meta: {
|
|
550
|
+
path: e.path,
|
|
551
|
+
op: e.op,
|
|
552
|
+
thread_id: MessageSyncMixin.getThreadIdFromPath(e.path),
|
|
553
|
+
},
|
|
554
|
+
update: updateValue,
|
|
555
|
+
});
|
|
556
|
+
} catch (errUpdate) {
|
|
557
|
+
client.emit('iris', { ...element, ...e, value: parsedValue });
|
|
558
|
+
}
|
|
559
|
+
} catch (inner) {
|
|
560
|
+
console.log(`[MESSAGE_SYNC] element handling error: ${inner?.message || inner}`);
|
|
361
561
|
}
|
|
362
562
|
}
|
|
563
|
+
} catch (outer) {
|
|
564
|
+
console.log(`[MESSAGE_SYNC] item error: ${outer?.message || outer}`);
|
|
363
565
|
}
|
|
364
566
|
}
|
|
365
567
|
}
|
|
366
568
|
|
|
367
569
|
static getThreadIdFromPath(path) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
570
|
+
if (!path) return undefined;
|
|
571
|
+
// Common patterns:
|
|
572
|
+
// /direct_v2/threads/<thread_id>/...
|
|
573
|
+
// /direct_v2/inbox/threads/<thread_id>/...
|
|
574
|
+
// /direct_v2/threads/<thread_id>
|
|
575
|
+
// possibly with trailing segments
|
|
576
|
+
try {
|
|
577
|
+
let m = path.match(/\/direct_v2\/threads\/(\d+)/);
|
|
578
|
+
if (m && m[1]) return m[1];
|
|
579
|
+
m = path.match(/\/direct_v2\/inbox\/threads\/(\d+)/);
|
|
580
|
+
if (m && m[1]) return m[1];
|
|
581
|
+
m = path.match(/\/direct_v2\/inbox\/(\d+)/);
|
|
582
|
+
if (m && m[1]) return m[1];
|
|
583
|
+
// last resort: look for any long numeric id in path
|
|
584
|
+
const anyId = path.match(/(\d{6,})/);
|
|
585
|
+
if (anyId && anyId[1]) return anyId[1];
|
|
586
|
+
} catch (e) {
|
|
587
|
+
// ignore
|
|
588
|
+
}
|
|
374
589
|
return undefined;
|
|
375
590
|
}
|
|
376
591
|
|
|
@@ -17,6 +17,7 @@ const error_handler_1 = require("./features/error-handler");
|
|
|
17
17
|
const gap_handler_1 = require("./features/gap-handler");
|
|
18
18
|
const enhanced_direct_commands_1 = require("./commands/enhanced.direct.commands");
|
|
19
19
|
const presence_typing_mixin_1 = require("./mixins/presence-typing.mixin");
|
|
20
|
+
|
|
20
21
|
class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
21
22
|
get mqtt() {
|
|
22
23
|
return this._mqtt;
|
|
@@ -47,25 +48,75 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
47
48
|
this.realtimeDebug(`Applying mixins: ${mixins.map(m => m.name).join(', ')}`);
|
|
48
49
|
(0, mixins_1.applyMixins)(mixins, this, this.ig);
|
|
49
50
|
|
|
50
|
-
//
|
|
51
|
+
// ---------- AUTO-CONNECT BLOCK (UPDATED to wait for MQTT creds like APK) ----------
|
|
52
|
+
// Keep folder same as before for compatibility, but WAIT until device/mqtt creds present
|
|
51
53
|
const folder = './auth_info_ig';
|
|
52
54
|
const { useMultiFileAuthState } = require('../useMultiFileAuthState');
|
|
53
55
|
const fs = require('fs');
|
|
54
56
|
const path = require('path');
|
|
57
|
+
|
|
58
|
+
// store optional attached authState for later use
|
|
59
|
+
this._attachedAuthState = null;
|
|
60
|
+
|
|
61
|
+
// helper: wait/poll until mqtt/device credentials available (CountDownLatch equivalent)
|
|
62
|
+
const waitForMqttCredentials = async (auth, timeoutMs = 15000, pollMs = 250) => {
|
|
63
|
+
const start = Date.now();
|
|
64
|
+
const hasCreds = () => {
|
|
65
|
+
try {
|
|
66
|
+
const d = (auth && typeof auth.getData === 'function') ? auth.getData() : (auth && auth.data ? auth.data : null);
|
|
67
|
+
if (!d) return false;
|
|
68
|
+
// Acceptable indicators: device.deviceSecret OR mqttAuth.jwt OR mqttAuth.deviceSecret
|
|
69
|
+
if (d.device && (d.device.deviceSecret || d.device.secret)) return true;
|
|
70
|
+
if (d.mqttAuth && (d.mqttAuth.jwt || d.mqttAuth.deviceSecret)) return true;
|
|
71
|
+
// fallback: creds/sessionid present (not ideal but better than nothing)
|
|
72
|
+
if (d.creds && (d.creds.sessionId || d.creds.csrfToken || d.creds.authorization)) return true;
|
|
73
|
+
return false;
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
// Poll until available or timeout
|
|
79
|
+
while (Date.now() - start < timeoutMs) {
|
|
80
|
+
if (hasCreds()) return true;
|
|
81
|
+
await new Promise(r => setTimeout(r, pollMs));
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Attempt auto-start only if creds.json exists — but wait for device/mqtt creds like APK does.
|
|
55
87
|
if (fs.existsSync(path.join(folder, 'creds.json'))) {
|
|
56
88
|
setTimeout(async () => {
|
|
57
89
|
try {
|
|
58
90
|
const auth = await useMultiFileAuthState(folder);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
91
|
+
// attach for later use
|
|
92
|
+
this._attachedAuthState = auth;
|
|
93
|
+
if (auth.hasSession && auth.hasSession()) {
|
|
94
|
+
console.log('[REALTIME] Auto-start candidate session detected — loading creds...');
|
|
95
|
+
try {
|
|
96
|
+
await auth.loadCreds(this.ig);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
// ignore load errors but log
|
|
99
|
+
console.warn('[REALTIME] loadCreds warning:', e?.message || e);
|
|
100
|
+
}
|
|
101
|
+
// Wait for MQTT/device credentials to be present (APK-like behaviour)
|
|
102
|
+
const ready = await waitForMqttCredentials(auth, 20000, 300);
|
|
103
|
+
if (!ready) {
|
|
104
|
+
console.warn('[REALTIME] MQTT/device credentials not found within timeout — auto-connect aborted (will still allow manual connect).');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
console.log('[REALTIME] Device/MQTT credentials present — attempting connectFromSavedSession...');
|
|
108
|
+
try {
|
|
109
|
+
await this.connectFromSavedSession(auth);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
console.error('[REALTIME] Constructor auto-connect failed:', e?.message || e);
|
|
112
|
+
}
|
|
63
113
|
}
|
|
64
114
|
} catch (e) {
|
|
65
|
-
console.error('[REALTIME] Constructor auto-
|
|
115
|
+
console.error('[REALTIME] Constructor auto-start exception:', e?.message || e);
|
|
66
116
|
}
|
|
67
117
|
}, 100);
|
|
68
118
|
}
|
|
119
|
+
// ---------- END AUTO-CONNECT BLOCK ----------
|
|
69
120
|
}
|
|
70
121
|
|
|
71
122
|
/**
|
|
@@ -193,17 +244,32 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
193
244
|
try {
|
|
194
245
|
const authHeader = this.ig.state.authorization;
|
|
195
246
|
if (!authHeader) return null;
|
|
196
|
-
//
|
|
197
|
-
const
|
|
198
|
-
//
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
247
|
+
// Attempt to decode possible bearer formats safely
|
|
248
|
+
const raw = String(authHeader || '');
|
|
249
|
+
// strip known prefixes
|
|
250
|
+
const candidate = raw.replace(/^Bearer\s*/i, '').replace(/^IGT:2:/i, '');
|
|
251
|
+
// if contains '.', might be JWT-like (header.payload.sig)
|
|
252
|
+
if (candidate.includes('.')) {
|
|
253
|
+
const parts = candidate.split('.');
|
|
254
|
+
if (parts.length >= 2) {
|
|
255
|
+
try {
|
|
256
|
+
const payload = Buffer.from(parts[1], 'base64').toString('utf8');
|
|
257
|
+
const parsed = JSON.parse(payload);
|
|
258
|
+
return parsed.sessionid || parsed.session_id || parsed.session || null;
|
|
259
|
+
} catch (e) {
|
|
260
|
+
// ignore parse error
|
|
261
|
+
}
|
|
262
|
+
}
|
|
205
263
|
}
|
|
206
|
-
|
|
264
|
+
// fallback: try base64 decode whole candidate
|
|
265
|
+
try {
|
|
266
|
+
const decoded = Buffer.from(candidate, 'base64').toString('utf8');
|
|
267
|
+
const parsed = JSON.parse(decoded);
|
|
268
|
+
return parsed.sessionid || parsed.session_id || null;
|
|
269
|
+
} catch (e) {
|
|
270
|
+
// ignore
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
207
273
|
} catch (e) {
|
|
208
274
|
return null;
|
|
209
275
|
}
|
|
@@ -221,45 +287,106 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
221
287
|
|
|
222
288
|
// Ensure deviceId is a string to avoid substring errors
|
|
223
289
|
const deviceId = String(this.ig.state.phoneId || this.ig.state.deviceId || 'device_unknown');
|
|
224
|
-
|
|
225
|
-
//
|
|
226
|
-
sessionid =
|
|
227
|
-
|
|
228
|
-
|
|
290
|
+
|
|
291
|
+
// Try multiple ways to determine sessionid/password and deviceSecret
|
|
292
|
+
let sessionid = null;
|
|
293
|
+
try {
|
|
294
|
+
// 1. Try extracting from JWT/Authorization header
|
|
295
|
+
sessionid = this.extractSessionIdFromJWT();
|
|
296
|
+
if (sessionid) {
|
|
297
|
+
this.realtimeDebug(`SessionID extracted from JWT-like auth: ${String(sessionid).substring(0, 20)}...`);
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
sessionid = null;
|
|
229
301
|
}
|
|
230
|
-
|
|
302
|
+
|
|
303
|
+
// 2. Try state helpers (some libs expose extractCookieValue)
|
|
231
304
|
if (!sessionid) {
|
|
232
305
|
try {
|
|
233
|
-
|
|
306
|
+
if (typeof this.ig.state.extractCookieValue === 'function') {
|
|
307
|
+
sessionid = this.ig.state.extractCookieValue('sessionid');
|
|
308
|
+
}
|
|
234
309
|
} catch (e) {
|
|
235
310
|
sessionid = null;
|
|
236
311
|
}
|
|
237
312
|
}
|
|
238
|
-
|
|
313
|
+
|
|
314
|
+
// 3. Try raw state fields
|
|
239
315
|
if (!sessionid) {
|
|
240
316
|
try {
|
|
241
|
-
sessionid = this.ig.state.
|
|
242
|
-
} catch (
|
|
317
|
+
sessionid = this.ig.state.sessionId || this.ig.state.sessionid || this.ig.state.cookies?.sessionid || null;
|
|
318
|
+
} catch (e) {
|
|
243
319
|
sessionid = null;
|
|
244
320
|
}
|
|
245
321
|
}
|
|
246
|
-
|
|
322
|
+
|
|
323
|
+
// 4. Try cookieJar inspection (best effort)
|
|
247
324
|
if (!sessionid) {
|
|
248
325
|
try {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
326
|
+
if (this.ig.state.cookieJar && typeof this.ig.state.cookieJar.getCookiesSync === 'function') {
|
|
327
|
+
const cookies = this.ig.state.cookieJar.getCookiesSync('https://i.instagram.com/') || [];
|
|
328
|
+
const found = cookies.find(c => (c.key === 'sessionid' || c.name === 'sessionid'));
|
|
329
|
+
if (found) sessionid = found.value;
|
|
330
|
+
}
|
|
252
331
|
} catch (e) {
|
|
253
|
-
|
|
332
|
+
// ignore
|
|
254
333
|
}
|
|
255
334
|
}
|
|
256
|
-
|
|
335
|
+
|
|
336
|
+
// 5. Last resort fallback (generated)
|
|
257
337
|
if (!sessionid) {
|
|
258
338
|
const userId = this.ig.state.cookieUserId || this.ig.state.userId || '0';
|
|
259
339
|
sessionid = String(userId) + '_' + Date.now();
|
|
260
340
|
this.realtimeDebug(`SessionID generated (fallback): ${sessionid}`);
|
|
261
341
|
}
|
|
262
|
-
|
|
342
|
+
|
|
343
|
+
// Determine deviceSecret if available via attached authState or ig.state
|
|
344
|
+
let deviceSecret = null;
|
|
345
|
+
try {
|
|
346
|
+
if (this._attachedAuthState && typeof this._attachedAuthState.getData === 'function') {
|
|
347
|
+
const d = this._attachedAuthState.getData();
|
|
348
|
+
if (d && d.device && (d.device.deviceSecret || d.device.secret)) {
|
|
349
|
+
deviceSecret = d.device.deviceSecret || d.device.secret;
|
|
350
|
+
}
|
|
351
|
+
// also check mqttAuth
|
|
352
|
+
if (!deviceSecret && d && d.mqttAuth && (d.mqttAuth.deviceSecret || d.mqttAuth.secret)) {
|
|
353
|
+
deviceSecret = d.mqttAuth.deviceSecret || d.mqttAuth.secret;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
} catch (e) {}
|
|
357
|
+
// also try ig.state
|
|
358
|
+
try {
|
|
359
|
+
if (!deviceSecret && (this.ig.state.deviceSecret || this.ig.state.mqttDeviceSecret)) {
|
|
360
|
+
deviceSecret = this.ig.state.deviceSecret || this.ig.state.mqttDeviceSecret;
|
|
361
|
+
}
|
|
362
|
+
} catch (e) {}
|
|
363
|
+
|
|
364
|
+
// Determine mqttAuth token if present
|
|
365
|
+
let mqttJwt = null;
|
|
366
|
+
try {
|
|
367
|
+
if (this._attachedAuthState && typeof this._attachedAuthState.getData === 'function') {
|
|
368
|
+
const d = this._attachedAuthState.getData();
|
|
369
|
+
if (d && d.mqttAuth && d.mqttAuth.jwt) mqttJwt = d.mqttAuth.jwt;
|
|
370
|
+
}
|
|
371
|
+
// fallback to ig.state
|
|
372
|
+
if (!mqttJwt && this.ig.state.mqttJwt) mqttJwt = this.ig.state.mqttJwt;
|
|
373
|
+
} catch (e) {}
|
|
374
|
+
|
|
375
|
+
// Build password: prefer mqttJwt if present, else sessionid style string
|
|
376
|
+
let password;
|
|
377
|
+
if (mqttJwt) {
|
|
378
|
+
password = `jwt=${mqttJwt}`;
|
|
379
|
+
} else {
|
|
380
|
+
password = `sessionid=${sessionid}`;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Determine clientType and other clientInfo modifications if secure deviceSecret exists
|
|
384
|
+
const clientType = deviceSecret ? 'secure_cookie_auth' : 'cookie_auth';
|
|
385
|
+
|
|
386
|
+
const clientMqttSessionId = (BigInt(Date.now()) & BigInt(0xffffffff));
|
|
387
|
+
|
|
388
|
+
const subscribeTopics = [88, 135, 149, 150, 133, 146];
|
|
389
|
+
|
|
263
390
|
this.connection = new mqttot_1.MQTToTConnection({
|
|
264
391
|
clientIdentifier: deviceId.substring(0, 20),
|
|
265
392
|
clientInfo: {
|
|
@@ -274,11 +401,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
274
401
|
isInitiallyForeground: true,
|
|
275
402
|
networkType: 1,
|
|
276
403
|
networkSubtype: 0,
|
|
277
|
-
clientMqttSessionId:
|
|
278
|
-
subscribeTopics
|
|
279
|
-
clientType
|
|
404
|
+
clientMqttSessionId: clientMqttSessionId,
|
|
405
|
+
subscribeTopics,
|
|
406
|
+
clientType,
|
|
280
407
|
appId: BigInt(567067343352427),
|
|
281
|
-
deviceSecret: '',
|
|
408
|
+
deviceSecret: deviceSecret || '',
|
|
282
409
|
clientStack: 3,
|
|
283
410
|
...(this.initOptions?.connectOverrides || {}),
|
|
284
411
|
},
|
|
@@ -390,6 +517,11 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
390
517
|
|
|
391
518
|
console.log('[RealtimeClient] Connecting from saved session...');
|
|
392
519
|
|
|
520
|
+
// Attach authState to this instance so constructConnection can read deviceSecret/mqttAuth
|
|
521
|
+
try {
|
|
522
|
+
this._attachedAuthState = authStateHelper;
|
|
523
|
+
} catch (e) {}
|
|
524
|
+
|
|
393
525
|
const savedOptions = authStateHelper.getMqttConnectOptions?.();
|
|
394
526
|
|
|
395
527
|
const connectOptions = {
|
|
@@ -405,6 +537,18 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
405
537
|
hasIrisData: !!connectOptions.irisData
|
|
406
538
|
});
|
|
407
539
|
|
|
540
|
+
// If authState has mqttAuth with expiresAt, optionally warn if expired (non-fatal)
|
|
541
|
+
try {
|
|
542
|
+
const d = authStateHelper.getData?.() || authStateHelper.data || {};
|
|
543
|
+
const mqttAuth = d.mqttAuth || null;
|
|
544
|
+
if (mqttAuth && mqttAuth.expiresAt) {
|
|
545
|
+
const t = new Date(mqttAuth.expiresAt).getTime();
|
|
546
|
+
if (!isNaN(t) && Date.now() > t) {
|
|
547
|
+
console.warn('[RealtimeClient] Warning: saved mqttAuth token appears expired.');
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
} catch (e) {}
|
|
551
|
+
|
|
408
552
|
await this.connect(connectOptions);
|
|
409
553
|
|
|
410
554
|
if (authStateHelper.saveMqttSession) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-insta-private-api-mqtt",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.12",
|
|
4
4
|
"description": "Complete Instagram MQTT protocol with FULL iOS + Android support. 33 device presets (21 iOS + 12 Android). iPhone 16/15/14/13/12, iPad Pro, Samsung, Pixel, Huawei. Real-time DM messaging, view-once media extraction, sub-500ms latency.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|