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