diff --git a/src/controllers/watch-status/index.ts b/src/controllers/watch-status/index.ts index 8c87f42..c1b7047 100644 --- a/src/controllers/watch-status/index.ts +++ b/src/controllers/watch-status/index.ts @@ -1,5 +1,4 @@ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi"; -import type { HonoRequest } from "hono"; import { AnilistUpdateType } from "~/libs/anilist/updateType.ts"; import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode"; diff --git a/src/libs/anilist/anilist-do.ts b/src/libs/anilist/anilist-do.ts index fe2a42e..65ed4cd 100644 --- a/src/libs/anilist/anilist-do.ts +++ b/src/libs/anilist/anilist-do.ts @@ -1,6 +1,7 @@ import type { TypedDocumentNode } from "@graphql-typed-document-node/core"; import { DurableObject } from "cloudflare:workers"; import { print } from "graphql"; +import { DateTime } from "luxon"; import { z } from "zod"; import { @@ -99,7 +100,7 @@ export class AnilistDurableObject extends DurableObject { }); return data?.Media; }, - 60 * 60 * 1000, + DateTime.now().plus({ hours: 1 }), ); } @@ -114,7 +115,7 @@ export class AnilistDurableObject extends DurableObject { }); return data?.Page; }, - 60 * 60 * 1000, + DateTime.now().plus({ hours: 1 }), ); } @@ -156,7 +157,7 @@ export class AnilistDurableObject extends DurableObject { page, }).then((data) => data?.Page); }, - 24 * 60 * 60 * 1000, + DateTime.now().plus({ days: 1 }), ); } @@ -169,15 +170,14 @@ export class AnilistDurableObject extends DurableObject { return this.handleCachedRequest( `popular:${JSON.stringify({ page, limit, season, seasonYear })}`, async () => { - const data = await this.fetchFromAnilist(GetPopularTitlesQuery, { + return this.fetchFromAnilist(GetPopularTitlesQuery, { page, limit, season, seasonYear, - }); - return data?.Page; + }).then((data) => data?.Page); }, - 24 * 60 * 60 * 1000, + DateTime.now().plus({ days: 1 }), ); } @@ -191,7 +191,7 @@ export class AnilistDurableObject extends DurableObject { }); return data?.Page; }, - 24 * 60 * 60 * 1000, + DateTime.now().plus({ days: 1 }), ); } @@ -210,7 +210,7 @@ export class AnilistDurableObject extends DurableObject { }); return data?.Page; }, - 24 * 60 * 60 * 1000, + DateTime.now().plus({ days: 1 }), ); } @@ -218,10 +218,10 @@ export class AnilistDurableObject extends DurableObject { return this.handleCachedRequest( `user:${token}`, async () => { - const data = await this.fetchFromAnilist(GetUserQuery, {}, token); + const data = await this.fetchFromAnilist(GetUserQuery, {}, { token }); return data?.Viewer; }, - 60 * 60 * 24 * 30 * 1000, + DateTime.now().plus({ days: 30 }), ); } @@ -232,11 +232,11 @@ export class AnilistDurableObject extends DurableObject { const data = await this.fetchFromAnilist( GetUserProfileQuery, { token }, - token, + { token }, ); return data?.Viewer; }, - 60 * 60 * 24 * 30 * 1000, + DateTime.now().plus({ days: 30 }), ); } @@ -248,7 +248,7 @@ export class AnilistDurableObject extends DurableObject { const data = await this.fetchFromAnilist( MarkEpisodeAsWatchedMutation, { titleId, episodeNumber }, - token, + { token }, ); return data?.SaveMediaListEntry; } @@ -257,7 +257,7 @@ export class AnilistDurableObject extends DurableObject { const data = await this.fetchFromAnilist( MarkTitleAsWatchedMutation, { titleId }, - token, + { token }, ); return data?.SaveMediaListEntry; } @@ -266,7 +266,7 @@ export class AnilistDurableObject extends DurableObject { async handleCachedRequest( key: string, fetcher: () => Promise, - ttl?: number | ((data: T) => number | undefined), + ttl?: DateTime | ((data: T) => DateTime | undefined), ) { const cache = await this.state.storage.get(key); console.debug(`Retrieving request ${key} from cache:`, cache != null); @@ -278,9 +278,8 @@ export class AnilistDurableObject extends DurableObject { await this.state.storage.put(key, result); const calculatedTtl = typeof ttl === "function" ? ttl(result) : ttl; - - if (calculatedTtl && calculatedTtl > 0) { - const alarmTime = Date.now() + calculatedTtl; + if (calculatedTtl) { + const alarmTime = calculatedTtl.toMillis(); await this.state.storage.setAlarm(alarmTime); await this.state.storage.put(`alarm:${key}`, alarmTime); } @@ -307,8 +306,11 @@ export class AnilistDurableObject extends DurableObject { async fetchFromAnilist( query: TypedDocumentNode, variables: Variables, - token?: string | undefined, - ): Promise { + { + token, + shouldRetryOnRateLimit = true, + }: { token?: string | undefined; shouldRetryOnRateLimit?: boolean } = {}, + ): Promise { const headers: any = { "Content-Type": "application/json", }; @@ -338,14 +340,17 @@ export class AnilistDurableObject extends DurableObject { }); // 1. Handle Rate Limiting (429) - if (response.status === 429) { + if (shouldRetryOnRateLimit && response.status === 429) { const retryAfter = await response - .json() + .json<{ headers: Record }>() .then(({ headers }) => new Headers(headers).get("Retry-After")); console.log("429, retrying in", retryAfter); await sleep(Number(retryAfter || 1) * 1000); // specific fallback or ensure logic - return this.fetchFromAnilist(query, variables, token); + return this.fetchFromAnilist(query, variables, { + token, + shouldRetryOnRateLimit: false, + }); } // 2. Handle HTTP Errors (like 404 or 500)