open-nami 1.3.1 → 1.6.0
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 +1435 -275
- 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.6.0",
|
|
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,25 @@ 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
|
+
let cookiesFromBrowser = "auto";
|
|
753
|
+
if (typeof parsed.cookiesFromBrowser === "string") {
|
|
754
|
+
const trimmed = parsed.cookiesFromBrowser.trim();
|
|
755
|
+
const lower = trimmed.toLowerCase();
|
|
756
|
+
if (lower === "none") {
|
|
757
|
+
cookiesFromBrowser = "none";
|
|
758
|
+
} else if (lower === "auto" || trimmed === "") {
|
|
759
|
+
cookiesFromBrowser = "auto";
|
|
760
|
+
} else {
|
|
761
|
+
const knownBrowsers = ["chrome", "edge", "brave", "firefox", "helium", "opera", "vivaldi"];
|
|
762
|
+
cookiesFromBrowser = knownBrowsers.includes(lower) ? lower : trimmed;
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
cookiesFromBrowser = "auto";
|
|
766
|
+
}
|
|
760
767
|
return {
|
|
761
768
|
theme: typeof parsed.theme === "string" ? parsed.theme : void 0,
|
|
762
769
|
cookiesDir: typeof parsed.cookiesDir === "string" ? parsed.cookiesDir.trim() : void 0,
|
|
770
|
+
cookiesFromBrowser,
|
|
763
771
|
downloadDir: typeof parsed.downloadDir === "string" ? parsed.downloadDir.trim() : void 0,
|
|
764
772
|
writeMetadata: typeof parsed.writeMetadata === "boolean" ? parsed.writeMetadata : void 0,
|
|
765
773
|
defaultLimit: typeof parsed.defaultLimit === "number" ? parsed.defaultLimit : void 0
|
|
@@ -781,22 +789,442 @@ function saveConfig(config, configPath = getDefaultConfigPath()) {
|
|
|
781
789
|
}
|
|
782
790
|
}
|
|
783
791
|
|
|
792
|
+
// src/lib/cookies.ts
|
|
793
|
+
import fs3 from "fs";
|
|
794
|
+
import fsPromises2 from "fs/promises";
|
|
795
|
+
import path4 from "path";
|
|
796
|
+
import os3 from "os";
|
|
797
|
+
|
|
798
|
+
// src/lib/cookies/browser-bridge.ts
|
|
799
|
+
import { execSync } from "child_process";
|
|
800
|
+
import fs2 from "fs";
|
|
801
|
+
import os2 from "os";
|
|
802
|
+
import path3 from "path";
|
|
803
|
+
function listDetectedBrowsers() {
|
|
804
|
+
const isWin = process.platform === "win32";
|
|
805
|
+
const isMac = process.platform === "darwin";
|
|
806
|
+
const home = os2.homedir();
|
|
807
|
+
const candidates = [
|
|
808
|
+
{
|
|
809
|
+
id: "chrome",
|
|
810
|
+
name: "Google Chrome",
|
|
811
|
+
winDir: path3.join(
|
|
812
|
+
process.env.LOCALAPPDATA || path3.join(home, "AppData", "Local"),
|
|
813
|
+
"Google",
|
|
814
|
+
"Chrome",
|
|
815
|
+
"User Data"
|
|
816
|
+
),
|
|
817
|
+
macDir: path3.join(home, "Library", "Application Support", "Google", "Chrome"),
|
|
818
|
+
linuxDir: path3.join(home, ".config", "google-chrome"),
|
|
819
|
+
engine: "chrome"
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
id: "edge",
|
|
823
|
+
name: "Microsoft Edge",
|
|
824
|
+
winDir: path3.join(
|
|
825
|
+
process.env.LOCALAPPDATA || path3.join(home, "AppData", "Local"),
|
|
826
|
+
"Microsoft",
|
|
827
|
+
"Edge",
|
|
828
|
+
"User Data"
|
|
829
|
+
),
|
|
830
|
+
macDir: path3.join(home, "Library", "Application Support", "Microsoft Edge"),
|
|
831
|
+
linuxDir: path3.join(home, ".config", "microsoft-edge"),
|
|
832
|
+
engine: "edge"
|
|
833
|
+
},
|
|
834
|
+
{
|
|
835
|
+
id: "brave",
|
|
836
|
+
name: "Brave Browser",
|
|
837
|
+
winDir: path3.join(
|
|
838
|
+
process.env.LOCALAPPDATA || path3.join(home, "AppData", "Local"),
|
|
839
|
+
"BraveSoftware",
|
|
840
|
+
"Brave-Browser",
|
|
841
|
+
"User Data"
|
|
842
|
+
),
|
|
843
|
+
macDir: path3.join(home, "Library", "Application Support", "BraveSoftware", "Brave-Browser"),
|
|
844
|
+
linuxDir: path3.join(home, ".config", "BraveSoftware", "Brave-Browser"),
|
|
845
|
+
engine: "brave"
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
id: "firefox",
|
|
849
|
+
name: "Mozilla Firefox",
|
|
850
|
+
winDir: path3.join(
|
|
851
|
+
process.env.APPDATA || path3.join(home, "AppData", "Roaming"),
|
|
852
|
+
"Mozilla",
|
|
853
|
+
"Firefox",
|
|
854
|
+
"Profiles"
|
|
855
|
+
),
|
|
856
|
+
macDir: path3.join(home, "Library", "Application Support", "Firefox", "Profiles"),
|
|
857
|
+
linuxDir: path3.join(home, ".mozilla", "firefox"),
|
|
858
|
+
engine: "firefox"
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
id: "helium",
|
|
862
|
+
name: "Helium",
|
|
863
|
+
winDir: path3.join(
|
|
864
|
+
process.env.LOCALAPPDATA || path3.join(home, "AppData", "Local"),
|
|
865
|
+
"imput",
|
|
866
|
+
"Helium",
|
|
867
|
+
"User Data"
|
|
868
|
+
),
|
|
869
|
+
macDir: path3.join(home, "Library", "Application Support", "net.imput.Helium"),
|
|
870
|
+
linuxDir: path3.join(home, ".config", "helium"),
|
|
871
|
+
engine: "chrome"
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
id: "opera",
|
|
875
|
+
name: "Opera",
|
|
876
|
+
winDir: path3.join(
|
|
877
|
+
process.env.APPDATA || path3.join(home, "AppData", "Roaming"),
|
|
878
|
+
"Opera Software",
|
|
879
|
+
"Opera Stable"
|
|
880
|
+
),
|
|
881
|
+
macDir: path3.join(home, "Library", "Application Support", "com.operasoftware.Opera"),
|
|
882
|
+
linuxDir: path3.join(home, ".config", "opera"),
|
|
883
|
+
engine: "opera"
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
id: "vivaldi",
|
|
887
|
+
name: "Vivaldi",
|
|
888
|
+
winDir: path3.join(
|
|
889
|
+
process.env.LOCALAPPDATA || path3.join(home, "AppData", "Local"),
|
|
890
|
+
"Vivaldi",
|
|
891
|
+
"User Data"
|
|
892
|
+
),
|
|
893
|
+
macDir: path3.join(home, "Library", "Application Support", "Vivaldi"),
|
|
894
|
+
linuxDir: path3.join(home, ".config", "vivaldi"),
|
|
895
|
+
engine: "vivaldi"
|
|
896
|
+
}
|
|
897
|
+
];
|
|
898
|
+
return candidates.map((c) => {
|
|
899
|
+
const profileDir = isWin ? c.winDir : isMac ? c.macDir : c.linuxDir;
|
|
900
|
+
return {
|
|
901
|
+
id: c.id,
|
|
902
|
+
name: c.name,
|
|
903
|
+
profileDir,
|
|
904
|
+
available: fs2.existsSync(profileDir),
|
|
905
|
+
engine: c.engine
|
|
906
|
+
};
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
function parseWindowsDefaultBrowserProgId(rawOutput) {
|
|
910
|
+
if (!rawOutput) return null;
|
|
911
|
+
const lines = rawOutput.split(/\r?\n/);
|
|
912
|
+
const pattern = /^\s*ProgId\s+REG_\w+\s+(\S.*)$/i;
|
|
913
|
+
for (const line of lines) {
|
|
914
|
+
const match = line.match(pattern);
|
|
915
|
+
if (match && match[1]) {
|
|
916
|
+
const progId = match[1].trim();
|
|
917
|
+
if (progId) return progId;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
function mapProgIdToBrowserId(progId, commandOutput, platformOverride = "win32") {
|
|
923
|
+
const p = (progId || "").trim();
|
|
924
|
+
const pLower = p.toLowerCase();
|
|
925
|
+
const cmd = (commandOutput || "").toLowerCase();
|
|
926
|
+
const isWin = platformOverride === "win32";
|
|
927
|
+
const isMac = platformOverride === "darwin";
|
|
928
|
+
const home = os2.homedir();
|
|
929
|
+
const heliumDir = isWin ? path3.join(
|
|
930
|
+
process.env.LOCALAPPDATA || path3.join(home, "AppData", "Local"),
|
|
931
|
+
"imput",
|
|
932
|
+
"Helium",
|
|
933
|
+
"User Data"
|
|
934
|
+
) : isMac ? path3.join(home, "Library", "Application Support", "net.imput.Helium") : path3.join(home, ".config", "helium");
|
|
935
|
+
if (pLower.includes("helium") || cmd.includes("helium")) {
|
|
936
|
+
return {
|
|
937
|
+
id: "helium",
|
|
938
|
+
name: "Helium",
|
|
939
|
+
engine: "chrome",
|
|
940
|
+
profileDir: heliumDir
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
if (pLower.startsWith("chromehtml") || pLower.includes("chrome") || cmd.includes("chrome.exe")) {
|
|
944
|
+
return {
|
|
945
|
+
id: "chrome",
|
|
946
|
+
name: "Google Chrome",
|
|
947
|
+
engine: "chrome"
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
if (pLower.startsWith("msedge") || pLower.includes("edge") || pLower.startsWith("appxq0fevzme2pys62n3e0fbqsw5cj08ege9") || cmd.includes("msedge.exe")) {
|
|
951
|
+
return {
|
|
952
|
+
id: "edge",
|
|
953
|
+
name: "Microsoft Edge",
|
|
954
|
+
engine: "edge"
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
if (pLower.startsWith("brave") || pLower.includes("brave") || cmd.includes("brave.exe")) {
|
|
958
|
+
return {
|
|
959
|
+
id: "brave",
|
|
960
|
+
name: "Brave Browser",
|
|
961
|
+
engine: "brave"
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
if (pLower.startsWith("firefox") || pLower.includes("firefox") || cmd.includes("firefox.exe")) {
|
|
965
|
+
return {
|
|
966
|
+
id: "firefox",
|
|
967
|
+
name: "Mozilla Firefox",
|
|
968
|
+
engine: "firefox"
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
if (pLower.startsWith("opera") || pLower.includes("opera") || cmd.includes("opera.exe")) {
|
|
972
|
+
return {
|
|
973
|
+
id: "opera",
|
|
974
|
+
name: "Opera",
|
|
975
|
+
engine: "opera"
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
if (pLower.startsWith("vivaldi") || pLower.includes("vivaldi") || cmd.includes("vivaldi.exe")) {
|
|
979
|
+
return {
|
|
980
|
+
id: "vivaldi",
|
|
981
|
+
name: "Vivaldi",
|
|
982
|
+
engine: "vivaldi"
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
return {
|
|
986
|
+
id: pLower || "chromium",
|
|
987
|
+
name: p || "Chromium",
|
|
988
|
+
engine: "chromium"
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function parseMacDefaultBrowserBundleId(rawOutput) {
|
|
992
|
+
if (!rawOutput) return null;
|
|
993
|
+
const blocks = rawOutput.split(/[{}]/);
|
|
994
|
+
let httpBundle = null;
|
|
995
|
+
for (const block of blocks) {
|
|
996
|
+
const isHttps = /LSHandlerURLScheme\s*=\s*https\b/i.test(block);
|
|
997
|
+
const isHttp = /LSHandlerURLScheme\s*=\s*http\b/i.test(block);
|
|
998
|
+
if (!isHttps && !isHttp) continue;
|
|
999
|
+
const roleMatch = block.match(/LSHandlerRole(?:All|Viewer)\s*=\s*["']?([^"';\s]+)["']?/i);
|
|
1000
|
+
if (roleMatch && roleMatch[1]) {
|
|
1001
|
+
const bundleId = roleMatch[1].trim();
|
|
1002
|
+
if (isHttps) {
|
|
1003
|
+
return bundleId;
|
|
1004
|
+
}
|
|
1005
|
+
if (!httpBundle) {
|
|
1006
|
+
httpBundle = bundleId;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
return httpBundle;
|
|
1011
|
+
}
|
|
1012
|
+
function mapBundleIdToBrowserId(bundleId) {
|
|
1013
|
+
const lower = bundleId.toLowerCase();
|
|
1014
|
+
if (lower.includes("helium")) {
|
|
1015
|
+
const home = os2.homedir();
|
|
1016
|
+
return {
|
|
1017
|
+
id: "helium",
|
|
1018
|
+
name: "Helium",
|
|
1019
|
+
engine: "chrome",
|
|
1020
|
+
profileDir: path3.join(home, "Library", "Application Support", "net.imput.Helium")
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
if (lower.includes("chrome")) {
|
|
1024
|
+
return { id: "chrome", name: "Google Chrome", engine: "chrome" };
|
|
1025
|
+
}
|
|
1026
|
+
if (lower.includes("edge")) {
|
|
1027
|
+
return { id: "edge", name: "Microsoft Edge", engine: "edge" };
|
|
1028
|
+
}
|
|
1029
|
+
if (lower.includes("brave")) {
|
|
1030
|
+
return { id: "brave", name: "Brave Browser", engine: "brave" };
|
|
1031
|
+
}
|
|
1032
|
+
if (lower.includes("firefox")) {
|
|
1033
|
+
return { id: "firefox", name: "Mozilla Firefox", engine: "firefox" };
|
|
1034
|
+
}
|
|
1035
|
+
if (lower.includes("opera")) {
|
|
1036
|
+
return { id: "opera", name: "Opera", engine: "opera" };
|
|
1037
|
+
}
|
|
1038
|
+
if (lower.includes("vivaldi")) {
|
|
1039
|
+
return { id: "vivaldi", name: "Vivaldi", engine: "vivaldi" };
|
|
1040
|
+
}
|
|
1041
|
+
if (lower.includes("safari")) {
|
|
1042
|
+
return { id: "safari", name: "Safari", engine: "safari" };
|
|
1043
|
+
}
|
|
1044
|
+
return { id: lower, name: bundleId, engine: "chromium" };
|
|
1045
|
+
}
|
|
1046
|
+
function parseLinuxDefaultBrowserDesktop(rawOutput) {
|
|
1047
|
+
if (!rawOutput) return null;
|
|
1048
|
+
const lines = rawOutput.split(/\r?\n/);
|
|
1049
|
+
for (const line of lines) {
|
|
1050
|
+
const trimmed = line.trim();
|
|
1051
|
+
if (trimmed && !trimmed.startsWith("ERROR:") && !trimmed.includes("not found")) {
|
|
1052
|
+
return trimmed;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
function mapLinuxDesktopToBrowserId(desktopFile) {
|
|
1058
|
+
const lower = desktopFile.toLowerCase();
|
|
1059
|
+
if (lower.includes("helium")) {
|
|
1060
|
+
const home = os2.homedir();
|
|
1061
|
+
return {
|
|
1062
|
+
id: "helium",
|
|
1063
|
+
name: "Helium",
|
|
1064
|
+
engine: "chrome",
|
|
1065
|
+
profileDir: path3.join(home, ".config", "helium")
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
if (lower.includes("chrome")) {
|
|
1069
|
+
return { id: "chrome", name: "Google Chrome", engine: "chrome" };
|
|
1070
|
+
}
|
|
1071
|
+
if (lower.includes("edge")) {
|
|
1072
|
+
return { id: "edge", name: "Microsoft Edge", engine: "edge" };
|
|
1073
|
+
}
|
|
1074
|
+
if (lower.includes("brave")) {
|
|
1075
|
+
return { id: "brave", name: "Brave Browser", engine: "brave" };
|
|
1076
|
+
}
|
|
1077
|
+
if (lower.includes("firefox")) {
|
|
1078
|
+
return { id: "firefox", name: "Mozilla Firefox", engine: "firefox" };
|
|
1079
|
+
}
|
|
1080
|
+
if (lower.includes("opera")) {
|
|
1081
|
+
return { id: "opera", name: "Opera", engine: "opera" };
|
|
1082
|
+
}
|
|
1083
|
+
if (lower.includes("vivaldi")) {
|
|
1084
|
+
return { id: "vivaldi", name: "Vivaldi", engine: "vivaldi" };
|
|
1085
|
+
}
|
|
1086
|
+
return { id: lower.replace(/\.desktop$/, ""), name: desktopFile, engine: "chromium" };
|
|
1087
|
+
}
|
|
1088
|
+
function queryWindows(execute, platformOverride) {
|
|
1089
|
+
let out = execute(
|
|
1090
|
+
'reg query "HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice" /v ProgId'
|
|
1091
|
+
);
|
|
1092
|
+
let progId = parseWindowsDefaultBrowserProgId(out);
|
|
1093
|
+
if (!progId) {
|
|
1094
|
+
out = execute(
|
|
1095
|
+
'reg query "HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice" /v ProgId'
|
|
1096
|
+
);
|
|
1097
|
+
progId = parseWindowsDefaultBrowserProgId(out);
|
|
1098
|
+
}
|
|
1099
|
+
if (progId) {
|
|
1100
|
+
const cmdOut = execute(`reg query "HKCR\\${progId}\\shell\\open\\command"`);
|
|
1101
|
+
return mapProgIdToBrowserId(progId, cmdOut, platformOverride ?? "win32");
|
|
1102
|
+
}
|
|
1103
|
+
return null;
|
|
1104
|
+
}
|
|
1105
|
+
function queryMac(execute) {
|
|
1106
|
+
const out = execute(
|
|
1107
|
+
"defaults read com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers"
|
|
1108
|
+
);
|
|
1109
|
+
const bundleId = parseMacDefaultBrowserBundleId(out);
|
|
1110
|
+
if (bundleId) {
|
|
1111
|
+
return mapBundleIdToBrowserId(bundleId);
|
|
1112
|
+
}
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
function queryLinux(execute) {
|
|
1116
|
+
let out = execute("xdg-settings get default-web-browser");
|
|
1117
|
+
let desktop = parseLinuxDefaultBrowserDesktop(out);
|
|
1118
|
+
if (!desktop) {
|
|
1119
|
+
out = execute("xdg-mime query default x-scheme-handler/https");
|
|
1120
|
+
desktop = parseLinuxDefaultBrowserDesktop(out);
|
|
1121
|
+
}
|
|
1122
|
+
if (!desktop) {
|
|
1123
|
+
out = execute("xdg-mime query default x-scheme-handler/http");
|
|
1124
|
+
desktop = parseLinuxDefaultBrowserDesktop(out);
|
|
1125
|
+
}
|
|
1126
|
+
if (desktop) {
|
|
1127
|
+
return mapLinuxDesktopToBrowserId(desktop);
|
|
1128
|
+
}
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
function detectSystemDefaultBrowser(execFn, platformOverride) {
|
|
1132
|
+
const platform = platformOverride ?? process.platform;
|
|
1133
|
+
const execute = execFn ?? ((cmd) => {
|
|
1134
|
+
try {
|
|
1135
|
+
return execSync(cmd, {
|
|
1136
|
+
encoding: "utf8",
|
|
1137
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
1138
|
+
timeout: 2500
|
|
1139
|
+
});
|
|
1140
|
+
} catch {
|
|
1141
|
+
return "";
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
let detectedInfo = null;
|
|
1145
|
+
if (platform === "win32") {
|
|
1146
|
+
detectedInfo = queryWindows(execute, platform);
|
|
1147
|
+
} else if (platform === "darwin") {
|
|
1148
|
+
detectedInfo = queryMac(execute);
|
|
1149
|
+
} else if (platform === "linux") {
|
|
1150
|
+
detectedInfo = queryLinux(execute);
|
|
1151
|
+
}
|
|
1152
|
+
if (!detectedInfo && execFn && !platformOverride) {
|
|
1153
|
+
if (platform !== "win32") detectedInfo = queryWindows(execute, "win32");
|
|
1154
|
+
if (!detectedInfo && platform !== "darwin") detectedInfo = queryMac(execute);
|
|
1155
|
+
if (!detectedInfo && platform !== "linux") detectedInfo = queryLinux(execute);
|
|
1156
|
+
}
|
|
1157
|
+
const allBrowsers = listDetectedBrowsers();
|
|
1158
|
+
if (detectedInfo) {
|
|
1159
|
+
const matched = allBrowsers.find((b) => b.id === detectedInfo.id);
|
|
1160
|
+
if (matched) {
|
|
1161
|
+
if (detectedInfo.profileDir) {
|
|
1162
|
+
return {
|
|
1163
|
+
...matched,
|
|
1164
|
+
profileDir: detectedInfo.profileDir,
|
|
1165
|
+
available: fs2.existsSync(detectedInfo.profileDir)
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
return matched;
|
|
1169
|
+
}
|
|
1170
|
+
const profDir = detectedInfo.profileDir || "";
|
|
1171
|
+
return {
|
|
1172
|
+
id: detectedInfo.id,
|
|
1173
|
+
name: detectedInfo.name || detectedInfo.id,
|
|
1174
|
+
profileDir: profDir,
|
|
1175
|
+
available: profDir ? fs2.existsSync(profDir) : false,
|
|
1176
|
+
engine: detectedInfo.engine
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
const availableFallback = allBrowsers.find((b) => b.available);
|
|
1180
|
+
return availableFallback || null;
|
|
1181
|
+
}
|
|
1182
|
+
function resolveBrowserCookieArg(browser) {
|
|
1183
|
+
if (!browser) {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
if (typeof browser === "string") {
|
|
1187
|
+
const trimmed = browser.trim();
|
|
1188
|
+
if (!trimmed || trimmed.toLowerCase() === "none") {
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1191
|
+
if (trimmed.toLowerCase() === "helium") {
|
|
1192
|
+
const detectedHelium = listDetectedBrowsers().find((b) => b.id === "helium");
|
|
1193
|
+
const profileDir = detectedHelium?.profileDir;
|
|
1194
|
+
return profileDir ? `chrome:${profileDir}` : "chrome";
|
|
1195
|
+
}
|
|
1196
|
+
if (trimmed.toLowerCase() === "auto") {
|
|
1197
|
+
const defaultBrowser = detectSystemDefaultBrowser();
|
|
1198
|
+
return resolveBrowserCookieArg(defaultBrowser);
|
|
1199
|
+
}
|
|
1200
|
+
return trimmed;
|
|
1201
|
+
}
|
|
1202
|
+
if (!browser.id || browser.id.toLowerCase() === "none") {
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
if (browser.id.toLowerCase() === "helium") {
|
|
1206
|
+
const profileDir = browser.profileDir || listDetectedBrowsers().find((b) => b.id === "helium")?.profileDir;
|
|
1207
|
+
return profileDir ? `chrome:${profileDir}` : "chrome";
|
|
1208
|
+
}
|
|
1209
|
+
return browser.id;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
784
1212
|
// src/lib/cookies.ts
|
|
785
1213
|
var CANDIDATE_DIRS = [
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
1214
|
+
path4.join(os3.homedir(), ".open-nami", "cookies"),
|
|
1215
|
+
path4.join(os3.homedir(), ".nami", "cookies"),
|
|
1216
|
+
path4.join(process.cwd(), "cookies")
|
|
789
1217
|
];
|
|
790
1218
|
function getCandidateDirs(customDir) {
|
|
791
1219
|
const dirs = [];
|
|
792
1220
|
const configured = loadConfig().cookiesDir;
|
|
793
1221
|
const custom = customDir || process.env.OPEN_NAMI_COOKIES_DIR || process.env.NAMI_COOKIES_DIR || configured;
|
|
794
1222
|
if (custom && custom.trim()) {
|
|
795
|
-
dirs.push(
|
|
1223
|
+
dirs.push(path4.resolve(custom.trim()));
|
|
796
1224
|
}
|
|
797
|
-
const seen = new Set(dirs.map((d) =>
|
|
1225
|
+
const seen = new Set(dirs.map((d) => path4.normalize(d).toLowerCase()));
|
|
798
1226
|
for (const dir of CANDIDATE_DIRS) {
|
|
799
|
-
const norm =
|
|
1227
|
+
const norm = path4.normalize(dir).toLowerCase();
|
|
800
1228
|
if (!seen.has(norm)) {
|
|
801
1229
|
seen.add(norm);
|
|
802
1230
|
dirs.push(dir);
|
|
@@ -816,12 +1244,12 @@ function findCookieFile(platform, customDir) {
|
|
|
816
1244
|
const filenames = domainNames[platform] || [];
|
|
817
1245
|
const candidateDirs = getCandidateDirs(customDir);
|
|
818
1246
|
for (const dir of candidateDirs) {
|
|
819
|
-
if (!
|
|
1247
|
+
if (!fs3.existsSync(dir)) continue;
|
|
820
1248
|
for (const file of filenames) {
|
|
821
|
-
const fullPath =
|
|
822
|
-
if (
|
|
1249
|
+
const fullPath = path4.join(dir, file);
|
|
1250
|
+
if (fs3.existsSync(fullPath)) {
|
|
823
1251
|
try {
|
|
824
|
-
const stats =
|
|
1252
|
+
const stats = fs3.statSync(fullPath);
|
|
825
1253
|
if (stats.size > 20) {
|
|
826
1254
|
return fullPath;
|
|
827
1255
|
}
|
|
@@ -837,14 +1265,14 @@ function detectAvailableCookies(customDir) {
|
|
|
837
1265
|
return platforms.filter((p) => Boolean(findCookieFile(p, customDir)));
|
|
838
1266
|
}
|
|
839
1267
|
async function createSafeCookieCopy(masterPath) {
|
|
840
|
-
const userTempDir =
|
|
1268
|
+
const userTempDir = path4.join(os3.homedir(), ".open-nami", "temp");
|
|
841
1269
|
let targetDir = userTempDir;
|
|
842
1270
|
try {
|
|
843
1271
|
await fsPromises2.mkdir(userTempDir, { recursive: true, mode: 448 });
|
|
844
1272
|
} catch {
|
|
845
|
-
targetDir =
|
|
1273
|
+
targetDir = os3.tmpdir();
|
|
846
1274
|
}
|
|
847
|
-
const tempPath =
|
|
1275
|
+
const tempPath = path4.join(targetDir, `nami_cookie_${Date.now()}_${Math.random().toString(36).slice(2)}.txt`);
|
|
848
1276
|
await fsPromises2.copyFile(masterPath, tempPath);
|
|
849
1277
|
await fsPromises2.chmod(tempPath, 384).catch(() => {
|
|
850
1278
|
});
|
|
@@ -860,141 +1288,6 @@ async function createSafeCookieCopy(masterPath) {
|
|
|
860
1288
|
};
|
|
861
1289
|
}
|
|
862
1290
|
|
|
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
1291
|
// src/lib/engines/extractors/instagram.ts
|
|
999
1292
|
import crypto from "crypto";
|
|
1000
1293
|
|
|
@@ -1379,8 +1672,8 @@ var InstagramExtractor = class {
|
|
|
1379
1672
|
}
|
|
1380
1673
|
const user = res.data.user;
|
|
1381
1674
|
if (user.is_private) {
|
|
1382
|
-
const
|
|
1383
|
-
const isFollowing =
|
|
1675
|
+
const fs18 = user.friendship_status;
|
|
1676
|
+
const isFollowing = fs18 ? Boolean(fs18.following) : Boolean(user.followed_by_viewer);
|
|
1384
1677
|
if (!isFollowing) {
|
|
1385
1678
|
throw new Error(`Instagram profile @${username} is private. You must provide a session cookie jar of an account following this profile.`);
|
|
1386
1679
|
}
|
|
@@ -2947,6 +3240,30 @@ var FacebookExtractor = class {
|
|
|
2947
3240
|
}
|
|
2948
3241
|
};
|
|
2949
3242
|
|
|
3243
|
+
// src/lib/engines/core/plugin.ts
|
|
3244
|
+
var ExtractorPluginRegistry = class {
|
|
3245
|
+
plugins = /* @__PURE__ */ new Map();
|
|
3246
|
+
register(plugin) {
|
|
3247
|
+
this.plugins.set(plugin.platform, plugin);
|
|
3248
|
+
}
|
|
3249
|
+
get(platform) {
|
|
3250
|
+
return this.plugins.get(platform);
|
|
3251
|
+
}
|
|
3252
|
+
has(platform) {
|
|
3253
|
+
return this.plugins.has(platform);
|
|
3254
|
+
}
|
|
3255
|
+
list() {
|
|
3256
|
+
return Array.from(this.plugins.values());
|
|
3257
|
+
}
|
|
3258
|
+
findByUrl(url) {
|
|
3259
|
+
return this.list().find((plugin) => plugin.canHandle(url));
|
|
3260
|
+
}
|
|
3261
|
+
clear() {
|
|
3262
|
+
this.plugins.clear();
|
|
3263
|
+
}
|
|
3264
|
+
};
|
|
3265
|
+
var defaultPluginRegistry = new ExtractorPluginRegistry();
|
|
3266
|
+
|
|
2950
3267
|
// src/lib/engines/extractors/index.ts
|
|
2951
3268
|
function getExtractor(platform) {
|
|
2952
3269
|
switch (platform) {
|
|
@@ -3750,7 +4067,7 @@ var PLATFORM_SLEEP = {
|
|
|
3750
4067
|
facebook: "1.0-1.8"
|
|
3751
4068
|
};
|
|
3752
4069
|
function buildGalleryDlArgs(options) {
|
|
3753
|
-
const { destDir, subDir, platform, cleanUrl, username, cookiePath, writeMetadata } = options;
|
|
4070
|
+
const { destDir, subDir, platform, cleanUrl, username, cookiePath, cookiesFromBrowser, writeMetadata } = options;
|
|
3754
4071
|
const args = [
|
|
3755
4072
|
"-D",
|
|
3756
4073
|
destDir,
|
|
@@ -3801,6 +4118,9 @@ function buildGalleryDlArgs(options) {
|
|
|
3801
4118
|
if (cookiePath) {
|
|
3802
4119
|
args.push("--cookies", cookiePath);
|
|
3803
4120
|
}
|
|
4121
|
+
if (cookiesFromBrowser && cookiesFromBrowser !== "none") {
|
|
4122
|
+
args.push("--cookies-from-browser", cookiesFromBrowser);
|
|
4123
|
+
}
|
|
3804
4124
|
args.push("--", targetUrl);
|
|
3805
4125
|
return args;
|
|
3806
4126
|
}
|
|
@@ -3815,6 +4135,7 @@ async function runGalleryDl(options) {
|
|
|
3815
4135
|
cleanUrl: profile.cleanUrl,
|
|
3816
4136
|
username: profile.username,
|
|
3817
4137
|
cookiePath,
|
|
4138
|
+
cookiesFromBrowser: options.cookiesFromBrowser,
|
|
3818
4139
|
writeMetadata: options.writeMetadata
|
|
3819
4140
|
});
|
|
3820
4141
|
let downloadedCount = 0;
|
|
@@ -3923,7 +4244,7 @@ import fs9 from "fs/promises";
|
|
|
3923
4244
|
var YTDLP_ARCHIVE = "archive_yt-dlp.txt";
|
|
3924
4245
|
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
4246
|
function buildYtDlpArgs(options) {
|
|
3926
|
-
const { destDir, platform, cleanUrl, cookiePath, writeMetadata } = options;
|
|
4247
|
+
const { destDir, platform, cleanUrl, cookiePath, cookiesFromBrowser, writeMetadata } = options;
|
|
3927
4248
|
const args = [
|
|
3928
4249
|
"-o",
|
|
3929
4250
|
path9.join(destDir, "%(title).150B [%(id)s].%(ext)s"),
|
|
@@ -3957,6 +4278,9 @@ function buildYtDlpArgs(options) {
|
|
|
3957
4278
|
if (cookiePath) {
|
|
3958
4279
|
args.push("--cookies", cookiePath);
|
|
3959
4280
|
}
|
|
4281
|
+
if (cookiesFromBrowser && cookiesFromBrowser !== "none") {
|
|
4282
|
+
args.push("--cookies-from-browser", cookiesFromBrowser);
|
|
4283
|
+
}
|
|
3960
4284
|
args.push("--", cleanUrl);
|
|
3961
4285
|
return args;
|
|
3962
4286
|
}
|
|
@@ -3969,6 +4293,7 @@ async function runYtDlp(options) {
|
|
|
3969
4293
|
platform: profile.platform,
|
|
3970
4294
|
cleanUrl: profile.cleanUrl,
|
|
3971
4295
|
cookiePath,
|
|
4296
|
+
cookiesFromBrowser: options.cookiesFromBrowser,
|
|
3972
4297
|
writeMetadata: options.writeMetadata
|
|
3973
4298
|
});
|
|
3974
4299
|
let downloadedCount = 0;
|
|
@@ -4071,78 +4396,489 @@ async function runYtDlp(options) {
|
|
|
4071
4396
|
skipped: skippedCount,
|
|
4072
4397
|
error: err.message
|
|
4073
4398
|
});
|
|
4074
|
-
});
|
|
4399
|
+
});
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
|
|
4403
|
+
// src/lib/engines/core/task-manager.ts
|
|
4404
|
+
import path12 from "path";
|
|
4405
|
+
import os7 from "os";
|
|
4406
|
+
import fs12 from "fs/promises";
|
|
4407
|
+
import fsSync from "fs";
|
|
4408
|
+
|
|
4409
|
+
// src/lib/engines/core/event-bus.ts
|
|
4410
|
+
import { EventEmitter } from "events";
|
|
4411
|
+
var EngineEventBus = class {
|
|
4412
|
+
emitter = new EventEmitter();
|
|
4413
|
+
constructor() {
|
|
4414
|
+
this.emitter.setMaxListeners(50);
|
|
4415
|
+
}
|
|
4416
|
+
on(event, listener) {
|
|
4417
|
+
this.emitter.on(event, listener);
|
|
4418
|
+
return () => this.emitter.off(event, listener);
|
|
4419
|
+
}
|
|
4420
|
+
off(event, listener) {
|
|
4421
|
+
this.emitter.off(event, listener);
|
|
4422
|
+
}
|
|
4423
|
+
once(event, listener) {
|
|
4424
|
+
this.emitter.once(event, listener);
|
|
4425
|
+
}
|
|
4426
|
+
emit(event, payload) {
|
|
4427
|
+
return this.emitter.emit(event, payload);
|
|
4428
|
+
}
|
|
4429
|
+
removeAllListeners(event) {
|
|
4430
|
+
if (event !== void 0) {
|
|
4431
|
+
this.emitter.removeAllListeners(event);
|
|
4432
|
+
} else {
|
|
4433
|
+
this.emitter.removeAllListeners();
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
};
|
|
4437
|
+
|
|
4438
|
+
// src/lib/engines/storage/ledger.ts
|
|
4439
|
+
import fs10 from "fs";
|
|
4440
|
+
import path10 from "path";
|
|
4441
|
+
import os5 from "os";
|
|
4442
|
+
import { createRequire } from "module";
|
|
4443
|
+
var require2 = createRequire(import.meta.url);
|
|
4444
|
+
var MediaLedger = class {
|
|
4445
|
+
db = null;
|
|
4446
|
+
inMemoryFallback = {
|
|
4447
|
+
media: /* @__PURE__ */ new Map(),
|
|
4448
|
+
checkpoints: /* @__PURE__ */ new Map()
|
|
4449
|
+
};
|
|
4450
|
+
isFallback = false;
|
|
4451
|
+
resolvedPath;
|
|
4452
|
+
constructor(dbPath, forceFallback = false) {
|
|
4453
|
+
const resolvedPath = dbPath ?? (forceFallback ? ":memory:" : path10.join(os5.homedir(), ".open-nami", "nami.db"));
|
|
4454
|
+
this.resolvedPath = resolvedPath;
|
|
4455
|
+
if (forceFallback) {
|
|
4456
|
+
this.isFallback = true;
|
|
4457
|
+
this.loadFallbackDisk();
|
|
4458
|
+
return;
|
|
4459
|
+
}
|
|
4460
|
+
try {
|
|
4461
|
+
if (resolvedPath !== ":memory:") {
|
|
4462
|
+
fs10.mkdirSync(path10.dirname(resolvedPath), { recursive: true });
|
|
4463
|
+
}
|
|
4464
|
+
const { DatabaseSync } = require2("node:sqlite");
|
|
4465
|
+
this.db = new DatabaseSync(resolvedPath);
|
|
4466
|
+
this.initTables();
|
|
4467
|
+
} catch {
|
|
4468
|
+
this.isFallback = true;
|
|
4469
|
+
this.loadFallbackDisk();
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
loadFallbackDisk() {
|
|
4473
|
+
if (this.resolvedPath === ":memory:") return;
|
|
4474
|
+
try {
|
|
4475
|
+
if (fs10.existsSync(this.resolvedPath)) {
|
|
4476
|
+
const raw = fs10.readFileSync(this.resolvedPath, "utf8");
|
|
4477
|
+
const data = JSON.parse(raw);
|
|
4478
|
+
if (data && typeof data === "object") {
|
|
4479
|
+
if (data.media && typeof data.media === "object") {
|
|
4480
|
+
for (const [k, v] of Object.entries(data.media)) {
|
|
4481
|
+
this.inMemoryFallback.media.set(k, v);
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
if (data.checkpoints && typeof data.checkpoints === "object") {
|
|
4485
|
+
for (const [k, v] of Object.entries(data.checkpoints)) {
|
|
4486
|
+
this.inMemoryFallback.checkpoints.set(k, v);
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
} catch {
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
flushFallbackDisk() {
|
|
4495
|
+
if (this.resolvedPath === ":memory:") return;
|
|
4496
|
+
try {
|
|
4497
|
+
fs10.mkdirSync(path10.dirname(this.resolvedPath), { recursive: true });
|
|
4498
|
+
const data = {
|
|
4499
|
+
media: Object.fromEntries(this.inMemoryFallback.media),
|
|
4500
|
+
checkpoints: Object.fromEntries(this.inMemoryFallback.checkpoints)
|
|
4501
|
+
};
|
|
4502
|
+
fs10.writeFileSync(this.resolvedPath, JSON.stringify(data, null, 2), "utf8");
|
|
4503
|
+
} catch {
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
initTables() {
|
|
4507
|
+
if (!this.db) return;
|
|
4508
|
+
this.db.exec(`
|
|
4509
|
+
CREATE TABLE IF NOT EXISTS media_ledger (
|
|
4510
|
+
id TEXT PRIMARY KEY,
|
|
4511
|
+
platform TEXT,
|
|
4512
|
+
author TEXT,
|
|
4513
|
+
url TEXT,
|
|
4514
|
+
filename TEXT,
|
|
4515
|
+
file_path TEXT,
|
|
4516
|
+
downloaded_at TEXT
|
|
4517
|
+
);
|
|
4518
|
+
CREATE TABLE IF NOT EXISTS checkpoints (
|
|
4519
|
+
key TEXT PRIMARY KEY,
|
|
4520
|
+
platform TEXT,
|
|
4521
|
+
username TEXT,
|
|
4522
|
+
target_dir TEXT,
|
|
4523
|
+
cursors_json TEXT,
|
|
4524
|
+
updated_at TEXT
|
|
4525
|
+
);
|
|
4526
|
+
`);
|
|
4527
|
+
}
|
|
4528
|
+
hasMedia(id) {
|
|
4529
|
+
if (this.isFallback || !this.db) {
|
|
4530
|
+
return this.inMemoryFallback.media.has(id);
|
|
4531
|
+
}
|
|
4532
|
+
const stmt = this.db.prepare("SELECT 1 FROM media_ledger WHERE id = ?");
|
|
4533
|
+
const row = stmt.get(id);
|
|
4534
|
+
return Boolean(row);
|
|
4535
|
+
}
|
|
4536
|
+
recordMedia(media) {
|
|
4537
|
+
if (this.isFallback || !this.db) {
|
|
4538
|
+
this.inMemoryFallback.media.set(media.id, media);
|
|
4539
|
+
this.flushFallbackDisk();
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4542
|
+
const stmt = this.db.prepare(`
|
|
4543
|
+
INSERT OR REPLACE INTO media_ledger (id, platform, author, url, filename, file_path, downloaded_at)
|
|
4544
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
4545
|
+
`);
|
|
4546
|
+
stmt.run(
|
|
4547
|
+
media.id,
|
|
4548
|
+
media.platform,
|
|
4549
|
+
media.author,
|
|
4550
|
+
media.url,
|
|
4551
|
+
media.filename,
|
|
4552
|
+
media.filePath,
|
|
4553
|
+
media.downloadedAt
|
|
4554
|
+
);
|
|
4555
|
+
}
|
|
4556
|
+
saveCheckpoint(checkpoint) {
|
|
4557
|
+
const key = `${checkpoint.platform}:${checkpoint.username.toLowerCase()}`;
|
|
4558
|
+
if (this.isFallback || !this.db) {
|
|
4559
|
+
this.inMemoryFallback.checkpoints.set(key, checkpoint);
|
|
4560
|
+
this.flushFallbackDisk();
|
|
4561
|
+
return;
|
|
4562
|
+
}
|
|
4563
|
+
const stmt = this.db.prepare(`
|
|
4564
|
+
INSERT OR REPLACE INTO checkpoints (key, platform, username, target_dir, cursors_json, updated_at)
|
|
4565
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
4566
|
+
`);
|
|
4567
|
+
stmt.run(
|
|
4568
|
+
key,
|
|
4569
|
+
checkpoint.platform,
|
|
4570
|
+
checkpoint.username,
|
|
4571
|
+
checkpoint.targetDir,
|
|
4572
|
+
JSON.stringify(checkpoint.cursors),
|
|
4573
|
+
checkpoint.updatedAt
|
|
4574
|
+
);
|
|
4575
|
+
}
|
|
4576
|
+
getCheckpoint(platform, username) {
|
|
4577
|
+
const key = `${platform}:${username.toLowerCase()}`;
|
|
4578
|
+
if (this.isFallback || !this.db) {
|
|
4579
|
+
return this.inMemoryFallback.checkpoints.get(key) || null;
|
|
4580
|
+
}
|
|
4581
|
+
const stmt = this.db.prepare(
|
|
4582
|
+
"SELECT platform, username, target_dir, cursors_json, updated_at FROM checkpoints WHERE key = ?"
|
|
4583
|
+
);
|
|
4584
|
+
const row = stmt.get(key);
|
|
4585
|
+
if (!row) return null;
|
|
4586
|
+
return {
|
|
4587
|
+
platform: row.platform,
|
|
4588
|
+
username: row.username,
|
|
4589
|
+
targetDir: row.target_dir,
|
|
4590
|
+
cursors: JSON.parse(row.cursors_json || "{}"),
|
|
4591
|
+
updatedAt: row.updated_at
|
|
4592
|
+
};
|
|
4593
|
+
}
|
|
4594
|
+
clearCheckpoint(platform, username) {
|
|
4595
|
+
const key = `${platform}:${username.toLowerCase()}`;
|
|
4596
|
+
if (this.isFallback || !this.db) {
|
|
4597
|
+
this.inMemoryFallback.checkpoints.delete(key);
|
|
4598
|
+
this.flushFallbackDisk();
|
|
4599
|
+
return;
|
|
4600
|
+
}
|
|
4601
|
+
const stmt = this.db.prepare("DELETE FROM checkpoints WHERE key = ?");
|
|
4602
|
+
stmt.run(key);
|
|
4603
|
+
}
|
|
4604
|
+
close() {
|
|
4605
|
+
if (this.db) {
|
|
4606
|
+
try {
|
|
4607
|
+
this.db.close();
|
|
4608
|
+
} catch {
|
|
4609
|
+
}
|
|
4610
|
+
this.db = null;
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
};
|
|
4614
|
+
|
|
4615
|
+
// src/lib/known-folders.ts
|
|
4616
|
+
import { execFileSync } from "child_process";
|
|
4617
|
+
import fs11 from "fs";
|
|
4618
|
+
import os6 from "os";
|
|
4619
|
+
import path11 from "path";
|
|
4620
|
+
var KNOWN_FOLDER_DOWNLOADS_GUID = "{374DE290-123F-4565-9164-39C4925E467B}";
|
|
4621
|
+
var USER_SHELL_FOLDERS_REG_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders";
|
|
4622
|
+
function expandWindowsEnv(str, env = process.env, homeDir) {
|
|
4623
|
+
return str.replace(/%([^%]+)%/g, (original, name) => {
|
|
4624
|
+
if (homeDir && name.toLowerCase() === "userprofile") {
|
|
4625
|
+
return homeDir;
|
|
4626
|
+
}
|
|
4627
|
+
const key = Object.keys(env).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
4628
|
+
if (key && env[key] !== void 0) {
|
|
4629
|
+
return env[key];
|
|
4630
|
+
}
|
|
4631
|
+
return original;
|
|
4632
|
+
});
|
|
4633
|
+
}
|
|
4634
|
+
function parseWindowsRegistryOutput(output, targetKey = KNOWN_FOLDER_DOWNLOADS_GUID) {
|
|
4635
|
+
const lines = output.split(/\r?\n/);
|
|
4636
|
+
const escaped = targetKey.replace(/[{}]/g, "\\$&");
|
|
4637
|
+
const pattern = new RegExp(`^\\s*${escaped}\\s+REG_\\w+\\s+(.*)$`, "i");
|
|
4638
|
+
for (const line of lines) {
|
|
4639
|
+
const match = line.match(pattern);
|
|
4640
|
+
if (match && match[1]) {
|
|
4641
|
+
const rawPath = match[1].trim();
|
|
4642
|
+
if (rawPath) return rawPath;
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
return void 0;
|
|
4646
|
+
}
|
|
4647
|
+
function parseLinuxUserDirsOutput(content) {
|
|
4648
|
+
const lines = content.split(/\r?\n/);
|
|
4649
|
+
for (const line of lines) {
|
|
4650
|
+
const trimmed = line.trim();
|
|
4651
|
+
if (trimmed.startsWith("#")) continue;
|
|
4652
|
+
const match = trimmed.match(/^XDG_DOWNLOAD_DIR=["']?([^"']+)["']?$/);
|
|
4653
|
+
if (match && match[1]) {
|
|
4654
|
+
return match[1].trim();
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
return void 0;
|
|
4658
|
+
}
|
|
4659
|
+
function isPathDriveAccessible(filePath, fsExists = fs11.existsSync, platform = process.platform) {
|
|
4660
|
+
try {
|
|
4661
|
+
if (fsExists(filePath)) {
|
|
4662
|
+
return true;
|
|
4663
|
+
}
|
|
4664
|
+
if (platform === "win32") {
|
|
4665
|
+
const match = filePath.match(/^([a-zA-Z]:[\\/])/i);
|
|
4666
|
+
const driveRoot = match ? match[1] : path11.win32.parse(path11.win32.resolve(filePath)).root;
|
|
4667
|
+
return fsExists(driveRoot);
|
|
4668
|
+
}
|
|
4669
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4670
|
+
const mountMatch = normalized.match(/^(\/(?:media(?:\/[^/]+)?|mnt|run\/media(?:\/[^/]+)?)\/[^/]+)/);
|
|
4671
|
+
if (mountMatch) {
|
|
4672
|
+
return fsExists(mountMatch[1]);
|
|
4673
|
+
}
|
|
4674
|
+
let curr = path11.posix.dirname(normalized);
|
|
4675
|
+
while (curr && curr !== "/") {
|
|
4676
|
+
if (fsExists(curr)) return true;
|
|
4677
|
+
curr = path11.posix.dirname(curr);
|
|
4678
|
+
}
|
|
4679
|
+
return fsExists("/");
|
|
4680
|
+
} catch {
|
|
4681
|
+
return false;
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
function resolveWindowsDownloadsDir(options = {}) {
|
|
4685
|
+
const homeDir = options.homeDir ?? os6.homedir();
|
|
4686
|
+
const env = options.env ?? process.env;
|
|
4687
|
+
const runQuery = options.regQueryFn ?? ((key, value) => {
|
|
4688
|
+
try {
|
|
4689
|
+
return execFileSync("reg", ["query", key, "/v", value], {
|
|
4690
|
+
encoding: "utf8",
|
|
4691
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
4692
|
+
});
|
|
4693
|
+
} catch {
|
|
4694
|
+
return "";
|
|
4695
|
+
}
|
|
4696
|
+
});
|
|
4697
|
+
const checkAccessible = options.driveAccessibleFn ?? ((p) => isPathDriveAccessible(p, fs11.existsSync, "win32"));
|
|
4698
|
+
let raw = parseWindowsRegistryOutput(
|
|
4699
|
+
runQuery(USER_SHELL_FOLDERS_REG_KEY, KNOWN_FOLDER_DOWNLOADS_GUID),
|
|
4700
|
+
KNOWN_FOLDER_DOWNLOADS_GUID
|
|
4701
|
+
);
|
|
4702
|
+
if (!raw) {
|
|
4703
|
+
raw = parseWindowsRegistryOutput(runQuery(USER_SHELL_FOLDERS_REG_KEY, "Downloads"), "Downloads");
|
|
4704
|
+
}
|
|
4705
|
+
if (raw) {
|
|
4706
|
+
const expanded = expandWindowsEnv(raw, env, homeDir);
|
|
4707
|
+
if (checkAccessible(expanded)) {
|
|
4708
|
+
return path11.win32.resolve(expanded);
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
return void 0;
|
|
4712
|
+
}
|
|
4713
|
+
function resolveLinuxDownloadsDir(options = {}) {
|
|
4714
|
+
const homeDir = options.homeDir ?? os6.homedir();
|
|
4715
|
+
const env = options.env ?? process.env;
|
|
4716
|
+
const readFn = options.readFileFn ?? ((p) => {
|
|
4717
|
+
try {
|
|
4718
|
+
return fs11.readFileSync(p, "utf8");
|
|
4719
|
+
} catch {
|
|
4720
|
+
return "";
|
|
4721
|
+
}
|
|
4075
4722
|
});
|
|
4723
|
+
const checkAccessible = options.driveAccessibleFn ?? ((p) => isPathDriveAccessible(p, fs11.existsSync, "linux"));
|
|
4724
|
+
const configHome = env.XDG_CONFIG_HOME || path11.join(homeDir, ".config");
|
|
4725
|
+
const userDirsFile = path11.join(configHome, "user-dirs.dirs");
|
|
4726
|
+
const content = readFn(userDirsFile);
|
|
4727
|
+
if (!content) return void 0;
|
|
4728
|
+
const raw = parseLinuxUserDirsOutput(content);
|
|
4729
|
+
if (!raw) return void 0;
|
|
4730
|
+
const expanded = raw.replace(/\$HOME/g, homeDir).replace(/\$\{HOME\}/g, homeDir);
|
|
4731
|
+
if (checkAccessible(expanded)) {
|
|
4732
|
+
return path11.posix.resolve(expanded);
|
|
4733
|
+
}
|
|
4734
|
+
return void 0;
|
|
4735
|
+
}
|
|
4736
|
+
function resolvePlatformDownloadsDir(options = {}) {
|
|
4737
|
+
const platform = options.platform ?? process.platform;
|
|
4738
|
+
const homeDir = options.homeDir ?? os6.homedir();
|
|
4739
|
+
const defaultFallback = path11.join(homeDir, "Downloads");
|
|
4740
|
+
if (platform === "win32") {
|
|
4741
|
+
const resolved = resolveWindowsDownloadsDir(options);
|
|
4742
|
+
if (resolved) return resolved;
|
|
4743
|
+
} else if (platform === "linux") {
|
|
4744
|
+
const resolved = resolveLinuxDownloadsDir(options);
|
|
4745
|
+
if (resolved) return resolved;
|
|
4746
|
+
}
|
|
4747
|
+
return defaultFallback;
|
|
4076
4748
|
}
|
|
4077
4749
|
|
|
4078
|
-
// src/lib/engines/
|
|
4079
|
-
var REFERER_MAP = {
|
|
4080
|
-
tiktok: "https://www.tiktok.com/",
|
|
4081
|
-
instagram: "https://www.instagram.com/",
|
|
4082
|
-
x: "https://x.com/",
|
|
4083
|
-
facebook: "https://www.facebook.com/"
|
|
4084
|
-
};
|
|
4750
|
+
// src/lib/engines/core/task-manager.ts
|
|
4085
4751
|
function expandPath(dir) {
|
|
4086
4752
|
if (dir.startsWith("~/") || dir.startsWith("~\\") || dir === "~") {
|
|
4087
|
-
return
|
|
4753
|
+
return path12.join(os7.homedir(), dir.slice(1));
|
|
4088
4754
|
}
|
|
4089
4755
|
return dir;
|
|
4090
4756
|
}
|
|
4091
4757
|
function resolveDownloadBaseDir(customBaseDir) {
|
|
4092
4758
|
if (customBaseDir && customBaseDir.trim()) {
|
|
4093
|
-
return
|
|
4759
|
+
return path12.resolve(expandPath(customBaseDir.trim()));
|
|
4094
4760
|
}
|
|
4095
4761
|
const envDir = process.env.OPEN_NAMI_DIR || process.env.NAMI_DIR;
|
|
4096
4762
|
if (envDir && envDir.trim()) {
|
|
4097
|
-
return
|
|
4763
|
+
return path12.resolve(expandPath(envDir.trim()));
|
|
4098
4764
|
}
|
|
4099
4765
|
const configDir = loadConfig().downloadDir;
|
|
4100
4766
|
if (configDir && configDir.trim()) {
|
|
4101
|
-
return
|
|
4767
|
+
return path12.resolve(expandPath(configDir.trim()));
|
|
4102
4768
|
}
|
|
4103
4769
|
if (process.env.NAMI_BASE_DIR && process.env.NAMI_BASE_DIR.trim()) {
|
|
4104
4770
|
const raw = process.env.NAMI_BASE_DIR.trim();
|
|
4105
|
-
return
|
|
4771
|
+
return path12.resolve(expandPath(raw.endsWith("downloads") ? raw : path12.join(raw, "downloads")));
|
|
4106
4772
|
}
|
|
4107
|
-
return
|
|
4773
|
+
return path12.join(resolvePlatformDownloadsDir(), "Open Nami");
|
|
4108
4774
|
}
|
|
4109
4775
|
function resolveDefaultDownloadDir(platform, username, baseDir) {
|
|
4110
4776
|
const root = resolveDownloadBaseDir(baseDir);
|
|
4111
|
-
return
|
|
4777
|
+
return path12.join(root, platform, username);
|
|
4112
4778
|
}
|
|
4113
4779
|
async function cleanupEmptyCategoryDirs(targetDir) {
|
|
4114
4780
|
const candidateDirs = ["All", "Stories", "Highlights", "Photos", "Videos"];
|
|
4115
4781
|
for (const dirName of candidateDirs) {
|
|
4116
|
-
const fullPath =
|
|
4782
|
+
const fullPath = path12.join(targetDir, dirName);
|
|
4117
4783
|
try {
|
|
4118
4784
|
if (!fsSync.existsSync(fullPath)) continue;
|
|
4119
|
-
const entries = await
|
|
4785
|
+
const entries = await fs12.readdir(fullPath);
|
|
4120
4786
|
const nonArchiveEntries = entries.filter(
|
|
4121
4787
|
(e) => e !== ".open-nami-archive.json" && e !== "archive.txt" && e !== "archive_yt-dlp.txt" && e !== "archive_gallery-dl.sqlite3"
|
|
4122
4788
|
);
|
|
4123
4789
|
if (nonArchiveEntries.length === 0) {
|
|
4124
4790
|
for (const entry of entries) {
|
|
4125
4791
|
try {
|
|
4126
|
-
await
|
|
4792
|
+
await fs12.unlink(path12.join(fullPath, entry));
|
|
4127
4793
|
} catch {
|
|
4128
4794
|
}
|
|
4129
4795
|
}
|
|
4130
|
-
await
|
|
4796
|
+
await fs12.rmdir(fullPath).catch(() => {
|
|
4131
4797
|
});
|
|
4132
4798
|
}
|
|
4133
4799
|
} catch {
|
|
4134
4800
|
}
|
|
4135
4801
|
}
|
|
4136
4802
|
}
|
|
4803
|
+
var TaskManager = class {
|
|
4804
|
+
events;
|
|
4805
|
+
ledger;
|
|
4806
|
+
plugins;
|
|
4807
|
+
constructor(eventBus, ledger, plugins) {
|
|
4808
|
+
this.events = eventBus || new EngineEventBus();
|
|
4809
|
+
this.ledger = ledger || new MediaLedger();
|
|
4810
|
+
this.plugins = plugins || defaultPluginRegistry;
|
|
4811
|
+
}
|
|
4812
|
+
async execute(options) {
|
|
4813
|
+
const { profile, choice } = options;
|
|
4814
|
+
const targetDir = options.outputDir || resolveDefaultDownloadDir(profile.platform, profile.username);
|
|
4815
|
+
this.events.emit("job:start", { profile, choice, outputDir: targetDir });
|
|
4816
|
+
let downloadedCount = 0;
|
|
4817
|
+
let skippedCount = 0;
|
|
4818
|
+
const errors = [];
|
|
4819
|
+
if (options.runnerOverrides?.nativeRunner) {
|
|
4820
|
+
const subDir = choice === "all" ? "All" : choice.charAt(0).toUpperCase() + choice.slice(1);
|
|
4821
|
+
const runRes = await options.runnerOverrides.nativeRunner(subDir);
|
|
4822
|
+
downloadedCount += runRes.downloaded;
|
|
4823
|
+
skippedCount += runRes.skipped;
|
|
4824
|
+
if (!runRes.success) errors.push(runRes.error || "Execution failed");
|
|
4825
|
+
}
|
|
4826
|
+
await cleanupEmptyCategoryDirs(targetDir);
|
|
4827
|
+
const result = {
|
|
4828
|
+
success: errors.length === 0,
|
|
4829
|
+
downloadedCount,
|
|
4830
|
+
skippedCount,
|
|
4831
|
+
errors,
|
|
4832
|
+
outputDir: targetDir
|
|
4833
|
+
};
|
|
4834
|
+
this.events.emit("job:complete", { result });
|
|
4835
|
+
return result;
|
|
4836
|
+
}
|
|
4837
|
+
};
|
|
4838
|
+
var defaultTaskManager = new TaskManager();
|
|
4839
|
+
|
|
4840
|
+
// src/lib/engines/dispatcher.ts
|
|
4841
|
+
var REFERER_MAP = {
|
|
4842
|
+
tiktok: "https://www.tiktok.com/",
|
|
4843
|
+
instagram: "https://www.instagram.com/",
|
|
4844
|
+
x: "https://x.com/",
|
|
4845
|
+
facebook: "https://www.facebook.com/"
|
|
4846
|
+
};
|
|
4137
4847
|
async function dispatchDownload(options) {
|
|
4138
4848
|
const { profile, choice, onProgress } = options;
|
|
4139
4849
|
const targetDir = options.outputDir || resolveDefaultDownloadDir(profile.platform, profile.username);
|
|
4140
|
-
await
|
|
4850
|
+
await fs13.mkdir(targetDir, { recursive: true });
|
|
4851
|
+
const rawBrowserPref = options.cookiesFromBrowser ?? loadConfig().cookiesFromBrowser ?? "auto";
|
|
4852
|
+
const resolvedBrowserArg = resolveBrowserCookieArg(rawBrowserPref);
|
|
4853
|
+
const cookiesFromBrowser = resolvedBrowserArg ?? void 0;
|
|
4854
|
+
defaultTaskManager.events.emit("job:start", { profile, choice, outputDir: targetDir });
|
|
4141
4855
|
const masterCookie = findCookieFile(profile.platform);
|
|
4142
4856
|
const cookieCopy = masterCookie ? await createSafeCookieCopy(masterCookie) : null;
|
|
4143
4857
|
let totalDownloaded = 0;
|
|
4144
4858
|
let totalSkipped = 0;
|
|
4145
4859
|
const errors = [];
|
|
4860
|
+
const recordError = (err) => {
|
|
4861
|
+
errors.push(err);
|
|
4862
|
+
defaultTaskManager.events.emit("job:error", { error: err });
|
|
4863
|
+
};
|
|
4864
|
+
const handleProgress = (progress) => {
|
|
4865
|
+
defaultTaskManager.events.emit("job:progress", progress);
|
|
4866
|
+
if (progress.currentFile && progress.statusText?.startsWith("Saved:")) {
|
|
4867
|
+
try {
|
|
4868
|
+
defaultTaskManager.ledger.recordMedia({
|
|
4869
|
+
id: `${profile.platform}:${profile.username}:${progress.currentFile}`,
|
|
4870
|
+
platform: profile.platform,
|
|
4871
|
+
author: profile.username,
|
|
4872
|
+
url: profile.cleanUrl || "",
|
|
4873
|
+
filename: progress.currentFile,
|
|
4874
|
+
filePath: path13.join(targetDir, progress.currentFile),
|
|
4875
|
+
downloadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4876
|
+
});
|
|
4877
|
+
} catch {
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
onProgress?.(progress);
|
|
4881
|
+
};
|
|
4146
4882
|
try {
|
|
4147
4883
|
const jobs = [];
|
|
4148
4884
|
if (choice === "photos") {
|
|
@@ -4166,7 +4902,7 @@ async function dispatchDownload(options) {
|
|
|
4166
4902
|
const savedCheckpoint = options.resume ? await loadCheckpoint(targetDir) : null;
|
|
4167
4903
|
const currentCursors = savedCheckpoint?.cursors ? { ...savedCheckpoint.cursors } : {};
|
|
4168
4904
|
const defaultRunNative = async (subDir) => {
|
|
4169
|
-
const dest = subDir === "All" ? targetDir :
|
|
4905
|
+
const dest = subDir === "All" ? targetDir : path13.join(targetDir, subDir);
|
|
4170
4906
|
const remainingLimit = globalLimit !== void 0 ? Math.max(0, globalLimit - totalDownloaded) : void 0;
|
|
4171
4907
|
if (remainingLimit === 0) {
|
|
4172
4908
|
return { success: true, downloaded: 0, skipped: 0 };
|
|
@@ -4208,8 +4944,8 @@ async function dispatchDownload(options) {
|
|
|
4208
4944
|
jobName: `${profile.username} ${subDir}`,
|
|
4209
4945
|
destDir: dest,
|
|
4210
4946
|
destDirMap: subDir === "All" ? {
|
|
4211
|
-
photo:
|
|
4212
|
-
video:
|
|
4947
|
+
photo: path13.join(targetDir, "Photos"),
|
|
4948
|
+
video: path13.join(targetDir, "Videos")
|
|
4213
4949
|
} : void 0,
|
|
4214
4950
|
cookiePath: cookieCopy?.filePath,
|
|
4215
4951
|
referer: REFERER_MAP[profile.platform],
|
|
@@ -4218,7 +4954,7 @@ async function dispatchDownload(options) {
|
|
|
4218
4954
|
writeMetadata: options.writeMetadata,
|
|
4219
4955
|
defaultPlatform: profile.platform,
|
|
4220
4956
|
defaultAuthor: profile.username,
|
|
4221
|
-
onProgress
|
|
4957
|
+
onProgress: handleProgress
|
|
4222
4958
|
});
|
|
4223
4959
|
return {
|
|
4224
4960
|
success: res.errors.length === 0,
|
|
@@ -4232,35 +4968,43 @@ async function dispatchDownload(options) {
|
|
|
4232
4968
|
success: false,
|
|
4233
4969
|
downloaded: 0,
|
|
4234
4970
|
skipped: 0,
|
|
4235
|
-
error: errMsg
|
|
4971
|
+
error: errMsg || "Execution failed"
|
|
4236
4972
|
};
|
|
4237
4973
|
}
|
|
4238
4974
|
};
|
|
4239
4975
|
const runNative = options.runnerOverrides?.nativeRunner ?? defaultRunNative;
|
|
4240
|
-
const runGdl =
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4976
|
+
const runGdl = (subDir) => {
|
|
4977
|
+
const runnerOpts = {
|
|
4978
|
+
profile,
|
|
4979
|
+
targetDir,
|
|
4980
|
+
subDir,
|
|
4981
|
+
cookiePath: cookieCopy?.filePath,
|
|
4982
|
+
cookiesFromBrowser,
|
|
4983
|
+
signal: options.signal,
|
|
4984
|
+
writeMetadata: options.writeMetadata,
|
|
4985
|
+
onProgress: handleProgress
|
|
4986
|
+
};
|
|
4987
|
+
return options.runnerOverrides?.gdlRunner ? options.runnerOverrides.gdlRunner(subDir, runnerOpts) : runGalleryDl(runnerOpts);
|
|
4988
|
+
};
|
|
4989
|
+
const runYt = () => {
|
|
4990
|
+
const runnerOpts = {
|
|
4991
|
+
profile,
|
|
4992
|
+
targetDir,
|
|
4993
|
+
cookiePath: cookieCopy?.filePath,
|
|
4994
|
+
cookiesFromBrowser,
|
|
4995
|
+
signal: options.signal,
|
|
4996
|
+
writeMetadata: options.writeMetadata,
|
|
4997
|
+
onProgress: handleProgress
|
|
4998
|
+
};
|
|
4999
|
+
return options.runnerOverrides?.ytRunner ? options.runnerOverrides.ytRunner(runnerOpts) : runYtDlp(runnerOpts);
|
|
5000
|
+
};
|
|
4257
5001
|
for (const job of jobs) {
|
|
4258
5002
|
if (options.signal?.aborted) break;
|
|
4259
5003
|
if (globalLimit !== void 0 && totalDownloaded >= globalLimit) break;
|
|
4260
5004
|
if (job.type === "all") {
|
|
4261
5005
|
let res = await runNative("All");
|
|
4262
5006
|
if ((!res.success || res.downloaded === 0 && res.skipped === 0) && !options.signal?.aborted) {
|
|
4263
|
-
|
|
5007
|
+
handleProgress({
|
|
4264
5008
|
job: "Photos",
|
|
4265
5009
|
downloadedCount: totalDownloaded,
|
|
4266
5010
|
skippedCount: totalSkipped,
|
|
@@ -4269,8 +5013,8 @@ async function dispatchDownload(options) {
|
|
|
4269
5013
|
const gdlPhotos = await runGdl("Photos");
|
|
4270
5014
|
totalDownloaded += gdlPhotos.downloaded;
|
|
4271
5015
|
totalSkipped += gdlPhotos.skipped;
|
|
4272
|
-
if (!gdlPhotos.success
|
|
4273
|
-
|
|
5016
|
+
if (!gdlPhotos.success) recordError(gdlPhotos.error ? `[Photos] ${gdlPhotos.error}` : "[Photos] Execution failed");
|
|
5017
|
+
handleProgress({
|
|
4274
5018
|
job: "Videos",
|
|
4275
5019
|
downloadedCount: totalDownloaded,
|
|
4276
5020
|
skippedCount: totalSkipped,
|
|
@@ -4280,9 +5024,9 @@ async function dispatchDownload(options) {
|
|
|
4280
5024
|
if (ytVideos.downloaded > 0 || ytVideos.skipped > 0 || ytVideos.success) {
|
|
4281
5025
|
totalDownloaded += ytVideos.downloaded;
|
|
4282
5026
|
totalSkipped += ytVideos.skipped;
|
|
4283
|
-
if (!ytVideos.success
|
|
5027
|
+
if (!ytVideos.success) recordError(ytVideos.error ? `[Videos] ${ytVideos.error}` : "[Videos] Execution failed");
|
|
4284
5028
|
} else if (!options.signal?.aborted) {
|
|
4285
|
-
|
|
5029
|
+
handleProgress({
|
|
4286
5030
|
job: "Videos",
|
|
4287
5031
|
downloadedCount: totalDownloaded,
|
|
4288
5032
|
skippedCount: totalSkipped,
|
|
@@ -4291,19 +5035,19 @@ async function dispatchDownload(options) {
|
|
|
4291
5035
|
const gdlVideos = await runGdl("Videos");
|
|
4292
5036
|
totalDownloaded += gdlVideos.downloaded;
|
|
4293
5037
|
totalSkipped += gdlVideos.skipped;
|
|
4294
|
-
if (!gdlVideos.success
|
|
5038
|
+
if (!gdlVideos.success) recordError(gdlVideos.error ? `[Videos] ${gdlVideos.error}` : "[Videos] Execution failed");
|
|
4295
5039
|
}
|
|
4296
5040
|
} else {
|
|
4297
5041
|
totalDownloaded += res.downloaded;
|
|
4298
5042
|
totalSkipped += res.skipped;
|
|
4299
|
-
if (!res.success
|
|
5043
|
+
if (!res.success) recordError(res.error ? `[All] ${res.error}` : "[All] Execution failed");
|
|
4300
5044
|
}
|
|
4301
5045
|
} else if (job.type === "photos" || job.type === "stories" || job.type === "highlights") {
|
|
4302
5046
|
const subDir = job.type === "photos" ? "Photos" : job.type === "stories" ? "Stories" : "Highlights";
|
|
4303
5047
|
let res = await runNative(subDir);
|
|
4304
5048
|
const isAuthError = Boolean(res.error && (res.error.includes("cookie") || res.error.includes("authenticated session") || res.error.includes("Settings (^s)")));
|
|
4305
5049
|
if (!isAuthError && (!res.success || res.downloaded === 0 && res.skipped === 0) && !options.signal?.aborted) {
|
|
4306
|
-
|
|
5050
|
+
handleProgress({
|
|
4307
5051
|
job: subDir,
|
|
4308
5052
|
downloadedCount: totalDownloaded,
|
|
4309
5053
|
skippedCount: totalSkipped,
|
|
@@ -4316,14 +5060,14 @@ async function dispatchDownload(options) {
|
|
|
4316
5060
|
}
|
|
4317
5061
|
totalDownloaded += res.downloaded;
|
|
4318
5062
|
totalSkipped += res.skipped;
|
|
4319
|
-
if (!res.success
|
|
5063
|
+
if (!res.success) recordError(res.error ? `[${subDir}] ${res.error}` : `[${subDir}] Execution failed`);
|
|
4320
5064
|
} else if (job.type === "videos") {
|
|
4321
5065
|
let res = await runNative("Videos");
|
|
4322
5066
|
if (res.downloaded > 0 || res.skipped > 0) {
|
|
4323
5067
|
totalDownloaded += res.downloaded;
|
|
4324
5068
|
totalSkipped += res.skipped;
|
|
4325
5069
|
} else if (!options.signal?.aborted) {
|
|
4326
|
-
|
|
5070
|
+
handleProgress({
|
|
4327
5071
|
job: "Videos",
|
|
4328
5072
|
downloadedCount: totalDownloaded,
|
|
4329
5073
|
skippedCount: totalSkipped,
|
|
@@ -4333,9 +5077,9 @@ async function dispatchDownload(options) {
|
|
|
4333
5077
|
if (fbRes.downloaded > 0 || fbRes.skipped > 0 || fbRes.success) {
|
|
4334
5078
|
totalDownloaded += fbRes.downloaded;
|
|
4335
5079
|
totalSkipped += fbRes.skipped;
|
|
4336
|
-
if (!fbRes.success
|
|
5080
|
+
if (!fbRes.success) recordError(fbRes.error ? `[Videos] ${fbRes.error}` : "[Videos] Execution failed");
|
|
4337
5081
|
} else if (!options.signal?.aborted) {
|
|
4338
|
-
|
|
5082
|
+
handleProgress({
|
|
4339
5083
|
job: "Videos",
|
|
4340
5084
|
downloadedCount: totalDownloaded,
|
|
4341
5085
|
skippedCount: totalSkipped,
|
|
@@ -4344,7 +5088,7 @@ async function dispatchDownload(options) {
|
|
|
4344
5088
|
const gdlRes = await runGdl("Videos");
|
|
4345
5089
|
totalDownloaded += gdlRes.downloaded;
|
|
4346
5090
|
totalSkipped += gdlRes.skipped;
|
|
4347
|
-
if (!gdlRes.success
|
|
5091
|
+
if (!gdlRes.success) recordError(gdlRes.error ? `[Videos] ${gdlRes.error}` : "[Videos] Execution failed");
|
|
4348
5092
|
}
|
|
4349
5093
|
}
|
|
4350
5094
|
}
|
|
@@ -4353,13 +5097,19 @@ async function dispatchDownload(options) {
|
|
|
4353
5097
|
if (errors.length === 0 && !options.signal?.aborted) {
|
|
4354
5098
|
await clearCheckpoint(targetDir);
|
|
4355
5099
|
}
|
|
4356
|
-
|
|
5100
|
+
const engineResult = {
|
|
4357
5101
|
success: errors.length === 0 || totalDownloaded > 0,
|
|
4358
5102
|
downloadedCount: totalDownloaded,
|
|
4359
5103
|
skippedCount: totalSkipped,
|
|
4360
5104
|
errors,
|
|
4361
5105
|
outputDir: targetDir
|
|
4362
5106
|
};
|
|
5107
|
+
defaultTaskManager.events.emit("job:complete", { result: engineResult });
|
|
5108
|
+
return engineResult;
|
|
5109
|
+
} catch (err) {
|
|
5110
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
5111
|
+
defaultTaskManager.events.emit("job:error", { error: errMsg || "Execution failed" });
|
|
5112
|
+
throw err;
|
|
4363
5113
|
} finally {
|
|
4364
5114
|
if (cookieCopy) {
|
|
4365
5115
|
await cookieCopy.cleanup();
|
|
@@ -4559,7 +5309,7 @@ function ProbingView({
|
|
|
4559
5309
|
|
|
4560
5310
|
// src/components/views/downloading-view.tsx
|
|
4561
5311
|
import { useState as useState5, useEffect as useEffect5 } from "react";
|
|
4562
|
-
import
|
|
5312
|
+
import os8 from "os";
|
|
4563
5313
|
import { Box as Box9, Text as Text11 } from "ink";
|
|
4564
5314
|
import Spinner from "ink-spinner";
|
|
4565
5315
|
|
|
@@ -4695,35 +5445,51 @@ function DownloadingView({
|
|
|
4695
5445
|
activeBadges.length > 0 ? /* @__PURE__ */ jsx12(Text11, { color: theme.gray, dimColor: theme.dimSecondary, children: activeBadges.join(" \xB7 ") }) : null,
|
|
4696
5446
|
outputDir ? /* @__PURE__ */ jsxs9(Text11, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
4697
5447
|
"saving to ",
|
|
4698
|
-
shortenPath(outputDir,
|
|
5448
|
+
shortenPath(outputDir, os8.homedir(), 54)
|
|
4699
5449
|
] }) : null
|
|
4700
5450
|
] });
|
|
4701
5451
|
}
|
|
4702
5452
|
|
|
4703
5453
|
// src/components/views/completion-view.tsx
|
|
4704
|
-
import
|
|
5454
|
+
import os9 from "os";
|
|
4705
5455
|
import { Box as Box10, Text as Text12 } from "ink";
|
|
4706
|
-
import {
|
|
5456
|
+
import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
4707
5457
|
var DONE_LABEL = "\u21B5 download another";
|
|
5458
|
+
var LEDGER_INDEXED_LABEL = "\u2713 indexed in media ledger";
|
|
5459
|
+
function shouldShowLedgerBadge(result, showLedgerBadge) {
|
|
5460
|
+
return showLedgerBadge ?? result.downloadedCount > 0;
|
|
5461
|
+
}
|
|
5462
|
+
function formatCompletionSummary(result) {
|
|
5463
|
+
const isZero = result.downloadedCount === 0 && result.skippedCount === 0;
|
|
5464
|
+
const plural = result.downloadedCount === 1 ? "" : "s";
|
|
5465
|
+
const skipText = result.skippedCount > 0 ? ` (${result.skippedCount} archived)` : "";
|
|
5466
|
+
return {
|
|
5467
|
+
isZero,
|
|
5468
|
+
headerPrefix: isZero ? "\u2713 profile scanned " : "\u2713 profile downloaded! ",
|
|
5469
|
+
headerSuffix: isZero ? ": 0 media items found" : "saved to:",
|
|
5470
|
+
itemsText: isZero ? "no photos or videos available" : `${result.downloadedCount} media item${plural} downloaded${skipText}`
|
|
5471
|
+
};
|
|
5472
|
+
}
|
|
5473
|
+
function LedgerBadge({ theme }) {
|
|
5474
|
+
return /* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: LEDGER_INDEXED_LABEL });
|
|
5475
|
+
}
|
|
4708
5476
|
function CompletionView({
|
|
4709
5477
|
profile,
|
|
4710
5478
|
result,
|
|
4711
5479
|
theme,
|
|
4712
|
-
gapComponent: Gap2
|
|
5480
|
+
gapComponent: Gap2,
|
|
5481
|
+
showLedgerBadge
|
|
4713
5482
|
}) {
|
|
4714
|
-
const
|
|
4715
|
-
const
|
|
4716
|
-
const skipText = result.skippedCount > 0 ? ` (${result.skippedCount} archived)` : "";
|
|
5483
|
+
const { headerPrefix, headerSuffix, itemsText } = formatCompletionSummary(result);
|
|
5484
|
+
const displayLedger = shouldShowLedgerBadge(result, showLedgerBadge);
|
|
4717
5485
|
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", alignItems: "center", children: [
|
|
4718
|
-
/* @__PURE__ */
|
|
4719
|
-
/* @__PURE__ */ jsx13(Text12, { bold: true, color: theme.primary, children:
|
|
4720
|
-
/* @__PURE__ */ jsx13(Text12, { color: theme.primary, children:
|
|
4721
|
-
] })
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
/* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: shortenPath(result.outputDir, os7.homedir(), 60) }),
|
|
4726
|
-
/* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: isZero ? "no photos or videos available" : `${result.downloadedCount} media item${plural} downloaded${skipText}` }),
|
|
5486
|
+
/* @__PURE__ */ jsxs10(Text12, { children: [
|
|
5487
|
+
/* @__PURE__ */ jsx13(Text12, { bold: true, color: theme.primary, children: headerPrefix }),
|
|
5488
|
+
/* @__PURE__ */ jsx13(Text12, { color: theme.primary, children: headerSuffix })
|
|
5489
|
+
] }),
|
|
5490
|
+
/* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: shortenPath(result.outputDir, os9.homedir(), 60) }),
|
|
5491
|
+
/* @__PURE__ */ jsx13(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: itemsText }),
|
|
5492
|
+
displayLedger && /* @__PURE__ */ jsx13(LedgerBadge, { theme }),
|
|
4727
5493
|
/* @__PURE__ */ jsx13(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text12, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
4728
5494
|
/* @__PURE__ */ jsx13(Text12, { bold: true, color: theme.primary, children: "[o]" }),
|
|
4729
5495
|
" reveal in folder"
|
|
@@ -4765,18 +5531,220 @@ function ErrorView({ message, hint, columns, theme }) {
|
|
|
4765
5531
|
}
|
|
4766
5532
|
|
|
4767
5533
|
// src/components/views/settings-view.tsx
|
|
4768
|
-
import { useState as useState6, useMemo as useMemo2, useCallback } from "react";
|
|
5534
|
+
import { useState as useState6, useMemo as useMemo2, useCallback, useEffect as useEffect6 } from "react";
|
|
4769
5535
|
import { Box as Box12, Text as Text14, useInput as useInput2 } from "ink";
|
|
4770
|
-
import
|
|
4771
|
-
import
|
|
4772
|
-
import
|
|
4773
|
-
|
|
5536
|
+
import fs15 from "fs";
|
|
5537
|
+
import path15 from "path";
|
|
5538
|
+
import os11 from "os";
|
|
5539
|
+
|
|
5540
|
+
// src/lib/diagnostics/doctor.ts
|
|
5541
|
+
import { execFile } from "child_process";
|
|
5542
|
+
import { promisify } from "util";
|
|
5543
|
+
import os10 from "os";
|
|
5544
|
+
import fs14 from "fs";
|
|
5545
|
+
import path14 from "path";
|
|
5546
|
+
var execFileAsync = promisify(execFile);
|
|
5547
|
+
async function defaultCheckBinary(bin) {
|
|
5548
|
+
const versionFlag = bin === "ffmpeg" ? "-version" : "--version";
|
|
5549
|
+
try {
|
|
5550
|
+
await execFileAsync(bin, [versionFlag], { timeout: 3e3 });
|
|
5551
|
+
return true;
|
|
5552
|
+
} catch {
|
|
5553
|
+
if (bin === "gallery-dl" || bin === "yt-dlp") {
|
|
5554
|
+
try {
|
|
5555
|
+
const resolved = resolveEngineCommand(bin);
|
|
5556
|
+
await execFileAsync(resolved.command, [...resolved.baseArgs, versionFlag], { timeout: 3e3 });
|
|
5557
|
+
return true;
|
|
5558
|
+
} catch {
|
|
5559
|
+
return false;
|
|
5560
|
+
}
|
|
5561
|
+
}
|
|
5562
|
+
return false;
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
async function runDoctor(options = {}) {
|
|
5566
|
+
const check = options.binaryChecker ?? defaultCheckBinary;
|
|
5567
|
+
const [gdl, ytdlp, ffmpeg] = await Promise.all([
|
|
5568
|
+
check("gallery-dl"),
|
|
5569
|
+
check("yt-dlp"),
|
|
5570
|
+
check("ffmpeg")
|
|
5571
|
+
]);
|
|
5572
|
+
const availableCookies = detectAvailableCookies(options.cookiesDir);
|
|
5573
|
+
const cookieStatus = {
|
|
5574
|
+
instagram: availableCookies.includes("instagram"),
|
|
5575
|
+
tiktok: availableCookies.includes("tiktok"),
|
|
5576
|
+
facebook: availableCookies.includes("facebook"),
|
|
5577
|
+
x: availableCookies.includes("x")
|
|
5578
|
+
};
|
|
5579
|
+
let storageOk = false;
|
|
5580
|
+
try {
|
|
5581
|
+
const testDir = options.storageDir || path14.join(os10.homedir(), ".open-nami");
|
|
5582
|
+
fs14.mkdirSync(testDir, { recursive: true });
|
|
5583
|
+
const testFile = path14.join(testDir, `.doctor_test_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
|
5584
|
+
fs14.writeFileSync(testFile, "ok");
|
|
5585
|
+
fs14.unlinkSync(testFile);
|
|
5586
|
+
storageOk = true;
|
|
5587
|
+
} catch {
|
|
5588
|
+
}
|
|
5589
|
+
return {
|
|
5590
|
+
nodeVersion: process.version,
|
|
5591
|
+
binaries: {
|
|
5592
|
+
"gallery-dl": gdl,
|
|
5593
|
+
"yt-dlp": ytdlp,
|
|
5594
|
+
ffmpeg
|
|
5595
|
+
},
|
|
5596
|
+
cookies: cookieStatus,
|
|
5597
|
+
storageOk
|
|
5598
|
+
};
|
|
5599
|
+
}
|
|
5600
|
+
|
|
5601
|
+
// src/components/views/settings-view.tsx
|
|
5602
|
+
import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
4774
5603
|
function expandDir(dir) {
|
|
4775
5604
|
if (dir.startsWith("~/") || dir.startsWith("~\\") || dir === "~") {
|
|
4776
|
-
return
|
|
5605
|
+
return path15.join(os11.homedir(), dir.slice(1));
|
|
4777
5606
|
}
|
|
4778
5607
|
return dir;
|
|
4779
5608
|
}
|
|
5609
|
+
function resolveBrowserOptions(detected) {
|
|
5610
|
+
const available = detected.filter((b) => b.available !== false);
|
|
5611
|
+
return ["auto", "none", ...available.map((b) => b.id)];
|
|
5612
|
+
}
|
|
5613
|
+
function formatBrowserFeedback(cookiesFromBrowser, detectedBrowsers = [], defaultBrowser) {
|
|
5614
|
+
if (cookiesFromBrowser === "auto") {
|
|
5615
|
+
if (defaultBrowser) {
|
|
5616
|
+
return `system default: ${defaultBrowser.name} (auto-extract session cookies)`;
|
|
5617
|
+
}
|
|
5618
|
+
return "system default: none detected (falls back to cookie files)";
|
|
5619
|
+
}
|
|
5620
|
+
if (!cookiesFromBrowser || cookiesFromBrowser === "none") {
|
|
5621
|
+
return "manual cookie files only";
|
|
5622
|
+
}
|
|
5623
|
+
const found = detectedBrowsers.find((b) => b.id === cookiesFromBrowser);
|
|
5624
|
+
const browserName = found ? found.name : cookiesFromBrowser;
|
|
5625
|
+
return `live session cookies from ${browserName}`;
|
|
5626
|
+
}
|
|
5627
|
+
function cycleBrowserOption(current, options, forward = true) {
|
|
5628
|
+
if (options.length === 0) return "auto";
|
|
5629
|
+
const idx = options.indexOf(current);
|
|
5630
|
+
const currentIndex = idx === -1 ? 0 : idx;
|
|
5631
|
+
const nextIdx = forward ? (currentIndex + 1) % options.length : (currentIndex - 1 + options.length) % options.length;
|
|
5632
|
+
return options[nextIdx];
|
|
5633
|
+
}
|
|
5634
|
+
function formatDoctorStorage(storageOk) {
|
|
5635
|
+
return {
|
|
5636
|
+
ok: storageOk,
|
|
5637
|
+
text: storageOk ? "\u2713 SQLite ready" : "\u2717 SQLite unavailable"
|
|
5638
|
+
};
|
|
5639
|
+
}
|
|
5640
|
+
function formatDoctorCookies(cookies) {
|
|
5641
|
+
const platforms = Object.entries(cookies).filter(([_, ok]) => ok).map(([name]) => name);
|
|
5642
|
+
const count = platforms.length;
|
|
5643
|
+
const text = count > 0 ? `\u2713 ${count} (${platforms.join(", ")})` : "none detected";
|
|
5644
|
+
return { count, platforms, text };
|
|
5645
|
+
}
|
|
5646
|
+
function DoctorCard({ boxWidth, theme, report }) {
|
|
5647
|
+
const cookieSummary = report ? formatDoctorCookies(report.cookies) : { count: 0, platforms: [], text: "" };
|
|
5648
|
+
const storageSummary = report ? formatDoctorStorage(report.storageOk) : { ok: false, text: "" };
|
|
5649
|
+
return /* @__PURE__ */ jsxs12(
|
|
5650
|
+
Box12,
|
|
5651
|
+
{
|
|
5652
|
+
width: boxWidth,
|
|
5653
|
+
borderStyle: "round",
|
|
5654
|
+
borderColor: theme.gray,
|
|
5655
|
+
borderDimColor: theme.dimSecondary,
|
|
5656
|
+
paddingX: 2,
|
|
5657
|
+
flexDirection: "column",
|
|
5658
|
+
children: [
|
|
5659
|
+
/* @__PURE__ */ jsxs12(Box12, { justifyContent: "space-between", children: [
|
|
5660
|
+
/* @__PURE__ */ jsx15(Text14, { color: theme.primary, bold: true, children: "System Diagnostics" }),
|
|
5661
|
+
/* @__PURE__ */ jsx15(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: report ? report.nodeVersion : "checking\u2026" })
|
|
5662
|
+
] }),
|
|
5663
|
+
report ? /* @__PURE__ */ jsxs12(Fragment2, { children: [
|
|
5664
|
+
/* @__PURE__ */ jsxs12(Box12, { marginTop: 0, children: [
|
|
5665
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5666
|
+
"Engines:",
|
|
5667
|
+
" "
|
|
5668
|
+
] }),
|
|
5669
|
+
/* @__PURE__ */ jsxs12(
|
|
5670
|
+
Text14,
|
|
5671
|
+
{
|
|
5672
|
+
color: report.binaries["gallery-dl"] ? "green" : "red",
|
|
5673
|
+
dimColor: !report.binaries["gallery-dl"] && theme.dimSecondary,
|
|
5674
|
+
children: [
|
|
5675
|
+
report.binaries["gallery-dl"] ? "\u2713" : "\u2717",
|
|
5676
|
+
" gallery-dl"
|
|
5677
|
+
]
|
|
5678
|
+
}
|
|
5679
|
+
),
|
|
5680
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5681
|
+
" ",
|
|
5682
|
+
"\xB7",
|
|
5683
|
+
" "
|
|
5684
|
+
] }),
|
|
5685
|
+
/* @__PURE__ */ jsxs12(
|
|
5686
|
+
Text14,
|
|
5687
|
+
{
|
|
5688
|
+
color: report.binaries["yt-dlp"] ? "green" : "red",
|
|
5689
|
+
dimColor: !report.binaries["yt-dlp"] && theme.dimSecondary,
|
|
5690
|
+
children: [
|
|
5691
|
+
report.binaries["yt-dlp"] ? "\u2713" : "\u2717",
|
|
5692
|
+
" yt-dlp"
|
|
5693
|
+
]
|
|
5694
|
+
}
|
|
5695
|
+
),
|
|
5696
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5697
|
+
" ",
|
|
5698
|
+
"\xB7",
|
|
5699
|
+
" "
|
|
5700
|
+
] }),
|
|
5701
|
+
/* @__PURE__ */ jsxs12(
|
|
5702
|
+
Text14,
|
|
5703
|
+
{
|
|
5704
|
+
color: report.binaries.ffmpeg ? "green" : "red",
|
|
5705
|
+
dimColor: !report.binaries.ffmpeg && theme.dimSecondary,
|
|
5706
|
+
children: [
|
|
5707
|
+
report.binaries.ffmpeg ? "\u2713" : "\u2717",
|
|
5708
|
+
" ffmpeg"
|
|
5709
|
+
]
|
|
5710
|
+
}
|
|
5711
|
+
)
|
|
5712
|
+
] }),
|
|
5713
|
+
/* @__PURE__ */ jsxs12(Box12, { justifyContent: "space-between", marginTop: 0, children: [
|
|
5714
|
+
/* @__PURE__ */ jsxs12(Box12, { children: [
|
|
5715
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5716
|
+
"Storage:",
|
|
5717
|
+
" "
|
|
5718
|
+
] }),
|
|
5719
|
+
/* @__PURE__ */ jsx15(
|
|
5720
|
+
Text14,
|
|
5721
|
+
{
|
|
5722
|
+
color: storageSummary.ok ? "green" : "red",
|
|
5723
|
+
dimColor: !storageSummary.ok && theme.dimSecondary,
|
|
5724
|
+
children: storageSummary.text
|
|
5725
|
+
}
|
|
5726
|
+
)
|
|
5727
|
+
] }),
|
|
5728
|
+
/* @__PURE__ */ jsxs12(Box12, { children: [
|
|
5729
|
+
/* @__PURE__ */ jsxs12(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: [
|
|
5730
|
+
"Cookies:",
|
|
5731
|
+
" "
|
|
5732
|
+
] }),
|
|
5733
|
+
/* @__PURE__ */ jsx15(
|
|
5734
|
+
Text14,
|
|
5735
|
+
{
|
|
5736
|
+
color: cookieSummary.count > 0 ? theme.primary : theme.gray,
|
|
5737
|
+
dimColor: cookieSummary.count === 0 && theme.dimSecondary,
|
|
5738
|
+
children: cookieSummary.text
|
|
5739
|
+
}
|
|
5740
|
+
)
|
|
5741
|
+
] })
|
|
5742
|
+
] })
|
|
5743
|
+
] }) : /* @__PURE__ */ jsx15(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: "checking system engines and storage\u2026" })
|
|
5744
|
+
]
|
|
5745
|
+
}
|
|
5746
|
+
);
|
|
5747
|
+
}
|
|
4780
5748
|
var LIMIT_OPTIONS = [void 0, 10, 25, 50, 100];
|
|
4781
5749
|
function SettingsView({
|
|
4782
5750
|
boxWidth,
|
|
@@ -4785,9 +5753,14 @@ function SettingsView({
|
|
|
4785
5753
|
focusedField: controlledFocus,
|
|
4786
5754
|
onFocusChange,
|
|
4787
5755
|
onSave,
|
|
4788
|
-
onCancel
|
|
5756
|
+
onCancel,
|
|
5757
|
+
doctorChecker,
|
|
5758
|
+
initialReport,
|
|
5759
|
+
browsers: browsersProp,
|
|
5760
|
+
defaultBrowser: defaultBrowserProp,
|
|
5761
|
+
initialConfig: initialConfigProp
|
|
4789
5762
|
}) {
|
|
4790
|
-
const initialConfig = useMemo2(() => loadConfig(), []);
|
|
5763
|
+
const initialConfig = useMemo2(() => initialConfigProp ?? loadConfig(), [initialConfigProp]);
|
|
4791
5764
|
const [internalFocus, setInternalFocus] = useState6("cookies");
|
|
4792
5765
|
const focusedField = controlledFocus ?? internalFocus;
|
|
4793
5766
|
const setField = useCallback(
|
|
@@ -4797,16 +5770,64 @@ function SettingsView({
|
|
|
4797
5770
|
},
|
|
4798
5771
|
[onFocusChange]
|
|
4799
5772
|
);
|
|
5773
|
+
const defaultBrowser = useMemo2(() => {
|
|
5774
|
+
return defaultBrowserProp !== void 0 ? defaultBrowserProp : detectSystemDefaultBrowser();
|
|
5775
|
+
}, [defaultBrowserProp]);
|
|
5776
|
+
const detectedBrowsers = useMemo2(() => {
|
|
5777
|
+
return browsersProp ?? listDetectedBrowsers();
|
|
5778
|
+
}, [browsersProp]);
|
|
5779
|
+
const browserOptions = useMemo2(() => {
|
|
5780
|
+
return resolveBrowserOptions(detectedBrowsers);
|
|
5781
|
+
}, [detectedBrowsers]);
|
|
4800
5782
|
const [cookiesDir, setCookiesDir] = useState6(initialConfig.cookiesDir ?? "");
|
|
5783
|
+
const [cookiesFromBrowser, setCookiesFromBrowser] = useState6(
|
|
5784
|
+
initialConfig.cookiesFromBrowser ?? "auto"
|
|
5785
|
+
);
|
|
4801
5786
|
const [downloadDir, setDownloadDir] = useState6(initialConfig.downloadDir ?? "");
|
|
4802
5787
|
const [writeMetadata, setWriteMetadata] = useState6(Boolean(initialConfig.writeMetadata));
|
|
4803
5788
|
const [defaultLimit, setDefaultLimit] = useState6(initialConfig.defaultLimit);
|
|
4804
5789
|
const [saved, setSaved] = useState6(false);
|
|
5790
|
+
const [doctorReport, setDoctorReport] = useState6(initialReport ?? null);
|
|
5791
|
+
useEffect6(() => {
|
|
5792
|
+
let cancelled = false;
|
|
5793
|
+
const fetchReport = async () => {
|
|
5794
|
+
try {
|
|
5795
|
+
const options = {
|
|
5796
|
+
cookiesDir: cookiesDir.trim() ? expandDir(cookiesDir.trim()) : void 0
|
|
5797
|
+
};
|
|
5798
|
+
let report;
|
|
5799
|
+
if (doctorChecker) {
|
|
5800
|
+
const res = await doctorChecker(options);
|
|
5801
|
+
if (res && typeof res === "object" && "binaries" in res) {
|
|
5802
|
+
report = res;
|
|
5803
|
+
} else {
|
|
5804
|
+
report = await runDoctor({
|
|
5805
|
+
...options,
|
|
5806
|
+
binaryChecker: doctorChecker
|
|
5807
|
+
});
|
|
5808
|
+
}
|
|
5809
|
+
} else {
|
|
5810
|
+
report = await runDoctor(options);
|
|
5811
|
+
}
|
|
5812
|
+
if (!cancelled) {
|
|
5813
|
+
setDoctorReport(report);
|
|
5814
|
+
}
|
|
5815
|
+
} catch {
|
|
5816
|
+
}
|
|
5817
|
+
};
|
|
5818
|
+
if (initialReport === void 0 || doctorChecker !== void 0) {
|
|
5819
|
+
fetchReport();
|
|
5820
|
+
}
|
|
5821
|
+
return () => {
|
|
5822
|
+
cancelled = true;
|
|
5823
|
+
};
|
|
5824
|
+
}, [cookiesDir, doctorChecker, initialReport]);
|
|
4805
5825
|
const handleSave = useCallback(() => {
|
|
4806
5826
|
const existing = loadConfig();
|
|
4807
5827
|
const nextConfig = {
|
|
4808
5828
|
...existing,
|
|
4809
5829
|
cookiesDir: cookiesDir.trim() ? cookiesDir.trim() : void 0,
|
|
5830
|
+
cookiesFromBrowser,
|
|
4810
5831
|
downloadDir: downloadDir.trim() ? downloadDir.trim() : void 0,
|
|
4811
5832
|
writeMetadata,
|
|
4812
5833
|
defaultLimit
|
|
@@ -4814,7 +5835,10 @@ function SettingsView({
|
|
|
4814
5835
|
saveConfig(nextConfig);
|
|
4815
5836
|
setSaved(true);
|
|
4816
5837
|
onSave();
|
|
4817
|
-
}, [cookiesDir, downloadDir, writeMetadata, defaultLimit, onSave]);
|
|
5838
|
+
}, [cookiesDir, cookiesFromBrowser, downloadDir, writeMetadata, defaultLimit, onSave]);
|
|
5839
|
+
const browserFeedback = useMemo2(() => {
|
|
5840
|
+
return formatBrowserFeedback(cookiesFromBrowser, detectedBrowsers, defaultBrowser);
|
|
5841
|
+
}, [cookiesFromBrowser, detectedBrowsers, defaultBrowser]);
|
|
4818
5842
|
const cookieFeedback = useMemo2(() => {
|
|
4819
5843
|
const dir = cookiesDir.trim();
|
|
4820
5844
|
if (!dir) {
|
|
@@ -4825,7 +5849,7 @@ function SettingsView({
|
|
|
4825
5849
|
};
|
|
4826
5850
|
}
|
|
4827
5851
|
const expanded = expandDir(dir);
|
|
4828
|
-
if (!
|
|
5852
|
+
if (!fs15.existsSync(expanded)) {
|
|
4829
5853
|
return {
|
|
4830
5854
|
ok: false,
|
|
4831
5855
|
text: "directory does not exist yet"
|
|
@@ -4848,14 +5872,20 @@ function SettingsView({
|
|
|
4848
5872
|
if (!dir) {
|
|
4849
5873
|
return "default: platform Downloads / Open Nami";
|
|
4850
5874
|
}
|
|
4851
|
-
return `target: ${
|
|
5875
|
+
return `target: ${path15.resolve(expandDir(dir))}`;
|
|
4852
5876
|
}, [downloadDir]);
|
|
4853
5877
|
const cycleFocus = useCallback((forward = true) => {
|
|
4854
|
-
const fields = ["cookies", "download", "metadata", "limit"];
|
|
5878
|
+
const fields = ["cookies", "browser", "download", "metadata", "limit"];
|
|
4855
5879
|
const idx = fields.indexOf(focusedField);
|
|
4856
5880
|
const nextIdx = forward ? (idx + 1) % fields.length : (idx - 1 + fields.length) % fields.length;
|
|
4857
5881
|
setField(fields[nextIdx]);
|
|
4858
5882
|
}, [focusedField, setField]);
|
|
5883
|
+
const cycleBrowser = useCallback((forward = true) => {
|
|
5884
|
+
setCookiesFromBrowser((prev) => {
|
|
5885
|
+
const next = cycleBrowserOption(prev ?? "auto", browserOptions, forward);
|
|
5886
|
+
return next;
|
|
5887
|
+
});
|
|
5888
|
+
}, [browserOptions]);
|
|
4859
5889
|
const cycleLimit = useCallback(() => {
|
|
4860
5890
|
setDefaultLimit((prev) => {
|
|
4861
5891
|
const idx = LIMIT_OPTIONS.indexOf(prev);
|
|
@@ -4872,6 +5902,20 @@ function SettingsView({
|
|
|
4872
5902
|
handleSave();
|
|
4873
5903
|
return;
|
|
4874
5904
|
}
|
|
5905
|
+
if (focusedField === "browser") {
|
|
5906
|
+
if (input === " " || key.return) {
|
|
5907
|
+
cycleBrowser(true);
|
|
5908
|
+
return;
|
|
5909
|
+
}
|
|
5910
|
+
if (key.tab || key.downArrow) {
|
|
5911
|
+
cycleFocus(true);
|
|
5912
|
+
return;
|
|
5913
|
+
}
|
|
5914
|
+
if (key.upArrow) {
|
|
5915
|
+
cycleFocus(false);
|
|
5916
|
+
return;
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
4875
5919
|
if (focusedField === "metadata") {
|
|
4876
5920
|
if (input === " " || key.return) {
|
|
4877
5921
|
setWriteMetadata((prev) => !prev);
|
|
@@ -4912,7 +5956,7 @@ function SettingsView({
|
|
|
4912
5956
|
{
|
|
4913
5957
|
value: cookiesDir,
|
|
4914
5958
|
onChange: setCookiesDir,
|
|
4915
|
-
onSubmit: () => setField("
|
|
5959
|
+
onSubmit: () => setField("browser"),
|
|
4916
5960
|
focus: focusedField === "cookies",
|
|
4917
5961
|
onTab: () => cycleFocus(true),
|
|
4918
5962
|
onArrowDown: () => cycleFocus(true),
|
|
@@ -4925,6 +5969,26 @@ function SettingsView({
|
|
|
4925
5969
|
),
|
|
4926
5970
|
/* @__PURE__ */ jsx15(Text14, { color: cookieFeedback.ok ? theme.primary : theme.gray, dimColor: !cookieFeedback.ok && theme.dimSecondary, children: cookieFeedback.text }),
|
|
4927
5971
|
/* @__PURE__ */ jsx15(Gap2, {}),
|
|
5972
|
+
/* @__PURE__ */ jsxs12(
|
|
5973
|
+
Box12,
|
|
5974
|
+
{
|
|
5975
|
+
width: boxWidth,
|
|
5976
|
+
borderStyle: "round",
|
|
5977
|
+
borderColor: focusedField === "browser" ? theme.primary : theme.gray,
|
|
5978
|
+
borderDimColor: focusedField !== "browser",
|
|
5979
|
+
paddingX: 2,
|
|
5980
|
+
justifyContent: "space-between",
|
|
5981
|
+
children: [
|
|
5982
|
+
/* @__PURE__ */ jsxs12(Text14, { color: focusedField === "browser" ? theme.primary : theme.gray, bold: focusedField === "browser", children: [
|
|
5983
|
+
focusedField === "browser" ? "\u25CF " : "\u25CB ",
|
|
5984
|
+
"Browser Cookie Bridge"
|
|
5985
|
+
] }),
|
|
5986
|
+
/* @__PURE__ */ jsx15(Text14, { color: cookiesFromBrowser && cookiesFromBrowser !== "none" ? theme.primary : theme.gray, bold: true, children: `[${(cookiesFromBrowser ?? "auto").toUpperCase()}]` })
|
|
5987
|
+
]
|
|
5988
|
+
}
|
|
5989
|
+
),
|
|
5990
|
+
/* @__PURE__ */ jsx15(Text14, { color: theme.gray, dimColor: theme.dimSecondary, children: browserFeedback }),
|
|
5991
|
+
/* @__PURE__ */ jsx15(Gap2, {}),
|
|
4928
5992
|
/* @__PURE__ */ jsx15(
|
|
4929
5993
|
FramedInput,
|
|
4930
5994
|
{
|
|
@@ -4986,12 +6050,14 @@ function SettingsView({
|
|
|
4986
6050
|
}
|
|
4987
6051
|
),
|
|
4988
6052
|
/* @__PURE__ */ jsx15(Gap2, {}),
|
|
6053
|
+
/* @__PURE__ */ jsx15(DoctorCard, { boxWidth, theme, report: doctorReport }),
|
|
6054
|
+
/* @__PURE__ */ jsx15(Gap2, {}),
|
|
4989
6055
|
/* @__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" })
|
|
4990
6056
|
] });
|
|
4991
6057
|
}
|
|
4992
6058
|
|
|
4993
6059
|
// src/components/views/watcher-view.tsx
|
|
4994
|
-
import { useState as useState7, useEffect as
|
|
6060
|
+
import { useState as useState7, useEffect as useEffect7, useRef as useRef3, useCallback as useCallback2 } from "react";
|
|
4995
6061
|
import { Box as Box13, Text as Text15, useInput as useInput3 } from "ink";
|
|
4996
6062
|
import Spinner2 from "ink-spinner";
|
|
4997
6063
|
|
|
@@ -5278,9 +6344,9 @@ function startClipboardWatcher(options = {}) {
|
|
|
5278
6344
|
}
|
|
5279
6345
|
|
|
5280
6346
|
// src/lib/reveal.ts
|
|
5281
|
-
import
|
|
6347
|
+
import fs16 from "fs";
|
|
5282
6348
|
import { spawn as spawn4 } from "child_process";
|
|
5283
|
-
import
|
|
6349
|
+
import path16 from "path";
|
|
5284
6350
|
function buildOpenBrowserCommand(url, platform = process.platform) {
|
|
5285
6351
|
try {
|
|
5286
6352
|
const parsed = new URL(url.trim());
|
|
@@ -5307,12 +6373,12 @@ function openBrowser(url) {
|
|
|
5307
6373
|
}
|
|
5308
6374
|
}
|
|
5309
6375
|
function getRevealCommand(targetPath, platform = process.platform, isDirectory) {
|
|
5310
|
-
const pathModule = platform === "win32" ?
|
|
6376
|
+
const pathModule = platform === "win32" ? path16.win32 : platform === "darwin" ? path16.posix : path16;
|
|
5311
6377
|
const resolved = pathModule.resolve(targetPath);
|
|
5312
6378
|
const isDir = isDirectory !== void 0 ? isDirectory : (() => {
|
|
5313
6379
|
try {
|
|
5314
|
-
if (
|
|
5315
|
-
return
|
|
6380
|
+
if (fs16.existsSync(resolved)) {
|
|
6381
|
+
return fs16.statSync(resolved).isDirectory();
|
|
5316
6382
|
}
|
|
5317
6383
|
} catch {
|
|
5318
6384
|
}
|
|
@@ -5360,13 +6426,13 @@ function WatcherView({
|
|
|
5360
6426
|
mediaChoiceRef.current = mediaChoice;
|
|
5361
6427
|
const writeMetadataRef = useRef3(writeMetadata);
|
|
5362
6428
|
writeMetadataRef.current = writeMetadata;
|
|
5363
|
-
|
|
6429
|
+
useEffect7(() => {
|
|
5364
6430
|
const pulseTimer = setInterval(() => {
|
|
5365
6431
|
setPulseTick((t) => t + 1);
|
|
5366
6432
|
}, 120);
|
|
5367
6433
|
return () => clearInterval(pulseTimer);
|
|
5368
6434
|
}, []);
|
|
5369
|
-
|
|
6435
|
+
useEffect7(() => {
|
|
5370
6436
|
const watcher = startClipboardWatcher({
|
|
5371
6437
|
choice: mediaChoiceRef.current,
|
|
5372
6438
|
getChoice: () => mediaChoiceRef.current,
|
|
@@ -5440,7 +6506,7 @@ function WatcherView({
|
|
|
5440
6506
|
const target = outputDir || resolveDefaultDownloadDir("instagram", "watcher");
|
|
5441
6507
|
revealInFileManager(target);
|
|
5442
6508
|
}, [outputDir]);
|
|
5443
|
-
|
|
6509
|
+
useEffect7(() => {
|
|
5444
6510
|
if (!onRegisterClickTargets) return;
|
|
5445
6511
|
const targets = [
|
|
5446
6512
|
{ match: "Target:", padX: 1, action: () => {
|
|
@@ -5606,10 +6672,10 @@ function WatcherView({
|
|
|
5606
6672
|
}
|
|
5607
6673
|
|
|
5608
6674
|
// src/components/views/batch-view.tsx
|
|
5609
|
-
import { useState as useState8, useRef as useRef4, useCallback as useCallback3, useEffect as
|
|
6675
|
+
import { useState as useState8, useRef as useRef4, useCallback as useCallback3, useEffect as useEffect8 } from "react";
|
|
5610
6676
|
import { Box as Box14, Text as Text16, useInput as useInput4 } from "ink";
|
|
5611
6677
|
import Spinner3 from "ink-spinner";
|
|
5612
|
-
import
|
|
6678
|
+
import fs17 from "fs";
|
|
5613
6679
|
|
|
5614
6680
|
// src/lib/batch.ts
|
|
5615
6681
|
import fsPromises5 from "fs/promises";
|
|
@@ -5816,7 +6882,7 @@ function BatchView({
|
|
|
5816
6882
|
setErrorMessage("Please enter a valid batch file path");
|
|
5817
6883
|
return;
|
|
5818
6884
|
}
|
|
5819
|
-
if (!
|
|
6885
|
+
if (!fs17.existsSync(trimmed)) {
|
|
5820
6886
|
setErrorMessage(`File not found: ${trimmed}`);
|
|
5821
6887
|
return;
|
|
5822
6888
|
}
|
|
@@ -6148,7 +7214,7 @@ function BatchView({
|
|
|
6148
7214
|
}
|
|
6149
7215
|
}
|
|
6150
7216
|
}, { isActive: Boolean(process.stdin.isTTY) });
|
|
6151
|
-
|
|
7217
|
+
useEffect8(() => {
|
|
6152
7218
|
if (!onRegisterClickTargets) return;
|
|
6153
7219
|
const targets = [];
|
|
6154
7220
|
if (phase === "input") {
|
|
@@ -6649,6 +7715,80 @@ function frameRowSpan(row) {
|
|
|
6649
7715
|
return [first + 1, line.trimEnd().length];
|
|
6650
7716
|
}
|
|
6651
7717
|
|
|
7718
|
+
// src/lib/engines/core/use-engine-events.ts
|
|
7719
|
+
import { useState as useState9, useEffect as useEffect9 } from "react";
|
|
7720
|
+
var INITIAL_ENGINE_EVENT_STATE = {
|
|
7721
|
+
isDownloading: false,
|
|
7722
|
+
activeJob: void 0,
|
|
7723
|
+
progress: void 0,
|
|
7724
|
+
lastError: void 0,
|
|
7725
|
+
lastResult: void 0
|
|
7726
|
+
};
|
|
7727
|
+
function subscribeToEngineEvents(bus, onChange) {
|
|
7728
|
+
let state = { ...INITIAL_ENGINE_EVENT_STATE };
|
|
7729
|
+
const onJobStart = (payload) => {
|
|
7730
|
+
state = {
|
|
7731
|
+
...state,
|
|
7732
|
+
isDownloading: true,
|
|
7733
|
+
activeJob: payload,
|
|
7734
|
+
progress: void 0,
|
|
7735
|
+
lastError: void 0,
|
|
7736
|
+
lastResult: void 0
|
|
7737
|
+
};
|
|
7738
|
+
onChange(state);
|
|
7739
|
+
};
|
|
7740
|
+
const onJobProgress = (payload) => {
|
|
7741
|
+
state = {
|
|
7742
|
+
...state,
|
|
7743
|
+
isDownloading: true,
|
|
7744
|
+
progress: payload
|
|
7745
|
+
};
|
|
7746
|
+
onChange(state);
|
|
7747
|
+
};
|
|
7748
|
+
const onJobComplete = (payload) => {
|
|
7749
|
+
state = {
|
|
7750
|
+
...state,
|
|
7751
|
+
isDownloading: false,
|
|
7752
|
+
lastResult: payload.result
|
|
7753
|
+
};
|
|
7754
|
+
onChange(state);
|
|
7755
|
+
};
|
|
7756
|
+
const onJobError = (payload) => {
|
|
7757
|
+
state = {
|
|
7758
|
+
...state,
|
|
7759
|
+
isDownloading: false,
|
|
7760
|
+
lastError: payload.error
|
|
7761
|
+
};
|
|
7762
|
+
onChange(state);
|
|
7763
|
+
};
|
|
7764
|
+
const unsubStart = bus.on("job:start", onJobStart);
|
|
7765
|
+
const unsubProgress = bus.on("job:progress", onJobProgress);
|
|
7766
|
+
const unsubComplete = bus.on("job:complete", onJobComplete);
|
|
7767
|
+
const unsubError = bus.on("job:error", onJobError);
|
|
7768
|
+
return () => {
|
|
7769
|
+
if (typeof bus.off === "function") {
|
|
7770
|
+
bus.off("job:start", onJobStart);
|
|
7771
|
+
bus.off("job:progress", onJobProgress);
|
|
7772
|
+
bus.off("job:complete", onJobComplete);
|
|
7773
|
+
bus.off("job:error", onJobError);
|
|
7774
|
+
}
|
|
7775
|
+
unsubStart?.();
|
|
7776
|
+
unsubProgress?.();
|
|
7777
|
+
unsubComplete?.();
|
|
7778
|
+
unsubError?.();
|
|
7779
|
+
};
|
|
7780
|
+
}
|
|
7781
|
+
function useEngineEvents(customBus) {
|
|
7782
|
+
const bus = customBus || defaultTaskManager.events;
|
|
7783
|
+
const [state, setState] = useState9(() => ({
|
|
7784
|
+
...INITIAL_ENGINE_EVENT_STATE
|
|
7785
|
+
}));
|
|
7786
|
+
useEffect9(() => {
|
|
7787
|
+
return subscribeToEngineEvents(bus, setState);
|
|
7788
|
+
}, [bus]);
|
|
7789
|
+
return state;
|
|
7790
|
+
}
|
|
7791
|
+
|
|
6652
7792
|
// src/app.tsx
|
|
6653
7793
|
import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
6654
7794
|
var TAGLINE = "grab any profile. paste. download. done.";
|
|
@@ -6728,7 +7868,7 @@ function App({
|
|
|
6728
7868
|
writeMetadata,
|
|
6729
7869
|
version = "1.0.0"
|
|
6730
7870
|
}) {
|
|
6731
|
-
const [themeMode, setThemeMode] =
|
|
7871
|
+
const [themeMode, setThemeMode] = useState10(initialThemeMode);
|
|
6732
7872
|
const cycleTheme = useCallback4(() => {
|
|
6733
7873
|
setThemeMode(nextThemeMode);
|
|
6734
7874
|
}, []);
|
|
@@ -6763,18 +7903,19 @@ function InnerApp({
|
|
|
6763
7903
|
const theme = useTheme();
|
|
6764
7904
|
const { exit } = useApp();
|
|
6765
7905
|
const { stdout } = useStdout3();
|
|
7906
|
+
const engineEvents = useEngineEvents();
|
|
6766
7907
|
const config = useMemo3(() => loadConfig(), []);
|
|
6767
7908
|
const effectiveLimit = limit ?? config.defaultLimit;
|
|
6768
7909
|
const effectiveWriteMetadata = writeMetadata ?? Boolean(config.writeMetadata);
|
|
6769
|
-
const [urlInput, setUrlInput] =
|
|
6770
|
-
const [clipboardUrl, setClipboardUrl] =
|
|
6771
|
-
const [settingsFocus, setSettingsFocus] =
|
|
6772
|
-
const [batchClickTargets, setBatchClickTargets] =
|
|
6773
|
-
const [watcherClickTargets, setWatcherClickTargets] =
|
|
7910
|
+
const [urlInput, setUrlInput] = useState10(initialInput ?? "");
|
|
7911
|
+
const [clipboardUrl, setClipboardUrl] = useState10(void 0);
|
|
7912
|
+
const [settingsFocus, setSettingsFocus] = useState10("cookies");
|
|
7913
|
+
const [batchClickTargets, setBatchClickTargets] = useState10([]);
|
|
7914
|
+
const [watcherClickTargets, setWatcherClickTargets] = useState10([]);
|
|
6774
7915
|
const columns = stdout?.columns && stdout.columns > 0 ? stdout.columns : 80;
|
|
6775
7916
|
const boxWidth = Math.max(14, Math.min(64, columns - 6));
|
|
6776
7917
|
const contentWidth = Math.max(10, Math.min(columns - 4, 78));
|
|
6777
|
-
const [phase, setPhase] =
|
|
7918
|
+
const [phase, setPhase] = useState10(() => {
|
|
6778
7919
|
if (initialInput) {
|
|
6779
7920
|
const parsed = parseProfileInput(initialInput);
|
|
6780
7921
|
if (parsed.platform !== "unknown" && parsed.cleanUrl) {
|
|
@@ -6786,7 +7927,7 @@ function InnerApp({
|
|
|
6786
7927
|
}
|
|
6787
7928
|
return { name: "input" };
|
|
6788
7929
|
});
|
|
6789
|
-
|
|
7930
|
+
useEffect10(() => {
|
|
6790
7931
|
try {
|
|
6791
7932
|
const clip = readClipboard();
|
|
6792
7933
|
if (clip && (clip.startsWith("http://") || clip.startsWith("https://") || clip.startsWith("@"))) {
|
|
@@ -6799,7 +7940,7 @@ function InnerApp({
|
|
|
6799
7940
|
const clipboardAccepted = Boolean(clipboardUrl) && urlInput === clipboardUrl;
|
|
6800
7941
|
const abortControllerRef = useRef5(null);
|
|
6801
7942
|
const probingTimeoutRef = useRef5(null);
|
|
6802
|
-
|
|
7943
|
+
useEffect10(() => {
|
|
6803
7944
|
return () => {
|
|
6804
7945
|
abortControllerRef.current?.abort();
|
|
6805
7946
|
if (probingTimeoutRef.current) {
|
|
@@ -7159,7 +8300,7 @@ function InnerApp({
|
|
|
7159
8300
|
{
|
|
7160
8301
|
profile: phase.profile,
|
|
7161
8302
|
choice: phase.choice,
|
|
7162
|
-
progress: phase.progress,
|
|
8303
|
+
progress: engineEvents.progress ?? phase.progress,
|
|
7163
8304
|
outputDir: resolveDefaultDownloadDir(phase.profile.platform, phase.profile.username, outputDir),
|
|
7164
8305
|
writeMetadata: effectiveWriteMetadata,
|
|
7165
8306
|
limit: effectiveLimit,
|
|
@@ -7439,7 +8580,7 @@ import { jsx as jsx19 } from "react/jsx-runtime";
|
|
|
7439
8580
|
var resolveVersion = () => {
|
|
7440
8581
|
try {
|
|
7441
8582
|
if (typeof import.meta !== "undefined" && import.meta?.url) {
|
|
7442
|
-
return
|
|
8583
|
+
return createRequire2(import.meta.url)("../package.json").version;
|
|
7443
8584
|
}
|
|
7444
8585
|
} catch {
|
|
7445
8586
|
}
|
|
@@ -7463,6 +8604,7 @@ var HELP = `
|
|
|
7463
8604
|
Options:
|
|
7464
8605
|
-h, --help Show this help message
|
|
7465
8606
|
-v, --version Show version
|
|
8607
|
+
--doctor Run environment & dependency diagnostics
|
|
7466
8608
|
-o, --output Set custom output directory
|
|
7467
8609
|
-w, --watch Watch clipboard for profile links and auto-download
|
|
7468
8610
|
-b, --batch <file> Download multiple profiles listed in a text file (one URL/user per line)
|
|
@@ -7509,6 +8651,24 @@ async function main() {
|
|
|
7509
8651
|
console.log(`open-nami v${VERSION}`);
|
|
7510
8652
|
process.exit(0);
|
|
7511
8653
|
}
|
|
8654
|
+
if (args.includes("--doctor")) {
|
|
8655
|
+
const report = await runDoctor();
|
|
8656
|
+
console.log(`
|
|
8657
|
+
\x1B[36mOpen Nami Doctor\x1B[0m (v${VERSION})
|
|
8658
|
+
`);
|
|
8659
|
+
console.log(` \x1B[1mRuntime:\x1B[0m Node.js ${report.nodeVersion}`);
|
|
8660
|
+
console.log(` \x1B[1mStorage:\x1B[0m ${report.storageOk ? "\x1B[32m\u2713 Ready\x1B[0m" : "\x1B[31m\u2717 Inaccessible\x1B[0m"}`);
|
|
8661
|
+
console.log(` \x1B[1mEngines:\x1B[0m`);
|
|
8662
|
+
for (const [bin, ok] of Object.entries(report.binaries)) {
|
|
8663
|
+
console.log(` ${ok ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m"} ${bin}`);
|
|
8664
|
+
}
|
|
8665
|
+
console.log(` \x1B[1mCookies:\x1B[0m`);
|
|
8666
|
+
for (const [platform, ok] of Object.entries(report.cookies)) {
|
|
8667
|
+
console.log(` ${ok ? "\x1B[32m\u2713\x1B[0m" : "\x1B[90m\u25CB\x1B[0m"} ${platform}`);
|
|
8668
|
+
}
|
|
8669
|
+
console.log();
|
|
8670
|
+
process.exit(0);
|
|
8671
|
+
}
|
|
7512
8672
|
const completionIdx = args.findIndex((arg) => arg === "--completion" || arg.startsWith("--completion="));
|
|
7513
8673
|
if (completionIdx !== -1) {
|
|
7514
8674
|
const arg = args[completionIdx];
|