From 83879a0fa01415999116cbc46377b4819fc19f96 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Fri, 5 Jun 2026 13:58:43 +0000 Subject: feat(schedule): add native track alongside sub and dub Source the native (original-language) broadcast schedule from AnimeSchedule's "raw" airType and expose it as a third track on the schedule-page toggle and the GraphQL airing query. --- src/graphql/anime/resolvers.ts | 7 +++--- src/graphql/anime/schema.graphql | 1 + src/lib/Locale/english.ts | 1 + src/lib/Locale/japanese.ts | 1 + src/lib/Locale/layout.ts | 1 + src/lib/Media/Anime/Airing/animeSchedule.ts | 36 ++++++++++++++++++++--------- src/lib/Media/Anime/Airing/classify.test.ts | 2 ++ src/lib/Schedule/Days.svelte | 20 +++++++++++----- src/routes/+layout.svelte | 4 +++- src/routes/api/animeschedule/+server.ts | 6 ++--- 10 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/graphql/anime/resolvers.ts b/src/graphql/anime/resolvers.ts index 8d87eb6c..d3d7801e 100644 --- a/src/graphql/anime/resolvers.ts +++ b/src/graphql/anime/resolvers.ts @@ -13,11 +13,12 @@ export const resolvers: WithIndex = { const token = env.ANIMESCHEDULE_CLIENT_TOKEN; const generatedAt = Math.floor(Date.now() / 1000); - if (!token) return { airing: { generatedAt, sub: [], dub: [] } }; + if (!token) + return { airing: { generatedAt, native: [], sub: [], dub: [] } }; - const { sub, dub } = await fetchTimetables(token); + const { native, sub, dub } = await fetchTimetables(token); - return { airing: { generatedAt, sub, dub } }; + return { airing: { generatedAt, native, sub, dub } }; }, }, }; diff --git a/src/graphql/anime/schema.graphql b/src/graphql/anime/schema.graphql index aad7afec..7ec6d6be 100644 --- a/src/graphql/anime/schema.graphql +++ b/src/graphql/anime/schema.graphql @@ -8,6 +8,7 @@ type Anime { type Airing { generatedAt: Int + native: [AiringRelease] sub: [AiringRelease] dub: [AiringRelease] } diff --git a/src/lib/Locale/english.ts b/src/lib/Locale/english.ts index fafcf5ef..8e7b5ffa 100644 --- a/src/lib/Locale/english.ts +++ b/src/lib/Locale/english.ts @@ -707,6 +707,7 @@ const English: Locale = { tracks: { sub: "Sub", dub: "Dub", + native: "Native", }, }, events: { diff --git a/src/lib/Locale/japanese.ts b/src/lib/Locale/japanese.ts index 60b92506..cd4cf879 100644 --- a/src/lib/Locale/japanese.ts +++ b/src/lib/Locale/japanese.ts @@ -22,6 +22,7 @@ const Japanese: Locale = { tracks: { sub: "字幕", dub: "吹き替え", + native: "ネイティブ", }, }, settings: { diff --git a/src/lib/Locale/layout.ts b/src/lib/Locale/layout.ts index e53d0884..ec66889f 100644 --- a/src/lib/Locale/layout.ts +++ b/src/lib/Locale/layout.ts @@ -639,6 +639,7 @@ export interface Locale { tracks?: { sub: LocaleValue; dub: LocaleValue; + native: LocaleValue; }; }; events?: { diff --git a/src/lib/Media/Anime/Airing/animeSchedule.ts b/src/lib/Media/Anime/Airing/animeSchedule.ts index f3f6f85d..7bfe5ac3 100644 --- a/src/lib/Media/Anime/Airing/animeSchedule.ts +++ b/src/lib/Media/Anime/Airing/animeSchedule.ts @@ -1,9 +1,11 @@ // Data model for AnimeSchedule.net's weekly timetable, the source of truth for -// when subbed and dubbed episodes actually release. Unlike a fansub schedule, -// every release carries an absolute timestamp, an episode number, delay windows, -// and the streaming platforms it lands on. +// when native, subbed, and dubbed episodes actually release. Unlike a fansub +// schedule, every release carries an absolute timestamp, an episode number, +// delay windows, and the streaming platforms it lands on. -export type AirType = "sub" | "dub"; +// A release track. "native" is AnimeSchedule's "raw" (original-language) +// broadcast; "sub"/"dub" are the localised releases. +export type AirType = "native" | "sub" | "dub"; export interface Stream { platform: string; @@ -25,9 +27,10 @@ export interface AiringEntry { streams: Stream[]; } -// The merged sub + dub schedule for the current week. +// The merged native + sub + dub schedule for the current week. export interface AiringSchedule { generatedAt: number; + native: AiringEntry[]; sub: AiringEntry[]; dub: AiringEntry[]; } @@ -91,12 +94,19 @@ export const parseTimetable = (raw: unknown): AiringEntry[] => { const TIMETABLE_ENDPOINT = "https://animeschedule.net/api/v3/timetables"; -// Fetch and parse the current week's sub and dub timetables in one shot. The -// caller supplies the AnimeSchedule application token. +// Fetch and parse the current week's native, sub, and dub timetables in one +// shot. The caller supplies the AnimeSchedule application token. AnimeSchedule +// names the native broadcast "raw". export const fetchTimetables = async ( token: string, -): Promise<{ sub: AiringEntry[]; dub: AiringEntry[] }> => { - const fetchOne = async (airType: AirType): Promise => { +): Promise<{ + native: AiringEntry[]; + sub: AiringEntry[]; + dub: AiringEntry[]; +}> => { + const fetchOne = async ( + airType: "raw" | "sub" | "dub", + ): Promise => { try { const response = await fetch(`${TIMETABLE_ENDPOINT}?airType=${airType}`, { headers: { Authorization: `Bearer ${token}` }, @@ -108,7 +118,11 @@ export const fetchTimetables = async ( } }; - const [sub, dub] = await Promise.all([fetchOne("sub"), fetchOne("dub")]); + const [native, sub, dub] = await Promise.all([ + fetchOne("raw"), + fetchOne("sub"), + fetchOne("dub"), + ]); - return { sub, dub }; + return { native, sub, dub }; }; diff --git a/src/lib/Media/Anime/Airing/classify.test.ts b/src/lib/Media/Anime/Airing/classify.test.ts index 1019b303..eeff2036 100644 --- a/src/lib/Media/Anime/Airing/classify.test.ts +++ b/src/lib/Media/Anime/Airing/classify.test.ts @@ -16,6 +16,7 @@ import settings from "$stores/settings"; // sub at `airingAt`. const subScheduleFor = (media: Media, airingAt: number): AiringSchedule => ({ generatedAt: Math.floor(Date.now() / 1000), + native: [], sub: [ { route: `fixture-${media.id}`, @@ -196,6 +197,7 @@ describe("countdown source fallback", () => { const schedule: AiringSchedule = { generatedAt: Math.floor(Date.now() / 1000), + native: [], sub: [], dub: [], }; diff --git a/src/lib/Schedule/Days.svelte b/src/lib/Schedule/Days.svelte index 7fe09d4e..ac27dd17 100644 --- a/src/lib/Schedule/Days.svelte +++ b/src/lib/Schedule/Days.svelte @@ -43,14 +43,14 @@ const trackParameter: string | null = parseOrDefault( null, ); +const isAirType = (value: string | null): value is AirType => + value === "native" || value === "sub" || value === "dub"; + // The view track defaults to the countdown source but is overridable via the // in-page toggle and a ?type= query param, independent of the global setting. -let selectedTrack: AirType = - trackParameter === "sub" || trackParameter === "dub" - ? trackParameter - : $settings.countdownSource === "dub" - ? "dub" - : "sub"; +let selectedTrack: AirType = isAirType(trackParameter) + ? trackParameter + : $settings.countdownSource; $: source = selectedTrack; @@ -216,6 +216,14 @@ const episode = (media: Media, weekday: string) => { > {$locale().schedule?.tracks?.dub ?? 'Dub'} + {#await mediaListPromise} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index be594460..fbc2ff8e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -289,7 +289,9 @@ $: { fetch(root("/api/animeschedule")) .then((r) => r.json()) .then((r) => airingSchedule.set(r)) - .catch(() => airingSchedule.set({ generatedAt: 0, sub: [], dub: [] })); + .catch(() => + airingSchedule.set({ generatedAt: 0, native: [], sub: [], dub: [] }), + ); } diff --git a/src/routes/api/animeschedule/+server.ts b/src/routes/api/animeschedule/+server.ts index c596bf41..75257569 100644 --- a/src/routes/api/animeschedule/+server.ts +++ b/src/routes/api/animeschedule/+server.ts @@ -8,14 +8,14 @@ export const GET = async () => { if (!token) return Response.json( - { generatedAt, sub: [], dub: [] }, + { generatedAt, native: [], sub: [], dub: [] }, { headers: appOriginHeaders({ "Cache-Control": "max-age=60" }) }, ); - const { sub, dub } = await fetchTimetables(token); + const { native, sub, dub } = await fetchTimetables(token); return Response.json( - { generatedAt, sub, dub }, + { generatedAt, native, sub, dub }, { headers: appOriginHeaders({ "Cache-Control": "max-age=86400, s-maxage=86400", -- cgit v1.2.3