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(c, "workerd"), c.req))) { return c.json(ErrorResponse, { status: 401 }); } if (!(await isWatchingTitle(env(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(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(c, "workerd"), aniListId, ); await Promise.allSettled( tokens.map(async (token) => { return sendFcmMessage( mapKeys( readEnvVariable( env(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(c, "workerd"), c.req, aniListId, ); return c.json(SuccessResponse, 200); }, ); export default app;