diff --git a/websites/S/Spotify Podcasts/Spotify Podcasts.json b/websites/S/Spotify Podcasts/Spotify Podcasts.json deleted file mode 100644 index 82b3bbf2da06..000000000000 --- a/websites/S/Spotify Podcasts/Spotify Podcasts.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "spotify.albumLike": { - "description": "\"Browsing through\" + this", - "message": "albums that they like" - }, - "spotify.artistsLike": { - "description": "\"Browsing through\" + this", - "message": "artists that they like" - }, - "spotify.bestPodcasts": { - "description": "", - "message": "Best podcasts" - }, - "spotify.browse": { - "description": "First section of the next 6 strings", - "message": "Browsing through" - }, - "spotify.charts": { - "description": "", - "message": "Viewing the charts" - }, - "spotify.discover": { - "description": "", - "message": "Discovering new songs" - }, - "spotify.download": { - "description": "", - "message": "Downloading Spotify" - }, - "spotify.featured": { - "description": "", - "message": "Featured songs" - }, - "spotify.genres": { - "description": "", - "message": "Browsing through genres" - }, - "spotify.latest": { - "description": "", - "message": "Viewing the latest releases" - }, - "spotify.madeForYou": { - "description": "\"Browsing through\" + this (Recommend by spotify is what it means)", - "message": "\"Made for you\"" - }, - "spotify.playlists": { - "description": "\"Browsing through\" + this", - "message": "their playlists" - }, - "spotify.podcastsLike": { - "description": "\"Browsing through\" + this", - "message": "podcasts that they like" - }, - "spotify.songsLike": { - "description": "\"Browsing through\" + this", - "message": "songs that they like" - } -} diff --git a/websites/S/Spotify Podcasts/metadata.json b/websites/S/Spotify Podcasts/metadata.json deleted file mode 100644 index a35a63fbd2a4..000000000000 --- a/websites/S/Spotify Podcasts/metadata.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "$schema": "https://schemas.premid.app/metadata/1.16", - "apiVersion": 1, - "author": { - "name": "Bas950", - "id": "241278257335500811" - }, - "contributors": [ - { - "name": "Anaxes", - "id": "567885938160697377" - } - ], - "service": "Spotify Podcasts", - "altnames": [ - "스포티파이 팟캐스트" - ], - "description": { - "en": "Music for everyone. Note: Only podcasts and site info! To show normal songs use the Spotify x Discord connection.", - "nl": "Muziek voor iedereen. Let op: Alleen podcasts en pagina informatie! Om normale liedjes te laten zien gebruik de Spotify x Discord connectie.", - "vi": "Âm nhạc dành cho tất cả mọi người! Ghi chú: Chỉ hiện thông tin podcast và trang web! Để hiện các bài hát thông thường hãy sử dụng kết nối Spotify x Discord.", - "zh": "音乐人人爱。注意:仅播客和网站信息!要显示普通歌曲,请使用Spotify x Discord连接。", - "zh-tw": "音樂人人愛。注意:僅播客和網站信息!要顯示歌曲信息,請使你的Spotify賬號與Discord連接。" - }, - "url": [ - "www.spotify.com", - "open.spotify.com", - "newsroom.spotify.com", - "artists.spotify.com", - "developer.spotify.com", - "investors.spotify.com", - "support.spotify.com", - "podcasters.spotify.com", - "accounts.spotify.com" - ], - "regExp": "^https?[:][/][/]([a-z0-9-]+[.])*spotify[.]com[/]", - "version": "2.5.0", - "logo": "https://cdn.rcd.gg/PreMiD/websites/S/Spotify%20Podcasts/assets/logo.png", - "thumbnail": "https://cdn.rcd.gg/PreMiD/websites/S/Spotify%20Podcasts/assets/thumbnail.png", - "color": "#1ed760", - "category": "music", - "tags": [ - "audio", - "podcast" - ], - "settings": [ - { - "id": "lang", - "multiLanguage": true - }, - { - "id": "privacy", - "title": "Privacy Mode", - "icon": "fad fa-user-secret", - "value": false - }, - { - "id": "timestamps", - "title": "Show timestamps", - "icon": "fad fa-stopwatch", - "value": true - }, - { - "id": "cover", - "title": "Show Cover", - "icon": "fad fa-images", - "value": true - }, - { - "id": "buttons", - "title": "Show Buttons", - "icon": "fad fa-compress-arrows-alt", - "value": true - } - ] -} diff --git a/websites/S/Spotify Podcasts/presence.ts b/websites/S/Spotify Podcasts/presence.ts deleted file mode 100644 index d30347edea3d..000000000000 --- a/websites/S/Spotify Podcasts/presence.ts +++ /dev/null @@ -1,438 +0,0 @@ -import { ActivityType, Assets } from 'premid' - -const presence = new Presence({ - clientId: '619561001234464789', -}) -const browsingStamp = Math.floor(Date.now() / 1000) - -let recentlyCleared = 0 - -enum ActivityAssets { - Logo = 'https://cdn.rcd.gg/PreMiD/websites/S/Spotify%20Podcasts/assets/logo.png', -} - -async function getStrings() { - return presence.getStrings( - { - play: 'general.playing', - pause: 'general.paused', - featured: 'spotify.featured', - bestPodcasts: 'spotify.bestPodcasts', - charts: 'spotify.charts', - genres: 'spotify.genres', - latest: 'spotify.latest', - discover: 'spotify.discover', - browse: 'spotify.browse', - podcastLike: 'spotify.podcastsLike', - artistLike: 'spotify.artistsLike', - albumLike: 'spotify.albumLike', - songLike: 'spotify.songsLike', - forMeh: 'spotify.madeForYou', - playlist: 'spotify.playlists', - viewPlaylist: 'general.viewPlaylist', - download: 'spotify.download', - view: 'general.view', - account: 'general.viewAccount', - search: 'general.search', - searchFor: 'general.searchFor', - searchSomething: 'general.searchSomething', - browsing: 'general.browsing', - listening: 'general.listeningMusic', - show: 'general.viewShow', - artist: 'general.buttonViewArtist', - viewPodcast: 'general.buttonViewPodcast', - buttonViewPlaylist: 'general.buttonViewPlaylist', - viewHome: 'general.viewHome', - viewPage: 'general.buttonViewPage', - }, - - ) -} - -let strings: Awaited> -let oldLang: string | null = null - -presence.on('UpdateData', async () => { - let presenceData: PresenceData = { - largeImageKey: ActivityAssets.Logo, - type: ActivityType.Listening, - } - - //* Update strings if user selected another language. - const [newLang, privacy, timestamps, cover, buttons] = await Promise.all([ - presence.getSetting('lang').catch(() => 'en'), - presence.getSetting('privacy'), - presence.getSetting('timestamps'), - presence.getSetting('cover'), - presence.getSetting('buttons'), - ]) - const { href, pathname, hostname } = document.location - - if (oldLang !== newLang || !strings) { - oldLang = newLang - strings = await getStrings() - } - - const pages: Record = { - '/browse/featured': { - details: strings.browse, - state: strings.featured, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/browse/podcasts': { - details: strings.browse, - state: strings.bestPodcasts, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/browse/charts': { - details: strings.charts, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/browse/genres': { - details: strings.browse, - state: strings.genres, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/browse/latest': { - details: strings.browse, - state: strings.latest, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/browse/discover': { - details: strings.discover, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/collection/playlists': { - details: strings.browse, - state: strings.playlist, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/collection/made-for-you': { - details: strings.browse, - state: strings.forMeh, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/collection/tracks': { - details: strings.browse, - state: strings.songLike, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/collection/albums': { - details: strings.browse, - state: strings.albumLike, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/collection/artists': { - details: strings.browse, - state: strings.artistLike, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/collection/podcasts': { - details: strings.browse, - state: strings.podcastLike, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/collection/episodes': { - details: strings.browse, - state: 'my episodes', - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - '/setting': { - details: strings.account, - buttons: [ - { - label: strings.viewPage, - url: href, - }, - ], - }, - } - const albumCover = document.querySelector( - ':is(a[data-testid=cover-art-link], a[data-testid=context-link])', - ) - - for (const [path, data] of Object.entries(pages)) { - if (pathname.includes(path)) - presenceData = { ...presenceData, ...data } as PresenceData - } - - let searching = false - - if ( - !(albumCover && /\/(?:show|episode)\/|your-episodes\?/.test(albumCover.href)) - ) { - if (timestamps) - presenceData.startTimestamp = browsingStamp - presenceData.smallImageKey = Assets.Reading - presenceData.smallImageText = strings.browsing - switch (hostname) { - case 'open.spotify.com': { - if (pathname === '/') { - presenceData.details = strings.viewHome - } - else if (pathname.includes('/search/')) { - const search = document.querySelector('input') - searching = true - presenceData.details = strings.searchFor - presenceData.state = search?.value - if (search && search.value.length <= 3) - presenceData.state = 'something...' - presenceData.smallImageKey = Assets.Search - } - else if (pathname.includes('/search')) { - searching = true - presenceData.details = strings.search - presenceData.smallImageKey = Assets.Search - } - else if (pathname.includes('/playlist/')) { - const playlistCover = document - .querySelector( - 'div.Ws8Ec3GREpT5PAUesr9b > div > img.mMx2LUixlnN_Fu45JpFB', - ) - ?.getAttribute('src') - presenceData.details = strings.viewPlaylist - presenceData.state = document.querySelector( - 'div.RP2rRchy4i8TIp1CTmb7 > span.rEN7ncpaUeSGL9z0NGQR > h1', - )?.textContent - presenceData.buttons = [ - { - label: strings.buttonViewPlaylist, - url: href, - }, - ] - if (playlistCover) { - presenceData.largeImageKey = playlistCover - presenceData.smallImageKey = ActivityAssets.Logo - } - } - else if (pathname.includes('/show/')) { - presenceData.details = strings.show - presenceData.state = document.querySelector( - 'div.RP2rRchy4i8TIp1CTmb7 > span.rEN7ncpaUeSGL9z0NGQR > h1 > span', - )?.textContent - presenceData.largeImageKey = document - .querySelector( - 'div._gLjHpwOxHFwo5nLM8hb > div > img.mMx2LUixlnN_Fu45JpFB', - ) - ?.getAttribute('src') - presenceData.smallImageKey = ActivityAssets.Logo - presenceData.buttons = [ - { - label: strings.artist, - url: href, - }, - ] - } - break - } - case 'accounts.spotify.com': { - if (pathname.includes('/login')) - presenceData.details = 'Logging in' - break - } - case 'support.spotify.com': { - presenceData.details = strings.browse - presenceData.state = 'Support Center' - - break - } - case 'investors.spotify.com': { - presenceData.details = strings.browse - presenceData.state = 'Support Center' - - break - } - case 'developer.spotify.com': { - presenceData.details = strings.browse - presenceData.state = 'Spotify for Developers' - - break - } - case 'artists.spotify.com': { - presenceData.details = strings.browse - presenceData.state = 'Spotify for Artists' - - break - } - case 'newsroom.spotify.com': { - presenceData.details = strings.browse - presenceData.state = 'Spotify for Newsroom' - - break - } - case 'podcasters.spotify.com': { - presenceData.details = strings.browse - presenceData.state = 'Spotify for Podcasters' - - break - } - case 'www.spotify.com': { - if (pathname.includes('/premium')) { - presenceData.details = strings.view - presenceData.state = 'Spotify Premium' - delete presenceData.smallImageKey - } - else if (pathname.includes('/download')) { - presenceData.details = strings.download - presenceData.smallImageKey = Assets.Downloading - } - else if (pathname.includes('/account')) { - presenceData.details = strings.account - delete presenceData.smallImageKey - } - - break - } - } - const control = document.querySelector( - 'div.player-controls__buttons > button', - ) - if ( - document.querySelector('.now-playing-bar-hidden') !== null - || control === null - || control.dataset.testid === 'control-button-play' - ) { - if (!presenceData.details) { - presence.setActivity() - } - else if (privacy) { - if (searching) { - presenceData.details = strings.searchSomething - delete presenceData.state - } - else { - presenceData.details = strings.browsing - delete presenceData.state - delete presenceData.smallImageKey - } - presence.setActivity(presenceData) - } - else { - presence.setActivity(presenceData) - } - } - else { - if (recentlyCleared < Date.now() - 1000) - presence.clearActivity() - - recentlyCleared = Date.now() - } - } - else { - const pause = document - .querySelector('[data-testid=control-button-playpause]') - ?.getAttribute('aria-label') === 'Play' - - presenceData.smallImageKey = pause ? Assets.Pause : Assets.Play - presenceData.smallImageText = pause ? strings.pause : strings.play; - [presenceData.startTimestamp, presenceData.endTimestamp] = presence.getTimestamps( - presence.timestampFromFormat( - document.querySelector('[data-testid="playback-position"]') - ?.textContent ?? '', - ), - presence.timestampFromFormat( - document.querySelector('[data-testid="playback-duration"]') - ?.textContent ?? '', - ), - ) - - if (pause || !timestamps) { - delete presenceData.startTimestamp - delete presenceData.endTimestamp - } - - if (cover) - presenceData.largeImageKey = albumCover?.querySelector('img')?.src - - presenceData.details = document.querySelector( - ':is(a[nowplaying-track-link], a[data-testid=context-item-link', - )?.textContent - presenceData.state = document.querySelector( - ':is(div[data-testid=track-info-artists], a[data-testid=context-item-info-show]', - )?.textContent - - presenceData.buttons = [ - { - label: strings.viewPodcast, - url: href, - }, - ] - - if (privacy) { - presenceData.details = strings.listening - delete presenceData.state - } - } - - if (!buttons && presenceData.buttons) - delete presenceData.buttons - - if (presenceData.details) - presence.setActivity(presenceData) - else presence.setActivity() -}) diff --git a/websites/S/Spotify/Spotify.json b/websites/S/Spotify/Spotify.json new file mode 100644 index 000000000000..fab1ffccce45 --- /dev/null +++ b/websites/S/Spotify/Spotify.json @@ -0,0 +1,18 @@ +{ + "spotify.listenOnSpotify": { + "description": "Button label for listening to a song", + "message": "Listen on Spotify" + }, + "spotify.listenToPodcast": { + "description": "Button label for listening to a podcast", + "message": "Listen to Podcast" + }, + "spotify.listeningToSong": { + "description": "Privacy mode text when listening to a song", + "message": "Listening to a song" + }, + "spotify.listeningToPodcast": { + "description": "Privacy mode text when listening to a podcast", + "message": "Listening to a podcast" + } +} diff --git a/websites/S/Spotify/metadata.json b/websites/S/Spotify/metadata.json new file mode 100644 index 000000000000..3abc15e73579 --- /dev/null +++ b/websites/S/Spotify/metadata.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://schemas.premid.app/metadata/1.16", + "apiVersion": 1, + "author": { + "name": "itsyashbtw", + "id": "568021861871517716" + }, + "contributors": [ + { + "name": "Bas950", + "id": "241278257335500811" + }, + { + "name": "Anaxes", + "id": "567885938160697377" + } + ], + "service": "Spotify", + "altnames": [ + "Spotify Web", + "Spotify Podcasts" + ], + "description": { + "en": "Spotify is a digital music, podcast, and video service that gives you access to millions of songs and other content from creators all over the world." + }, + "url": "open.spotify.com", + "regExp": "^https?[:][/][/]open[.]spotify[.]com[/]", + "version": "1.0.0", + "logo": "https://cdn.rcd.gg/PreMiD/websites/S/Spotify%20Podcasts/assets/logo.png", + "thumbnail": "https://cdn.rcd.gg/PreMiD/websites/S/Spotify%20Podcasts/assets/thumbnail.png", + "color": "#1DB954", + "category": "music", + "tags": [ + "music", + "streaming", + "audio", + "podcast" + ], + "settings": [ + { + "id": "lang", + "multiLanguage": true + }, + { + "id": "privacy", + "title": "Privacy Mode", + "icon": "fad fa-user-secret", + "value": false + }, + { + "id": "timestamps", + "title": "Show timestamps", + "icon": "fad fa-stopwatch", + "value": true + }, + { + "id": "cover", + "title": "Show Cover Art", + "icon": "fad fa-images", + "value": true + }, + { + "id": "buttons", + "title": "Show Buttons", + "icon": "fad fa-compress-arrows-alt", + "value": true + }, + { + "id": "podcastsOnly", + "title": "Podcasts Only (Hide Music)", + "icon": "fad fa-podcast", + "value": false + } + ] +} diff --git a/websites/S/Spotify/presence.ts b/websites/S/Spotify/presence.ts new file mode 100644 index 000000000000..7df0b507bf61 --- /dev/null +++ b/websites/S/Spotify/presence.ts @@ -0,0 +1,130 @@ +import { ActivityType, Assets, getTimestamps, timestampFromFormat } from 'premid' + +const presence = new Presence({ + clientId: '1458014647193047042', +}) + +enum ActivityAssets { + Logo = 'https://cdn.rcd.gg/PreMiD/websites/S/Spotify%20Podcasts/assets/logo.png', +} + +async function getStrings() { + return presence.getStrings({ + play: 'general.playing', + pause: 'general.paused', + listening: 'general.listeningMusic', + listeningPodcast: 'general.listeningPodcast', + listenOnSpotify: 'spotify.listenOnSpotify', + listenToPodcast: 'spotify.listenToPodcast', + listeningToSong: 'spotify.listeningToSong', + listeningToPodcast: 'spotify.listeningToPodcast', + }) +} + +let strings: Awaited> +let oldLang: string | null = null + +presence.on('UpdateData', async () => { + const [newLang, privacy, timestamps, cover, buttons, podcastsOnly] = await Promise.all([ + presence.getSetting('lang').catch(() => 'en'), + presence.getSetting('privacy'), + presence.getSetting('timestamps'), + presence.getSetting('cover'), + presence.getSetting('buttons'), + presence.getSetting('podcastsOnly'), + ]) + + if (oldLang !== newLang || !strings) { + oldLang = newLang + strings = await getStrings() + } + + // Check if music/podcast is playing + const playPauseButton = document.querySelector('[data-testid=control-button-playpause]') + const isPlaying = playPauseButton?.getAttribute('aria-label')?.toLowerCase().includes('pause') + + // Only show presence when content is playing + if (!isPlaying) { + presence.clearActivity() + return + } + + // Check if it's a podcast or music + const albumCover = document.querySelector( + ':is(a[data-testid=cover-art-link], a[data-testid=context-link])', + ) + const isPodcast = albumCover && /\/(?:show|episode)\/|your-episodes\?/.test(albumCover.href) + + // If podcastsOnly mode is enabled, skip music + if (podcastsOnly && !isPodcast) { + presence.clearActivity() + return + } + + // Get track/episode info + const trackName = document.querySelector( + '[data-testid="context-item-link"], [data-testid="nowplaying-track-link"]', + )?.textContent + + const artistOrShowName = document.querySelector( + isPodcast + ? '[data-testid="context-item-info-show"], [data-testid="track-info-artists"]' + : '[data-testid="context-item-info-artist"], [data-testid="track-info-artists"]', + )?.textContent + + if (!trackName) { + presence.clearActivity() + return + } + + const presenceData: PresenceData = { + largeImageKey: ActivityAssets.Logo, + type: ActivityType.Listening, + details: trackName, + state: artistOrShowName || (isPodcast ? 'Unknown Show' : 'Unknown Artist'), + smallImageKey: Assets.Play, + smallImageText: strings.play, + } + + // Get timestamps + if (timestamps) { + const currentTime = document.querySelector('[data-testid="playback-position"]')?.textContent + const duration = document.querySelector('[data-testid="playback-duration"]')?.textContent + + if (currentTime && duration) { + [presenceData.startTimestamp, presenceData.endTimestamp] = getTimestamps( + timestampFromFormat(currentTime), + timestampFromFormat(duration), + ) + } + } + + // Get cover art + if (cover && albumCover) { + const coverImg = albumCover.querySelector('img')?.src + if (coverImg) { + presenceData.largeImageKey = coverImg + presenceData.smallImageKey = ActivityAssets.Logo + } + } + + // Add button + if (buttons) { + presenceData.buttons = [ + { + label: isPodcast ? strings.listenToPodcast : strings.listenOnSpotify, + url: document.location.href, + }, + ] + } + + // Apply privacy mode + if (privacy) { + presenceData.details = isPodcast ? strings.listeningToPodcast : strings.listeningToSong + delete presenceData.state + presenceData.largeImageKey = ActivityAssets.Logo + delete presenceData.smallImageKey + } + + presence.setActivity(presenceData) +})