您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Mimics hosts file behavior with user-managed redirections via local storage.
// ==UserScript== // @name Advanced Browser Hosts File (User Managed) // @description Mimics hosts file behavior with user-managed redirections via local storage. // @namespace http://tampermonkey.net/ // @version 1.1 // @author s3b // @license MIT // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @run-at document-start // ==/UserScript== (function() { 'use strict'; // --- CONFIGURATION --- const STORAGE_KEY = 'browserHostsRules'; const MANAGEMENT_TRIGGER_KEY = 'Alt+Shift+H'; // Keyboard shortcut to open management UI const MANAGEMENT_URL_PATH = '/_tmredirect'; // A unique path to trigger the management UI // --- END CONFIGURATION --- let hostsRules = {}; // Will be loaded from localStorage // --- UI Styles --- GM_addStyle(` #tmHostsManagerOverlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); display: flex; justify-content: center; align-items: center; z-index: 99999; font-family: sans-serif; color: #333; } #tmHostsManagerPanel { background-color: #fff; padding: 25px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); max-width: 600px; width: 90%; max-height: 90%; overflow-y: auto; position: relative; } #tmHostsManagerPanel h2 { margin-top: 0; color: #0056b3; border-bottom: 2px solid #eee; padding-bottom: 10px; margin-bottom: 20px; } #tmHostsManagerPanel label { display: block; margin-bottom: 5px; font-weight: bold; color: #555; } #tmHostsManagerPanel input[type="text"] { width: calc(100% - 22px); padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } #tmHostsManagerPanel button { background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; margin-right: 10px; transition: background-color 0.2s ease; } #tmHostsManagerPanel button:hover { background-color: #0056b3; } #tmHostsManagerPanel button.delete { background-color: #dc3545; } #tmHostsManagerPanel button.delete:hover { background-color: #c82333; } #tmHostsManagerPanel button.close { background-color: #6c757d; } #tmHostsManagerPanel button.close:hover { background-color: #5a6268; } #tmHostsManagerPanel #rulesList { margin-top: 20px; border-top: 1px solid #eee; padding-top: 20px; } #tmHostsManagerPanel .rule-item { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px dashed #eee; } #tmHostsManagerPanel .rule-item:last-child { border-bottom: none; } #tmHostsManagerPanel .rule-item span { flex-grow: 1; word-break: break-all; } #tmHostsManagerPanel .rule-item .actions { margin-left: 15px; display: flex; gap: 5px; } #tmHostsManagerPanel .rule-item .actions button { padding: 5px 10px; font-size: 14px; margin-right: 0; } #tmHostsManagerPanel .info-text { font-size: 0.9em; color: #666; margin-bottom: 15px; background-color: #f9f9f9; border-left: 4px solid #007bff; padding: 10px; border-radius: 4px; } #tmHostsManagerPanel .close-button-top { position: absolute; top: 10px; right: 15px; font-size: 24px; font-weight: bold; color: #aaa; cursor: pointer; line-height: 1; padding: 0 5px; } #tmHostsManagerPanel .close-button-top:hover { color: #666; } `); // --- Core Functions --- async function loadRules() { try { const storedRules = await GM_getValue(STORAGE_KEY, '{}'); hostsRules = JSON.parse(storedRules); } catch (e) { console.error('Tampermonkey Hosts: Failed to load rules from localStorage', e); hostsRules = {}; // Reset if parsing fails } } async function saveRules() { try { await GM_setValue(STORAGE_KEY, JSON.stringify(hostsRules)); console.log('Tampermonkey Hosts: Rules saved.'); } catch (e) { console.error('Tampermonkey Hosts: Failed to save rules to localStorage', e); } } function addRule(source, target) { source = source.trim().toLowerCase(); target = target.trim(); if (source && target) { hostsRules[source] = target; saveRules(); renderRules(); // Re-render UI if it's open return true; } return false; } function deleteRule(source) { if (confirm(`Are you sure you want to delete the rule for "${source}"?`)) { delete hostsRules[source]; saveRules(); renderRules(); // Re-render UI if it's open } } function clearRuleInputs() { document.getElementById('tmSourceInput').value = ''; document.getElementById('tmTargetInput').value = ''; } // --- UI Management --- function createManagementUI() { if (document.getElementById('tmHostsManagerOverlay')) { // UI already exists return; } const overlay = document.createElement('div'); overlay.id = 'tmHostsManagerOverlay'; overlay.innerHTML = ` <div id="tmHostsManagerPanel"> <span class="close-button-top" id="tmCloseManagerBtnTop">×</span> <h2>Browser Hosts Manager</h2> <div class="info-text"> Add rules to redirect websites. Use 'BLOCK' as the target URL to redirect to <code>about:blank</code> (effectively blocking). <br>Rules are matched against the hostname (e.g., <code>www.google.com</code>) or its subdomains. </div> <div> <label for="tmSourceInput">Source Hostname (e.g., youtube.com, www.facebook.com):</label> <input type="text" id="tmSourceInput" placeholder="Enter source domain (e.g., badsite.com)" required> </div> <div> <label for="tmTargetInput">Target URL (e.g., https://newsite.com, or BLOCK):</label> <input type="text" id="tmTargetInput" placeholder="Enter target URL or 'BLOCK'" required> </div> <button id="tmAddRuleBtn">Add/Update Rule</button> <button id="tmCloseManagerBtnBottom" class="close">Close</button> <div id="rulesList"> <h3>Current Rules:</h3> <div id="tmRulesContainer"> </div> </div> </div> `; document.body.appendChild(overlay); // Event Listeners document.getElementById('tmAddRuleBtn').addEventListener('click', () => { const source = document.getElementById('tmSourceInput').value; const target = document.getElementById('tmTargetInput').value; if (addRule(source, target)) { clearRuleInputs(); } else { alert('Please provide both a source hostname and a target URL.'); } }); document.getElementById('tmCloseManagerBtnTop').addEventListener('click', closeManagementUI); document.getElementById('tmCloseManagerBtnBottom').addEventListener('click', closeManagementUI); // Allow clicking outside the panel to close it overlay.addEventListener('click', (event) => { if (event.target === overlay) { closeManagementUI(); } }); renderRules(); } function renderRules() { const rulesContainer = document.getElementById('tmRulesContainer'); if (!rulesContainer) return; // UI not open rulesContainer.innerHTML = ''; // Clear previous rules if (Object.keys(hostsRules).length === 0) { rulesContainer.innerHTML = '<p>No rules added yet. Add your first rule above!</p>'; return; } for (const source in hostsRules) { if (hostsRules.hasOwnProperty(source)) { const target = hostsRules[source]; const ruleItem = document.createElement('div'); ruleItem.classList.add('rule-item'); ruleItem.innerHTML = ` <span><b>${source}</b> → ${target === 'BLOCK' ? '<span style="color:red; font-weight:bold;">BLOCK</span>' : target}</span> <div class="actions"> <button class="delete" data-source="${source}">Delete</button> </div> `; rulesContainer.appendChild(ruleItem); ruleItem.querySelector('.delete').addEventListener('click', (event) => { deleteRule(event.target.dataset.source); }); } } } function openManagementUI() { createManagementUI(); document.getElementById('tmHostsManagerOverlay').style.display = 'flex'; renderRules(); // Ensure rules are up-to-date when opening } function closeManagementUI() { const overlay = document.getElementById('tmHostsManagerOverlay'); if (overlay) { overlay.style.display = 'none'; } } // --- Main Script Logic --- async function initializeAndRun() { await loadRules(); // Load rules asynchronously at script start const currentHostname = window.location.hostname; const currentPath = window.location.pathname; // Check for management UI trigger URL if (currentPath === MANAGEMENT_URL_PATH) { // Prevent the original page from loading document.documentElement.innerHTML = ''; // Clear existing content document.head.innerHTML = ''; // Clear head content document.title = 'Browser Hosts Manager'; // Set a temporary title openManagementUI(); // Stop further script execution for the main page logic return; } // Check for redirection rules for (const domain in hostsRules) { if (hostsRules.hasOwnProperty(domain)) { // More robust check: exact match or currentHostname ends with '.' + domain // This covers www.example.com for example.com, but not example.com for an-example.com if (currentHostname === domain || currentHostname.endsWith('.' + domain)) { const redirectTarget = hostsRules[domain]; if (redirectTarget === 'BLOCK') { // Redirect to about:blank to "block" the site window.location.replace('about:blank'); // Prevent the original page from loading further throw new Error('Site blocked by Tampermonkey script.'); } else { // Redirect to a specific URL window.location.replace(redirectTarget); // Prevent the original page from loading further throw new Error('Site redirected by Tampermonkey script.'); } } } } } // Run the initialization and main logic initializeAndRun(); // Add keyboard shortcut listener for convenience document.addEventListener('keydown', (e) => { const [modifier, key] = MANAGEMENT_TRIGGER_KEY.split('+'); if (e.altKey === (modifier === 'Alt') && e.shiftKey === (modifier === 'Shift') && e.ctrlKey === (modifier === 'Control') && e.key.toUpperCase() === key.toUpperCase()) { e.preventDefault(); // Prevent default browser action openManagementUI(); } }); // Option to open management UI from Tampermonkey's menu // This part is more for a context menu or similar, Tampermonkey doesn't have a direct GM_registerMenuCommand // However, the keyboard shortcut or the specific URL will serve the purpose. })();