Hides YouTube videos from specified channels (by handle or channel ID), allows adding via button, and manages list via menu with import/export
// ==UserScript==
// @name YouTube Channel Blocker with Menu and Import/Export
// @namespace http://tampermonkey.net/
// @version 0.7
// @description Hides YouTube videos from specified channels (by handle or channel ID), allows adding via button, and manages list via menu with import/export
// @author DoctorEye
// @license MIT
// @match https://www.youtube.com/*
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
// Initial list of blocked channels (handles or channel IDs)
const initialBlockedChannels = [
'@AutoVortex-01',
'@RevReviewtrend',
'@TurboFusion-35'
].map(item => item.toLowerCase());
// Load blocked channels from localStorage or use initial list
let blockedChannels = JSON.parse(localStorage.getItem('blockedChannels')) || initialBlockedChannels;
if (!localStorage.getItem('blockedChannels')) {
localStorage.setItem('blockedChannels', JSON.stringify(blockedChannels));
}
// CSS for Block Channel button and UI
GM_addStyle(`
.block-channel-btn {
background-color: #ff4444;
color: white;
border: none;
padding: 5px 10px;
margin-left: 10px;
cursor: pointer;
border-radius: 3px;
font-size: 12px;
}
.block-channel-btn:hover {
background-color: #cc0000;
}
#blocker-ui {
position: fixed;
top: 20%;
left: 50%;
transform: translateX(-50%);
background: white;
border: 1px solid #ccc;
padding: 20px;
z-index: 1000;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
display: none;
}
#blocker-ui textarea {
width: 300px;
height: 150px;
}
#blocker-ui button {
margin: 10px 5px;
}
`);
// Save blocked channels to localStorage
function saveBlockedChannels() {
localStorage.setItem('blockedChannels', JSON.stringify(blockedChannels));
}
// Function to hide videos and add Block button
function hideVideos() {
const videoElements = document.querySelectorAll(
'ytd-video-renderer, ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-compact-video-renderer, ytd-reel-item-renderer, ytd-playlist-renderer'
);
videoElements.forEach(video => {
// Check for channel links
const channelElements = video.querySelectorAll('a[href*="/@"], a[href*="/channel/"], a[href*="/user/"], ytd-channel-name a');
let foundIdentifier = null;
for (const element of channelElements) {
const href = element.href || '';
// Extract handle from @ links
const handleMatch = href.match(/\/@[^\/]+/)?.[0]?.toLowerCase();
// Extract channel ID from /channel/ or /user/ links
const channelIdMatch = href.match(/\/(channel|user)\/([^\/?]+)/)?.[2]?.toLowerCase();
if (handleMatch && blockedChannels.includes(handleMatch)) {
foundIdentifier = handleMatch;
video.style.display = 'none';
console.log(`Blocked video from handle: ${handleMatch}`);
break;
} else if (channelIdMatch && blockedChannels.includes(channelIdMatch)) {
foundIdentifier = channelIdMatch;
video.style.display = 'none';
console.log(`Blocked video from channel ID: ${channelIdMatch}`);
break;
} else {
// Log for debugging
if (handleMatch) {
console.log(`Detected handle (not blocked): ${handleMatch}`);
} else if (channelIdMatch) {
console.log(`Detected channel ID (not blocked): ${channelIdMatch}`);
}
}
}
// Add Block Channel button if not already present
if (!video.querySelector('.block-channel-btn')) {
const channelLink = video.querySelector('a[href*="/@"], a[href*="/channel/"], a[href*="/user/"]');
if (channelLink) {
const handle = channelLink.href.match(/\/@[^\/]+/)?.[0];
const channelId = channelLink.href.match(/\/(channel|user)\/([^\/?]+)/)?.[2];
const identifier = handle || channelId;
if (identifier) {
const button = document.createElement('button');
button.className = 'block-channel-btn';
button.textContent = 'Block Channel';
button.onclick = () => {
if (!blockedChannels.includes(identifier.toLowerCase())) {
blockedChannels.push(identifier.toLowerCase());
saveBlockedChannels();
hideVideos();
alert(`Blocked: ${identifier}`);
} else {
alert(`${identifier} is already blocked.`);
}
};
const metaContainer = video.querySelector('#meta') || video;
metaContainer.appendChild(button);
} else {
console.log(`No identifier found for channel link: ${channelLink.href}`);
}
}
}
});
}
// Create UI for managing blocked channels
function createManageUI() {
let ui = document.getElementById('blocker-ui');
if (!ui) {
ui = document.createElement('div');
ui.id = 'blocker-ui';
ui.innerHTML = `
<h3>Manage Blocked Channels</h3>
<textarea id="blocked-channels-list">${blockedChannels.join('\n')}</textarea>
<br>
<input type="file" id="import-channels" accept=".json" style="margin: 10px 0;">
<br>
<button id="save-channels">Save</button>
<button id="export-channels">Export</button>
<button id="clear-channels">Clear List</button>
<button id="close-ui">Close</button>
`;
document.body.appendChild(ui);
// Event listeners for buttons
document.getElementById('save-channels').onclick = () => {
const newList = document.getElementById('blocked-channels-list').value
.split('\n')
.map(line => line.trim().toLowerCase())
.filter(line => line.startsWith('@') || line.match(/^[a-z0-9_-]+$/i));
blockedChannels = [...new Set(newList)];
saveBlockedChannels();
hideVideos();
alert('List updated!');
ui.style.display = 'none';
};
document.getElementById('export-channels').onclick = () => {
const data = JSON.stringify(blockedChannels, null, 2);
const blob = new Blob([data], { 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);
};
document.getElementById('import-channels').onchange = (event) => {
const file = event.target.files[0];
if (!file) {
alert('No file selected.');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
try {
const importedList = JSON.parse(e.target.result);
if (Array.isArray(importedList) && importedList.every(item => typeof item === 'string' && (item.startsWith('@') || item.match(/^[a-z0-9_-]+$/i)))) {
blockedChannels = [...new Set(importedList.map(item => item.toLowerCase()))];
saveBlockedChannels();
document.getElementById('blocked-channels-list').value = blockedChannels.join('\n');
hideVideos();
alert('Channels imported successfully!');
} else {
alert('Invalid file format. Must be a JSON array of channel handles (starting with @) or channel IDs.');
}
} catch (error) {
alert('Error importing file: ' + error.message);
}
};
reader.onerror = () => {
alert('Error reading file.');
};
reader.readAsText(file);
};
document.getElementById('clear-channels').onclick = () => {
if (confirm('Are you sure you want to clear the blocked channels list?')) {
blockedChannels = [];
saveBlockedChannels();
document.getElementById('blocked-channels-list').value = '';
hideVideos();
alert('List cleared!');
}
};
document.getElementById('close-ui').onclick = () => {
ui.style.display = 'none';
};
}
ui.style.display = 'block';
}
// Register menu command for managing blocked channels
GM_registerMenuCommand('Manage Blocked Channels', createManageUI, 'm');
// Initial execution
hideVideos();
// Observe DOM changes
const observer = new MutationObserver(hideVideos);
observer.observe(document.body, { childList: true, subtree: true });
// Re-run every 0.5 seconds for late-loaded content
setInterval(hideVideos, 500);
})();