nodejs-insta-private-api-mqtt 1.1.5 → 1.1.7
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/README.md +3 -3
- package/dist/realtime/realtime.client.js +48 -419
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -902,7 +902,7 @@ const {
|
|
|
902
902
|
RealtimeClient,
|
|
903
903
|
downloadContentFromMessage,
|
|
904
904
|
isViewOnceMedia
|
|
905
|
-
} = require('nodejs-insta-private-api
|
|
905
|
+
} = require('nodejs-insta-private-api');
|
|
906
906
|
const fs = require('fs');
|
|
907
907
|
|
|
908
908
|
const ig = new IgApiClient();
|
|
@@ -955,7 +955,7 @@ const {
|
|
|
955
955
|
downloadMediaBuffer,
|
|
956
956
|
hasMedia,
|
|
957
957
|
getMediaType
|
|
958
|
-
} = require('nodejs-insta-private-api');
|
|
958
|
+
} = require('nodejs-insta-private-api-mqtt');
|
|
959
959
|
|
|
960
960
|
realtime.on('message', async (data) => {
|
|
961
961
|
const msg = data.message;
|
|
@@ -995,7 +995,7 @@ realtime.on('message', async (data) => {
|
|
|
995
995
|
### Extract Media URLs Without Downloading
|
|
996
996
|
|
|
997
997
|
```javascript
|
|
998
|
-
const { extractMediaUrls } = require('nodejs-insta-private-api');
|
|
998
|
+
const { extractMediaUrls } = require('nodejs-insta-private-api-mqtt');
|
|
999
999
|
|
|
1000
1000
|
realtime.on('message', async (data) => {
|
|
1001
1001
|
const msg = data.message;
|
|
@@ -17,9 +17,6 @@ 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
|
-
const fs = require('fs');
|
|
21
|
-
const path = require('path');
|
|
22
|
-
|
|
23
20
|
class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
24
21
|
get mqtt() {
|
|
25
22
|
return this._mqtt;
|
|
@@ -38,20 +35,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
38
35
|
this.emitWarning = (e) => this.emit('warning', e);
|
|
39
36
|
this.ig = ig;
|
|
40
37
|
this.threads = new Map();
|
|
41
|
-
|
|
42
|
-
// === New: observability & metrics ===
|
|
43
|
-
this.metrics = {
|
|
44
|
-
reconnectCount: 0,
|
|
45
|
-
messagesReceived: 0,
|
|
46
|
-
parseErrors: 0,
|
|
47
|
-
lastConnectTime: null,
|
|
48
|
-
backoffAttempts: 0,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// Session state persistence file (changeable)
|
|
52
|
-
this._sessionStateFile = path.join('.', '.mqtt_session_state.json');
|
|
53
|
-
this._sessionState = this._loadSessionState();
|
|
54
|
-
|
|
38
|
+
|
|
55
39
|
this.irisHandshake = new iris_handshake_1.IrisHandshake(this);
|
|
56
40
|
this.skywalkerProtocol = new skywalker_protocol_1.SkywalkerProtocol(this);
|
|
57
41
|
this.presenceManager = new presence_manager_1.PresenceManager(this);
|
|
@@ -59,52 +43,21 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
59
43
|
this.errorHandler = new error_handler_1.ErrorHandler(this);
|
|
60
44
|
this.gapHandler = new gap_handler_1.GapHandler(this);
|
|
61
45
|
this.directCommands = new enhanced_direct_commands_1.EnhancedDirectCommands(this);
|
|
62
|
-
|
|
46
|
+
|
|
63
47
|
this.realtimeDebug(`Applying mixins: ${mixins.map(m => m.name).join(', ')}`);
|
|
64
48
|
(0, mixins_1.applyMixins)(mixins, this, this.ig);
|
|
65
|
-
|
|
66
|
-
// Periodic metrics logging (only if not disabled)
|
|
67
|
-
this._metricsInterval = setInterval(() => {
|
|
68
|
-
try {
|
|
69
|
-
if (this.metrics) {
|
|
70
|
-
this.realtimeDebug('METRICS', JSON.stringify(this.metrics));
|
|
71
|
-
}
|
|
72
|
-
} catch (e) { }
|
|
73
|
-
}, 60 * 1000); // every 60s
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// -------------------- Session state persistence --------------------
|
|
77
|
-
_loadSessionState() {
|
|
78
|
-
try {
|
|
79
|
-
if (fs.existsSync(this._sessionStateFile)) {
|
|
80
|
-
const raw = fs.readFileSync(this._sessionStateFile, 'utf8');
|
|
81
|
-
return JSON.parse(raw || '{}');
|
|
82
|
-
}
|
|
83
|
-
} catch (e) {
|
|
84
|
-
// ignore
|
|
85
|
-
}
|
|
86
|
-
return {};
|
|
87
|
-
}
|
|
88
|
-
_saveSessionState() {
|
|
89
|
-
try {
|
|
90
|
-
fs.writeFileSync(this._sessionStateFile, JSON.stringify(this._sessionState || {}, null, 2), 'utf8');
|
|
91
|
-
} catch (e) {
|
|
92
|
-
this.realtimeDebug('Failed to save session state:', e?.message || e);
|
|
93
|
-
}
|
|
94
49
|
}
|
|
95
50
|
|
|
96
|
-
// ------------------------------------------------------------------
|
|
97
|
-
|
|
98
51
|
/**
|
|
99
52
|
* Start Real-Time Listener with Auto-Inbox Fetch + MQTT
|
|
100
53
|
*/
|
|
101
54
|
async startRealTimeListener(options = {}) {
|
|
102
55
|
try {
|
|
103
56
|
console.log('[REALTIME] Starting Real-Time Listener...');
|
|
104
|
-
|
|
57
|
+
|
|
105
58
|
console.log('[REALTIME] Fetching inbox (IRIS data)...');
|
|
106
59
|
const inboxData = await this.ig.direct.getInbox();
|
|
107
|
-
|
|
60
|
+
|
|
108
61
|
console.log('[REALTIME] Connecting to MQTT with IRIS subscription...');
|
|
109
62
|
await this.connect({
|
|
110
63
|
graphQlSubs: [
|
|
@@ -117,15 +70,15 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
117
70
|
],
|
|
118
71
|
irisData: inboxData
|
|
119
72
|
});
|
|
120
|
-
|
|
73
|
+
|
|
121
74
|
console.log('[REALTIME] MQTT Connected with IRIS');
|
|
122
75
|
console.log('----------------------------------------');
|
|
123
76
|
console.log('[REALTIME] Real-Time Listener ACTIVE');
|
|
124
77
|
console.log('[REALTIME] Waiting for messages...');
|
|
125
78
|
console.log('----------------------------------------');
|
|
126
|
-
|
|
79
|
+
|
|
127
80
|
this._setupMessageHandlers();
|
|
128
|
-
|
|
81
|
+
|
|
129
82
|
return { success: true };
|
|
130
83
|
} catch (error) {
|
|
131
84
|
console.error('[REALTIME] Failed:', error.message);
|
|
@@ -137,92 +90,46 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
137
90
|
* Setup automatic message handlers
|
|
138
91
|
*/
|
|
139
92
|
_setupMessageHandlers() {
|
|
140
|
-
// handle raw mqtt 'message' events (parsed by this._parseMessage)
|
|
141
93
|
this.on('message', (data) => {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.emit('message_raw', parsed._rawMessage);
|
|
146
|
-
}
|
|
147
|
-
// normalize and emit only if it's a real message
|
|
148
|
-
const normalized = this._normalizeToYowsupLike(parsed);
|
|
149
|
-
if (normalized) {
|
|
150
|
-
this.metrics.messagesReceived++;
|
|
151
|
-
// emit cleaned payload as the primary "live" message (Yowsup-like)
|
|
152
|
-
this.emit('message_live', normalized);
|
|
153
|
-
} else {
|
|
154
|
-
// not a real message -> no message_live; keep raw available
|
|
94
|
+
const msg = this._parseMessage(data);
|
|
95
|
+
if (msg) {
|
|
96
|
+
this.emit('message_live', msg);
|
|
155
97
|
}
|
|
156
98
|
});
|
|
157
99
|
|
|
158
|
-
// handle iris events (graph/iris)
|
|
159
100
|
this.on('iris', (data) => {
|
|
160
|
-
const
|
|
161
|
-
if (
|
|
162
|
-
this.emit('
|
|
163
|
-
}
|
|
164
|
-
const normalized = this._normalizeToYowsupLike(parsed);
|
|
165
|
-
if (normalized) {
|
|
166
|
-
this.metrics.messagesReceived++;
|
|
167
|
-
this.emit('message_live', normalized);
|
|
168
|
-
} else {
|
|
169
|
-
// not a message event
|
|
101
|
+
const msg = this._parseIrisMessage(data);
|
|
102
|
+
if (msg) {
|
|
103
|
+
this.emit('message_live', msg);
|
|
170
104
|
}
|
|
171
105
|
});
|
|
172
106
|
}
|
|
173
107
|
|
|
174
108
|
/**
|
|
175
109
|
* Parse direct message
|
|
176
|
-
* Returns an object that always contains the original raw as _rawMessage when available
|
|
177
110
|
*/
|
|
178
111
|
_parseMessage(data) {
|
|
179
112
|
try {
|
|
180
113
|
const msg = data.message;
|
|
181
114
|
if (!msg) return null;
|
|
182
|
-
|
|
115
|
+
|
|
183
116
|
if (data.parsed) {
|
|
184
|
-
|
|
185
|
-
const base = Object.assign({}, data.parsed);
|
|
186
|
-
base.mqttString = `Mqtt client: [${base.username || (base.userId ? 'user_' + base.userId : 'unknown')}] ${base.text || ''}`;
|
|
187
|
-
base.mqtt = {
|
|
188
|
-
from: base.username || base.userId,
|
|
189
|
-
body: base.text || '',
|
|
190
|
-
timestamp: base.timestamp || Date.now()
|
|
191
|
-
};
|
|
192
|
-
base.status = 'received'; // changed from 'good'
|
|
193
|
-
base._rawMessage = msg;
|
|
194
|
-
return base;
|
|
117
|
+
return data.parsed;
|
|
195
118
|
}
|
|
196
|
-
|
|
119
|
+
|
|
197
120
|
const threadInfo = this.threads.get(msg.thread_id);
|
|
198
|
-
|
|
199
|
-
const text = msg.text || msg.body || '';
|
|
200
|
-
|
|
201
|
-
// Provide mqtt-like representations
|
|
202
|
-
const mqttString = `Mqtt client: [${username}] ${text}`;
|
|
203
|
-
const mqtt = {
|
|
204
|
-
from: username,
|
|
205
|
-
body: text,
|
|
206
|
-
timestamp: msg.timestamp || Date.now()
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const out = {
|
|
121
|
+
return {
|
|
210
122
|
id: msg.item_id || msg.id,
|
|
211
123
|
userId: msg.user_id || msg.from_user_id,
|
|
212
|
-
username
|
|
213
|
-
text,
|
|
124
|
+
username: msg.username || msg.from_username || `user_${msg.user_id || 'unknown'}`,
|
|
125
|
+
text: msg.text || msg.body || '',
|
|
214
126
|
itemType: msg.item_type || 'text',
|
|
215
127
|
thread: threadInfo?.title || `Thread ${msg.thread_id}`,
|
|
216
128
|
thread_id: msg.thread_id,
|
|
217
129
|
timestamp: msg.timestamp,
|
|
218
130
|
isGroup: threadInfo?.isGroup,
|
|
219
|
-
status: '
|
|
220
|
-
mqttString,
|
|
221
|
-
mqtt,
|
|
222
|
-
_rawMessage: msg
|
|
131
|
+
status: 'good'
|
|
223
132
|
};
|
|
224
|
-
|
|
225
|
-
return out;
|
|
226
133
|
} catch (e) {
|
|
227
134
|
return null;
|
|
228
135
|
}
|
|
@@ -234,125 +141,22 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
234
141
|
_parseIrisMessage(data) {
|
|
235
142
|
try {
|
|
236
143
|
if (data.event !== 'message_create' && !data.type?.includes('message')) {
|
|
237
|
-
|
|
238
|
-
return { _rawIris: data };
|
|
144
|
+
return null;
|
|
239
145
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const text = data.text || '';
|
|
243
|
-
|
|
244
|
-
const mqttString = `Mqtt client: [${username}] ${text}`;
|
|
245
|
-
const mqtt = {
|
|
246
|
-
from: username,
|
|
247
|
-
body: text,
|
|
248
|
-
timestamp: data.timestamp || Date.now()
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const out = {
|
|
146
|
+
|
|
147
|
+
return {
|
|
252
148
|
id: data.item_id || data.id,
|
|
253
149
|
userId: data.user_id || data.from_user_id,
|
|
254
|
-
username
|
|
255
|
-
text,
|
|
150
|
+
username: data.username || data.from_username || `user_${data.user_id || 'unknown'}`,
|
|
151
|
+
text: data.text || '',
|
|
256
152
|
itemType: data.item_type || 'text',
|
|
257
153
|
thread_id: data.thread_id,
|
|
258
154
|
timestamp: data.timestamp,
|
|
259
|
-
status: '
|
|
260
|
-
mqttString,
|
|
261
|
-
mqtt,
|
|
262
|
-
_rawIris: data
|
|
155
|
+
status: 'good'
|
|
263
156
|
};
|
|
264
|
-
|
|
265
|
-
return out;
|
|
266
157
|
} catch (e) {
|
|
267
|
-
return { _rawIris: data };
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* NORMALIZE to Yowsup-like full payload (but branded "Mqtt client")
|
|
273
|
-
* Returns normalized object or null if the parsed input is NOT a real message
|
|
274
|
-
*
|
|
275
|
-
* Normalized schema (Yowsup-like full payload):
|
|
276
|
-
* {
|
|
277
|
-
* client: 'Mqtt client',
|
|
278
|
-
* from: 'username',
|
|
279
|
-
* fromId: 'numeric id',
|
|
280
|
-
* body: 'text or description',
|
|
281
|
-
* timestamp: 1700000000000,
|
|
282
|
-
* type: 'text'|'image'|'video'|'voice'|'sticker'|'unknown',
|
|
283
|
-
* thread_id: '...',
|
|
284
|
-
* id: 'message id',
|
|
285
|
-
* raw: { original raw object }
|
|
286
|
-
* }
|
|
287
|
-
*/
|
|
288
|
-
_normalizeToYowsupLike(parsed) {
|
|
289
|
-
if (!parsed) return null;
|
|
290
|
-
|
|
291
|
-
// get raw object for detection
|
|
292
|
-
const raw = parsed._rawMessage || parsed._rawIris || parsed;
|
|
293
|
-
|
|
294
|
-
// Heuristics to decide if this is a "real message" (text/media/voice)
|
|
295
|
-
// Accept if:
|
|
296
|
-
// - parsed.text or parsed.body present and non-empty
|
|
297
|
-
// - or raw contains visual_media/media fields
|
|
298
|
-
// - or itemType indicates media
|
|
299
|
-
const hasText = Boolean(parsed.text && String(parsed.text).trim().length > 0);
|
|
300
|
-
const hasBodyField = Boolean(parsed.body && String(parsed.body).trim().length > 0);
|
|
301
|
-
const hasMediaField = Boolean(
|
|
302
|
-
(raw && (raw.visual_media || raw.media || raw.media_share || raw.items || raw.has_media)) ||
|
|
303
|
-
(parsed.itemType && String(parsed.itemType).toLowerCase() !== 'text')
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
if (!hasText && !hasBodyField && !hasMediaField) {
|
|
307
|
-
// Not a message that Yowsup would surface — drop from message_live
|
|
308
158
|
return null;
|
|
309
159
|
}
|
|
310
|
-
|
|
311
|
-
// Determine type
|
|
312
|
-
let type = 'text';
|
|
313
|
-
try {
|
|
314
|
-
if (hasMediaField) {
|
|
315
|
-
// try to deduce image/video/voice from raw fields if possible
|
|
316
|
-
const rl = raw || {};
|
|
317
|
-
if (rl.item_type && String(rl.item_type).toLowerCase().includes('video')) type = 'video';
|
|
318
|
-
else if (rl.item_type && String(rl.item_type).toLowerCase().includes('voice')) type = 'voice';
|
|
319
|
-
else if (rl.media && Array.isArray(rl.media)) {
|
|
320
|
-
// naive check for media types in array
|
|
321
|
-
const m0 = rl.media[0] || {};
|
|
322
|
-
if (m0.media_type === 2 || String(m0.media_type) === '2') type = 'video';
|
|
323
|
-
else type = 'image';
|
|
324
|
-
} else if (rl.visual_media) {
|
|
325
|
-
type = 'image';
|
|
326
|
-
} else {
|
|
327
|
-
type = 'unknown';
|
|
328
|
-
}
|
|
329
|
-
} else {
|
|
330
|
-
type = 'text';
|
|
331
|
-
}
|
|
332
|
-
} catch (e) {
|
|
333
|
-
type = 'unknown';
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// body selection
|
|
337
|
-
const body = hasText ? String(parsed.text).trim() : (hasBodyField ? String(parsed.body).trim() : (type !== 'text' ? `[${type.toUpperCase()}]` : ''));
|
|
338
|
-
|
|
339
|
-
// timestamp fallback
|
|
340
|
-
const ts = parsed.timestamp ? Number(parsed.timestamp) : (Date.now());
|
|
341
|
-
|
|
342
|
-
// Build normalized object (Yowsup-like full payload)
|
|
343
|
-
const normalized = {
|
|
344
|
-
client: 'Mqtt client',
|
|
345
|
-
from: parsed.username || parsed.from || (parsed.userId ? `user_${parsed.userId}` : 'unknown'),
|
|
346
|
-
fromId: String(parsed.userId || (raw && (raw.from_user_id || raw.user_id)) || 'unknown'),
|
|
347
|
-
body: body,
|
|
348
|
-
timestamp: ts,
|
|
349
|
-
type,
|
|
350
|
-
thread_id: parsed.thread_id || parsed.thread || (raw && (raw.thread_id || raw.thread)) || null,
|
|
351
|
-
id: parsed.id || (raw && (raw.item_id || raw.id)) || null,
|
|
352
|
-
raw: raw
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
return normalized;
|
|
356
160
|
}
|
|
357
161
|
|
|
358
162
|
setInitOptions(initOptions) {
|
|
@@ -426,11 +230,6 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
426
230
|
this.realtimeDebug(`SessionID generated (fallback): ${sessionid}`);
|
|
427
231
|
}
|
|
428
232
|
const password = `sessionid=${sessionid}`;
|
|
429
|
-
|
|
430
|
-
// Use persisted clientMqttSessionId if present (session continuity)
|
|
431
|
-
const persistedClientMqttSessionId = this._sessionState?.clientMqttSessionId ? BigInt(this._sessionState.clientMqttSessionId) : null;
|
|
432
|
-
const clientMqttSessionIdValue = persistedClientMqttSessionId ?? (BigInt(Date.now()) & BigInt(0xffffffff));
|
|
433
|
-
|
|
434
233
|
this.connection = new mqttot_1.MQTToTConnection({
|
|
435
234
|
clientIdentifier: deviceId.substring(0, 20),
|
|
436
235
|
clientInfo: {
|
|
@@ -445,7 +244,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
445
244
|
isInitiallyForeground: true,
|
|
446
245
|
networkType: 1,
|
|
447
246
|
networkSubtype: 0,
|
|
448
|
-
clientMqttSessionId:
|
|
247
|
+
clientMqttSessionId: BigInt(Date.now()) & BigInt(0xffffffff),
|
|
449
248
|
subscribeTopics: [88, 135, 149, 150, 133, 146],
|
|
450
249
|
clientType: 'cookie_auth',
|
|
451
250
|
appId: BigInt(567067343352427),
|
|
@@ -467,123 +266,50 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
467
266
|
'Accept-Language': this.ig.state.language.replace('_', '-'),
|
|
468
267
|
},
|
|
469
268
|
});
|
|
470
|
-
|
|
471
|
-
// update persisted state with chosen session id
|
|
472
|
-
try {
|
|
473
|
-
this._sessionState = this._sessionState || {};
|
|
474
|
-
this._sessionState.clientMqttSessionId = String(clientMqttSessionIdValue);
|
|
475
|
-
this._saveSessionState();
|
|
476
|
-
} catch (e) {
|
|
477
|
-
// ignore
|
|
478
|
-
}
|
|
479
269
|
}
|
|
480
270
|
async connect(options) {
|
|
481
271
|
this.setInitOptions(options);
|
|
482
272
|
this.constructConnection();
|
|
483
273
|
const { MQTToTClient } = require("../mqttot");
|
|
484
274
|
const { compressDeflate } = require("../shared");
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
return await compressDeflate(this.connection.toThrift());
|
|
498
|
-
},
|
|
499
|
-
autoReconnect: true,
|
|
500
|
-
requirePayload: true,
|
|
501
|
-
// pass through any socks/proxy options if provided
|
|
502
|
-
socksOptions: this.initOptions?.socksOptions,
|
|
503
|
-
additionalOptions: this.initOptions?.additionalOptions
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
await this._mqtt.connect();
|
|
507
|
-
|
|
508
|
-
// mark metrics
|
|
509
|
-
this.metrics.reconnectCount++;
|
|
510
|
-
this.metrics.lastConnectTime = Date.now();
|
|
511
|
-
this.metrics.backoffAttempts += (attempt - 1);
|
|
512
|
-
|
|
513
|
-
// If we reach here, connected successfully
|
|
514
|
-
break;
|
|
515
|
-
} catch (err) {
|
|
516
|
-
lastError = err;
|
|
517
|
-
this.realtimeDebug(`MQTT connect attempt ${attempt} failed: ${err?.message || err}`);
|
|
518
|
-
|
|
519
|
-
// exponential backoff with jitter
|
|
520
|
-
const base = 1000; // 1s
|
|
521
|
-
const delayMs = Math.min(30000, base * Math.pow(2, attempt - 1)) + Math.floor(Math.random() * 250);
|
|
522
|
-
this.realtimeDebug(`Waiting ${delayMs}ms before next connect attempt...`);
|
|
523
|
-
await (0, shared_1.delay)(delayMs);
|
|
524
|
-
|
|
525
|
-
// cooldown behavior: if too many attempts, wait longer
|
|
526
|
-
if (attempt >= maxAttempts) {
|
|
527
|
-
this.realtimeDebug('Max connect attempts reached, giving up for now.');
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
if (!this._mqtt) {
|
|
532
|
-
// fail with the last error
|
|
533
|
-
throw lastError || new Error('Failed to create MQTT client');
|
|
534
|
-
}
|
|
535
|
-
// ----------------------------------------------------------------------------
|
|
536
|
-
|
|
275
|
+
|
|
276
|
+
this._mqtt = new MQTToTClient({
|
|
277
|
+
url: 'edge-mqtt.facebook.com',
|
|
278
|
+
payloadProvider: async () => {
|
|
279
|
+
return await compressDeflate(this.connection.toThrift());
|
|
280
|
+
},
|
|
281
|
+
autoReconnect: true,
|
|
282
|
+
requirePayload: true,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
await this._mqtt.connect();
|
|
286
|
+
|
|
537
287
|
this.commands = new commands_1.Commands(this._mqtt);
|
|
538
|
-
|
|
288
|
+
|
|
539
289
|
this.emit('connected');
|
|
540
|
-
|
|
541
|
-
// Enhanced message handling: try tolerant parse, log unknown fields, fallback raw
|
|
290
|
+
|
|
542
291
|
this._mqtt.on('message', async (msg) => {
|
|
543
292
|
const topicMap = this.mqtt?.topicMap;
|
|
544
293
|
const topic = topicMap?.get(msg.topic);
|
|
545
|
-
|
|
294
|
+
|
|
546
295
|
if (topic && topic.parser && !topic.noParse) {
|
|
547
296
|
try {
|
|
548
297
|
const unzipped = await (0, shared_1.tryUnzipAsync)(msg.payload);
|
|
549
|
-
|
|
550
|
-
try {
|
|
551
|
-
parsedMessages = topic.parser.parseMessage(topic, unzipped);
|
|
552
|
-
} catch (parseErr) {
|
|
553
|
-
// Tolerant parsing: log and attempt safe fallback
|
|
554
|
-
this.metrics.parseErrors++;
|
|
555
|
-
this.realtimeDebug('Parse error (topic parser) - attempting tolerant fallback:', parseErr?.message || parseErr);
|
|
556
|
-
|
|
557
|
-
// Attempt to decode as JSON if possible (some thrift->json conversions may be present in tooling)
|
|
558
|
-
try {
|
|
559
|
-
const s = unzipped.toString('utf8');
|
|
560
|
-
parsedMessages = JSON.parse(s);
|
|
561
|
-
this.realtimeDebug('Fallback parsed as JSON.');
|
|
562
|
-
} catch (jsonErr) {
|
|
563
|
-
// Last resort: emit raw payload (preserve for debugging)
|
|
564
|
-
this.realtimeDebug('Fallback JSON parse failed; emitting raw payload.');
|
|
565
|
-
this.emit('receiveRaw', { topic: msg.topic, payload: unzipped, original: msg });
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
|
|
298
|
+
const parsedMessages = topic.parser.parseMessage(topic, unzipped);
|
|
570
299
|
this.emit('receive', topic, Array.isArray(parsedMessages) ? parsedMessages : [parsedMessages]);
|
|
571
|
-
} catch
|
|
572
|
-
// Silent parse error
|
|
573
|
-
this.metrics.parseErrors++;
|
|
574
|
-
this.realtimeDebug('Silent parse/unzip error in message handler:', e?.message || e);
|
|
300
|
+
} catch(e) {
|
|
301
|
+
// Silent parse error
|
|
575
302
|
}
|
|
576
303
|
} else {
|
|
577
304
|
try {
|
|
578
305
|
await (0, shared_1.tryUnzipAsync)(msg.payload);
|
|
579
306
|
this.emit('receiveRaw', msg);
|
|
580
|
-
} catch
|
|
307
|
+
} catch(e) {
|
|
581
308
|
// Silent decompress error
|
|
582
309
|
}
|
|
583
310
|
}
|
|
584
311
|
});
|
|
585
312
|
this._mqtt.on('error', this.emitError);
|
|
586
|
-
|
|
587
313
|
await (0, shared_1.delay)(100);
|
|
588
314
|
if (this.initOptions.graphQlSubs && this.initOptions.graphQlSubs.length > 0) {
|
|
589
315
|
await this.graphQlSubscribe(this.initOptions.graphQlSubs);
|
|
@@ -614,23 +340,12 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
614
340
|
url: '/api/v1/direct_v2/threads/get_most_recent_message/',
|
|
615
341
|
method: 'POST',
|
|
616
342
|
});
|
|
617
|
-
} catch
|
|
343
|
+
} catch(e) {
|
|
618
344
|
// Silent force fetch error
|
|
619
345
|
}
|
|
620
346
|
} catch (error) {
|
|
621
347
|
// Silent inbox fetch error - MQTT still listening
|
|
622
348
|
}
|
|
623
|
-
|
|
624
|
-
// Persist last used clientMqttSessionId and optionally last seq_id if present in initOptions
|
|
625
|
-
try {
|
|
626
|
-
const clientMqttSessionId = this.connection?.toThrift()?.clientInfo?.clientMqttSessionId || this._sessionState?.clientMqttSessionId;
|
|
627
|
-
if (clientMqttSessionId) {
|
|
628
|
-
this._sessionState = this._sessionState || {};
|
|
629
|
-
this._sessionState.clientMqttSessionId = String(clientMqttSessionId);
|
|
630
|
-
this._saveSessionState();
|
|
631
|
-
}
|
|
632
|
-
} catch (e) { /* ignore */ }
|
|
633
|
-
|
|
634
349
|
this._setupMessageHandlers();
|
|
635
350
|
}
|
|
636
351
|
/**
|
|
@@ -646,7 +361,7 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
646
361
|
console.log('[RealtimeClient] Connecting from saved session...');
|
|
647
362
|
|
|
648
363
|
const savedOptions = authStateHelper.getMqttConnectOptions?.();
|
|
649
|
-
|
|
364
|
+
|
|
650
365
|
const connectOptions = {
|
|
651
366
|
graphQlSubs: options.graphQlSubs || savedOptions?.graphQlSubs || ['ig_sub_direct', 'ig_sub_direct_v2_message_sync'],
|
|
652
367
|
skywalkerSubs: options.skywalkerSubs || savedOptions?.skywalkerSubs || ['presence_subscribe', 'typing_subscribe'],
|
|
@@ -660,12 +375,8 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
660
375
|
hasIrisData: !!connectOptions.irisData
|
|
661
376
|
});
|
|
662
377
|
|
|
663
|
-
// merge any persisted mqtt connect options into initOptions for continuity
|
|
664
|
-
this.initOptions = { ...(this.initOptions || {}), ...(savedOptions || {}) };
|
|
665
|
-
|
|
666
378
|
await this.connect(connectOptions);
|
|
667
379
|
|
|
668
|
-
// Save session state into auth helper if supported
|
|
669
380
|
if (authStateHelper.saveMqttSession) {
|
|
670
381
|
try {
|
|
671
382
|
await authStateHelper.saveMqttSession(this);
|
|
@@ -675,11 +386,6 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
675
386
|
}
|
|
676
387
|
}
|
|
677
388
|
|
|
678
|
-
// Also persist locally
|
|
679
|
-
try {
|
|
680
|
-
if (this._sessionState) this._saveSessionState();
|
|
681
|
-
} catch (e) { }
|
|
682
|
-
|
|
683
389
|
return this;
|
|
684
390
|
}
|
|
685
391
|
/**
|
|
@@ -689,21 +395,13 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
689
395
|
async saveSession(authStateHelper) {
|
|
690
396
|
if (!authStateHelper || !authStateHelper.saveMqttSession) {
|
|
691
397
|
console.warn('[RealtimeClient] No authStateHelper provided');
|
|
692
|
-
// still save local state
|
|
693
|
-
try { this._saveSessionState(); } catch (e) { }
|
|
694
398
|
return false;
|
|
695
399
|
}
|
|
696
400
|
await authStateHelper.saveMqttSession(this);
|
|
697
|
-
// also persist locally
|
|
698
|
-
try { this._saveSessionState(); } catch (e) { }
|
|
699
401
|
return true;
|
|
700
402
|
}
|
|
701
403
|
disconnect() {
|
|
702
404
|
this.safeDisconnect = true;
|
|
703
|
-
// clear metrics interval when shutting down gracefully
|
|
704
|
-
try {
|
|
705
|
-
if (this._metricsInterval) clearInterval(this._metricsInterval);
|
|
706
|
-
} catch (e) { }
|
|
707
405
|
return this.mqtt?.disconnect() ?? Promise.resolve();
|
|
708
406
|
}
|
|
709
407
|
graphQlSubscribe(sub) {
|
|
@@ -737,13 +435,6 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
737
435
|
throw new mqtts_1.IllegalStateError('connect() must be called before irisSubscribe()');
|
|
738
436
|
}
|
|
739
437
|
this.realtimeDebug(`Iris Sub to: seqId: ${seq_id}, snapshot: ${snapshot_at_ms}`);
|
|
740
|
-
// persist last seq_id for continuity
|
|
741
|
-
try {
|
|
742
|
-
this._sessionState = this._sessionState || {};
|
|
743
|
-
if (seq_id) this._sessionState.last_seq_id = seq_id;
|
|
744
|
-
if (snapshot_at_ms) this._sessionState.last_snapshot_at_ms = snapshot_at_ms;
|
|
745
|
-
this._saveSessionState();
|
|
746
|
-
} catch (e) { /* ignore */ }
|
|
747
438
|
return this.commands.updateSubscriptions({
|
|
748
439
|
topic: constants_1.Topics.IRIS_SUB,
|
|
749
440
|
data: {
|
|
@@ -753,68 +444,6 @@ class RealtimeClient extends eventemitter3_1.EventEmitter {
|
|
|
753
444
|
},
|
|
754
445
|
});
|
|
755
446
|
}
|
|
756
|
-
|
|
757
|
-
// ---------------- Dynamic subscription helpers ----------------
|
|
758
|
-
async addGraphQlSub(sub) {
|
|
759
|
-
try {
|
|
760
|
-
const current = this.initOptions.graphQlSubs || [];
|
|
761
|
-
if (!current.includes(sub)) {
|
|
762
|
-
current.push(sub);
|
|
763
|
-
this.initOptions.graphQlSubs = current;
|
|
764
|
-
if (this.commands) {
|
|
765
|
-
await this.graphQlSubscribe(current);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
return true;
|
|
769
|
-
} catch (e) {
|
|
770
|
-
this.realtimeDebug('addGraphQlSub error:', e?.message || e);
|
|
771
|
-
return false;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
async removeGraphQlSub(sub) {
|
|
775
|
-
try {
|
|
776
|
-
const current = (this.initOptions.graphQlSubs || []).filter(s => s !== sub);
|
|
777
|
-
this.initOptions.graphQlSubs = current;
|
|
778
|
-
if (this.commands) {
|
|
779
|
-
await this.graphQlSubscribe(current);
|
|
780
|
-
}
|
|
781
|
-
return true;
|
|
782
|
-
} catch (e) {
|
|
783
|
-
this.realtimeDebug('removeGraphQlSub error:', e?.message || e);
|
|
784
|
-
return false;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
async addSkywalkerSub(sub) {
|
|
788
|
-
try {
|
|
789
|
-
const current = this.initOptions.skywalkerSubs || [];
|
|
790
|
-
if (!current.includes(sub)) {
|
|
791
|
-
current.push(sub);
|
|
792
|
-
this.initOptions.skywalkerSubs = current;
|
|
793
|
-
if (this.commands) {
|
|
794
|
-
await this.skywalkerSubscribe(current);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
return true;
|
|
798
|
-
} catch (e) {
|
|
799
|
-
this.realtimeDebug('addSkywalkerSub error:', e?.message || e);
|
|
800
|
-
return false;
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
async removeSkywalkerSub(sub) {
|
|
804
|
-
try {
|
|
805
|
-
const current = (this.initOptions.skywalkerSubs || []).filter(s => s !== sub);
|
|
806
|
-
this.initOptions.skywalkerSubs = current;
|
|
807
|
-
if (this.commands) {
|
|
808
|
-
await this.skywalkerSubscribe(current);
|
|
809
|
-
}
|
|
810
|
-
return true;
|
|
811
|
-
} catch (e) {
|
|
812
|
-
this.realtimeDebug('removeSkywalkerSub error:', e?.message || e);
|
|
813
|
-
return false;
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
// ----------------------------------------------------------------
|
|
817
|
-
|
|
818
447
|
}
|
|
819
448
|
exports.RealtimeClient = RealtimeClient;
|
|
820
449
|
//# sourceMappingURL=realtime.client.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-insta-private-api-mqtt",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
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": {
|