您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Advanced privacy proxy redirector with intelligent instance selection and I2P support
当前为
// ==UserScript== // @name Auto-PROXY-SF // @description Advanced privacy proxy redirector with intelligent instance selection and I2P support // @namespace https://anonymousik.is-a.dev/userscripts // @version 1.0.1 // @author Anonymousik // @homepageURL https://anonymousik.is-a.dev // @supportURL https://anonymousik.is-a.dev // @license AGPL-3.0-only // @match *://*/* // @grant GM.getValue // @grant GM.setValue // @grant GM.deleteValue // @grant GM.xmlHttpRequest // @grant GM.registerMenuCommand // @run-at document-start // @connect nadeko.net // @connect puffyan.us // @connect yewtu.be // @connect tux.pizza // @connect nitter.net // @connect xcancel.com // @connect privacydev.net // @connect spike.codes // @connect privacy.com.de // @connect searx.be // @connect mdosch.de // @connect pabloferreiro.es // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48dGV4dCB5PSI0MDAiIGZvbnQtc2l6ZT0iNDAwIj7wn5S3PC90ZXh0Pjwvc3ZnPg== // ==/UserScript== (function() { 'use strict'; // Configuration constants const CONFIG = { VERSION: '1.0.1', HEALTH_CHECK_INTERVAL: 300000, // 5 minutes INSTANCE_TIMEOUT: 4000, // 4 seconds PARALLEL_CHECKS: 4, MAX_RETRY_ATTEMPTS: 2 }; // Static instance lists - no external fetching const I2P_INSTANCES = { invidious: [ 'http://inv.vern.i2p', 'http://inv.cn.i2p', 'http://ytmous.i2p', 'http://tube.i2p' ], nitter: [ 'http://tm4rwkeysv3zz3q5yacyr4rlmca2c4etkdobfvuqzt6vsfsu4weq.b32.i2p' ], libreddit: [ 'http://woo5ugmoomzbtaq6z46q4wgei5mqmc6jkafqfi5c37zni7xc4ymq.b32.i2p' ], searx: [ 'http://ransack.i2p', 'http://mqamk4cfykdvhw5kjez2gnvse56gmnqxn7vkvvbuor4k4j2lbbnq.b32.i2p' ], proxitok: [ 'http://qr.vern.i2p' ] }; const CLEARNET_INSTANCES = { invidious: [ 'https://inv.nadeko.net', 'https://vid.puffyan.us', 'https://yewtu.be', 'https://inv.tux.pizza' ], nitter: [ 'https://nitter.net', 'https://xcancel.com', 'https://nitter.privacydev.net' ], libreddit: [ 'https://libreddit.spike.codes', 'https://libreddit.privacy.com.de' ], searx: [ 'https://searx.be', 'https://search.mdosch.de' ], proxitok: [ 'https://proxitok.pabloferreiro.es' ] }; // Service detection patterns const SERVICE_PATTERNS = { invidious: { regex: /^(?:www\.)?(?:youtube\.com|youtu\.be)$/, pathBuilder: function(url) { const videoId = url.searchParams.get('v'); return videoId ? '/watch?v=' + videoId : url.pathname + url.search; } }, nitter: { regex: /^(?:www\.)?(?:twitter\.com|x\.com)$/, pathBuilder: function(url) { return url.pathname + url.search; } }, libreddit: { regex: /^(?:www\.)?(?:old\.)?reddit\.com$/, pathBuilder: function(url) { return url.pathname + url.search; } }, searx: { regex: /^(?:www\.)?google\.com$/, pathCheck: /^\/search/, pathBuilder: function(url) { const query = url.searchParams.get('q'); return query ? '/search?q=' + encodeURIComponent(query) : '/'; } }, proxitok: { regex: /^(?:www\.)?tiktok\.com$/, pathBuilder: function(url) { return url.pathname; } } }; // Loading page class - expanded CSS for readability class LoadingPage { static show(targetUrl, instanceUrl) { const hostname = new URL(instanceUrl).hostname; // Readable, unminified HTML const htmlContent = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Auto-PROXY-SF - Redirecting</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; color: #ffffff; overflow: hidden; } .container { text-align: center; padding: 2rem; max-width: 600px; animation: fadeIn 0.5s ease-in; } @keyframes fadeIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .logo { font-size: 4rem; margin-bottom: 1rem; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.1); } } h1 { font-size: 2.5rem; margin-bottom: 1rem; font-weight: 700; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); } .subtitle { font-size: 1.2rem; margin-bottom: 2rem; opacity: 0.9; } .loader { width: 60px; height: 60px; border: 5px solid rgba(255, 255, 255, 0.3); border-top: 5px solid #ffffff; border-radius: 50%; margin: 2rem auto; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .status { font-size: 1rem; margin-top: 2rem; padding: 1rem; background: rgba(255, 255, 255, 0.1); border-radius: 10px; backdrop-filter: blur(10px); } .instance-info { margin-top: 1rem; font-size: 0.9rem; opacity: 0.8; word-break: break-all; } .progress-bar { width: 100%; height: 4px; background: rgba(255, 255, 255, 0.2); border-radius: 2px; margin-top: 2rem; overflow: hidden; } .progress-fill { height: 100%; background: #ffffff; width: 0%; animation: progress 3s ease-in-out; } @keyframes progress { 0% { width: 0%; } 100% { width: 100%; } } .footer { margin-top: 3rem; font-size: 0.85rem; opacity: 0.7; } .footer a { color: #ffffff; text-decoration: none; border-bottom: 1px solid rgba(255, 255, 255, 0.3); transition: border-color 0.3s; } .footer a:hover { border-bottom-color: #ffffff; } .particles { position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: -1; } .particle { position: absolute; width: 4px; height: 4px; background: rgba(255, 255, 255, 0.5); border-radius: 50%; animation: float 10s infinite; } @keyframes float { 0%, 100% { transform: translateY(0) translateX(0); opacity: 0; } 10% { opacity: 1; } 90% { opacity: 1; } 100% { transform: translateY(-100vh) translateX(50px); opacity: 0; } } </style> </head> <body> <div class="particles" id="particles"></div> <div class="container"> <div class="logo">🔒</div> <h1>Auto-PROXY-SF</h1> <div class="subtitle">Securing your privacy...</div> <div class="loader"></div> <div class="status"> <div>Redirecting through privacy proxy</div> <div class="instance-info">Instance: ${hostname}</div> </div> <div class="progress-bar"> <div class="progress-fill"></div> </div> <div class="footer"> Powered by <a href="https://anonymousik.is-a.dev" target="_blank">Anonymousik</a> <br> SecFerro Division </div> </div> <script> // Create floating particles var particlesContainer = document.getElementById('particles'); for (var i = 0; i < 20; i++) { var particle = document.createElement('div'); particle.className = 'particle'; particle.style.left = Math.random() * 100 + '%'; particle.style.animationDelay = Math.random() * 10 + 's'; particle.style.animationDuration = (Math.random() * 10 + 10) + 's'; particlesContainer.appendChild(particle); } // Redirect after animation completes setTimeout(function() { window.location.href = '${targetUrl}'; }, 3000); </script> </body> </html>`; document.open(); document.write(htmlContent); document.close(); } } // Health monitoring system class HealthMonitor { constructor() { this.healthData = {}; this.checking = new Set(); } async initialize() { try { const cached = await GM.getValue('healthData', '{}'); this.healthData = JSON.parse(cached); this.cleanExpiredData(); } catch (error) { console.error('[Auto-PROXY-SF] Health data init failed:', error); this.healthData = {}; } } cleanExpiredData() { const now = Date.now(); const threshold = CONFIG.HEALTH_CHECK_INTERVAL * 2; for (const url in this.healthData) { if (this.healthData.hasOwnProperty(url)) { const data = this.healthData[url]; if (now - data.lastCheck > threshold) { delete this.healthData[url]; } } } } async checkHealth(url) { const self = this; // Prevent duplicate checks if (this.checking.has(url)) { return new Promise(function(resolve) { const interval = setInterval(function() { if (!self.checking.has(url)) { clearInterval(interval); const health = self.healthData[url]; resolve(health ? health.healthy : false); } }, 100); }); } // Check cache const cached = this.healthData[url]; if (cached && Date.now() - cached.lastCheck < CONFIG.HEALTH_CHECK_INTERVAL) { return cached.healthy; } this.checking.add(url); return new Promise(function(resolve) { const startTime = Date.now(); let resolved = false; const finish = function(healthy, latency) { if (resolved) return; resolved = true; self.checking.delete(url); self.updateHealth(url, healthy, latency); resolve(healthy); }; const timeoutId = setTimeout(function() { finish(false, null); }, CONFIG.INSTANCE_TIMEOUT); GM.xmlHttpRequest({ method: 'HEAD', url: url, timeout: CONFIG.INSTANCE_TIMEOUT, anonymous: true, onload: function(response) { clearTimeout(timeoutId); const latency = Date.now() - startTime; const healthy = response.status >= 200 && response.status < 400; finish(healthy, latency); }, onerror: function() { clearTimeout(timeoutId); finish(false, null); }, ontimeout: function() { clearTimeout(timeoutId); finish(false, null); } }); }); } updateHealth(url, healthy, latency) { const data = this.healthData[url] || { healthy: false, lastCheck: 0, failures: 0, latencies: [] }; data.healthy = healthy; data.lastCheck = Date.now(); if (healthy) { data.failures = 0; if (latency !== null) { data.latencies.push(latency); if (data.latencies.length > 10) { data.latencies.shift(); } // Calculate average latency const sum = data.latencies.reduce(function(a, b) { return a + b; }, 0); data.avgLatency = sum / data.latencies.length; } } else { data.failures++; } this.healthData[url] = data; // Asynchronous save GM.setValue('healthData', JSON.stringify(this.healthData)); } getScore(url) { const data = this.healthData[url]; if (!data || !data.healthy) return 0; let score = 50; // Base score for healthy instance // Latency scoring (max 30 points) if (data.avgLatency) { const latencyScore = Math.max(5, 30 - (data.avgLatency / 100) * 3); score += latencyScore; } // Reliability scoring (max 20 points) const reliabilityScore = Math.min(20, (10 - data.failures) * 2); score += reliabilityScore; return score; } getBestInstance(instances) { if (!instances || instances.length === 0) return null; const self = this; const scored = instances.map(function(url) { return { url: url, score: self.getScore(url) }; }).filter(function(item) { return item.score > 0; }).sort(function(a, b) { return b.score - a.score; }); return scored.length > 0 ? scored[0].url : instances[0]; } } // Instance manager class InstanceManager { constructor() { this.healthMonitor = new HealthMonitor(); this.preferredNetwork = 'clearnet'; } async initialize() { await this.healthMonitor.initialize(); this.preferredNetwork = await GM.getValue('preferredNetwork', 'clearnet'); } async getInstances(service) { if (this.preferredNetwork === 'i2p') { return I2P_INSTANCES[service] || []; } else { return CLEARNET_INSTANCES[service] || []; } } async getBestInstance(service, retryCount) { retryCount = retryCount || 0; const instances = await this.getInstances(service); if (instances.length === 0) { console.warn('[Auto-PROXY-SF] No instances for ' + service); return null; } const checkCount = Math.min(CONFIG.PARALLEL_CHECKS, instances.length); const toCheck = instances.slice(0, checkCount); const self = this; const checks = toCheck.map(function(url) { return self.healthMonitor.checkHealth(url); }); await Promise.all(checks); const best = this.healthMonitor.getBestInstance(instances); if (!best && retryCount < CONFIG.MAX_RETRY_ATTEMPTS) { console.log('[Auto-PROXY-SF] No healthy instance, retry ' + (retryCount + 1)); await new Promise(function(resolve) { setTimeout(resolve, 1000); }); return this.getBestInstance(service, retryCount + 1); } return best; } async setNetwork(network) { this.preferredNetwork = network; await GM.setValue('preferredNetwork', network); } } // URL processor class URLProcessor { constructor(manager) { this.manager = manager; this.processed = new WeakSet(); } detectService(hostname, pathname) { for (const service in SERVICE_PATTERNS) { if (SERVICE_PATTERNS.hasOwnProperty(service)) { const pattern = SERVICE_PATTERNS[service]; if (pattern.regex.test(hostname)) { if (pattern.pathCheck && !pattern.pathCheck.test(pathname)) { continue; } return service; } } } return null; } async processURL(originalUrl) { try { const url = new URL(originalUrl); const service = this.detectService(url.hostname, url.pathname); if (!service) return null; const instance = await this.manager.getBestInstance(service); if (!instance) return null; const pattern = SERVICE_PATTERNS[service]; const newPath = pattern.pathBuilder(url); return instance + newPath; } catch (error) { console.error('[Auto-PROXY-SF] URL processing error:', error); return null; } } async processLink(linkElement) { if (this.processed.has(linkElement)) return; this.processed.add(linkElement); const newUrl = await this.processURL(linkElement.href); if (newUrl) { linkElement.dataset.originalHref = linkElement.href; linkElement.href = newUrl; linkElement.style.color = '#2ea44f'; linkElement.title = 'Redirects via privacy proxy'; } } } // Page handler class PageHandler { constructor(processor, manager) { this.processor = processor; this.manager = manager; } async checkCurrentPage() { const url = new URL(window.location.href); const service = this.processor.detectService(url.hostname, url.pathname); if (service) { const instance = await this.manager.getBestInstance(service); if (instance) { const pattern = SERVICE_PATTERNS[service]; const targetUrl = instance + pattern.pathBuilder(url); LoadingPage.show(targetUrl, instance); } } } initialize() { this.checkCurrentPage(); const self = this; const observer = new IntersectionObserver( function(entries) { for (let i = 0; i < entries.length; i++) { const entry = entries[i]; if (entry.isIntersecting) { self.processor.processLink(entry.target); observer.unobserve(entry.target); } } }, { rootMargin: '100px' } ); const processLinks = function() { const links = document.querySelectorAll('a[href^="http"]:not([data-proxy-processed])'); for (let i = 0; i < links.length; i++) { const link = links[i]; link.dataset.proxyProcessed = 'true'; observer.observe(link); } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', processLinks); } else { processLinks(); } if (document.body) { const mutationObserver = new MutationObserver(function() { if (typeof requestIdleCallback === 'function') { requestIdleCallback(processLinks, { timeout: 2000 }); } else { setTimeout(processLinks, 100); } }); mutationObserver.observe(document.body, { childList: true, subtree: true }); } } } // Main initialization async function main() { console.log('[Auto-PROXY-SF] v' + CONFIG.VERSION + ' by Anonymousik'); const manager = new InstanceManager(); await manager.initialize(); const processor = new URLProcessor(manager); const handler = new PageHandler(processor, manager); handler.initialize(); // Menu commands const currentNetwork = await GM.getValue('preferredNetwork', 'clearnet'); GM.registerMenuCommand('Network: ' + currentNetwork.toUpperCase(), async function() { const networks = ['clearnet', 'i2p']; const currentIndex = networks.indexOf(currentNetwork); const newNetwork = networks[(currentIndex + 1) % networks.length]; await manager.setNetwork(newNetwork); alert('Auto-PROXY-SF: Switched to ' + newNetwork.toUpperCase()); window.location.reload(); }); GM.registerMenuCommand('Clear Cache', async function() { await GM.deleteValue('healthData'); manager.healthMonitor.healthData = {}; alert('Cache cleared successfully'); }); GM.registerMenuCommand('About', function() { alert('Auto-PROXY-SF v' + CONFIG.VERSION + '\n\nAuthor: Anonymousik\nSecFerro Division\n\nhttps://anonymousik.is-a.dev'); }); } // Start when ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); } })();