From 281829ea76a7febbfae7c1ae7430acb2fd8b5ba9 Mon Sep 17 00:00:00 2001 From: hibna Date: Sun, 26 Apr 2026 00:20:58 +0300 Subject: [PATCH] Add Prime Video presence --- presences/primevideo/metadata.json | 16 ++++ presences/primevideo/src/presence.ts | 120 +++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 presences/primevideo/metadata.json create mode 100644 presences/primevideo/src/presence.ts diff --git a/presences/primevideo/metadata.json b/presences/primevideo/metadata.json new file mode 100644 index 0000000..05f53fc --- /dev/null +++ b/presences/primevideo/metadata.json @@ -0,0 +1,16 @@ +{ + "id": "primevideo", + "name": "Prime Video", + "description": "Shows the title you're watching on Amazon Prime Video.", + "version": "1.0.0", + "author": "hibna", + "match": [ + "https://www.primevideo.com/*", + "https://www.amazon.com/gp/video/*", + "https://www.amazon.com/-/*/gp/video/*", + "https://www.amazon.co.uk/gp/video/*", + "https://www.amazon.de/gp/video/*" + ], + "tickInterval": 1500, + "sdkVersion": "^0.1.0" +} diff --git a/presences/primevideo/src/presence.ts b/presences/primevideo/src/presence.ts new file mode 100644 index 0000000..39fa030 --- /dev/null +++ b/presences/primevideo/src/presence.ts @@ -0,0 +1,120 @@ +import { ActivityType, definePresence } from "@source/presence-sdk"; + +function findVideo(): HTMLVideoElement | null { + return document.querySelector( + "video.webPlayerElement, video.rendererContainer__video, video[src], video", + ); +} + +function isPlayerOpen(): boolean { + if (document.querySelector(".webPlayerSDKContainer")) return true; + if (document.querySelector("[data-automation-id='webPlayer']")) return true; + return /\/(detail|gp\/video\/detail)\//.test(location.pathname) === false && + document.querySelector("video") !== null && + location.pathname.includes("/video/"); +} + +function readPlayerTitle(): string | null { + const candidates = [ + "h1.atvwebplayersdk-title-text", + ".atvwebplayersdk-title-text", + "[data-automation-id='title']", + ".webPlayerSDKContainer h1", + ]; + for (const sel of candidates) { + const node = document.querySelector(sel); + const text = node?.textContent?.trim(); + if (text) return text; + } + return null; +} + +function readPlayerSubtitle(): string | null { + const candidates = [ + ".atvwebplayersdk-subtitle-text", + "[data-automation-id='subtitle']", + ]; + for (const sel of candidates) { + const node = document.querySelector(sel); + const text = node?.textContent?.trim(); + if (text) return text; + } + return null; +} + +function readDetailTitle(): string | null { + const node = + document.querySelector("[data-automation-id='title']") ?? + document.querySelector("h1"); + return node?.textContent?.trim() ?? null; +} + +function browsingLabel(): string { + const path = location.pathname; + if (/\/(detail|gp\/video\/detail)\//.test(path)) return "Viewing title"; + if (path.includes("/search/") || location.search.includes("phrase=")) return "Searching"; + if (path.includes("/storefront/")) return "Browsing store"; + if (path.includes("/library/") || path.includes("/watchlist")) return "Browsing watchlist"; + return "Browsing"; +} + +export default definePresence({ + match: [ + "https://www.primevideo.com/*", + "https://www.amazon.com/gp/video/*", + "https://www.amazon.com/-/*/gp/video/*", + "https://www.amazon.co.uk/gp/video/*", + "https://www.amazon.de/gp/video/*", + ], + tickInterval: 1500, + tick(ctx) { + const video = findVideo(); + if (video && isPlayerOpen()) { + const title = readPlayerTitle(); + if (!title) { + ctx.setActivity({ + type: ActivityType.WATCHING, + name: "Prime Video", + details: "Loading…", + }); + return; + } + const subtitle = readPlayerSubtitle(); + const paused = video.paused; + const remainingMs = + Number.isFinite(video.duration) && video.duration > 0 + ? Math.floor((video.duration - video.currentTime) * 1000) + : null; + ctx.setActivity({ + type: ActivityType.WATCHING, + name: "Prime Video", + details: title, + state: subtitle + ? `${paused ? "Paused" : "Watching"} · ${subtitle}` + : paused + ? "Paused" + : "Watching", + url: location.href, + startedAt: paused ? null : Date.now() - Math.floor(video.currentTime * 1000), + endsAt: paused || remainingMs === null ? null : Date.now() + remainingMs, + }); + return; + } + + if (/\/(detail|gp\/video\/detail)\//.test(location.pathname)) { + const title = readDetailTitle(); + ctx.setActivity({ + type: ActivityType.WATCHING, + name: "Prime Video", + details: title ? `Viewing ${title}` : "Viewing title", + }); + return; + } + + ctx.setActivity({ + type: ActivityType.WATCHING, + name: "Prime Video", + details: browsingLabel(), + }); + }, +});