nodejs-insta-private-api-mqt 1.3.80 → 1.3.81

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.
@@ -168,10 +168,7 @@ class State {
168
168
 
169
169
  extractCookieValue(key) {
170
170
  const cookie = this.extractCookie(key);
171
- if (!cookie) {
172
- throw new Error(`Could not find cookie: ${key}`);
173
- }
174
- return cookie.value;
171
+ return cookie ? cookie.value : null;
175
172
  }
176
173
 
177
174
  get cookieCsrfToken() {
@@ -310,7 +307,8 @@ class State {
310
307
  connectionTypeHeader: this.connectionTypeHeader,
311
308
  capabilitiesHeader: this.capabilitiesHeader,
312
309
  requestId: this.requestId,
313
- traySessionId: this.traySessionId
310
+ traySessionId: this.traySessionId,
311
+ userId: this._userId || null
314
312
  };
315
313
  return obj;
316
314
  }
@@ -357,6 +355,11 @@ class State {
357
355
  }
358
356
  }
359
357
 
358
+ // If serialized state carried a raw userId, restore it into the internal field
359
+ if (obj.userId && !this._userId) {
360
+ this._userId = obj.userId;
361
+ }
362
+
360
363
  // refresh parsed authorization (if any)
361
364
  this.updateAuthorization();
362
365
  }
@@ -25,6 +25,329 @@ class AccountRepository extends Repository {
25
25
  }
26
26
  }
27
27
 
28
+ /**
29
+ * Helper: try to extract an IGT bearer token from various sources:
30
+ * - response.headers (IG-Set-Authorization or ig-set-authorization)
31
+ * - response.body top-level fields
32
+ * - body.layout (stringified layout)
33
+ * - any embedded login_response.headers JSON inside layout or body
34
+ *
35
+ * When found, sets this.client.state.authorization and calls updateAuthorization().
36
+ */
37
+ _extractAndSaveAuthorization(response) {
38
+ if (!response) return null;
39
+
40
+ // Normalize headers (case-insensitive)
41
+ const headers = {};
42
+ if (response.headers && typeof response.headers === 'object') {
43
+ for (const k of Object.keys(response.headers)) {
44
+ headers[k.toLowerCase()] = response.headers[k];
45
+ }
46
+ }
47
+
48
+ // 1) Check response headers first (server may set IG-Set-Authorization)
49
+ const headerCandidates = [
50
+ headers['ig-set-authorization'],
51
+ headers['ig-set-authorization'.toLowerCase()],
52
+ headers['ig-set-authorization'.toUpperCase()],
53
+ headers['ig-set-authorization'],
54
+ headers['ig-set-authorization'],
55
+ headers['ig-set-authorization'],
56
+ headers['ig-set-authorization'],
57
+ headers['ig-set-authorization'],
58
+ headers['ig-set-authorization'],
59
+ headers['ig-set-authorization'],
60
+ headers['ig-set-authorization'],
61
+ headers['ig-set-authorization'],
62
+ headers['ig-set-authorization'],
63
+ headers['ig-set-authorization'],
64
+ headers['ig-set-authorization'],
65
+ headers['ig-set-authorization'],
66
+ headers['ig-set-authorization'],
67
+ headers['ig-set-authorization'],
68
+ headers['ig-set-authorization'],
69
+ headers['ig-set-authorization'],
70
+ headers['ig-set-authorization'],
71
+ headers['ig-set-authorization'],
72
+ headers['ig-set-authorization'],
73
+ headers['ig-set-authorization'],
74
+ headers['ig-set-authorization'],
75
+ headers['ig-set-authorization'],
76
+ headers['ig-set-authorization'],
77
+ headers['ig-set-authorization'],
78
+ headers['ig-set-authorization'],
79
+ headers['ig-set-authorization'],
80
+ headers['ig-set-authorization'],
81
+ headers['ig-set-authorization'],
82
+ headers['ig-set-authorization'],
83
+ headers['ig-set-authorization'],
84
+ headers['ig-set-authorization'],
85
+ headers['ig-set-authorization'],
86
+ headers['ig-set-authorization'],
87
+ headers['ig-set-authorization'],
88
+ headers['ig-set-authorization'],
89
+ headers['ig-set-authorization'],
90
+ ]; // (we'll just check the normalized header below)
91
+
92
+ // Simpler: look for any header that contains 'IG-Set-Authorization' or 'ig-set-authorization'
93
+ for (const key of Object.keys(headers)) {
94
+ const val = headers[key];
95
+ if (!val) continue;
96
+ if (key.includes('ig-set-authorization') || key === 'ig-set-authorization' || key === 'ig-set-authorization'.toLowerCase()) {
97
+ const token = this._normalizeTokenString(val);
98
+ if (token) {
99
+ this.client.state.authorization = token;
100
+ try { this.client.state.updateAuthorization(); } catch (e) {}
101
+ return token;
102
+ }
103
+ }
104
+ // Some servers include the header value inside a JSON-like string in other headers
105
+ if (typeof val === 'string' && val.includes('Bearer IGT:2:')) {
106
+ const token = this._findBearerInString(val);
107
+ if (token) {
108
+ this.client.state.authorization = token;
109
+ try { this.client.state.updateAuthorization(); } catch (e) {}
110
+ return token;
111
+ }
112
+ }
113
+ }
114
+
115
+ // Extract IG-U headers (user id, region, claim) when present
116
+ const dsUserIdHeader =
117
+ headers['ig-set-ig-u-ds-user-id'] ||
118
+ headers['ig-u-ds-user-id'] ||
119
+ headers['x-ig-set-ig-u-ds-user-id'];
120
+ if (dsUserIdHeader) {
121
+ const m = String(dsUserIdHeader).match(/(\d{3,})/);
122
+ if (m) {
123
+ const uid = m[1];
124
+ this.client.state.cookieUserId = uid;
125
+ this.client.state._userId = uid;
126
+ }
127
+ }
128
+
129
+ const rurHeader =
130
+ headers['ig-set-ig-u-rur'] ||
131
+ headers['ig-u-rur'] ||
132
+ headers['x-ig-set-ig-u-rur'];
133
+ if (rurHeader) {
134
+ const rur = String(rurHeader).trim().replace(/^"|"$/g, '');
135
+ if (rur) {
136
+ this.client.state.igURur = rur;
137
+ }
138
+ }
139
+
140
+ const wwwClaimHeader =
141
+ headers['x-ig-set-www-claim'] ||
142
+ headers['ig-set-www-claim'] ||
143
+ headers['ig-u-www-claim'];
144
+ if (wwwClaimHeader) {
145
+ const claim = String(wwwClaimHeader).trim().replace(/^"|"$/g, '');
146
+ if (claim) {
147
+ this.client.state.igWWWClaim = claim;
148
+ }
149
+ }
150
+
151
+ const midHeader =
152
+ headers['ig-set-x-mid'] ||
153
+ headers['x-mid'] ||
154
+ headers['ig-set-mid'];
155
+ if (midHeader) {
156
+ const mid = String(midHeader).trim().replace(/^"|"$/g, '');
157
+ if (mid) {
158
+ this.client.state.mid = mid;
159
+ }
160
+ }
161
+
162
+ // 2) Check response.body for common fields
163
+ const body = response.body;
164
+ if (body) {
165
+ // If body has headers string (some responses embed headers JSON as a string)
166
+ if (body.headers && typeof body.headers === 'string') {
167
+ const token = this._findBearerInString(body.headers);
168
+ if (token) {
169
+ this.client.state.authorization = token;
170
+ try { this.client.state.updateAuthorization(); } catch (e) {}
171
+ return token;
172
+ }
173
+ }
174
+
175
+ // If body contains a layout object or string, stringify and search it
176
+ try {
177
+ let layoutStr = null;
178
+ if (body.layout) {
179
+ layoutStr = typeof body.layout === 'string' ? body.layout : JSON.stringify(body.layout);
180
+ } else if (body?.layout?.bloks_payload) {
181
+ layoutStr = JSON.stringify(body.layout.bloks_payload);
182
+ } else if (body?.bloks_payload) {
183
+ layoutStr = JSON.stringify(body.bloks_payload);
184
+ }
185
+
186
+ if (!layoutStr && typeof body === 'string') {
187
+ layoutStr = body;
188
+ }
189
+
190
+ if (layoutStr) {
191
+ const token = this._findBearerInString(layoutStr);
192
+ if (token) {
193
+ this.client.state.authorization = token;
194
+ try { this.client.state.updateAuthorization(); } catch (e) {}
195
+ return token;
196
+ }
197
+
198
+ // Some responses embed a login_response JSON string inside layout; try to extract it
199
+ const loginResponseMatch = layoutStr.match(/"login_response"\s*:\s*"(\\?{.*?\\?})"/s);
200
+ if (loginResponseMatch) {
201
+ // Unescape JSON string
202
+ let jsonStr = loginResponseMatch[1];
203
+ try {
204
+ jsonStr = jsonStr.replace(/\\"/g, '"').replace(/\\n/g, '');
205
+ const parsed = JSON.parse(jsonStr);
206
+ if (parsed && parsed.headers) {
207
+ const token = this._findBearerInString(parsed.headers);
208
+ if (token) {
209
+ this.client.state.authorization = token;
210
+ try { this.client.state.updateAuthorization(); } catch (e) {}
211
+ return token;
212
+ }
213
+ }
214
+ } catch (e) {
215
+ // ignore parse errors
216
+ }
217
+ }
218
+
219
+ // Also try to find IG-Set-Password-Encryption headers (not token but useful)
220
+ const encKeyIdMatch = layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i) ||
221
+ layoutStr.match(/password.encryption.key.id[\\"\s:]+(\d+)/i);
222
+ if (encKeyIdMatch) {
223
+ this.client.state.passwordEncryptionKeyId = parseInt(encKeyIdMatch[1]);
224
+ }
225
+ const encPubKeyMatch = layoutStr.match(/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i) ||
226
+ layoutStr.match(/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i);
227
+ if (encPubKeyMatch) {
228
+ this.client.state.passwordEncryptionPubKey = encPubKeyMatch[1];
229
+ }
230
+
231
+ // Extract user/account identifiers from layout string
232
+ const dsIdMatch =
233
+ layoutStr.match(/ig-set-ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
234
+ layoutStr.match(/ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
235
+ layoutStr.match(/"strong_id__"\s*:\s*"(\d+)"/i) ||
236
+ layoutStr.match(/"pk_id"\s*:\s*"(\d+)"/i) ||
237
+ layoutStr.match(/"pk"\s*:\s*(\d+)/i);
238
+ if (dsIdMatch) {
239
+ const uid = String(dsIdMatch[1]);
240
+ this.client.state.cookieUserId = uid;
241
+ this.client.state._userId = uid;
242
+ }
243
+
244
+ const rurMatch =
245
+ layoutStr.match(/ig-set-ig-u-rur[\\"\s:]+"([^"\\]+)/i) ||
246
+ layoutStr.match(/ig-u-rur[\\"\s:]+"([^"\\]+)/i);
247
+ if (rurMatch) {
248
+ this.client.state.igURur = rurMatch[1];
249
+ }
250
+
251
+ const wwwClaimMatch = layoutStr.match(/x-ig-set-www-claim[\\"\s:]+"([^"\\]+)/i);
252
+ if (wwwClaimMatch) {
253
+ this.client.state.igWWWClaim = wwwClaimMatch[1];
254
+ }
255
+
256
+ const midMatch = layoutStr.match(/"mid"\s*:\s*"([^"\\]+)"/i);
257
+ if (midMatch) {
258
+ this.client.state.mid = midMatch[1];
259
+ }
260
+ }
261
+ } catch (e) {
262
+ // ignore
263
+ }
264
+
265
+ // 3) If body.logged_in_user exists and we have authorization already, return it
266
+ if (body.logged_in_user && this.client.state.authorization) {
267
+ return this.client.state.authorization;
268
+ }
269
+ }
270
+
271
+ return null;
272
+ }
273
+
274
+ // Normalize token string: accept either raw token or "Bearer IGT:2:..." and return trimmed token string
275
+ _normalizeTokenString(val) {
276
+ if (!val || typeof val !== 'string') return null;
277
+ // If header already contains "Bearer IGT:2:..."
278
+ const bearer = this._findBearerInString(val);
279
+ if (bearer) return bearer;
280
+ // If header contains JSON with IG-Set-Authorization field
281
+ try {
282
+ const maybeJson = JSON.parse(val);
283
+ if (maybeJson['IG-Set-Authorization']) {
284
+ return this._findBearerInString(maybeJson['IG-Set-Authorization']);
285
+ }
286
+ } catch (e) {
287
+ // not JSON
288
+ }
289
+ // fallback: return trimmed value
290
+ return val.trim();
291
+ }
292
+
293
+ // Find bearer token in arbitrary string using multiple patterns
294
+ _findBearerInString(str) {
295
+ if (!str || typeof str !== 'string') return null;
296
+ // Patterns to match the Bearer token in many possible encodings/escapes
297
+ const bearerPatterns = [
298
+ /Bearer IGT:2:[A-Za-z0-9+\/=]+/,
299
+ /Bearer\s+IGT:2:[A-Za-z0-9+\/=]+/,
300
+ /\\?"Bearer IGT:2:([A-Za-z0-9+\/=]+)\\?"/,
301
+ /ig-set-authorization[\\"\s:]*Bearer IGT:2:([A-Za-z0-9+\/=]+)/i,
302
+ /"IG-Set-Authorization"\s*:\s*"(Bearer IGT:2:[A-Za-z0-9+\/=]+)"/i,
303
+ /IG-Set-Authorization[\s:]+(Bearer IGT:2:[A-Za-z0-9+\/=]+)/i,
304
+ /Bearer IGT:2:([A-Za-z0-9+\/=]+)/,
305
+ ];
306
+
307
+ for (const pattern of bearerPatterns) {
308
+ const m = str.match(pattern);
309
+ if (m) {
310
+ // If capturing group present, prefer it
311
+ if (m[1]) {
312
+ const token = m[1].includes('Bearer IGT:2:') ? m[1] : `Bearer IGT:2:${m[1]}`;
313
+ return token.replace(/\\?"/g, '').trim();
314
+ }
315
+ // Otherwise m[0] contains the full token
316
+ return m[0].replace(/\\?"/g, '').trim();
317
+ }
318
+ }
319
+
320
+ return null;
321
+ }
322
+
323
+ // Ensure we have a valid csrftoken cookie (and mid) before sensitive endpoints.
324
+ // Instagram typically sets csrftoken via /api/v1/si/fetch_headers/.
325
+ async ensureCsrfToken() {
326
+ const cookieToken = this.client.state.cookieCsrfToken;
327
+ if (cookieToken && cookieToken !== 'missing' && cookieToken !== 'pending') {
328
+ try { this.client.state.csrfToken = cookieToken; } catch {}
329
+ return cookieToken;
330
+ }
331
+
332
+ // Warmup endpoint that returns/set cookies (csrftoken, mid, etc.)
333
+ try {
334
+ await this.client.request.send({
335
+ method: 'GET',
336
+ url: `/api/v1/si/fetch_headers/?challenge_type=signup&guid=${this.client.state.uuid}`,
337
+ });
338
+ } catch (e) {
339
+ // ignore warmup failures
340
+ }
341
+
342
+ const token = this.client.state.cookieCsrfToken;
343
+ if (token && token !== 'missing' && token !== 'pending') {
344
+ try { this.client.state.csrfToken = token; } catch {}
345
+ return token;
346
+ }
347
+ return null;
348
+ }
349
+
350
+
28
351
  async login(credentialsOrUsername, passwordArg) {
29
352
  let username, password;
30
353
  if (typeof credentialsOrUsername === 'object' && credentialsOrUsername !== null) {
@@ -44,7 +367,9 @@ class AccountRepository extends Repository {
44
367
 
45
368
  const { encrypted, time } = this.encryptPassword(password);
46
369
 
47
- return this.requestWithRetry(async () => {
370
+
371
+ const csrfToken = await this.ensureCsrfToken();
372
+ return this.requestWithRetry(async () => {
48
373
  const aacInitTimestamp = Math.floor(Date.now() / 1000) - Math.floor(Math.random() * 50);
49
374
  const aacjid = crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4();
50
375
  const aaccs = crypto.randomBytes(32).toString('base64url');
@@ -92,35 +417,65 @@ class AccountRepository extends Repository {
92
417
  const attestParams = AccountRepository.generateAttestParams(this.client.state);
93
418
 
94
419
  const bloksHeaders = {
420
+ ...(csrfToken ? { 'X-CSRFToken': csrfToken } : {}),
421
+ 'Accept-Language': `${(this.client.state.language || 'ro_RO').replace('_', '-')} , en-US`,
422
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
423
+
424
+ 'IG-Intended-User-Id': '0',
425
+
426
+ 'X-Bloks-Is-Layout-Rtl': 'false',
95
427
  'X-Bloks-Prism-Ax-Base-Colors-Enabled': 'false',
96
428
  'X-Bloks-Prism-Button-Version': 'CONTROL',
97
429
  'X-Bloks-Prism-Colors-Enabled': 'true',
98
430
  'X-Bloks-Prism-Font-Enabled': 'false',
99
431
  'X-Bloks-Prism-Indigo-Link-Version': '0',
432
+ 'X-Bloks-Version-Id': this.client.state.bloksVersionId,
433
+
434
+ 'X-FB-Client-Ip': 'True',
435
+ 'X-FB-Connection-Type': 'WIFI',
100
436
  'X-FB-Friendly-Name': 'IgApi: bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
437
+ 'X-FB-Network-Properties': 'VPN;Validated;LocalAddrs=/10.0.0.2,;',
101
438
  'X-FB-Request-Analytics-Tags': JSON.stringify({
102
439
  network_tags: {
103
- product: this.client.state.fbAnalyticsApplicationId,
440
+ product: String(this.client.state.fbAnalyticsApplicationId || ''),
104
441
  purpose: 'fetch',
105
442
  surface: 'undefined',
106
443
  request_category: 'api',
107
444
  retry_attempt: '0',
108
445
  },
109
446
  }),
110
- 'X-IG-Client-Endpoint': 'com.bloks.www.caa.login.aymh_single_profile_screen_entry',
111
- 'X-Tigon-Is-Retry': 'False',
447
+ 'X-FB-Server-Cluster': 'True',
112
448
  'X-FB-RMD': 'state=URL_ELIGIBLE',
113
- 'X-IG-Attest-Params': JSON.stringify(attestParams),
114
- 'X-FB-Connection-Type': 'MOBILE.LTE',
115
- 'X-FB-Network-Properties': 'VPN;Metered;Validated;LocalAddrs=/10.0.0.2,;',
116
449
  'X-FB-Conn-UUID-Client': crypto.randomBytes(16).toString('hex'),
450
+ 'X-FB-HTTP-Engine': 'MNS/TCP',
451
+
452
+ 'X-IG-Client-Endpoint': 'com.bloks.www.caa.login.aymh_single_profile_screen_entry',
453
+ 'X-IG-Connection-Type': 'WIFI',
454
+ 'X-IG-Capabilities': '3brTv10=',
455
+ 'X-IG-App-ID': String(this.client.state.fbAnalyticsApplicationId || ''),
456
+ 'X-IG-App-Locale': (this.client.state.language || 'ro_RO'),
457
+ 'X-IG-Device-Locale': (this.client.state.language || 'ro_RO'),
458
+ 'X-IG-Mapped-Locale': (this.client.state.language || 'ro_RO'),
459
+ 'X-IG-Android-Id': this.client.state.androidDeviceId || this.client.state.deviceId || '',
460
+ 'X-IG-Device-Id': this.client.state.deviceId,
461
+ 'X-IG-Family-Device-Id': this.client.state.phoneId,
462
+ 'X-IG-Timezone-Offset': String(typeof this.client.state.timezoneOffset === 'number' ? this.client.state.timezoneOffset : 7200),
463
+ 'X-IG-WWW-Claim': this.client.state.igWWWClaim || '0',
464
+ 'X-IG-Nav-Chain': 'LockoutFragment:dogfooding_lockout:1:cold_start:...caa.login.aymh_single_profile_screen_entry:9:button:0::0',
465
+
466
+ 'X-IG-Attest-Params': JSON.stringify(attestParams),
467
+
468
+ 'X-Mid': this.client.state.mid || '',
469
+ 'X-Pigeon-Rawclienttime': (Date.now() / 1000).toFixed(3),
470
+ 'X-Pigeon-Session-Id': this.client.state.pigeonSessionId || 'UFS-' + (crypto.randomBytes(16).toString('hex')),
471
+ 'X-Tigon-Is-Retry': 'False',
472
+
117
473
  'X-IG-Bandwidth-Speed-KBPS': (Math.random() * 1500 + 1500).toFixed(3),
118
474
  'X-IG-Bandwidth-TotalBytes-B': '0',
119
475
  'X-IG-Bandwidth-TotalTime-MS': '0',
120
- 'X-IG-Connection-Type': 'MOBILE(LTE)',
121
- 'X-IG-Capabilities': '3brTv10=',
476
+
122
477
  'Accept-Encoding': 'gzip, deflate',
123
- 'X-FB-HTTP-Engine': 'MNS/TCP',
478
+ 'User-Agent': this.client.state.userAgent || 'Instagram 371.0.0.0.23 Android (30/11; 320dpi; 720x1449; Xiaomi/Redmi; M2006C3MNG; angelican; mt6765; ro_RO; 703217507)',
124
479
  };
125
480
 
126
481
  const response = await this.client.request.send({
@@ -129,35 +484,65 @@ class AccountRepository extends Repository {
129
484
  form: { params: paramsJson },
130
485
  headers: bloksHeaders,
131
486
  });
487
+ // === DEBUG LOGIN: salvați răspunsul brut de la server pentru analiză ===
488
+ try {
489
+ const fs = require('fs');
490
+ const path = require('path');
491
+
492
+ const debugDir = path.join(process.cwd(), 'authinfo_instagram');
493
+ const debugFile = path.join(debugDir, 'login-debug.json');
494
+
495
+ // asigură-te că folderul există
496
+ try {
497
+ fs.mkdirSync(debugDir, { recursive: true });
498
+ } catch (e) {}
499
+
500
+ const debugPayload = {
501
+ at: new Date().toISOString(),
502
+ statusCode: response.statusCode || response.status || null,
503
+ headers: response.headers || null,
504
+ body: response.body || null,
505
+ };
506
+
507
+ fs.writeFileSync(debugFile, JSON.stringify(debugPayload, null, 2), 'utf8');
508
+ } catch (e) {
509
+ // nu stricăm login-ul dacă nu merge debug-ul
510
+ }
511
+ // === SFÂRȘIT DEBUG LOGIN ===
132
512
 
133
513
  const body = response.body;
134
514
 
135
- if (body.two_factor_required) {
515
+ // Immediately attempt to extract and save authorization token from the response
516
+ // This covers tokens returned in headers, in layout payloads, or embedded login_response headers.
517
+ this._extractAndSaveAuthorization(response);
518
+
519
+ if (body && body.two_factor_required) {
136
520
  const err = new Error('Two factor authentication required');
137
521
  err.name = 'IgLoginTwoFactorRequiredError';
138
522
  err.twoFactorInfo = body.two_factor_info;
139
523
  throw err;
140
524
  }
141
- if (body.error_type === 'bad_password') {
525
+ if (body && body.error_type === 'bad_password') {
142
526
  const err = new Error('Bad password');
143
527
  err.name = 'IgLoginBadPasswordError';
144
528
  throw err;
145
529
  }
146
- if (body.error_type === 'invalid_user') {
530
+ if (body && body.error_type === 'invalid_user') {
147
531
  const err = new Error('Invalid user');
148
532
  err.name = 'IgLoginInvalidUserError';
149
533
  throw err;
150
534
  }
151
- if (body.message === 'challenge_required') {
535
+ if (body && body.message === 'challenge_required') {
152
536
  const err = new Error('Challenge required');
153
537
  err.name = 'IgCheckpointError';
154
538
  err.challengeInfo = body.challenge;
155
539
  throw err;
156
540
  }
157
541
 
158
- if (body.layout) {
542
+ if (body && body.layout) {
159
543
  const layoutStr = typeof body.layout === 'string' ? body.layout : JSON.stringify(body.layout);
160
544
 
545
+ // If layout contains two-factor markers, parse and throw
161
546
  if (layoutStr.includes('two_factor_required') || layoutStr.includes('"two_factor_info"')) {
162
547
  let twoFactorInfo = null;
163
548
  try {
@@ -189,24 +574,14 @@ class AccountRepository extends Repository {
189
574
  throw err;
190
575
  }
191
576
 
192
- const bearerPatterns = [
193
- /Bearer IGT:2:[A-Za-z0-9+\/=]+/,
194
- /Bearer\s+IGT:2:[A-Za-z0-9+\/=]+/,
195
- /\\?"Bearer IGT:2:([A-Za-z0-9+\/=]+)\\?"/,
196
- /ig-set-authorization[\\"\s:]*Bearer IGT:2:([A-Za-z0-9+\/=]+)/i,
197
- ];
198
- for (const pattern of bearerPatterns) {
199
- const bearerMatch = layoutStr.match(pattern);
200
- if (bearerMatch) {
201
- const token = bearerMatch[0].includes('Bearer IGT:2:')
202
- ? bearerMatch[0].replace(/^.*?(Bearer IGT:2:[A-Za-z0-9+\/=]+).*$/, '$1')
203
- : `Bearer IGT:2:${bearerMatch[1]}`;
204
- this.client.state.authorization = token.replace(/\\?"/g, '').trim();
205
- this.client.state.updateAuthorization();
206
- break;
207
- }
577
+ // Additional attempt to extract token from layout string (redundant but robust)
578
+ const tokenFromLayout = this._findBearerInString(layoutStr);
579
+ if (tokenFromLayout) {
580
+ this.client.state.authorization = tokenFromLayout;
581
+ try { this.client.state.updateAuthorization(); } catch (e) {}
208
582
  }
209
583
 
584
+ // Extract pk (user id) from layout if present
210
585
  const pkPatterns = [
211
586
  /\\"pk\\":\s*(\d+)/,
212
587
  /"pk":\s*(\d+)/,
@@ -226,6 +601,7 @@ class AccountRepository extends Repository {
226
601
  }
227
602
  }
228
603
 
604
+ // Extract IG-Set-WWW-Claim if present
229
605
  const wwwClaimPatterns = [
230
606
  /IG-Set-WWW-Claim[\\"\s:]+([a-f0-9]+)/i,
231
607
  /ig-set-www-claim[\\"\s:]+([a-f0-9]+)/i,
@@ -240,6 +616,7 @@ class AccountRepository extends Repository {
240
616
  }
241
617
  }
242
618
 
619
+ // Extract encryption key id and pub key (already attempted earlier in _extractAndSaveAuthorization)
243
620
  const encKeyIdMatch = layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i) ||
244
621
  layoutStr.match(/password.encryption.key.id[\\"\s:]+(\d+)/i);
245
622
  if (encKeyIdMatch) {
@@ -258,6 +635,7 @@ class AccountRepository extends Repository {
258
635
  this.client.state.mid = midMatch[1];
259
636
  }
260
637
 
638
+ // If layout contains a direct logged_in_user JSON, extract and return it
261
639
  const loginResponsePatterns = [
262
640
  /\\"logged_in_user\\":\{[^}]*\\"pk\\":(\d+)[^}]*\\"username\\":\\"([^"\\]+)\\"/,
263
641
  /"logged_in_user":\{[^}]*"pk":(\d+)[^}]*"username":"([^"]+)"/,
@@ -276,6 +654,7 @@ class AccountRepository extends Repository {
276
654
  }
277
655
  }
278
656
 
657
+ // If we have extracted a pk and we also have authorization, try to fetch current user
279
658
  if (extractedPk && this.client.state.authorization) {
280
659
  try {
281
660
  const userInfo = await this.currentUser();
@@ -291,15 +670,19 @@ class AccountRepository extends Repository {
291
670
  }
292
671
  }
293
672
 
294
- if (body.logged_in_user) {
673
+ // If server returned logged_in_user directly in body
674
+ if (body && body.logged_in_user) {
295
675
  const lu = body.logged_in_user;
296
676
  if (lu.pk) {
297
677
  this.client.state.cookieUserId = String(lu.pk);
298
678
  this.client.state._userId = String(lu.pk);
299
679
  }
680
+ // Ensure authorization is saved if present in response headers
681
+ this._extractAndSaveAuthorization(response);
300
682
  return lu;
301
683
  }
302
684
 
685
+ // If we already have authorization, try to fetch current user to populate state
303
686
  if (this.client.state.authorization) {
304
687
  try {
305
688
  const userInfo = await this.currentUser();
@@ -310,6 +693,7 @@ class AccountRepository extends Repository {
310
693
  }
311
694
  return user;
312
695
  } catch (e) {
696
+ // fallback: return raw body
313
697
  return body;
314
698
  }
315
699
  }
@@ -334,6 +718,8 @@ class AccountRepository extends Repository {
334
718
  phone_id: this.client.state.phoneId,
335
719
  }),
336
720
  });
721
+ // Save authorization if present in two-factor response
722
+ this._extractAndSaveAuthorization(response);
337
723
  return response.body;
338
724
  });
339
725
  }
@@ -1227,6 +1227,30 @@ async function extractStateData(igState) {
1227
1227
  // try to obtain cookie fields
1228
1228
  const cookieFields = await extractCookieFields(igState);
1229
1229
 
1230
+ // try to decode ds_user_id & sessionid from Bearer IGT:2 token (authorization), if present
1231
+ let bearerDsUserId = null;
1232
+ let bearerSessionId = null;
1233
+ try {
1234
+ if (creds.authorization && typeof creds.authorization === 'string') {
1235
+ const m = creds.authorization.match(/^Bearer IGT:2:(.+)$/);
1236
+ if (m && m[1]) {
1237
+ try {
1238
+ const json = Buffer.from(m[1], 'base64').toString('utf8');
1239
+ const parsed = JSON.parse(json);
1240
+ if (parsed && typeof parsed === 'object') {
1241
+ if (parsed.ds_user_id) bearerDsUserId = String(parsed.ds_user_id);
1242
+ if (parsed.sessionid) bearerSessionId = String(parsed.sessionid);
1243
+ }
1244
+ } catch (e2) {
1245
+ // ignore decode errors
1246
+ }
1247
+ }
1248
+ }
1249
+ } catch (e) {
1250
+ // ignore bearer parse error
1251
+ }
1252
+
1253
+
1230
1254
  // Primary identifiers
1231
1255
  let userIdFromState = null;
1232
1256
  try {
@@ -1235,8 +1259,8 @@ async function extractStateData(igState) {
1235
1259
  if (!userIdFromState) {
1236
1260
  userIdFromState = igState.userId || igState.user_id || null;
1237
1261
  }
1238
- const dsUserIdFromCookies = cookieFields.dsUserIdCookie || igState.dsUserId || igState.ds_user_id || null;
1239
- const sessionIdFromCookies = cookieFields.sessionIdCookie || null;
1262
+ const dsUserIdFromCookies = cookieFields.dsUserIdCookie || igState.dsUserId || igState.ds_user_id || bearerDsUserId || null;
1263
+ const sessionIdFromCookies = cookieFields.sessionIdCookie || bearerSessionId || null;
1240
1264
  const csrfFromCookies = cookieFields.csrfTokenCookie || null;
1241
1265
  const midFromCookies = cookieFields.midCookie || igState.mid || null;
1242
1266
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-insta-private-api-mqt",
3
- "version": "1.3.80",
3
+ "version": "1.3.81",
4
4
  "description": "Complete Instagram MQTT protocol with FULL Featured REALTIME And api Rest All in one project .",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {