ani-client 1.0.0 → 1.2.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/dist/index.js CHANGED
@@ -33,6 +33,20 @@ var MEDIA_FIELDS = `
33
33
  trending
34
34
  tags { id name description category rank isMediaSpoiler }
35
35
  studios { nodes { id name isAnimationStudio siteUrl } }
36
+ relations {
37
+ edges {
38
+ relationType(version: 2)
39
+ node {
40
+ id
41
+ title { romaji english native userPreferred }
42
+ type
43
+ format
44
+ status
45
+ coverImage { large medium }
46
+ siteUrl
47
+ }
48
+ }
49
+ }
36
50
  isAdult
37
51
  siteUrl
38
52
  `;
@@ -181,6 +195,158 @@ query ($name: String!) {
181
195
  ${USER_FIELDS}
182
196
  }
183
197
  }`;
198
+ var QUERY_AIRING_SCHEDULE = `
199
+ query ($airingAt_greater: Int, $airingAt_lesser: Int, $sort: [AiringSort], $page: Int, $perPage: Int) {
200
+ Page(page: $page, perPage: $perPage) {
201
+ pageInfo { total perPage currentPage lastPage hasNextPage }
202
+ airingSchedules(airingAt_greater: $airingAt_greater, airingAt_lesser: $airingAt_lesser, sort: $sort) {
203
+ id
204
+ airingAt
205
+ timeUntilAiring
206
+ episode
207
+ mediaId
208
+ media {
209
+ ${MEDIA_FIELDS}
210
+ }
211
+ }
212
+ }
213
+ }`;
214
+ var QUERY_RECENT_CHAPTERS = `
215
+ query ($page: Int, $perPage: Int) {
216
+ Page(page: $page, perPage: $perPage) {
217
+ pageInfo { total perPage currentPage lastPage hasNextPage }
218
+ media(type: MANGA, status: RELEASING, sort: UPDATED_AT_DESC) {
219
+ ${MEDIA_FIELDS}
220
+ }
221
+ }
222
+ }`;
223
+ var QUERY_PLANNING = `
224
+ query ($type: MediaType, $sort: [MediaSort], $page: Int, $perPage: Int) {
225
+ Page(page: $page, perPage: $perPage) {
226
+ pageInfo { total perPage currentPage lastPage hasNextPage }
227
+ media(type: $type, status: NOT_YET_RELEASED, sort: $sort) {
228
+ ${MEDIA_FIELDS}
229
+ }
230
+ }
231
+ }`;
232
+ var QUERY_MEDIA_BY_SEASON = `
233
+ query ($season: MediaSeason!, $seasonYear: Int!, $type: MediaType, $sort: [MediaSort], $page: Int, $perPage: Int) {
234
+ Page(page: $page, perPage: $perPage) {
235
+ pageInfo { total perPage currentPage lastPage hasNextPage }
236
+ media(season: $season, seasonYear: $seasonYear, type: $type, sort: $sort) {
237
+ ${MEDIA_FIELDS}
238
+ }
239
+ }
240
+ }`;
241
+ var MEDIA_LIST_FIELDS = `
242
+ id
243
+ mediaId
244
+ status
245
+ score(format: POINT_100)
246
+ progress
247
+ progressVolumes
248
+ repeat
249
+ priority
250
+ private
251
+ notes
252
+ startedAt { year month day }
253
+ completedAt { year month day }
254
+ updatedAt
255
+ createdAt
256
+ media {
257
+ ${MEDIA_FIELDS}
258
+ }
259
+ `;
260
+ var QUERY_RECOMMENDATIONS = `
261
+ query ($mediaId: Int!, $page: Int, $perPage: Int, $sort: [RecommendationSort]) {
262
+ Media(id: $mediaId) {
263
+ recommendations(page: $page, perPage: $perPage, sort: $sort) {
264
+ pageInfo { total perPage currentPage lastPage hasNextPage }
265
+ nodes {
266
+ id
267
+ rating
268
+ userRating
269
+ mediaRecommendation {
270
+ id
271
+ idMal
272
+ title { romaji english native userPreferred }
273
+ type
274
+ format
275
+ status
276
+ coverImage { extraLarge large medium color }
277
+ bannerImage
278
+ genres
279
+ averageScore
280
+ meanScore
281
+ popularity
282
+ favourites
283
+ siteUrl
284
+ }
285
+ user {
286
+ id
287
+ name
288
+ avatar { large medium }
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }`;
294
+ var QUERY_USER_MEDIA_LIST = `
295
+ query ($userId: Int, $userName: String, $type: MediaType!, $status: MediaListStatus, $sort: [MediaListSort], $page: Int, $perPage: Int) {
296
+ Page(page: $page, perPage: $perPage) {
297
+ pageInfo { total perPage currentPage lastPage hasNextPage }
298
+ mediaList(userId: $userId, userName: $userName, type: $type, status: $status, sort: $sort) {
299
+ ${MEDIA_LIST_FIELDS}
300
+ }
301
+ }
302
+ }`;
303
+ var STUDIO_FIELDS = `
304
+ id
305
+ name
306
+ isAnimationStudio
307
+ siteUrl
308
+ favourites
309
+ media(page: 1, perPage: 25, sort: POPULARITY_DESC) {
310
+ pageInfo { total perPage currentPage lastPage hasNextPage }
311
+ nodes {
312
+ id
313
+ title { romaji english native userPreferred }
314
+ type
315
+ format
316
+ coverImage { large medium }
317
+ siteUrl
318
+ }
319
+ }
320
+ `;
321
+ var QUERY_STUDIO_BY_ID = `
322
+ query ($id: Int!) {
323
+ Studio(id: $id) {
324
+ ${STUDIO_FIELDS}
325
+ }
326
+ }`;
327
+ var QUERY_STUDIO_SEARCH = `
328
+ query ($search: String, $page: Int, $perPage: Int) {
329
+ Page(page: $page, perPage: $perPage) {
330
+ pageInfo { total perPage currentPage lastPage hasNextPage }
331
+ studios(search: $search) {
332
+ ${STUDIO_FIELDS}
333
+ }
334
+ }
335
+ }`;
336
+ var QUERY_GENRES = `
337
+ query {
338
+ GenreCollection
339
+ }`;
340
+ var QUERY_TAGS = `
341
+ query {
342
+ MediaTagCollection {
343
+ id
344
+ name
345
+ description
346
+ category
347
+ isAdult
348
+ }
349
+ }`;
184
350
 
185
351
  // src/errors/index.ts
186
352
  var AniListError = class extends Error {
@@ -443,6 +609,269 @@ var AniListClient = class {
443
609
  async raw(query, variables) {
444
610
  return this.request(query, variables);
445
611
  }
612
+ /**
613
+ * Get recently aired anime episodes.
614
+ *
615
+ * By default returns episodes that aired in the last 24 hours.
616
+ *
617
+ * @param options - Filter / pagination parameters
618
+ * @returns Paginated list of airing schedule entries
619
+ *
620
+ * @example
621
+ * ```ts
622
+ * // Episodes that aired in the last 48h
623
+ * const recent = await client.getAiredEpisodes({
624
+ * airingAtGreater: Math.floor(Date.now() / 1000) - 48 * 3600,
625
+ * });
626
+ * ```
627
+ */
628
+ async getAiredEpisodes(options = {}) {
629
+ const now = Math.floor(Date.now() / 1e3);
630
+ const variables = {
631
+ airingAt_greater: options.airingAtGreater ?? now - 24 * 3600,
632
+ airingAt_lesser: options.airingAtLesser ?? now,
633
+ sort: options.sort ?? ["TIME_DESC"],
634
+ page: options.page ?? 1,
635
+ perPage: options.perPage ?? 20
636
+ };
637
+ const data = await this.request(QUERY_AIRING_SCHEDULE, variables);
638
+ return { pageInfo: data.Page.pageInfo, results: data.Page.airingSchedules };
639
+ }
640
+ /**
641
+ * Get manga that are currently releasing, sorted by most recently updated.
642
+ *
643
+ * This is the closest equivalent to "recently released chapters" on AniList,
644
+ * since the API does not expose per-chapter airing schedules for manga.
645
+ *
646
+ * @param options - Pagination parameters
647
+ * @returns Paginated list of currently releasing manga
648
+ *
649
+ * @example
650
+ * ```ts
651
+ * const chapters = await client.getAiredChapters({ perPage: 10 });
652
+ * ```
653
+ */
654
+ async getAiredChapters(options = {}) {
655
+ const variables = {
656
+ page: options.page ?? 1,
657
+ perPage: options.perPage ?? 20
658
+ };
659
+ const data = await this.request(QUERY_RECENT_CHAPTERS, variables);
660
+ return { pageInfo: data.Page.pageInfo, results: data.Page.media };
661
+ }
662
+ /**
663
+ * Get upcoming (not yet released) anime and/or manga, sorted by popularity.
664
+ *
665
+ * @param options - Filter / pagination parameters
666
+ * @returns Paginated list of planned media
667
+ *
668
+ * @example
669
+ * ```ts
670
+ * import { MediaType } from "ani-client";
671
+ *
672
+ * // Most anticipated upcoming anime
673
+ * const planning = await client.getPlanning({ type: MediaType.ANIME, perPage: 10 });
674
+ * ```
675
+ */
676
+ async getPlanning(options = {}) {
677
+ const variables = {
678
+ type: options.type,
679
+ sort: options.sort ?? ["POPULARITY_DESC"],
680
+ page: options.page ?? 1,
681
+ perPage: options.perPage ?? 20
682
+ };
683
+ const data = await this.request(QUERY_PLANNING, variables);
684
+ return { pageInfo: data.Page.pageInfo, results: data.Page.media };
685
+ }
686
+ /**
687
+ * Get recommendations for a specific media.
688
+ *
689
+ * Returns other anime/manga that users have recommended based on the given media.
690
+ *
691
+ * @param mediaId - The AniList media ID
692
+ * @param options - Optional sort / pagination parameters
693
+ * @returns Paginated list of recommendations
694
+ *
695
+ * @example
696
+ * ```ts
697
+ * // Get recommendations for Cowboy Bebop
698
+ * const recs = await client.getRecommendations(1);
699
+ * recs.results.forEach((r) =>
700
+ * console.log(`${r.mediaRecommendation.title.romaji} (rating: ${r.rating})`)
701
+ * );
702
+ * ```
703
+ */
704
+ async getRecommendations(mediaId, options = {}) {
705
+ const variables = {
706
+ mediaId,
707
+ sort: options.sort ?? ["RATING_DESC"],
708
+ page: options.page ?? 1,
709
+ perPage: options.perPage ?? 20
710
+ };
711
+ const data = await this.request(QUERY_RECOMMENDATIONS, variables);
712
+ return {
713
+ pageInfo: data.Media.recommendations.pageInfo,
714
+ results: data.Media.recommendations.nodes
715
+ };
716
+ }
717
+ /**
718
+ * Get anime (or manga) for a specific season and year.
719
+ *
720
+ * @param options - Season, year and optional filter / pagination parameters
721
+ * @returns Paginated list of media for the given season
722
+ *
723
+ * @example
724
+ * ```ts
725
+ * import { MediaSeason } from "ani-client";
726
+ *
727
+ * const winter2026 = await client.getMediaBySeason({
728
+ * season: MediaSeason.WINTER,
729
+ * seasonYear: 2026,
730
+ * perPage: 10,
731
+ * });
732
+ * ```
733
+ */
734
+ async getMediaBySeason(options) {
735
+ const variables = {
736
+ season: options.season,
737
+ seasonYear: options.seasonYear,
738
+ type: options.type ?? "ANIME",
739
+ sort: options.sort ?? ["POPULARITY_DESC"],
740
+ page: options.page ?? 1,
741
+ perPage: options.perPage ?? 20
742
+ };
743
+ const data = await this.request(QUERY_MEDIA_BY_SEASON, variables);
744
+ return { pageInfo: data.Page.pageInfo, results: data.Page.media };
745
+ }
746
+ /**
747
+ * Get a user's anime or manga list.
748
+ *
749
+ * Provide either `userId` or `userName` to identify the user.
750
+ * Requires `type` (ANIME or MANGA). Optionally filter by list status.
751
+ *
752
+ * @param options - User identifier, media type, and optional filters
753
+ * @returns Paginated list of media list entries
754
+ *
755
+ * @example
756
+ * ```ts
757
+ * import { MediaType, MediaListStatus } from "ani-client";
758
+ *
759
+ * // Get a user's completed anime list
760
+ * const list = await client.getUserMediaList({
761
+ * userName: "AniList",
762
+ * type: MediaType.ANIME,
763
+ * status: MediaListStatus.COMPLETED,
764
+ * });
765
+ * list.results.forEach((entry) =>
766
+ * console.log(`${entry.media.title.romaji} — ${entry.score}/100`)
767
+ * );
768
+ * ```
769
+ */
770
+ async getUserMediaList(options) {
771
+ if (!options.userId && !options.userName) {
772
+ throw new Error("Either userId or userName must be provided");
773
+ }
774
+ const variables = {
775
+ userId: options.userId,
776
+ userName: options.userName,
777
+ type: options.type,
778
+ status: options.status,
779
+ sort: options.sort,
780
+ page: options.page ?? 1,
781
+ perPage: options.perPage ?? 20
782
+ };
783
+ const data = await this.request(QUERY_USER_MEDIA_LIST, variables);
784
+ return { pageInfo: data.Page.pageInfo, results: data.Page.mediaList };
785
+ }
786
+ /**
787
+ * Fetch a studio by its AniList ID.
788
+ *
789
+ * Returns studio details along with its most popular productions.
790
+ *
791
+ * @param id - The AniList studio ID
792
+ */
793
+ async getStudio(id) {
794
+ const data = await this.request(QUERY_STUDIO_BY_ID, { id });
795
+ return data.Studio;
796
+ }
797
+ /**
798
+ * Search for studios by name.
799
+ *
800
+ * @param options - Search / pagination parameters
801
+ * @returns Paginated list of studios
802
+ *
803
+ * @example
804
+ * ```ts
805
+ * const studios = await client.searchStudios({ query: "MAPPA" });
806
+ * ```
807
+ */
808
+ async searchStudios(options = {}) {
809
+ const variables = {
810
+ search: options.query,
811
+ page: options.page ?? 1,
812
+ perPage: options.perPage ?? 20
813
+ };
814
+ const data = await this.request(QUERY_STUDIO_SEARCH, variables);
815
+ return { pageInfo: data.Page.pageInfo, results: data.Page.studios };
816
+ }
817
+ /**
818
+ * Get all available genres on AniList.
819
+ *
820
+ * @returns Array of genre strings (e.g. "Action", "Adventure", ...)
821
+ */
822
+ async getGenres() {
823
+ const data = await this.request(QUERY_GENRES);
824
+ return data.GenreCollection;
825
+ }
826
+ /**
827
+ * Get all available media tags on AniList.
828
+ *
829
+ * @returns Array of tag objects with id, name, description, category, isAdult
830
+ */
831
+ async getTags() {
832
+ const data = await this.request(QUERY_TAGS);
833
+ return data.MediaTagCollection;
834
+ }
835
+ /**
836
+ * Auto-paginating async iterator.
837
+ *
838
+ * Wraps any paginated method and yields individual items across all pages.
839
+ * Stops when `hasNextPage` is `false` or `maxPages` is reached.
840
+ *
841
+ * @param fetchPage - A function that takes a page number and returns a `PagedResult<T>`
842
+ * @param maxPages - Maximum number of pages to fetch (default: Infinity)
843
+ * @returns An async iterable iterator of individual items
844
+ *
845
+ * @example
846
+ * ```ts
847
+ * // Iterate over all search results
848
+ * for await (const anime of client.paginate((page) =>
849
+ * client.searchMedia({ query: "Naruto", page, perPage: 10 })
850
+ * )) {
851
+ * console.log(anime.title.romaji);
852
+ * }
853
+ *
854
+ * // Limit to 3 pages
855
+ * for await (const anime of client.paginate(
856
+ * (page) => client.getTrending(MediaType.ANIME, page, 20),
857
+ * 3,
858
+ * )) {
859
+ * console.log(anime.title.romaji);
860
+ * }
861
+ * ```
862
+ */
863
+ async *paginate(fetchPage, maxPages = Infinity) {
864
+ let page = 1;
865
+ let hasNext = true;
866
+ while (hasNext && page <= maxPages) {
867
+ const result = await fetchPage(page);
868
+ for (const item of result.results) {
869
+ yield item;
870
+ }
871
+ hasNext = result.pageInfo.hasNextPage === true;
872
+ page++;
873
+ }
874
+ }
446
875
  /**
447
876
  * Clear the entire response cache.
448
877
  */
@@ -511,6 +940,17 @@ var MediaSort = /* @__PURE__ */ ((MediaSort2) => {
511
940
  MediaSort2["SEARCH_MATCH"] = "SEARCH_MATCH";
512
941
  return MediaSort2;
513
942
  })(MediaSort || {});
943
+ var AiringSort = /* @__PURE__ */ ((AiringSort2) => {
944
+ AiringSort2["ID"] = "ID";
945
+ AiringSort2["ID_DESC"] = "ID_DESC";
946
+ AiringSort2["MEDIA_ID"] = "MEDIA_ID";
947
+ AiringSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
948
+ AiringSort2["TIME"] = "TIME";
949
+ AiringSort2["TIME_DESC"] = "TIME_DESC";
950
+ AiringSort2["EPISODE"] = "EPISODE";
951
+ AiringSort2["EPISODE_DESC"] = "EPISODE_DESC";
952
+ return AiringSort2;
953
+ })(AiringSort || {});
514
954
  var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
515
955
  CharacterSort2["ID"] = "ID";
516
956
  CharacterSort2["ROLE"] = "ROLE";
@@ -518,16 +958,86 @@ var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
518
958
  CharacterSort2["FAVOURITES"] = "FAVOURITES";
519
959
  return CharacterSort2;
520
960
  })(CharacterSort || {});
961
+ var MediaRelationType = /* @__PURE__ */ ((MediaRelationType2) => {
962
+ MediaRelationType2["ADAPTATION"] = "ADAPTATION";
963
+ MediaRelationType2["PREQUEL"] = "PREQUEL";
964
+ MediaRelationType2["SEQUEL"] = "SEQUEL";
965
+ MediaRelationType2["PARENT"] = "PARENT";
966
+ MediaRelationType2["SIDE_STORY"] = "SIDE_STORY";
967
+ MediaRelationType2["CHARACTER"] = "CHARACTER";
968
+ MediaRelationType2["SUMMARY"] = "SUMMARY";
969
+ MediaRelationType2["ALTERNATIVE"] = "ALTERNATIVE";
970
+ MediaRelationType2["SPIN_OFF"] = "SPIN_OFF";
971
+ MediaRelationType2["OTHER"] = "OTHER";
972
+ MediaRelationType2["SOURCE"] = "SOURCE";
973
+ MediaRelationType2["COMPILATION"] = "COMPILATION";
974
+ MediaRelationType2["CONTAINS"] = "CONTAINS";
975
+ return MediaRelationType2;
976
+ })(MediaRelationType || {});
977
+ var RecommendationSort = /* @__PURE__ */ ((RecommendationSort2) => {
978
+ RecommendationSort2["ID"] = "ID";
979
+ RecommendationSort2["ID_DESC"] = "ID_DESC";
980
+ RecommendationSort2["RATING"] = "RATING";
981
+ RecommendationSort2["RATING_DESC"] = "RATING_DESC";
982
+ return RecommendationSort2;
983
+ })(RecommendationSort || {});
984
+ var MediaListStatus = /* @__PURE__ */ ((MediaListStatus2) => {
985
+ MediaListStatus2["CURRENT"] = "CURRENT";
986
+ MediaListStatus2["PLANNING"] = "PLANNING";
987
+ MediaListStatus2["COMPLETED"] = "COMPLETED";
988
+ MediaListStatus2["DROPPED"] = "DROPPED";
989
+ MediaListStatus2["PAUSED"] = "PAUSED";
990
+ MediaListStatus2["REPEATING"] = "REPEATING";
991
+ return MediaListStatus2;
992
+ })(MediaListStatus || {});
993
+ var MediaListSort = /* @__PURE__ */ ((MediaListSort2) => {
994
+ MediaListSort2["MEDIA_ID"] = "MEDIA_ID";
995
+ MediaListSort2["MEDIA_ID_DESC"] = "MEDIA_ID_DESC";
996
+ MediaListSort2["SCORE"] = "SCORE";
997
+ MediaListSort2["SCORE_DESC"] = "SCORE_DESC";
998
+ MediaListSort2["STATUS"] = "STATUS";
999
+ MediaListSort2["STATUS_DESC"] = "STATUS_DESC";
1000
+ MediaListSort2["PROGRESS"] = "PROGRESS";
1001
+ MediaListSort2["PROGRESS_DESC"] = "PROGRESS_DESC";
1002
+ MediaListSort2["PROGRESS_VOLUMES"] = "PROGRESS_VOLUMES";
1003
+ MediaListSort2["PROGRESS_VOLUMES_DESC"] = "PROGRESS_VOLUMES_DESC";
1004
+ MediaListSort2["REPEAT"] = "REPEAT";
1005
+ MediaListSort2["REPEAT_DESC"] = "REPEAT_DESC";
1006
+ MediaListSort2["PRIORITY"] = "PRIORITY";
1007
+ MediaListSort2["PRIORITY_DESC"] = "PRIORITY_DESC";
1008
+ MediaListSort2["STARTED_ON"] = "STARTED_ON";
1009
+ MediaListSort2["STARTED_ON_DESC"] = "STARTED_ON_DESC";
1010
+ MediaListSort2["FINISHED_ON"] = "FINISHED_ON";
1011
+ MediaListSort2["FINISHED_ON_DESC"] = "FINISHED_ON_DESC";
1012
+ MediaListSort2["ADDED_TIME"] = "ADDED_TIME";
1013
+ MediaListSort2["ADDED_TIME_DESC"] = "ADDED_TIME_DESC";
1014
+ MediaListSort2["UPDATED_TIME"] = "UPDATED_TIME";
1015
+ MediaListSort2["UPDATED_TIME_DESC"] = "UPDATED_TIME_DESC";
1016
+ MediaListSort2["MEDIA_TITLE_ROMAJI"] = "MEDIA_TITLE_ROMAJI";
1017
+ MediaListSort2["MEDIA_TITLE_ROMAJI_DESC"] = "MEDIA_TITLE_ROMAJI_DESC";
1018
+ MediaListSort2["MEDIA_TITLE_ENGLISH"] = "MEDIA_TITLE_ENGLISH";
1019
+ MediaListSort2["MEDIA_TITLE_ENGLISH_DESC"] = "MEDIA_TITLE_ENGLISH_DESC";
1020
+ MediaListSort2["MEDIA_TITLE_NATIVE"] = "MEDIA_TITLE_NATIVE";
1021
+ MediaListSort2["MEDIA_TITLE_NATIVE_DESC"] = "MEDIA_TITLE_NATIVE_DESC";
1022
+ MediaListSort2["MEDIA_POPULARITY"] = "MEDIA_POPULARITY";
1023
+ MediaListSort2["MEDIA_POPULARITY_DESC"] = "MEDIA_POPULARITY_DESC";
1024
+ return MediaListSort2;
1025
+ })(MediaListSort || {});
521
1026
 
1027
+ exports.AiringSort = AiringSort;
522
1028
  exports.AniListClient = AniListClient;
523
1029
  exports.AniListError = AniListError;
524
1030
  exports.CharacterSort = CharacterSort;
525
1031
  exports.MediaFormat = MediaFormat;
1032
+ exports.MediaListSort = MediaListSort;
1033
+ exports.MediaListStatus = MediaListStatus;
1034
+ exports.MediaRelationType = MediaRelationType;
526
1035
  exports.MediaSeason = MediaSeason;
527
1036
  exports.MediaSort = MediaSort;
528
1037
  exports.MediaStatus = MediaStatus;
529
1038
  exports.MediaType = MediaType;
530
1039
  exports.MemoryCache = MemoryCache;
531
1040
  exports.RateLimiter = RateLimiter;
1041
+ exports.RecommendationSort = RecommendationSort;
532
1042
  //# sourceMappingURL=index.js.map
533
1043
  //# sourceMappingURL=index.js.map