@irfanshadikrishad/anilist 1.0.3 → 1.0.6

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.
@@ -1,3 +1,21 @@
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 inquirer from "inquirer";
11
+ import open from "open";
12
+ import { join } from "path";
13
+ import { homedir } from "os";
14
+ import { parse } from "json2csv";
15
+ import { writeFile, readdir, readFile } from "fs/promises";
16
+ import { currentUsersName } from "./auth.js";
17
+ import { saveAnimeWithProgressMutation, saveMangaWithProgressMutation, } from "./mutations.js";
18
+ import { fetcher } from "./fetcher.js";
1
19
  const aniListEndpoint = `https://graphql.anilist.co`;
2
20
  const redirectUri = "https://anilist.co/api/v2/oauth/pin";
3
21
  function getTitle(title) {
@@ -51,4 +69,163 @@ function removeHtmlAndMarkdown(input) {
51
69
  }
52
70
  return input;
53
71
  }
54
- export { aniListEndpoint, redirectUri, getTitle, getNextSeasonAndYear, formatDateObject, removeHtmlAndMarkdown, };
72
+ function getDownloadFolderPath() {
73
+ const homeDirectory = homedir();
74
+ // Determine the Downloads folder path based on the platform
75
+ if (process.platform === "win32") {
76
+ return join(homeDirectory, "Downloads");
77
+ }
78
+ else if (process.platform === "darwin" || process.platform === "linux") {
79
+ return join(homeDirectory, "Downloads");
80
+ }
81
+ return homeDirectory;
82
+ }
83
+ function getFormattedDate() {
84
+ const date = new Date();
85
+ const day = String(date.getDate()).padStart(2, "0");
86
+ const month = String(date.getMonth() + 1).padStart(2, "0");
87
+ const year = date.getFullYear();
88
+ const hours = String(date.getHours()).padStart(2, "0");
89
+ const minutes = String(date.getMinutes()).padStart(2, "0");
90
+ // Format as DD-MM-YYYY-HH-MM
91
+ return `${day}-${month}-${year}-${hours}-${minutes}`;
92
+ }
93
+ /**
94
+ * Export JSON as JSON
95
+ * @param js0n
96
+ * @param dataType (eg: anime/manga)
97
+ */
98
+ function saveJSONasJSON(js0n, dataType) {
99
+ return __awaiter(this, void 0, void 0, function* () {
100
+ try {
101
+ const jsonData = JSON.stringify(js0n, null, 2);
102
+ const path = join(getDownloadFolderPath(), `${yield currentUsersName()}@irfanshadikrishad-anilist-${dataType}-${getFormattedDate()}.json`);
103
+ yield writeFile(path, jsonData, "utf8");
104
+ console.log(`\nSaved as JSON successfully.`);
105
+ open(getDownloadFolderPath());
106
+ }
107
+ catch (error) {
108
+ console.error("\nError saving JSON data:", error);
109
+ }
110
+ });
111
+ }
112
+ /**
113
+ * Export JSON as CSV
114
+ * @param js0n
115
+ * @param dataType (eg: anime/manga)
116
+ */
117
+ function saveJSONasCSV(js0n, dataType) {
118
+ return __awaiter(this, void 0, void 0, function* () {
119
+ try {
120
+ const csvData = parse(js0n);
121
+ const path = join(getDownloadFolderPath(), `${yield currentUsersName()}@irfanshadikrishad-anilist-${dataType}-${getFormattedDate()}.csv`);
122
+ yield writeFile(path, csvData, "utf8");
123
+ console.log(`\nSaved as CSV successfully.`);
124
+ open(getDownloadFolderPath());
125
+ }
126
+ catch (error) {
127
+ console.error("\nError saving CSV data:", error);
128
+ }
129
+ });
130
+ }
131
+ function listFilesInDownloadFolder() {
132
+ return __awaiter(this, void 0, void 0, function* () {
133
+ const downloadFolderPath = getDownloadFolderPath();
134
+ const files = yield readdir(downloadFolderPath);
135
+ return files;
136
+ });
137
+ }
138
+ function selectFile() {
139
+ return __awaiter(this, void 0, void 0, function* () {
140
+ try {
141
+ const files = yield listFilesInDownloadFolder();
142
+ const onlyJSONfiles = files.filter((file) => file.endsWith(".json"));
143
+ if (onlyJSONfiles.length > 0) {
144
+ const answers = yield inquirer.prompt([
145
+ {
146
+ type: "list",
147
+ name: "fileName",
148
+ message: "Select a file to import:",
149
+ choices: onlyJSONfiles,
150
+ },
151
+ ]);
152
+ return answers.fileName;
153
+ }
154
+ else {
155
+ throw new Error(`\nNo importable JSON file(s) found in download folder.`);
156
+ }
157
+ }
158
+ catch (error) {
159
+ console.error("\nError selecting file:", error);
160
+ throw error;
161
+ }
162
+ });
163
+ }
164
+ function importAnimeListFromExportedJSON() {
165
+ return __awaiter(this, void 0, void 0, function* () {
166
+ var _a, _b;
167
+ try {
168
+ const filename = yield selectFile();
169
+ const filePath = join(getDownloadFolderPath(), filename);
170
+ const fileContent = yield readFile(filePath, "utf8");
171
+ const importedData = JSON.parse(fileContent);
172
+ for (let anime of importedData) {
173
+ const query = saveAnimeWithProgressMutation;
174
+ const variables = {
175
+ mediaId: anime === null || anime === void 0 ? void 0 : anime.id,
176
+ progress: anime === null || anime === void 0 ? void 0 : anime.progress,
177
+ status: anime === null || anime === void 0 ? void 0 : anime.status,
178
+ hiddenFromStatusLists: false,
179
+ };
180
+ const save = yield fetcher(query, variables);
181
+ if (save) {
182
+ 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;
183
+ console.log(`${anime === null || anime === void 0 ? void 0 : anime.id}-${id} ✅`);
184
+ }
185
+ else {
186
+ console.error(`\nError saving ${anime === null || anime === void 0 ? void 0 : anime.id}`);
187
+ }
188
+ // avoiding rate-limit
189
+ yield new Promise((resolve) => setTimeout(resolve, 2000));
190
+ }
191
+ }
192
+ catch (error) {
193
+ console.error(`\n${error.message}`);
194
+ }
195
+ });
196
+ }
197
+ function importMangaListFromExportedJSON() {
198
+ return __awaiter(this, void 0, void 0, function* () {
199
+ var _a, _b;
200
+ try {
201
+ const filename = yield selectFile();
202
+ const filePath = join(getDownloadFolderPath(), filename);
203
+ const fileContent = yield readFile(filePath, "utf8");
204
+ const importedData = JSON.parse(fileContent);
205
+ for (let manga of importedData) {
206
+ const query = saveMangaWithProgressMutation;
207
+ const variables = {
208
+ mediaId: manga === null || manga === void 0 ? void 0 : manga.id,
209
+ progress: manga === null || manga === void 0 ? void 0 : manga.progress,
210
+ status: manga === null || manga === void 0 ? void 0 : manga.status,
211
+ hiddenFromStatusLists: false,
212
+ private: manga === null || manga === void 0 ? void 0 : manga.private,
213
+ };
214
+ const save = yield fetcher(query, variables);
215
+ if (save) {
216
+ 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;
217
+ console.log(`${manga === null || manga === void 0 ? void 0 : manga.id}-${id} ✅`);
218
+ }
219
+ else {
220
+ console.error(`\nError saving ${manga === null || manga === void 0 ? void 0 : manga.id}`);
221
+ }
222
+ // avoiding rate-limit
223
+ yield new Promise((resolve) => setTimeout(resolve, 2000));
224
+ }
225
+ }
226
+ catch (error) {
227
+ console.error(`\n${error.message}`);
228
+ }
229
+ });
230
+ }
231
+ export { aniListEndpoint, redirectUri, getTitle, getNextSeasonAndYear, formatDateObject, removeHtmlAndMarkdown, saveJSONasJSON, saveJSONasCSV, importAnimeListFromExportedJSON, importMangaListFromExportedJSON, };
package/bin/index.js CHANGED
@@ -9,11 +9,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  import { Command } from "commander";
12
- import { anilistUserLogin, currentUserInfo, logoutUser, } from "./helpers/auth.js";
12
+ import { anilistUserLogin, currentUserInfo, isLoggedIn, logoutUser, } from "./helpers/auth.js";
13
13
  import { deleteAnimeCollection, deleteMangaCollection, getPopular, getTrending, getUpcomingAnimes, loggedInUsersAnimeLists, loggedInUsersMangaLists, } from "./helpers/lists.js";
14
- import { getAnimeDetailsByID, getAnimeSearchResults, getMangaSearchResults, deleteUserActivities, getUserInfoByUsername, } from "./helpers/more.js";
14
+ import { getAnimeDetailsByID, getAnimeSearchResults, getMangaSearchResults, deleteUserActivities, getUserInfoByUsername, writeTextActivity, exportAnimeList, exportMangaList, importAnimeList, importMangaList, } from "./helpers/more.js";
15
15
  const cli = new Command();
16
- cli.name("anilist").description("Unofficial AniList CLI").version("1.0.3");
16
+ cli
17
+ .name("anilist")
18
+ .description("Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts.")
19
+ .version("1.0.6");
17
20
  cli
18
21
  .command("login")
19
22
  .description("Login with AniList")
@@ -24,7 +27,7 @@ cli
24
27
  yield anilistUserLogin(id, secret);
25
28
  }
26
29
  else {
27
- console.log("Tokens not provided correctly!");
30
+ console.log("\nMust provide both ClientId and ClientSecret!");
28
31
  }
29
32
  }));
30
33
  cli
@@ -69,7 +72,7 @@ cli
69
72
  .option("-m, --manga", "For manga list of authenticated user", false)
70
73
  .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ anime, manga }) {
71
74
  if ((!anime && !manga) || (anime && manga)) {
72
- console.error(`Must select an option, either --anime or --manga`);
75
+ console.error(`\nMust select an option, either --anime or --manga`);
73
76
  }
74
77
  else if (anime) {
75
78
  yield loggedInUsersAnimeLists();
@@ -88,11 +91,11 @@ cli
88
91
  .action((_a) => __awaiter(void 0, [_a], void 0, function* ({ anime, manga, activity }) {
89
92
  const selectedOptions = [anime, manga, activity].filter(Boolean).length;
90
93
  if (selectedOptions === 0) {
91
- console.error(`Must select one option: either --anime, --manga, or --activity`);
94
+ console.error(`\nMust select one option: either --anime, --manga, or --activity`);
92
95
  process.exit(1);
93
96
  }
94
97
  if (selectedOptions > 1) {
95
- console.error(`Only one option can be selected at a time: --anime, --manga, or --activity`);
98
+ console.error(`\nOnly one option can be selected at a time: --anime, --manga, or --activity`);
96
99
  process.exit(1);
97
100
  }
98
101
  if (anime) {
@@ -121,7 +124,7 @@ cli
121
124
  yield getAnimeDetailsByID(Number(id));
122
125
  }
123
126
  else {
124
- console.error("Invalid or missing ID. Please provide a valid numeric ID.");
127
+ console.error(`\nInvalid or missing ID (${id}). Please provide a valid numeric ID.`);
125
128
  }
126
129
  }));
127
130
  cli
@@ -134,7 +137,7 @@ cli
134
137
  .option("-c, --count <number>", "Number of search results to show.", "10")
135
138
  .action((query_1, _a) => __awaiter(void 0, [query_1, _a], void 0, function* (query, { anime, manga, count }) {
136
139
  if ((!anime && !manga) || (anime && manga)) {
137
- console.error(`Must select an option, either --anime or --manga`);
140
+ console.error(`\nMust select an option, either --anime or --manga`);
138
141
  }
139
142
  else {
140
143
  if (anime) {
@@ -144,7 +147,58 @@ cli
144
147
  yield getMangaSearchResults(query, Number(count));
145
148
  }
146
149
  else {
147
- console.error(`Must select an option, either --anime or --manga`);
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 writeTextActivity(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 exportAnimeList();
175
+ }
176
+ else if (manga) {
177
+ yield exportMangaList();
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 isLoggedIn()) {
193
+ if (anime) {
194
+ yield importAnimeList();
195
+ }
196
+ else if (manga) {
197
+ yield importMangaList();
198
+ }
199
+ }
200
+ else {
201
+ console.error(`\nPlease login to use this feature.`);
148
202
  }
149
203
  }
150
204
  }));
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@irfanshadikrishad/anilist",
3
- "description": "Unofficial AniList CLI",
3
+ "description": "Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts",
4
4
  "author": "Irfan Shadik Rishad",
5
- "version": "1.0.3",
5
+ "version": "1.0.6",
6
6
  "main": "./bin/index.js",
7
7
  "type": "module",
8
8
  "types": "./bin/index.d.ts",
@@ -17,7 +17,25 @@
17
17
  },
18
18
  "keywords": [
19
19
  "anilist",
20
- "CLI"
20
+ "CLI",
21
+ "anime",
22
+ "manga",
23
+ "anime list",
24
+ "manga list",
25
+ "anime tracker",
26
+ "manga tracker",
27
+ "anilist API",
28
+ "anime progress",
29
+ "manga progress",
30
+ "media list",
31
+ "export anime",
32
+ "import anime",
33
+ "export manga",
34
+ "import manga",
35
+ "status tracker",
36
+ "watchlist",
37
+ "reading list",
38
+ "graphql"
21
39
  ],
22
40
  "repository": {
23
41
  "type": "git",
@@ -29,12 +47,14 @@
29
47
  },
30
48
  "license": "MPL-2.0",
31
49
  "devDependencies": {
32
- "@types/node": "^22.7.6",
50
+ "@types/json2csv": "^5.0.7",
51
+ "@types/node": "^22.7.7",
33
52
  "typescript": "^5.6.3"
34
53
  },
35
54
  "dependencies": {
36
55
  "commander": "^12.1.0",
37
56
  "inquirer": "^12.0.0",
57
+ "json2csv": "^6.0.0-alpha.2",
38
58
  "node-fetch": "^3.3.2",
39
59
  "open": "^10.1.0"
40
60
  }