nodejs-insta-private-api-mqt 1.3.86 → 1.3.87

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.
@@ -1,18 +1,20 @@
1
1
  const Repository = require('../core/repository');
2
2
  const crypto = require('crypto');
3
+
3
4
  class AccountRepository extends Repository {
4
5
  constructor(client) {
5
6
  super(client);
6
7
  this.maxRetries = 3;
7
8
  }
9
+
8
10
  async requestWithRetry(requestFn, retries = 0) {
9
11
  try {
10
12
  const result = await requestFn();
11
13
  return result;
12
- }
13
- catch (error) {
14
- const shouldRetry = (error.data?.error_type === 'server_error' ||
15
- error.data?.error_type === 'rate_limited') &&
14
+ } catch (error) {
15
+ const shouldRetry =
16
+ (error.data?.error_type === 'server_error' ||
17
+ error.data?.error_type === 'rate_limited') &&
16
18
  retries < this.maxRetries;
17
19
  if (shouldRetry) {
18
20
  const delay = 1000 * (retries + 1);
@@ -22,6 +24,7 @@ class AccountRepository extends Repository {
22
24
  throw error;
23
25
  }
24
26
  }
27
+
25
28
  /**
26
29
  * Helper: try to extract an IGT bearer token from various sources:
27
30
  * - response.headers (IG-Set-Authorization or ig-set-authorization)
@@ -32,8 +35,8 @@ class AccountRepository extends Repository {
32
35
  * When found, sets this.client.state.authorization and calls updateAuthorization().
33
36
  */
34
37
  _extractAndSaveAuthorization(response) {
35
- if (!response)
36
- return null;
38
+ if (!response) return null;
39
+
37
40
  // Normalize headers (case-insensitive)
38
41
  const headers = {};
39
42
  if (response.headers && typeof response.headers === 'object') {
@@ -41,19 +44,19 @@ class AccountRepository extends Repository {
41
44
  headers[k.toLowerCase()] = response.headers[k];
42
45
  }
43
46
  }
47
+
44
48
  // Simpler: look for any header that contains ig-set-authorization
45
49
  for (const key of Object.keys(headers)) {
46
50
  const val = headers[key];
47
- if (!val)
48
- continue;
51
+ if (!val) continue;
52
+
49
53
  if (key.includes('ig-set-authorization')) {
50
54
  const token = this._normalizeTokenString(val);
51
55
  if (token) {
52
56
  this.client.state.authorization = token;
53
57
  try {
54
58
  this.client.state.updateAuthorization();
55
- }
56
- catch (e) { }
59
+ } catch (e) {}
57
60
  return token;
58
61
  }
59
62
  }
@@ -64,14 +67,15 @@ class AccountRepository extends Repository {
64
67
  this.client.state.authorization = token;
65
68
  try {
66
69
  this.client.state.updateAuthorization();
67
- }
68
- catch (e) { }
70
+ } catch (e) {}
69
71
  return token;
70
72
  }
71
73
  }
72
74
  }
75
+
73
76
  // Extract IG-U headers (user id, region, claim) when present
74
- const dsUserIdHeader = headers['ig-set-ig-u-ds-user-id'] ||
77
+ const dsUserIdHeader =
78
+ headers['ig-set-ig-u-ds-user-id'] ||
75
79
  headers['ig-u-ds-user-id'] ||
76
80
  headers['x-ig-set-ig-u-ds-user-id'];
77
81
  if (dsUserIdHeader) {
@@ -82,7 +86,9 @@ class AccountRepository extends Repository {
82
86
  this.client.state._userId = uid;
83
87
  }
84
88
  }
85
- const rurHeader = headers['ig-set-ig-u-rur'] ||
89
+
90
+ const rurHeader =
91
+ headers['ig-set-ig-u-rur'] ||
86
92
  headers['ig-u-rur'] ||
87
93
  headers['x-ig-set-ig-u-rur'];
88
94
  if (rurHeader) {
@@ -91,7 +97,9 @@ class AccountRepository extends Repository {
91
97
  this.client.state.igURur = rur;
92
98
  }
93
99
  }
94
- const wwwClaimHeader = headers['x-ig-set-www-claim'] ||
100
+
101
+ const wwwClaimHeader =
102
+ headers['x-ig-set-www-claim'] ||
95
103
  headers['ig-set-www-claim'] ||
96
104
  headers['ig-u-www-claim'];
97
105
  if (wwwClaimHeader) {
@@ -100,7 +108,9 @@ class AccountRepository extends Repository {
100
108
  this.client.state.igWWWClaim = claim;
101
109
  }
102
110
  }
103
- const midHeader = headers['ig-set-x-mid'] ||
111
+
112
+ const midHeader =
113
+ headers['ig-set-x-mid'] ||
104
114
  headers['x-mid'] ||
105
115
  headers['ig-set-mid'];
106
116
  if (midHeader) {
@@ -109,6 +119,7 @@ class AccountRepository extends Repository {
109
119
  this.client.state.mid = mid;
110
120
  }
111
121
  }
122
+
112
123
  // 2) Check response.body for common fields
113
124
  const body = response.body;
114
125
  if (body) {
@@ -119,21 +130,22 @@ class AccountRepository extends Repository {
119
130
  this.client.state.authorization = token;
120
131
  try {
121
132
  this.client.state.updateAuthorization();
122
- }
123
- catch (e) { }
133
+ } catch (e) {}
124
134
  return token;
125
135
  }
126
136
  }
137
+
127
138
  // If body contains a layout object or string, stringify and search it
128
139
  try {
129
140
  let layoutStr = null;
130
141
  if (body.layout) {
131
- layoutStr = typeof body.layout === 'string' ? body.layout : JSON.stringify(body.layout);
132
- }
133
- else if (body?.layout?.bloks_payload) {
142
+ layoutStr =
143
+ typeof body.layout === 'string'
144
+ ? body.layout
145
+ : JSON.stringify(body.layout);
146
+ } else if (body?.layout?.bloks_payload) {
134
147
  layoutStr = JSON.stringify(body.layout.bloks_payload);
135
- }
136
- else if (body?.bloks_payload) {
148
+ } else if (body?.bloks_payload) {
137
149
  layoutStr = JSON.stringify(body.bloks_payload);
138
150
  }
139
151
  if (!layoutStr && typeof body === 'string') {
@@ -145,49 +157,72 @@ class AccountRepository extends Repository {
145
157
  this.client.state.authorization = token;
146
158
  try {
147
159
  this.client.state.updateAuthorization();
148
- }
149
- catch (e) { }
160
+ } catch (e) {}
150
161
  return token;
151
162
  }
163
+
152
164
  // Some responses embed a login_response JSON string inside layout; try to extract it
153
- const loginResponseMatch = layoutStr.match(/"login_response"\s*:\s*"(\\?{.*?\\?})"/s);
165
+ const loginResponseMatch = layoutStr.match(
166
+ /"login_response"\s*:\s*"(\\?{.*?\\?})"/s
167
+ );
154
168
  if (loginResponseMatch) {
155
169
  // Unescape JSON string
156
170
  let jsonStr = loginResponseMatch[1];
157
171
  try {
158
- jsonStr = jsonStr.replace(/\\"/g, '"').replace(/\\n/g, '');
172
+ jsonStr = jsonStr
173
+ .replace(/\\"/g, '"')
174
+ .replace(/\\n/g, '');
159
175
  const parsed = JSON.parse(jsonStr);
160
176
  if (parsed && parsed.headers) {
161
- const token = this._findBearerInString(parsed.headers);
177
+ const token = this._findBearerInString(
178
+ parsed.headers
179
+ );
162
180
  if (token) {
163
181
  this.client.state.authorization = token;
164
182
  try {
165
183
  this.client.state.updateAuthorization();
166
- }
167
- catch (e) { }
184
+ } catch (e) {}
168
185
  return token;
169
186
  }
170
187
  }
171
- }
172
- catch (e) {
188
+ } catch (e) {
173
189
  // ignore parse errors
174
190
  }
175
191
  }
192
+
176
193
  // Also try to find IG-Set-Password-Encryption headers (not token but useful)
177
- const encKeyIdMatch = layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i) ||
178
- layoutStr.match(/password.encryption.key.id[\\"\s:]+(\d+)/i);
194
+ const encKeyIdMatch =
195
+ layoutStr.match(
196
+ /IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i
197
+ ) ||
198
+ layoutStr.match(
199
+ /password.encryption.key.id[\\"\s:]+(\d+)/i
200
+ );
179
201
  if (encKeyIdMatch) {
180
- this.client.state.passwordEncryptionKeyId = parseInt(encKeyIdMatch[1]);
202
+ this.client.state.passwordEncryptionKeyId =
203
+ parseInt(encKeyIdMatch[1]);
181
204
  }
182
- const encPubKeyMatch = layoutStr.match(/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i) ||
183
- layoutStr.match(/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i);
205
+ const encPubKeyMatch =
206
+ layoutStr.match(
207
+ /IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
208
+ ) ||
209
+ layoutStr.match(
210
+ /password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
211
+ );
184
212
  if (encPubKeyMatch) {
185
- this.client.state.passwordEncryptionPubKey = encPubKeyMatch[1];
213
+ this.client.state.passwordEncryptionPubKey =
214
+ encPubKeyMatch[1];
186
215
  }
216
+
187
217
  // Extract user/account identifiers from layout string
188
- const dsIdMatch = layoutStr.match(/ig-set-ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
218
+ const dsIdMatch =
219
+ layoutStr.match(
220
+ /ig-set-ig-u-ds-user-id[\\"\s:]+(\d+)/i
221
+ ) ||
189
222
  layoutStr.match(/ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
190
- layoutStr.match(/"strong_id__"\s*:\s*"(\d+)"/i) ||
223
+ layoutStr.match(
224
+ /"strong_id__"\s*:\s*"(\d+)"/i
225
+ ) ||
191
226
  layoutStr.match(/"pk_id"\s*:\s*"(\d+)"/i) ||
192
227
  layoutStr.match(/"pk"\s*:\s*(\d+)/i);
193
228
  if (dsIdMatch) {
@@ -195,56 +230,71 @@ class AccountRepository extends Repository {
195
230
  this.client.state.cookieUserId = uid;
196
231
  this.client.state._userId = uid;
197
232
  }
198
- const rurMatch = layoutStr.match(/ig-set-ig-u-rur[\\"\s:]+"([^"\\]+)/i) ||
233
+
234
+ const rurMatch =
235
+ layoutStr.match(
236
+ /ig-set-ig-u-rur[\\"\s:]+"([^"\\]+)/i
237
+ ) ||
199
238
  layoutStr.match(/ig-u-rur[\\"\s:]+"([^"\\]+)/i);
200
239
  if (rurMatch) {
201
240
  this.client.state.igURur = rurMatch[1];
202
241
  }
203
- const wwwClaimMatch = layoutStr.match(/x-ig-set-www-claim[\\"\s:]+"([^"\\]+)/i);
242
+
243
+ const wwwClaimMatch = layoutStr.match(
244
+ /x-ig-set-www-claim[\\"\s:]+"([^"\\]+)/i
245
+ );
204
246
  if (wwwClaimMatch) {
205
247
  this.client.state.igWWWClaim = wwwClaimMatch[1];
206
248
  }
207
- const midMatch = layoutStr.match(/"mid"\s*:\s*"([^"\\]+)"/i);
249
+
250
+ const midMatch = layoutStr.match(
251
+ /"mid"\s*:\s*"([^"\\]+)"/i
252
+ );
208
253
  if (midMatch) {
209
254
  this.client.state.mid = midMatch[1];
210
255
  }
211
256
  }
212
- }
213
- catch (e) {
257
+ } catch (e) {
214
258
  // ignore
215
259
  }
260
+
216
261
  // 3) If body.logged_in_user exists and we have authorization already, return it
217
262
  if (body.logged_in_user && this.client.state.authorization) {
218
263
  return this.client.state.authorization;
219
264
  }
220
265
  }
266
+
221
267
  return null;
222
268
  }
269
+
223
270
  // Normalize token string: accept either raw token or "Bearer IGT:2:..." and return trimmed token string
224
271
  _normalizeTokenString(val) {
225
- if (!val || typeof val !== 'string')
226
- return null;
272
+ if (!val || typeof val !== 'string') return null;
273
+
227
274
  // If header already contains "Bearer IGT:2:..."
228
275
  const bearer = this._findBearerInString(val);
229
- if (bearer)
230
- return bearer;
276
+ if (bearer) return bearer;
277
+
231
278
  // If header contains JSON with IG-Set-Authorization field
232
279
  try {
233
280
  const maybeJson = JSON.parse(val);
234
281
  if (maybeJson['IG-Set-Authorization']) {
235
- return this._findBearerInString(maybeJson['IG-Set-Authorization']);
282
+ return this._findBearerInString(
283
+ maybeJson['IG-Set-Authorization']
284
+ );
236
285
  }
237
- }
238
- catch (e) {
286
+ } catch (e) {
239
287
  // not JSON
240
288
  }
289
+
241
290
  // fallback: return trimmed value
242
291
  return val.trim();
243
292
  }
293
+
244
294
  // Find bearer token in arbitrary string using multiple patterns
245
295
  _findBearerInString(str) {
246
- if (!str || typeof str !== 'string')
247
- return null;
296
+ if (!str || typeof str !== 'string') return null;
297
+
248
298
  // Patterns to match the Bearer token in many possible encodings/escapes
249
299
  const bearerPatterns = [
250
300
  /Bearer IGT:2:[A-Za-z0-9+\/=]+/,
@@ -255,98 +305,372 @@ class AccountRepository extends Repository {
255
305
  /IG-Set-Authorization[\s:]+(Bearer IGT:2:[A-Za-z0-9+\/=]+)/i,
256
306
  /Bearer IGT:2:([A-Za-z0-9+\/=]+)/,
257
307
  ];
308
+
258
309
  for (const pattern of bearerPatterns) {
259
310
  const m = str.match(pattern);
260
311
  if (m) {
261
312
  // If capturing group present, prefer it
262
313
  if (m[1]) {
263
- const token = m[1].includes('Bearer IGT:2:') ? m[1] : `Bearer IGT:2:${m[1]}`;
314
+ const token = m[1].includes('Bearer IGT:2:')
315
+ ? m[1]
316
+ : `Bearer IGT:2:${m[1]}`;
264
317
  return token.replace(/\?"/g, '').trim();
265
318
  }
266
319
  // Otherwise m[0] contains the full token
267
320
  return m[0].replace(/\?"/g, '').trim();
268
321
  }
269
322
  }
323
+
270
324
  return null;
271
325
  }
326
+
272
327
  // Ensure we have a valid csrftoken cookie (and mid) before sensitive endpoints.
273
328
  // Instagram typically sets csrftoken via /api/v1/si/fetch_headers/.
274
329
  async ensureCsrfToken() {
275
330
  const cookieToken = this.client.state.cookieCsrfToken;
276
- if (cookieToken && cookieToken !== 'missing' && cookieToken !== 'pending') {
331
+ if (
332
+ cookieToken &&
333
+ cookieToken !== 'missing' &&
334
+ cookieToken !== 'pending'
335
+ ) {
277
336
  try {
278
337
  this.client.state.csrfToken = cookieToken;
279
- }
280
- catch { }
338
+ } catch {}
281
339
  return cookieToken;
282
340
  }
341
+
283
342
  // Warmup endpoint that returns/set cookies (csrftoken, mid, etc.)
284
343
  try {
285
344
  await this.client.request.send({
286
345
  method: 'GET',
287
346
  url: `/api/v1/si/fetch_headers/?challenge_type=signup&guid=${this.client.state.uuid}`,
288
347
  });
289
- }
290
- catch (e) {
348
+ } catch (e) {
291
349
  // ignore warmup failures
292
350
  }
351
+
293
352
  const token = this.client.state.cookieCsrfToken;
294
- if (token && token !== 'missing' && token !== 'pending') {
353
+ if (
354
+ token &&
355
+ token !== 'missing' &&
356
+ token !== 'pending'
357
+ ) {
295
358
  try {
296
359
  this.client.state.csrfToken = token;
297
- }
298
- catch { }
360
+ } catch {}
299
361
  return token;
300
362
  }
363
+
301
364
  return null;
302
365
  }
366
+
367
+ /**
368
+ * Internal: perform the Bloks OAuth token fetch step that the official app does
369
+ * just before password login (com.bloks.www.caa.login.oauth.token.fetch.async).
370
+ * This is best-effort only and never throws to the caller.
371
+ */
372
+ async _prefetchOauthTokenForLogin(username) {
373
+ if (!username) return null;
374
+
375
+ try {
376
+ const nowSec = Math.floor(Date.now() / 1000);
377
+
378
+ // Device identifiers – keep them consistent with the main login flow
379
+ const androidDeviceId =
380
+ this.client.state.androidDeviceId ||
381
+ this.client.state.deviceId ||
382
+ `android-${crypto.randomBytes(8).toString('hex')}`;
383
+
384
+ const familyDeviceId =
385
+ this.client.state.phoneId ||
386
+ this.client.state.familyDeviceId ||
387
+ (crypto.randomUUID
388
+ ? crypto.randomUUID()
389
+ : require('uuid').v4());
390
+
391
+ const qeDeviceId =
392
+ this.client.state.deviceId ||
393
+ this.client.state.qeDeviceId ||
394
+ (crypto.randomUUID
395
+ ? crypto.randomUUID()
396
+ : require('uuid').v4());
397
+
398
+ const waterfallId = crypto.randomUUID
399
+ ? crypto.randomUUID()
400
+ : require('uuid').v4();
401
+
402
+ const latencyMarkerId = 36707139;
403
+ const latencyInstanceId = Number(
404
+ `${Date.now()}${Math.floor(Math.random() * 1000)}`
405
+ );
406
+
407
+ // AAC object as seen in real traffic: timestamp + UUID + random token
408
+ const aacInitTimestamp =
409
+ nowSec - Math.floor(Math.random() * 50);
410
+ const aacjid = crypto.randomUUID
411
+ ? crypto.randomUUID()
412
+ : require('uuid').v4();
413
+ const aaccs = crypto
414
+ .randomBytes(32)
415
+ .toString('base64')
416
+ .replace(/=/g, '');
417
+
418
+ const clientInputParams = {
419
+ username_input: username,
420
+ aac: JSON.stringify({
421
+ aac_init_timestamp: aacInitTimestamp,
422
+ aacjid,
423
+ aaccs,
424
+ }),
425
+ lois_settings: { lois_token: '' },
426
+ cloud_trust_token: null,
427
+ zero_balance_state: '',
428
+ network_bssid: null,
429
+ };
430
+
431
+ const serverParams = {
432
+ is_from_logged_out: 0,
433
+ layered_homepage_experiment_group: null,
434
+ device_id: androidDeviceId,
435
+ login_surface: 'switcher',
436
+ waterfall_id: waterfallId,
437
+ INTERNAL__latency_qpl_instance_id: latencyInstanceId,
438
+ is_platform_login: 0,
439
+ INTERNAL__latency_qpl_marker_id: latencyMarkerId,
440
+ family_device_id: familyDeviceId,
441
+ offline_experiment_group: 'caa_iteration_v3_perf_ig_4',
442
+ access_flow_version: 'pre_mt_behavior',
443
+ is_from_logged_in_switcher: 1,
444
+ qe_device_id: qeDeviceId,
445
+ };
446
+
447
+ const paramsJson = JSON.stringify({
448
+ client_input_params: clientInputParams,
449
+ server_params: serverParams,
450
+ });
451
+
452
+ const bloksVersionId =
453
+ this.client.state.bloksVersionId ||
454
+ '5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
455
+
456
+ const bkClientContext = JSON.stringify({
457
+ bloks_version: bloksVersionId,
458
+ styles_id: 'instagram',
459
+ });
460
+
461
+ const lang = this.client.state.language || 'ro_RO';
462
+ const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
463
+ const userAgent =
464
+ this.client.state.userAgent ||
465
+ 'Instagram 371.0.0.0.23 Android (30/11; 320dpi; 720x1449; Xiaomi/Redmi; M2006C3MNG; angelican; mt6765; ro_RO; 703217507)';
466
+
467
+ const timezoneOffset =
468
+ typeof this.client.state.timezoneOffset === 'number'
469
+ ? this.client.state.timezoneOffset
470
+ : 7200;
471
+
472
+ const bloksHeaders = {
473
+ 'accept-language': acceptLanguage,
474
+ 'content-type':
475
+ 'application/x-www-form-urlencoded; charset=UTF-8',
476
+ 'ig-intended-user-id': '0',
477
+ // Critical: matches the captured request header
478
+ priority: 'u=3',
479
+
480
+ 'x-bloks-is-layout-rtl': 'false',
481
+ 'x-bloks-prism-ax-base-colors-enabled': 'false',
482
+ 'x-bloks-prism-button-version': 'CONTROL',
483
+ 'x-bloks-prism-colors-enabled': 'true',
484
+ 'x-bloks-prism-font-enabled': 'false',
485
+ 'x-bloks-prism-indigo-link-version': '0',
486
+ 'x-bloks-version-id': bloksVersionId,
487
+
488
+ 'x-ig-android-id': androidDeviceId,
489
+ 'x-ig-device-id': qeDeviceId,
490
+ 'x-ig-family-device-id': familyDeviceId,
491
+ 'x-ig-timezone-offset': String(timezoneOffset),
492
+ 'x-ig-app-id': String(
493
+ this.client.state.fbAnalyticsApplicationId ||
494
+ '567067343352427'
495
+ ),
496
+ 'x-ig-app-locale': lang,
497
+ 'x-ig-device-locale': lang,
498
+ 'x-ig-mapped-locale': lang,
499
+
500
+ 'x-ig-client-endpoint':
501
+ 'com.bloks.www.caa.login.login_homepage',
502
+ 'x-ig-nav-chain':
503
+ 'com.bloks.www.caa.login.login_homepage:com.bloks.www.caa.login.login_homepage:1:button:0:::0',
504
+
505
+ 'x-ig-connection-type': 'WIFI',
506
+ 'x-ig-capabilities': '3brTv10=',
507
+ 'x-ig-www-claim': this.client.state.igWWWClaim || '0',
508
+
509
+ 'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
510
+ Math.random() * 1000
511
+ )
512
+ .toString()
513
+ .padStart(3, '0')}`,
514
+ 'x-pigeon-session-id':
515
+ this.client.state.pigeonSessionId ||
516
+ `UFS-${crypto.randomBytes(16).toString('hex')}-2`,
517
+
518
+ 'x-mid':
519
+ this.client.state.mid ||
520
+ `aZ${crypto.randomBytes(8).toString('hex')}`,
521
+ 'x-tigon-is-retry': 'False',
522
+
523
+ 'x-fb-client-ip': 'True',
524
+ 'x-fb-connection-type': 'WIFI',
525
+ 'x-fb-server-cluster': 'True',
526
+ 'x-fb-network-properties':
527
+ 'VPN;Validated;LocalAddrs=/10.0.0.2,;',
528
+ 'x-fb-request-analytics-tags': JSON.stringify({
529
+ network_tags: {
530
+ product: String(
531
+ this.client.state.fbAnalyticsApplicationId ||
532
+ '567067343352427'
533
+ ),
534
+ purpose: 'fetch',
535
+ surface: 'undefined',
536
+ request_category: 'api',
537
+ retry_attempt: '0',
538
+ },
539
+ }),
540
+ 'x-fb-friendly-name':
541
+ 'IgApi: bloks/async_action/com.bloks.www.caa.login.oauth.token.fetch.async/',
542
+ 'x-fb-http-engine': 'MNS/TCP',
543
+ 'x-fb-rmd': 'state=URL_ELIGIBLE',
544
+
545
+ 'user-agent': userAgent,
546
+ };
547
+
548
+ const response = await this.client.request.send({
549
+ method: 'POST',
550
+ url: '/api/v1/bloks/async_action/com.bloks.www.caa.login.oauth.token.fetch.async/',
551
+ form: {
552
+ params: paramsJson,
553
+ bk_client_context: bkClientContext,
554
+ bloks_versioning_id: bloksVersionId,
555
+ },
556
+ headers: bloksHeaders,
557
+ });
558
+
559
+ // Optional debug dump so you can inspect the exact server response.
560
+ try {
561
+ const fs = require('fs');
562
+ const path = require('path');
563
+ const debugDir = path.join(
564
+ process.cwd(),
565
+ 'authinfo_instagram'
566
+ );
567
+ const debugFile = path.join(
568
+ debugDir,
569
+ 'oauth-token-debug.json'
570
+ );
571
+ try {
572
+ fs.mkdirSync(debugDir, { recursive: true });
573
+ } catch (e) {}
574
+
575
+ const debugPayload = {
576
+ at: new Date().toISOString(),
577
+ statusCode:
578
+ response.statusCode || response.status || null,
579
+ headers: response.headers || null,
580
+ body: response.body || null,
581
+ };
582
+ fs.writeFileSync(
583
+ debugFile,
584
+ JSON.stringify(debugPayload, null, 2),
585
+ 'utf8'
586
+ );
587
+ } catch (e) {
588
+ // Never break login on debug error
589
+ }
590
+
591
+ return response.body;
592
+ } catch (e) {
593
+ // Completely swallow any errors; this step should never break login.
594
+ return null;
595
+ }
596
+ }
597
+
303
598
  async login(credentialsOrUsername, passwordArg) {
304
599
  let username, password;
305
- if (typeof credentialsOrUsername === 'object' && credentialsOrUsername !== null) {
600
+ if (
601
+ typeof credentialsOrUsername === 'object' &&
602
+ credentialsOrUsername !== null
603
+ ) {
306
604
  username = credentialsOrUsername.username;
307
605
  password = credentialsOrUsername.password;
308
- }
309
- else {
606
+ } else {
310
607
  username = credentialsOrUsername;
311
608
  password = passwordArg;
312
609
  }
610
+
313
611
  if (!username || !password) {
314
612
  throw new Error('Username and password are required');
315
613
  }
614
+
316
615
  if (!this.client.state.passwordEncryptionPubKey) {
317
616
  await this.syncLoginExperiments();
318
617
  }
618
+
319
619
  const { encrypted, time } = this.encryptPassword(password);
620
+
320
621
  // optional CSRF warmup (like app does before login)
321
622
  await this.ensureCsrfToken();
623
+
624
+ // Pre-login Bloks OAuth token fetch (mirrors captured request).
625
+ // This helps the flow look closer to the real app, but is best-effort only.
626
+ try {
627
+ await this._prefetchOauthTokenForLogin(username);
628
+ } catch (e) {
629
+ // Ignore any errors from the prefetch step.
630
+ }
631
+
322
632
  return this.requestWithRetry(async () => {
323
- // ====== client_input_params (oglindă după traficul real) ======
633
+ // ====== client_input_params (mirror of real traffic with password) ======
324
634
  const nowSec = Math.floor(Date.now() / 1000);
325
- const aacInitTimestamp = nowSec - Math.floor(Math.random() * 50);
326
- const aacjid = crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4();
327
- const aaccs = crypto.randomBytes(32).toString('base64').replace(/=/g, '');
328
- const androidDeviceId = this.client.state.androidDeviceId ||
635
+ const aacInitTimestamp =
636
+ nowSec - Math.floor(Math.random() * 50);
637
+ const aacjid = crypto.randomUUID
638
+ ? crypto.randomUUID()
639
+ : require('uuid').v4();
640
+ const aaccs = crypto
641
+ .randomBytes(32)
642
+ .toString('base64')
643
+ .replace(/=/g, '');
644
+
645
+ const androidDeviceId =
646
+ this.client.state.androidDeviceId ||
329
647
  this.client.state.deviceId ||
330
648
  `android-${crypto.randomBytes(8).toString('hex')}`;
331
- const machineId = this.client.state.mid ||
649
+ const machineId =
650
+ this.client.state.mid ||
332
651
  this.client.state.machineId ||
333
652
  `aZ${crypto.randomBytes(8).toString('hex')}`;
334
- const familyDeviceId = this.client.state.phoneId ||
653
+ const familyDeviceId =
654
+ this.client.state.phoneId ||
335
655
  this.client.state.familyDeviceId ||
336
- crypto.randomUUID
337
- ? crypto.randomUUID()
338
- : require('uuid').v4();
339
- const qeDeviceId = this.client.state.deviceId ||
656
+ (crypto.randomUUID
657
+ ? crypto.randomUUID()
658
+ : require('uuid').v4());
659
+ const qeDeviceId =
660
+ this.client.state.deviceId ||
340
661
  this.client.state.qeDeviceId ||
341
- crypto.randomUUID
342
- ? crypto.randomUUID()
343
- : require('uuid').v4();
662
+ (crypto.randomUUID
663
+ ? crypto.randomUUID()
664
+ : require('uuid').v4());
665
+
344
666
  const accountsList = this.client.state.accountsList || [];
667
+
345
668
  const flashCallPermissionStatus = {
346
669
  READ_PHONE_STATE: 'GRANTED',
347
670
  READ_CALL_LOG: 'GRANTED',
348
671
  ANSWER_PHONE_CALLS: 'GRANTED',
349
672
  };
673
+
350
674
  const clientInputParams = {
351
675
  aac: JSON.stringify({
352
676
  aac_init_timestamp: aacInitTimestamp,
@@ -380,7 +704,8 @@ class AccountRepository extends Repository {
380
704
  machine_id: machineId,
381
705
  flash_call_permission_status: flashCallPermissionStatus,
382
706
  accounts_list: accountsList,
383
- gms_incoming_call_retriever_eligibility: 'client_not_supported',
707
+ gms_incoming_call_retriever_eligibility:
708
+ 'client_not_supported',
384
709
  family_device_id: familyDeviceId,
385
710
  fb_ig_device_id: [],
386
711
  device_emails: [],
@@ -391,10 +716,16 @@ class AccountRepository extends Repository {
391
716
  openid_tokens: {},
392
717
  contact_point: username,
393
718
  };
394
- // ====== server_params (oglindă după traficul real) ======
395
- const waterfallId = crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4();
719
+
720
+ // ====== server_params (mirror after reverse engineering) ======
721
+ const waterfallId = crypto.randomUUID
722
+ ? crypto.randomUUID()
723
+ : require('uuid').v4();
396
724
  const latencyMarkerId = 36707139;
397
- const latencyInstanceId = Number(`${Date.now()}${Math.floor(Math.random() * 1000)}`);
725
+ const latencyInstanceId = Number(
726
+ `${Date.now()}${Math.floor(Math.random() * 1000)}`
727
+ );
728
+
398
729
  const serverParams = {
399
730
  should_trigger_override_login_2fa_action: 0,
400
731
  is_vanilla_password_page_empty_password: 0,
@@ -408,7 +739,8 @@ class AccountRepository extends Repository {
408
739
  is_platform_login: 0,
409
740
  INTERNAL__latency_qpl_marker_id: latencyMarkerId,
410
741
  is_from_aymh: 0,
411
- offline_experiment_group: 'caa_iteration_v3_perf_ig_4',
742
+ offline_experiment_group:
743
+ 'caa_iteration_v3_perf_ig_4',
412
744
  is_from_landing_page: 0,
413
745
  left_nav_button_action: 'NONE',
414
746
  password_text_input_id: 'z0jejq:194',
@@ -421,7 +753,8 @@ class AccountRepository extends Repository {
421
753
  device_id: androidDeviceId,
422
754
  login_surface: 'switcher',
423
755
  INTERNAL__latency_qpl_instance_id: latencyInstanceId,
424
- reg_flow_source: 'login_home_native_integration_point',
756
+ reg_flow_source:
757
+ 'login_home_native_integration_point',
425
758
  is_caa_perf_enabled: 1,
426
759
  credential_type: 'password',
427
760
  is_from_password_entry_page: 0,
@@ -431,39 +764,59 @@ class AccountRepository extends Repository {
431
764
  access_flow_version: 'pre_mt_behavior',
432
765
  is_from_logged_in_switcher: 1,
433
766
  };
767
+
434
768
  const paramsJson = JSON.stringify({
435
769
  client_input_params: clientInputParams,
436
770
  server_params: serverParams,
437
771
  });
438
- const attestParams = AccountRepository.generateAttestParams(this.client.state);
772
+
773
+ const attestParams =
774
+ AccountRepository.generateAttestParams(
775
+ this.client.state
776
+ );
777
+ const bloksVersionId =
778
+ this.client.state.bloksVersionId ||
779
+ '5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
780
+
439
781
  const bkClientContext = JSON.stringify({
440
- bloks_version: this.client.state.bloksVersionId,
782
+ bloks_version: bloksVersionId,
441
783
  styles_id: 'instagram',
442
784
  });
443
- // ====== HEADERS – modelate după request_i.instagram.com_mlwzj8x1.txt ======
444
- const lang = (this.client.state.language || 'ro_RO');
785
+
786
+ // ====== HEADERS modelled after real traffic ======
787
+ const lang = this.client.state.language || 'ro_RO';
445
788
  const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
446
- const userAgent = this.client.state.userAgent ||
789
+ const userAgent =
790
+ this.client.state.userAgent ||
447
791
  'Instagram 371.0.0.0.23 Android (30/11; 320dpi; 720x1449; Xiaomi/Redmi; M2006C3MNG; angelican; mt6765; ro_RO; 703217507)';
792
+
448
793
  const bloksHeaders = {
449
794
  'accept-language': acceptLanguage,
450
- 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
795
+ 'content-type':
796
+ 'application/x-www-form-urlencoded; charset=UTF-8',
451
797
  'ig-intended-user-id': '0',
452
- 'priority': 'u=3',
798
+ priority: 'u=3',
799
+
453
800
  'x-bloks-is-layout-rtl': 'false',
454
801
  'x-bloks-prism-ax-base-colors-enabled': 'false',
455
802
  'x-bloks-prism-button-version': 'CONTROL',
456
803
  'x-bloks-prism-colors-enabled': 'true',
457
804
  'x-bloks-prism-font-enabled': 'false',
458
805
  'x-bloks-prism-indigo-link-version': '0',
459
- 'x-bloks-version-id': this.client.state.bloksVersionId,
806
+ 'x-bloks-version-id': bloksVersionId,
807
+
460
808
  'x-fb-client-ip': 'True',
461
809
  'x-fb-connection-type': 'WIFI',
462
- 'x-fb-friendly-name': 'IgApi: bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
463
- 'x-fb-network-properties': 'VPN;Validated;LocalAddrs=/10.0.0.2,;',
810
+ 'x-fb-friendly-name':
811
+ 'IgApi: bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
812
+ 'x-fb-network-properties':
813
+ 'VPN;Validated;LocalAddrs=/10.0.0.2,;',
464
814
  'x-fb-request-analytics-tags': JSON.stringify({
465
815
  network_tags: {
466
- product: String(this.client.state.fbAnalyticsApplicationId || '567067343352427'),
816
+ product: String(
817
+ this.client.state.fbAnalyticsApplicationId ||
818
+ '567067343352427'
819
+ ),
467
820
  purpose: 'fetch',
468
821
  surface: 'undefined',
469
822
  request_category: 'api',
@@ -471,78 +824,111 @@ class AccountRepository extends Repository {
471
824
  },
472
825
  }),
473
826
  'x-fb-server-cluster': 'True',
827
+
474
828
  'x-ig-android-id': androidDeviceId,
475
- 'x-ig-app-id': String(this.client.state.fbAnalyticsApplicationId || '567067343352427'),
829
+ 'x-ig-app-id': String(
830
+ this.client.state.fbAnalyticsApplicationId ||
831
+ '567067343352427'
832
+ ),
476
833
  'x-ig-app-locale': lang,
477
834
  'x-ig-attest-params': JSON.stringify(attestParams),
478
- 'x-ig-bandwidth-speed-kbps': (Math.random() * 1500 + 800).toFixed(3),
835
+ 'x-ig-bandwidth-speed-kbps': (
836
+ Math.random() * 1500 +
837
+ 800
838
+ ).toFixed(3),
479
839
  'x-ig-bandwidth-totalbytes-b': '0',
480
840
  'x-ig-bandwidth-totaltime-ms': '0',
481
- 'x-ig-client-endpoint': 'com.bloks.www.caa.login.aymh_single_profile_screen_entry',
841
+ 'x-ig-client-endpoint':
842
+ 'com.bloks.www.caa.login.aymh_single_profile_screen_entry',
482
843
  'x-ig-capabilities': '3brTv10=',
483
844
  'x-ig-connection-type': 'WIFI',
484
845
  'x-ig-device-id': qeDeviceId,
485
846
  'x-ig-device-locale': lang,
486
847
  'x-ig-family-device-id': familyDeviceId,
487
848
  'x-ig-mapped-locale': lang,
488
- 'x-ig-nav-chain': 'LockoutFragment:dogfooding_lockout:1:cold_start:...,' +
489
- 'com.bloks.www.caa.login.aymh_single_profile_screen_entry:com.bloks.www.caa.login.aymh_single_profile_screen_entry:14:button:0:::0',
490
- 'x-ig-timezone-offset': String(typeof this.client.state.timezoneOffset === 'number'
491
- ? this.client.state.timezoneOffset
492
- : 7200),
849
+ 'x-ig-nav-chain':
850
+ 'LockoutFragment:dogfooding_lockout:1:cold_start:...,com.bloks.www.caa.login.aymh_single_profile_screen_entry:com.bloks.www.caa.login.aymh_single_profile_screen_entry:14:button:0:::0',
851
+ 'x-ig-timezone-offset': String(
852
+ typeof this.client.state.timezoneOffset ===
853
+ 'number'
854
+ ? this.client.state.timezoneOffset
855
+ : 7200
856
+ ),
493
857
  'x-ig-www-claim': this.client.state.igWWWClaim || '0',
494
858
  'x-mid': machineId,
495
- 'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(Math.random() * 1000)
859
+ 'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(
860
+ Math.random() * 1000
861
+ )
496
862
  .toString()
497
863
  .padStart(3, '0')}`,
498
- 'x-pigeon-session-id': this.client.state.pigeonSessionId ||
499
- `UFS-${crypto.randomBytes(16).toString('hex')}-1`,
864
+ 'x-pigeon-session-id':
865
+ this.client.state.pigeonSessionId ||
866
+ `UFS-${crypto.randomBytes(16).toString(
867
+ 'hex'
868
+ )}-1`,
500
869
  'x-tigon-is-retry': 'False',
501
- // ...
870
+
502
871
  'accept-encoding': 'gzip, deflate, br',
503
- // ...
504
872
  'user-agent': userAgent,
505
- 'x-fb-conn-uuid-client': crypto.randomBytes(16).toString('hex'),
873
+ 'x-fb-conn-uuid-client':
874
+ crypto.randomBytes(16).toString('hex'),
506
875
  'x-fb-http-engine': 'MNS/TCP',
507
876
  'x-fb-rmd': 'state=URL_ELIGIBLE',
508
877
  };
878
+
509
879
  const response = await this.client.request.send({
510
880
  method: 'POST',
511
881
  url: '/api/v1/bloks/async_action/com.bloks.www.bloks.caa.login.async.send_login_request/',
512
882
  form: {
513
883
  params: paramsJson,
514
884
  bk_client_context: bkClientContext,
515
- bloks_versioning_id: this.client.state.bloksVersionId,
885
+ bloks_versioning_id: bloksVersionId,
516
886
  },
517
887
  headers: bloksHeaders,
518
888
  });
519
- // === DEBUG LOGIN: salvăm răspunsul pentru analiză ===
889
+
890
+ // === DEBUG LOGIN: save response for offline analysis ===
520
891
  try {
521
892
  const fs = require('fs');
522
893
  const path = require('path');
523
- const debugDir = path.join(process.cwd(), 'authinfo_instagram');
524
- const debugFile = path.join(debugDir, 'login-debug.json');
894
+ const debugDir = path.join(
895
+ process.cwd(),
896
+ 'authinfo_instagram'
897
+ );
898
+ const debugFile = path.join(
899
+ debugDir,
900
+ 'login-debug.json'
901
+ );
525
902
  try {
526
903
  fs.mkdirSync(debugDir, { recursive: true });
527
- }
528
- catch (e) { }
904
+ } catch (e) {}
905
+
529
906
  const debugPayload = {
530
907
  at: new Date().toISOString(),
531
- statusCode: response.statusCode || response.status || null,
908
+ statusCode:
909
+ response.statusCode || response.status || null,
532
910
  headers: response.headers || null,
533
911
  body: response.body || null,
534
912
  };
535
- fs.writeFileSync(debugFile, JSON.stringify(debugPayload, null, 2), 'utf8');
913
+ fs.writeFileSync(
914
+ debugFile,
915
+ JSON.stringify(debugPayload, null, 2),
916
+ 'utf8'
917
+ );
918
+ } catch (e) {
919
+ // don't break login on debug failure
536
920
  }
537
- catch (e) {
538
- // nu stricăm login-ul dacă nu merge debug-ul
539
- }
540
- // === SFÂRȘIT DEBUG LOGIN ===
921
+ // === END DEBUG LOGIN ===
922
+
541
923
  const body = response.body;
924
+
542
925
  // Immediately attempt to extract and save authorization token from the response
543
926
  this._extractAndSaveAuthorization(response);
927
+
544
928
  if (body && body.two_factor_required) {
545
- const err = new Error('Two factor authentication required');
929
+ const err = new Error(
930
+ 'Two factor authentication required'
931
+ );
546
932
  err.name = 'IgLoginTwoFactorRequiredError';
547
933
  err.twoFactorInfo = body.two_factor_info;
548
934
  throw err;
@@ -563,28 +949,46 @@ class AccountRepository extends Repository {
563
949
  err.challengeInfo = body.challenge;
564
950
  throw err;
565
951
  }
952
+
566
953
  if (body && body.layout) {
567
- const layoutStr = typeof body.layout === 'string' ? body.layout : JSON.stringify(body.layout);
954
+ const layoutStr =
955
+ typeof body.layout === 'string'
956
+ ? body.layout
957
+ : JSON.stringify(body.layout);
958
+
568
959
  // If layout contains two-factor markers, parse and throw
569
- if (layoutStr.includes('two_factor_required') || layoutStr.includes('"two_factor_info"')) {
960
+ if (
961
+ layoutStr.includes('two_factor_required') ||
962
+ layoutStr.includes('"two_factor_info"')
963
+ ) {
570
964
  let twoFactorInfo = null;
571
965
  try {
572
- const match = layoutStr.match(/"two_factor_info"\s*:\s*(\{[^}]+\})/);
966
+ const match = layoutStr.match(
967
+ /"two_factor_info"\s*:\s*(\{[^}]+\})/
968
+ );
573
969
  if (match)
574
970
  twoFactorInfo = JSON.parse(match[1]);
575
- }
576
- catch (e) { }
577
- const err = new Error('Two factor authentication required');
971
+ } catch (e) {}
972
+ const err = new Error(
973
+ 'Two factor authentication required'
974
+ );
578
975
  err.name = 'IgLoginTwoFactorRequiredError';
579
976
  err.twoFactorInfo = twoFactorInfo;
580
977
  throw err;
581
978
  }
582
- if (layoutStr.includes('bad_password') || layoutStr.includes('incorrect_password')) {
979
+
980
+ if (
981
+ layoutStr.includes('bad_password') ||
982
+ layoutStr.includes('incorrect_password')
983
+ ) {
583
984
  const err = new Error('Bad password');
584
985
  err.name = 'IgLoginBadPasswordError';
585
986
  throw err;
586
987
  }
587
- if (layoutStr.includes('invalid_user') || layoutStr.includes('user_not_found')) {
988
+ if (
989
+ layoutStr.includes('invalid_user') ||
990
+ layoutStr.includes('user_not_found')
991
+ ) {
588
992
  const err = new Error('Invalid user');
589
993
  err.name = 'IgLoginInvalidUserError';
590
994
  throw err;
@@ -595,15 +999,18 @@ class AccountRepository extends Repository {
595
999
  err.challengeInfo = body.challenge || null;
596
1000
  throw err;
597
1001
  }
598
- // Additional attempt to extract token from layout string (redundant but robust)
599
- const tokenFromLayout = this._findBearerInString(layoutStr);
1002
+
1003
+ // Additional attempt to extract token from layout string
1004
+ const tokenFromLayout =
1005
+ this._findBearerInString(layoutStr);
600
1006
  if (tokenFromLayout) {
601
- this.client.state.authorization = tokenFromLayout;
1007
+ this.client.state.authorization =
1008
+ tokenFromLayout;
602
1009
  try {
603
1010
  this.client.state.updateAuthorization();
604
- }
605
- catch (e) { }
1011
+ } catch (e) {}
606
1012
  }
1013
+
607
1014
  // Extract pk (user id) from layout if present
608
1015
  const pkPatterns = [
609
1016
  /\\"pk\\":\s*(\d+)/,
@@ -618,11 +1025,13 @@ class AccountRepository extends Repository {
618
1025
  const pkMatch = layoutStr.match(pattern);
619
1026
  if (pkMatch) {
620
1027
  extractedPk = pkMatch[1];
621
- this.client.state.cookieUserId = extractedPk;
1028
+ this.client.state.cookieUserId =
1029
+ extractedPk;
622
1030
  this.client.state._userId = extractedPk;
623
1031
  break;
624
1032
  }
625
1033
  }
1034
+
626
1035
  // Extract IG-Set-WWW-Claim if present
627
1036
  const wwwClaimPatterns = [
628
1037
  /IG-Set-WWW-Claim[\\"\s:]+([a-f0-9]+)/i,
@@ -633,26 +1042,48 @@ class AccountRepository extends Repository {
633
1042
  for (const pattern of wwwClaimPatterns) {
634
1043
  const wwwClaimMatch = layoutStr.match(pattern);
635
1044
  if (wwwClaimMatch) {
636
- this.client.state.igWWWClaim = wwwClaimMatch[1];
1045
+ this.client.state.igWWWClaim =
1046
+ wwwClaimMatch[1];
637
1047
  break;
638
1048
  }
639
1049
  }
640
- // Extract encryption key id and pub key (already attempted earlier in _extractAndSaveAuthorization)
641
- const encKeyIdMatch = layoutStr.match(/IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i) ||
642
- layoutStr.match(/password.encryption.key.id[\\"\s:]+(\d+)/i);
1050
+
1051
+ // Extract encryption key id and pub key (already attempted earlier)
1052
+ const encKeyIdMatch =
1053
+ layoutStr.match(
1054
+ /IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i
1055
+ ) ||
1056
+ layoutStr.match(
1057
+ /password.encryption.key.id[\\"\s:]+(\d+)/i
1058
+ );
643
1059
  if (encKeyIdMatch) {
644
- this.client.state.passwordEncryptionKeyId = parseInt(encKeyIdMatch[1]);
1060
+ this.client.state.passwordEncryptionKeyId =
1061
+ parseInt(encKeyIdMatch[1]);
645
1062
  }
646
- const encPubKeyMatch = layoutStr.match(/IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i) ||
647
- layoutStr.match(/password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i);
1063
+
1064
+ const encPubKeyMatch =
1065
+ layoutStr.match(
1066
+ /IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
1067
+ ) ||
1068
+ layoutStr.match(
1069
+ /password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
1070
+ );
648
1071
  if (encPubKeyMatch) {
649
- this.client.state.passwordEncryptionPubKey = encPubKeyMatch[1];
1072
+ this.client.state.passwordEncryptionPubKey =
1073
+ encPubKeyMatch[1];
650
1074
  }
651
- const midMatch = layoutStr.match(/ig-set-x-mid[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i) ||
652
- layoutStr.match(/\\"x-mid\\"[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i);
1075
+
1076
+ const midMatch =
1077
+ layoutStr.match(
1078
+ /ig-set-x-mid[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i
1079
+ ) ||
1080
+ layoutStr.match(
1081
+ /\\"x-mid\\"[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i
1082
+ );
653
1083
  if (midMatch) {
654
1084
  this.client.state.mid = midMatch[1];
655
1085
  }
1086
+
656
1087
  // If layout contains a direct logged_in_user JSON, extract and return it
657
1088
  const loginResponsePatterns = [
658
1089
  /\\"logged_in_user\\":\{[^}]*\\"pk\\":(\d+)[^}]*\\"username\\":\\"([^"\\]+)\\"/,
@@ -660,10 +1091,13 @@ class AccountRepository extends Repository {
660
1091
  /\\"logged_in_user\\".*?\\"pk\\":\s*(\d+).*?\\"username\\":\s*\\"([^"\\]+)\\"/s,
661
1092
  ];
662
1093
  for (const pattern of loginResponsePatterns) {
663
- const loginResponseMatch = layoutStr.match(pattern);
1094
+ const loginResponseMatch =
1095
+ layoutStr.match(pattern);
664
1096
  if (loginResponseMatch) {
665
- this.client.state.cookieUserId = loginResponseMatch[1];
666
- this.client.state._userId = loginResponseMatch[1];
1097
+ this.client.state.cookieUserId =
1098
+ loginResponseMatch[1];
1099
+ this.client.state._userId =
1100
+ loginResponseMatch[1];
667
1101
  return {
668
1102
  pk: parseInt(loginResponseMatch[1]),
669
1103
  pk_id: loginResponseMatch[1],
@@ -671,22 +1105,31 @@ class AccountRepository extends Repository {
671
1105
  };
672
1106
  }
673
1107
  }
1108
+
674
1109
  // If we have extracted a pk and we also have authorization, try to fetch current user
675
- if (extractedPk && this.client.state.authorization) {
1110
+ if (
1111
+ extractedPk &&
1112
+ this.client.state.authorization
1113
+ ) {
676
1114
  try {
677
1115
  const userInfo = await this.currentUser();
678
1116
  const user = userInfo.user || userInfo;
679
1117
  if (user && user.pk) {
680
- this.client.state.cookieUserId = String(user.pk);
681
- this.client.state._userId = String(user.pk);
1118
+ this.client.state.cookieUserId =
1119
+ String(user.pk);
1120
+ this.client.state._userId =
1121
+ String(user.pk);
682
1122
  }
683
1123
  return user;
684
- }
685
- catch (e) {
686
- return { pk: parseInt(extractedPk), pk_id: extractedPk };
1124
+ } catch (e) {
1125
+ return {
1126
+ pk: parseInt(extractedPk),
1127
+ pk_id: extractedPk,
1128
+ };
687
1129
  }
688
1130
  }
689
1131
  }
1132
+
690
1133
  // If server returned logged_in_user directly in body
691
1134
  if (body && body.logged_in_user) {
692
1135
  const lu = body.logged_in_user;
@@ -698,26 +1141,34 @@ class AccountRepository extends Repository {
698
1141
  this._extractAndSaveAuthorization(response);
699
1142
  return lu;
700
1143
  }
1144
+
701
1145
  // If we already have authorization, try to fetch current user to populate state
702
1146
  if (this.client.state.authorization) {
703
1147
  try {
704
1148
  const userInfo = await this.currentUser();
705
1149
  const user = userInfo.user || userInfo;
706
1150
  if (user && user.pk) {
707
- this.client.state.cookieUserId = String(user.pk);
1151
+ this.client.state.cookieUserId =
1152
+ String(user.pk);
708
1153
  this.client.state._userId = String(user.pk);
709
1154
  }
710
1155
  return user;
711
- }
712
- catch (e) {
1156
+ } catch (e) {
713
1157
  // fallback: return raw body
714
1158
  return body;
715
1159
  }
716
1160
  }
1161
+
717
1162
  return body;
718
1163
  });
719
1164
  }
720
- async twoFactorLogin(username, verificationCode, twoFactorIdentifier, verificationMethod = '1') {
1165
+
1166
+ async twoFactorLogin(
1167
+ username,
1168
+ verificationCode,
1169
+ twoFactorIdentifier,
1170
+ verificationMethod = '1'
1171
+ ) {
721
1172
  return this.requestWithRetry(async () => {
722
1173
  const response = await this.client.request.send({
723
1174
  method: 'POST',
@@ -738,6 +1189,7 @@ class AccountRepository extends Repository {
738
1189
  return response.body;
739
1190
  });
740
1191
  }
1192
+
741
1193
  async logout() {
742
1194
  return this.requestWithRetry(async () => {
743
1195
  const response = await this.client.request.send({
@@ -750,6 +1202,7 @@ class AccountRepository extends Repository {
750
1202
  return response.body;
751
1203
  });
752
1204
  }
1205
+
753
1206
  async currentUser() {
754
1207
  return this.requestWithRetry(async () => {
755
1208
  const response = await this.client.request.send({
@@ -760,10 +1213,19 @@ class AccountRepository extends Repository {
760
1213
  return response.body;
761
1214
  });
762
1215
  }
1216
+
763
1217
  async accountInfo() {
764
1218
  return this.currentUser();
765
1219
  }
766
- async editProfile({ externalUrl, phoneNumber, username, fullName, biography, email } = {}) {
1220
+
1221
+ async editProfile({
1222
+ externalUrl,
1223
+ phoneNumber,
1224
+ username,
1225
+ fullName,
1226
+ biography,
1227
+ email,
1228
+ } = {}) {
767
1229
  return this.requestWithRetry(async () => {
768
1230
  const current = await this.currentUser();
769
1231
  const user = current.user || current;
@@ -772,26 +1234,48 @@ class AccountRepository extends Repository {
772
1234
  url: '/api/v1/accounts/edit_profile/',
773
1235
  form: this.client.request.sign({
774
1236
  _uuid: this.client.state.uuid,
775
- external_url: externalUrl !== undefined ? externalUrl : (user.external_url || ''),
776
- phone_number: phoneNumber !== undefined ? phoneNumber : (user.phone_number || ''),
777
- username: username !== undefined ? username : user.username,
778
- full_name: fullName !== undefined ? fullName : (user.full_name || ''),
779
- biography: biography !== undefined ? biography : (user.biography || ''),
780
- email: email !== undefined ? email : (user.email || ''),
1237
+ external_url:
1238
+ externalUrl !== undefined
1239
+ ? externalUrl
1240
+ : user.external_url || '',
1241
+ phone_number:
1242
+ phoneNumber !== undefined
1243
+ ? phoneNumber
1244
+ : user.phone_number || '',
1245
+ username:
1246
+ username !== undefined
1247
+ ? username
1248
+ : user.username,
1249
+ full_name:
1250
+ fullName !== undefined
1251
+ ? fullName
1252
+ : user.full_name || '',
1253
+ biography:
1254
+ biography !== undefined
1255
+ ? biography
1256
+ : user.biography || '',
1257
+ email:
1258
+ email !== undefined
1259
+ ? email
1260
+ : user.email || '',
781
1261
  }),
782
1262
  });
783
1263
  return response.body;
784
1264
  });
785
1265
  }
1266
+
786
1267
  async setBiography(biography) {
787
1268
  return this.editProfile({ biography });
788
1269
  }
1270
+
789
1271
  async setExternalUrl(url) {
790
1272
  return this.editProfile({ externalUrl: url });
791
1273
  }
1274
+
792
1275
  async removeBioLinks() {
793
1276
  return this.editProfile({ externalUrl: '' });
794
1277
  }
1278
+
795
1279
  async setGender(gender) {
796
1280
  return this.requestWithRetry(async () => {
797
1281
  const response = await this.client.request.send({
@@ -805,6 +1289,7 @@ class AccountRepository extends Repository {
805
1289
  return response.body;
806
1290
  });
807
1291
  }
1292
+
808
1293
  async setPrivate() {
809
1294
  return this.requestWithRetry(async () => {
810
1295
  const response = await this.client.request.send({
@@ -817,6 +1302,7 @@ class AccountRepository extends Repository {
817
1302
  return response.body;
818
1303
  });
819
1304
  }
1305
+
820
1306
  async setPublic() {
821
1307
  return this.requestWithRetry(async () => {
822
1308
  const response = await this.client.request.send({
@@ -829,6 +1315,7 @@ class AccountRepository extends Repository {
829
1315
  return response.body;
830
1316
  });
831
1317
  }
1318
+
832
1319
  async changePassword(oldPassword, newPassword) {
833
1320
  const oldEnc = this.encryptPassword(oldPassword);
834
1321
  const newEnc = this.encryptPassword(newPassword);
@@ -846,6 +1333,7 @@ class AccountRepository extends Repository {
846
1333
  return response.body;
847
1334
  });
848
1335
  }
1336
+
849
1337
  async sendConfirmEmail() {
850
1338
  return this.requestWithRetry(async () => {
851
1339
  const response = await this.client.request.send({
@@ -859,6 +1347,7 @@ class AccountRepository extends Repository {
859
1347
  return response.body;
860
1348
  });
861
1349
  }
1350
+
862
1351
  async sendConfirmPhoneNumber(phoneNumber) {
863
1352
  return this.requestWithRetry(async () => {
864
1353
  const response = await this.client.request.send({
@@ -872,6 +1361,7 @@ class AccountRepository extends Repository {
872
1361
  return response.body;
873
1362
  });
874
1363
  }
1364
+
875
1365
  async profilePictureChange(uploadId) {
876
1366
  return this.requestWithRetry(async () => {
877
1367
  const response = await this.client.request.send({
@@ -886,6 +1376,7 @@ class AccountRepository extends Repository {
886
1376
  return response.body;
887
1377
  });
888
1378
  }
1379
+
889
1380
  async profilePictureRemove() {
890
1381
  return this.requestWithRetry(async () => {
891
1382
  const response = await this.client.request.send({
@@ -898,6 +1389,7 @@ class AccountRepository extends Repository {
898
1389
  return response.body;
899
1390
  });
900
1391
  }
1392
+
901
1393
  async newsInbox() {
902
1394
  return this.requestWithRetry(async () => {
903
1395
  const response = await this.client.request.send({
@@ -907,6 +1399,7 @@ class AccountRepository extends Repository {
907
1399
  return response.body;
908
1400
  });
909
1401
  }
1402
+
910
1403
  async syncLoginExperiments() {
911
1404
  return this.requestWithRetry(async () => {
912
1405
  const response = await this.client.request.send({
@@ -915,20 +1408,23 @@ class AccountRepository extends Repository {
915
1408
  form: this.client.request.sign({
916
1409
  id: this.client.state.uuid,
917
1410
  server_config_retrieval: '1',
918
- experiments: this.client.state.constants.LOGIN_EXPERIMENTS,
1411
+ experiments:
1412
+ this.client.state.constants
1413
+ .LOGIN_EXPERIMENTS,
919
1414
  }),
920
1415
  });
921
1416
  return response.body;
922
1417
  });
923
1418
  }
1419
+
924
1420
  async syncPostLoginExperiments() {
925
1421
  let userId;
926
1422
  try {
927
1423
  userId = this.client.state.cookieUserId;
928
- }
929
- catch {
1424
+ } catch {
930
1425
  userId = '0';
931
1426
  }
1427
+
932
1428
  return this.requestWithRetry(async () => {
933
1429
  const response = await this.client.request.send({
934
1430
  method: 'POST',
@@ -938,12 +1434,14 @@ class AccountRepository extends Repository {
938
1434
  _uid: userId,
939
1435
  _uuid: this.client.state.uuid,
940
1436
  server_config_retrieval: '1',
941
- experiments: this.client.state.constants.EXPERIMENTS,
1437
+ experiments:
1438
+ this.client.state.constants.EXPERIMENTS,
942
1439
  }),
943
1440
  });
944
1441
  return response.body;
945
1442
  });
946
1443
  }
1444
+
947
1445
  async syncLauncher(preLogin = true) {
948
1446
  const data = {
949
1447
  id: this.client.state.uuid,
@@ -953,9 +1451,9 @@ class AccountRepository extends Repository {
953
1451
  try {
954
1452
  data._uid = this.client.state.cookieUserId;
955
1453
  data._uuid = this.client.state.uuid;
956
- }
957
- catch { }
1454
+ } catch {}
958
1455
  }
1456
+
959
1457
  return this.requestWithRetry(async () => {
960
1458
  const response = await this.client.request.send({
961
1459
  method: 'POST',
@@ -965,6 +1463,7 @@ class AccountRepository extends Repository {
965
1463
  return response.body;
966
1464
  });
967
1465
  }
1466
+
968
1467
  async syncDeviceFeatures() {
969
1468
  return this.requestWithRetry(async () => {
970
1469
  const response = await this.client.request.send({
@@ -978,6 +1477,7 @@ class AccountRepository extends Repository {
978
1477
  return response.body;
979
1478
  });
980
1479
  }
1480
+
981
1481
  async getPrefillCandidates() {
982
1482
  return this.requestWithRetry(async () => {
983
1483
  const response = await this.client.request.send({
@@ -993,6 +1493,7 @@ class AccountRepository extends Repository {
993
1493
  return response.body;
994
1494
  });
995
1495
  }
1496
+
996
1497
  async contactPointPrefill(usage = 'prefill') {
997
1498
  return this.requestWithRetry(async () => {
998
1499
  const response = await this.client.request.send({
@@ -1006,6 +1507,7 @@ class AccountRepository extends Repository {
1006
1507
  return response.body;
1007
1508
  });
1008
1509
  }
1510
+
1009
1511
  async getZrToken(params = {}) {
1010
1512
  return this.requestWithRetry(async () => {
1011
1513
  const response = await this.client.request.send({
@@ -1022,6 +1524,7 @@ class AccountRepository extends Repository {
1022
1524
  return response.body;
1023
1525
  });
1024
1526
  }
1527
+
1025
1528
  async getConsentSignupConfig() {
1026
1529
  return this.requestWithRetry(async () => {
1027
1530
  const response = await this.client.request.send({
@@ -1035,6 +1538,7 @@ class AccountRepository extends Repository {
1035
1538
  return response.body;
1036
1539
  });
1037
1540
  }
1541
+
1038
1542
  async sendRecoveryFlowEmail(query) {
1039
1543
  return this.requestWithRetry(async () => {
1040
1544
  const response = await this.client.request.send({
@@ -1050,6 +1554,7 @@ class AccountRepository extends Repository {
1050
1554
  return response.body;
1051
1555
  });
1052
1556
  }
1557
+
1053
1558
  async sendRecoveryFlowSms(query) {
1054
1559
  return this.requestWithRetry(async () => {
1055
1560
  const response = await this.client.request.send({
@@ -1065,6 +1570,7 @@ class AccountRepository extends Repository {
1065
1570
  return response.body;
1066
1571
  });
1067
1572
  }
1573
+
1068
1574
  static generateAttestParams(state) {
1069
1575
  // Emulate Instagram's keystore attestation as closely as possible:
1070
1576
  // - version: 2
@@ -1077,28 +1583,65 @@ class AccountRepository extends Repository {
1077
1583
  //
1078
1584
  // NOTE: This is *not* a real hardware-backed attestation chain, but it mirrors
1079
1585
  // the structure of the official app very closely so the server sees the same
1080
- // shape: a single attestation object with a 4certificate chain.
1081
- const challengeNonce = crypto.randomBytes(24).toString('base64url');
1082
- const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
1083
- namedCurve: 'prime256v1',
1084
- });
1586
+ // shape: a single attestation object with a 4-certificate chain.
1587
+ const challengeNonce = crypto
1588
+ .randomBytes(24)
1589
+ .toString('base64url');
1590
+ const { privateKey, publicKey } = crypto.generateKeyPairSync(
1591
+ 'ec',
1592
+ {
1593
+ namedCurve: 'prime256v1',
1594
+ }
1595
+ );
1085
1596
  // Sign the challenge nonce with the private key (simulating TEE signing).
1086
- const signedData = crypto.sign(null, Buffer.from(challengeNonce), privateKey);
1597
+ const signedData = crypto.sign(
1598
+ null,
1599
+ Buffer.from(challengeNonce),
1600
+ privateKey
1601
+ );
1087
1602
  const signedNonce = signedData.toString('base64');
1088
- const publicKeyDer = publicKey.export({ type: 'spki', format: 'der' });
1089
- const keyHash = crypto.createHash('sha256').update(publicKeyDer).digest('hex');
1090
- // Build a 4‑certificate chain (leaf + 2 intermediates + root), just like
1091
- // what we saw in the captured traffic from the real Instagram app.
1092
- const leafCertPem = AccountRepository._generateSelfSignedCert(privateKey, publicKey, 'Android Keystore Key');
1093
- const intermediate1Pem = AccountRepository._generateSelfSignedCert(privateKey, publicKey, 'Android Keystore Key Attestation');
1094
- const intermediate2Pem = AccountRepository._generateSelfSignedCert(privateKey, publicKey, 'Android Hardware Keystore');
1095
- const rootCertPem = AccountRepository._generateSelfSignedCert(privateKey, publicKey, 'Android Keystore Root');
1603
+ const publicKeyDer = publicKey.export({
1604
+ type: 'spki',
1605
+ format: 'der',
1606
+ });
1607
+ const keyHash = crypto
1608
+ .createHash('sha256')
1609
+ .update(publicKeyDer)
1610
+ .digest('hex');
1611
+
1612
+ // Build a 4-certificate chain (leaf + 2 intermediates + root)
1613
+ const leafCertPem =
1614
+ AccountRepository._generateSelfSignedCert(
1615
+ privateKey,
1616
+ publicKey,
1617
+ 'Android Keystore Key'
1618
+ );
1619
+ const intermediate1Pem =
1620
+ AccountRepository._generateSelfSignedCert(
1621
+ privateKey,
1622
+ publicKey,
1623
+ 'Android Keystore Key Attestation'
1624
+ );
1625
+ const intermediate2Pem =
1626
+ AccountRepository._generateSelfSignedCert(
1627
+ privateKey,
1628
+ publicKey,
1629
+ 'Android Hardware Keystore'
1630
+ );
1631
+ const rootCertPem =
1632
+ AccountRepository._generateSelfSignedCert(
1633
+ privateKey,
1634
+ publicKey,
1635
+ 'Android Keystore Root'
1636
+ );
1637
+
1096
1638
  const certificateChain = [
1097
1639
  leafCertPem,
1098
1640
  intermediate1Pem,
1099
1641
  intermediate2Pem,
1100
1642
  rootCertPem,
1101
1643
  ].join('\n');
1644
+
1102
1645
  return {
1103
1646
  attestation: [
1104
1647
  {
@@ -1113,12 +1656,20 @@ class AccountRepository extends Repository {
1113
1656
  ],
1114
1657
  };
1115
1658
  }
1659
+
1116
1660
  static _generateSelfSignedCert(privateKey, publicKey, cn) {
1117
- const publicKeyDer = publicKey.export({ type: 'spki', format: 'der' });
1661
+ const publicKeyDer = publicKey.export({
1662
+ type: 'spki',
1663
+ format: 'der',
1664
+ });
1118
1665
  const serialNumber = crypto.randomBytes(8);
1119
1666
  const now = new Date();
1120
- const notBefore = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000);
1121
- const notAfter = new Date(now.getTime() + 10 * 365 * 24 * 60 * 60 * 1000);
1667
+ const notBefore = new Date(
1668
+ now.getTime() - 365 * 24 * 60 * 60 * 1000
1669
+ );
1670
+ const notAfter = new Date(
1671
+ now.getTime() + 10 * 365 * 24 * 60 * 60 * 1000
1672
+ );
1122
1673
  const cnBytes = Buffer.from(cn, 'utf8');
1123
1674
  const cnSeq = Buffer.concat([
1124
1675
  Buffer.from([0x30, cnBytes.length + 13]),
@@ -1128,39 +1679,75 @@ class AccountRepository extends Repository {
1128
1679
  Buffer.from([0x0c, cnBytes.length]),
1129
1680
  cnBytes,
1130
1681
  ]);
1682
+
1131
1683
  function encodeTime(date) {
1132
- const str = date.toISOString().replace(/[-:T]/g, '').slice(2, 14) + 'Z';
1133
- return Buffer.concat([Buffer.from([0x17, str.length]), Buffer.from(str)]);
1684
+ const str =
1685
+ date
1686
+ .toISOString()
1687
+ .replace(/[-:T]/g, '')
1688
+ .slice(2, 14) + 'Z';
1689
+ return Buffer.concat([
1690
+ Buffer.from([0x17, str.length]),
1691
+ Buffer.from(str),
1692
+ ]);
1134
1693
  }
1694
+
1135
1695
  const validityBuf = Buffer.concat([
1136
1696
  encodeTime(notBefore),
1137
1697
  encodeTime(notAfter),
1138
1698
  ]);
1139
- const validity = Buffer.concat([Buffer.from([0x30, validityBuf.length]), validityBuf]);
1699
+ const validity = Buffer.concat([
1700
+ Buffer.from([0x30, validityBuf.length]),
1701
+ validityBuf,
1702
+ ]);
1703
+
1140
1704
  const tbs = Buffer.concat([
1141
1705
  Buffer.from([0xa0, 0x03, 0x02, 0x01, 0x02]),
1142
- Buffer.from([0x02, serialNumber.length]), serialNumber,
1143
- Buffer.from([0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]),
1706
+ Buffer.from([0x02, serialNumber.length]),
1707
+ serialNumber,
1708
+ Buffer.from([
1709
+ 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
1710
+ 0x04, 0x03, 0x02,
1711
+ ]),
1144
1712
  cnSeq,
1145
1713
  validity,
1146
1714
  cnSeq,
1147
1715
  publicKeyDer,
1148
1716
  ]);
1149
- const tbsSeq = Buffer.concat([Buffer.from([0x30, 0x82]), Buffer.alloc(2), tbs]);
1717
+
1718
+ const tbsSeq = Buffer.concat([
1719
+ Buffer.from([0x30, 0x82]),
1720
+ Buffer.alloc(2),
1721
+ tbs,
1722
+ ]);
1150
1723
  tbsSeq.writeUInt16BE(tbs.length, 2);
1724
+
1151
1725
  const signature = crypto.sign(null, tbsSeq, privateKey);
1152
1726
  const sigBitString = Buffer.concat([
1153
1727
  Buffer.from([0x03, signature.length + 1, 0x00]),
1154
1728
  signature,
1155
1729
  ]);
1156
- const algId = Buffer.from([0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]);
1730
+ const algId = Buffer.from([
1731
+ 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
1732
+ 0x04, 0x03, 0x02,
1733
+ ]);
1157
1734
  const certBody = Buffer.concat([tbsSeq, algId, sigBitString]);
1158
- const cert = Buffer.concat([Buffer.from([0x30, 0x82]), Buffer.alloc(2), certBody]);
1735
+ const cert = Buffer.concat([
1736
+ Buffer.from([0x30, 0x82]),
1737
+ Buffer.alloc(2),
1738
+ certBody,
1739
+ ]);
1159
1740
  cert.writeUInt16BE(certBody.length, 2);
1741
+
1160
1742
  const b64 = cert.toString('base64');
1161
1743
  const lines = b64.match(/.{1,76}/g) || [b64];
1162
- return '-----BEGIN CERTIFICATE-----\n' + lines.join('\n') + '\n-----END CERTIFICATE-----';
1744
+ return (
1745
+ '-----BEGIN CERTIFICATE-----\n' +
1746
+ lines.join('\n') +
1747
+ '\n-----END CERTIFICATE-----'
1748
+ );
1163
1749
  }
1750
+
1164
1751
  static createJazoest(input) {
1165
1752
  const buf = Buffer.from(input || '', 'ascii');
1166
1753
  let sum = 0;
@@ -1169,45 +1756,70 @@ class AccountRepository extends Repository {
1169
1756
  }
1170
1757
  return `2${sum}`;
1171
1758
  }
1759
+
1172
1760
  encryptPassword(password) {
1173
1761
  if (!this.client.state.passwordEncryptionPubKey) {
1174
- return { time: Math.floor(Date.now() / 1000).toString(), encrypted: password };
1762
+ return {
1763
+ time: Math.floor(Date.now() / 1000).toString(),
1764
+ encrypted: password,
1765
+ };
1175
1766
  }
1176
1767
  const randKey = crypto.randomBytes(32);
1177
1768
  const iv = crypto.randomBytes(12);
1178
- const rsaEncrypted = crypto.publicEncrypt({
1179
- key: Buffer.from(this.client.state.passwordEncryptionPubKey, 'base64').toString(),
1180
- padding: crypto.constants.RSA_PKCS1_PADDING,
1181
- }, randKey);
1182
- const cipher = crypto.createCipheriv('aes-256-gcm', randKey, iv);
1769
+ const rsaEncrypted = crypto.publicEncrypt(
1770
+ {
1771
+ key: Buffer.from(
1772
+ this.client.state.passwordEncryptionPubKey,
1773
+ 'base64'
1774
+ ).toString(),
1775
+ padding: crypto.constants.RSA_PKCS1_PADDING,
1776
+ },
1777
+ randKey
1778
+ );
1779
+ const cipher = crypto.createCipheriv(
1780
+ 'aes-256-gcm',
1781
+ randKey,
1782
+ iv
1783
+ );
1183
1784
  const time = Math.floor(Date.now() / 1000).toString();
1184
1785
  cipher.setAAD(Buffer.from(time));
1185
- const aesEncrypted = Buffer.concat([cipher.update(password, 'utf8'), cipher.final()]);
1786
+ const aesEncrypted = Buffer.concat([
1787
+ cipher.update(password, 'utf8'),
1788
+ cipher.final(),
1789
+ ]);
1186
1790
  const sizeBuffer = Buffer.alloc(2, 0);
1187
1791
  sizeBuffer.writeInt16LE(rsaEncrypted.byteLength, 0);
1188
1792
  const authTag = cipher.getAuthTag();
1189
1793
  return {
1190
1794
  time,
1191
1795
  encrypted: Buffer.concat([
1192
- Buffer.from([1, this.client.state.passwordEncryptionKeyId || 0]),
1796
+ Buffer.from([
1797
+ 1,
1798
+ this.client.state.passwordEncryptionKeyId || 0,
1799
+ ]),
1193
1800
  iv,
1194
1801
  sizeBuffer,
1195
1802
  rsaEncrypted,
1196
1803
  authTag,
1197
- aesEncrypted
1198
- ]).toString('base64')
1804
+ aesEncrypted,
1805
+ ]).toString('base64'),
1199
1806
  };
1200
1807
  }
1808
+
1201
1809
  async passwordPublicKeys() {
1202
1810
  const response = await this.client.request.send({
1203
1811
  method: 'GET',
1204
1812
  url: '/api/v1/qe/sync/',
1205
1813
  });
1206
1814
  const headers = response.headers || {};
1207
- const keyId = parseInt(headers['ig-set-password-encryption-key-id'] || '0');
1208
- const pubKey = headers['ig-set-password-encryption-pub-key'] || '';
1815
+ const keyId = parseInt(
1816
+ headers['ig-set-password-encryption-key-id'] || '0'
1817
+ );
1818
+ const pubKey =
1819
+ headers['ig-set-password-encryption-pub-key'] || '';
1209
1820
  return { keyId, pubKey };
1210
1821
  }
1822
+
1211
1823
  async setPresenceDisabled(disabled = true) {
1212
1824
  return this.requestWithRetry(async () => {
1213
1825
  const response = await this.client.request.send({
@@ -1221,6 +1833,7 @@ class AccountRepository extends Repository {
1221
1833
  return response.body;
1222
1834
  });
1223
1835
  }
1836
+
1224
1837
  async getCommentFilter() {
1225
1838
  return this.requestWithRetry(async () => {
1226
1839
  const response = await this.client.request.send({
@@ -1230,6 +1843,7 @@ class AccountRepository extends Repository {
1230
1843
  return response.body;
1231
1844
  });
1232
1845
  }
1846
+
1233
1847
  async setCommentFilter(configValue = 0) {
1234
1848
  return this.requestWithRetry(async () => {
1235
1849
  const response = await this.client.request.send({
@@ -1243,6 +1857,7 @@ class AccountRepository extends Repository {
1243
1857
  return response.body;
1244
1858
  });
1245
1859
  }
1860
+
1246
1861
  async pushPreferences(preferences = 'default') {
1247
1862
  return this.requestWithRetry(async () => {
1248
1863
  const response = await this.client.request.send({
@@ -1262,4 +1877,5 @@ class AccountRepository extends Repository {
1262
1877
  });
1263
1878
  }
1264
1879
  }
1880
+
1265
1881
  module.exports = AccountRepository;