fix: rename route from /upcoming/titles to /upcoming-titles
This commit is contained in:
89
src/controllers/upcoming-titles/anilist.ts
Normal file
89
src/controllers/upcoming-titles/anilist.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { graphql } from "gql.tada";
|
||||
import { GraphQLClient } from "graphql-request";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
import { getValue, setValue } from "~/models/kv";
|
||||
import type { Env } from "~/types/env";
|
||||
import type { Title } from "~/types/title";
|
||||
import { MediaFragment } from "~/types/title/mediaFragment";
|
||||
|
||||
const GetUpcomingTitlesQuery = graphql(
|
||||
`
|
||||
query GetUpcomingTitles(
|
||||
$page: Int!
|
||||
$airingAtLowerBound: Int!
|
||||
$airingAtUpperBound: Int!
|
||||
) {
|
||||
Page(page: $page) {
|
||||
airingSchedules(
|
||||
notYetAired: true
|
||||
sort: TIME
|
||||
airingAt_lesser: $airingAtUpperBound
|
||||
airingAt_greater: $airingAtLowerBound
|
||||
) {
|
||||
id
|
||||
airingAt
|
||||
timeUntilAiring
|
||||
episode
|
||||
media {
|
||||
...Media
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
[MediaFragment],
|
||||
);
|
||||
|
||||
type AiringSchedule = {
|
||||
media: Title;
|
||||
episode: number;
|
||||
timeUntilAiring: number;
|
||||
airingAt: number;
|
||||
id: number;
|
||||
};
|
||||
|
||||
export async function getUpcomingTitlesFromAnilist(env: Env) {
|
||||
const client = new GraphQLClient("https://graphql.anilist.co/");
|
||||
const lastCheckedScheduleAt = await getValue(
|
||||
env,
|
||||
"schedule_last_checked_at",
|
||||
).then((value) => (value ? Number(value) : DateTime.now().toUnixInteger()));
|
||||
console.log(lastCheckedScheduleAt);
|
||||
const twoDaysFromNow = DateTime.now().plus({ days: 2 }).toUnixInteger();
|
||||
|
||||
let currentPage = 1;
|
||||
let scheduleList: AiringSchedule[] = [];
|
||||
let shouldContinue = true;
|
||||
|
||||
do {
|
||||
const { Page } = await client.request(GetUpcomingTitlesQuery, {
|
||||
page: currentPage++,
|
||||
airingAtLowerBound: lastCheckedScheduleAt,
|
||||
airingAtUpperBound: twoDaysFromNow,
|
||||
});
|
||||
|
||||
const { airingSchedules, pageInfo } = Page!;
|
||||
scheduleList = scheduleList.concat(
|
||||
airingSchedules!.filter(
|
||||
(schedule): schedule is AiringSchedule => !!schedule,
|
||||
),
|
||||
);
|
||||
shouldContinue = pageInfo?.hasNextPage ?? false;
|
||||
} while (shouldContinue);
|
||||
|
||||
if (scheduleList.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
await setValue(
|
||||
env,
|
||||
"schedule_last_checked_at",
|
||||
scheduleList[scheduleList.length - 1].airingAt.toString(),
|
||||
);
|
||||
|
||||
return scheduleList;
|
||||
}
|
||||
76
src/controllers/upcoming-titles/index.ts
Normal file
76
src/controllers/upcoming-titles/index.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Hono } from "hono";
|
||||
import { env } from "hono/adapter";
|
||||
import mapKeys from "lodash.mapkeys";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
import { Case, changeStringCase } from "~/libs/changeStringCase";
|
||||
import type { AdminSdkCredentials } from "~/libs/fcm/getGoogleAuthToken";
|
||||
import { sendFcmMessage } from "~/libs/fcm/sendFcmMessage";
|
||||
import { verifyQstashHeader } from "~/libs/qstash/verifyQstashHeader";
|
||||
import { readEnvVariable } from "~/libs/readEnvVariable";
|
||||
import type { Env } from "~/types/env";
|
||||
import { ErrorResponse, SuccessResponse } from "~/types/schema";
|
||||
|
||||
import { getUpcomingTitlesFromAnilist } from "./anilist";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
app.post("/", async (c) => {
|
||||
if (
|
||||
!(await verifyQstashHeader(
|
||||
env<Env, typeof c>(c, "workerd"),
|
||||
c.req.path,
|
||||
c.req.header("Upstash-Signature"),
|
||||
await c.req.text(),
|
||||
))
|
||||
) {
|
||||
return c.json(ErrorResponse, { status: 401 });
|
||||
}
|
||||
|
||||
const titles = await getUpcomingTitlesFromAnilist(
|
||||
env<Env, typeof c>(c, "workerd"),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
titles.map(async (title) => {
|
||||
const titleName =
|
||||
title.media.title?.userPreferred ??
|
||||
title.media.title?.english ??
|
||||
"Unknown Title";
|
||||
|
||||
return sendFcmMessage(
|
||||
mapKeys(
|
||||
readEnvVariable<AdminSdkCredentials>(c.env, "ADMIN_SDK_JSON"),
|
||||
(_, key) => changeStringCase(key, Case.snake_case, Case.camelCase),
|
||||
) as unknown as AdminSdkCredentials,
|
||||
{
|
||||
topic: "newTitles",
|
||||
data: {
|
||||
type: "new_title",
|
||||
aniListId: title.media.id.toString(),
|
||||
title: titleName,
|
||||
airingAt: title.airingAt.toString(),
|
||||
},
|
||||
notification: {
|
||||
title: "New Series Alert",
|
||||
body: `${titleName} will be released on ${DateTime.fromSeconds(title.airingAt).toRelative({ unit: "days" })}`,
|
||||
image:
|
||||
title.media.coverImage?.medium ??
|
||||
title.media.coverImage?.large ??
|
||||
title.media.coverImage?.extraLarge ??
|
||||
undefined,
|
||||
},
|
||||
android: {
|
||||
notification: {
|
||||
click_action: "HANDLE_FCM_NOTIFICATION",
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
return c.json(SuccessResponse, 200);
|
||||
});
|
||||
|
||||
export default app;
|
||||
Reference in New Issue
Block a user