@anjishnusengupta/ny-cli 3.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/LICENSE +21 -0
- package/README.md +419 -0
- package/backend.mjs +189 -0
- package/install.sh +161 -0
- package/ny-cli +1219 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Anjishnu Sengupta
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ✦ NY-CLI
|
|
4
|
+
|
|
5
|
+
<samp>ネコアニメ CLI — Watch Anime from Your Terminal</samp>
|
|
6
|
+
|
|
7
|
+
<br/>
|
|
8
|
+
|
|
9
|
+
[](https://github.com/AnjishnuSengupta/ny-cli/releases)
|
|
10
|
+
[](https://www.npmjs.com/package/@anjishnusengupta/ny-cli)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
[](https://github.com/AnjishnuSengupta/ny-cli/stargazers)
|
|
13
|
+
[](https://www.instagram.com/anjishnu.prolly)
|
|
14
|
+
|
|
15
|
+
<br/>
|
|
16
|
+
|
|
17
|
+
<kbd>[🌐 **NyAnime Website**](https://nyanime.tech)</kbd>
|
|
18
|
+
<kbd>[📦 **Releases**](https://github.com/AnjishnuSengupta/ny-cli/releases)</kbd>
|
|
19
|
+
<kbd>[🐛 **Report Bug**](https://github.com/AnjishnuSengupta/ny-cli/issues)</kbd>
|
|
20
|
+
|
|
21
|
+
<br/>
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<br/>
|
|
28
|
+
|
|
29
|
+
## 🎯 What's New in v3.0.0
|
|
30
|
+
|
|
31
|
+
<table>
|
|
32
|
+
<tr>
|
|
33
|
+
<td>🔧</td>
|
|
34
|
+
<td><b>Self-Hosted Scraping</b></td>
|
|
35
|
+
<td>Uses the <code>aniwatch</code> npm package directly — no external API dependency</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr>
|
|
38
|
+
<td>⚡</td>
|
|
39
|
+
<td><b>Parallel Server Racing</b></td>
|
|
40
|
+
<td><code>Promise.any()</code> races HD-1, HD-2, StreamTape & StreamSB simultaneously for fastest response</td>
|
|
41
|
+
</tr>
|
|
42
|
+
<tr>
|
|
43
|
+
<td>🔄</td>
|
|
44
|
+
<td><b>Sub/Dub Fallback</b></td>
|
|
45
|
+
<td>Automatically falls back from sub to dub if all sub servers fail</td>
|
|
46
|
+
</tr>
|
|
47
|
+
<tr>
|
|
48
|
+
<td>🚫</td>
|
|
49
|
+
<td><b>Zero External APIs</b></td>
|
|
50
|
+
<td>Everything runs locally via Node.js — no cold-start delays or API rate limits</td>
|
|
51
|
+
</tr>
|
|
52
|
+
<tr>
|
|
53
|
+
<td>☁️</td>
|
|
54
|
+
<td><b>Cloud Sync</b></td>
|
|
55
|
+
<td>Watch history syncs between CLI and <a href="https://nyanime.tech">nyanime.tech</a> web app</td>
|
|
56
|
+
</tr>
|
|
57
|
+
</table>
|
|
58
|
+
|
|
59
|
+
<br/>
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
<br/>
|
|
64
|
+
|
|
65
|
+
## ✨ Features
|
|
66
|
+
|
|
67
|
+
<div align="center">
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
╭─────────────────────────────────────────────────────────────────╮
|
|
71
|
+
│ │
|
|
72
|
+
│ 🎬 STREAMING 👤 EXPERIENCE 🔧 TECHNICAL │
|
|
73
|
+
│ ─────────────── ─────────────── ─────────────── │
|
|
74
|
+
│ │
|
|
75
|
+
│ ▸ HLS Streaming ▸ User Accounts ▸ POSIX Shell │
|
|
76
|
+
│ ▸ Multi-Server ▸ Watch History ▸ Node.js 18+ │
|
|
77
|
+
│ ▸ Sub/Dub Toggle ▸ Cloud Sync ▸ aniwatch pkg │
|
|
78
|
+
│ ▸ Skip Intro ▸ Continue Watch ▸ Self-Hosted │
|
|
79
|
+
│ ▸ Auto Subtitles ▸ Random Anime ▸ XDG Dirs │
|
|
80
|
+
│ ▸ MPV/VLC/IINA ▸ Profile System ▸ Zero Config │
|
|
81
|
+
│ │
|
|
82
|
+
╰─────────────────────────────────────────────────────────────────╯
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<br/>
|
|
88
|
+
|
|
89
|
+
<details>
|
|
90
|
+
<summary><b>📺 Streaming Highlights</b></summary>
|
|
91
|
+
|
|
92
|
+
<br/>
|
|
93
|
+
|
|
94
|
+
| Feature | Description |
|
|
95
|
+
|:--------|:------------|
|
|
96
|
+
| **🔄 Multi-Server Racing** | Races HD-1, HD-2, StreamTape, StreamSB in parallel via `Promise.any()` |
|
|
97
|
+
| **⏭️ Skip Intro** | Press `s` during intro to skip — uses API-provided timestamps |
|
|
98
|
+
| **📝 Multi-Language Subs** | Auto-selects English, with all available languages loaded for switching |
|
|
99
|
+
| **🔁 Sub/Dub Fallback** | If all sub servers fail, automatically retries with dub |
|
|
100
|
+
| **🎚️ Player Support** | MPV (recommended), VLC, IINA — auto-detected or configurable |
|
|
101
|
+
|
|
102
|
+
</details>
|
|
103
|
+
|
|
104
|
+
<details>
|
|
105
|
+
<summary><b>👤 User Features</b></summary>
|
|
106
|
+
|
|
107
|
+
<br/>
|
|
108
|
+
|
|
109
|
+
| Feature | Description |
|
|
110
|
+
|:--------|:------------|
|
|
111
|
+
| **🔐 Browser Auth** | Login via nyanime.tech — just paste your User ID |
|
|
112
|
+
| **📜 Watch History** | Track all watched episodes with timestamps |
|
|
113
|
+
| **☁️ Cloud Sync** | Seamless sync between CLI and nyanime.tech website |
|
|
114
|
+
| **📍 Continue Watching** | Resume from exactly where you left off |
|
|
115
|
+
| **🎲 Random Mode** | Discover new anime with random selection |
|
|
116
|
+
| **🔍 Quick Search** | Search directly from command line or interactive menu |
|
|
117
|
+
|
|
118
|
+
</details>
|
|
119
|
+
|
|
120
|
+
<br/>
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
<br/>
|
|
125
|
+
|
|
126
|
+
## 🖥️ Terminal Demo
|
|
127
|
+
|
|
128
|
+
<div align="center">
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
╔══════════════════════════════════════════╗
|
|
132
|
+
║ ║
|
|
133
|
+
║ $ ny-cli "one piece" ║
|
|
134
|
+
║ ║
|
|
135
|
+
║ Searching for 'one piece'... ║
|
|
136
|
+
║ 1) One Piece (TV, 1120 eps) ║
|
|
137
|
+
║ 2) One Piece Film: Red (Movie) ║
|
|
138
|
+
║ ║
|
|
139
|
+
║ Select [1-20]: 1 ║
|
|
140
|
+
║ Loading episodes... ║
|
|
141
|
+
║ Episode [1-1120]: 1120 ║
|
|
142
|
+
║ ║
|
|
143
|
+
║ ▸ Starting playback... ║
|
|
144
|
+
║ One Piece - Episode 1120 ║
|
|
145
|
+
║ ║
|
|
146
|
+
╚══════════════════════════════════════════╝
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<br/>
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
<br/>
|
|
156
|
+
|
|
157
|
+
## 🚀 Quick Start
|
|
158
|
+
|
|
159
|
+
<br/>
|
|
160
|
+
|
|
161
|
+
### Prerequisites
|
|
162
|
+
|
|
163
|
+
- **Node.js** 18+
|
|
164
|
+
- **npm**
|
|
165
|
+
- **mpv** (recommended video player)
|
|
166
|
+
|
|
167
|
+
<br/>
|
|
168
|
+
|
|
169
|
+
### Installation
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# One-line install
|
|
173
|
+
curl -sL https://raw.githubusercontent.com/AnjishnuSengupta/ny-cli/main/install.sh | sh
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
# Or via npm
|
|
178
|
+
npm install -g @anjishnusengupta/ny-cli
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
<details>
|
|
182
|
+
<summary><b>📥 Manual Install</b></summary>
|
|
183
|
+
|
|
184
|
+
<br/>
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# Clone the repository
|
|
188
|
+
git clone https://github.com/AnjishnuSengupta/ny-cli.git
|
|
189
|
+
cd ny-cli
|
|
190
|
+
|
|
191
|
+
# Install dependencies
|
|
192
|
+
npm install --production
|
|
193
|
+
|
|
194
|
+
# Make executable and install
|
|
195
|
+
chmod +x ny-cli
|
|
196
|
+
sudo ln -sf "$(pwd)/ny-cli" /usr/local/bin/ny-cli
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
</details>
|
|
200
|
+
|
|
201
|
+
<details>
|
|
202
|
+
<summary><b>🐧 Arch Linux (AUR)</b></summary>
|
|
203
|
+
|
|
204
|
+
<br/>
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
yay -S ny-cli
|
|
208
|
+
# or
|
|
209
|
+
paru -S ny-cli
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
</details>
|
|
213
|
+
|
|
214
|
+
<br/>
|
|
215
|
+
|
|
216
|
+
### Usage
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
# Interactive mode
|
|
220
|
+
ny-cli
|
|
221
|
+
|
|
222
|
+
# Quick search
|
|
223
|
+
ny-cli "attack on titan"
|
|
224
|
+
|
|
225
|
+
# Direct search
|
|
226
|
+
ny-cli -s "one piece"
|
|
227
|
+
|
|
228
|
+
# Continue watching
|
|
229
|
+
ny-cli -c
|
|
230
|
+
|
|
231
|
+
# Trending anime
|
|
232
|
+
ny-cli -t
|
|
233
|
+
|
|
234
|
+
# Random anime
|
|
235
|
+
ny-cli -r
|
|
236
|
+
|
|
237
|
+
# Login for cloud sync
|
|
238
|
+
ny-cli -l
|
|
239
|
+
|
|
240
|
+
# Help
|
|
241
|
+
ny-cli -h
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
<br/>
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
<br/>
|
|
249
|
+
|
|
250
|
+
## 🛠️ Tech Stack
|
|
251
|
+
|
|
252
|
+
<br/>
|
|
253
|
+
|
|
254
|
+
<div align="center">
|
|
255
|
+
|
|
256
|
+
| Layer | Technologies |
|
|
257
|
+
|:-----:|:-------------|
|
|
258
|
+
| **CLI** |   |
|
|
259
|
+
| **Backend** |   |
|
|
260
|
+
| **Scraping** |   |
|
|
261
|
+
| **Sync** |   |
|
|
262
|
+
| **Players** |   |
|
|
263
|
+
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<br/>
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
<br/>
|
|
271
|
+
|
|
272
|
+
## 📁 Project Structure
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
ny-cli/
|
|
276
|
+
├── 📄 ny-cli # Main CLI script (POSIX shell)
|
|
277
|
+
├── 📄 backend.mjs # Node.js scraping backend (aniwatch)
|
|
278
|
+
├── 📄 package.json # npm dependencies
|
|
279
|
+
├── 📄 install.sh # One-line installer
|
|
280
|
+
├── 📂 debian/ # Debian packaging
|
|
281
|
+
│ ├── changelog
|
|
282
|
+
│ ├── control
|
|
283
|
+
│ ├── install
|
|
284
|
+
│ └── rules
|
|
285
|
+
├── 📄 PKGBUILD # Arch Linux AUR package
|
|
286
|
+
├── 📄 ny-cli.spec # RPM spec file
|
|
287
|
+
└── 📄 LICENSE # MIT License
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
<br/>
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
<br/>
|
|
295
|
+
|
|
296
|
+
## 🎮 Controls
|
|
297
|
+
|
|
298
|
+
### During Playback (mpv)
|
|
299
|
+
|
|
300
|
+
| Key | Action |
|
|
301
|
+
|:---:|:-------|
|
|
302
|
+
| `Space` | Play / Pause |
|
|
303
|
+
| `←` / `→` | Seek ±5s |
|
|
304
|
+
| `↑` / `↓` | Seek ±60s |
|
|
305
|
+
| `s` | Skip intro |
|
|
306
|
+
| `f` | Fullscreen |
|
|
307
|
+
| `v` | Toggle subtitles |
|
|
308
|
+
| `m` | Mute |
|
|
309
|
+
| `q` | Quit |
|
|
310
|
+
|
|
311
|
+
<br/>
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
<br/>
|
|
316
|
+
|
|
317
|
+
## 🤝 Contributing
|
|
318
|
+
|
|
319
|
+
<br/>
|
|
320
|
+
|
|
321
|
+
Contributions are welcome! Here's how you can help:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
# 1. Fork the repository
|
|
325
|
+
|
|
326
|
+
# 2. Create your feature branch
|
|
327
|
+
git checkout -b feature/amazing-feature
|
|
328
|
+
|
|
329
|
+
# 3. Commit your changes
|
|
330
|
+
git commit -m "feat: add amazing feature"
|
|
331
|
+
|
|
332
|
+
# 4. Push to the branch
|
|
333
|
+
git push origin feature/amazing-feature
|
|
334
|
+
|
|
335
|
+
# 5. Open a Pull Request
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
<br/>
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
<br/>
|
|
343
|
+
|
|
344
|
+
## 🔗 Links & Resources
|
|
345
|
+
|
|
346
|
+
<br/>
|
|
347
|
+
|
|
348
|
+
<div align="center">
|
|
349
|
+
|
|
350
|
+
| | |
|
|
351
|
+
|:-:|:-:|
|
|
352
|
+
| 🌐 **Website** | [nyanime.tech](https://nyanime.tech) |
|
|
353
|
+
| 🖥️ **Web App** | [github.com/AnjishnuSengupta/nyanime](https://github.com/AnjishnuSengupta/nyanime) |
|
|
354
|
+
| 📦 **npm** | [@anjishnusengupta/ny-cli](https://www.npmjs.com/package/@anjishnusengupta/ny-cli) |
|
|
355
|
+
| 📚 **aniwatch** | [ghoshRitesh12/aniwatch](https://github.com/ghoshRitesh12/aniwatch) |
|
|
356
|
+
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<br/>
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
<br/>
|
|
364
|
+
|
|
365
|
+
## 📜 License
|
|
366
|
+
|
|
367
|
+
<br/>
|
|
368
|
+
|
|
369
|
+
<div align="center">
|
|
370
|
+
|
|
371
|
+
This project is licensed under the **MIT License**.
|
|
372
|
+
|
|
373
|
+
Use freely. Give credit. Build cool things. 💜
|
|
374
|
+
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<br/>
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
<br/>
|
|
382
|
+
|
|
383
|
+
<div align="center">
|
|
384
|
+
|
|
385
|
+
### ⚠️ Disclaimer
|
|
386
|
+
|
|
387
|
+
<samp>
|
|
388
|
+
This is an educational project. No video content is hosted on our servers.<br/>
|
|
389
|
+
All streams are fetched from third-party sources. Use responsibly.
|
|
390
|
+
</samp>
|
|
391
|
+
|
|
392
|
+
<br/>
|
|
393
|
+
<br/>
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
<br/>
|
|
398
|
+
|
|
399
|
+
<img src="https://capsule-render.vercel.app/api?type=waving&color=a855f7&height=100§ion=footer" width="100%" />
|
|
400
|
+
|
|
401
|
+
<br/>
|
|
402
|
+
|
|
403
|
+
<samp>
|
|
404
|
+
|
|
405
|
+
*"In a world full of filler episodes, be the main arc."* ✦
|
|
406
|
+
|
|
407
|
+
</samp>
|
|
408
|
+
|
|
409
|
+
<br/>
|
|
410
|
+
|
|
411
|
+
**Made with 💜 by [Anjishnu](https://github.com/AnjishnuSengupta)**
|
|
412
|
+
|
|
413
|
+
[](https://www.instagram.com/anjishnu.prolly)
|
|
414
|
+
|
|
415
|
+
<br/>
|
|
416
|
+
|
|
417
|
+
⭐ Star this repo if you found it useful!
|
|
418
|
+
|
|
419
|
+
</div>
|
package/backend.mjs
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
3
|
+
// NY-CLI Backend — Direct scraping via aniwatch npm package
|
|
4
|
+
// No external API dependency — self-hosted, just like nyanime.tech
|
|
5
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
import { HiAnime } from "aniwatch";
|
|
8
|
+
|
|
9
|
+
const hianime = new HiAnime.Scraper();
|
|
10
|
+
|
|
11
|
+
const action = process.argv[2];
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
try {
|
|
15
|
+
switch (action) {
|
|
16
|
+
// ── Search ──
|
|
17
|
+
case "search": {
|
|
18
|
+
const query = process.argv[3];
|
|
19
|
+
const page = parseInt(process.argv[4]) || 1;
|
|
20
|
+
if (!query) {
|
|
21
|
+
console.log(JSON.stringify({ error: "Missing search query" }));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
const data = await hianime.search(query, page);
|
|
25
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── Home / Trending ──
|
|
30
|
+
case "home": {
|
|
31
|
+
const data = await hianime.getHomePage();
|
|
32
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ── Anime Info ──
|
|
37
|
+
case "info": {
|
|
38
|
+
const animeId = process.argv[3];
|
|
39
|
+
if (!animeId) {
|
|
40
|
+
console.log(JSON.stringify({ error: "Missing anime id" }));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const data = await hianime.getInfo(animeId);
|
|
44
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Episodes ──
|
|
49
|
+
case "episodes": {
|
|
50
|
+
const animeId = process.argv[3];
|
|
51
|
+
if (!animeId) {
|
|
52
|
+
console.log(JSON.stringify({ error: "Missing anime id" }));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const data = await hianime.getEpisodes(animeId);
|
|
56
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Episode Servers ──
|
|
61
|
+
case "servers": {
|
|
62
|
+
const episodeId = process.argv[3];
|
|
63
|
+
if (!episodeId) {
|
|
64
|
+
console.log(JSON.stringify({ error: "Missing episode id" }));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const data = await hianime.getEpisodeServers(episodeId);
|
|
68
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Episode Sources (with multi-server fallback) ──
|
|
73
|
+
case "sources": {
|
|
74
|
+
const episodeId = process.argv[3];
|
|
75
|
+
const category = process.argv[4] || "sub";
|
|
76
|
+
if (!episodeId) {
|
|
77
|
+
console.log(JSON.stringify({ error: "Missing episode id" }));
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Faster timeout — self-hosted scraping is local, no cold-start
|
|
82
|
+
const PER_SERVER_TIMEOUT = 8000;
|
|
83
|
+
|
|
84
|
+
// Helper: try a single server with timeout
|
|
85
|
+
const tryServer = (server, cat) =>
|
|
86
|
+
Promise.race([
|
|
87
|
+
hianime.getEpisodeSources(episodeId, server, cat),
|
|
88
|
+
new Promise((_, reject) =>
|
|
89
|
+
setTimeout(() => reject(new Error(`${server} timed out`)), PER_SERVER_TIMEOUT)
|
|
90
|
+
),
|
|
91
|
+
]).then((srcData) => {
|
|
92
|
+
if (srcData?.sources?.length > 0) {
|
|
93
|
+
srcData._usedServer = server;
|
|
94
|
+
srcData._usedCategory = cat;
|
|
95
|
+
return srcData;
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`${server}: no sources`);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Preferred order: hd-1/hd-2 are fastest, then others
|
|
101
|
+
const preferredOrder = ["hd-1", "hd-2", "streamtape", "streamsb"];
|
|
102
|
+
|
|
103
|
+
// Race all servers in parallel — first success wins
|
|
104
|
+
const raceServers = async (cat) => {
|
|
105
|
+
let availableServers;
|
|
106
|
+
try {
|
|
107
|
+
const serverData = await Promise.race([
|
|
108
|
+
hianime.getEpisodeServers(episodeId),
|
|
109
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("server list timeout")), 5000)),
|
|
110
|
+
]);
|
|
111
|
+
const serverList = cat === "dub" ? serverData.dub : serverData.sub;
|
|
112
|
+
availableServers = (serverList || []).map((s) => s.serverName);
|
|
113
|
+
} catch {
|
|
114
|
+
availableServers = ["hd-1", "hd-2"];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const serversToTry = preferredOrder.filter((s) => availableServers.includes(s));
|
|
118
|
+
if (serversToTry.length === 0) serversToTry.push("hd-1");
|
|
119
|
+
|
|
120
|
+
// Promise.any resolves as soon as ANY server succeeds
|
|
121
|
+
return Promise.any(serversToTry.map((s) => tryServer(s, cat))).then((srcData) => {
|
|
122
|
+
srcData._availableServers = availableServers;
|
|
123
|
+
srcData._triedServers = serversToTry;
|
|
124
|
+
return srcData;
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const srcData = await raceServers(category);
|
|
130
|
+
console.log(JSON.stringify({ success: true, data: srcData }));
|
|
131
|
+
return;
|
|
132
|
+
} catch {
|
|
133
|
+
// All servers failed for this category
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// If sub failed, try dub as fallback
|
|
137
|
+
if (category === "sub") {
|
|
138
|
+
try {
|
|
139
|
+
const srcData = await raceServers("dub");
|
|
140
|
+
console.log(JSON.stringify({ success: true, data: srcData }));
|
|
141
|
+
return;
|
|
142
|
+
} catch {
|
|
143
|
+
// dub also failed
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(JSON.stringify({ error: "All servers failed" }));
|
|
148
|
+
process.exit(1);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Search Suggestions ──
|
|
153
|
+
case "suggestions": {
|
|
154
|
+
const query = process.argv[3];
|
|
155
|
+
if (!query) {
|
|
156
|
+
console.log(JSON.stringify({ error: "Missing query" }));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
const data = await hianime.searchSuggestions(query);
|
|
160
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── Category ──
|
|
165
|
+
case "category": {
|
|
166
|
+
const name = process.argv[3];
|
|
167
|
+
const page = parseInt(process.argv[4]) || 1;
|
|
168
|
+
if (!name) {
|
|
169
|
+
console.log(JSON.stringify({ error: "Missing category name" }));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
const data = await hianime.getCategoryAnime(name, page);
|
|
173
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
default:
|
|
178
|
+
console.log(
|
|
179
|
+
JSON.stringify({ error: `Unknown action: ${action || "(none)"}` })
|
|
180
|
+
);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.log(JSON.stringify({ error: err.message || "Scraper error" }));
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
main();
|