import { SUPPORTS } from '@/modules/support.js' import levenshtein from 'js-levenshtein' export function countdown (s) { const d = Math.floor(s / (3600 * 24)) s -= d * 3600 * 24 const h = Math.floor(s / 3600) s -= h * 3600 const m = Math.floor(s / 60) s -= m * 60 const tmp = [] if (d) tmp.push(d + 'd') if (d || h) tmp.push(h + 'h') if (d || h || m) tmp.push(m + 'm') return tmp.join(' ') } const formatter = (typeof Intl !== 'undefined') && new Intl.RelativeTimeFormat('en') const ranges = { years: 3600 * 24 * 365, months: 3600 * 24 * 30, weeks: 3600 * 24 * 7, days: 3600 * 24, hours: 3600, minutes: 60, seconds: 1 } /** * @template T * @param {T[]} arr * @param {number} n */ export function * chunks (arr, n) { for (let i = 0; i < arr.length; i += n) { yield arr.slice(i, i + n) } } /** @param {Date} date */ export function since (date) { const secondsElapsed = (date.getTime() - Date.now()) / 1000 for (const key in ranges) { if (ranges[key] < Math.abs(secondsElapsed)) { const delta = secondsElapsed / ranges[key] // @ts-ignore return formatter.format(Math.round(delta), key) } } } const units = [' B', ' kB', ' MB', ' GB', ' TB'] export function fastPrettyBytes (num) { if (isNaN(num)) return '0 B' if (num < 1) return num + ' B' const exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), units.length - 1) return Number((num / Math.pow(1000, exponent)).toFixed(2)) + units[exponent] } /** @type {DOMParser['parseFromString']} */ export const DOMPARSER = (typeof DOMParser !== 'undefined') && DOMParser.prototype.parseFromString.bind(new DOMParser()) export const sleep = t => new Promise(resolve => setTimeout(resolve, t).unref?.()) export function toTS (sec, full) { if (isNaN(sec) || sec < 0) { switch (full) { case 1: return '0:00:00.00' case 2: return '0:00:00' case 3: return '00:00' default: return '0:00' } } const hours = Math.floor(sec / 3600) /** @type {any} */ let minutes = Math.floor(sec / 60) - hours * 60 /** @type {any} */ let seconds = full === 1 ? (sec % 60).toFixed(2) : Math.floor(sec % 60) if (minutes < 10 && (hours > 0 || full)) minutes = '0' + minutes if (seconds < 10) seconds = '0' + seconds return (hours > 0 || full === 1 || full === 2) ? hours + ':' + minutes + ':' + seconds : minutes + ':' + seconds } export function generateRandomHexCode (len) { let hexCode = '' while (hexCode.length < len) { hexCode += (Math.round(Math.random() * 15)).toString(16) } return hexCode } export function generateRandomString(length) { let string = '' const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~' for (let i = 0; i < length; i++) { string += possible.charAt(Math.floor(Math.random() * possible.length)) } return string } export function matchPhrase(search, phrase, threshold) { if (!search) return false const normalizedSearch = search.toLowerCase().replace(/[^\w\s]/g, '') phrase = Array.isArray(phrase) ? phrase : [phrase] for (let p of phrase) { const normalizedPhrase = p.toLowerCase().replace(/[^\w\s]/g, '') if (normalizedSearch.includes(normalizedPhrase)) return true const wordsInFileName = normalizedSearch.split(/\s+/) for (let word of wordsInFileName) { if (levenshtein(word, normalizedPhrase) <= threshold) return true } } return false } export function throttle (fn, time) { let wait = false return (...args) => { if (!wait) { fn(...args) wait = true setTimeout(() => { fn(...args) wait = false }, time).unref?.() } } } export function debounce (fn, time) { let timeout return (...args) => { const later = () => { timeout = null fn(...args) } clearTimeout(timeout) timeout = setTimeout(later, time) timeout.unref?.() } } export const defaults = { volume: 1, playerAutoplay: true, playerPause: true, playerAutocomplete: true, playerAutoSkip: false, playerDeband: false, rssQuality: '1080', rssFeedsNew: SUPPORTS.extensions ? [['New Releases', 'ASW [Small Size]']] : [], rssAutoplay: false, torrentSpeed: 5, // legacy downloadSpeed: 5, uploadSpeed: 1, slowSeeding: true, disableStartupVideo: true, amoledTheme: true, enableAutoUpdate: !SUPPORTS.isAndroid, sortByEco: true, torrentPersist: false, torrentDHT: false, torrentPeX: false, torrentPort: 0, torrentStreamedDownload: true, dhtPort: 0, missingFont: true, maxConns: 50, subtitleRenderHeight: SUPPORTS.isAndroid ? '1080' : '0', subtitleLanguage: 'eng', audioLanguage: 'jpn', enableDoH: false, doHURL: 'https://cloudflare-dns.com/dns-query', disableSubtitleBlur: SUPPORTS.isAndroid, enableRPC: false, showDetailsInRPC: true, smoothScroll: false, cards: 'small', cardAudio: false, titleLang: 'english', hideMyAnime: false, expandingSidebar: !SUPPORTS.isAndroid, torrentPathNew: undefined, font: undefined, angle: 'default', toshoURL: SUPPORTS.extensions ? decodeURIComponent(atob('aHR0cHM6Ly9mZWVkLmFuaW1ldG9zaG8ub3JnLw==')) : '', extensions: SUPPORTS.extensions ? ['anisearch'] : [], sources: {}, enableExternal: false, playerPath: '', playerSeek: 5 } export const subtitleExtensions = ['srt', 'vtt', 'ass', 'ssa', 'sub', 'txt'] export const subRx = new RegExp(`.(${subtitleExtensions.join('|')})$`, 'i') export const videoExtensions = ['3g2', '3gp', 'asf', 'avi', 'dv', 'flv', 'gxf', 'm2ts', 'm4a', 'm4b', 'm4p', 'm4r', 'm4v', 'mkv', 'mov', 'mp4', 'mpd', 'mpeg', 'mpg', 'mxf', 'nut', 'ogm', 'ogv', 'swf', 'ts', 'vob', 'webm', 'wmv', 'wtv'] export const videoRx = new RegExp(`.(${videoExtensions.join('|')})$`, 'i') // freetype supported export const fontExtensions = ['ttf', 'ttc', 'woff', 'woff2', 'otf', 'cff', 'otc', 'pfa', 'pfb', 'pcf', 'fnt', 'bdf', 'pfr', 'eot'] export const fontRx = new RegExp(`.(${fontExtensions.join('|')})$`, 'i')