UNPKG

@irfanshadikrishad/anilist

Version:

Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts

888 lines (887 loc) 64.1 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 { Auth } from "./auth.js"; import { fetcher } from "./fetcher.js"; import { addAnimeToListMutation, addMangaToListMutation, 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"; 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: "list", 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: "list", 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: "list", 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: "list", 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: "list", 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: "list", 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: "list", 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: "list", 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: "list", 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.log(`\nTotal Entries Processed: ${count}`); } else {