// ==UserScript== // @name 网页元素屏蔽器 // @namespace http://tampermonkey.net/ // @version 0.2 // @description 屏蔽任意网站上的元素,支持缩略图记录和正则/简单模式屏蔽 // @author JerryChiang // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 添加样式 const style = document.createElement('style'); style.textContent = ` .highlight { outline: 2px solid red !important; background-color: rgba(255, 0, 0, 0.1) !important; } .blocker-popup { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; z-index: 9999; box-shadow: 0 0 10px rgba(0,0,0,0.3); border-radius: 8px; font-family: Arial, sans-serif; width: 600px; max-height: 80vh; overflow-y: auto; } .blocker-popup p { margin: 0 0 10px; font-size: 16px; color: #333; } .blocker-popup button { margin: 5px; padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background-color 0.2s; } .blocker-popup button:hover { opacity: 0.9; } #regex-mode, #simple-mode { background-color: #007bff; color: white; } #preview-rule { background-color: #28a745; color: white; } #add-rule-row { background-color: #17a2b8; color: white; } #save-rules { background-color: #17a2b8; color: white; } #cancel-rule { background-color: #dc3545; color: white; } .blocker-popup input, .blocker-popup select { margin: 5px; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; } .blocker-popup select { width: 100px; } .blocker-popup input[type="text"], .blocker-popup input[type="number"] { width: 200px; } .rule-row { display: flex; align-items: center; margin: 5px 0; padding: 5px; border-bottom: 1px solid #eee; } .rule-row button { background-color: #dc3545; color: white; } .blocker-list { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; z-index: 9999; max-height: 80vh; overflow-y: auto; width: 500px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.3); } .blocker-list h3 { margin: 0 0 15px; font-size: 18px; color: #333; } .blocker-list ul { list-style: none; padding: 0; } .blocker-list li { margin: 10px 0; display: flex; align-items: center; width: 100%; padding: 5px; border-bottom: 1px solid #eee; } .blocker-list img { max-width: 400px; max-height: 100px; object-fit: contain; border: 1px solid #ddd; flex-shrink: 0; } .blocker-list button { margin-left: auto; flex-shrink: 0; padding: 5px 10px; background-color: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; } .blocker-list button:hover { background-color: #c82333; } `; document.head.appendChild(style); // 注册菜单 GM_registerMenuCommand('屏蔽设置', startBlockingMode); GM_registerMenuCommand('按规则屏蔽', showRegexBlockInput); GM_registerMenuCommand('查看屏蔽记录', showBlockList); // 进入元素选择模式 function startBlockingMode() { // 移除 alert,直接进入高亮选择 document.body.addEventListener('mouseover', highlightElement); document.body.addEventListener('click', selectElement, true); } // 高亮悬停元素 function highlightElement(event) { if (window.lastHighlighted) { window.lastHighlighted.classList.remove('highlight'); } event.target.classList.add('highlight'); window.lastHighlighted = event.target; } // 选择元素并弹出确认窗口 function selectElement(event) { event.preventDefault(); event.stopPropagation(); document.body.removeEventListener('mouseover', highlightElement); document.body.removeEventListener('click', selectElement, true); const selectedElement = event.target; window.lastHighlighted.classList.remove('highlight'); showConfirmation(selectedElement); } // 显示确认弹窗 function showConfirmation(element) { const popup = document.createElement('div'); popup.className = 'blocker-popup'; popup.innerHTML = `
是否屏蔽此元素?
`; document.body.appendChild(popup); let isPreviewHidden = false; const confirmBtn = document.getElementById('confirm'); confirmBtn.addEventListener('click', async () => { confirmBtn.disabled = true; try { await saveBlockWithThumbnail(element); element.style.display = 'none'; document.body.removeChild(popup); } catch (e) { console.error('屏蔽失败:', e); confirmBtn.disabled = false; } }, { once: true }); document.getElementById('preview').addEventListener('click', () => { if (!isPreviewHidden) { element.style.display = 'none'; isPreviewHidden = true; } else { element.style.display = ''; isPreviewHidden = false; } }); document.getElementById('cancel').addEventListener('click', () => { document.body.removeChild(popup); }); } // 保存屏蔽信息并生成缩略图 async function saveBlockWithThumbnail(element) { const domain = window.location.hostname; const selector = getSelector(element); const canvas = await html2canvas(element, { scale: 1 }); const originalWidth = canvas.width; const originalHeight = canvas.height; let scale = Math.min(400 / originalWidth, 100 / originalHeight, 1); const thumbnailCanvas = document.createElement('canvas'); thumbnailCanvas.width = originalWidth * scale; thumbnailCanvas.height = originalHeight * scale; const ctx = thumbnailCanvas.getContext('2d'); ctx.drawImage(canvas, 0, 0, thumbnailCanvas.width, thumbnailCanvas.height); const thumbnail = thumbnailCanvas.toDataURL('image/png'); let blocks = GM_getValue('blocks', {}); if (!blocks[domain]) { blocks[domain] = []; } if (!blocks[domain].some(item => item.selector === selector)) { blocks[domain].push({ selector, thumbnail }); GM_setValue('blocks', blocks); } } // 生成简单 CSS 选择器 function getSelector(element) { if (element.id) return `#${element.id}`; let path = []; while (element && element.nodeType === Node.ELEMENT_NODE) { let selector = element.tagName.toLowerCase(); if (element.className && typeof element.className === 'string') { selector += '.' + element.className.trim().replace(/\s+/g, '.'); } path.unshift(selector); element = element.parentElement; } return path.join(' > '); } // 应用屏蔽规则 function applyBlocks() { const domain = window.location.hostname; const blocks = GM_getValue('blocks', {}); if (blocks[domain]) { blocks[domain].forEach(item => { try { document.querySelectorAll(item.selector).forEach(el => { el.style.display = 'none'; }); } catch (e) { console.error(`无法应用选择器: ${item.selector}`, e); } }); } applyRegexBlocks(); } // 显示屏蔽记录窗口 function showBlockList() { const domain = window.location.hostname; const blocks = GM_getValue('blocks', {}); const blockList = blocks[domain] || []; const listWindow = document.createElement('div'); listWindow.className = 'blocker-list'; listWindow.innerHTML = `设置屏蔽规则(层级:0 表示当前元素,1 表示父元素,依此类推):
暂无规则
'; return; } tempRules.forEach((rule, index) => { const row = document.createElement('div'); row.className = 'rule-row'; row.innerHTML = ` `; const regexInput = row.querySelector('.rule-regex'); const levelInput = row.querySelector('.rule-level'); const deleteBtn = row.querySelector('.delete-rule'); // 实时更新临时规则 regexInput.addEventListener('input', () => { tempRules[index].regex = regexInput.value; }); levelInput.addEventListener('input', () => { tempRules[index].level = parseInt(levelInput.value, 10); }); // 删除规则 deleteBtn.addEventListener('click', () => { tempRules.splice(index, 1); renderRules(); }); rulesRows.appendChild(row); }); } // 新增规则行 document.getElementById('add-rule-row').addEventListener('click', () => { let regex, level; if (isSimpleMode) { const logic = inputContainer.querySelector('.logic-select').value; const text = inputContainer.querySelector('.simple-input').value.trim(); level = parseInt(inputContainer.querySelector('.level-input').value, 10); if (!text || isNaN(level) || level < 0) { alert('请输入有效的文本和层级'); return; } regex = convertSimpleToRegex(logic, text); } else { regex = inputContainer.querySelector('.regex-input').value.trim(); level = parseInt(inputContainer.querySelector('.level-input').value, 10); if (!regex || isNaN(level) || level < 0) { alert('请输入有效的正则规则和层级'); return; } } tempRules.push({ regex, level }); renderRules(); // 清空输入框 if (isSimpleMode) { inputContainer.querySelector('.simple-input').value = ''; inputContainer.querySelector('.level-input').value = '0'; } else { inputContainer.querySelector('.regex-input').value = ''; inputContainer.querySelector('.level-input').value = '0'; } }); // 保存规则 document.getElementById('save-rules').addEventListener('click', () => { regexBlocks[domain] = tempRules; GM_setValue('regexBlocks', regexBlocks); applyRegexBlocks(); document.body.removeChild(popup); }); // 取消 document.getElementById('cancel-rule').addEventListener('click', () => { document.body.removeChild(popup); }); // 将简单模式转换为正则表达式 function convertSimpleToRegex(logic, text) { const escapedText = text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); switch (logic) { case 'contains': return `.*${escapedText}.*`; case 'not-contains': return `^(?!.*${escapedText}).*$`; case 'equals': return `^${escapedText}$`; default: return escapedText; } } // 为预览按钮绑定事件 function attachPreviewListeners() { const previewBtn = inputContainer.querySelector('.preview-rule'); let previewActive = false; let affectedElements = []; previewBtn.addEventListener('click', () => { if (!previewActive) { let regex, level; if (isSimpleMode) { const logic = inputContainer.querySelector('.logic-select').value; const text = inputContainer.querySelector('.simple-input').value.trim(); level = parseInt(inputContainer.querySelector('.level-input').value, 10); if (!text || isNaN(level) || level < 0) { alert('请输入有效的文本和层级'); return; } regex = convertSimpleToRegex(logic, text); } else { regex = inputContainer.querySelector('.regex-input').value.trim(); level = parseInt(inputContainer.querySelector('.level-input').value, 10); if (!regex || isNaN(level) || level < 0) { alert('请输入有效的正则规则和层级'); return; } } try { const ruleRegex = new RegExp(regex); const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); let node; affectedElements = []; while (node = walker.nextNode()) { if (ruleRegex.test(node.textContent)) { let element = node.parentElement; for (let i = 0; i < level; i++) { if (element.parentElement) { element = element.parentElement; } else { break; } } element.style.display = 'none'; affectedElements.push(element); } } previewActive = true; previewBtn.textContent = '取消预览'; } catch (e) { alert('正则表达式无效,请检查输入'); } } else { affectedElements.forEach(el => el.style.display = ''); affectedElements = []; previewActive = false; previewBtn.textContent = '预览'; } }); } // 初始渲染规则 renderRules(); } // 应用规则屏蔽 function applyRegexBlocks() { const domain = window.location.hostname; const regexBlocks = GM_getValue('regexBlocks', {}); const rules = regexBlocks[domain] || []; rules.forEach(rule => { try { const regex = new RegExp(rule.regex); const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); let node; while (node = walker.nextNode()) { if (regex.test(node.textContent)) { let element = node.parentElement; for (let i = 0; i < rule.level; i++) { if (element.parentElement) { element = element.parentElement; } else { break; } } element.style.display = 'none'; } } } catch (e) { console.error(`无法应用规则: ${rule.regex}`, e); } }); } // 页面加载时立即应用屏蔽规则 applyBlocks(); const observer = new MutationObserver(() => applyBlocks()); observer.observe(document.body, { childList: true, subtree: true }); })();