diff --git a/presences/netflix/metadata.json b/presences/netflix/metadata.json new file mode 100644 index 0000000..ca26c47 --- /dev/null +++ b/presences/netflix/metadata.json @@ -0,0 +1,10 @@ +{ + "id": "netflix", + "name": "Netflix", + "description": "Shows the title and episode you're watching on Netflix.", + "version": "1.0.0", + "author": "hibna", + "match": ["https://www.netflix.com/*"], + "tickInterval": 1500, + "sdkVersion": "^0.1.0" +} diff --git a/presences/netflix/src/presence.ts b/presences/netflix/src/presence.ts new file mode 100644 index 0000000..024ea16 --- /dev/null +++ b/presences/netflix/src/presence.ts @@ -0,0 +1,85 @@ +import { ActivityType, definePresence } from "@source/presence-sdk"; + +function findVideo(): HTMLVideoElement | null { + return document.querySelector("video"); +} + +function readShowTitle(): string | null { + const node = + document.querySelector("[data-uia='video-title'] h4") ?? + document.querySelector(".video-title h4") ?? + document.querySelector("[data-uia='player-title']"); + return node?.textContent?.trim() ?? null; +} + +function readEpisodeInfo(): string | null { + const root = + document.querySelector("[data-uia='video-title']") ?? + document.querySelector(".video-title"); + if (!root) return null; + const spans = Array.from(root.querySelectorAll("span")) + .map((s) => s.textContent?.trim()) + .filter((t): t is string => !!t && t.length > 0); + return spans.length > 0 ? spans.join(" · ") : null; +} + +function isPlayerOpen(): boolean { + return location.pathname.startsWith("/watch/"); +} + +function browsingLabel(): string { + if (location.pathname.startsWith("/browse/genre")) return "Browsing genres"; + if (location.pathname.startsWith("/latest")) return "Browsing new releases"; + if (location.pathname.startsWith("/my-list")) return "Browsing my list"; + if (location.pathname.startsWith("/search")) return "Searching"; + if (location.pathname.startsWith("/title/")) return "Viewing title"; + return "Browsing"; +} + +export default definePresence({ + match: ["https://www.netflix.com/*"], + tickInterval: 1500, + tick(ctx) { + if (!isPlayerOpen()) { + ctx.setActivity({ + type: ActivityType.WATCHING, + name: "Netflix", + details: browsingLabel(), + }); + return; + } + + const video = findVideo(); + const title = readShowTitle(); + + if (!video || !title) { + ctx.setActivity({ + type: ActivityType.WATCHING, + name: "Netflix", + details: "Loading…", + }); + return; + } + + const episode = readEpisodeInfo(); + 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: "Netflix", + details: title, + state: episode + ? `${paused ? "Paused" : "Watching"} · ${episode}` + : paused + ? "Paused" + : "Watching", + url: location.href, + startedAt: paused ? null : Date.now() - Math.floor(video.currentTime * 1000), + endsAt: paused || remainingMs === null ? null : Date.now() + remainingMs, + }); + }, +});