Auto-PROXY-SF

Advanced privacy proxy redirector with intelligent instance selection, automated scraping, bypass shortlinks, and comprehensive configuration

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

// ==UserScript==
// @name         Auto-PROXY-SF
// @description  Advanced privacy proxy redirector with intelligent instance selection, automated scraping, bypass shortlinks, and comprehensive configuration
// @name:en         Auto-PROXY-SF
// @description:en  Advanced privacy proxy redirector with intelligent instance selection, automated scraping, bypass shortlinks, and comprehensive configuration
// @namespace    https://anonymousik.is-a.dev/userscripts
// @version      2.0.0
// @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
// @grant        GM.unregisterMenuCommand
// @grant        GM.notification
// @grant        GM.openInTab
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_notification
// @grant        GM_openInTab
// @require      https://update.greasyfork.org/scripts/528923/1599357/MonkeyConfig%20Mod.js
// @run-at       document-start
// @connect      api.invidious.io
// @connect      raw.githubusercontent.com
// @connect      github.com
// @connect      searx.space
// @connect      codeberg.org
// @connect      nadeko.net
// @connect      puffyan.us
// @connect      yewtu.be
// @connect      tux.pizza
// @connect      privacydev.net
// @connect      nitter.net
// @connect      xcancel.com
// @connect      poast.org
// @connect      nitter.it
// @connect      unixfox.eu
// @connect      spike.codes
// @connect      privacy.com.de
// @connect      dcs0.hu
// @connect      lunar.icu
// @connect      artemislena.eu
// @connect      searx.be
// @connect      mdosch.de
// @connect      tiekoetter.com
// @connect      bus-hit.me
// @connect      pabloferreiro.es
// @connect      habedieeh.re
// @connect      pussthecat.org
// @connect      totaldarkness.net
// @connect      rip
// @connect      citizen4.eu
// @connect      iket.me
// @connect      vern.cc
// @connect      antifandom.com
// @connect      whatever.social
// @connect      hostux.net
// @connect      *
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48dGV4dCB5PSI0MDAiIGZvbnQtc2l6ZT0iNDAwIj7wn5S3PC90ZXh0Pjwvc3ZnPg==
// ==/UserScript==

(function() {
    'use strict';

    const CONFIG = {
        VERSION: '2.0.0',
        HEALTH_CHECK_INTERVAL: 300000,
        INSTANCE_TIMEOUT: 5000,
        SCRAPER_TIMEOUT: 15000,
        PARALLEL_CHECKS: 6,
        MAX_RETRY_ATTEMPTS: 3,
        SCRAPER_UPDATE_INTERVAL: 43200000,
        MIN_INSTANCE_SCORE: 30,
        CACHE_EXPIRY: 86400000,
        QUEUE_DELAY: 100,
        EXPONENTIAL_BACKOFF_BASE: 1000,
        MAX_BACKOFF_DELAY: 30000,
        SHORTLINK_MAX_REDIRECTS: 5
    };

    const GMCompat = {
        getValue: typeof GM !== 'undefined' && GM.getValue ? GM.getValue : GM_getValue,
        setValue: typeof GM !== 'undefined' && GM.setValue ? GM.setValue : GM_setValue,
        deleteValue: typeof GM !== 'undefined' && GM.deleteValue ? GM.deleteValue : GM_deleteValue,
        xmlHttpRequest: typeof GM !== 'undefined' && GM.xmlHttpRequest ? GM.xmlHttpRequest : GM_xmlhttpRequest,
        registerMenuCommand: typeof GM !== 'undefined' && GM.registerMenuCommand ? GM.registerMenuCommand : GM_registerMenuCommand,
        unregisterMenuCommand: typeof GM !== 'undefined' && GM.unregisterMenuCommand ? GM.unregisterMenuCommand : GM_unregisterMenuCommand,
        notification: typeof GM !== 'undefined' && GM.notification ? GM.notification : GM_notification,
        openInTab: typeof GM !== 'undefined' && GM.openInTab ? GM.openInTab : GM_openInTab
    };

    const configDefinition = {
        title: 'Auto-PROXY-SF Configuration',
        menuCommand: true,
        params: {
            enabled: {
                label: 'Enable Auto-PROXY-SF',
                type: 'checkbox',
                default: true
            },
            network: {
                label: 'Preferred Network',
                type: 'select',
                options: ['clearnet', 'i2p'],
                default: 'clearnet'
            },
            autoRedirect: {
                label: 'Automatic Page Redirection',
                type: 'checkbox',
                default: true
            },
            linkRewriting: {
                label: 'Dynamic Link Rewriting',
                type: 'checkbox',
                default: true
            },
            bypassShortlinks: {
                label: 'Bypass Shortlinks',
                type: 'checkbox',
                default: true
            },
            showLoadingPage: {
                label: 'Show Loading Animation',
                type: 'checkbox',
                default: true
            },
            autoUpdateInstances: {
                label: 'Automatic Instance Updates',
                type: 'checkbox',
                default: true
            },
            notificationsEnabled: {
                label: 'Enable Notifications',
                type: 'checkbox',
                default: true
            },
            minInstanceScore: {
                label: 'Minimum Instance Score',
                type: 'number',
                default: 30,
                min: 0,
                max: 100
            },
            healthCheckInterval: {
                label: 'Health Check Interval (minutes)',
                type: 'number',
                default: 5,
                min: 1,
                max: 60
            },
            instanceTimeout: {
                label: 'Instance Timeout (seconds)',
                type: 'number',
                default: 5,
                min: 1,
                max: 30
            },
            parallelChecks: {
                label: 'Parallel Health Checks',
                type: 'number',
                default: 6,
                min: 1,
                max: 20
            },
            services: {
                label: 'Enabled Services',
                type: 'section',
                children: {
                    invidious: {
                        label: 'YouTube → Invidious',
                        type: 'checkbox',
                        default: true
                    },
                    nitter: {
                        label: 'Twitter/X → Nitter',
                        type: 'checkbox',
                        default: true
                    },
                    libreddit: {
                        label: 'Reddit → Libreddit',
                        type: 'checkbox',
                        default: true
                    },
                    teddit: {
                        label: 'Reddit → Teddit',
                        type: 'checkbox',
                        default: true
                    },
                    searx: {
                        label: 'Google → SearX',
                        type: 'checkbox',
                        default: true
                    },
                    proxitok: {
                        label: 'TikTok → ProxiTok',
                        type: 'checkbox',
                        default: true
                    },
                    rimgo: {
                        label: 'Imgur → Rimgo',
                        type: 'checkbox',
                        default: true
                    },
                    scribe: {
                        label: 'Medium → Scribe',
                        type: 'checkbox',
                        default: true
                    },
                    quetre: {
                        label: 'Quora → Quetre',
                        type: 'checkbox',
                        default: true
                    },
                    libremdb: {
                        label: 'IMDB → LibreMDB',
                        type: 'checkbox',
                        default: true
                    },
                    breezewiki: {
                        label: 'Fandom → BreezeWiki',
                        type: 'checkbox',
                        default: true
                    },
                    anonymousoverflow: {
                        label: 'StackOverflow → AnonymousOverflow',
                        type: 'checkbox',
                        default: true
                    },
                    bibliogram: {
                        label: 'Instagram → Bibliogram',
                        type: 'checkbox',
                        default: true
                    },
                    wikiless: {
                        label: 'Wikipedia → Wikiless',
                        type: 'checkbox',
                        default: true
                    }
                }
            }
        },
        events: {
            save: function() {
                console.log('[Auto-PROXY-SF] Configuration saved successfully');
                if (scriptConfig.get('notificationsEnabled')) {
                    showNotification('Configuration saved', 'Settings have been updated successfully');
                }
            }
        }
    };

    let scriptConfig;
    
    try {
        scriptConfig = new MonkeyConfig(configDefinition);
    } catch (error) {
        console.error('[Auto-PROXY-SF] MonkeyConfig initialization failed:', error);
        scriptConfig = {
            get: function(key) {
                const defaults = {
                    enabled: true,
                    network: 'clearnet',
                    autoRedirect: true,
                    linkRewriting: true,
                    bypassShortlinks: true,
                    showLoadingPage: true,
                    autoUpdateInstances: true,
                    notificationsEnabled: true,
                    minInstanceScore: 30,
                    healthCheckInterval: 5,
                    instanceTimeout: 5,
                    parallelChecks: 6,
                    services: {
                        invidious: true, nitter: true, libreddit: true, teddit: true,
                        searx: true, proxitok: true, rimgo: true, scribe: true,
                        quetre: true, libremdb: true, breezewiki: true,
                        anonymousoverflow: true, bibliogram: true, wikiless: true
                    }
                };
                if (key in defaults.services) return defaults.services[key];
                return defaults[key] !== undefined ? defaults[key] : true;
            }
        };
    }

    function showNotification(title, message) {
        if (scriptConfig.get('notificationsEnabled')) {
            try {
                if (typeof GMCompat.notification === 'function') {
                    GMCompat.notification({
                        text: message,
                        title: 'Auto-PROXY-SF: ' + title,
                        timeout: 4000
                    });
                }
            } catch (error) {
                console.log('[Auto-PROXY-SF] ' + title + ': ' + message);
            }
        }
    }

    const SHORTLINK_PATTERNS = [
        /^(?:www\.)?(?:bit\.ly|bitly\.com|goo\.gl|ow\.ly|short\.io|tiny\.cc|tinyurl\.com|is\.gd|buff\.ly|adf\.ly|bc\.vc|linkbucks\.com|shorte\.st|ouo\.io|ouo\.press|clk\.sh|exe\.io|linkshrink\.net|shrinkme\.io|gplinks\.in|droplink\.co|earnl\.xyz|try2link\.com|mboost\.me|du-link\.in)$/i,
        /^(?:www\.)?(?:1ink\.cc|123link\.top|1cloudfile\.com|1fichier\.com|2короткая\.ссылка|4links\.org|4slink\.com|7r6\.com|adfly\.fr|adrinolinks\.in|aegispro\.xyz|aiotpay\.com|aiotpay\.in)$/i,
        /^(?:www\.)?(?:al\.ly|allcryptoz\.net|android-news\.org|apkadmin\.com|appsfire\.org|arabylinks\.online|atglinks\.com|ay\.live|bc\.game|beastapk\.com|bdlinks\.pw|besturl\.link|bluemediafile\.com)$/i,
        /^(?:www\.)?(?:boost\.ink|bootdey\.com|cespapa\.com|clicksfly\.com|clk\.wiki|clkmein\.com|clksh\.com|coincroco\.com|coinsward\.com|compucalitv\.com|crazyslink\.in|criptologico\.com)$/i,
        /^(?:www\.)?(?:cryptorotator\.com|cuon\.io|cut-urls\.com|cutt\.ly|cutt\.us|cuttly\.com|cutwin\.com|cybertechng\.com|dailyuploads\.net|datanodes\.to|dddrive\.me|de\.gl|destyy\.com)$/i,
        /^(?:www\.)?(?:dfe\.bz|digitalproductreviews\.com|directlinks\.online|dlmob\.pw|dosya\.co|drhd\.link|drive\.google\.com|dropgalaxy\.com|droplink\.co|dz-linkk\.com|earn4link\.in|earnmony\.xyz)$/i,
        /^(?:www\.)?(?:earnow\.online|easycut\.io|easysky\.in|efukt\.link|eklablog\.com|emturbovid\.com|enagato\.com|eni\.ch|escheat\.com|exey\.io|extra-mili\.com|ezvn\.link|f\.xeovo\.com)$/i,
        /^(?:www\.)?(?:faceclips\.net|faucetcrypto\.com|fc-lc\.xyz|fc\.lc|file-upload\.com|file-upload\.net|filecrypt\.cc|filecrypt\.co|filerio\.in|flashlink\.online|flvto\.ch|forex\.world)$/i,
        /^(?:www\.)?(?:forexmab\.com|forextraderz\.com|freecoursesite\.com|freethescience\.com|fulltchat\.app|fx4vip\.com|gadgetlove\.me|gadgetsreviewer\.com|gamesmega\.net|gatling\.link)$/i,
        /^(?:www\.)?(?:gdr\.vip|gdtot\.fun|gdurl\.com|geradaurl\.com|get\.app\.link|getmega\.net|geturl\.link|gistify\.com|goo-gl\.me|goo-gl\.ru\.com|gplinks\.co|gplinks\.in)$/i
    ];

    const BYPASS_DOMAINS = new Set();
    SHORTLINK_PATTERNS.forEach(regex => {
        const matches = regex.source.match(/\((?:www\\\.)?\?\:([^\)\|\\]+)/g);
        if (matches) {
            matches.forEach(m => {
                m.match(/\|([^|\\]+)/g)?.forEach(d => BYPASS_DOMAINS.add(d.substring(1).replace(/\\\./g, '.')));
            });
        }
    });

    const INSTANCE_SOURCES = {
        invidious: [
            { url: 'https://api.invidious.io/instances.json', type: 'json', parser: 'invidiousAPI', priority: 1 },
            { url: 'https://raw.githubusercontent.com/iv-org/documentation/master/docs/instances.md', type: 'markdown', parser: 'markdownTable', priority: 2 }
        ],
        nitter: [
            { url: 'https://raw.githubusercontent.com/zedeus/nitter/master/instances.json', type: 'json', parser: 'nitterJSON', priority: 1 },
            { url: 'https://github.com/zedeus/nitter/wiki/Instances', type: 'html', parser: 'githubWiki', priority: 2 }
        ],
        libreddit: [
            { url: 'https://raw.githubusercontent.com/libreddit/libreddit-instances/master/instances.json', type: 'json', parser: 'genericJSON', priority: 1 }
        ],
        searx: [
            { url: 'https://searx.space/data/instances.json', type: 'json', parser: 'searxSpace', priority: 1 }
        ],
        proxitok: [
            { url: 'https://raw.githubusercontent.com/pablouser1/ProxiTok/master/instances.md', type: 'markdown', parser: 'markdownList', priority: 1 }
        ],
        rimgo: [
            { url: 'https://codeberg.org/rimgo/instances/raw/branch/main/instances.json', type: 'json', parser: 'genericJSON', priority: 1 }
        ]
    };

    const STATIC_INSTANCES = {
        clearnet: {
            invidious: [
                'https://inv.nadeko.net', 'https://vid.puffyan.us', 'https://yewtu.be', 'https://inv.tux.pizza',
                'https://invidious.privacydev.net', 'https://inv.riverside.rocks', 'https://yt.artemislena.eu',
                'https://invidious.flokinet.to'
            ],
            nitter: [
                'https://nitter.net', 'https://xcancel.com', 'https://nitter.privacydev.net', 'https://nitter.poast.org',
                'https://nitter.it', 'https://nitter.unixfox.eu', 'https://nitter.1d4.us', 'https://nitter.kavin.rocks'
            ],
            libreddit: [
                'https://libreddit.spike.codes', 'https://libreddit.privacy.com.de', 'https://libreddit.dcs0.hu',
                'https://libreddit.lunar.icu', 'https://reddit.artemislena.eu', 'https://lr.riverside.rocks'
            ],
            teddit: ['https://teddit.net', 'https://teddit.privacydev.net', 'https://teddit.hostux.net'],
            searx: [
                'https://searx.be', 'https://search.mdosch.de', 'https://searx.tiekoetter.com',
                'https://search.bus-hit.me', 'https://searx.work', 'https://searx.fmac.xyz'
            ],
            proxitok: [
                'https://proxitok.pabloferreiro.es', 'https://proxitok.privacy.com.de', 'https://tok.habedieeh.re',
                'https://proxitok.pussthecat.org'
            ],
            rimgo: [
                'https://rimgo.pussthecat.org', 'https://rimgo.totaldarkness.net', 'https://rimgo.bus-hit.me',
                'https://rimgo.privacydev.net'
            ],
            scribe: [
                'https://scribe.rip', 'https://scribe.citizen4.eu', 'https://scribe.bus-hit.me',
                'https://scribe.privacydev.net'
            ],
            quetre: [
                'https://quetre.iket.me', 'https://quetre.pussthecat.org', 'https://quetre.privacydev.net',
                'https://quetre.projectsegfau.lt'
            ],
            libremdb: [
                'https://libremdb.iket.me', 'https://ld.vern.cc', 'https://lmdb.hostux.net'
            ],
            breezewiki: [
                'https://antifandom.com', 'https://breezewiki.nadeko.net', 'https://bw.vern.cc'
            ],
            anonymousoverflow: [
                'https://code.whatever.social', 'https://overflow.hostux.net', 'https://ao.vern.cc'
            ],
            bibliogram: ['https://bibliogram.art', 'https://bibliogram.snopyta.org'],
            wikiless: ['https://wikiless.org', 'https://wikiless.northboot.xyz']
        },
        i2p: {
            invidious: [
                'http://inv.vern.i2p', 'http://inv.cn.i2p', 'http://ytmous.i2p', 'http://tube.i2p'
            ],
            nitter: [
                'http://tm4rwkeysv3zz3q5yacyr4rlmca2c4etkdobfvuqzt6vsfsu4weq.b32.i2p',
                'http://nitter.i2p'
            ],
            libreddit: [
                'http://woo5ugmoomzbtaq6z46q4wgei5mqmc6jkafqfi5c37zni7xc4ymq.b32.i2p',
                'http://reddit.i2p'
            ],
            teddit: [
                'http://k62ptris7p72aborr4zoanee7xai6wguucveptwgxs5vbgt7qzpq.b32.i2p',
                'http://teddit.i2p'
            ],
            searx: [
                'http://ransack.i2p',
                'http://mqamk4cfykdvhw5kjez2gnvse56gmnqxn7vkvvbuor4k4j2lbbnq.b32.i2p'
            ],
            wikiless: ['http://wikiless.i2p'],
            proxitok: ['http://qr.vern.i2p']
        }
    };

    const SERVICE_PATTERNS = {
        invidious: {
            regex: /^(?:www\.|m\.)?(?:youtube\.com|youtu\.be|invidious\.io|yt\.be)(?:\/watch\?v=|\/embed\/|\/v\/|\/shorts\/|\/)([a-zA-Z0-9_-]{11})/,
            targetPath: '/watch?v=$1'
        },
        nitter: {
            regex: /^(?:www\.)?(?:twitter|x)\.com\/(?:#!\/)?(?:[^\/]+\/status\/(\d+)|([^\/]+))/,
            targetPath: function(match) {
                if (match[1]) return '/$2/status/$1'; 
                return `/${match[2] || ''}`; 
            }
        },
        libreddit: {
            regex: /^(?:www\.)?(?:old\.|np\.|m\.)?reddit\.com(?:\/r\/[^\/]+|\/user\/[^\/]+)?(\/.*)?$/,
            targetPath: '$1'
        },
        teddit: {
            regex: /^(?:www\.)?(?:old\.|np\.|m\.)?reddit\.com(?:\/r\/[^\/]+|\/user\/[^\/]+)?(\/.*)?$/,
            targetPath: '$1'
        },
        searx: {
            regex: /^(?:www\.)?(?:google|duckduckgo|bing)\.com\/(search|q)\?(.+)/,
            targetPath: function(match) {
                const urlParams = new URLSearchParams(match[2]);
                const query = urlParams.get('q') || urlParams.get('search');
                return query ? `/search?q=${encodeURIComponent(query)}` : null;
            }
        },
        proxitok: {
            regex: /^(?:www\.)?tiktok\.com\/@([^\/]+)(?:\/video\/(\d+))?/,
            targetPath: function(match) {
                if (match[2]) return `/@${match[1]}/video/${match[2]}`;
                return `/@${match[1]}`;
            }
        },
        rimgo: {
            regex: /^(?:www\.)?imgur\.com(\/.*)?$/,
            targetPath: '$1'
        },
        scribe: {
            regex: /^(?:www\.)?medium\.com(\/.*)?$/,
            targetPath: '$1'
        },
        quetre: {
            regex: /^(?:www\.)?quora\.com(\/.*)?$/,
            targetPath: '$1'
        },
        libremdb: {
            regex: /^(?:www\.)?imdb\.com(\/.*)?$/,
            targetPath: '$1'
        },
        breezewiki: {
            regex: /^(?:www\.)?fandom\.com(\/.*)?$/,
            targetPath: '$1'
        },
        anonymousoverflow: {
            regex: /^(?:www\.)?stackoverflow\.com(\/.*)?$/,
            targetPath: '$1'
        },
        bibliogram: {
            regex: /^(?:www\.)?instagram\.com(\/.*)?$/,
            targetPath: '$1'
        },
        wikiless: {
            regex: /^(?:[a-z]{2}\.)?wikipedia\.org(\/.*)?$/,
            targetPath: '$1'
        }
    };

    /**
     * Klasa do monitorowania kondycji i wybierania najlepszych instancji.
     */
    class HealthMonitor {
        constructor() {
            this.healthData = {};
            this.checking = new Set();
            this.queueProcessing = false;
            this.checkQueue = [];
        }

        async initialize() {
            this.healthData = await GMCompat.getValue('healthData', {});
            this.startHealthCheckLoop();
        }

        startHealthCheckLoop() {
            this.checkAllInstances();
            setInterval(() => this.checkAllInstances(), CONFIG.HEALTH_CHECK_INTERVAL);
        }

        async checkAllInstances() {
            const allInstances = new Set();
            Object.values(STATIC_INSTANCES).forEach(network => {
                Object.values(network).forEach(urls => {
                    urls.forEach(url => allInstances.add(url));
                });
            });

            const instancesToCheck = Array.from(allInstances).filter(url => {
                const data = this.healthData[url];
                return !data || data.timestamp < (Date.now() - CONFIG.HEALTH_CHECK_INTERVAL);
            });

            console.log(`[HealthMonitor] Checking ${instancesToCheck.length} stale instances.`);

            const results = await Promise.all(
                instancesToCheck.map(url => this.enqueueCheck(url))
            );

            results.forEach((result, index) => {
                if (result) {
                    this.updateHealthData(instancesToCheck[index], result.healthy, result.latency);
                }
            });
            await GMCompat.setValue('healthData', this.healthData);
        }

        async enqueueCheck(url) {
            return new Promise(resolve => {
                this.checkQueue.push({ url: url, resolve: resolve });
                if (!this.queueProcessing) {
                    this.processQueue();
                }
            });
        }

        async processQueue() {
            if (this.queueProcessing || this.checkQueue.length === 0) return;
            
            this.queueProcessing = true;
            const self = this;

            while (this.checkQueue.length > 0) {
                const batch = this.checkQueue.splice(0, scriptConfig.get('parallelChecks') || CONFIG.PARALLEL_CHECKS);
                
                await Promise.all(batch.map(item => 
                    self.performHealthCheck(item.url).then(result => item.resolve(result))
                ));

                if (this.checkQueue.length > 0) {
                    await new Promise(resolve => setTimeout(resolve, CONFIG.QUEUE_DELAY));
                }
            }
            this.queueProcessing = false;
        }

        performHealthCheck(url) {
            const self = this;
            this.checking.add(url);
            const timeout = (scriptConfig.get('instanceTimeout') || CONFIG.INSTANCE_TIMEOUT / 1000) * 1000;

            return new Promise(resolve => {
                const startTime = Date.now();
                let resolved = false;

                const finish = (healthy, latency) => {
                    if (resolved) return;
                    resolved = true;
                    self.checking.delete(url);
                    resolve({ healthy, latency });
                };

                const xhr = GMCompat.xmlHttpRequest({
                    method: 'GET',
                    url: url,
                    onload: function(response) {
                        const latency = Date.now() - startTime;
                        // Sprawdzamy, czy URL jest instancją Nittera, która używa 302 na statusie 200, 
                        // lub czy status jest ogólnie w zakresie 200-399.
                        const healthy = response.status >= 200 && response.status < 400; 
                        finish(healthy, latency);
                    },
                    onerror: function() {
                        finish(false, Date.now() - startTime);
                    },
                    ontimeout: function() {
                        finish(false, timeout);
                    },
                    timeout: timeout,
                    // Ważne dla bezpieczeństwa - zapobiega wysyłaniu ciasteczek
                    anonymous: true 
                });

                if (xhr && typeof xhr.abort === 'function') {
                    // W przypadku starszych menedżerów (GM_xmlhttpRequest) to może nie być dostępne,
                    // ale jest to dobra praktyka dla kompatybilności.
                }

                // Domyślny timeout (mechanizm awaryjny)
                setTimeout(() => finish(false, timeout), timeout + 500); 
            });
        }

        updateHealthData(url, healthy, latency) {
            const score = healthy ? Math.max(0, 100 - (latency / 10)) : 0; // Prosta metryka
            this.healthData[url] = {
                healthy: healthy,
                latency: latency,
                score: Math.round(score),
                timestamp: Date.now()
            };
        }

        getScore(url) {
            const data = this.healthData[url];
            if (!data) return 0;
            // Przeterminowane dane to również niski wynik
            if (data.timestamp < (Date.now() - CONFIG.HEALTH_CHECK_INTERVAL * 2)) return 0; 
            return data.score;
        }
    }

    /**
     * Klasa do zarządzania instancjami, wybierania najlepszej i przechowywania listy.
     */
    class InstanceManager {
        constructor() {
            this.instances = {};
            this.healthMonitor = new HealthMonitor();
            this.currentNetwork = 'clearnet';
        }

        async initialize() {
            await this.healthMonitor.initialize();
            this.currentNetwork = scriptConfig.get('network') || 'clearnet';
            this.instances = await GMCompat.getValue('allInstances', STATIC_INSTANCES);
            
            // Wymuś inicjalizację instancji statycznych, jeśli są puste
            if (Object.keys(this.instances).length === 0) {
                 this.instances = STATIC_INSTANCES;
            }
        }

        async setNetwork(network) {
            this.currentNetwork = network;
            await scriptConfig.set('network', network);
            // Uruchomienie ponownego sprawdzania kondycji po zmianie sieci może być przydatne
            this.healthMonitor.checkAllInstances(); 
        }

        getNetwork() {
            return this.currentNetwork;
        }

        getBestInstance(service) {
            if (!scriptConfig.get('services')[service]) return null;
            
            const networkInstances = this.instances[this.currentNetwork] || {};
            const serviceUrls = networkInstances[service] || [];
            
            let bestUrl = null;
            let bestScore = -1;

            serviceUrls.forEach(url => {
                const score = this.healthMonitor.getScore(url);
                if (score >= (scriptConfig.get('minInstanceScore') || CONFIG.MIN_INSTANCE_SCORE) && score > bestScore) {
                    bestScore = score;
                    bestUrl = url;
                }
            });

            if (bestUrl) {
                console.log(`[InstanceManager] Best instance for ${service} (${this.currentNetwork}): ${bestUrl} (Score: ${bestScore})`);
            } else {
                console.warn(`[InstanceManager] No healthy instance found for ${service} (${this.currentNetwork}).`);
                if (scriptConfig.get('notificationsEnabled')) {
                    showNotification('Brak instancji', `Brak dostępnych zdrowych instancji dla serwisu: ${service}.`);
                }
            }

            return bestUrl;
        }
    }

    /**
     * Klasa do obsługi logiki przekierowania URL i omijania krótkich linków.
     */
    class URLProcessor {
        constructor(manager) {
            this.manager = manager;
        }

        // Metoda do przekierowania głównej strony
        getProxyUrl(originalUrl) {
            try {
                const url = new URL(originalUrl);
                const hostname = url.hostname.replace(/^www\./, '');

                for (const service in SERVICE_PATTERNS) {
                    const pattern = SERVICE_PATTERNS[service];
                    const match = hostname.match(pattern.regex);

                    if (match && scriptConfig.get('services')[service]) {
                        const bestInstance = this.manager.getBestInstance(service);
                        if (bestInstance) {
                            let targetPath;
                            if (typeof pattern.targetPath === 'function') {
                                targetPath = pattern.targetPath(match);
                                if (!targetPath) return null; // W przypadku np. braku 'q' w zapytaniu Google
                            } else {
                                targetPath = pattern.targetPath.replace(/\$([0-9])/g, (m, index) => match[parseInt(index)] || '');
                            }
                            
                            const fullUrl = new URL(targetPath, bestInstance).href;
                            console.log(`[URLProcessor] Redirect: ${originalUrl} -> ${fullUrl}`);
                            return fullUrl;
                        }
                    }
                }
            } catch (e) {
                console.error('[URLProcessor] Error processing URL:', e);
            }
            return null;
        }
        
        // Metoda do omijania krótkich linków, wykorzystująca GM.xmlHttpRequest do śledzenia 302
        async bypassShortlink(shortUrl) {
            if (!scriptConfig.get('bypassShortlinks')) return null;
            
            try {
                const urlObj = new URL(shortUrl);
                const hostname = urlObj.hostname.replace(/^www\./, '');
                
                // Szybkie sprawdzenie czy to znana domena skracacza
                let isShortlinkDomain = false;
                BYPASS_DOMAINS.forEach(d => {
                    if (hostname.endsWith(d)) {
                        isShortlinkDomain = true;
                    }
                });
                
                if (!isShortlinkDomain) return null;
                
                console.log(`[Bypass] Attempting to bypass shortlink: ${shortUrl}`);
                
                return new Promise((resolve, reject) => {
                    let finalUrl = shortUrl;
                    let redirects = 0;

                    const followRedirect = (url) => {
                        if (redirects >= CONFIG.SHORTLINK_MAX_REDIRECTS) {
                            console.warn('[Bypass] Max redirects reached.');
                            return resolve(finalUrl); // Zwróć ostatni znany URL
                        }

                        GMCompat.xmlHttpRequest({
                            method: 'HEAD',
                            url: url,
                            onload: function(response) {
                                if (response.status >= 300 && response.status < 400 && response.finalUrl) {
                                    redirects++;
                                    finalUrl = response.finalUrl;
                                    console.log(`[Bypass] Redirect ${redirects} to: ${finalUrl}`);
                                    followRedirect(finalUrl);
                                } else {
                                    // Status 200, 4xx, 5xx lub 3xx bez finalUrl
                                    resolve(finalUrl);
                                }
                            },
                            onerror: function() {
                                resolve(finalUrl); 
                            },
                            ontimeout: function() {
                                resolve(finalUrl);
                            },
                            timeout: CONFIG.INSTANCE_TIMEOUT,
                            anonymous: true
                        });
                    };
                    
                    followRedirect(shortUrl);
                });

            } catch (e) {
                console.error('[Bypass] Error during shortlink bypass:', e);
                return null;
            }
        }
    }

    /**
     * Klasa do dynamicznego przepisywania linków na stronie.
     */
    class LinkRewriter {
        constructor(processor) {
            this.processor = processor;
            this.processedLinks = new Set();
            this.observer = new IntersectionObserver(this.handleIntersections.bind(this), { threshold: 0 });
        }

        initialize() {
            if (scriptConfig.get('linkRewriting')) {
                // Przetwarzanie wszystkich istniejących linków przy starcie
                this.setupInitialLinks();
                // Obserwowanie nowych linków dodawanych do DOM
                this.observeNewLinks();
            }
        }

        setupInitialLinks() {
            document.querySelectorAll('a[href]').forEach(a => this.processLink(a));
        }

        observeNewLinks() {
            const mutationObserver = new MutationObserver(mutationsList => {
                for (const mutation of mutationsList) {
                    if (mutation.type === 'childList') {
                        mutation.addedNodes.forEach(node => {
                            if (node.nodeType === 1) { // ELEMENT_NODE
                                if (node.tagName === 'A' && node.hasAttribute('href')) {
                                    this.processLink(node);
                                }
                                node.querySelectorAll('a[href]').forEach(a => this.processLink(a));
                            }
                        });
                    }
                }
            });

            mutationObserver.observe(document.body || document.documentElement, { childList: true, subtree: true });
        }

        processLink(a) {
            // Unikaj przetwarzania linków, które już zostały zmienione lub są w trakcie
            if (this.processedLinks.has(a) || a.dataset.proxySfProcessed) return;

            const originalHref = a.href;
            if (!originalHref) return;
            
            try {
                const url = new URL(originalHref);
                const domain = url.hostname.replace(/^www\./, '');

                // 1. Sprawdzenie, czy link to skracacz
                if (Array.from(BYPASS_DOMAINS).some(d => domain.endsWith(d))) {
                    // Dodaj atrybut do ominięcia (np. w kontekście kliknięcia)
                    a.dataset.proxySfShortlink = originalHref;
                    a.dataset.proxySfProcessed = 'true';
                    this.processedLinks.add(a);
                    a.addEventListener('click', this.handleShortlinkClick.bind(this, a), { once: true });
                    return;
                }
                
                // 2. Sprawdzenie, czy link wymaga przekierowania proxy
                const proxyUrl = this.processor.getProxyUrl(originalHref);
                if (proxyUrl && proxyUrl !== originalHref) {
                    a.setAttribute('href', proxyUrl);
                    a.dataset.proxySfOriginal = originalHref; // Przechowaj oryginał
                    a.dataset.proxySfProcessed = 'true';
                    this.processedLinks.add(a);
                }

            } catch (e) {
                //console.error('[LinkRewriter] Invalid URL:', originalHref);
            }
        }
        
        async handleShortlinkClick(a, event) {
            event.preventDefault(); // Zatrzymaj domyślną akcję
            
            const shortUrl = a.dataset.proxySfShortlink;
            if (!shortUrl) return;

            // Tymczasowy wizualny feedback
            a.style.opacity = '0.5'; 
            const originalText = a.textContent;
            a.textContent = 'Bypassing...';

            const finalUrl = await this.processor.bypassShortlink(shortUrl);
            
            a.style.opacity = '1';
            a.textContent = originalText;

            if (finalUrl && finalUrl !== shortUrl) {
                // Po udanym ominięciu, spróbuj przekierować do proxy lub otwórz finalny URL
                const proxyUrl = this.processor.getProxyUrl(finalUrl);
                const targetUrl = proxyUrl || finalUrl;

                console.log(`[Bypass] Final destination: ${targetUrl}. Redirecting...`);
                window.location.href = targetUrl;
            } else {
                 console.log(`[Bypass] Could not bypass. Redirecting to original: ${shortUrl}.`);
                 window.location.href = shortUrl;
            }
        }
        
        handleIntersections(entries) {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.processLink(entry.target);
                    this.observer.unobserve(entry.target);
                }
            });
        }
    }


    /**
     * Główna klasa obsługująca ładowanie i przekierowanie strony.
     */
    class PageHandler {
        constructor(processor, manager) {
            this.processor = processor;
            this.manager = manager;
            this.linkRewriter = new LinkRewriter(processor);
        }

        initialize() {
            // Krok 1: Przekierowanie strony (jeśli jesteśmy na obsługiwanym serwisie)
            this.handlePageRedirect();

            // Krok 2: Uruchomienie przepisywania linków (jeśli nie przekierowaliśmy)
            if (scriptConfig.get('linkRewriting')) {
                this.linkRewriter.initialize();
            }
        }

        handlePageRedirect() {
            const currentUrl = window.location.href;
            const proxyUrl = this.processor.getProxyUrl(currentUrl);

            if (proxyUrl && scriptConfig.get('autoRedirect')) {
                // Jeśli URL to proxy, ale jest uszkodzone, NIE przekierowuj dalej!
                if (currentUrl.startsWith(proxyUrl.substring(0, proxyUrl.indexOf('/', 8)))) {
                    console.log('[PageHandler] Already on a proxy instance. Aborting redirect.');
                    return;
                }
                
                if (scriptConfig.get('showLoadingPage')) {
                    this.showLoadingPage(proxyUrl);
                } else {
                    window.location.replace(proxyUrl);
                }
            }
        }

        showLoadingPage(redirectUrl) {
            // Prosta strona ładowania zgodna z estetyką
            const style = `
                body { background: #1a1a2e; color: #e0e0e0; font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; overflow: hidden; }
                .loader-container { text-align: center; }
                .loader { border: 8px solid #30264b; border-top: 8px solid #8B5CF6; border-radius: 50%; width: 60px; height: 60px; animation: spin 2s linear infinite; margin: 20px auto; }
                @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
                .progress-bar-container { width: 250px; height: 8px; background: #30264b; border-radius: 4px; margin: 10px auto; overflow: hidden; }
                .progress-bar { height: 100%; width: 0%; background: #8B5CF6; transition: width 0.1s linear; }
                h1 { font-size: 1.5em; }
                p { color: #aaa; }
            `;

            document.documentElement.innerHTML = `
                <head>
                    <title>Redirecting...</title>
                    <style>${style}</style>
                </head>
                <body>
                    <div class="loader-container">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="100" height="100" fill="#8B5CF6" style="margin-bottom: 20px;">
                             <text y="400" font-size="400">🔒</text> 
                        </svg>
                        <h1>Auto-PROXY-SF</h1>
                        <p>Przekierowanie do prywatnej instancji...</p>
                        <div class="loader"></div>
                        <div class="progress-bar-container">
                            <div class="progress-bar" id="proxy-sf-progress"></div>
                        </div>
                        <p id="proxy-sf-countdown">3 sekundy do przekierowania...</p>
                    </div>
                </body>
            `;
            
            const totalTime = 3000;
            let elapsed = 0;
            const interval = 100;

            const progressBar = document.getElementById('proxy-sf-progress');
            const countdownText = document.getElementById('proxy-sf-countdown');

            const timer = setInterval(() => {
                elapsed += interval;
                const progress = (elapsed / totalTime) * 100;
                
                if (progressBar) {
                    progressBar.style.width = `${progress}%`;
                }
                
                const remainingTime = Math.max(0, totalTime - elapsed);
                if (countdownText) {
                    countdownText.textContent = `${(remainingTime / 1000).toFixed(1)} sekundy do przekierowania...`;
                }

                if (elapsed >= totalTime) {
                    clearInterval(timer);
                    window.location.replace(redirectUrl);
                }
            }, interval);
        }
    }


    // Główna funkcja skryptu
    async function main() {
        if (!scriptConfig.get('enabled')) {
            console.log('[Auto-PROXY-SF] Script is disabled in configuration.');
            return;
        }

        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();

        // Usuń stare menu, jeśli istnieje, i zarejestruj nowe (dla czystości)
        if (typeof GMCompat.unregisterMenuCommand === 'function') {
             // W prawdziwej implementacji trzeba by zachować ID, ale dla bezpieczeństwa użyjemy
             // prostego mechanizmu rejestracji, zakładając, że MonkeyConfig obsłuży własne polecenia.
        }

        // Menu commands (uzupełnione o aktualny status)
        let currentNetwork = await scriptConfig.get('network');
        
        GMCompat.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);
            await scriptConfig.set('network', newNetwork);
            showNotification('Network Switched', `Switched to ${newNetwork.toUpperCase()}. Reloading page...`);
            setTimeout(() => window.location.reload(), 500);
        });

        GMCompat.registerMenuCommand('⚡ Re-Check Instances (Now)', async function() {
            showNotification('Health Check', 'Starting manual instance health check...');
            await manager.healthMonitor.checkAllInstances();
            showNotification('Health Check Complete', 'Instance scores updated.');
        });
        
        GMCompat.registerMenuCommand('🗑️ Clear Instance Cache', async function() {
            await GMCompat.deleteValue('healthData');
            manager.healthMonitor.healthData = {};
            showNotification('Cache Cleared', 'Instance health data has been reset.');
        });

        GMCompat.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();
    }

})();