From db81232f0fc30030e878254be4c66e4c7475c2cb Mon Sep 17 00:00:00 2001 From: lordlogo2002 Date: Tue, 20 Jan 2026 07:46:05 +0100 Subject: [PATCH] Enhance video tracking functionality: implement seen and member indices for improved video metadata management --- content.js | 210 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 202 insertions(+), 8 deletions(-) diff --git a/content.js b/content.js index dbc34b4..b7f3e0e 100644 --- a/content.js +++ b/content.js @@ -4,7 +4,9 @@ let whitelistHandles = []; let debugEnabled = false; // In-memory index of known member-only videos. const memberOnlyIndex = new Map(); // id -> { channelKey, hidden } -const sharedIndex = new Map(); // stableId -> meta for cross-tab HUD +const sharedIndex = new Map(); // stableId -> meta for cross-tab HUD (hidden only) +const sharedSeenIndex = new Map(); // stableId -> meta for all seen videos +const sharedMemberIndex = new Map(); // stableId -> meta for member-only videos // 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`; @@ -13,6 +15,8 @@ let generatedIdCounter = 0; let hudDirty = false; let lastHudSignature = ""; const STORAGE_KEY = "memberOnlyHidden"; +const STORAGE_KEY_SEEN = "memberOnlySeen"; +const STORAGE_KEY_MEMBER = "memberOnlyDetected"; const DEFAULT_MAX_ARCHIVE = 500; const MAX_SNAPSHOT_CHARS = 20000; let persistTimer = null; @@ -312,6 +316,18 @@ chrome.storage.onChanged.addListener(changes => { items.forEach(item => sharedIndex.set(item.id, item)); updateHudDot(); } + if (changes[STORAGE_KEY_SEEN]) { + const items = changes[STORAGE_KEY_SEEN].newValue || []; + sharedSeenIndex.clear(); + items.forEach(item => sharedSeenIndex.set(item.id, item)); + updateHudDot(); + } + if (changes[STORAGE_KEY_MEMBER]) { + const items = changes[STORAGE_KEY_MEMBER].newValue || []; + sharedMemberIndex.clear(); + items.forEach(item => sharedMemberIndex.set(item.id, item)); + updateHudDot(); + } if (changes.whitelist || changes.debug) { loadSettings(); } @@ -322,6 +338,7 @@ chrome.storage.onChanged.addListener(changes => { // Detect member-only videos and tag them for future updates. function process(root = document) { if (isWhitelistedChannelPage()) return; + scanAllVideos(root); const badges = root.querySelectorAll("badge-shape"); badges.forEach(badge => { @@ -362,6 +379,18 @@ function process(root = document) { thumb }); hudDirty = true; + updateMemberIndex({ + stableId, + channelKey, + channelLabel: channel, + channelUrl + }); + updateSeenIndex({ + stableId, + channelKey, + channelLabel: channel, + channelUrl + }); } else { // Fill missing metadata as it becomes available. if (!existing.title && title) existing.title = title; @@ -373,6 +402,18 @@ function process(root = document) { if (!existing.snapshot) existing.snapshot = safeSnapshot(video); updateSharedIndex(existing, true); } + updateMemberIndex({ + stableId: existing.stableId, + channelKey: existing.channelKey, + channelLabel: existing.channelLabel, + channelUrl: existing.channelUrl + }); + updateSeenIndex({ + stableId: existing.stableId, + channelKey: existing.channelKey, + channelLabel: existing.channelLabel, + channelUrl: existing.channelUrl + }); } } @@ -410,6 +451,27 @@ function process(root = document) { updateHudDot(); } +function scanAllVideos(root) { + const selector = + "ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer"; + const videos = root.querySelectorAll(selector); + const rootIsVideo = + root instanceof HTMLElement && root.matches && root.matches(selector); + const allVideos = rootIsVideo ? [root, ...videos] : Array.from(videos); + + allVideos.forEach(video => { + const videoMeta = getVideoMeta(video); + if (!videoMeta.videoId) return; + const channelInfo = getChannelInfo(video); + updateSeenIndex({ + stableId: videoMeta.videoId, + channelKey: channelInfo.key, + channelLabel: channelInfo.label, + channelUrl: channelInfo.url + }); + }); +} + // Apply whitelist to already-tagged member-only videos. function updateKnownVisibility() { if (isWhitelistedChannelPage()) { @@ -717,6 +779,48 @@ function updateSharedIndex(meta, hidden) { schedulePersistSharedIndex(); } +function updateSeenIndex(meta) { + if (!meta.stableId) return; + const existing = sharedSeenIndex.get(meta.stableId); + if (!existing) { + sharedSeenIndex.set(meta.stableId, { + id: meta.stableId, + channelKey: meta.channelKey, + channelLabel: meta.channelLabel, + channelUrl: meta.channelUrl, + lastSeen: Date.now() + }); + schedulePersistSeenIndex(); + return; + } + existing.channelKey = meta.channelKey || existing.channelKey; + existing.channelLabel = meta.channelLabel || existing.channelLabel; + existing.channelUrl = meta.channelUrl || existing.channelUrl; + existing.lastSeen = Date.now(); + schedulePersistSeenIndex(); +} + +function updateMemberIndex(meta) { + if (!meta.stableId) return; + const existing = sharedMemberIndex.get(meta.stableId); + if (!existing) { + sharedMemberIndex.set(meta.stableId, { + id: meta.stableId, + channelKey: meta.channelKey, + channelLabel: meta.channelLabel, + channelUrl: meta.channelUrl, + lastSeen: Date.now() + }); + schedulePersistMemberIndex(); + return; + } + existing.channelKey = meta.channelKey || existing.channelKey; + existing.channelLabel = meta.channelLabel || existing.channelLabel; + existing.channelUrl = meta.channelUrl || existing.channelUrl; + existing.lastSeen = Date.now(); + schedulePersistMemberIndex(); +} + function schedulePersistSharedIndex() { if (persistTimer) return; persistTimer = setTimeout(() => { @@ -741,13 +845,68 @@ function persistSharedIndex() { }); } +function schedulePersistSeenIndex() { + if (persistTimer) return; + persistTimer = setTimeout(() => { + persistTimer = null; + persistSeenIndex(); + }, 200); +} + +function persistSeenIndex() { + if (!chrome?.storage?.local || contextInvalidated) return; + const items = Array.from(sharedSeenIndex.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 (max < 0) { + safeStorageSet({ [STORAGE_KEY_SEEN]: items }); + return; + } + safeStorageSet({ [STORAGE_KEY_SEEN]: items.slice(0, max) }); + }); +} + +function schedulePersistMemberIndex() { + if (persistTimer) return; + persistTimer = setTimeout(() => { + persistTimer = null; + persistMemberIndex(); + }, 200); +} + +function persistMemberIndex() { + if (!chrome?.storage?.local || contextInvalidated) return; + const items = Array.from(sharedMemberIndex.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 (max < 0) { + safeStorageSet({ [STORAGE_KEY_MEMBER]: items }); + return; + } + safeStorageSet({ [STORAGE_KEY_MEMBER]: 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)); - updateHudDot(); - }); + chrome.storage.local.get( + { [STORAGE_KEY]: [], [STORAGE_KEY_SEEN]: [], [STORAGE_KEY_MEMBER]: [] }, + data => { + sharedIndex.clear(); + data[STORAGE_KEY].forEach(item => sharedIndex.set(item.id, item)); + sharedSeenIndex.clear(); + data[STORAGE_KEY_SEEN].forEach(item => sharedSeenIndex.set(item.id, item)); + sharedMemberIndex.clear(); + data[STORAGE_KEY_MEMBER].forEach(item => + sharedMemberIndex.set(item.id, item) + ); + updateHudDot(); + } + ); } function renderHudPanel(panel) { @@ -789,6 +948,9 @@ function renderHudPanel(panel) { return; } + const totals = getSeenTotalsByCreator(); + const memberTotals = getMemberTotalsByCreator(); + for (const [creator, items] of groups.entries()) { const details = document.createElement("details"); details.className = `${DATA_PREFIX}-channel`; @@ -799,7 +961,10 @@ function renderHudPanel(panel) { const label = items[0]?.channelLabel && items[0].channelLabel !== "(no channel)" ? items[0].channelLabel : creator; - summary.textContent = `${label} (${items.length})`; + const total = totals.get(creator) || items.length; + const memberCount = memberTotals.get(creator) || items.length; + const percent = total > 0 ? Math.round((memberCount / total) * 100) : 0; + summary.textContent = `${label} (${memberCount}/${total} known, ${percent}%)`; details.appendChild(summary); const list = document.createElement("div"); @@ -840,9 +1005,38 @@ function renderHudPanel(panel) { } } +function getSeenTotalsByCreator() { + const totals = new Map(); + for (const meta of sharedSeenIndex.values()) { + const key = + meta.channelKey || + getChannelKeyFromData(meta.channelLabel, meta.channelUrl) || + "(unknown)"; + totals.set(key, (totals.get(key) || 0) + 1); + } + return totals; +} + +function getMemberTotalsByCreator() { + const totals = new Map(); + for (const meta of sharedMemberIndex.values()) { + const key = + meta.channelKey || + getChannelKeyFromData(meta.channelLabel, meta.channelUrl) || + "(unknown)"; + totals.set(key, (totals.get(key) || 0) + 1); + } + return totals; +} + function clearArchive() { sharedIndex.clear(); - safeStorageSet({ [STORAGE_KEY]: [] }, () => updateHudDot()); + sharedSeenIndex.clear(); + sharedMemberIndex.clear(); + safeStorageSet( + { [STORAGE_KEY]: [], [STORAGE_KEY_SEEN]: [], [STORAGE_KEY_MEMBER]: [] }, + () => updateHudDot() + ); } function safeStorageSet(payload, callback) {