From f708833cdf308cb075975038f4ff30166485e190 Mon Sep 17 00:00:00 2001 From: lordlogo2002 Date: Tue, 20 Jan 2026 07:20:32 +0100 Subject: [PATCH] Enhance video metadata handling: implement snapshot creation, improve data stripping, and optimize storage interactions --- content.js | 233 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 197 insertions(+), 36 deletions(-) diff --git a/content.js b/content.js index e167c8a..634ed1b 100644 --- a/content.js +++ b/content.js @@ -14,7 +14,42 @@ let hudDirty = false; let lastHudSignature = ""; const STORAGE_KEY = "memberOnlyHidden"; const DEFAULT_MAX_ARCHIVE = 500; +const MAX_SNAPSHOT_CHARS = 20000; let persistTimer = null; +let contextInvalidated = false; + +function safeSnapshot(video) { + if (typeof buildSnapshot !== "function") return ""; + return buildSnapshot(video); +} + +function stripHiddenMarkers(html) { + const pattern = new RegExp( + `\\s*data-${DATA_PREFIX}-[^=\\s>]+(?:=(\"[^\"]*\"|'[^']*'|[^\\s>]*))?`, + "gi" + ); + return html + .replace(pattern, "") + .replace(new RegExp(`data-${DATA_PREFIX}-[^\\s>]+`, "gi"), ""); +} + +function sanitizeSnapshotToFragment(html) { + const template = document.createElement("template"); + const cleaned = stripHiddenMarkers(html); + template.innerHTML = cleaned; + return template.content; +} + +function forceStripDataMarkers(root) { + const all = root.querySelectorAll("*"); + all.forEach(el => { + Array.from(el.attributes).forEach(attr => { + if (attr.name.includes(`data-${DATA_PREFIX}-`)) { + el.removeAttribute(attr.name); + } + }); + }); +} /* ---------------- CSS injection ---------------- */ @@ -168,6 +203,26 @@ function injectStyleOnce() { .${DATA_PREFIX}-hud-link:hover { text-decoration: underline; } + .${DATA_PREFIX}-snapshot { + border: 1px solid #3a2c22; + border-radius: 8px; + padding: 8px; + background: #232323; + color: #f5efe6; + font-size: 11px; + line-height: 1.35; + } + .${DATA_PREFIX}-snapshot a { + color: #ffb26a; + text-decoration: none; + } + .${DATA_PREFIX}-snapshot img { + max-width: 100%; + height: auto; + border-radius: 6px; + display: block; + margin-bottom: 6px; + } `; document.head.appendChild(style); } @@ -221,6 +276,7 @@ function normalizeWhitelist(list) { // Read settings and re-apply visibility immediately. function loadSettings() { + if (!chrome?.storage?.local) return; chrome.storage.local.get({ whitelist: [], debug: false }, data => { const { names, handles } = normalizeWhitelist(data.whitelist); whitelist = names; @@ -269,36 +325,16 @@ function process(root = document) { if (!video) return; - const linkEl = - video.querySelector('a[href^="/watch"]') || - video.querySelector("#video-title-link") || - video.querySelector("#video-title"); - const title = - linkEl?.getAttribute("title") || - linkEl?.textContent?.trim() || - "(no title)"; - const rawUrl = linkEl?.getAttribute("href") || ""; - const url = rawUrl.startsWith("http") - ? rawUrl - : rawUrl - ? new URL(rawUrl, location.origin).toString() - : ""; - const thumbImg = - video.querySelector("ytd-thumbnail img") || - video.querySelector("yt-img-shadow img") || - video.querySelector("img#img"); - const thumb = - thumbImg?.currentSrc || - thumbImg?.getAttribute("src") || - thumbImg?.getAttribute("data-thumb") || - thumbImg?.getAttribute("data-src") || - ""; + const videoMeta = getVideoMeta(video); + const title = videoMeta.title; + const url = videoMeta.url; + const thumb = videoMeta.thumb; const channelInfo = getChannelInfo(video); const channel = channelInfo.label || "(no channel)"; const channelUrl = channelInfo.url || ""; const channelKey = channelInfo.key; - const id = getOrCreateVideoId(video, url); - const stableId = getStableId(video, url, id); + const id = videoMeta.videoId || getOrCreateVideoId(video, url); + const stableId = videoMeta.videoId || getStableId(video, url, id); video.setAttribute(`data-${DATA_PREFIX}-member-only`, "true"); if (channelKey) video.setAttribute(`data-${DATA_PREFIX}-channel`, channelKey); @@ -325,6 +361,10 @@ function process(root = document) { if (!existing.thumb && thumb) existing.thumb = thumb; if (!existing.channelLabel && channel) existing.channelLabel = channel; if (!existing.channelUrl && channelUrl) existing.channelUrl = channelUrl; + if (existing.hidden) { + if (!existing.snapshot) existing.snapshot = safeSnapshot(video); + updateSharedIndex(existing, true); + } } } @@ -339,13 +379,14 @@ function process(root = document) { debugGroupEnd(); if (!whitelisted) { - video.setAttribute(`data-${DATA_PREFIX}-hidden`, "true"); if (id && memberOnlyIndex.has(id)) { const meta = memberOnlyIndex.get(id); + if (!meta.snapshot) meta.snapshot = safeSnapshot(video); if (!meta.hidden) hudDirty = true; meta.hidden = true; updateSharedIndex(meta, true); } + video.setAttribute(`data-${DATA_PREFIX}-hidden`, "true"); } else { video.removeAttribute(`data-${DATA_PREFIX}-hidden`); if (id && memberOnlyIndex.has(id)) { @@ -355,6 +396,7 @@ function process(root = document) { updateSharedIndex(meta, false); } } + if (videoMeta.needsRetry) scheduleMetaRetry(video); }); updateHudDot(); } @@ -381,6 +423,7 @@ function updateKnownVisibility() { video.setAttribute(`data-${DATA_PREFIX}-hidden`, "true"); if (!meta.hidden) hudDirty = true; meta.hidden = true; + if (!meta.snapshot) meta.snapshot = safeSnapshot(video); updateSharedIndex(meta, true); } } @@ -427,6 +470,102 @@ function getStableId(video, url, fallbackId) { return fallbackId || ""; } +function getVideoMeta(video) { + const linkEl = findWatchLink(video); + const href = linkEl?.getAttribute("href") || ""; + const videoId = + getVideoIdFromHref(href) || + video.getAttribute("data-video-id") || + video.querySelector("[data-video-id]")?.getAttribute("data-video-id") || + ""; + const url = videoId + ? `https://www.youtube.com/watch?v=${videoId}` + : href.startsWith("http") + ? href + : href + ? new URL(href, location.origin).toString() + : ""; + const title = + getLinkTitle(linkEl) || getFallbackTitle(video) || "(no title)"; + const thumb = + (videoId ? `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg` : "") || + video.querySelector("img")?.currentSrc || + video.querySelector("img")?.getAttribute("src") || + ""; + const needsRetry = !videoId || !title || title === "(no title)"; + return { title, url, thumb, videoId, needsRetry }; +} + +function findWatchLink(video) { + const links = Array.from(video.querySelectorAll("a[href]")); + let best = null; + for (const link of links) { + const href = link.getAttribute("href") || ""; + if (!href.includes("watch?v=")) continue; + if (!best) best = link; + const titled = getLinkTitle(link) || link.textContent?.trim(); + if (titled) return link; + } + return best; +} + +function getLinkTitle(link) { + if (!link) return ""; + const title = link.getAttribute("title") || link.getAttribute("aria-label"); + return title ? title.trim() : ""; +} + +function getFallbackTitle(video) { + const titled = Array.from(video.querySelectorAll("[title]")) + .map(el => el.getAttribute("title") || "") + .map(text => text.trim()) + .find(text => text && text.toLowerCase() !== "undefined"); + if (titled) return titled; + const aria = Array.from(video.querySelectorAll("[aria-label]")) + .map(el => el.getAttribute("aria-label") || "") + .map(text => text.trim()) + .find(text => text); + return aria || ""; +} + +function scheduleMetaRetry(video) { + const attr = `data-${DATA_PREFIX}-meta-retry`; + const current = parseInt(video.getAttribute(attr) || "0", 10); + if (current >= 3) return; + video.setAttribute(attr, String(current + 1)); + setTimeout(() => { + process(video); + }, 400 + current * 400); +} + +function getVideoIdFromHref(href) { + if (!href) return ""; + const match = href.match(/[?&]v=([^&]+)/); + if (match) return match[1]; + const shortMatch = href.match(/youtu\.be\/([^?]+)/); + if (shortMatch) return shortMatch[1]; + return ""; +} + +function buildSnapshot(video) { + try { + const root = + video.closest( + "ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer" + ) || video; + const clone = root.cloneNode(true); + removeDataMarkers(clone); + const html = stripHiddenMarkers(clone.outerHTML).trim(); + if (!html) return ""; + if (html.length > MAX_SNAPSHOT_CHARS) { + return html.slice(0, MAX_SNAPSHOT_CHARS); + } + return html; + } catch (error) { + return ""; + } +} + // Read the channel key from DOM or fallback to text. function getChannelKey(video) { const stored = video.getAttribute(`data-${DATA_PREFIX}-channel`); @@ -541,14 +680,16 @@ function updateSharedIndex(meta, hidden) { } if (!meta.stableId) return; if (hidden) { + const existing = sharedIndex.get(meta.stableId) || {}; sharedIndex.set(meta.stableId, { id: meta.stableId, - channelKey: meta.channelKey, - channelLabel: meta.channelLabel, - channelUrl: meta.channelUrl, - title: meta.title, - url: meta.url, - thumb: meta.thumb, + channelKey: meta.channelKey || existing.channelKey, + channelLabel: meta.channelLabel || existing.channelLabel, + channelUrl: meta.channelUrl || existing.channelUrl, + title: meta.title || existing.title, + url: meta.url || existing.url, + thumb: meta.thumb || existing.thumb, + snapshot: meta.snapshot ? stripHiddenMarkers(meta.snapshot) : existing.snapshot, hidden: true, lastSeen: Date.now() }); @@ -567,20 +708,23 @@ function schedulePersistSharedIndex() { } function persistSharedIndex() { + if (!chrome?.storage?.local || contextInvalidated) return; const items = Array.from(sharedIndex.values()); items.sort((a, b) => (b.lastSeen || 0) - (a.lastSeen || 0)); chrome.storage.local.get({ maxArchive: DEFAULT_MAX_ARCHIVE }, data => { const maxArchive = Number(data.maxArchive); const max = Number.isFinite(maxArchive) ? maxArchive : DEFAULT_MAX_ARCHIVE; + if (!chrome?.storage?.local || contextInvalidated) return; if (max < 0) { - chrome.storage.local.set({ [STORAGE_KEY]: items }); + safeStorageSet({ [STORAGE_KEY]: items }); return; } - chrome.storage.local.set({ [STORAGE_KEY]: items.slice(0, max) }); + safeStorageSet({ [STORAGE_KEY]: items.slice(0, max) }); }); } function loadStoredIndex() { + if (!chrome?.storage?.local) return; chrome.storage.local.get({ [STORAGE_KEY]: [] }, data => { sharedIndex.clear(); data[STORAGE_KEY].forEach(item => sharedIndex.set(item.id, item)); @@ -644,6 +788,15 @@ function renderHudPanel(panel) { list.className = `${DATA_PREFIX}-video-list`; items.forEach(meta => { + if (meta.snapshot) { + const snap = document.createElement("div"); + snap.className = `${DATA_PREFIX}-snapshot`; + snap.appendChild(sanitizeSnapshotToFragment(meta.snapshot)); + forceStripDataMarkers(snap); + list.appendChild(snap); + return; + } + const item = document.createElement("div"); item.className = `${DATA_PREFIX}-hud-item`; @@ -671,7 +824,15 @@ function renderHudPanel(panel) { function clearArchive() { sharedIndex.clear(); - chrome.storage.local.set({ [STORAGE_KEY]: [] }, () => updateHudDot()); + safeStorageSet({ [STORAGE_KEY]: [] }, () => updateHudDot()); +} + +function safeStorageSet(payload, callback) { + try { + chrome.storage.local.set(payload, callback); + } catch (error) { + contextInvalidated = true; + } } /* ---------------- initial + observer ---------------- */