nodejs-insta-private-api-mqt 1.4.5 → 1.4.7

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.
@@ -13,7 +13,7 @@ const LOGIN_EXPERIMENTS = 'ig_android_fci_onboarding_friend_search,ig_android_de
13
13
  const FACEBOOK_ANALYTICS_APPLICATION_ID = '567067343352427';
14
14
  const FACEBOOK_OTA_FIELDS = 'update%7Bdownload_uri%2Cdownload_uri_delta_base%2Cversion_code_delta_base%2Cdownload_uri_delta%2Cfallback_to_full_update%2Cfile_size_delta%2Cversion_code%2Cpublished_date%2Cfile_size%2Cota_bundle_type%2Cresources_checksum%2Callowed_networks%2Crelease_id%7D';
15
15
  const FACEBOOK_ORCA_APPLICATION_ID = '124024574287414';
16
- const BLOKS_VERSION_ID = 'ce555e5500576acd8e84a66018f54a05720f2dce29f0bb5a1f97f0c10d6fac48';
16
+ const BLOKS_VERSION_ID = '5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
17
17
  const SUPPORTED_CAPABILITIES = [
18
18
  {
19
19
  value: '119.0,120.0,121.0,122.0,123.0,124.0,125.0,126.0,127.0,128.0,129.0,130.0,131.0,132.0,133.0,134.0,135.0,136.0,137.0,138.0,139.0,140.0,141.0,142.0',
@@ -295,14 +295,18 @@ class AccountRepository extends Repository {
295
295
  }
296
296
 
297
297
  /**
298
- * Resolves a valid User-Agent from state / realistic fallback.
299
- * IMPORTANT: IG version, build number and bloksVersionId must be
300
- * in sync. Update all three when Instagram updates.
298
+ * Resolves a valid User-Agent from state / dynamically generated fallback.
299
+ * Priority:
300
+ * 1. state.userAgent / state.appUserAgent / request.userAgent / state.deviceString
301
+ * 2. Built dynamically from state device parameters (androidRelease, manufacturer, model, etc.)
302
+ * 3. Last-resort realistic static fallback
303
+ * This way the user-agent always reflects the emulated device set in state.
301
304
  */
302
305
  _resolveUserAgent() {
303
306
  const state = this.client.state || {};
304
307
  const req = this.client.request || {};
305
308
 
309
+ // 1) Explicit user-agent already set in state
306
310
  const candidates = [
307
311
  state.userAgent,
308
312
  state.appUserAgent,
@@ -310,17 +314,204 @@ class AccountRepository extends Repository {
310
314
  state.deviceString,
311
315
  ].filter(Boolean);
312
316
 
313
- let userAgent = candidates.length > 0 ? candidates[0] : null;
317
+ if (candidates.length > 0) {
318
+ this.client.state.userAgent = candidates[0];
319
+ return candidates[0];
320
+ }
321
+
322
+ // 2) Build dynamically from device parameters stored in state
323
+ try {
324
+ const igVersion = state.appVersion || state.igVersion || '415.0.0.36.76';
325
+ const buildNumber = state.appVersionCode || state.buildNumber || '580610226';
326
+ const androidApi = state.androidVersion || state.androidApiLevel || '35';
327
+ const androidRel = state.androidRelease || state.androidOsVersion || '15';
328
+ const dpi = state.dpi || state.screenDpi || '480dpi';
329
+ const resolution = state.resolution || state.screenResolution || '1080x2400';
330
+ const manufacturer = state.manufacturer || state.deviceManufacturer || 'Google';
331
+ const brand = state.brand || state.deviceBrand || manufacturer;
332
+ const model = state.model || state.deviceModel || 'Pixel 7';
333
+ const device = state.device || state.deviceName || 'panther';
334
+ const cpu = state.cpu || state.deviceCpu || 'gs201';
335
+ const lang = (state.language || 'en_US').replace('-', '_');
336
+
337
+ const ua = `Instagram ${igVersion} Android (${androidApi}/${androidRel}; ${dpi}; ${resolution}; ${brand}/${manufacturer}; ${model}; ${device}; ${cpu}; ${lang}; ${buildNumber})`;
338
+ this.client.state.userAgent = ua;
339
+ return ua;
340
+ } catch (e) {}
341
+
342
+ // 3) Last-resort static fallback
343
+ const fallback = 'Instagram 415.0.0.36.76 Android (35/15; 480dpi; 1280x2856; Google; Pixel 9 Pro; caiman; google; en_US; 580610226)';
344
+ this.client.state.userAgent = fallback;
345
+ return fallback;
346
+ }
347
+
348
+ /**
349
+ * Fetches the latest bloksVersionId from Instagram's launcher/sync endpoint.
350
+ * Saves result to bloks-version.json in the session folder (authinfo_instagram/<username>/).
351
+ * If fetch fails, keeps the existing bloksVersionId from state or falls back to hardcoded.
352
+ */
353
+ async _fetchAndSaveBloksVersion(username) {
354
+ const fs = require('fs');
355
+ const path = require('path');
356
+ const FALLBACK_BLOKS = '5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
357
+
358
+ // Session folder: authinfo_instagram/<username>/
359
+ const sessionDir = path.join(
360
+ process.cwd(),
361
+ 'authinfo_instagram',
362
+ username ? String(username) : 'default'
363
+ );
364
+ const bloksFile = path.join(sessionDir, 'bloks-version.json');
365
+
366
+ try { fs.mkdirSync(sessionDir, { recursive: true }); } catch (e) {}
367
+
368
+ const state = this.client.state || {};
369
+ const userAgent = this._resolveUserAgent();
370
+ const lang = state.language || 'ro_RO';
371
+ const nowSec = Math.floor(Date.now() / 1000);
372
+ const androidDeviceId =
373
+ state.androidDeviceId || state.deviceId ||
374
+ `android-${crypto.randomBytes(8).toString('hex')}`;
375
+ const deviceUUID = state.uuid || state.deviceId ||
376
+ (crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
377
+ const networkProps = buildNetworkProperties();
378
+
379
+ // Current bloksVersionId as starting point
380
+ const currentBloks = state.bloksVersionId || FALLBACK_BLOKS;
381
+
382
+ // Helper: extract 64-char hex bloksVersionId from any object/string
383
+ const extractBloks = (body) => {
384
+ if (!body) return null;
385
+ const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
386
+ // Direct fields
387
+ if (body.bloks_version_id && /^[a-f0-9]{64}$/.test(body.bloks_version_id)) return body.bloks_version_id;
388
+ if (body.bloksVersionId && /^[a-f0-9]{64}$/.test(body.bloksVersionId)) return body.bloksVersionId;
389
+ // Search in JSON string
390
+ const m = bodyStr.match(/"bloks_version_id"\s*:\s*"([a-f0-9]{64})"/) ||
391
+ bodyStr.match(/"bloksVersionId"\s*:\s*"([a-f0-9]{64})"/) ||
392
+ bodyStr.match(/"x-bloks-version-id"\s*:\s*"([a-f0-9]{64})"/) ||
393
+ bodyStr.match(/([a-f0-9]{64})/);
394
+ if (m && m[1] && /^[a-f0-9]{64}$/.test(m[1])) return m[1];
395
+ return null;
396
+ };
314
397
 
315
- if (!userAgent) {
316
- // FIX: real existing version 415.0.0.36.76 (build 580610226)
317
- // Keep in sync with bloksVersionId below.
318
- userAgent =
319
- 'Instagram 415.0.0.36.76 Android (35/15; 480dpi; 1280x2856; Google; Pixel 9 Pro; caiman; google; en_US; 580610226)';
398
+ // Helper: safe state setter for bloksVersionId
399
+ // state.bloksVersionId may be read-only (getter-only via Object.defineProperty)
400
+ // so we force-redefine it
401
+ const setBloksInState = (val) => {
402
+ try {
403
+ // Try direct set first
404
+ this.client.state.bloksVersionId = val;
405
+ return;
406
+ } catch (e) {}
407
+ try {
408
+ // Force redefine the property as writable
409
+ Object.defineProperty(this.client.state, 'bloksVersionId', {
410
+ value: val,
411
+ writable: true,
412
+ configurable: true,
413
+ enumerable: true,
414
+ });
415
+ return;
416
+ } catch (e) {}
417
+ try { this.client.state._bloksVersionId = val; } catch (e) {}
418
+ try {
419
+ // Last resort: patch constants object if that's where it comes from
420
+ if (this.client.state.constants) {
421
+ this.client.state.constants.BLOKS_VERSION_ID = val;
422
+ }
423
+ } catch (e) {}
424
+ };
425
+
426
+ const commonHeaders = {
427
+ 'accept-language': `${lang.replace('_', '-')}, en-US`,
428
+ 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
429
+ 'x-bloks-version-id': currentBloks,
430
+ 'x-ig-android-id': androidDeviceId,
431
+ 'x-ig-app-id': String(state.fbAnalyticsApplicationId || '567067343352427'),
432
+ 'x-ig-app-locale': lang,
433
+ 'x-ig-device-locale': lang,
434
+ 'x-ig-mapped-locale': lang,
435
+ 'x-ig-capabilities': '3brTv10=',
436
+ 'x-ig-connection-type': 'MOBILE(UNKNOWN)',
437
+ 'x-fb-connection-type': 'MOBILE.UNKNOWN',
438
+ 'x-fb-client-ip': 'True',
439
+ 'x-fb-server-cluster': 'True',
440
+ 'x-fb-http-engine': 'MNS/TCP',
441
+ 'x-fb-network-properties': networkProps,
442
+ 'x-ig-timezone-offset': String(typeof state.timezoneOffset === 'number' ? state.timezoneOffset : 7200),
443
+ 'x-mid': state.mid || `aZ${crypto.randomBytes(8).toString('hex')}`,
444
+ 'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`,
445
+ 'x-pigeon-session-id': state.pigeonSessionId || `UFS-${crypto.randomBytes(16).toString('hex')}-0`,
446
+ 'x-tigon-is-retry': 'False',
447
+ 'x-ig-www-claim': state.igWWWClaim || '0',
448
+ 'accept-encoding': 'gzip, deflate',
449
+ 'user-agent': userAgent,
450
+ 'x-fb-rmd': 'state=URL_ELIGIBLE',
451
+ };
452
+
453
+ let fetchedBloks = null;
454
+ let rawBody = null;
455
+ let endpointUsed = null;
456
+
457
+ try {
458
+ // Endpoint 1: launcher/sync — sometimes returns bloks in config
459
+ const r1 = await this.client.request.send({
460
+ method: 'POST',
461
+ url: '/api/v1/launcher/sync/',
462
+ form: this.client.request.sign({ id: deviceUUID, server_config_retrieval: '1' }),
463
+ headers: { ...commonHeaders, 'x-fb-friendly-name': 'IgApi: launcher/sync/' },
464
+ });
465
+ rawBody = r1.body;
466
+ fetchedBloks = extractBloks(r1.body);
467
+ if (fetchedBloks) endpointUsed = 'launcher/sync';
468
+ } catch (e) {}
469
+
470
+ if (!fetchedBloks) {
471
+ try {
472
+ // Endpoint 2: qe/sync — often contains bloks version in headers/body
473
+ const r2 = await this.client.request.send({
474
+ method: 'POST',
475
+ url: '/api/v1/qe/sync/',
476
+ form: this.client.request.sign({ id: deviceUUID, server_config_retrieval: '1' }),
477
+ headers: { ...commonHeaders, 'x-fb-friendly-name': 'IgApi: qe/sync/' },
478
+ });
479
+ // Check response headers too
480
+ const hdrs = r2.headers || {};
481
+ const bloksHeader = hdrs['x-bloks-version-id'] || hdrs['bloks-version-id'];
482
+ if (bloksHeader && /^[a-f0-9]{64}$/.test(bloksHeader)) {
483
+ fetchedBloks = bloksHeader;
484
+ endpointUsed = 'qe/sync (header)';
485
+ } else {
486
+ fetchedBloks = extractBloks(r2.body);
487
+ if (fetchedBloks) endpointUsed = 'qe/sync (body)';
488
+ }
489
+ if (!rawBody) rawBody = r2.body;
490
+ } catch (e) {}
320
491
  }
321
492
 
322
- this.client.state.userAgent = userAgent;
323
- return userAgent;
493
+ const gotNew = !!(fetchedBloks && fetchedBloks !== currentBloks);
494
+ const bloksToUse = fetchedBloks || currentBloks;
495
+
496
+ // Update state safely
497
+ setBloksInState(bloksToUse);
498
+
499
+ // Save to bloks-version.json in session folder
500
+ const saveData = {
501
+ username: username || 'unknown',
502
+ bloksVersionId: bloksToUse,
503
+ previousBloksVersionId: currentBloks,
504
+ updatedAt: new Date().toISOString(),
505
+ gotNewVersion: gotNew,
506
+ fetchSuccess: !!fetchedBloks,
507
+ endpointUsed: endpointUsed || 'none — kept existing',
508
+ rawResponse: rawBody || null,
509
+ };
510
+ try {
511
+ fs.writeFileSync(bloksFile, JSON.stringify(saveData, null, 2), 'utf8');
512
+ } catch (writeErr) {}
513
+
514
+ return bloksToUse;
324
515
  }
325
516
 
326
517
  async ensureCsrfToken() {
@@ -861,6 +1052,9 @@ class AccountRepository extends Repository {
861
1052
 
862
1053
  if (!username || !password) throw new Error('Username and password are required');
863
1054
 
1055
+ // Fetch latest bloksVersionId from Instagram and save to session folder
1056
+ try { await this._fetchAndSaveBloksVersion(username); } catch (e) {}
1057
+
864
1058
  await this.ensureCsrfToken();
865
1059
 
866
1060
  try { await this._launcherMobileConfig(true); } catch (e) {}
@@ -885,7 +1079,7 @@ class AccountRepository extends Repository {
885
1079
  try { await this._phoneNumberPrefill(); } catch (e) {}
886
1080
  try { await this._prefetchOauthTokenForLogin(username); } catch (e) {}
887
1081
 
888
- return this.requestWithRetry(async () => {
1082
+ const loginResult = await this.requestWithRetry(async () => {
889
1083
  const nowSec = Math.floor(Date.now() / 1000);
890
1084
  const aacInitTimestamp = nowSec - Math.floor(Math.random() * 50);
891
1085
  const aacjid = crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4();
@@ -1261,6 +1455,126 @@ class AccountRepository extends Repository {
1261
1455
 
1262
1456
  return body;
1263
1457
  });
1458
+
1459
+ // Post-login: set E2EE eligibility (as real app does after login)
1460
+ try { await this._setE2eeEligibility(); } catch (e) {}
1461
+
1462
+ return loginResult;
1463
+ }
1464
+
1465
+ async _setE2eeEligibility(eligibility = 4) {
1466
+ const state = this.client.state || {};
1467
+ const lang = state.language || 'ro_RO';
1468
+ const acceptLanguage = `${lang.replace('_', '-')}, en-US`;
1469
+ const userAgent = this._resolveUserAgent();
1470
+ const bloksVersionId =
1471
+ state.bloksVersionId ||
1472
+ '5e47baf35c5a270b44c8906c8b99063564b30ef69779f3dee0b828bee2e4ef5b';
1473
+ const networkProps = buildNetworkProperties();
1474
+ const nowSec = Math.floor(Date.now() / 1000);
1475
+
1476
+ const androidDeviceId =
1477
+ state.androidDeviceId ||
1478
+ state.deviceId ||
1479
+ `android-${crypto.randomBytes(8).toString('hex')}`;
1480
+ const familyDeviceId =
1481
+ state.phoneId ||
1482
+ state.familyDeviceId ||
1483
+ (crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
1484
+ const qeDeviceId =
1485
+ state.deviceId ||
1486
+ state.qeDeviceId ||
1487
+ (crypto.randomUUID ? crypto.randomUUID() : require('uuid').v4());
1488
+ const machineId =
1489
+ state.mid ||
1490
+ state.machineId ||
1491
+ `aZ${crypto.randomBytes(8).toString('hex')}`;
1492
+
1493
+ const headers = {
1494
+ 'accept-language': acceptLanguage,
1495
+ 'authorization': state.authorization || '',
1496
+ 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
1497
+ 'ig-intended-user-id': String(state.cookieUserId || '0'),
1498
+ 'ig-u-ds-user-id': String(state.cookieUserId || '0'),
1499
+ 'priority': 'u=3',
1500
+ 'x-bloks-is-layout-rtl': 'false',
1501
+ 'x-bloks-prism-ax-base-colors-enabled': 'false',
1502
+ 'x-bloks-prism-button-version': 'CONTROL',
1503
+ 'x-bloks-prism-colors-enabled': 'true',
1504
+ 'x-bloks-prism-font-enabled': 'false',
1505
+ 'x-bloks-prism-indigo-link-version': '0',
1506
+ 'x-bloks-version-id': bloksVersionId,
1507
+ 'x-fb-client-ip': 'True',
1508
+ 'x-fb-connection-type': 'MOBILE.UNKNOWN',
1509
+ 'x-fb-friendly-name': 'IgApi: direct_v2/set_e2ee_eligibility/',
1510
+ 'x-fb-network-properties': networkProps,
1511
+ 'x-fb-request-analytics-tags': JSON.stringify({
1512
+ network_tags: {
1513
+ product: String(state.fbAnalyticsApplicationId || '567067343352427'),
1514
+ purpose: 'fetch',
1515
+ surface: 'undefined',
1516
+ request_category: 'api',
1517
+ retry_attempt: '0',
1518
+ },
1519
+ }),
1520
+ 'x-fb-server-cluster': 'True',
1521
+ 'x-ig-android-id': androidDeviceId,
1522
+ 'x-ig-app-id': String(state.fbAnalyticsApplicationId || '567067343352427'),
1523
+ 'x-ig-app-locale': lang,
1524
+ 'x-ig-bandwidth-speed-kbps': (Math.random() * 1500 + 800).toFixed(3),
1525
+ 'x-ig-bandwidth-totalbytes-b': '0',
1526
+ 'x-ig-bandwidth-totaltime-ms': '0',
1527
+ 'x-ig-capabilities': '3brTv10=',
1528
+ 'x-ig-client-endpoint': 'empty',
1529
+ 'x-ig-connection-type': 'MOBILE(UNKNOWN)',
1530
+ 'x-ig-device-id': qeDeviceId,
1531
+ 'x-ig-device-languages': `{"system_languages":"${lang}"}`,
1532
+ 'x-ig-device-locale': lang,
1533
+ 'x-ig-family-device-id': familyDeviceId,
1534
+ 'x-ig-mapped-locale': lang,
1535
+ 'x-ig-nav-chain': 'LockoutFragment:dogfooding_lockout:1:cold_start',
1536
+ 'x-ig-salt-ids': state.igSaltIds || '220140399,332020310,974466465,974460658',
1537
+ 'x-ig-timezone-offset': String(
1538
+ typeof state.timezoneOffset === 'number' ? state.timezoneOffset : 7200
1539
+ ),
1540
+ 'x-ig-www-claim': state.igWWWClaim || '0',
1541
+ 'x-mid': machineId,
1542
+ 'x-pigeon-rawclienttime': `${nowSec}.${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`,
1543
+ 'x-pigeon-session-id':
1544
+ state.pigeonSessionId ||
1545
+ `UFS-${crypto.randomBytes(16).toString('hex')}-3`,
1546
+ 'x-tigon-is-retry': 'False',
1547
+ 'accept-encoding': 'gzip, deflate',
1548
+ 'user-agent': userAgent,
1549
+ 'x-fb-conn-uuid-client': crypto.randomBytes(16).toString('hex'),
1550
+ 'x-fb-http-engine': 'MNS/TCP',
1551
+ 'x-fb-rmd': 'state=URL_ELIGIBLE',
1552
+ };
1553
+
1554
+ if (state.igURur) headers['ig-u-rur'] = state.igURur;
1555
+
1556
+ try {
1557
+ const response = await this.client.request.send({
1558
+ method: 'POST',
1559
+ url: '/api/v1/direct_v2/set_e2ee_eligibility/',
1560
+ form: {
1561
+ _uuid: qeDeviceId,
1562
+ e2ee_eligibility: String(eligibility),
1563
+ },
1564
+ headers,
1565
+ });
1566
+
1567
+ debugWrite('e2ee-eligibility-debug.json', {
1568
+ at: new Date().toISOString(),
1569
+ statusCode: response.statusCode || response.status || null,
1570
+ headers: response.headers || null,
1571
+ body: response.body || null,
1572
+ });
1573
+
1574
+ return response.body;
1575
+ } catch (e) {
1576
+ return null;
1577
+ }
1264
1578
  }
1265
1579
 
1266
1580
  async twoFactorLogin(username, verificationCode, twoFactorIdentifier, verificationMethod = '1') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodejs-insta-private-api-mqt",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
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",