diff --git a/websites/A/Anikura/metadata.json b/websites/A/Anikura/metadata.json new file mode 100644 index 000000000000..e3e37c55c91d --- /dev/null +++ b/websites/A/Anikura/metadata.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://schemas.premid.app/metadata/1.16", + "apiVersion": 1, + "author": { + "name": "q_wo2", + "id": "1238250053836079160" + }, + "service": "Anikura", + "description": { + "en": "Stream your presence from Anikura to your friends on Discord.", + "ja": "Anikuraからあなたの存在をDiscordの友達にストリーミングしましょう。" + }, + "url": "anikura.club", + "regExp": "^https?[:][/][/](www[.])?anikura[.]club[/]", + "version": "1.0.0", + "logo": "https://i.imgur.com/7J9jL7R.png", + "thumbnail": "https://i.imgur.com/xK5jHDy.png", + "color": "#6366f1", + "category": "anime", + "tags": [ + "anime", + "streaming" + ], + "settings": [ + { + "id": "multiLanguage", + "title": "Multi-language support", + "icon": "fas fa-language", + "value": true + }, + { + "id": "showAnimeAsTitle", + "title": "Use anime name as header", + "icon": "fas fa-heading", + "value": false + }, + { + "id": "buttons", + "title": "Show buttons", + "icon": "fas fa-link", + "value": true + }, + { + "id": "showEpTitle", + "title": "Show episode title / chapter", + "icon": "fas fa-file-alt", + "value": true + } + ] +} diff --git a/websites/A/Anikura/presence.ts b/websites/A/Anikura/presence.ts new file mode 100644 index 000000000000..2b87cbcd3ac1 --- /dev/null +++ b/websites/A/Anikura/presence.ts @@ -0,0 +1,373 @@ +import { ActivityType, Assets, getTimestamps } from 'premid' + +const presence = new Presence({ + clientId: '1373817718192734268', +}) + +const siteStartTimestamp = Math.floor(Date.now() / 1000) +enum ActivityAssets { + Logo = 'https://i.imgur.com/7J9jL7R.png', +} + +function formatAnimeSlug(slug: string | null): string | null { + if (!slug) + return null + return slug + .split('-') + .map(w => w.charAt(0).toUpperCase() + w.slice(1)) + .join(' ') +} +function getAnimeTitleFromDOM(): string | null { + const titleSpan = document.querySelector('span[class*="truncate"][class*="text-snow"]') + || document.querySelector('span[style*="view-transition-name: title-"]') + if (titleSpan && titleSpan.textContent?.trim()) { + return titleSpan.textContent.trim() + } + const titleElem = document.querySelector('.anime-title, .show-title, h1[class*="text-snow"], h1') + if (titleElem && titleElem.textContent?.trim()) { + return titleElem.textContent.trim() + } + return null +} +function getChapterOrSubtitleFromDOM(): { chapter: string | null, subtitleLang: string | null } { + const chapterElem = document.querySelector('.vds-chapter-title') + const chapter = chapterElem?.textContent?.trim() || null + const trackElem = document.querySelector('track[default], track[kind="captions"][src]') + const subtitleLang = trackElem?.label || trackElem?.srclang?.toUpperCase() || null + return { chapter, subtitleLang } +} +function getEpisodeTitleFromDOM(): string | null { + const epTitleSpan = document.querySelector('.ep-title') + || document.querySelector('h1[class*="text-[clamp"]') + || document.querySelector('h1.text-snow') + if (epTitleSpan && epTitleSpan.textContent?.trim()) { + return epTitleSpan.textContent.trim() + } + const epImg = document.querySelector('img[alt*="titled"]') + if (!epImg) + return null + const altText = epImg.getAttribute('alt') || '' + const match = altText.match(/titled\s+['"](.+?)['"]/i) + return match && match[1] ? match[1].trim() : null +} +function parseTimeToSeconds(timeStr: string | null): number | null { + if (!timeStr) + return null + const parts = timeStr.trim().split(':').map(Number) + if (parts.some(Number.isNaN)) + return null + if (parts.length === 2) { + const [minutes = 0, seconds = 0] = parts + return minutes * 60 + seconds + } + else if (parts.length === 3) { + const [hours = 0, minutes = 0, seconds = 0] = parts + return hours * 3600 + minutes * 60 + seconds + } + return null +} +function getFollowersCountFromDOM(): string | null { + const links = Array.from(document.querySelectorAll('a, button, span, div')) + const followersLink = links.find(el => el.textContent?.toLowerCase().includes('followers')) + if (followersLink) { + const span = followersLink.querySelector('.tabular-nums') || followersLink.querySelector('span') + if (span && span.textContent?.trim()) { + return span.textContent.trim() + } + } + return null +} +function getFollowingCountFromDOM(): string | null { + const links = Array.from(document.querySelectorAll('a, button, span, div')) + const followingLink = links.find((el) => { + const text = el.textContent?.toLowerCase() || '' + return text.includes('following') && !text.includes('followers') + }) + if (followingLink) { + const span = followingLink.querySelector('.tabular-nums') || followingLink.querySelector('span') + if (span && span.textContent?.trim()) { + return span.textContent.trim() + } + } + return null +} +function getCoverImageFromDOM(epNum?: string, lang?: string, isProfile?: boolean): string | null { + if (isProfile) { + const avatarImg = document.querySelector('main img[src*="/avatars/"]') + || document.querySelector('img[src*="/avatars/"]') + const src = avatarImg?.src || avatarImg?.getAttribute('src') + if (src && src.startsWith('http')) + return src + } + + if (epNum) { + const langQuery = lang ? `[href*="lang=${lang}"]` : '' + const activeLink = document.querySelector(`a[href*="ep=${epNum}"]${langQuery}`) + || document.querySelector(`a[href*="ep=${epNum}"]`) + + const activeImg = activeLink?.querySelector('img') + if (activeImg) { + const src = activeImg.src || activeImg.getAttribute('src') + if (src && src.startsWith('http')) + return src + } + } + + const coverImg = document.querySelector('img[src*="anilistcdn/media/anime/cover/"]') + || document.querySelector('img[data-nimg="fill"]') + || document.querySelector('img[src*="screencap"]') + || document.querySelector('img[src*="episode"]') + || document.querySelector('img[class*="object-cover"]') + || document.querySelector('img._infoImage_aojp4_125') + || document.querySelector('img[class*="_infoImage_"]') + || document.querySelector('img._coverImg_2wrhc_89') + || document.querySelector('img[class*="_coverImg_"]') + const src = coverImg?.src || coverImg?.getAttribute('src') + return src && src.startsWith('http') ? src : null +} +presence.on('UpdateData', async () => { + const { pathname, href, search } = document.location + const searchParams = new URLSearchParams(search) + const [useMultiLanguage, showAnimeAsTitle, showButtons, showEpTitle] = await Promise.all([ + presence.getSetting('multiLanguage'), + presence.getSetting('showAnimeAsTitle'), + presence.getSetting('buttons'), + presence.getSetting('showEpTitle'), + ]) + const rawStrings = await presence.getStrings({ + browsing: 'general.browsing', + searching: 'general.searching', + viewHome: 'general.viewHome', + viewing: 'general.viewing', + }) + const getString = (key: keyof typeof rawStrings, fallback: string) => { + if (!useMultiLanguage) + return fallback + const val = rawStrings[key] + return val && !val.startsWith('general.') ? val : fallback + } + const presenceData: PresenceData = { + type: ActivityType.Watching, + largeImageKey: ActivityAssets.Logo, + startTimestamp: siteStartTimestamp, + } + switch (true) { + case pathname === '/' || pathname === '': { + presenceData.details = 'Anikura' + presenceData.state = getString('viewHome', 'On Homepage') + break + } + case pathname === '/login': { + presenceData.details = 'Anikura' + presenceData.state = 'Logging In' + break + } + case pathname === '/browse': { + presenceData.details = 'Anikura' + const sort = searchParams.get('sort') + const year = searchParams.get('year') + const status = searchParams.get('status') + const format = searchParams.get('format') + const audio = searchParams.get('audio') + + let base = 'Anime' + const parts: string[] = [] + if (status) + parts.push(formatAnimeSlug(status) || '') + if (year) + parts.push(year) + if (format) + parts.push(format.toUpperCase()) + + let sortText = '' + if (sort === 'score') + sortText = 'Top Rated' + else if (sort === 'year') + sortText = 'Newest' + else if (sort === 'title') + sortText = 'A-Z' + else if (sort) + sortText = `by ${formatAnimeSlug(sort)}` + + if (sortText) { + if (sortText === 'A-Z') { + base = 'Anime (A-Z)' + } + else { + parts.push(sortText) + } + } + + const filterDesc = parts.length > 0 ? `${parts.join(' ')} ${base}` : base + const audioText = audio ? ` [${formatAnimeSlug(audio)}]` : '' + presenceData.state = `Browsing ${filterDesc}${audioText}` + break + } + case pathname === '/genres' || pathname.startsWith('/genres/'): { + presenceData.details = 'Anikura' + if (pathname.startsWith('/genres/')) { + const genreSlug = pathname.replace('/genres/', '') + const genreName = formatAnimeSlug(genreSlug) + presenceData.state = `Browsing ${genreName} Genre` + } + else { + presenceData.state = 'Browsing Genres' + } + break + } + case pathname === '/profile' || pathname.startsWith('/@'): { + presenceData.details = 'Anikura' + const isProfile = pathname.startsWith('/@') + if (isProfile) { + const username = pathname.replace('/@', '') + const view = searchParams.get('view') + const tab = searchParams.get('tab') + + let subText = '' + if (tab === 'followers') { + const count = getFollowersCountFromDOM() + subText = ` (Followers${count ? `: ${count}` : ''})` + } + else if (tab === 'following') { + const count = getFollowingCountFromDOM() + subText = ` (Following${count ? `: ${count}` : ''})` + } + else if (tab) { + subText = ` (${formatAnimeSlug(tab)})` + } + else if (view) { + subText = ` (${formatAnimeSlug(view)} List)` + } + + presenceData.state = `Viewing ${username}'s Profile${subText}` + } + else { + presenceData.state = 'Viewing Profile' + } + + const coverUrl = getCoverImageFromDOM(undefined, undefined, isProfile || pathname === '/profile') + if (coverUrl) { + presenceData.largeImageKey = coverUrl + } + break + } + case pathname === '/membership': { + presenceData.details = 'Anikura' + presenceData.state = 'Viewing Membership' + break + } + case pathname.includes('/search') || searchParams.has('query') || searchParams.has('q'): { + const query = searchParams.get('query') ?? searchParams.get('q') ?? searchParams.get('search') ?? '' + const searchingStr = getString('searching', 'Searching') + presenceData.details = 'Anikura' + presenceData.state = query ? `${searchingStr} "${query}"` : `${searchingStr}...` + break + } + case /\/anime\/\d+\/[^?/#]+/i.test(pathname): { + const infoMatch = pathname.match(/\/anime\/\d+\/([^?/#]+)/i) + const animeTitle = getAnimeTitleFromDOM() || (infoMatch && infoMatch[1] ? formatAnimeSlug(infoMatch[1]) : null) + const coverUrl = getCoverImageFromDOM() + presenceData.details = animeTitle ? `${getString('viewing', 'Viewing')} ${animeTitle}` : 'Browsing Anime Info' + presenceData.state = animeTitle ? 'Reading Details & Overview' : 'Exploring Info Catalog' + presenceData.largeImageKey = coverUrl || ActivityAssets.Logo + if (showButtons) { + presenceData.buttons = [ + { + label: 'View Info', + url: href, + }, + ] + } + break + } + case /\/watch\/\d+\/[^?/#]+/i.test(pathname): { + const watchMatch = pathname.match(/\/watch\/\d+\/([^?/#]+)/i) + const epNum = searchParams.get('ep') ?? '1' + const lang = searchParams.get('lang') ?? '' + const showTitle = getAnimeTitleFromDOM() || (watchMatch && watchMatch[1] ? formatAnimeSlug(watchMatch[1]) : null) || 'Anime' + const epTitle = getEpisodeTitleFromDOM() + const { chapter, subtitleLang } = getChapterOrSubtitleFromDOM() + const coverUrl = getCoverImageFromDOM(epNum, lang) + let epLine = `Episode ${epNum}` + const finalEpTitle = showEpTitle ? epTitle : null + if (showEpTitle && (chapter || finalEpTitle)) { + epLine += ` - ${chapter || finalEpTitle}` + } + const finalLang = lang || subtitleLang + if (finalLang) { + epLine += ` [${finalLang.charAt(0).toUpperCase() + finalLang.slice(1)}]` + } + if (showAnimeAsTitle && showTitle) { + presenceData.name = showTitle + presenceData.details = epLine + delete presenceData.state + } + else { + delete presenceData.name + presenceData.details = showTitle + presenceData.state = epLine + } + presenceData.largeImageKey = coverUrl || ActivityAssets.Logo + + const video = document.querySelector('video') + const isPaused = video ? video.paused : true + + if (!isPaused) { + delete presenceData.smallImageKey + delete presenceData.smallImageText + + const timeSpans = Array.from(document.querySelectorAll('span[class*="font-medium"][class*="tracking-wide"]')) + const currentTimeText = timeSpans[0]?.textContent || null + const durationText = timeSpans[1]?.textContent || null + + const elementTime = parseTimeToSeconds(currentTimeText) ?? video?.currentTime ?? 0 + const elementDuration = parseTimeToSeconds(durationText) ?? video?.duration ?? 0 + + if (elementDuration > 0) { + const [start, end] = getTimestamps(elementTime, elementDuration) + presenceData.startTimestamp = start + presenceData.endTimestamp = end + } + else { + presenceData.startTimestamp = siteStartTimestamp + delete presenceData.endTimestamp + } + } + else { + presenceData.smallImageKey = Assets.Play + presenceData.smallImageText = 'Paused' + + presenceData.startTimestamp = siteStartTimestamp + delete presenceData.endTimestamp + } + + if (showButtons) { + presenceData.buttons = [ + { + label: 'Watch Episode', + url: href, + }, + ] + } + break + } + default: { + const catalogMatch = pathname.match(/\/(?:anime|watch)\/\d+\/([^?/#]+)/i) + const animeTitle = getAnimeTitleFromDOM() || (catalogMatch && catalogMatch[1] ? formatAnimeSlug(catalogMatch[1]) : null) + const coverUrl = getCoverImageFromDOM() + presenceData.details = getString('browsing', 'Browsing...') + presenceData.state = animeTitle ? `${getString('viewing', 'Viewing')} ${animeTitle}` : 'Exploring Catalog' + presenceData.largeImageKey = coverUrl || ActivityAssets.Logo + if (showButtons) { + presenceData.buttons = [ + { + label: 'View Page', + url: href, + }, + ] + } + break + } + } + presence.setActivity(presenceData) +}) diff --git a/websites/N/Nekowatch/metadata.json b/websites/N/Nekowatch/metadata.json new file mode 100644 index 000000000000..24828a4d0bcc --- /dev/null +++ b/websites/N/Nekowatch/metadata.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://schemas.premid.app/metadata/1.16", + "apiVersion": 1, + "author": { + "name": "q_wo2", + "id": "1238250053836079160" + }, + "service": "Nekowatch", + "description": { + "en": "Stream your presence from Nekowatch to your friends on Discord.", + "ja": "Nekowatchからあなたの存在をDiscordの友達にストリーミングしましょう。" + }, + "url": "nekowatch.xyz", + "regExp": "^https?[:][/][/](www[.])?nekowatch[.]xyz[/]", + "version": "1.0.0", + "logo": "https://i.imgur.com/LtP6hmP.jpeg", + "thumbnail": "https://i.imgur.com/Q7jwfg0.png", + "color": "#6366f1", + "category": "anime", + "tags": [ + "anime", + "streaming" + ], + "settings": [ + { + "id": "multiLanguage", + "title": "Multi-language support", + "icon": "fas fa-language", + "value": true + }, + { + "id": "showAnimeAsTitle", + "title": "Use anime name as header", + "icon": "fas fa-heading", + "value": false + }, + { + "id": "buttons", + "title": "Show buttons", + "icon": "fas fa-link", + "value": true + } + ] +} diff --git a/websites/N/Nekowatch/presence.ts b/websites/N/Nekowatch/presence.ts new file mode 100644 index 000000000000..f8ed34a27ecd --- /dev/null +++ b/websites/N/Nekowatch/presence.ts @@ -0,0 +1,305 @@ +import { ActivityType, Assets, getTimestamps } from 'premid' + +const presence = new Presence({ + clientId: '1373817718192734268', +}) + +const siteStartTimestamp = Math.floor(Date.now() / 1000) +let pauseStartTimestamp: number | null = null + +function formatAnimeSlug(slug: string | null): string | null { + if (!slug) + return null + return slug + .split(/[-_]/) + .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()) + .join(' ') +} +function getAnimeTitleFromDOM(pathname: string): string | null { + if (pathname === '/info.html') { + const titleElem = document.querySelector('#info-title') + if (titleElem && titleElem.textContent?.trim()) { + return titleElem.textContent.trim() + } + } + if (pathname === '/anime.html') { + const titleElem = document.querySelector('#aa-name') + if (titleElem && titleElem.textContent?.trim()) { + return titleElem.textContent.trim() + } + } + return null +} +function parseTimeToSeconds(timeStr: string | null): number | null { + if (!timeStr) + return null + const parts = timeStr.trim().split(':').map(Number) + if (parts.some(Number.isNaN)) + return null + if (parts.length === 2) { + const [minutes = 0, seconds = 0] = parts + return minutes * 60 + seconds + } + else if (parts.length === 3) { + const [hours = 0, minutes = 0, seconds = 0] = parts + return hours * 3600 + minutes * 60 + seconds + } + return null +} + +function getCoverImageFromDOM(pathname: string, isProfile?: boolean): string | null { + if (isProfile || pathname === '/profile.html') { + const avatarImg = document.querySelector('img[src*="user/avatar/"], img[alt*="avatar"]') + const src = avatarImg?.src || avatarImg?.getAttribute('src') + if (src && src.startsWith('http')) + return src + } + + if (pathname === '/anime.html') { + const posterDiv = document.querySelector('#aa-poster') + if (posterDiv) { + const style = posterDiv.style.backgroundImage + const match = style?.match(/url\((['"]?)(.*?)\1\)/) + const src = match ? match[2] : null + if (src && src.startsWith('http')) + return src + } + } + + if (pathname === '/info.html') { + const infoPoster = document.querySelector('#info-poster-img') + if (infoPoster) { + const src = infoPoster.getAttribute('data-nw-last-good-src') + || infoPoster.getAttribute('data-nw-motion-source') + || infoPoster.src + || infoPoster.getAttribute('src') + if (src && src.startsWith('http')) + return src + } + } + + const coverImg = document.querySelector('img[src*="anilistcdn/media/anime/cover/"]') + || document.querySelector('img[data-nw-last-good-src*="anilistcdn"]') + || document.querySelector('img[data-nimg="fill"]') + || document.querySelector('img[src*="screencap"]') + || document.querySelector('img[src*="episode"]') + || document.querySelector('img[class*="object-cover"]') + const src = coverImg?.src || coverImg?.getAttribute('src') || coverImg?.getAttribute('data-nw-last-good-src') + return src && src.startsWith('http') ? src : null +} +presence.on('UpdateData', async () => { + const { pathname, href, search } = document.location + const searchParams = new URLSearchParams(search) + const [useMultiLanguage, showAnimeAsTitle, showButtons] = await Promise.all([ + presence.getSetting('multiLanguage'), + presence.getSetting('showAnimeAsTitle'), + presence.getSetting('buttons'), + ]) + const rawStrings = await presence.getStrings({ + browsing: 'general.browsing', + searching: 'general.searching', + viewHome: 'general.viewHome', + viewing: 'general.viewing', + }) + const getString = (key: keyof typeof rawStrings, fallback: string) => { + if (!useMultiLanguage) + return fallback + const val = rawStrings[key] + return val && !val.startsWith('general.') ? val : fallback + } + const presenceData: PresenceData = { + type: ActivityType.Watching, + largeImageKey: 'https://i.imgur.com/LtP6hmP.jpeg', + startTimestamp: siteStartTimestamp, + } + switch (true) { + case pathname === '/' || pathname === '/home': { + presenceData.details = 'Nekowatch' + presenceData.state = getString('viewHome', 'On Homepage') + break + } + case pathname === '/browse.html': { + presenceData.details = 'Nekowatch' + const q = searchParams.get('q') + const sort = searchParams.get('sort') + const season = searchParams.get('season') + const status = searchParams.get('status') + const format = searchParams.get('format') + const year = searchParams.get('year') + const audio = searchParams.get('audio') + + presenceData.details = q ? `Searching: "${q}"` : 'Browsing Catalog' + + const filters: string[] = [] + if (season) + filters.push(formatAnimeSlug(season) || '') + if (year) + filters.push(year) + if (format) + filters.push(format.toUpperCase() === 'TV' ? 'TV' : (formatAnimeSlug(format) || '')) + if (status) + filters.push(formatAnimeSlug(status) || '') + if (audio) + filters.push(formatAnimeSlug(audio) || '') + + let sortText = '' + if (sort === 'SCORE_DESC') + sortText = 'Top Rated' + else if (sort === 'POPULARITY_DESC') + sortText = 'Popular' + else if (sort === 'UPDATED_AT_DESC') + sortText = 'Recently Updated' + else if (sort === 'START_DATE_DESC') + sortText = 'Newest' + else if (sort) + sortText = `Sort: ${formatAnimeSlug(sort.replace('_DESC', ''))}` + + if (sortText) { + filters.push(sortText) + } + + presenceData.state = filters.length > 0 ? filters.join(' • ') : 'All Anime' + break + } + case pathname === '/circles': { + presenceData.details = 'Nekowatch' + presenceData.state = 'Watching in Circles' + break + } + case pathname === '/schedule.html': { + presenceData.details = 'Nekowatch' + presenceData.state = 'Checking Release Schedule' + break + } + case pathname === '/donate': { + presenceData.details = 'Nekowatch' + presenceData.state = 'Viewing Donation Page' + break + } + case pathname === '/settings.html': { + presenceData.details = 'Nekowatch' + presenceData.state = 'Viewing Settings' + break + } + case pathname === '/feedback': { + presenceData.details = 'Nekowatch' + presenceData.state = 'Viewing Feedback Page' + break + } + case pathname === '/profile.html': { + presenceData.details = 'Nekowatch' + const usernameElem = document.querySelector('.anilist-profile-name') + const username = usernameElem?.textContent?.trim() || null + presenceData.state = username ? `Viewing ${username}'s Profile` : 'Viewing Profile' + + const coverUrl = getCoverImageFromDOM(pathname, true) + if (coverUrl) { + presenceData.largeImageKey = coverUrl + } + break + } + case pathname === '/info.html': { + const animeTitle = getAnimeTitleFromDOM(pathname) + presenceData.details = 'Viewing Anime Info' + presenceData.state = animeTitle || 'Reading Details & Overview' + presenceData.largeImageKey = 'https://i.imgur.com/LtP6hmP.jpeg' + if (showButtons) { + presenceData.buttons = [ + { + label: 'View Info', + url: href, + }, + ] + } + break + } + case pathname === '/anime.html': { + const epNum = searchParams.get('ep') ?? '1' + const lang = searchParams.get('audio') ?? '' + const showTitle = getAnimeTitleFromDOM(pathname) || 'Anime' + const coverUrl = getCoverImageFromDOM(pathname) + let epLine = `Episode ${epNum}` + if (lang) { + epLine += ` [${lang.charAt(0).toUpperCase() + lang.slice(1)}]` + } + if (showAnimeAsTitle && showTitle) { + presenceData.name = showTitle + presenceData.details = epLine + delete presenceData.state + } + else { + delete presenceData.name + presenceData.details = showTitle + presenceData.state = epLine + } + presenceData.largeImageKey = coverUrl || 'https://i.imgur.com/LtP6hmP.jpeg' + + const videos = Array.from(document.querySelectorAll('video')) + const video = videos.find(v => v.src && !v.src.startsWith('blob:') && v.src.startsWith('http')) + || videos.find(v => v.src) + || videos[0] + || null + const isPaused = video ? video.paused : true + + const timeSpans = Array.from(document.querySelectorAll('span[class*="font-medium"][class*="tracking-wide"]')) + const currentTimeText = timeSpans[0]?.textContent || null + const durationText = timeSpans[1]?.textContent || null + + const elementTime = parseTimeToSeconds(currentTimeText) ?? video?.currentTime ?? 0 + const elementDuration = parseTimeToSeconds(durationText) ?? video?.duration ?? 0 + + if (!isPaused) { + pauseStartTimestamp = null + delete presenceData.smallImageKey + delete presenceData.smallImageText + + if (elementDuration > 0) { + const [start, end] = getTimestamps(elementTime, elementDuration) + presenceData.startTimestamp = start + presenceData.endTimestamp = end + } + else { + presenceData.startTimestamp = siteStartTimestamp + delete presenceData.endTimestamp + } + } + else { + presenceData.smallImageKey = Assets.Play + presenceData.smallImageText = 'Paused' + + if (!pauseStartTimestamp) { + pauseStartTimestamp = Math.floor(Date.now() / 1000) + } + presenceData.startTimestamp = pauseStartTimestamp + delete presenceData.endTimestamp + } + + if (showButtons) { + presenceData.buttons = [ + { + label: 'Watch Episode', + url: href, + }, + ] + } + break + } + default: { + const animeTitle = getAnimeTitleFromDOM(pathname) + const coverUrl = getCoverImageFromDOM(pathname) + presenceData.details = getString('browsing', 'Browsing...') + presenceData.state = animeTitle ? `${getString('viewing', 'Viewing')} ${animeTitle}` : 'Exploring Catalog' + presenceData.largeImageKey = coverUrl || 'https://i.imgur.com/LtP6hmP.jpeg' + if (showButtons) { + presenceData.buttons = [ + { + label: 'View Page', + url: href, + }, + ] + } + break + } + } + presence.setActivity(presenceData) +})