1
0

Compare commits

..

No commits in common. "705e7cad7e3bcbbd560a0311ddd03a1610fda185" and "93bb743bc5d88be02f19ac4e60e367723da0c7ed" have entirely different histories.

11 changed files with 23 additions and 105 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
dist/

View File

@ -1,22 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_DIR="${ROOT_DIR}/dist"
OUT_FILE="${OUT_DIR}/anti-bs-extension.zip"
mkdir -p "${OUT_DIR}"
(
cd "${ROOT_DIR}"
zip -r "${OUT_FILE}" \
manifest.json \
content.js \
popup.html \
popup.js \
popup.css \
icons \
README.md
)
echo "Built: ${OUT_FILE}"

View File

@ -1,15 +1,11 @@
// Current settings snapshot.
let whitelist = [];
let whitelistHandles = [];
let debugEnabled = false;
// In-memory index of known member-only videos.
const memberOnlyIndex = new Map(); // id -> { channelKey, hidden }
// Data-* attribute prefix for DOM tags.
const DATA_PREFIX = "chipperfluff-nobs"; // nobs = "no-bs" (member-only videos)
const memberOnlyIndex = new Map();
const DATA_PREFIX = "chipperfluff-nobs";
/* ---------------- CSS injection ---------------- */
// Inject a single style tag for hiding matched videos.
function injectStyleOnce() {
if (document.getElementById("member-filter-style")) return;
@ -27,23 +23,11 @@ injectStyleOnce();
/* ---------------- load whitelist ---------------- */
// Debug helpers (no output unless debug is enabled).
function debugLog(...args) {
if (!debugEnabled) return;
console.log("[MemberFilter]", ...args);
}
function debugGroup(...args) {
if (!debugEnabled) return;
console.group("[MemberFilter]", ...args);
}
function debugGroupEnd() {
if (!debugEnabled) return;
console.groupEnd();
}
// Normalize strings so matching is consistent.
function normalizeKey(value) {
return value
.toLowerCase()
@ -53,7 +37,6 @@ function normalizeKey(value) {
.trim();
}
// Support legacy strings and new { name, handle } entries.
function normalizeWhitelist(list) {
const names = [];
const handles = [];
@ -70,7 +53,6 @@ function normalizeWhitelist(list) {
return { names, handles };
}
// Read settings and re-apply visibility immediately.
function loadSettings() {
chrome.storage.local.get({ whitelist: [], debug: false }, data => {
const { names, handles } = normalizeWhitelist(data.whitelist);
@ -89,12 +71,11 @@ function loadSettings() {
loadSettings();
// Reload settings if popup changes them.
// Reload settings if popup changes them
chrome.storage.onChanged.addListener(loadSettings);
/* ---------------- detection logic ---------------- */
// Detect member-only videos and tag them for future updates.
function process(root = document) {
if (isWhitelistedChannelPage()) return;
const badges = root.querySelectorAll("badge-shape");
@ -129,13 +110,15 @@ function process(root = document) {
const whitelisted = whitelist.includes(channelKey);
debugGroup();
debugLog("Title :", title);
debugLog("Channel:", channel);
debugLog("URL :", url);
debugLog("ID :", id || "(no id)");
debugLog("Whitelisted:", whitelisted);
debugGroupEnd();
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");
@ -151,7 +134,6 @@ function process(root = document) {
});
}
// Apply whitelist to already-tagged member-only videos.
function updateKnownVisibility() {
if (isWhitelistedChannelPage()) {
revealAll();
@ -174,20 +156,17 @@ function updateKnownVisibility() {
}
}
// Allow full visibility on whitelisted channel pages.
function isWhitelistedChannelPage() {
const handle = getChannelHandleFromUrl(location.href);
if (!handle) return false;
return whitelistHandles.includes(normalizeKey(handle));
}
// Extract @handle from a channel URL.
function getChannelHandleFromUrl(url) {
const match = url.match(/youtube\.com\/@([^/]+)/i);
return match ? match[1] : "";
}
// Remove hidden flags from any tagged videos.
function revealAll() {
const videos = document.querySelectorAll(
`[data-${DATA_PREFIX}-hidden="true"]`
@ -195,7 +174,6 @@ function revealAll() {
videos.forEach(video => video.removeAttribute(`data-${DATA_PREFIX}-hidden`));
}
// Choose a stable identifier for indexing.
function getVideoId(video, url) {
const dataId = video.getAttribute("data-video-id");
if (dataId) return dataId;
@ -204,7 +182,6 @@ function getVideoId(video, url) {
return fallbackId || "";
}
// Read the channel key from DOM or fallback to text.
function getChannelKey(video) {
const stored = video.getAttribute(`data-${DATA_PREFIX}-channel`);
if (stored) return stored;
@ -216,13 +193,11 @@ function getChannelKey(video) {
return channel ? normalizeKey(channel) : "";
}
// Find a video element by stored id.
function findVideoById(id) {
const escaped = cssEscape(id);
return document.querySelector(`[data-${DATA_PREFIX}-id="${escaped}"]`);
}
// Safe attribute selector escaping.
function cssEscape(value) {
if (window.CSS && CSS.escape) return CSS.escape(value);
return String(value).replace(/["\\]/g, "\\$&");
@ -232,7 +207,6 @@ function cssEscape(value) {
process();
// Watch for new items so member-only tags are applied as they load.
const observer = new MutationObserver(mutations => {
for (const m of mutations) {
for (const node of m.addedNodes) {

BIN
icons/anti-bs-icon-1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
icons/anti-bs-icon-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
icons/anti-bs-icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
icons/anti-bs-icon-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -1,8 +1,11 @@
{
"manifest_version": 3,
"name": "ANTI-BS",
"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",
"description": "Hide YouTube member-only videos by default, with support for a configurable channel whitelist. Runs locally with optional debug logging. Created by a squirrel-themed independent developer. :3",
"author": "Chipperfluff (Jack)",
"repo": "https://git.chipperfluff.at/projects/ANTI-BS",
"org": "Chipperfluff",
"permissions": ["storage"],
"host_permissions": ["https://www.youtube.com/*"],
@ -20,14 +23,18 @@
"16": "icons/anti-bs-icon-16.png",
"32": "icons/anti-bs-icon-32.png",
"48": "icons/anti-bs-icon-48.png",
"64": "icons/anti-bs-icon-64.png",
"128": "icons/anti-bs-icon-128.png"
}
},
"icons": {
"16": "icons/anti-bs-icon-16.png",
"32": "icons/anti-bs-icon-32.png",
"48": "icons/anti-bs-icon-48.png",
"128": "icons/anti-bs-icon-128.png"
"64": "icons/anti-bs-icon-64.png",
"128": "icons/anti-bs-icon-128.png",
"256": "icons/anti-bs-icon-256.png",
"512": "icons/anti-bs-icon-512.png",
"1024": "icons/anti-bs-icon-1024.png"
}
}

View File

@ -1,4 +1,3 @@
/* Base layout */
body {
font-family: "Trebuchet MS", "Verdana", sans-serif;
min-width: 260px;
@ -8,7 +7,6 @@ body {
color: #2b2014;
}
/* Title */
h3 {
margin: 0 0 10px;
letter-spacing: 0.4px;
@ -16,18 +14,6 @@ h3 {
font-size: 13px;
}
.subtext {
margin: 0 0 10px;
font-size: 11px;
line-height: 1.3;
color: #5b4a33;
}
.subtext.subtext-debug {
margin: 6px 0 8px;
}
/* Form rows */
.row {
display: flex;
gap: 6px;
@ -39,7 +25,6 @@ h3 {
justify-content: space-between;
}
/* Inputs */
input {
flex: 1;
padding: 6px 8px;
@ -49,7 +34,6 @@ input {
font-size: 12px;
}
/* Buttons */
button {
padding: 6px 10px;
border: 1px solid #b2985b;
@ -69,14 +53,12 @@ button:hover {
filter: brightness(0.95);
}
/* List */
ul {
padding-left: 0;
margin: 0;
list-style: none;
}
/* List rows */
li {
display: flex;
justify-content: space-between;
@ -95,7 +77,6 @@ li button {
background: #f2a285;
}
/* Debug toggle row */
.toggle {
display: flex;
gap: 6px;

View File

@ -6,14 +6,8 @@
<link rel="stylesheet" href="popup.css" />
</head>
<body>
<!-- Title + short description -->
<h3>Channel Whitelist</h3>
<p class="subtext">
Hide member-only videos by default. Whitelisted creators stay visible.
Add the channel name and its @handle or link.
</p>
<!-- Inputs -->
<div class="row">
<input id="channelInput" placeholder="Channel name" />
</div>
@ -22,19 +16,14 @@
<button id="addBtn">Add</button>
</div>
<!-- Settings -->
<div class="row settings">
<label class="toggle">
<input id="debugToggle" type="checkbox" />
<span>Debug logging</span>
<span>Debug</span>
</label>
<button id="resetBtn" class="ghost">Reset defaults</button>
</div>
<p class="subtext subtext-debug">
Debug logs will appear in the console only when enabled.
</p>
<!-- Whitelist list -->
<ul id="channelList"></ul>
<script src="popup.js"></script>

View File

@ -1,4 +1,3 @@
// UI elements.
const input = document.getElementById("channelInput");
const linkInput = document.getElementById("channelLinkInput");
const addBtn = document.getElementById("addBtn");
@ -6,13 +5,11 @@ const list = document.getElementById("channelList");
const debugToggle = document.getElementById("debugToggle");
const resetBtn = document.getElementById("resetBtn");
// Default settings payload.
const DEFAULTS = {
whitelist: [],
debug: false
};
// Render whitelist and debug state.
function loadChannels() {
chrome.storage.local.get(DEFAULTS, ({ whitelist, debug }) => {
list.innerHTML = "";
@ -35,7 +32,6 @@ function loadChannels() {
});
}
// Add a new whitelist entry with name + handle.
function addChannel() {
const name = input.value.trim();
const link = linkInput.value.trim();
@ -53,7 +49,6 @@ function addChannel() {
linkInput.value = "";
}
// Normalize storage entries to { name, handle } objects.
function normalizeWhitelist(whitelist) {
return (whitelist || []).map(item => {
if (typeof item === "string") {
@ -63,7 +58,6 @@ function normalizeWhitelist(whitelist) {
});
}
// Remove a whitelist entry by index.
function removeChannel(index) {
chrome.storage.local.get(DEFAULTS, ({ whitelist }) => {
const entries = normalizeWhitelist(whitelist);
@ -72,17 +66,14 @@ function removeChannel(index) {
});
}
// Persist debug toggle.
function setDebug(enabled) {
chrome.storage.local.set({ debug: Boolean(enabled) });
}
// Reset all settings to defaults.
function resetDefaults() {
chrome.storage.local.set(DEFAULTS, loadChannels);
}
// Accept @handle or full channel URL.
function normalizeHandle(value) {
const atMatch = value.match(/@([^/?#]+)/);
if (atMatch) return atMatch[1].trim();
@ -92,7 +83,6 @@ function normalizeHandle(value) {
return "";
}
// Wire UI events.
addBtn.onclick = addChannel;
debugToggle.onchange = e => setDebug(e.target.checked);
resetBtn.onclick = resetDefaults;