@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 +10 -0
- package/CODE_OF_CONDUCT.md +43 -0
- package/CONTRIBUTING.md +75 -0
- package/README.md +29 -1
- package/bin/helpers/auth.js +2 -1
- package/bin/helpers/more.d.ts +5 -1
- package/bin/helpers/more.js +160 -3
- package/bin/helpers/mutations.d.ts +3 -1
- package/bin/helpers/mutations.js +14 -1
- package/bin/helpers/queries.d.ts +2 -2
- package/bin/helpers/queries.js +6 -4
- package/bin/helpers/workers.d.ts +15 -1
- package/bin/helpers/workers.js +178 -1
- package/bin/index.js +46 -3
- package/package.json +23 -3
package/CHANGELOG.md
ADDED
|
@@ -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.
|
package/CONTRIBUTING.md
ADDED
|
@@ -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
|
|
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 💙_**
|
package/bin/helpers/auth.js
CHANGED
|
@@ -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(
|
|
163
|
+
console.log(`\nLogout successful. See you soon, ${username}.`);
|
|
163
164
|
}
|
|
164
165
|
catch (error) {
|
|
165
166
|
console.error("\nError logging out:", error);
|
package/bin/helpers/more.d.ts
CHANGED
|
@@ -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
|
-
|
|
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, };
|
package/bin/helpers/more.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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, };
|
package/bin/helpers/mutations.js
CHANGED
|
@@ -22,4 +22,17 @@ mutation SaveTextActivity($status: String!) {
|
|
|
22
22
|
SaveTextActivity(text: $status) { id text userId createdAt }
|
|
23
23
|
}
|
|
24
24
|
`;
|
|
25
|
-
|
|
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, };
|
package/bin/helpers/queries.d.ts
CHANGED
|
@@ -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}";
|
package/bin/helpers/queries.js
CHANGED
|
@@ -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
|
}`;
|
package/bin/helpers/workers.d.ts
CHANGED
|
@@ -14,4 +14,18 @@ declare function getNextSeasonAndYear(): {
|
|
|
14
14
|
nextYear: number;
|
|
15
15
|
};
|
|
16
16
|
declare function removeHtmlAndMarkdown(input: string): string;
|
|
17
|
-
|
|
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, };
|
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,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.
|
|
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
|
+
"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
|
}
|