nodejs-insta-private-api-mqt 1.3.87 → 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.
@@ -45,7 +45,7 @@ class AccountRepository extends Repository {
45
45
  }
46
46
  }
47
47
 
48
- // Simpler: look for any header that contains ig-set-authorization
48
+ // 1) Try straight headers for IG-Set-Authorization
49
49
  for (const key of Object.keys(headers)) {
50
50
  const val = headers[key];
51
51
  if (!val) continue;
@@ -60,7 +60,6 @@ class AccountRepository extends Repository {
60
60
  return token;
61
61
  }
62
62
  }
63
- // Some servers include the header value inside a JSON-like string in other headers
64
63
  if (typeof val === 'string' && val.includes('Bearer IGT:2:')) {
65
64
  const token = this._findBearerInString(val);
66
65
  if (token) {
@@ -120,10 +119,9 @@ class AccountRepository extends Repository {
120
119
  }
121
120
  }
122
121
 
123
- // 2) Check response.body for common fields
122
+ // 2) Check response.body for common fields / embedded layout/login_response
124
123
  const body = response.body;
125
124
  if (body) {
126
- // If body has headers string (some responses embed headers JSON as a string)
127
125
  if (body.headers && typeof body.headers === 'string') {
128
126
  const token = this._findBearerInString(body.headers);
129
127
  if (token) {
@@ -135,22 +133,20 @@ class AccountRepository extends Repository {
135
133
  }
136
134
  }
137
135
 
138
- // If body contains a layout object or string, stringify and search it
139
136
  try {
140
137
  let layoutStr = null;
141
138
  if (body.layout) {
142
- layoutStr =
143
- typeof body.layout === 'string'
144
- ? body.layout
145
- : JSON.stringify(body.layout);
139
+ layoutStr = typeof body.layout === 'string'
140
+ ? body.layout
141
+ : JSON.stringify(body.layout);
146
142
  } else if (body?.layout?.bloks_payload) {
147
143
  layoutStr = JSON.stringify(body.layout.bloks_payload);
148
144
  } else if (body?.bloks_payload) {
149
145
  layoutStr = JSON.stringify(body.bloks_payload);
150
- }
151
- if (!layoutStr && typeof body === 'string') {
146
+ } else if (typeof body === 'string') {
152
147
  layoutStr = body;
153
148
  }
149
+
154
150
  if (layoutStr) {
155
151
  const token = this._findBearerInString(layoutStr);
156
152
  if (token) {
@@ -161,12 +157,11 @@ class AccountRepository extends Repository {
161
157
  return token;
162
158
  }
163
159
 
164
- // Some responses embed a login_response JSON string inside layout; try to extract it
160
+ // Embedded login_response JSON string
165
161
  const loginResponseMatch = layoutStr.match(
166
162
  /"login_response"\s*:\s*"(\\?{.*?\\?})"/s
167
163
  );
168
164
  if (loginResponseMatch) {
169
- // Unescape JSON string
170
165
  let jsonStr = loginResponseMatch[1];
171
166
  try {
172
167
  jsonStr = jsonStr
@@ -174,23 +169,19 @@ class AccountRepository extends Repository {
174
169
  .replace(/\\n/g, '');
175
170
  const parsed = JSON.parse(jsonStr);
176
171
  if (parsed && parsed.headers) {
177
- const token = this._findBearerInString(
178
- parsed.headers
179
- );
180
- if (token) {
181
- this.client.state.authorization = token;
172
+ const t = this._findBearerInString(parsed.headers);
173
+ if (t) {
174
+ this.client.state.authorization = t;
182
175
  try {
183
176
  this.client.state.updateAuthorization();
184
177
  } catch (e) {}
185
- return token;
178
+ return t;
186
179
  }
187
180
  }
188
- } catch (e) {
189
- // ignore parse errors
190
- }
181
+ } catch (e) {}
191
182
  }
192
183
 
193
- // Also try to find IG-Set-Password-Encryption headers (not token but useful)
184
+ // Encryption key id + pub key in layout
194
185
  const encKeyIdMatch =
195
186
  layoutStr.match(
196
187
  /IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i
@@ -202,6 +193,7 @@ class AccountRepository extends Repository {
202
193
  this.client.state.passwordEncryptionKeyId =
203
194
  parseInt(encKeyIdMatch[1]);
204
195
  }
196
+
205
197
  const encPubKeyMatch =
206
198
  layoutStr.match(
207
199
  /IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
@@ -214,15 +206,13 @@ class AccountRepository extends Repository {
214
206
  encPubKeyMatch[1];
215
207
  }
216
208
 
217
- // Extract user/account identifiers from layout string
209
+ // User / pk id
218
210
  const dsIdMatch =
219
211
  layoutStr.match(
220
212
  /ig-set-ig-u-ds-user-id[\\"\s:]+(\d+)/i
221
213
  ) ||
222
214
  layoutStr.match(/ig-u-ds-user-id[\\"\s:]+(\d+)/i) ||
223
- layoutStr.match(
224
- /"strong_id__"\s*:\s*"(\d+)"/i
225
- ) ||
215
+ layoutStr.match(/"strong_id__"\s*:\s*"(\d+)"/i) ||
226
216
  layoutStr.match(/"pk_id"\s*:\s*"(\d+)"/i) ||
227
217
  layoutStr.match(/"pk"\s*:\s*(\d+)/i);
228
218
  if (dsIdMatch) {
@@ -254,11 +244,8 @@ class AccountRepository extends Repository {
254
244
  this.client.state.mid = midMatch[1];
255
245
  }
256
246
  }
257
- } catch (e) {
258
- // ignore
259
- }
247
+ } catch (e) {}
260
248
 
261
- // 3) If body.logged_in_user exists and we have authorization already, return it
262
249
  if (body.logged_in_user && this.client.state.authorization) {
263
250
  return this.client.state.authorization;
264
251
  }
@@ -267,15 +254,12 @@ class AccountRepository extends Repository {
267
254
  return null;
268
255
  }
269
256
 
270
- // Normalize token string: accept either raw token or "Bearer IGT:2:..." and return trimmed token string
271
257
  _normalizeTokenString(val) {
272
258
  if (!val || typeof val !== 'string') return null;
273
259
 
274
- // If header already contains "Bearer IGT:2:..."
275
260
  const bearer = this._findBearerInString(val);
276
261
  if (bearer) return bearer;
277
262
 
278
- // If header contains JSON with IG-Set-Authorization field
279
263
  try {
280
264
  const maybeJson = JSON.parse(val);
281
265
  if (maybeJson['IG-Set-Authorization']) {
@@ -283,19 +267,14 @@ class AccountRepository extends Repository {
283
267
  maybeJson['IG-Set-Authorization']
284
268
  );
285
269
  }
286
- } catch (e) {
287
- // not JSON
288
- }
270
+ } catch (e) {}
289
271
 
290
- // fallback: return trimmed value
291
272
  return val.trim();
292
273
  }
293
274
 
294
- // Find bearer token in arbitrary string using multiple patterns
295
275
  _findBearerInString(str) {
296
276
  if (!str || typeof str !== 'string') return null;
297
277
 
298
- // Patterns to match the Bearer token in many possible encodings/escapes
299
278
  const bearerPatterns = [
300
279
  /Bearer IGT:2:[A-Za-z0-9+\/=]+/,
301
280
  /Bearer\s+IGT:2:[A-Za-z0-9+\/=]+/,
@@ -309,14 +288,12 @@ class AccountRepository extends Repository {
309
288
  for (const pattern of bearerPatterns) {
310
289
  const m = str.match(pattern);
311
290
  if (m) {
312
- // If capturing group present, prefer it
313
291
  if (m[1]) {
314
292
  const token = m[1].includes('Bearer IGT:2:')
315
293
  ? m[1]
316
294
  : `Bearer IGT:2:${m[1]}`;
317
295
  return token.replace(/\?"/g, '').trim();
318
296
  }
319
- // Otherwise m[0] contains the full token
320
297
  return m[0].replace(/\?"/g, '').trim();
321
298
  }
322
299
  }
@@ -324,8 +301,35 @@ class AccountRepository extends Repository {
324
301
  return null;
325
302
  }
326
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
+
327
332
  // Ensure we have a valid csrftoken cookie (and mid) before sensitive endpoints.
328
- // Instagram typically sets csrftoken via /api/v1/si/fetch_headers/.
329
333
  async ensureCsrfToken() {
330
334
  const cookieToken = this.client.state.cookieCsrfToken;
331
335
  if (
@@ -339,15 +343,12 @@ class AccountRepository extends Repository {
339
343
  return cookieToken;
340
344
  }
341
345
 
342
- // Warmup endpoint that returns/set cookies (csrftoken, mid, etc.)
343
346
  try {
344
347
  await this.client.request.send({
345
348
  method: 'GET',
346
349
  url: `/api/v1/si/fetch_headers/?challenge_type=signup&guid=${this.client.state.uuid}`,
347
350
  });
348
- } catch (e) {
349
- // ignore warmup failures
350
- }
351
+ } catch (e) {}
351
352
 
352
353
  const token = this.client.state.cookieCsrfToken;
353
354
  if (
@@ -365,9 +366,8 @@ class AccountRepository extends Repository {
365
366
  }
366
367
 
367
368
  /**
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.
369
+ * Internal: pre-login Bloks OAuth token fetch, imită exact apelul oficial
370
+ * com.bloks.www.caa.login.oauth.token.fetch.async
371
371
  */
372
372
  async _prefetchOauthTokenForLogin(username) {
373
373
  if (!username) return null;
@@ -375,7 +375,6 @@ class AccountRepository extends Repository {
375
375
  try {
376
376
  const nowSec = Math.floor(Date.now() / 1000);
377
377
 
378
- // Device identifiers – keep them consistent with the main login flow
379
378
  const androidDeviceId =
380
379
  this.client.state.androidDeviceId ||
381
380
  this.client.state.deviceId ||
@@ -404,7 +403,6 @@ class AccountRepository extends Repository {
404
403
  `${Date.now()}${Math.floor(Math.random() * 1000)}`
405
404
  );
406
405
 
407
- // AAC object as seen in real traffic: timestamp + UUID + random token
408
406
  const aacInitTimestamp =
409
407
  nowSec - Math.floor(Math.random() * 50);
410
408
  const aacjid = crypto.randomUUID
@@ -460,9 +458,7 @@ class AccountRepository extends Repository {
460
458
 
461
459
  const lang = this.client.state.language || 'ro_RO';
462
460
  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)';
461
+ const userAgent = this._resolveUserAgent();
466
462
 
467
463
  const timezoneOffset =
468
464
  typeof this.client.state.timezoneOffset === 'number'
@@ -474,7 +470,6 @@ class AccountRepository extends Repository {
474
470
  'content-type':
475
471
  'application/x-www-form-urlencoded; charset=UTF-8',
476
472
  'ig-intended-user-id': '0',
477
- // Critical: matches the captured request header
478
473
  priority: 'u=3',
479
474
 
480
475
  'x-bloks-is-layout-rtl': 'false',
@@ -556,7 +551,6 @@ class AccountRepository extends Repository {
556
551
  headers: bloksHeaders,
557
552
  });
558
553
 
559
- // Optional debug dump so you can inspect the exact server response.
560
554
  try {
561
555
  const fs = require('fs');
562
556
  const path = require('path');
@@ -584,13 +578,10 @@ class AccountRepository extends Repository {
584
578
  JSON.stringify(debugPayload, null, 2),
585
579
  'utf8'
586
580
  );
587
- } catch (e) {
588
- // Never break login on debug error
589
- }
581
+ } catch (e) {}
590
582
 
591
583
  return response.body;
592
584
  } catch (e) {
593
- // Completely swallow any errors; this step should never break login.
594
585
  return null;
595
586
  }
596
587
  }
@@ -618,19 +609,13 @@ class AccountRepository extends Repository {
618
609
 
619
610
  const { encrypted, time } = this.encryptPassword(password);
620
611
 
621
- // optional CSRF warmup (like app does before login)
622
612
  await this.ensureCsrfToken();
623
613
 
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
614
  try {
627
615
  await this._prefetchOauthTokenForLogin(username);
628
- } catch (e) {
629
- // Ignore any errors from the prefetch step.
630
- }
616
+ } catch (e) {}
631
617
 
632
618
  return this.requestWithRetry(async () => {
633
- // ====== client_input_params (mirror of real traffic with password) ======
634
619
  const nowSec = Math.floor(Date.now() / 1000);
635
620
  const aacInitTimestamp =
636
621
  nowSec - Math.floor(Math.random() * 50);
@@ -717,7 +702,6 @@ class AccountRepository extends Repository {
717
702
  contact_point: username,
718
703
  };
719
704
 
720
- // ====== server_params (mirror after reverse engineering) ======
721
705
  const waterfallId = crypto.randomUUID
722
706
  ? crypto.randomUUID()
723
707
  : require('uuid').v4();
@@ -783,12 +767,9 @@ class AccountRepository extends Repository {
783
767
  styles_id: 'instagram',
784
768
  });
785
769
 
786
- // ====== HEADERS – modelled after real traffic ======
787
770
  const lang = this.client.state.language || 'ro_RO';
788
771
  const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
789
- const userAgent =
790
- this.client.state.userAgent ||
791
- 'Instagram 371.0.0.0.23 Android (30/11; 320dpi; 720x1449; Xiaomi/Redmi; M2006C3MNG; angelican; mt6765; ro_RO; 703217507)';
772
+ const userAgent = this._resolveUserAgent();
792
773
 
793
774
  const bloksHeaders = {
794
775
  'accept-language': acceptLanguage,
@@ -887,7 +868,7 @@ class AccountRepository extends Repository {
887
868
  headers: bloksHeaders,
888
869
  });
889
870
 
890
- // === DEBUG LOGIN: save response for offline analysis ===
871
+ // DEBUG login response
891
872
  try {
892
873
  const fs = require('fs');
893
874
  const path = require('path');
@@ -915,14 +896,10 @@ class AccountRepository extends Repository {
915
896
  JSON.stringify(debugPayload, null, 2),
916
897
  'utf8'
917
898
  );
918
- } catch (e) {
919
- // don't break login on debug failure
920
- }
921
- // === END DEBUG LOGIN ===
899
+ } catch (e) {}
922
900
 
923
901
  const body = response.body;
924
902
 
925
- // Immediately attempt to extract and save authorization token from the response
926
903
  this._extractAndSaveAuthorization(response);
927
904
 
928
905
  if (body && body.two_factor_required) {
@@ -956,7 +933,6 @@ class AccountRepository extends Repository {
956
933
  ? body.layout
957
934
  : JSON.stringify(body.layout);
958
935
 
959
- // If layout contains two-factor markers, parse and throw
960
936
  if (
961
937
  layoutStr.includes('two_factor_required') ||
962
938
  layoutStr.includes('"two_factor_info"')
@@ -1000,7 +976,6 @@ class AccountRepository extends Repository {
1000
976
  throw err;
1001
977
  }
1002
978
 
1003
- // Additional attempt to extract token from layout string
1004
979
  const tokenFromLayout =
1005
980
  this._findBearerInString(layoutStr);
1006
981
  if (tokenFromLayout) {
@@ -1011,7 +986,6 @@ class AccountRepository extends Repository {
1011
986
  } catch (e) {}
1012
987
  }
1013
988
 
1014
- // Extract pk (user id) from layout if present
1015
989
  const pkPatterns = [
1016
990
  /\\"pk\\":\s*(\d+)/,
1017
991
  /"pk":\s*(\d+)/,
@@ -1032,7 +1006,6 @@ class AccountRepository extends Repository {
1032
1006
  }
1033
1007
  }
1034
1008
 
1035
- // Extract IG-Set-WWW-Claim if present
1036
1009
  const wwwClaimPatterns = [
1037
1010
  /IG-Set-WWW-Claim[\\"\s:]+([a-f0-9]+)/i,
1038
1011
  /ig-set-www-claim[\\"\s:]+([a-f0-9]+)/i,
@@ -1048,43 +1021,41 @@ class AccountRepository extends Repository {
1048
1021
  }
1049
1022
  }
1050
1023
 
1051
- // Extract encryption key id and pub key (already attempted earlier)
1052
- const encKeyIdMatch =
1024
+ const encKeyIdMatch2 =
1053
1025
  layoutStr.match(
1054
1026
  /IG-Set-Password-Encryption-Key-Id[\\"\s:]+(\d+)/i
1055
1027
  ) ||
1056
1028
  layoutStr.match(
1057
1029
  /password.encryption.key.id[\\"\s:]+(\d+)/i
1058
1030
  );
1059
- if (encKeyIdMatch) {
1031
+ if (encKeyIdMatch2) {
1060
1032
  this.client.state.passwordEncryptionKeyId =
1061
- parseInt(encKeyIdMatch[1]);
1033
+ parseInt(encKeyIdMatch2[1]);
1062
1034
  }
1063
1035
 
1064
- const encPubKeyMatch =
1036
+ const encPubKeyMatch2 =
1065
1037
  layoutStr.match(
1066
1038
  /IG-Set-Password-Encryption-Pub-Key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
1067
1039
  ) ||
1068
1040
  layoutStr.match(
1069
1041
  /password.encryption.pub.key[\\"\s:]+([A-Za-z0-9+\/=]+)/i
1070
1042
  );
1071
- if (encPubKeyMatch) {
1043
+ if (encPubKeyMatch2) {
1072
1044
  this.client.state.passwordEncryptionPubKey =
1073
- encPubKeyMatch[1];
1045
+ encPubKeyMatch2[1];
1074
1046
  }
1075
1047
 
1076
- const midMatch =
1048
+ const midMatch2 =
1077
1049
  layoutStr.match(
1078
1050
  /ig-set-x-mid[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i
1079
1051
  ) ||
1080
1052
  layoutStr.match(
1081
1053
  /\\"x-mid\\"[\\"\s:]+([A-Za-z0-9+\/=_-]+)/i
1082
1054
  );
1083
- if (midMatch) {
1084
- this.client.state.mid = midMatch[1];
1055
+ if (midMatch2) {
1056
+ this.client.state.mid = midMatch2[1];
1085
1057
  }
1086
1058
 
1087
- // If layout contains a direct logged_in_user JSON, extract and return it
1088
1059
  const loginResponsePatterns = [
1089
1060
  /\\"logged_in_user\\":\{[^}]*\\"pk\\":(\d+)[^}]*\\"username\\":\\"([^"\\]+)\\"/,
1090
1061
  /"logged_in_user":\{[^}]*"pk":(\d+)[^}]*"username":"([^"]+)"/,
@@ -1106,7 +1077,6 @@ class AccountRepository extends Repository {
1106
1077
  }
1107
1078
  }
1108
1079
 
1109
- // If we have extracted a pk and we also have authorization, try to fetch current user
1110
1080
  if (
1111
1081
  extractedPk &&
1112
1082
  this.client.state.authorization
@@ -1130,19 +1100,16 @@ class AccountRepository extends Repository {
1130
1100
  }
1131
1101
  }
1132
1102
 
1133
- // If server returned logged_in_user directly in body
1134
1103
  if (body && body.logged_in_user) {
1135
1104
  const lu = body.logged_in_user;
1136
1105
  if (lu.pk) {
1137
1106
  this.client.state.cookieUserId = String(lu.pk);
1138
1107
  this.client.state._userId = String(lu.pk);
1139
1108
  }
1140
- // Ensure authorization is saved if present in response headers
1141
1109
  this._extractAndSaveAuthorization(response);
1142
1110
  return lu;
1143
1111
  }
1144
1112
 
1145
- // If we already have authorization, try to fetch current user to populate state
1146
1113
  if (this.client.state.authorization) {
1147
1114
  try {
1148
1115
  const userInfo = await this.currentUser();
@@ -1154,7 +1121,6 @@ class AccountRepository extends Repository {
1154
1121
  }
1155
1122
  return user;
1156
1123
  } catch (e) {
1157
- // fallback: return raw body
1158
1124
  return body;
1159
1125
  }
1160
1126
  }
@@ -1184,7 +1150,6 @@ class AccountRepository extends Repository {
1184
1150
  phone_id: this.client.state.phoneId,
1185
1151
  }),
1186
1152
  });
1187
- // Save authorization if present in two-factor response
1188
1153
  this._extractAndSaveAuthorization(response);
1189
1154
  return response.body;
1190
1155
  });
@@ -1572,18 +1537,6 @@ class AccountRepository extends Repository {
1572
1537
  }
1573
1538
 
1574
1539
  static generateAttestParams(state) {
1575
- // Emulate Instagram's keystore attestation as closely as possible:
1576
- // - version: 2
1577
- // - type: "keystore"
1578
- // - errors: [0]
1579
- // - challenge_nonce: random base64url
1580
- // - signed_nonce: ECDSA signature over the nonce
1581
- // - key_hash: sha256(spki(publicKey))
1582
- // - certificate_chain: 4 PEM certificates concatenated (leaf + 2 intermediates + root)
1583
- //
1584
- // NOTE: This is *not* a real hardware-backed attestation chain, but it mirrors
1585
- // the structure of the official app very closely so the server sees the same
1586
- // shape: a single attestation object with a 4-certificate chain.
1587
1540
  const challengeNonce = crypto
1588
1541
  .randomBytes(24)
1589
1542
  .toString('base64url');
@@ -1593,7 +1546,6 @@ class AccountRepository extends Repository {
1593
1546
  namedCurve: 'prime256v1',
1594
1547
  }
1595
1548
  );
1596
- // Sign the challenge nonce with the private key (simulating TEE signing).
1597
1549
  const signedData = crypto.sign(
1598
1550
  null,
1599
1551
  Buffer.from(challengeNonce),
@@ -1609,7 +1561,6 @@ class AccountRepository extends Repository {
1609
1561
  .update(publicKeyDer)
1610
1562
  .digest('hex');
1611
1563
 
1612
- // Build a 4-certificate chain (leaf + 2 intermediates + root)
1613
1564
  const leafCertPem =
1614
1565
  AccountRepository._generateSelfSignedCert(
1615
1566
  privateKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-insta-private-api-mqt",
3
- "version": "1.3.87",
3
+ "version": "1.3.88",
4
4
  "description": "Complete Instagram MQTT protocol with full-featured REALTIME and REST API — all in one project.",
5
5
 
6
6
  "main": "dist/dist/index.js",