UNPKG

wakitsu

Version:

Hobby project for managing anime watch list on Kitsu through CLI

177 lines 7.09 kB
import { CLIFlag } from '../cli.js'; import { Printer } from '../../printer/printer.js'; import { readdir } from 'fs/promises'; import { parseFansubFilename, pathJoin } from '../../utils.js'; import { Config } from '../../config.js'; import open from 'open'; import { displayWatchError, displayWatchProgress, useAnimeAutoWatcher } from '../../watch.js'; export class WhatToWatch extends CLIFlag { name = ['wtw', 'what-to-watch']; type = 'simple'; helpAliases = [ ...this.name, 'what to watch', 'recommend', 'recommend what to watch', 'recommendation', 'give recommendation', ]; shortHelpDisplay = `Displays all anime on disk that you haven't watched yet`; getHelpLogs() { return [ ['h1', ['What to Watch']], [ 'p', 'This flag tries to find files that you have yet to watch, that are part ' + 'of your watch list. It then displays a numbered list of titles it ' + 'found. Selecting one of the numbered titles, will start that anime in ' + 'your default media player.', ], null, [ 'p', 'Once the media player is ;x;closed;bk;, you will be prompted to set the ' + 'progress for that anime using the auto-progress feature; this allows for ' + 'a very seamless experience from watching an anime to updating its progress.', ], null, [ 'p', ';m;NOTE: ;bk;If you have not yet watched at least one episode of a ' + 'title it finds, then its associated file will be considered ' + ';x;untracked;bk;.', ], null, [ 'p', ';m;UNTRACKED: ;bk;To begin tracking your files, you need to use the default ' + 'command for watching anime. For more information on how to use the ' + 'default watch command, use the following command:', ], null, ['p', ';by;wak ;c;-h ;y;usage', 5], null, null, ]; } getSyntaxHelpLogs() { return [ ['h2', ['Usage']], ['s', ['wtw', 'what-to-watch'], ''], null, ['h2', ['Examples']], ['e', ['wtw', '']], ['e', ['what-to-watch', '']], ]; } async exec() { const filesAndFolders = await readdir(pathJoin(process.cwd()), { withFileTypes: true }); let fileEntries = filesAndFolders.filter((ff) => ff.isFile()).sort(); if (!fileEntries.length) { return Printer.printWarning('No fansub files were found in the current directory.', 'Operation Aborted'); } const fansubFileData = []; while (fileEntries.length) { const entry = fileEntries[0]; const [error, parts] = parseFansubFilename(entry.name); if (error) { fileEntries.splice(0, 1); continue; } const { title } = parts; const fileCount = fileEntries.length; fileEntries = fileEntries.filter((entry) => !entry.name.includes(title)); fansubFileData.push({ ...parts, fileCount: fileCount - fileEntries.length, fileName: entry.name, }); } const [whatToWatch, skippedFiles] = findWhatToWatch(fansubFileData); displayWhatToWatch(whatToWatch.map((wtw) => wtw[0][0])); if (skippedFiles.length) { Printer.print([null]); Printer.printWarning(skippedFiles, 'Untracked Files'); } const selection = await Printer.prompt('Enter a number from the list above or hit enter to skip this.'); if (selection == '') return; const selectedNumber = Number(selection); if (!selectedNumber || selectedNumber > whatToWatch.length) { return Printer.printError(`Make sure you enter a number between 1 and ${whatToWatch.length}`, 'Invalid Selection', 3); } const selectedIndex = selectedNumber - 1; const [animeToWatch, wtwData] = whatToWatch[selectedIndex]; await open(`${pathJoin(process.cwd(), wtwData.fileName)}`, { wait: true }); Printer.print([null, ['h3', ['Auto Incrementing Anime']]]); const [watchError, watcher] = await useAnimeAutoWatcher({ titleOrCache: animeToWatch, }); if (watchError) { return displayWatchError(watchError, wtwData.title); } const { anime, fileData, setProgress, moveFansubFile } = watcher; Printer.print([ null, ['py', ['JP Title', anime.jpTitle]], ['py', ['EN Title', anime.enTitle ?? '']], ['py', ['File', `;y;${fileData.title} ;by;${fileData.paddedEpNum}`], 4], null, [ 'p', `;b;Progress will be set from ;bg;${anime.epProgress} ;b;to ;by;${anime.epProgress + 1}`, ], ]); const hasConsent = await Printer.promptYesNo('Do you want to proceed with the changes above'); if (!hasConsent) { return Printer.printWarning('User cancelled the operation manually.', 'Operation Aborted', 3); } const stopLoader = Printer.printLoader('Updating Progress', 2); moveFansubFile(); const { completed, anime: newAnimeObj, tokenExpiresIn } = await setProgress(); stopLoader(); displayWatchProgress({ anime: newAnimeObj, autoIncrement: true, completed, tokenExpiresIn, }); } } function findWhatToWatch(fileData) { const badFiles = []; const whatToWatch = []; for (const data of fileData) { const fileBinding = Config.getKitsuProp('fileBindings').find((fb) => fb.name.toLowerCase() == data.title.toLowerCase()); if (!fileBinding) { badFiles.push(`;r;[${data.fansub}] ${data.title} - ${data.paddedEpNum}`); continue; } let anime; let cacheIndex = 0; const cache = Config.getKitsuProp('cache'); for (let i = 0; i < cache.length; i++) { if (cache[i].libID == fileBinding.id) { anime = cache[i]; cacheIndex = i; break; } } if (!anime) { throw Error(`${data.title} does not exist in anime cache`); } whatToWatch.push([[anime, cacheIndex, fileBinding.name], data]); } return [whatToWatch, badFiles]; } function displayWhatToWatch(anime) { anime.forEach((a, i) => { Printer.print([ null, ['h1', [`${i + 1}`]], ['py', ['Title JP', `;x;${a.jpTitle}`]], ['py', ['Title EN', `${a.enTitle}`]], ]); }); } //# sourceMappingURL=flag-wtw.js.map