aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-06-05 13:58:43 +0000
committerFuwn <[email protected]>2026-06-05 13:58:43 +0000
commit83879a0fa01415999116cbc46377b4819fc19f96 (patch)
treea15d2870b29f727923b2dcdc3b5f017dd4575407 /src/lib
parentfix(schedule): use masonry columns so day panels collapse cleanly (diff)
downloaddue.moe-83879a0fa01415999116cbc46377b4819fc19f96.tar.xz
due.moe-83879a0fa01415999116cbc46377b4819fc19f96.zip
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.
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Locale/english.ts1
-rw-r--r--src/lib/Locale/japanese.ts1
-rw-r--r--src/lib/Locale/layout.ts1
-rw-r--r--src/lib/Media/Anime/Airing/animeSchedule.ts36
-rw-r--r--src/lib/Media/Anime/Airing/classify.test.ts2
-rw-r--r--src/lib/Schedule/Days.svelte20
6 files changed, 44 insertions, 17 deletions
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<AiringEntry[]> => {
+): Promise<{
+ native: AiringEntry[];
+ sub: AiringEntry[];
+ dub: AiringEntry[];
+}> => {
+ const fetchOne = async (
+ airType: "raw" | "sub" | "dub",
+ ): Promise<AiringEntry[]> => {
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'}
</button>
+ <button
+ type="button"
+ aria-pressed={source === 'native'}
+ class:button-action={source === 'native'}
+ onclick={() => selectTrack('native')}
+ >
+ {$locale().schedule?.tracks?.native ?? 'Native'}
+ </button>
</div>
{#await mediaListPromise}