ani-client 1.1.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
  `;
@@ -215,6 +229,124 @@ query ($type: MediaType, $sort: [MediaSort], $page: Int, $perPage: Int) {
215
229
  }
216
230
  }
217
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
+ }`;
218
350
 
219
351
  // src/errors/index.ts
220
352
  var AniListError = class extends Error {
@@ -551,6 +683,195 @@ var AniListClient = class {
551
683
  const data = await this.request(QUERY_PLANNING, variables);
552
684
  return { pageInfo: data.Page.pageInfo, results: data.Page.media };
553
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
+ }
554
875
  /**
555
876
  * Clear the entire response cache.
556
877
  */
@@ -637,17 +958,86 @@ var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
637
958
  CharacterSort2["FAVOURITES"] = "FAVOURITES";
638
959
  return CharacterSort2;
639
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 || {});
640
1026
 
641
1027
  exports.AiringSort = AiringSort;
642
1028
  exports.AniListClient = AniListClient;
643
1029
  exports.AniListError = AniListError;
644
1030
  exports.CharacterSort = CharacterSort;
645
1031
  exports.MediaFormat = MediaFormat;
1032
+ exports.MediaListSort = MediaListSort;
1033
+ exports.MediaListStatus = MediaListStatus;
1034
+ exports.MediaRelationType = MediaRelationType;
646
1035
  exports.MediaSeason = MediaSeason;
647
1036
  exports.MediaSort = MediaSort;
648
1037
  exports.MediaStatus = MediaStatus;
649
1038
  exports.MediaType = MediaType;
650
1039
  exports.MemoryCache = MemoryCache;
651
1040
  exports.RateLimiter = RateLimiter;
1041
+ exports.RecommendationSort = RecommendationSort;
652
1042
  //# sourceMappingURL=index.js.map
653
1043
  //# sourceMappingURL=index.js.map