import {useNotification} from '@kyvg/vue3-notification' const {notify} = useNotification(); const AG_SEPARATOR = '.' const guid_links = { 'episode': { 'imdb': 'https://www.imdb.com/title/{_guid}', 'tmdb': 'https://www.themoviedb.org/tv/{parent.guid_tmdb}/season/{season}/episode/{episode}', 'tvdb': 'https://thetvdb.com/dereferrer/episode/{_guid}', 'tvmaze': 'https://www.tvmaze.com/episodes/{_guid}', 'anidb': 'https://anidb.net/episode/{_guid}', 'youtube_video': 'https://www.youtube.com/watch?v={_guid}', }, 'series': { 'imdb': 'https://www.imdb.com/title/{_guid}', 'tmdb': 'https://www.themoviedb.org/tv/{_guid}', 'tvdb': 'https://thetvdb.com/dereferrer/series/{_guid}', 'tvmaze': 'https://www.tvmaze.com/shows/{_guid}/-', 'anidb': 'https://anidb.net/anime/{_guid}', 'youtube_channel': 'https://www.youtube.com/channel/{_guid}', 'youtube_playlist': 'https://www.youtube.com/playlist?list={_guid}', }, 'movie': { 'imdb': 'https://www.imdb.com/title/{_guid}', 'tmdb': 'https://www.themoviedb.org/movie/{_guid}', 'tvdb': 'https://thetvdb.com/dereferrer/movie/{_guid}', 'anidb': 'https://anidb.net/anime/{_guid}', 'youtube_video': 'https://www.youtube.com/watch?v={_guid}', }, } const YT_CH = new RegExp('(UC|HC)[a-zA-Z0-9\\-_]{22}') const YT_PL = new RegExp('PL[^\\[\\]]{32}|PL[^\\[\\]]{16}|(UU|FL|LP|RD)[^\\[\\]]{22}') /** * Get value from object or function * * @param {Function|*} obj * @returns {*} */ const getValue = (obj) => 'function' === typeof obj ? obj() : obj; /** * Get value from object or function and return default value if it's undefined or null * * @param {Object|Array} obj The object to get the value from. * @param {string} path The path to the value. * @param {*} defaultValue The default value to return if the path is not found. * * @returns {*} The value at the path or the default value. */ const ag = (obj, path, defaultValue = null) => { const keys = path.split(AG_SEPARATOR) let at = obj for (let key of keys) { if (typeof at === 'object' && at !== null && key in at) { at = at[key] } else { return getValue(defaultValue) } } return getValue(at) } /** * Set value in object by path * * @param {Object} obj The object to set the value in. * @param {string} path The path to the value. * @param {*} value The value to set. * * @returns {Object} The object with the value set. */ const ag_set = (obj, path, value) => { const keys = path.split(AG_SEPARATOR) let at = obj while (keys.length > 0) { if (keys.length === 1) { if (typeof at === 'object' && at !== null) { at[keys.shift()] = value } else { throw new Error(`Cannot set value at this path (${path}) because it's not an object.`) } } else { const key = keys.shift(); if (!at[key]) { at[key] = {} } at = at[key] } } return obj } /** * Convert bytes to human-readable file size * * @param {number} bytes The number of bytes. * @param {boolean} showUnit Whether to show the unit. * @param {number} decimals The number of decimals. * @param {number} mod The mod. * * @returns {string} The human-readable file size. */ const humanFileSize = (bytes = 0, showUnit = true, decimals = 2, mod = 1000) => { const sz = 'BKMGTP' const factor = Math.floor((bytes.toString().length - 1) / 3) return `${(bytes / (mod ** factor)).toFixed(decimals)}${showUnit ? sz[factor] : ''}` } /** * Wait for an element to be loaded in the DOM * * @param {string} sel The selector of the element. * @param {Function} callback The callback function. * * @returns {void} */ const awaitElement = (sel, callback) => { let interval = undefined let $elm = document.querySelector(sel) if ($elm) { callback(sel, $elm) return } interval = setInterval(() => { let $elm = document.querySelector(sel) if ($elm) { clearInterval(interval) callback(sel, $elm) } }, 200) } /** * Uppercase the first letter of a string * * @param {string} str The string to uppercase. * * @returns {string} The string with the first letter uppercased. */ const ucFirst = (str) => { if (typeof str !== 'string') { return str } return str.charAt(0).toUpperCase() + str.slice(1); } /** * Display a notification * * @param {string} type The type of the notification. * @param {string} title The title of the notification. * @param {string} text The text of the notification. * @param {number} duration The duration of the notification. * * @returns {void} */ const notification = (type, title, text, duration = 3000) => { let classes = '' switch (type.toLowerCase()) { case 'info': default: classes = 'has-background-info has-text-white' break case 'success': classes = 'has-background-success has-text-white' break case 'warning': classes = 'has-background-warning has-text-white' break case 'error': classes = 'has-background-danger has-text-white' if (duration === 3000) { duration = 10000 } break } return notify({title, text, type: classes, duration}) } /** * Replace tags in text with values from context * * @param {string} text The text with tags * @param {object} context The context with values * * @returns {string} The text with replaced tags */ const r = (text, context = {}) => { const tagLeft = '{'; const tagRight = '}'; if (!text.includes(tagLeft) || !text.includes(tagRight)) { return text } const pattern = new RegExp(`${tagLeft}([\\w_.]+)${tagRight}`, 'g'); const matches = text.match(pattern); if (!matches) { return text } let replacements = {}; matches.forEach(match => replacements[match] = ag(context, match.slice(1, -1), '')); for (let key in replacements) { text = text.replace(new RegExp(key, 'g'), replacements[key]); } return text } /** * Make GUID link * * @param {string} type * @param {string} source * @param {string} guid * @param {object} data * * @returns {string} */ const makeGUIDLink = (type, source, guid, data) => { if ('youtube' === source) { if (YT_CH.test(guid)) { source = 'youtube_channel' } else if (YT_PL.test(guid)) { source = 'youtube_playlist' } else { source = 'youtube_video' } } const link = ag(guid_links, `${type}.${source}`, null) return null == link ? '' : r(link, {_guid: guid, ...toRaw(data)}) } export {ag_set, ag, humanFileSize, awaitElement, ucFirst, notification, makeGUIDLink}