Files
aniplay-api/src/controllers/internal/new-episode/index.ts

119 lines
3.4 KiB
TypeScript

import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { env } from "hono/adapter";
import mapKeys from "lodash.mapkeys";
import { z } from "zod";
import { fetchEpisodeUrlFromAllProviders } from "~/controllers/episodes/getEpisodeUrl";
import { Case, changeStringCase } from "~/libs/changeStringCase";
import type { AdminSdkCredentials } from "~/libs/gcloud/getGoogleAuthToken";
import { sendFcmMessage } from "~/libs/gcloud/sendFcmMessage";
import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode";
import { verifyQstashHeader } from "~/libs/qstash/verifyQstashHeader";
import { readEnvVariable } from "~/libs/readEnvVariable";
import { getTokensSubscribedToTitle } from "~/models/token";
import { isWatchingTitle } from "~/models/watchStatus";
import type { Env } from "~/types/env";
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 });
}
if (!(await isWatchingTitle(env<Env, typeof c>(c, "workerd"), aniListId))) {
console.log(`Title ${aniListId} is no longer being watched`);
return c.json(
{ success: true, result: { isNoLongerWatching: true } },
200,
);
}
const { episodes, fetchUrlResult } = await fetchEpisodeUrlFromAllProviders(
aniListId,
episodeNumber,
env<Env, typeof c>(c, "workerd"),
);
if (!episodes) {
console.error(`Failed to fetch episodes for title ${aniListId}`);
return c.json(
{ success: false, message: "Failed to fetch episodes" },
500,
);
}
if (!fetchUrlResult) {
console.error(`Failed to fetch episode URL for episode`);
return c.json(
{ success: false, message: "Failed to fetch episode URL" },
500,
);
}
const tokens = await getTokensSubscribedToTitle(
env<Env, typeof c>(c, "workerd"),
aniListId,
);
await Promise.allSettled(
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);
},
);
export default app;