refactor: Replace generic AnilistDurableObject fetch endpoint with dedicated methods and update their usage.

This commit is contained in:
2025-11-29 06:22:08 -05:00
parent b1e46ad6eb
commit 25f5f80696
13 changed files with 869 additions and 655 deletions

View File

@@ -6,25 +6,19 @@ export async function getUser(aniListToken: string): Promise<User> {
const durableObjectId = env.ANILIST_DO.idFromName("GLOBAL");
const stub = env.ANILIST_DO.get(durableObjectId);
const response = await stub.fetch("http://anilist-do/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
operationName: "GetUser",
variables: { token: aniListToken },
}),
});
if (!response.ok) {
if (response.status === 401) {
let data;
try {
data = await stub.getUser(aniListToken);
} catch (e: any) {
if (e.message.includes("401")) {
return null;
}
throw new Error(`Failed to fetch user: ${response.statusText}`);
throw e;
}
const data = (await response.json()) as any;
if (!data) {
return null;
}
return {
...data,

View File

@@ -9,32 +9,20 @@ export async function markEpisodeAsWatched(
const durableObjectId = env.ANILIST_DO.idFromName("GLOBAL");
const stub = env.ANILIST_DO.get(durableObjectId);
const operationName = markTitleAsComplete
? "MarkTitleAsWatched"
: "MarkEpisodeAsWatched";
const variables = markTitleAsComplete
? { titleId, token: aniListToken }
: { titleId, episodeNumber, token: aniListToken };
const response = await stub.fetch("http://anilist-do/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
operationName,
variables,
}),
});
if (!response.ok) {
throw new Error(
`Failed to mark episode as watched: ${response.statusText}`,
let data;
if (markTitleAsComplete) {
data = await stub.markTitleAsWatched(titleId, aniListToken);
} else {
data = await stub.markEpisodeAsWatched(
titleId,
episodeNumber,
aniListToken,
);
}
const data = (await response.json()) as any;
if (!data) {
throw new Error(`Failed to mark episode as watched`);
}
return {
...data?.user,

View File

@@ -30,27 +30,12 @@ export async function getUpcomingTitlesFromAnilist(req: HonoRequest) {
let shouldContinue = true;
do {
const response = await stub.fetch("http://anilist-do/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
operationName: "GetUpcomingTitles",
variables: {
page: currentPage++,
airingAtLowerBound: lastCheckedScheduleAt,
airingAtUpperBound: twoDaysFromNow,
},
}),
});
const Page = await stub.getUpcomingTitles(
currentPage++,
lastCheckedScheduleAt,
twoDaysFromNow,
);
if (!response.ok) {
// If failed, break loop or handle error. For now, break.
break;
}
const Page = (await response.json()) as any;
if (!Page) break;
const { airingSchedules, pageInfo } = Page;

View File

@@ -7,36 +7,21 @@ import { mapTitle } from "../mapTitle";
export async function fetchPopularTitlesFromAnilist(
limit: number,
): Promise<any> {
const durableObjectId = env.ANILIST_DO.idFromName("GLOBAL");
const stub = env.ANILIST_DO.get(durableObjectId);
const stub = env.ANILIST_DO.getByName("GLOBAL");
const {
current: { season: currentSeason, year: currentYear },
next: { season: nextSeason, year: nextYear },
} = getCurrentAndNextSeason();
const response = await stub.fetch("http://anilist-do/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
operationName: "BrowsePopular",
variables: {
limit,
season: currentSeason,
seasonYear: currentYear,
nextSeason,
nextYear,
},
}),
});
const data = await stub.browsePopular(
currentSeason,
currentYear,
nextSeason,
nextYear,
limit,
);
if (!response.ok) {
return undefined;
}
const data = (await response.json()) as any;
if (!data) return undefined;
const trendingTitles = data.trending?.media?.map((title: any) =>
@@ -53,29 +38,11 @@ export async function fetchPopularTitlesFromAnilist(
};
}
const nextSeasonResponse = await stub.fetch("http://anilist-do/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
operationName: "NextSeasonPopular",
variables: {
limit,
nextSeason,
nextYear,
},
}),
});
if (!nextSeasonResponse.ok) {
return {
trending: trendingTitles,
popular: popularSeasonTitles,
};
}
const nextSeasonData = (await nextSeasonResponse.json()) as any;
const nextSeasonData = await stub.nextSeasonPopular(
nextSeason,
nextYear,
limit,
);
return {
trending: trendingTitles,

View File

@@ -15,50 +15,31 @@ export async function fetchPopularTitlesFromAnilist(
const { current, next } = getCurrentAndNextSeason();
let operationName = "";
let variables: any = { limit, page };
let data;
switch (category) {
case "trending":
operationName = "GetTrendingTitles";
data = await stub.getTrendingTitles(page, limit);
break;
case "popular":
operationName = "GetPopularTitles";
variables = {
...variables,
season: current.season,
seasonYear: current.year,
};
data = await stub.getPopularTitles(
page,
limit,
current.season,
current.year,
);
break;
case "upcoming":
operationName = "NextSeasonPopular";
variables = {
...variables,
nextSeason: next.season,
nextYear: next.year,
};
data = await stub.nextSeasonPopular(next.season, next.year, limit);
break;
default:
throw new Error(`Unknown category: ${category}`);
}
const response = await stub.fetch("http://anilist-do/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
operationName,
variables,
}),
});
if (!response.ok) {
if (!data) {
return { results: [], hasNextPage: false };
}
const data = (await response.json()) as any;
return {
results: data?.media?.map((title: any) => mapTitle(title)),
hasNextPage: data?.pageInfo?.hasNextPage,

View File

@@ -8,27 +8,13 @@ export async function fetchSearchResultsFromAnilist(
const durableObjectId = env.ANILIST_DO.idFromName("GLOBAL");
const stub = env.ANILIST_DO.get(durableObjectId);
const response = await stub.fetch("http://anilist-do/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
operationName: "Search",
variables: { query, page, limit },
}),
});
const Page = await stub.search(query, page, limit);
if (!response.ok) {
if (!Page || Page.media?.length === 0) {
return undefined;
}
const data = (await response.json()) as any;
if (!data || data.media?.length === 0) {
return undefined;
}
const { media: results, pageInfo } = data;
const { media: results, pageInfo } = Page;
return {
results: results?.map((result: any) => {
if (!result) return null;