@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 +14 -0
- package/dist/api/api-config.js +31 -0
- package/dist/api/api-config.js.map +1 -0
- package/dist/api/my-instants.api.js +88 -28
- package/dist/api/my-instants.api.js.map +1 -1
- package/dist/api/my-instants.api.mock.js +136 -0
- package/dist/api/my-instants.api.mock.js.map +1 -0
- package/dist/api/validation-schemas.js +67 -0
- package/dist/api/validation-schemas.js.map +1 -0
- package/dist/common/types/query.type.js +3 -0
- package/dist/common/types/query.type.js.map +1 -0
- package/dist/main.js +286 -92
- package/dist/main.js.map +1 -1
- package/dist/service/my-instants.service.js +115 -25
- package/dist/service/my-instants.service.js.map +1 -1
- package/dist/utils/pagination-utils.js +82 -0
- package/dist/utils/pagination-utils.js.map +1 -0
- package/package.json +3 -2
- package/src/api/README.md +128 -0
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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.
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 @@
|
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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":";;;AAEA,
|
|
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.
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
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.
|