diff options
| author | Fuwn <[email protected]> | 2023-12-17 04:36:30 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2023-12-17 04:36:30 -0800 |
| commit | e3f24a4f0fd4848bdd225ed5788f69f814b10ee0 (patch) | |
| tree | a208fcbcd6ba6e25de0d220404d9699f72b8002b /src/lib/Media/Anime/airing.ts | |
| parent | fix(sequelspy): always use native release (diff) | |
| download | due.moe-e3f24a4f0fd4848bdd225ed5788f69f814b10ee0.tar.xz due.moe-e3f24a4f0fd4848bdd225ed5788f69f814b10ee0.zip | |
refactor(anime): move airingTime
Diffstat (limited to 'src/lib/Media/Anime/airing.ts')
| -rw-r--r-- | src/lib/Media/Anime/airing.ts | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/src/lib/Media/Anime/airing.ts b/src/lib/Media/Anime/airing.ts new file mode 100644 index 00000000..6eaa2f39 --- /dev/null +++ b/src/lib/Media/Anime/airing.ts @@ -0,0 +1,167 @@ +import { get } from 'svelte/store'; +import type { Media } from '../../AniList/media'; +import settings from '../../../stores/settings'; +import type { MediaPrequel } from '$lib/AniList/prequels'; +import type { SubsPlease } from '$lib/subsPlease'; +import levenshtein from 'fast-levenshtein'; +import { totalEpisodes } from '../anime'; + +interface Time { + title: string; + time: string; + day: string; +} + +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 | null, upcoming = false) => { + const airingAt = anime.nextAiringEpisode?.airingAt; + let untilAiring; + let time = new Date(airingAt ? airingAt * 1000 : 0).toLocaleTimeString([], { + hour12: !settings.get().display24HourTime, + hour: 'numeric', + minute: '2-digit' + }); + + if (get(settings).displayNativeCountdown || !subsPlease) { + 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 foundTime: Time | null = findClosestMatch(times, [ + anime.title.romaji, + anime.title.english, + ...anime.synonyms + ]); + + if (foundTime) { + untilAiring = secondsUntil((foundTime as Time).time, (foundTime as Time).day); + time = new Date(Date.now() + untilAiring * 1000).toLocaleTimeString([], { + hour12: !settings.get().display24HourTime, + hour: 'numeric', + minute: '2-digit' + }); + } else + untilAiring = airingAt ? Math.round((airingAt - Date.now() / 1000) * 100) / 100 : undefined; + } + + let timeFrame; + let hours = null; + + if ( + (anime as unknown as MediaPrequel).startDate && + new Date( + anime.startDate.year, + (anime as unknown as MediaPrequel).startDate.month, + (anime as unknown as MediaPrequel).startDate.day + ) < new Date() + ) + return `<span style="opacity: 50%">on ${new Date( + anime.startDate.year, + (anime as unknown as MediaPrequel).startDate.month, + (anime as unknown as MediaPrequel).startDate.day + ).toLocaleDateString()}</span>`; + + if (untilAiring !== undefined) { + let minutes = untilAiring / 60; + let few = true; + + if (minutes > 60) { + hours = minutes / 60; + + if (hours >= 24) { + // let weeks = Math.floor(Math.floor(hours / 24) / 7); + + few = false; + + // if (weeks >= 1) { + // weeks = Math.round(weeks); + + // timeFrame = `${weeks} week${weeks === 1 ? '' : 's'}`; + // } else { + const days = Math.round(Math.floor(hours / 24)); + + timeFrame = `${days.toFixed(0)} day${days === 1 ? '' : 's'}`; + // } + } else timeFrame = `${hours.toFixed(1)} hour${hours === 1 ? '' : 's'}`; + } else { + minutes = Math.round(minutes); + + timeFrame = `${minutes} minute${minutes === 1 ? '' : 's'}`; + } + + const opacity = Math.max(50, 100 - (untilAiring / 60 / 60 / 24 / 7) * 50); + + if (upcoming) + return `<span title="${ + hours ? `${hours.toFixed(3)} hours` : '' + }" style="opacity: ${opacity}%;">${anime.nextAiringEpisode?.episode}${totalEpisodes( + anime + )} in ${timeFrame} <span style="opacity: 50%">${few ? `(${time})` : ''}</span></span>`; + else + return `<span title="${ + hours ? `${hours.toFixed(3)} hours` : '' + }" style="opacity: ${opacity}%;">${anime.nextAiringEpisode?.episode} in ${ + few ? '<b>' : '' + }${timeFrame}${few ? '</b>' : ''} ${few ? `(${time})` : ''}</span>`; + } + + return ''; +}; |