mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-03-30 06:58:46 +00:00
160 lines
5.9 KiB
TypeScript
160 lines
5.9 KiB
TypeScript
import { authAggregator } from '../auth'
|
|
|
|
import type { ScheduleMedia } from './queries'
|
|
import type { Media } from './types'
|
|
import type { Episode, Episodes } from '../anizip/types'
|
|
import type { ResultOf } from 'gql.tada'
|
|
|
|
export function banner (media: Pick<Media, 'trailer' | 'bannerImage' | 'coverImage'>): string | undefined {
|
|
if (media.bannerImage) return media.bannerImage
|
|
if (media.trailer?.id) return `https://i.ytimg.com/vi/${media.trailer.id}/maxresdefault.jpg`
|
|
return media.coverImage?.extraLarge as string | undefined
|
|
}
|
|
|
|
export const STATUS_LABELS = {
|
|
CURRENT: 'Watching',
|
|
PLANNING: 'Plan to Watch',
|
|
COMPLETED: 'Completed',
|
|
PAUSED: 'Paused',
|
|
DROPPED: 'Dropped',
|
|
REPEATING: 'Re-Watching'
|
|
}
|
|
|
|
export function cover (media: Pick<Media, 'trailer' | 'bannerImage' | 'coverImage'>): string | undefined {
|
|
return media.coverImage?.extraLarge ?? banner(media)
|
|
}
|
|
|
|
export function coverMedium (media: Pick<Media, 'trailer' | 'bannerImage' | 'coverImage'>): string | undefined {
|
|
return media.coverImage?.medium?.replace('/small/', '/medium/') ?? banner(media)
|
|
}
|
|
|
|
export function coverSmall (media: Pick<Media, 'trailer' | 'bannerImage' | 'coverImage'>): string | undefined {
|
|
return media.coverImage?.medium ?? banner(media)
|
|
}
|
|
|
|
export function title (media: Pick<Media, 'title'>): string {
|
|
return media.title?.userPreferred ?? 'TBA'
|
|
}
|
|
|
|
const STATUS_MAP = {
|
|
RELEASING: 'Releasing',
|
|
NOT_YET_RELEASED: 'Not Yet Released',
|
|
FINISHED: 'Finished',
|
|
CANCELLED: 'Cancelled',
|
|
HIATUS: 'Hiatus'
|
|
}
|
|
|
|
export function status (media: Pick<Media, 'status'>): string {
|
|
if (media.status != null) return STATUS_MAP[media.status]
|
|
|
|
return 'N/A'
|
|
}
|
|
|
|
const RELATION_MAP = {
|
|
ADAPTATION: 'Adaptation',
|
|
PREQUEL: 'Prequel',
|
|
SEQUEL: 'Sequel',
|
|
PARENT: 'Parent',
|
|
SIDE_STORY: 'Side Story',
|
|
CHARACTER: 'Character',
|
|
SUMMARY: 'Summary',
|
|
ALTERNATIVE: 'Alternative',
|
|
SPIN_OFF: 'Spin Off',
|
|
OTHER: 'Other',
|
|
SOURCE: 'Source',
|
|
COMPILATION: 'Compilation',
|
|
CONTAINS: 'Contains'
|
|
}
|
|
|
|
export function relation (relation: 'ADAPTATION' | 'PREQUEL' | 'SEQUEL' | 'PARENT' | 'SIDE_STORY' | 'CHARACTER' | 'SUMMARY' | 'ALTERNATIVE' | 'SPIN_OFF' | 'OTHER' | 'SOURCE' | 'COMPILATION' | 'CONTAINS' | null) {
|
|
if (!relation) return 'N/A'
|
|
return RELATION_MAP[relation]
|
|
}
|
|
|
|
const FORMAT_MAP = {
|
|
TV: 'TV Series',
|
|
TV_SHORT: 'TV Short',
|
|
MOVIE: 'Movie',
|
|
SPECIAL: 'Special',
|
|
OVA: 'OVA',
|
|
ONA: 'ONA',
|
|
MUSIC: 'Music',
|
|
MANGA: 'Manga',
|
|
NOVEL: 'Novel',
|
|
ONE_SHOT: 'One Shot'
|
|
}
|
|
|
|
export function format (media: Pick<Media, 'format'>): string {
|
|
if (media.format != null) return FORMAT_MAP[media.format]
|
|
|
|
return 'N/A'
|
|
}
|
|
|
|
export function episodes (media: Pick<Media, 'aired' | 'notaired' | 'episodes' | 'mediaListEntry' | 'id'>): number | undefined {
|
|
if (media.episodes) return media.episodes
|
|
|
|
const upcoming = media.aired?.n?.[media.aired.n.length - 1]?.e ?? 0
|
|
const past = media.notaired?.n?.[media.notaired.n.length - 1]?.e ?? 0
|
|
const progress = authAggregator.mediaListEntry(media)?.progress ?? 0
|
|
|
|
return Math.max(upcoming, past, progress)
|
|
}
|
|
|
|
export function season (media: Pick<Media, 'season' | 'seasonYear'>) {
|
|
return [media.season?.toLowerCase(), media.seasonYear].filter(s => s).join(' ')
|
|
}
|
|
|
|
export function duration (media: Pick<Media, 'duration'>) {
|
|
if (!media.duration) return
|
|
return `${media.duration} Minute${media.duration > 1 ? 's' : ''}`
|
|
}
|
|
|
|
export function desc (media: Pick<Media, 'description'>) {
|
|
return notes(media.description?.replace(/<[^>]+>/g, '').replace(/\n+/g, '\n') ?? 'No description available.')
|
|
}
|
|
|
|
export function notes (string: string) {
|
|
return string.replace(/\n?\(?Source: [^)]+\)?\n?/m, '').replace(/\n?Notes?:[ |\n][^\n]+\n?/m, '')
|
|
}
|
|
|
|
export function isMovie (media: Pick<Media, 'format' | 'title' | 'synonyms' | 'duration' | 'episodes'>) {
|
|
if (media.format === 'MOVIE') return true
|
|
if ([...Object.values(media.title ?? {}), ...media.synonyms ?? []].some(title => title?.toLowerCase().includes('movie'))) return true
|
|
// if (!getParentForSpecial(media)) return true // this is good for checking movies, but false positives with normal TV shows
|
|
return (media.duration ?? 0) > 80 && media.episodes === 1
|
|
}
|
|
|
|
const date = new Date()
|
|
export const currentSeason = ['WINTER', 'SPRING', 'SUMMER', 'FALL'][Math.floor((date.getMonth() / 12) * 4) % 4] as 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL'
|
|
export const currentYear = date.getFullYear()
|
|
export const nextSeason = ['WINTER', 'SPRING', 'SUMMER', 'FALL'][Math.floor(((date.getMonth() + 3) / 12) * 4) % 4] as 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL'
|
|
export const nextYear = date.getFullYear() + (nextSeason === 'WINTER' ? 1 : 0)
|
|
export const lastSeason = ['WINTER', 'SPRING', 'SUMMER', 'FALL'].at(Math.floor(((date.getMonth() - 3) / 12) * 4) % 4) as 'WINTER' | 'SPRING' | 'SUMMER' | 'FALL'
|
|
export const lastYear = date.getFullYear() - (lastSeason === 'FALL' ? 1 : 0)
|
|
|
|
export function episodeByAirDate (alDate: Date | undefined, episodes: Episodes, episode: number): Episode | undefined {
|
|
if (!alDate || !+alDate) return episodes[Number(episode)] ?? episodes[episode]
|
|
// 1 is key for episod 1, not index
|
|
|
|
// find closest episodes by air date, multiple episodes can have the same air date distance
|
|
// ineffcient but reliable
|
|
const closestEpisodes: Episode[] = Object.values(episodes).reduce<Episode[]>((prev, curr) => {
|
|
if (!prev[0]) return [curr]
|
|
const prevDate = Math.abs(+new Date(prev[0].airdate ?? 0) - +alDate)
|
|
const currDate = Math.abs(+new Date(curr.airdate ?? 0) - +alDate)
|
|
if (prevDate === currDate) {
|
|
prev.push(curr)
|
|
return prev
|
|
}
|
|
if (currDate < prevDate) return [curr]
|
|
return prev
|
|
}, [])
|
|
|
|
return closestEpisodes.reduce((prev, curr) => {
|
|
return Math.abs(Number(curr.episode) - episode) < Math.abs(Number(prev.episode) - episode) ? curr : prev
|
|
})
|
|
}
|
|
|
|
export function dedupeAiring (media: ResultOf<typeof ScheduleMedia>) {
|
|
return [...media.aired?.n ?? [], ...media.notaired?.n ?? []].filter((v, i, a) => v != null && a.findIndex(s => s?.e === v.e) === i) as Array<{ a: number, e: number }>
|
|
}
|