@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.
@@ -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 (fileContent) {
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}→${getStatus(status, ownEpisodes)}`);
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 };
@@ -162,12 +162,13 @@ function selectFile(fileType) {
162
162
  return answers.fileName;
163
163
  }
164
164
  else {
165
- throw new Error(`\nNo importable ${fileType} file(s) found in download folder.`);
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
- throw error;
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
- const xmlEntries = mediaWithProgress.map((anime) => {
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
- const xmlEntries = mediaWithProgress.map((manga) => {
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.7",
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
  }