123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- // Note: Developer has been working on a new website design for ages,
- // maybe at some point he'll finish it and this will need updating.
- import { Assets, getTimestamps } from 'premid'
- const presence = new Presence({
- clientId: '629355416714739732',
- })
- async function getStrings() {
- return presence.getStrings(
- {
- play: 'general.playing',
- pause: 'general.paused',
- browse: 'general.browsing',
- page: 'general.page',
- episode: 'general.episode',
- watching: 'general.watching',
- watchingMovie: 'general.watchingMovie',
- view: 'general.view',
- viewGenre: 'general.viewGenre',
- viewCategory: 'general.viewCategory',
- viewPage: 'general.viewPage',
- viewMovie: 'general.viewMovie',
- watchEpisode: 'general.buttonViewEpisode',
- watchMovie: 'general.buttonViewMovie',
- latest: 'animepahe.latestRelease',
- season: 'animepahe.season',
- special: 'animepahe.special',
- viewOn: 'animepahe.view',
- timeSeason: 'animepahe.timeSeason',
- },
- )
- }
- let strings: Awaited<ReturnType<typeof getStrings>>
- let oldLang: string | null = null
- let iframeResponse = {
- paused: true,
- duration: 0,
- currentTime: 0,
- }
- type storeType = Record<
- string,
- { id: number, listing: [string, string], time: number }
- >
- class AnimeStorage {
- private list: storeType
- public anime(title: string, listing: [string, string] | false) {
- if (this.list[title] && this.list[title].listing) {
- return this.list[title]
- }
- else if (listing) {
- this.list[title] = {
- id: Number(
- document.querySelector<HTMLMetaElement>('meta[name=id]')?.content ?? '',
- ),
- listing,
- time: Date.now(),
- }
- // Removes the oldest stored anime if the store length has exceeded 10
- if (Object.keys(this.list).length === 11) {
- delete this.list[
- Object.entries(Object.assign({}, this.list)).sort(
- (a, b) => a[1].time - b[1].time,
- )[0]![0]
- ]
- }
- localStorage.setItem('presence_data', btoa(JSON.stringify(this.list)))
- }
- }
- constructor() {
- let storage: storeType | string | null = localStorage.getItem('presence_data')
- if (storage) {
- storage = JSON.parse(atob(storage))
- this.list = storage as storeType
- if (!Object.entries(this.list)[0]![1].listing)
- this.list = {}
- }
- else {
- this.list = {}
- }
- }
- }
- const animeStore = new AnimeStorage()
- function getTimes(time: number): {
- sec: number
- min: number
- hrs: number
- } {
- let seconds = Math.round(time)
- let minutes = Math.floor(seconds / 60)
- seconds -= minutes * 60
- const hours = Math.floor(minutes / 60)
- minutes -= hours * 60
- return {
- sec: seconds,
- min: minutes,
- hrs: hours,
- }
- }
- const lessTen = (d: number) => (d < 10 ? '0' : '')
- function getTimestamp(time: number): string {
- const { sec, min, hrs } = getTimes(time)
- return hrs > 0
- ? `${hrs}:${lessTen(min)}${min}:${lessTen(sec)}${sec}`
- : `${min}:${lessTen(sec)}${sec}`
- }
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
- const uncapitalize = (s: string) => s.charAt(0).toLowerCase() + s.slice(1)
- function parseInfo(dom: HTMLParagraphElement[]) {
- const entries: Record<string, string | HTMLAnchorElement[]> = {}
- for (const entry of dom) {
- let title = entry.children[0]?.textContent?.slice(0, -1) ?? ''
- const [, secondChild] = entry.childNodes
- if (title.includes(' ')) {
- title = title
- .split(' ')
- .map(e => uncapitalize(e))
- .join('_')
- }
- else {
- title = uncapitalize(title)
- }
- if (secondChild?.nodeName === '#text' && entry.childNodes.length === 2) {
- entries[title] = secondChild.textContent ?? ''
- }
- else {
- entries[title] = []
- for (const node of entry.childNodes) {
- if (node.nodeName !== 'STRONG' && node.nodeName !== '#text') {
- (entries[title] as HTMLAnchorElement[]).push(
- node as HTMLAnchorElement,
- )
- }
- }
- }
- }
- return entries
- }
- presence.on(
- 'iFrameData',
- (data: unknown) => {
- iframeResponse = data as typeof iframeResponse
- },
- )
- enum ActivityAssets {
- Logo = 'https://cdn.rcd.gg/PreMiD/websites/A/animepahe/assets/logo.png',
- BrowsingHome = 'https://cdn.rcd.gg/PreMiD/websites/A/animepahe/assets/0.png',
- BrowsingAll = 'https://cdn.rcd.gg/PreMiD/websites/A/animepahe/assets/1.png',
- BrowsingGenre = 'https://cdn.rcd.gg/PreMiD/websites/A/animepahe/assets/2.png',
- BrowsingTime = 'https://cdn.rcd.gg/PreMiD/websites/A/animepahe/assets/3.png',
- BrowsingSeason = 'https://cdn.rcd.gg/PreMiD/websites/A/animepahe/assets/4.png',
- }
- presence.on('UpdateData', async () => {
- const path = document.location.pathname.split('/').slice(1)
- const presenceData: PresenceData = {
- largeImageKey: ActivityAssets.Logo,
- details: 'loading',
- startTimestamp: Math.floor(Date.now() / 1000),
- }
- const newLang = await presence.getSetting<string>('lang').catch(() => 'en')
- if (oldLang !== newLang || !strings) {
- oldLang = newLang
- strings = await getStrings()
- }
- const viewing = strings.view.slice(0, -1)
- switch (path[0]) {
- // homepage / browsing new releases
- case '':
- {
- presenceData.details = strings.latest
- let page = new URLSearchParams(document.location.href)
- .values()
- .next()
- .value
- if (page === '')
- page = '1'
- presenceData.state = `${strings.page} ${page}`
- presenceData.smallImageKey = ActivityAssets.BrowsingHome
- presenceData.smallImageText = strings.browse
- }
- break
- case 'anime': {
- // browsing a-z all
- if (path.length === 1) {
- presenceData.details = `${viewing} A-Z:`
- presenceData.state = document.querySelector('a.nav-link.active')?.textContent
- presenceData.smallImageKey = ActivityAssets.BrowsingAll
- presenceData.smallImageText = strings.browse
- }
- else {
- switch (path[1]) {
- case 'genre':
- {
- // viewing genre
- presenceData.details = strings.viewGenre
- presenceData.state = capitalize(path[2]!)
- presenceData.smallImageKey = ActivityAssets.BrowsingGenre
- presenceData.smallImageText = strings.browse
- break
- }
- case 'season':
- {
- // viewing anime/time season
- presenceData.details = `${viewing} Anime ${strings.timeSeason}:`
- presenceData.state = document.querySelectorAll('h1')[0]?.textContent
- presenceData.smallImageKey = ActivityAssets.BrowsingTime
- presenceData.smallImageText = strings.browse
- break
- }
- default: {
- if (
- !path[1]?.match(
- /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i,
- )
- ) {
- // viewing a misc. category (eg. Airing, TV, etc)
- presenceData.details = strings.viewCategory
- const heading = document.querySelectorAll('h1')[0]?.textContent
- presenceData.state = heading?.includes(' ')
- ? heading
- .split(' ')
- .map(s => capitalize(s))
- .join(' ')
- : capitalize(heading ?? '')
- presenceData.smallImageKey = ActivityAssets.BrowsingAll
- presenceData.smallImageText = strings.browse
- }
- else {
- // viewing specific
- const info = parseInfo(
- document.querySelectorAll('.anime-info')[0]!
- .children as unknown as HTMLParagraphElement[],
- )
- const title = document.querySelectorAll('.title-wrapper')[0]?.children[1]
- ?.textContent
- const listing = (() => {
- const links = info.external_links as HTMLAnchorElement[]
- if (links[0]?.textContent === 'AniList')
- return ['AniList', links[0].href]
- for (const link of links) {
- if (link.textContent === 'MyAnimeList')
- return ['MAL', link.href]
- }
- })() as [string, string]
- presenceData.details = (() => {
- switch ((info.type?.[0] as HTMLAnchorElement)?.textContent) {
- case 'Movie':
- return strings.viewMovie
- case 'TV':
- return `${viewing} ${strings.season}:`
- case 'Special':
- return `${viewing} ${strings.special}:`
- default:
- return `${viewing} ${
- (info.type?.[0] as HTMLAnchorElement)?.textContent
- }:`
- }
- })()
- presenceData.state = title
- presenceData.largeImageKey = document.querySelector<HTMLAnchorElement>(
- '.youtube-preview',
- )?.href ?? ''
- presenceData.smallImageKey = ActivityAssets.BrowsingSeason
- presenceData.smallImageText = strings.browse
- presenceData.buttons = [
- {
- label: strings.viewOn.replace('{0}', 'Pahe'),
- url: `https://pahe.win/a/${
- animeStore.anime(title ?? '', listing)?.id ?? ''
- }`,
- },
- {
- label: strings.viewOn.replace('{0}', listing[0]),
- url: listing[1],
- },
- ]
- }
- }
- }
- }
- break
- }
- // playback
- case 'play':
- {
- const movie: boolean = document.querySelectorAll('.anime-status')[0]?.firstElementChild
- ?.textContent === 'Movie'
- const title = document.querySelectorAll('.theatre-info')[0]?.children[1]
- ?.children[1]
- ?.textContent
- const episode = Number.parseInt(
- document
- .querySelector('#episodeMenu')
- ?.textContent
- ?.split('Episode ')[1]
- ?.replace(/^\s+|\s+$/g, '') ?? '',
- )
- if (!movie) {
- presenceData.details = `${strings.watching.slice(0, -1)} ${
- strings.episode
- } ${episode}`
- }
- else {
- presenceData.details = strings.watchingMovie
- }
- presenceData.state = title
- presenceData.largeImageKey = document
- .querySelector<HTMLImageElement>('.anime-poster')
- ?.src
- ?.replace('.th', '') ?? ''
- presenceData.smallImageKey = iframeResponse.paused
- ? Assets.Pause
- : Assets.Play
- presenceData.smallImageText = iframeResponse.paused
- ? strings.pause
- : strings.play
- if (!iframeResponse.paused) {
- [presenceData.startTimestamp, presenceData.endTimestamp] = getTimestamps(
- Math.floor(iframeResponse.currentTime),
- Math.floor(iframeResponse.duration),
- )
- }
- else {
- presenceData.smallImageText += ` - ${getTimestamp(
- iframeResponse.currentTime,
- )}`
- }
- const anime = animeStore.anime(title ?? '', false)
- if (anime) {
- presenceData.buttons = [
- {
- label: movie ? strings.watchMovie : strings.watchEpisode,
- url: `https://pahe.win/a/${anime.id}/${episode}`,
- },
- {
- label: strings.viewOn.replace('{0}', anime.listing[0]),
- url: anime.listing[1],
- },
- ]
- }
- }
- break
- default: {
- presenceData.details = strings.viewPage
- presenceData.state = document.querySelectorAll('h1')[0]?.textContent
- }
- }
- presence.setActivity(presenceData)
- })
|