1
0

Compare commits

..

No commits in common. "a4f2f5c60bae66954c22679f3d050801b32b78c1" and "88abb559d1df46fabe69601855b0ee2955cbb237" have entirely different histories.

View File

@ -14,43 +14,7 @@ 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;
let hudFlashTimer = null;
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 ---------------- */
@ -83,13 +47,6 @@ function injectStyleOnce() {
box-shadow: 0 0 0 3px rgba(255, 140, 0, 0.4);
animation: ${DATA_PREFIX}-pulse 1.8s ease-in-out infinite;
}
#${HUD_DOT_ID}[data-${DATA_PREFIX}-flash="true"] {
background: #ffd84a;
border-color: #e0a600;
transform: scale(1.8);
box-shadow: 0 0 0 10px rgba(255, 216, 74, 0.4);
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
@keyframes ${DATA_PREFIX}-pulse {
0% {
transform: scale(0.9);
@ -211,26 +168,6 @@ 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);
}
@ -284,7 +221,6 @@ 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;
@ -333,16 +269,36 @@ function process(root = document) {
if (!video) return;
const videoMeta = getVideoMeta(video);
const title = videoMeta.title;
const url = videoMeta.url;
const thumb = videoMeta.thumb;
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 channelInfo = getChannelInfo(video);
const channel = channelInfo.label || "(no channel)";
const channelUrl = channelInfo.url || "";
const channelKey = channelInfo.key;
const id = videoMeta.videoId || getOrCreateVideoId(video, url);
const stableId = videoMeta.videoId || getStableId(video, url, id);
const id = getOrCreateVideoId(video, url);
const stableId = getStableId(video, url, id);
video.setAttribute(`data-${DATA_PREFIX}-member-only`, "true");
if (channelKey) video.setAttribute(`data-${DATA_PREFIX}-channel`, channelKey);
@ -369,10 +325,6 @@ 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);
}
}
}
@ -387,15 +339,13 @@ 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.hidden) flashHudDot();
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)) {
@ -405,7 +355,6 @@ function process(root = document) {
updateSharedIndex(meta, false);
}
}
if (videoMeta.needsRetry) scheduleMetaRetry(video);
});
updateHudDot();
}
@ -432,7 +381,6 @@ 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);
}
}
@ -479,102 +427,6 @@ 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`);
@ -683,31 +535,20 @@ function updateHudDot() {
}
}
function flashHudDot() {
const dot = ensureHudDot();
dot.setAttribute(`data-${DATA_PREFIX}-flash`, "true");
if (hudFlashTimer) clearTimeout(hudFlashTimer);
hudFlashTimer = setTimeout(() => {
dot.removeAttribute(`data-${DATA_PREFIX}-flash`);
}, 900);
}
function updateSharedIndex(meta, hidden) {
if (!meta.stableId) {
meta.stableId = meta.url || meta.id || "";
}
if (!meta.stableId) return;
if (hidden) {
const existing = sharedIndex.get(meta.stableId) || {};
sharedIndex.set(meta.stableId, {
id: meta.stableId,
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,
channelKey: meta.channelKey,
channelLabel: meta.channelLabel,
channelUrl: meta.channelUrl,
title: meta.title,
url: meta.url,
thumb: meta.thumb,
hidden: true,
lastSeen: Date.now()
});
@ -726,23 +567,20 @@ 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) {
safeStorageSet({ [STORAGE_KEY]: items });
chrome.storage.local.set({ [STORAGE_KEY]: items });
return;
}
safeStorageSet({ [STORAGE_KEY]: items.slice(0, max) });
chrome.storage.local.set({ [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));
@ -806,15 +644,6 @@ 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`;
@ -842,15 +671,7 @@ function renderHudPanel(panel) {
function clearArchive() {
sharedIndex.clear();
safeStorageSet({ [STORAGE_KEY]: [] }, () => updateHudDot());
}
function safeStorageSet(payload, callback) {
try {
chrome.storage.local.set(payload, callback);
} catch (error) {
contextInvalidated = true;
}
chrome.storage.local.set({ [STORAGE_KEY]: [] }, () => updateHudDot());
}
/* ---------------- initial + observer ---------------- */