Auto-PROXY-SF

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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();
    }

})();