1
0

Enhance popup and content script: add channel link input, normalize whitelist handling, and improve video visibility logic

This commit is contained in:
Chipperfluff 2026-01-19 22:43:30 +01:00
parent debdc67ed6
commit b18ab4ac24
4 changed files with 183 additions and 39 deletions

View File

@ -1,5 +1,8 @@
let whitelist = []; let whitelist = [];
let whitelistHandles = [];
let debugEnabled = false; let debugEnabled = false;
const memberOnlyIndex = new Map();
const DATA_PREFIX = "chipperfluff-nobs";
/* ---------------- CSS injection ---------------- */ /* ---------------- CSS injection ---------------- */
@ -9,7 +12,7 @@ function injectStyleOnce() {
const style = document.createElement("style"); const style = document.createElement("style");
style.id = "member-filter-style"; style.id = "member-filter-style";
style.textContent = ` style.textContent = `
[data-member-filter-hidden="true"] { [data-${DATA_PREFIX}-hidden="true"] {
display: none !important; display: none !important;
} }
`; `;
@ -25,11 +28,43 @@ function debugLog(...args) {
console.log("[MemberFilter]", ...args); console.log("[MemberFilter]", ...args);
} }
function normalizeKey(value) {
return value
.toLowerCase()
.replace(/\u00a0/g, " ")
.replace(/\s+/g, " ")
.replace(/\u2022/g, "")
.trim();
}
function normalizeWhitelist(list) {
const names = [];
const handles = [];
(list || []).forEach(item => {
if (typeof item === "string") {
if (item) names.push(normalizeKey(item));
return;
}
const name = normalizeKey(item.name || "");
const handle = normalizeKey(item.handle || "");
if (name) names.push(name);
if (handle) handles.push(handle);
});
return { names, handles };
}
function loadSettings() { function loadSettings() {
chrome.storage.local.get({ whitelist: [], debug: false }, data => { chrome.storage.local.get({ whitelist: [], debug: false }, data => {
whitelist = data.whitelist.map(n => n.toLowerCase()); const { names, handles } = normalizeWhitelist(data.whitelist);
whitelist = names;
whitelistHandles = handles;
debugEnabled = Boolean(data.debug); debugEnabled = Boolean(data.debug);
debugLog("settings loaded:", { whitelist, debugEnabled }); debugLog("settings loaded:", { whitelist, whitelistHandles, debugEnabled });
if (isWhitelistedChannelPage()) {
revealAll();
return;
}
updateKnownVisibility();
process(); process();
}); });
} }
@ -41,56 +76,133 @@ chrome.storage.onChanged.addListener(loadSettings);
/* ---------------- detection logic ---------------- */ /* ---------------- detection logic ---------------- */
function isMemberOnly(video) {
const badges = video.querySelectorAll("badge-shape");
for (const badge of badges) {
if (badge.textContent?.includes("Nur für Kanalmitglieder")) {
return true;
}
}
return false;
}
function process(root = document) { function process(root = document) {
const selector = if (isWhitelistedChannelPage()) return;
"ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer"; const badges = root.querySelectorAll("badge-shape");
const videos = root.querySelectorAll(selector);
const rootIsVideo =
root instanceof HTMLElement && root.matches && root.matches(selector);
const allVideos = rootIsVideo ? [root, ...videos] : Array.from(videos); badges.forEach(badge => {
if (!badge.textContent?.includes("Nur für Kanalmitglieder")) return;
const video = badge.closest(
"ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer"
);
if (!video) return;
allVideos.forEach(video => {
const titleEl = video.querySelector("#video-title"); const titleEl = video.querySelector("#video-title");
const channelEl = const channelEl =
video.querySelector("ytd-channel-name a") || video.querySelector("ytd-channel-name a") ||
video.querySelector(".ytd-channel-name a"); video.querySelector(".ytd-channel-name a");
const channel = channelEl?.textContent?.trim() ?? ""; const title = titleEl?.textContent?.trim() ?? "(no title)";
const channelKey = channel.toLowerCase(); const url = titleEl?.href ?? "";
const channel = channelEl?.textContent?.trim() ?? "(no channel)";
const channelKey = normalizeKey(channel);
const id = getVideoId(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 });
}
const whitelisted = whitelist.includes(channelKey); const whitelisted = whitelist.includes(channelKey);
const memberOnly = isMemberOnly(video);
if (debugEnabled) { if (debugEnabled) {
const title = titleEl?.textContent?.trim() ?? "(no title)";
const url = titleEl?.href ?? "(no url)";
console.group("[MemberFilter]"); console.group("[MemberFilter]");
console.log("Title :", title); console.log("Title :", title);
console.log("Channel:", channel || "(no channel)"); console.log("Channel:", channel);
console.log("URL :", url); console.log("URL :", url);
console.log("MemberOnly:", memberOnly); console.log("ID :", id || "(no id)");
console.log("Whitelisted:", whitelisted); console.log("Whitelisted:", whitelisted);
console.groupEnd(); console.groupEnd();
} }
if (memberOnly && !whitelisted) { if (!whitelisted) {
video.setAttribute("data-member-filter-hidden", "true"); video.setAttribute(`data-${DATA_PREFIX}-hidden`, "true");
if (id && memberOnlyIndex.has(id)) {
memberOnlyIndex.get(id).hidden = true;
}
} else { } else {
video.removeAttribute("data-member-filter-hidden"); video.removeAttribute(`data-${DATA_PREFIX}-hidden`);
if (id && memberOnlyIndex.has(id)) {
memberOnlyIndex.get(id).hidden = false;
}
} }
}); });
} }
function updateKnownVisibility() {
if (isWhitelistedChannelPage()) {
revealAll();
return;
}
for (const [id, meta] of memberOnlyIndex.entries()) {
if (!meta.channelKey) continue;
const video = findVideoById(id);
if (!video) {
memberOnlyIndex.delete(id);
continue;
}
if (whitelist.includes(meta.channelKey)) {
video.removeAttribute(`data-${DATA_PREFIX}-hidden`);
meta.hidden = false;
} else {
video.setAttribute(`data-${DATA_PREFIX}-hidden`, "true");
meta.hidden = true;
}
}
}
function isWhitelistedChannelPage() {
const handle = getChannelHandleFromUrl(location.href);
if (!handle) return false;
return whitelistHandles.includes(normalizeKey(handle));
}
function getChannelHandleFromUrl(url) {
const match = url.match(/youtube\.com\/@([^/]+)/i);
return match ? match[1] : "";
}
function revealAll() {
const videos = document.querySelectorAll(
`[data-${DATA_PREFIX}-hidden="true"]`
);
videos.forEach(video => video.removeAttribute(`data-${DATA_PREFIX}-hidden`));
}
function getVideoId(video, url) {
const dataId = video.getAttribute("data-video-id");
if (dataId) return dataId;
if (url) return url;
const fallbackId = video.id;
return fallbackId || "";
}
function getChannelKey(video) {
const stored = video.getAttribute(`data-${DATA_PREFIX}-channel`);
if (stored) return stored;
const channelEl =
video.querySelector("ytd-channel-name a") ||
video.querySelector(".ytd-channel-name a");
const channel = channelEl?.textContent?.trim() ?? "";
return channel ? normalizeKey(channel) : "";
}
function findVideoById(id) {
const escaped = cssEscape(id);
return document.querySelector(`[data-${DATA_PREFIX}-id="${escaped}"]`);
}
function cssEscape(value) {
if (window.CSS && CSS.escape) return CSS.escape(value);
return String(value).replace(/["\\]/g, "\\$&");
}
/* ---------------- initial + observer ---------------- */ /* ---------------- initial + observer ---------------- */
process(); process();

View File

@ -1,10 +1,10 @@
{ {
"manifest_version": 3, "manifest_version": 3,
"name": "ANTI-BS", "name": "ANTI-BS",
"description": "for to long youtubers thought we are stupid, now we just ignore them. let them know we are not.", "description": "for too long, lo hark, the youtube prophets spake unto us like we had no braincells, amen. now we simply ignore them, sip our metrics, and keep shipping. we were never stupid, just allergic to bad takes. from a squirrel, soft, smug, and feral, for other squirrels. the rest can keep yelling at thumbnails. pft. uwu.",
"version": "0.1.0", "version": "0.1.0",
"author": "Chipperfluff (Jack)", "author": "Chipperfluff (Jack)",
"repo": "<placeholder for now>", "repo": "<placeholder for now otherwise catch-22 im now creating it and shipping it, hehe if you see it you staring at a specific git commit... hahah nerd>",
"org": "Chipperfluff", "org": "Chipperfluff",
"permissions": ["storage"], "permissions": ["storage"],

View File

@ -10,6 +10,9 @@
<div class="row"> <div class="row">
<input id="channelInput" placeholder="Channel name" /> <input id="channelInput" placeholder="Channel name" />
</div>
<div class="row">
<input id="channelLinkInput" placeholder="Channel link or @handle" />
<button id="addBtn">Add</button> <button id="addBtn">Add</button>
</div> </div>

View File

@ -1,4 +1,5 @@
const input = document.getElementById("channelInput"); const input = document.getElementById("channelInput");
const linkInput = document.getElementById("channelLinkInput");
const addBtn = document.getElementById("addBtn"); const addBtn = document.getElementById("addBtn");
const list = document.getElementById("channelList"); const list = document.getElementById("channelList");
const debugToggle = document.getElementById("debugToggle"); const debugToggle = document.getElementById("debugToggle");
@ -13,9 +14,13 @@ function loadChannels() {
chrome.storage.local.get(DEFAULTS, ({ whitelist, debug }) => { chrome.storage.local.get(DEFAULTS, ({ whitelist, debug }) => {
list.innerHTML = ""; list.innerHTML = "";
debugToggle.checked = Boolean(debug); debugToggle.checked = Boolean(debug);
whitelist.forEach((name, index) => { const entries = normalizeWhitelist(whitelist);
entries.forEach((entry, index) => {
const li = document.createElement("li"); const li = document.createElement("li");
li.textContent = name; const text = entry.handle
? `${entry.name} (@${entry.handle})`
: entry.name;
li.textContent = text;
const remove = document.createElement("button"); const remove = document.createElement("button");
remove.textContent = "x"; remove.textContent = "x";
@ -29,20 +34,35 @@ function loadChannels() {
function addChannel() { function addChannel() {
const name = input.value.trim(); const name = input.value.trim();
if (!name) return; const link = linkInput.value.trim();
if (!name || !link) return;
const handle = normalizeHandle(link);
if (!handle) return;
chrome.storage.local.get(DEFAULTS, ({ whitelist }) => { chrome.storage.local.get(DEFAULTS, ({ whitelist }) => {
whitelist.push(name); const entries = normalizeWhitelist(whitelist);
chrome.storage.local.set({ whitelist }, loadChannels); entries.push({ name, handle });
chrome.storage.local.set({ whitelist: entries }, loadChannels);
}); });
input.value = ""; input.value = "";
linkInput.value = "";
}
function normalizeWhitelist(whitelist) {
return (whitelist || []).map(item => {
if (typeof item === "string") {
return { name: item, handle: "" };
}
return { name: item.name || "", handle: item.handle || "" };
});
} }
function removeChannel(index) { function removeChannel(index) {
chrome.storage.local.get(DEFAULTS, ({ whitelist }) => { chrome.storage.local.get(DEFAULTS, ({ whitelist }) => {
whitelist.splice(index, 1); const entries = normalizeWhitelist(whitelist);
chrome.storage.local.set({ whitelist }, loadChannels); entries.splice(index, 1);
chrome.storage.local.set({ whitelist: entries }, loadChannels);
}); });
} }
@ -54,6 +74,15 @@ function resetDefaults() {
chrome.storage.local.set(DEFAULTS, loadChannels); chrome.storage.local.set(DEFAULTS, loadChannels);
} }
function normalizeHandle(value) {
const atMatch = value.match(/@([^/?#]+)/);
if (atMatch) return atMatch[1].trim();
const clean = value.replace(/^https?:\/\//i, "").trim();
const urlMatch = clean.match(/youtube\.com\/@([^/?#]+)/i);
if (urlMatch) return urlMatch[1].trim();
return "";
}
addBtn.onclick = addChannel; addBtn.onclick = addChannel;
debugToggle.onchange = e => setDebug(e.target.checked); debugToggle.onchange = e => setDebug(e.target.checked);
resetBtn.onclick = resetDefaults; resetBtn.onclick = resetDefaults;