diff --git a/websites/M/Monochrome/metadata.json b/websites/M/Monochrome/metadata.json new file mode 100644 index 000000000000..cb7bd4a81a14 --- /dev/null +++ b/websites/M/Monochrome/metadata.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://schemas.premid.app/metadata/1.16", + "apiVersion": 1, + "author": { + "id": "838652741965971517", + "name": "p1nkhamster" + }, + "service": "Monochrome", + "description": { + "en": "Monochrome is an open-source, privacy-respecting, ad-free TIDAL web UI, built on top of Hi-Fi." + }, + "url": "monochrome.samidy.com", + "regExp": "^https?[:][/][/](monochrome[.]samidy[.]com|monochrome[.]tf|monochrome[.]prigoana[.]com|monochrome-back[.]pages[.]dev)[/]", + "version": "1.0.0", + "logo": "https://i.imgur.com/XHf3H8Y.png", + "thumbnail": "https://i.imgur.com/LeV2zuA.png", + "color": "#000000", + "category": "music", + "tags": [ + "music", + "tidal" + ] +} diff --git a/websites/M/Monochrome/presence.ts b/websites/M/Monochrome/presence.ts new file mode 100644 index 000000000000..9bbbe45496db --- /dev/null +++ b/websites/M/Monochrome/presence.ts @@ -0,0 +1,93 @@ +import { ActivityType } from 'premid' + +declare const Presence: any + +// Static Asset Configuration +enum ActivityAssets { + Logo = 'https://i.imgur.com/XHf3H8Y.png', + Play = 'https://i.imgur.com/ryNutI5.png', + Pause = 'https://i.imgur.com/TlMwR5i.png', +} + +const presence = new Presence({ + clientId: '1459594619972096248', +}) + +presence.on('UpdateData', async () => { + // 1. DYNAMIC IMAGE LOGIC + // Default to the static logo + let currentLargeImage: string = ActivityAssets.Logo + + // standard mediaSession check for high-res artwork + const artwork = navigator.mediaSession?.metadata?.artwork + + if (artwork && artwork.length > 0) { + // Select the last image in the array (typically the highest resolution) + const coverUrl = artwork[artwork.length - 1]?.src + if (coverUrl) { + currentLargeImage = coverUrl + } + } + + // 2. INITIALIZE ACTIVITY DATA + const presenceData: any = { + type: ActivityType.Listening, + largeImageKey: currentLargeImage, + largeImageText: 'Listening on Monochrome', + // Default small icon (overwritten below if paused) + smallImageKey: ActivityAssets.Play, + smallImageText: 'Playing', + } + + // 3. TEXT STRATEGY (Browser Tab) + // Parses "Song - Artist" or "Song • Artist" from the document title + const tabTitle = document.title || '' + let separator = '' + + if (tabTitle.includes(' - ')) + separator = ' - ' + else if (tabTitle.includes(' • ')) + separator = ' • ' + + if (separator) { + const parts = tabTitle.split(separator) + presenceData.details = parts[0]?.trim() || 'Unknown Song' + presenceData.state = parts.slice(1).join(separator).trim() || 'Unknown Artist' + } + else { + // Fallback for non-standard titles + presenceData.details = 'Monochrome' + presenceData.state = 'Listening...' + } + + // 4. AUDIO STATUS & TIMESTAMPS + const mediaElement = document.querySelector('audio') + + if (mediaElement) { + if (!mediaElement.paused) { + // -- PLAYING STATE -- + presenceData.smallImageKey = ActivityAssets.Play + presenceData.smallImageText = 'Playing' + + // Calculate timestamps using native Date.now() for accuracy + const now = Date.now() + presenceData.startTimestamp = now - (mediaElement.currentTime * 1000) + + // Only set endTimestamp if duration is finite and positive + if (mediaElement.duration && Number.isFinite(mediaElement.duration) && mediaElement.duration > 0) { + presenceData.endTimestamp = now + ((mediaElement.duration - mediaElement.currentTime) * 1000) + } + } + else { + // -- PAUSED STATE -- + presenceData.smallImageKey = ActivityAssets.Pause + presenceData.smallImageText = 'Paused' + // Note: We do not set timestamps here, effectively hiding the time bar + } + + presence.setActivity(presenceData) + } + else { + presence.clearActivity() + } +})