anichi 2.2.5 → 2.3.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 +39 -8
- package/dist/api.d.ts +3 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +15 -2
- package/dist/api.js.map +1 -1
- package/dist/index.js +221 -85
- package/dist/index.js.map +1 -1
- package/dist/player.d.ts.map +1 -1
- package/dist/player.js +85 -199
- package/dist/player.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +3 -2
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +97 -14
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
**
|
|
9
|
+
**Streaming Anime CLI By ShDitz**
|
|
10
10
|
|
|
11
11
|
Your modern, feature-rich, and interactive command-line tool for Anime streaming, downloading, and scheduling.
|
|
12
12
|
|
|
@@ -18,11 +18,16 @@ Your modern, feature-rich, and interactive command-line tool for Anime streaming
|
|
|
18
18
|
|
|
19
19
|
## 🌟 Features
|
|
20
20
|
|
|
21
|
+
## 🌟 Features
|
|
22
|
+
|
|
21
23
|
Anichi is built to provide a premium experience right from your terminal.
|
|
22
24
|
|
|
23
25
|
- **🎨 Modern UI**: Gradient banners, clean tables, and colorful typography using `chalk`, `boxen`, and `figlet`.
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
+
- **📂 Comprehensive Browsing**:
|
|
27
|
+
- **Ongoing & Completed Anime**: Browse lists with pagination (15 items per page).
|
|
28
|
+
- **Search by Genre**: Filter anime by specific genres.
|
|
29
|
+
- **Schedule**: View anime airing schedule by day (Senin - Minggu).
|
|
30
|
+
- **🔍 Smart Search**: Quickly find anime by title with detailed results.
|
|
26
31
|
- **📺 Streaming**: Play anime directly with your favorite external media player (MPV, VLC) or fallback to browser.
|
|
27
32
|
- **⬇️ Downloads**: Support for single episode downloads and **Batch Downloads** with multiple quality and provider options.
|
|
28
33
|
- **⚡ Performance**: Integrated caching system (`node-cache`) for instant loading of previously accessed data.
|
|
@@ -113,11 +118,11 @@ You will be greeted with the main menu banner. From there, you can navigate usin
|
|
|
113
118
|
|
|
114
119
|
#### 1. Ongoing Anime
|
|
115
120
|
|
|
116
|
-
Shows currently airing anime with their release day and latest episode.
|
|
121
|
+
Shows currently airing anime with their release day and latest episode. Supports **pagination** for browsing more results.
|
|
117
122
|
|
|
118
123
|
#### 2. Completed Anime
|
|
119
124
|
|
|
120
|
-
Shows completed anime with their score and last release date.
|
|
125
|
+
Shows completed anime with their score and last release date. Supports **pagination**.
|
|
121
126
|
|
|
122
127
|
#### 3. Search Anime
|
|
123
128
|
|
|
@@ -125,13 +130,21 @@ Search for any anime by its title.
|
|
|
125
130
|
|
|
126
131
|
> **Tip**: Type "Naruto" or "One Piece".
|
|
127
132
|
|
|
128
|
-
#### 4.
|
|
133
|
+
#### 4. Search by Genre
|
|
134
|
+
|
|
135
|
+
Filter anime by specific genres (Action, Adventure, Comedy, etc.) and browse through **paginated** results.
|
|
136
|
+
|
|
137
|
+
#### 5. Schedule Anime
|
|
129
138
|
|
|
130
139
|
View the broadcast schedule.
|
|
131
140
|
|
|
132
141
|
- **By Number**: Type `1` for Senin, `2` for Selasa, etc.
|
|
133
142
|
- **By Name**: Type "Senin", "Selasa", or "Minggu" to filter.
|
|
134
143
|
|
|
144
|
+
#### 6. FAQ
|
|
145
|
+
|
|
146
|
+
View frequently asked questions and help guides.
|
|
147
|
+
|
|
135
148
|
---
|
|
136
149
|
|
|
137
150
|
### 🎞️ How to Install MPV (Recommended Player)
|
|
@@ -156,7 +169,25 @@ choco install mpv
|
|
|
156
169
|
|
|
157
170
|
#### 3. Verify Installation
|
|
158
171
|
|
|
159
|
-
Type `mpv` in your terminal. If the player opens, installation is successful.
|
|
172
|
+
Type `mpv` in your terminal. If the player opens, installation is successful.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### 📼 How to Install yt-dlp (Required for some streams)
|
|
177
|
+
|
|
178
|
+
Some streaming links or sources require `yt-dlp` to fetch the direct video URL. Without it, the player might fail to load certain streams.
|
|
179
|
+
|
|
180
|
+
#### 1. Install yt-dlp using Chocolatey
|
|
181
|
+
|
|
182
|
+
Open **PowerShell** or **CMD** and run:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
choco install yt-dlp
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### 2. Verify Installation
|
|
189
|
+
|
|
190
|
+
Type `yt-dlp --version` in your terminal. If it outputs a version number, it's installed successfully.
|
|
160
191
|
|
|
161
192
|
---
|
|
162
193
|
|
package/dist/api.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
declare class ApiClient {
|
|
2
2
|
private client;
|
|
3
3
|
private cache;
|
|
4
|
+
private lastRequestTime;
|
|
4
5
|
constructor();
|
|
5
6
|
private request;
|
|
6
7
|
getHome(): Promise<any>;
|
|
8
|
+
getOngoing(page?: number): Promise<any>;
|
|
9
|
+
getCompleted(page?: number): Promise<any>;
|
|
7
10
|
getAnime(slug: string): Promise<any>;
|
|
8
11
|
getGenre(): Promise<any>;
|
|
9
12
|
getEpisode(slug: string): Promise<any>;
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAwBA,cAAM,SAAS;IACb,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,eAAe,CAAa;;YAUtB,OAAO;IAgDf,OAAO;IAIP,UAAU,CAAC,IAAI,GAAE,MAAU;IAI3B,YAAY,CAAC,IAAI,GAAE,MAAU;IAI7B,QAAQ,CAAC,IAAI,EAAE,MAAM;IAGrB,QAAQ;IAGR,UAAU,CAAC,IAAI,EAAE,MAAM;IAGvB,SAAS,CAAC,EAAE,EAAE,MAAM;IAGpB,QAAQ,CAAC,IAAI,EAAE,MAAM;IAGrB,SAAS,CAAC,OAAO,EAAE,MAAM;IAGzB,WAAW;IAGX,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAU;IAIlD,UAAU;CAIX;;AAED,wBAA+B"}
|
package/dist/api.js
CHANGED
|
@@ -10,8 +10,10 @@ const ui_1 = require("./ui");
|
|
|
10
10
|
const BASE_URL = "https://www.sankavollerei.com/anime";
|
|
11
11
|
const CACHE_TTL = 3600;
|
|
12
12
|
const MAX_RETRIES = 3;
|
|
13
|
+
const MIN_REQUEST_DELAY = 600;
|
|
13
14
|
class ApiClient {
|
|
14
15
|
constructor() {
|
|
16
|
+
this.lastRequestTime = 0;
|
|
15
17
|
this.client = axios_1.default.create({
|
|
16
18
|
baseURL: BASE_URL,
|
|
17
19
|
timeout: 60000,
|
|
@@ -19,6 +21,13 @@ class ApiClient {
|
|
|
19
21
|
this.cache = new node_cache_1.default({ stdTTL: CACHE_TTL, checkperiod: 600 });
|
|
20
22
|
}
|
|
21
23
|
async request(url) {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const elapsed = now - this.lastRequestTime;
|
|
26
|
+
if (elapsed < MIN_REQUEST_DELAY) {
|
|
27
|
+
const waitTime = MIN_REQUEST_DELAY - elapsed;
|
|
28
|
+
await new Promise((r) => setTimeout(r, waitTime));
|
|
29
|
+
}
|
|
30
|
+
this.lastRequestTime = now;
|
|
22
31
|
const cached = this.cache.get(url);
|
|
23
32
|
if (cached)
|
|
24
33
|
return cached;
|
|
@@ -35,7 +44,6 @@ class ApiClient {
|
|
|
35
44
|
}
|
|
36
45
|
if (status !== 200)
|
|
37
46
|
throw new Error(`HTTP ${status}`);
|
|
38
|
-
// Hanya simpan jika success (perbaikan sebelumnya)
|
|
39
47
|
if (data.ok === true || data.status === "success") {
|
|
40
48
|
this.cache.set(url, data);
|
|
41
49
|
}
|
|
@@ -57,6 +65,12 @@ class ApiClient {
|
|
|
57
65
|
async getHome() {
|
|
58
66
|
return this.request("/home");
|
|
59
67
|
}
|
|
68
|
+
async getOngoing(page = 1) {
|
|
69
|
+
return this.request(`/ongoing-anime?page=${page}`);
|
|
70
|
+
}
|
|
71
|
+
async getCompleted(page = 1) {
|
|
72
|
+
return this.request(`/complete-anime?page=${page}`);
|
|
73
|
+
}
|
|
60
74
|
async getAnime(slug) {
|
|
61
75
|
return this.request(`/anime/${slug}`);
|
|
62
76
|
}
|
|
@@ -78,7 +92,6 @@ class ApiClient {
|
|
|
78
92
|
async getSchedule() {
|
|
79
93
|
return this.request(`/schedule`);
|
|
80
94
|
}
|
|
81
|
-
// Method Baru: Get Anime by Genre with Pagination
|
|
82
95
|
async getGenreAnime(slug, page = 1) {
|
|
83
96
|
return this.request(`/genre/${slug}?page=${page}`);
|
|
84
97
|
}
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":";;;;;AAAA,kDAA2C;AAC3C,4DAAmC;AACnC,8CAAsB;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -46,7 +46,6 @@ const tryGetServer = async (serverId) => {
|
|
|
46
46
|
return null;
|
|
47
47
|
}
|
|
48
48
|
};
|
|
49
|
-
// --- Handle Batch ---
|
|
50
49
|
const handleBatch = async (animeId, batchData) => {
|
|
51
50
|
(0, ui_1.clearScreen)();
|
|
52
51
|
if (!batchData?.downloadUrl?.formats || batchData.downloadUrl.formats.length === 0) {
|
|
@@ -58,7 +57,7 @@ const handleBatch = async (animeId, batchData) => {
|
|
|
58
57
|
(0, ui_1.createHeader)("Download Batch", "#00ff9f");
|
|
59
58
|
(0, ui_1.printBatchFormats)(formats);
|
|
60
59
|
ui_1.logger.br();
|
|
61
|
-
ui_1.logger.muted("
|
|
60
|
+
ui_1.logger.muted(" Pilih format (1-" + formats.length + ") atau 'back'\n");
|
|
62
61
|
const ansFormat = await ask("Format:");
|
|
63
62
|
const fmtChoice = ansFormat.toLowerCase();
|
|
64
63
|
if (fmtChoice === "back" || fmtChoice === "0")
|
|
@@ -71,11 +70,11 @@ const handleBatch = async (animeId, batchData) => {
|
|
|
71
70
|
}
|
|
72
71
|
const selectedFormat = formats[fIndex];
|
|
73
72
|
(0, ui_1.clearScreen)();
|
|
74
|
-
(0, ui_1.createHeader)(`
|
|
73
|
+
(0, ui_1.createHeader)(`Pilih Quality (${selectedFormat.title})`, "#00ff9f");
|
|
75
74
|
(0, ui_1.printBatchQualities)(selectedFormat.qualities);
|
|
76
75
|
ui_1.logger.br();
|
|
77
|
-
ui_1.logger.muted("
|
|
78
|
-
const ansQuality = await ask("
|
|
76
|
+
ui_1.logger.muted(" Pilih kualitas (1-" + selectedFormat.qualities.length + ") atau 'back'\n");
|
|
77
|
+
const ansQuality = await ask("Kualitas:");
|
|
79
78
|
const qChoice = ansQuality.toLowerCase();
|
|
80
79
|
if (qChoice === "back" || qChoice === "0")
|
|
81
80
|
return await handleBatch(animeId, batchData);
|
|
@@ -87,10 +86,10 @@ const handleBatch = async (animeId, batchData) => {
|
|
|
87
86
|
}
|
|
88
87
|
const selectedQuality = selectedFormat.qualities[qIndex];
|
|
89
88
|
(0, ui_1.clearScreen)();
|
|
90
|
-
(0, ui_1.createHeader)(`
|
|
89
|
+
(0, ui_1.createHeader)(`Pilih Provider (${selectedQuality.title})`, "#00ff9f");
|
|
91
90
|
(0, ui_1.printBatchProviders)(selectedQuality.urls);
|
|
92
91
|
ui_1.logger.br();
|
|
93
|
-
ui_1.logger.muted("
|
|
92
|
+
ui_1.logger.muted(" Pilih provider (1-" + selectedQuality.urls.length + ") atau 'back'\n");
|
|
94
93
|
const ansProv = await ask("Provider:");
|
|
95
94
|
const pChoice = ansProv.toLowerCase();
|
|
96
95
|
if (pChoice === "back" || pChoice === "0")
|
|
@@ -106,14 +105,13 @@ const handleBatch = async (animeId, batchData) => {
|
|
|
106
105
|
await (0, open_1.default)(provider.url);
|
|
107
106
|
await new Promise((r) => setTimeout(r, 2000));
|
|
108
107
|
};
|
|
109
|
-
// --- Handle Episode Play/Download ---
|
|
110
108
|
const selectQuality = async (qualities) => {
|
|
111
109
|
(0, ui_1.clearScreen)();
|
|
112
|
-
(0, ui_1.createHeader)("
|
|
110
|
+
(0, ui_1.createHeader)("Pilih Quality", "#ff6b9d");
|
|
113
111
|
(0, ui_1.printQualityOptions)(qualities);
|
|
114
112
|
ui_1.logger.br();
|
|
115
|
-
ui_1.logger.muted("
|
|
116
|
-
const answer = await ask("
|
|
113
|
+
ui_1.logger.muted(" Pilih kualitas (1-" + qualities.length + ") atau 'back'\n");
|
|
114
|
+
const answer = await ask("Kualitas:");
|
|
117
115
|
const choice = answer.toLowerCase();
|
|
118
116
|
if (choice === "back" || choice === "0")
|
|
119
117
|
return null;
|
|
@@ -135,8 +133,8 @@ const handleDownload = async (episodeData) => {
|
|
|
135
133
|
(0, ui_1.createHeader)("Download Episode", "#00ff9f");
|
|
136
134
|
(0, ui_1.printDownloadOptions)(downloads);
|
|
137
135
|
ui_1.logger.br();
|
|
138
|
-
ui_1.logger.muted("
|
|
139
|
-
const answer = await ask("
|
|
136
|
+
ui_1.logger.muted(" Pilih kualitas (1-" + downloads.length + ") atau 'back'\n");
|
|
137
|
+
const answer = await ask("Kualitas:");
|
|
140
138
|
const choice = answer.toLowerCase();
|
|
141
139
|
if (choice === "back" || choice === "0")
|
|
142
140
|
return;
|
|
@@ -154,7 +152,7 @@ const handleDownload = async (episodeData) => {
|
|
|
154
152
|
console.log(`${num} ${chalk_1.default.white(provider.title)}`);
|
|
155
153
|
});
|
|
156
154
|
ui_1.logger.br();
|
|
157
|
-
ui_1.logger.muted("
|
|
155
|
+
ui_1.logger.muted(" Pilih provider (1-" + selected.urls.length + ") atau 'back'\n");
|
|
158
156
|
const provAnswer = await ask("Provider:");
|
|
159
157
|
const provChoice = provAnswer.toLowerCase();
|
|
160
158
|
if (provChoice === "back" || provChoice === "0")
|
|
@@ -254,13 +252,13 @@ const showEpisodeMenu = async (slug, data) => {
|
|
|
254
252
|
(0, ui_1.createHeader)("Episodes", "#ff6b9d");
|
|
255
253
|
(0, ui_1.printEpisodeList)(data.episodeList);
|
|
256
254
|
ui_1.logger.br();
|
|
257
|
-
ui_1.logger.muted("
|
|
258
|
-
ui_1.logger.muted(" • [
|
|
255
|
+
ui_1.logger.muted(" Perintah:");
|
|
256
|
+
ui_1.logger.muted(" • [nomor] atau 'latest' - Tonton episode");
|
|
259
257
|
if (data.batch)
|
|
260
|
-
ui_1.logger.muted(" • 'b'
|
|
261
|
-
ui_1.logger.muted(" • 'd'
|
|
262
|
-
ui_1.logger.muted(" • 'back' -
|
|
263
|
-
const answer = await ask("
|
|
258
|
+
ui_1.logger.muted(" • 'b' atau 'batch' - Download batch");
|
|
259
|
+
ui_1.logger.muted(" • 'd' atau 'download' - Download episode");
|
|
260
|
+
ui_1.logger.muted(" • 'back' - Kembali ke daftar anime\n");
|
|
261
|
+
const answer = await ask("Perintah:");
|
|
264
262
|
const choice = answer.toLowerCase();
|
|
265
263
|
if (choice === "back" || choice === "0")
|
|
266
264
|
return;
|
|
@@ -295,14 +293,14 @@ const showEpisodeMenu = async (slug, data) => {
|
|
|
295
293
|
}
|
|
296
294
|
if (choice === "d" || choice === "download") {
|
|
297
295
|
(0, ui_1.clearScreen)();
|
|
298
|
-
(0, ui_1.createHeader)("
|
|
296
|
+
(0, ui_1.createHeader)("Pilih Episode untuk Download", "#00ff9f");
|
|
299
297
|
(0, ui_1.printEpisodeList)(data.episodeList);
|
|
300
298
|
ui_1.logger.br();
|
|
301
|
-
ui_1.logger.muted("
|
|
299
|
+
ui_1.logger.muted(" Ketik nomor episode atau 'back'\n");
|
|
302
300
|
const epAnswer = await ask("Episode:");
|
|
303
301
|
const epChoice = epAnswer.toLowerCase();
|
|
304
302
|
if (epChoice !== "back" && epChoice !== "0") {
|
|
305
|
-
const epNum = parseInt(
|
|
303
|
+
const epNum = parseInt(epAnswer);
|
|
306
304
|
if (!isNaN(epNum) || epChoice === "latest") {
|
|
307
305
|
const episode = resolveEpisode(epChoice === "latest" ? "latest" : epNum, data.episodeList);
|
|
308
306
|
if (episode) {
|
|
@@ -334,43 +332,148 @@ const showAnimeDetail = async (slug) => {
|
|
|
334
332
|
ui_1.logger.error("Failed to load anime");
|
|
335
333
|
}
|
|
336
334
|
};
|
|
337
|
-
const
|
|
338
|
-
(0,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
335
|
+
const handleOngoingMenu = async (currentPage) => {
|
|
336
|
+
const spinner = (0, ora_1.default)(`Fetching Ongoing page ${currentPage}...`).start();
|
|
337
|
+
let res;
|
|
338
|
+
try {
|
|
339
|
+
res = await api_1.default.getOngoing(currentPage);
|
|
340
|
+
spinner.stop();
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
spinner.fail();
|
|
344
|
+
ui_1.logger.error("Failed to fetch ongoing anime");
|
|
345
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
346
|
+
return await runHome();
|
|
347
|
+
}
|
|
348
|
+
if (res.ok && res.data && res.data.animeList) {
|
|
349
|
+
(0, ui_1.clearScreen)();
|
|
350
|
+
(0, ui_1.createHeader)("Ongoing Anime", "#00d9ff");
|
|
351
|
+
(0, ui_1.printAnimeList)(res.data.animeList, true);
|
|
352
|
+
(0, ui_1.printPaginationControls)(res.pagination, "Ongoing Anime", true);
|
|
353
|
+
const answer = await ask("Perintah:");
|
|
354
|
+
const choice = answer.toLowerCase();
|
|
355
|
+
if (choice === "b" || choice === "back") {
|
|
356
|
+
return await runHome();
|
|
357
|
+
}
|
|
358
|
+
if (choice === "n" || choice === "next") {
|
|
359
|
+
if (res.pagination.hasNextPage) {
|
|
360
|
+
return await handleOngoingMenu(res.pagination.nextPage);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
ui_1.logger.warn("No next page available");
|
|
364
|
+
return await handleOngoingMenu(currentPage);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (choice === "p" || choice === "prev") {
|
|
368
|
+
if (res.pagination.hasPrevPage) {
|
|
369
|
+
return await handleOngoingMenu(res.pagination.prevPage);
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
ui_1.logger.warn("No previous page available");
|
|
373
|
+
return await handleOngoingMenu(currentPage);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const index = parseInt(choice) - 1;
|
|
377
|
+
if (!isNaN(index) && index >= 0 && index < res.data.animeList.length) {
|
|
378
|
+
await showAnimeDetail(res.data.animeList[index].animeId);
|
|
379
|
+
return await handleOngoingMenu(currentPage);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
ui_1.logger.warn("Invalid selection");
|
|
383
|
+
return await handleOngoingMenu(currentPage);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
ui_1.logger.error("No ongoing anime found");
|
|
388
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
389
|
+
return await runHome();
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
const handleCompletedMenu = async (currentPage) => {
|
|
393
|
+
const spinner = (0, ora_1.default)(`Fetching Completed page ${currentPage}...`).start();
|
|
394
|
+
let res;
|
|
395
|
+
try {
|
|
396
|
+
res = await api_1.default.getCompleted(currentPage);
|
|
397
|
+
spinner.stop();
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
spinner.fail();
|
|
401
|
+
ui_1.logger.error("Failed to fetch completed anime");
|
|
402
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
403
|
+
return await runHome();
|
|
404
|
+
}
|
|
405
|
+
if (res.ok && res.data && res.data.animeList) {
|
|
406
|
+
(0, ui_1.clearScreen)();
|
|
407
|
+
(0, ui_1.createHeader)("Completed Anime", "#00d9ff");
|
|
408
|
+
(0, ui_1.printAnimeList)(res.data.animeList, false);
|
|
409
|
+
(0, ui_1.printPaginationControls)(res.pagination, "Completed Anime", true);
|
|
410
|
+
const answer = await ask("Perintah:");
|
|
411
|
+
const choice = answer.toLowerCase();
|
|
412
|
+
if (choice === "b" || choice === "back") {
|
|
413
|
+
return await runHome();
|
|
414
|
+
}
|
|
415
|
+
if (choice === "n" || choice === "next") {
|
|
416
|
+
if (res.pagination.hasNextPage) {
|
|
417
|
+
return await handleCompletedMenu(res.pagination.nextPage);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
ui_1.logger.warn("No next page available");
|
|
421
|
+
return await handleCompletedMenu(currentPage);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (choice === "p" || choice === "prev") {
|
|
425
|
+
if (res.pagination.hasPrevPage) {
|
|
426
|
+
return await handleCompletedMenu(res.pagination.prevPage);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
ui_1.logger.warn("No previous page available");
|
|
430
|
+
return await handleCompletedMenu(currentPage);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const index = parseInt(choice) - 1;
|
|
434
|
+
if (!isNaN(index) && index >= 0 && index < res.data.animeList.length) {
|
|
435
|
+
await showAnimeDetail(res.data.animeList[index].animeId);
|
|
436
|
+
return await handleCompletedMenu(currentPage);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
ui_1.logger.warn("Invalid selection");
|
|
440
|
+
return await handleCompletedMenu(currentPage);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
ui_1.logger.error("No completed anime found");
|
|
445
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
446
|
+
return await runHome();
|
|
351
447
|
}
|
|
352
|
-
ui_1.logger.warn("Invalid selection");
|
|
353
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
354
|
-
return await showAnimeList(list, title, isOngoing);
|
|
355
448
|
};
|
|
356
449
|
const handleSearch = async () => {
|
|
357
450
|
(0, ui_1.clearScreen)();
|
|
358
451
|
(0, ui_1.createHeader)("Search Anime", "#ff6b9d");
|
|
452
|
+
ui_1.logger.br();
|
|
359
453
|
const keyword = await ask("Enter keyword (e.g. Naruto):");
|
|
360
454
|
if (!keyword) {
|
|
361
455
|
ui_1.logger.warn("Keyword cannot be empty");
|
|
362
456
|
return await runHome();
|
|
363
457
|
}
|
|
364
458
|
const spinner = (0, ora_1.default)("Searching...").start();
|
|
365
|
-
|
|
366
|
-
|
|
459
|
+
let res;
|
|
460
|
+
try {
|
|
461
|
+
res = await api_1.default.getSearch(keyword);
|
|
462
|
+
spinner.stop();
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
spinner.fail();
|
|
466
|
+
ui_1.logger.error("Network error or server issue (500).");
|
|
467
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
468
|
+
return await runHome();
|
|
469
|
+
}
|
|
367
470
|
if (res.ok && res.data && res.data.animeList && res.data.animeList.length > 0) {
|
|
368
471
|
(0, ui_1.clearScreen)();
|
|
369
|
-
(0, ui_1.createHeader)("Search
|
|
472
|
+
(0, ui_1.createHeader)("Hasil Search", "#ff6b9d");
|
|
370
473
|
(0, ui_1.printSearchResults)(res.data.animeList);
|
|
371
474
|
ui_1.logger.br();
|
|
372
|
-
ui_1.logger.muted(`
|
|
373
|
-
const answer = await ask("
|
|
475
|
+
ui_1.logger.muted(` Pilih anime (1-${res.data.animeList.length}) atau 'back'\n`);
|
|
476
|
+
const answer = await ask("Pilih:");
|
|
374
477
|
const choice = answer.toLowerCase();
|
|
375
478
|
if (choice === "back" || choice === "0")
|
|
376
479
|
return await runHome();
|
|
@@ -391,16 +494,29 @@ const handleSearch = async () => {
|
|
|
391
494
|
}
|
|
392
495
|
};
|
|
393
496
|
const handleGenreMenu = async () => {
|
|
497
|
+
(0, ui_1.clearScreen)();
|
|
498
|
+
(0, ui_1.createHeader)("Pilih Genre", "#ffaa00");
|
|
499
|
+
ui_1.logger.br();
|
|
394
500
|
const spinner = (0, ora_1.default)("Fetching genres...").start();
|
|
395
|
-
|
|
396
|
-
|
|
501
|
+
let res;
|
|
502
|
+
try {
|
|
503
|
+
res = await api_1.default.getGenre();
|
|
504
|
+
spinner.stop();
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
spinner.fail();
|
|
508
|
+
ui_1.logger.error("Failed to fetch genres");
|
|
509
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
510
|
+
return await runHome();
|
|
511
|
+
}
|
|
397
512
|
if (res.ok && res.data && res.data.genreList) {
|
|
398
513
|
(0, ui_1.clearScreen)();
|
|
399
|
-
(0, ui_1.createHeader)("
|
|
514
|
+
(0, ui_1.createHeader)("Pilih Genre", "#ffaa00");
|
|
515
|
+
ui_1.logger.br();
|
|
400
516
|
(0, ui_1.printGenreList)(res.data.genreList);
|
|
401
517
|
ui_1.logger.br();
|
|
402
|
-
ui_1.logger.muted(`
|
|
403
|
-
const answer = await ask("
|
|
518
|
+
ui_1.logger.muted(` Pilih genre (1-${res.data.genreList.length}) atau 'back'\n`);
|
|
519
|
+
const answer = await ask("Pilih:");
|
|
404
520
|
const choice = answer.toLowerCase();
|
|
405
521
|
if (choice === "back" || choice === "0")
|
|
406
522
|
return await runHome();
|
|
@@ -422,15 +538,24 @@ const handleGenreMenu = async () => {
|
|
|
422
538
|
};
|
|
423
539
|
const handleGenreAnimeList = async (genreTitle, genreSlug, currentPage) => {
|
|
424
540
|
const spinner = (0, ora_1.default)(`Fetching page ${currentPage}...`).start();
|
|
425
|
-
|
|
426
|
-
|
|
541
|
+
let res;
|
|
542
|
+
try {
|
|
543
|
+
res = await api_1.default.getGenreAnime(genreSlug, currentPage);
|
|
544
|
+
spinner.stop();
|
|
545
|
+
}
|
|
546
|
+
catch (err) {
|
|
547
|
+
spinner.fail();
|
|
548
|
+
ui_1.logger.error("Network error or server issue (500).");
|
|
549
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
550
|
+
return await handleGenreMenu();
|
|
551
|
+
}
|
|
427
552
|
if (res.ok && res.data && res.data.animeList) {
|
|
428
553
|
(0, ui_1.clearScreen)();
|
|
429
554
|
(0, ui_1.createHeader)(`Genre: ${genreTitle}`, "#ffaa00");
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
(0, ui_1.printPaginationControls)(res.pagination, genreTitle);
|
|
433
|
-
const answer = await ask("
|
|
555
|
+
ui_1.logger.br();
|
|
556
|
+
(0, ui_1.printGenreAnimeList)(res.data.animeList);
|
|
557
|
+
(0, ui_1.printPaginationControls)(res.pagination, `: ${genreTitle}`, true);
|
|
558
|
+
const answer = await ask("Perintah:");
|
|
434
559
|
const choice = answer.toLowerCase();
|
|
435
560
|
if (choice === "b" || choice === "back") {
|
|
436
561
|
return await handleGenreMenu();
|
|
@@ -470,18 +595,27 @@ const handleGenreAnimeList = async (genreTitle, genreSlug, currentPage) => {
|
|
|
470
595
|
};
|
|
471
596
|
const handleSchedule = async () => {
|
|
472
597
|
const spinner = (0, ora_1.default)("Fetching schedule...").start();
|
|
473
|
-
|
|
474
|
-
|
|
598
|
+
let res;
|
|
599
|
+
try {
|
|
600
|
+
res = await api_1.default.getSchedule();
|
|
601
|
+
spinner.stop();
|
|
602
|
+
}
|
|
603
|
+
catch (err) {
|
|
604
|
+
spinner.fail();
|
|
605
|
+
ui_1.logger.error("Failed to fetch schedule");
|
|
606
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
607
|
+
return await runHome();
|
|
608
|
+
}
|
|
475
609
|
if (res && res.status === "success" && res.data) {
|
|
476
610
|
(0, ui_1.clearScreen)();
|
|
477
611
|
(0, ui_1.createHeader)("Anime Schedule", "#ffaa00");
|
|
478
612
|
(0, ui_1.printSchedule)(res.data);
|
|
479
613
|
ui_1.logger.br();
|
|
480
|
-
ui_1.logger.muted("
|
|
481
|
-
ui_1.logger.muted(" • [
|
|
482
|
-
ui_1.logger.muted(" • [
|
|
483
|
-
ui_1.logger.muted(" • 'back' -
|
|
484
|
-
const answer = await ask("
|
|
614
|
+
ui_1.logger.muted(" Perintah:");
|
|
615
|
+
ui_1.logger.muted(" • [nomor] - Pilih hari (1-Minggu)");
|
|
616
|
+
ui_1.logger.muted(" • [hari] - Filter berdasarkan hari (e.g. Senin, Selasa)");
|
|
617
|
+
ui_1.logger.muted(" • 'back' - Kembali ke home\n");
|
|
618
|
+
const answer = await ask("Perintah:");
|
|
485
619
|
const choice = answer.toLowerCase();
|
|
486
620
|
if (choice === "back" || choice === "0")
|
|
487
621
|
return await runHome();
|
|
@@ -493,8 +627,8 @@ const handleSchedule = async () => {
|
|
|
493
627
|
(0, ui_1.printSchedule)([selectedDay]);
|
|
494
628
|
ui_1.logger.br();
|
|
495
629
|
if (selectedDay.anime_list && selectedDay.anime_list.length > 0) {
|
|
496
|
-
ui_1.logger.muted(`
|
|
497
|
-
const ansAnime = await ask("
|
|
630
|
+
ui_1.logger.muted(` Pilih anime (1-${selectedDay.anime_list.length}) atau 'back'\n`);
|
|
631
|
+
const ansAnime = await ask("Pilih:");
|
|
498
632
|
const aChoice = ansAnime.toLowerCase();
|
|
499
633
|
if (aChoice === "back" || aChoice === "0")
|
|
500
634
|
return await handleSchedule();
|
|
@@ -515,8 +649,8 @@ const handleSchedule = async () => {
|
|
|
515
649
|
(0, ui_1.createHeader)(`Schedule: ${dayData.day}`, "#ffaa00");
|
|
516
650
|
(0, ui_1.printSchedule)([dayData]);
|
|
517
651
|
ui_1.logger.br();
|
|
518
|
-
ui_1.logger.muted(`
|
|
519
|
-
const ansAnime = await ask("
|
|
652
|
+
ui_1.logger.muted(` Pilih anime (1-${dayData.anime_list.length}) atau 'back'\n`);
|
|
653
|
+
const ansAnime = await ask("Pilih:");
|
|
520
654
|
const aChoice = ansAnime.toLowerCase();
|
|
521
655
|
if (aChoice === "back" || aChoice === "0")
|
|
522
656
|
return await handleSchedule();
|
|
@@ -541,6 +675,19 @@ const handleSchedule = async () => {
|
|
|
541
675
|
return await runHome();
|
|
542
676
|
}
|
|
543
677
|
};
|
|
678
|
+
const handleFAQ = async () => {
|
|
679
|
+
(0, ui_1.clearScreen)();
|
|
680
|
+
(0, ui_1.createHeader)("Frequently Asked Questions", "#ffaa00");
|
|
681
|
+
(0, ui_1.printFAQ)();
|
|
682
|
+
const answer = await ask("Perintah:");
|
|
683
|
+
const choice = answer.toLowerCase();
|
|
684
|
+
if (choice === "back" || choice === "0") {
|
|
685
|
+
return await runHome();
|
|
686
|
+
}
|
|
687
|
+
ui_1.logger.warn("Invalid command, returning to home...");
|
|
688
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
689
|
+
return await runHome();
|
|
690
|
+
};
|
|
544
691
|
const runHome = async () => {
|
|
545
692
|
(0, ui_1.clearScreen)();
|
|
546
693
|
(0, ui_1.showBanner)();
|
|
@@ -552,28 +699,14 @@ const runHome = async () => {
|
|
|
552
699
|
"Search Anime",
|
|
553
700
|
"Search by Genre",
|
|
554
701
|
"Schedule Anime",
|
|
702
|
+
"FAQ",
|
|
555
703
|
]);
|
|
556
|
-
const answer = await ask("
|
|
557
|
-
const res = await api_1.default.getHome();
|
|
704
|
+
const answer = await ask("Pilih:");
|
|
558
705
|
if (answer === "1") {
|
|
559
|
-
|
|
560
|
-
if (list.length === 0) {
|
|
561
|
-
ui_1.logger.warn("No ongoing anime");
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
|
-
const action = await showAnimeList(list, "Ongoing Anime", true);
|
|
565
|
-
if (action === "home")
|
|
566
|
-
await runHome();
|
|
706
|
+
await handleOngoingMenu(1);
|
|
567
707
|
}
|
|
568
708
|
else if (answer === "2") {
|
|
569
|
-
|
|
570
|
-
if (list.length === 0) {
|
|
571
|
-
ui_1.logger.warn("No completed anime");
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
const action = await showAnimeList(list, "Completed Anime", false);
|
|
575
|
-
if (action === "home")
|
|
576
|
-
await runHome();
|
|
709
|
+
await handleCompletedMenu(1);
|
|
577
710
|
}
|
|
578
711
|
else if (answer === "3") {
|
|
579
712
|
await handleSearch();
|
|
@@ -584,6 +717,9 @@ const runHome = async () => {
|
|
|
584
717
|
else if (answer === "5") {
|
|
585
718
|
await handleSchedule();
|
|
586
719
|
}
|
|
720
|
+
else if (answer === "6") {
|
|
721
|
+
await handleFAQ();
|
|
722
|
+
}
|
|
587
723
|
else {
|
|
588
724
|
ui_1.logger.warn("Invalid choice");
|
|
589
725
|
await new Promise((r) => setTimeout(r, 1000));
|