@irfanshadikrishad/anilist 1.2.3 → 1.2.5
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/LICENSE +115 -124
- package/README.md +23 -20
- package/bin/helpers/auth.d.ts +35 -6
- package/bin/helpers/auth.js +49 -55
- package/bin/helpers/fetcher.d.ts +1 -1
- package/bin/helpers/lists.d.ts +5 -2
- package/bin/helpers/lists.js +181 -142
- package/bin/helpers/queries.d.ts +5 -3
- package/bin/helpers/queries.js +17 -3
- package/bin/helpers/types.d.ts +231 -8
- package/bin/helpers/workers.d.ts +6 -4
- package/bin/helpers/workers.js +60 -2
- package/bin/index.js +2 -2
- package/package.json +8 -7
package/bin/helpers/types.d.ts
CHANGED
|
@@ -84,13 +84,7 @@ declare enum MALMangaStatus {
|
|
|
84
84
|
interface AnimeList {
|
|
85
85
|
data?: {
|
|
86
86
|
MediaListCollection: {
|
|
87
|
-
lists:
|
|
88
|
-
name: string;
|
|
89
|
-
entries: {
|
|
90
|
-
id: number;
|
|
91
|
-
progress: number;
|
|
92
|
-
}[];
|
|
93
|
-
}[];
|
|
87
|
+
lists: MediaList[];
|
|
94
88
|
};
|
|
95
89
|
};
|
|
96
90
|
errors?: {
|
|
@@ -108,4 +102,233 @@ interface MediaWithProgress {
|
|
|
108
102
|
romaji?: string;
|
|
109
103
|
};
|
|
110
104
|
}
|
|
111
|
-
|
|
105
|
+
interface MediaTitle {
|
|
106
|
+
english?: string;
|
|
107
|
+
romaji?: string;
|
|
108
|
+
native?: string;
|
|
109
|
+
userPreferred?: string;
|
|
110
|
+
}
|
|
111
|
+
interface Media {
|
|
112
|
+
id: number;
|
|
113
|
+
idMal?: number;
|
|
114
|
+
title: MediaTitle;
|
|
115
|
+
chapters?: number;
|
|
116
|
+
}
|
|
117
|
+
interface MediaEntry {
|
|
118
|
+
media: Media;
|
|
119
|
+
private: boolean;
|
|
120
|
+
progress: number;
|
|
121
|
+
status: string;
|
|
122
|
+
hiddenFromStatusLists: boolean;
|
|
123
|
+
}
|
|
124
|
+
interface List {
|
|
125
|
+
name: string;
|
|
126
|
+
entries: MediaEntry[];
|
|
127
|
+
}
|
|
128
|
+
interface MediaList {
|
|
129
|
+
id(id: number | string): string;
|
|
130
|
+
title: {
|
|
131
|
+
english?: string;
|
|
132
|
+
romaji?: string;
|
|
133
|
+
};
|
|
134
|
+
name: string;
|
|
135
|
+
entries: MediaListEntry[];
|
|
136
|
+
}
|
|
137
|
+
interface Myself {
|
|
138
|
+
data?: {
|
|
139
|
+
Viewer: {
|
|
140
|
+
id: number;
|
|
141
|
+
name: string;
|
|
142
|
+
siteUrl: string;
|
|
143
|
+
options: {
|
|
144
|
+
profileColor: string;
|
|
145
|
+
timezone: string;
|
|
146
|
+
activityMergeTime: string;
|
|
147
|
+
};
|
|
148
|
+
donatorTier: string;
|
|
149
|
+
donatorBadge: string;
|
|
150
|
+
unreadNotificationCount: number;
|
|
151
|
+
createdAt: number;
|
|
152
|
+
updatedAt: number;
|
|
153
|
+
statistics: {
|
|
154
|
+
anime: {
|
|
155
|
+
count: number;
|
|
156
|
+
meanScore: string;
|
|
157
|
+
minutesWatched: string;
|
|
158
|
+
episodesWatched: number;
|
|
159
|
+
};
|
|
160
|
+
manga: {
|
|
161
|
+
count: number;
|
|
162
|
+
meanScore: string;
|
|
163
|
+
chaptersRead: number;
|
|
164
|
+
volumesRead: number;
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
errors?: {
|
|
170
|
+
message: string;
|
|
171
|
+
}[];
|
|
172
|
+
}
|
|
173
|
+
interface DateMonthYear {
|
|
174
|
+
day?: number;
|
|
175
|
+
month?: number;
|
|
176
|
+
year?: number;
|
|
177
|
+
}
|
|
178
|
+
interface AnimeDetails {
|
|
179
|
+
data?: {
|
|
180
|
+
Media: {
|
|
181
|
+
id: number;
|
|
182
|
+
title: MediaTitle;
|
|
183
|
+
description: string;
|
|
184
|
+
duration: string;
|
|
185
|
+
startDate: DateMonthYear;
|
|
186
|
+
endDate: DateMonthYear;
|
|
187
|
+
countryOfOrigin: string;
|
|
188
|
+
isAdult: boolean;
|
|
189
|
+
status: string;
|
|
190
|
+
season: string;
|
|
191
|
+
format: string;
|
|
192
|
+
genres: [string];
|
|
193
|
+
siteUrl: string;
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
errors?: {
|
|
197
|
+
message: string;
|
|
198
|
+
}[];
|
|
199
|
+
}
|
|
200
|
+
interface MediaListEntry {
|
|
201
|
+
id?: number;
|
|
202
|
+
media: {
|
|
203
|
+
id?: number;
|
|
204
|
+
idMal?: number;
|
|
205
|
+
title?: MediaTitle;
|
|
206
|
+
episodes?: number;
|
|
207
|
+
siteUrl?: string;
|
|
208
|
+
chapters?: number;
|
|
209
|
+
};
|
|
210
|
+
progress?: number;
|
|
211
|
+
status?: string;
|
|
212
|
+
hiddenFromStatusLists?: boolean;
|
|
213
|
+
private?: boolean;
|
|
214
|
+
}
|
|
215
|
+
type UserActivitiesResponse = {
|
|
216
|
+
data?: {
|
|
217
|
+
Page: {
|
|
218
|
+
activities: {
|
|
219
|
+
status: string;
|
|
220
|
+
progress: number;
|
|
221
|
+
createdAt: number;
|
|
222
|
+
media: {
|
|
223
|
+
title: MediaTitle;
|
|
224
|
+
};
|
|
225
|
+
}[];
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
errors?: {
|
|
229
|
+
message: string;
|
|
230
|
+
}[];
|
|
231
|
+
};
|
|
232
|
+
type UserResponse = {
|
|
233
|
+
data?: {
|
|
234
|
+
User: {
|
|
235
|
+
id: number;
|
|
236
|
+
name: string;
|
|
237
|
+
siteUrl: string;
|
|
238
|
+
donatorTier: string;
|
|
239
|
+
donatorBadge: string;
|
|
240
|
+
createdAt: number;
|
|
241
|
+
updatedAt: number;
|
|
242
|
+
isBlocked: boolean;
|
|
243
|
+
isFollower: boolean;
|
|
244
|
+
isFollowing: boolean;
|
|
245
|
+
options: {
|
|
246
|
+
profileColor: string;
|
|
247
|
+
timezone: string;
|
|
248
|
+
};
|
|
249
|
+
statistics: {
|
|
250
|
+
anime: {
|
|
251
|
+
count: number;
|
|
252
|
+
episodesWatched: number;
|
|
253
|
+
minutesWatched: number;
|
|
254
|
+
};
|
|
255
|
+
manga: {
|
|
256
|
+
count: number;
|
|
257
|
+
chaptersRead: number;
|
|
258
|
+
volumesRead: number;
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
};
|
|
262
|
+
};
|
|
263
|
+
errors?: {
|
|
264
|
+
message: string;
|
|
265
|
+
}[];
|
|
266
|
+
};
|
|
267
|
+
type UserFollower = {
|
|
268
|
+
data?: {
|
|
269
|
+
Page: {
|
|
270
|
+
pageInfo: {
|
|
271
|
+
total: number;
|
|
272
|
+
perPage: number;
|
|
273
|
+
currentPage: number;
|
|
274
|
+
lastPage: number;
|
|
275
|
+
hasNextPage: boolean;
|
|
276
|
+
};
|
|
277
|
+
followers: {
|
|
278
|
+
id: number;
|
|
279
|
+
name: string;
|
|
280
|
+
avatar: {
|
|
281
|
+
large: string;
|
|
282
|
+
medium: string;
|
|
283
|
+
};
|
|
284
|
+
bannerImage: string;
|
|
285
|
+
}[];
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
errors?: {
|
|
289
|
+
message: string;
|
|
290
|
+
}[];
|
|
291
|
+
};
|
|
292
|
+
type UserFollowing = {
|
|
293
|
+
data?: {
|
|
294
|
+
Page: {
|
|
295
|
+
pageInfo: {
|
|
296
|
+
total: number;
|
|
297
|
+
perPage: number;
|
|
298
|
+
currentPage: number;
|
|
299
|
+
lastPage: number;
|
|
300
|
+
hasNextPage: boolean;
|
|
301
|
+
};
|
|
302
|
+
following: {
|
|
303
|
+
id: number;
|
|
304
|
+
name: string;
|
|
305
|
+
avatar: {
|
|
306
|
+
large: string;
|
|
307
|
+
medium: string;
|
|
308
|
+
};
|
|
309
|
+
bannerImage: string;
|
|
310
|
+
}[];
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
errors?: {
|
|
314
|
+
message: string;
|
|
315
|
+
}[];
|
|
316
|
+
};
|
|
317
|
+
type AnimeSearchResponse = {
|
|
318
|
+
data?: {
|
|
319
|
+
Page: {
|
|
320
|
+
media: {
|
|
321
|
+
id: number;
|
|
322
|
+
title: MediaTitle;
|
|
323
|
+
startDate: DateMonthYear;
|
|
324
|
+
episodes: number;
|
|
325
|
+
status: string;
|
|
326
|
+
description: string;
|
|
327
|
+
}[];
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
errors?: {
|
|
331
|
+
message: string;
|
|
332
|
+
}[];
|
|
333
|
+
};
|
|
334
|
+
export { AniListMediaStatus, AnimeDetails, AnimeList, AnimeSearchResponse, DateMonthYear, DeleteMangaResponse, List, MALAnimeStatus, MALAnimeXML, MALMangaStatus, MalIdToAnilistIdResponse, MediaEntry, MediaList, MediaListEntry, MediaTitle, MediaWithProgress, Myself, UserActivitiesResponse, UserFollower, UserFollowing, UserResponse, saveAnimeWithProgressResponse, };
|
package/bin/helpers/workers.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ declare function getTitle(title: {
|
|
|
6
6
|
romaji?: string;
|
|
7
7
|
}): string;
|
|
8
8
|
declare function formatDateObject(dateObj: {
|
|
9
|
-
day?:
|
|
10
|
-
month?:
|
|
11
|
-
year?:
|
|
9
|
+
day?: number;
|
|
10
|
+
month?: number;
|
|
11
|
+
year?: number;
|
|
12
12
|
} | null): string;
|
|
13
13
|
declare function getNextSeasonAndYear(): {
|
|
14
14
|
nextSeason: string;
|
|
@@ -35,4 +35,6 @@ declare function createMangaXML(malId: number, progress: number, status: MALMang
|
|
|
35
35
|
declare function createAnimeListXML(mediaWithProgress: MediaWithProgress[]): Promise<string>;
|
|
36
36
|
declare function createMangaListXML(mediaWithProgress: MediaWithProgress[]): Promise<string>;
|
|
37
37
|
declare function getCurrentPackageVersion(): string | null;
|
|
38
|
-
|
|
38
|
+
declare function timestampToTimeAgo(timestamp: number): string;
|
|
39
|
+
declare const anidbToanilistMapper: (romanjiName: string, year: number, englishName?: string) => Promise<number | null>;
|
|
40
|
+
export { anidbToanilistMapper, aniListEndpoint, createAnimeListXML, createAnimeXML, createMangaListXML, createMangaXML, formatDateObject, getCurrentPackageVersion, getDownloadFolderPath, getFormattedDate, getNextSeasonAndYear, getTitle, redirectUri, removeHtmlAndMarkdown, saveJSONasCSV, saveJSONasJSON, selectFile, timestampToTimeAgo, };
|
package/bin/helpers/workers.js
CHANGED
|
@@ -17,7 +17,9 @@ import { homedir } from "os";
|
|
|
17
17
|
import { join } from "path";
|
|
18
18
|
import process from "process";
|
|
19
19
|
import { Auth } from "./auth.js";
|
|
20
|
-
import {
|
|
20
|
+
import { fetcher } from "./fetcher.js";
|
|
21
|
+
import { animeSearchQuery } from "./queries.js";
|
|
22
|
+
import { MALAnimeStatus, MALMangaStatus, } from "./types.js";
|
|
21
23
|
const aniListEndpoint = `https://graphql.anilist.co`;
|
|
22
24
|
const redirectUri = "https://anilist.co/api/v2/oauth/pin";
|
|
23
25
|
function getTitle(title) {
|
|
@@ -287,4 +289,60 @@ function getCurrentPackageVersion() {
|
|
|
287
289
|
const version = packageJson.version;
|
|
288
290
|
return version || null;
|
|
289
291
|
}
|
|
290
|
-
|
|
292
|
+
function timestampToTimeAgo(timestamp) {
|
|
293
|
+
const now = Math.floor(Date.now() / 1000);
|
|
294
|
+
const elapsed = now - timestamp;
|
|
295
|
+
if (elapsed < 60) {
|
|
296
|
+
return `${elapsed} second${elapsed === 1 ? "" : "s"} ago`;
|
|
297
|
+
}
|
|
298
|
+
else if (elapsed < 3600) {
|
|
299
|
+
const minutes = Math.floor(elapsed / 60);
|
|
300
|
+
return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
301
|
+
}
|
|
302
|
+
else if (elapsed < 86400) {
|
|
303
|
+
const hours = Math.floor(elapsed / 3600);
|
|
304
|
+
return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
305
|
+
}
|
|
306
|
+
else if (elapsed < 2592000) {
|
|
307
|
+
const days = Math.floor(elapsed / 86400);
|
|
308
|
+
return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
309
|
+
}
|
|
310
|
+
else if (elapsed < 31536000) {
|
|
311
|
+
const months = Math.floor(elapsed / 2592000);
|
|
312
|
+
return `${months} month${months === 1 ? "" : "s"} ago`;
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
const years = Math.floor(elapsed / 31536000);
|
|
316
|
+
return `${years} year${years === 1 ? "" : "s"} ago`;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const anidbToanilistMapper = (romanjiName, year, englishName) => __awaiter(void 0, void 0, void 0, function* () {
|
|
320
|
+
const fetchAnime = (search) => __awaiter(void 0, void 0, void 0, function* () {
|
|
321
|
+
var _a;
|
|
322
|
+
try {
|
|
323
|
+
const response = yield fetcher(animeSearchQuery, {
|
|
324
|
+
search,
|
|
325
|
+
perPage: 50,
|
|
326
|
+
});
|
|
327
|
+
return ((_a = response.data) === null || _a === void 0 ? void 0 : _a.Page.media) || [];
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
console.error("Error fetching AniList data:", error);
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
// Search using romanjiName first
|
|
335
|
+
let results = yield fetchAnime(romanjiName);
|
|
336
|
+
// If no results, fallback to englishName
|
|
337
|
+
if (!results.length && englishName) {
|
|
338
|
+
results = yield fetchAnime(englishName);
|
|
339
|
+
}
|
|
340
|
+
// Match using year
|
|
341
|
+
for (const anime of results) {
|
|
342
|
+
if (anime.startDate.year === year) {
|
|
343
|
+
return anime.id;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return null;
|
|
347
|
+
});
|
|
348
|
+
export { anidbToanilistMapper, aniListEndpoint, createAnimeListXML, createAnimeXML, createMangaListXML, createMangaXML, formatDateObject, getCurrentPackageVersion, getDownloadFolderPath, getFormattedDate, getNextSeasonAndYear, getTitle, redirectUri, removeHtmlAndMarkdown, saveJSONasCSV, saveJSONasJSON, selectFile, timestampToTimeAgo, };
|
package/bin/index.js
CHANGED
|
@@ -32,7 +32,7 @@ cli
|
|
|
32
32
|
}
|
|
33
33
|
}));
|
|
34
34
|
cli
|
|
35
|
-
.command("
|
|
35
|
+
.command("whoami")
|
|
36
36
|
.description("Get details of the logged in user")
|
|
37
37
|
.action(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
38
|
yield Auth.Myself();
|
|
@@ -88,7 +88,7 @@ cli
|
|
|
88
88
|
.description("Delete entire collections of anime or manga")
|
|
89
89
|
.option("-a, --anime", "For anime list of authenticated user", false)
|
|
90
90
|
.option("-m, --manga", "For manga list of authenticated user", false)
|
|
91
|
-
.option("-
|
|
91
|
+
.option("-s, --activity", "For activity of authenticated user", false)
|
|
92
92
|
.action((_a) => __awaiter(void 0, [_a], void 0, function* ({ anime, manga, activity }) {
|
|
93
93
|
const selectedOptions = [anime, manga, activity].filter(Boolean).length;
|
|
94
94
|
if (selectedOptions === 0) {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@irfanshadikrishad/anilist",
|
|
3
3
|
"description": "Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts",
|
|
4
4
|
"author": "Irfan Shadik Rishad",
|
|
5
|
-
"version": "1.2.
|
|
5
|
+
"version": "1.2.5",
|
|
6
6
|
"main": "./bin/index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"types": "./bin/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"format:check": "prettier . --check",
|
|
20
20
|
"lint": "eslint ./dist",
|
|
21
21
|
"lint:fix": "eslint ./dist --fix",
|
|
22
|
-
"all": "npm run lint && npm run lint:fix && npm run format",
|
|
22
|
+
"all": "npm run lint && npm run lint:fix && npm run format && npm test",
|
|
23
23
|
"test": "jest ./tests"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
@@ -54,10 +54,12 @@
|
|
|
54
54
|
},
|
|
55
55
|
"license": "MPL-2.0",
|
|
56
56
|
"devDependencies": {
|
|
57
|
+
"@babel/preset-env": "^7.26.0",
|
|
57
58
|
"@eslint/js": "^9.17.0",
|
|
58
59
|
"@types/jest": "^29.5.14",
|
|
59
60
|
"@types/json2csv": "^5.0.7",
|
|
60
|
-
"@types/node": "^22.10.
|
|
61
|
+
"@types/node": "^22.10.5",
|
|
62
|
+
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
|
61
63
|
"eslint": "^9.17.0",
|
|
62
64
|
"globals": "^15.14.0",
|
|
63
65
|
"jest": "^29.7.0",
|
|
@@ -65,15 +67,14 @@
|
|
|
65
67
|
"prettier-plugin-organize-imports": "^4.1.0",
|
|
66
68
|
"ts-jest": "^29.2.5",
|
|
67
69
|
"ts-node": "^10.9.2",
|
|
68
|
-
"typescript": "^5.7.2"
|
|
69
|
-
"@babel/preset-env": "^7.26.0",
|
|
70
|
-
"@typescript-eslint/eslint-plugin": "^8.19.0"
|
|
70
|
+
"typescript": "^5.7.2"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"commander": "^
|
|
73
|
+
"commander": "^13.0.0",
|
|
74
74
|
"fast-xml-parser": "^4.5.1",
|
|
75
75
|
"inquirer": "^12.3.0",
|
|
76
76
|
"json2csv": "^6.0.0-alpha.2",
|
|
77
|
+
"jsonrepair": "^3.11.2",
|
|
77
78
|
"node-fetch": "^3.3.2",
|
|
78
79
|
"open": "^10.1.0"
|
|
79
80
|
}
|