import { get } from 'svelte/store'; import anime from '../../stores/anime'; import { mediaListCollection, type Media, Type } from '../AniList/media'; 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); export const updateMedia = (id: number, progress: number | undefined, callback: () => void) => { fetch(`/api/anilist/increment?id=${id}&progress=${(progress || 0) + 1}`).then(callback); }; export const totalEpisodes = (anime: Media) => anime.episodes === null ? '' : `/${anime.episodes}`; 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; let untilAiring; let time = new Date(airingAt ? airingAt * 1000 : 0).toLocaleTimeString([], { hour12: !settings.get().display24HourTime, hour: 'numeric', minute: '2-digit' }); 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 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 `on ${new Date( anime.startDate.year, (anime as unknown as MediaPrequel).startDate.month, (anime as unknown as MediaPrequel).startDate.day ).toLocaleDateString()}`; 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 `${anime.nextAiringEpisode?.episode}${totalEpisodes( anime )} in ${timeFrame} ${few ? `(${time})` : ''}`; else return `${anime.nextAiringEpisode?.episode} in ${ few ? '' : '' }${timeFrame}${few ? '' : ''} ${few ? `(${time})` : ''}`; } return ''; };