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/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.2.6");
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(" Select format (1-" + formats.length + ") or 'back'\n");
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)(`Select Quality (${selectedFormat.title})`, "#00ff9f");
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(" Select quality (1-" + selectedFormat.qualities.length + ") or 'back'\n");
78
- const ansQuality = await ask("Quality:");
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)(`Select Provider (${selectedQuality.title})`, "#00ff9f");
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(" Select provider (1-" + selectedQuality.urls.length + ") or 'back'\n");
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)("Select Quality", "#ff6b9d");
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(" Select quality (1-" + qualities.length + ") or 'back'\n");
116
- const answer = await ask("Quality:");
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(" Select quality (1-" + downloads.length + ") or 'back'\n");
139
- const answer = await ask("Quality:");
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(" Select provider (1-" + selected.urls.length + ") or 'back'\n");
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(provChoice) - 1;
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
- const epRes = await api_1.default.getEpisode(episode.episodeId);
190
- if (!epRes.ok || !epRes.data) {
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
- const qualities = epRes.data.server?.qualities || [];
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
- const url = epRes.data.defaultStreamingUrl || episode.otakudesuUrl;
198
- if (url) {
199
- ui_1.logger.info("Opening default stream...");
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 = epRes.data.defaultStreamingUrl;
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(" Commands:");
258
- ui_1.logger.muted(" • [number] or 'latest' - Watch episode");
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' or 'batch' - Download batch");
261
- ui_1.logger.muted(" • 'd' or 'download' - Download episode");
262
- ui_1.logger.muted(" • 'back' - Return to anime list\n");
263
- const answer = await ask("Command:");
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)("Select Episode to Download", "#00ff9f");
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(" Type episode number or 'back'\n");
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(epChoice);
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
- const epRes = await api_1.default.getEpisode(episode.episodeId);
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 showAnimeList = async (list, title, isOngoing) => {
338
- (0, ui_1.clearScreen)();
339
- (0, ui_1.createHeader)(title, "#00d9ff");
340
- (0, ui_1.printAnimeList)(list, isOngoing);
341
- ui_1.logger.br();
342
- ui_1.logger.muted(` Select anime (1-${list.length}) or 'home' to return\n`);
343
- const answer = await ask("Select:");
344
- const choice = answer.toLowerCase();
345
- if (choice === "home" || choice === "0")
346
- return "home";
347
- const index = parseInt(choice) - 1;
348
- if (!isNaN(index) && index >= 0 && index < list.length) {
349
- await showAnimeDetail(list[index].animeId);
350
- return await showAnimeList(list, title, isOngoing);
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
- const res = await api_1.default.getSearch(keyword);
366
- spinner.stop();
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 Results", "#ff6b9d");
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(` Select anime (1-${res.data.animeList.length}) or 'back'\n`);
373
- const answer = await ask("Select:");
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
- const res = await api_1.default.getGenre();
396
- spinner.stop();
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)("Select Genre", "#ffaa00");
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(` Select genre (1-${res.data.genreList.length}) or 'back'\n`);
403
- const answer = await ask("Select:");
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
- const res = await api_1.default.getGenreAnime(genreSlug, currentPage);
426
- spinner.stop();
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
- (0, ui_1.printSearchResults)(res.data.animeList);
431
- // PERBAIKAN: Gunakan res.pagination (root level), bukan res.data.pagination
432
- (0, ui_1.printPaginationControls)(res.pagination, genreTitle);
433
- const answer = await ask("Command:");
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
- const res = await api_1.default.getSchedule();
474
- spinner.stop();
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(" Commands:");
481
- ui_1.logger.muted(" • [number] - Select day (1-Minggu)");
482
- ui_1.logger.muted(" • [day] - Filter by day (e.g. Senin, Selasa)");
483
- ui_1.logger.muted(" • 'back' - Return to home\n");
484
- const answer = await ask("Command:");
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(` Select anime (1-${selectedDay.anime_list.length}) or 'back'\n`);
497
- const ansAnime = await ask("Select:");
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(` Select anime (1-${dayData.anime_list.length}) or 'back'\n`);
519
- const ansAnime = await ask("Select:");
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("Choose:");
557
- const res = await api_1.default.getHome();
767
+ const answer = await ask("Pilih:");
558
768
  if (answer === "1") {
559
- const list = res.data?.ongoing?.animeList || [];
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
- const list = res.data?.completed?.animeList || [];
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 handleSearch();
775
+ await handlePopularMenu(1);
580
776
  }
581
777
  else if (answer === "4") {
582
- await handleGenreMenu();
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));