diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/AniList/media.ts | 3 | ||||
| -rw-r--r-- | src/lib/List/Anime/CleanAnimeList.svelte | 67 | ||||
| -rw-r--r-- | src/lib/Media/anime.ts | 88 | ||||
| -rw-r--r-- | src/lib/Tools/SequelSpy.svelte | 10 | ||||
| -rw-r--r-- | src/lib/subsPlease.ts | 11 |
5 files changed, 143 insertions, 36 deletions
diff --git a/src/lib/AniList/media.ts b/src/lib/AniList/media.ts index ff43732e..3338dd9a 100644 --- a/src/lib/AniList/media.ts +++ b/src/lib/AniList/media.ts @@ -41,6 +41,7 @@ export interface Media { episode: number; airingAt?: number; }; + synonyms: string[]; mediaListEntry?: { progress: number; progressVolumes: number; @@ -105,7 +106,7 @@ const collectionQueryTemplate = (type: Type, userId: number, includeCompleted: b lists { name entries { media { - id idMal status type episodes chapters format duration + id idMal status type episodes chapters format duration synonyms title { romaji english native } nextAiringEpisode { episode airingAt } mediaListEntry { diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte index 0b130d15..fd31963c 100644 --- a/src/lib/List/Anime/CleanAnimeList.svelte +++ b/src/lib/List/Anime/CleanAnimeList.svelte @@ -8,7 +8,8 @@ import ListTitle from '../ListTitle.svelte'; import MediaTitle from '../MediaTitleDisplay.svelte'; import { outboundLink } from '$lib/Media/media'; - import { onDestroy } from 'svelte'; + import { onDestroy, onMount } from 'svelte'; + import type { SubsPlease } from '$lib/subsPlease'; export let media: Media[]; export let title: string; @@ -73,42 +74,46 @@ {#if title !== 'Upcoming Episodes' || !$settings.displayCountdownRightAligned} <span style="opacity: 50%;">|</span> {/if} - {#if title !== 'Upcoming Episodes'} - <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} --> - {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} - <a - href={'#'} - style={pendingUpdate === anime.id ? 'pointer-events: none; opacity: 50%;' : ''} - on:click={() => { - if (pendingUpdate !== anime.id) { - lastUpdatedMedia = anime.id; - pendingUpdate = anime.id; + {#await fetch(`/api/subsplease?tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}`).then( (r) => r.json() )} + Loading ... + {:then subsPlease} + {#if title !== 'Upcoming Episodes'} + <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} --> + {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} + <a + href={'#'} + style={pendingUpdate === anime.id ? 'pointer-events: none; opacity: 50%;' : ''} + on:click={() => { + if (pendingUpdate !== anime.id) { + lastUpdatedMedia = anime.id; + pendingUpdate = anime.id; - updateMedia(anime.id, anime.mediaListEntry?.progress, () => { - const mediaListEntry = media.find((m) => m.id === anime.id)?.mediaListEntry; + updateMedia(anime.id, anime.mediaListEntry?.progress, () => { + const mediaListEntry = media.find((m) => m.id === anime.id)?.mediaListEntry; - if (mediaListEntry) mediaListEntry.progress = progress + 1; + if (mediaListEntry) mediaListEntry.progress = progress + 1; - previousAnimeList = media; - animeLists = cleanCache(user, identity); - pendingUpdate = null; - }); - } - }}>+</a - > - {#if !completed} - [{anime.nextAiringEpisode?.episode === -1 - ? '?' - : (anime.nextAiringEpisode?.episode || 1) - 1}] + previousAnimeList = media; + animeLists = cleanCache(user, identity); + pendingUpdate = null; + }); + } + }}>+</a + > + {#if !completed} + [{anime.nextAiringEpisode?.episode === -1 + ? '?' + : (anime.nextAiringEpisode?.episode || 1) - 1}] + <span class:countdown={$settings.displayCountdownRightAligned}> + {@html airingTime(anime, subsPlease)} + </span> + {/if} + {:else} <span class:countdown={$settings.displayCountdownRightAligned}> - {@html airingTime(anime)} + {@html airingTime(anime, subsPlease, true)} </span> {/if} - {:else} - <span class:countdown={$settings.displayCountdownRightAligned}> - {@html airingTime(anime, true)} - </span> - {/if} + {/await} </span> </li> {/if} diff --git a/src/lib/Media/anime.ts b/src/lib/Media/anime.ts index 927da31b..b9227562 100644 --- a/src/lib/Media/anime.ts +++ b/src/lib/Media/anime.ts @@ -5,6 +5,14 @@ import lastPruneTimes from '../../stores/lastPruneTimes'; import type { AniListAuthorisation, UserIdentity } from '../AniList/identity'; import settings from '../../stores/settings'; import type { MediaPrequel } from '$lib/AniList/prequels'; +import type { SubsPlease } from '$lib/subsPlease'; +import levenshtein from 'fast-levenshtein'; + +interface Time { + title: string; + time: string; + day: string; +} export const cleanCache = (user: AniListAuthorisation, identity: UserIdentity) => mediaListCollection(user, identity, Type.Anime, get(anime), get(lastPruneTimes).anime, true); @@ -16,9 +24,85 @@ export const updateMedia = (id: number, progress: number | undefined, callback: export const totalEpisodes = (anime: Media) => anime.episodes === null ? '' : `<span style="opacity: 50%">/${anime.episodes}</span>`; -export const airingTime = (anime: Media, upcoming = false) => { +const secondsUntil = (targetTime: string, targetDay: string) => { + const now = new Date(); + const [targetHour, targetMinute] = targetTime.split(':').map(Number); + let dayDifference = + ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'].indexOf( + targetDay + ) - now.getDay(); + + if (dayDifference < 0) dayDifference += 7; + + const targetDate = new Date(now); + + targetDate.setDate(now.getDate() + dayDifference); + targetDate.setHours(targetHour, targetMinute, 0, 0); + + const secondsDifference = (Number(targetDate) - Number(now)) / 1000; + + return secondsDifference > 0 ? secondsDifference : secondsDifference + 7 * 24 * 60 * 60; +}; + +const normalizeTitle = (title: string) => + (title || '') + .toLowerCase() + .replace(/season \d+|s\d+/g, '') + .trim(); + +const findClosestMatch = (times: Time[], titles: string[]) => { + let closestMatch = null; + let smallestDistance = Infinity; + + titles.forEach((animeTitle) => { + const normalizedAnimeTitle = normalizeTitle(animeTitle); + + times.forEach((item) => { + const normalizedItemTitle = normalizeTitle(item.title); + const distance = levenshtein.get(normalizedAnimeTitle, normalizedItemTitle); + + if (distance < smallestDistance) { + smallestDistance = distance; + closestMatch = item; + } + }); + }); + + return closestMatch; +}; + +export const airingTime = (anime: Media, subsPlease: SubsPlease, upcoming = false) => { const airingAt = anime.nextAiringEpisode?.airingAt; - const untilAiring = airingAt ? Math.round((airingAt - Date.now() / 1000) * 100) / 100 : undefined; + let untilAiring; + + if (get(settings).displayNativeCountdown) { + untilAiring = airingAt ? Math.round((airingAt - Date.now() / 1000) * 100) / 100 : undefined; + } else { + const times: Time[] = []; + + for (const [key, value] of Object.entries(subsPlease.schedule)) { + const flattenedValue = Array.isArray(value) ? value.flat() : []; + + for (const time of flattenedValue) { + times.push({ + title: time.title, + time: time.time, + day: key + }); + } + } + + const time: Time | null = findClosestMatch(times, [ + anime.title.romaji, + anime.title.english, + ...anime.synonyms + ]); + + if (time) untilAiring = secondsUntil((time as Time).time, (time as Time).day); + else + untilAiring = airingAt ? Math.round((airingAt - Date.now() / 1000) * 100) / 100 : undefined; + } + let timeFrame; const time = new Date(airingAt ? airingAt * 1000 : 0).toLocaleTimeString([], { hour12: !settings.get().display24HourTime, diff --git a/src/lib/Tools/SequelSpy.svelte b/src/lib/Tools/SequelSpy.svelte index 56f40956..e8cc98bf 100644 --- a/src/lib/Tools/SequelSpy.svelte +++ b/src/lib/Tools/SequelSpy.svelte @@ -8,6 +8,7 @@ import type { Media } from '$lib/AniList/media'; import { page } from '$app/stores'; import { browser } from '$app/environment'; + import type { SubsPlease } from '$lib/subsPlease'; export let user: AniListAuthorisation; @@ -42,7 +43,8 @@ onMount(() => clearAllParameters(['year', 'season'])); - const prequelAiringTime = (prequel: MediaPrequel) => airingTime(prequel as unknown as Media); + const prequelAiringTime = (prequel: MediaPrequel, subsPlease: SubsPlease) => + airingTime(prequel as unknown as Media, subsPlease); </script> <p> @@ -66,7 +68,11 @@ </a> <span style="opacity: 50%;">|</span> {prequel.seen}<span style="opacity: 50%;">/{prequel.episodes}</span> - {@html prequelAiringTime(prequel)} + {#await fetch(`/api/subsplease?tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}`).then( (r) => r.json() )} + Loading ... + {:then subsPlease} + {@html prequelAiringTime(prequel, subsPlease)} + {/await} </li> {/each} </ul> diff --git a/src/lib/subsPlease.ts b/src/lib/subsPlease.ts new file mode 100644 index 00000000..b2bcb30e --- /dev/null +++ b/src/lib/subsPlease.ts @@ -0,0 +1,11 @@ +export interface SubsPlease { + tz: string; + schedule: { + [key in string]: { + title: string; + page: string; + image_url: string; + time: string; + }[]; + }[]; +} |