diff options
| author | Fuwn <[email protected]> | 2026-06-05 11:10:22 +0000 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-06-05 11:10:22 +0000 |
| commit | 4b56194ee6807acb56abf0949394efadabf830d4 (patch) | |
| tree | 5cb2074a8d012bf9b7c900e7e44cbdfd0e15123f /src/lib/Schedule | |
| parent | fix(lists): tick count down when media leaves a list (diff) | |
| download | due.moe-4b56194ee6807acb56abf0949394efadabf830d4.tar.xz due.moe-4b56194ee6807acb56abf0949394efadabf830d4.zip | |
feat(airing): replace SubsPlease with AnimeSchedule (sub+dub)
Source both subbed and dubbed episode schedules from AnimeSchedule.net v3 (absolute timestamps, episode numbers, delay windows, streams), keyed to AniList shows by title. Removes SubsPlease and its ~650-line fuzzy matcher. Countdown source is now a setting (native|sub|dub) with a dub->sub->native fallback.
Requires ANIMESCHEDULE_CLIENT_TOKEN.
Diffstat (limited to 'src/lib/Schedule')
| -rw-r--r-- | src/lib/Schedule/CoverBypass.svelte | 4 | ||||
| -rw-r--r-- | src/lib/Schedule/Days.svelte | 91 |
2 files changed, 56 insertions, 39 deletions
diff --git a/src/lib/Schedule/CoverBypass.svelte b/src/lib/Schedule/CoverBypass.svelte index 2656a696..af452bf6 100644 --- a/src/lib/Schedule/CoverBypass.svelte +++ b/src/lib/Schedule/CoverBypass.svelte @@ -1,7 +1,7 @@ <script lang="ts"> import type { Media } from "$lib/Data/AniList/media"; import MediaTitleDisplay from "$lib/List/MediaTitleDisplay.svelte"; -import type { SubsPleaseEpisode } from "$lib/Media/Anime/Airing/Subtitled/subsPlease"; +import type { AiringEntry } from "$lib/Media/Anime/Airing/animeSchedule"; import { outboundLink } from "$lib/Media/links"; import tooltip from "$lib/Tooltip/tooltip"; @@ -9,7 +9,7 @@ import { abbreviate } from "$lib/Utility/string"; import settings from "$stores/settings"; export let media: Media | null; -export let entry: SubsPleaseEpisode; +export let entry: AiringEntry; export let cover = true; export let showTooltip = true; diff --git a/src/lib/Schedule/Days.svelte b/src/lib/Schedule/Days.svelte index 5a57905a..987bc816 100644 --- a/src/lib/Schedule/Days.svelte +++ b/src/lib/Schedule/Days.svelte @@ -3,11 +3,12 @@ import { browser } from "$app/environment"; import { hydrateMediaListCache } from "$lib/Data/AniList/cacheHydration"; import type { AniListAuthorisation } from "$lib/Data/AniList/identity"; import { type Media, mediaListCollection, Type } from "$lib/Data/AniList/media"; -import { findClosestMedia } from "$lib/Media/Anime/Airing/Subtitled/match"; import type { - SubsPlease, - SubsPleaseEpisode, -} from "$lib/Media/Anime/Airing/Subtitled/subsPlease"; + AiringEntry, + AiringSchedule, + AirType, +} from "$lib/Media/Anime/Airing/animeSchedule"; +import { findClosestMedia } from "$lib/Media/Anime/Airing/match"; import { outboundLink } from "$lib/Media/links"; import { parseOrDefault } from "$lib/Utility/parameters"; import settings from "$stores/settings"; @@ -23,7 +24,7 @@ import anime from "$stores/anime"; import identity from "$stores/identity"; import lastPruneTimes from "$stores/lastPruneTimes"; -export let subsPlease: SubsPlease; +export let schedule: AiringSchedule; export let scheduledMedia: Partial<Media[]>; export let forceListMode = false; export let user: AniListAuthorisation | undefined; @@ -35,6 +36,8 @@ let day: string | null = parseOrDefault(urlParameters, "day", null); let mediaListPromise: Promise<Media[]>; +$: source = ($settings.countdownSource === "dub" ? "dub" : "sub") as AirType; + onMount(async () => { if (user === undefined || $identity.id === -2) mediaListPromise = Promise.resolve([]); @@ -54,40 +57,54 @@ onMount(async () => { } }); -const shiftSubsPleaseSchedule = (schedule: SubsPlease["schedule"]) => { - const shiftedSchedule: { [key: string]: SubsPleaseEpisode[] } = {}; +const WEEKDAYS = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +]; + +const formatTime = (airingAt: number) => + new Date(airingAt * 1000).toLocaleTimeString([], { + hour12: !$settings.display24HourTime, + hour: "numeric", + minute: "2-digit", + }); + +// Bucket releases by the viewer's local weekday, ordered from today onward. +const scheduleByDay = (entries: AiringEntry[]) => { + const buckets: { [key: string]: AiringEntry[] } = {}; + + for (const entry of entries) { + if (!entry.airingAt) continue; + + const weekday = new Date(entry.airingAt * 1000).toLocaleString("en-us", { + weekday: "long", + }); - if (day && Object.keys(schedule).includes(day)) { - shiftedSchedule[day] = schedule[ - day as keyof typeof schedule - ] as unknown as SubsPleaseEpisode[]; + if (!buckets[weekday]) buckets[weekday] = []; - return shiftedSchedule; + buckets[weekday].push(entry); } - const days = Object.keys(schedule); - const currentDayIndex = days.indexOf( - new Date().toLocaleString("en-us", { weekday: "long" }), - ); + for (const weekday in buckets) + buckets[weekday].sort((a, b) => a.airingAt - b.airingAt); - days - .slice(currentDayIndex) - .concat(days.slice(0, currentDayIndex)) - .forEach((day) => { - const scheduleEntry = schedule[day as keyof typeof schedule]; + if (day && buckets[day]) return { [day]: buckets[day] }; - shiftedSchedule[day] = Array.isArray(scheduleEntry) - ? scheduleEntry - : ([scheduleEntry] as unknown as SubsPleaseEpisode[]); - }); + const todayIndex = new Date().getDay(); + const ordered: { [key: string]: AiringEntry[] } = {}; - Object.entries(shiftedSchedule).forEach(([day, scheduleEntry]) => { - if (scheduleEntry.length === 0) { - delete shiftedSchedule[day]; - } - }); + for (let offset = 0; offset < 7; offset += 1) { + const weekday = WEEKDAYS[(todayIndex + offset) % 7]; + + if (buckets[weekday]?.length) ordered[weekday] = buckets[weekday]; + } - return shiftedSchedule; + return ordered; }; const associateMedia = ( @@ -123,7 +140,7 @@ const episode = (media: Media, weekday: string) => { <Skeleton grid={true} count={7} height="15em" width="49.5%" /> {:then mediaList} - {#each Object.entries(shiftSubsPleaseSchedule(subsPlease.schedule)) as [day, scheduleEntry], dayIndex} + {#each Object.entries(scheduleByDay(schedule[source])) as [day, scheduleEntry], dayIndex} <details open class="list" @@ -136,7 +153,7 @@ const episode = (media: Media, weekday: string) => { class="covers" style={`grid-template-columns: repeat(auto-fill, minmax(${$settings.displayCoverWidth}px, 1fr))`} > - {#each Object.values(scheduleEntry) as entry, entryIndex} + {#each scheduleEntry as entry, entryIndex} {@const media = associateMedia(scheduledMedia, entry.title, mediaList)} {#if ($settings.displayScheduleFilterList && media) || !$settings.displayScheduleFilterList} @@ -161,7 +178,7 @@ const episode = (media: Media, weekday: string) => { ? $settings.displayDataSaver ? media.coverImage.medium : media.coverImage.extraLarge - : `https://subsplease.org${entry.image_url}`} + : entry.imageUrl} limit={12.5} alternativeText="Cover" /> @@ -179,7 +196,7 @@ const episode = (media: Media, weekday: string) => { {episode(media, day)}{media.episodes ? `/${media.episodes}` : ''} at </span> {/if} - {entry.time} + {formatTime(entry.airingAt)} </span> </div> </LinkedTooltip> @@ -189,7 +206,7 @@ const episode = (media: Media, weekday: string) => { </div> {:else} <ol> - {#each Object.values(scheduleEntry) as entry} + {#each scheduleEntry as entry} {@const media = associateMedia(scheduledMedia, entry.title, mediaList)} {#if ($settings.displayScheduleFilterList && media) || !$settings.displayScheduleFilterList} @@ -204,7 +221,7 @@ const episode = (media: Media, weekday: string) => { {episode(media, day)}{media.episodes ? `/${media.episodes}` : ''} at </span> {/if} - {entry.time} + {formatTime(entry.airingAt)} </span> </li> {/if} |