UNPKG

@irfanshadikrishad/anilist

Version:

Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts

887 lines 71.4 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { XMLParser } from 'fast-xml-parser'; import { readFile } from 'fs/promises'; import inquirer from 'inquirer'; import { jsonrepair } from 'jsonrepair'; import { join } from 'path'; import Spinner from 'tiny-spinner'; import { Auth } from './auth.js'; import { fetcher } from './fetcher.js'; import { addAnimeToListMutation, addMangaToListMutation, moveListMutation, saveAnimeWithProgressMutation, saveMangaWithProgressMutation, } from './mutations.js'; import { animeDetailsQuery, animeSearchQuery, currentUserAnimeList, currentUserMangaList, malIdToAnilistAnimeId, malIdToAnilistMangaId, mangaDetailsQuery, mangaSearchQuery, popularQuery, trendingQuery, upcomingAnimesQuery, userActivityQuery, userFollowersQuery, userFollowingQuery, userQuery, } from './queries.js'; import { responsiveOutput } from './truncate.js'; import { AniListMediaStatus, } from './types.js'; import { Validate } from './validation.js'; import { anidbToanilistMapper, formatDateObject, getDownloadFolderPath, getNextSeasonAndYear, getTitle, logUserDetails, removeHtmlAndMarkdown, saveJSONasCSV, saveJSONasJSON, saveJSONasXML, selectFile, simpleDateFormat, timestampToTimeAgo, } from './workers.js'; const spinner = new Spinner(); class AniList { static importAnime() { return __awaiter(this, void 0, void 0, function* () { try { const filename = yield selectFile('.json'); if (!filename) { return; } const filePath = join(getDownloadFolderPath(), filename); const fileContent = yield readFile(filePath, 'utf8'); const importedData = JSON.parse(fileContent); if (!Validate.Import_JSON(importedData)) { console.error(`\nInvalid JSON file.`); return; } let count = 0; const batchSize = 1; for (let i = 0; i < importedData.length; i += batchSize) { const batch = importedData.slice(i, i + batchSize); yield Promise.all(batch.map((anime) => __awaiter(this, void 0, void 0, function* () { var _a, _b; const query = saveAnimeWithProgressMutation; const variables = { mediaId: anime === null || anime === void 0 ? void 0 : anime.id, progress: anime === null || anime === void 0 ? void 0 : anime.progress, status: anime === null || anime === void 0 ? void 0 : anime.status, hiddenFromStatusLists: false, }; try { const save = yield fetcher(query, variables); if (save) { const id = (_b = (_a = save === null || save === void 0 ? void 0 : save.data) === null || _a === void 0 ? void 0 : _a.SaveMediaListEntry) === null || _b === void 0 ? void 0 : _b.id; count++; console.log(`[${count}]\t${id}\t${anime === null || anime === void 0 ? void 0 : anime.id} ✔`); } else { console.error(`\nError saving ${anime === null || anime === void 0 ? void 0 : anime.id}`); } } catch (error) { console.error(`\nError saving ${anime === null || anime === void 0 ? void 0 : anime.id}: ${error.message}`); } }))); } console.log(`\nTotal ${count} anime(s) imported successfully.`); } catch (error) { console.error(`\n${error.message}`); } }); } static importManga() { return __awaiter(this, void 0, void 0, function* () { try { const filename = yield selectFile('.json'); if (!filename) { return; } const filePath = join(getDownloadFolderPath(), filename); const fileContent = yield readFile(filePath, 'utf8'); const importedData = JSON.parse(fileContent); if (!Validate.Import_JSON(importedData)) { console.error(`\nInvalid JSON file.`); return; } let count = 0; const batchSize = 1; for (let i = 0; i < importedData.length; i += batchSize) { const batch = importedData.slice(i, i + batchSize); yield Promise.all(batch.map((manga) => __awaiter(this, void 0, void 0, function* () { var _a, _b; const query = saveMangaWithProgressMutation; const variables = { mediaId: manga === null || manga === void 0 ? void 0 : manga.id, progress: manga === null || manga === void 0 ? void 0 : manga.progress, status: manga === null || manga === void 0 ? void 0 : manga.status, hiddenFromStatusLists: false, private: manga === null || manga === void 0 ? void 0 : manga.private, }; try { const save = yield fetcher(query, variables); if (save) { const id = (_b = (_a = save === null || save === void 0 ? void 0 : save.data) === null || _a === void 0 ? void 0 : _a.SaveMediaListEntry) === null || _b === void 0 ? void 0 : _b.id; count++; console.log(`[${count}]\t${id}\t${manga === null || manga === void 0 ? void 0 : manga.id} ✅`); } } catch (err) { console.error(`\nError saving ${manga === null || manga === void 0 ? void 0 : manga.id}: ${err.message}`); } }))); } console.log(`\nTotal ${count} manga(s) imported successfully.`); } catch (error) { console.error(`\nError: ${error.message}`); } }); } static exportAnime() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; if (!(yield Auth.isLoggedIn())) { console.error(`\nMust login to use this feature.`); return; } const { exportType } = yield inquirer.prompt([ { type: 'select', name: 'exportType', message: 'Choose export type:', choices: [ { name: 'CSV', value: 1 }, { name: 'JSON', value: 2 }, { name: 'XML (MyAnimeList/AniDB)', value: 3 }, ], pageSize: 10, }, ]); const animeList = yield fetcher(currentUserAnimeList, { id: yield Auth.MyUserId(), }); if (animeList) { const lists = (_c = (_b = (_a = animeList === null || animeList === void 0 ? void 0 : animeList.data) === null || _a === void 0 ? void 0 : _a.MediaListCollection) === null || _b === void 0 ? void 0 : _b.lists) !== null && _c !== void 0 ? _c : []; const mediaWithProgress = lists.flatMap((list) => list.entries.map((entry) => { var _a, _b, _c, _d; return ({ id: (_a = entry === null || entry === void 0 ? void 0 : entry.media) === null || _a === void 0 ? void 0 : _a.id, title: (_b = entry === null || entry === void 0 ? void 0 : entry.media) === null || _b === void 0 ? void 0 : _b.title, episodes: (_c = entry === null || entry === void 0 ? void 0 : entry.media) === null || _c === void 0 ? void 0 : _c.episodes, siteUrl: (_d = entry === null || entry === void 0 ? void 0 : entry.media) === null || _d === void 0 ? void 0 : _d.siteUrl, progress: entry.progress, status: entry === null || entry === void 0 ? void 0 : entry.status, hiddenFromStatusLists: entry.hiddenFromStatusLists, }); })); switch (exportType) { case 1: yield saveJSONasCSV(mediaWithProgress, 'anime'); break; case 2: yield saveJSONasJSON(mediaWithProgress, 'anime'); break; case 3: yield MyAnimeList.exportAnime(); break; default: console.log(`\nInvalid export type. ${exportType}`); break; } } else { console.error(`\nNo anime(s) found in your lists.`); } }); } static exportManga() { return __awaiter(this, void 0, void 0, function* () { var _a, _b; if (!(yield Auth.isLoggedIn())) { console.error(`\nPlease login to use this feature.`); return; } const mangaLists = yield fetcher(currentUserMangaList, { id: yield Auth.MyUserId(), }); if (!(mangaLists === null || mangaLists === void 0 ? void 0 : mangaLists.data)) { console.error(`\nCould not get manga list.`); return; } const lists = ((_b = (_a = mangaLists === null || mangaLists === void 0 ? void 0 : mangaLists.data) === null || _a === void 0 ? void 0 : _a.MediaListCollection) === null || _b === void 0 ? void 0 : _b.lists) || []; if (lists.length > 0) { const { exportType } = yield inquirer.prompt([ { type: 'select', name: 'exportType', message: 'Choose export type:', choices: [ { name: 'CSV', value: 1 }, { name: 'JSON', value: 2 }, { name: 'XML (MyAnimeList)', value: 3 }, ], pageSize: 10, }, ]); const mediaWithProgress = lists.flatMap((list) => list.entries.map((entry) => { var _a, _b; return ({ id: (_a = entry === null || entry === void 0 ? void 0 : entry.media) === null || _a === void 0 ? void 0 : _a.id, title: (_b = entry === null || entry === void 0 ? void 0 : entry.media) === null || _b === void 0 ? void 0 : _b.title, private: entry.private, chapters: entry.media.chapters, progress: entry.progress, status: entry === null || entry === void 0 ? void 0 : entry.status, hiddenFromStatusLists: entry.hiddenFromStatusLists, }); })); switch (exportType) { case 1: yield saveJSONasCSV(mediaWithProgress, 'manga'); break; case 2: yield saveJSONasJSON(mediaWithProgress, 'manga'); break; case 3: yield MyAnimeList.exportManga(); break; default: console.log(`\nInvalid export type. ${exportType}`); break; } } else { console.log(`\nList seems to be empty.`); } }); } static MyAnime() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; try { if (!(yield Auth.isLoggedIn())) { return console.error(`\nPlease log in first to access your lists.`); } if (!(yield Auth.MyUserId())) { return console.log(`\nFailed getting current user Id.`); } const data = yield fetcher(currentUserAnimeList, { id: yield Auth.MyUserId() }); if (data === null || data === void 0 ? void 0 : data.errors) { return console.log(`\nSomething went wrong. ${(_b = (_a = data === null || data === void 0 ? void 0 : data.errors) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message}`); } const lists = (_d = (_c = data === null || data === void 0 ? void 0 : data.data) === null || _c === void 0 ? void 0 : _c.MediaListCollection) === null || _d === void 0 ? void 0 : _d.lists; if (!lists || lists.length === 0) { return console.log(`\nYou seem to have no anime(s) in your lists.`); } const { selectedList } = yield inquirer.prompt([ { type: 'list', name: 'selectedList', message: 'Select an anime list:', choices: lists.map((list) => list.name), }, ]); const selectedEntries = lists.find((list) => list.name === selectedList); if (!selectedEntries || !selectedEntries.entries.length) { return console.log(`\nNo entries found or not available at this moment.`); } console.log(`\nEntries for '${selectedEntries.name}':`); const { selectedAnime } = yield inquirer.prompt([ { type: 'select', name: 'selectedAnime', message: 'Select anime to add to the list:', choices: selectedEntries.entries.map((entry, idx) => ({ name: `[${idx + 1}] ${getTitle(entry.media.title)}`, value: entry.media.id, })), pageSize: 10, }, ]); const { selectedListType } = yield inquirer.prompt([ { type: 'list', name: 'selectedListType', message: 'Select the list where you want to save this anime:', choices: [ { name: 'Planning', value: 'PLANNING' }, { name: 'Watching', value: 'CURRENT' }, { name: 'Completed', value: 'COMPLETED' }, { name: 'Paused', value: 'PAUSED' }, { name: 'Dropped', value: 'DROPPED' }, ], }, ]); const saveResponse = yield fetcher(addAnimeToListMutation, { mediaId: selectedAnime, status: selectedListType, }); if (saveResponse) { const savedEntry = (_e = saveResponse.data) === null || _e === void 0 ? void 0 : _e.SaveMediaListEntry; console.log(`\nEntry ${savedEntry === null || savedEntry === void 0 ? void 0 : savedEntry.id}. Saved as ${savedEntry === null || savedEntry === void 0 ? void 0 : savedEntry.status}.`); } else { console.error(`\nPlease log in first to use this feature.`); } } catch (error) { console.log(`\nSomething went wrong. ${error.message}`); } }); } static MyManga() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g; try { if (!(yield Auth.isLoggedIn())) { return console.error(`\nPlease log in first to access your lists.`); } const userId = yield Auth.MyUserId(); if (!userId) { return console.error(`\nFailed to get the current user ID.`); } const response = yield fetcher(currentUserMangaList, { id: userId }); if (!(response === null || response === void 0 ? void 0 : response.data)) { return console.error(`\nFailed to fetch manga lists. ${((_b = (_a = response === null || response === void 0 ? void 0 : response.errors) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message) || 'Unknown error'}`); } const lists = (_d = (_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.MediaListCollection) === null || _d === void 0 ? void 0 : _d.lists; if (!lists || lists.length === 0) { return console.log("\nYou don't seem to have any manga in your lists."); } const { selectedList } = yield inquirer.prompt([ { type: 'list', name: 'selectedList', message: 'Select a manga list:', choices: lists.map((list) => list.name), }, ]); const selectedEntries = lists.find((list) => list.name === selectedList); if (!selectedEntries || selectedEntries.entries.length === 0) { return console.log('\nNo manga entries found in the selected list.'); } console.log(`\nEntries for '${selectedEntries.name}':`); const { selectedManga } = yield inquirer.prompt([ { type: 'select', name: 'selectedManga', message: 'Select a manga to add to the list:', choices: selectedEntries.entries.map((entry, idx) => { var _a; return ({ name: `[${idx + 1}] ${getTitle(entry.media.title)}`, value: (_a = entry === null || entry === void 0 ? void 0 : entry.media) === null || _a === void 0 ? void 0 : _a.id, }); }), pageSize: 10, }, ]); const { selectedListType } = yield inquirer.prompt([ { type: 'list', name: 'selectedListType', message: 'Select the list where you want to save this manga:', choices: [ { name: 'Planning', value: 'PLANNING' }, { name: 'Reading', value: 'CURRENT' }, { name: 'Completed', value: 'COMPLETED' }, { name: 'Paused', value: 'PAUSED' }, { name: 'Dropped', value: 'DROPPED' }, ], }, ]); const saveResponse = yield fetcher(addMangaToListMutation, { mediaId: selectedManga, status: selectedListType, }); const saved = (_e = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.data) === null || _e === void 0 ? void 0 : _e.SaveMediaListEntry; if (saved) { console.log(`\nEntry ${saved.id}. Saved as ${saved.status}.`); } else { console.error(`\nFailed to save the manga. ${((_g = (_f = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.errors) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.message) || 'Unknown error'}`); } } catch (error) { console.error(`\nSomething went wrong. ${error.message}`); } }); } static getTrendingAnime(count) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g; try { let page = 1; let allTrending = []; while (true) { const response = yield fetcher(trendingQuery, { page, perPage: count }); if (response === null || response === void 0 ? void 0 : response.errors) { console.error(`\nSomething went wrong. ${((_b = (_a = response === null || response === void 0 ? void 0 : response.errors) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message) || 'Unknown error'}`); return; } const media = (_d = (_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.Page) === null || _d === void 0 ? void 0 : _d.media; if (!media || media.length === 0) { console.log(`\nNo more trending anime available.`); break; } allTrending = [...allTrending, ...media]; const choices = allTrending.map((anime, idx) => ({ name: `[${idx + 1}] ${getTitle(anime === null || anime === void 0 ? void 0 : anime.title)}`, value: String(anime === null || anime === void 0 ? void 0 : anime.id), })); choices.push({ name: 'See more', value: 'see_more' }); const { selectedAnime } = yield inquirer.prompt([ { type: 'select', name: 'selectedAnime', message: 'Select anime to add to the list:', choices, pageSize: choices.length + 1, }, ]); if (selectedAnime === 'see_more') { page++; continue; } else { const { selectedListType } = yield inquirer.prompt([ { type: 'list', name: 'selectedListType', message: 'Select the list where you want to save this anime:', choices: [ { name: 'Planning', value: 'PLANNING' }, { name: 'Watching', value: 'CURRENT' }, { name: 'Completed', value: 'COMPLETED' }, { name: 'Paused', value: 'PAUSED' }, { name: 'Dropped', value: 'DROPPED' }, ], }, ]); if (!(yield Auth.isLoggedIn())) { console.error(`\nPlease log in first to use this feature.`); return; } const variables = { mediaId: selectedAnime, status: selectedListType }; const saveResponse = yield fetcher(addAnimeToListMutation, variables); const saved = (_e = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.data) === null || _e === void 0 ? void 0 : _e.SaveMediaListEntry; if (saved) { console.log(`\nEntry ${saved.id}. Saved as ${saved.status}.`); } else { console.error(`\nFailed to save the anime. ${((_g = (_f = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.errors) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.message) || 'Unknown error'}`); } break; } } } catch (error) { console.error(`\nSomething went wrong. ${error.message}`); } }); } static getPopularAnime(count) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g; try { let page = 1; let allMedia = []; while (true) { const response = yield fetcher(popularQuery, { page, perPage: count }); if (!(response === null || response === void 0 ? void 0 : response.data)) { console.error(`\nSomething went wrong. ${((_b = (_a = response === null || response === void 0 ? void 0 : response.errors) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message) || 'Unknown error'}`); return; } const newMedia = (_d = (_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.Page) === null || _d === void 0 ? void 0 : _d.media; if (!newMedia || newMedia.length === 0) { console.log(`\nNo more popular anime available.`); break; } allMedia = [...allMedia, ...newMedia]; const choices = allMedia.map((anime, idx) => ({ name: `[${idx + 1}] ${getTitle(anime === null || anime === void 0 ? void 0 : anime.title)}`, value: String(anime === null || anime === void 0 ? void 0 : anime.id), })); choices.push({ name: 'See more', value: 'see_more' }); const { selectedAnime } = yield inquirer.prompt([ { type: 'select', name: 'selectedAnime', message: 'Select anime to add to the list:', choices, pageSize: choices.length, }, ]); if (selectedAnime === 'see_more') { page++; continue; } else { const { selectedListType } = yield inquirer.prompt([ { type: 'list', name: 'selectedListType', message: 'Select the list where you want to save this anime:', choices: [ { name: 'Planning', value: 'PLANNING' }, { name: 'Watching', value: 'CURRENT' }, { name: 'Completed', value: 'COMPLETED' }, { name: 'Paused', value: 'PAUSED' }, { name: 'Dropped', value: 'DROPPED' }, ], }, ]); if (!(yield Auth.isLoggedIn())) { return console.error(`\nPlease log in first to use this feature.`); } const variables = { mediaId: selectedAnime, status: selectedListType }; const saveResponse = yield fetcher(addAnimeToListMutation, variables); const saved = (_e = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.data) === null || _e === void 0 ? void 0 : _e.SaveMediaListEntry; if (saved) { console.log(`\nEntry ${saved.id}. Saved as ${saved.status}.`); } else { console.error(`\nFailed to save the anime. ${((_g = (_f = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.errors) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.message) || 'Unknown error'}`); } break; } } } catch (error) { console.error(`\nSomething went wrong. ${error.message}`); } }); } static getUpcomingAnime(count) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f; try { const { nextSeason, nextYear } = getNextSeasonAndYear(); let page = 1; let allUpcoming = []; while (true) { const request = yield fetcher(upcomingAnimesQuery, { nextSeason, nextYear, page, perPage: count, }); if (!request || !request.data) { console.error(`\nSomething went wrong. ${((_b = (_a = request === null || request === void 0 ? void 0 : request.errors) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message) || 'Unknown error'}`); return; } const newUpcoming = (_c = request.data.Page.media) !== null && _c !== void 0 ? _c : []; if (newUpcoming.length === 0) { console.log(`\nNo more upcoming anime available.`); break; } allUpcoming = [...allUpcoming, ...newUpcoming]; const choices = allUpcoming.map((anime, idx) => ({ name: `[${idx + 1}] ${getTitle(anime === null || anime === void 0 ? void 0 : anime.title)}`, value: String(anime === null || anime === void 0 ? void 0 : anime.id), })); choices.push({ name: 'See more', value: 'see_more' }); const { selectedAnime } = yield inquirer.prompt([ { type: 'select', name: 'selectedAnime', message: 'Select anime to add to the list:', choices, pageSize: choices.length + 2, }, ]); if (selectedAnime === 'see_more') { page++; continue; } else { const { selectedListType } = yield inquirer.prompt([ { type: 'list', name: 'selectedListType', message: 'Select the list where you want to save this anime:', choices: [ { name: 'Planning', value: 'PLANNING' }, { name: 'Watching', value: 'CURRENT' }, { name: 'Completed', value: 'COMPLETED' }, { name: 'Paused', value: 'PAUSED' }, { name: 'Dropped', value: 'DROPPED' }, ], }, ]); if (!(yield Auth.isLoggedIn())) { return console.error(`\nPlease log in first to use this feature.`); } const variables = { mediaId: selectedAnime, status: selectedListType }; const saveResponse = yield fetcher(addAnimeToListMutation, variables); const saved = (_d = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.data) === null || _d === void 0 ? void 0 : _d.SaveMediaListEntry; if (saved) { console.log(`\nEntry ${saved.id}. Saved as ${saved.status}.`); } else { console.error(`\nFailed to save the anime. ${((_f = (_e = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.errors) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.message) || 'Unknown error'}`); } break; } } } catch (error) { console.error(`\nError getting upcoming animes. ${error.message}`); } }); } static getUserByUsername(username) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; try { const response = yield fetcher(userQuery, { username }); if (!((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.User)) { return console.error(`\n${((_c = (_b = response === null || response === void 0 ? void 0 : response.errors) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.message) || 'Unknown error'}`); } const user = response.data.User; const userActivityResponse = yield fetcher(userActivityQuery, { id: user.id, page: 1, perPage: 10, }); const activities = (_f = (_e = (_d = userActivityResponse === null || userActivityResponse === void 0 ? void 0 : userActivityResponse.data) === null || _d === void 0 ? void 0 : _d.Page) === null || _e === void 0 ? void 0 : _e.activities) !== null && _f !== void 0 ? _f : []; // Get follower/following information const req_followers = yield fetcher(userFollowersQuery, { userId: user === null || user === void 0 ? void 0 : user.id, }); const req_following = yield fetcher(userFollowingQuery, { userId: user === null || user === void 0 ? void 0 : user.id, }); const followersCount = ((_j = (_h = (_g = req_followers === null || req_followers === void 0 ? void 0 : req_followers.data) === null || _g === void 0 ? void 0 : _g.Page) === null || _h === void 0 ? void 0 : _h.pageInfo) === null || _j === void 0 ? void 0 : _j.total) || 0; const followingCount = ((_m = (_l = (_k = req_following === null || req_following === void 0 ? void 0 : req_following.data) === null || _k === void 0 ? void 0 : _k.Page) === null || _l === void 0 ? void 0 : _l.pageInfo) === null || _m === void 0 ? void 0 : _m.total) || 0; logUserDetails(user, followersCount, followingCount); if (activities.length > 0) { console.log(`\nRecent Activities:`); activities.forEach(({ status, progress, media, createdAt }) => { responsiveOutput(`${timestampToTimeAgo(createdAt)}\t${status} ${progress ? `${progress} of ` : ''}${getTitle(media === null || media === void 0 ? void 0 : media.title)}`); }); } else { console.log('\nNo recent activities.'); } } catch (error) { console.error(`\nSomething went wrong. ${error.message}`); } }); } static getAnimeDetailsByID(anilistID) { return __awaiter(this, void 0, void 0, function* () { var _a; const details = yield fetcher(animeDetailsQuery, { id: anilistID, }); if ((_a = details === null || details === void 0 ? void 0 : details.data) === null || _a === void 0 ? void 0 : _a.Media) { const { id, title, description, duration, startDate, endDate, countryOfOrigin, isAdult, status, season, format, genres, siteUrl, } = details.data.Media; console.log(`\nID: ${id}`); console.log(`Title: ${(title === null || title === void 0 ? void 0 : title.userPreferred) || getTitle(title)}`); console.log(`Description: ${removeHtmlAndMarkdown(description)}`); console.log(`Episode Duration: ${duration || 'Unknown'} min`); console.log(`Origin: ${countryOfOrigin || 'N/A'}`); console.log(`Status: ${status || 'N/A'}`); console.log(`Format: ${format || 'N/A'}`); console.log(`Genres: ${genres.length ? genres.join(', ') : 'N/A'}`); console.log(`Season: ${season || 'N/A'}`); console.log(`Url: ${siteUrl || 'N/A'}`); console.log(`isAdult: ${isAdult ? 'Yes' : 'No'}`); console.log(`Released: ${formatDateObject(startDate) || 'Unknown'}`); console.log(`Finished: ${formatDateObject(endDate) || 'Ongoing'}`); } }); } static getMangaDetailsByID(mangaID) { return __awaiter(this, void 0, void 0, function* () { var _a; try { const response = yield fetcher(mangaDetailsQuery, { id: mangaID, }); if (response === null || response === void 0 ? void 0 : response.errors) { console.error(`${response.errors[0].message}`); return; } const manga = (_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.Media; if (manga) { console.log(`\n[${getTitle(manga.title)}]`); console.log(`${manga.description}`); console.log(`Chapters: ${manga.chapters}\t Volumes: ${manga.volumes}`); console.log(`Status:\t${manga.status}`); console.log(`Genres:\t${manga.genres.join(', ')}`); console.log(`Start:\t${simpleDateFormat(manga.startDate)}`); console.log(`End:\t${simpleDateFormat(manga.endDate)}`); } } catch (error) { console.error(`${error.message}`); } }); } static searchAnime(search, count) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const searchResults = yield fetcher(animeSearchQuery, { search, page: 1, perPage: count, }); if (searchResults) { const results = (_b = (_a = searchResults === null || searchResults === void 0 ? void 0 : searchResults.data) === null || _a === void 0 ? void 0 : _a.Page) === null || _b === void 0 ? void 0 : _b.media; if (results.length > 0) { const { selectedAnime } = yield inquirer.prompt([ { type: 'select', name: 'selectedAnime', message: 'Select anime to add to your list:', choices: results.map((res, idx) => ({ name: `[${idx + 1}] ${getTitle(res === null || res === void 0 ? void 0 : res.title)}`, value: res === null || res === void 0 ? void 0 : res.id, })), pageSize: 10, }, ]); const { selectedListType } = yield inquirer.prompt([ { type: 'list', name: 'selectedListType', message: 'Select the list where you want to save this anime:', choices: [ { name: 'Planning', value: 'PLANNING' }, { name: 'Watching', value: 'CURRENT' }, { name: 'Completed', value: 'COMPLETED' }, { name: 'Paused', value: 'PAUSED' }, { name: 'Dropped', value: 'DROPPED' }, ], }, ]); // Save selected anime to chosen list type if (yield Auth.isLoggedIn()) { const response = yield fetcher(addAnimeToListMutation, { mediaId: selectedAnime, status: selectedListType, }); if (response) { const saved = (_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.SaveMediaListEntry; console.log(`\nEntry ${saved === null || saved === void 0 ? void 0 : saved.id}. Saved as ${saved === null || saved === void 0 ? void 0 : saved.status}.`); } } else { console.error(`\nPlease log in first to use this feature.`); } } else { console.log(`\nNo search results found.`); } } else { console.error(`\nSomething went wrong.`); } }); } static searchManga(search, count) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const mangaSearchResult = yield fetcher(mangaSearchQuery, { search, page: 1, perPage: count, }); if (mangaSearchResult) { const results = (_b = (_a = mangaSearchResult === null || mangaSearchResult === void 0 ? void 0 : mangaSearchResult.data) === null || _a === void 0 ? void 0 : _a.Page) === null || _b === void 0 ? void 0 : _b.media; // List of manga search results const { selectedMangaId } = yield inquirer.prompt([ { type: 'select', name: 'selectedMangaId', message: 'Select manga to add to your list:', choices: results.map((res, idx) => ({ name: `[${idx + 1}] ${getTitle(res === null || res === void 0 ? void 0 : res.title)}`, value: res === null || res === void 0 ? void 0 : res.id, })), pageSize: 10, }, ]); // Options to save to the list const { selectedListType } = yield inquirer.prompt([ { type: 'list', name: 'selectedListType', message: 'Select the list where you want to save this manga:', choices: [ { name: 'Planning', value: 'PLANNING' }, { name: 'Reading', value: 'CURRENT' }, { name: 'Completed', value: 'COMPLETED' }, { name: 'Paused', value: 'PAUSED' }, { name: 'Dropped', value: 'DROPPED' }, ], }, ]); // If logged in save to the list if (yield Auth.isLoggedIn()) { const response = yield fetcher(addMangaToListMutation, { mediaId: selectedMangaId, status: selectedListType, }); if (response) { const saved = (_c = response === null || response === void 0 ? void 0 : response.data) === null || _c === void 0 ? void 0 : _c.SaveMediaListEntry; console.log(`\nEntry ${saved === null || saved === void 0 ? void 0 : saved.id}. Saved as ${saved === null || saved === void 0 ? void 0 : saved.status}.`); } } else { console.error(`\nPlease log in first to use this feature.`); } } else { console.error(`\nSomething went wrong.`); } }); } } class MyAnimeList { static importAnime() { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e; try { const filename = yield selectFile('.xml'); if (!filename) { return; } const filePath = join(getDownloadFolderPath(), filename); const fileContent = yield readFile(filePath, 'utf8'); if (!(yield Validate.Import_AnimeXML(fileContent))) { console.error(`\nInvalid XML file.`); return; } const parser = new XMLParser(); if (fileContent) { const XMLObject = parser.parse(fileContent); const animeList = (_a = XMLObject === null || XMLObject === void 0 ? void 0 : XMLObject.myanimelist) === null || _a === void 0 ? void 0 : _a.anime; if ((animeList === null || animeList === void 0 ? void 0 : animeList.length) > 0) { let count = 0; const statusMap = { 'On-Hold': AniListMediaStatus.PAUSED, 'Dropped': AniListMediaStatus.DROPPED, 'Completed': AniListMediaStatus.COMPLETED, 'Watching': AniListMediaStatus.CURRENT, 'Plan to Watch': AniListMediaStatus.PLANNING, }; for (const anime of animeList) { const malId = anime.series_animedb_id; const progress = anime.my_watched_episodes; const status = statusMap[anime.my_status]; try { // Fetch AniList ID using MAL ID const anilistResponse = yield fetcher(malIdToAnilistAnimeId, { malId }); const anilistId = (_c = (_b = anilistResponse === null || anilistResponse === void 0 ? void 0 : anilistResponse.data) === null || _b === void 0 ? void 0 : _b.Media) === null || _c === void 0 ? void 0 : _c.id; if (anilistId) { // Save anime entry with progress const saveResponse = yield fetcher(saveAnimeWithProgressMutation, { mediaId: anilistId, progress, status, hiddenFromStatusLists: false, private: false, }); const entryId = (_e = (_d = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.data) === null || _d === void 0 ? void 0 : _d.SaveMediaListEntry) === null || _e === void 0 ? void 0 : _e.id; if (entryId) { count++; console.log(`[${count}] ${entryId} ✔`); } } else { console.error(`Could not retrieve AniList ID for MAL ID ${malId}`); } } catch (error) { console.error(`Error processing MAL ID ${malId}: ${error.message}`); } } console