ani-client 1.6.0 → 1.7.0
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/README.md +2 -2
- package/dist/index.d.mts +37 -46
- package/dist/index.d.ts +37 -46
- package/dist/index.js +208 -101
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +208 -102
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,33 +1,91 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/utils/markdown.ts
|
|
4
|
+
function isSafeUrl(url) {
|
|
5
|
+
return /^https?:\/\//i.test(url);
|
|
6
|
+
}
|
|
4
7
|
function parseAniListMarkdown(text) {
|
|
5
8
|
if (!text) return "";
|
|
6
9
|
let html = text;
|
|
10
|
+
html = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
11
|
+
html = html.replace(/```([\s\S]*?)```/g, (_match, code) => {
|
|
12
|
+
return `<pre><code>${code.trim()}</code></pre>`;
|
|
13
|
+
});
|
|
14
|
+
html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
7
15
|
html = html.replace(/~!(.*?)!~/gs, '<span class="anilist-spoiler">$1</span>');
|
|
8
16
|
html = html.replace(/~~~(.*?)~~~/gs, '<div class="anilist-center">$1</div>');
|
|
9
|
-
html = html.replace(/img(\d+)\((.*?)\)/gi, '<img src="$2" width="$1" alt="" class="anilist-image" />');
|
|
10
|
-
html = html.replace(/img\((.*?)\)/gi, '<img src="$1" alt="" class="anilist-image" />');
|
|
11
17
|
html = html.replace(
|
|
12
|
-
/
|
|
13
|
-
|
|
18
|
+
/img(\d+)\((.*?)\)/gi,
|
|
19
|
+
(_match, width, url) => isSafeUrl(url) ? `<img src="${url}" width="${width}" alt="" class="anilist-image" />` : ""
|
|
20
|
+
);
|
|
21
|
+
html = html.replace(
|
|
22
|
+
/img\((.*?)\)/gi,
|
|
23
|
+
(_match, url) => isSafeUrl(url) ? `<img src="${url}" alt="" class="anilist-image" />` : ""
|
|
14
24
|
);
|
|
15
|
-
html = html.replace(/
|
|
25
|
+
html = html.replace(/youtube\((.*?)\)/gi, (_match, id) => {
|
|
26
|
+
if (!/^[\w-]+$/.test(id)) return "";
|
|
27
|
+
return `<iframe src="https://www.youtube.com/embed/${id}" frameborder="0" allowfullscreen class="anilist-youtube"></iframe>`;
|
|
28
|
+
});
|
|
29
|
+
html = html.replace(
|
|
30
|
+
/webm\((.*?)\)/gi,
|
|
31
|
+
(_match, url) => isSafeUrl(url) ? `<video src="${url}" controls class="anilist-webm"></video>` : ""
|
|
32
|
+
);
|
|
33
|
+
html = html.replace(/^######\s+(.+)$/gm, "<h6>$1</h6>");
|
|
34
|
+
html = html.replace(/^#####\s+(.+)$/gm, "<h5>$1</h5>");
|
|
35
|
+
html = html.replace(/^####\s+(.+)$/gm, "<h4>$1</h4>");
|
|
36
|
+
html = html.replace(/^###\s+(.+)$/gm, "<h3>$1</h3>");
|
|
37
|
+
html = html.replace(/^##\s+(.+)$/gm, "<h2>$1</h2>");
|
|
38
|
+
html = html.replace(/^#\s+(.+)$/gm, "<h1>$1</h1>");
|
|
16
39
|
html = html.replace(/__(.*?)__/g, "<strong>$1</strong>");
|
|
17
40
|
html = html.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
|
|
18
41
|
html = html.replace(/_(.*?)_/g, "<em>$1</em>");
|
|
19
42
|
html = html.replace(/(?<!\*)\*(?!\*)(.*?)(?<!\*)\*(?!\*)/g, "<em>$1</em>");
|
|
20
43
|
html = html.replace(/~~(.*?)~~/g, "<del>$1</del>");
|
|
21
|
-
html = html.replace(
|
|
44
|
+
html = html.replace(
|
|
45
|
+
/\[(.*?)\]\((.*?)\)/g,
|
|
46
|
+
(_match, text2, url) => isSafeUrl(url) ? `<a href="${url}" target="_blank" rel="noopener noreferrer">${text2}</a>` : text2
|
|
47
|
+
);
|
|
22
48
|
html = html.replace(/\r\n/g, "\n");
|
|
49
|
+
const lines = html.split("\n");
|
|
50
|
+
const processed = [];
|
|
51
|
+
let listType = null;
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
const ulMatch = line.match(/^[\s]*[-*]\s+(.*)/);
|
|
54
|
+
const olMatch = line.match(/^[\s]*\d+\.\s+(.*)/);
|
|
55
|
+
if (ulMatch) {
|
|
56
|
+
if (listType !== "ul") {
|
|
57
|
+
if (listType) processed.push(`</${listType}>`);
|
|
58
|
+
processed.push("<ul>");
|
|
59
|
+
listType = "ul";
|
|
60
|
+
}
|
|
61
|
+
processed.push(`<li>${ulMatch[1]}</li>`);
|
|
62
|
+
} else if (olMatch) {
|
|
63
|
+
if (listType !== "ol") {
|
|
64
|
+
if (listType) processed.push(`</${listType}>`);
|
|
65
|
+
processed.push("<ol>");
|
|
66
|
+
listType = "ol";
|
|
67
|
+
}
|
|
68
|
+
processed.push(`<li>${olMatch[1]}</li>`);
|
|
69
|
+
} else {
|
|
70
|
+
if (listType) {
|
|
71
|
+
processed.push(`</${listType}>`);
|
|
72
|
+
listType = null;
|
|
73
|
+
}
|
|
74
|
+
processed.push(line);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (listType) processed.push(`</${listType}>`);
|
|
78
|
+
html = processed.join("\n");
|
|
23
79
|
const paragraphs = html.split(/\n{2,}/);
|
|
24
80
|
html = paragraphs.map((p) => {
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
81
|
+
const trimmed = p.trim();
|
|
82
|
+
if (!trimmed) return "";
|
|
83
|
+
const withBr = trimmed.replace(/\n/g, "<br />");
|
|
84
|
+
if (withBr.match(/^(<div|<iframe|<video|<img|<h[1-6]|<ul|<ol|<pre)/)) {
|
|
27
85
|
return withBr;
|
|
28
86
|
}
|
|
29
87
|
return `<p>${withBr}</p>`;
|
|
30
|
-
}).join("\n");
|
|
88
|
+
}).filter(Boolean).join("\n");
|
|
31
89
|
return html;
|
|
32
90
|
}
|
|
33
91
|
|
|
@@ -430,6 +488,48 @@ var STUDIO_FIELDS = `
|
|
|
430
488
|
}
|
|
431
489
|
}
|
|
432
490
|
`;
|
|
491
|
+
var THREAD_FIELDS = `
|
|
492
|
+
id
|
|
493
|
+
title
|
|
494
|
+
body(asHtml: false)
|
|
495
|
+
userId
|
|
496
|
+
replyUserId
|
|
497
|
+
replyCommentId
|
|
498
|
+
replyCount
|
|
499
|
+
viewCount
|
|
500
|
+
isLocked
|
|
501
|
+
isSticky
|
|
502
|
+
isSubscribed
|
|
503
|
+
repliedAt
|
|
504
|
+
createdAt
|
|
505
|
+
updatedAt
|
|
506
|
+
siteUrl
|
|
507
|
+
user {
|
|
508
|
+
id
|
|
509
|
+
name
|
|
510
|
+
avatar { large medium }
|
|
511
|
+
}
|
|
512
|
+
replyUser {
|
|
513
|
+
id
|
|
514
|
+
name
|
|
515
|
+
avatar { large medium }
|
|
516
|
+
}
|
|
517
|
+
categories {
|
|
518
|
+
id
|
|
519
|
+
name
|
|
520
|
+
}
|
|
521
|
+
mediaCategories {
|
|
522
|
+
id
|
|
523
|
+
title { romaji english native userPreferred }
|
|
524
|
+
type
|
|
525
|
+
coverImage { large medium }
|
|
526
|
+
siteUrl
|
|
527
|
+
}
|
|
528
|
+
likes {
|
|
529
|
+
id
|
|
530
|
+
name
|
|
531
|
+
}
|
|
532
|
+
`;
|
|
433
533
|
|
|
434
534
|
// src/queries/media.ts
|
|
435
535
|
var QUERY_MEDIA_BY_ID = `
|
|
@@ -678,10 +778,10 @@ query ($id: Int!) {
|
|
|
678
778
|
}
|
|
679
779
|
}`;
|
|
680
780
|
var QUERY_STUDIO_SEARCH = `
|
|
681
|
-
query ($search: String, $page: Int, $perPage: Int) {
|
|
781
|
+
query ($search: String, $sort: [StudioSort], $page: Int, $perPage: Int) {
|
|
682
782
|
Page(page: $page, perPage: $perPage) {
|
|
683
783
|
pageInfo { total perPage currentPage lastPage hasNextPage }
|
|
684
|
-
studios(search: $search) {
|
|
784
|
+
studios(search: $search, sort: $sort) {
|
|
685
785
|
${STUDIO_FIELDS}
|
|
686
786
|
}
|
|
687
787
|
}
|
|
@@ -712,7 +812,7 @@ function buildMediaByIdQuery(include) {
|
|
|
712
812
|
}
|
|
713
813
|
if (include.characters) {
|
|
714
814
|
const opts = typeof include.characters === "object" ? include.characters : {};
|
|
715
|
-
const perPage = opts.perPage ?? 25;
|
|
815
|
+
const perPage = clampPerPage(opts.perPage ?? 25);
|
|
716
816
|
const sortClause = opts.sort !== false ? ", sort: [ROLE, RELEVANCE, ID]" : "";
|
|
717
817
|
const voiceActorBlock = opts.voiceActors ? `
|
|
718
818
|
voiceActors {
|
|
@@ -730,7 +830,7 @@ function buildMediaByIdQuery(include) {
|
|
|
730
830
|
}
|
|
731
831
|
if (include.staff) {
|
|
732
832
|
const opts = typeof include.staff === "object" ? include.staff : {};
|
|
733
|
-
const perPage = opts.perPage ?? 25;
|
|
833
|
+
const perPage = clampPerPage(opts.perPage ?? 25);
|
|
734
834
|
const sortClause = opts.sort !== false ? ", sort: [RELEVANCE, ID]" : "";
|
|
735
835
|
extra.push(`
|
|
736
836
|
staff(perPage: ${perPage}${sortClause}) {
|
|
@@ -743,7 +843,9 @@ function buildMediaByIdQuery(include) {
|
|
|
743
843
|
}`);
|
|
744
844
|
}
|
|
745
845
|
if (include.recommendations) {
|
|
746
|
-
const perPage =
|
|
846
|
+
const perPage = clampPerPage(
|
|
847
|
+
typeof include.recommendations === "object" ? include.recommendations.perPage ?? 10 : 10
|
|
848
|
+
);
|
|
747
849
|
extra.push(`
|
|
748
850
|
recommendations(perPage: ${perPage}, sort: [RATING_DESC]) {
|
|
749
851
|
nodes {
|
|
@@ -802,53 +904,11 @@ function buildBatchQuery(ids, typeName, fields, prefix) {
|
|
|
802
904
|
${aliases}
|
|
803
905
|
}`;
|
|
804
906
|
}
|
|
805
|
-
var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media",
|
|
907
|
+
var buildBatchMediaQuery = (ids) => buildBatchQuery(ids, "Media", MEDIA_FIELDS, "m");
|
|
806
908
|
var buildBatchCharacterQuery = (ids) => buildBatchQuery(ids, "Character", CHARACTER_FIELDS, "c");
|
|
807
909
|
var buildBatchStaffQuery = (ids) => buildBatchQuery(ids, "Staff", STAFF_FIELDS, "s");
|
|
808
910
|
|
|
809
911
|
// src/queries/thread.ts
|
|
810
|
-
var THREAD_FIELDS = `
|
|
811
|
-
id
|
|
812
|
-
title
|
|
813
|
-
body(asHtml: false)
|
|
814
|
-
userId
|
|
815
|
-
replyUserId
|
|
816
|
-
replyCommentId
|
|
817
|
-
replyCount
|
|
818
|
-
viewCount
|
|
819
|
-
isLocked
|
|
820
|
-
isSticky
|
|
821
|
-
isSubscribed
|
|
822
|
-
repliedAt
|
|
823
|
-
createdAt
|
|
824
|
-
updatedAt
|
|
825
|
-
siteUrl
|
|
826
|
-
user {
|
|
827
|
-
id
|
|
828
|
-
name
|
|
829
|
-
avatar { large medium }
|
|
830
|
-
}
|
|
831
|
-
replyUser {
|
|
832
|
-
id
|
|
833
|
-
name
|
|
834
|
-
avatar { large medium }
|
|
835
|
-
}
|
|
836
|
-
categories {
|
|
837
|
-
id
|
|
838
|
-
name
|
|
839
|
-
}
|
|
840
|
-
mediaCategories {
|
|
841
|
-
id
|
|
842
|
-
title { romaji english native userPreferred }
|
|
843
|
-
type
|
|
844
|
-
coverImage { large medium }
|
|
845
|
-
siteUrl
|
|
846
|
-
}
|
|
847
|
-
likes {
|
|
848
|
-
id
|
|
849
|
-
name
|
|
850
|
-
}
|
|
851
|
-
`;
|
|
852
912
|
var QUERY_THREAD_BY_ID = `
|
|
853
913
|
query ($id: Int!) {
|
|
854
914
|
Thread(id: $id) {
|
|
@@ -870,6 +930,8 @@ var RateLimiter = class {
|
|
|
870
930
|
constructor(options = {}) {
|
|
871
931
|
this.head = 0;
|
|
872
932
|
this.count = 0;
|
|
933
|
+
/** @internal — active sleep timers for cleanup */
|
|
934
|
+
this.activeTimers = /* @__PURE__ */ new Set();
|
|
873
935
|
this.maxRequests = options.maxRequests ?? 85;
|
|
874
936
|
this.windowMs = options.windowMs ?? 6e4;
|
|
875
937
|
this.maxRetries = options.maxRetries ?? 3;
|
|
@@ -887,8 +949,7 @@ var RateLimiter = class {
|
|
|
887
949
|
if (!this.enabled) return;
|
|
888
950
|
if (this.count >= this.maxRequests) {
|
|
889
951
|
const oldest = this.timestamps[this.head];
|
|
890
|
-
const
|
|
891
|
-
const elapsed = now2 - oldest;
|
|
952
|
+
const elapsed = Date.now() - oldest;
|
|
892
953
|
if (elapsed < this.windowMs) {
|
|
893
954
|
const waitMs = this.windowMs - elapsed + 50;
|
|
894
955
|
await this.sleep(waitMs);
|
|
@@ -952,14 +1013,32 @@ var RateLimiter = class {
|
|
|
952
1013
|
if (this.timeoutMs <= 0) return fetch(url, init);
|
|
953
1014
|
const controller = new AbortController();
|
|
954
1015
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1016
|
+
const signals = [controller.signal, init.signal].filter(Boolean);
|
|
1017
|
+
const combinedSignal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
|
|
955
1018
|
try {
|
|
956
|
-
return await fetch(url, { ...init, signal:
|
|
1019
|
+
return await fetch(url, { ...init, signal: combinedSignal });
|
|
957
1020
|
} finally {
|
|
958
1021
|
clearTimeout(timer);
|
|
959
1022
|
}
|
|
960
1023
|
}
|
|
961
1024
|
sleep(ms) {
|
|
962
|
-
return new Promise((resolve) =>
|
|
1025
|
+
return new Promise((resolve) => {
|
|
1026
|
+
const timer = setTimeout(() => {
|
|
1027
|
+
this.activeTimers.delete(timer);
|
|
1028
|
+
resolve();
|
|
1029
|
+
}, ms);
|
|
1030
|
+
this.activeTimers.add(timer);
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
/** Cancel all pending sleep timers and reset internal state. */
|
|
1034
|
+
dispose() {
|
|
1035
|
+
for (const timer of this.activeTimers) {
|
|
1036
|
+
clearTimeout(timer);
|
|
1037
|
+
}
|
|
1038
|
+
this.activeTimers.clear();
|
|
1039
|
+
this.head = 0;
|
|
1040
|
+
this.count = 0;
|
|
1041
|
+
this.timestamps.fill(0);
|
|
963
1042
|
}
|
|
964
1043
|
};
|
|
965
1044
|
var RETRYABLE_NETWORK_CODES = /* @__PURE__ */ new Set([
|
|
@@ -1163,6 +1242,18 @@ var UserSort = /* @__PURE__ */ ((UserSort2) => {
|
|
|
1163
1242
|
return UserSort2;
|
|
1164
1243
|
})(UserSort || {});
|
|
1165
1244
|
|
|
1245
|
+
// src/types/studio.ts
|
|
1246
|
+
var StudioSort = /* @__PURE__ */ ((StudioSort2) => {
|
|
1247
|
+
StudioSort2["ID"] = "ID";
|
|
1248
|
+
StudioSort2["ID_DESC"] = "ID_DESC";
|
|
1249
|
+
StudioSort2["NAME"] = "NAME";
|
|
1250
|
+
StudioSort2["NAME_DESC"] = "NAME_DESC";
|
|
1251
|
+
StudioSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
|
|
1252
|
+
StudioSort2["FAVOURITES"] = "FAVOURITES";
|
|
1253
|
+
StudioSort2["FAVOURITES_DESC"] = "FAVOURITES_DESC";
|
|
1254
|
+
return StudioSort2;
|
|
1255
|
+
})(StudioSort || {});
|
|
1256
|
+
|
|
1166
1257
|
// src/types/lists.ts
|
|
1167
1258
|
var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
|
|
1168
1259
|
MediaListStatus2["CURRENT"] = "CURRENT";
|
|
@@ -1275,7 +1366,7 @@ async function getAiredEpisodes(client, options = {}) {
|
|
|
1275
1366
|
"airingSchedules"
|
|
1276
1367
|
);
|
|
1277
1368
|
}
|
|
1278
|
-
async function
|
|
1369
|
+
async function getRecentlyUpdatedManga(client, options = {}) {
|
|
1279
1370
|
return client.pagedRequest(
|
|
1280
1371
|
QUERY_RECENT_CHAPTERS,
|
|
1281
1372
|
{
|
|
@@ -1298,6 +1389,7 @@ async function getPlanning(client, options = {}) {
|
|
|
1298
1389
|
);
|
|
1299
1390
|
}
|
|
1300
1391
|
async function getRecommendations(client, mediaId, options = {}) {
|
|
1392
|
+
validateId(mediaId, "mediaId");
|
|
1301
1393
|
const data = await client.request(QUERY_RECOMMENDATIONS, {
|
|
1302
1394
|
mediaId,
|
|
1303
1395
|
page: options.page ?? 1,
|
|
@@ -1333,14 +1425,12 @@ async function getWeeklySchedule(client, date = /* @__PURE__ */ new Date()) {
|
|
|
1333
1425
|
Saturday: [],
|
|
1334
1426
|
Sunday: []
|
|
1335
1427
|
};
|
|
1336
|
-
const
|
|
1337
|
-
const
|
|
1338
|
-
const
|
|
1339
|
-
startOfWeek.setDate(diff);
|
|
1340
|
-
startOfWeek.setHours(0, 0, 0, 0);
|
|
1428
|
+
const utcDay = date.getUTCDay();
|
|
1429
|
+
const diff = utcDay === 0 ? -6 : 1 - utcDay;
|
|
1430
|
+
const startOfWeek = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() + diff, 0, 0, 0));
|
|
1341
1431
|
const endOfWeek = new Date(startOfWeek);
|
|
1342
|
-
endOfWeek.
|
|
1343
|
-
endOfWeek.
|
|
1432
|
+
endOfWeek.setUTCDate(startOfWeek.getUTCDate() + 6);
|
|
1433
|
+
endOfWeek.setUTCHours(23, 59, 59, 999);
|
|
1344
1434
|
const startTimestamp = Math.floor(startOfWeek.getTime() / 1e3);
|
|
1345
1435
|
const endTimestamp = Math.floor(endOfWeek.getTime() / 1e3);
|
|
1346
1436
|
const iterator = client.paginate(
|
|
@@ -1349,12 +1439,13 @@ async function getWeeklySchedule(client, date = /* @__PURE__ */ new Date()) {
|
|
|
1349
1439
|
airingAtLesser: endTimestamp,
|
|
1350
1440
|
page,
|
|
1351
1441
|
perPage: 50
|
|
1352
|
-
})
|
|
1442
|
+
}),
|
|
1443
|
+
20
|
|
1353
1444
|
);
|
|
1354
1445
|
const names = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
1355
1446
|
for await (const episode of iterator) {
|
|
1356
1447
|
const epDate = new Date(episode.airingAt * 1e3);
|
|
1357
|
-
const dayName = names[epDate.
|
|
1448
|
+
const dayName = names[epDate.getUTCDay()];
|
|
1358
1449
|
schedule[dayName].push(episode);
|
|
1359
1450
|
}
|
|
1360
1451
|
return schedule;
|
|
@@ -1387,12 +1478,14 @@ async function getStudio(client, id) {
|
|
|
1387
1478
|
return data.Studio;
|
|
1388
1479
|
}
|
|
1389
1480
|
async function searchStudios(client, options = {}) {
|
|
1481
|
+
const { query: search, page = 1, perPage = 20, sort } = options;
|
|
1390
1482
|
return client.pagedRequest(
|
|
1391
1483
|
QUERY_STUDIO_SEARCH,
|
|
1392
1484
|
{
|
|
1393
|
-
search
|
|
1394
|
-
|
|
1395
|
-
|
|
1485
|
+
search,
|
|
1486
|
+
sort,
|
|
1487
|
+
page,
|
|
1488
|
+
perPage: clampPerPage(perPage)
|
|
1396
1489
|
},
|
|
1397
1490
|
"studios"
|
|
1398
1491
|
);
|
|
@@ -1438,6 +1531,9 @@ async function getUserMediaList(client, options) {
|
|
|
1438
1531
|
if (!options.userId && !options.userName) {
|
|
1439
1532
|
throw new AniListError("getUserMediaList requires either userId or userName", 0, []);
|
|
1440
1533
|
}
|
|
1534
|
+
if (options.userId) {
|
|
1535
|
+
validateId(options.userId, "userId");
|
|
1536
|
+
}
|
|
1441
1537
|
return client.pagedRequest(
|
|
1442
1538
|
QUERY_USER_MEDIA_LIST,
|
|
1443
1539
|
{
|
|
@@ -1477,13 +1573,15 @@ function mapFavorites(fav) {
|
|
|
1477
1573
|
|
|
1478
1574
|
// src/client/index.ts
|
|
1479
1575
|
var DEFAULT_API_URL = "https://graphql.anilist.co";
|
|
1576
|
+
var LIB_VERSION = "1.7.0" ;
|
|
1480
1577
|
var AniListClient = class {
|
|
1481
1578
|
constructor(options = {}) {
|
|
1482
1579
|
this.inFlight = /* @__PURE__ */ new Map();
|
|
1483
1580
|
this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
1484
1581
|
this.headers = {
|
|
1485
1582
|
"Content-Type": "application/json",
|
|
1486
|
-
Accept: "application/json"
|
|
1583
|
+
Accept: "application/json",
|
|
1584
|
+
"User-Agent": `ani-client/${LIB_VERSION}`
|
|
1487
1585
|
};
|
|
1488
1586
|
if (options.token) {
|
|
1489
1587
|
this.headers.Authorization = `Bearer ${options.token}`;
|
|
@@ -1507,7 +1605,6 @@ var AniListClient = class {
|
|
|
1507
1605
|
get lastRequestMeta() {
|
|
1508
1606
|
return this._lastRequestMeta;
|
|
1509
1607
|
}
|
|
1510
|
-
// ── Core infrastructure (internal) ──
|
|
1511
1608
|
/** @internal */
|
|
1512
1609
|
async request(query, variables = {}) {
|
|
1513
1610
|
const cacheKey = MemoryCache.key(query, variables);
|
|
@@ -1534,20 +1631,29 @@ var AniListClient = class {
|
|
|
1534
1631
|
const start = Date.now();
|
|
1535
1632
|
this.hooks.onRequest?.(query, variables);
|
|
1536
1633
|
const minifiedQuery = normalizeQuery(query);
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1634
|
+
let res;
|
|
1635
|
+
try {
|
|
1636
|
+
res = await this.rateLimiter.fetchWithRetry(
|
|
1637
|
+
this.apiUrl,
|
|
1638
|
+
{
|
|
1639
|
+
method: "POST",
|
|
1640
|
+
headers: this.headers,
|
|
1641
|
+
body: JSON.stringify({ query: minifiedQuery, variables }),
|
|
1642
|
+
signal: this.signal
|
|
1643
|
+
},
|
|
1644
|
+
{ onRetry: this.hooks.onRetry, onRateLimit: this.hooks.onRateLimit }
|
|
1645
|
+
);
|
|
1646
|
+
} catch (err) {
|
|
1647
|
+
const error = err instanceof AniListError ? err : new AniListError(err.message ?? "Network request failed", 0, [err]);
|
|
1648
|
+
this.hooks.onError?.(error, query, variables);
|
|
1649
|
+
throw error;
|
|
1650
|
+
}
|
|
1547
1651
|
const json = await res.json();
|
|
1548
1652
|
if (!res.ok || json.errors) {
|
|
1549
1653
|
const message = json.errors?.[0]?.message ?? `AniList API error (HTTP ${res.status})`;
|
|
1550
|
-
|
|
1654
|
+
const error = new AniListError(message, res.status, json.errors ?? []);
|
|
1655
|
+
this.hooks.onError?.(error, query, variables);
|
|
1656
|
+
throw error;
|
|
1551
1657
|
}
|
|
1552
1658
|
const rlLimit = res.headers.get("X-RateLimit-Limit");
|
|
1553
1659
|
const rlRemaining = res.headers.get("X-RateLimit-Remaining");
|
|
@@ -1576,7 +1682,6 @@ var AniListClient = class {
|
|
|
1576
1682
|
}
|
|
1577
1683
|
return { pageInfo: data.Page.pageInfo, results };
|
|
1578
1684
|
}
|
|
1579
|
-
// ── Media ──
|
|
1580
1685
|
/**
|
|
1581
1686
|
* Fetch a single media entry by its AniList ID.
|
|
1582
1687
|
*
|
|
@@ -1613,9 +1718,19 @@ var AniListClient = class {
|
|
|
1613
1718
|
async getAiredEpisodes(options = {}) {
|
|
1614
1719
|
return getAiredEpisodes(this, options);
|
|
1615
1720
|
}
|
|
1616
|
-
/**
|
|
1721
|
+
/**
|
|
1722
|
+
* Get currently releasing manga sorted by most recently updated.
|
|
1723
|
+
*
|
|
1724
|
+
* @param options - Pagination parameters
|
|
1725
|
+
*/
|
|
1726
|
+
async getRecentlyUpdatedManga(options = {}) {
|
|
1727
|
+
return getRecentlyUpdatedManga(this, options);
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* @deprecated Use `getRecentlyUpdatedManga` instead. This alias will be removed in v2.
|
|
1731
|
+
*/
|
|
1617
1732
|
async getAiredChapters(options = {}) {
|
|
1618
|
-
return
|
|
1733
|
+
return this.getRecentlyUpdatedManga(options);
|
|
1619
1734
|
}
|
|
1620
1735
|
/** Get the detailed schedule for the current week, sorted by day. */
|
|
1621
1736
|
async getWeeklySchedule(date) {
|
|
@@ -1633,7 +1748,6 @@ var AniListClient = class {
|
|
|
1633
1748
|
async getMediaBySeason(options) {
|
|
1634
1749
|
return getMediaBySeason(this, options);
|
|
1635
1750
|
}
|
|
1636
|
-
// ── Characters ──
|
|
1637
1751
|
/** Fetch a character by AniList ID. Pass `{ voiceActors: true }` to include VA data. */
|
|
1638
1752
|
async getCharacter(id, include) {
|
|
1639
1753
|
return getCharacter(this, id, include);
|
|
@@ -1642,7 +1756,6 @@ var AniListClient = class {
|
|
|
1642
1756
|
async searchCharacters(options = {}) {
|
|
1643
1757
|
return searchCharacters(this, options);
|
|
1644
1758
|
}
|
|
1645
|
-
// ── Staff ──
|
|
1646
1759
|
/** Fetch a staff member by AniList ID. Pass `{ media: true }` or `{ media: { perPage } }` for media credits. */
|
|
1647
1760
|
async getStaff(id, include) {
|
|
1648
1761
|
return getStaff(this, id, include);
|
|
@@ -1651,7 +1764,6 @@ var AniListClient = class {
|
|
|
1651
1764
|
async searchStaff(options = {}) {
|
|
1652
1765
|
return searchStaff(this, options);
|
|
1653
1766
|
}
|
|
1654
|
-
// ── Users ──
|
|
1655
1767
|
/**
|
|
1656
1768
|
* Fetch a user by AniList ID or username.
|
|
1657
1769
|
*
|
|
@@ -1683,7 +1795,6 @@ var AniListClient = class {
|
|
|
1683
1795
|
async getUserFavorites(idOrName) {
|
|
1684
1796
|
return getUserFavorites(this, idOrName);
|
|
1685
1797
|
}
|
|
1686
|
-
// ── Studios ──
|
|
1687
1798
|
/** Fetch a studio by its AniList ID. */
|
|
1688
1799
|
async getStudio(id) {
|
|
1689
1800
|
return getStudio(this, id);
|
|
@@ -1692,8 +1803,6 @@ var AniListClient = class {
|
|
|
1692
1803
|
async searchStudios(options = {}) {
|
|
1693
1804
|
return searchStudios(this, options);
|
|
1694
1805
|
}
|
|
1695
|
-
// ── Metadata ──
|
|
1696
|
-
// ── Threads ──
|
|
1697
1806
|
/** Fetch a forum thread by its AniList ID. */
|
|
1698
1807
|
async getThread(id) {
|
|
1699
1808
|
return getThread(this, id);
|
|
@@ -1712,12 +1821,10 @@ var AniListClient = class {
|
|
|
1712
1821
|
const data = await this.request(QUERY_TAGS);
|
|
1713
1822
|
return data.MediaTagCollection;
|
|
1714
1823
|
}
|
|
1715
|
-
// ── Raw query ──
|
|
1716
1824
|
/** Execute an arbitrary GraphQL query against the AniList API. */
|
|
1717
1825
|
async raw(query, variables) {
|
|
1718
1826
|
return this.request(query, variables ?? {});
|
|
1719
1827
|
}
|
|
1720
|
-
// ── Pagination ──
|
|
1721
1828
|
/**
|
|
1722
1829
|
* Auto-paginating async iterator. Yields individual items across all pages.
|
|
1723
1830
|
*
|
|
@@ -1736,7 +1843,6 @@ var AniListClient = class {
|
|
|
1736
1843
|
page++;
|
|
1737
1844
|
}
|
|
1738
1845
|
}
|
|
1739
|
-
// ── Batch queries ──
|
|
1740
1846
|
/** Fetch multiple media entries in a single API request. */
|
|
1741
1847
|
async getMediaBatch(ids) {
|
|
1742
1848
|
if (ids.length === 0) return [];
|
|
@@ -1770,13 +1876,12 @@ var AniListClient = class {
|
|
|
1770
1876
|
);
|
|
1771
1877
|
return chunkResults.flat();
|
|
1772
1878
|
}
|
|
1773
|
-
// ── Cache management ──
|
|
1774
1879
|
/** Clear the entire response cache. */
|
|
1775
1880
|
async clearCache() {
|
|
1776
1881
|
await this.cacheAdapter.clear();
|
|
1777
1882
|
}
|
|
1778
1883
|
/** Number of entries currently in the cache. */
|
|
1779
|
-
|
|
1884
|
+
async cacheSize() {
|
|
1780
1885
|
return this.cacheAdapter.size;
|
|
1781
1886
|
}
|
|
1782
1887
|
/** Remove cache entries whose key matches the given pattern. */
|
|
@@ -1799,6 +1904,7 @@ var AniListClient = class {
|
|
|
1799
1904
|
async destroy() {
|
|
1800
1905
|
await this.cacheAdapter.clear();
|
|
1801
1906
|
this.inFlight.clear();
|
|
1907
|
+
this.rateLimiter.dispose();
|
|
1802
1908
|
}
|
|
1803
1909
|
};
|
|
1804
1910
|
|
|
@@ -1904,6 +2010,7 @@ exports.RateLimiter = RateLimiter;
|
|
|
1904
2010
|
exports.RecommendationSort = RecommendationSort;
|
|
1905
2011
|
exports.RedisCache = RedisCache;
|
|
1906
2012
|
exports.StaffSort = StaffSort;
|
|
2013
|
+
exports.StudioSort = StudioSort;
|
|
1907
2014
|
exports.ThreadSort = ThreadSort;
|
|
1908
2015
|
exports.UserSort = UserSort;
|
|
1909
2016
|
exports.parseAniListMarkdown = parseAniListMarkdown;
|