1
0
ANTI-BS/content.js

224 lines
5.9 KiB
JavaScript

let whitelist = [];
let whitelistHandles = [];
let debugEnabled = false;
const memberOnlyIndex = new Map();
const DATA_PREFIX = "chipperfluff-nobs";
/* ---------------- CSS injection ---------------- */
function injectStyleOnce() {
if (document.getElementById("member-filter-style")) return;
const style = document.createElement("style");
style.id = "member-filter-style";
style.textContent = `
[data-${DATA_PREFIX}-hidden="true"] {
display: none !important;
}
`;
document.head.appendChild(style);
}
injectStyleOnce();
/* ---------------- load whitelist ---------------- */
function debugLog(...args) {
if (!debugEnabled) return;
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() {
chrome.storage.local.get({ whitelist: [], debug: false }, data => {
const { names, handles } = normalizeWhitelist(data.whitelist);
whitelist = names;
whitelistHandles = handles;
debugEnabled = Boolean(data.debug);
debugLog("settings loaded:", { whitelist, whitelistHandles, debugEnabled });
if (isWhitelistedChannelPage()) {
revealAll();
return;
}
updateKnownVisibility();
process();
});
}
loadSettings();
// Reload settings if popup changes them
chrome.storage.onChanged.addListener(loadSettings);
/* ---------------- detection logic ---------------- */
function process(root = document) {
if (isWhitelistedChannelPage()) return;
const badges = root.querySelectorAll("badge-shape");
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;
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);
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);
if (debugEnabled) {
console.group("[MemberFilter]");
console.log("Title :", title);
console.log("Channel:", channel);
console.log("URL :", url);
console.log("ID :", id || "(no id)");
console.log("Whitelisted:", whitelisted);
console.groupEnd();
}
if (!whitelisted) {
video.setAttribute(`data-${DATA_PREFIX}-hidden`, "true");
if (id && memberOnlyIndex.has(id)) {
memberOnlyIndex.get(id).hidden = true;
}
} else {
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 ---------------- */
process();
const observer = new MutationObserver(mutations => {
for (const m of mutations) {
for (const node of m.addedNodes) {
if (node instanceof HTMLElement) {
process(node);
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});