open-nami 1.2.4 → 1.2.6

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 +594 -118
  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.6",
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,6 +5755,8 @@ 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"];
5749
5762
  function BatchView({
@@ -5754,16 +5767,18 @@ function BatchView({
5754
5767
  gapComponent: Gap2,
5755
5768
  defaultChoice = "all",
5756
5769
  defaultLimit,
5757
- defaultWriteMetadata = false
5770
+ defaultWriteMetadata = false,
5771
+ onRegisterClickTargets
5758
5772
  }) {
5759
5773
  const [phase, setPhase] = useState8("input");
5760
5774
  const [inputField, setInputField] = useState8("text");
5761
5775
  const [rawText, setRawText] = useState8("");
5762
5776
  const [filePath, setFilePath] = useState8("");
5763
5777
  const [errorMessage, setErrorMessage] = useState8(null);
5764
- const [focusSection, setFocusSection] = useState8("input");
5778
+ const [focusedControl, setFocusedControl] = useState8("input");
5765
5779
  const [customLimitText, setCustomLimitText] = useState8("");
5766
5780
  const [customDateText, setCustomDateText] = useState8("");
5781
+ const [defaultPlatform, setDefaultPlatform] = useState8("instagram");
5767
5782
  const [choice, setChoice] = useState8(defaultChoice);
5768
5783
  const [limit, setLimit] = useState8(defaultLimit);
5769
5784
  const [dateFilter, setDateFilter] = useState8("all");
@@ -5775,11 +5790,16 @@ function BatchView({
5775
5790
  const [isCooldown, setIsCooldown] = useState8(false);
5776
5791
  const [result, setResult] = useState8(null);
5777
5792
  const abortControllerRef = useRef4(null);
5793
+ const cyclePlatform = useCallback4(() => {
5794
+ setDefaultPlatform((prev) => {
5795
+ const idx = PLATFORMS.indexOf(prev);
5796
+ return PLATFORMS[(idx + 1) % PLATFORMS.length];
5797
+ });
5798
+ }, []);
5778
5799
  const cycleChoice = useCallback4(() => {
5779
5800
  setChoice((prev) => {
5780
- if (prev === "all") return "photos";
5781
- if (prev === "photos") return "videos";
5782
- return "all";
5801
+ const idx = CHOICES.indexOf(prev);
5802
+ return CHOICES[(idx + 1) % CHOICES.length];
5783
5803
  });
5784
5804
  }, []);
5785
5805
  const cycleLimit = useCallback4(() => {
@@ -5788,7 +5808,7 @@ function BatchView({
5788
5808
  const nextVal = LIMIT_PRESETS2[nextIdx];
5789
5809
  if (nextVal === "custom") {
5790
5810
  setCustomLimitText(limit ? String(limit) : "24");
5791
- setFocusSection("custom-limit");
5811
+ setFocusedControl("custom-limit");
5792
5812
  } else {
5793
5813
  setLimit(nextVal);
5794
5814
  }
@@ -5798,8 +5818,8 @@ function BatchView({
5798
5818
  const nextIdx = (curIdx + 1) % DATE_PRESETS2.length;
5799
5819
  const nextVal = DATE_PRESETS2[nextIdx];
5800
5820
  if (nextVal === "custom") {
5801
- setCustomDateText(dateFilter !== "all" ? dateFilter : "2021");
5802
- setFocusSection("custom-date");
5821
+ setCustomDateText(dateFilter !== "all" ? dateFilter : "2024");
5822
+ setFocusedControl("custom-date");
5803
5823
  } else {
5804
5824
  setDateFilter(nextVal);
5805
5825
  }
@@ -5824,7 +5844,11 @@ function BatchView({
5824
5844
  return;
5825
5845
  }
5826
5846
  } else {
5827
- lines = rawText.split(/[\n,;]+/).map((s) => s.trim()).filter(Boolean);
5847
+ lines = Array.from(
5848
+ new Set(
5849
+ rawText.split(/[\r\n,;\s]+/).map((s) => s.trim()).filter(Boolean)
5850
+ )
5851
+ );
5828
5852
  if (lines.length === 0) {
5829
5853
  setErrorMessage("Please enter at least one profile URL or username");
5830
5854
  return;
@@ -5839,6 +5863,7 @@ function BatchView({
5839
5863
  filePath: inputField === "file" ? filePath.trim() : void 0,
5840
5864
  lines: inputField === "text" ? lines : void 0,
5841
5865
  choice,
5866
+ defaultPlatform,
5842
5867
  outputDir,
5843
5868
  since: dateRange.since,
5844
5869
  until: dateRange.until,
@@ -5849,7 +5874,7 @@ function BatchView({
5849
5874
  onAccountStart: (item, idx) => {
5850
5875
  setIsCooldown(false);
5851
5876
  setCurrentIndex(idx);
5852
- setCurrentProgressText(`Probing @${item.profile.username || item.raw}\u2026`);
5877
+ setCurrentProgressText(`Probing @${item.profile.username || item.raw} on ${item.profile.platform}\u2026`);
5853
5878
  setQueueItems((prev) => {
5854
5879
  const next = [...prev];
5855
5880
  if (next[idx]) next[idx] = { ...item };
@@ -5891,15 +5916,18 @@ function BatchView({
5891
5916
  setErrorMessage(msg);
5892
5917
  setPhase("input");
5893
5918
  }
5894
- }, [inputField, filePath, rawText, choice, outputDir, dateFilter, limit, resume, writeMetadata]);
5919
+ }, [inputField, filePath, rawText, choice, defaultPlatform, outputDir, dateFilter, limit, resume, writeMetadata]);
5895
5920
  useInput5((input, key) => {
5921
+ if (key.ctrl && (input === "c" || input === "C")) {
5922
+ return;
5923
+ }
5896
5924
  if (key.escape) {
5897
- if (focusSection === "custom-limit" || focusSection === "custom-date") {
5898
- setFocusSection("options");
5925
+ if (focusedControl === "custom-limit" || focusedControl === "custom-date") {
5926
+ setFocusedControl("start");
5899
5927
  return;
5900
5928
  }
5901
- if (focusSection === "options") {
5902
- setFocusSection("input");
5929
+ if (focusedControl !== "input" && phase === "input") {
5930
+ setFocusedControl("input");
5903
5931
  return;
5904
5932
  }
5905
5933
  if (phase === "running") {
@@ -5922,107 +5950,471 @@ function BatchView({
5922
5950
  }
5923
5951
  return;
5924
5952
  }
5953
+ if (phase === "running") {
5954
+ return;
5955
+ }
5925
5956
  if (phase === "input") {
5926
- if (focusSection === "custom-limit" || focusSection === "custom-date") {
5957
+ if (focusedControl === "custom-limit" || focusedControl === "custom-date") {
5927
5958
  return;
5928
5959
  }
5929
- if (key.ctrl) {
5930
- if (input === "l" || input === "L") {
5931
- setCustomLimitText(limit ? String(limit) : "24");
5932
- setFocusSection("custom-limit");
5960
+ if (focusedControl === "input") {
5961
+ if (key.downArrow) {
5962
+ setFocusedControl("platform");
5933
5963
  return;
5934
5964
  }
5935
- if (input === "d" || input === "D") {
5936
- setCustomDateText(dateFilter !== "all" ? dateFilter : "2021");
5937
- setFocusSection("custom-date");
5965
+ if (key.upArrow) {
5966
+ setFocusedControl("tabs");
5938
5967
  return;
5939
5968
  }
5940
- if (input === "c" || input === "C") {
5941
- cycleChoice();
5969
+ return;
5970
+ }
5971
+ if (focusedControl === "tabs") {
5972
+ if (key.leftArrow || key.rightArrow || input === " " || key.return) {
5973
+ setInputField((prev) => prev === "text" ? "file" : "text");
5942
5974
  return;
5943
5975
  }
5944
- if (input === "m" || input === "M") {
5945
- toggleMetadata();
5976
+ if (key.downArrow || key.tab) {
5977
+ setFocusedControl("input");
5946
5978
  return;
5947
5979
  }
5948
- if (input === "r" || input === "R") {
5949
- toggleResume();
5980
+ return;
5981
+ }
5982
+ if (focusedControl === "platform") {
5983
+ if (key.rightArrow) {
5984
+ setFocusedControl("choice");
5950
5985
  return;
5951
5986
  }
5952
- }
5953
- if (focusSection === "input") {
5954
5987
  if (key.downArrow) {
5955
- setFocusSection("options");
5988
+ setFocusedControl("limit");
5956
5989
  return;
5957
5990
  }
5958
- } else if (focusSection === "options") {
5959
5991
  if (key.upArrow) {
5960
- setFocusSection("input");
5992
+ setFocusedControl("input");
5961
5993
  return;
5962
5994
  }
5963
- if (key.return) {
5964
- startQueue();
5995
+ if (key.tab && !key.shift) {
5996
+ setFocusedControl("choice");
5965
5997
  return;
5966
5998
  }
5967
- if (input === "c" || input === "C") {
5968
- cycleChoice();
5999
+ if (key.tab && key.shift) {
6000
+ setFocusedControl("input");
5969
6001
  return;
5970
6002
  }
5971
- if (input === "l" || input === "L") {
5972
- cycleLimit();
6003
+ } else if (focusedControl === "choice") {
6004
+ if (key.leftArrow) {
6005
+ setFocusedControl("platform");
5973
6006
  return;
5974
6007
  }
5975
- if (input === "d" || input === "D") {
5976
- cycleDate();
6008
+ if (key.downArrow) {
6009
+ setFocusedControl("date");
5977
6010
  return;
5978
6011
  }
5979
- if (input === "m" || input === "M") {
5980
- toggleMetadata();
6012
+ if (key.upArrow) {
6013
+ setFocusedControl("input");
5981
6014
  return;
5982
6015
  }
5983
- if (input === "r" || input === "R") {
5984
- toggleResume();
6016
+ if (key.tab && !key.shift) {
6017
+ setFocusedControl("limit");
6018
+ return;
6019
+ }
6020
+ if (key.tab && key.shift) {
6021
+ setFocusedControl("platform");
6022
+ return;
6023
+ }
6024
+ } else if (focusedControl === "limit") {
6025
+ if (key.upArrow) {
6026
+ setFocusedControl("platform");
6027
+ return;
6028
+ }
6029
+ if (key.rightArrow) {
6030
+ setFocusedControl("date");
6031
+ return;
6032
+ }
6033
+ if (key.downArrow) {
6034
+ setFocusedControl("meta");
6035
+ return;
6036
+ }
6037
+ if (key.tab && !key.shift) {
6038
+ setFocusedControl("date");
6039
+ return;
6040
+ }
6041
+ if (key.tab && key.shift) {
6042
+ setFocusedControl("choice");
6043
+ return;
6044
+ }
6045
+ } else if (focusedControl === "date") {
6046
+ if (key.upArrow) {
6047
+ setFocusedControl("choice");
6048
+ return;
6049
+ }
6050
+ if (key.leftArrow) {
6051
+ setFocusedControl("limit");
6052
+ return;
6053
+ }
6054
+ if (key.downArrow) {
6055
+ setFocusedControl("resume");
6056
+ return;
6057
+ }
6058
+ if (key.tab && !key.shift) {
6059
+ setFocusedControl("meta");
6060
+ return;
6061
+ }
6062
+ if (key.tab && key.shift) {
6063
+ setFocusedControl("limit");
6064
+ return;
6065
+ }
6066
+ } else if (focusedControl === "meta") {
6067
+ if (key.upArrow) {
6068
+ setFocusedControl("limit");
6069
+ return;
6070
+ }
6071
+ if (key.rightArrow) {
6072
+ setFocusedControl("resume");
6073
+ return;
6074
+ }
6075
+ if (key.downArrow) {
6076
+ setFocusedControl("start");
6077
+ return;
6078
+ }
6079
+ if (key.tab && !key.shift) {
6080
+ setFocusedControl("resume");
6081
+ return;
6082
+ }
6083
+ if (key.tab && key.shift) {
6084
+ setFocusedControl("date");
6085
+ return;
6086
+ }
6087
+ } else if (focusedControl === "resume") {
6088
+ if (key.upArrow) {
6089
+ setFocusedControl("date");
6090
+ return;
6091
+ }
6092
+ if (key.leftArrow) {
6093
+ setFocusedControl("meta");
6094
+ return;
6095
+ }
6096
+ if (key.downArrow) {
6097
+ setFocusedControl("start");
6098
+ return;
6099
+ }
6100
+ if (key.tab && !key.shift) {
6101
+ setFocusedControl("start");
6102
+ return;
6103
+ }
6104
+ if (key.tab && key.shift) {
6105
+ setFocusedControl("meta");
6106
+ return;
6107
+ }
6108
+ } else if (focusedControl === "start") {
6109
+ if (key.upArrow) {
6110
+ setFocusedControl("meta");
6111
+ return;
6112
+ }
6113
+ if (key.downArrow || key.tab && !key.shift) {
6114
+ setFocusedControl("tabs");
6115
+ return;
6116
+ }
6117
+ if (key.tab && key.shift) {
6118
+ setFocusedControl("resume");
5985
6119
  return;
5986
6120
  }
5987
6121
  }
6122
+ if (input === " " || key.return) {
6123
+ switch (focusedControl) {
6124
+ case "platform":
6125
+ cyclePlatform();
6126
+ break;
6127
+ case "choice":
6128
+ cycleChoice();
6129
+ break;
6130
+ case "limit":
6131
+ cycleLimit();
6132
+ break;
6133
+ case "date":
6134
+ cycleDate();
6135
+ break;
6136
+ case "meta":
6137
+ toggleMetadata();
6138
+ break;
6139
+ case "resume":
6140
+ toggleResume();
6141
+ break;
6142
+ case "start":
6143
+ startQueue();
6144
+ break;
6145
+ }
6146
+ return;
6147
+ }
6148
+ if (input === "p" || input === "P") {
6149
+ cyclePlatform();
6150
+ return;
6151
+ }
6152
+ if (input === "c" || input === "C") {
6153
+ cycleChoice();
6154
+ return;
6155
+ }
6156
+ if (input === "l" || input === "L") {
6157
+ cycleLimit();
6158
+ return;
6159
+ }
6160
+ if (input === "d" || input === "D") {
6161
+ cycleDate();
6162
+ return;
6163
+ }
6164
+ if (input === "m" || input === "M") {
6165
+ toggleMetadata();
6166
+ return;
6167
+ }
6168
+ if (input === "r" || input === "R") {
6169
+ toggleResume();
6170
+ return;
6171
+ }
5988
6172
  }
5989
6173
  }, { isActive: Boolean(process.stdin.isTTY) });
6174
+ useEffect7(() => {
6175
+ if (!onRegisterClickTargets) return;
6176
+ const targets = [];
6177
+ if (phase === "input") {
6178
+ targets.push({
6179
+ match: "Links / Handles",
6180
+ padX: 2,
6181
+ padY: 0,
6182
+ action: () => {
6183
+ setInputField("text");
6184
+ setFocusedControl("input");
6185
+ }
6186
+ });
6187
+ targets.push({
6188
+ match: "File (.txt)",
6189
+ padX: 2,
6190
+ padY: 0,
6191
+ action: () => {
6192
+ setInputField("file");
6193
+ setFocusedControl("input");
6194
+ }
6195
+ });
6196
+ targets.push({
6197
+ match: "download",
6198
+ padX: 2,
6199
+ padY: 1,
6200
+ action: startQueue
6201
+ });
6202
+ targets.push({
6203
+ match: "Platform:",
6204
+ padX: 2,
6205
+ padY: 0,
6206
+ action: () => {
6207
+ setFocusedControl("platform");
6208
+ cyclePlatform();
6209
+ }
6210
+ });
6211
+ targets.push({
6212
+ match: "Media:",
6213
+ padX: 2,
6214
+ padY: 0,
6215
+ action: () => {
6216
+ setFocusedControl("choice");
6217
+ cycleChoice();
6218
+ }
6219
+ });
6220
+ targets.push({
6221
+ match: "Posts:",
6222
+ padX: 2,
6223
+ padY: 0,
6224
+ action: () => {
6225
+ setFocusedControl("limit");
6226
+ cycleLimit();
6227
+ }
6228
+ });
6229
+ targets.push({
6230
+ match: "Date:",
6231
+ padX: 2,
6232
+ padY: 0,
6233
+ action: () => {
6234
+ setFocusedControl("date");
6235
+ cycleDate();
6236
+ }
6237
+ });
6238
+ targets.push({
6239
+ match: "Meta:",
6240
+ padX: 2,
6241
+ padY: 0,
6242
+ action: () => {
6243
+ setFocusedControl("meta");
6244
+ toggleMetadata();
6245
+ }
6246
+ });
6247
+ targets.push({
6248
+ match: "Resume:",
6249
+ padX: 2,
6250
+ padY: 0,
6251
+ action: () => {
6252
+ setFocusedControl("resume");
6253
+ toggleResume();
6254
+ }
6255
+ });
6256
+ targets.push({
6257
+ match: "Start Batch Queue",
6258
+ padX: 3,
6259
+ padY: 0,
6260
+ action: () => {
6261
+ setFocusedControl("start");
6262
+ startQueue();
6263
+ }
6264
+ });
6265
+ targets.push({
6266
+ match: "tab switch field",
6267
+ action: () => setInputField((prev) => prev === "text" ? "file" : "text")
6268
+ });
6269
+ targets.push({
6270
+ match: "c target",
6271
+ action: cycleChoice
6272
+ });
6273
+ targets.push({
6274
+ match: "m sidecar",
6275
+ action: toggleMetadata
6276
+ });
6277
+ targets.push({
6278
+ match: "l limit",
6279
+ action: cycleLimit
6280
+ });
6281
+ targets.push({
6282
+ match: "d date",
6283
+ action: cycleDate
6284
+ });
6285
+ targets.push({
6286
+ match: "r resume",
6287
+ action: toggleResume
6288
+ });
6289
+ targets.push({
6290
+ match: "\u21B5 start",
6291
+ action: startQueue
6292
+ });
6293
+ }
6294
+ if (phase === "running") {
6295
+ targets.push({
6296
+ match: "Stop queue and return",
6297
+ padX: 2,
6298
+ action: () => {
6299
+ abortControllerRef.current?.abort();
6300
+ setPhase("input");
6301
+ }
6302
+ });
6303
+ targets.push({
6304
+ match: "Stop queue",
6305
+ padX: 2,
6306
+ action: () => {
6307
+ abortControllerRef.current?.abort();
6308
+ setPhase("input");
6309
+ }
6310
+ });
6311
+ }
6312
+ if (phase === "done") {
6313
+ targets.push({
6314
+ match: "Open Downloads Folder",
6315
+ padX: 2,
6316
+ action: () => {
6317
+ const target = outputDir || resolveDefaultDownloadDir("instagram", "batch");
6318
+ revealInFileManager(target);
6319
+ }
6320
+ });
6321
+ targets.push({
6322
+ match: "Return to Start",
6323
+ padX: 2,
6324
+ action: () => setPhase("input")
6325
+ });
6326
+ }
6327
+ onRegisterClickTargets(targets);
6328
+ return () => {
6329
+ onRegisterClickTargets([]);
6330
+ };
6331
+ }, [
6332
+ phase,
6333
+ inputField,
6334
+ focusedControl,
6335
+ onRegisterClickTargets,
6336
+ cyclePlatform,
6337
+ cycleChoice,
6338
+ cycleLimit,
6339
+ cycleDate,
6340
+ toggleMetadata,
6341
+ toggleResume,
6342
+ startQueue,
6343
+ outputDir
6344
+ ]);
6345
+ const panelWidth = Math.max(64, boxWidth);
6346
+ const inputFrameWidth = panelWidth - 18;
6347
+ const innerContentWidth = panelWidth - 6;
6348
+ const colWidth = Math.floor((innerContentWidth - 4) / 2);
5990
6349
  const currentItem = queueItems[currentIndex];
5991
6350
  const completedCount = queueItems.filter((i) => i.status === "success" || i.status === "failed").length;
5992
6351
  const totalCount = queueItems.length > 0 ? queueItems.length : 1;
5993
6352
  const percent = Math.min(100, Math.floor(completedCount / totalCount * 100));
5994
6353
  return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", alignItems: "center", children: [
5995
- phase === "input" && /* @__PURE__ */ jsx17(Panel, { title: "Batch Queue", width: Math.max(60, boxWidth), children: /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingY: 1, children: [
6354
+ phase === "input" && /* @__PURE__ */ jsx17(Panel, { title: "Batch Queue", width: panelWidth, children: /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", alignItems: "center", paddingY: 1, children: [
5996
6355
  /* @__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" }),
5998
- /* @__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)" })
6356
+ /* @__PURE__ */ jsx17(Box14, { marginRight: 2, children: /* @__PURE__ */ jsx17(
6357
+ Text16,
6358
+ {
6359
+ bold: true,
6360
+ color: inputField === "text" ? theme.primary : theme.gray,
6361
+ inverse: inputField === "text",
6362
+ children: " Links / Handles "
6363
+ }
6364
+ ) }),
6365
+ /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsx17(
6366
+ Text16,
6367
+ {
6368
+ bold: true,
6369
+ color: inputField === "file" ? theme.primary : theme.gray,
6370
+ inverse: inputField === "file",
6371
+ children: " File (.txt) "
6372
+ }
6373
+ ) })
6000
6374
  ] }),
6001
- inputField === "text" ? /* @__PURE__ */ jsx17(FramedInput, { title: "Links or @handles", width: Math.max(56, boxWidth - 4), children: /* @__PURE__ */ jsx17(
6002
- TextInput,
6375
+ inputField === "text" ? /* @__PURE__ */ jsx17(
6376
+ FramedInput,
6003
6377
  {
6004
- value: rawText,
6005
- onChange: setRawText,
6006
- onSubmit: startQueue,
6007
- placeholder: "@user1, @user2, tiktok.com/@user3\u2026",
6008
- width: Math.max(50, boxWidth - 10),
6009
- focus: focusSection === "input",
6010
- onTab: () => setInputField("file"),
6011
- onArrowDown: () => setFocusSection("options")
6012
- }
6013
- ) }) : /* @__PURE__ */ jsx17(FramedInput, { title: "Batch file (.txt)", width: Math.max(56, boxWidth - 4), children: /* @__PURE__ */ jsx17(
6014
- TextInput,
6378
+ title: "Links or @handles",
6379
+ width: inputFrameWidth,
6380
+ button: "download",
6381
+ children: /* @__PURE__ */ jsx17(
6382
+ TextInput,
6383
+ {
6384
+ value: rawText,
6385
+ onChange: setRawText,
6386
+ onSubmit: startQueue,
6387
+ placeholder: "@user1, @user2, tiktok.com/@user3\u2026",
6388
+ width: inputFrameWidth - 4,
6389
+ focus: focusedControl === "input",
6390
+ onArrowDown: () => setFocusedControl("platform"),
6391
+ onArrowUp: () => setFocusedControl("tabs"),
6392
+ onTab: () => setFocusedControl("platform")
6393
+ }
6394
+ )
6395
+ }
6396
+ ) : /* @__PURE__ */ jsx17(
6397
+ FramedInput,
6015
6398
  {
6016
- value: filePath,
6017
- onChange: setFilePath,
6018
- onSubmit: startQueue,
6019
- placeholder: "e.g. creators.txt or C:\\path\\creators.txt",
6020
- width: Math.max(50, boxWidth - 10),
6021
- focus: focusSection === "input",
6022
- onTab: () => setInputField("text"),
6023
- onArrowDown: () => setFocusSection("options")
6399
+ title: "Batch file (.txt)",
6400
+ width: inputFrameWidth,
6401
+ button: "download",
6402
+ children: /* @__PURE__ */ jsx17(
6403
+ TextInput,
6404
+ {
6405
+ value: filePath,
6406
+ onChange: setFilePath,
6407
+ onSubmit: startQueue,
6408
+ placeholder: "e.g. creators.txt or C:\\path\\creators.txt",
6409
+ width: inputFrameWidth - 4,
6410
+ focus: focusedControl === "input",
6411
+ onArrowDown: () => setFocusedControl("platform"),
6412
+ onArrowUp: () => setFocusedControl("tabs"),
6413
+ onTab: () => setFocusedControl("platform")
6414
+ }
6415
+ )
6024
6416
  }
6025
- ) }),
6417
+ ),
6026
6418
  errorMessage ? /* @__PURE__ */ jsx17(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text16, { color: "red", children: [
6027
6419
  "\u2717 ",
6028
6420
  errorMessage
@@ -6031,12 +6423,13 @@ function BatchView({
6031
6423
  Box14,
6032
6424
  {
6033
6425
  flexDirection: "column",
6426
+ width: innerContentWidth,
6034
6427
  marginTop: 1,
6035
6428
  borderStyle: "round",
6036
- borderColor: focusSection === "options" || focusSection === "custom-limit" || focusSection === "custom-date" ? theme.primary : theme.gray,
6037
- borderDimColor: focusSection === "input",
6429
+ borderColor: focusedControl !== "input" && focusedControl !== "tabs" && focusedControl !== "start" ? theme.primary : theme.gray,
6430
+ borderDimColor: focusedControl === "input" || focusedControl === "tabs" || focusedControl === "start",
6038
6431
  paddingX: 1,
6039
- children: focusSection === "custom-limit" ? /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
6432
+ children: focusedControl === "custom-limit" ? /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
6040
6433
  /* @__PURE__ */ jsx17(Text16, { bold: true, color: theme.primary, children: "Set post limit (e.g. 24, all):" }),
6041
6434
  /* @__PURE__ */ jsx17(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx17(
6042
6435
  TextInput,
@@ -6046,14 +6439,14 @@ function BatchView({
6046
6439
  onSubmit: (val) => {
6047
6440
  const parsed = parseLimit(val);
6048
6441
  setLimit(parsed);
6049
- setFocusSection("options");
6442
+ setFocusedControl("limit");
6050
6443
  },
6051
6444
  width: 24
6052
6445
  }
6053
6446
  ) }),
6054
6447
  /* @__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):" }),
6448
+ ] }) : focusedControl === "custom-date" ? /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
6449
+ /* @__PURE__ */ jsx17(Text16, { bold: true, color: theme.primary, children: "Set date or year (e.g. 2024, 7d):" }),
6057
6450
  /* @__PURE__ */ jsx17(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx17(
6058
6451
  TextInput,
6059
6452
  {
@@ -6062,44 +6455,106 @@ function BatchView({
6062
6455
  onSubmit: (val) => {
6063
6456
  const trimmed = val.trim();
6064
6457
  setDateFilter(trimmed ? trimmed : "all");
6065
- setFocusSection("options");
6458
+ setFocusedControl("date");
6066
6459
  },
6067
6460
  width: 24
6068
6461
  }
6069
6462
  ) }),
6070
6463
  /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "\u21B5 confirm \xB7 esc cancel" })
6071
6464
  ] }) : /* @__PURE__ */ jsxs14(Fragment4, { children: [
6072
- /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", children: [
6073
- /* @__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() })
6076
- ] }),
6077
- /* @__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" })
6465
+ /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", width: "100%", children: [
6466
+ /* @__PURE__ */ jsxs14(Box14, { width: colWidth, children: [
6467
+ /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "[p] Platform: " }),
6468
+ /* @__PURE__ */ jsx17(
6469
+ Text16,
6470
+ {
6471
+ bold: true,
6472
+ color: theme.primary,
6473
+ inverse: focusedControl === "platform",
6474
+ children: ` ${defaultPlatform.toUpperCase()} `
6475
+ }
6476
+ )
6080
6477
  ] }),
6081
- /* @__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() })
6478
+ /* @__PURE__ */ jsxs14(Box14, { width: colWidth, children: [
6479
+ /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "[c] Media: " }),
6480
+ /* @__PURE__ */ jsx17(
6481
+ Text16,
6482
+ {
6483
+ bold: true,
6484
+ color: theme.primary,
6485
+ inverse: focusedControl === "choice",
6486
+ children: ` ${choice === "all" ? "EVERYTHING" : choice.toUpperCase()} `
6487
+ }
6488
+ )
6084
6489
  ] })
6085
6490
  ] }),
6086
- /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", marginTop: 1, children: [
6087
- /* @__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" })
6491
+ /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", width: "100%", marginTop: 1, children: [
6492
+ /* @__PURE__ */ jsxs14(Box14, { width: colWidth, children: [
6493
+ /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "[l] Posts: " }),
6494
+ /* @__PURE__ */ jsx17(
6495
+ Text16,
6496
+ {
6497
+ bold: true,
6498
+ color: theme.primary,
6499
+ inverse: focusedControl === "limit",
6500
+ children: ` ${limit ? String(limit) : "ALL"} `
6501
+ }
6502
+ )
6090
6503
  ] }),
6091
- /* @__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" })
6504
+ /* @__PURE__ */ jsxs14(Box14, { width: colWidth, children: [
6505
+ /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "[d] Date: " }),
6506
+ /* @__PURE__ */ jsx17(
6507
+ Text16,
6508
+ {
6509
+ bold: true,
6510
+ color: theme.primary,
6511
+ inverse: focusedControl === "date",
6512
+ children: ` ${dateFilter.toUpperCase()} `
6513
+ }
6514
+ )
6515
+ ] })
6516
+ ] }),
6517
+ /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", width: "100%", marginTop: 1, children: [
6518
+ /* @__PURE__ */ jsxs14(Box14, { width: colWidth, children: [
6519
+ /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "[m] Meta: " }),
6520
+ /* @__PURE__ */ jsx17(
6521
+ Text16,
6522
+ {
6523
+ bold: true,
6524
+ color: theme.primary,
6525
+ inverse: focusedControl === "meta",
6526
+ children: ` ${writeMetadata ? "ON" : "OFF"} `
6527
+ }
6528
+ )
6094
6529
  ] }),
6095
- /* @__PURE__ */ jsx17(Text16, { color: theme.primary, bold: true, children: "[\u21B5] Start Queue" })
6530
+ /* @__PURE__ */ jsxs14(Box14, { width: colWidth, children: [
6531
+ /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "[r] Resume: " }),
6532
+ /* @__PURE__ */ jsx17(
6533
+ Text16,
6534
+ {
6535
+ bold: true,
6536
+ color: theme.primary,
6537
+ inverse: focusedControl === "resume",
6538
+ children: ` ${resume ? "ON" : "OFF"} `
6539
+ }
6540
+ )
6541
+ ] })
6096
6542
  ] })
6097
6543
  ] })
6098
6544
  }
6099
6545
  ),
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" }) })
6546
+ /* @__PURE__ */ jsx17(Box14, { justifyContent: "center", marginTop: 1, children: /* @__PURE__ */ jsx17(
6547
+ Text16,
6548
+ {
6549
+ bold: true,
6550
+ color: theme.primary,
6551
+ inverse: focusedControl === "start",
6552
+ children: " \u21B5 Start Batch Queue "
6553
+ }
6554
+ ) }),
6555
+ /* @__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 or click download to start" : focusedControl === "tabs" ? "Space to switch mode \xB7 \u2193 to edit input" : "\u2190/\u2192/Tab select \xB7 Space/Enter toggle \xB7 \u21B5 start queue \xB7 \u2191 edit input" }) })
6101
6556
  ] }) }),
6102
- phase === "running" && /* @__PURE__ */ jsx17(Panel, { title: "Batch Queue Running", width: Math.max(60, boxWidth), children: /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingY: 1, children: [
6557
+ phase === "running" && /* @__PURE__ */ jsx17(Panel, { title: "Batch Queue Running", width: panelWidth, children: /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingY: 1, children: [
6103
6558
  /* @__PURE__ */ jsxs14(Box14, { justifyContent: "space-between", marginBottom: 1, children: [
6104
6559
  /* @__PURE__ */ jsxs14(Text16, { color: theme.primary, bold: true, children: [
6105
6560
  "Account ",
@@ -6112,7 +6567,7 @@ function BatchView({
6112
6567
  ] }),
6113
6568
  /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: isCooldown ? "\u23F3 Cooldown active" : "\u25B6 Downloading" })
6114
6569
  ] }),
6115
- /* @__PURE__ */ jsx17(ProgressBar, { percent, width: 54 }),
6570
+ /* @__PURE__ */ jsx17(ProgressBar, { percent, width: panelWidth - 10 }),
6116
6571
  /* @__PURE__ */ jsx17(Gap2, {}),
6117
6572
  currentItem ? /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", borderStyle: "round", borderColor: theme.primary, paddingX: 1, paddingY: 1, children: [
6118
6573
  /* @__PURE__ */ jsxs14(Box14, { children: [
@@ -6134,7 +6589,7 @@ function BatchView({
6134
6589
  /* @__PURE__ */ jsx17(Gap2, {}),
6135
6590
  /* @__PURE__ */ jsx17(Text16, { color: theme.gray, dimColor: theme.dimSecondary, children: "[esc] Stop queue and return" })
6136
6591
  ] }) }),
6137
- phase === "done" && result && /* @__PURE__ */ jsx17(Panel, { title: "Batch Queue Completed", width: Math.max(60, boxWidth), children: /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingY: 1, children: [
6592
+ phase === "done" && result && /* @__PURE__ */ jsx17(Panel, { title: "Batch Queue Completed", width: panelWidth, children: /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingY: 1, children: [
6138
6593
  /* @__PURE__ */ jsxs14(Text16, { bold: true, color: theme.primary, children: [
6139
6594
  "\u2713 Successfully processed ",
6140
6595
  result.total,
@@ -6337,6 +6792,7 @@ function InnerApp({
6337
6792
  const [urlInput, setUrlInput] = useState9(initialInput ?? "");
6338
6793
  const [clipboardUrl, setClipboardUrl] = useState9(void 0);
6339
6794
  const [settingsFocus, setSettingsFocus] = useState9("cookies");
6795
+ const [batchClickTargets, setBatchClickTargets] = useState9([]);
6340
6796
  const columns = stdout?.columns && stdout.columns > 0 ? stdout.columns : 80;
6341
6797
  const boxWidth = Math.max(14, Math.min(64, columns - 6));
6342
6798
  const contentWidth = Math.max(10, Math.min(columns - 4, 78));
@@ -6352,7 +6808,7 @@ function InnerApp({
6352
6808
  }
6353
6809
  return { name: "input" };
6354
6810
  });
6355
- useEffect7(() => {
6811
+ useEffect8(() => {
6356
6812
  try {
6357
6813
  const clip = readClipboard();
6358
6814
  if (clip && (clip.startsWith("http://") || clip.startsWith("https://") || clip.startsWith("@"))) {
@@ -6365,7 +6821,7 @@ function InnerApp({
6365
6821
  const clipboardAccepted = Boolean(clipboardUrl) && urlInput === clipboardUrl;
6366
6822
  const abortControllerRef = useRef5(null);
6367
6823
  const probingTimeoutRef = useRef5(null);
6368
- useEffect7(() => {
6824
+ useEffect8(() => {
6369
6825
  return () => {
6370
6826
  abortControllerRef.current?.abort();
6371
6827
  if (probingTimeoutRef.current) {
@@ -6506,10 +6962,13 @@ function InnerApp({
6506
6962
  return;
6507
6963
  }
6508
6964
  if (key.escape) {
6509
- if (phase.name === "settings" || phase.name === "watcher" || phase.name === "batch") {
6965
+ if (phase.name === "settings" || phase.name === "watcher") {
6510
6966
  setPhase({ name: "input" });
6511
6967
  return;
6512
6968
  }
6969
+ if (phase.name === "batch") {
6970
+ return;
6971
+ }
6513
6972
  if (phase.name === "platform") {
6514
6973
  setPhase({ name: "input" });
6515
6974
  return;
@@ -6601,6 +7060,9 @@ function InnerApp({
6601
7060
  targets.push({ match: "Cookies Folder", padX: 2, padY: 1, action: () => setSettingsFocus("cookies") });
6602
7061
  targets.push({ match: "Download Directory", padX: 2, padY: 1, action: () => setSettingsFocus("download") });
6603
7062
  }
7063
+ if (phase.name === "batch") {
7064
+ targets.push(...batchClickTargets);
7065
+ }
6604
7066
  for (const [key, label] of hints) {
6605
7067
  const action = hintAction(key);
6606
7068
  if (action) targets.push({ match: `${key} ${label}`, action });
@@ -6620,7 +7082,8 @@ function InnerApp({
6620
7082
  handleInputSubmit,
6621
7083
  handlePlatformSelect,
6622
7084
  handleModeSelect,
6623
- resetToInput
7085
+ resetToInput,
7086
+ batchClickTargets
6624
7087
  ]);
6625
7088
  useMouseClick(
6626
7089
  (x, y) => {
@@ -6761,7 +7224,8 @@ function InnerApp({
6761
7224
  gapComponent: Gap,
6762
7225
  defaultChoice: initialChoice,
6763
7226
  defaultLimit: effectiveLimit,
6764
- defaultWriteMetadata: effectiveWriteMetadata
7227
+ defaultWriteMetadata: effectiveWriteMetadata,
7228
+ onRegisterClickTargets: setBatchClickTargets
6765
7229
  }
6766
7230
  ),
6767
7231
  hints.length > 0 ? /* @__PURE__ */ jsxs15(Fragment5, { children: [
@@ -7114,6 +7578,7 @@ async function main() {
7114
7578
  let themeMode = "auto";
7115
7579
  let isWatchMode = false;
7116
7580
  let batchFile;
7581
+ let cliPlatform;
7117
7582
  let since;
7118
7583
  let until;
7119
7584
  let limit;
@@ -7163,6 +7628,16 @@ async function main() {
7163
7628
  batchFile = args[++i];
7164
7629
  } else if (arg.startsWith("--batch=")) {
7165
7630
  batchFile = arg.slice("--batch=".length);
7631
+ } else if (arg === "-p" || arg === "--platform") {
7632
+ const val = args[++i]?.toLowerCase();
7633
+ if (val === "instagram" || val === "tiktok" || val === "facebook" || val === "x") {
7634
+ cliPlatform = val;
7635
+ }
7636
+ } else if (arg.startsWith("--platform=")) {
7637
+ const val = arg.slice("--platform=".length).toLowerCase();
7638
+ if (val === "instagram" || val === "tiktok" || val === "facebook" || val === "x") {
7639
+ cliPlatform = val;
7640
+ }
7166
7641
  } else if (!arg.startsWith("-") && !initialInput) {
7167
7642
  initialInput = arg;
7168
7643
  }
@@ -7226,6 +7701,7 @@ async function main() {
7226
7701
  const result = await runBatchQueue({
7227
7702
  filePath: batchFile,
7228
7703
  choice,
7704
+ defaultPlatform: cliPlatform || "instagram",
7229
7705
  outputDir,
7230
7706
  since,
7231
7707
  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.6",
4
4
  "description": "Open Nami: terminal-first bulk social media profile downloader for Instagram, TikTok, Facebook & X.",
5
5
  "type": "module",
6
6
  "bin": {