From 8f1a0fe28fa97f4bc34841f33e38f8587421c629 Mon Sep 17 00:00:00 2001 From: lordlogo2002 Date: Tue, 20 Jan 2026 05:44:12 +0100 Subject: [PATCH] Enhance HUD functionality: add visual indicators for member-only videos and improve channel information display --- content.js | 301 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 286 insertions(+), 15 deletions(-) diff --git a/content.js b/content.js index d473bf5..b7e39e7 100644 --- a/content.js +++ b/content.js @@ -6,6 +6,9 @@ let debugEnabled = false; const memberOnlyIndex = new Map(); // id -> { channelKey, hidden } // Data-* attribute prefix for DOM tags. const DATA_PREFIX = "chipperfluff-nobs"; // nobs = "no-bs" (member-only videos) +const HUD_DOT_ID = `${DATA_PREFIX}-hud-dot`; +const HUD_PANEL_ID = `${DATA_PREFIX}-hud-panel`; +let generatedIdCounter = 0; /* ---------------- CSS injection ---------------- */ @@ -19,6 +22,121 @@ function injectStyleOnce() { [data-${DATA_PREFIX}-hidden="true"] { display: none !important; } + #${HUD_DOT_ID} { + position: fixed; + right: 18px; + bottom: 18px; + width: 12px; + height: 12px; + border-radius: 999px; + background: #9b9b9b; + border: 2px solid #6f6f6f; + box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.12); + z-index: 2147483647; + cursor: pointer; + } + #${HUD_DOT_ID}[data-${DATA_PREFIX}-active="true"] { + background: #ff9c1a; + border-color: #b04a00; + box-shadow: 0 0 0 3px rgba(255, 140, 0, 0.4); + animation: ${DATA_PREFIX}-pulse 1.8s ease-in-out infinite; + } + @keyframes ${DATA_PREFIX}-pulse { + 0% { + transform: scale(0.9); + box-shadow: 0 0 0 0 rgba(255, 140, 0, 0.6); + } + 70% { + transform: scale(1.1); + box-shadow: 0 0 0 6px rgba(255, 140, 0, 0); + } + 100% { + transform: scale(0.9); + box-shadow: 0 0 0 0 rgba(255, 140, 0, 0); + } + } + #${HUD_PANEL_ID} { + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 520px; + height: 520px; + overflow: auto; + padding: 10px; + border-radius: 10px; + border: 1px solid #b04a00; + background: #fff6e8; + color: #2b2014; + font-size: 12px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18); + z-index: 2147483647; + } + #${HUD_PANEL_ID}[data-${DATA_PREFIX}-hidden="true"] { + display: none; + } + .${DATA_PREFIX}-hud-title { + font-weight: 700; + margin: 0 0 8px; + text-transform: uppercase; + font-size: 11px; + letter-spacing: 0.4px; + } + .${DATA_PREFIX}-channel { + margin: 6px 0; + padding: 6px 8px; + border-radius: 8px; + background: #fffaf0; + border: 1px solid #e2d7be; + } + .${DATA_PREFIX}-channel-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + cursor: pointer; + font-weight: 700; + } + .${DATA_PREFIX}-channel-header::before { + content: "▸"; + margin-right: 6px; + color: #8b5a2a; + font-weight: 700; + } + details[open] > .${DATA_PREFIX}-channel-header::before { + content: "▾"; + } + .${DATA_PREFIX}-video-list { + margin: 6px 0 0; + padding-left: 18px; + border-left: 2px solid #f0d8b4; + } + .${DATA_PREFIX}-hud-item { + display: grid; + grid-template-columns: 64px 1fr; + gap: 8px; + align-items: center; + margin: 6px 0; + padding: 6px; + border-radius: 8px; + background: #fffaf0; + border: 1px solid #e2d7be; + } + .${DATA_PREFIX}-hud-thumb { + width: 64px; + height: 36px; + object-fit: cover; + border-radius: 4px; + background: #eee2cc; + } + .${DATA_PREFIX}-hud-link { + color: #5a2b00; + text-decoration: none; + font-weight: 700; + } + .${DATA_PREFIX}-hud-link:hover { + text-decoration: underline; + } `; document.head.appendChild(style); } @@ -84,6 +202,7 @@ function loadSettings() { } updateKnownVisibility(); process(); + updateHudDot(); }); } @@ -109,22 +228,34 @@ function process(root = document) { if (!video) return; const titleEl = video.querySelector("#video-title"); - const channelEl = - video.querySelector("ytd-channel-name a") || - video.querySelector(".ytd-channel-name a"); - const title = titleEl?.textContent?.trim() ?? "(no title)"; const url = titleEl?.href ?? ""; - const channel = channelEl?.textContent?.trim() ?? "(no channel)"; - const channelKey = normalizeKey(channel); - const id = getVideoId(video, url); + const thumbImg = video.querySelector("ytd-thumbnail img"); + const thumb = + thumbImg?.getAttribute("src") || + thumbImg?.getAttribute("data-thumb") || + thumbImg?.getAttribute("data-src") || + ""; + const channelInfo = getChannelInfo(video); + const channel = channelInfo.label || "(no channel)"; + const channelUrl = channelInfo.url || ""; + const channelKey = channelInfo.key; + const id = getOrCreateVideoId(video, url); video.setAttribute(`data-${DATA_PREFIX}-member-only`, "true"); if (channelKey) video.setAttribute(`data-${DATA_PREFIX}-channel`, channelKey); if (id) video.setAttribute(`data-${DATA_PREFIX}-id`, id); if (id) { - memberOnlyIndex.set(id, { channelKey, hidden: false }); + memberOnlyIndex.set(id, { + channelKey, + channelLabel: channel, + channelUrl, + hidden: false, + title, + url, + thumb + }); } const whitelisted = whitelist.includes(channelKey); @@ -149,6 +280,7 @@ function process(root = document) { } } }); + updateHudDot(); } // Apply whitelist to already-tagged member-only videos. @@ -172,6 +304,7 @@ function updateKnownVisibility() { meta.hidden = true; } } + updateHudDot(); } // Allow full visibility on whitelisted channel pages. @@ -196,24 +329,51 @@ function revealAll() { } // Choose a stable identifier for indexing. -function getVideoId(video, url) { +function getOrCreateVideoId(video, url) { const dataId = video.getAttribute("data-video-id"); if (dataId) return dataId; if (url) return url; const fallbackId = video.id; - return fallbackId || ""; + if (fallbackId) return fallbackId; + const generated = `mf-${generatedIdCounter++}`; + video.setAttribute(`data-${DATA_PREFIX}-id`, generated); + return generated; } // Read the channel key from DOM or fallback to text. function getChannelKey(video) { const stored = video.getAttribute(`data-${DATA_PREFIX}-channel`); if (stored) return stored; + return getChannelInfo(video).key; +} - const channelEl = - video.querySelector("ytd-channel-name a") || - video.querySelector(".ytd-channel-name a"); - const channel = channelEl?.textContent?.trim() ?? ""; - return channel ? normalizeKey(channel) : ""; +function getChannelKeyFromData(channelText, channelUrl) { + const handle = getChannelHandleFromUrl(channelUrl); + if (handle) return normalizeKey(handle); + return channelText ? normalizeKey(channelText) : ""; +} + +function getChannelInfo(video) { + const channelBlock = + video.querySelector("ytd-channel-name") || + video.querySelector(".ytd-channel-name"); + const linkEl = + channelBlock?.querySelector("a") || + video.querySelector('a[href^="/@"]') || + video.querySelector('a[href^="/channel/"]') || + video.querySelector('a[href^="/c/"]'); + const labelEl = + channelBlock?.querySelector("#text") || + channelBlock?.querySelector("yt-formatted-string") || + channelBlock; + const label = labelEl?.textContent?.trim() ?? ""; + const url = linkEl?.href ?? ""; + const handleFromUrl = getChannelHandleFromUrl(url); + const handleFromPage = getChannelHandleFromUrl(location.href); + const fallbackLabel = handleFromUrl || handleFromPage || ""; + const finalLabel = label || fallbackLabel; + const key = getChannelKeyFromData(finalLabel, url || handleFromPage); + return { label: finalLabel, url: url || handleFromPage, key }; } // Find a video element by stored id. @@ -228,6 +388,117 @@ function cssEscape(value) { return String(value).replace(/["\\]/g, "\\$&"); } +function ensureHudDot() { + let dot = document.getElementById(HUD_DOT_ID); + if (!dot) { + dot = document.createElement("div"); + dot.id = HUD_DOT_ID; + dot.setAttribute(`data-${DATA_PREFIX}-active`, "false"); + dot.title = "Member-only videos: 0 (0 hidden)"; + document.body.appendChild(dot); + } + let panel = document.getElementById(HUD_PANEL_ID); + if (!panel) { + panel = document.createElement("div"); + panel.id = HUD_PANEL_ID; + panel.setAttribute(`data-${DATA_PREFIX}-hidden`, "true"); + document.body.appendChild(panel); + } + if (!dot.dataset.bound) { + dot.dataset.bound = "true"; + dot.addEventListener("click", () => { + const isHidden = + panel.getAttribute(`data-${DATA_PREFIX}-hidden`) === "true"; + panel.setAttribute( + `data-${DATA_PREFIX}-hidden`, + isHidden ? "false" : "true" + ); + if (isHidden) renderHudPanel(panel); + }); + } + return dot; +} + +function updateHudDot() { + const dot = ensureHudDot(); + let total = 0; + let hidden = 0; + for (const meta of memberOnlyIndex.values()) { + total += 1; + if (meta.hidden) hidden += 1; + } + dot.title = `Member-only videos: ${total} (${hidden} hidden)`; + dot.setAttribute( + `data-${DATA_PREFIX}-active`, + total > 0 ? "true" : "false" + ); +} + +function renderHudPanel(panel) { + panel.innerHTML = ""; + const title = document.createElement("div"); + title.className = `${DATA_PREFIX}-hud-title`; + title.textContent = "Hidden member-only videos"; + panel.appendChild(title); + + const groups = new Map(); + for (const meta of memberOnlyIndex.values()) { + if (!meta.hidden) continue; + const key = meta.channelKey || getChannelKeyFromData(meta.channelLabel, meta.channelUrl) || "(unknown)"; + const list = groups.get(key) || []; + list.push(meta); + groups.set(key, list); + } + + if (groups.size === 0) { + const empty = document.createElement("div"); + empty.textContent = "No hidden member-only videos."; + panel.appendChild(empty); + return; + } + + for (const [creator, items] of groups.entries()) { + const details = document.createElement("details"); + details.className = `${DATA_PREFIX}-channel`; + details.open = false; + + const summary = document.createElement("summary"); + summary.className = `${DATA_PREFIX}-channel-header`; + const label = items[0]?.channelLabel && items[0].channelLabel !== "(no channel)" + ? items[0].channelLabel + : creator; + summary.textContent = `${label} (${items.length})`; + details.appendChild(summary); + + const list = document.createElement("div"); + list.className = `${DATA_PREFIX}-video-list`; + + items.forEach(meta => { + const item = document.createElement("div"); + item.className = `${DATA_PREFIX}-hud-item`; + + const img = document.createElement("img"); + img.className = `${DATA_PREFIX}-hud-thumb`; + img.alt = meta.title || "thumbnail"; + if (meta.thumb) img.src = meta.thumb; + item.appendChild(img); + + const link = document.createElement("a"); + link.className = `${DATA_PREFIX}-hud-link`; + link.textContent = meta.title || meta.url || "(no title)"; + link.href = meta.url || "#"; + link.target = "_blank"; + link.rel = "noopener noreferrer"; + item.appendChild(link); + + list.appendChild(item); + }); + + panel.appendChild(details); + details.appendChild(list); + } +} + /* ---------------- initial + observer ---------------- */ process();