open-nami 1.3.0 → 1.5.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 +1115 -358
- 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.
|
|
12
|
+
version: "1.5.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: {
|
|
@@ -73,11 +73,11 @@ var require_package = __commonJS({
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
// src/cli.tsx
|
|
76
|
-
import { createRequire } from "module";
|
|
76
|
+
import { createRequire as createRequire2 } from "module";
|
|
77
77
|
import { render } from "ink";
|
|
78
78
|
|
|
79
79
|
// src/app.tsx
|
|
80
|
-
import { useState as
|
|
80
|
+
import { useState as useState10, useCallback as useCallback4, useRef as useRef5, useEffect as useEffect10, useMemo as useMemo3 } from "react";
|
|
81
81
|
import { Box as Box15, Text as Text17, useApp, useInput as useInput5, useStdout as useStdout3 } from "ink";
|
|
82
82
|
import Spinner4 from "ink-spinner";
|
|
83
83
|
|
|
@@ -730,16 +730,8 @@ async function clearCheckpoint(destDir) {
|
|
|
730
730
|
}
|
|
731
731
|
|
|
732
732
|
// src/lib/engines/dispatcher.ts
|
|
733
|
-
import
|
|
734
|
-
import
|
|
735
|
-
import fs10 from "fs/promises";
|
|
736
|
-
import fsSync from "fs";
|
|
737
|
-
|
|
738
|
-
// src/lib/cookies.ts
|
|
739
|
-
import fs2 from "fs";
|
|
740
|
-
import fsPromises2 from "fs/promises";
|
|
741
|
-
import path3 from "path";
|
|
742
|
-
import os2 from "os";
|
|
733
|
+
import path13 from "path";
|
|
734
|
+
import fs13 from "fs/promises";
|
|
743
735
|
|
|
744
736
|
// src/lib/config.ts
|
|
745
737
|
import fs from "fs";
|
|
@@ -757,9 +749,13 @@ function loadConfig(configPath = getDefaultConfigPath()) {
|
|
|
757
749
|
const raw = fs.readFileSync(configPath, "utf8");
|
|
758
750
|
const parsed = JSON.parse(raw);
|
|
759
751
|
if (parsed && typeof parsed === "object") {
|
|
752
|
+
const rawBrowser = typeof parsed.cookiesFromBrowser === "string" ? parsed.cookiesFromBrowser.trim().toLowerCase() : void 0;
|
|
753
|
+
const validBrowsers = ["chrome", "edge", "brave", "firefox", "none"];
|
|
754
|
+
const cookiesFromBrowser = rawBrowser && validBrowsers.includes(rawBrowser) ? rawBrowser : void 0;
|
|
760
755
|
return {
|
|
761
756
|
theme: typeof parsed.theme === "string" ? parsed.theme : void 0,
|
|
762
757
|
cookiesDir: typeof parsed.cookiesDir === "string" ? parsed.cookiesDir.trim() : void 0,
|
|
758
|
+
cookiesFromBrowser,
|
|
763
759
|
downloadDir: typeof parsed.downloadDir === "string" ? parsed.downloadDir.trim() : void 0,
|
|
764
760
|
writeMetadata: typeof parsed.writeMetadata === "boolean" ? parsed.writeMetadata : void 0,
|
|
765
761
|
defaultLimit: typeof parsed.defaultLimit === "number" ? parsed.defaultLimit : void 0
|
|
@@ -781,22 +777,77 @@ function saveConfig(config, configPath = getDefaultConfigPath()) {
|
|
|
781
777
|
}
|
|
782
778
|
}
|
|
783
779
|
|
|
780
|
+
// src/lib/cookies.ts
|
|
781
|
+
import fs3 from "fs";
|
|
782
|
+
import fsPromises2 from "fs/promises";
|
|
783
|
+
import path4 from "path";
|
|
784
|
+
import os3 from "os";
|
|
785
|
+
|
|
786
|
+
// src/lib/cookies/browser-bridge.ts
|
|
787
|
+
import fs2 from "fs";
|
|
788
|
+
import path3 from "path";
|
|
789
|
+
import os2 from "os";
|
|
790
|
+
function listDetectedBrowsers() {
|
|
791
|
+
const isWin = process.platform === "win32";
|
|
792
|
+
const isMac = process.platform === "darwin";
|
|
793
|
+
const home = os2.homedir();
|
|
794
|
+
const candidates = [
|
|
795
|
+
{
|
|
796
|
+
id: "chrome",
|
|
797
|
+
name: "Google Chrome",
|
|
798
|
+
winDir: path3.join(process.env.LOCALAPPDATA || path3.join(home, "AppData", "Local"), "Google", "Chrome", "User Data"),
|
|
799
|
+
macDir: path3.join(home, "Library", "Application Support", "Google", "Chrome"),
|
|
800
|
+
linuxDir: path3.join(home, ".config", "google-chrome")
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
id: "edge",
|
|
804
|
+
name: "Microsoft Edge",
|
|
805
|
+
winDir: path3.join(process.env.LOCALAPPDATA || path3.join(home, "AppData", "Local"), "Microsoft", "Edge", "User Data"),
|
|
806
|
+
macDir: path3.join(home, "Library", "Application Support", "Microsoft Edge"),
|
|
807
|
+
linuxDir: path3.join(home, ".config", "microsoft-edge")
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
id: "brave",
|
|
811
|
+
name: "Brave Browser",
|
|
812
|
+
winDir: path3.join(process.env.LOCALAPPDATA || path3.join(home, "AppData", "Local"), "BraveSoftware", "Brave-Browser", "User Data"),
|
|
813
|
+
macDir: path3.join(home, "Library", "Application Support", "BraveSoftware", "Brave-Browser"),
|
|
814
|
+
linuxDir: path3.join(home, ".config", "BraveSoftware", "Brave-Browser")
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
id: "firefox",
|
|
818
|
+
name: "Mozilla Firefox",
|
|
819
|
+
winDir: path3.join(process.env.APPDATA || path3.join(home, "AppData", "Roaming"), "Mozilla", "Firefox", "Profiles"),
|
|
820
|
+
macDir: path3.join(home, "Library", "Application Support", "Firefox", "Profiles"),
|
|
821
|
+
linuxDir: path3.join(home, ".mozilla", "firefox")
|
|
822
|
+
}
|
|
823
|
+
];
|
|
824
|
+
return candidates.map((c) => {
|
|
825
|
+
const profileDir = isWin ? c.winDir : isMac ? c.macDir : c.linuxDir;
|
|
826
|
+
return {
|
|
827
|
+
id: c.id,
|
|
828
|
+
name: c.name,
|
|
829
|
+
profileDir,
|
|
830
|
+
available: fs2.existsSync(profileDir)
|
|
831
|
+
};
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
784
835
|
// src/lib/cookies.ts
|
|
785
836
|
var CANDIDATE_DIRS = [
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
837
|
+
path4.join(os3.homedir(), ".open-nami", "cookies"),
|
|
838
|
+
path4.join(os3.homedir(), ".nami", "cookies"),
|
|
839
|
+
path4.join(process.cwd(), "cookies")
|
|
789
840
|
];
|
|
790
841
|
function getCandidateDirs(customDir) {
|
|
791
842
|
const dirs = [];
|
|
792
843
|
const configured = loadConfig().cookiesDir;
|
|
793
844
|
const custom = customDir || process.env.OPEN_NAMI_COOKIES_DIR || process.env.NAMI_COOKIES_DIR || configured;
|
|
794
845
|
if (custom && custom.trim()) {
|
|
795
|
-
dirs.push(
|
|
846
|
+
dirs.push(path4.resolve(custom.trim()));
|
|
796
847
|
}
|
|
797
|
-
const seen = new Set(dirs.map((d) =>
|
|
848
|
+
const seen = new Set(dirs.map((d) => path4.normalize(d).toLowerCase()));
|
|
798
849
|
for (const dir of CANDIDATE_DIRS) {
|
|
799
|
-
const norm =
|
|
850
|
+
const norm = path4.normalize(dir).toLowerCase();
|
|
800
851
|
if (!seen.has(norm)) {
|
|
801
852
|
seen.add(norm);
|
|
802
853
|
dirs.push(dir);
|
|
@@ -816,12 +867,12 @@ function findCookieFile(platform, customDir) {
|
|
|
816
867
|
const filenames = domainNames[platform] || [];
|
|
817
868
|
const candidateDirs = getCandidateDirs(customDir);
|
|
818
869
|
for (const dir of candidateDirs) {
|
|
819
|
-
if (!
|
|
870
|
+
if (!fs3.existsSync(dir)) continue;
|
|
820
871
|
for (const file of filenames) {
|
|
821
|
-
const fullPath =
|
|
822
|
-
if (
|
|
872
|
+
const fullPath = path4.join(dir, file);
|
|
873
|
+
if (fs3.existsSync(fullPath)) {
|
|
823
874
|
try {
|
|
824
|
-
const stats =
|
|
875
|
+
const stats = fs3.statSync(fullPath);
|
|
825
876
|
if (stats.size > 20) {
|
|
826
877
|
return fullPath;
|
|
827
878
|
}
|
|
@@ -837,14 +888,14 @@ function detectAvailableCookies(customDir) {
|
|
|
837
888
|
return platforms.filter((p) => Boolean(findCookieFile(p, customDir)));
|
|
838
889
|
}
|
|
839
890
|
async function createSafeCookieCopy(masterPath) {
|
|
840
|
-
const userTempDir =
|
|
891
|
+
const userTempDir = path4.join(os3.homedir(), ".open-nami", "temp");
|
|
841
892
|
let targetDir = userTempDir;
|
|
842
893
|
try {
|
|
843
894
|
await fsPromises2.mkdir(userTempDir, { recursive: true, mode: 448 });
|
|
844
895
|
} catch {
|
|
845
|
-
targetDir =
|
|
896
|
+
targetDir = os3.tmpdir();
|
|
846
897
|
}
|
|
847
|
-
const tempPath =
|
|
898
|
+
const tempPath = path4.join(targetDir, `nami_cookie_${Date.now()}_${Math.random().toString(36).slice(2)}.txt`);
|
|
848
899
|
await fsPromises2.copyFile(masterPath, tempPath);
|
|
849
900
|
await fsPromises2.chmod(tempPath, 384).catch(() => {
|
|
850
901
|
});
|
|
@@ -860,141 +911,6 @@ async function createSafeCookieCopy(masterPath) {
|
|
|
860
911
|
};
|
|
861
912
|
}
|
|
862
913
|
|
|
863
|
-
// src/lib/known-folders.ts
|
|
864
|
-
import { execFileSync } from "child_process";
|
|
865
|
-
import fs3 from "fs";
|
|
866
|
-
import os3 from "os";
|
|
867
|
-
import path4 from "path";
|
|
868
|
-
var KNOWN_FOLDER_DOWNLOADS_GUID = "{374DE290-123F-4565-9164-39C4925E467B}";
|
|
869
|
-
var USER_SHELL_FOLDERS_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders";
|
|
870
|
-
function expandWindowsEnv(str, env = process.env, homeDir) {
|
|
871
|
-
return str.replace(/%([^%]+)%/g, (original, name) => {
|
|
872
|
-
if (homeDir && name.toLowerCase() === "userprofile") {
|
|
873
|
-
return homeDir;
|
|
874
|
-
}
|
|
875
|
-
const key = Object.keys(env).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
876
|
-
if (key && env[key] !== void 0) {
|
|
877
|
-
return env[key];
|
|
878
|
-
}
|
|
879
|
-
return original;
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
function parseWindowsRegistryOutput(output, targetKey = KNOWN_FOLDER_DOWNLOADS_GUID) {
|
|
883
|
-
const lines = output.split(/\r?\n/);
|
|
884
|
-
const escaped = targetKey.replace(/[{}]/g, "\\$&");
|
|
885
|
-
const pattern = new RegExp(`^\\s*${escaped}\\s+REG_\\w+\\s+(.*)$`, "i");
|
|
886
|
-
for (const line of lines) {
|
|
887
|
-
const match = line.match(pattern);
|
|
888
|
-
if (match && match[1]) {
|
|
889
|
-
const rawPath = match[1].trim();
|
|
890
|
-
if (rawPath) return rawPath;
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
return void 0;
|
|
894
|
-
}
|
|
895
|
-
function parseLinuxUserDirsOutput(content) {
|
|
896
|
-
const lines = content.split(/\r?\n/);
|
|
897
|
-
for (const line of lines) {
|
|
898
|
-
const trimmed = line.trim();
|
|
899
|
-
if (trimmed.startsWith("#")) continue;
|
|
900
|
-
const match = trimmed.match(/^XDG_DOWNLOAD_DIR=["']?([^"']+)["']?$/);
|
|
901
|
-
if (match && match[1]) {
|
|
902
|
-
return match[1].trim();
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
return void 0;
|
|
906
|
-
}
|
|
907
|
-
function isPathDriveAccessible(filePath, fsExists = fs3.existsSync, platform = process.platform) {
|
|
908
|
-
try {
|
|
909
|
-
if (fsExists(filePath)) {
|
|
910
|
-
return true;
|
|
911
|
-
}
|
|
912
|
-
if (platform === "win32") {
|
|
913
|
-
const match = filePath.match(/^([a-zA-Z]:[\\/])/i);
|
|
914
|
-
const driveRoot = match ? match[1] : path4.win32.parse(path4.win32.resolve(filePath)).root;
|
|
915
|
-
return fsExists(driveRoot);
|
|
916
|
-
}
|
|
917
|
-
const normalized = filePath.replace(/\\/g, "/");
|
|
918
|
-
const mountMatch = normalized.match(/^(\/(?:media(?:\/[^/]+)?|mnt|run\/media(?:\/[^/]+)?)\/[^/]+)/);
|
|
919
|
-
if (mountMatch) {
|
|
920
|
-
return fsExists(mountMatch[1]);
|
|
921
|
-
}
|
|
922
|
-
let curr = path4.posix.dirname(normalized);
|
|
923
|
-
while (curr && curr !== "/") {
|
|
924
|
-
if (fsExists(curr)) return true;
|
|
925
|
-
curr = path4.posix.dirname(curr);
|
|
926
|
-
}
|
|
927
|
-
return fsExists("/");
|
|
928
|
-
} catch {
|
|
929
|
-
return false;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
function resolveWindowsDownloadsDir(options = {}) {
|
|
933
|
-
const homeDir = options.homeDir ?? os3.homedir();
|
|
934
|
-
const env = options.env ?? process.env;
|
|
935
|
-
const runQuery = options.regQueryFn ?? ((key, value) => {
|
|
936
|
-
try {
|
|
937
|
-
return execFileSync("reg", ["query", key, "/v", value], {
|
|
938
|
-
encoding: "utf8",
|
|
939
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
940
|
-
});
|
|
941
|
-
} catch {
|
|
942
|
-
return "";
|
|
943
|
-
}
|
|
944
|
-
});
|
|
945
|
-
const checkAccessible = options.driveAccessibleFn ?? ((p) => isPathDriveAccessible(p, fs3.existsSync, "win32"));
|
|
946
|
-
let raw = parseWindowsRegistryOutput(
|
|
947
|
-
runQuery(USER_SHELL_FOLDERS_REG_KEY, KNOWN_FOLDER_DOWNLOADS_GUID),
|
|
948
|
-
KNOWN_FOLDER_DOWNLOADS_GUID
|
|
949
|
-
);
|
|
950
|
-
if (!raw) {
|
|
951
|
-
raw = parseWindowsRegistryOutput(runQuery(USER_SHELL_FOLDERS_REG_KEY, "Downloads"), "Downloads");
|
|
952
|
-
}
|
|
953
|
-
if (raw) {
|
|
954
|
-
const expanded = expandWindowsEnv(raw, env, homeDir);
|
|
955
|
-
if (checkAccessible(expanded)) {
|
|
956
|
-
return path4.win32.resolve(expanded);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
return void 0;
|
|
960
|
-
}
|
|
961
|
-
function resolveLinuxDownloadsDir(options = {}) {
|
|
962
|
-
const homeDir = options.homeDir ?? os3.homedir();
|
|
963
|
-
const env = options.env ?? process.env;
|
|
964
|
-
const readFn = options.readFileFn ?? ((p) => {
|
|
965
|
-
try {
|
|
966
|
-
return fs3.readFileSync(p, "utf8");
|
|
967
|
-
} catch {
|
|
968
|
-
return "";
|
|
969
|
-
}
|
|
970
|
-
});
|
|
971
|
-
const checkAccessible = options.driveAccessibleFn ?? ((p) => isPathDriveAccessible(p, fs3.existsSync, "linux"));
|
|
972
|
-
const configHome = env.XDG_CONFIG_HOME || path4.join(homeDir, ".config");
|
|
973
|
-
const userDirsFile = path4.join(configHome, "user-dirs.dirs");
|
|
974
|
-
const content = readFn(userDirsFile);
|
|
975
|
-
if (!content) return void 0;
|
|
976
|
-
const raw = parseLinuxUserDirsOutput(content);
|
|
977
|
-
if (!raw) return void 0;
|
|
978
|
-
const expanded = raw.replace(/\$HOME/g, homeDir).replace(/\$\{HOME\}/g, homeDir);
|
|
979
|
-
if (checkAccessible(expanded)) {
|
|
980
|
-
return path4.posix.resolve(expanded);
|
|
981
|
-
}
|
|
982
|
-
return void 0;
|
|
983
|
-
}
|
|
984
|
-
function resolvePlatformDownloadsDir(options = {}) {
|
|
985
|
-
const platform = options.platform ?? process.platform;
|
|
986
|
-
const homeDir = options.homeDir ?? os3.homedir();
|
|
987
|
-
const defaultFallback = path4.join(homeDir, "Downloads");
|
|
988
|
-
if (platform === "win32") {
|
|
989
|
-
const resolved = resolveWindowsDownloadsDir(options);
|
|
990
|
-
if (resolved) return resolved;
|
|
991
|
-
} else if (platform === "linux") {
|
|
992
|
-
const resolved = resolveLinuxDownloadsDir(options);
|
|
993
|
-
if (resolved) return resolved;
|
|
994
|
-
}
|
|
995
|
-
return defaultFallback;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
914
|
// src/lib/engines/extractors/instagram.ts
|
|
999
915
|
import crypto from "crypto";
|
|
1000
916
|
|
|
@@ -1379,8 +1295,8 @@ var InstagramExtractor = class {
|
|
|
1379
1295
|
}
|
|
1380
1296
|
const user = res.data.user;
|
|
1381
1297
|
if (user.is_private) {
|
|
1382
|
-
const
|
|
1383
|
-
const isFollowing =
|
|
1298
|
+
const fs18 = user.friendship_status;
|
|
1299
|
+
const isFollowing = fs18 ? Boolean(fs18.following) : Boolean(user.followed_by_viewer);
|
|
1384
1300
|
if (!isFollowing) {
|
|
1385
1301
|
throw new Error(`Instagram profile @${username} is private. You must provide a session cookie jar of an account following this profile.`);
|
|
1386
1302
|
}
|
|
@@ -2947,6 +2863,30 @@ var FacebookExtractor = class {
|
|
|
2947
2863
|
}
|
|
2948
2864
|
};
|
|
2949
2865
|
|
|
2866
|
+
// src/lib/engines/core/plugin.ts
|
|
2867
|
+
var ExtractorPluginRegistry = class {
|
|
2868
|
+
plugins = /* @__PURE__ */ new Map();
|
|
2869
|
+
register(plugin) {
|
|
2870
|
+
this.plugins.set(plugin.platform, plugin);
|
|
2871
|
+
}
|
|
2872
|
+
get(platform) {
|
|
2873
|
+
return this.plugins.get(platform);
|
|
2874
|
+
}
|
|
2875
|
+
has(platform) {
|
|
2876
|
+
return this.plugins.has(platform);
|
|
2877
|
+
}
|
|
2878
|
+
list() {
|
|
2879
|
+
return Array.from(this.plugins.values());
|
|
2880
|
+
}
|
|
2881
|
+
findByUrl(url) {
|
|
2882
|
+
return this.list().find((plugin) => plugin.canHandle(url));
|
|
2883
|
+
}
|
|
2884
|
+
clear() {
|
|
2885
|
+
this.plugins.clear();
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
var defaultPluginRegistry = new ExtractorPluginRegistry();
|
|
2889
|
+
|
|
2950
2890
|
// src/lib/engines/extractors/index.ts
|
|
2951
2891
|
function getExtractor(platform) {
|
|
2952
2892
|
switch (platform) {
|
|
@@ -3750,7 +3690,7 @@ var PLATFORM_SLEEP = {
|
|
|
3750
3690
|
facebook: "1.0-1.8"
|
|
3751
3691
|
};
|
|
3752
3692
|
function buildGalleryDlArgs(options) {
|
|
3753
|
-
const { destDir, subDir, platform, cleanUrl, username, cookiePath, writeMetadata } = options;
|
|
3693
|
+
const { destDir, subDir, platform, cleanUrl, username, cookiePath, cookiesFromBrowser, writeMetadata } = options;
|
|
3754
3694
|
const args = [
|
|
3755
3695
|
"-D",
|
|
3756
3696
|
destDir,
|
|
@@ -3801,6 +3741,9 @@ function buildGalleryDlArgs(options) {
|
|
|
3801
3741
|
if (cookiePath) {
|
|
3802
3742
|
args.push("--cookies", cookiePath);
|
|
3803
3743
|
}
|
|
3744
|
+
if (cookiesFromBrowser && cookiesFromBrowser !== "none") {
|
|
3745
|
+
args.push("--cookies-from-browser", cookiesFromBrowser);
|
|
3746
|
+
}
|
|
3804
3747
|
args.push("--", targetUrl);
|
|
3805
3748
|
return args;
|
|
3806
3749
|
}
|
|
@@ -3815,6 +3758,7 @@ async function runGalleryDl(options) {
|
|
|
3815
3758
|
cleanUrl: profile.cleanUrl,
|
|
3816
3759
|
username: profile.username,
|
|
3817
3760
|
cookiePath,
|
|
3761
|
+
cookiesFromBrowser: options.cookiesFromBrowser,
|
|
3818
3762
|
writeMetadata: options.writeMetadata
|
|
3819
3763
|
});
|
|
3820
3764
|
let downloadedCount = 0;
|
|
@@ -3923,7 +3867,7 @@ import fs9 from "fs/promises";
|
|
|
3923
3867
|
var YTDLP_ARCHIVE = "archive_yt-dlp.txt";
|
|
3924
3868
|
var UA2 = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
3925
3869
|
function buildYtDlpArgs(options) {
|
|
3926
|
-
const { destDir, platform, cleanUrl, cookiePath, writeMetadata } = options;
|
|
3870
|
+
const { destDir, platform, cleanUrl, cookiePath, cookiesFromBrowser, writeMetadata } = options;
|
|
3927
3871
|
const args = [
|
|
3928
3872
|
"-o",
|
|
3929
3873
|
path9.join(destDir, "%(title).150B [%(id)s].%(ext)s"),
|
|
@@ -3957,6 +3901,9 @@ function buildYtDlpArgs(options) {
|
|
|
3957
3901
|
if (cookiePath) {
|
|
3958
3902
|
args.push("--cookies", cookiePath);
|
|
3959
3903
|
}
|
|
3904
|
+
if (cookiesFromBrowser && cookiesFromBrowser !== "none") {
|
|
3905
|
+
args.push("--cookies-from-browser", cookiesFromBrowser);
|
|
3906
|
+
}
|
|
3960
3907
|
args.push("--", cleanUrl);
|
|
3961
3908
|
return args;
|
|
3962
3909
|
}
|
|
@@ -3969,6 +3916,7 @@ async function runYtDlp(options) {
|
|
|
3969
3916
|
platform: profile.platform,
|
|
3970
3917
|
cleanUrl: profile.cleanUrl,
|
|
3971
3918
|
cookiePath,
|
|
3919
|
+
cookiesFromBrowser: options.cookiesFromBrowser,
|
|
3972
3920
|
writeMetadata: options.writeMetadata
|
|
3973
3921
|
});
|
|
3974
3922
|
let downloadedCount = 0;
|
|
@@ -4075,98 +4023,507 @@ async function runYtDlp(options) {
|
|
|
4075
4023
|
});
|
|
4076
4024
|
}
|
|
4077
4025
|
|
|
4078
|
-
// src/lib/engines/
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4026
|
+
// src/lib/engines/core/task-manager.ts
|
|
4027
|
+
import path12 from "path";
|
|
4028
|
+
import os7 from "os";
|
|
4029
|
+
import fs12 from "fs/promises";
|
|
4030
|
+
import fsSync from "fs";
|
|
4031
|
+
|
|
4032
|
+
// src/lib/engines/core/event-bus.ts
|
|
4033
|
+
import { EventEmitter } from "events";
|
|
4034
|
+
var EngineEventBus = class {
|
|
4035
|
+
emitter = new EventEmitter();
|
|
4036
|
+
constructor() {
|
|
4037
|
+
this.emitter.setMaxListeners(50);
|
|
4088
4038
|
}
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
if (customBaseDir && customBaseDir.trim()) {
|
|
4093
|
-
return path10.resolve(expandPath(customBaseDir.trim()));
|
|
4039
|
+
on(event, listener) {
|
|
4040
|
+
this.emitter.on(event, listener);
|
|
4041
|
+
return () => this.emitter.off(event, listener);
|
|
4094
4042
|
}
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
return path10.resolve(expandPath(envDir.trim()));
|
|
4043
|
+
off(event, listener) {
|
|
4044
|
+
this.emitter.off(event, listener);
|
|
4098
4045
|
}
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
return path10.resolve(expandPath(configDir.trim()));
|
|
4046
|
+
once(event, listener) {
|
|
4047
|
+
this.emitter.once(event, listener);
|
|
4102
4048
|
}
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
return path10.resolve(expandPath(raw.endsWith("downloads") ? raw : path10.join(raw, "downloads")));
|
|
4049
|
+
emit(event, payload) {
|
|
4050
|
+
return this.emitter.emit(event, payload);
|
|
4106
4051
|
}
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
}
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4052
|
+
removeAllListeners(event) {
|
|
4053
|
+
if (event !== void 0) {
|
|
4054
|
+
this.emitter.removeAllListeners(event);
|
|
4055
|
+
} else {
|
|
4056
|
+
this.emitter.removeAllListeners();
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
};
|
|
4060
|
+
|
|
4061
|
+
// src/lib/engines/storage/ledger.ts
|
|
4062
|
+
import fs10 from "fs";
|
|
4063
|
+
import path10 from "path";
|
|
4064
|
+
import os5 from "os";
|
|
4065
|
+
import { createRequire } from "module";
|
|
4066
|
+
var require2 = createRequire(import.meta.url);
|
|
4067
|
+
var MediaLedger = class {
|
|
4068
|
+
db = null;
|
|
4069
|
+
inMemoryFallback = {
|
|
4070
|
+
media: /* @__PURE__ */ new Map(),
|
|
4071
|
+
checkpoints: /* @__PURE__ */ new Map()
|
|
4072
|
+
};
|
|
4073
|
+
isFallback = false;
|
|
4074
|
+
resolvedPath;
|
|
4075
|
+
constructor(dbPath, forceFallback = false) {
|
|
4076
|
+
const resolvedPath = dbPath ?? (forceFallback ? ":memory:" : path10.join(os5.homedir(), ".open-nami", "nami.db"));
|
|
4077
|
+
this.resolvedPath = resolvedPath;
|
|
4078
|
+
if (forceFallback) {
|
|
4079
|
+
this.isFallback = true;
|
|
4080
|
+
this.loadFallbackDisk();
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
4117
4083
|
try {
|
|
4118
|
-
if (
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
);
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4084
|
+
if (resolvedPath !== ":memory:") {
|
|
4085
|
+
fs10.mkdirSync(path10.dirname(resolvedPath), { recursive: true });
|
|
4086
|
+
}
|
|
4087
|
+
const { DatabaseSync } = require2("node:sqlite");
|
|
4088
|
+
this.db = new DatabaseSync(resolvedPath);
|
|
4089
|
+
this.initTables();
|
|
4090
|
+
} catch {
|
|
4091
|
+
this.isFallback = true;
|
|
4092
|
+
this.loadFallbackDisk();
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
loadFallbackDisk() {
|
|
4096
|
+
if (this.resolvedPath === ":memory:") return;
|
|
4097
|
+
try {
|
|
4098
|
+
if (fs10.existsSync(this.resolvedPath)) {
|
|
4099
|
+
const raw = fs10.readFileSync(this.resolvedPath, "utf8");
|
|
4100
|
+
const data = JSON.parse(raw);
|
|
4101
|
+
if (data && typeof data === "object") {
|
|
4102
|
+
if (data.media && typeof data.media === "object") {
|
|
4103
|
+
for (const [k, v] of Object.entries(data.media)) {
|
|
4104
|
+
this.inMemoryFallback.media.set(k, v);
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
if (data.checkpoints && typeof data.checkpoints === "object") {
|
|
4108
|
+
for (const [k, v] of Object.entries(data.checkpoints)) {
|
|
4109
|
+
this.inMemoryFallback.checkpoints.set(k, v);
|
|
4110
|
+
}
|
|
4128
4111
|
}
|
|
4129
4112
|
}
|
|
4130
|
-
await fs10.rmdir(fullPath).catch(() => {
|
|
4131
|
-
});
|
|
4132
4113
|
}
|
|
4133
4114
|
} catch {
|
|
4134
4115
|
}
|
|
4135
4116
|
}
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
try {
|
|
4147
|
-
const jobs = [];
|
|
4148
|
-
if (choice === "photos") {
|
|
4149
|
-
jobs.push({ type: "photos" });
|
|
4150
|
-
} else if (choice === "videos") {
|
|
4151
|
-
jobs.push({ type: "videos" });
|
|
4152
|
-
} else if (choice === "stories") {
|
|
4153
|
-
jobs.push({ type: "stories" });
|
|
4154
|
-
} else if (choice === "highlights") {
|
|
4155
|
-
jobs.push({ type: "highlights" });
|
|
4156
|
-
} else if (choice === "all") {
|
|
4157
|
-
jobs.push({ type: "all" });
|
|
4158
|
-
if (profile.platform === "instagram") {
|
|
4159
|
-
jobs.push({ type: "stories" });
|
|
4160
|
-
jobs.push({ type: "highlights" });
|
|
4161
|
-
}
|
|
4117
|
+
flushFallbackDisk() {
|
|
4118
|
+
if (this.resolvedPath === ":memory:") return;
|
|
4119
|
+
try {
|
|
4120
|
+
fs10.mkdirSync(path10.dirname(this.resolvedPath), { recursive: true });
|
|
4121
|
+
const data = {
|
|
4122
|
+
media: Object.fromEntries(this.inMemoryFallback.media),
|
|
4123
|
+
checkpoints: Object.fromEntries(this.inMemoryFallback.checkpoints)
|
|
4124
|
+
};
|
|
4125
|
+
fs10.writeFileSync(this.resolvedPath, JSON.stringify(data, null, 2), "utf8");
|
|
4126
|
+
} catch {
|
|
4162
4127
|
}
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4128
|
+
}
|
|
4129
|
+
initTables() {
|
|
4130
|
+
if (!this.db) return;
|
|
4131
|
+
this.db.exec(`
|
|
4132
|
+
CREATE TABLE IF NOT EXISTS media_ledger (
|
|
4133
|
+
id TEXT PRIMARY KEY,
|
|
4134
|
+
platform TEXT,
|
|
4135
|
+
author TEXT,
|
|
4136
|
+
url TEXT,
|
|
4137
|
+
filename TEXT,
|
|
4138
|
+
file_path TEXT,
|
|
4139
|
+
downloaded_at TEXT
|
|
4140
|
+
);
|
|
4141
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
4142
|
+
key TEXT PRIMARY KEY,
|
|
4143
|
+
platform TEXT,
|
|
4144
|
+
username TEXT,
|
|
4145
|
+
target_dir TEXT,
|
|
4146
|
+
cursors_json TEXT,
|
|
4147
|
+
updated_at TEXT
|
|
4148
|
+
);
|
|
4149
|
+
`);
|
|
4150
|
+
}
|
|
4151
|
+
hasMedia(id) {
|
|
4152
|
+
if (this.isFallback || !this.db) {
|
|
4153
|
+
return this.inMemoryFallback.media.has(id);
|
|
4154
|
+
}
|
|
4155
|
+
const stmt = this.db.prepare("SELECT 1 FROM media_ledger WHERE id = ?");
|
|
4156
|
+
const row = stmt.get(id);
|
|
4157
|
+
return Boolean(row);
|
|
4158
|
+
}
|
|
4159
|
+
recordMedia(media) {
|
|
4160
|
+
if (this.isFallback || !this.db) {
|
|
4161
|
+
this.inMemoryFallback.media.set(media.id, media);
|
|
4162
|
+
this.flushFallbackDisk();
|
|
4163
|
+
return;
|
|
4164
|
+
}
|
|
4165
|
+
const stmt = this.db.prepare(`
|
|
4166
|
+
INSERT OR REPLACE INTO media_ledger (id, platform, author, url, filename, file_path, downloaded_at)
|
|
4167
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
4168
|
+
`);
|
|
4169
|
+
stmt.run(
|
|
4170
|
+
media.id,
|
|
4171
|
+
media.platform,
|
|
4172
|
+
media.author,
|
|
4173
|
+
media.url,
|
|
4174
|
+
media.filename,
|
|
4175
|
+
media.filePath,
|
|
4176
|
+
media.downloadedAt
|
|
4177
|
+
);
|
|
4178
|
+
}
|
|
4179
|
+
saveCheckpoint(checkpoint) {
|
|
4180
|
+
const key = `${checkpoint.platform}:${checkpoint.username.toLowerCase()}`;
|
|
4181
|
+
if (this.isFallback || !this.db) {
|
|
4182
|
+
this.inMemoryFallback.checkpoints.set(key, checkpoint);
|
|
4183
|
+
this.flushFallbackDisk();
|
|
4184
|
+
return;
|
|
4185
|
+
}
|
|
4186
|
+
const stmt = this.db.prepare(`
|
|
4187
|
+
INSERT OR REPLACE INTO checkpoints (key, platform, username, target_dir, cursors_json, updated_at)
|
|
4188
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
4189
|
+
`);
|
|
4190
|
+
stmt.run(
|
|
4191
|
+
key,
|
|
4192
|
+
checkpoint.platform,
|
|
4193
|
+
checkpoint.username,
|
|
4194
|
+
checkpoint.targetDir,
|
|
4195
|
+
JSON.stringify(checkpoint.cursors),
|
|
4196
|
+
checkpoint.updatedAt
|
|
4197
|
+
);
|
|
4198
|
+
}
|
|
4199
|
+
getCheckpoint(platform, username) {
|
|
4200
|
+
const key = `${platform}:${username.toLowerCase()}`;
|
|
4201
|
+
if (this.isFallback || !this.db) {
|
|
4202
|
+
return this.inMemoryFallback.checkpoints.get(key) || null;
|
|
4203
|
+
}
|
|
4204
|
+
const stmt = this.db.prepare(
|
|
4205
|
+
"SELECT platform, username, target_dir, cursors_json, updated_at FROM checkpoints WHERE key = ?"
|
|
4206
|
+
);
|
|
4207
|
+
const row = stmt.get(key);
|
|
4208
|
+
if (!row) return null;
|
|
4209
|
+
return {
|
|
4210
|
+
platform: row.platform,
|
|
4211
|
+
username: row.username,
|
|
4212
|
+
targetDir: row.target_dir,
|
|
4213
|
+
cursors: JSON.parse(row.cursors_json || "{}"),
|
|
4214
|
+
updatedAt: row.updated_at
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4217
|
+
clearCheckpoint(platform, username) {
|
|
4218
|
+
const key = `${platform}:${username.toLowerCase()}`;
|
|
4219
|
+
if (this.isFallback || !this.db) {
|
|
4220
|
+
this.inMemoryFallback.checkpoints.delete(key);
|
|
4221
|
+
this.flushFallbackDisk();
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
4224
|
+
const stmt = this.db.prepare("DELETE FROM checkpoints WHERE key = ?");
|
|
4225
|
+
stmt.run(key);
|
|
4226
|
+
}
|
|
4227
|
+
close() {
|
|
4228
|
+
if (this.db) {
|
|
4229
|
+
try {
|
|
4230
|
+
this.db.close();
|
|
4231
|
+
} catch {
|
|
4232
|
+
}
|
|
4233
|
+
this.db = null;
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
};
|
|
4237
|
+
|
|
4238
|
+
// src/lib/known-folders.ts
|
|
4239
|
+
import { execFileSync } from "child_process";
|
|
4240
|
+
import fs11 from "fs";
|
|
4241
|
+
import os6 from "os";
|
|
4242
|
+
import path11 from "path";
|
|
4243
|
+
var KNOWN_FOLDER_DOWNLOADS_GUID = "{374DE290-123F-4565-9164-39C4925E467B}";
|
|
4244
|
+
var USER_SHELL_FOLDERS_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders";
|
|
4245
|
+
function expandWindowsEnv(str, env = process.env, homeDir) {
|
|
4246
|
+
return str.replace(/%([^%]+)%/g, (original, name) => {
|
|
4247
|
+
if (homeDir && name.toLowerCase() === "userprofile") {
|
|
4248
|
+
return homeDir;
|
|
4249
|
+
}
|
|
4250
|
+
const key = Object.keys(env).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
4251
|
+
if (key && env[key] !== void 0) {
|
|
4252
|
+
return env[key];
|
|
4253
|
+
}
|
|
4254
|
+
return original;
|
|
4255
|
+
});
|
|
4256
|
+
}
|
|
4257
|
+
function parseWindowsRegistryOutput(output, targetKey = KNOWN_FOLDER_DOWNLOADS_GUID) {
|
|
4258
|
+
const lines = output.split(/\r?\n/);
|
|
4259
|
+
const escaped = targetKey.replace(/[{}]/g, "\\$&");
|
|
4260
|
+
const pattern = new RegExp(`^\\s*${escaped}\\s+REG_\\w+\\s+(.*)$`, "i");
|
|
4261
|
+
for (const line of lines) {
|
|
4262
|
+
const match = line.match(pattern);
|
|
4263
|
+
if (match && match[1]) {
|
|
4264
|
+
const rawPath = match[1].trim();
|
|
4265
|
+
if (rawPath) return rawPath;
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
return void 0;
|
|
4269
|
+
}
|
|
4270
|
+
function parseLinuxUserDirsOutput(content) {
|
|
4271
|
+
const lines = content.split(/\r?\n/);
|
|
4272
|
+
for (const line of lines) {
|
|
4273
|
+
const trimmed = line.trim();
|
|
4274
|
+
if (trimmed.startsWith("#")) continue;
|
|
4275
|
+
const match = trimmed.match(/^XDG_DOWNLOAD_DIR=["']?([^"']+)["']?$/);
|
|
4276
|
+
if (match && match[1]) {
|
|
4277
|
+
return match[1].trim();
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
return void 0;
|
|
4281
|
+
}
|
|
4282
|
+
function isPathDriveAccessible(filePath, fsExists = fs11.existsSync, platform = process.platform) {
|
|
4283
|
+
try {
|
|
4284
|
+
if (fsExists(filePath)) {
|
|
4285
|
+
return true;
|
|
4286
|
+
}
|
|
4287
|
+
if (platform === "win32") {
|
|
4288
|
+
const match = filePath.match(/^([a-zA-Z]:[\\/])/i);
|
|
4289
|
+
const driveRoot = match ? match[1] : path11.win32.parse(path11.win32.resolve(filePath)).root;
|
|
4290
|
+
return fsExists(driveRoot);
|
|
4291
|
+
}
|
|
4292
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4293
|
+
const mountMatch = normalized.match(/^(\/(?:media(?:\/[^/]+)?|mnt|run\/media(?:\/[^/]+)?)\/[^/]+)/);
|
|
4294
|
+
if (mountMatch) {
|
|
4295
|
+
return fsExists(mountMatch[1]);
|
|
4296
|
+
}
|
|
4297
|
+
let curr = path11.posix.dirname(normalized);
|
|
4298
|
+
while (curr && curr !== "/") {
|
|
4299
|
+
if (fsExists(curr)) return true;
|
|
4300
|
+
curr = path11.posix.dirname(curr);
|
|
4301
|
+
}
|
|
4302
|
+
return fsExists("/");
|
|
4303
|
+
} catch {
|
|
4304
|
+
return false;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
function resolveWindowsDownloadsDir(options = {}) {
|
|
4308
|
+
const homeDir = options.homeDir ?? os6.homedir();
|
|
4309
|
+
const env = options.env ?? process.env;
|
|
4310
|
+
const runQuery = options.regQueryFn ?? ((key, value) => {
|
|
4311
|
+
try {
|
|
4312
|
+
return execFileSync("reg", ["query", key, "/v", value], {
|
|
4313
|
+
encoding: "utf8",
|
|
4314
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
4315
|
+
});
|
|
4316
|
+
} catch {
|
|
4317
|
+
return "";
|
|
4318
|
+
}
|
|
4319
|
+
});
|
|
4320
|
+
const checkAccessible = options.driveAccessibleFn ?? ((p) => isPathDriveAccessible(p, fs11.existsSync, "win32"));
|
|
4321
|
+
let raw = parseWindowsRegistryOutput(
|
|
4322
|
+
runQuery(USER_SHELL_FOLDERS_REG_KEY, KNOWN_FOLDER_DOWNLOADS_GUID),
|
|
4323
|
+
KNOWN_FOLDER_DOWNLOADS_GUID
|
|
4324
|
+
);
|
|
4325
|
+
if (!raw) {
|
|
4326
|
+
raw = parseWindowsRegistryOutput(runQuery(USER_SHELL_FOLDERS_REG_KEY, "Downloads"), "Downloads");
|
|
4327
|
+
}
|
|
4328
|
+
if (raw) {
|
|
4329
|
+
const expanded = expandWindowsEnv(raw, env, homeDir);
|
|
4330
|
+
if (checkAccessible(expanded)) {
|
|
4331
|
+
return path11.win32.resolve(expanded);
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
return void 0;
|
|
4335
|
+
}
|
|
4336
|
+
function resolveLinuxDownloadsDir(options = {}) {
|
|
4337
|
+
const homeDir = options.homeDir ?? os6.homedir();
|
|
4338
|
+
const env = options.env ?? process.env;
|
|
4339
|
+
const readFn = options.readFileFn ?? ((p) => {
|
|
4340
|
+
try {
|
|
4341
|
+
return fs11.readFileSync(p, "utf8");
|
|
4342
|
+
} catch {
|
|
4343
|
+
return "";
|
|
4344
|
+
}
|
|
4345
|
+
});
|
|
4346
|
+
const checkAccessible = options.driveAccessibleFn ?? ((p) => isPathDriveAccessible(p, fs11.existsSync, "linux"));
|
|
4347
|
+
const configHome = env.XDG_CONFIG_HOME || path11.join(homeDir, ".config");
|
|
4348
|
+
const userDirsFile = path11.join(configHome, "user-dirs.dirs");
|
|
4349
|
+
const content = readFn(userDirsFile);
|
|
4350
|
+
if (!content) return void 0;
|
|
4351
|
+
const raw = parseLinuxUserDirsOutput(content);
|
|
4352
|
+
if (!raw) return void 0;
|
|
4353
|
+
const expanded = raw.replace(/\$HOME/g, homeDir).replace(/\$\{HOME\}/g, homeDir);
|
|
4354
|
+
if (checkAccessible(expanded)) {
|
|
4355
|
+
return path11.posix.resolve(expanded);
|
|
4356
|
+
}
|
|
4357
|
+
return void 0;
|
|
4358
|
+
}
|
|
4359
|
+
function resolvePlatformDownloadsDir(options = {}) {
|
|
4360
|
+
const platform = options.platform ?? process.platform;
|
|
4361
|
+
const homeDir = options.homeDir ?? os6.homedir();
|
|
4362
|
+
const defaultFallback = path11.join(homeDir, "Downloads");
|
|
4363
|
+
if (platform === "win32") {
|
|
4364
|
+
const resolved = resolveWindowsDownloadsDir(options);
|
|
4365
|
+
if (resolved) return resolved;
|
|
4366
|
+
} else if (platform === "linux") {
|
|
4367
|
+
const resolved = resolveLinuxDownloadsDir(options);
|
|
4368
|
+
if (resolved) return resolved;
|
|
4369
|
+
}
|
|
4370
|
+
return defaultFallback;
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4373
|
+
// src/lib/engines/core/task-manager.ts
|
|
4374
|
+
function expandPath(dir) {
|
|
4375
|
+
if (dir.startsWith("~/") || dir.startsWith("~\\") || dir === "~") {
|
|
4376
|
+
return path12.join(os7.homedir(), dir.slice(1));
|
|
4377
|
+
}
|
|
4378
|
+
return dir;
|
|
4379
|
+
}
|
|
4380
|
+
function resolveDownloadBaseDir(customBaseDir) {
|
|
4381
|
+
if (customBaseDir && customBaseDir.trim()) {
|
|
4382
|
+
return path12.resolve(expandPath(customBaseDir.trim()));
|
|
4383
|
+
}
|
|
4384
|
+
const envDir = process.env.OPEN_NAMI_DIR || process.env.NAMI_DIR;
|
|
4385
|
+
if (envDir && envDir.trim()) {
|
|
4386
|
+
return path12.resolve(expandPath(envDir.trim()));
|
|
4387
|
+
}
|
|
4388
|
+
const configDir = loadConfig().downloadDir;
|
|
4389
|
+
if (configDir && configDir.trim()) {
|
|
4390
|
+
return path12.resolve(expandPath(configDir.trim()));
|
|
4391
|
+
}
|
|
4392
|
+
if (process.env.NAMI_BASE_DIR && process.env.NAMI_BASE_DIR.trim()) {
|
|
4393
|
+
const raw = process.env.NAMI_BASE_DIR.trim();
|
|
4394
|
+
return path12.resolve(expandPath(raw.endsWith("downloads") ? raw : path12.join(raw, "downloads")));
|
|
4395
|
+
}
|
|
4396
|
+
return path12.join(resolvePlatformDownloadsDir(), "Open Nami");
|
|
4397
|
+
}
|
|
4398
|
+
function resolveDefaultDownloadDir(platform, username, baseDir) {
|
|
4399
|
+
const root = resolveDownloadBaseDir(baseDir);
|
|
4400
|
+
return path12.join(root, platform, username);
|
|
4401
|
+
}
|
|
4402
|
+
async function cleanupEmptyCategoryDirs(targetDir) {
|
|
4403
|
+
const candidateDirs = ["All", "Stories", "Highlights", "Photos", "Videos"];
|
|
4404
|
+
for (const dirName of candidateDirs) {
|
|
4405
|
+
const fullPath = path12.join(targetDir, dirName);
|
|
4406
|
+
try {
|
|
4407
|
+
if (!fsSync.existsSync(fullPath)) continue;
|
|
4408
|
+
const entries = await fs12.readdir(fullPath);
|
|
4409
|
+
const nonArchiveEntries = entries.filter(
|
|
4410
|
+
(e) => e !== ".open-nami-archive.json" && e !== "archive.txt" && e !== "archive_yt-dlp.txt" && e !== "archive_gallery-dl.sqlite3"
|
|
4411
|
+
);
|
|
4412
|
+
if (nonArchiveEntries.length === 0) {
|
|
4413
|
+
for (const entry of entries) {
|
|
4414
|
+
try {
|
|
4415
|
+
await fs12.unlink(path12.join(fullPath, entry));
|
|
4416
|
+
} catch {
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
await fs12.rmdir(fullPath).catch(() => {
|
|
4420
|
+
});
|
|
4421
|
+
}
|
|
4422
|
+
} catch {
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
}
|
|
4426
|
+
var TaskManager = class {
|
|
4427
|
+
events;
|
|
4428
|
+
ledger;
|
|
4429
|
+
plugins;
|
|
4430
|
+
constructor(eventBus, ledger, plugins) {
|
|
4431
|
+
this.events = eventBus || new EngineEventBus();
|
|
4432
|
+
this.ledger = ledger || new MediaLedger();
|
|
4433
|
+
this.plugins = plugins || defaultPluginRegistry;
|
|
4434
|
+
}
|
|
4435
|
+
async execute(options) {
|
|
4436
|
+
const { profile, choice } = options;
|
|
4437
|
+
const targetDir = options.outputDir || resolveDefaultDownloadDir(profile.platform, profile.username);
|
|
4438
|
+
this.events.emit("job:start", { profile, choice, outputDir: targetDir });
|
|
4439
|
+
let downloadedCount = 0;
|
|
4440
|
+
let skippedCount = 0;
|
|
4441
|
+
const errors = [];
|
|
4442
|
+
if (options.runnerOverrides?.nativeRunner) {
|
|
4443
|
+
const subDir = choice === "all" ? "All" : choice.charAt(0).toUpperCase() + choice.slice(1);
|
|
4444
|
+
const runRes = await options.runnerOverrides.nativeRunner(subDir);
|
|
4445
|
+
downloadedCount += runRes.downloaded;
|
|
4446
|
+
skippedCount += runRes.skipped;
|
|
4447
|
+
if (!runRes.success) errors.push(runRes.error || "Execution failed");
|
|
4448
|
+
}
|
|
4449
|
+
await cleanupEmptyCategoryDirs(targetDir);
|
|
4450
|
+
const result = {
|
|
4451
|
+
success: errors.length === 0,
|
|
4452
|
+
downloadedCount,
|
|
4453
|
+
skippedCount,
|
|
4454
|
+
errors,
|
|
4455
|
+
outputDir: targetDir
|
|
4456
|
+
};
|
|
4457
|
+
this.events.emit("job:complete", { result });
|
|
4458
|
+
return result;
|
|
4459
|
+
}
|
|
4460
|
+
};
|
|
4461
|
+
var defaultTaskManager = new TaskManager();
|
|
4462
|
+
|
|
4463
|
+
// src/lib/engines/dispatcher.ts
|
|
4464
|
+
var REFERER_MAP = {
|
|
4465
|
+
tiktok: "https://www.tiktok.com/",
|
|
4466
|
+
instagram: "https://www.instagram.com/",
|
|
4467
|
+
x: "https://x.com/",
|
|
4468
|
+
facebook: "https://www.facebook.com/"
|
|
4469
|
+
};
|
|
4470
|
+
async function dispatchDownload(options) {
|
|
4471
|
+
const { profile, choice, onProgress } = options;
|
|
4472
|
+
const targetDir = options.outputDir || resolveDefaultDownloadDir(profile.platform, profile.username);
|
|
4473
|
+
await fs13.mkdir(targetDir, { recursive: true });
|
|
4474
|
+
const cookiesFromBrowser = options.cookiesFromBrowser ?? loadConfig().cookiesFromBrowser;
|
|
4475
|
+
defaultTaskManager.events.emit("job:start", { profile, choice, outputDir: targetDir });
|
|
4476
|
+
const masterCookie = findCookieFile(profile.platform);
|
|
4477
|
+
const cookieCopy = masterCookie ? await createSafeCookieCopy(masterCookie) : null;
|
|
4478
|
+
let totalDownloaded = 0;
|
|
4479
|
+
let totalSkipped = 0;
|
|
4480
|
+
const errors = [];
|
|
4481
|
+
const recordError = (err) => {
|
|
4482
|
+
errors.push(err);
|
|
4483
|
+
defaultTaskManager.events.emit("job:error", { error: err });
|
|
4484
|
+
};
|
|
4485
|
+
const handleProgress = (progress) => {
|
|
4486
|
+
defaultTaskManager.events.emit("job:progress", progress);
|
|
4487
|
+
if (progress.currentFile && progress.statusText?.startsWith("Saved:")) {
|
|
4488
|
+
try {
|
|
4489
|
+
defaultTaskManager.ledger.recordMedia({
|
|
4490
|
+
id: `${profile.platform}:${profile.username}:${progress.currentFile}`,
|
|
4491
|
+
platform: profile.platform,
|
|
4492
|
+
author: profile.username,
|
|
4493
|
+
url: profile.cleanUrl || "",
|
|
4494
|
+
filename: progress.currentFile,
|
|
4495
|
+
filePath: path13.join(targetDir, progress.currentFile),
|
|
4496
|
+
downloadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4497
|
+
});
|
|
4498
|
+
} catch {
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
onProgress?.(progress);
|
|
4502
|
+
};
|
|
4503
|
+
try {
|
|
4504
|
+
const jobs = [];
|
|
4505
|
+
if (choice === "photos") {
|
|
4506
|
+
jobs.push({ type: "photos" });
|
|
4507
|
+
} else if (choice === "videos") {
|
|
4508
|
+
jobs.push({ type: "videos" });
|
|
4509
|
+
} else if (choice === "stories") {
|
|
4510
|
+
jobs.push({ type: "stories" });
|
|
4511
|
+
} else if (choice === "highlights") {
|
|
4512
|
+
jobs.push({ type: "highlights" });
|
|
4513
|
+
} else if (choice === "all") {
|
|
4514
|
+
jobs.push({ type: "all" });
|
|
4515
|
+
if (profile.platform === "instagram") {
|
|
4516
|
+
jobs.push({ type: "stories" });
|
|
4517
|
+
jobs.push({ type: "highlights" });
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
const sinceSeconds = parseDateBoundary(options.since, "since");
|
|
4521
|
+
const untilSeconds = parseDateBoundary(options.until, "until");
|
|
4522
|
+
const globalLimit = parseLimit(options.limit);
|
|
4523
|
+
const savedCheckpoint = options.resume ? await loadCheckpoint(targetDir) : null;
|
|
4524
|
+
const currentCursors = savedCheckpoint?.cursors ? { ...savedCheckpoint.cursors } : {};
|
|
4525
|
+
const defaultRunNative = async (subDir) => {
|
|
4526
|
+
const dest = subDir === "All" ? targetDir : path13.join(targetDir, subDir);
|
|
4170
4527
|
const remainingLimit = globalLimit !== void 0 ? Math.max(0, globalLimit - totalDownloaded) : void 0;
|
|
4171
4528
|
if (remainingLimit === 0) {
|
|
4172
4529
|
return { success: true, downloaded: 0, skipped: 0 };
|
|
@@ -4208,8 +4565,8 @@ async function dispatchDownload(options) {
|
|
|
4208
4565
|
jobName: `${profile.username} ${subDir}`,
|
|
4209
4566
|
destDir: dest,
|
|
4210
4567
|
destDirMap: subDir === "All" ? {
|
|
4211
|
-
photo:
|
|
4212
|
-
video:
|
|
4568
|
+
photo: path13.join(targetDir, "Photos"),
|
|
4569
|
+
video: path13.join(targetDir, "Videos")
|
|
4213
4570
|
} : void 0,
|
|
4214
4571
|
cookiePath: cookieCopy?.filePath,
|
|
4215
4572
|
referer: REFERER_MAP[profile.platform],
|
|
@@ -4218,7 +4575,7 @@ async function dispatchDownload(options) {
|
|
|
4218
4575
|
writeMetadata: options.writeMetadata,
|
|
4219
4576
|
defaultPlatform: profile.platform,
|
|
4220
4577
|
defaultAuthor: profile.username,
|
|
4221
|
-
onProgress
|
|
4578
|
+
onProgress: handleProgress
|
|
4222
4579
|
});
|
|
4223
4580
|
return {
|
|
4224
4581
|
success: res.errors.length === 0,
|
|
@@ -4232,7 +4589,7 @@ async function dispatchDownload(options) {
|
|
|
4232
4589
|
success: false,
|
|
4233
4590
|
downloaded: 0,
|
|
4234
4591
|
skipped: 0,
|
|
4235
|
-
error: errMsg
|
|
4592
|
+
error: errMsg || "Execution failed"
|
|
4236
4593
|
};
|
|
4237
4594
|
}
|
|
4238
4595
|
};
|
|
@@ -4242,17 +4599,19 @@ async function dispatchDownload(options) {
|
|
|
4242
4599
|
targetDir,
|
|
4243
4600
|
subDir,
|
|
4244
4601
|
cookiePath: cookieCopy?.filePath,
|
|
4602
|
+
cookiesFromBrowser,
|
|
4245
4603
|
signal: options.signal,
|
|
4246
4604
|
writeMetadata: options.writeMetadata,
|
|
4247
|
-
onProgress
|
|
4605
|
+
onProgress: handleProgress
|
|
4248
4606
|
}));
|
|
4249
4607
|
const runYt = options.runnerOverrides?.ytRunner ?? (() => runYtDlp({
|
|
4250
4608
|
profile,
|
|
4251
4609
|
targetDir,
|
|
4252
4610
|
cookiePath: cookieCopy?.filePath,
|
|
4611
|
+
cookiesFromBrowser,
|
|
4253
4612
|
signal: options.signal,
|
|
4254
4613
|
writeMetadata: options.writeMetadata,
|
|
4255
|
-
onProgress
|
|
4614
|
+
onProgress: handleProgress
|
|
4256
4615
|
}));
|
|
4257
4616
|
for (const job of jobs) {
|
|
4258
4617
|
if (options.signal?.aborted) break;
|
|
@@ -4260,7 +4619,7 @@ async function dispatchDownload(options) {
|
|
|
4260
4619
|
if (job.type === "all") {
|
|
4261
4620
|
let res = await runNative("All");
|
|
4262
4621
|
if ((!res.success || res.downloaded === 0 && res.skipped === 0) && !options.signal?.aborted) {
|
|
4263
|
-
|
|
4622
|
+
handleProgress({
|
|
4264
4623
|
job: "Photos",
|
|
4265
4624
|
downloadedCount: totalDownloaded,
|
|
4266
4625
|
skippedCount: totalSkipped,
|
|
@@ -4269,8 +4628,8 @@ async function dispatchDownload(options) {
|
|
|
4269
4628
|
const gdlPhotos = await runGdl("Photos");
|
|
4270
4629
|
totalDownloaded += gdlPhotos.downloaded;
|
|
4271
4630
|
totalSkipped += gdlPhotos.skipped;
|
|
4272
|
-
if (!gdlPhotos.success
|
|
4273
|
-
|
|
4631
|
+
if (!gdlPhotos.success) recordError(gdlPhotos.error ? `[Photos] ${gdlPhotos.error}` : "[Photos] Execution failed");
|
|
4632
|
+
handleProgress({
|
|
4274
4633
|
job: "Videos",
|
|
4275
4634
|
downloadedCount: totalDownloaded,
|
|
4276
4635
|
skippedCount: totalSkipped,
|
|
@@ -4280,9 +4639,9 @@ async function dispatchDownload(options) {
|
|
|
4280
4639
|
if (ytVideos.downloaded > 0 || ytVideos.skipped > 0 || ytVideos.success) {
|
|
4281
4640
|
totalDownloaded += ytVideos.downloaded;
|
|
4282
4641
|
totalSkipped += ytVideos.skipped;
|
|
4283
|
-
if (!ytVideos.success
|
|
4642
|
+
if (!ytVideos.success) recordError(ytVideos.error ? `[Videos] ${ytVideos.error}` : "[Videos] Execution failed");
|
|
4284
4643
|
} else if (!options.signal?.aborted) {
|
|
4285
|
-
|
|
4644
|
+
handleProgress({
|
|
4286
4645
|
job: "Videos",
|
|
4287
4646
|
downloadedCount: totalDownloaded,
|
|
4288
4647
|
skippedCount: totalSkipped,
|
|
@@ -4291,19 +4650,19 @@ async function dispatchDownload(options) {
|
|
|
4291
4650
|
const gdlVideos = await runGdl("Videos");
|
|
4292
4651
|
totalDownloaded += gdlVideos.downloaded;
|
|
4293
4652
|
totalSkipped += gdlVideos.skipped;
|
|
4294
|
-
if (!gdlVideos.success
|
|
4653
|
+
if (!gdlVideos.success) recordError(gdlVideos.error ? `[Videos] ${gdlVideos.error}` : "[Videos] Execution failed");
|
|
4295
4654
|
}
|
|
4296
4655
|
} else {
|
|
4297
4656
|
totalDownloaded += res.downloaded;
|
|
4298
4657
|
totalSkipped += res.skipped;
|
|
4299
|
-
if (!res.success
|
|
4658
|
+
if (!res.success) recordError(res.error ? `[All] ${res.error}` : "[All] Execution failed");
|
|
4300
4659
|
}
|
|
4301
4660
|
} else if (job.type === "photos" || job.type === "stories" || job.type === "highlights") {
|
|
4302
4661
|
const subDir = job.type === "photos" ? "Photos" : job.type === "stories" ? "Stories" : "Highlights";
|
|
4303
4662
|
let res = await runNative(subDir);
|
|
4304
4663
|
const isAuthError = Boolean(res.error && (res.error.includes("cookie") || res.error.includes("authenticated session") || res.error.includes("Settings (^s)")));
|
|
4305
4664
|
if (!isAuthError && (!res.success || res.downloaded === 0 && res.skipped === 0) && !options.signal?.aborted) {
|
|
4306
|
-
|
|
4665
|
+
handleProgress({
|
|
4307
4666
|
job: subDir,
|
|
4308
4667
|
downloadedCount: totalDownloaded,
|
|
4309
4668
|
skippedCount: totalSkipped,
|
|
@@ -4316,14 +4675,14 @@ async function dispatchDownload(options) {
|
|
|
4316
4675
|
}
|
|
4317
4676
|
totalDownloaded += res.downloaded;
|
|
4318
4677
|
totalSkipped += res.skipped;
|
|
4319
|
-
if (!res.success
|
|
4678
|
+
if (!res.success) recordError(res.error ? `[${subDir}] ${res.error}` : `[${subDir}] Execution failed`);
|
|
4320
4679
|
} else if (job.type === "videos") {
|
|
4321
4680
|
let res = await runNative("Videos");
|
|
4322
4681
|
if (res.downloaded > 0 || res.skipped > 0) {
|
|
4323
4682
|
totalDownloaded += res.downloaded;
|
|
4324
4683
|
totalSkipped += res.skipped;
|
|
4325
4684
|
} else if (!options.signal?.aborted) {
|
|
4326
|
-
|
|
4685
|
+
handleProgress({
|
|
4327
4686
|
job: "Videos",
|
|
4328
4687
|
downloadedCount: totalDownloaded,
|
|
4329
4688
|
skippedCount: totalSkipped,
|
|
@@ -4333,9 +4692,9 @@ async function dispatchDownload(options) {
|
|
|
4333
4692
|
if (fbRes.downloaded > 0 || fbRes.skipped > 0 || fbRes.success) {
|
|
4334
4693
|
totalDownloaded += fbRes.downloaded;
|
|
4335
4694
|
totalSkipped += fbRes.skipped;
|
|
4336
|
-
if (!fbRes.success
|
|
4695
|
+
if (!fbRes.success) recordError(fbRes.error ? `[Videos] ${fbRes.error}` : "[Videos] Execution failed");
|
|
4337
4696
|
} else if (!options.signal?.aborted) {
|
|
4338
|
-
|
|
4697
|
+
handleProgress({
|
|
4339
4698
|
job: "Videos",
|
|
4340
4699
|
downloadedCount: totalDownloaded,
|
|
4341
4700
|
skippedCount: totalSkipped,
|
|
@@ -4344,7 +4703,7 @@ async function dispatchDownload(options) {
|
|
|
4344
4703
|
const gdlRes = await runGdl("Videos");
|
|
4345
4704
|
totalDownloaded += gdlRes.downloaded;
|
|
4346
4705
|
totalSkipped += gdlRes.skipped;
|
|
4347
|
-
if (!gdlRes.success
|
|
4706
|
+
if (!gdlRes.success) recordError(gdlRes.error ? `[Videos] ${gdlRes.error}` : "[Videos] Execution failed");
|
|
4348
4707
|
}
|
|
4349
4708
|
}
|
|
4350
4709
|
}
|
|
@@ -4353,13 +4712,19 @@ async function dispatchDownload(options) {
|
|
|
4353
4712
|
if (errors.length === 0 && !options.signal?.aborted) {
|
|
4354
4713
|
await clearCheckpoint(targetDir);
|
|
4355
4714
|
}
|
|
4356
|
-
|
|
4715
|
+
const engineResult = {
|
|
4357
4716
|
success: errors.length === 0 || totalDownloaded > 0,
|
|
4358
4717
|
downloadedCount: totalDownloaded,
|
|
4359
4718
|
skippedCount: totalSkipped,
|
|
4360
4719
|
errors,
|
|
4361
4720
|
outputDir: targetDir
|
|
4362
4721
|
};
|
|
4722
|
+
defaultTaskManager.events.emit("job:complete", { result: engineResult });
|
|
4723
|
+
return engineResult;
|
|
4724
|
+
} catch (err) {
|
|
4725
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
4726
|
+
defaultTaskManager.events.emit("job:error", { error: errMsg || "Execution failed" });
|
|
4727
|
+
throw err;
|
|
4363
4728
|
} finally {
|
|
4364
4729
|
if (cookieCopy) {
|
|
4365
4730
|
await cookieCopy.cleanup();
|
|
@@ -4368,7 +4733,7 @@ async function dispatchDownload(options) {
|
|
|
4368
4733
|
}
|
|
4369
4734
|
|
|
4370
4735
|
// src/components/views/picking-view.tsx
|
|
4371
|
-
import {
|
|
4736
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
4372
4737
|
function ChoiceIndicator({ isSelected }) {
|
|
4373
4738
|
const theme = useTheme();
|
|
4374
4739
|
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
@@ -4416,7 +4781,7 @@ function getPickingOptions(platform) {
|
|
|
4416
4781
|
return items;
|
|
4417
4782
|
}
|
|
4418
4783
|
function PickingView({
|
|
4419
|
-
contentWidth
|
|
4784
|
+
contentWidth,
|
|
4420
4785
|
profile,
|
|
4421
4786
|
outputDir,
|
|
4422
4787
|
initialLimit,
|
|
@@ -4427,7 +4792,7 @@ function PickingView({
|
|
|
4427
4792
|
onSelect,
|
|
4428
4793
|
onHighlight,
|
|
4429
4794
|
theme,
|
|
4430
|
-
gapComponent:
|
|
4795
|
+
gapComponent: Gap2
|
|
4431
4796
|
}) {
|
|
4432
4797
|
const platformName = profile.platform.charAt(0).toUpperCase() + profile.platform.slice(1);
|
|
4433
4798
|
const items = getPickingOptions(profile.platform);
|
|
@@ -4455,48 +4820,44 @@ function PickingView({
|
|
|
4455
4820
|
resume: initialResume
|
|
4456
4821
|
});
|
|
4457
4822
|
};
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4823
|
+
const boxWidth = Math.min(48, Math.max(44, (contentWidth || 80) - 8));
|
|
4824
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "center", width: "100%", children: [
|
|
4825
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "center", marginBottom: 1, children: [
|
|
4461
4826
|
/* @__PURE__ */ jsxs6(Text7, { bold: true, color: theme.primary, children: [
|
|
4462
4827
|
"@",
|
|
4463
4828
|
profile.username
|
|
4464
4829
|
] }),
|
|
4465
|
-
/* @__PURE__ */ jsx8(
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
4473
|
-
/* @__PURE__ */ jsx8(Text7, { color: theme.primary, children: "\u258E " }),
|
|
4830
|
+
/* @__PURE__ */ jsx8(Gap2, {}),
|
|
4831
|
+
/* @__PURE__ */ jsxs6(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
4832
|
+
"\u25B8 ",
|
|
4833
|
+
platformName,
|
|
4834
|
+
profile.cleanUrl ? ` \xB7 ${profile.cleanUrl}` : ""
|
|
4835
|
+
] }),
|
|
4836
|
+
/* @__PURE__ */ jsx8(Gap2, {}),
|
|
4474
4837
|
/* @__PURE__ */ jsx8(Text7, { color: theme.gray, dimColor: theme.dimSecondary, children: "Select target media:" })
|
|
4475
4838
|
] }),
|
|
4476
|
-
/* @__PURE__ */
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
/* @__PURE__ */ jsx8(Box6, { children: /* @__PURE__ */ jsx8(Text7, { color: theme.primary, children: "\u258E" }) }),
|
|
4494
|
-
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
4839
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width: boxWidth, children: [
|
|
4840
|
+
/* @__PURE__ */ jsx8(
|
|
4841
|
+
SelectInput,
|
|
4842
|
+
{
|
|
4843
|
+
indicatorComponent: ChoiceIndicator,
|
|
4844
|
+
itemComponent: ChoiceItem,
|
|
4845
|
+
items: items.map((item, index) => ({
|
|
4846
|
+
key: String(index),
|
|
4847
|
+
label: item.label,
|
|
4848
|
+
value: item.value,
|
|
4849
|
+
description: item.description
|
|
4850
|
+
})),
|
|
4851
|
+
onSelect: (item) => handleItemSelect(item.value),
|
|
4852
|
+
onHighlight: (item) => onHighlight?.(item.value)
|
|
4853
|
+
}
|
|
4854
|
+
),
|
|
4855
|
+
hasCheckpoint ? /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
|
|
4495
4856
|
/* @__PURE__ */ jsx8(Text7, { color: theme.primary, children: "\u258E " }),
|
|
4496
4857
|
/* @__PURE__ */ jsx8(Text7, { color: theme.primary, bold: true, children: "\u26A1 Checkpoint found (cursor saved)" })
|
|
4497
|
-
] })
|
|
4498
|
-
] })
|
|
4499
|
-
] })
|
|
4858
|
+
] }) : null
|
|
4859
|
+
] })
|
|
4860
|
+
] });
|
|
4500
4861
|
}
|
|
4501
4862
|
|
|
4502
4863
|
// src/components/views/platform-view.tsx
|
|
@@ -4563,7 +4924,7 @@ function ProbingView({
|
|
|
4563
4924
|
|
|
4564
4925
|
// src/components/views/downloading-view.tsx
|
|
4565
4926
|
import { useState as useState5, useEffect as useEffect5 } from "react";
|
|
4566
|
-
import
|
|
4927
|
+
import os8 from "os";
|
|
4567
4928
|
import { Box as Box9, Text as Text11 } from "ink";
|
|
4568
4929
|
import Spinner from "ink-spinner";
|
|
4569
4930
|
|
|
@@ -4699,35 +5060,51 @@ function DownloadingView({
|
|
|
4699
5060
|
activeBadges.length > 0 ? /* @__PURE__ */ jsx12(Text11, { color: theme.gray, dimColor: theme.dimSecondary, children: activeBadges.join(" \xB7 ") }) : null,
|
|
4700
5061
|
outputDir ? /* @__PURE__ */ jsxs9(Text11, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
4701
5062
|
"saving to ",
|
|
4702
|
-
shortenPath(outputDir,
|
|
5063
|
+
shortenPath(outputDir, os8.homedir(), 54)
|
|
4703
5064
|
] }) : null
|
|
4704
5065
|
] });
|
|
4705
5066
|
}
|
|
4706
5067
|
|
|
4707
5068
|
// src/components/views/completion-view.tsx
|
|
4708
|
-
import
|
|
5069
|
+
import os9 from "os";
|
|
4709
5070
|
import { Box as Box10, Text as Text12 } from "ink";
|
|
4710
|
-
import {
|
|
5071
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
4711
5072
|
var DONE_LABEL = "\u21B5 download another";
|
|
5073
|
+
var LEDGER_INDEXED_LABEL = "\u2713 indexed in media ledger";
|
|
5074
|
+
function shouldShowLedgerBadge(result, showLedgerBadge) {
|
|
5075
|
+
return showLedgerBadge ?? result.downloadedCount > 0;
|
|
5076
|
+
}
|
|
5077
|
+
function formatCompletionSummary(result) {
|
|
5078
|
+
const isZero = result.downloadedCount === 0 && result.skippedCount === 0;
|
|
5079
|
+
const plural = result.downloadedCount === 1 ? "" : "s";
|
|
5080
|
+
const skipText = result.skippedCount > 0 ? ` (${result.skippedCount} archived)` : "";
|
|
5081
|
+
return {
|
|
5082
|
+
isZero,
|
|
5083
|
+
headerPrefix: isZero ? "\u2713 profile scanned " : "\u2713 profile downloaded! ",
|
|
5084
|
+
headerSuffix: isZero ? ": 0 media items found" : "saved to:",
|
|
5085
|
+
itemsText: isZero ? "no photos or videos available" : `${result.downloadedCount} media item${plural} downloaded${skipText}`
|
|
5086
|
+
};
|
|
5087
|
+
}
|
|
5088
|
+
function LedgerBadge({ theme }) {
|
|
5089
|
+
return /* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: LEDGER_INDEXED_LABEL });
|
|
5090
|
+
}
|
|
4712
5091
|
function CompletionView({
|
|
4713
5092
|
profile,
|
|
4714
5093
|
result,
|
|
4715
5094
|
theme,
|
|
4716
|
-
gapComponent: Gap2
|
|
5095
|
+
gapComponent: Gap2,
|
|
5096
|
+
showLedgerBadge
|
|
4717
5097
|
}) {
|
|
4718
|
-
const
|
|
4719
|
-
const
|
|
4720
|
-
const skipText = result.skippedCount > 0 ? ` (${result.skippedCount} archived)` : "";
|
|
5098
|
+
const { headerPrefix, headerSuffix, itemsText } = formatCompletionSummary(result);
|
|
5099
|
+
const displayLedger = shouldShowLedgerBadge(result, showLedgerBadge);
|
|
4721
5100
|
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", alignItems: "center", children: [
|
|
4722
|
-
/* @__PURE__ */
|
|
4723
|
-
/* @__PURE__ */ jsx13(Text12, { bold: true, color: theme.primary, children:
|
|
4724
|
-
/* @__PURE__ */ jsx13(Text12, { color: theme.primary, children:
|
|
4725
|
-
] })
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
/* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: shortenPath(result.outputDir, os7.homedir(), 60) }),
|
|
4730
|
-
/* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: isZero ? "no photos or videos available" : `${result.downloadedCount} media item${plural} downloaded${skipText}` }),
|
|
5101
|
+
/* @__PURE__ */ jsxs10(Text12, { children: [
|
|
5102
|
+
/* @__PURE__ */ jsx13(Text12, { bold: true, color: theme.primary, children: headerPrefix }),
|
|
5103
|
+
/* @__PURE__ */ jsx13(Text12, { color: theme.primary, children: headerSuffix })
|
|
5104
|
+
] }),
|
|
5105
|
+
/* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: shortenPath(result.outputDir, os9.homedir(), 60) }),
|
|
5106
|
+
/* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: itemsText }),
|
|
5107
|
+
displayLedger && /* @__PURE__ */ jsx13(LedgerBadge, { theme }),
|
|
4731
5108
|
/* @__PURE__ */ jsx13(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
4732
5109
|
/* @__PURE__ */ jsx13(Text12, { bold: true, color: theme.primary, children: "[o]" }),
|
|
4733
5110
|
" reveal in folder"
|
|
@@ -4769,18 +5146,206 @@ function ErrorView({ message, hint, columns, theme }) {
|
|
|
4769
5146
|
}
|
|
4770
5147
|
|
|
4771
5148
|
// src/components/views/settings-view.tsx
|
|
4772
|
-
import { useState as useState6, useMemo as useMemo2, useCallback } from "react";
|
|
5149
|
+
import { useState as useState6, useMemo as useMemo2, useCallback, useEffect as useEffect6 } from "react";
|
|
4773
5150
|
import { Box as Box12, Text as Text14, useInput as useInput2 } from "ink";
|
|
4774
|
-
import
|
|
4775
|
-
import
|
|
4776
|
-
import
|
|
4777
|
-
|
|
5151
|
+
import fs15 from "fs";
|
|
5152
|
+
import path15 from "path";
|
|
5153
|
+
import os11 from "os";
|
|
5154
|
+
|
|
5155
|
+
// src/lib/diagnostics/doctor.ts
|
|
5156
|
+
import { execFile } from "child_process";
|
|
5157
|
+
import { promisify } from "util";
|
|
5158
|
+
import os10 from "os";
|
|
5159
|
+
import fs14 from "fs";
|
|
5160
|
+
import path14 from "path";
|
|
5161
|
+
var execFileAsync = promisify(execFile);
|
|
5162
|
+
async function defaultCheckBinary(bin) {
|
|
5163
|
+
const versionFlag = bin === "ffmpeg" ? "-version" : "--version";
|
|
5164
|
+
try {
|
|
5165
|
+
await execFileAsync(bin, [versionFlag], { timeout: 3e3 });
|
|
5166
|
+
return true;
|
|
5167
|
+
} catch {
|
|
5168
|
+
if (bin === "gallery-dl" || bin === "yt-dlp") {
|
|
5169
|
+
try {
|
|
5170
|
+
const resolved = resolveEngineCommand(bin);
|
|
5171
|
+
await execFileAsync(resolved.command, [...resolved.baseArgs, versionFlag], { timeout: 3e3 });
|
|
5172
|
+
return true;
|
|
5173
|
+
} catch {
|
|
5174
|
+
return false;
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
return false;
|
|
5178
|
+
}
|
|
5179
|
+
}
|
|
5180
|
+
async function runDoctor(options = {}) {
|
|
5181
|
+
const check = options.binaryChecker ?? defaultCheckBinary;
|
|
5182
|
+
const [gdl, ytdlp, ffmpeg] = await Promise.all([
|
|
5183
|
+
check("gallery-dl"),
|
|
5184
|
+
check("yt-dlp"),
|
|
5185
|
+
check("ffmpeg")
|
|
5186
|
+
]);
|
|
5187
|
+
const availableCookies = detectAvailableCookies(options.cookiesDir);
|
|
5188
|
+
const cookieStatus = {
|
|
5189
|
+
instagram: availableCookies.includes("instagram"),
|
|
5190
|
+
tiktok: availableCookies.includes("tiktok"),
|
|
5191
|
+
facebook: availableCookies.includes("facebook"),
|
|
5192
|
+
x: availableCookies.includes("x")
|
|
5193
|
+
};
|
|
5194
|
+
let storageOk = false;
|
|
5195
|
+
try {
|
|
5196
|
+
const testDir = options.storageDir || path14.join(os10.homedir(), ".open-nami");
|
|
5197
|
+
fs14.mkdirSync(testDir, { recursive: true });
|
|
5198
|
+
const testFile = path14.join(testDir, `.doctor_test_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
5199
|
+
fs14.writeFileSync(testFile, "ok");
|
|
5200
|
+
fs14.unlinkSync(testFile);
|
|
5201
|
+
storageOk = true;
|
|
5202
|
+
} catch {
|
|
5203
|
+
}
|
|
5204
|
+
return {
|
|
5205
|
+
nodeVersion: process.version,
|
|
5206
|
+
binaries: {
|
|
5207
|
+
"gallery-dl": gdl,
|
|
5208
|
+
"yt-dlp": ytdlp,
|
|
5209
|
+
ffmpeg
|
|
5210
|
+
},
|
|
5211
|
+
cookies: cookieStatus,
|
|
5212
|
+
storageOk
|
|
5213
|
+
};
|
|
5214
|
+
}
|
|
5215
|
+
|
|
5216
|
+
// src/components/views/settings-view.tsx
|
|
5217
|
+
import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4778
5218
|
function expandDir(dir) {
|
|
4779
5219
|
if (dir.startsWith("~/") || dir.startsWith("~\\") || dir === "~") {
|
|
4780
|
-
return
|
|
5220
|
+
return path15.join(os11.homedir(), dir.slice(1));
|
|
4781
5221
|
}
|
|
4782
5222
|
return dir;
|
|
4783
5223
|
}
|
|
5224
|
+
function resolveBrowserOptions(detected) {
|
|
5225
|
+
const available = detected.filter((b) => b.available !== false);
|
|
5226
|
+
return ["none", ...available.map((b) => b.id)];
|
|
5227
|
+
}
|
|
5228
|
+
function cycleBrowserOption(current, options, forward = true) {
|
|
5229
|
+
if (options.length === 0) return "none";
|
|
5230
|
+
const idx = options.indexOf(current);
|
|
5231
|
+
const currentIndex = idx === -1 ? 0 : idx;
|
|
5232
|
+
const nextIdx = forward ? (currentIndex + 1) % options.length : (currentIndex - 1 + options.length) % options.length;
|
|
5233
|
+
return options[nextIdx];
|
|
5234
|
+
}
|
|
5235
|
+
function formatDoctorStorage(storageOk) {
|
|
5236
|
+
return {
|
|
5237
|
+
ok: storageOk,
|
|
5238
|
+
text: storageOk ? "\u2713 SQLite ready" : "\u2717 SQLite unavailable"
|
|
5239
|
+
};
|
|
5240
|
+
}
|
|
5241
|
+
function formatDoctorCookies(cookies) {
|
|
5242
|
+
const platforms = Object.entries(cookies).filter(([_, ok]) => ok).map(([name]) => name);
|
|
5243
|
+
const count = platforms.length;
|
|
5244
|
+
const text = count > 0 ? `\u2713 ${count} (${platforms.join(", ")})` : "none detected";
|
|
5245
|
+
return { count, platforms, text };
|
|
5246
|
+
}
|
|
5247
|
+
function DoctorCard({ boxWidth, theme, report }) {
|
|
5248
|
+
const cookieSummary = report ? formatDoctorCookies(report.cookies) : { count: 0, platforms: [], text: "" };
|
|
5249
|
+
const storageSummary = report ? formatDoctorStorage(report.storageOk) : { ok: false, text: "" };
|
|
5250
|
+
return /* @__PURE__ */ jsxs12(
|
|
5251
|
+
Box12,
|
|
5252
|
+
{
|
|
5253
|
+
width: boxWidth,
|
|
5254
|
+
borderStyle: "round",
|
|
5255
|
+
borderColor: theme.gray,
|
|
5256
|
+
borderDimColor: theme.dimSecondary,
|
|
5257
|
+
paddingX: 2,
|
|
5258
|
+
flexDirection: "column",
|
|
5259
|
+
children: [
|
|
5260
|
+
/* @__PURE__ */ jsxs12(Box12, { justifyContent: "space-between", children: [
|
|
5261
|
+
/* @__PURE__ */ jsx15(Text14, { color: theme.primary, bold: true, children: "System Diagnostics" }),
|
|
5262
|
+
/* @__PURE__ */ jsx15(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: report ? report.nodeVersion : "checking\u2026" })
|
|
5263
|
+
] }),
|
|
5264
|
+
report ? /* @__PURE__ */ jsxs12(Fragment2, { children: [
|
|
5265
|
+
/* @__PURE__ */ jsxs12(Box12, { marginTop: 0, children: [
|
|
5266
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5267
|
+
"Engines:",
|
|
5268
|
+
" "
|
|
5269
|
+
] }),
|
|
5270
|
+
/* @__PURE__ */ jsxs12(
|
|
5271
|
+
Text14,
|
|
5272
|
+
{
|
|
5273
|
+
color: report.binaries["gallery-dl"] ? "green" : "red",
|
|
5274
|
+
dimColor: !report.binaries["gallery-dl"] && theme.dimSecondary,
|
|
5275
|
+
children: [
|
|
5276
|
+
report.binaries["gallery-dl"] ? "\u2713" : "\u2717",
|
|
5277
|
+
" gallery-dl"
|
|
5278
|
+
]
|
|
5279
|
+
}
|
|
5280
|
+
),
|
|
5281
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5282
|
+
" ",
|
|
5283
|
+
"\xB7",
|
|
5284
|
+
" "
|
|
5285
|
+
] }),
|
|
5286
|
+
/* @__PURE__ */ jsxs12(
|
|
5287
|
+
Text14,
|
|
5288
|
+
{
|
|
5289
|
+
color: report.binaries["yt-dlp"] ? "green" : "red",
|
|
5290
|
+
dimColor: !report.binaries["yt-dlp"] && theme.dimSecondary,
|
|
5291
|
+
children: [
|
|
5292
|
+
report.binaries["yt-dlp"] ? "\u2713" : "\u2717",
|
|
5293
|
+
" yt-dlp"
|
|
5294
|
+
]
|
|
5295
|
+
}
|
|
5296
|
+
),
|
|
5297
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5298
|
+
" ",
|
|
5299
|
+
"\xB7",
|
|
5300
|
+
" "
|
|
5301
|
+
] }),
|
|
5302
|
+
/* @__PURE__ */ jsxs12(
|
|
5303
|
+
Text14,
|
|
5304
|
+
{
|
|
5305
|
+
color: report.binaries.ffmpeg ? "green" : "red",
|
|
5306
|
+
dimColor: !report.binaries.ffmpeg && theme.dimSecondary,
|
|
5307
|
+
children: [
|
|
5308
|
+
report.binaries.ffmpeg ? "\u2713" : "\u2717",
|
|
5309
|
+
" ffmpeg"
|
|
5310
|
+
]
|
|
5311
|
+
}
|
|
5312
|
+
)
|
|
5313
|
+
] }),
|
|
5314
|
+
/* @__PURE__ */ jsxs12(Box12, { justifyContent: "space-between", marginTop: 0, children: [
|
|
5315
|
+
/* @__PURE__ */ jsxs12(Box12, { children: [
|
|
5316
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5317
|
+
"Storage:",
|
|
5318
|
+
" "
|
|
5319
|
+
] }),
|
|
5320
|
+
/* @__PURE__ */ jsx15(
|
|
5321
|
+
Text14,
|
|
5322
|
+
{
|
|
5323
|
+
color: storageSummary.ok ? "green" : "red",
|
|
5324
|
+
dimColor: !storageSummary.ok && theme.dimSecondary,
|
|
5325
|
+
children: storageSummary.text
|
|
5326
|
+
}
|
|
5327
|
+
)
|
|
5328
|
+
] }),
|
|
5329
|
+
/* @__PURE__ */ jsxs12(Box12, { children: [
|
|
5330
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5331
|
+
"Cookies:",
|
|
5332
|
+
" "
|
|
5333
|
+
] }),
|
|
5334
|
+
/* @__PURE__ */ jsx15(
|
|
5335
|
+
Text14,
|
|
5336
|
+
{
|
|
5337
|
+
color: cookieSummary.count > 0 ? theme.primary : theme.gray,
|
|
5338
|
+
dimColor: cookieSummary.count === 0 && theme.dimSecondary,
|
|
5339
|
+
children: cookieSummary.text
|
|
5340
|
+
}
|
|
5341
|
+
)
|
|
5342
|
+
] })
|
|
5343
|
+
] })
|
|
5344
|
+
] }) : /* @__PURE__ */ jsx15(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: "checking system engines and storage\u2026" })
|
|
5345
|
+
]
|
|
5346
|
+
}
|
|
5347
|
+
);
|
|
5348
|
+
}
|
|
4784
5349
|
var LIMIT_OPTIONS = [void 0, 10, 25, 50, 100];
|
|
4785
5350
|
function SettingsView({
|
|
4786
5351
|
boxWidth,
|
|
@@ -4789,7 +5354,10 @@ function SettingsView({
|
|
|
4789
5354
|
focusedField: controlledFocus,
|
|
4790
5355
|
onFocusChange,
|
|
4791
5356
|
onSave,
|
|
4792
|
-
onCancel
|
|
5357
|
+
onCancel,
|
|
5358
|
+
doctorChecker,
|
|
5359
|
+
initialReport,
|
|
5360
|
+
browsers: browsersProp
|
|
4793
5361
|
}) {
|
|
4794
5362
|
const initialConfig = useMemo2(() => loadConfig(), []);
|
|
4795
5363
|
const [internalFocus, setInternalFocus] = useState6("cookies");
|
|
@@ -4801,16 +5369,61 @@ function SettingsView({
|
|
|
4801
5369
|
},
|
|
4802
5370
|
[onFocusChange]
|
|
4803
5371
|
);
|
|
5372
|
+
const detectedBrowsers = useMemo2(() => {
|
|
5373
|
+
return browsersProp ?? listDetectedBrowsers();
|
|
5374
|
+
}, [browsersProp]);
|
|
5375
|
+
const browserOptions = useMemo2(() => {
|
|
5376
|
+
return resolveBrowserOptions(detectedBrowsers);
|
|
5377
|
+
}, [detectedBrowsers]);
|
|
4804
5378
|
const [cookiesDir, setCookiesDir] = useState6(initialConfig.cookiesDir ?? "");
|
|
5379
|
+
const [cookiesFromBrowser, setCookiesFromBrowser] = useState6(
|
|
5380
|
+
initialConfig.cookiesFromBrowser ?? "none"
|
|
5381
|
+
);
|
|
4805
5382
|
const [downloadDir, setDownloadDir] = useState6(initialConfig.downloadDir ?? "");
|
|
4806
5383
|
const [writeMetadata, setWriteMetadata] = useState6(Boolean(initialConfig.writeMetadata));
|
|
4807
5384
|
const [defaultLimit, setDefaultLimit] = useState6(initialConfig.defaultLimit);
|
|
4808
5385
|
const [saved, setSaved] = useState6(false);
|
|
5386
|
+
const [doctorReport, setDoctorReport] = useState6(initialReport ?? null);
|
|
5387
|
+
useEffect6(() => {
|
|
5388
|
+
let cancelled = false;
|
|
5389
|
+
const fetchReport = async () => {
|
|
5390
|
+
try {
|
|
5391
|
+
const options = {
|
|
5392
|
+
cookiesDir: cookiesDir.trim() ? expandDir(cookiesDir.trim()) : void 0
|
|
5393
|
+
};
|
|
5394
|
+
let report;
|
|
5395
|
+
if (doctorChecker) {
|
|
5396
|
+
const res = await doctorChecker(options);
|
|
5397
|
+
if (res && typeof res === "object" && "binaries" in res) {
|
|
5398
|
+
report = res;
|
|
5399
|
+
} else {
|
|
5400
|
+
report = await runDoctor({
|
|
5401
|
+
...options,
|
|
5402
|
+
binaryChecker: doctorChecker
|
|
5403
|
+
});
|
|
5404
|
+
}
|
|
5405
|
+
} else {
|
|
5406
|
+
report = await runDoctor(options);
|
|
5407
|
+
}
|
|
5408
|
+
if (!cancelled) {
|
|
5409
|
+
setDoctorReport(report);
|
|
5410
|
+
}
|
|
5411
|
+
} catch {
|
|
5412
|
+
}
|
|
5413
|
+
};
|
|
5414
|
+
if (initialReport === void 0 || doctorChecker !== void 0) {
|
|
5415
|
+
fetchReport();
|
|
5416
|
+
}
|
|
5417
|
+
return () => {
|
|
5418
|
+
cancelled = true;
|
|
5419
|
+
};
|
|
5420
|
+
}, [cookiesDir, doctorChecker, initialReport]);
|
|
4809
5421
|
const handleSave = useCallback(() => {
|
|
4810
5422
|
const existing = loadConfig();
|
|
4811
5423
|
const nextConfig = {
|
|
4812
5424
|
...existing,
|
|
4813
5425
|
cookiesDir: cookiesDir.trim() ? cookiesDir.trim() : void 0,
|
|
5426
|
+
cookiesFromBrowser,
|
|
4814
5427
|
downloadDir: downloadDir.trim() ? downloadDir.trim() : void 0,
|
|
4815
5428
|
writeMetadata,
|
|
4816
5429
|
defaultLimit
|
|
@@ -4818,7 +5431,15 @@ function SettingsView({
|
|
|
4818
5431
|
saveConfig(nextConfig);
|
|
4819
5432
|
setSaved(true);
|
|
4820
5433
|
onSave();
|
|
4821
|
-
}, [cookiesDir, downloadDir, writeMetadata, defaultLimit, onSave]);
|
|
5434
|
+
}, [cookiesDir, cookiesFromBrowser, downloadDir, writeMetadata, defaultLimit, onSave]);
|
|
5435
|
+
const browserFeedback = useMemo2(() => {
|
|
5436
|
+
if (!cookiesFromBrowser || cookiesFromBrowser === "none") {
|
|
5437
|
+
return "manual cookie files only";
|
|
5438
|
+
}
|
|
5439
|
+
const found = detectedBrowsers.find((b) => b.id === cookiesFromBrowser);
|
|
5440
|
+
const browserName = found ? found.name : cookiesFromBrowser;
|
|
5441
|
+
return `live session cookies from ${browserName}`;
|
|
5442
|
+
}, [cookiesFromBrowser, detectedBrowsers]);
|
|
4822
5443
|
const cookieFeedback = useMemo2(() => {
|
|
4823
5444
|
const dir = cookiesDir.trim();
|
|
4824
5445
|
if (!dir) {
|
|
@@ -4829,7 +5450,7 @@ function SettingsView({
|
|
|
4829
5450
|
};
|
|
4830
5451
|
}
|
|
4831
5452
|
const expanded = expandDir(dir);
|
|
4832
|
-
if (!
|
|
5453
|
+
if (!fs15.existsSync(expanded)) {
|
|
4833
5454
|
return {
|
|
4834
5455
|
ok: false,
|
|
4835
5456
|
text: "directory does not exist yet"
|
|
@@ -4852,14 +5473,20 @@ function SettingsView({
|
|
|
4852
5473
|
if (!dir) {
|
|
4853
5474
|
return "default: platform Downloads / Open Nami";
|
|
4854
5475
|
}
|
|
4855
|
-
return `target: ${
|
|
5476
|
+
return `target: ${path15.resolve(expandDir(dir))}`;
|
|
4856
5477
|
}, [downloadDir]);
|
|
4857
5478
|
const cycleFocus = useCallback((forward = true) => {
|
|
4858
|
-
const fields = ["cookies", "download", "metadata", "limit"];
|
|
5479
|
+
const fields = ["cookies", "browser", "download", "metadata", "limit"];
|
|
4859
5480
|
const idx = fields.indexOf(focusedField);
|
|
4860
5481
|
const nextIdx = forward ? (idx + 1) % fields.length : (idx - 1 + fields.length) % fields.length;
|
|
4861
5482
|
setField(fields[nextIdx]);
|
|
4862
5483
|
}, [focusedField, setField]);
|
|
5484
|
+
const cycleBrowser = useCallback((forward = true) => {
|
|
5485
|
+
setCookiesFromBrowser((prev) => {
|
|
5486
|
+
const next = cycleBrowserOption(prev ?? "none", browserOptions, forward);
|
|
5487
|
+
return next;
|
|
5488
|
+
});
|
|
5489
|
+
}, [browserOptions]);
|
|
4863
5490
|
const cycleLimit = useCallback(() => {
|
|
4864
5491
|
setDefaultLimit((prev) => {
|
|
4865
5492
|
const idx = LIMIT_OPTIONS.indexOf(prev);
|
|
@@ -4876,6 +5503,20 @@ function SettingsView({
|
|
|
4876
5503
|
handleSave();
|
|
4877
5504
|
return;
|
|
4878
5505
|
}
|
|
5506
|
+
if (focusedField === "browser") {
|
|
5507
|
+
if (input === " " || key.return) {
|
|
5508
|
+
cycleBrowser(true);
|
|
5509
|
+
return;
|
|
5510
|
+
}
|
|
5511
|
+
if (key.tab || key.downArrow) {
|
|
5512
|
+
cycleFocus(true);
|
|
5513
|
+
return;
|
|
5514
|
+
}
|
|
5515
|
+
if (key.upArrow) {
|
|
5516
|
+
cycleFocus(false);
|
|
5517
|
+
return;
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
4879
5520
|
if (focusedField === "metadata") {
|
|
4880
5521
|
if (input === " " || key.return) {
|
|
4881
5522
|
setWriteMetadata((prev) => !prev);
|
|
@@ -4916,7 +5557,7 @@ function SettingsView({
|
|
|
4916
5557
|
{
|
|
4917
5558
|
value: cookiesDir,
|
|
4918
5559
|
onChange: setCookiesDir,
|
|
4919
|
-
onSubmit: () => setField("
|
|
5560
|
+
onSubmit: () => setField("browser"),
|
|
4920
5561
|
focus: focusedField === "cookies",
|
|
4921
5562
|
onTab: () => cycleFocus(true),
|
|
4922
5563
|
onArrowDown: () => cycleFocus(true),
|
|
@@ -4929,6 +5570,26 @@ function SettingsView({
|
|
|
4929
5570
|
),
|
|
4930
5571
|
/* @__PURE__ */ jsx15(Text14, { color: cookieFeedback.ok ? theme.primary : theme.gray, dimColor: !cookieFeedback.ok && theme.dimSecondary, children: cookieFeedback.text }),
|
|
4931
5572
|
/* @__PURE__ */ jsx15(Gap2, {}),
|
|
5573
|
+
/* @__PURE__ */ jsxs12(
|
|
5574
|
+
Box12,
|
|
5575
|
+
{
|
|
5576
|
+
width: boxWidth,
|
|
5577
|
+
borderStyle: "round",
|
|
5578
|
+
borderColor: focusedField === "browser" ? theme.primary : theme.gray,
|
|
5579
|
+
borderDimColor: focusedField !== "browser",
|
|
5580
|
+
paddingX: 2,
|
|
5581
|
+
justifyContent: "space-between",
|
|
5582
|
+
children: [
|
|
5583
|
+
/* @__PURE__ */ jsxs12(Text14, { color: focusedField === "browser" ? theme.primary : theme.gray, bold: focusedField === "browser", children: [
|
|
5584
|
+
focusedField === "browser" ? "\u25CF " : "\u25CB ",
|
|
5585
|
+
"Browser Cookie Bridge"
|
|
5586
|
+
] }),
|
|
5587
|
+
/* @__PURE__ */ jsx15(Text14, { color: cookiesFromBrowser && cookiesFromBrowser !== "none" ? theme.primary : theme.gray, bold: true, children: `[${(cookiesFromBrowser ?? "none").toUpperCase()}]` })
|
|
5588
|
+
]
|
|
5589
|
+
}
|
|
5590
|
+
),
|
|
5591
|
+
/* @__PURE__ */ jsx15(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: browserFeedback }),
|
|
5592
|
+
/* @__PURE__ */ jsx15(Gap2, {}),
|
|
4932
5593
|
/* @__PURE__ */ jsx15(
|
|
4933
5594
|
FramedInput,
|
|
4934
5595
|
{
|
|
@@ -4990,12 +5651,14 @@ function SettingsView({
|
|
|
4990
5651
|
}
|
|
4991
5652
|
),
|
|
4992
5653
|
/* @__PURE__ */ jsx15(Gap2, {}),
|
|
5654
|
+
/* @__PURE__ */ jsx15(DoctorCard, { boxWidth, theme, report: doctorReport }),
|
|
5655
|
+
/* @__PURE__ */ jsx15(Gap2, {}),
|
|
4993
5656
|
/* @__PURE__ */ jsx15(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: saved ? "\u2713 preferences saved to ~/.open-nami/config.json" : "tab / \u2191\u2193 switch field \xB7 space toggle/cycle \xB7 ^s / \u21B5 save \xB7 esc back" })
|
|
4994
5657
|
] });
|
|
4995
5658
|
}
|
|
4996
5659
|
|
|
4997
5660
|
// src/components/views/watcher-view.tsx
|
|
4998
|
-
import { useState as useState7, useEffect as
|
|
5661
|
+
import { useState as useState7, useEffect as useEffect7, useRef as useRef3, useCallback as useCallback2 } from "react";
|
|
4999
5662
|
import { Box as Box13, Text as Text15, useInput as useInput3 } from "ink";
|
|
5000
5663
|
import Spinner2 from "ink-spinner";
|
|
5001
5664
|
|
|
@@ -5282,9 +5945,9 @@ function startClipboardWatcher(options = {}) {
|
|
|
5282
5945
|
}
|
|
5283
5946
|
|
|
5284
5947
|
// src/lib/reveal.ts
|
|
5285
|
-
import
|
|
5948
|
+
import fs16 from "fs";
|
|
5286
5949
|
import { spawn as spawn4 } from "child_process";
|
|
5287
|
-
import
|
|
5950
|
+
import path16 from "path";
|
|
5288
5951
|
function buildOpenBrowserCommand(url, platform = process.platform) {
|
|
5289
5952
|
try {
|
|
5290
5953
|
const parsed = new URL(url.trim());
|
|
@@ -5311,12 +5974,12 @@ function openBrowser(url) {
|
|
|
5311
5974
|
}
|
|
5312
5975
|
}
|
|
5313
5976
|
function getRevealCommand(targetPath, platform = process.platform, isDirectory) {
|
|
5314
|
-
const pathModule = platform === "win32" ?
|
|
5977
|
+
const pathModule = platform === "win32" ? path16.win32 : platform === "darwin" ? path16.posix : path16;
|
|
5315
5978
|
const resolved = pathModule.resolve(targetPath);
|
|
5316
5979
|
const isDir = isDirectory !== void 0 ? isDirectory : (() => {
|
|
5317
5980
|
try {
|
|
5318
|
-
if (
|
|
5319
|
-
return
|
|
5981
|
+
if (fs16.existsSync(resolved)) {
|
|
5982
|
+
return fs16.statSync(resolved).isDirectory();
|
|
5320
5983
|
}
|
|
5321
5984
|
} catch {
|
|
5322
5985
|
}
|
|
@@ -5364,13 +6027,13 @@ function WatcherView({
|
|
|
5364
6027
|
mediaChoiceRef.current = mediaChoice;
|
|
5365
6028
|
const writeMetadataRef = useRef3(writeMetadata);
|
|
5366
6029
|
writeMetadataRef.current = writeMetadata;
|
|
5367
|
-
|
|
6030
|
+
useEffect7(() => {
|
|
5368
6031
|
const pulseTimer = setInterval(() => {
|
|
5369
6032
|
setPulseTick((t) => t + 1);
|
|
5370
6033
|
}, 120);
|
|
5371
6034
|
return () => clearInterval(pulseTimer);
|
|
5372
6035
|
}, []);
|
|
5373
|
-
|
|
6036
|
+
useEffect7(() => {
|
|
5374
6037
|
const watcher = startClipboardWatcher({
|
|
5375
6038
|
choice: mediaChoiceRef.current,
|
|
5376
6039
|
getChoice: () => mediaChoiceRef.current,
|
|
@@ -5444,7 +6107,7 @@ function WatcherView({
|
|
|
5444
6107
|
const target = outputDir || resolveDefaultDownloadDir("instagram", "watcher");
|
|
5445
6108
|
revealInFileManager(target);
|
|
5446
6109
|
}, [outputDir]);
|
|
5447
|
-
|
|
6110
|
+
useEffect7(() => {
|
|
5448
6111
|
if (!onRegisterClickTargets) return;
|
|
5449
6112
|
const targets = [
|
|
5450
6113
|
{ match: "Target:", padX: 1, action: () => {
|
|
@@ -5610,10 +6273,10 @@ function WatcherView({
|
|
|
5610
6273
|
}
|
|
5611
6274
|
|
|
5612
6275
|
// src/components/views/batch-view.tsx
|
|
5613
|
-
import { useState as useState8, useRef as useRef4, useCallback as useCallback3, useEffect as
|
|
6276
|
+
import { useState as useState8, useRef as useRef4, useCallback as useCallback3, useEffect as useEffect8 } from "react";
|
|
5614
6277
|
import { Box as Box14, Text as Text16, useInput as useInput4 } from "ink";
|
|
5615
6278
|
import Spinner3 from "ink-spinner";
|
|
5616
|
-
import
|
|
6279
|
+
import fs17 from "fs";
|
|
5617
6280
|
|
|
5618
6281
|
// src/lib/batch.ts
|
|
5619
6282
|
import fsPromises5 from "fs/promises";
|
|
@@ -5735,7 +6398,7 @@ async function runBatchQueue(options) {
|
|
|
5735
6398
|
}
|
|
5736
6399
|
|
|
5737
6400
|
// src/components/views/batch-view.tsx
|
|
5738
|
-
import { Fragment as
|
|
6401
|
+
import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
5739
6402
|
var PLATFORMS = ["instagram", "tiktok", "facebook", "x"];
|
|
5740
6403
|
var CHOICES2 = ["all", "photos", "videos", "stories", "highlights"];
|
|
5741
6404
|
var DATE_PRESETS = ["all", "24h", "7d", "30d", "2024", "2023", "2022", "2021", "custom"];
|
|
@@ -5820,7 +6483,7 @@ function BatchView({
|
|
|
5820
6483
|
setErrorMessage("Please enter a valid batch file path");
|
|
5821
6484
|
return;
|
|
5822
6485
|
}
|
|
5823
|
-
if (!
|
|
6486
|
+
if (!fs17.existsSync(trimmed)) {
|
|
5824
6487
|
setErrorMessage(`File not found: ${trimmed}`);
|
|
5825
6488
|
return;
|
|
5826
6489
|
}
|
|
@@ -6152,7 +6815,7 @@ function BatchView({
|
|
|
6152
6815
|
}
|
|
6153
6816
|
}
|
|
6154
6817
|
}, { isActive: Boolean(process.stdin.isTTY) });
|
|
6155
|
-
|
|
6818
|
+
useEffect8(() => {
|
|
6156
6819
|
if (!onRegisterClickTargets) return;
|
|
6157
6820
|
const targets = [];
|
|
6158
6821
|
if (phase === "input") {
|
|
@@ -6442,7 +7105,7 @@ function BatchView({
|
|
|
6442
7105
|
}
|
|
6443
7106
|
) }),
|
|
6444
7107
|
/* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "\u21B5 confirm \xB7 esc cancel" })
|
|
6445
|
-
] }) : /* @__PURE__ */ jsxs14(
|
|
7108
|
+
] }) : /* @__PURE__ */ jsxs14(Fragment3, { children: [
|
|
6446
7109
|
/* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", width: "100%", children: [
|
|
6447
7110
|
/* @__PURE__ */ jsxs14(Box14, { width: colWidth, children: [
|
|
6448
7111
|
/* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "[p] Platform: " }),
|
|
@@ -6653,8 +7316,82 @@ function frameRowSpan(row) {
|
|
|
6653
7316
|
return [first + 1, line.trimEnd().length];
|
|
6654
7317
|
}
|
|
6655
7318
|
|
|
7319
|
+
// src/lib/engines/core/use-engine-events.ts
|
|
7320
|
+
import { useState as useState9, useEffect as useEffect9 } from "react";
|
|
7321
|
+
var INITIAL_ENGINE_EVENT_STATE = {
|
|
7322
|
+
isDownloading: false,
|
|
7323
|
+
activeJob: void 0,
|
|
7324
|
+
progress: void 0,
|
|
7325
|
+
lastError: void 0,
|
|
7326
|
+
lastResult: void 0
|
|
7327
|
+
};
|
|
7328
|
+
function subscribeToEngineEvents(bus, onChange) {
|
|
7329
|
+
let state = { ...INITIAL_ENGINE_EVENT_STATE };
|
|
7330
|
+
const onJobStart = (payload) => {
|
|
7331
|
+
state = {
|
|
7332
|
+
...state,
|
|
7333
|
+
isDownloading: true,
|
|
7334
|
+
activeJob: payload,
|
|
7335
|
+
progress: void 0,
|
|
7336
|
+
lastError: void 0,
|
|
7337
|
+
lastResult: void 0
|
|
7338
|
+
};
|
|
7339
|
+
onChange(state);
|
|
7340
|
+
};
|
|
7341
|
+
const onJobProgress = (payload) => {
|
|
7342
|
+
state = {
|
|
7343
|
+
...state,
|
|
7344
|
+
isDownloading: true,
|
|
7345
|
+
progress: payload
|
|
7346
|
+
};
|
|
7347
|
+
onChange(state);
|
|
7348
|
+
};
|
|
7349
|
+
const onJobComplete = (payload) => {
|
|
7350
|
+
state = {
|
|
7351
|
+
...state,
|
|
7352
|
+
isDownloading: false,
|
|
7353
|
+
lastResult: payload.result
|
|
7354
|
+
};
|
|
7355
|
+
onChange(state);
|
|
7356
|
+
};
|
|
7357
|
+
const onJobError = (payload) => {
|
|
7358
|
+
state = {
|
|
7359
|
+
...state,
|
|
7360
|
+
isDownloading: false,
|
|
7361
|
+
lastError: payload.error
|
|
7362
|
+
};
|
|
7363
|
+
onChange(state);
|
|
7364
|
+
};
|
|
7365
|
+
const unsubStart = bus.on("job:start", onJobStart);
|
|
7366
|
+
const unsubProgress = bus.on("job:progress", onJobProgress);
|
|
7367
|
+
const unsubComplete = bus.on("job:complete", onJobComplete);
|
|
7368
|
+
const unsubError = bus.on("job:error", onJobError);
|
|
7369
|
+
return () => {
|
|
7370
|
+
if (typeof bus.off === "function") {
|
|
7371
|
+
bus.off("job:start", onJobStart);
|
|
7372
|
+
bus.off("job:progress", onJobProgress);
|
|
7373
|
+
bus.off("job:complete", onJobComplete);
|
|
7374
|
+
bus.off("job:error", onJobError);
|
|
7375
|
+
}
|
|
7376
|
+
unsubStart?.();
|
|
7377
|
+
unsubProgress?.();
|
|
7378
|
+
unsubComplete?.();
|
|
7379
|
+
unsubError?.();
|
|
7380
|
+
};
|
|
7381
|
+
}
|
|
7382
|
+
function useEngineEvents(customBus) {
|
|
7383
|
+
const bus = customBus || defaultTaskManager.events;
|
|
7384
|
+
const [state, setState] = useState9(() => ({
|
|
7385
|
+
...INITIAL_ENGINE_EVENT_STATE
|
|
7386
|
+
}));
|
|
7387
|
+
useEffect9(() => {
|
|
7388
|
+
return subscribeToEngineEvents(bus, setState);
|
|
7389
|
+
}, [bus]);
|
|
7390
|
+
return state;
|
|
7391
|
+
}
|
|
7392
|
+
|
|
6656
7393
|
// src/app.tsx
|
|
6657
|
-
import { Fragment as
|
|
7394
|
+
import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
6658
7395
|
var TAGLINE = "grab any profile. paste. download. done.";
|
|
6659
7396
|
var DOWNLOAD_BUTTON = "download";
|
|
6660
7397
|
var Gap = ({ lines = 1 }) => /* @__PURE__ */ jsx18(Box15, { flexDirection: "column", flexShrink: 0, children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx18(Text17, { children: " " }, i)) });
|
|
@@ -6732,7 +7469,7 @@ function App({
|
|
|
6732
7469
|
writeMetadata,
|
|
6733
7470
|
version = "1.0.0"
|
|
6734
7471
|
}) {
|
|
6735
|
-
const [themeMode, setThemeMode] =
|
|
7472
|
+
const [themeMode, setThemeMode] = useState10(initialThemeMode);
|
|
6736
7473
|
const cycleTheme = useCallback4(() => {
|
|
6737
7474
|
setThemeMode(nextThemeMode);
|
|
6738
7475
|
}, []);
|
|
@@ -6767,18 +7504,19 @@ function InnerApp({
|
|
|
6767
7504
|
const theme = useTheme();
|
|
6768
7505
|
const { exit } = useApp();
|
|
6769
7506
|
const { stdout } = useStdout3();
|
|
7507
|
+
const engineEvents = useEngineEvents();
|
|
6770
7508
|
const config = useMemo3(() => loadConfig(), []);
|
|
6771
7509
|
const effectiveLimit = limit ?? config.defaultLimit;
|
|
6772
7510
|
const effectiveWriteMetadata = writeMetadata ?? Boolean(config.writeMetadata);
|
|
6773
|
-
const [urlInput, setUrlInput] =
|
|
6774
|
-
const [clipboardUrl, setClipboardUrl] =
|
|
6775
|
-
const [settingsFocus, setSettingsFocus] =
|
|
6776
|
-
const [batchClickTargets, setBatchClickTargets] =
|
|
6777
|
-
const [watcherClickTargets, setWatcherClickTargets] =
|
|
7511
|
+
const [urlInput, setUrlInput] = useState10(initialInput ?? "");
|
|
7512
|
+
const [clipboardUrl, setClipboardUrl] = useState10(void 0);
|
|
7513
|
+
const [settingsFocus, setSettingsFocus] = useState10("cookies");
|
|
7514
|
+
const [batchClickTargets, setBatchClickTargets] = useState10([]);
|
|
7515
|
+
const [watcherClickTargets, setWatcherClickTargets] = useState10([]);
|
|
6778
7516
|
const columns = stdout?.columns && stdout.columns > 0 ? stdout.columns : 80;
|
|
6779
7517
|
const boxWidth = Math.max(14, Math.min(64, columns - 6));
|
|
6780
7518
|
const contentWidth = Math.max(10, Math.min(columns - 4, 78));
|
|
6781
|
-
const [phase, setPhase] =
|
|
7519
|
+
const [phase, setPhase] = useState10(() => {
|
|
6782
7520
|
if (initialInput) {
|
|
6783
7521
|
const parsed = parseProfileInput(initialInput);
|
|
6784
7522
|
if (parsed.platform !== "unknown" && parsed.cleanUrl) {
|
|
@@ -6790,7 +7528,7 @@ function InnerApp({
|
|
|
6790
7528
|
}
|
|
6791
7529
|
return { name: "input" };
|
|
6792
7530
|
});
|
|
6793
|
-
|
|
7531
|
+
useEffect10(() => {
|
|
6794
7532
|
try {
|
|
6795
7533
|
const clip = readClipboard();
|
|
6796
7534
|
if (clip && (clip.startsWith("http://") || clip.startsWith("https://") || clip.startsWith("@"))) {
|
|
@@ -6803,7 +7541,7 @@ function InnerApp({
|
|
|
6803
7541
|
const clipboardAccepted = Boolean(clipboardUrl) && urlInput === clipboardUrl;
|
|
6804
7542
|
const abortControllerRef = useRef5(null);
|
|
6805
7543
|
const probingTimeoutRef = useRef5(null);
|
|
6806
|
-
|
|
7544
|
+
useEffect10(() => {
|
|
6807
7545
|
return () => {
|
|
6808
7546
|
abortControllerRef.current?.abort();
|
|
6809
7547
|
if (probingTimeoutRef.current) {
|
|
@@ -7163,7 +7901,7 @@ function InnerApp({
|
|
|
7163
7901
|
{
|
|
7164
7902
|
profile: phase.profile,
|
|
7165
7903
|
choice: phase.choice,
|
|
7166
|
-
progress: phase.progress,
|
|
7904
|
+
progress: engineEvents.progress ?? phase.progress,
|
|
7167
7905
|
outputDir: resolveDefaultDownloadDir(phase.profile.platform, phase.profile.username, outputDir),
|
|
7168
7906
|
writeMetadata: effectiveWriteMetadata,
|
|
7169
7907
|
limit: effectiveLimit,
|
|
@@ -7229,7 +7967,7 @@ function InnerApp({
|
|
|
7229
7967
|
onRegisterClickTargets: setBatchClickTargets
|
|
7230
7968
|
}
|
|
7231
7969
|
),
|
|
7232
|
-
hints.length > 0 ? /* @__PURE__ */ jsxs15(
|
|
7970
|
+
hints.length > 0 ? /* @__PURE__ */ jsxs15(Fragment4, { children: [
|
|
7233
7971
|
/* @__PURE__ */ jsx18(Gap, { lines: 2 }),
|
|
7234
7972
|
/* @__PURE__ */ jsx18(
|
|
7235
7973
|
Shortcuts,
|
|
@@ -7443,7 +8181,7 @@ import { jsx as jsx19 } from "react/jsx-runtime";
|
|
|
7443
8181
|
var resolveVersion = () => {
|
|
7444
8182
|
try {
|
|
7445
8183
|
if (typeof import.meta !== "undefined" && import.meta?.url) {
|
|
7446
|
-
return
|
|
8184
|
+
return createRequire2(import.meta.url)("../package.json").version;
|
|
7447
8185
|
}
|
|
7448
8186
|
} catch {
|
|
7449
8187
|
}
|
|
@@ -7467,6 +8205,7 @@ var HELP = `
|
|
|
7467
8205
|
Options:
|
|
7468
8206
|
-h, --help Show this help message
|
|
7469
8207
|
-v, --version Show version
|
|
8208
|
+
--doctor Run environment & dependency diagnostics
|
|
7470
8209
|
-o, --output Set custom output directory
|
|
7471
8210
|
-w, --watch Watch clipboard for profile links and auto-download
|
|
7472
8211
|
-b, --batch <file> Download multiple profiles listed in a text file (one URL/user per line)
|
|
@@ -7513,6 +8252,24 @@ async function main() {
|
|
|
7513
8252
|
console.log(`open-nami v${VERSION}`);
|
|
7514
8253
|
process.exit(0);
|
|
7515
8254
|
}
|
|
8255
|
+
if (args.includes("--doctor")) {
|
|
8256
|
+
const report = await runDoctor();
|
|
8257
|
+
console.log(`
|
|
8258
|
+
\x1B[36mOpen Nami Doctor\x1B[0m (v${VERSION})
|
|
8259
|
+
`);
|
|
8260
|
+
console.log(` \x1B[1mRuntime:\x1B[0m Node.js ${report.nodeVersion}`);
|
|
8261
|
+
console.log(` \x1B[1mStorage:\x1B[0m ${report.storageOk ? "\x1B[32m\u2713 Ready\x1B[0m" : "\x1B[31m\u2717 Inaccessible\x1B[0m"}`);
|
|
8262
|
+
console.log(` \x1B[1mEngines:\x1B[0m`);
|
|
8263
|
+
for (const [bin, ok] of Object.entries(report.binaries)) {
|
|
8264
|
+
console.log(` ${ok ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m"} ${bin}`);
|
|
8265
|
+
}
|
|
8266
|
+
console.log(` \x1B[1mCookies:\x1B[0m`);
|
|
8267
|
+
for (const [platform, ok] of Object.entries(report.cookies)) {
|
|
8268
|
+
console.log(` ${ok ? "\x1B[32m\u2713\x1B[0m" : "\x1B[90m\u25CB\x1B[0m"} ${platform}`);
|
|
8269
|
+
}
|
|
8270
|
+
console.log();
|
|
8271
|
+
process.exit(0);
|
|
8272
|
+
}
|
|
7516
8273
|
const completionIdx = args.findIndex((arg) => arg === "--completion" || arg.startsWith("--completion="));
|
|
7517
8274
|
if (completionIdx !== -1) {
|
|
7518
8275
|
const arg = args[completionIdx];
|