open-nami 1.2.1 → 1.2.3

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.
Files changed (2) hide show
  1. package/dist/cli.js +135 -35
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ var require_package = __commonJS({
9
9
  "package.json"(exports, module) {
10
10
  module.exports = {
11
11
  name: "open-nami",
12
- version: "1.2.1",
12
+ version: "1.2.3",
13
13
  description: "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
14
14
  type: "module",
15
15
  bin: {
@@ -1205,12 +1205,16 @@ var IG_QUERIES = {
1205
1205
  PolarisProfilePostsTabContentQuery_connection: "39229011733413759",
1206
1206
  PolarisProfileReelsTabContentQuery_connection: "28989741540630190",
1207
1207
  PolarisProfileTaggedTabContentQuery_connection: "28794602806842110",
1208
- PolarisProfileStoryHighlightsTrayContentQuery: "26970053832668570",
1208
+ ProfileStoryHighlightsTrayContentQuery_connection: "38168009559465141",
1209
+ PolarisProfileStoryHighlightsTrayContentQuery: "38168009559465141",
1209
1210
  PolarisStoriesV3ReelPageGalleryQuery: "28262315486766731",
1210
1211
  PolarisStoriesV3TrayContainerQuery: "27703822975903310",
1211
1212
  PolarisProfilePageContentQuery: "28649589871350527"
1212
1213
  };
1213
1214
  var IG_POLARIS_DOC_ID = IG_QUERIES.PolarisProfilePostsTabContentQuery_connection;
1215
+ function isExplicitAuthError(msg) {
1216
+ return msg.includes("401") || msg.includes("403") || msg.includes("login_required") || msg.includes("checkpoint_required") || msg.includes("not-logged-in");
1217
+ }
1214
1218
  function logExtractorDebug(endpoint, err) {
1215
1219
  if (process.env.DEBUG || process.env.OPEN_NAMI_DEBUG) {
1216
1220
  const message = err instanceof Error ? err.message : String(err);
@@ -1389,12 +1393,18 @@ var InstagramExtractor = class {
1389
1393
  __user: "0",
1390
1394
  __a: "1",
1391
1395
  __req: "0",
1396
+ __hs: "20731.HYP:instagram_web_pkg.2.1...0",
1392
1397
  dpr: "1",
1398
+ __ccg: "EXCELLENT",
1399
+ __rev: "1049285836",
1400
+ __hsi: "7693193318680449885",
1401
+ __comet_req: "7",
1402
+ jazoest: "26461",
1393
1403
  fb_api_caller_class: "RelayModern",
1394
- fb_api_req_friendly_name: "PolarisProfileStoryHighlightsTrayContentQuery",
1404
+ fb_api_req_friendly_name: "ProfileStoryHighlightsTrayContentQuery_connection",
1395
1405
  server_timestamps: "true",
1396
1406
  variables,
1397
- doc_id: IG_QUERIES.PolarisProfileStoryHighlightsTrayContentQuery
1407
+ doc_id: IG_QUERIES.ProfileStoryHighlightsTrayContentQuery_connection
1398
1408
  });
1399
1409
  if (tokens?.fbDtsg) bodyParams.set("fb_dtsg", tokens.fbDtsg);
1400
1410
  if (tokens?.lsd) bodyParams.set("lsd", tokens.lsd);
@@ -1402,14 +1412,18 @@ var InstagramExtractor = class {
1402
1412
  "Accept": "*/*",
1403
1413
  "Content-Type": "application/x-www-form-urlencoded",
1404
1414
  "X-IG-App-ID": IG_APP_ID,
1405
- "X-FB-Friendly-Name": "PolarisProfileStoryHighlightsTrayContentQuery",
1415
+ "X-IG-Max-Touch-Points": "0",
1416
+ "X-FB-Friendly-Name": "ProfileStoryHighlightsTrayContentQuery_connection",
1406
1417
  "Referer": "https://www.instagram.com/",
1407
1418
  "Origin": "https://www.instagram.com",
1408
- "X-ASBD-ID": "359341"
1419
+ "X-ASBD-ID": "359341",
1420
+ "Sec-Fetch-Dest": "empty",
1421
+ "Sec-Fetch-Mode": "cors",
1422
+ "Sec-Fetch-Site": "same-origin"
1409
1423
  };
1410
1424
  if (tokens?.csrfToken) headers["X-CSRFToken"] = tokens.csrfToken;
1411
1425
  if (tokens?.lsd) headers["X-FB-LSD"] = tokens.lsd;
1412
- const res = await this.httpClient.fetchJson("https://www.instagram.com/graphql/query", {
1426
+ const res = await this.httpClient.fetchJson("https://www.instagram.com/api/graphql", {
1413
1427
  method: "POST",
1414
1428
  body: bodyParams.toString(),
1415
1429
  cookiePath: options.cookiePath,
@@ -1418,22 +1432,22 @@ var InstagramExtractor = class {
1418
1432
  headers
1419
1433
  });
1420
1434
  const edges = res?.data?.highlights?.edges;
1421
- if (Array.isArray(edges) && edges.length > 0) {
1435
+ if (Array.isArray(edges)) {
1422
1436
  if (res?.errors && Array.isArray(res.errors) && res.errors.length > 0) {
1423
- logExtractorDebug("PolarisProfileStoryHighlightsTrayContentQuery partial field errors", res.errors);
1437
+ logExtractorDebug("ProfileStoryHighlightsTrayContentQuery_connection partial field errors", res.errors);
1424
1438
  }
1425
- return edges.map((e) => e.node);
1439
+ return edges.map((e) => e.node).filter(Boolean);
1426
1440
  }
1427
1441
  if (res?.errors && Array.isArray(res.errors) && res.errors.length > 0) {
1428
1442
  const errMsg = res.errors.map((e) => e.message || JSON.stringify(e)).join("; ");
1429
- if (errMsg.includes("login") || errMsg.includes("auth") || errMsg.includes("checkpoint")) {
1443
+ if (isExplicitAuthError(errMsg)) {
1430
1444
  authError = new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1431
1445
  }
1432
1446
  }
1433
1447
  } catch (err) {
1434
- logExtractorDebug("PolarisProfileStoryHighlightsTrayContentQuery", err);
1448
+ logExtractorDebug("ProfileStoryHighlightsTrayContentQuery_connection", err);
1435
1449
  const msg = err instanceof Error ? err.message : String(err);
1436
- if (msg.includes("401") || msg.includes("403") || msg.includes("login") || msg.includes("not-logged-in") || msg.includes("<!DOCTYPE")) {
1450
+ if (isExplicitAuthError(msg)) {
1437
1451
  authError = new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1438
1452
  }
1439
1453
  }
@@ -1448,22 +1462,90 @@ var InstagramExtractor = class {
1448
1462
  "Referer": "https://www.instagram.com/"
1449
1463
  }
1450
1464
  });
1451
- if (res.message === "login_required" || res.message === "checkpoint_required") {
1465
+ if (res?.message === "login_required" || res?.message === "checkpoint_required") {
1452
1466
  throw new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1453
1467
  }
1454
- return res.tray ?? [];
1468
+ return res?.tray ?? [];
1455
1469
  } catch (err) {
1456
1470
  logExtractorDebug("Highlights legacy REST", err);
1457
1471
  const msg = err instanceof Error ? err.message : String(err);
1458
- if (msg.includes("401") || msg.includes("403") || msg.includes("login") || msg.includes("not-logged-in") || msg.includes("<!DOCTYPE") || msg.includes("Unexpected token")) {
1472
+ if (isExplicitAuthError(msg)) {
1459
1473
  throw new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1460
1474
  }
1461
1475
  if (authError) throw authError;
1462
1476
  return [];
1463
1477
  }
1464
1478
  }
1465
- async fetchReelsMedia(reelIds, options) {
1479
+ async fetchReelsMediaGraphQL(reelIds, options, tokens) {
1480
+ if (reelIds.length === 0) return [];
1481
+ try {
1482
+ const variables = JSON.stringify({
1483
+ initial_reel_id: reelIds[0],
1484
+ reel_ids: reelIds,
1485
+ first: 3,
1486
+ last: 2,
1487
+ __relay_internal__pv__PolarisCommunityNoteStoriesLabelEnabledrelayprovider: true
1488
+ });
1489
+ const bodyParams = new URLSearchParams({
1490
+ av: "17841415137994167",
1491
+ __d: "www",
1492
+ __user: "0",
1493
+ __a: "1",
1494
+ __req: "0",
1495
+ __hs: "20731.HYP:instagram_web_pkg.2.1...0",
1496
+ dpr: "1",
1497
+ __ccg: "EXCELLENT",
1498
+ __rev: "1049285836",
1499
+ __hsi: "7693193318680449885",
1500
+ __comet_req: "7",
1501
+ jazoest: "26461",
1502
+ fb_api_caller_class: "RelayModern",
1503
+ fb_api_req_friendly_name: "PolarisStoriesV3ReelPageGalleryQuery",
1504
+ server_timestamps: "true",
1505
+ variables,
1506
+ doc_id: IG_QUERIES.PolarisStoriesV3ReelPageGalleryQuery
1507
+ });
1508
+ if (tokens?.fbDtsg) bodyParams.set("fb_dtsg", tokens.fbDtsg);
1509
+ if (tokens?.lsd) bodyParams.set("lsd", tokens.lsd);
1510
+ const headers = {
1511
+ "Accept": "*/*",
1512
+ "Content-Type": "application/x-www-form-urlencoded",
1513
+ "X-IG-App-ID": IG_APP_ID,
1514
+ "X-IG-Max-Touch-Points": "0",
1515
+ "X-FB-Friendly-Name": "PolarisStoriesV3ReelPageGalleryQuery",
1516
+ "Referer": "https://www.instagram.com/",
1517
+ "Origin": "https://www.instagram.com",
1518
+ "X-ASBD-ID": "359341",
1519
+ "Sec-Fetch-Dest": "empty",
1520
+ "Sec-Fetch-Mode": "cors",
1521
+ "Sec-Fetch-Site": "same-origin"
1522
+ };
1523
+ if (tokens?.csrfToken) headers["X-CSRFToken"] = tokens.csrfToken;
1524
+ if (tokens?.lsd) headers["X-FB-LSD"] = tokens.lsd;
1525
+ const res = await this.httpClient.fetchJson("https://www.instagram.com/api/graphql", {
1526
+ method: "POST",
1527
+ body: bodyParams.toString(),
1528
+ cookiePath: options.cookiePath,
1529
+ cookieDomain: "instagram.com",
1530
+ signal: options.signal,
1531
+ headers
1532
+ });
1533
+ const connection = res?.data?.xdt_api__v1__feed__reels_media__connection;
1534
+ const edges = connection?.edges;
1535
+ if (Array.isArray(edges) && edges.length > 0) {
1536
+ return edges.map((e) => e.node).filter(Boolean);
1537
+ }
1538
+ } catch (err) {
1539
+ logExtractorDebug("PolarisStoriesV3ReelPageGalleryQuery", err);
1540
+ }
1541
+ return [];
1542
+ }
1543
+ async fetchReelsMedia(reelIds, options, tokens) {
1466
1544
  if (reelIds.length === 0) return [];
1545
+ const gqlReels = await this.fetchReelsMediaGraphQL(reelIds, options, tokens);
1546
+ if (gqlReels.length > 0) {
1547
+ return gqlReels;
1548
+ }
1467
1549
  const idsParam = reelIds.join(",");
1468
1550
  const url = `https://www.instagram.com/api/v1/feed/reels_media/?reel_ids=${encodeURIComponent(idsParam)}`;
1469
1551
  try {
@@ -1476,17 +1558,18 @@ var InstagramExtractor = class {
1476
1558
  "Referer": "https://www.instagram.com/"
1477
1559
  }
1478
1560
  });
1479
- if (res.message === "login_required" || res.message === "checkpoint_required") {
1561
+ if (res?.message === "login_required" || res?.message === "checkpoint_required") {
1480
1562
  throw new Error("Instagram Stories require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1481
1563
  }
1482
- if (res.reels_media) return res.reels_media;
1483
- if (res.reels) return Object.values(res.reels);
1564
+ if (res?.reels_media) return res.reels_media;
1565
+ if (res?.reels) return Object.values(res.reels);
1484
1566
  return [];
1485
1567
  } catch (err) {
1486
1568
  const msg = err instanceof Error ? err.message : String(err);
1487
- if (msg.includes("401") || msg.includes("403") || msg.includes("login") || msg.includes("not-logged-in") || msg.includes("<!DOCTYPE") || msg.includes("Unexpected token")) {
1569
+ if (isExplicitAuthError(msg)) {
1488
1570
  throw new Error("Instagram Stories require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1489
1571
  }
1572
+ logExtractorDebug("Reels media legacy REST", err);
1490
1573
  return [];
1491
1574
  }
1492
1575
  }
@@ -1807,13 +1890,24 @@ var InstagramExtractor = class {
1807
1890
  found: 0,
1808
1891
  message: `Fetching stories for @${username}...`
1809
1892
  });
1810
- const reels = await this.fetchReelsMedia([userId], options);
1893
+ const reels = await this.fetchReelsMedia([userId], options, webTokens);
1894
+ let count = 0;
1811
1895
  for (const reel of reels) {
1812
1896
  for (const rawItem of reel.items || []) {
1813
1897
  const item = parseReelItemToMediaItem(rawItem, username);
1814
- if (item) yield item;
1898
+ if (item) {
1899
+ count++;
1900
+ yield item;
1901
+ }
1815
1902
  }
1816
1903
  }
1904
+ if (count === 0) {
1905
+ options.onProgress?.({
1906
+ stage: "Stories",
1907
+ found: 0,
1908
+ message: `Profile @${username} has no active stories in the last 24 hours.`
1909
+ });
1910
+ }
1817
1911
  return;
1818
1912
  }
1819
1913
  if (subDir === "Highlights") {
@@ -1827,20 +1921,26 @@ var InstagramExtractor = class {
1827
1921
  });
1828
1922
  const tray = await this.fetchHighlightsTray(userId, options, webTokens);
1829
1923
  const highlightIds = tray.map((t) => t.id).filter(Boolean);
1830
- if (highlightIds.length > 0) {
1831
- const chunkSize = 5;
1832
- for (let i = 0; i < highlightIds.length; i += chunkSize) {
1833
- if (options.signal?.aborted) break;
1834
- const chunk = highlightIds.slice(i, i + chunkSize);
1835
- const reels = await this.fetchReelsMedia(chunk, options);
1836
- for (const reel of reels) {
1837
- for (const rawItem of reel.items || []) {
1838
- const item = parseReelItemToMediaItem(rawItem, username);
1839
- if (item) yield item;
1840
- }
1924
+ if (highlightIds.length === 0) {
1925
+ options.onProgress?.({
1926
+ stage: "Highlights",
1927
+ found: 0,
1928
+ message: `Profile @${username} has no available highlights.`
1929
+ });
1930
+ return;
1931
+ }
1932
+ const chunkSize = 5;
1933
+ for (let i = 0; i < highlightIds.length; i += chunkSize) {
1934
+ if (options.signal?.aborted) break;
1935
+ const chunk = highlightIds.slice(i, i + chunkSize);
1936
+ const reels = await this.fetchReelsMedia(chunk, options, webTokens);
1937
+ for (const reel of reels) {
1938
+ for (const rawItem of reel.items || []) {
1939
+ const item = parseReelItemToMediaItem(rawItem, username);
1940
+ if (item) yield item;
1841
1941
  }
1842
- await sleep(1e3, 1500, options.signal);
1843
1942
  }
1943
+ await sleep(1e3, 1500, options.signal);
1844
1944
  }
1845
1945
  return;
1846
1946
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-nami",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
5
5
  "type": "module",
6
6
  "bin": {