@irfanshadikrishad/anilist 1.0.5 → 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 ADDED
@@ -0,0 +1,10 @@
1
+ #### Changelog
2
+
3
+ #### v1.0.6
4
+
5
+ - Users can import/export the anime or manga list
6
+
7
+ #### v1.0.4 - v1.0.5
8
+
9
+ - Better error handling
10
+ - Write command for writing status
@@ -0,0 +1,43 @@
1
+ #### Contributor Covenant Code of Conduct
2
+
3
+ #### Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ #### Our Standards
8
+
9
+ Examples of behavior that contributes to creating a positive environment include:
10
+
11
+ - Using welcoming and inclusive language
12
+ - Being respectful of differing viewpoints and experiences
13
+ - Gracefully accepting constructive criticism
14
+ - Focusing on what is best for the community
15
+ - Showing empathy towards other community members
16
+
17
+ Examples of unacceptable behavior by participants include:
18
+
19
+ - The use of sexualized language or imagery and unwelcome sexual attention or advances
20
+ - Trolling, insulting/derogatory comments, and personal or political attacks
21
+ - Public or private harassment
22
+ - Publishing others' private information, such as a physical or electronic address, without explicit permission
23
+ - Other conduct which could reasonably be considered inappropriate in a professional setting
24
+
25
+ #### Our Responsibilities
26
+
27
+ Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28
+
29
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that do not align with this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project.
30
+
31
+ #### Scope
32
+
33
+ This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
34
+
35
+ #### Enforcement
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [discord](https://discordid.netlify.app/?id=1119275731021725707). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances.
38
+
39
+ The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
40
+
41
+ #### Attribution
42
+
43
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 1.4.
@@ -0,0 +1,75 @@
1
+ #### Contribution Guidelines
2
+
3
+ Thank you for considering contributing to **@irfanshadikrishad/anilist**! We welcome contributions from everyone and appreciate your interest in improving the project. Please follow the guidelines below to ensure a smooth contribution process.
4
+
5
+ #### How to Contribute?
6
+
7
+ #### 1. Fork the Repository
8
+
9
+ 1. Click the "Fork" button in the top right corner of the repository page.
10
+ 2. Clone your forked repository to your local machine:
11
+ ```bash
12
+ git clone https://github.com/your-username/anilist.git
13
+ ```
14
+ 3. Navigate to the project directory:
15
+ ```bash
16
+ cd anilist
17
+ ```
18
+ 4. Set Up the Development Environment
19
+
20
+ 1. Install the required dependencies:
21
+
22
+ ```bash
23
+ npm install
24
+ ```
25
+
26
+ 5. Create a New Branch
27
+ Create a new branch for your feature or bug fix:
28
+ ```bash
29
+ git checkout -b feature/my-new-feature
30
+ ```
31
+ or
32
+ ```bash
33
+ git checkout -b fix/my-bug-fix
34
+ ```
35
+ 6. Make Your Changes
36
+ Make your changes in the codebase. Ensure your code adheres to the project's coding standards and style guide.
37
+ 7. Test Your Changes
38
+ Run the tests to ensure everything works as expected:
39
+ ```bash
40
+ npm test
41
+ ```
42
+ Add new tests if you’re introducing new functionality.
43
+ 8. Commit Your Changes
44
+ Commit your changes with a clear and descriptive commit message:
45
+ ```bash
46
+ git commit -m "Add new feature: [describe the feature]"
47
+ ```
48
+ or for bug fixes:
49
+ ```bash
50
+ git commit -m "Fix: [describe the bug fix]"
51
+ ```
52
+ 9. Push to Your Fork
53
+ Push your changes to your forked repository:
54
+ ```bash
55
+ git push origin feature/my-new-feature
56
+ ```
57
+ 10. Create a Pull Request
58
+ 1. Go to the original repository where you want to submit your changes.
59
+ 2. Click on the "New Pull Request" button.
60
+ 3. Select your branch from the dropdown and submit the pull request.
61
+ 4. Provide a clear description of the changes you made and why they are needed.
62
+
63
+ #### Code of Conduct
64
+
65
+ By participating in this project, you agree to abide by our [Code of Conduct](CODE_OF_CONDUCT.md). We expect everyone to be respectful and considerate in their interactions.
66
+
67
+ #### Additional Notes
68
+
69
+ - Issues: If you encounter any issues or have feature requests, please open an issue in the repository.
70
+ - Documentation: Ensure to update the documentation for any new features or changes you introduce.
71
+ - Formatting: Use a linter (like ESLint) to maintain code quality. Run `npm run lint` to check your code.
72
+
73
+ #### Thank You!
74
+
75
+ Your contributions help make @irfanshadikrishad/anilist a better tool for everyone. We appreciate your time and effort in making this project great!
package/README.md CHANGED
@@ -54,7 +54,9 @@ here `<client-id>` and `<client-secret>` should be replaced by the ones that you
54
54
  | **`upcoming`** <br> _(alias:`up`)_ | `-c (default: 10)` | Fetch upcoming anime (default count is 10) |
55
55
  | **`anime`** | `<anime-id>` | Get anime details by Anime Id |
56
56
  | **`search`** <br> _(alias:`srch`/`find`)_ | `<query>` <br> `-a, --anime` <br> `-m, --manga` <br> `-c (default: 10)` | Get anime/manga search results |
57
- | **`status`** <br> _(alias: `write`/`post`)_ | `<status>` (text/markdown/html but wrap it with quotation mark). | Write a status... |
57
+ | **`status`** <br> _(alias: `write`/`post`)_ | `<status>` | Write a status... (text/markdown/html) |
58
+ | **`export`** <br> _(alias: `exp`)_ | `-a, --anime` <br> `-m, --manga` | Export anime or manga list |
59
+ | **`import`** <br> _(alias: `imp`)_ | `-a, --anime` <br> `-m, --manga` | Import anime or manga list |
58
60
 
59
61
  #### Command Breakdown:
60
62
 
@@ -197,8 +199,34 @@ anilist write <status>
197
199
  - `<status>` : This is what you want to write, It can be HTML, Markdown and/or Text. But wrap it with quotation mark (") else it might get cut-off.
198
200
  - **Description**: Get anime/manga search results
199
201
 
202
+ #### `export` _(alias: `exp`)_:
203
+
204
+ ```bash
205
+ anilist export -a
206
+ ```
207
+
208
+ - **Options**:
209
+ - `-a, --anime`: To export anime list.
210
+ - `-m, --manga`: To export manga list.
211
+ - **Description**: Export anime or manga list
212
+
213
+ #### `import` _(alias: `imp`)_:
214
+
215
+ ```bash
216
+ anilist import -m
217
+ ```
218
+
219
+ - **Options**:
220
+ - `-a, --anime`: To import anime list.
221
+ - `-m, --manga`: To import manga list.
222
+ - **Description**: Import anime or manga list
223
+
200
224
  #### Security
201
225
 
202
226
  Since you are creating your own API client for login no else else can get your credentials and the generated access token will be stored in your own system. So, As long as you don't share your device (in case you do, just logout) you are safe.
203
227
 
228
+ #### Contribution
229
+
230
+ Want to contribute to the project? Check out complete guideline [here](CONTRIBUTING.md).
231
+
204
232
  #### **_Thanks for visiting 💙_**
@@ -158,8 +158,9 @@ function logoutUser() {
158
158
  return __awaiter(this, void 0, void 0, function* () {
159
159
  if (fs.existsSync(save_path)) {
160
160
  try {
161
+ const username = yield currentUsersName();
161
162
  fs.unlinkSync(save_path);
162
- console.log("\nLogout successful.");
163
+ console.log(`\nLogout successful. See you soon, ${username}.`);
163
164
  }
164
165
  catch (error) {
165
166
  console.error("\nError logging out:", error);
@@ -4,4 +4,8 @@ declare function getAnimeSearchResults(search: string, count: number): Promise<v
4
4
  declare function getMangaSearchResults(search: string, count: number): Promise<void>;
5
5
  declare function deleteUserActivities(): Promise<void>;
6
6
  declare function writeTextActivity(status: string): Promise<void>;
7
- export { getUserInfoByUsername, getAnimeDetailsByID, getAnimeSearchResults, getMangaSearchResults, deleteUserActivities, writeTextActivity, };
7
+ declare function exportAnimeList(): Promise<void>;
8
+ declare function exportMangaList(): Promise<void>;
9
+ declare function importAnimeList(): Promise<void>;
10
+ declare function importMangaList(): Promise<void>;
11
+ export { getUserInfoByUsername, getAnimeDetailsByID, getAnimeSearchResults, getMangaSearchResults, deleteUserActivities, writeTextActivity, exportAnimeList, exportMangaList, importAnimeList, importMangaList, };
@@ -8,9 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import fetch from "node-fetch";
11
- import { activityAllQuery, activityAnimeListQuery, activityMangaListQuery, activityMediaList, activityMessageQuery, activityTextQuery, animeDetailsQuery, animeSearchQuery, mangaSearchQuery, userActivityQuery, userQuery, } from "./queries.js";
11
+ import { activityAllQuery, activityAnimeListQuery, activityMangaListQuery, activityMediaList, activityMessageQuery, activityTextQuery, animeDetailsQuery, animeSearchQuery, currentUserAnimeList, currentUserMangaList, mangaSearchQuery, userActivityQuery, userQuery, } from "./queries.js";
12
12
  import { currentUsersId, isLoggedIn, retriveAccessToken } from "./auth.js";
13
- import { aniListEndpoint, formatDateObject, getTitle, removeHtmlAndMarkdown, } from "./workers.js";
13
+ import { aniListEndpoint, formatDateObject, getTitle, importAnimeListFromExportedJSON, importMangaListFromExportedJSON, removeHtmlAndMarkdown, saveJSONasCSV, saveJSONasJSON, } from "./workers.js";
14
14
  import { fetcher } from "./fetcher.js";
15
15
  import inquirer from "inquirer";
16
16
  import { addAnimeToListMutation, addMangaToListMutation, deleteActivityMutation, saveTextActivityMutation, } from "./mutations.js";
@@ -314,4 +314,161 @@ function writeTextActivity(status) {
314
314
  }
315
315
  });
316
316
  }
317
- export { getUserInfoByUsername, getAnimeDetailsByID, getAnimeSearchResults, getMangaSearchResults, deleteUserActivities, writeTextActivity, };
317
+ function exportAnimeList() {
318
+ return __awaiter(this, void 0, void 0, function* () {
319
+ var _a, _b, _c;
320
+ if (yield isLoggedIn()) {
321
+ const { exportType } = yield inquirer.prompt([
322
+ {
323
+ type: "list",
324
+ name: "exportType",
325
+ message: "Choose export type:",
326
+ choices: [
327
+ { name: "CSV", value: 1 },
328
+ { name: "JSON", value: 2 },
329
+ ],
330
+ pageSize: 10,
331
+ },
332
+ ]);
333
+ const animeList = yield fetcher(currentUserAnimeList, {
334
+ id: yield currentUsersId(),
335
+ });
336
+ if (animeList) {
337
+ const lists = (_c = (_b = (_a = animeList === null || animeList === void 0 ? void 0 : animeList.data) === null || _a === void 0 ? void 0 : _a.MediaListCollection) === null || _b === void 0 ? void 0 : _b.lists) !== null && _c !== void 0 ? _c : [];
338
+ const mediaWithProgress = lists.flatMap((list) => list.entries.map((entry) => {
339
+ var _a, _b, _c, _d, _e;
340
+ return ({
341
+ id: (_a = entry === null || entry === void 0 ? void 0 : entry.media) === null || _a === void 0 ? void 0 : _a.id,
342
+ title: exportType === 1
343
+ ? getTitle((_b = entry === null || entry === void 0 ? void 0 : entry.media) === null || _b === void 0 ? void 0 : _b.title)
344
+ : (_c = entry === null || entry === void 0 ? void 0 : entry.media) === null || _c === void 0 ? void 0 : _c.title,
345
+ episodes: (_d = entry === null || entry === void 0 ? void 0 : entry.media) === null || _d === void 0 ? void 0 : _d.episodes,
346
+ siteUrl: (_e = entry === null || entry === void 0 ? void 0 : entry.media) === null || _e === void 0 ? void 0 : _e.siteUrl,
347
+ progress: entry.progress,
348
+ status: entry === null || entry === void 0 ? void 0 : entry.status,
349
+ hiddenFromStatusLists: entry.hiddenFromStatusLists,
350
+ });
351
+ }));
352
+ switch (exportType) {
353
+ case 1:
354
+ yield saveJSONasCSV(mediaWithProgress, "anime");
355
+ break;
356
+ case 2:
357
+ yield saveJSONasJSON(mediaWithProgress, "anime");
358
+ break;
359
+ }
360
+ }
361
+ else {
362
+ console.error(`\nNo anime(s) found in your lists.`);
363
+ }
364
+ }
365
+ else {
366
+ console.error(`\nMust login to use this feature.`);
367
+ }
368
+ });
369
+ }
370
+ function exportMangaList() {
371
+ return __awaiter(this, void 0, void 0, function* () {
372
+ var _a, _b;
373
+ if (yield isLoggedIn()) {
374
+ const mangaLists = yield fetcher(currentUserMangaList, {
375
+ id: yield currentUsersId(),
376
+ });
377
+ if (mangaLists) {
378
+ const lists = ((_b = (_a = mangaLists === null || mangaLists === void 0 ? void 0 : mangaLists.data) === null || _a === void 0 ? void 0 : _a.MediaListCollection) === null || _b === void 0 ? void 0 : _b.lists) || [];
379
+ if (lists.length > 0) {
380
+ const { exportType } = yield inquirer.prompt([
381
+ {
382
+ type: "list",
383
+ name: "exportType",
384
+ message: "Choose export type:",
385
+ choices: [
386
+ { name: "CSV", value: 1 },
387
+ { name: "JSON", value: 2 },
388
+ ],
389
+ pageSize: 10,
390
+ },
391
+ ]);
392
+ const mediaWithProgress = lists.flatMap((list) => list.entries.map((entry) => {
393
+ var _a, _b, _c;
394
+ return ({
395
+ id: (_a = entry === null || entry === void 0 ? void 0 : entry.media) === null || _a === void 0 ? void 0 : _a.id,
396
+ title: exportType === 1
397
+ ? getTitle((_b = entry === null || entry === void 0 ? void 0 : entry.media) === null || _b === void 0 ? void 0 : _b.title)
398
+ : (_c = entry === null || entry === void 0 ? void 0 : entry.media) === null || _c === void 0 ? void 0 : _c.title,
399
+ private: entry.private,
400
+ chapters: entry.media.chapters,
401
+ progress: entry.progress,
402
+ status: entry === null || entry === void 0 ? void 0 : entry.status,
403
+ hiddenFromStatusLists: entry.hiddenFromStatusLists,
404
+ });
405
+ }));
406
+ switch (exportType) {
407
+ case 1:
408
+ yield saveJSONasCSV(mediaWithProgress, "manga");
409
+ break;
410
+ case 2:
411
+ yield saveJSONasJSON(mediaWithProgress, "manga");
412
+ break;
413
+ }
414
+ }
415
+ else {
416
+ console.log(`\nList seems to be empty.`);
417
+ }
418
+ }
419
+ else {
420
+ console.error(`\nCould not get manga list.`);
421
+ }
422
+ }
423
+ else {
424
+ console.error(`\nPlease login to use this feature.`);
425
+ }
426
+ });
427
+ }
428
+ function importAnimeList() {
429
+ return __awaiter(this, void 0, void 0, function* () {
430
+ try {
431
+ const { source } = yield inquirer.prompt([
432
+ {
433
+ type: "list",
434
+ name: "source",
435
+ message: "Select a source:",
436
+ choices: [{ name: "Exported JSON file.", value: 1 }],
437
+ pageSize: 10,
438
+ },
439
+ ]);
440
+ switch (source) {
441
+ case 1:
442
+ yield importAnimeListFromExportedJSON();
443
+ break;
444
+ }
445
+ }
446
+ catch (error) {
447
+ console.error(`\n${error.message}`);
448
+ }
449
+ });
450
+ }
451
+ function importMangaList() {
452
+ return __awaiter(this, void 0, void 0, function* () {
453
+ try {
454
+ const { source } = yield inquirer.prompt([
455
+ {
456
+ type: "list",
457
+ name: "source",
458
+ message: "Select a source:",
459
+ choices: [{ name: "Exported JSON file.", value: 1 }],
460
+ pageSize: 10,
461
+ },
462
+ ]);
463
+ switch (source) {
464
+ case 1:
465
+ yield importMangaListFromExportedJSON();
466
+ break;
467
+ }
468
+ }
469
+ catch (error) {
470
+ console.error(`\n${error.message}`);
471
+ }
472
+ });
473
+ }
474
+ export { getUserInfoByUsername, getAnimeDetailsByID, getAnimeSearchResults, getMangaSearchResults, deleteUserActivities, writeTextActivity, exportAnimeList, exportMangaList, importAnimeList, importMangaList, };
@@ -2,4 +2,6 @@ declare const addAnimeToListMutation = "\nmutation($mediaId: Int, $status: Media
2
2
  declare const addMangaToListMutation = "\n mutation($mediaId: Int, $status: MediaListStatus) {\n SaveMediaListEntry(mediaId: $mediaId, status: $status) {\n id\n status\n media { id title { romaji english } }\n }\n }\n";
3
3
  declare const deleteActivityMutation = "\nmutation($id: Int!) {\n DeleteActivity(id: $id) { deleted }\n}\n";
4
4
  declare const saveTextActivityMutation = "\nmutation SaveTextActivity($status: String!) {\n SaveTextActivity(text: $status) { id text userId createdAt }\n}\n";
5
- export { addAnimeToListMutation, addMangaToListMutation, deleteActivityMutation, saveTextActivityMutation, };
5
+ declare const saveAnimeWithProgressMutation = "\nmutation ($mediaId: Int, $progress: Int, $status: MediaListStatus, $hiddenFromStatusLists: Boolean) {\n SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: $status, hiddenFromStatusLists: $hiddenFromStatusLists) {\n id progress hiddenFromStatusLists\n }\n}\n";
6
+ declare const saveMangaWithProgressMutation = "\nmutation ($mediaId: Int, $progress: Int, $status: MediaListStatus, $hiddenFromStatusLists: Boolean, $private: Boolean) {\n SaveMediaListEntry( mediaId: $mediaId, progress: $progress, status: $status, hiddenFromStatusLists: $hiddenFromStatusLists, private: $private\n ) { id progress hiddenFromStatusLists private }\n}\n";
7
+ export { addAnimeToListMutation, addMangaToListMutation, deleteActivityMutation, saveTextActivityMutation, saveAnimeWithProgressMutation, saveMangaWithProgressMutation, };
@@ -22,4 +22,17 @@ mutation SaveTextActivity($status: String!) {
22
22
  SaveTextActivity(text: $status) { id text userId createdAt }
23
23
  }
24
24
  `;
25
- export { addAnimeToListMutation, addMangaToListMutation, deleteActivityMutation, saveTextActivityMutation, };
25
+ const saveAnimeWithProgressMutation = `
26
+ mutation ($mediaId: Int, $progress: Int, $status: MediaListStatus, $hiddenFromStatusLists: Boolean) {
27
+ SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: $status, hiddenFromStatusLists: $hiddenFromStatusLists) {
28
+ id progress hiddenFromStatusLists
29
+ }
30
+ }
31
+ `;
32
+ const saveMangaWithProgressMutation = `
33
+ mutation ($mediaId: Int, $progress: Int, $status: MediaListStatus, $hiddenFromStatusLists: Boolean, $private: Boolean) {
34
+ SaveMediaListEntry( mediaId: $mediaId, progress: $progress, status: $status, hiddenFromStatusLists: $hiddenFromStatusLists, private: $private
35
+ ) { id progress hiddenFromStatusLists private }
36
+ }
37
+ `;
38
+ export { addAnimeToListMutation, addMangaToListMutation, deleteActivityMutation, saveTextActivityMutation, saveAnimeWithProgressMutation, saveMangaWithProgressMutation, };
@@ -2,8 +2,8 @@ declare const currentUserQuery = "{\n Viewer {\n id name about bans siteUrl
2
2
  declare const trendingQuery = "query ($page: Int, $perPage: Int) {\n Page(page: $page, perPage: $perPage) {\n media(sort: TRENDING_DESC, type: ANIME) { id title { romaji english } }\n }\n}";
3
3
  declare const popularQuery = "query ($page: Int, $perPage: Int) {\n Page(page: $page, perPage: $perPage) {\n media(sort: POPULARITY_DESC, type: ANIME) { id title { romaji english } }\n }\n}";
4
4
  declare const userQuery = "query ($username: String) {\n User(name: $username) {\n id name siteUrl donatorTier donatorBadge createdAt updatedAt previousNames { name createdAt updatedAt }\n isBlocked isFollower isFollowing options { profileColor timezone activityMergeTime }\n statistics { anime { count episodesWatched minutesWatched } manga { count chaptersRead volumesRead } }\n }\n}";
5
- declare const currentUserAnimeList = "query ($id: Int) {\n MediaListCollection(userId: $id, type: ANIME) {\n lists { name entries { id media { id title { romaji english } } } }\n }\n}";
6
- declare const currentUserMangaList = "query ($id: Int) {\n MediaListCollection(userId: $id, type: MANGA) {\n lists { name entries { id media { id title { romaji english } } } }\n }\n}";
5
+ declare const currentUserAnimeList = "query ($id: Int) {\n MediaListCollection(userId: $id, type: ANIME) {\n lists { name entries { id progress hiddenFromStatusLists status media { id title { romaji english } status episodes siteUrl } } }\n }\n}\n";
6
+ declare const currentUserMangaList = "query ($id: Int) {\n MediaListCollection(userId: $id, type: MANGA) {\n lists { name entries { id progress hiddenFromStatusLists private status media { id title { romaji english } status chapters } } }\n }\n}\n";
7
7
  declare const deleteMediaEntryMutation = "mutation($id: Int!) {\n DeleteMediaListEntry(id: $id) { deleted }\n}";
8
8
  declare const deleteMangaEntryMutation = "mutation($id: Int) {\n DeleteMediaListEntry(id: $id) { deleted }\n}";
9
9
  declare const upcomingAnimesQuery = "query GetNextSeasonAnime($nextSeason: MediaSeason, $nextYear: Int, $perPage: Int) {\n Page(perPage: $perPage) {\n media(season: $nextSeason, seasonYear: $nextYear, type: ANIME, sort: POPULARITY_DESC) {\n id title { romaji english native userPreferred } season seasonYear startDate { year month day }\n episodes description genres\n }\n }\n}";
@@ -26,14 +26,16 @@ const userQuery = `query ($username: String) {
26
26
  }`;
27
27
  const currentUserAnimeList = `query ($id: Int) {
28
28
  MediaListCollection(userId: $id, type: ANIME) {
29
- lists { name entries { id media { id title { romaji english } } } }
29
+ lists { name entries { id progress hiddenFromStatusLists status media { id title { romaji english } status episodes siteUrl } } }
30
30
  }
31
- }`;
31
+ }
32
+ `;
32
33
  const currentUserMangaList = `query ($id: Int) {
33
34
  MediaListCollection(userId: $id, type: MANGA) {
34
- lists { name entries { id media { id title { romaji english } } } }
35
+ lists { name entries { id progress hiddenFromStatusLists private status media { id title { romaji english } status chapters } } }
35
36
  }
36
- }`;
37
+ }
38
+ `;
37
39
  const deleteMediaEntryMutation = `mutation($id: Int!) {
38
40
  DeleteMediaListEntry(id: $id) { deleted }
39
41
  }`;
@@ -14,4 +14,18 @@ declare function getNextSeasonAndYear(): {
14
14
  nextYear: number;
15
15
  };
16
16
  declare function removeHtmlAndMarkdown(input: string): string;
17
- export { aniListEndpoint, redirectUri, getTitle, getNextSeasonAndYear, formatDateObject, removeHtmlAndMarkdown, };
17
+ /**
18
+ * Export JSON as JSON
19
+ * @param js0n
20
+ * @param dataType (eg: anime/manga)
21
+ */
22
+ declare function saveJSONasJSON(js0n: object, dataType: string): Promise<void>;
23
+ /**
24
+ * Export JSON as CSV
25
+ * @param js0n
26
+ * @param dataType (eg: anime/manga)
27
+ */
28
+ declare function saveJSONasCSV(js0n: object, dataType: string): Promise<void>;
29
+ declare function importAnimeListFromExportedJSON(): Promise<void>;
30
+ declare function importMangaListFromExportedJSON(): Promise<void>;
31
+ export { aniListEndpoint, redirectUri, getTitle, getNextSeasonAndYear, formatDateObject, removeHtmlAndMarkdown, saveJSONasJSON, saveJSONasCSV, importAnimeListFromExportedJSON, importMangaListFromExportedJSON, };
@@ -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,14 +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, writeTextActivity, } 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
16
  cli
17
17
  .name("anilist")
18
18
  .description("Minimalist unofficial AniList CLI for Anime and Manga Enthusiasts.")
19
- .version("1.0.5");
19
+ .version("1.0.6");
20
20
  cli
21
21
  .command("login")
22
22
  .description("Login with AniList")
@@ -159,4 +159,47 @@ cli
159
159
  .action((status) => __awaiter(void 0, void 0, void 0, function* () {
160
160
  yield writeTextActivity(status);
161
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.`);
202
+ }
203
+ }
204
+ }));
162
205
  cli.parse(process.argv);
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.0.5",
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
  }