您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Advanced privacy proxy redirector with intelligent instance selection, automated scraping, bypass shortlinks, and comprehensive configuration
当前为
// ==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(); } })();