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.
- package/dist/cli.js +135 -35
- 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.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
|
-
|
|
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: "
|
|
1404
|
+
fb_api_req_friendly_name: "ProfileStoryHighlightsTrayContentQuery_connection",
|
|
1395
1405
|
server_timestamps: "true",
|
|
1396
1406
|
variables,
|
|
1397
|
-
doc_id: IG_QUERIES.
|
|
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-
|
|
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
|
|
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)
|
|
1435
|
+
if (Array.isArray(edges)) {
|
|
1422
1436
|
if (res?.errors && Array.isArray(res.errors) && res.errors.length > 0) {
|
|
1423
|
-
logExtractorDebug("
|
|
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 (
|
|
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("
|
|
1448
|
+
logExtractorDebug("ProfileStoryHighlightsTrayContentQuery_connection", err);
|
|
1435
1449
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1436
|
-
if (
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
1483
|
-
if (res
|
|
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 (
|
|
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)
|
|
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
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
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
|
}
|