145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
import { zValidator } from "@hono/zod-validator";
|
|
import { Client } from "@upstash/qstash";
|
|
import { Hono, type HonoRequest } from "hono";
|
|
import { env } from "hono/adapter";
|
|
import mapKeys from "lodash.mapkeys";
|
|
import { z } from "zod";
|
|
|
|
import { Case, changeStringCase } from "~/libs/changeStringCase";
|
|
import type { AdminSdkCredentials } from "~/libs/fcm/getGoogleAuthToken";
|
|
import { sendFcmMessage } from "~/libs/fcm/sendFcmMessage";
|
|
import { getCurrentDomain } from "~/libs/getCurrentDomain";
|
|
import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode";
|
|
import { verifyQstashHeader } from "~/libs/qstash/verifyQstashHeader";
|
|
import { readEnvVariable } from "~/libs/readEnvVariable";
|
|
import { getTokensSubscribedToTitle } from "~/models/token";
|
|
import type { Env } from "~/types/env";
|
|
import type { EpisodesResponseSchema } from "~/types/episode";
|
|
import type { FetchUrlResponse } from "~/types/episode/fetch-url-response";
|
|
import {
|
|
AniListIdSchema,
|
|
EpisodeNumberSchema,
|
|
ErrorResponse,
|
|
SuccessResponse,
|
|
} from "~/types/schema";
|
|
|
|
const app = new Hono();
|
|
|
|
app.post(
|
|
"/",
|
|
zValidator(
|
|
"json",
|
|
z.object({
|
|
aniListId: AniListIdSchema,
|
|
episodeNumber: EpisodeNumberSchema,
|
|
}),
|
|
),
|
|
async (c) => {
|
|
const { aniListId, episodeNumber } = await c.req.json<{
|
|
aniListId: number;
|
|
episodeNumber: number;
|
|
}>();
|
|
console.log(
|
|
`Internal new episode route, aniListId: ${aniListId}, episodeNumber: ${episodeNumber}`,
|
|
);
|
|
|
|
if (!(await verifyQstashHeader(env<Env, typeof c>(c, "workerd"), c.req))) {
|
|
return c.json(ErrorResponse, { status: 401 });
|
|
}
|
|
|
|
const domain = getCurrentDomain(c.req);
|
|
const { success, result: fetchEpisodesResult } = await fetch(
|
|
`${domain}/episodes/${aniListId}`,
|
|
).then((res) => res.json<EpisodesResponseSchema>());
|
|
if (!success) {
|
|
await scheduleRetry(
|
|
readEnvVariable(env<Env, typeof c>(c, "workerd"), "QSTASH_TOKEN"),
|
|
c.req,
|
|
);
|
|
return c.json(ErrorResponse, { status: 500 });
|
|
}
|
|
|
|
const { episodes, providerId } = fetchEpisodesResult;
|
|
const episode = episodes.find(
|
|
(episode) => episode.number === episodeNumber,
|
|
);
|
|
if (!episode) {
|
|
await scheduleRetry(
|
|
readEnvVariable(env<Env, typeof c>(c, "workerd"), "QSTASH_TOKEN"),
|
|
c.req,
|
|
);
|
|
return c.json(ErrorResponse, { status: 404 });
|
|
}
|
|
|
|
const { success: fetchUrlSuccess, result: fetchUrlResult } = await fetch(
|
|
`${domain}/episodes/${aniListId}/url`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
id: episode.id,
|
|
provider: providerId,
|
|
}),
|
|
},
|
|
).then((res) => res.json<FetchUrlResponse>());
|
|
if (!fetchUrlSuccess) {
|
|
await scheduleRetry(
|
|
readEnvVariable(env<Env, typeof c>(c, "workerd"), "QSTASH_TOKEN"),
|
|
c.req,
|
|
);
|
|
return c.json(ErrorResponse, { status: 500 });
|
|
}
|
|
|
|
const tokens = await getTokensSubscribedToTitle(
|
|
env<Env, typeof c>(c, "workerd"),
|
|
aniListId,
|
|
);
|
|
|
|
await Promise.all(
|
|
tokens.map(async (token) => {
|
|
return sendFcmMessage(
|
|
mapKeys(
|
|
readEnvVariable<AdminSdkCredentials>(
|
|
env<Env, typeof c>(c, "workerd"),
|
|
"ADMIN_SDK_JSON",
|
|
),
|
|
(_, key) => changeStringCase(key, Case.snake_case, Case.camelCase),
|
|
) as unknown as AdminSdkCredentials,
|
|
{
|
|
token,
|
|
data: {
|
|
type: "new_episode",
|
|
episodes: JSON.stringify(episodes),
|
|
episodeStreamInfo: JSON.stringify(fetchUrlResult),
|
|
aniListId: aniListId.toString(),
|
|
episodeNumber: episodeNumber.toString(),
|
|
},
|
|
android: { priority: "high" },
|
|
},
|
|
);
|
|
}),
|
|
);
|
|
|
|
await maybeScheduleNextAiringEpisode(
|
|
env<Env, typeof c>(c, "workerd"),
|
|
c.req,
|
|
aniListId,
|
|
);
|
|
|
|
return c.json(SuccessResponse, 200);
|
|
},
|
|
);
|
|
|
|
async function scheduleRetry(qstashToken: string, req: HonoRequest) {
|
|
return new Client({ token: qstashToken }).publishJSON({
|
|
body: await req.json(),
|
|
url: req.url,
|
|
retries: 0,
|
|
delay: "1h",
|
|
});
|
|
}
|
|
|
|
export default app;
|