open-nami 1.2.1 → 1.2.2

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 +122 -27
  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.2",
13
13
  description: "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
14
14
  type: "module",
15
15
  bin: {
@@ -1211,6 +1211,9 @@ var IG_QUERIES = {
1211
1211
  PolarisProfilePageContentQuery: "28649589871350527"
1212
1212
  };
1213
1213
  var IG_POLARIS_DOC_ID = IG_QUERIES.PolarisProfilePostsTabContentQuery_connection;
1214
+ function isExplicitAuthError(msg) {
1215
+ return msg.includes("401") || msg.includes("403") || msg.includes("login_required") || msg.includes("checkpoint_required") || msg.includes("not-logged-in");
1216
+ }
1214
1217
  function logExtractorDebug(endpoint, err) {
1215
1218
  if (process.env.DEBUG || process.env.OPEN_NAMI_DEBUG) {
1216
1219
  const message = err instanceof Error ? err.message : String(err);
@@ -1418,22 +1421,22 @@ var InstagramExtractor = class {
1418
1421
  headers
1419
1422
  });
1420
1423
  const edges = res?.data?.highlights?.edges;
1421
- if (Array.isArray(edges) && edges.length > 0) {
1424
+ if (Array.isArray(edges)) {
1422
1425
  if (res?.errors && Array.isArray(res.errors) && res.errors.length > 0) {
1423
1426
  logExtractorDebug("PolarisProfileStoryHighlightsTrayContentQuery partial field errors", res.errors);
1424
1427
  }
1425
- return edges.map((e) => e.node);
1428
+ return edges.map((e) => e.node).filter(Boolean);
1426
1429
  }
1427
1430
  if (res?.errors && Array.isArray(res.errors) && res.errors.length > 0) {
1428
1431
  const errMsg = res.errors.map((e) => e.message || JSON.stringify(e)).join("; ");
1429
- if (errMsg.includes("login") || errMsg.includes("auth") || errMsg.includes("checkpoint")) {
1432
+ if (isExplicitAuthError(errMsg)) {
1430
1433
  authError = new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1431
1434
  }
1432
1435
  }
1433
1436
  } catch (err) {
1434
1437
  logExtractorDebug("PolarisProfileStoryHighlightsTrayContentQuery", err);
1435
1438
  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")) {
1439
+ if (isExplicitAuthError(msg)) {
1437
1440
  authError = new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1438
1441
  }
1439
1442
  }
@@ -1448,22 +1451,80 @@ var InstagramExtractor = class {
1448
1451
  "Referer": "https://www.instagram.com/"
1449
1452
  }
1450
1453
  });
1451
- if (res.message === "login_required" || res.message === "checkpoint_required") {
1454
+ if (res?.message === "login_required" || res?.message === "checkpoint_required") {
1452
1455
  throw new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1453
1456
  }
1454
- return res.tray ?? [];
1457
+ return res?.tray ?? [];
1455
1458
  } catch (err) {
1456
1459
  logExtractorDebug("Highlights legacy REST", err);
1457
1460
  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")) {
1461
+ if (isExplicitAuthError(msg)) {
1459
1462
  throw new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1460
1463
  }
1461
1464
  if (authError) throw authError;
1462
1465
  return [];
1463
1466
  }
1464
1467
  }
1465
- async fetchReelsMedia(reelIds, options) {
1468
+ async fetchReelsMediaGraphQL(reelIds, options, tokens) {
1469
+ if (reelIds.length === 0) return [];
1470
+ try {
1471
+ const variables = JSON.stringify({
1472
+ initial_reel_id: reelIds[0],
1473
+ reel_ids: reelIds,
1474
+ first: 3,
1475
+ last: 2,
1476
+ __relay_internal__pv__PolarisCommunityNoteStoriesLabelEnabledrelayprovider: true
1477
+ });
1478
+ const bodyParams = new URLSearchParams({
1479
+ av: "17841415137994167",
1480
+ __d: "www",
1481
+ __user: "0",
1482
+ __a: "1",
1483
+ __req: "0",
1484
+ dpr: "1",
1485
+ fb_api_caller_class: "RelayModern",
1486
+ fb_api_req_friendly_name: "PolarisStoriesV3ReelPageGalleryQuery",
1487
+ server_timestamps: "true",
1488
+ variables,
1489
+ doc_id: IG_QUERIES.PolarisStoriesV3ReelPageGalleryQuery
1490
+ });
1491
+ if (tokens?.fbDtsg) bodyParams.set("fb_dtsg", tokens.fbDtsg);
1492
+ if (tokens?.lsd) bodyParams.set("lsd", tokens.lsd);
1493
+ const headers = {
1494
+ "Accept": "*/*",
1495
+ "Content-Type": "application/x-www-form-urlencoded",
1496
+ "X-IG-App-ID": IG_APP_ID,
1497
+ "X-FB-Friendly-Name": "PolarisStoriesV3ReelPageGalleryQuery",
1498
+ "Referer": "https://www.instagram.com/",
1499
+ "Origin": "https://www.instagram.com",
1500
+ "X-ASBD-ID": "359341"
1501
+ };
1502
+ if (tokens?.csrfToken) headers["X-CSRFToken"] = tokens.csrfToken;
1503
+ if (tokens?.lsd) headers["X-FB-LSD"] = tokens.lsd;
1504
+ const res = await this.httpClient.fetchJson("https://www.instagram.com/graphql/query", {
1505
+ method: "POST",
1506
+ body: bodyParams.toString(),
1507
+ cookiePath: options.cookiePath,
1508
+ cookieDomain: "instagram.com",
1509
+ signal: options.signal,
1510
+ headers
1511
+ });
1512
+ const connection = res?.data?.xdt_api__v1__feed__reels_media__connection;
1513
+ const edges = connection?.edges;
1514
+ if (Array.isArray(edges) && edges.length > 0) {
1515
+ return edges.map((e) => e.node).filter(Boolean);
1516
+ }
1517
+ } catch (err) {
1518
+ logExtractorDebug("PolarisStoriesV3ReelPageGalleryQuery", err);
1519
+ }
1520
+ return [];
1521
+ }
1522
+ async fetchReelsMedia(reelIds, options, tokens) {
1466
1523
  if (reelIds.length === 0) return [];
1524
+ const gqlReels = await this.fetchReelsMediaGraphQL(reelIds, options, tokens);
1525
+ if (gqlReels.length > 0) {
1526
+ return gqlReels;
1527
+ }
1467
1528
  const idsParam = reelIds.join(",");
1468
1529
  const url = `https://www.instagram.com/api/v1/feed/reels_media/?reel_ids=${encodeURIComponent(idsParam)}`;
1469
1530
  try {
@@ -1476,17 +1537,18 @@ var InstagramExtractor = class {
1476
1537
  "Referer": "https://www.instagram.com/"
1477
1538
  }
1478
1539
  });
1479
- if (res.message === "login_required" || res.message === "checkpoint_required") {
1540
+ if (res?.message === "login_required" || res?.message === "checkpoint_required") {
1480
1541
  throw new Error("Instagram Stories require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1481
1542
  }
1482
- if (res.reels_media) return res.reels_media;
1483
- if (res.reels) return Object.values(res.reels);
1543
+ if (res?.reels_media) return res.reels_media;
1544
+ if (res?.reels) return Object.values(res.reels);
1484
1545
  return [];
1485
1546
  } catch (err) {
1486
1547
  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")) {
1548
+ if (isExplicitAuthError(msg)) {
1488
1549
  throw new Error("Instagram Stories require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
1489
1550
  }
1551
+ logExtractorDebug("Reels media legacy REST", err);
1490
1552
  return [];
1491
1553
  }
1492
1554
  }
@@ -1802,24 +1864,51 @@ var InstagramExtractor = class {
1802
1864
  if (!userId || userId === "[object Object]") {
1803
1865
  throw new Error(`Could not resolve user ID for @${username}. Profile may be private or unavailable.`);
1804
1866
  }
1867
+ if (user?.latest_reel_media === 0) {
1868
+ options.onProgress?.({
1869
+ stage: "Stories",
1870
+ found: 0,
1871
+ message: `Profile @${username} has no active stories in the last 24 hours.`
1872
+ });
1873
+ return;
1874
+ }
1805
1875
  options.onProgress?.({
1806
1876
  stage: "Stories",
1807
1877
  found: 0,
1808
1878
  message: `Fetching stories for @${username}...`
1809
1879
  });
1810
- const reels = await this.fetchReelsMedia([userId], options);
1880
+ const reels = await this.fetchReelsMedia([userId], options, webTokens);
1881
+ let count = 0;
1811
1882
  for (const reel of reels) {
1812
1883
  for (const rawItem of reel.items || []) {
1813
1884
  const item = parseReelItemToMediaItem(rawItem, username);
1814
- if (item) yield item;
1885
+ if (item) {
1886
+ count++;
1887
+ yield item;
1888
+ }
1815
1889
  }
1816
1890
  }
1891
+ if (count === 0) {
1892
+ options.onProgress?.({
1893
+ stage: "Stories",
1894
+ found: 0,
1895
+ message: `Profile @${username} has no active stories in the last 24 hours.`
1896
+ });
1897
+ }
1817
1898
  return;
1818
1899
  }
1819
1900
  if (subDir === "Highlights") {
1820
1901
  if (!userId || userId === "[object Object]") {
1821
1902
  throw new Error(`Could not resolve user ID for @${username}. Profile may be private or unavailable.`);
1822
1903
  }
1904
+ if (user?.highlight_reel_count === 0) {
1905
+ options.onProgress?.({
1906
+ stage: "Highlights",
1907
+ found: 0,
1908
+ message: `Profile @${username} has no available highlights.`
1909
+ });
1910
+ return;
1911
+ }
1823
1912
  options.onProgress?.({
1824
1913
  stage: "Highlights",
1825
1914
  found: 0,
@@ -1827,20 +1916,26 @@ var InstagramExtractor = class {
1827
1916
  });
1828
1917
  const tray = await this.fetchHighlightsTray(userId, options, webTokens);
1829
1918
  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
- }
1919
+ if (highlightIds.length === 0) {
1920
+ options.onProgress?.({
1921
+ stage: "Highlights",
1922
+ found: 0,
1923
+ message: `Profile @${username} has no available highlights.`
1924
+ });
1925
+ return;
1926
+ }
1927
+ const chunkSize = 5;
1928
+ for (let i = 0; i < highlightIds.length; i += chunkSize) {
1929
+ if (options.signal?.aborted) break;
1930
+ const chunk = highlightIds.slice(i, i + chunkSize);
1931
+ const reels = await this.fetchReelsMedia(chunk, options, webTokens);
1932
+ for (const reel of reels) {
1933
+ for (const rawItem of reel.items || []) {
1934
+ const item = parseReelItemToMediaItem(rawItem, username);
1935
+ if (item) yield item;
1841
1936
  }
1842
- await sleep(1e3, 1500, options.signal);
1843
1937
  }
1938
+ await sleep(1e3, 1500, options.signal);
1844
1939
  }
1845
1940
  return;
1846
1941
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-nami",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
5
5
  "type": "module",
6
6
  "bin": {