@irfanshadikrishad/anilist
Version:
Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts
887 lines • 71.4 kB
JavaScript
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