@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.
- package/CHANGELOG.md +10 -0
- package/CODE_OF_CONDUCT.md +43 -0
- package/CONTRIBUTING.md +75 -0
- package/README.md +56 -17
- package/bin/helpers/auth.js +6 -5
- package/bin/helpers/fetcher.js +2 -2
- package/bin/helpers/lists.js +36 -36
- package/bin/helpers/more.d.ts +6 -1
- package/bin/helpers/more.js +208 -18
- package/bin/helpers/mutations.d.ts +4 -1
- package/bin/helpers/mutations.js +19 -1
- package/bin/helpers/queries.d.ts +10 -10
- package/bin/helpers/queries.js +13 -26
- package/bin/helpers/workers.d.ts +15 -1
- package/bin/helpers/workers.js +178 -1
- package/bin/index.js +64 -10
- package/package.json +24 -4
package/bin/helpers/workers.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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("
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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": "
|
|
3
|
+
"description": "Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts",
|
|
4
4
|
"author": "Irfan Shadik Rishad",
|
|
5
|
-
"version": "1.0.
|
|
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/
|
|
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
|
}
|