open-nami 1.2.4 → 1.2.5

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.
Files changed (2) hide show
  1. package/dist/cli.js +413 -81
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ var require_package = __commonJS({
9
9
  "package.json"(exports, module) {
10
10
  module.exports = {
11
11
  name: "open-nami",
12
- version: "1.2.4",
12
+ version: "1.2.5",
13
13
  description: "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
14
14
  type: "module",
15
15
  bin: {
@@ -77,7 +77,7 @@ import { createRequire } from "module";
77
77
  import { render } from "ink";
78
78
 
79
79
  // src/app.tsx
80
- import { useState as useState9, useCallback as useCallback5, useRef as useRef5, useEffect as useEffect7, useMemo as useMemo3 } from "react";
80
+ import { useState as useState9, useCallback as useCallback5, useRef as useRef5, useEffect as useEffect8, useMemo as useMemo3 } from "react";
81
81
  import { Box as Box15, Text as Text17, useApp, useInput as useInput6, useStdout as useStdout3 } from "ink";
82
82
  import Spinner4 from "ink-spinner";
83
83
 
@@ -5629,7 +5629,7 @@ function WatcherView({
5629
5629
  }
5630
5630
 
5631
5631
  // src/components/views/batch-view.tsx
5632
- import { useState as useState8, useRef as useRef4, useCallback as useCallback4 } from "react";
5632
+ import { useState as useState8, useRef as useRef4, useCallback as useCallback4, useEffect as useEffect7 } from "react";
5633
5633
  import { Box as Box14, Text as Text16, useInput as useInput5 } from "ink";
5634
5634
  import Spinner3 from "ink-spinner";
5635
5635
  import fs13 from "fs";
@@ -5667,13 +5667,19 @@ async function runBatchQueue(options) {
5667
5667
  const choice = options.choice || "all";
5668
5668
  const minDelay = options.minDelayMs ?? 2e3;
5669
5669
  const maxDelay = options.maxDelayMs ?? 5e3;
5670
- const items = rawEntries.map((raw) => ({
5671
- raw,
5672
- profile: parseProfileInput(raw),
5673
- status: "pending",
5674
- downloaded: 0,
5675
- skipped: 0
5676
- }));
5670
+ const items = rawEntries.map((raw) => {
5671
+ let profile = parseProfileInput(raw);
5672
+ if (options.defaultPlatform && profile.platform === "unknown" && profile.username) {
5673
+ profile = buildCanonicalProfile(options.defaultPlatform, profile.username, raw);
5674
+ }
5675
+ return {
5676
+ raw,
5677
+ profile,
5678
+ status: "pending",
5679
+ downloaded: 0,
5680
+ skipped: 0
5681
+ };
5682
+ });
5677
5683
  let totalDownloaded = 0;
5678
5684
  let totalSkipped = 0;
5679
5685
  let succeeded = 0;
@@ -5693,10 +5699,15 @@ async function runBatchQueue(options) {
5693
5699
  }
5694
5700
  options.onAccountStart?.(item, i, items.length);
5695
5701
  try {
5702
+ const targetDir = resolveDefaultDownloadDir(
5703
+ item.profile.platform,
5704
+ item.profile.username,
5705
+ options.outputDir
5706
+ );
5696
5707
  const res = await dispatchDownload({
5697
5708
  profile: item.profile,
5698
5709
  choice,
5699
- outputDir: options.outputDir,
5710
+ outputDir: targetDir,
5700
5711
  since: options.since,
5701
5712
  until: options.until,
5702
5713
  limit: options.limit,
@@ -5744,8 +5755,11 @@ async function runBatchQueue(options) {
5744
5755
 
5745
5756
  // src/components/views/batch-view.tsx
5746
5757
  import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
5758
+ var PLATFORMS = ["instagram", "tiktok", "facebook", "x"];
5759
+ var CHOICES = ["all", "photos", "videos", "stories", "highlights"];
5747
5760
  var DATE_PRESETS2 = ["all", "24h", "7d", "30d", "2024", "2023", "2022", "2021", "custom"];
5748
5761
  var LIMIT_PRESETS2 = [void 0, 10, 24, 50, 100, "custom"];
5762
+ var OPTION_CONTROLS = ["platform", "choice", "limit", "date", "meta", "resume", "start"];
5749
5763
  function BatchView({
5750
5764
  onBack,
5751
5765
  outputDir,
@@ -5754,16 +5768,18 @@ function BatchView({
5754
5768
  gapComponent: Gap2,
5755
5769
  defaultChoice = "all",
5756
5770
  defaultLimit,
5757
- defaultWriteMetadata = false
5771
+ defaultWriteMetadata = false,
5772
+ onRegisterClickTargets
5758
5773
  }) {
5759
5774
  const [phase, setPhase] = useState8("input");
5760
5775
  const [inputField, setInputField] = useState8("text");
5761
5776
  const [rawText, setRawText] = useState8("");
5762
5777
  const [filePath, setFilePath] = useState8("");
5763
5778
  const [errorMessage, setErrorMessage] = useState8(null);
5764
- const [focusSection, setFocusSection] = useState8("input");
5779
+ const [focusedControl, setFocusedControl] = useState8("input");
5765
5780
  const [customLimitText, setCustomLimitText] = useState8("");
5766
5781
  const [customDateText, setCustomDateText] = useState8("");
5782
+ const [defaultPlatform, setDefaultPlatform] = useState8("instagram");
5767
5783
  const [choice, setChoice] = useState8(defaultChoice);
5768
5784
  const [limit, setLimit] = useState8(defaultLimit);
5769
5785
  const [dateFilter, setDateFilter] = useState8("all");
@@ -5775,11 +5791,16 @@ function BatchView({
5775
5791
  const [isCooldown, setIsCooldown] = useState8(false);
5776
5792
  const [result, setResult] = useState8(null);
5777
5793
  const abortControllerRef = useRef4(null);
5794
+ const cyclePlatform = useCallback4(() => {
5795
+ setDefaultPlatform((prev) => {
5796
+ const idx = PLATFORMS.indexOf(prev);
5797
+ return PLATFORMS[(idx + 1) % PLATFORMS.length];
5798
+ });
5799
+ }, []);
5778
5800
  const cycleChoice = useCallback4(() => {
5779
5801
  setChoice((prev) => {
5780
- if (prev === "all") return "photos";
5781
- if (prev === "photos") return "videos";
5782
- return "all";
5802
+ const idx = CHOICES.indexOf(prev);
5803
+ return CHOICES[(idx + 1) % CHOICES.length];
5783
5804
  });
5784
5805
  }, []);
5785
5806
  const cycleLimit = useCallback4(() => {
@@ -5788,7 +5809,7 @@ function BatchView({
5788
5809
  const nextVal = LIMIT_PRESETS2[nextIdx];
5789
5810
  if (nextVal === "custom") {
5790
5811
  setCustomLimitText(limit ? String(limit) : "24");
5791
- setFocusSection("custom-limit");
5812
+ setFocusedControl("custom-limit");
5792
5813
  } else {
5793
5814
  setLimit(nextVal);
5794
5815
  }
@@ -5798,8 +5819,8 @@ function BatchView({
5798
5819
  const nextIdx = (curIdx + 1) % DATE_PRESETS2.length;
5799
5820
  const nextVal = DATE_PRESETS2[nextIdx];
5800
5821
  if (nextVal === "custom") {
5801
- setCustomDateText(dateFilter !== "all" ? dateFilter : "2021");
5802
- setFocusSection("custom-date");
5822
+ setCustomDateText(dateFilter !== "all" ? dateFilter : "2024");
5823
+ setFocusedControl("custom-date");
5803
5824
  } else {
5804
5825
  setDateFilter(nextVal);
5805
5826
  }
@@ -5824,7 +5845,11 @@ function BatchView({
5824
5845
  return;
5825
5846
  }
5826
5847
  } else {
5827
- lines = rawText.split(/[\n,;]+/).map((s) => s.trim()).filter(Boolean);
5848
+ lines = Array.from(
5849
+ new Set(
5850
+ rawText.split(/[\r\n,;\s]+/).map((s) => s.trim()).filter(Boolean)
5851
+ )
5852
+ );
5828
5853
  if (lines.length === 0) {
5829
5854
  setErrorMessage("Please enter at least one profile URL or username");
5830
5855
  return;
@@ -5839,6 +5864,7 @@ function BatchView({
5839
5864
  filePath: inputField === "file" ? filePath.trim() : void 0,
5840
5865
  lines: inputField === "text" ? lines : void 0,
5841
5866
  choice,
5867
+ defaultPlatform,
5842
5868
  outputDir,
5843
5869
  since: dateRange.since,
5844
5870
  until: dateRange.until,
@@ -5849,7 +5875,7 @@ function BatchView({
5849
5875
  onAccountStart: (item, idx) => {
5850
5876
  setIsCooldown(false);
5851
5877
  setCurrentIndex(idx);
5852
- setCurrentProgressText(`Probing @${item.profile.username || item.raw}\u2026`);
5878
+ setCurrentProgressText(`Probing @${item.profile.username || item.raw} on ${item.profile.platform}\u2026`);
5853
5879
  setQueueItems((prev) => {
5854
5880
  const next = [...prev];
5855
5881
  if (next[idx]) next[idx] = { ...item };
@@ -5891,15 +5917,18 @@ function BatchView({
5891
5917
  setErrorMessage(msg);
5892
5918
  setPhase("input");
5893
5919
  }
5894
- }, [inputField, filePath, rawText, choice, outputDir, dateFilter, limit, resume, writeMetadata]);
5920
+ }, [inputField, filePath, rawText, choice, defaultPlatform, outputDir, dateFilter, limit, resume, writeMetadata]);
5895
5921
  useInput5((input, key) => {
5922
+ if (key.ctrl && (input === "c" || input === "C")) {
5923
+ return;
5924
+ }
5896
5925
  if (key.escape) {
5897
- if (focusSection === "custom-limit" || focusSection === "custom-date") {
5898
- setFocusSection("options");
5926
+ if (focusedControl === "custom-limit" || focusedControl === "custom-date") {
5927
+ setFocusedControl("start");
5899
5928
  return;
5900
5929
  }
5901
- if (focusSection === "options") {
5902
- setFocusSection("input");
5930
+ if (focusedControl !== "input" && phase === "input") {
5931
+ setFocusedControl("input");
5903
5932
  return;
5904
5933
  }
5905
5934
  if (phase === "running") {
@@ -5922,46 +5951,78 @@ function BatchView({
5922
5951
  }
5923
5952
  return;
5924
5953
  }
5954
+ if (phase === "running") {
5955
+ return;
5956
+ }
5925
5957
  if (phase === "input") {
5926
- if (focusSection === "custom-limit" || focusSection === "custom-date") {
5958
+ if (focusedControl === "custom-limit" || focusedControl === "custom-date") {
5927
5959
  return;
5928
5960
  }
5929
- if (key.ctrl) {
5930
- if (input === "l" || input === "L") {
5931
- setCustomLimitText(limit ? String(limit) : "24");
5932
- setFocusSection("custom-limit");
5961
+ if (focusedControl === "input") {
5962
+ if (key.downArrow) {
5963
+ setFocusedControl("platform");
5933
5964
  return;
5934
5965
  }
5935
- if (input === "d" || input === "D") {
5936
- setCustomDateText(dateFilter !== "all" ? dateFilter : "2021");
5937
- setFocusSection("custom-date");
5966
+ if (key.upArrow) {
5967
+ setFocusedControl("tabs");
5938
5968
  return;
5939
5969
  }
5940
- if (input === "c" || input === "C") {
5941
- cycleChoice();
5970
+ return;
5971
+ }
5972
+ if (focusedControl === "tabs") {
5973
+ if (key.leftArrow || key.rightArrow || input === " " || key.return) {
5974
+ setInputField((prev) => prev === "text" ? "file" : "text");
5942
5975
  return;
5943
5976
  }
5944
- if (input === "m" || input === "M") {
5945
- toggleMetadata();
5977
+ if (key.downArrow || key.tab) {
5978
+ setFocusedControl("input");
5946
5979
  return;
5947
5980
  }
5948
- if (input === "r" || input === "R") {
5949
- toggleResume();
5981
+ return;
5982
+ }
5983
+ if (OPTION_CONTROLS.includes(focusedControl)) {
5984
+ if (key.upArrow) {
5985
+ setFocusedControl("input");
5950
5986
  return;
5951
5987
  }
5952
- }
5953
- if (focusSection === "input") {
5954
- if (key.downArrow) {
5955
- setFocusSection("options");
5988
+ if (key.rightArrow || key.tab && !key.shift) {
5989
+ const idx = OPTION_CONTROLS.indexOf(focusedControl);
5990
+ setFocusedControl(OPTION_CONTROLS[(idx + 1) % OPTION_CONTROLS.length]);
5956
5991
  return;
5957
5992
  }
5958
- } else if (focusSection === "options") {
5959
- if (key.upArrow) {
5960
- setFocusSection("input");
5993
+ if (key.leftArrow || key.tab && key.shift) {
5994
+ const idx = OPTION_CONTROLS.indexOf(focusedControl);
5995
+ setFocusedControl(OPTION_CONTROLS[(idx - 1 + OPTION_CONTROLS.length) % OPTION_CONTROLS.length]);
5961
5996
  return;
5962
5997
  }
5963
- if (key.return) {
5964
- startQueue();
5998
+ if (input === " " || key.return) {
5999
+ switch (focusedControl) {
6000
+ case "platform":
6001
+ cyclePlatform();
6002
+ break;
6003
+ case "choice":
6004
+ cycleChoice();
6005
+ break;
6006
+ case "limit":
6007
+ cycleLimit();
6008
+ break;
6009
+ case "date":
6010
+ cycleDate();
6011
+ break;
6012
+ case "meta":
6013
+ toggleMetadata();
6014
+ break;
6015
+ case "resume":
6016
+ toggleResume();
6017
+ break;
6018
+ case "start":
6019
+ startQueue();
6020
+ break;
6021
+ }
6022
+ return;
6023
+ }
6024
+ if (input === "p" || input === "P") {
6025
+ cyclePlatform();
5965
6026
  return;
5966
6027
  }
5967
6028
  if (input === "c" || input === "C") {
@@ -5987,6 +6048,172 @@ function BatchView({
5987
6048
  }
5988
6049
  }
5989
6050
  }, { isActive: Boolean(process.stdin.isTTY) });
6051
+ useEffect7(() => {
6052
+ if (!onRegisterClickTargets) return;
6053
+ const targets = [];
6054
+ if (phase === "input") {
6055
+ targets.push({
6056
+ match: "Links / Handles",
6057
+ padX: 2,
6058
+ action: () => {
6059
+ setInputField("text");
6060
+ setFocusedControl("input");
6061
+ }
6062
+ });
6063
+ targets.push({
6064
+ match: "File (.txt)",
6065
+ padX: 2,
6066
+ action: () => {
6067
+ setInputField("file");
6068
+ setFocusedControl("input");
6069
+ }
6070
+ });
6071
+ targets.push({
6072
+ match: "Links or @handles",
6073
+ padX: 2,
6074
+ action: () => setFocusedControl("input")
6075
+ });
6076
+ targets.push({
6077
+ match: "Batch file (.txt)",
6078
+ padX: 2,
6079
+ action: () => setFocusedControl("input")
6080
+ });
6081
+ targets.push({
6082
+ match: "Platform:",
6083
+ padX: 2,
6084
+ action: () => {
6085
+ setFocusedControl("platform");
6086
+ cyclePlatform();
6087
+ }
6088
+ });
6089
+ targets.push({
6090
+ match: "Media:",
6091
+ padX: 2,
6092
+ action: () => {
6093
+ setFocusedControl("choice");
6094
+ cycleChoice();
6095
+ }
6096
+ });
6097
+ targets.push({
6098
+ match: "Posts:",
6099
+ padX: 2,
6100
+ action: () => {
6101
+ setFocusedControl("limit");
6102
+ cycleLimit();
6103
+ }
6104
+ });
6105
+ targets.push({
6106
+ match: "Date:",
6107
+ padX: 2,
6108
+ action: () => {
6109
+ setFocusedControl("date");
6110
+ cycleDate();
6111
+ }
6112
+ });
6113
+ targets.push({
6114
+ match: "Meta:",
6115
+ padX: 2,
6116
+ action: () => {
6117
+ setFocusedControl("meta");
6118
+ toggleMetadata();
6119
+ }
6120
+ });
6121
+ targets.push({
6122
+ match: "Resume:",
6123
+ padX: 2,
6124
+ action: () => {
6125
+ setFocusedControl("resume");
6126
+ toggleResume();
6127
+ }
6128
+ });
6129
+ targets.push({
6130
+ match: "Start Queue",
6131
+ padX: 3,
6132
+ action: () => {
6133
+ setFocusedControl("start");
6134
+ startQueue();
6135
+ }
6136
+ });
6137
+ targets.push({
6138
+ match: "tab switch field",
6139
+ action: () => setInputField((prev) => prev === "text" ? "file" : "text")
6140
+ });
6141
+ targets.push({
6142
+ match: "c target",
6143
+ action: cycleChoice
6144
+ });
6145
+ targets.push({
6146
+ match: "m sidecar",
6147
+ action: toggleMetadata
6148
+ });
6149
+ targets.push({
6150
+ match: "l limit",
6151
+ action: cycleLimit
6152
+ });
6153
+ targets.push({
6154
+ match: "d date",
6155
+ action: cycleDate
6156
+ });
6157
+ targets.push({
6158
+ match: "r resume",
6159
+ action: toggleResume
6160
+ });
6161
+ targets.push({
6162
+ match: "\u21B5 start",
6163
+ action: startQueue
6164
+ });
6165
+ }
6166
+ if (phase === "running") {
6167
+ targets.push({
6168
+ match: "Stop queue and return",
6169
+ padX: 2,
6170
+ action: () => {
6171
+ abortControllerRef.current?.abort();
6172
+ setPhase("input");
6173
+ }
6174
+ });
6175
+ targets.push({
6176
+ match: "Stop queue",
6177
+ padX: 2,
6178
+ action: () => {
6179
+ abortControllerRef.current?.abort();
6180
+ setPhase("input");
6181
+ }
6182
+ });
6183
+ }
6184
+ if (phase === "done") {
6185
+ targets.push({
6186
+ match: "Open Downloads Folder",
6187
+ padX: 2,
6188
+ action: () => {
6189
+ const target = outputDir || resolveDefaultDownloadDir("instagram", "batch");
6190
+ revealInFileManager(target);
6191
+ }
6192
+ });
6193
+ targets.push({
6194
+ match: "Return to Start",
6195
+ padX: 2,
6196
+ action: () => setPhase("input")
6197
+ });
6198
+ }
6199
+ onRegisterClickTargets(targets);
6200
+ return () => {
6201
+ onRegisterClickTargets([]);
6202
+ };
6203
+ }, [
6204
+ phase,
6205
+ inputField,
6206
+ focusedControl,
6207
+ onRegisterClickTargets,
6208
+ cyclePlatform,
6209
+ cycleChoice,
6210
+ cycleLimit,
6211
+ cycleDate,
6212
+ toggleMetadata,
6213
+ toggleResume,
6214
+ startQueue,
6215
+ outputDir
6216
+ ]);
5990
6217
  const currentItem = queueItems[currentIndex];
5991
6218
  const completedCount = queueItems.filter((i) => i.status === "success" || i.status === "failed").length;
5992
6219
  const totalCount = queueItems.length > 0 ? queueItems.length : 1;
@@ -5994,9 +6221,25 @@ function BatchView({
5994
6221
  return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", alignItems: "center", children: [
5995
6222
  phase === "input" && /* @__PURE__ */ jsx17(Panel, { title: "Batch Queue", width: Math.max(60, boxWidth), children: /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingY: 1, children: [
5996
6223
  /* @__PURE__ */ jsxs14(Box14, { marginBottom: 1, justifyContent: "center", children: [
5997
- /* @__PURE__ */ jsx17(Text16, { bold: true, color: inputField === "text" ? theme.primary : theme.gray, children: inputField === "text" ? "\u25CF Links / Handles" : "\u25CB Links / Handles" }),
6224
+ /* @__PURE__ */ jsx17(
6225
+ Text16,
6226
+ {
6227
+ bold: true,
6228
+ color: inputField === "text" ? theme.primary : theme.gray,
6229
+ inverse: focusedControl === "tabs" && inputField === "text",
6230
+ children: inputField === "text" ? "\u25CF Links / Handles" : "\u25CB Links / Handles"
6231
+ }
6232
+ ),
5998
6233
  /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: " \xB7 " }),
5999
- /* @__PURE__ */ jsx17(Text16, { bold: true, color: inputField === "file" ? theme.primary : theme.gray, children: inputField === "file" ? "\u25CF File (.txt)" : "\u25CB File (.txt)" })
6234
+ /* @__PURE__ */ jsx17(
6235
+ Text16,
6236
+ {
6237
+ bold: true,
6238
+ color: inputField === "file" ? theme.primary : theme.gray,
6239
+ inverse: focusedControl === "tabs" && inputField === "file",
6240
+ children: inputField === "file" ? "\u25CF File (.txt)" : "\u25CB File (.txt)"
6241
+ }
6242
+ )
6000
6243
  ] }),
6001
6244
  inputField === "text" ? /* @__PURE__ */ jsx17(FramedInput, { title: "Links or @handles", width: Math.max(56, boxWidth - 4), children: /* @__PURE__ */ jsx17(
6002
6245
  TextInput,
@@ -6006,9 +6249,10 @@ function BatchView({
6006
6249
  onSubmit: startQueue,
6007
6250
  placeholder: "@user1, @user2, tiktok.com/@user3\u2026",
6008
6251
  width: Math.max(50, boxWidth - 10),
6009
- focus: focusSection === "input",
6010
- onTab: () => setInputField("file"),
6011
- onArrowDown: () => setFocusSection("options")
6252
+ focus: focusedControl === "input",
6253
+ onArrowDown: () => setFocusedControl("start"),
6254
+ onArrowUp: () => setFocusedControl("tabs"),
6255
+ onTab: () => setFocusedControl("start")
6012
6256
  }
6013
6257
  ) }) : /* @__PURE__ */ jsx17(FramedInput, { title: "Batch file (.txt)", width: Math.max(56, boxWidth - 4), children: /* @__PURE__ */ jsx17(
6014
6258
  TextInput,
@@ -6018,9 +6262,10 @@ function BatchView({
6018
6262
  onSubmit: startQueue,
6019
6263
  placeholder: "e.g. creators.txt or C:\\path\\creators.txt",
6020
6264
  width: Math.max(50, boxWidth - 10),
6021
- focus: focusSection === "input",
6022
- onTab: () => setInputField("text"),
6023
- onArrowDown: () => setFocusSection("options")
6265
+ focus: focusedControl === "input",
6266
+ onArrowDown: () => setFocusedControl("start"),
6267
+ onArrowUp: () => setFocusedControl("tabs"),
6268
+ onTab: () => setFocusedControl("start")
6024
6269
  }
6025
6270
  ) }),
6026
6271
  errorMessage ? /* @__PURE__ */ jsx17(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text16, { color: "red", children: [
@@ -6033,10 +6278,10 @@ function BatchView({
6033
6278
  flexDirection: "column",
6034
6279
  marginTop: 1,
6035
6280
  borderStyle: "round",
6036
- borderColor: focusSection === "options" || focusSection === "custom-limit" || focusSection === "custom-date" ? theme.primary : theme.gray,
6037
- borderDimColor: focusSection === "input",
6281
+ borderColor: focusedControl !== "input" && focusedControl !== "tabs" ? theme.primary : theme.gray,
6282
+ borderDimColor: focusedControl === "input" || focusedControl === "tabs",
6038
6283
  paddingX: 1,
6039
- children: focusSection === "custom-limit" ? /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
6284
+ children: focusedControl === "custom-limit" ? /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
6040
6285
  /* @__PURE__ */ jsx17(Text16, { bold: true, color: theme.primary, children: "Set post limit (e.g. 24, all):" }),
6041
6286
  /* @__PURE__ */ jsx17(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx17(
6042
6287
  TextInput,
@@ -6046,14 +6291,14 @@ function BatchView({
6046
6291
  onSubmit: (val) => {
6047
6292
  const parsed = parseLimit(val);
6048
6293
  setLimit(parsed);
6049
- setFocusSection("options");
6294
+ setFocusedControl("start");
6050
6295
  },
6051
6296
  width: 24
6052
6297
  }
6053
6298
  ) }),
6054
6299
  /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "\u21B5 confirm \xB7 esc cancel" })
6055
- ] }) : focusSection === "custom-date" ? /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
6056
- /* @__PURE__ */ jsx17(Text16, { bold: true, color: theme.primary, children: "Set date or year (e.g. 2021, 2022, 7d):" }),
6300
+ ] }) : focusedControl === "custom-date" ? /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
6301
+ /* @__PURE__ */ jsx17(Text16, { bold: true, color: theme.primary, children: "Set date or year (e.g. 2024, 7d):" }),
6057
6302
  /* @__PURE__ */ jsx17(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx17(
6058
6303
  TextInput,
6059
6304
  {
@@ -6062,7 +6307,7 @@ function BatchView({
6062
6307
  onSubmit: (val) => {
6063
6308
  const trimmed = val.trim();
6064
6309
  setDateFilter(trimmed ? trimmed : "all");
6065
- setFocusSection("options");
6310
+ setFocusedControl("start");
6066
6311
  },
6067
6312
  width: 24
6068
6313
  }
@@ -6071,33 +6316,99 @@ function BatchView({
6071
6316
  ] }) : /* @__PURE__ */ jsxs14(Fragment4, { children: [
6072
6317
  /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", children: [
6073
6318
  /* @__PURE__ */ jsxs14(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: [
6074
- "[c] Media: ",
6075
- /* @__PURE__ */ jsx17(Text16, { color: theme.primary, bold: true, children: choice.toUpperCase() })
6319
+ "[p] Platform:",
6320
+ " ",
6321
+ /* @__PURE__ */ jsx17(
6322
+ Text16,
6323
+ {
6324
+ color: theme.primary,
6325
+ bold: true,
6326
+ inverse: focusedControl === "platform",
6327
+ children: defaultPlatform.toUpperCase()
6328
+ }
6329
+ )
6076
6330
  ] }),
6077
6331
  /* @__PURE__ */ jsxs14(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: [
6078
- "[l] Posts: ",
6079
- /* @__PURE__ */ jsx17(Text16, { color: theme.primary, bold: true, children: limit ? `${limit}` : "ALL" })
6332
+ "[c] Media:",
6333
+ " ",
6334
+ /* @__PURE__ */ jsx17(
6335
+ Text16,
6336
+ {
6337
+ color: theme.primary,
6338
+ bold: true,
6339
+ inverse: focusedControl === "choice",
6340
+ children: choice === "all" ? "EVERYTHING" : choice.toUpperCase()
6341
+ }
6342
+ )
6080
6343
  ] }),
6081
6344
  /* @__PURE__ */ jsxs14(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: [
6082
- "[d] Date: ",
6083
- /* @__PURE__ */ jsx17(Text16, { color: theme.primary, bold: true, children: dateFilter.toUpperCase() })
6345
+ "[l] Posts:",
6346
+ " ",
6347
+ /* @__PURE__ */ jsx17(
6348
+ Text16,
6349
+ {
6350
+ color: theme.primary,
6351
+ bold: true,
6352
+ inverse: focusedControl === "limit",
6353
+ children: limit ? `${limit}` : "ALL"
6354
+ }
6355
+ )
6084
6356
  ] })
6085
6357
  ] }),
6086
6358
  /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", marginTop: 1, children: [
6087
6359
  /* @__PURE__ */ jsxs14(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: [
6088
- "[m] Meta: ",
6089
- /* @__PURE__ */ jsx17(Text16, { color: theme.primary, bold: true, children: writeMetadata ? "ON" : "OFF" })
6360
+ "[d] Date:",
6361
+ " ",
6362
+ /* @__PURE__ */ jsx17(
6363
+ Text16,
6364
+ {
6365
+ color: theme.primary,
6366
+ bold: true,
6367
+ inverse: focusedControl === "date",
6368
+ children: dateFilter.toUpperCase()
6369
+ }
6370
+ )
6090
6371
  ] }),
6091
6372
  /* @__PURE__ */ jsxs14(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: [
6092
- "[r] Resume: ",
6093
- /* @__PURE__ */ jsx17(Text16, { color: theme.primary, bold: true, children: resume ? "ON" : "OFF" })
6373
+ "[m] Meta:",
6374
+ " ",
6375
+ /* @__PURE__ */ jsx17(
6376
+ Text16,
6377
+ {
6378
+ color: theme.primary,
6379
+ bold: true,
6380
+ inverse: focusedControl === "meta",
6381
+ children: writeMetadata ? "ON" : "OFF"
6382
+ }
6383
+ )
6384
+ ] }),
6385
+ /* @__PURE__ */ jsxs14(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: [
6386
+ "[r] Resume:",
6387
+ " ",
6388
+ /* @__PURE__ */ jsx17(
6389
+ Text16,
6390
+ {
6391
+ color: theme.primary,
6392
+ bold: true,
6393
+ inverse: focusedControl === "resume",
6394
+ children: resume ? "ON" : "OFF"
6395
+ }
6396
+ )
6094
6397
  ] }),
6095
- /* @__PURE__ */ jsx17(Text16, { color: theme.primary, bold: true, children: "[\u21B5] Start Queue" })
6398
+ /* @__PURE__ */ jsx17(
6399
+ Text16,
6400
+ {
6401
+ color: theme.primary,
6402
+ bold: true,
6403
+ inverse: focusedControl === "start",
6404
+ children: "[\u21B5] Start Queue"
6405
+ }
6406
+ )
6096
6407
  ] })
6097
6408
  ] })
6098
6409
  }
6099
6410
  ),
6100
- /* @__PURE__ */ jsx17(Box14, { marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: focusSection === "input" ? "Press \u2193 to adjust options \xB7 \u21B5 to start queue" : "Hotkeys [c] [l] [d] [m] [r] \xB7 \u2191 to edit text \xB7 \u21B5 to start" }) })
6411
+ /* @__PURE__ */ jsx17(Box14, { marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: focusedControl === "input" ? "Type handles or URLs \xB7 \u21B5 to start \xB7 \u2193 to customize options" : focusedControl === "tabs" ? "Space to switch mode \xB7 \u2193 to edit input" : "\u2190/\u2192/Tab choose option \xB7 Space/Enter toggle \xB7 \u21B5 start queue \xB7 \u2191 edit input" }) })
6101
6412
  ] }) }),
6102
6413
  phase === "running" && /* @__PURE__ */ jsx17(Panel, { title: "Batch Queue Running", width: Math.max(60, boxWidth), children: /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingY: 1, children: [
6103
6414
  /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", marginBottom: 1, children: [
@@ -6337,6 +6648,7 @@ function InnerApp({
6337
6648
  const [urlInput, setUrlInput] = useState9(initialInput ?? "");
6338
6649
  const [clipboardUrl, setClipboardUrl] = useState9(void 0);
6339
6650
  const [settingsFocus, setSettingsFocus] = useState9("cookies");
6651
+ const [batchClickTargets, setBatchClickTargets] = useState9([]);
6340
6652
  const columns = stdout?.columns && stdout.columns > 0 ? stdout.columns : 80;
6341
6653
  const boxWidth = Math.max(14, Math.min(64, columns - 6));
6342
6654
  const contentWidth = Math.max(10, Math.min(columns - 4, 78));
@@ -6352,7 +6664,7 @@ function InnerApp({
6352
6664
  }
6353
6665
  return { name: "input" };
6354
6666
  });
6355
- useEffect7(() => {
6667
+ useEffect8(() => {
6356
6668
  try {
6357
6669
  const clip = readClipboard();
6358
6670
  if (clip && (clip.startsWith("http://") || clip.startsWith("https://") || clip.startsWith("@"))) {
@@ -6365,7 +6677,7 @@ function InnerApp({
6365
6677
  const clipboardAccepted = Boolean(clipboardUrl) && urlInput === clipboardUrl;
6366
6678
  const abortControllerRef = useRef5(null);
6367
6679
  const probingTimeoutRef = useRef5(null);
6368
- useEffect7(() => {
6680
+ useEffect8(() => {
6369
6681
  return () => {
6370
6682
  abortControllerRef.current?.abort();
6371
6683
  if (probingTimeoutRef.current) {
@@ -6506,10 +6818,13 @@ function InnerApp({
6506
6818
  return;
6507
6819
  }
6508
6820
  if (key.escape) {
6509
- if (phase.name === "settings" || phase.name === "watcher" || phase.name === "batch") {
6821
+ if (phase.name === "settings" || phase.name === "watcher") {
6510
6822
  setPhase({ name: "input" });
6511
6823
  return;
6512
6824
  }
6825
+ if (phase.name === "batch") {
6826
+ return;
6827
+ }
6513
6828
  if (phase.name === "platform") {
6514
6829
  setPhase({ name: "input" });
6515
6830
  return;
@@ -6601,6 +6916,9 @@ function InnerApp({
6601
6916
  targets.push({ match: "Cookies Folder", padX: 2, padY: 1, action: () => setSettingsFocus("cookies") });
6602
6917
  targets.push({ match: "Download Directory", padX: 2, padY: 1, action: () => setSettingsFocus("download") });
6603
6918
  }
6919
+ if (phase.name === "batch") {
6920
+ targets.push(...batchClickTargets);
6921
+ }
6604
6922
  for (const [key, label] of hints) {
6605
6923
  const action = hintAction(key);
6606
6924
  if (action) targets.push({ match: `${key} ${label}`, action });
@@ -6620,7 +6938,8 @@ function InnerApp({
6620
6938
  handleInputSubmit,
6621
6939
  handlePlatformSelect,
6622
6940
  handleModeSelect,
6623
- resetToInput
6941
+ resetToInput,
6942
+ batchClickTargets
6624
6943
  ]);
6625
6944
  useMouseClick(
6626
6945
  (x, y) => {
@@ -6761,7 +7080,8 @@ function InnerApp({
6761
7080
  gapComponent: Gap,
6762
7081
  defaultChoice: initialChoice,
6763
7082
  defaultLimit: effectiveLimit,
6764
- defaultWriteMetadata: effectiveWriteMetadata
7083
+ defaultWriteMetadata: effectiveWriteMetadata,
7084
+ onRegisterClickTargets: setBatchClickTargets
6765
7085
  }
6766
7086
  ),
6767
7087
  hints.length > 0 ? /* @__PURE__ */ jsxs15(Fragment5, { children: [
@@ -7114,6 +7434,7 @@ async function main() {
7114
7434
  let themeMode = "auto";
7115
7435
  let isWatchMode = false;
7116
7436
  let batchFile;
7437
+ let cliPlatform;
7117
7438
  let since;
7118
7439
  let until;
7119
7440
  let limit;
@@ -7163,6 +7484,16 @@ async function main() {
7163
7484
  batchFile = args[++i];
7164
7485
  } else if (arg.startsWith("--batch=")) {
7165
7486
  batchFile = arg.slice("--batch=".length);
7487
+ } else if (arg === "-p" || arg === "--platform") {
7488
+ const val = args[++i]?.toLowerCase();
7489
+ if (val === "instagram" || val === "tiktok" || val === "facebook" || val === "x") {
7490
+ cliPlatform = val;
7491
+ }
7492
+ } else if (arg.startsWith("--platform=")) {
7493
+ const val = arg.slice("--platform=".length).toLowerCase();
7494
+ if (val === "instagram" || val === "tiktok" || val === "facebook" || val === "x") {
7495
+ cliPlatform = val;
7496
+ }
7166
7497
  } else if (!arg.startsWith("-") && !initialInput) {
7167
7498
  initialInput = arg;
7168
7499
  }
@@ -7226,6 +7557,7 @@ async function main() {
7226
7557
  const result = await runBatchQueue({
7227
7558
  filePath: batchFile,
7228
7559
  choice,
7560
+ defaultPlatform: cliPlatform || "instagram",
7229
7561
  outputDir,
7230
7562
  since,
7231
7563
  until,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-nami",
3
- "version": "1.2.4",
3
+ "version": "1.2.5",
4
4
  "description": "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
5
5
  "type": "module",
6
6
  "bin": {