import { type ClassValue, clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; import { goto } from '$app/navigation'; import { resolve } from '$app/paths'; import { toast } from 'svelte-sonner'; import client from '$lib/api'; import type { components } from '$lib/api/api'; export const qualityMap: { [key: number]: string } = { 1: '4K/UHD', 2: '1080p/FullHD', 3: '720p/HD', 4: '480p/SD', 5: 'unknown' }; export const torrentStatusMap: { [key: number]: string } = { 1: 'finished', 2: 'downloading', 3: 'error', 4: 'unknown' }; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } export function getTorrentQualityString(value: number): string { return qualityMap[value] || 'unknown'; } export function getTorrentStatusString(value: number): string { return torrentStatusMap[value] || 'unknown'; } export function getFullyQualifiedMediaName(media: { name: string; year: number | null }): string { let name = media.name; if (media.year != null) { name += ' (' + media.year + ')'; } return name; } export function convertTorrentSeasonRangeToIntegerRange(seasons: number[]): string { if (seasons.length === 1) return seasons[0]?.toString() || 'unknown'; else if (seasons.length > 1) { const lastSeason = seasons.at(-1); return seasons[0]?.toString() + '-' + (lastSeason?.toString() || 'unknown'); } else { console.log('Error parsing season range: ' + seasons); return 'Error parsing season range: ' + seasons; } } export function convertTorrentEpisodeRangeToIntegerRange(episodes: number[]): string { if (episodes.length === 1) return episodes[0]?.toString() || 'unknown'; else if (episodes.length > 1) { const lastEpisode = episodes.at(-1); return episodes[0]?.toString() + '-' + (lastEpisode?.toString() || 'unknown'); } else { console.log('Error parsing episode range: ' + episodes); return 'Error parsing episode range: ' + episodes; } } export async function handleLogout() { await client.POST('/api/v1/auth/cookie/logout'); await goto(resolve('/login', {})); } export async function handleOauth() { const { error, data } = await client.GET(`/api/v1/auth/oauth/authorize`, { params: { query: { scopes: ['openid', 'email', 'profile'] } } }); if (!error && data?.authorization_url) { window.location.href = data.authorization_url; } else { toast.error('Failed to initiate OAuth login.'); } } export function formatSecondsToOptimalUnit(seconds: number): string { if (seconds < 0) return '0s'; const units = [ { name: 'y', seconds: 365.25 * 24 * 60 * 60 }, // year (accounting for leap years) { name: 'mo', seconds: 30.44 * 24 * 60 * 60 }, // month (average) { name: 'd', seconds: 24 * 60 * 60 }, // day { name: 'h', seconds: 60 * 60 }, // hour { name: 'm', seconds: 60 }, // minute { name: 's', seconds: 1 } // second ]; for (const unit of units) { const value = seconds / unit.seconds; if (value >= 1) { return `${Math.floor(value)}${unit.name}`; } } return '0s'; } export function handleQueryNotificationToast(count: number = 0, query: string = '') { if (count > 0 && query.length > 0) toast.success(`Found ${count} ${count > 1 ? 'result' : 'results'} for search term "${query}".`); else if (count == 0) toast.info(`No results found for "${query}".`); } export function saveDirectoryPreview( media: components['schemas']['Show'] | components['schemas']['Movie'], filePathSuffix: string = '' ) { let path = '/' + getFullyQualifiedMediaName(media) + ' [' + media.metadata_provider + 'id-' + media.external_id + ']/'; if ('seasons' in media) { path += ' Season XX/SXXEXX' + (filePathSuffix === '' ? '' : ' - ' + filePathSuffix) + '.mkv'; } else { path += media.name + (filePathSuffix === '' ? '' : ' - ' + filePathSuffix) + '.mkv'; } return path; } export function semverIsGreater(a: string, b: string) { return a.localeCompare(b, undefined, { numeric: true }) === 1; } export function isSemver(str: string) { return str.match( /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/ ); }