@purpleproser/soundboard-downloader-cli 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  This changelog is automatically generated by [release-please](https://github.com/google-github-actions/release-please-action).
4
4
 
5
+ ## [1.6.0](https://github.com/blacksagres/soundboard-downloader-cli/compare/v1.5.0...v1.6.0) (2026-03-01)
6
+
7
+
8
+ ### Features
9
+
10
+ * data validation with valibot ([1357286](https://github.com/blacksagres/soundboard-downloader-cli/commit/1357286c9c22153fccc6571f1b7dd1f554bbb057))
11
+ * paginate search list to avoid blastinhg the myinstants page with requests ([439751c](https://github.com/blacksagres/soundboard-downloader-cli/commit/439751cb70d3586aae8d026db902932d41fc2a68))
12
+ * restore single selection for sounds to keep UI clean ([c21895d](https://github.com/blacksagres/soundboard-downloader-cli/commit/c21895dcf373d55b1b87659371c5550d21773355))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * use a mock api for integration tests ([9f410df](https://github.com/blacksagres/soundboard-downloader-cli/commit/9f410dfa796bafc11dc49177667388a84e638e41))
18
+
5
19
  ## [1.5.0](https://github.com/blacksagres/soundboard-downloader-cli/compare/v1.4.0...v1.5.0) (2026-02-26)
6
20
 
7
21
 
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ /**
3
+ * API Configuration - allows switching between real and mock API
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isMockMode = exports.getApiMode = exports.setApiMode = void 0;
7
+ // Default to real API, but can be overridden for testing
8
+ let currentApiMode = 'real';
9
+ /**
10
+ * Set the API mode
11
+ * @param mode - 'real' for production, 'mock' for testing
12
+ */
13
+ const setApiMode = (mode) => {
14
+ currentApiMode = mode;
15
+ };
16
+ exports.setApiMode = setApiMode;
17
+ /**
18
+ * Get the current API mode
19
+ */
20
+ const getApiMode = () => {
21
+ return currentApiMode;
22
+ };
23
+ exports.getApiMode = getApiMode;
24
+ /**
25
+ * Check if we're using mock API
26
+ */
27
+ const isMockMode = () => {
28
+ return currentApiMode === 'mock';
29
+ };
30
+ exports.isMockMode = isMockMode;
31
+ //# sourceMappingURL=api-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-config.js","sourceRoot":"","sources":["../../src/api/api-config.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAIH,yDAAyD;AACzD,IAAI,cAAc,GAAY,MAAM,CAAC;AAErC;;;GAGG;AACI,MAAM,UAAU,GAAG,CAAC,IAAa,EAAQ,EAAE;IAChD,cAAc,GAAG,IAAI,CAAC;AACxB,CAAC,CAAC;AAFW,QAAA,UAAU,cAErB;AAEF;;GAEG;AACI,MAAM,UAAU,GAAG,GAAY,EAAE;IACtC,OAAO,cAAc,CAAC;AACxB,CAAC,CAAC;AAFW,QAAA,UAAU,cAErB;AAEF;;GAEG;AACI,MAAM,UAAU,GAAG,GAAY,EAAE;IACtC,OAAO,cAAc,KAAK,MAAM,CAAC;AACnC,CAAC,CAAC;AAFW,QAAA,UAAU,cAErB"}
@@ -1,46 +1,106 @@
1
1
  "use strict";
2
2
  /**
3
- * Fetch the html from myinstants.com.
3
+ * Fetch the html from myinstants.com with server-side pagination support.
4
4
  *
5
- * 1. Query the html for the sound based on name
6
- * 2 .Then find the corresponding link to that sound
7
- * 3. Transform the found html nodes to an object with `{ label: string, download_url: string }`
8
- * 4. List them in the console - a user can pick one of the list (search in terminal)
9
- * 5. Download the sound (donwload folder from the user)
5
+ * This module provides functions to fetch sound nodes page by page,
6
+ * enabling efficient browsing of search results without loading everything at once.
7
+ *
8
+ * Can be switched to mock mode for testing via api-config.ts
10
9
  */
11
10
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.getNodeDownloadPage = exports.getSoundNodes = void 0;
13
- const getSoundNodes = async (searchString) => {
14
- let page = 1;
15
- const result = [];
11
+ exports.getNodeDownloadPage = exports.getAllSoundNodes = exports.hasNextPage = exports.getSoundNodesPage = void 0;
12
+ const api_config_1 = require("./api-config");
13
+ const MockApi = require("./my-instants.api.mock");
14
+ /**
15
+ * Fetch a single page of sound nodes from myinstants.com
16
+ * @param searchString The search term
17
+ * @param page The page number to fetch (default: 1)
18
+ * @returns HTML content of the requested page
19
+ * @throws Error if the page cannot be fetched
20
+ */
21
+ const getSoundNodesPage = async ({ searchString, page = 1 }) => {
22
+ // Use mock data if in mock mode
23
+ if ((0, api_config_1.isMockMode)()) {
24
+ return MockApi.getSoundNodesPage({ searchString, page });
25
+ }
16
26
  const escapedSearchParam = encodeURIComponent(searchString);
17
- /**
18
- * Yeah so that page does a little trick to make an infinite scroll.
19
- *
20
- * Whenever you hit the page in your browser, there's a script that will try to fetch the next page and
21
- * append the results to the current one, until it hits a 404 (ran out of pages). Here we try to do the same.
22
- *
23
- * One idea was to append this all into one string document but we just return 1 array with all
24
- * the pages we found. Then he service layer can crunch on this to determine what to pick.
25
- */
27
+ const url = `https://www.myinstants.com/en/search/?name=${escapedSearchParam}&page=${page}`;
28
+ const response = await fetch(url);
29
+ if (!response.ok) {
30
+ throw new Error(`Failed to fetch page ${page} for search: ${searchString}`);
31
+ }
32
+ return response.text();
33
+ };
34
+ exports.getSoundNodesPage = getSoundNodesPage;
35
+ /**
36
+ * Check if next page exists using HEAD request for efficiency
37
+ * @param searchString The search term
38
+ * @param page The current page number (default: 1)
39
+ * @returns true if next page exists, false otherwise
40
+ */
41
+ const hasNextPage = async ({ searchString, page = 1 }) => {
42
+ // Use mock data if in mock mode
43
+ if ((0, api_config_1.isMockMode)()) {
44
+ return MockApi.hasNextPage({ searchString, page });
45
+ }
46
+ const escapedSearchParam = encodeURIComponent(searchString);
47
+ const url = `https://www.myinstants.com/en/search/?name=${escapedSearchParam}&page=${page + 1}`;
48
+ try {
49
+ const response = await fetch(url, { method: 'HEAD' });
50
+ return response.ok;
51
+ }
52
+ catch (error) {
53
+ return false;
54
+ }
55
+ };
56
+ exports.hasNextPage = hasNextPage;
57
+ /**
58
+ * Fetch all pages (for --all flag)
59
+ * This maintains the original functionality but as an explicit option
60
+ * @param searchString The search term
61
+ * @returns Array of HTML content for all pages
62
+ */
63
+ const getAllSoundNodes = async ({ searchString }) => {
64
+ // Use mock data if in mock mode
65
+ if ((0, api_config_1.isMockMode)()) {
66
+ return MockApi.getAllSoundNodes({ searchString });
67
+ }
68
+ const result = [];
69
+ let page = 1;
26
70
  while (true) {
27
- const url = `https://www.myinstants.com/en/search/?name=${escapedSearchParam}&page=${page}`;
28
- const response = await fetch(url);
29
- if (response.ok) {
30
- const htmlResult = await response.text();
31
- result.push(htmlResult);
71
+ try {
72
+ const html = await (0, exports.getSoundNodesPage)({ searchString, page });
73
+ result.push(html);
32
74
  page++;
33
- continue;
75
+ // Check if next page exists
76
+ const nextExists = await (0, exports.hasNextPage)({ searchString, page });
77
+ if (!nextExists)
78
+ break;
79
+ }
80
+ catch (error) {
81
+ // If we get an error fetching a page, assume we've reached the end
82
+ break;
34
83
  }
35
- break;
36
84
  }
37
85
  return result;
38
86
  };
39
- exports.getSoundNodes = getSoundNodes;
87
+ exports.getAllSoundNodes = getAllSoundNodes;
88
+ /**
89
+ * Fetch the download page for a specific sound node
90
+ * @param soundNodeDetailsURL The relative URL of the sound detail page
91
+ * @returns HTML content of the sound detail page
92
+ */
40
93
  const getNodeDownloadPage = async (soundNodeDetailsURL) => {
94
+ // Use mock data if in mock mode
95
+ if ((0, api_config_1.isMockMode)()) {
96
+ return MockApi.getNodeDownloadPage(soundNodeDetailsURL);
97
+ }
41
98
  const root = `https://www.myinstants.com${soundNodeDetailsURL}`;
42
99
  const response = await fetch(root);
43
- return await response.text();
100
+ if (!response.ok) {
101
+ throw new Error(`Failed to fetch sound detail page: ${soundNodeDetailsURL}`);
102
+ }
103
+ return response.text();
44
104
  };
45
105
  exports.getNodeDownloadPage = getNodeDownloadPage;
46
106
  //# sourceMappingURL=my-instants.api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"my-instants.api.js","sourceRoot":"","sources":["../../src/api/my-instants.api.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEI,MAAM,aAAa,GAAG,KAAK,EAAE,YAAoB,EAAE,EAAE;IAC1D,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,MAAM,kBAAkB,GAAG,kBAAkB,CAAC,YAAY,CAAC,CAAC;IAE5D;;;;;;;;OAQG;IAEH,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,GAAG,GAAG,8CAA8C,kBAAkB,SAAS,IAAI,EAAE,CAAC;QAC5F,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAElC,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YAChB,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACzC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAExB,IAAI,EAAE,CAAC;YAEP,SAAS;QACX,CAAC;QAED,MAAM;IACR,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAjCW,QAAA,aAAa,iBAiCxB;AAEK,MAAM,mBAAmB,GAAG,KAAK,EAAE,mBAA2B,EAAE,EAAE;IACvE,MAAM,IAAI,GAAG,6BAA6B,mBAAmB,EAAE,CAAC;IAEhE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,CAAC;IAEnC,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC/B,CAAC,CAAC;AANW,QAAA,mBAAmB,uBAM9B"}
1
+ {"version":3,"file":"my-instants.api.js","sourceRoot":"","sources":["../../src/api/my-instants.api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ /**
3
+ * Mock API layer for testing purposes
4
+ * Returns predictable data without making actual HTTP requests
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getNodeDownloadPage = exports.getAllSoundNodes = exports.hasNextPage = exports.getSoundNodesPage = void 0;
8
+ // Mock data for testing
9
+ const mockSounds = [
10
+ {
11
+ label: "Wilhelm Scream",
12
+ detail_url: "/en/instant/wilhelm-scream/"
13
+ },
14
+ {
15
+ label: "THX Deep Note",
16
+ detail_url: "/en/instant/thx-deep-note/"
17
+ },
18
+ {
19
+ label: "Test Sound 1",
20
+ detail_url: "/en/instant/test-sound-1/"
21
+ },
22
+ {
23
+ label: "Test Sound 2",
24
+ detail_url: "/en/instant/test-sound-2/"
25
+ },
26
+ {
27
+ label: "Test Sound 3",
28
+ detail_url: "/en/instant/test-sound-3/"
29
+ },
30
+ {
31
+ label: "Test Sound 4",
32
+ detail_url: "/en/instant/test-sound-4/"
33
+ },
34
+ {
35
+ label: "Test Sound 5",
36
+ detail_url: "/en/instant/test-sound-5/"
37
+ },
38
+ {
39
+ label: "Test Sound 6",
40
+ detail_url: "/en/instant/test-sound-6/"
41
+ },
42
+ {
43
+ label: "Test Sound 7",
44
+ detail_url: "/en/instant/test-sound-7/"
45
+ },
46
+ {
47
+ label: "Test Sound 8",
48
+ detail_url: "/en/instant/test-sound-8/"
49
+ },
50
+ {
51
+ label: "Test Sound 9",
52
+ detail_url: "/en/instant/test-sound-9/"
53
+ },
54
+ {
55
+ label: "Test Sound 10",
56
+ detail_url: "/en/instant/test-sound-10/"
57
+ },
58
+ {
59
+ label: "Test Sound 11",
60
+ detail_url: "/en/instant/test-sound-11/"
61
+ },
62
+ {
63
+ label: "Test Sound 12",
64
+ detail_url: "/en/instant/test-sound-12/"
65
+ },
66
+ ];
67
+ // Mock HTML templates
68
+ const mockSearchPage = (sounds, page) => `
69
+ <!DOCTYPE html>
70
+ <html>
71
+ <head><title>MyInstants - Search Results</title></head>
72
+ <body>
73
+ <div class="instant-list">
74
+ ${sounds.map(sound => `
75
+ <div class="instant">
76
+ <a href="${sound.detail_url}" class="instant-link">${sound.label}</a>
77
+ </div>
78
+ `).join('')}
79
+ </div>
80
+ </body>
81
+ </html>
82
+ `;
83
+ const mockDetailPage = (downloadUrl) => `
84
+ <!DOCTYPE html>
85
+ <html>
86
+ <head><title>MyInstants - Sound Detail</title></head>
87
+ <body>
88
+ <a href="${downloadUrl}" download="sound.mp3">Download MP3</a>
89
+ </body>
90
+ </html>
91
+ `;
92
+ /**
93
+ * Mock implementation of getSoundNodesPage
94
+ */
95
+ const getSoundNodesPage = async ({ searchString, page = 1 }) => {
96
+ // Return mock data for any search string
97
+ const pageSize = 6;
98
+ const startIndex = (page - 1) * pageSize;
99
+ const pageSounds = mockSounds.slice(startIndex, startIndex + pageSize);
100
+ return mockSearchPage(pageSounds, page);
101
+ };
102
+ exports.getSoundNodesPage = getSoundNodesPage;
103
+ /**
104
+ * Mock implementation of hasNextPage
105
+ */
106
+ const hasNextPage = async ({ searchString, page = 1 }) => {
107
+ const pageSize = 6;
108
+ const startIndex = (page - 1) * pageSize;
109
+ return startIndex + pageSize < mockSounds.length;
110
+ };
111
+ exports.hasNextPage = hasNextPage;
112
+ /**
113
+ * Mock implementation of getAllSoundNodes
114
+ */
115
+ const getAllSoundNodes = async ({ searchString }) => {
116
+ const pageSize = 6;
117
+ const pages = [];
118
+ for (let page = 1;; page++) {
119
+ const startIndex = (page - 1) * pageSize;
120
+ const pageSounds = mockSounds.slice(startIndex, startIndex + pageSize);
121
+ if (pageSounds.length === 0)
122
+ break;
123
+ pages.push(mockSearchPage(pageSounds, page));
124
+ }
125
+ return pages;
126
+ };
127
+ exports.getAllSoundNodes = getAllSoundNodes;
128
+ /**
129
+ * Mock implementation of getNodeDownloadPage
130
+ */
131
+ const getNodeDownloadPage = async (soundNodeDetailsURL) => {
132
+ // Return a mock detail page with a valid download URL
133
+ return mockDetailPage("https://www.myinstants.com/media/sounds/test-sound.mp3");
134
+ };
135
+ exports.getNodeDownloadPage = getNodeDownloadPage;
136
+ //# sourceMappingURL=my-instants.api.mock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"my-instants.api.mock.js","sourceRoot":"","sources":["../../src/api/my-instants.api.mock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ /**
3
+ * Valibot validation schemas for API responses
4
+ * Ensures both real and mock API responses match expected structure
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.validateSoundsArray = exports.safeValidateSound = exports.validateHtml = exports.validatePaginatedResults = exports.validateSound = void 0;
8
+ const valibot_1 = require("valibot");
9
+ // Schema for a single sound result
10
+ const SoundSchema = (0, valibot_1.object)({
11
+ label: (0, valibot_1.string)('Label must be a string'),
12
+ download_url: (0, valibot_1.string)('Download URL must be a string'),
13
+ });
14
+ // Schema for paginated results
15
+ const PaginatedResultsSchema = (0, valibot_1.object)({
16
+ results: (0, valibot_1.array)(SoundSchema, 'Results must be an array of sounds'),
17
+ hasNextPage: (0, valibot_1.boolean)('hasNextPage should be a boolean'),
18
+ hasPreviousPage: (0, valibot_1.boolean)('hasPreviousPage should be a boolean'),
19
+ currentPage: (0, valibot_1.number)('currentPage should be a number'),
20
+ });
21
+ // Schema for HTML response (used internally)
22
+ const HtmlSchema = (0, valibot_1.string)('HTML response must be a string');
23
+ /**
24
+ * Validate a single sound result
25
+ * @throws ValidationError if sound doesn't match schema
26
+ */
27
+ const validateSound = (sound) => {
28
+ return (0, valibot_1.parse)(SoundSchema, sound);
29
+ };
30
+ exports.validateSound = validateSound;
31
+ /**
32
+ * Validate paginated results
33
+ * @throws ValidationError if results don't match schema
34
+ */
35
+ const validatePaginatedResults = (results) => {
36
+ return (0, valibot_1.parse)(PaginatedResultsSchema, results);
37
+ };
38
+ exports.validatePaginatedResults = validatePaginatedResults;
39
+ /**
40
+ * Validate HTML response
41
+ * @throws ValidationError if HTML is not a string
42
+ */
43
+ const validateHtml = (html) => {
44
+ return (0, valibot_1.parse)(HtmlSchema, html);
45
+ };
46
+ exports.validateHtml = validateHtml;
47
+ /**
48
+ * Safe validation that returns null instead of throwing
49
+ */
50
+ const safeValidateSound = (sound) => {
51
+ try {
52
+ return (0, exports.validateSound)(sound);
53
+ }
54
+ catch (error) {
55
+ console.error('Validation failed:', error);
56
+ return null;
57
+ }
58
+ };
59
+ exports.safeValidateSound = safeValidateSound;
60
+ /**
61
+ * Validate an array of sounds
62
+ */
63
+ const validateSoundsArray = (sounds) => {
64
+ return sounds.map(sound => (0, exports.validateSound)(sound));
65
+ };
66
+ exports.validateSoundsArray = validateSoundsArray;
67
+ //# sourceMappingURL=validation-schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-schemas.js","sourceRoot":"","sources":["../../src/api/validation-schemas.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,qCAA+F;AAE/F,mCAAmC;AACnC,MAAM,WAAW,GAAG,IAAA,gBAAM,EAAC;IACzB,KAAK,EAAE,IAAA,gBAAM,EAAC,wBAAwB,CAAC;IACvC,YAAY,EAAE,IAAA,gBAAM,EAAC,+BAA+B,CAAC;CACtD,CAAC,CAAC;AAIH,+BAA+B;AAC/B,MAAM,sBAAsB,GAAG,IAAA,gBAAM,EAAC;IACpC,OAAO,EAAE,IAAA,eAAK,EAAC,WAAW,EAAE,oCAAoC,CAAC;IACjE,WAAW,EAAE,IAAA,iBAAO,EAAC,iCAAiC,CAAC;IACvD,eAAe,EAAE,IAAA,iBAAO,EAAC,qCAAqC,CAAC;IAC/D,WAAW,EAAE,IAAA,gBAAM,EAAC,gCAAgC,CAAC;CACtD,CAAC,CAAC;AAIH,6CAA6C;AAC7C,MAAM,UAAU,GAAG,IAAA,gBAAM,EAAC,gCAAgC,CAAC,CAAC;AAG5D;;;GAGG;AACI,MAAM,aAAa,GAAG,CAAC,KAAc,EAAkB,EAAE;IAC9D,OAAO,IAAA,eAAK,EAAC,WAAW,EAAE,KAAK,CAAC,CAAC;AACnC,CAAC,CAAC;AAFW,QAAA,aAAa,iBAExB;AAEF;;;GAGG;AACI,MAAM,wBAAwB,GAAG,CAAC,OAAgB,EAA6B,EAAE;IACtF,OAAO,IAAA,eAAK,EAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC,CAAC;AAFW,QAAA,wBAAwB,4BAEnC;AAEF;;;GAGG;AACI,MAAM,YAAY,GAAG,CAAC,IAAa,EAAiB,EAAE;IAC3D,OAAO,IAAA,eAAK,EAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACjC,CAAC,CAAC;AAFW,QAAA,YAAY,gBAEvB;AAEF;;GAEG;AACI,MAAM,iBAAiB,GAAG,CAAC,KAAc,EAAyB,EAAE;IACzE,IAAI,CAAC;QACH,OAAO,IAAA,qBAAa,EAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAPW,QAAA,iBAAiB,qBAO5B;AAEF;;GAEG;AACI,MAAM,mBAAmB,GAAG,CAAC,MAAiB,EAAoB,EAAE;IACzE,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAA,qBAAa,EAAC,KAAK,CAAC,CAAC,CAAC;AACnD,CAAC,CAAC;AAFW,QAAA,mBAAmB,uBAE9B"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=query.type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.type.js","sourceRoot":"","sources":["../../../src/common/types/query.type.ts"],"names":[],"mappings":""}
package/dist/main.js CHANGED
@@ -4,9 +4,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const my_instants_service_1 = require("./service/my-instants.service");
5
5
  const ora_1 = require("ora");
6
6
  const prompts_1 = require("@inquirer/prompts");
7
+ const inquirer = require("@inquirer/prompts");
7
8
  const open_1 = require("open");
8
9
  const file_downloader_service_1 = require("./service/file-downloader.service");
9
10
  const selection_utils_1 = require("./utils/selection-utils");
11
+ const pagination_utils_1 = require("./utils/pagination-utils");
10
12
  process.on("uncaughtException", (error) => {
11
13
  if (error instanceof Error && error.name === "ExitPromptError") {
12
14
  console.log("👋 see ya!");
@@ -17,110 +19,302 @@ process.on("uncaughtException", (error) => {
17
19
  }
18
20
  });
19
21
  (async function () {
20
- const answer = await (0, prompts_1.input)({
22
+ // Check for --all flag
23
+ const args = process.argv.slice(2);
24
+ let shouldFetchAll = args.includes("--all");
25
+ let answer = await (0, prompts_1.input)({
21
26
  message: "🔍 What sound effects are you looking for?",
22
27
  required: true,
23
28
  default: "wilhelm scream",
24
29
  });
25
- const spinner = (0, ora_1.default)({
26
- text: "Loading result...",
27
- color: "magenta",
28
- spinner: "bouncingBall",
29
- }).start();
30
- const sounds = await (0, my_instants_service_1.getSoundNodes)(answer);
31
- spinner.stop();
32
- console.log(`🎉 Found ${sounds.length} sound${sounds.length !== 1 ? 's' : ''}!`);
33
- const rawSelections = await (0, prompts_1.checkbox)({
34
- message: "🎵 Which sounds to download? (use space to select multiple, arrows to navigate, search for something directly)",
35
- pageSize: 30,
36
- choices: sounds.map((sound) => ({
37
- name: `${sound.label} 🎵`,
38
- value: `${sound.label}||${sound.download_url}`,
39
- })),
40
- });
41
- // Parse and validate selections
42
- const selections = (0, selection_utils_1.parseSelections)(rawSelections);
43
- const multipleSelected = (0, selection_utils_1.isMultipleSelection)(selections);
44
- // Determine available actions based on selection count
45
- let actions = [
46
- {
47
- name: "💾 Download",
48
- value: "action:download",
49
- },
50
- ];
51
- // For single selection, add additional options
52
- if (!multipleSelected) {
53
- actions = [
54
- ...actions,
55
- {
56
- name: "▶️ Play",
57
- value: "action:play",
58
- },
59
- {
60
- name: "🔗 Show download URL (you can pipe this to other commands)",
61
- value: "action:show-url",
62
- }
63
- ];
64
- }
65
- const action = await (0, prompts_1.select)({
66
- message: (0, selection_utils_1.getSelectionMessage)(selections),
67
- choices: actions,
68
- });
69
- if (action === "action:show-url" && !multipleSelected) {
70
- const firstSelection = (0, selection_utils_1.getFirstSelection)(selections);
71
- if (firstSelection) {
72
- console.log(firstSelection.downloadUrl);
73
- }
74
- }
75
- if (action === "action:play" && !multipleSelected) {
76
- const firstSelection = (0, selection_utils_1.getFirstSelection)(selections);
77
- if (firstSelection) {
78
- (0, open_1.default)(firstSelection.downloadUrl);
79
- }
80
- }
81
- if (action === "action:download") {
82
- const totalDownloads = selections.length;
83
- let completedDownloads = 0;
84
- let failedDownloads = 0;
85
- const failedFiles = [];
86
- const masterSpinner = (0, ora_1.default)({
87
- text: `Preparing to download ${totalDownloads} file${totalDownloads !== 1 ? 's' : ''}...`,
88
- color: "cyan",
89
- spinner: "growHorizontal",
90
- }).start();
91
- // Process all downloads sequentially
92
- for (const [index, selection] of selections.entries()) {
93
- masterSpinner.text = `Downloading ${selection.label} (${index + 1}/${totalDownloads})...`;
30
+ // Pagination flow
31
+ if (!shouldFetchAll) {
32
+ let currentPage = 1;
33
+ let shouldContinuePagination = true;
34
+ while (shouldContinuePagination) {
35
+ const spinner = (0, ora_1.default)({
36
+ text: `Loading page ${currentPage}...`,
37
+ color: "magenta",
38
+ spinner: "bouncingBall",
39
+ }).start();
94
40
  try {
95
- const downloadFileName = selection.downloadUrl.split("/").pop();
96
- if (!downloadFileName) {
97
- throw new ReferenceError(`Could not find download file name for: [${selection.downloadUrl}], could it be a malformed link?`);
41
+ const { results, hasNextPage, hasPreviousPage } = await (0, my_instants_service_1.getPaginatedResults)(answer, currentPage);
42
+ spinner.stop();
43
+ console.log((0, pagination_utils_1.getPaginationInfo)(currentPage, results.length, hasNextPage));
44
+ // Prepare sound choices
45
+ const soundChoices = results.map((sound) => ({
46
+ name: `${sound.label} 🎵`,
47
+ value: `${sound.label}||${sound.download_url}`,
48
+ }));
49
+ // Add navigation choices as separate actions
50
+ const navigationChoices = [
51
+ new inquirer.Separator("=== Navigation ==="),
52
+ {
53
+ name: hasPreviousPage ? "⏮️ Previous page" : "⏮️ Previous page (disabled)",
54
+ value: "action:prev-page",
55
+ disabled: !hasPreviousPage
56
+ },
57
+ {
58
+ name: hasNextPage ? "⏭️ Next page" : "⏭️ Next page (disabled)",
59
+ value: "action:next-page",
60
+ disabled: !hasNextPage
61
+ },
62
+ {
63
+ name: "🔍 New search",
64
+ value: "action:new-search"
65
+ },
66
+ {
67
+ name: "📋 Show all results",
68
+ value: "action:show-all"
69
+ }
70
+ ];
71
+ const allChoices = [...soundChoices, ...navigationChoices];
72
+ // Use radio selection instead of checkbox for single selection
73
+ const selection = await (0, prompts_1.select)({
74
+ message: "🎵 Select a sound or choose a navigation option:",
75
+ choices: allChoices,
76
+ });
77
+ // Handle navigation actions
78
+ if ((0, pagination_utils_1.isNavigationAction)(selection)) {
79
+ switch (selection) {
80
+ case "action:prev-page":
81
+ if (hasPreviousPage)
82
+ currentPage--;
83
+ continue;
84
+ case "action:next-page":
85
+ if (hasNextPage)
86
+ currentPage++;
87
+ continue;
88
+ case "action:new-search":
89
+ // Reset for new search
90
+ answer = await (0, prompts_1.input)({
91
+ message: "🔍 What sound effects are you looking for?",
92
+ required: true,
93
+ default: answer,
94
+ });
95
+ currentPage = 1;
96
+ continue;
97
+ case "action:show-all":
98
+ // Switch to fetch-all mode
99
+ shouldFetchAll = true;
100
+ shouldContinuePagination = false;
101
+ break;
102
+ }
103
+ continue;
98
104
  }
99
- // Download directly to current working directory
100
- await (0, file_downloader_service_1.downloadFile)(selection.downloadUrl, {
101
- destination: downloadFileName,
105
+ // If we get here, user selected a sound
106
+ shouldContinuePagination = false;
107
+ // Process the single sound selection
108
+ const selections = (0, selection_utils_1.parseSelections)([selection]);
109
+ // Available actions for single sound selection
110
+ const actions = [
111
+ {
112
+ name: "💾 Download",
113
+ value: "action:download",
114
+ },
115
+ {
116
+ name: "▶️ Play",
117
+ value: "action:play",
118
+ },
119
+ {
120
+ name: "🔗 Show download URL (you can pipe this to other commands)",
121
+ value: "action:show-url",
122
+ },
123
+ ];
124
+ const action = await (0, prompts_1.select)({
125
+ message: (0, selection_utils_1.getSelectionMessage)(selections),
126
+ choices: actions,
102
127
  });
103
- completedDownloads++;
104
- masterSpinner.text = `Downloaded ${completedDownloads}/${totalDownloads} files...`;
128
+ if (action === "action:show-url") {
129
+ const firstSelection = (0, selection_utils_1.getFirstSelection)(selections);
130
+ if (firstSelection) {
131
+ console.log(firstSelection.downloadUrl);
132
+ }
133
+ }
134
+ if (action === "action:play") {
135
+ const firstSelection = (0, selection_utils_1.getFirstSelection)(selections);
136
+ if (firstSelection) {
137
+ (0, open_1.default)(firstSelection.downloadUrl);
138
+ }
139
+ }
140
+ if (action === "action:download") {
141
+ const totalDownloads = selections.length;
142
+ let completedDownloads = 0;
143
+ let failedDownloads = 0;
144
+ const failedFiles = [];
145
+ const masterSpinner = (0, ora_1.default)({
146
+ text: `Preparing to download ${totalDownloads} file${totalDownloads !== 1 ? "s" : ""}...`,
147
+ color: "cyan",
148
+ spinner: "growHorizontal",
149
+ }).start();
150
+ // Process all downloads sequentially
151
+ for (const [index, selection] of selections.entries()) {
152
+ masterSpinner.text = `Downloading ${selection.label} (${index + 1}/${totalDownloads})...`;
153
+ try {
154
+ const downloadFileName = selection.downloadUrl.split("/").pop();
155
+ if (!downloadFileName) {
156
+ throw new ReferenceError(`Could not find download file name for: [${selection.downloadUrl}], could it be a malformed link?`);
157
+ }
158
+ // Download directly to current working directory
159
+ await (0, file_downloader_service_1.downloadFile)(selection.downloadUrl, {
160
+ destination: downloadFileName,
161
+ });
162
+ completedDownloads++;
163
+ masterSpinner.text = `Downloaded ${completedDownloads}/${totalDownloads} files...`;
164
+ }
165
+ catch (error) {
166
+ failedDownloads++;
167
+ failedFiles.push(selection.label);
168
+ masterSpinner.text = `Error downloading ${selection.label} (${completedDownloads} successful, ${failedDownloads} failed)`;
169
+ console.error(`\nFailed to download ${selection.label}:`, error instanceof Error ? error.message : String(error));
170
+ }
171
+ }
172
+ // Final summary - only show once at the end
173
+ masterSpinner.stop();
174
+ if (failedDownloads === 0) {
175
+ console.log(`✅ Download complete! All ${completedDownloads} file${completedDownloads !== 1 ? "s" : ""} downloaded successfully.`);
176
+ }
177
+ else {
178
+ console.log(`⚠️ Download partially complete: ${completedDownloads} successful, ${failedDownloads} failed.`);
179
+ if (failedFiles.length > 0) {
180
+ console.log("\nFailed files:");
181
+ failedFiles.forEach((file) => console.log(` - ${file}`));
182
+ }
183
+ }
184
+ }
105
185
  }
106
186
  catch (error) {
107
- failedDownloads++;
108
- failedFiles.push(selection.label);
109
- masterSpinner.text = `Error downloading ${selection.label} (${completedDownloads} successful, ${failedDownloads} failed)`;
110
- console.error(`\nFailed to download ${selection.label}:`, error instanceof Error ? error.message : String(error));
187
+ spinner.stop();
188
+ console.error("❌ Error loading results:", error instanceof Error ? error.message : String(error));
189
+ const retry = await (0, prompts_1.select)({
190
+ message: "What would you like to do?",
191
+ choices: [
192
+ { name: "🔍 Try a new search", value: "new-search" },
193
+ { name: "🚪 Exit", value: "exit" },
194
+ ],
195
+ });
196
+ if (retry === "new-search") {
197
+ answer = await (0, prompts_1.input)({
198
+ message: "🔍 What sound effects are you looking for?",
199
+ required: true,
200
+ default: answer,
201
+ });
202
+ currentPage = 1;
203
+ continue;
204
+ }
205
+ else {
206
+ process.exit(0);
207
+ }
111
208
  }
112
209
  }
113
- // Final summary - only show once at the end
114
- masterSpinner.stop();
115
- if (failedDownloads === 0) {
116
- console.log(`✅ Download complete! All ${completedDownloads} file${completedDownloads !== 1 ? 's' : ''} downloaded successfully.`);
117
- }
118
- else {
119
- console.log(`⚠️ Download partially complete: ${completedDownloads} successful, ${failedDownloads} failed.`);
120
- if (failedFiles.length > 0) {
121
- console.log("\nFailed files:");
122
- failedFiles.forEach(file => console.log(` - ${file}`));
210
+ }
211
+ // Fetch-all mode
212
+ if (shouldFetchAll) {
213
+ const spinner = (0, ora_1.default)({
214
+ text: "Loading all results...",
215
+ color: "magenta",
216
+ spinner: "bouncingBall",
217
+ }).start();
218
+ try {
219
+ const allResults = await (0, my_instants_service_1.getAllResults)(answer);
220
+ spinner.stop();
221
+ console.log(`🎉 Found ${allResults.length} sound${allResults.length !== 1 ? "s" : ""}!`);
222
+ const rawSelections = await (0, prompts_1.checkbox)({
223
+ message: "🎵 Which sounds to download? (use space to select multiple, arrows to navigate, search for something directly)",
224
+ choices: allResults.map((sound) => ({
225
+ name: `${sound.label} 🎵`,
226
+ value: `${sound.label}||${sound.download_url}`,
227
+ })),
228
+ });
229
+ // Parse and validate selections
230
+ const selections = (0, selection_utils_1.parseSelections)(rawSelections);
231
+ const multipleSelected = (0, selection_utils_1.isMultipleSelection)(selections);
232
+ // Determine available actions based on selection count
233
+ let actions = [
234
+ {
235
+ name: "💾 Download",
236
+ value: "action:download",
237
+ },
238
+ ];
239
+ // For single selection, add additional options
240
+ if (!multipleSelected) {
241
+ actions = [
242
+ ...actions,
243
+ {
244
+ name: "▶️ Play",
245
+ value: "action:play",
246
+ },
247
+ {
248
+ name: "🔗 Show download URL (you can pipe this to other commands)",
249
+ value: "action:show-url",
250
+ },
251
+ ];
252
+ }
253
+ const action = await (0, prompts_1.select)({
254
+ message: (0, selection_utils_1.getSelectionMessage)(selections),
255
+ choices: actions,
256
+ });
257
+ if (action === "action:show-url" && !multipleSelected) {
258
+ const firstSelection = (0, selection_utils_1.getFirstSelection)(selections);
259
+ if (firstSelection) {
260
+ console.log(firstSelection.downloadUrl);
261
+ }
123
262
  }
263
+ if (action === "action:play" && !multipleSelected) {
264
+ const firstSelection = (0, selection_utils_1.getFirstSelection)(selections);
265
+ if (firstSelection) {
266
+ (0, open_1.default)(firstSelection.downloadUrl);
267
+ }
268
+ }
269
+ if (action === "action:download") {
270
+ const totalDownloads = selections.length;
271
+ let completedDownloads = 0;
272
+ let failedDownloads = 0;
273
+ const failedFiles = [];
274
+ const masterSpinner = (0, ora_1.default)({
275
+ text: `Preparing to download ${totalDownloads} file${totalDownloads !== 1 ? "s" : ""}...`,
276
+ color: "cyan",
277
+ spinner: "growHorizontal",
278
+ }).start();
279
+ // Process all downloads sequentially
280
+ for (const [index, selection] of selections.entries()) {
281
+ masterSpinner.text = `Downloading ${selection.label} (${index + 1}/${totalDownloads})...`;
282
+ try {
283
+ const downloadFileName = selection.downloadUrl.split("/").pop();
284
+ if (!downloadFileName) {
285
+ throw new ReferenceError(`Could not find download file name for: [${selection.downloadUrl}], could it be a malformed link?`);
286
+ }
287
+ // Download directly to current working directory
288
+ await (0, file_downloader_service_1.downloadFile)(selection.downloadUrl, {
289
+ destination: downloadFileName,
290
+ });
291
+ completedDownloads++;
292
+ masterSpinner.text = `Downloaded ${completedDownloads}/${totalDownloads} files...`;
293
+ }
294
+ catch (error) {
295
+ failedDownloads++;
296
+ failedFiles.push(selection.label);
297
+ masterSpinner.text = `Error downloading ${selection.label} (${completedDownloads} successful, ${failedDownloads} failed)`;
298
+ console.error(`\nFailed to download ${selection.label}:`, error instanceof Error ? error.message : String(error));
299
+ }
300
+ }
301
+ // Final summary - only show once at the end
302
+ masterSpinner.stop();
303
+ if (failedDownloads === 0) {
304
+ console.log(`✅ Download complete! All ${completedDownloads} file${completedDownloads !== 1 ? "s" : ""} downloaded successfully.`);
305
+ }
306
+ else {
307
+ console.log(`⚠️ Download partially complete: ${completedDownloads} successful, ${failedDownloads} failed.`);
308
+ if (failedFiles.length > 0) {
309
+ console.log("\nFailed files:");
310
+ failedFiles.forEach((file) => console.log(` - ${file}`));
311
+ }
312
+ }
313
+ }
314
+ }
315
+ catch (error) {
316
+ spinner.stop();
317
+ console.error("❌ Error loading all results:", error instanceof Error ? error.message : String(error));
124
318
  }
125
319
  }
126
320
  })();
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getSoundNodes = void 0;
3
+ exports.getAllResults = exports.getPaginatedResults = exports.getSoundNodesPage = void 0;
4
4
  const my_instants_api_1 = require("../api/my-instants.api");
5
+ const validation_schemas_1 = require("../api/validation-schemas");
5
6
  const jsdom = require("jsdom");
7
+ // Simple in-memory cache for paginated results
8
+ const pageCache = new Map();
9
+ const getCacheKey = (searchString, page) => {
10
+ return `${searchString}||${page}`;
11
+ };
12
+ const getCachedPage = (searchString, page) => {
13
+ return pageCache.get(getCacheKey(searchString, page));
14
+ };
15
+ const cachePage = (searchString, page, html) => {
16
+ pageCache.set(getCacheKey(searchString, page), html);
17
+ };
18
+ const clearCache = (searchString) => {
19
+ // Clear all cached pages for this search
20
+ for (const key of pageCache.keys()) {
21
+ if (key.startsWith(`${searchString}||`)) {
22
+ pageCache.delete(key);
23
+ }
24
+ }
25
+ };
6
26
  const getDownloadUrl = async (originalUrl) => {
7
27
  if (!originalUrl) {
8
28
  return "not-found";
@@ -21,34 +41,104 @@ const getDownloadUrl = async (originalUrl) => {
21
41
  const cleanSoundName = originalUrl
22
42
  .replace("/en/instant/", "")
23
43
  .replace("/", "");
24
- const downloadPage = await (0, my_instants_api_1.getNodeDownloadPage)(originalUrl);
25
- const downloadLink = Array.from(new jsdom.JSDOM(downloadPage).window.document.querySelectorAll("a")).find((node) => {
26
- return node.href.includes(".mp3") && node.hasAttribute("download");
27
- });
28
- if (downloadLink) {
29
- return `https://www.myinstants.com${downloadLink.href}`;
44
+ try {
45
+ const downloadPage = await (0, my_instants_api_1.getNodeDownloadPage)(originalUrl);
46
+ const downloadLink = Array.from(new jsdom.JSDOM(downloadPage).window.document.querySelectorAll("a")).find((node) => {
47
+ return node.href.includes(".mp3") && node.hasAttribute("download");
48
+ });
49
+ if (downloadLink) {
50
+ return `https://www.myinstants.com${downloadLink.href}`;
51
+ }
52
+ }
53
+ catch (error) {
54
+ console.error(`Failed to fetch download URL for ${originalUrl}:`, error);
30
55
  }
31
56
  return "not-found";
32
57
  };
33
- const getSoundNodes = async (searchString) => {
34
- const result = await (0, my_instants_api_1.getSoundNodes)(searchString);
35
- const allLabels = [];
36
- const allDownloadLinks = [];
37
- for (const page of result) {
38
- const document = new jsdom.JSDOM(page);
39
- document.window.document
40
- .querySelectorAll("div.instant > a.instant-link")
41
- .forEach((node) => {
42
- allLabels.push(node.textContent);
43
- allDownloadLinks.push(getDownloadUrl(node.getAttribute("href")));
44
- });
58
+ /**
59
+ * Get sound nodes for a specific page with caching
60
+ */
61
+ const getSoundNodesPage = async (params) => {
62
+ const page = params.page || 1;
63
+ const cacheKey = getCacheKey(params.searchString, page);
64
+ let html = getCachedPage(params.searchString, page);
65
+ if (!html) {
66
+ html = await (0, my_instants_api_1.getSoundNodesPage)(params);
67
+ cachePage(params.searchString, page, html);
45
68
  }
46
- const allLinksResolved = await Promise.all(allDownloadLinks);
47
- const finalList = allLabels.map((label, index) => ({
48
- label,
49
- download_url: allLinksResolved[index] ?? "not-found",
69
+ const document = new jsdom.JSDOM(html);
70
+ const soundNodes = Array.from(document.window.document.querySelectorAll("div.instant > a.instant-link"));
71
+ const results = [];
72
+ // First, extract labels and detail URLs
73
+ soundNodes.forEach((node) => {
74
+ const detailUrl = node.getAttribute("href");
75
+ if (detailUrl) {
76
+ results.push({
77
+ label: node.textContent?.trim() || "Unknown",
78
+ download_url: "pending",
79
+ detail_url: detailUrl
80
+ });
81
+ }
82
+ });
83
+ // Resolve download URLs in parallel by fetching each detail page
84
+ const downloadPromises = results.map(result => getDownloadUrl(result.detail_url));
85
+ const downloadUrls = await Promise.all(downloadPromises);
86
+ return results.map((result, index) => ({
87
+ label: result.label,
88
+ download_url: downloadUrls[index] || "not-found"
50
89
  }));
51
- return finalList.toSorted((a, b) => a.label.localeCompare(b.label));
52
90
  };
53
- exports.getSoundNodes = getSoundNodes;
91
+ exports.getSoundNodesPage = getSoundNodesPage;
92
+ /**
93
+ * Get paginated results with navigation information
94
+ */
95
+ const getPaginatedResults = async (searchString, page = 1) => {
96
+ // Clear cache for new searches
97
+ if (page === 1) {
98
+ clearCache(searchString);
99
+ }
100
+ const currentResults = await (0, exports.getSoundNodesPage)({ searchString, page });
101
+ const hasNext = await (0, my_instants_api_1.hasNextPage)({ searchString, page });
102
+ // Validate the results before returning
103
+ const validatedResults = (0, validation_schemas_1.validatePaginatedResults)({
104
+ results: currentResults,
105
+ hasNextPage: hasNext,
106
+ hasPreviousPage: page > 1,
107
+ currentPage: page
108
+ });
109
+ return validatedResults;
110
+ };
111
+ exports.getPaginatedResults = getPaginatedResults;
112
+ /**
113
+ * Get all results (for --all flag)
114
+ */
115
+ const getAllResults = async (searchString) => {
116
+ const allPages = await (0, my_instants_api_1.getAllSoundNodes)({ searchString });
117
+ const allResults = [];
118
+ // First collect all detail URLs
119
+ const soundDetails = [];
120
+ for (const pageHtml of allPages) {
121
+ const document = new jsdom.JSDOM(pageHtml);
122
+ const soundNodes = Array.from(document.window.document.querySelectorAll("div.instant > a.instant-link"));
123
+ soundNodes.forEach((node) => {
124
+ const detailUrl = node.getAttribute("href");
125
+ if (detailUrl) {
126
+ soundDetails.push({
127
+ label: node.textContent?.trim() || "Unknown",
128
+ detail_url: detailUrl
129
+ });
130
+ }
131
+ });
132
+ }
133
+ // Resolve all download URLs
134
+ const downloadPromises = soundDetails.map(detail => getDownloadUrl(detail.detail_url));
135
+ const downloadUrls = await Promise.all(downloadPromises);
136
+ const finalResults = soundDetails.map((detail, index) => ({
137
+ label: detail.label,
138
+ download_url: downloadUrls[index] || "not-found"
139
+ })).sort((a, b) => a.label.localeCompare(b.label));
140
+ // Validate all results before returning
141
+ return (0, validation_schemas_1.validateSoundsArray)(finalResults);
142
+ };
143
+ exports.getAllResults = getAllResults;
54
144
  //# sourceMappingURL=my-instants.service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"my-instants.service.js","sourceRoot":"","sources":["../../src/service/my-instants.service.ts"],"names":[],"mappings":";;;AAAA,4DAGgC;AAChC,+BAA+B;AAE/B,MAAM,cAAc,GAAG,KAAK,EAAE,WAA0B,EAAE,EAAE;IAC1D,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,WAAW,CAAC;IACrB,CAAC;IAED;;;;;;;;;;OAUG;IACH,MAAM,cAAc,GAAG,WAAW;SAC/B,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;SAC3B,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAEpB,MAAM,YAAY,GAAG,MAAM,IAAA,qCAAmB,EAAC,WAAW,CAAC,CAAC;IAE5D,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAC7B,IAAI,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CACpE,CAAC,IAAI,CAAC,CAAC,IAAuB,EAAE,EAAE;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,6BAA6B,YAAY,CAAC,IAAI,EAAE,CAAC;IAC1D,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC,CAAC;AAEK,MAAM,aAAa,GAAG,KAAK,EAAE,YAAoB,EAAE,EAAE;IAC1D,MAAM,MAAM,GAAG,MAAM,IAAA,+BAAgB,EAAC,YAAY,CAAC,CAAC;IAEpD,MAAM,SAAS,GAAkB,EAAE,CAAC;IACpC,MAAM,gBAAgB,GAA2B,EAAE,CAAC;IAEpD,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEvC,QAAQ,CAAC,MAAM,CAAC,QAAQ;aACrB,gBAAgB,CAAC,8BAA8B,CAAC;aAChD,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAChB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACjC,gBAAgB,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;IACP,CAAC;IAED,MAAM,gBAAgB,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAE7D,MAAM,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACjD,KAAK;QACL,YAAY,EAAE,gBAAgB,CAAC,KAAK,CAAC,IAAI,WAAW;KACrD,CAAC,CAAC,CAAC;IAEJ,OAAO,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC;AAzBW,QAAA,aAAa,iBAyBxB"}
1
+ {"version":3,"file":"my-instants.service.js","sourceRoot":"","sources":["../../src/service/my-instants.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * Pagination utility functions for the soundboard downloader CLI
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getPaginationInfo = exports.getSoundSelections = exports.getNavigationAction = exports.createPaginationChoices = exports.isPaginationAction = exports.isNavigationAction = void 0;
7
+ /**
8
+ * Type guard to check if a value is a navigation action
9
+ */
10
+ const isNavigationAction = (value) => {
11
+ return value.startsWith("action:");
12
+ };
13
+ exports.isNavigationAction = isNavigationAction;
14
+ /**
15
+ * Type guard to check if a value is a specific pagination action
16
+ */
17
+ const isPaginationAction = (value) => {
18
+ return (0, exports.isNavigationAction)(value) && [
19
+ 'action:prev-page',
20
+ 'action:next-page',
21
+ 'action:new-search',
22
+ 'action:show-all'
23
+ ].includes(value);
24
+ };
25
+ exports.isPaginationAction = isPaginationAction;
26
+ /**
27
+ * Create pagination choices for the CLI interface
28
+ */
29
+ const createPaginationChoices = (currentPage, hasNextPage, hasPreviousPage, includeAllOption = false) => {
30
+ const choices = [
31
+ {
32
+ name: hasPreviousPage ? "⏮️ Previous page" : "⏮️ Previous page",
33
+ value: "action:prev-page",
34
+ disabled: !hasPreviousPage
35
+ },
36
+ {
37
+ name: hasNextPage ? "⏭️ Next page" : "⏭️ Next page",
38
+ value: "action:next-page",
39
+ disabled: !hasNextPage
40
+ },
41
+ {
42
+ name: "🔍 New search",
43
+ value: "action:new-search"
44
+ }
45
+ ];
46
+ if (includeAllOption) {
47
+ choices.push({
48
+ name: "📋 Show all results",
49
+ value: "action:show-all"
50
+ });
51
+ }
52
+ return choices;
53
+ };
54
+ exports.createPaginationChoices = createPaginationChoices;
55
+ /**
56
+ * Extract navigation action from selections
57
+ * @returns The navigation action if found, null otherwise
58
+ */
59
+ const getNavigationAction = (selections) => {
60
+ const navigationAction = selections.find(exports.isPaginationAction);
61
+ return navigationAction || null;
62
+ };
63
+ exports.getNavigationAction = getNavigationAction;
64
+ /**
65
+ * Filter out navigation actions from selections
66
+ * @returns Array of sound selections only
67
+ */
68
+ const getSoundSelections = (selections) => {
69
+ return selections.filter((selection) => !(0, exports.isNavigationAction)(selection));
70
+ };
71
+ exports.getSoundSelections = getSoundSelections;
72
+ /**
73
+ * Get pagination info string for display
74
+ */
75
+ const getPaginationInfo = (currentPage, resultsCount, hasNextPage) => {
76
+ const pageInfo = hasNextPage
77
+ ? `Page ${currentPage}`
78
+ : `Page ${currentPage} (last page)`;
79
+ return `🎉 ${pageInfo}: ${resultsCount} sound${resultsCount !== 1 ? "s" : ""}!`;
80
+ };
81
+ exports.getPaginationInfo = getPaginationInfo;
82
+ //# sourceMappingURL=pagination-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pagination-utils.js","sourceRoot":"","sources":["../../src/utils/pagination-utils.ts"],"names":[],"mappings":";AAAA;;GAEG;;;AAIH;;GAEG;AACI,MAAM,kBAAkB,GAAG,CAAC,KAAa,EAA6B,EAAE;IAC7E,OAAO,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;AACrC,CAAC,CAAC;AAFW,QAAA,kBAAkB,sBAE7B;AAEF;;GAEG;AACI,MAAM,kBAAkB,GAAG,CAAC,KAAa,EAA6B,EAAE;IAC7E,OAAO,IAAA,0BAAkB,EAAC,KAAK,CAAC,IAAI;QAClC,kBAAkB;QAClB,kBAAkB;QAClB,mBAAmB;QACnB,iBAAiB;KAClB,CAAC,QAAQ,CAAC,KAAyB,CAAC,CAAC;AACxC,CAAC,CAAC;AAPW,QAAA,kBAAkB,sBAO7B;AAEF;;GAEG;AACI,MAAM,uBAAuB,GAAG,CACrC,WAAmB,EACnB,WAAoB,EACpB,eAAwB,EACxB,mBAA4B,KAAK,EACqC,EAAE;IACxE,MAAM,OAAO,GAAyE;QACpF;YACE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,kBAAkB;YAC/D,KAAK,EAAE,kBAAkB;YACzB,QAAQ,EAAE,CAAC,eAAe;SAC3B;QACD;YACE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,cAAc;YACnD,KAAK,EAAE,kBAAkB;YACzB,QAAQ,EAAE,CAAC,WAAW;SACvB;QACD;YACE,IAAI,EAAE,eAAe;YACrB,KAAK,EAAE,mBAAmB;SAC3B;KACF,CAAC;IAEF,IAAI,gBAAgB,EAAE,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,qBAAqB;YAC3B,KAAK,EAAE,iBAAiB;SACzB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AA/BW,QAAA,uBAAuB,2BA+BlC;AAEF;;;GAGG;AACI,MAAM,mBAAmB,GAAG,CAAC,UAAoB,EAA2B,EAAE;IACnF,MAAM,gBAAgB,GAAG,UAAU,CAAC,IAAI,CAAC,0BAAkB,CAAC,CAAC;IAC7D,OAAO,gBAAgB,IAAI,IAAI,CAAC;AAClC,CAAC,CAAC;AAHW,QAAA,mBAAmB,uBAG9B;AAEF;;;GAGG;AACI,MAAM,kBAAkB,GAAG,CAAC,UAAoB,EAAoB,EAAE;IAC3E,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAA+B,EAAE,CAClE,CAAC,IAAA,0BAAkB,EAAC,SAAS,CAAC,CAC/B,CAAC;AACJ,CAAC,CAAC;AAJW,QAAA,kBAAkB,sBAI7B;AAEF;;GAEG;AACI,MAAM,iBAAiB,GAAG,CAC/B,WAAmB,EACnB,YAAoB,EACpB,WAAoB,EACZ,EAAE;IACV,MAAM,QAAQ,GAAG,WAAW;QAC1B,CAAC,CAAC,QAAQ,WAAW,EAAE;QACvB,CAAC,CAAC,QAAQ,WAAW,cAAc,CAAC;IAEtC,OAAO,MAAM,QAAQ,KAAK,YAAY,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;AAClF,CAAC,CAAC;AAVW,QAAA,iBAAiB,qBAU5B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@purpleproser/soundboard-downloader-cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Easily download sounds from myinstants.com using this interactive CLI tool",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -44,6 +44,7 @@
44
44
  "@inquirer/prompts": "^8.2.0",
45
45
  "jsdom": "^28.0.0",
46
46
  "open": "^11.0.0",
47
- "ora": "^9.3.0"
47
+ "ora": "^9.3.0",
48
+ "valibot": "^1.2.0"
48
49
  }
49
50
  }
@@ -0,0 +1,128 @@
1
+ # API Mocking System
2
+
3
+ This directory contains the API mocking system for the soundboard downloader CLI.
4
+
5
+ ## Overview
6
+
7
+ The mocking system allows you to switch between real API calls to myinstants.com and mock data for testing purposes. This is particularly useful for:
8
+
9
+ - Running tests in CI environments (GitHub Actions, etc.)
10
+ - Avoiding rate limiting issues
11
+ - Having predictable test data
12
+ - Faster test execution
13
+ - Offline development
14
+
15
+ ## Files
16
+
17
+ - `api-config.ts` - Configuration system for switching between real and mock API
18
+ - `my-instants.api.ts` - Main API layer with conditional mock support
19
+ - `my-instants.api.mock.ts` - Mock implementation with predictable test data
20
+
21
+ ## Usage
22
+
23
+ ### In Tests
24
+
25
+ ```typescript
26
+ import { setApiMode } from "./api/api-config";
27
+ import { getPaginatedResults, getAllResults } from "./service/my-instants.service";
28
+
29
+ // Switch to mock mode before running tests
30
+ beforeAll(() => {
31
+ setApiMode("mock");
32
+ });
33
+
34
+ // Now all API calls will use mock data
35
+ test("pagination works", async () => {
36
+ const results = await getPaginatedResults("test", 1);
37
+ // results will contain predictable mock data
38
+ });
39
+ ```
40
+
41
+ ### In Development
42
+
43
+ ```typescript
44
+ import { setApiMode, getApiMode, isMockMode } from "./api/api-config";
45
+
46
+ // Check current mode
47
+ console.log(`Current mode: ${getApiMode()}`); // 'real' or 'mock'
48
+ console.log(`Using mock: ${isMockMode()}`); // true or false
49
+
50
+ // Switch modes
51
+ setApiMode("mock"); // Use mock data
52
+ setApiMode("real"); // Use real API
53
+ ```
54
+
55
+ ## Mock Data Structure
56
+
57
+ The mock API provides predictable test data:
58
+
59
+ - **14 total sounds** in the mock database
60
+ - **6 sounds per page** for pagination
61
+ - **Valid download URLs** for all sounds
62
+ - **Consistent labels** (Wilhelm Scream, THX Deep Note, Test Sound 1-12)
63
+
64
+ ### Example Mock Results
65
+
66
+ **Pagination (Page 1):**
67
+ ```json
68
+ [
69
+ {"label": "Wilhelm Scream", "download_url": "https://.../test-sound.mp3"},
70
+ {"label": "THX Deep Note", "download_url": "https://.../test-sound.mp3"},
71
+ {"label": "Test Sound 1", "download_url": "https://.../test-sound.mp3"},
72
+ {"label": "Test Sound 2", "download_url": "https://.../test-sound.mp3"},
73
+ {"label": "Test Sound 3", "download_url": "https://.../test-sound.mp3"},
74
+ {"label": "Test Sound 4", "download_url": "https://.../test-sound.mp3"}
75
+ ]
76
+ ```
77
+
78
+ **Fetch All:**
79
+ - Returns all 14 sounds sorted alphabetically
80
+ - Same structure as pagination results
81
+
82
+ ## CI Integration
83
+
84
+ The mock API is automatically used in CI environments. In your GitHub Actions workflow:
85
+
86
+ ```yaml
87
+ - name: Run tests
88
+ run: npm test
89
+ env:
90
+ NODE_ENV: test
91
+ ```
92
+
93
+ The test file automatically detects CI environments and uses mock data.
94
+
95
+ ## Benefits
96
+
97
+ 1. **Reliability**: Tests don't depend on external API availability
98
+ 2. **Speed**: Mock responses are instantaneous
99
+ 3. **Predictability**: Always get the same test data
100
+ 4. **No Rate Limiting**: Avoid GitHub Actions IP blocking
101
+ 5. **Offline Testing**: Can run tests without internet connection
102
+
103
+ ## Implementation Details
104
+
105
+ The mock system works by:
106
+
107
+ 1. **Intercepting API calls**: Each function in `my-instants.api.ts` checks `isMockMode()`
108
+ 2. **Returning mock data**: If in mock mode, calls the corresponding mock function
109
+ 3. **Maintaining interface**: Mock functions have the same signature as real functions
110
+ 4. **HTML templates**: Mock functions return HTML strings that match the real site structure
111
+
112
+ The mock HTML templates include the necessary DOM structure that the service layer expects to parse.
113
+
114
+ ## Adding More Mock Data
115
+
116
+ To add more test sounds, edit the `mockSounds` array in `my-instants.api.mock.ts`:
117
+
118
+ ```typescript
119
+ const mockSounds = [
120
+ ...existingSounds,
121
+ {
122
+ label: "New Test Sound",
123
+ detail_url: "/en/instant/new-test-sound/"
124
+ }
125
+ ];
126
+ ```
127
+
128
+ The system will automatically handle pagination based on the array length.