diff --git a/package-lock.json b/package-lock.json index cf5c66dcdef2..5a8eaaedf244 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,6 @@ "chokidar": "^5.0.0", "discord-rpc": "^4.0.1", "esbuild": "^0.28.0", - "form-data": "^4.0.5", "gitdiff-parser": "^0.3.1", "globby": "^16.2.0", "got": "^15.0.3", @@ -474,6 +473,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -491,6 +491,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -508,6 +509,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -525,6 +527,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -542,6 +545,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -559,6 +563,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -576,6 +581,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -593,6 +599,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -610,6 +617,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -627,6 +635,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -644,6 +653,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -661,6 +671,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -678,6 +689,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -695,6 +707,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -712,6 +725,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -729,6 +743,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -746,6 +761,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -763,6 +779,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -780,6 +797,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -797,6 +815,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -814,6 +833,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -831,6 +851,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -848,6 +869,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -865,6 +887,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -882,6 +905,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -899,6 +923,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -1241,9 +1266,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1261,9 +1283,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1281,9 +1300,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1301,9 +1317,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1321,9 +1334,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1341,9 +1351,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1361,9 +1368,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1381,9 +1385,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1401,9 +1402,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1427,9 +1425,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1453,9 +1448,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1479,9 +1471,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1505,9 +1494,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1531,9 +1517,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1557,9 +1540,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1583,9 +1563,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2432,9 +2409,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2452,9 +2426,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2472,9 +2443,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2492,9 +2460,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2512,9 +2477,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2532,9 +2494,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2552,9 +2511,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2572,9 +2528,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2765,9 +2718,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2785,9 +2735,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2805,9 +2752,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2825,9 +2769,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2845,9 +2786,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2865,9 +2803,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -5889,23 +5824,6 @@ "dev": true, "license": "ISC" }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -6804,9 +6722,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6828,9 +6743,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6852,9 +6764,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6876,9 +6785,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ diff --git a/websites/M/Media/metadata.json b/websites/M/Media/metadata.json new file mode 100644 index 000000000000..fbfe81fd4722 --- /dev/null +++ b/websites/M/Media/metadata.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://schemas.premid.app/metadata/1.16", + "apiVersion": 1, + "author": { + "name": "siq", + "id": "123456789012345678" + }, + "service": "Media", + "description": { + "en": "Self-hosted media tracking platform for music, books, movies, games, and more." + }, + "url": "media.siqnole.dev", + "regExp": "^(?:https?[:][/][/])?(?:www[.])?(?:media[.]siqnole[.]dev|localhost:5173)[/]", + "version": "1.0.0", + "logo": "https://media.siqnole.dev/mydia.png", + "thumbnail": "https://media.siqnole.dev/mydia.png", + "color": "#a688e8", + "category": "socials", + "tags": [ + "tracking", + "ratings", + "entertainment" + ] +} diff --git a/websites/M/Media/presence.ts b/websites/M/Media/presence.ts new file mode 100644 index 000000000000..48d200b65be1 --- /dev/null +++ b/websites/M/Media/presence.ts @@ -0,0 +1,264 @@ +import { ActivityType } from 'premid' + +const APP_LOGO_URL = 'https://media.siqnole.dev/mydia.png' + +const presence = new Presence({ + clientId: '1511807266913910855', +}) + +let browsingTimestamp = Math.floor(Date.now() / 1000) +let lastPath = '' + +presence.on('UpdateData', async () => { + const { pathname } = document.location + + // Reset timestamp when switching major pages + if (pathname !== lastPath) { + browsingTimestamp = Math.floor(Date.now() / 1000) + lastPath = pathname + } + + let presenceData: PresenceData + + // Check if any modal is open + const modalOverlay = document.querySelector('.modal-overlay.open') + if (modalOverlay) { + const modalTitle = modalOverlay.querySelector('.modal-title')?.textContent?.trim() || '' + const quickRateTitle = modalOverlay.querySelector('h3')?.textContent?.trim() || '' + + if (modalTitle.includes('Customize Profile')) { + // 1. Customize Profile Modal + const activeTab = modalOverlay.querySelector('.tabs .tab-btn.active')?.textContent?.trim() || 'Settings' + presenceData = { + largeImageKey: APP_LOGO_URL, + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: 'Customizing Profile', + state: `Editing: ${activeTab}`, + } + } + else if (quickRateTitle.includes('Quick Rate')) { + // 2. Quick Rate Modal + const itemTitle = modalOverlay.querySelector('.quick-rate-card h4')?.textContent?.trim() || '' + const itemMeta = modalOverlay.querySelector('.quick-rate-card h4 + span')?.textContent?.trim() || '' + + let state = 'No unrated items left' + if (itemTitle) { + state = `Rating: ${itemTitle}` + if (itemMeta) { + state += ` (${itemMeta})` + } + } + + presenceData = { + largeImageKey: APP_LOGO_URL, + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: 'Quick Rating Items', + state, + } + } + else if (modalTitle.includes('Media Overview')) { + // 3. Media Overview / Item Details Modal + const title = modalOverlay.querySelector('.modal-grid-two-cols h3')?.textContent?.trim() || '' + const meta = modalOverlay.querySelector('.modal-grid-two-cols h3 + div')?.textContent?.trim() || '' + const category = modalOverlay.querySelector('.modal-grid-two-cols strong')?.textContent?.trim() || '' + const imgEl = modalOverlay.querySelector('.modal-grid-two-cols .cover img') as HTMLImageElement | null + const coverUrl = (imgEl && imgEl.src) ? imgEl.src : null + + const stateText = meta ? `${title} (${meta})` : title + const largeImage = coverUrl || APP_LOGO_URL + const smallImage = coverUrl ? APP_LOGO_URL : undefined + + // Map category to the correct Discord Activity Type + const lowerCategory = category.toLowerCase() + if (lowerCategory.includes('music') || lowerCategory.includes('album')) { + presenceData = { + largeImageKey: largeImage, + largeImageText: `Category: ${category}`, + smallImageKey: smallImage, + smallImageText: 'media.siqnole.dev', + startTimestamp: browsingTimestamp, + type: ActivityType.Listening, + details: `Browsing Album:`, + state: stateText, + } + } + else if (lowerCategory.includes('watching') || lowerCategory.includes('film') || lowerCategory.includes('movie') || lowerCategory.includes('show')) { + presenceData = { + largeImageKey: largeImage, + largeImageText: `Category: ${category}`, + smallImageKey: smallImage, + smallImageText: 'media.siqnole.dev', + startTimestamp: browsingTimestamp, + type: ActivityType.Watching, + details: `Viewing Movie Info:`, + state: stateText, + } + } + else { + // playing / reading - Non-media (ActivityType.Playing). No largeImageText is allowed in PreMiD. + presenceData = { + largeImageKey: largeImage, + smallImageKey: smallImage, + smallImageText: 'media.siqnole.dev', + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: lowerCategory.includes('playing') || lowerCategory.includes('game') || lowerCategory.includes('steam') ? 'Viewing Game Info:' : 'Viewing Book Info:', + state: stateText, + } + } + } + else { + // Default fallback for other modals (e.g. Bug Report Modal) + presenceData = { + largeImageKey: APP_LOGO_URL, + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: 'Browsing Details', + state: modalTitle || 'Viewing details', + } + } + } + else { + // No modal is open - standard route views + if (pathname === '/') { + presenceData = { + largeImageKey: APP_LOGO_URL, + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: 'Browsing Landing Page', + state: 'Welcome to Media', + } + } + else if (pathname === '/home') { + presenceData = { + largeImageKey: APP_LOGO_URL, + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: 'Checking Feed', + state: 'Browsing recent activity', + } + } + else if (pathname === '/shop') { + presenceData = { + largeImageKey: APP_LOGO_URL, + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: 'Browsing the Shop', + state: 'Checking out cosmetics', + } + } + else if (pathname === '/admin') { + presenceData = { + largeImageKey: APP_LOGO_URL, + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: 'Managing Platform', + state: 'Admin Dashboard', + } + } + else if (pathname.startsWith('/@')) { + const profileUser = pathname.substring(2) + + // Identify active Now Live items + let activeMedia: { label: string, title: string, sub: string, coverUrl: string | null } | null = null + const firstNowItem = document.querySelector('.now-grid .now-item') + if (firstNowItem) { + const label = firstNowItem.querySelector('.now-label')?.textContent?.trim().toLowerCase() || '' + const title = firstNowItem.querySelector('.now-title')?.textContent?.trim() || '' + const sub = firstNowItem.querySelector('.now-sub')?.textContent?.trim() || '' + + // Scrape cover image if it exists (e.g., Spotify, Steam, or custom reading cover) + const imgEl = firstNowItem.querySelector('.now-art img') as HTMLImageElement | null + const coverUrl = (imgEl && imgEl.src) ? imgEl.src : null + + if (title) { + activeMedia = { label, title, sub, coverUrl } + } + } + + if (activeMedia) { + // Dynamic live activity + const { label, title, sub, coverUrl } = activeMedia + const stateText = sub ? `${title} (${sub})` : title + const largeImage = coverUrl || APP_LOGO_URL + const smallImage = coverUrl ? APP_LOGO_URL : undefined + + if (label.includes('listening')) { + presenceData = { + largeImageKey: largeImage, + largeImageText: `Viewing @${profileUser}'s profile`, + smallImageKey: smallImage, + smallImageText: 'media.siqnole.dev', + startTimestamp: browsingTimestamp, + type: ActivityType.Listening, + details: `Listening to:`, + state: stateText, + } + } + else if (label.includes('watching') || label.includes('film') || label.includes('movie')) { + presenceData = { + largeImageKey: largeImage, + largeImageText: `Viewing @${profileUser}'s profile`, + smallImageKey: smallImage, + smallImageText: 'media.siqnole.dev', + startTimestamp: browsingTimestamp, + type: ActivityType.Watching, + details: `Watching:`, + state: stateText, + } + } + else { + // playing / reading + presenceData = { + largeImageKey: largeImage, + smallImageKey: smallImage, + smallImageText: 'media.siqnole.dev', + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: label.includes('playing') || label.includes('steam') ? 'Playing:' : 'Reading:', + state: stateText, + } + } + } + else { + // No live activity - show current shelf tab details + const activeTabLabel = document.querySelector('.tabs .tab-btn.active')?.textContent?.trim() || '' + const activeSubtabLabel = document.querySelector('.shelf-controls-bar .tab-btn.active')?.textContent?.trim() || '' + + let state = 'Viewing profile' + if (activeTabLabel) { + state = `Browsing: ${activeTabLabel}` + if (activeSubtabLabel) { + state += ` (${activeSubtabLabel})` + } + } + + presenceData = { + largeImageKey: APP_LOGO_URL, + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: `Viewing @${profileUser}'s profile`, + state, + } + } + } + else { + presenceData = { + largeImageKey: APP_LOGO_URL, + startTimestamp: browsingTimestamp, + type: ActivityType.Playing, + details: 'Browsing media.siqnole.dev', + state: 'Home', + } + } + } + + if (presenceData.details) { + presence.setActivity(presenceData) + } + else { + presence.clearActivity() + } +})