Auto-PROXY-SecFerro

Advanced privacy proxy redirector with intelligent instance selection and I2P support

当前为 2025-10-06 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name      Auto-PROXY-SecFerro
// @name:en      Auto-PROXY-SF
// @name:pl      Auto-PROXY-SecFerroDivision
// @description:en  Advanced privacy proxy redirector with intelligent instance selection and I2P support
// @description:pl  Zaawansowany przekierowujący serwer proxy z inteligentnym wyborem instancji i obsługą I2P
// @namespace    https://anonymousik.is-a.dev/userscripts
// @version      1.0.2
// @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==
// @description Advanced privacy proxy redirector with intelligent instance selection and I2P support
// ==/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();
    }

})();