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

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