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.
- package/dist/cli.js +122 -27
- 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.
|
|
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)
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
1483
|
-
if (res
|
|
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 (
|
|
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)
|
|
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
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
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
|
}
|