@irfanshadikrishad/anilist 1.2.7 → 1.2.9
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/bin/helpers/lists.js +38 -2
- package/bin/helpers/validation.d.ts +29 -0
- package/bin/helpers/validation.js +117 -0
- package/bin/helpers/workers.js +9 -4
- package/package.json +4 -2
package/bin/helpers/lists.js
CHANGED
|
@@ -18,15 +18,23 @@ import { fetcher } from "./fetcher.js";
|
|
|
18
18
|
import { addAnimeToListMutation, addMangaToListMutation, saveAnimeWithProgressMutation, saveMangaWithProgressMutation, } from "./mutations.js";
|
|
19
19
|
import { animeDetailsQuery, animeSearchQuery, currentUserAnimeList, currentUserMangaList, malIdToAnilistAnimeId, malIdToAnilistMangaId, mangaSearchQuery, popularQuery, trendingQuery, upcomingAnimesQuery, userActivityQuery, userFollowersQuery, userFollowingQuery, userQuery, } from "./queries.js";
|
|
20
20
|
import { AniListMediaStatus, } from "./types.js";
|
|
21
|
+
import { Validate } from "./validation.js";
|
|
21
22
|
import { anidbToanilistMapper, createAnimeListXML, createMangaListXML, formatDateObject, getDownloadFolderPath, getFormattedDate, getNextSeasonAndYear, getTitle, removeHtmlAndMarkdown, saveJSONasCSV, saveJSONasJSON, selectFile, timestampToTimeAgo, } from "./workers.js";
|
|
22
23
|
class AniList {
|
|
23
24
|
static importAnime() {
|
|
24
25
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
26
|
try {
|
|
26
27
|
const filename = yield selectFile(".json");
|
|
28
|
+
if (!filename) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
27
31
|
const filePath = join(getDownloadFolderPath(), filename);
|
|
28
32
|
const fileContent = yield readFile(filePath, "utf8");
|
|
29
33
|
const importedData = JSON.parse(fileContent);
|
|
34
|
+
if (!Validate.Import_JSON(importedData)) {
|
|
35
|
+
console.error(`\nInvalid JSON file.`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
30
38
|
let count = 0;
|
|
31
39
|
const batchSize = 1; // Number of requests in each batch
|
|
32
40
|
const delay = 1100; // delay to avoid rate-limiting
|
|
@@ -70,9 +78,16 @@ class AniList {
|
|
|
70
78
|
return __awaiter(this, void 0, void 0, function* () {
|
|
71
79
|
try {
|
|
72
80
|
const filename = yield selectFile(".json");
|
|
81
|
+
if (!filename) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
73
84
|
const filePath = join(getDownloadFolderPath(), filename);
|
|
74
85
|
const fileContent = yield readFile(filePath, "utf8");
|
|
75
86
|
const importedData = JSON.parse(fileContent);
|
|
87
|
+
if (!Validate.Import_JSON(importedData)) {
|
|
88
|
+
console.error(`\nInvalid JSON file.`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
76
91
|
let count = 0;
|
|
77
92
|
const batchSize = 1; // Adjust batch size as per rate-limit constraints
|
|
78
93
|
const delay = 1100; // 2 seconds delay to avoid rate-limit
|
|
@@ -813,8 +828,15 @@ class MyAnimeList {
|
|
|
813
828
|
var _a, _b, _c, _d, _e;
|
|
814
829
|
try {
|
|
815
830
|
const filename = yield selectFile(".xml");
|
|
831
|
+
if (!filename) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
816
834
|
const filePath = join(getDownloadFolderPath(), filename);
|
|
817
835
|
const fileContent = yield readFile(filePath, "utf8");
|
|
836
|
+
if (!(yield Validate.Import_AnimeXML(fileContent))) {
|
|
837
|
+
console.error(`\nInvalid XML file.`);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
818
840
|
const parser = new XMLParser();
|
|
819
841
|
if (fileContent) {
|
|
820
842
|
const XMLObject = parser.parse(fileContent);
|
|
@@ -878,8 +900,15 @@ class MyAnimeList {
|
|
|
878
900
|
var _a, _b, _c, _d, _e;
|
|
879
901
|
try {
|
|
880
902
|
const filename = yield selectFile(".xml");
|
|
903
|
+
if (!filename) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
881
906
|
const filePath = join(getDownloadFolderPath(), filename);
|
|
882
907
|
const fileContent = yield readFile(filePath, "utf8");
|
|
908
|
+
if (!(yield Validate.Import_MangaXML(fileContent))) {
|
|
909
|
+
console.error(`\nInvalid XML file.`);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
883
912
|
const parser = new XMLParser();
|
|
884
913
|
if (fileContent) {
|
|
885
914
|
const XMLObject = parser.parse(fileContent);
|
|
@@ -1023,10 +1052,17 @@ class AniDB {
|
|
|
1023
1052
|
var _a, _b;
|
|
1024
1053
|
try {
|
|
1025
1054
|
const filename = yield selectFile(".json");
|
|
1055
|
+
if (!filename) {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1026
1058
|
const filePath = join(getDownloadFolderPath(), filename);
|
|
1027
1059
|
const fileContent = yield readFile(filePath, "utf8");
|
|
1028
1060
|
const js0n_repaired = jsonrepair(fileContent);
|
|
1029
|
-
if (
|
|
1061
|
+
if (!(yield Validate.Import_AniDBJSONLarge(js0n_repaired))) {
|
|
1062
|
+
console.error(`\nInvalid JSON Large file.`);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
if (js0n_repaired) {
|
|
1030
1066
|
const obj3ct = yield JSON.parse(js0n_repaired);
|
|
1031
1067
|
const animeList = obj3ct === null || obj3ct === void 0 ? void 0 : obj3ct.anime;
|
|
1032
1068
|
if ((animeList === null || animeList === void 0 ? void 0 : animeList.length) > 0) {
|
|
@@ -1068,7 +1104,7 @@ class AniDB {
|
|
|
1068
1104
|
const entryId = (_b = (_a = saveResponse === null || saveResponse === void 0 ? void 0 : saveResponse.data) === null || _a === void 0 ? void 0 : _a.SaveMediaListEntry) === null || _b === void 0 ? void 0 : _b.id;
|
|
1069
1105
|
if (entryId) {
|
|
1070
1106
|
count++;
|
|
1071
|
-
console.log(`[${count}]\t${entryId} ✅\t${anidbId}\t${anilistId}\t(${ownEpisodes}/${totalEpisodes})\t${status}
|
|
1107
|
+
console.log(`[${count}]\t${entryId} ✅\t${anidbId}\t${anilistId}\t(${ownEpisodes}/${totalEpisodes})\t${status}–>${getStatus(status, ownEpisodes)}`);
|
|
1072
1108
|
}
|
|
1073
1109
|
// Rate limit each API call to avoid server overload
|
|
1074
1110
|
// await new Promise((resolve) => setTimeout(resolve, 1100))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
declare class Validate {
|
|
2
|
+
/**
|
|
3
|
+
* Validate importable JSON file
|
|
4
|
+
* @param data string
|
|
5
|
+
* @returns boolean
|
|
6
|
+
*/
|
|
7
|
+
static Import_JSON(data: {
|
|
8
|
+
id: number;
|
|
9
|
+
}[]): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Validate if MyAnimeList Anime XML file is valid or not
|
|
12
|
+
* @param xmlData string
|
|
13
|
+
* @returns boolean
|
|
14
|
+
*/
|
|
15
|
+
static Import_AnimeXML(xmlData: string): Promise<boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* Validate if MyAnimeList Anime XML file is valid or not
|
|
18
|
+
* @param xmlData string
|
|
19
|
+
* @returns boolean
|
|
20
|
+
*/
|
|
21
|
+
static Import_MangaXML(xmlData: string): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Validate AniDB json-large file
|
|
24
|
+
* @param file string of anidb json-large
|
|
25
|
+
* @returns boolean
|
|
26
|
+
*/
|
|
27
|
+
static Import_AniDBJSONLarge(file: string): Promise<boolean>;
|
|
28
|
+
}
|
|
29
|
+
export { Validate };
|
|
@@ -0,0 +1,117 @@
|
|
|
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 { parseStringPromise } from "xml2js";
|
|
11
|
+
class Validate {
|
|
12
|
+
/**
|
|
13
|
+
* Validate importable JSON file
|
|
14
|
+
* @param data string
|
|
15
|
+
* @returns boolean
|
|
16
|
+
*/
|
|
17
|
+
static Import_JSON(data) {
|
|
18
|
+
return (Array.isArray(data) &&
|
|
19
|
+
data.every((item) => typeof item === "object" && item !== null && "id" in item));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate if MyAnimeList Anime XML file is valid or not
|
|
23
|
+
* @param xmlData string
|
|
24
|
+
* @returns boolean
|
|
25
|
+
*/
|
|
26
|
+
static Import_AnimeXML(xmlData) {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
try {
|
|
29
|
+
const result = yield parseStringPromise(xmlData, { explicitArray: false });
|
|
30
|
+
if (!result || !result.myanimelist) {
|
|
31
|
+
console.error("Invalid XML structure: Missing 'myanimelist' root element.");
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const animeList = result.myanimelist.anime;
|
|
35
|
+
if (!animeList) {
|
|
36
|
+
console.error("Invalid XML structure: Missing 'anime' elements.");
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const animeArray = Array.isArray(animeList) ? animeList : [animeList];
|
|
40
|
+
const isValid = animeArray.every((anime) => {
|
|
41
|
+
const isValidId = anime.series_animedb_id && !isNaN(Number(anime.series_animedb_id));
|
|
42
|
+
const hasRequiredFields = anime.series_title && anime.my_status;
|
|
43
|
+
return isValidId && hasRequiredFields;
|
|
44
|
+
});
|
|
45
|
+
if (!isValid) {
|
|
46
|
+
console.error("Validation failed: Some anime entries are missing required fields or have invalid IDs.");
|
|
47
|
+
}
|
|
48
|
+
return isValid;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.error("Error parsing or validating XML:", error);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Validate if MyAnimeList Anime XML file is valid or not
|
|
58
|
+
* @param xmlData string
|
|
59
|
+
* @returns boolean
|
|
60
|
+
*/
|
|
61
|
+
static Import_MangaXML(xmlData) {
|
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
try {
|
|
64
|
+
const result = yield parseStringPromise(xmlData, { explicitArray: false });
|
|
65
|
+
if (!result || !result.myanimelist) {
|
|
66
|
+
console.error("Invalid XML structure: Missing 'myanimelist' root element.");
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const mangaList = result.myanimelist.manga;
|
|
70
|
+
if (!mangaList) {
|
|
71
|
+
console.error("Invalid XML structure: Missing 'manga' elements.");
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const mangaArray = Array.isArray(mangaList) ? mangaList : [mangaList];
|
|
75
|
+
const isValid = mangaArray.every((manga) => {
|
|
76
|
+
const isValidId = manga.manga_mangadb_id && !isNaN(Number(manga.manga_mangadb_id));
|
|
77
|
+
const hasRequiredFields = manga.manga_title && manga.my_status;
|
|
78
|
+
return isValidId && hasRequiredFields;
|
|
79
|
+
});
|
|
80
|
+
if (!isValid) {
|
|
81
|
+
console.error("Validation failed: Some manga entries are missing required fields or have invalid IDs.");
|
|
82
|
+
}
|
|
83
|
+
return isValid;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error("Error parsing or validating XML:", error);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Validate AniDB json-large file
|
|
93
|
+
* @param file string of anidb json-large
|
|
94
|
+
* @returns boolean
|
|
95
|
+
*/
|
|
96
|
+
static Import_AniDBJSONLarge(file) {
|
|
97
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
+
try {
|
|
99
|
+
if (!(file === null || file === void 0 ? void 0 : file.trim())) {
|
|
100
|
+
console.error("File content is empty or invalid.");
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const obj3ct = JSON.parse(file);
|
|
104
|
+
if (!obj3ct || !Array.isArray(obj3ct.anime)) {
|
|
105
|
+
console.error("Invalid JSON structure: Missing or malformed 'anime' array.");
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error("Failed to parse JSON file:", error);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export { Validate };
|
package/bin/helpers/workers.js
CHANGED
|
@@ -162,12 +162,13 @@ function selectFile(fileType) {
|
|
|
162
162
|
return answers.fileName;
|
|
163
163
|
}
|
|
164
164
|
else {
|
|
165
|
-
|
|
165
|
+
console.error(`\nNo importable ${fileType} file(s) found in download folder.`);
|
|
166
|
+
return null;
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
catch (error) {
|
|
169
170
|
console.error("\nError selecting file:", error);
|
|
170
|
-
|
|
171
|
+
return null;
|
|
171
172
|
}
|
|
172
173
|
});
|
|
173
174
|
}
|
|
@@ -226,7 +227,9 @@ function createAnimeListXML(mediaWithProgress) {
|
|
|
226
227
|
PAUSED: MALAnimeStatus.ON_HOLD,
|
|
227
228
|
DROPPED: MALAnimeStatus.DROPPED,
|
|
228
229
|
};
|
|
229
|
-
|
|
230
|
+
// Filter out anime without malId
|
|
231
|
+
const filteredMedia = mediaWithProgress.filter((anime) => anime.malId);
|
|
232
|
+
const xmlEntries = filteredMedia.map((anime) => {
|
|
230
233
|
const malId = anime.malId;
|
|
231
234
|
const progress = anime.progress;
|
|
232
235
|
const episodes = anime.episodes;
|
|
@@ -259,7 +262,9 @@ function createMangaListXML(mediaWithProgress) {
|
|
|
259
262
|
PAUSED: MALMangaStatus.ON_HOLD,
|
|
260
263
|
DROPPED: MALMangaStatus.DROPPED,
|
|
261
264
|
};
|
|
262
|
-
|
|
265
|
+
// Filter out manga without malId
|
|
266
|
+
const filteredMedia = mediaWithProgress.filter((manga) => manga.malId);
|
|
267
|
+
const xmlEntries = filteredMedia.map((manga) => {
|
|
263
268
|
const malId = manga.malId;
|
|
264
269
|
const progress = manga.progress;
|
|
265
270
|
const chapters = manga.chapters;
|
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.9",
|
|
6
6
|
"main": "./bin/index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"types": "./bin/index.d.ts",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"@types/jest": "^29.5.14",
|
|
60
60
|
"@types/json2csv": "^5.0.7",
|
|
61
61
|
"@types/node": "^22.10.5",
|
|
62
|
+
"@types/xml2js": "^0.4.14",
|
|
62
63
|
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
|
63
64
|
"eslint": "^9.17.0",
|
|
64
65
|
"globals": "^15.14.0",
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
"jsonrepair": "^3.11.2",
|
|
78
79
|
"node-fetch": "^3.3.2",
|
|
79
80
|
"open": "^10.1.0",
|
|
80
|
-
"tiny-spinner": "^2.0.4"
|
|
81
|
+
"tiny-spinner": "^2.0.4",
|
|
82
|
+
"xml2js": "^0.6.2"
|
|
81
83
|
}
|
|
82
84
|
}
|