diff --git a/src/controllers/auth/anilist/getWatchingTitles.ts b/src/controllers/auth/anilist/getWatchingTitles.ts deleted file mode 100644 index c6917cd..0000000 --- a/src/controllers/auth/anilist/getWatchingTitles.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { graphql } from "gql.tada"; -import { GraphQLClient } from "graphql-request"; - -import { sleep } from "~/libs/sleep"; - -const GetWatchingTitlesQuery = graphql(` - query GetWatchingTitles($userName: String!, $page: Int!) { - Page(page: $page, perPage: 50) { - mediaList( - userName: $userName - type: ANIME - sort: UPDATED_TIME_DESC - status_in: [CURRENT, REPEATING, PLANNING] - ) { - media { - id - idMal - title { - english - userPreferred - } - description - episodes - genres - status - bannerImage - averageScore - coverImage { - extraLarge - large - medium - } - countryOfOrigin - mediaListEntry { - id - progress - status - updatedAt - } - nextAiringEpisode { - timeUntilAiring - airingAt - episode - } - } - } - pageInfo { - currentPage - hasNextPage - perPage - total - } - } - } -`); - -export function getWatchingTitles( - username: string, - page: number, - aniListToken: string, -): Promise { - const client = new GraphQLClient("https://graphql.anilist.co/"); - - return client - .request( - GetWatchingTitlesQuery, - { userName: username, page }, - { Authorization: `Bearer ${aniListToken}` }, - ) - .then((data) => data?.Page!) - .catch((err) => { - console.error("Failed to get watching titles"); - console.error(err); - - const response = err.response; - if (response.status === 429) { - console.log("429, retrying in", response.headers.get("Retry-After")); - return sleep(Number(response.headers.get("Retry-After")!) * 1000).then( - () => getWatchingTitles(username, page, aniListToken), - ); - } - - throw err; - }); -} - -type GetWatchingTitles = { - mediaList: - | ({ - media: { - id: number; - idMal: number | null; - title: { - english: string | null; - userPreferred: string | null; - } | null; - description: string | null; - episodes: number | null; - genres: (string | null)[] | null; - status: - | "FINISHED" - | "RELEASING" - | "NOT_YET_RELEASED" - | "CANCELLED" - | "HIATUS" - | null; - bannerImage: string | null; - averageScore: number | null; - coverImage: { - extraLarge: string | null; - large: string | null; - medium: string | null; - } | null; - countryOfOrigin: unknown; - mediaListEntry: { - id: number; - progress: number | null; - status: - | "CURRENT" - | "REPEATING" - | "PLANNING" - | "COMPLETED" - | "DROPPED" - | "PAUSED" - | null; - updatedAt: number; - } | null; - nextAiringEpisode: { - timeUntilAiring: number; - airingAt: number; - episode: number; - } | null; - } | null; - } | null)[] - | null; - pageInfo: { - currentPage: number | null; - hasNextPage: boolean | null; - perPage: number | null; - total: number | null; - } | null; -}; diff --git a/src/controllers/auth/anilist/index.ts b/src/controllers/auth/anilist/index.ts index 4ab0856..77e686e 100644 --- a/src/controllers/auth/anilist/index.ts +++ b/src/controllers/auth/anilist/index.ts @@ -1,4 +1,5 @@ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi"; +import { env } from "cloudflare:workers"; import { streamSSE } from "hono/streaming"; import { fetchEpisodes } from "~/controllers/episodes/getByAniListId"; @@ -10,7 +11,6 @@ import { ErrorResponse, ErrorResponseSchema } from "~/types/schema"; import { Title } from "~/types/title"; import { getUser } from "./getUser"; -import { getWatchingTitles } from "./getWatchingTitles"; const UserSchema = z.object({ name: z.string(), @@ -129,11 +129,15 @@ app.openapi(route, async (c) => { let hasNextPage = true; do { - const { mediaList, pageInfo } = await getWatchingTitles( - user.name!, - currentPage++, - aniListToken, - ); + const stub = env.ANILIST_DO.getByName(user.name!); + const { mediaList, pageInfo } = await stub + .getTitles( + user.name!, + currentPage, + ["CURRENT", "PLANNING", "PAUSED", "REPEATING"], + aniListToken, + ) + .then((data) => data!); if (!mediaList) { break; } diff --git a/src/libs/anilist/anilist-do.ts b/src/libs/anilist/anilist-do.ts index 22c1117..a402512 100644 --- a/src/libs/anilist/anilist-do.ts +++ b/src/libs/anilist/anilist-do.ts @@ -12,6 +12,7 @@ import { GetUpcomingTitlesQuery, GetUserProfileQuery, GetUserQuery, + GetWatchingTitlesQuery, MarkEpisodeAsWatchedMutation, MarkTitleAsWatchedMutation, NextSeasonPopularQuery, @@ -260,6 +261,33 @@ export class AnilistDurableObject extends DurableObject { return data?.SaveMediaListEntry; } + async getTitles( + userName: string, + page: number, + statusFilters: ( + | "CURRENT" + | "COMPLETED" + | "PLANNING" + | "DROPPED" + | "PAUSED" + | "REPEATING" + )[], + aniListToken: string, + ) { + return await this.handleCachedRequest( + `titles:${JSON.stringify({ page, statusFilters })}`, + async () => { + const data = await this.fetchFromAnilist( + GetWatchingTitlesQuery, + { userName, page, statusFilters }, + aniListToken, + ); + return data?.Page; + }, + 60 * 60 * 1000, + ); + } + // Helper to handle caching logic async handleCachedRequest( key: string, diff --git a/src/libs/anilist/queries.ts b/src/libs/anilist/queries.ts index 08dd640..de28eee 100644 --- a/src/libs/anilist/queries.ts +++ b/src/libs/anilist/queries.ts @@ -266,3 +266,36 @@ export const NextSeasonPopularQuery = graphql( `, [HomeTitleFragment], ); + +export const GetWatchingTitlesQuery = graphql( + ` + query GetWatchingTitles( + $userName: String! + $page: Int! + $statusFilters: [MediaListStatus!] + ) { + Page(page: $page, perPage: 50) { + mediaList( + userName: $userName + type: ANIME + sort: UPDATED_TIME_DESC + status_in: $statusFilters + ) { + media { + ...Media + mediaListEntry { + updatedAt + } + } + } + pageInfo { + currentPage + hasNextPage + perPage + total + } + } + } + `, + [MediaFragment], +);