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.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
  `;
@@ -213,6 +227,124 @@ query ($type: MediaType, $sort: [MediaSort], $page: Int, $perPage: Int) {
213
227
  }
214
228
  }
215
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
+ }`;
216
348
 
217
349
  // src/errors/index.ts
218
350
  var AniListError = class extends Error {
@@ -549,6 +681,195 @@ var AniListClient = class {
549
681
  const data = await this.request(QUERY_PLANNING, variables);
550
682
  return { pageInfo: data.Page.pageInfo, results: data.Page.media };
551
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
+ }
552
873
  /**
553
874
  * Clear the entire response cache.
554
875
  */
@@ -635,7 +956,72 @@ var CharacterSort = /* @__PURE__ */ ((CharacterSort2) => {
635
956
  CharacterSort2["FAVOURITES"] = "FAVOURITES";
636
957
  return CharacterSort2;
637
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 || {});
638
1024
 
639
- export { AiringSort, 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 };
640
1026
  //# sourceMappingURL=index.mjs.map
641
1027
  //# sourceMappingURL=index.mjs.map