@irfanshadikrishad/anilist 1.0.0-forbidden.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,282 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import fs from "fs";
11
+ import { readdir, writeFile } from "fs/promises";
12
+ import inquirer from "inquirer";
13
+ import { parse } from "json2csv";
14
+ import open from "open";
15
+ import { homedir } from "os";
16
+ import { join } from "path";
17
+ import process from "process";
18
+ import { Auth } from "./auth.js";
19
+ import { MALAnimeStatus, MALMangaStatus } from "./types.js";
20
+ const aniListEndpoint = `https://graphql.anilist.co`;
21
+ const redirectUri = "https://anilist.co/api/v2/oauth/pin";
22
+ function getTitle(title) {
23
+ return (title === null || title === void 0 ? void 0 : title.english) || (title === null || title === void 0 ? void 0 : title.romaji) || "???";
24
+ }
25
+ function formatDateObject(dateObj) {
26
+ if (!dateObj)
27
+ return "null";
28
+ return ([dateObj.day, dateObj.month, dateObj.year].filter(Boolean).join("/") ||
29
+ "null");
30
+ }
31
+ function getNextSeasonAndYear() {
32
+ const currentMonth = new Date().getMonth() + 1;
33
+ const currentYear = new Date().getFullYear();
34
+ let nextSeason;
35
+ let nextYear;
36
+ // Determine the current season
37
+ if (currentMonth >= 12 || currentMonth <= 2) {
38
+ nextSeason = "SPRING";
39
+ nextYear = currentMonth === 12 ? currentYear + 1 : currentYear;
40
+ }
41
+ else if (currentMonth >= 3 && currentMonth <= 5) {
42
+ nextSeason = "SUMMER";
43
+ nextYear = currentYear;
44
+ }
45
+ else if (currentMonth >= 6 && currentMonth <= 8) {
46
+ nextSeason = "FALL";
47
+ nextYear = currentYear;
48
+ }
49
+ else if (currentMonth >= 9 && currentMonth <= 11) {
50
+ nextSeason = "WINTER";
51
+ nextYear = currentYear + 1;
52
+ }
53
+ return { nextSeason, nextYear };
54
+ }
55
+ function removeHtmlAndMarkdown(input) {
56
+ if (input) {
57
+ input = input.replace(/<\/?[^>]+(>|$)/g, "");
58
+ input = input.replace(/(^|\n)#{1,6}\s+(.+?)(\n|$)/g, "$2 ");
59
+ input = input.replace(/(\*\*|__)(.*?)\1/g, "$2");
60
+ input = input.replace(/(\*|_)(.*?)\1/g, "$2");
61
+ input = input.replace(/`(.+?)`/g, "$1");
62
+ input = input.replace(/\[(.*?)\]\(.*?\)/g, "$1");
63
+ input = input.replace(/!\[(.*?)\]\(.*?\)/g, "$1");
64
+ input = input.replace(/(^|\n)>\s+(.+?)(\n|$)/g, "$2 ");
65
+ input = input.replace(/(^|\n)-\s+(.+?)(\n|$)/g, "$2 ");
66
+ input = input.replace(/(^|\n)\d+\.\s+(.+?)(\n|$)/g, "$2 ");
67
+ input = input.replace(/(^|\n)\s*([-*_]){3,}\s*(\n|$)/g, "$1");
68
+ input = input.replace(/~~(.*?)~~/g, "$1");
69
+ input = input.replace(/\s+/g, " ").trim();
70
+ }
71
+ return input;
72
+ }
73
+ function getDownloadFolderPath() {
74
+ const homeDirectory = homedir();
75
+ // Determine the Downloads folder path based on the platform
76
+ if (process.platform === "win32") {
77
+ return join(homeDirectory, "Downloads");
78
+ }
79
+ else if (process.platform === "darwin" || process.platform === "linux") {
80
+ return join(homeDirectory, "Downloads");
81
+ }
82
+ return homeDirectory;
83
+ }
84
+ function getFormattedDate() {
85
+ const date = new Date();
86
+ const day = String(date.getDate()).padStart(2, "0");
87
+ const month = String(date.getMonth() + 1).padStart(2, "0");
88
+ const year = date.getFullYear();
89
+ const hours = String(date.getHours()).padStart(2, "0");
90
+ const minutes = String(date.getMinutes()).padStart(2, "0");
91
+ // Format as DD-MM-YYYY-HH-MM
92
+ return `${day}-${month}-${year}-${hours}-${minutes}`;
93
+ }
94
+ /**
95
+ * Export JSON as JSON
96
+ * @param js0n
97
+ * @param dataType (eg: anime/manga)
98
+ */
99
+ function saveJSONasJSON(js0n, dataType) {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ try {
102
+ const jsonData = JSON.stringify(js0n, null, 2);
103
+ const path = join(getDownloadFolderPath(), `${yield Auth.MyUserName()}@irfanshadikrishad-anilist-${dataType}-${getFormattedDate()}.json`);
104
+ yield writeFile(path, jsonData, "utf8");
105
+ console.log(`\nSaved as JSON successfully.`);
106
+ open(getDownloadFolderPath());
107
+ }
108
+ catch (error) {
109
+ console.error("\nError saving JSON data:", error);
110
+ }
111
+ });
112
+ }
113
+ /**
114
+ * Export JSON as CSV
115
+ * @param js0n
116
+ * @param dataType (eg: anime/manga)
117
+ */
118
+ function saveJSONasCSV(js0n, dataType) {
119
+ return __awaiter(this, void 0, void 0, function* () {
120
+ try {
121
+ const csvData = parse(js0n);
122
+ const path = join(getDownloadFolderPath(), `${yield Auth.MyUserName()}@irfanshadikrishad-anilist-${dataType}-${getFormattedDate()}.csv`);
123
+ yield writeFile(path, csvData, "utf8");
124
+ console.log(`\nSaved as CSV successfully.`);
125
+ open(getDownloadFolderPath());
126
+ }
127
+ catch (error) {
128
+ console.error("\nError saving CSV data:", error);
129
+ }
130
+ });
131
+ }
132
+ function listFilesInDownloadFolder() {
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ const downloadFolderPath = getDownloadFolderPath();
135
+ const files = yield readdir(downloadFolderPath);
136
+ return files;
137
+ });
138
+ }
139
+ function selectFile(fileType) {
140
+ return __awaiter(this, void 0, void 0, function* () {
141
+ try {
142
+ const files = yield listFilesInDownloadFolder();
143
+ // Filter to include only files, not directories, with the specified extension
144
+ const onlyFiles = files.filter((file) => {
145
+ const filePath = `./downloads/${file}`; // Adjust this to the correct path
146
+ const isFile = fs.lstatSync(filePath).isFile(); // Check if it's a file
147
+ return isFile && file.endsWith(fileType);
148
+ });
149
+ if (onlyFiles.length > 0) {
150
+ const answers = yield inquirer.prompt([
151
+ {
152
+ type: "list",
153
+ name: "fileName",
154
+ message: "Select a file to import:",
155
+ choices: onlyFiles,
156
+ },
157
+ ]);
158
+ return answers.fileName;
159
+ }
160
+ else {
161
+ throw new Error(`\nNo importable ${fileType} file(s) found in download folder.`);
162
+ }
163
+ }
164
+ catch (error) {
165
+ console.error("\nError selecting file:", error);
166
+ throw error;
167
+ }
168
+ });
169
+ }
170
+ function createAnimeXML(malId, progress, status, episodes, title) {
171
+ return `
172
+ <anime>
173
+ <series_animedb_id>${malId}</series_animedb_id>
174
+ <series_title><![CDATA[${title}]]></series_title>
175
+ <series_type>""</series_type>
176
+ <series_episodes>${episodes}</series_episodes>
177
+ <my_id>0</my_id>
178
+ <my_watched_episodes>${progress}</my_watched_episodes>
179
+ <my_start_date>0000-00-00</my_start_date>
180
+ <my_finish_date>0000-00-00</my_finish_date>
181
+ <my_score>0</my_score>
182
+ <my_storage_value>0.00</my_storage_value>
183
+ <my_status>${status}</my_status>
184
+ <my_comments><![CDATA[]]></my_comments>
185
+ <my_times_watched>0</my_times_watched>
186
+ <my_rewatch_value></my_rewatch_value>
187
+ <my_priority>LOW</my_priority>
188
+ <my_tags><![CDATA[]]></my_tags>
189
+ <my_rewatching>0</my_rewatching>
190
+ <my_rewatching_ep>0</my_rewatching_ep>
191
+ <my_discuss>0</my_discuss>
192
+ <my_sns>default</my_sns>
193
+ <update_on_import>1</update_on_import>
194
+ </anime>`;
195
+ }
196
+ function createMangaXML(malId, progress, status, chapters, title) {
197
+ return `
198
+ <manga>
199
+ <manga_mangadb_id>${malId}</manga_mangadb_id>
200
+ <manga_title><![CDATA[${title ? title : "unknown"}]]></manga_title>
201
+ <manga_volumes>0</manga_volumes>
202
+ <manga_chapters>${chapters ? chapters : 0}</manga_chapters>
203
+ <my_id>0</my_id>
204
+ <my_read_chapters>${progress}</my_read_chapters>
205
+ <my_start_date>0000-00-00</my_start_date>
206
+ <my_finish_date>0000-00-00</my_finish_date>
207
+ <my_score>0</my_score>
208
+ <my_status>${status}</my_status>
209
+ <my_reread_value></my_reread_value>
210
+ <my_priority>LOW</my_priority>
211
+ <my_rereading>0</my_rereading>
212
+ <my_discuss>0</my_discuss>
213
+ <update_on_import>1</update_on_import>
214
+ </manga>`;
215
+ }
216
+ function createAnimeListXML(mediaWithProgress) {
217
+ return __awaiter(this, void 0, void 0, function* () {
218
+ const statusMap = {
219
+ PLANNING: MALAnimeStatus.PLAN_TO_WATCH,
220
+ COMPLETED: MALAnimeStatus.COMPLETED,
221
+ CURRENT: MALAnimeStatus.WATCHING,
222
+ PAUSED: MALAnimeStatus.ON_HOLD,
223
+ DROPPED: MALAnimeStatus.DROPPED,
224
+ };
225
+ const xmlEntries = mediaWithProgress.map((anime) => {
226
+ const malId = anime.malId;
227
+ const progress = anime.progress;
228
+ const episodes = anime.episodes;
229
+ const title = getTitle(anime.title);
230
+ const status = statusMap[anime.status];
231
+ return createAnimeXML(malId, progress, status, episodes, title);
232
+ });
233
+ return `<myanimelist>
234
+ <myinfo>
235
+ <user_id/>
236
+ <user_name>${yield Auth.MyUserName()}</user_name>
237
+ <user_export_type>1</user_export_type>
238
+ <user_total_anime>0</user_total_anime>
239
+ <user_total_watching>0</user_total_watching>
240
+ <user_total_completed>0</user_total_completed>
241
+ <user_total_onhold>0</user_total_onhold>
242
+ <user_total_dropped>0</user_total_dropped>
243
+ <user_total_plantowatch>0</user_total_plantowatch>
244
+ </myinfo>
245
+ \n${xmlEntries.join("\n")}\n
246
+ </myanimelist>`;
247
+ });
248
+ }
249
+ function createMangaListXML(mediaWithProgress) {
250
+ return __awaiter(this, void 0, void 0, function* () {
251
+ const statusMap = {
252
+ PLANNING: MALMangaStatus.PLAN_TO_READ,
253
+ COMPLETED: MALMangaStatus.COMPLETED,
254
+ CURRENT: MALMangaStatus.READING,
255
+ PAUSED: MALMangaStatus.ON_HOLD,
256
+ DROPPED: MALMangaStatus.DROPPED,
257
+ };
258
+ const xmlEntries = mediaWithProgress.map((manga) => {
259
+ const malId = manga.malId;
260
+ const progress = manga.progress;
261
+ const chapters = manga.chapters;
262
+ const title = getTitle(manga.title);
263
+ const status = statusMap[manga.status];
264
+ return createMangaXML(malId, progress, status, chapters, title);
265
+ });
266
+ return `<myanimelist>
267
+ <myinfo>
268
+ <user_id/>
269
+ <user_name>${yield Auth.MyUserName()}</user_name>
270
+ <user_export_type>2</user_export_type>
271
+ <user_total_manga>5</user_total_manga>
272
+ <user_total_reading>1</user_total_reading>
273
+ <user_total_completed>1</user_total_completed>
274
+ <user_total_onhold>1</user_total_onhold>
275
+ <user_total_dropped>1</user_total_dropped>
276
+ <user_total_plantoread>1</user_total_plantoread>
277
+ </myinfo>
278
+ \n${xmlEntries.join("\n")}\n
279
+ </myanimelist>`;
280
+ });
281
+ }
282
+ export { aniListEndpoint, createAnimeListXML, createAnimeXML, createMangaListXML, createMangaXML, formatDateObject, getDownloadFolderPath, getFormattedDate, getNextSeasonAndYear, getTitle, redirectUri, removeHtmlAndMarkdown, saveJSONasCSV, saveJSONasJSON, selectFile, };
package/bin/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/bin/index.js ADDED
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import { Command } from "commander";
12
+ import process from "process";
13
+ import { Auth } from "./helpers/auth.js";
14
+ import { AniList } from "./helpers/lists.js";
15
+ const cli = new Command();
16
+ cli
17
+ .name("anilist")
18
+ .description("Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts.")
19
+ .version("1.0.0-forbidden.0");
20
+ cli
21
+ .command("login")
22
+ .description("Login with AniList")
23
+ .requiredOption("-i, --id <number>", null)
24
+ .requiredOption("-s, --secret <string>", null)
25
+ .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ id, secret }) {
26
+ if (id && secret) {
27
+ yield Auth.Login(id, secret);
28
+ }
29
+ else {
30
+ console.log("\nMust provide both ClientId and ClientSecret!");
31
+ }
32
+ }));
33
+ cli
34
+ .command("me")
35
+ .description("Get details of the logged in user")
36
+ .action(() => __awaiter(void 0, void 0, void 0, function* () {
37
+ yield Auth.Myself();
38
+ }));
39
+ cli
40
+ .command("trending")
41
+ .alias("tr")
42
+ .description("Get the trending list from AniList")
43
+ .option("-c, --count <number>", "Number of list items to get", "10")
44
+ .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ count }) {
45
+ yield AniList.getTrendingAnime(Number(count));
46
+ }));
47
+ cli
48
+ .command("popular")
49
+ .alias("plr")
50
+ .description("Get the popular list from AniList")
51
+ .option("-c, --count <number>", "Number of list items to get", "10")
52
+ .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ count }) {
53
+ yield AniList.getPopularAnime(Number(count));
54
+ }));
55
+ cli
56
+ .command("user <username>")
57
+ .description("Get user information")
58
+ .action((username) => __awaiter(void 0, void 0, void 0, function* () {
59
+ yield AniList.getUserByUsername(username);
60
+ }));
61
+ cli
62
+ .command("logout")
63
+ .description("Log out the current user.")
64
+ .action(() => __awaiter(void 0, void 0, void 0, function* () {
65
+ yield Auth.Logout();
66
+ }));
67
+ cli
68
+ .command("lists")
69
+ .alias("ls")
70
+ .description("Get anime or manga list of authenticated user.")
71
+ .option("-a, --anime", "For anime list of authenticated user", false)
72
+ .option("-m, --manga", "For manga list of authenticated user", false)
73
+ .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ anime, manga }) {
74
+ if ((!anime && !manga) || (anime && manga)) {
75
+ console.error(`\nMust select an option, either --anime or --manga`);
76
+ }
77
+ else if (anime) {
78
+ yield AniList.MyAnime();
79
+ }
80
+ else if (manga) {
81
+ yield AniList.MyManga();
82
+ }
83
+ }));
84
+ cli
85
+ .command("delete")
86
+ .alias("del")
87
+ .description("Delete entire collections of anime or manga")
88
+ .option("-a, --anime", "For anime list of authenticated user", false)
89
+ .option("-m, --manga", "For manga list of authenticated user", false)
90
+ .option("-ac, --activity", "For activity of authenticated user", false)
91
+ .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ anime, manga, activity }) {
92
+ const selectedOptions = [anime, manga, activity].filter(Boolean).length;
93
+ if (selectedOptions === 0) {
94
+ console.error(`\nMust select one option: either --anime, --manga, or --activity`);
95
+ process.exit(1);
96
+ }
97
+ if (selectedOptions > 1) {
98
+ console.error(`\nOnly one option can be selected at a time: --anime, --manga, or --activity`);
99
+ process.exit(1);
100
+ }
101
+ if (anime) {
102
+ yield Auth.DeleteMyAnimeList();
103
+ }
104
+ else if (manga) {
105
+ yield Auth.DeleteMyMangaList();
106
+ }
107
+ else if (activity) {
108
+ yield Auth.DeleteMyActivities();
109
+ }
110
+ }));
111
+ cli
112
+ .command("upcoming")
113
+ .alias("up")
114
+ .description("Anime that will be released in upcoming season")
115
+ .option("-c, --count <number>", "Number of items to get", "10")
116
+ .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ count }) {
117
+ yield AniList.getUpcomingAnime(Number(count));
118
+ }));
119
+ cli
120
+ .command("anime <id>")
121
+ .description("Get anime details by their ID")
122
+ .action((id) => __awaiter(void 0, void 0, void 0, function* () {
123
+ if (id && !Number.isNaN(Number(id))) {
124
+ yield AniList.getAnimeDetailsByID(Number(id));
125
+ }
126
+ else {
127
+ console.error(`\nInvalid or missing ID (${id}). Please provide a valid numeric ID.`);
128
+ }
129
+ }));
130
+ cli
131
+ .command("search <query>")
132
+ .alias("srch")
133
+ .alias("find")
134
+ .description("Search anime or manga.")
135
+ .option("-a, --anime", "To get the anime search results.", false)
136
+ .option("-m, --manga", "To get the manga search results.", false)
137
+ .option("-c, --count <number>", "Number of search results to show.", "10")
138
+ .action((query_1, _a) => __awaiter(void 0, [query_1, _a], void 0, function* (query, { anime, manga, count }) {
139
+ if ((!anime && !manga) || (anime && manga)) {
140
+ console.error(`\nMust select an option, either --anime or --manga`);
141
+ }
142
+ else {
143
+ if (anime) {
144
+ yield AniList.searchAnime(query, Number(count));
145
+ }
146
+ else if (manga) {
147
+ yield AniList.searchManga(query, Number(count));
148
+ }
149
+ else {
150
+ console.error(`\nMust select an option, either --anime or --manga`);
151
+ }
152
+ }
153
+ }));
154
+ cli
155
+ .command("status <status>")
156
+ .alias("post")
157
+ .alias("write")
158
+ .description("Write a status...")
159
+ .action((status) => __awaiter(void 0, void 0, void 0, function* () {
160
+ yield Auth.Write(status);
161
+ }));
162
+ cli
163
+ .command("export")
164
+ .alias("exp")
165
+ .description("Export your anime or manga list.")
166
+ .option("-a, --anime", "To get the anime search results.", false)
167
+ .option("-m, --manga", "To get the manga search results.", false)
168
+ .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ anime, manga }) {
169
+ if ((!anime && !manga) || (anime && manga)) {
170
+ console.error(`\nMust select an option, either --anime or --manga`);
171
+ }
172
+ else {
173
+ if (anime) {
174
+ yield AniList.exportAnime();
175
+ }
176
+ else if (manga) {
177
+ yield AniList.exportManga();
178
+ }
179
+ }
180
+ }));
181
+ cli
182
+ .command("import")
183
+ .alias("imp")
184
+ .description("Import your anime or manga from anilist or other sources.")
185
+ .option("-a, --anime", "To get the anime search results.", false)
186
+ .option("-m, --manga", "To get the manga search results.", false)
187
+ .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ anime, manga }) {
188
+ if ((!anime && !manga) || (anime && manga)) {
189
+ console.error(`\nMust select an option, either --anime or --manga`);
190
+ }
191
+ else {
192
+ if (yield Auth.isLoggedIn()) {
193
+ if (anime) {
194
+ yield Auth.callAnimeImporter();
195
+ }
196
+ else if (manga) {
197
+ yield Auth.callMangaImporter();
198
+ }
199
+ }
200
+ else {
201
+ console.error(`\nPlease login to use this feature.`);
202
+ }
203
+ }
204
+ }));
205
+ cli
206
+ .command("autolike")
207
+ .alias("al")
208
+ .description("Autolike following or global activities.")
209
+ .action(() => __awaiter(void 0, void 0, void 0, function* () {
210
+ yield Auth.AutoLike();
211
+ }));
212
+ cli.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@irfanshadikrishad/anilist",
3
+ "description": "Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts",
4
+ "author": "Irfan Shadik Rishad",
5
+ "version": "1.0.0-forbidden.0",
6
+ "main": "./bin/index.js",
7
+ "type": "module",
8
+ "types": "./bin/index.d.ts",
9
+ "bin": {
10
+ "anilist": "./bin/index.js"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "scripts": {
16
+ "build": "rm -rf ./bin && tsc -w",
17
+ "format": "prettier . --write",
18
+ "format:check": "prettier . --check",
19
+ "lint": "eslint ./dist",
20
+ "lint:fix": "eslint ./dist --fix",
21
+ "all": "npm run lint && npm run lint:fix && npm run format"
22
+ },
23
+ "keywords": [
24
+ "anilist",
25
+ "CLI",
26
+ "anime",
27
+ "manga",
28
+ "anime list",
29
+ "manga list",
30
+ "anime tracker",
31
+ "manga tracker",
32
+ "anilist API",
33
+ "anime progress",
34
+ "manga progress",
35
+ "media list",
36
+ "export anime",
37
+ "import anime",
38
+ "export manga",
39
+ "import manga",
40
+ "status tracker",
41
+ "watchlist",
42
+ "reading list",
43
+ "graphql"
44
+ ],
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/irfanshadikrishad/anilist"
48
+ },
49
+ "homepage": "https://github.com/irfanshadikrishad/anilist",
50
+ "bugs": {
51
+ "url": "https://github.com/irfanshadikrishad/anilist/issues"
52
+ },
53
+ "license": "MPL-2.0",
54
+ "devDependencies": {
55
+ "@eslint/js": "^9.13.0",
56
+ "@types/json2csv": "^5.0.7",
57
+ "@types/node": "^22.7.9",
58
+ "eslint": "^9.13.0",
59
+ "globals": "^15.11.0",
60
+ "prettier": "^3.3.3",
61
+ "prettier-plugin-organize-imports": "^4.1.0",
62
+ "typescript": "^5.6.3",
63
+ "typescript-eslint": "^8.11.0"
64
+ },
65
+ "dependencies": {
66
+ "commander": "^12.1.0",
67
+ "fast-xml-parser": "^4.5.0",
68
+ "inquirer": "^12.0.0",
69
+ "json2csv": "^6.0.0-alpha.2",
70
+ "node-fetch": "^3.3.2",
71
+ "open": "^10.1.0"
72
+ }
73
+ }