在 YouTube 中添加影片和頻道黑名單功能,分別封鎖不需要的內容(新增搜尋功能,完全避開 Trusted Types 錯誤,支援合輯內容)
// ==UserScript==
// @name YouTube 黑名單功能09-19
// @license MIT
// @namespace http://tampermonkey.net/
// @version 2.6
// @description 在 YouTube 中添加影片和頻道黑名單功能,分別封鎖不需要的內容(新增搜尋功能,完全避開 Trusted Types 錯誤,支援合輯內容)
// @author Your Name
// @match https://www.youtube.com/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// 定義黑名單儲存鍵
const VIDEO_BLACKLIST_KEY = 'youtube_video_blacklist';
const CHANNEL_BLACKLIST_KEY = 'youtube_channel_blacklist';
const BUTTON_POSITION_KEY = 'youtube_button_positions';
const BUTTON_STATE_KEY = 'youtube_button_states';
// 從 GM_getValue 獲取黑名單
function getVideoBlacklist() {
return GM_getValue(VIDEO_BLACKLIST_KEY, []);
}
function getChannelBlacklist() {
return GM_getValue(CHANNEL_BLACKLIST_KEY, []);
}
// 儲存黑名單到 GM_setValue
function saveVideoBlacklist(blacklist) {
GM_setValue(VIDEO_BLACKLIST_KEY, blacklist);
}
function saveChannelBlacklist(blacklist) {
GM_setValue(CHANNEL_BLACKLIST_KEY, blacklist);
}
// 獲取和儲存按鈕位置
function getButtonPositions() {
return GM_getValue(BUTTON_POSITION_KEY, {
videoBtn: { x: 200, y: 70 },
channelBtn: { x: 10, y: 70 }
});
}
function saveButtonPositions(positions) {
GM_setValue(BUTTON_POSITION_KEY, positions);
}
// 獲取和儲存按鈕狀態
function getButtonStates() {
return GM_getValue(BUTTON_STATE_KEY, {
videoBtn: { minimized: false, hidden: false },
channelBtn: { minimized: false, hidden: false }
});
}
function saveButtonStates(states) {
GM_setValue(BUTTON_STATE_KEY, states);
}
// 安全創建元素函數(完全避開 innerHTML)
function createSafeElement(tag, attributes = {}, styles = {}, textContent = '') {
const element = document.createElement(tag);
// 設置屬性
for (const [key, value] of Object.entries(attributes)) {
element.setAttribute(key, value);
}
// 設置樣式
for (const [key, value] of Object.entries(styles)) {
element.style[key] = value;
}
// 設置文字內容
if (textContent) {
element.textContent = textContent;
}
return element;
}
// 提取影片標題 (增強版,支援合輯內容)
function extractVideoTitle(element) {
// 嘗試多種選擇器來找到標題元素
const titleSelectors = [
'h3 a',
'#video-title',
'.yt-lockup-metadata-view-model__title',
'a[aria-label*="秒"]',
'a[aria-label*="分"]',
'a[aria-label*="時"]',
'yt-formatted-string.ytd-video-renderer',
'yt-formatted-string.ytd-rich-item-renderer'
];
let titleElement = null;
for (const selector of titleSelectors) {
titleElement = element.querySelector(selector);
if (titleElement) break;
}
if (!titleElement) return null;
// 從不同屬性獲取標題文本
let title = titleElement.getAttribute('title') ||
titleElement.getAttribute('aria-label') ||
titleElement.textContent.trim();
// 處理包含時間信息的標題 (例如: "10:30 影片標題")
if (title && (title.includes('秒') || title.includes('分') || title.includes('時'))) {
// 分割字符串並取最後一部分作為標題
const parts = title.split(' ');
if (parts.length > 1) {
title = parts.slice(1).join(' ');
}
}
return title;
}
// 提取頻道名稱
function extractChannelName(element) {
const channelSelectors = [
'ytd-channel-name a',
'#channel-name a',
'ytd-video-meta-block a',
'a.yt-formatted-string[href*="/channel/"]',
'a.yt-formatted-string[href*="/user/"]',
'a.yt-core-attributed-string__link',
'.yt-content-metadata-view-model__metadata-text' // 合輯頻道名稱選擇器
];
let channelElement = null;
for (const selector of channelSelectors) {
channelElement = element.querySelector(selector);
if (channelElement) break;
}
if (!channelElement) return null;
let channelName = channelElement.getAttribute('title') ||
channelElement.textContent.trim();
if (channelName) {
channelName = channelName.replace(/\s*•\s*$/, '').trim();
}
return channelName;
}
// 隱藏黑名單中的影片
function hideBlacklistedVideos() {
const blacklist = getVideoBlacklist();
if (blacklist.length === 0) return;
// 選擇所有可能的影片元素
const videoSelectors = [
'ytd-rich-item-renderer',
'ytd-video-renderer',
'ytd-grid-video-renderer',
'ytd-playlist-renderer' // 新增合輯選擇器
];
videoSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(card => {
const title = extractVideoTitle(card);
if (title && blacklist.some(blacklistedTitle =>
title.includes(blacklistedTitle) || blacklistedTitle.includes(title))) {
card.style.display = 'none';
}
});
});
}
// 隱藏黑名單頻道的影片
function hideBlacklistedChannels() {
const blacklist = getChannelBlacklist();
if (blacklist.length === 0) return;
// 選擇所有可能的影片元素
const videoSelectors = [
'ytd-rich-item-renderer',
'ytd-video-renderer',
'ytd-grid-video-renderer',
'ytd-playlist-renderer' // 新增合輯選擇器
];
videoSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(card => {
const channelName = extractChannelName(card);
if (channelName && blacklist.includes(channelName)) {
card.style.display = 'none';
}
});
});
}
// 為每個影片添加移除按鈕
function addRemoveButtons() {
const videoBlacklist = getVideoBlacklist();
const channelBlacklist = getChannelBlacklist();
// 選擇所有可能的影片元素
const videoSelectors = [
'ytd-rich-item-renderer',
'ytd-video-renderer',
'ytd-grid-video-renderer',
'ytd-playlist-renderer' // 新增合輯選擇器
];
videoSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(card => {
// 檢查是否已經添加了按鈕
if (card.querySelector('.video-remove-btn') && card.querySelector('.channel-remove-btn')) return;
const title = extractVideoTitle(card);
const channelName = extractChannelName(card);
if (!title || !channelName) return;
// 確保卡片有相對定位,以便按鈕可以絕對定位
if (getComputedStyle(card).position === 'static') {
card.style.position = 'relative';
}
// 添加影片移除按鈕
if (!card.querySelector('.video-remove-btn') && !videoBlacklist.some(t => title.includes(t) || t.includes(title))) {
const removeBtn = createVideoRemoveButton(title);
card.appendChild(removeBtn);
}
// 添加頻道移除按鈕
if (!card.querySelector('.channel-remove-btn') && !channelBlacklist.includes(channelName)) {
const channelBtn = createChannelRemoveButton(channelName);
card.appendChild(channelBtn);
}
});
});
}
// 創建影片移除按鈕
function createVideoRemoveButton(title) {
const removeBtn = document.createElement('button');
removeBtn.className = 'video-remove-btn';
removeBtn.title = '移除此影片';
removeBtn.dataset.title = title;
removeBtn.style.cssText = `
position: absolute;
top: 8px;
right: 8px;
z-index: 10;
width: 30px;
height: 30px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: none;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
`;
removeBtn.textContent = '✕';
removeBtn.addEventListener('mouseover', function() {
this.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
this.style.transform = 'scale(1.1)';
});
removeBtn.addEventListener('mouseout', function() {
this.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
this.style.transform = 'scale(1)';
});
removeBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
const videoTitle = this.dataset.title;
let blacklist = getVideoBlacklist();
if (!blacklist.includes(videoTitle)) {
blacklist.push(videoTitle);
saveVideoBlacklist(blacklist);
this.parentElement.style.display = 'none';
}
});
return removeBtn;
}
// 創建頻道移除按鈕
function createChannelRemoveButton(channelName) {
const removeBtn = document.createElement('button');
removeBtn.className = 'channel-remove-btn';
removeBtn.title = '封鎖此頻道';
removeBtn.dataset.channel = channelName;
removeBtn.style.cssText = `
position: absolute;
top: 8px;
right: 43px;
z-index: 10;
width: 30px;
height: 30px;
border-radius: 50%;
background-color: rgba(255, 0, 0, 0.7);
color: white;
border: none;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
`;
removeBtn.textContent = '✕';
removeBtn.addEventListener('mouseover', function() {
this.style.backgroundColor = 'rgba(255, 0, 0, 0.9)';
this.style.transform = 'scale(1.1)';
});
removeBtn.addEventListener('mouseout', function() {
this.style.backgroundColor = 'rgba(255, 0, 0, 0.7)';
this.style.transform = 'scale(1)';
});
removeBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
const channel = this.dataset.channel;
let blacklist = getChannelBlacklist();
if (!blacklist.includes(channel)) {
blacklist.push(channel);
saveChannelBlacklist(blacklist);
// 隱藏這個頻道的所有影片
hideBlacklistedChannels();
}
});
return removeBtn;
}
// 顯示影片黑名單管理面板(完全避開 innerHTML)
function showVideoBlacklistPanel() {
const existingPanel = document.querySelector('.video-blacklist-panel');
if (existingPanel) {
existingPanel.remove();
return;
}
const blacklist = getVideoBlacklist();
const panel = document.createElement('div');
panel.className = 'video-blacklist-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #1c1c1c;
border: 1px solid #333;
padding: 20px;
z-index: 10000;
max-height: 80vh;
overflow-y: auto;
width: 500px;
box-shadow: 0 4px 8px rgba(0,0,0,0.6);
border-radius: 8px;
color: #fff;
font-family: inherit;
`;
if (blacklist.length === 0) {
// 創建標題
const title = createSafeElement('h2', {}, {
marginTop: '0',
color: '#ffcc00',
borderBottom: '1px solid #444',
paddingBottom: '10px'
}, '影片黑名單管理');
panel.appendChild(title);
// 創建空狀態提示
const emptyMsg = createSafeElement('p', {}, {
textAlign: 'center',
padding: '20px'
}, '黑名單為空');
panel.appendChild(emptyMsg);
// 創建按鈕容器
const buttonContainer = createSafeElement('div', {}, {
marginTop: '20px',
textAlign: 'right'
});
// 創建關閉按鈕
const closeBtn = createSafeElement('button', {
id: 'close-btn'
}, {
padding: '8px 16px',
border: 'none',
borderRadius: '5px',
backgroundColor: '#6c757d',
color: 'white',
cursor: 'pointer'
}, '關閉');
closeBtn.addEventListener('click', function() {
panel.remove();
});
buttonContainer.appendChild(closeBtn);
panel.appendChild(buttonContainer);
} else {
// 創建標題
const title = createSafeElement('h2', {}, {
marginTop: '0',
color: '#ffcc00',
borderBottom: '1px solid #444',
paddingBottom: '10px'
}, '影片黑名單管理');
panel.appendChild(title);
// 創建統計信息
const statsText = createSafeElement('p', {}, {
color: '#aaa',
fontSize: '14px'
}, `已屏蔽 ${blacklist.length} 個影片`);
panel.appendChild(statsText);
// 創建搜尋框容器
const searchContainer = createSafeElement('div', {}, {
margin: '15px 0'
});
// 創建搜尋框
const searchInput = createSafeElement('input', {
type: 'text',
id: 'video-search-input',
placeholder: '搜尋影片標題...'
}, {
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #444',
backgroundColor: '#2a2a2a',
color: 'white',
boxSizing: 'border-box'
});
searchContainer.appendChild(searchInput);
panel.appendChild(searchContainer);
// 創建列表容器
const blacklistItems = createSafeElement('ul', {
id: 'video-blacklist-items'
}, {
listStyle: 'none',
padding: '0',
maxHeight: '300px',
overflowY: 'auto'
});
panel.appendChild(blacklistItems);
// 創建按鈕容器
const buttonContainer = createSafeElement('div', {}, {
marginTop: '20px',
textAlign: 'right'
});
// 創建清空按鈕
const clearBtn = createSafeElement('button', {
id: 'clear-all-btn'
}, {
padding: '8px 16px',
border: 'none',
borderRadius: '5px',
backgroundColor: '#dc3545',
color: 'white',
cursor: 'pointer',
marginRight: '10px'
}, '清空黑名單');
// 創建關閉按鈕
const closeBtn = createSafeElement('button', {
id: 'close-btn'
}, {
padding: '8px 16px',
border: 'none',
borderRadius: '5px',
backgroundColor: '#6c757d',
color: 'white',
cursor: 'pointer'
}, '關閉');
buttonContainer.appendChild(clearBtn);
buttonContainer.appendChild(closeBtn);
panel.appendChild(buttonContainer);
// 渲染黑名單項目
function renderBlacklistItems(filterText = '') {
try {
// 清空列表
while (blacklistItems.firstChild) {
blacklistItems.removeChild(blacklistItems.firstChild);
}
const filteredList = filterText ?
blacklist.filter(title => title.toLowerCase().includes(filterText.toLowerCase())) :
blacklist;
if (filteredList.length === 0) {
const emptyMsg = createSafeElement('p', {}, {
textAlign: 'center',
padding: '20px',
color: filterText ? '#aaa' : 'inherit'
}, filterText ? '沒有找到匹配的影片' : '黑名單為空');
blacklistItems.appendChild(emptyMsg);
// 更新統計信息
statsText.textContent = filterText ?
`找到 0 個匹配的影片 (共 ${blacklist.length} 個)` :
`已屏蔽 ${blacklist.length} 個影片`;
return;
}
filteredList.forEach(title => {
const li = createSafeElement('li', {}, {
marginBottom: '10px',
padding: '8px',
backgroundColor: '#2a2a2a',
borderRadius: '4px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
});
const titleSpan = createSafeElement('span', {}, {
flexGrow: '1',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}, title);
li.appendChild(titleSpan);
const restoreBtn = createSafeElement('button', {
className: 'restore-btn',
'data-title': title
}, {
backgroundColor: '#28a745',
color: 'white',
border: 'none',
borderRadius: '3px',
padding: '5px 10px',
cursor: 'pointer',
marginLeft: '10px'
}, '恢復');
li.appendChild(restoreBtn);
blacklistItems.appendChild(li);
});
// 更新統計信息
statsText.textContent = filterText ?
`找到 ${filteredList.length} 個匹配的影片 (共 ${blacklist.length} 個)` :
`已屏蔽 ${blacklist.length} 個影片`;
} catch (error) {
console.error('渲染黑名單時發生錯誤:', error);
// 清空列表
while (blacklistItems.firstChild) {
blacklistItems.removeChild(blacklistItems.firstChild);
}
const errorMsg = createSafeElement('p', {}, {
textAlign: 'center',
padding: '20px',
color: '#ff6b6b'
}, '渲染錯誤,請刷新頁面重試');
blacklistItems.appendChild(errorMsg);
}
}
// 初始渲染
renderBlacklistItems();
// 搜尋功能
searchInput.addEventListener('input', function() {
renderBlacklistItems(this.value);
});
// 清空按鈕事件
clearBtn.addEventListener('click', function() {
if (confirm('確定要清空影片黑名單嗎?所有隱藏的影片將重新顯示。')) {
saveVideoBlacklist([]);
panel.remove();
location.reload();
}
});
// 使用事件委託處理恢復按鈕
panel.addEventListener('click', function(e) {
if (e.target.classList.contains('restore-btn')) {
const title = e.target.dataset.title;
let blacklist = getVideoBlacklist();
if (blacklist.includes(title)) {
blacklist = blacklist.filter(t => t !== title);
saveVideoBlacklist(blacklist);
// 重新渲染列表
renderBlacklistItems(searchInput.value);
if (blacklist.length === 0) {
clearBtn.style.display = 'none';
}
}
}
});
// 關閉按鈕事件
closeBtn.addEventListener('click', function() {
panel.remove();
});
}
document.body.appendChild(panel);
}
// 顯示頻道黑名單管理面板(完全避開 innerHTML)
function showChannelBlacklistPanel() {
const existingPanel = document.querySelector('.channel-blacklist-panel');
if (existingPanel) {
existingPanel.remove();
return;
}
const blacklist = getChannelBlacklist();
const panel = document.createElement('div');
panel.className = 'channel-blacklist-panel';
panel.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #1c1c1c;
border: 1px solid #333;
padding: 20px;
z-index: 10000;
max-height: 80vh;
overflow-y: auto;
width: 500px;
box-shadow: 0 4px 8px rgba(0,0,0,0.6);
border-radius: 8px;
color: #fff;
font-family: inherit;
`;
if (blacklist.length === 0) {
// 創建標題
const title = createSafeElement('h2', {}, {
marginTop: '0',
color: '#ffcc00',
borderBottom: '1px solid #444',
paddingBottom: '10px'
}, '頻道黑名單管理');
panel.appendChild(title);
// 創建空狀態提示
const emptyMsg = createSafeElement('p', {}, {
textAlign: 'center',
padding: '20px'
}, '黑名單為空');
panel.appendChild(emptyMsg);
// 創建按鈕容器
const buttonContainer = createSafeElement('div', {}, {
marginTop: '20px',
textAlign: 'right'
});
// 創建關閉按鈕
const closeBtn = createSafeElement('button', {
id: 'close-btn'
}, {
padding: '8px 16px',
border: 'none',
borderRadius: '5px',
backgroundColor: '#6c757d',
color: 'white',
cursor: 'pointer'
}, '關閉');
closeBtn.addEventListener('click', function() {
panel.remove();
});
buttonContainer.appendChild(closeBtn);
panel.appendChild(buttonContainer);
} else {
// 創建標題
const title = createSafeElement('h2', {}, {
marginTop: '0',
color: '#ffcc00',
borderBottom: '1px solid #444',
paddingBottom: '10px'
}, '頻道黑名單管理');
panel.appendChild(title);
// 創建統計信息
const statsText = createSafeElement('p', {}, {
color: '#aaa',
fontSize: '14px'
}, `已屏蔽 ${blacklist.length} 個頻道`);
panel.appendChild(statsText);
// 創建搜尋框容器
const searchContainer = createSafeElement('div', {}, {
margin: '15px 0'
});
// 創建搜尋框
const searchInput = createSafeElement('input', {
type: 'text',
id: 'channel-search-input',
placeholder: '搜尋頻道名稱...'
}, {
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #444',
backgroundColor: '#2a2a2a',
color: 'white',
boxSizing: 'border-box'
});
searchContainer.appendChild(searchInput);
panel.appendChild(searchContainer);
// 創建列表容器
const blacklistItems = createSafeElement('ul', {
id: 'channel-blacklist-items'
}, {
listStyle: 'none',
padding: '0',
maxHeight: '300px',
overflowY: 'auto'
});
panel.appendChild(blacklistItems);
// 創建按鈕容器
const buttonContainer = createSafeElement('div', {}, {
marginTop: '20px',
textAlign: 'right'
});
// 創建清空按鈕
const clearBtn = createSafeElement('button', {
id: 'clear-all-btn'
}, {
padding: '8px 16px',
border: 'none',
borderRadius: '5px',
backgroundColor: '#dc3545',
color: 'white',
cursor: 'pointer',
marginRight: '10px'
}, '清空黑名單');
// 創建關閉按鈕
const closeBtn = createSafeElement('button', {
id: 'close-btn'
}, {
padding: '8px 16px',
border: 'none',
borderRadius: '5px',
backgroundColor: '#6c757d',
color: 'white',
cursor: 'pointer'
}, '關閉');
buttonContainer.appendChild(clearBtn);
buttonContainer.appendChild(closeBtn);
panel.appendChild(buttonContainer);
// 渲染黑名單項目
function renderBlacklistItems(filterText = '') {
try {
// 清空列表
while (blacklistItems.firstChild) {
blacklistItems.removeChild(blacklistItems.firstChild);
}
const filteredList = filterText ?
blacklist.filter(channel => channel.toLowerCase().includes(filterText.toLowerCase())) :
blacklist;
if (filteredList.length === 0) {
const emptyMsg = createSafeElement('p', {}, {
textAlign: 'center',
padding: '20px',
color: filterText ? '#aaa' : 'inherit'
}, filterText ? '沒有找到匹配的頻道' : '黑名單為空');
blacklistItems.appendChild(emptyMsg);
// 更新統計信息
statsText.textContent = filterText ?
`找到 0 個匹配的頻道 (共 ${blacklist.length} 個)` :
`已屏蔽 ${blacklist.length} 個頻道`;
return;
}
filteredList.forEach(channel => {
const li = createSafeElement('li', {}, {
marginBottom: '10px',
padding: '8px',
backgroundColor: '#2a2a2a',
borderRadius: '4px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
});
const channelSpan = createSafeElement('span', {}, {
flexGrow: '1',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}, channel);
li.appendChild(channelSpan);
const restoreBtn = createSafeElement('button', {
className: 'restore-btn',
'data-channel': channel
}, {
backgroundColor: '#28a745',
color: 'white',
border: 'none',
borderRadius: '3px',
padding: '5px 10px',
cursor: 'pointer',
marginLeft: '10px'
}, '恢復');
li.appendChild(restoreBtn);
blacklistItems.appendChild(li);
});
// 更新統計信息
statsText.textContent = filterText ?
`找到 ${filteredList.length} 個匹配的頻道 (共 ${blacklist.length} 個)` :
`已屏蔽 ${blacklist.length} 個頻道`;
} catch (error) {
console.error('渲染黑名單時發生錯誤:', error);
// 清空列表
while (blacklistItems.firstChild) {
blacklistItems.removeChild(blacklistItems.firstChild);
}
const errorMsg = createSafeElement('p', {}, {
textAlign: 'center',
padding: '20px',
color: '#ff6b6b'
}, '渲染錯誤,請刷新頁面重試');
blacklistItems.appendChild(errorMsg);
}
}
// 初始渲染
renderBlacklistItems();
// 搜尋功能
searchInput.addEventListener('input', function() {
renderBlacklistItems(this.value);
});
// 清空按鈕事件
clearBtn.addEventListener('click', function() {
if (confirm('確定要清空頻道黑名單嗎?所有隱藏的頻道將重新顯示。')) {
saveChannelBlacklist([]);
panel.remove();
location.reload();
}
});
// 使用事件委託處理恢復按鈕
panel.addEventListener('click', function(e) {
if (e.target.classList.contains('restore-btn')) {
const channel = e.target.dataset.channel;
let blacklist = getChannelBlacklist();
if (blacklist.includes(channel)) {
blacklist = blacklist.filter(c => c !== channel);
saveChannelBlacklist(blacklist);
// 重新渲染列表
renderBlacklistItems(searchInput.value);
if (blacklist.length === 0) {
clearBtn.style.display = 'none';
}
}
}
});
// 關閉按鈕事件
closeBtn.addEventListener('click', function() {
panel.remove();
});
}
document.body.appendChild(panel);
}
// 創建可拖動的管理按鈕
function createManageButton(type, text, onClick, defaultX, defaultY) {
const positions = getButtonPositions();
const states = getButtonStates();
const btn = document.createElement('button');
btn.className = `${type}-manage-btn`;
btn.textContent = text;
// 設置初始位置
const pos = positions[type] || { x: defaultX, y: defaultY };
btn.style.left = `${pos.x}px`;
btn.style.top = `${pos.y}px`;
// 設置初始狀態
const state = states[type] || { minimized: false, hidden: false };
if (state.minimized) {
minimizeButton(btn, type);
}
if (state.hidden) btn.style.display = 'none';
btn.style.cssText += `
position: fixed;
z-index: 9999;
padding: 10px 15px;
border: none;
border-radius: 5px;
background-color: #ffcc00;
color: #000;
cursor: pointer;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
`;
// 添加點擊事件
btn.addEventListener('click', onClick);
// 添加拖動功能
makeDraggable(btn, type);
// 添加右鍵菜單
btn.addEventListener('contextmenu', (e) => {
e.preventDefault();
showButtonContextMenu(e, btn, type);
});
document.body.appendChild(btn);
return btn;
}
// 使元素可拖動
function makeDraggable(element, type) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
element.onmousedown = dragMouseDown;
function dragMouseDown(e) {
if (e.button !== 0) return; // 只允許左鍵拖動
e.preventDefault();
// 獲取鼠標位置
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e.preventDefault();
// 計算新位置
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// 設置元素的新位置
element.style.top = (element.offsetTop - pos2) + "px";
element.style.left = (element.offsetLeft - pos1) + "px";
}
function closeDragElement() {
// 停止移動
document.onmouseup = null;
document.onmousemove = null;
// 保存新位置
const positions = getButtonPositions();
positions[type] = {
x: element.offsetLeft,
y: element.offsetTop
};
saveButtonPositions(positions);
}
}
// 顯示按鈕的右鍵菜單
function showButtonContextMenu(e, button, type) {
// 移除現有的上下文菜單
const existingMenu = document.querySelector('.button-context-menu');
if (existingMenu) existingMenu.remove();
const menu = document.createElement('div');
menu.className = 'button-context-menu';
menu.style.cssText = `
position: fixed;
left: ${e.pageX}px;
top: ${e.pageY}px;
background-color: #2c2c2c;
border: 1px solid #444;
border-radius: 4px;
padding: 5px 0;
z-index: 10000;
box-shadow: 0 2px 10px rgba(0,0,0,0.5);
`;
const minimizeOption = document.createElement('div');
minimizeOption.textContent = '縮小/還原';
minimizeOption.style.cssText = `
padding: 8px 15px;
cursor: pointer;
color: #fff;
`;
minimizeOption.addEventListener('click', () => {
toggleMinimizeButton(button, type);
menu.remove();
});
const hideOption = document.createElement('div');
hideOption.textContent = button.style.display === 'none' ? '顯示' : '隱藏';
hideOption.style.cssText = `
padding: 8px 15px;
cursor: pointer;
color: #fff;
`;
hideOption.addEventListener('click', () => {
toggleHideButton(button, type);
menu.remove();
});
menu.appendChild(minimizeOption);
menu.appendChild(hideOption);
document.body.appendChild(menu);
// 點擊其他地方關閉菜單
const closeMenu = (e) => {
if (!menu.contains(e.target)) {
menu.remove();
document.removeEventListener('click', closeMenu);
}
};
setTimeout(() => {
document.addEventListener('click', closeMenu);
}, 0);
}
// 縮小按鈕
function minimizeButton(button, type) {
button.textContent = type === 'videoBtn' ? '影' : '頻';
button.style.width = '40px';
button.style.height = '40px';
button.style.padding = '0';
button.style.borderRadius = '50%';
}
// 還原按鈕
function restoreButton(button, type) {
button.textContent = type === 'videoBtn' ? '管理影片黑名單' : '管理頻道黑名單';
button.style.width = '';
button.style.height = '';
button.style.padding = '10px 15px';
button.style.borderRadius = '5px';
}
// 切換按鈕的縮小狀態
function toggleMinimizeButton(button, type) {
const states = getButtonStates();
const state = states[type] || { minimized: false, hidden: false };
if (state.minimized) {
// 還原按鈕
restoreButton(button, type);
state.minimized = false;
} else {
// 縮小按鈕
minimizeButton(button, type);
state.minimized = true;
}
states[type] = state;
saveButtonStates(states);
}
// 切換按鈕的隱藏狀態
function toggleHideButton(button, type) {
const states = getButtonStates();
const state = states[type] || { minimized: false, hidden: false };
if (button.style.display === 'none') {
button.style.display = 'block';
state.hidden = false;
} else {
button.style.display = 'none';
state.hidden = true;
}
states[type] = state;
saveButtonStates(states);
// 檢查是否需要顯示全局觸發按鈕
checkGlobalTrigger();
}
// 檢查是否需要顯示全局觸發按鈕
function checkGlobalTrigger() {
const states = getButtonStates();
const videoHidden = states.videoBtn?.hidden || false;
const channelHidden = states.channelBtn?.hidden || false;
if (videoHidden && channelHidden) {
addGlobalTrigger();
} else {
removeGlobalTrigger();
}
}
// 添加全局觸發按鈕
function addGlobalTrigger() {
if (document.querySelector('.global-trigger-btn')) return;
const trigger = document.createElement('button');
trigger.className = 'global-trigger-btn';
trigger.textContent = '黑';
trigger.title = '顯示黑名單管理按鈕';
trigger.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
z-index: 9999;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #ffcc00;
color: #000;
border: none;
cursor: pointer;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
`;
trigger.addEventListener('click', showAllButtons);
document.body.appendChild(trigger);
}
// 移除全局觸發按鈕
function removeGlobalTrigger() {
const trigger = document.querySelector('.global-trigger-btn');
if (trigger) trigger.remove();
}
// 顯示所有按鈕
function showAllButtons() {
const states = getButtonStates();
states.videoBtn.hidden = false;
states.channelBtn.hidden = false;
saveButtonStates(states);
document.querySelectorAll('.video-manage-btn, .channel-manage-btn').forEach(btn => {
btn.style.display = 'block';
});
removeGlobalTrigger();
}
// 添加管理按鈕
function addManageButtons() {
if (document.querySelector('.video-manage-btn') && document.querySelector('.channel-manage-btn')) return;
// 添加影片黑名單管理按鈕
if (!document.querySelector('.video-manage-btn')) {
createManageButton('videoBtn', '管理影片黑名單', showVideoBlacklistPanel, 200, 70);
}
// 添加頻道黑名單管理按鈕
if (!document.querySelector('.channel-manage-btn')) {
createManageButton('channelBtn', '管理頻道黑名單', showChannelBlacklistPanel, 10, 70);
}
// 檢查是否需要顯示全局觸發按鈕
checkGlobalTrigger();
}
// 監聽動態內容加載
const observer = new MutationObserver(function(mutations) {
let shouldProcess = false;
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length > 0) {
shouldProcess = true;
}
});
if (shouldProcess) {
hideBlacklistedVideos();
hideBlacklistedChannels();
addRemoveButtons();
}
});
// 開始監聽頁面變化
observer.observe(document.body, {
childList: true,
subtree: true
});
// 頁面加載時執行
window.addEventListener('load', function() {
console.log('YouTube 黑名單腳本開始執行...');
hideBlacklistedVideos();
hideBlacklistedChannels();
addRemoveButtons();
addManageButtons();
});
// 添加一些樣式
GM_addStyle(`
.video-remove-btn:hover {
background-color: rgba(0, 0, 0, 0.9) !important;
}
.channel-remove-btn:hover {
background-color: rgba(255, 0, 0, 0.9) !important;
}
.video-blacklist-panel, .channel-blacklist-panel {
font-family: 'YouTube Noto', Roboto, Arial, sans-serif;
}
.video-blacklist-panel h2, .channel-blacklist-panel h2 {
font-size: 1.5rem;
margin-bottom: 15px;
}
.video-blacklist-panel input, .channel-blacklist-panel input {
font-size: 14px;
}
.restore-btn:hover {
background-color: #218838 !important;
}
#clear-all-btn:hover {
background-color: #c82333 !important;
}
#close-btn:hover {
background-color: #5a6268 !important;
}
.button-context-menu div:hover {
background-color: #3c3c3c;
}
`);
})();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址