@purpleproser/soundboard-downloader-cli 1.0.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/README.md +173 -0
- package/dist/api/my-instants.api.js +46 -0
- package/dist/api/my-instants.api.js.map +1 -0
- package/dist/main.js +62 -0
- package/dist/main.js.map +1 -0
- package/dist/service/file-downloader.service.js +21 -0
- package/dist/service/file-downloader.service.js.map +1 -0
- package/dist/service/my-instants.service.js +54 -0
- package/dist/service/my-instants.service.js.map +1 -0
- package/package.json +50 -0
- package/soundboard-downloader-cli-1.0.0.tgz +0 -0
- package/tsconfig.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# @purpleproser/soundboard-downloader-cli
|
|
2
|
+
|
|
3
|
+
A command-line tool to download soundboard sounds from MyInstants.
|
|
4
|
+
|
|
5
|
+
**npm Package**: `@purpleproser/soundboard-downloader-cli`
|
|
6
|
+
**GitHub Repository**: [blacksagres/soundboard-downloader-cli](https://github.com/blacksagres/soundboard-downloader-cli) Built with TypeScript and Node.js.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 🔍 Search for sounds by name using interactive prompts
|
|
11
|
+
- 🎵 Preview sounds by playing them in your browser
|
|
12
|
+
- ⬇️ Download individual sounds to your local machine
|
|
13
|
+
- ✨ Simple and easy-to-use interactive interface
|
|
14
|
+
|
|
15
|
+
## Tech Stack
|
|
16
|
+
|
|
17
|
+
- **TypeScript** - Type-safe development
|
|
18
|
+
- **Node.js** - Runtime environment
|
|
19
|
+
- **Inquirer** - Interactive CLI prompts
|
|
20
|
+
- **jsdom** - HTML parsing and DOM manipulation
|
|
21
|
+
- **ora** - Elegant terminal spinners
|
|
22
|
+
- **open** - Opens URLs in the user's browser
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- Node.js 16.x or higher
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
### Local Installation
|
|
31
|
+
|
|
32
|
+
1. Clone this repository:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/blacksagres/soundboard-downloader-cli.git
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
2. Navigate to the project directory:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd soundboard-downloader-cli
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
3. Install dependencies:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Global Installation
|
|
51
|
+
|
|
52
|
+
Install globally to use the `soundboard-downloader` command anywhere:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install -g @purpleproser/soundboard-downloader-cli
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Or from the project directory:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm link
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Note**: This package uses the `@purpleproser` npm scope (my npm username) while the GitHub repository remains under `blacksagres`. Make sure to include the `@purpleproser/` prefix when installing!
|
|
65
|
+
|
|
66
|
+
**For Developers**: If you fork this project and want to publish your own version, either:
|
|
67
|
+
1. Use a different package name, or
|
|
68
|
+
2. Create your own npm scope and update the package name, or
|
|
69
|
+
3. Add `"publishConfig": { "access": "public" }` to your package.json
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
### Development Mode
|
|
74
|
+
|
|
75
|
+
Run directly with ts-node (no build required):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm start
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Production Build
|
|
82
|
+
|
|
83
|
+
Build the TypeScript project:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm run build
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then run the compiled JavaScript:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
node dist/main.js
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Global Command
|
|
96
|
+
|
|
97
|
+
If installed globally:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
soundboard-downloader
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Interactive Workflow
|
|
104
|
+
|
|
105
|
+
The CLI will guide you through an interactive process:
|
|
106
|
+
|
|
107
|
+
1. **Search**: You'll be prompted to enter what sound effects you're looking for
|
|
108
|
+
2. **Select**: Choose from the list of matching sounds
|
|
109
|
+
3. **Action**: Decide whether to play the sound in your browser or download it to your `./temp/` directory
|
|
110
|
+
|
|
111
|
+
### Example Workflow
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
$ npm start
|
|
115
|
+
|
|
116
|
+
? What sound effects are you looking for? wilhelm scream
|
|
117
|
+
✔ Loading result...
|
|
118
|
+
? Which one to download? (Use arrow keys)
|
|
119
|
+
> Wilhelm Scream - Original
|
|
120
|
+
Wilhelm Scream - Remastered
|
|
121
|
+
Wilhelm Scream - Short Version
|
|
122
|
+
? Selected: Wilhelm Scream - Original
|
|
123
|
+
? (Use arrow keys)
|
|
124
|
+
> Download
|
|
125
|
+
Play
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
### Project Structure
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
src/
|
|
134
|
+
├── main.ts # Entry point
|
|
135
|
+
├── api/
|
|
136
|
+
│ └── my-instants.api.ts # API layer for MyInstants
|
|
137
|
+
├── components/ # CLI components
|
|
138
|
+
└── service/
|
|
139
|
+
└── my-instants.service.ts # Business logic
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Available Scripts
|
|
143
|
+
|
|
144
|
+
- `npm start` - Run in development mode with ts-node
|
|
145
|
+
- `npm run build` - Compile TypeScript to JavaScript
|
|
146
|
+
- `npm test` - Run tests (not yet implemented)
|
|
147
|
+
|
|
148
|
+
### Download Location
|
|
149
|
+
|
|
150
|
+
Downloaded sounds are saved to the `./temp/` directory in the project root.
|
|
151
|
+
|
|
152
|
+
## Contributing
|
|
153
|
+
|
|
154
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
155
|
+
|
|
156
|
+
## Legal Disclaimer
|
|
157
|
+
|
|
158
|
+
**Important Notice About Copyright and Usage:**
|
|
159
|
+
|
|
160
|
+
This tool is an automation layer that simulates what a real user would do manually on the MyInstants website. It uses the official download functionality provided by MyInstants and does not bypass any restrictions or access protected content.
|
|
161
|
+
|
|
162
|
+
**User Responsibility:**
|
|
163
|
+
|
|
164
|
+
- You are solely responsible for ensuring that your use of downloaded content complies with all applicable laws and MyInstants' Terms of Service
|
|
165
|
+
- This application is not responsible for any copyright infringement that may occur through the use of downloaded audio files
|
|
166
|
+
- Many sound effects may be copyrighted - check the specific rights and licenses for each sound before using it
|
|
167
|
+
|
|
168
|
+
**Experimental Nature:**
|
|
169
|
+
This project is a simple experiment with Node.js and CLI development. It is provided "as is" without warranty of any kind. The developers are not affiliated with MyInstants and cannot guarantee the continued functionality of this tool.
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
This project is licensed under the MIT License.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Fetch the html from myinstants.com.
|
|
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)
|
|
10
|
+
*/
|
|
11
|
+
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 = [];
|
|
16
|
+
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
|
+
*/
|
|
26
|
+
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);
|
|
32
|
+
page++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
exports.getSoundNodes = getSoundNodes;
|
|
40
|
+
const getNodeDownloadPage = async (soundNodeDetailsURL) => {
|
|
41
|
+
const root = `https://www.myinstants.com${soundNodeDetailsURL}`;
|
|
42
|
+
const response = await fetch(root);
|
|
43
|
+
return await response.text();
|
|
44
|
+
};
|
|
45
|
+
exports.getNodeDownloadPage = getNodeDownloadPage;
|
|
46
|
+
//# sourceMappingURL=my-instants.api.js.map
|
|
@@ -0,0 +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"}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const my_instants_service_1 = require("./service/my-instants.service");
|
|
5
|
+
const ora_1 = require("ora");
|
|
6
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
7
|
+
const open_1 = require("open");
|
|
8
|
+
const file_downloader_service_1 = require("./service/file-downloader.service");
|
|
9
|
+
(async function () {
|
|
10
|
+
const answer = await (0, prompts_1.input)({
|
|
11
|
+
message: "What sound effects are you looking for?",
|
|
12
|
+
});
|
|
13
|
+
const spinner = (0, ora_1.default)({
|
|
14
|
+
text: "Loading result...",
|
|
15
|
+
color: "magenta",
|
|
16
|
+
spinner: "bouncingBall",
|
|
17
|
+
}).start();
|
|
18
|
+
const sounds = await (0, my_instants_service_1.getSoundNodes)(answer);
|
|
19
|
+
spinner.stop();
|
|
20
|
+
const selection = await (0, prompts_1.select)({
|
|
21
|
+
message: "Which one to download?",
|
|
22
|
+
choices: sounds.map((sound) => ({
|
|
23
|
+
name: sound.label,
|
|
24
|
+
value: `${sound.label}||${sound.download_url}`,
|
|
25
|
+
})),
|
|
26
|
+
});
|
|
27
|
+
const [label, downloadUrl] = selection.split("||");
|
|
28
|
+
if (!downloadUrl) {
|
|
29
|
+
throw new ReferenceError(`There is no download URL for: ${label}`);
|
|
30
|
+
}
|
|
31
|
+
const actions = [
|
|
32
|
+
{
|
|
33
|
+
name: "Download",
|
|
34
|
+
value: "action:download",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "Play",
|
|
38
|
+
value: "action:play",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
const action = await (0, prompts_1.select)({
|
|
42
|
+
message: `Selected: ${label}`,
|
|
43
|
+
choices: actions,
|
|
44
|
+
});
|
|
45
|
+
if (action === "action:play") {
|
|
46
|
+
(0, open_1.default)(downloadUrl);
|
|
47
|
+
}
|
|
48
|
+
if (action === "action:download") {
|
|
49
|
+
const downloadSpinner = (0, ora_1.default)({
|
|
50
|
+
text: "Downloading file...",
|
|
51
|
+
color: "cyan",
|
|
52
|
+
spinner: "growHorizontal",
|
|
53
|
+
}).start();
|
|
54
|
+
const downloadFileName = downloadUrl.split("/").pop();
|
|
55
|
+
(0, file_downloader_service_1.downloadFile)(downloadUrl, {
|
|
56
|
+
destination: `./temp/${downloadFileName}`,
|
|
57
|
+
onStart: () => downloadSpinner.start(),
|
|
58
|
+
onFinish: () => downloadSpinner.stop(),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;;AAEA,uEAA8D;AAC9D,6BAAsB;AACtB,+CAAkD;AAClD,+BAAwB;AACxB,+EAAiE;AAEjE,CAAC,KAAK;IACJ,MAAM,MAAM,GAAG,MAAM,IAAA,eAAK,EAAC;QACzB,OAAO,EAAE,yCAAyC;KACnD,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,IAAA,aAAG,EAAC;QAClB,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,cAAc;KACxB,CAAC,CAAC,KAAK,EAAE,CAAC;IAEX,MAAM,MAAM,GAAG,MAAM,IAAA,mCAAa,EAAC,MAAM,CAAC,CAAC;IAE3C,OAAO,CAAC,IAAI,EAAE,CAAC;IAEf,MAAM,SAAS,GAAG,MAAM,IAAA,gBAAM,EAAC;QAC7B,OAAO,EAAE,wBAAwB;QACjC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,KAAK,CAAC,KAAK;YACjB,KAAK,EAAE,GAAG,KAAK,CAAC,KAAK,KAAK,KAAK,CAAC,YAAY,EAAW;SACxD,CAAC,CAAC;KACJ,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEnD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,cAAc,CAAC,iCAAiC,KAAK,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,OAAO,GAAG;QACd;YACE,IAAI,EAAE,UAAU;YAChB,KAAK,EAAE,iBAAiB;SACzB;QACD;YACE,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,aAAa;SACrB;KACO,CAAC;IAEX,MAAM,MAAM,GAAG,MAAM,IAAA,gBAAM,EAAC;QAC1B,OAAO,EAAE,aAAa,KAAK,EAAE;QAC7B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,IAAI,MAAM,KAAK,aAAa,EAAE,CAAC;QAC7B,IAAA,cAAI,EAAC,WAAW,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,MAAM,KAAK,iBAAiB,EAAE,CAAC;QACjC,MAAM,eAAe,GAAG,IAAA,aAAG,EAAC;YAC1B,IAAI,EAAE,qBAAqB;YAC3B,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,gBAAgB;SAC1B,CAAC,CAAC,KAAK,EAAE,CAAC;QAEX,MAAM,gBAAgB,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;QAEtD,IAAA,sCAAY,EAAC,WAAW,EAAE;YACxB,WAAW,EAAE,UAAU,gBAAgB,EAAE;YACzC,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE;YACtC,QAAQ,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE;SACvC,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC,EAAE,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.downloadFile = void 0;
|
|
4
|
+
const https = require("https");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const downloadFile = (url, config) => {
|
|
7
|
+
const file = fs.createWriteStream(config.destination);
|
|
8
|
+
https.get(url, function (response) {
|
|
9
|
+
response.pipe(file);
|
|
10
|
+
file.on("open", () => {
|
|
11
|
+
config.onStart?.();
|
|
12
|
+
});
|
|
13
|
+
file.on("finish", () => {
|
|
14
|
+
file.close();
|
|
15
|
+
config.onFinish?.();
|
|
16
|
+
console.log("Download Completed");
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
exports.downloadFile = downloadFile;
|
|
21
|
+
//# sourceMappingURL=file-downloader.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-downloader.service.js","sourceRoot":"","sources":["../../src/service/file-downloader.service.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAC/B,yBAAyB;AAElB,MAAM,YAAY,GAAG,CAC1B,GAAW,EACX,MAIC,EACD,EAAE;IACF,MAAM,IAAI,GAAG,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAEtD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,QAAQ;QAC/B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEpB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACnB,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAvBW,QAAA,YAAY,gBAuBvB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSoundNodes = void 0;
|
|
4
|
+
const my_instants_api_1 = require("../api/my-instants.api");
|
|
5
|
+
const jsdom = require("jsdom");
|
|
6
|
+
const getDownloadUrl = async (originalUrl) => {
|
|
7
|
+
if (!originalUrl) {
|
|
8
|
+
return "not-found";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* For example, this is what the url looks like from the link:
|
|
12
|
+
*
|
|
13
|
+
* '/en/instant/nemesis-resident-evil-3-stars/'
|
|
14
|
+
*
|
|
15
|
+
* And we want it to be like this:
|
|
16
|
+
*
|
|
17
|
+
* 'nemesis-resident-evil-3-stars'
|
|
18
|
+
*
|
|
19
|
+
* To eventually combine with the download url template and make a final link.
|
|
20
|
+
*/
|
|
21
|
+
const cleanSoundName = originalUrl
|
|
22
|
+
.replace("/en/instant/", "")
|
|
23
|
+
.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}`;
|
|
30
|
+
}
|
|
31
|
+
return "not-found";
|
|
32
|
+
};
|
|
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
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const allLinksResolved = await Promise.all(allDownloadLinks);
|
|
47
|
+
const finalList = allLabels.map((label, index) => ({
|
|
48
|
+
label,
|
|
49
|
+
download_url: allLinksResolved[index] ?? "not-found",
|
|
50
|
+
}));
|
|
51
|
+
return finalList.toSorted((a, b) => a.label.localeCompare(b.label));
|
|
52
|
+
};
|
|
53
|
+
exports.getSoundNodes = getSoundNodes;
|
|
54
|
+
//# sourceMappingURL=my-instants.service.js.map
|
|
@@ -0,0 +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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@purpleproser/soundboard-downloader-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Easily download sounds from myinstants.com using this interactive CLI tool",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"author": "blacksagres",
|
|
10
|
+
"type": "commonjs",
|
|
11
|
+
"main": "dist/main.js",
|
|
12
|
+
"bin": {
|
|
13
|
+
"soundboard-downloader": "./dist/main.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
17
|
+
"start": "ts-node ./src/main.ts",
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"soundboard",
|
|
24
|
+
"myinstants",
|
|
25
|
+
"cli",
|
|
26
|
+
"downloader",
|
|
27
|
+
"audio",
|
|
28
|
+
"interactive"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/blacksagres/soundboard-downloader-cli.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/blacksagres/soundboard-downloader-cli/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/blacksagres/soundboard-downloader-cli#readme",
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/jsdom": "^27.0.0",
|
|
40
|
+
"@types/node": "^25.2.3",
|
|
41
|
+
"ts-node": "^10.9.2",
|
|
42
|
+
"typescript": "^5.9.3"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@inquirer/prompts": "^8.2.0",
|
|
46
|
+
"jsdom": "^28.0.0",
|
|
47
|
+
"open": "^11.0.0",
|
|
48
|
+
"ora": "^9.3.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
Binary file
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Visit https://aka.ms/tsconfig to read more about this file
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
// File Layout
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
|
|
8
|
+
// Environment Settings
|
|
9
|
+
// See also https://aka.ms/tsconfig/module
|
|
10
|
+
"module": "commonjs",
|
|
11
|
+
"target": "esnext",
|
|
12
|
+
// For nodejs:
|
|
13
|
+
"lib": ["esnext"],
|
|
14
|
+
"types": ["node"],
|
|
15
|
+
|
|
16
|
+
// Other Outputs
|
|
17
|
+
"sourceMap": true,
|
|
18
|
+
"declaration": true,
|
|
19
|
+
"declarationMap": true,
|
|
20
|
+
|
|
21
|
+
// Stricter Typechecking Options
|
|
22
|
+
"noUncheckedIndexedAccess": true,
|
|
23
|
+
"exactOptionalPropertyTypes": true,
|
|
24
|
+
|
|
25
|
+
// Style Options
|
|
26
|
+
// "noImplicitReturns": true,
|
|
27
|
+
// "noImplicitOverride": true,
|
|
28
|
+
// "noUnusedLocals": true,
|
|
29
|
+
// "noUnusedParameters": true,
|
|
30
|
+
// "noFallthroughCasesInSwitch": true,
|
|
31
|
+
// "noPropertyAccessFromIndexSignature": true,
|
|
32
|
+
|
|
33
|
+
// Recommended Options
|
|
34
|
+
"strict": true,
|
|
35
|
+
"jsx": "react-jsx",
|
|
36
|
+
"verbatimModuleSyntax": false,
|
|
37
|
+
"isolatedModules": true,
|
|
38
|
+
"noUncheckedSideEffectImports": true,
|
|
39
|
+
"moduleDetection": "force",
|
|
40
|
+
"skipLibCheck": true
|
|
41
|
+
}
|
|
42
|
+
}
|