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.
|
@@ -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) {
|