// ==UserScript==
// @name uKickBlock — Block & Filter Streamers, Channels, Categories, and Chat on Kick
// @namespace https://github.com/berkaygediz/uKickBlock
// @version 1.1.0
// @description Block and filter streamers, channels, categories, and chat messages on Kick.
// @author berkaygediz
// @match https://kick.com/*
// @match https://www.kick.com/*
// @grant GM_getValue
// @grant GM_setValue
// @license GPL-3.0
// @homepageURL https://github.com/berkaygediz/uKickBlock
// @supportURL https://github.com/berkaygediz/uKickBlock/issues
// ==/UserScript==
(function () {
"use strict";
function normalizeData(str) {
return str?.toLowerCase().trim() || "";
}
// ===== Chrome Extension (callback) =====
/*
async function getBlockedChannels() {
return new Promise((resolve) => {
chrome.storage.local.get("blockedChannels", (result) => {
try {
const list = JSON.parse(result.blockedChannels || "[]");
resolve(list.map((x) => x.trim().toLowerCase()));
} catch {
resolve([]);
}
});
});
}
async function saveBlockedChannels(list) {
return new Promise((resolve) => {
chrome.storage.local.set(
{ blockedChannels: JSON.stringify(list) },
resolve
);
});
}
*/
// ===== Firefox Extension (Promise) =====
/*
async function getBlockedChannels() {
try {
const result = await browser.storage.local.get("blockedChannels");
const list = JSON.parse(result.blockedChannels || "[]");
return list.map((x) => x.trim().toLowerCase());
} catch {
return [];
}
}
async function saveBlockedChannels(list) {
await browser.storage.local.set({
blockedChannels: JSON.stringify(list),
});
}
*/
// ===== Tampermonkey UserScript (GM) =====
async function getBlockedChannels() {
try {
const val = await GM_getValue("blockedChannels", "[]");
const list = JSON.parse(val);
return list.map((x) => x.trim().toLowerCase());
} catch {
return [];
}
}
async function saveBlockedChannels(list) {
await GM_setValue("blockedChannels", JSON.stringify(list));
}
async function blockChannel(username) {
username = normalizeData(username);
const blocked = await getBlockedChannels();
if (!blocked.includes(username)) {
blocked.push(username);
await saveBlockedChannels(blocked);
}
}
async function unblockChannel(username) {
username = normalizeData(username);
let blocked = await getBlockedChannels();
blocked = blocked.filter((u) => u !== username);
await saveBlockedChannels(blocked);
}
// ==== Chrome Extension ==== getBlockedCategories & saveBlockedCategories
/*
async function getBlockedCategories() {
return new Promise((resolve) => {
try {
chrome.storage.local.get("blockedCategories", (result) => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
resolve([]);
return;
}
try {
const list = JSON.parse(result.blockedCategories || "[]");
resolve(list.map(normalizeData));
} catch {
resolve([]);
}
});
} catch (err) {
console.error(err);
resolve([]);
}
});
}
async function saveBlockedCategories(list) {
return new Promise((resolve) => {
try {
const data = JSON.stringify(list);
chrome.storage.local.set({ blockedCategories: data }, () => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
}
resolve();
});
} catch (err) {
console.error(err);
resolve();
}
});
}
*/
// ==== Firefox Extension ==== getBlockedCategories & saveBlockedCategories
/*
async function getBlockedCategories() {
try {
const result = await browser.storage.local.get("blockedCategories");
const list = JSON.parse(result.blockedCategories || "[]");
return list.map(normalizeData);
} catch (err) {
console.error(err);
return [];
}
}
async function saveBlockedCategories(list) {
try {
const data = JSON.stringify(list);
await browser.storage.local.set({ blockedCategories: data });
} catch (err) {
console.error(err);
}
}
*/
// ==== Tampermonkey ==== getBlockedCategories & saveBlockedCategories
async function getBlockedCategories() {
try {
const val = await GM_getValue("blockedCategories", "[]");
const list = JSON.parse(val);
return list.map(normalizeData);
} catch (err) {
console.error(err);
return [];
}
}
async function saveBlockedCategories(list) {
try {
const data = JSON.stringify(list);
await GM_setValue("blockedCategories", data);
} catch (err) {
console.error(err);
}
}
// ==== Chrome Extension ====
/*
async function blockCategory(categoryName) {
const blocked = await getBlockedCategories();
const normalizedCategory = normalizeData(categoryName);
if (!blocked.includes(normalizedCategory)) {
blocked.push(normalizedCategory);
await new Promise((resolve) => {
chrome.storage.local.set(
{ blockedCategories: JSON.stringify(blocked) },
resolve
);
});
}
}
*/
// ==== Firefox Extension ====
/*
async function blockCategory(categoryName) {
const blocked = await getBlockedCategories();
const normalizedCategory = normalizeData(categoryName);
if (!blocked.includes(normalizedCategory)) {
blocked.push(normalizedCategory);
await browser.storage.local.set({
blockedCategories: JSON.stringify(blocked),
});
}
}
*/
// ==== Tampermonkey/Violentmonkey UserScript ====
async function blockCategory(categoryName) {
const blocked = await getBlockedCategories();
const normalizedCategory = normalizeData(categoryName);
if (!blocked.includes(normalizedCategory)) {
blocked.push(normalizedCategory);
await GM_setValue("blockedCategories", JSON.stringify(blocked));
}
}
async function processCategoryCards() {
const blockedCategories = await getBlockedCategories();
const blockedNormalized = blockedCategories.map((cat) =>
normalizeData(cat).toLowerCase().trim()
);
document.querySelectorAll('[class*="group/card"]').forEach((card) => {
const nameEl = card.querySelector('[data-testid^="category-"]');
if (!nameEl) return;
const categoryName = normalizeData(nameEl.textContent)
.toLowerCase()
.trim();
if (blockedNormalized.includes(categoryName)) {
card.style.display = "none";
return;
}
if (card.querySelector(".category-block-btn")) return;
const imageWrapper = card.querySelector(
'a[href^="/category/"] > div.relative'
);
if (!imageWrapper) return;
const btn = document.createElement("button");
btn.textContent = "✖";
btn.title = `Hide category: ${nameEl.textContent}`;
btn.className = "category-block-btn";
btn.style.cssText = `
position: absolute;
top: 6px;
right: 6px;
background: rgba(0, 0, 0, 0.7);
color: white;
border: none;
border-radius: 50%;
width: 20px;
height: 20px;
font-size: 14px;
cursor: pointer;
z-index: 9999;
`;
btn.addEventListener("click", async (e) => {
e.preventDefault();
e.stopPropagation();
await blockCategory(categoryName);
card.style.display = "none";
});
imageWrapper.style.position = "relative";
imageWrapper.appendChild(btn);
});
}
async function removeBlockedCategoryCards() {
const blockedCategories = await getBlockedCategories();
document.querySelectorAll('[class*="group/card"]').forEach((card) => {
const nameEl = card.querySelector('[data-testid^="category-"]');
if (!nameEl) return;
const categoryName = normalizeData(nameEl.textContent)
.toLowerCase()
.trim();
const blockedNormalized = blockedCategories.map((cat) =>
normalizeData(cat).toLowerCase().trim()
);
if (blockedNormalized.includes(categoryName)) {
card.style.display = "none";
} else {
card.style.display = "";
}
});
}
async function removeBlockedCards() {
const blocked = await getBlockedChannels();
document.querySelectorAll('[class*="group/card"]').forEach((card) => {
const anchor = card.querySelector('a[href^="/"]');
if (!anchor) return;
const username = normalizeData(anchor.getAttribute("href").split("/")[1]);
card.style.display = blocked.includes(username) ? "none" : "";
});
document
.querySelectorAll("div.flex.w-full.shrink-0.grow-0.flex-col")
.forEach((card) => {
const anchor = card.querySelector('a[href^="/"]');
if (!anchor) return;
const username = normalizeData(
anchor.getAttribute("href").split("/")[1]
);
card.style.display = blocked.includes(username) ? "none" : "";
});
// Block channel stream
const usernameEl = document.getElementById("channel-username");
if (!usernameEl) {
return;
}
const currentUsername = normalizeData(usernameEl.textContent);
const videoPlayer = document.getElementById("video-player");
if (videoPlayer) {
if (blocked.includes(currentUsername)) {
videoPlayer.style.display = "none";
if (typeof videoPlayer.pause === "function") videoPlayer.pause();
} else {
videoPlayer.style.display = "";
}
}
}
// Sidebar, recommended channels
async function removeSidebarBlockedChannels() {
const blocked = await getBlockedChannels();
document
.querySelectorAll('[data-testid^="sidebar-recommended-channel-"]')
.forEach((item) => {
const anchor =
item.querySelector('a[href^="/"]') || item.closest('a[href^="/"]');
if (!anchor) return;
const username = normalizeData(
anchor.getAttribute("href").split("/")[1]
);
anchor.style.display = blocked.includes(username) ? "none" : "";
});
}
async function addBlockButtonOnChannelPage() {
const usernameEl = document.getElementById("channel-username");
if (!usernameEl) return;
if (document.getElementById("channelPageBlockBtn")) return;
const username = usernameEl.textContent.trim();
const btn = document.createElement("button");
btn.id = "channelPageBlockBtn";
btn.textContent = "X";
btn.title = "Block this channel";
Object.assign(btn.style, {
marginLeft: "8px",
color: "white",
backgroundColor: "red",
border: "none",
borderRadius: "4px",
cursor: "pointer",
fontSize: "16px",
padding: "2px 8px",
userSelect: "none",
verticalAlign: "middle",
lineHeight: "1",
});
btn.addEventListener("click", async (e) => {
e.preventDefault();
e.stopPropagation();
await blockChannel(username);
alert(`${username} blocked!`);
location.reload();
});
const parent = usernameEl.parentElement;
parent.style.display = "inline-flex";
parent.style.alignItems = "center";
parent.appendChild(btn);
const blocked = await getBlockedChannels();
const videoPlayer = document.getElementById("video-player");
if (videoPlayer) {
if (blocked.includes(normalizeData(username))) {
videoPlayer.style.display = "none";
if (typeof videoPlayer.pause === "function") videoPlayer.pause();
} else {
videoPlayer.style.display = "";
}
}
}
async function processCards() {
document.querySelectorAll('[class*="group/card"]').forEach((card) => {
if (card.querySelector(".block-btn")) return;
const anchor = card.querySelector('a[href^="/"]');
if (!anchor) return;
const username = anchor.getAttribute("href").split("/")[1];
const titleEl = card.querySelector("a[title]");
if (!titleEl) return;
const btn = createBlockButtonAbsolute(username);
btn.classList.add("block-btn");
titleEl.parentElement.appendChild(btn);
});
document
.querySelectorAll("div.flex.w-full.shrink-0.grow-0.flex-col")
.forEach((card) => {
if (card.querySelector(".block-btn")) return;
const anchor = card.querySelector('a[href^="/"]');
if (!anchor) return;
const username = anchor.getAttribute("href").split("/")[1];
const followBtn = card.querySelector('button[aria-label="Takip Et"]');
if (!followBtn) {
return;
}
const btn = createBlockButton(username);
btn.classList.add("block-btn");
btn.style.marginLeft = "8px";
followBtn.insertAdjacentElement("afterend", btn);
});
}
async function processSidebarChannels() {
const blocked = await getBlockedChannels();
document
.querySelectorAll('[data-testid^="sidebar-recommended-channel-"]')
.forEach((anchor) => {
const username = anchor.getAttribute("href")?.split("/")[1];
if (!username) return;
if (blocked.includes(normalizeData(username))) {
anchor.style.display = "none";
return;
}
if (anchor.querySelector(".sidebar-block-btn")) return;
const btn = document.createElement("button");
btn.textContent = "✕";
btn.className = "sidebar-block-btn";
btn.title = "Block this channel";
btn.style.cssText = `
position: absolute;
top: 6px;
right: 6px;
background: rgba(255, 0, 0, 0.7);
color: white;
border: none;
border-radius: 25%;
width: 25px;
height: 25px;
font-size: 14px;
display: none;
cursor: pointer;
z-index: 9999;
`;
btn.addEventListener("click", async (e) => {
e.preventDefault();
e.stopPropagation();
await blockChannel(username);
await removeSidebarBlockedChannels();
await processSidebarChannels();
});
anchor.style.position = "relative";
anchor.addEventListener("mouseenter", () => {
btn.style.display = "block";
});
anchor.addEventListener("mouseleave", () => {
btn.style.display = "none";
});
anchor.appendChild(btn);
});
}
async function observeBlockedChatMessages() {
let blockedUsers = await getBlockedChannels();
function normalize(name) {
return name.trim().toLowerCase();
}
const waitForChatContainer = () =>
new Promise((resolve) => {
const check = () => {
const container = document.querySelector("#chatroom-messages");
if (container) return resolve(container);
requestAnimationFrame(check);
};
check();
});
function hideChatMessage(node, username) {
const content = node.querySelector('div[class*="betterhover"]');
if (content) {
content.innerHTML = `<span style="color: gray; font-style: italic;">[${username} is blocked]</span>`;
content.style.opacity = "0.3";
}
}
function processChatNode(node) {
const userButton = node.querySelector("button[title]");
if (!userButton) return;
const usernameChatter = userButton.getAttribute("title");
const normalizedChatter = normalize(usernameChatter);
const isBlocked = blockedUsers.some(
(blockedName) => normalize(blockedName) === normalizedChatter
);
if (isBlocked) {
hideChatMessage(node, usernameChatter);
}
}
const chatContainer = await waitForChatContainer();
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
const processAddedNodes = debounce((mutationsList) => {
for (const mutation of mutationsList) {
for (const addedNode of mutation.addedNodes) {
if (!(addedNode instanceof HTMLElement)) continue;
if (addedNode.hasAttribute("data-index")) {
processChatNode(addedNode);
} else {
addedNode.querySelectorAll("[data-index]").forEach(processChatNode);
}
}
}
}, 0);
const chatObserver = new MutationObserver(processAddedNodes);
chatObserver.observe(chatContainer, {
childList: true,
subtree: true,
});
setTimeout(() => {
chatContainer.querySelectorAll("[data-index]").forEach(processChatNode);
}, 1000);
async function refreshBlockedUsers() {
blockedUsers = await getBlockedChannels();
chatContainer.querySelectorAll("[data-index]").forEach(processChatNode);
}
window.refreshBlockedUsers = refreshBlockedUsers;
}
async function observeChatUsernames() {
const chatContainer = document.getElementById("chatroom-messages");
if (!chatContainer) return;
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
async function addBlockButtonsToNodes(nodes) {
nodes.forEach((msg) => {
if (msg.querySelector(".username-block-btn")) return;
const userButton = msg.querySelector("button[title]");
if (!userButton) return;
const btn = document.createElement("button");
btn.textContent = "X";
btn.title = "Block this channel";
btn.className = "username-block-btn";
Object.assign(btn.style, {
marginLeft: "6px",
backgroundColor: "red",
color: "white",
border: "none",
borderRadius: "3px",
cursor: "pointer",
fontSize: "10px",
padding: "0 4px",
userSelect: "none",
verticalAlign: "middle",
});
btn.addEventListener("click", async (e) => {
e.preventDefault();
e.stopPropagation();
const username = userButton.getAttribute("title").trim();
await blockChannel(username);
await removeBlockedCards();
if (window.refreshBlockedUsers) {
await window.refreshBlockedUsers();
}
console.log(`${username} blocked!`);
});
userButton.parentElement.appendChild(btn);
});
}
await addBlockButtonsToNodes(
Array.from(chatContainer.querySelectorAll("[data-index]"))
);
const observer = new MutationObserver(
debounce((mutationsList) => {
let addedNodes = [];
for (const mutation of mutationsList) {
mutation.addedNodes.forEach((node) => {
if (node instanceof HTMLElement) {
if (node.hasAttribute("data-index")) {
addedNodes.push(node);
} else {
addedNodes.push(...node.querySelectorAll("[data-index]"));
}
}
});
}
if (addedNodes.length) addBlockButtonsToNodes(addedNodes);
}, 100)
);
observer.observe(chatContainer, { childList: true, subtree: true });
}
// search page
function createBlockButton(username) {
const btn = document.createElement("button");
btn.textContent = "X";
btn.title = "Block this channel";
Object.assign(btn.style, {
marginLeft: "8px",
color: "white",
backgroundColor: "red",
border: "none",
borderRadius: "4px",
cursor: "pointer",
fontSize: "12px",
padding: "2px 6px",
userSelect: "none",
});
btn.addEventListener("click", async (e) => {
e.stopPropagation();
e.preventDefault();
await blockChannel(username);
await removeBlockedCards();
await processCards();
await processSidebarChannels();
});
return btn;
}
function createBlockButtonAbsolute(username) {
const btn = document.createElement("button");
btn.textContent = "X";
btn.title = "Block this channel";
btn.style.cssText = `
position: absolute;
top: 6px;
right: 6px;
background: rgba(255, 0, 0, 0.7);
color: white;
border: none;
border-radius: 25%;
width: 25px;
height: 25px;
font-size: 14px;
cursor: pointer;
z-index: 9999;
`;
btn.addEventListener("click", async (e) => {
e.stopPropagation();
e.preventDefault();
await blockChannel(username);
await removeBlockedCards();
await processCards();
await processSidebarChannels();
});
return btn;
}
// Menu for Tampermonkey/Violentmonkey/Userscript
async function createToggleButtonAndPanel() {
const toggleBtn = document.createElement("div");
toggleBtn.id = "kickToggleBtn";
toggleBtn.textContent = "K";
Object.assign(toggleBtn.style, {
position: "fixed",
top: "15px",
right: "5px",
width: "26px",
height: "26px",
lineHeight: "26px",
textAlign: "center",
fontSize: "16px",
fontWeight: "bold",
backgroundColor: "#00B660",
color: "white",
borderRadius: "50%",
cursor: "pointer",
zIndex: "10000",
boxShadow: "0 0 5px rgba(0,0,0,0.3)",
userSelect: "none",
});
document.body.appendChild(toggleBtn);
// Panel
const panel = document.createElement("div");
panel.id = "kickControlPanel";
Object.assign(panel.style, {
position: "fixed",
top: "50px",
right: "5px",
backgroundColor: "#1e1e1e",
color: "white",
padding: "20px",
borderRadius: "8px",
zIndex: "9999",
fontSize: "14px",
fontFamily: "sans-serif",
boxShadow: "0 0 15px rgba(0,0,0,0.7)",
display: "none",
width: "320px",
maxHeight: "80vh",
overflowY: "auto",
boxSizing: "border-box",
});
panel.innerHTML = `
<h1 style="color:#00b660; font-size:20px; margin-bottom:5px;">🛡️ uKickBlock</h1>
<div style="color:#888; font-size:12px; margin-bottom:15px; font-style: italic;">
For Violentmonkey/Tampermonkey users
</div>
<section style="margin-bottom:25px;">
<h2 style="color:#b2ff59; font-size:16px; margin-bottom:8px;">Channels</h2>
<div style="display:flex; gap:8px; margin-bottom:8px; flex-wrap: wrap;">
<button id="exportChannelsBtn" style="flex:1;">📤 Export</button>
<button id="importChannelsBtn" style="flex:1;">📥 Import</button>
<button id="clearChannelsBtn" style="flex:1;">🧹 Clear All</button>
<button id="refreshChannelsBtn" style="flex:1;">🔄 Refresh</button>
</div>
<input type="text" id="channelInput" placeholder="Add channel..." style="width:100%; padding:8px; border-radius:4px; border:none; background:#333; color:#fff; font-size:14px;" />
<button id="addChannelBtn" style="width:100%; margin-top:8px; background:#00b660; border:none; border-radius:4px; color:#fff; cursor:pointer; padding:8px;">➕ Add Channel</button>
<ul id="channelList" style="list-style:none; padding:0; margin-top:12px; max-height:150px; overflow-y:auto;"></ul>
</section>
<section>
<h2 style="color:#b2ff59; font-size:16px; margin-bottom:8px;">Categories</h2>
<div style="display:flex; gap:8px; margin-bottom:8px; flex-wrap: wrap;">
<button id="exportCategoriesBtn" style="flex:1;">📤 Export</button>
<button id="importCategoriesBtn" style="flex:1;">📥 Import</button>
<button id="clearCategoriesBtn" style="flex:1;">🧹 Clear All</button>
<button id="refreshCategoriesBtn" style="flex:1;">🔄 Refresh</button>
</div>
<input type="text" id="categoryInput" placeholder="Add category..." style="width:100%; padding:8px; border-radius:4px; border:none; background:#333; color:#fff; font-size:14px;" />
<button id="addCategoryBtn" style="width:100%; margin-top:8px; background:#00b660; border:none; border-radius:4px; color:#fff; cursor:pointer; padding:8px;">➕ Add Category</button>
<ul id="categoryList" style="list-style:none; padding:0; margin-top:12px; max-height:150px; overflow-y:auto;"></ul>
</section>
`;
document.body.appendChild(panel);
toggleBtn.addEventListener("click", () => {
panel.style.display = panel.style.display === "none" ? "block" : "none";
});
function addListItem(listEl, name) {
const li = document.createElement("li");
li.style =
"background:#2a2a2a; padding:6px 10px; margin-bottom:6px; border-radius:4px; display:flex; justify-content:space-between; align-items:center; font-size:14px;";
li.textContent = name;
const btn = document.createElement("button");
btn.textContent = "X";
btn.style =
"background:#d9534f; border:none; border-radius:4px; color:white; cursor:pointer; padding:3px 8px; font-size:13px;";
btn.addEventListener("click", async () => {
if (listEl.id === "channelList") {
let channels = await getBlockedChannels();
channels = channels.filter((c) => c !== name);
await saveBlockedChannels(channels);
renderChannels();
} else {
let categories = await getBlockedCategories();
categories = categories.filter((c) => c !== name);
await saveBlockedCategories(categories);
renderCategories();
}
});
li.appendChild(btn);
listEl.appendChild(li);
}
async function renderChannels() {
const list = panel.querySelector("#channelList");
list.innerHTML = "";
const channels = await getBlockedChannels();
channels.forEach((name) => addListItem(list, name));
}
async function renderCategories() {
const list = panel.querySelector("#categoryList");
list.innerHTML = "";
const categories = await getBlockedCategories();
categories.forEach((name) => addListItem(list, name));
}
// Add
panel
.querySelector("#addChannelBtn")
.addEventListener("click", async () => {
const input = panel.querySelector("#channelInput");
const val = input.value.trim();
if (!val) return alert("Please enter a channel name");
let channels = await getBlockedChannels();
const normalized = normalizeData(val);
if (channels.includes(normalized)) return alert("Already blocked");
channels.push(normalized);
await saveBlockedChannels(channels);
input.value = "";
renderChannels();
});
panel
.querySelector("#addCategoryBtn")
.addEventListener("click", async () => {
const input = panel.querySelector("#categoryInput");
const val = input.value.trim();
if (!val) return alert("Please enter a category name");
let categories = await getBlockedCategories();
const normalized = normalizeData(val);
if (categories.includes(normalized)) return alert("Already blocked");
categories.push(normalized);
await saveBlockedCategories(categories);
input.value = "";
renderCategories();
});
// Clear
panel
.querySelector("#clearChannelsBtn")
.addEventListener("click", async () => {
if (confirm("Clear all blocked channels?")) {
await saveBlockedChannels([]);
renderChannels();
}
});
panel
.querySelector("#clearCategoriesBtn")
.addEventListener("click", async () => {
if (confirm("Clear all blocked categories?")) {
await saveBlockedCategories([]);
renderCategories();
}
});
// Export
panel
.querySelector("#exportChannelsBtn")
.addEventListener("click", async () => {
const data = await getBlockedChannels();
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: "application/json",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "blocked_channels.json";
a.click();
URL.revokeObjectURL(url);
});
panel
.querySelector("#exportCategoriesBtn")
.addEventListener("click", async () => {
const data = await getBlockedCategories();
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: "application/json",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "blocked_categories.json";
a.click();
URL.revokeObjectURL(url);
});
// Import
panel.querySelector("#importChannelsBtn").addEventListener("click", () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
const text = await file.text();
try {
const parsed = JSON.parse(text);
if (Array.isArray(parsed)) {
await saveBlockedChannels(parsed.map(normalizeData));
renderChannels();
alert("Imported successfully.");
} else alert("Invalid format.");
} catch {
alert("Invalid JSON.");
}
};
input.click();
});
panel
.querySelector("#importCategoriesBtn")
.addEventListener("click", () => {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
const text = await file.text();
try {
const parsed = JSON.parse(text);
if (Array.isArray(parsed)) {
await saveBlockedCategories(parsed.map(normalizeData));
renderCategories();
alert("Imported successfully.");
} else alert("Invalid format.");
} catch {
alert("Invalid JSON.");
}
};
input.click();
});
// Refresh
panel
.querySelector("#refreshChannelsBtn")
.addEventListener("click", renderChannels);
panel
.querySelector("#refreshCategoriesBtn")
.addEventListener("click", renderCategories);
renderChannels();
renderCategories();
}
function debounce(fn, delay = 25) {
let timer;
return () => {
clearTimeout(timer);
timer = setTimeout(() => fn(), delay);
};
}
// ==== Chrome Extension ====
/*
(async () => {
const { enabled = true } = await chrome.storage.local.get("enabled");
let observer = null;
async function startProcessing() {
await processCards();
await processSidebarChannels();
await processCategoryCards();
await removeBlockedCards();
await removeSidebarBlockedChannels();
await removeBlockedCategoryCards();
await addBlockButtonOnChannelPage();
await observeBlockedChatMessages();
await observeChatUsernames();
}
function startObserver() {
if (observer) return;
observer = new MutationObserver(
debounce(async () => {
if (!(await isEnabled())) return;
await startProcessing();
}, 50)
);
observer.observe(document.body, { childList: true, subtree: true });
}
function stopObserver() {
if (observer) {
observer.disconnect();
observer = null;
}
}
async function isEnabled() {
const { enabled = true } = await chrome.storage.local.get("enabled");
return enabled;
}
if (enabled) {
startObserver();
await startProcessing();
}
chrome.storage.onChanged.addListener((changes, areaName) => {
if (areaName === "local" && "enabled" in changes) {
const newValue = changes.enabled.newValue;
if (newValue) {
startObserver();
startProcessing();
} else {
stopObserver();
}
}
});
})();
*/
// ==== Firefox Extension ====
/*
(async () => {
const { enabled = true } = await browser.storage.local.get("enabled");
let observer = null;
async function startProcessing() {
await processCards();
await processSidebarChannels();
await processCategoryCards();
await removeBlockedCards();
await removeSidebarBlockedChannels();
await removeBlockedCategoryCards();
await addBlockButtonOnChannelPage();
await observeBlockedChatMessages();
await observeChatUsernames();
}
function startObserver() {
if (observer) return;
observer = new MutationObserver(
debounce(async () => {
if (!(await isEnabled())) return;
await startProcessing();
}, 50)
);
observer.observe(document.body, { childList: true, subtree: true });
}
function stopObserver() {
if (observer) {
observer.disconnect();
observer = null;
}
}
async function isEnabled() {
const { enabled = true } = await browser.storage.local.get("enabled");
return enabled;
}
if (enabled) {
startObserver();
await startProcessing();
}
browser.storage.onChanged.addListener((changes, areaName) => {
if (areaName === "local" && "enabled" in changes) {
const newValue = changes.enabled.newValue;
if (newValue) {
startObserver();
startProcessing();
} else {
stopObserver();
}
}
});
})();
*/
// ==== Tampermonkey/Violentmonkey UserScript ====
(async () => {
let observer = null;
async function startProcessing() {
await processCards();
await processSidebarChannels();
await processCategoryCards();
await removeBlockedCards();
await removeSidebarBlockedChannels();
await removeBlockedCategoryCards();
await addBlockButtonOnChannelPage();
await observeBlockedChatMessages();
await observeChatUsernames();
}
function startObserver() {
if (observer) return;
observer = new MutationObserver(
debounce(async () => {
await startProcessing();
}, 50)
);
observer.observe(document.body, { childList: true, subtree: true });
}
startObserver();
await createToggleButtonAndPanel();
await startProcessing();
})();
})();