grab-url 1.0.6 → 1.0.8

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 (47) hide show
  1. package/dist/download.cjs.js +3 -0
  2. package/dist/download.cjs.js.map +1 -0
  3. package/dist/download.d.ts +1 -0
  4. package/dist/download.es.js +3715 -0
  5. package/dist/download.es.js.map +1 -0
  6. package/dist/grab-api.cjs.js +1 -1
  7. package/dist/grab-api.cjs.js.map +1 -1
  8. package/dist/grab-api.d.ts +64 -55
  9. package/dist/grab-api.es.js +87 -223
  10. package/dist/grab-api.es.js.map +1 -1
  11. package/dist/grab-api.umd.js +2 -0
  12. package/dist/grab-api.umd.js.map +1 -0
  13. package/dist/icons.cjs.js +1 -1
  14. package/dist/icons.cjs.js.map +1 -1
  15. package/dist/icons.d.ts +0 -0
  16. package/dist/icons.es.js +3 -3
  17. package/dist/icons.es.js.map +1 -1
  18. package/dist/log.cjs.js +2 -0
  19. package/dist/log.cjs.js.map +1 -0
  20. package/dist/log.d.ts +123 -0
  21. package/dist/log.es.js +299 -0
  22. package/dist/log.es.js.map +1 -0
  23. package/package.json +17 -13
  24. package/readme.md +7 -7
  25. package/src/grab-api.ts +114 -72
  26. package/src/grab-url.js +15 -81
  27. package/src/icons/cli/spinners.js +818 -0
  28. package/src/icons/svg/index.ts +0 -0
  29. package/src/icons/svg/loading-bouncy-ball.svg +0 -0
  30. package/src/icons/svg/loading-double-ring.svg +0 -0
  31. package/src/icons/svg/loading-eclipse.svg +0 -0
  32. package/src/icons/svg/loading-ellipsis.svg +0 -0
  33. package/src/icons/svg/loading-floating-search.svg +0 -0
  34. package/src/icons/svg/loading-gears.svg +0 -0
  35. package/src/icons/svg/loading-infinity.svg +0 -0
  36. package/src/icons/svg/loading-orbital.svg +0 -0
  37. package/src/icons/svg/loading-pacman.svg +0 -0
  38. package/src/icons/svg/loading-pulse-bars.svg +0 -0
  39. package/src/icons/svg/loading-red-blue-ball.svg +0 -0
  40. package/src/icons/svg/loading-reload-arrow.svg +0 -0
  41. package/src/icons/svg/loading-ring.svg +0 -0
  42. package/src/icons/svg/loading-ripple.svg +0 -0
  43. package/src/icons/svg/loading-spinner-oval.svg +0 -0
  44. package/src/icons/svg/loading-spinner.svg +0 -0
  45. package/src/icons/svg/loading-square-blocks.svg +0 -0
  46. package/src/{log.ts → log-json.ts} +128 -129
  47. package/src/icons/cli/spinners.json +0 -1074
package/src/grab-api.ts CHANGED
@@ -1,29 +1,15 @@
1
1
  import {
2
2
  printJSONStructure,
3
3
  log,
4
- showAlert,
5
- setupDevTools,
6
4
  type LogOptions,
7
- } from "./log";
8
-
9
- /**
10
- * TODO
11
- * - react tests
12
- * - grab error popup and dev tool
13
- * - show net log in alert
14
- * - progress
15
- * - pagination working
16
- * - tests in stackblitz
17
- * - loading icons
18
- * - cache revalidation
19
- */
5
+ } from "./log-json";
20
6
 
21
7
  /**
22
8
  * ### GRAB: Generate Request to API from Browser
23
- * ![GrabAPILogo](https://i.imgur.com/qrQWkeb.png)
9
+ * ![GrabAPILogo](https://i.imgur.com/xWD7gyV.png)
24
10
  *
25
11
  * 1. **GRAB is the FBEST Request Manager: Functionally Brilliant, Elegantly Simple Tool**: One Function, no dependencies,
26
- * minimalist syntax, [more features than alternatives](https://grab.js.org/guide/Comparisons)
12
+ * minimalist syntax, [more features than alternatives](https://grab.js.org/docs/Comparisons)
27
13
  * 2. **Auto-JSON Convert**: Pass parameters and get response or error in JSON, handling other data types as is.
28
14
  * 3. **isLoading Status**: Sets `.isLoading=true` on the pre-initialized response object so you can show a "Loading..." in any framework
29
15
  * 4. **Debug Logging**: Adds global `log()` and prints colored JSON structure, response, timing for requests in test.
@@ -39,7 +25,7 @@ import {
39
25
  * 14. **Framework Agnostic**: Alternatives like TanStack work only in component initialization and depend on React & others.
40
26
  * 15. **Globals**: Adds to window in browser or global in Node.js so you only import once: `grab()`, `log()`, `grab.log`, `grab.mock`, `grab.defaults`
41
27
  * 16. **TypeScript Tooltips**: Developers can hover over option names and autocomplete TypeScript.
42
- * 17. **Request Stategies**: [🎯 Examples](https://grab.js.org/guide/Examples) show common stategies like debounce, repeat, proxy, unit tests, interceptors, file upload, etc
28
+ * 17. **Request Stategies**: [🎯 Examples](https://grab.js.org/docs/Examples) show common stategies like debounce, repeat, proxy, unit tests, interceptors, file upload, etc
43
29
  * 18. **Rate Limiting**: Built-in rate limiting to prevent multi-click cascading responses, require to wait seconds between requests.
44
30
  * 19. **Repeat**: Repeat request this many times, or repeat every X seconds to poll for updates.
45
31
  * 20. **Loading Icons**: Import from `grab-url/icons` to get enhanced animated loading icons.
@@ -77,33 +63,27 @@ import {
77
63
  * @param {any} [...params] All other params become GET params, POST body, and other methods.
78
64
  * @returns {Promise<Object>} The response object with resulting data or .error if error.
79
65
  * @author [vtempest (2025)](https://github.com/vtempest/GRAB-URL)
80
- * @see [🎯 Examples](https://grab.js.org/guide/Examples) [📑 Docs](https://grab.js.org)
81
- * @example import grab from 'grab-url';
82
- * let res = {};
83
- * await grab('search', {
84
- * response: res,
85
- * query: "search words"
86
- * })
66
+ * @see [🎯 Examples](https://grab.js.org/docs/Examples) [📑 Docs](https://grab.js.org)
87
67
  */
88
68
  export default async function grab<TResponse = any, TParams = any>(
89
69
  path: string,
90
70
  options: GrabOptions<TResponse, TParams>
91
71
  ): Promise<GrabResponse<TResponse>> {
92
- let {
72
+ var {
93
73
  headers,
94
74
  response = {} as any, // Pre-initialized object to set the response in. isLoading and error are also set on this object.
95
75
  method = options.post // set post: true for POST, omit for GET
96
76
  ? "POST"
97
77
  : options.put
98
- ? "PUT"
99
- : options.patch
100
- ? "PATCH"
101
- : "GET",
78
+ ? "PUT"
79
+ : options.patch
80
+ ? "PATCH"
81
+ : "GET",
102
82
  cache = false, // Enable/disable frontend caching
103
83
  cacheForTime = 60, // Seconds to consider data stale and invalidate cache
104
84
  timeout = 30, // Request timeout in seconds
105
85
  baseURL = (typeof process !== "undefined" && process.env.SERVER_API_URL) ||
106
- "/api/", // Use env var or default to /api/
86
+ "/api/", // Use env var or default to /api/
107
87
  cancelOngoingIfNew = false, // Cancel previous request for same path
108
88
  cancelNewIfOngoing = false, // Don't make new request if one is ongoing
109
89
  rateLimit = 0, // Minimum seconds between requests
@@ -127,7 +107,7 @@ export default async function grab<TResponse = any, TParams = any>(
127
107
  put = false,
128
108
  patch = false,
129
109
  body = null,
130
- ...params // All other params become request params/query
110
+ ...params // All other params become request params/query
131
111
  } = {
132
112
  // Destructure options with defaults, merging with any globally set defaults
133
113
  ...(typeof window !== "undefined"
@@ -181,7 +161,7 @@ export default async function grab<TResponse = any, TParams = any>(
181
161
  }
182
162
 
183
163
  // regrab on stale, on window refocus, on network
184
- if (typeof window !== undefined) {
164
+ if (typeof window !== "undefined") {
185
165
  const regrab = async () => await grab(path, { ...options, cache: false });
186
166
  if (regrabOnStale && cache) setTimeout(regrab, 1000 * cacheForTime);
187
167
  if (regrabOnNetwork) window.addEventListener("online", regrab);
@@ -202,19 +182,23 @@ export default async function grab<TResponse = any, TParams = any>(
202
182
 
203
183
  // Configure infinite scroll behavior if enabled
204
184
  // Attaches scroll listener to specified element that triggers next page load
205
- if (infiniteScroll?.length && typeof window !== "undefined") {
185
+ if (infiniteScroll?.length && typeof paginateElement !== "undefined"
186
+ && typeof window !== "undefined") {
206
187
  let paginateDOM =
207
188
  typeof paginateElement === "string"
208
189
  ? document.querySelector(paginateElement)
209
190
  : paginateElement;
210
191
 
211
- if (window.scrollListener)
192
+ if (!paginateDOM) log("paginateDOM not found", { color: "red" });
193
+ else if (window.scrollListener
194
+ && typeof paginateDOM !== "undefined"
195
+ && typeof paginateDOM.removeEventListener === "function")
212
196
  paginateDOM.removeEventListener("scroll", window.scrollListener);
213
197
 
214
198
  // Your modified scroll listener with position saving
215
199
  window.scrollListener = async (event) => {
216
200
  const t = event.target as HTMLElement;
217
-
201
+
218
202
  // Save scroll position whenever user scrolls
219
203
  localStorage.setItem(
220
204
  "scroll",
@@ -230,7 +214,8 @@ export default async function grab<TResponse = any, TParams = any>(
230
214
  }
231
215
  };
232
216
 
233
- paginateDOM.addEventListener("scroll", window.scrollListener);
217
+ if (paginateDOM)
218
+ paginateDOM.addEventListener("scroll", window.scrollListener);
234
219
  }
235
220
 
236
221
  // Check request history for a previous request with same path/params
@@ -276,8 +261,7 @@ export default async function grab<TResponse = any, TParams = any>(
276
261
 
277
262
  // Update page tracking
278
263
  if (priorRequest) priorRequest.currentPage = pageNumber;
279
- // @ts-ignore
280
- params[paginateKey] = pageNumber;
264
+ params = { ...params, [paginateKey]: pageNumber };
281
265
  }
282
266
 
283
267
  // Set loading state on response object
@@ -373,7 +357,7 @@ export default async function grab<TResponse = any, TParams = any>(
373
357
  // Make actual API request and handle response based on content type
374
358
  res = await fetch(baseURL + path + paramsGETRequest, fetchParams).catch(
375
359
  (e) => {
376
- throw new Error(e);
360
+ throw new Error(e.message);
377
361
  }
378
362
  );
379
363
 
@@ -390,8 +374,8 @@ export default async function grab<TResponse = any, TParams = any>(
390
374
  ? res && res.json()
391
375
  : type.includes("application/pdf") ||
392
376
  type.includes("application/octet-stream")
393
- ? res.blob()
394
- : res.text()
377
+ ? res.blob()
378
+ : res.text()
395
379
  : res.json()
396
380
  ).catch((e) => {
397
381
  throw new Error("Error parsing response: " + e);
@@ -423,15 +407,15 @@ export default async function grab<TResponse = any, TParams = any>(
423
407
  if (debug) {
424
408
  logger(
425
409
  "Path:" +
426
- baseURL +
427
- path +
428
- paramsGETRequest +
429
- "\n" +
430
- JSON.stringify(options, null, 2) +
431
- "\nTime: " +
432
- elapsedTime +
433
- "s\nResponse: " +
434
- printJSONStructure(res)
410
+ baseURL +
411
+ path +
412
+ paramsGETRequest +
413
+ "\n" +
414
+ JSON.stringify(options, null, 2) +
415
+ "\nTime: " +
416
+ elapsedTime +
417
+ "s\nResponse: " +
418
+ printJSONStructure(res)
435
419
  );
436
420
  // console.log(res);
437
421
  }
@@ -467,8 +451,9 @@ export default async function grab<TResponse = any, TParams = any>(
467
451
  // Handle any errors that occurred during request processing
468
452
  let errorMessage =
469
453
  "Error: " + error.message + "\nPath:" + baseURL + path + "\n";
470
- JSON.stringify(params);
454
+ // JSON.stringify(params);
471
455
 
456
+ // if onError hook is passed
472
457
  if (typeof onError === "function")
473
458
  onError(error.message, baseURL + path, params);
474
459
 
@@ -483,7 +468,7 @@ export default async function grab<TResponse = any, TParams = any>(
483
468
  // Do not show errors for duplicate aborted requests
484
469
  if (!error.message.includes("signal") && options.debug) {
485
470
  logger(errorMessage, { color: "red" });
486
- if (debug && typeof document !== undefined) showAlert(errorMessage);
471
+ if (debug && typeof document !== "undefined") showAlert(errorMessage);
487
472
  }
488
473
  response.error = error.message;
489
474
  if (typeof response === "function") {
@@ -513,8 +498,8 @@ export default async function grab<TResponse = any, TParams = any>(
513
498
  */
514
499
  grab.instance =
515
500
  (defaults = {}) =>
516
- (path, options = {}) =>
517
- grab(path, { ...defaults, ...options });
501
+ (path, options = {}) =>
502
+ grab(path, { ...defaults, ...options });
518
503
 
519
504
  // delays execution so that future calls may override and only executes last one
520
505
  const debouncer = async (func, wait) => {
@@ -532,7 +517,6 @@ const debouncer = async (func, wait) => {
532
517
  // Add globals to window in browser, or global in Node.js
533
518
  if (typeof window !== "undefined") {
534
519
  window.log = log;
535
- // @ts-ignore
536
520
  window.grab = grab;
537
521
 
538
522
  window.grab.log = [];
@@ -564,6 +548,73 @@ if (typeof window !== "undefined") {
564
548
  globalThis.grab = grab.instance();
565
549
  }
566
550
 
551
+
552
+
553
+ /**
554
+ * Shows message in a modal overlay with scrollable message stack
555
+ * and is easier to dismiss unlike alert() which blocks window.
556
+ * Creates a semi-transparent overlay with a white box containing the message.
557
+ * @param {string} msg - The message to display
558
+ */
559
+ export function showAlert(msg) {
560
+ if (typeof document === "undefined") return;
561
+ let o = document.getElementById("alert-overlay"),
562
+ list;
563
+
564
+ // Create overlay and alert box if they don't exist
565
+ if (!o) {
566
+ o = document.body.appendChild(document.createElement("div"));
567
+ o.id = "alert-overlay";
568
+ o.setAttribute(
569
+ "style",
570
+ "position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center"
571
+ );
572
+ o.innerHTML = `<div id="alert-box" style="background:#fff;padding:1.5em 2em;border-radius:8px;box-shadow:0 2px 16px #0003;min-width:220px;max-height:80vh;position:relative;display:flex;flex-direction:column;">
573
+ <button id="close-alert" style="position:absolute;top:12px;right:20px;font-size:1.5em;background:none;border:none;cursor:pointer;color:black;">&times;</button>
574
+ <div id="alert-list" style="overflow:auto;flex:1;"></div>
575
+ </div>`;
576
+
577
+ // Add click handlers to close overlay
578
+ o.addEventListener("click", (e) => e.target == o && o.remove());
579
+ document.getElementById("close-alert").onclick = () => o.remove();
580
+ }
581
+
582
+ list = o.querySelector("#alert-list");
583
+
584
+ // Add new message to list
585
+ list.innerHTML += `<div style="border-bottom:1px solid #333; font-size:1.2em;margin:0.5em 0;">${msg}</div>`;
586
+ }
587
+
588
+ /**
589
+ * Sets up development tools for debugging API requests
590
+ * Adds a keyboard shortcut (Ctrl+Alt+I) that shows a modal with request history
591
+ * Each request entry shows:
592
+ * - Request path
593
+ * - Request details
594
+ * - Response data
595
+ * - Timestamp
596
+ */
597
+ export function setupDevTools() {
598
+ // Keyboard shortcut (Ctrl+Alt+I) to toggle debug view
599
+ document.addEventListener("keydown", (e) => {
600
+ if (e.key === "i" && e.ctrlKey && e.altKey) {
601
+ // Create HTML of the grab.log requests
602
+ let html = " ";
603
+ for (let request of grab.log) {
604
+ html += `<div style="margin-bottom:1em; border-bottom:1px solid #ccc; padding-bottom:1em;">
605
+ <b>Path:</b> ${request.path}<br>
606
+ <b>Request:</b> ${printJSONStructure(request.request, 0, 'html')}<br>
607
+ <b>Response:</b> ${printJSONStructure(request.response, 0, 'html')}<br>
608
+ <b>Time:</b> ${new Date(request.lastFetchTime).toLocaleString()}
609
+ </div>`;
610
+ }
611
+ showAlert(html);
612
+ }
613
+ });
614
+ }
615
+
616
+
617
+
567
618
  /***************** TYPESCRIPT INTERFACES *****************/
568
619
 
569
620
  // Core response object that gets populated with API response data
@@ -602,7 +653,7 @@ export type GrabOptions<TResponse = any, TParams = any> = TParams & {
602
653
  /** default=false Whether to log the request and response */
603
654
  debug?: boolean;
604
655
  /** default=null [page key, response field to concatenate, element with results] */
605
- infiniteScroll?: [string, string, string];
656
+ infiniteScroll?: [string, string, string | HTMLElement];
606
657
  /** default=false Pass this with options to set those options as defaults for all requests */
607
658
  setDefaults?: boolean;
608
659
  /** default=0 Retry failed requests this many times */
@@ -689,23 +740,23 @@ export interface GrabGlobal {
689
740
  export interface GrabFunction {
690
741
  /**
691
742
  * ### GRAB: Generate Request to API from Browser
692
- * ![grabAPILogo](https://i.imgur.com/qrQWkeb.png)
743
+ * ![grabAPILogo](https://i.imgur.com/xWD7gyV.png)
693
744
  * Make API request with path
694
745
  * @returns {Promise<Object>} The response object with resulting data or .error if error.
695
746
  * @author [vtempest (2025)](https://github.com/vtempest/GRAB-URL)
696
- * @see [🎯 Examples](https://grab.js.org/guide/Examples) [📑 Docs](https://grab.js.org/lib)
747
+ * @see [🎯 Examples](https://grab.js.org/docs/Examples) [📑 Docs](https://grab.js.org/lib)
697
748
  */
698
- <TResponse = any, TParams = Record<string, any>>(path: string): Promise<
749
+ <TResponse = any, TParams = Record<string, any>>(path: string, options?: GrabOptions<TResponse, TParams>): Promise<
699
750
  GrabResponse<TResponse>
700
751
  >;
701
752
 
702
753
  /**
703
754
  * ### GRAB: Generate Request to API from Browser
704
- * ![grabAPILogo](https://i.imgur.com/qrQWkeb.png)
755
+ * ![grabAPILogo](https://i.imgur.com/xWD7gyV.png)
705
756
  * Make API request with path and options/parameters
706
757
  * @returns {Promise<Object>} The response object with resulting data or .error if error.
707
758
  * @author [vtempest (2025)](https://github.com/vtempest/GRAB-URL)
708
- * @see [🎯 Examples](https://grab.js.org/guide/Examples) [📑 Docs](https://grab.js.org/lib)
759
+ * @see [🎯 Examples](https://grab.js.org/docs/Examples) [📑 Docs](https://grab.js.org/lib)
709
760
  */
710
761
  <TResponse = any, TParams = Record<string, any>>(
711
762
  path: string,
@@ -744,15 +795,6 @@ export interface printJSONStructureFunction {
744
795
  (obj: any): string;
745
796
  }
746
797
 
747
- // Helper type for creating typed API clients
748
- // export type TypedGrabFunction = <
749
- // TResponse = any,
750
- // TParams = Record<string, any>
751
- // >(
752
- // path: string,
753
- // config?: GrabOptions<TResponse, TParams>
754
- // ) => Promise<GrabResponse<TResponse>>;
755
-
756
798
  declare global {
757
799
  // Browser globals
758
800
  interface Window {
@@ -774,4 +816,4 @@ declare global {
774
816
  var grab: GrabFunction;
775
817
  }
776
818
 
777
- export { grab, log, showAlert, printJSONStructure };
819
+ export { grab, log, printJSONStructure };
package/src/grab-url.js CHANGED
@@ -4,48 +4,17 @@ import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { pipeline } from 'stream/promises';
6
6
  import { Readable } from 'stream';
7
- import cliProgress from 'cli-progress';
8
- import chalk from 'chalk';
9
- import ora from 'ora';
10
- import Table from 'cli-table3';
11
- import grab, { log } from '../dist/grab-api.es.js';
12
7
  import readline from 'readline';
13
8
  import { readFileSync } from 'fs';
14
- import { fileURLToPath, pathToFileURL } from 'url';
15
- import { dirname, join } from 'path';
16
-
17
- // Try multiple possible paths for spinners.json
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
20
-
21
- let spinners;
22
- try {
23
- // Try the icons/cli path first (where it actually exists)
24
- spinners = JSON.parse(readFileSync(join(__dirname, 'icons', 'cli', 'spinners.json'), 'utf8'));
25
- } catch (error) {
26
- try {
27
- // Try the local path
28
- spinners = JSON.parse(readFileSync(join(__dirname, 'spinners.json'), 'utf8'));
29
- } catch (error2) {
30
- try {
31
- // Try the parent directory (src)
32
- spinners = JSON.parse(readFileSync(join(__dirname, '..', 'spinners.json'), 'utf8'));
33
- } catch (error3) {
34
- try {
35
- // Try the current working directory
36
- spinners = JSON.parse(readFileSync(join(process.cwd(), 'src', 'spinners.json'), 'utf8'));
37
- } catch (error4) {
38
- // Fallback to default spinners if file not found
39
- console.warn('Could not load spinners.json, using defaults');
40
- spinners = {
41
- dots: { frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] },
42
- line: { frames: ['-', '\\', '|', '/'] },
43
- arrow: { frames: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'] }
44
- };
45
- }
46
- }
47
- }
48
- }
9
+ import { join } from 'path';
10
+ import spinners from './icons/cli/spinners';
11
+ import grab, { log } from './grab-api';
12
+ import { pathToFileURL } from 'url';
13
+
14
+ import cliProgress from 'cli-progress';
15
+ import chalk from 'chalk';
16
+ // Use cli-spinners for spinner animations
17
+ const __dirname = dirname(import.meta.url);
49
18
 
50
19
  // --- ArgParser from grab-cli.js ---
51
20
  class ArgParser {
@@ -741,16 +710,14 @@ formatTotalSpeed(bytesPerSecond) {
741
710
  */
742
711
  async downloadMultipleFiles(downloads) {
743
712
  try {
744
- console.log(this.colors.primary(`🚀 Starting download of ${downloads.length} files...\n`));
745
-
746
713
  // Set up global keyboard listener for pause/resume and add URL BEFORE starting downloads
747
714
  this.setupGlobalKeyboardListener();
748
715
 
749
716
  // Print header row with emojis
750
- this.printHeaderRow();
717
+ // this.printHeaderRow();
751
718
 
752
719
  // Show keyboard shortcut info for pause/resume in multibar view
753
- console.log(this.colors.info('💡 Press p to pause/resume downloads, a to add URL.'));
720
+ // console.log(this.colors.info('💡 [p] pause/resume downloads, [a] add URL.'));
754
721
 
755
722
  // Get random colors for the multibar
756
723
  const masterBarColor = this.getRandomBarColor();
@@ -973,7 +940,6 @@ async downloadMultipleFiles(downloads) {
973
940
  const successful = results.filter(r => r.status === 'fulfilled' && r.value.success).length;
974
941
  const failed = results.length - successful;
975
942
 
976
- console.log(this.colors.green(`✅ Successful: ${successful}/${downloads.length}`));
977
943
  if (failed > 0) {
978
944
  console.log(this.colors.error(`❌ Failed: ${failed}/${downloads.length}`));
979
945
 
@@ -985,11 +951,11 @@ async downloadMultipleFiles(downloads) {
985
951
  }
986
952
  });
987
953
  }
988
-
954
+
989
955
  // Random celebration emoji
990
956
  const celebrationEmojis = ['🥳', '🎊', '🎈', '🌟', '💯', '🚀', '✨', '🔥'];
991
957
  const randomEmoji = celebrationEmojis[Math.floor(Math.random() * celebrationEmojis.length)];
992
- console.log(this.colors.success(`${randomEmoji} Batch download completed! ${randomEmoji}`));
958
+ console.log(this.colors.green(`${randomEmoji} Success: ${successful}/${downloads.length}`));
993
959
 
994
960
  this.clearAbortControllers();
995
961
 
@@ -1819,7 +1785,7 @@ setupFallbackKeyboardListener() {
1819
1785
  };
1820
1786
 
1821
1787
  process.stdin.on('data', handleKeypress);
1822
- console.log(this.colors.info('💡 Keyboard listener active: Press p to pause/resume, a to add URL'));
1788
+ // console.log(this.colors.info('💡 Keyboard listener active: [p] pause/resume, [a] add URL'));
1823
1789
  }
1824
1790
  }
1825
1791
 
@@ -1972,22 +1938,6 @@ if (__isMain) {
1972
1938
  if (i === 0 && outputFile) filename = outputFile;
1973
1939
  return { url, outputPath: filename };
1974
1940
  });
1975
- // Show detected downloads in a table
1976
- const detectedTable = new Table({
1977
- head: ['#', 'URL'],
1978
- colWidths: [4, 80],
1979
- colAligns: ['right', 'left'],
1980
- style: { 'padding-left': 1, 'padding-right': 1, head: [], border: [] }
1981
- });
1982
- downloads.forEach((download, index) => {
1983
- detectedTable.push([
1984
- (index + 1).toString(),
1985
- download.url
1986
- ]);
1987
- });
1988
- console.log(chalk.cyan.bold(`Detected ${downloads.length} download(s):`));
1989
- console.log(detectedTable.toString());
1990
- console.log('');
1991
1941
  // Prepare download objects with filenames
1992
1942
  const downloadObjects = downloads.map((download, index) => {
1993
1943
  let actualUrl = download.url;
@@ -2005,23 +1955,7 @@ if (__isMain) {
2005
1955
  filename: path.basename(filename)
2006
1956
  };
2007
1957
  });
2008
- // Show download queue in a table
2009
- const queueTable = new Table({
2010
- head: ['#', 'Filename', 'Output Path'],
2011
- colWidths: [4, 32, 54],
2012
- colAligns: ['right', 'left', 'left'],
2013
- style: { 'padding-left': 1, 'padding-right': 1, head: [], border: [] }
2014
- });
2015
- downloadObjects.forEach((downloadObj, index) => {
2016
- queueTable.push([
2017
- (index + 1).toString(),
2018
- downloadObj.filename,
2019
- downloadObj.outputPath
2020
- ]);
2021
- });
2022
- console.log(chalk.cyan.bold('\nDownload Queue:'));
2023
- console.log(queueTable.toString());
2024
- console.log('');
1958
+
2025
1959
  try {
2026
1960
  await downloader.downloadMultipleFiles(downloadObjects);
2027
1961
  // Display individual file stats in a table