nodejs-insta-private-api-mqt 1.3.79 → 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,30 +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
+
473
+ 'X-IG-Bandwidth-Speed-KBPS': (Math.random() * 1500 + 1500).toFixed(3),
474
+ 'X-IG-Bandwidth-TotalBytes-B': '0',
475
+ 'X-IG-Bandwidth-TotalTime-MS': '0',
476
+
117
477
  'Accept-Encoding': 'gzip, deflate',
118
- 'X-FB-HTTP-Engine': 'Liger',
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)',
119
479
  };
120
480
 
121
481
  const response = await this.client.request.send({
@@ -124,35 +484,65 @@ class AccountRepository extends Repository {
124
484
  form: { params: paramsJson },
125
485
  headers: bloksHeaders,
126
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 ===
127
512
 
128
513
  const body = response.body;
129
514
 
130
- 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) {
131
520
  const err = new Error('Two factor authentication required');
132
521
  err.name = 'IgLoginTwoFactorRequiredError';
133
522
  err.twoFactorInfo = body.two_factor_info;
134
523
  throw err;
135
524
  }
136
- if (body.error_type === 'bad_password') {
525
+ if (body && body.error_type === 'bad_password') {
137
526
  const err = new Error('Bad password');
138
527
  err.name = 'IgLoginBadPasswordError';
139
528
  throw err;
140
529
  }
141
- if (body.error_type === 'invalid_user') {
530
+ if (body && body.error_type === 'invalid_user') {
142
531
  const err = new Error('Invalid user');
143
532
  err.name = 'IgLoginInvalidUserError';
144
533
  throw err;
145
534
  }
146
- if (body.message === 'challenge_required') {
535
+ if (body && body.message === 'challenge_required') {
147
536
  const err = new Error('Challenge required');
148
537
  err.name = 'IgCheckpointError';
149
538
  err.challengeInfo = body.challenge;
150
539
  throw err;
151
540
  }
152
541
 
153
- if (body.layout) {
542
+ if (body && body.layout) {
154
543
  const layoutStr = typeof body.layout === 'string' ? body.layout : JSON.stringify(body.layout);
155
544
 
545
+ // If layout contains two-factor markers, parse and throw
156
546
  if (layoutStr.includes('two_factor_required') || layoutStr.includes('"two_factor_info"')) {
157
547
  let twoFactorInfo = null;
158
548
  try {
@@ -184,52 +574,131 @@ class AccountRepository extends Repository {
184
574
  throw err;
185
575
  }
186
576
 
187
- const bearerMatch = layoutStr.match(/Bearer IGT:2:[A-Za-z0-9+\/=]+/);
188
- if (bearerMatch) {
189
- this.client.state.authorization = bearerMatch[0];
190
- this.client.state.updateAuthorization();
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) {}
191
582
  }
192
583
 
193
- const pkMatch = layoutStr.match(/\\"pk\\":(\d+)/);
194
- if (pkMatch) {
195
- this.client.state.cookieUserId = pkMatch[1];
584
+ // Extract pk (user id) from layout if present
585
+ const pkPatterns = [
586
+ /\\"pk\\":\s*(\d+)/,
587
+ /"pk":\s*(\d+)/,
588
+ /\\"pk_id\\":\s*\\"(\d+)\\"/,
589
+ /"pk_id":\s*"(\d+)"/,
590
+ /logged_in_user.*?"pk":\s*(\d+)/,
591
+ /logged_in_user.*?\\"pk\\":\s*(\d+)/,
592
+ ];
593
+ let extractedPk = null;
594
+ for (const pattern of pkPatterns) {
595
+ const pkMatch = layoutStr.match(pattern);
596
+ if (pkMatch) {
597
+ extractedPk = pkMatch[1];
598
+ this.client.state.cookieUserId = extractedPk;
599
+ this.client.state._userId = extractedPk;
600
+ break;
601
+ }
196
602
  }
197
603
 
198
- const wwwClaimMatch = layoutStr.match(/IG-Set-WWW-Claim[\\"\s:]+([a-f0-9]+)/i);
199
- if (wwwClaimMatch) {
200
- this.client.state.igWWWClaim = wwwClaimMatch[1];
604
+ // Extract IG-Set-WWW-Claim if present
605
+ const wwwClaimPatterns = [
606
+ /IG-Set-WWW-Claim[\\"\s:]+([a-f0-9]+)/i,
607
+ /ig-set-www-claim[\\"\s:]+([a-f0-9]+)/i,
608
+ /www.claim[\\"\s:]+([a-f0-9]+)/i,
609
+ /x-ig-set-www-claim[\\"\s:]+([a-f0-9]+)/i,
610
+ ];
611
+ for (const pattern of wwwClaimPatterns) {
612
+ const wwwClaimMatch = layoutStr.match(pattern);
613
+ if (wwwClaimMatch) {
614
+ this.client.state.igWWWClaim = wwwClaimMatch[1];
615
+ break;
616
+ }
201
617
  }
202
618
 
203
- const encKeyIdMatch = layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i);
619
+ // Extract encryption key id and pub key (already attempted earlier in _extractAndSaveAuthorization)
620
+ const encKeyIdMatch = layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i) ||
621
+ layoutStr.match(/password.encryption.key.id[\\"\s:]+(\d+)/i);
204
622
  if (encKeyIdMatch) {
205
623
  this.client.state.passwordEncryptionKeyId = parseInt(encKeyIdMatch[1]);
206
624
  }
207
625
 
208
- const encPubKeyMatch = layoutStr.match(/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i);
626
+ const encPubKeyMatch = layoutStr.match(/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i) ||
627
+ layoutStr.match(/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i);
209
628
  if (encPubKeyMatch) {
210
629
  this.client.state.passwordEncryptionPubKey = encPubKeyMatch[1];
211
630
  }
212
631
 
213
- const loginResponseMatch = layoutStr.match(/\\"logged_in_user\\":\{[^}]*\\"pk\\":(\d+)[^}]*\\"username\\":\\"([^"\\]+)\\"/);
214
- if (loginResponseMatch) {
215
- return {
216
- pk: parseInt(loginResponseMatch[1]),
217
- pk_id: loginResponseMatch[1],
218
- username: loginResponseMatch[2],
219
- };
632
+ const midMatch = layoutStr.match(/ig-set-x-mid[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i) ||
633
+ layoutStr.match(/\\"x-mid\\"[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i);
634
+ if (midMatch) {
635
+ this.client.state.mid = midMatch[1];
636
+ }
637
+
638
+ // If layout contains a direct logged_in_user JSON, extract and return it
639
+ const loginResponsePatterns = [
640
+ /\\"logged_in_user\\":\{[^}]*\\"pk\\":(\d+)[^}]*\\"username\\":\\"([^"\\]+)\\"/,
641
+ /"logged_in_user":\{[^}]*"pk":(\d+)[^}]*"username":"([^"]+)"/,
642
+ /\\"logged_in_user\\".*?\\"pk\\":\s*(\d+).*?\\"username\\":\s*\\"([^"\\]+)\\"/s,
643
+ ];
644
+ for (const pattern of loginResponsePatterns) {
645
+ const loginResponseMatch = layoutStr.match(pattern);
646
+ if (loginResponseMatch) {
647
+ this.client.state.cookieUserId = loginResponseMatch[1];
648
+ this.client.state._userId = loginResponseMatch[1];
649
+ return {
650
+ pk: parseInt(loginResponseMatch[1]),
651
+ pk_id: loginResponseMatch[1],
652
+ username: loginResponseMatch[2],
653
+ };
654
+ }
655
+ }
656
+
657
+ // If we have extracted a pk and we also have authorization, try to fetch current user
658
+ if (extractedPk && this.client.state.authorization) {
659
+ try {
660
+ const userInfo = await this.currentUser();
661
+ const user = userInfo.user || userInfo;
662
+ if (user && user.pk) {
663
+ this.client.state.cookieUserId = String(user.pk);
664
+ this.client.state._userId = String(user.pk);
665
+ }
666
+ return user;
667
+ } catch (e) {
668
+ return { pk: parseInt(extractedPk), pk_id: extractedPk };
669
+ }
220
670
  }
221
671
  }
222
672
 
223
- if (body.logged_in_user) {
224
- return body.logged_in_user;
673
+ // If server returned logged_in_user directly in body
674
+ if (body && body.logged_in_user) {
675
+ const lu = body.logged_in_user;
676
+ if (lu.pk) {
677
+ this.client.state.cookieUserId = String(lu.pk);
678
+ this.client.state._userId = String(lu.pk);
679
+ }
680
+ // Ensure authorization is saved if present in response headers
681
+ this._extractAndSaveAuthorization(response);
682
+ return lu;
225
683
  }
226
684
 
227
- try {
228
- const userInfo = await this.currentUser();
229
- return userInfo.user || userInfo;
230
- } catch (e) {
231
- return body;
685
+ // If we already have authorization, try to fetch current user to populate state
686
+ if (this.client.state.authorization) {
687
+ try {
688
+ const userInfo = await this.currentUser();
689
+ const user = userInfo.user || userInfo;
690
+ if (user && user.pk) {
691
+ this.client.state.cookieUserId = String(user.pk);
692
+ this.client.state._userId = String(user.pk);
693
+ }
694
+ return user;
695
+ } catch (e) {
696
+ // fallback: return raw body
697
+ return body;
698
+ }
232
699
  }
700
+
701
+ return body;
233
702
  });
234
703
  }
235
704
 
@@ -249,6 +718,8 @@ class AccountRepository extends Repository {
249
718
  phone_id: this.client.state.phoneId,
250
719
  }),
251
720
  });
721
+ // Save authorization if present in two-factor response
722
+ this._extractAndSaveAuthorization(response);
252
723
  return response.body;
253
724
  });
254
725
  }
@@ -12,10 +12,31 @@ class DirectRepository extends Repository {
12
12
  const result = await requestFn();
13
13
  return result;
14
14
  } catch (error) {
15
- const shouldRetry =
16
- (error.data?.error_type === 'server_error' ||
17
- error.data?.error_type === 'rate_limited') &&
18
- retries < this.maxRetries;
15
+ const errorCode = error.data?.content?.error_code || error.data?.error_code;
16
+ const errorType = error.data?.error_type;
17
+ const statusCode = error.status || error.data?.status_code;
18
+
19
+ const isPromptContribution = errorCode === 4415001;
20
+ const isAuthIssue = statusCode === 400 && isPromptContribution;
21
+ const isServerError = errorType === 'server_error';
22
+ const isRateLimited = errorType === 'rate_limited';
23
+ const isLoginRequired = error.data?.message === 'login_required' || errorType === 'login_required';
24
+
25
+ if ((isAuthIssue || isLoginRequired) && retries < this.maxRetries) {
26
+ try {
27
+ if (this.client.state.authorization) {
28
+ this.client.state.updateAuthorization();
29
+ }
30
+ if (this.client.account && typeof this.client.account.syncPostLoginExperiments === 'function') {
31
+ await this.client.account.syncPostLoginExperiments().catch(() => {});
32
+ }
33
+ } catch (syncErr) {}
34
+ const delay = 2000 * (retries + 1);
35
+ await new Promise(resolve => setTimeout(resolve, delay));
36
+ return this.requestWithRetry(requestFn, retries + 1);
37
+ }
38
+
39
+ const shouldRetry = (isServerError || isRateLimited) && retries < this.maxRetries;
19
40
  if (shouldRetry) {
20
41
  const delay = 1000 * (retries + 1);
21
42
  await new Promise(resolve => setTimeout(resolve, delay));
@@ -159,6 +180,19 @@ class DirectRepository extends Repository {
159
180
  }
160
181
 
161
182
  async getInbox(cursor = null, limit = 20) {
183
+ if (!this.client.state.authorization) {
184
+ throw new Error('Not authenticated. Please login first before accessing inbox.');
185
+ }
186
+
187
+ const userId = this.client.state.cookieUserId || this.client.state._userId;
188
+ if (!userId || userId === '0' || userId === 0) {
189
+ this.client.state.updateAuthorization();
190
+ if (this.client.state.parsedAuthorization && this.client.state.parsedAuthorization.ds_user_id) {
191
+ this.client.state._userId = this.client.state.parsedAuthorization.ds_user_id;
192
+ this.client.state.cookieUserId = this.client.state.parsedAuthorization.ds_user_id;
193
+ }
194
+ }
195
+
162
196
  return this.requestWithRetry(async () => {
163
197
  const qs = { persistentBadging: true, limit };
164
198
  if (cursor) qs.cursor = cursor;
@@ -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.79",
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": {