// ==UserScript== // @name Via风格自定义规则管理器 // @namespace http://tampermonkey.net/ // @version 3.9.5 // @description 类似Via浏览器的自定义规则功能,支持域名分组管理 // @author Custom Rules Manager // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant unsafeWindow // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; // ========== 规则存储和初始化 ========== const STORAGE_KEY = 'custom_rules_v5'; // 初始为空 const DEFAULT_RULES = []; // 获取所有规则 function getAllRules() { const saved = GM_getValue(STORAGE_KEY, JSON.stringify(DEFAULT_RULES)); try { return JSON.parse(saved); } catch (e) { return DEFAULT_RULES; } } // 保存规则 function saveRules(rules) { GM_setValue(STORAGE_KEY, JSON.stringify(rules)); applyRules(); // 重新应用规则 } // ========== 规则解析和应用 ========== function parseRule(ruleString) { const rule = ruleString.trim(); if (!rule || rule.startsWith('!') || rule.startsWith('#')) { return null; } const match = rule.match(/^([^#]+)##(.+)$/); if (match) { return { domain: match[1].trim(), selector: match[2].trim(), raw: rule }; } return null; } // 检查域名匹配 function domainMatches(ruleDomain, currentDomain) { if (ruleDomain === '*') return true; if (ruleDomain.startsWith('*.')) { const baseDomain = ruleDomain.substring(2); return currentDomain === baseDomain || currentDomain.endsWith('.' + baseDomain); } return currentDomain === ruleDomain; } // 应用规则 function applyRules() { const currentDomain = window.location.hostname; const rules = getAllRules(); let css = ''; rules.forEach(ruleStr => { const rule = parseRule(ruleStr); if (!rule) return; if (domainMatches(rule.domain, currentDomain)) { css += `${rule.selector} { display: none !important; }\n`; } }); if (css) { const oldStyle = document.getElementById('custom-rules-style'); if (oldStyle) oldStyle.remove(); const style = document.createElement('style'); style.id = 'custom-rules-style'; style.textContent = css; document.head.appendChild(style); } } // ========== UI 管理 ========== let uiVisible = false; let currentDomain = window.location.hostname; // 创建UI function createRuleUI() { // 如果UI已存在,先移除 const oldUI = document.getElementById('custom-rules-ui'); if (oldUI) oldUI.remove(); const oldOverlay = document.getElementById('custom-rules-overlay'); if (oldOverlay) oldOverlay.remove(); // 创建宿主元素 const host = document.createElement('div'); host.id = 'custom-rules-host'; // 创建Shadow DOM - 使用open模式 const shadowRoot = host.attachShadow({ mode: 'open' }); // 添加样式 const style = document.createElement('style'); style.textContent = ` :host { all: initial; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } * { box-sizing: border-box; margin: 0; padding: 0; } .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 2147483647; opacity: 0; transition: opacity 0.3s; } .ui-container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.9); width: 90%; max-width: 600px; max-height: 80vh; background: white; border-radius: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); z-index: 2147483647; opacity: 0; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; display: flex; flex-direction: column; } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 16px 20px; position: relative; flex-shrink: 0; } .title { margin: 0; font-size: 16px; font-weight: 600; } .close-btn { position: absolute; top: 12px; right: 12px; background: rgba(255,255,255,0.2); border: none; color: white; width: 28px; height: 28px; border-radius: 50%; cursor: pointer; font-size: 18px; line-height: 1; display: flex; align-items: center; justify-content: center; } .stats { margin-top: 8px; font-size: 12px; opacity: 0.9; line-height: 1.4; } .tabs { display: flex; background: #f8f9fa; border-bottom: 1px solid #e0e0e0; flex-shrink: 0; } .tab-btn { flex: 1; padding: 12px; border: none; background: none; cursor: pointer; font-size: 13px; font-weight: 500; color: #666; transition: all 0.2s; } .tab-btn.active { color: #667eea; border-bottom: 2px solid #667eea; } .main-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; min-height: 0; } .content-area { flex: 1; overflow-y: auto; padding: 16px; position: relative; } .footer { padding: 12px 20px; border-top: 1px solid #f0f0f0; background: #fafafa; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .help-btn { background: none; border: none; color: #666; font-size: 12px; cursor: pointer; padding: 6px 12px; border-radius: 4px; transition: background-color 0.2s; } .help-btn:hover { background-color: #f0f0f0; } .status-message { font-size: 12px; color: #666; } .domain-group { margin-bottom: 12px; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; background: white; transition: transform 0.2s, box-shadow 0.2s; } .domain-group.current-domain { border-color: #667eea; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2); } .domain-group.current-domain .domain-header { background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%); } .domain-group.current-domain .domain-header .domain-name { color: #667eea; font-weight: 600; } .domain-header { background: #f5f5f5; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; position: relative; z-index: 1; } .domain-info { display: flex; align-items: center; gap: 8px; flex: 1; } .status-indicator { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex-shrink: 0; } .status-active { background-color: #4CAF50; box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2); } .status-inactive { background-color: #ccc; } .domain-content { flex: 1; display: flex; flex-direction: column; } .domain-name-row { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; } .domain-name { font-weight: 500; color: #333; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .current-domain-tag { background: #667eea; color: white; font-size: 10px; padding: 1px 6px; border-radius: 10px; font-weight: bold; margin-left: 4px; flex-shrink: 0; } .rule-count-row { display: flex; align-items: center; } .rule-count { font-size: 12px; color: #666; font-weight: normal; padding-left: 16px; } .delete-domain { background: #ff4444; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500; flex-shrink: 0; margin-left: 12px; height: 28px; } .rules-container { background: white; max-height: 0; overflow: hidden; transition: max-height 0.3s ease; position: relative; z-index: 0; } .rules-container.expanded { max-height: none; } .rule-item { padding: 12px 16px; border-bottom: 1px solid #f5f5f5; display: flex; justify-content: space-between; align-items: center; background: #fafafa; } .rule-item:nth-child(odd) { background: #fff; } .rule-info { flex: 1; } .rule-domain { font-size: 12px; color: #666; margin-bottom: 4px; } .rule-selector { font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; color: #333; word-break: break-all; background: rgba(0, 0, 0, 0.02); padding: 4px 8px; border-radius: 4px; border-left: 2px solid #667eea; } .delete-rule { background: #ff9800; color: white; border: none; padding: 4px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; font-weight: 500; margin-left: 12px; flex-shrink: 0; } .input-group { margin-bottom: 16px; } .input-label { display: block; margin-bottom: 6px; color: #333; font-weight: 500; font-size: 13px; } .input-field { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; box-sizing: border-box; } .input-hint { font-size: 11px; color: #888; margin-top: 4px; } .preview-box { margin-bottom: 20px; padding: 10px; background: #f8f9fa; border-radius: 6px; } .preview-title { font-size: 12px; color: #666; margin-bottom: 6px; } .preview-content { font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; color: #999; min-height: 20px; } .action-buttons { display: flex; gap: 10px; } .action-btn { flex: 1; padding: 12px; color: white; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; } .batch-section { margin-top: 20px; } .batch-textarea { width: 100%; height: 200px; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; box-sizing: border-box; margin-bottom: 10px; resize: vertical; } .empty-state { text-align: center; padding: 30px 16px; } .empty-icon { font-size: 36px; color: #ddd; margin-bottom: 12px; } .empty-text { color: #999; margin-bottom: 6px; font-size: 13px; } .empty-hint { color: #aaa; font-size: 12px; } .chevron { display: inline-block; transition: transform 0.3s; font-size: 10px; color: #666; flex-shrink: 0; } .chevron.down { transform: rotate(90deg); } /* 滚动条样式 */ .content-area::-webkit-scrollbar { width: 8px; } .content-area::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } .content-area::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; } .content-area::-webkit-scrollbar-thumb:hover { background: #a8a8a8; } /* 排序选项样式 - 改进版 */ .sort-options { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid #f0f0f0; } .sort-label { font-size: 13px; color: #666; font-weight: 500; } .sort-toggle { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; padding: 4px 8px; border-radius: 4px; transition: background-color 0.2s; } .sort-toggle:hover { background-color: #f0f0f0; } .toggle-switch { position: relative; display: inline-block; width: 44px; height: 24px; } .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 34px; } .toggle-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .4s; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,0.2); } .toggle-switch input:checked + .toggle-slider { background-color: #667eea; } .toggle-switch input:checked + .toggle-slider:before { transform: translateX(20px); } .toggle-switch input { display: none; } .toggle-text { font-size: 13px; color: #333; font-weight: 500; } `; shadowRoot.appendChild(style); // 创建遮罩层 const overlay = document.createElement('div'); overlay.className = 'overlay'; // 创建主界面 const ui = document.createElement('div'); ui.className = 'ui-container'; ui.id = 'custom-rules-ui'; // 渲染界面内容 renderUI(shadowRoot, ui); shadowRoot.appendChild(overlay); shadowRoot.appendChild(ui); // 将宿主元素添加到文档 document.body.appendChild(host); // 显示动画 setTimeout(() => { overlay.style.opacity = '1'; ui.style.opacity = '1'; ui.style.transform = 'translate(-50%, -50%) scale(1)'; }, 10); // 关闭事件 overlay.addEventListener('click', (e) => { if (e.target === overlay) { closeUI(); } }); uiVisible = true; // 返回关闭函数 function closeUI() { overlay.style.opacity = '0'; ui.style.opacity = '0'; ui.style.transform = 'translate(-50%, -50%) scale(0.9)'; setTimeout(() => { if (host.parentNode) host.parentNode.removeChild(host); uiVisible = false; }, 300); } // 绑定关闭按钮 const closeBtn = shadowRoot.querySelector('#close-ui'); if (closeBtn) { closeBtn.addEventListener('click', closeUI); } return closeUI; } // 渲染界面内容 function renderUI(shadowRoot, container) { const rules = getAllRules(); const sortByCurrentDomain = GM_getValue('sort_by_current_domain', true); // 按域名分组 const domainGroups = {}; rules.forEach(ruleStr => { const rule = parseRule(ruleStr); if (!rule) return; if (!domainGroups[rule.domain]) { domainGroups[rule.domain] = []; } domainGroups[rule.domain].push(rule); }); // 计算统计信息 const totalRules = rules.length; const totalDomains = Object.keys(domainGroups).length; const activeRules = rules.filter(ruleStr => { const rule = parseRule(ruleStr); return rule && domainMatches(rule.domain, currentDomain); }).length; // 构建HTML container.innerHTML = `