anichi 2.2.6 → 2.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/LICENSE +2 -2
- package/README.md +39 -8
- package/dist/api.d.ts +3 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +16 -3
- package/dist/api.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -2
- package/dist/config.js.map +1 -1
- package/dist/index.js +305 -103
- package/dist/index.js.map +1 -1
- package/dist/player.d.ts.map +1 -1
- package/dist/player.js +107 -216
- package/dist/player.js.map +1 -1
- package/dist/types.d.ts +13 -1
- 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 +108 -15
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ const player_1 = require("./player");
|
|
|
13
13
|
const config_1 = require("./config");
|
|
14
14
|
const ui_1 = require("./ui");
|
|
15
15
|
const program = new commander_1.Command();
|
|
16
|
-
program.name("anichi").description("Anime streaming for CLI").version("2.
|
|
16
|
+
program.name("anichi").description("Anime streaming for CLI").version("2.6.0");
|
|
17
17
|
const ask = (query) => {
|
|
18
18
|
const rl = readline_1.default.createInterface({
|
|
19
19
|
input: process.stdin,
|
|
@@ -27,7 +27,7 @@ const ask = (query) => {
|
|
|
27
27
|
const resolveEpisode = (eps, list) => {
|
|
28
28
|
if (eps === "latest")
|
|
29
29
|
return list[0];
|
|
30
|
-
return list.find((e) => e.eps === eps);
|
|
30
|
+
return list.find((e) => e.eps === eps || e.episode === eps);
|
|
31
31
|
};
|
|
32
32
|
const tryGetServer = async (serverId) => {
|
|
33
33
|
try {
|
|
@@ -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,12 +152,12 @@ 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")
|
|
161
159
|
return await handleDownload(episodeData);
|
|
162
|
-
const pIndex = parseInt(
|
|
160
|
+
const pIndex = parseInt(provAnswer) - 1;
|
|
163
161
|
if (isNaN(pIndex) || pIndex < 0 || pIndex >= selected.urls.length) {
|
|
164
162
|
ui_1.logger.warn("Invalid selection");
|
|
165
163
|
await new Promise((r) => setTimeout(r, 1000));
|
|
@@ -186,18 +184,21 @@ const handlePlay = async (slug, episodeStr, animeData) => {
|
|
|
186
184
|
ui_1.logger.error(`Episode ${episodeStr} not found`);
|
|
187
185
|
return false;
|
|
188
186
|
}
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
let epRes;
|
|
188
|
+
epRes = await api_1.default.getEpisode(episode.episodeId);
|
|
189
|
+
if ((!epRes.ok && epRes.status !== "success") || !epRes.data) {
|
|
191
190
|
ui_1.logger.error("Failed to fetch episode data");
|
|
192
191
|
return false;
|
|
193
192
|
}
|
|
194
|
-
|
|
193
|
+
let qualities = [];
|
|
194
|
+
let defaultUrl = "";
|
|
195
|
+
qualities = epRes.data.server?.qualities || [];
|
|
196
|
+
defaultUrl = epRes.data.defaultStreamingUrl || "";
|
|
195
197
|
if (qualities.length === 0) {
|
|
196
198
|
ui_1.logger.warn("No streaming qualities available");
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
await (0, open_1.default)(url);
|
|
199
|
+
if (defaultUrl) {
|
|
200
|
+
ui_1.logger.info("Opening default stream in browser...");
|
|
201
|
+
await (0, open_1.default)(defaultUrl);
|
|
201
202
|
return true;
|
|
202
203
|
}
|
|
203
204
|
return false;
|
|
@@ -222,7 +223,7 @@ const handlePlay = async (slug, episodeStr, animeData) => {
|
|
|
222
223
|
}
|
|
223
224
|
if (!url) {
|
|
224
225
|
ui_1.logger.warn("No working server found, using default...");
|
|
225
|
-
url =
|
|
226
|
+
url = defaultUrl;
|
|
226
227
|
}
|
|
227
228
|
if (!url) {
|
|
228
229
|
ui_1.logger.error("No stream URL available");
|
|
@@ -237,10 +238,6 @@ const handlePlay = async (slug, episodeStr, animeData) => {
|
|
|
237
238
|
const playerPath = config.player || config.playerPath;
|
|
238
239
|
const args = config.playerArgs ? config.playerArgs.split(" ") : [];
|
|
239
240
|
const success = await (0, player_1.playUrl)(url, playerPath, args, false);
|
|
240
|
-
if (!success) {
|
|
241
|
-
ui_1.logger.warn("Opening in browser...");
|
|
242
|
-
await (0, open_1.default)(url);
|
|
243
|
-
}
|
|
244
241
|
return success;
|
|
245
242
|
}
|
|
246
243
|
catch (err) {
|
|
@@ -254,13 +251,13 @@ const showEpisodeMenu = async (slug, data) => {
|
|
|
254
251
|
(0, ui_1.createHeader)("Episodes", "#ff6b9d");
|
|
255
252
|
(0, ui_1.printEpisodeList)(data.episodeList);
|
|
256
253
|
ui_1.logger.br();
|
|
257
|
-
ui_1.logger.muted("
|
|
258
|
-
ui_1.logger.muted(" • [
|
|
254
|
+
ui_1.logger.muted(" Perintah:");
|
|
255
|
+
ui_1.logger.muted(" • [nomor] atau 'latest' - Tonton episode");
|
|
259
256
|
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("
|
|
257
|
+
ui_1.logger.muted(" • 'b' atau 'batch' - Download batch");
|
|
258
|
+
ui_1.logger.muted(" • 'd' atau 'download' - Download episode");
|
|
259
|
+
ui_1.logger.muted(" • 'back' - Kembali ke daftar anime\n");
|
|
260
|
+
const answer = await ask("Perintah:");
|
|
264
261
|
const choice = answer.toLowerCase();
|
|
265
262
|
if (choice === "back" || choice === "0")
|
|
266
263
|
return;
|
|
@@ -295,18 +292,19 @@ const showEpisodeMenu = async (slug, data) => {
|
|
|
295
292
|
}
|
|
296
293
|
if (choice === "d" || choice === "download") {
|
|
297
294
|
(0, ui_1.clearScreen)();
|
|
298
|
-
(0, ui_1.createHeader)("
|
|
295
|
+
(0, ui_1.createHeader)("Pilih Episode untuk Download", "#00ff9f");
|
|
299
296
|
(0, ui_1.printEpisodeList)(data.episodeList);
|
|
300
297
|
ui_1.logger.br();
|
|
301
|
-
ui_1.logger.muted("
|
|
298
|
+
ui_1.logger.muted(" Ketik nomor episode atau 'back'\n");
|
|
302
299
|
const epAnswer = await ask("Episode:");
|
|
303
300
|
const epChoice = epAnswer.toLowerCase();
|
|
304
301
|
if (epChoice !== "back" && epChoice !== "0") {
|
|
305
|
-
const epNum = parseInt(
|
|
302
|
+
const epNum = parseInt(epAnswer);
|
|
306
303
|
if (!isNaN(epNum) || epChoice === "latest") {
|
|
307
304
|
const episode = resolveEpisode(epChoice === "latest" ? "latest" : epNum, data.episodeList);
|
|
308
305
|
if (episode) {
|
|
309
|
-
|
|
306
|
+
let epRes;
|
|
307
|
+
epRes = await api_1.default.getEpisode(episode.episodeId);
|
|
310
308
|
if (epRes.ok && epRes.data) {
|
|
311
309
|
await handleDownload(epRes.data);
|
|
312
310
|
}
|
|
@@ -325,6 +323,68 @@ const showEpisodeMenu = async (slug, data) => {
|
|
|
325
323
|
await new Promise((r) => setTimeout(r, 1000));
|
|
326
324
|
return await showEpisodeMenu(slug, data);
|
|
327
325
|
};
|
|
326
|
+
const handlePopularMenu = async (currentPage) => {
|
|
327
|
+
const spinner = (0, ora_1.default)(`Fetching Popular Anime (Page ${currentPage})...`).start();
|
|
328
|
+
let res;
|
|
329
|
+
try {
|
|
330
|
+
res = await api_1.default.getCompleted(currentPage);
|
|
331
|
+
spinner.stop();
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
spinner.fail();
|
|
335
|
+
ui_1.logger.error("Failed to fetch popular anime");
|
|
336
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
337
|
+
return await runHome();
|
|
338
|
+
}
|
|
339
|
+
if (res.ok && res.data && res.data.animeList) {
|
|
340
|
+
const popularList = [...res.data.animeList].sort((a, b) => {
|
|
341
|
+
const scoreA = parseFloat(a.score) || 0;
|
|
342
|
+
const scoreB = parseFloat(b.score) || 0;
|
|
343
|
+
return scoreB - scoreA;
|
|
344
|
+
});
|
|
345
|
+
(0, ui_1.clearScreen)();
|
|
346
|
+
(0, ui_1.createHeader)("Anime Populer", "#00d9ff");
|
|
347
|
+
(0, ui_1.printAnimeList)(popularList, false);
|
|
348
|
+
(0, ui_1.printPaginationControls)(res.pagination, "Anime Populer", true);
|
|
349
|
+
const answer = await ask("Perintah:");
|
|
350
|
+
const choice = answer.toLowerCase();
|
|
351
|
+
if (choice === "b" || choice === "back") {
|
|
352
|
+
return await runHome();
|
|
353
|
+
}
|
|
354
|
+
if (choice === "n" || choice === "next") {
|
|
355
|
+
if (res.pagination.hasNextPage) {
|
|
356
|
+
return await handlePopularMenu(res.pagination.nextPage);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
ui_1.logger.warn("No next page available");
|
|
360
|
+
return await handlePopularMenu(currentPage);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (choice === "p" || choice === "prev") {
|
|
364
|
+
if (res.pagination.hasPrevPage) {
|
|
365
|
+
return await handlePopularMenu(res.pagination.prevPage);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
ui_1.logger.warn("No previous page available");
|
|
369
|
+
return await handlePopularMenu(currentPage);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const index = parseInt(choice) - 1;
|
|
373
|
+
if (!isNaN(index) && index >= 0 && index < popularList.length) {
|
|
374
|
+
await showAnimeDetail(popularList[index].animeId);
|
|
375
|
+
return await handlePopularMenu(currentPage);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
ui_1.logger.warn("Invalid selection");
|
|
379
|
+
return await handlePopularMenu(currentPage);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
ui_1.logger.error("No popular anime found");
|
|
384
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
385
|
+
return await runHome();
|
|
386
|
+
}
|
|
387
|
+
};
|
|
328
388
|
const showAnimeDetail = async (slug) => {
|
|
329
389
|
try {
|
|
330
390
|
const res = await api_1.default.getAnime(slug);
|
|
@@ -334,43 +394,148 @@ const showAnimeDetail = async (slug) => {
|
|
|
334
394
|
ui_1.logger.error("Failed to load anime");
|
|
335
395
|
}
|
|
336
396
|
};
|
|
337
|
-
const
|
|
338
|
-
(0,
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
397
|
+
const handleOngoingMenu = async (currentPage) => {
|
|
398
|
+
const spinner = (0, ora_1.default)(`Fetching Ongoing page ${currentPage}...`).start();
|
|
399
|
+
let res;
|
|
400
|
+
try {
|
|
401
|
+
res = await api_1.default.getOngoing(currentPage);
|
|
402
|
+
spinner.stop();
|
|
403
|
+
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
spinner.fail();
|
|
406
|
+
ui_1.logger.error("Failed to fetch ongoing anime");
|
|
407
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
408
|
+
return await runHome();
|
|
409
|
+
}
|
|
410
|
+
if (res.ok && res.data && res.data.animeList) {
|
|
411
|
+
(0, ui_1.clearScreen)();
|
|
412
|
+
(0, ui_1.createHeader)("Ongoing Anime", "#00d9ff");
|
|
413
|
+
(0, ui_1.printAnimeList)(res.data.animeList, true);
|
|
414
|
+
(0, ui_1.printPaginationControls)(res.pagination, "Ongoing Anime", true);
|
|
415
|
+
const answer = await ask("Perintah:");
|
|
416
|
+
const choice = answer.toLowerCase();
|
|
417
|
+
if (choice === "b" || choice === "back") {
|
|
418
|
+
return await runHome();
|
|
419
|
+
}
|
|
420
|
+
if (choice === "n" || choice === "next") {
|
|
421
|
+
if (res.pagination.hasNextPage) {
|
|
422
|
+
return await handleOngoingMenu(res.pagination.nextPage);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
ui_1.logger.warn("No next page available");
|
|
426
|
+
return await handleOngoingMenu(currentPage);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (choice === "p" || choice === "prev") {
|
|
430
|
+
if (res.pagination.hasPrevPage) {
|
|
431
|
+
return await handleOngoingMenu(res.pagination.prevPage);
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
ui_1.logger.warn("No previous page available");
|
|
435
|
+
return await handleOngoingMenu(currentPage);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const index = parseInt(choice) - 1;
|
|
439
|
+
if (!isNaN(index) && index >= 0 && index < res.data.animeList.length) {
|
|
440
|
+
await showAnimeDetail(res.data.animeList[index].animeId);
|
|
441
|
+
return await handleOngoingMenu(currentPage);
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
ui_1.logger.warn("Invalid selection");
|
|
445
|
+
return await handleOngoingMenu(currentPage);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
ui_1.logger.error("No ongoing anime found");
|
|
450
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
451
|
+
return await runHome();
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
const handleCompletedMenu = async (currentPage) => {
|
|
455
|
+
const spinner = (0, ora_1.default)(`Fetching Completed page ${currentPage}...`).start();
|
|
456
|
+
let res;
|
|
457
|
+
try {
|
|
458
|
+
res = await api_1.default.getCompleted(currentPage);
|
|
459
|
+
spinner.stop();
|
|
460
|
+
}
|
|
461
|
+
catch (err) {
|
|
462
|
+
spinner.fail();
|
|
463
|
+
ui_1.logger.error("Failed to fetch completed anime");
|
|
464
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
465
|
+
return await runHome();
|
|
466
|
+
}
|
|
467
|
+
if (res.ok && res.data && res.data.animeList) {
|
|
468
|
+
(0, ui_1.clearScreen)();
|
|
469
|
+
(0, ui_1.createHeader)("Completed Anime", "#00d9ff");
|
|
470
|
+
(0, ui_1.printAnimeList)(res.data.animeList, false);
|
|
471
|
+
(0, ui_1.printPaginationControls)(res.pagination, "Completed Anime", true);
|
|
472
|
+
const answer = await ask("Perintah:");
|
|
473
|
+
const choice = answer.toLowerCase();
|
|
474
|
+
if (choice === "b" || choice === "back") {
|
|
475
|
+
return await runHome();
|
|
476
|
+
}
|
|
477
|
+
if (choice === "n" || choice === "next") {
|
|
478
|
+
if (res.pagination.hasNextPage) {
|
|
479
|
+
return await handleCompletedMenu(res.pagination.nextPage);
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
ui_1.logger.warn("No next page available");
|
|
483
|
+
return await handleCompletedMenu(currentPage);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (choice === "p" || choice === "prev") {
|
|
487
|
+
if (res.pagination.hasPrevPage) {
|
|
488
|
+
return await handleCompletedMenu(res.pagination.prevPage);
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
ui_1.logger.warn("No previous page available");
|
|
492
|
+
return await handleCompletedMenu(currentPage);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
const index = parseInt(choice) - 1;
|
|
496
|
+
if (!isNaN(index) && index >= 0 && index < res.data.animeList.length) {
|
|
497
|
+
await showAnimeDetail(res.data.animeList[index].animeId);
|
|
498
|
+
return await handleCompletedMenu(currentPage);
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
ui_1.logger.warn("Invalid selection");
|
|
502
|
+
return await handleCompletedMenu(currentPage);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
ui_1.logger.error("No completed anime found");
|
|
507
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
508
|
+
return await runHome();
|
|
351
509
|
}
|
|
352
|
-
ui_1.logger.warn("Invalid selection");
|
|
353
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
354
|
-
return await showAnimeList(list, title, isOngoing);
|
|
355
510
|
};
|
|
356
511
|
const handleSearch = async () => {
|
|
357
512
|
(0, ui_1.clearScreen)();
|
|
358
513
|
(0, ui_1.createHeader)("Search Anime", "#ff6b9d");
|
|
514
|
+
ui_1.logger.br();
|
|
359
515
|
const keyword = await ask("Enter keyword (e.g. Naruto):");
|
|
360
516
|
if (!keyword) {
|
|
361
517
|
ui_1.logger.warn("Keyword cannot be empty");
|
|
362
518
|
return await runHome();
|
|
363
519
|
}
|
|
364
520
|
const spinner = (0, ora_1.default)("Searching...").start();
|
|
365
|
-
|
|
366
|
-
|
|
521
|
+
let res;
|
|
522
|
+
try {
|
|
523
|
+
res = await api_1.default.getSearch(keyword);
|
|
524
|
+
spinner.stop();
|
|
525
|
+
}
|
|
526
|
+
catch (err) {
|
|
527
|
+
spinner.fail();
|
|
528
|
+
ui_1.logger.error("Network error or server issue (500).");
|
|
529
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
530
|
+
return await runHome();
|
|
531
|
+
}
|
|
367
532
|
if (res.ok && res.data && res.data.animeList && res.data.animeList.length > 0) {
|
|
368
533
|
(0, ui_1.clearScreen)();
|
|
369
|
-
(0, ui_1.createHeader)("Search
|
|
534
|
+
(0, ui_1.createHeader)("Hasil Search", "#ff6b9d");
|
|
370
535
|
(0, ui_1.printSearchResults)(res.data.animeList);
|
|
371
536
|
ui_1.logger.br();
|
|
372
|
-
ui_1.logger.muted(`
|
|
373
|
-
const answer = await ask("
|
|
537
|
+
ui_1.logger.muted(` Pilih anime (1-${res.data.animeList.length}) atau 'back'\n`);
|
|
538
|
+
const answer = await ask("Pilih:");
|
|
374
539
|
const choice = answer.toLowerCase();
|
|
375
540
|
if (choice === "back" || choice === "0")
|
|
376
541
|
return await runHome();
|
|
@@ -391,16 +556,29 @@ const handleSearch = async () => {
|
|
|
391
556
|
}
|
|
392
557
|
};
|
|
393
558
|
const handleGenreMenu = async () => {
|
|
559
|
+
(0, ui_1.clearScreen)();
|
|
560
|
+
(0, ui_1.createHeader)("Pilih Genre", "#ffaa00");
|
|
561
|
+
ui_1.logger.br();
|
|
394
562
|
const spinner = (0, ora_1.default)("Fetching genres...").start();
|
|
395
|
-
|
|
396
|
-
|
|
563
|
+
let res;
|
|
564
|
+
try {
|
|
565
|
+
res = await api_1.default.getGenre();
|
|
566
|
+
spinner.stop();
|
|
567
|
+
}
|
|
568
|
+
catch (err) {
|
|
569
|
+
spinner.fail();
|
|
570
|
+
ui_1.logger.error("Failed to fetch genres");
|
|
571
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
572
|
+
return await runHome();
|
|
573
|
+
}
|
|
397
574
|
if (res.ok && res.data && res.data.genreList) {
|
|
398
575
|
(0, ui_1.clearScreen)();
|
|
399
|
-
(0, ui_1.createHeader)("
|
|
576
|
+
(0, ui_1.createHeader)("Pilih Genre", "#ffaa00");
|
|
577
|
+
ui_1.logger.br();
|
|
400
578
|
(0, ui_1.printGenreList)(res.data.genreList);
|
|
401
579
|
ui_1.logger.br();
|
|
402
|
-
ui_1.logger.muted(`
|
|
403
|
-
const answer = await ask("
|
|
580
|
+
ui_1.logger.muted(` Pilih genre (1-${res.data.genreList.length}) atau 'back'\n`);
|
|
581
|
+
const answer = await ask("Pilih:");
|
|
404
582
|
const choice = answer.toLowerCase();
|
|
405
583
|
if (choice === "back" || choice === "0")
|
|
406
584
|
return await runHome();
|
|
@@ -422,15 +600,24 @@ const handleGenreMenu = async () => {
|
|
|
422
600
|
};
|
|
423
601
|
const handleGenreAnimeList = async (genreTitle, genreSlug, currentPage) => {
|
|
424
602
|
const spinner = (0, ora_1.default)(`Fetching page ${currentPage}...`).start();
|
|
425
|
-
|
|
426
|
-
|
|
603
|
+
let res;
|
|
604
|
+
try {
|
|
605
|
+
res = await api_1.default.getGenreAnime(genreSlug, currentPage);
|
|
606
|
+
spinner.stop();
|
|
607
|
+
}
|
|
608
|
+
catch (err) {
|
|
609
|
+
spinner.fail();
|
|
610
|
+
ui_1.logger.error("Network error or server issue (500).");
|
|
611
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
612
|
+
return await handleGenreMenu();
|
|
613
|
+
}
|
|
427
614
|
if (res.ok && res.data && res.data.animeList) {
|
|
428
615
|
(0, ui_1.clearScreen)();
|
|
429
616
|
(0, ui_1.createHeader)(`Genre: ${genreTitle}`, "#ffaa00");
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
(0, ui_1.printPaginationControls)(res.pagination, genreTitle);
|
|
433
|
-
const answer = await ask("
|
|
617
|
+
ui_1.logger.br();
|
|
618
|
+
(0, ui_1.printGenreAnimeList)(res.data.animeList);
|
|
619
|
+
(0, ui_1.printPaginationControls)(res.pagination, `: ${genreTitle}`, true);
|
|
620
|
+
const answer = await ask("Perintah:");
|
|
434
621
|
const choice = answer.toLowerCase();
|
|
435
622
|
if (choice === "b" || choice === "back") {
|
|
436
623
|
return await handleGenreMenu();
|
|
@@ -470,18 +657,27 @@ const handleGenreAnimeList = async (genreTitle, genreSlug, currentPage) => {
|
|
|
470
657
|
};
|
|
471
658
|
const handleSchedule = async () => {
|
|
472
659
|
const spinner = (0, ora_1.default)("Fetching schedule...").start();
|
|
473
|
-
|
|
474
|
-
|
|
660
|
+
let res;
|
|
661
|
+
try {
|
|
662
|
+
res = await api_1.default.getSchedule();
|
|
663
|
+
spinner.stop();
|
|
664
|
+
}
|
|
665
|
+
catch (err) {
|
|
666
|
+
spinner.fail();
|
|
667
|
+
ui_1.logger.error("Failed to fetch schedule");
|
|
668
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
669
|
+
return await runHome();
|
|
670
|
+
}
|
|
475
671
|
if (res && res.status === "success" && res.data) {
|
|
476
672
|
(0, ui_1.clearScreen)();
|
|
477
673
|
(0, ui_1.createHeader)("Anime Schedule", "#ffaa00");
|
|
478
674
|
(0, ui_1.printSchedule)(res.data);
|
|
479
675
|
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("
|
|
676
|
+
ui_1.logger.muted(" Perintah:");
|
|
677
|
+
ui_1.logger.muted(" • [nomor] - Pilih hari (1-Minggu)");
|
|
678
|
+
ui_1.logger.muted(" • [hari] - Filter berdasarkan hari (e.g. Senin, Selasa)");
|
|
679
|
+
ui_1.logger.muted(" • 'back' - Kembali ke home\n");
|
|
680
|
+
const answer = await ask("Perintah:");
|
|
485
681
|
const choice = answer.toLowerCase();
|
|
486
682
|
if (choice === "back" || choice === "0")
|
|
487
683
|
return await runHome();
|
|
@@ -493,8 +689,8 @@ const handleSchedule = async () => {
|
|
|
493
689
|
(0, ui_1.printSchedule)([selectedDay]);
|
|
494
690
|
ui_1.logger.br();
|
|
495
691
|
if (selectedDay.anime_list && selectedDay.anime_list.length > 0) {
|
|
496
|
-
ui_1.logger.muted(`
|
|
497
|
-
const ansAnime = await ask("
|
|
692
|
+
ui_1.logger.muted(` Pilih anime (1-${selectedDay.anime_list.length}) atau 'back'\n`);
|
|
693
|
+
const ansAnime = await ask("Pilih:");
|
|
498
694
|
const aChoice = ansAnime.toLowerCase();
|
|
499
695
|
if (aChoice === "back" || aChoice === "0")
|
|
500
696
|
return await handleSchedule();
|
|
@@ -515,8 +711,8 @@ const handleSchedule = async () => {
|
|
|
515
711
|
(0, ui_1.createHeader)(`Schedule: ${dayData.day}`, "#ffaa00");
|
|
516
712
|
(0, ui_1.printSchedule)([dayData]);
|
|
517
713
|
ui_1.logger.br();
|
|
518
|
-
ui_1.logger.muted(`
|
|
519
|
-
const ansAnime = await ask("
|
|
714
|
+
ui_1.logger.muted(` Pilih anime (1-${dayData.anime_list.length}) atau 'back'\n`);
|
|
715
|
+
const ansAnime = await ask("Pilih:");
|
|
520
716
|
const aChoice = ansAnime.toLowerCase();
|
|
521
717
|
if (aChoice === "back" || aChoice === "0")
|
|
522
718
|
return await handleSchedule();
|
|
@@ -541,6 +737,19 @@ const handleSchedule = async () => {
|
|
|
541
737
|
return await runHome();
|
|
542
738
|
}
|
|
543
739
|
};
|
|
740
|
+
const handleFAQ = async () => {
|
|
741
|
+
(0, ui_1.clearScreen)();
|
|
742
|
+
(0, ui_1.createHeader)("Frequently Asked Questions", "#ffaa00");
|
|
743
|
+
(0, ui_1.printFAQ)();
|
|
744
|
+
const answer = await ask("Perintah:");
|
|
745
|
+
const choice = answer.toLowerCase();
|
|
746
|
+
if (choice === "back" || choice === "0") {
|
|
747
|
+
return await runHome();
|
|
748
|
+
}
|
|
749
|
+
ui_1.logger.warn("Invalid command, returning to home...");
|
|
750
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
751
|
+
return await runHome();
|
|
752
|
+
};
|
|
544
753
|
const runHome = async () => {
|
|
545
754
|
(0, ui_1.clearScreen)();
|
|
546
755
|
(0, ui_1.showBanner)();
|
|
@@ -549,41 +758,34 @@ const runHome = async () => {
|
|
|
549
758
|
(0, ui_1.showMenu)([
|
|
550
759
|
"Ongoing Anime",
|
|
551
760
|
"Completed Anime",
|
|
761
|
+
"Anime Populer",
|
|
552
762
|
"Search Anime",
|
|
553
763
|
"Search by Genre",
|
|
554
764
|
"Schedule Anime",
|
|
765
|
+
"FAQ",
|
|
555
766
|
]);
|
|
556
|
-
const answer = await ask("
|
|
557
|
-
const res = await api_1.default.getHome();
|
|
767
|
+
const answer = await ask("Pilih:");
|
|
558
768
|
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();
|
|
769
|
+
await handleOngoingMenu(1);
|
|
567
770
|
}
|
|
568
771
|
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();
|
|
772
|
+
await handleCompletedMenu(1);
|
|
577
773
|
}
|
|
578
774
|
else if (answer === "3") {
|
|
579
|
-
await
|
|
775
|
+
await handlePopularMenu(1);
|
|
580
776
|
}
|
|
581
777
|
else if (answer === "4") {
|
|
582
|
-
await
|
|
778
|
+
await handleSearch();
|
|
583
779
|
}
|
|
584
780
|
else if (answer === "5") {
|
|
781
|
+
await handleGenreMenu();
|
|
782
|
+
}
|
|
783
|
+
else if (answer === "6") {
|
|
585
784
|
await handleSchedule();
|
|
586
785
|
}
|
|
786
|
+
else if (answer === "7") {
|
|
787
|
+
await handleFAQ();
|
|
788
|
+
}
|
|
587
789
|
else {
|
|
588
790
|
ui_1.logger.warn("Invalid choice");
|
|
589
791
|
await new Promise((r) => setTimeout(r, 1000));
|