open-nami 1.2.0 → 1.2.1
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 +113 -33
- 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.1",
|
|
13
13
|
description: "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
|
|
14
14
|
type: "module",
|
|
15
15
|
bin: {
|
|
@@ -729,6 +729,7 @@ async function clearCheckpoint(destDir) {
|
|
|
729
729
|
import path10 from "path";
|
|
730
730
|
import os5 from "os";
|
|
731
731
|
import fs10 from "fs/promises";
|
|
732
|
+
import fsSync from "fs";
|
|
732
733
|
|
|
733
734
|
// src/lib/cookies.ts
|
|
734
735
|
import fs2 from "fs";
|
|
@@ -1378,25 +1379,43 @@ var InstagramExtractor = class {
|
|
|
1378
1379
|
}
|
|
1379
1380
|
return user;
|
|
1380
1381
|
}
|
|
1381
|
-
async fetchHighlightsTray(userId, options) {
|
|
1382
|
+
async fetchHighlightsTray(userId, options, tokens) {
|
|
1383
|
+
let authError = null;
|
|
1382
1384
|
try {
|
|
1383
1385
|
const variables = JSON.stringify({ user_id: userId });
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1386
|
+
const bodyParams = new URLSearchParams({
|
|
1387
|
+
av: "17841415137994167",
|
|
1388
|
+
__d: "www",
|
|
1389
|
+
__user: "0",
|
|
1390
|
+
__a: "1",
|
|
1391
|
+
__req: "0",
|
|
1392
|
+
dpr: "1",
|
|
1393
|
+
fb_api_caller_class: "RelayModern",
|
|
1394
|
+
fb_api_req_friendly_name: "PolarisProfileStoryHighlightsTrayContentQuery",
|
|
1395
|
+
server_timestamps: "true",
|
|
1396
|
+
variables,
|
|
1397
|
+
doc_id: IG_QUERIES.PolarisProfileStoryHighlightsTrayContentQuery
|
|
1398
|
+
});
|
|
1399
|
+
if (tokens?.fbDtsg) bodyParams.set("fb_dtsg", tokens.fbDtsg);
|
|
1400
|
+
if (tokens?.lsd) bodyParams.set("lsd", tokens.lsd);
|
|
1401
|
+
const headers = {
|
|
1402
|
+
"Accept": "*/*",
|
|
1403
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1404
|
+
"X-IG-App-ID": IG_APP_ID,
|
|
1405
|
+
"X-FB-Friendly-Name": "PolarisProfileStoryHighlightsTrayContentQuery",
|
|
1406
|
+
"Referer": "https://www.instagram.com/",
|
|
1407
|
+
"Origin": "https://www.instagram.com",
|
|
1408
|
+
"X-ASBD-ID": "359341"
|
|
1409
|
+
};
|
|
1410
|
+
if (tokens?.csrfToken) headers["X-CSRFToken"] = tokens.csrfToken;
|
|
1411
|
+
if (tokens?.lsd) headers["X-FB-LSD"] = tokens.lsd;
|
|
1388
1412
|
const res = await this.httpClient.fetchJson("https://www.instagram.com/graphql/query", {
|
|
1389
1413
|
method: "POST",
|
|
1390
|
-
body,
|
|
1414
|
+
body: bodyParams.toString(),
|
|
1391
1415
|
cookiePath: options.cookiePath,
|
|
1392
1416
|
cookieDomain: "instagram.com",
|
|
1393
1417
|
signal: options.signal,
|
|
1394
|
-
headers
|
|
1395
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
1396
|
-
"X-IG-App-ID": IG_APP_ID,
|
|
1397
|
-
"X-FB-Friendly-Name": "PolarisProfileStoryHighlightsTrayContentQuery",
|
|
1398
|
-
"Referer": "https://www.instagram.com/"
|
|
1399
|
-
}
|
|
1418
|
+
headers
|
|
1400
1419
|
});
|
|
1401
1420
|
const edges = res?.data?.highlights?.edges;
|
|
1402
1421
|
if (Array.isArray(edges) && edges.length > 0) {
|
|
@@ -1407,10 +1426,16 @@ var InstagramExtractor = class {
|
|
|
1407
1426
|
}
|
|
1408
1427
|
if (res?.errors && Array.isArray(res.errors) && res.errors.length > 0) {
|
|
1409
1428
|
const errMsg = res.errors.map((e) => e.message || JSON.stringify(e)).join("; ");
|
|
1410
|
-
|
|
1429
|
+
if (errMsg.includes("login") || errMsg.includes("auth") || errMsg.includes("checkpoint")) {
|
|
1430
|
+
authError = new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
|
|
1431
|
+
}
|
|
1411
1432
|
}
|
|
1412
1433
|
} catch (err) {
|
|
1413
1434
|
logExtractorDebug("PolarisProfileStoryHighlightsTrayContentQuery", err);
|
|
1435
|
+
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")) {
|
|
1437
|
+
authError = new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
|
|
1438
|
+
}
|
|
1414
1439
|
}
|
|
1415
1440
|
const url = `https://www.instagram.com/api/v1/highlights/${userId}/highlights_tray/`;
|
|
1416
1441
|
try {
|
|
@@ -1419,12 +1444,21 @@ var InstagramExtractor = class {
|
|
|
1419
1444
|
cookieDomain: "instagram.com",
|
|
1420
1445
|
signal: options.signal,
|
|
1421
1446
|
headers: {
|
|
1422
|
-
"X-IG-App-ID": IG_APP_ID
|
|
1447
|
+
"X-IG-App-ID": IG_APP_ID,
|
|
1448
|
+
"Referer": "https://www.instagram.com/"
|
|
1423
1449
|
}
|
|
1424
1450
|
});
|
|
1451
|
+
if (res.message === "login_required" || res.message === "checkpoint_required") {
|
|
1452
|
+
throw new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
|
|
1453
|
+
}
|
|
1425
1454
|
return res.tray ?? [];
|
|
1426
1455
|
} catch (err) {
|
|
1427
1456
|
logExtractorDebug("Highlights legacy REST", err);
|
|
1457
|
+
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")) {
|
|
1459
|
+
throw new Error("Instagram Highlights require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
|
|
1460
|
+
}
|
|
1461
|
+
if (authError) throw authError;
|
|
1428
1462
|
return [];
|
|
1429
1463
|
}
|
|
1430
1464
|
}
|
|
@@ -1438,13 +1472,21 @@ var InstagramExtractor = class {
|
|
|
1438
1472
|
cookieDomain: "instagram.com",
|
|
1439
1473
|
signal: options.signal,
|
|
1440
1474
|
headers: {
|
|
1441
|
-
"X-IG-App-ID": IG_APP_ID
|
|
1475
|
+
"X-IG-App-ID": IG_APP_ID,
|
|
1476
|
+
"Referer": "https://www.instagram.com/"
|
|
1442
1477
|
}
|
|
1443
1478
|
});
|
|
1479
|
+
if (res.message === "login_required" || res.message === "checkpoint_required") {
|
|
1480
|
+
throw new Error("Instagram Stories require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
|
|
1481
|
+
}
|
|
1444
1482
|
if (res.reels_media) return res.reels_media;
|
|
1445
1483
|
if (res.reels) return Object.values(res.reels);
|
|
1446
1484
|
return [];
|
|
1447
|
-
} catch {
|
|
1485
|
+
} catch (err) {
|
|
1486
|
+
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")) {
|
|
1488
|
+
throw new Error("Instagram Stories require an active authenticated session. Your session cookie is missing or expired. Update your cookies in Settings (^s).");
|
|
1489
|
+
}
|
|
1448
1490
|
return [];
|
|
1449
1491
|
}
|
|
1450
1492
|
}
|
|
@@ -1570,16 +1612,23 @@ var InstagramExtractor = class {
|
|
|
1570
1612
|
for (const edge of connection.edges) {
|
|
1571
1613
|
const node = edge?.node;
|
|
1572
1614
|
if (node?.owner_id) {
|
|
1573
|
-
|
|
1574
|
-
|
|
1615
|
+
if (typeof node.owner_id === "object" && node.owner_id !== null) {
|
|
1616
|
+
resolvedUserId = String(node.owner_id.pk || node.owner_id.id || "");
|
|
1617
|
+
} else {
|
|
1618
|
+
resolvedUserId = String(node.owner_id);
|
|
1619
|
+
}
|
|
1620
|
+
if (resolvedUserId && resolvedUserId !== "[object Object]") break;
|
|
1575
1621
|
}
|
|
1576
1622
|
if (node?.user?.pk || node?.user?.id) {
|
|
1577
1623
|
resolvedUserId = String(node.user.pk || node.user.id);
|
|
1578
|
-
break;
|
|
1624
|
+
if (resolvedUserId && resolvedUserId !== "[object Object]") break;
|
|
1579
1625
|
}
|
|
1580
1626
|
if (typeof node?.id === "string" && node.id.includes("_")) {
|
|
1581
|
-
|
|
1582
|
-
|
|
1627
|
+
const candidate = node.id.split("_")[1];
|
|
1628
|
+
if (candidate && candidate !== "[object Object]") {
|
|
1629
|
+
resolvedUserId = candidate;
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1583
1632
|
}
|
|
1584
1633
|
}
|
|
1585
1634
|
return {
|
|
@@ -1739,10 +1788,10 @@ var InstagramExtractor = class {
|
|
|
1739
1788
|
logExtractorDebug("fetchWebTokens", err);
|
|
1740
1789
|
}
|
|
1741
1790
|
let userId = user?.id || webTokens.userId;
|
|
1742
|
-
if (!userId && (subDir === "Stories" || subDir === "Highlights")) {
|
|
1791
|
+
if ((!userId || userId === "[object Object]") && (subDir === "Stories" || subDir === "Highlights")) {
|
|
1743
1792
|
try {
|
|
1744
1793
|
const probe = await this.fetchPostsTabGraphQL(username, void 0, webTokens, options);
|
|
1745
|
-
if (probe.userId) {
|
|
1794
|
+
if (probe.userId && probe.userId !== "[object Object]") {
|
|
1746
1795
|
userId = probe.userId;
|
|
1747
1796
|
}
|
|
1748
1797
|
} catch (err) {
|
|
@@ -1750,7 +1799,9 @@ var InstagramExtractor = class {
|
|
|
1750
1799
|
}
|
|
1751
1800
|
}
|
|
1752
1801
|
if (subDir === "Stories") {
|
|
1753
|
-
if (!userId)
|
|
1802
|
+
if (!userId || userId === "[object Object]") {
|
|
1803
|
+
throw new Error(`Could not resolve user ID for @${username}. Profile may be private or unavailable.`);
|
|
1804
|
+
}
|
|
1754
1805
|
options.onProgress?.({
|
|
1755
1806
|
stage: "Stories",
|
|
1756
1807
|
found: 0,
|
|
@@ -1766,13 +1817,15 @@ var InstagramExtractor = class {
|
|
|
1766
1817
|
return;
|
|
1767
1818
|
}
|
|
1768
1819
|
if (subDir === "Highlights") {
|
|
1769
|
-
if (!userId)
|
|
1820
|
+
if (!userId || userId === "[object Object]") {
|
|
1821
|
+
throw new Error(`Could not resolve user ID for @${username}. Profile may be private or unavailable.`);
|
|
1822
|
+
}
|
|
1770
1823
|
options.onProgress?.({
|
|
1771
1824
|
stage: "Highlights",
|
|
1772
1825
|
found: 0,
|
|
1773
1826
|
message: `Fetching highlights tray for @${username}...`
|
|
1774
1827
|
});
|
|
1775
|
-
const tray = await this.fetchHighlightsTray(userId, options);
|
|
1828
|
+
const tray = await this.fetchHighlightsTray(userId, options, webTokens);
|
|
1776
1829
|
const highlightIds = tray.map((t) => t.id).filter(Boolean);
|
|
1777
1830
|
if (highlightIds.length > 0) {
|
|
1778
1831
|
const chunkSize = 5;
|
|
@@ -2967,11 +3020,12 @@ async function downloadMediaStream(items, options) {
|
|
|
2967
3020
|
}
|
|
2968
3021
|
return arch;
|
|
2969
3022
|
};
|
|
2970
|
-
|
|
2971
|
-
if (options.destDirMap) {
|
|
3023
|
+
if (options.destDirMap && Object.values(options.destDirMap).some(Boolean)) {
|
|
2972
3024
|
for (const d of Object.values(options.destDirMap)) {
|
|
2973
3025
|
if (d) await getArchiveForDir(d);
|
|
2974
3026
|
}
|
|
3027
|
+
} else {
|
|
3028
|
+
await getArchiveForDir(destDir);
|
|
2975
3029
|
}
|
|
2976
3030
|
let downloaded = 0;
|
|
2977
3031
|
let skipped = 0;
|
|
@@ -3605,7 +3659,7 @@ function buildGalleryDlArgs(options) {
|
|
|
3605
3659
|
}
|
|
3606
3660
|
async function runGalleryDl(options) {
|
|
3607
3661
|
const { profile, targetDir, subDir, cookiePath, onProgress } = options;
|
|
3608
|
-
const destDir = path8.join(targetDir, subDir);
|
|
3662
|
+
const destDir = subDir === "All" ? targetDir : path8.join(targetDir, subDir);
|
|
3609
3663
|
await fs8.mkdir(destDir, { recursive: true });
|
|
3610
3664
|
const args = buildGalleryDlArgs({
|
|
3611
3665
|
destDir,
|
|
@@ -3909,6 +3963,30 @@ function resolveDefaultDownloadDir(platform, username, baseDir) {
|
|
|
3909
3963
|
const root = resolveDownloadBaseDir(baseDir);
|
|
3910
3964
|
return path10.join(root, platform, username);
|
|
3911
3965
|
}
|
|
3966
|
+
async function cleanupEmptyCategoryDirs(targetDir) {
|
|
3967
|
+
const candidateDirs = ["All", "Stories", "Highlights", "Photos", "Videos"];
|
|
3968
|
+
for (const dirName of candidateDirs) {
|
|
3969
|
+
const fullPath = path10.join(targetDir, dirName);
|
|
3970
|
+
try {
|
|
3971
|
+
if (!fsSync.existsSync(fullPath)) continue;
|
|
3972
|
+
const entries = await fs10.readdir(fullPath);
|
|
3973
|
+
const nonArchiveEntries = entries.filter(
|
|
3974
|
+
(e) => e !== ".open-nami-archive.json" && e !== "archive.txt" && e !== "archive_yt-dlp.txt" && e !== "archive_gallery-dl.sqlite3"
|
|
3975
|
+
);
|
|
3976
|
+
if (nonArchiveEntries.length === 0) {
|
|
3977
|
+
for (const entry of entries) {
|
|
3978
|
+
try {
|
|
3979
|
+
await fs10.unlink(path10.join(fullPath, entry));
|
|
3980
|
+
} catch {
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
await fs10.rmdir(fullPath).catch(() => {
|
|
3984
|
+
});
|
|
3985
|
+
}
|
|
3986
|
+
} catch {
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3912
3990
|
async function dispatchDownload(options) {
|
|
3913
3991
|
const { profile, choice, onProgress } = options;
|
|
3914
3992
|
const targetDir = options.outputDir || resolveDefaultDownloadDir(profile.platform, profile.username);
|
|
@@ -3941,7 +4019,7 @@ async function dispatchDownload(options) {
|
|
|
3941
4019
|
const savedCheckpoint = options.resume ? await loadCheckpoint(targetDir) : null;
|
|
3942
4020
|
const currentCursors = savedCheckpoint?.cursors ? { ...savedCheckpoint.cursors } : {};
|
|
3943
4021
|
const defaultRunNative = async (subDir) => {
|
|
3944
|
-
const dest = path10.join(targetDir, subDir);
|
|
4022
|
+
const dest = subDir === "All" ? targetDir : path10.join(targetDir, subDir);
|
|
3945
4023
|
const remainingLimit = globalLimit !== void 0 ? Math.max(0, globalLimit - totalDownloaded) : void 0;
|
|
3946
4024
|
if (remainingLimit === 0) {
|
|
3947
4025
|
return { success: true, downloaded: 0, skipped: 0 };
|
|
@@ -4076,7 +4154,8 @@ async function dispatchDownload(options) {
|
|
|
4076
4154
|
} else if (job.type === "photos" || job.type === "stories" || job.type === "highlights") {
|
|
4077
4155
|
const subDir = job.type === "photos" ? "Photos" : job.type === "stories" ? "Stories" : "Highlights";
|
|
4078
4156
|
let res = await runNative(subDir);
|
|
4079
|
-
|
|
4157
|
+
const isAuthError = Boolean(res.error && (res.error.includes("cookie") || res.error.includes("authenticated session") || res.error.includes("Settings (^s)")));
|
|
4158
|
+
if (!isAuthError && (!res.success || res.downloaded === 0 && res.skipped === 0) && !options.signal?.aborted) {
|
|
4080
4159
|
onProgress?.({
|
|
4081
4160
|
job: subDir,
|
|
4082
4161
|
downloadedCount: totalDownloaded,
|
|
@@ -4084,7 +4163,7 @@ async function dispatchDownload(options) {
|
|
|
4084
4163
|
statusText: "switching to gallery-dl engine\u2026"
|
|
4085
4164
|
});
|
|
4086
4165
|
const gdlRes = await runGdl(subDir);
|
|
4087
|
-
if (gdlRes.downloaded > 0 || gdlRes.skipped > 0 || gdlRes.success) {
|
|
4166
|
+
if (gdlRes.downloaded > 0 || gdlRes.skipped > 0 || gdlRes.success && !res.error) {
|
|
4088
4167
|
res = gdlRes;
|
|
4089
4168
|
}
|
|
4090
4169
|
}
|
|
@@ -4123,6 +4202,7 @@ async function dispatchDownload(options) {
|
|
|
4123
4202
|
}
|
|
4124
4203
|
}
|
|
4125
4204
|
}
|
|
4205
|
+
await cleanupEmptyCategoryDirs(targetDir);
|
|
4126
4206
|
if (errors.length === 0 && !options.signal?.aborted) {
|
|
4127
4207
|
await clearCheckpoint(targetDir);
|
|
4128
4208
|
}
|