// ==UserScript==
// @name:en Auto-PROXY-SF
// @description:en Advanced proxy redirection with intelligent instance selection, automatic scraping, shortlink bypassing and full configuration.
// @name Auto-PROXY-SF
// @description Zaawansowane przekierowanie proxy z inteligentnym wyborem instancji, automatycznym scrapingiem, omijaniem shortlinków i pełną konfiguracją.
// @namespace https://anonymousik.is-a.dev/userscripts
// @version 2.0.2
// @author Anonymousik
// @homepageURL https://anonymousik.is-a.dev
// @supportURL https://anonymousik.is-a.dev
// @license AGPL-3.0-only
// @match *://*/*
// @grant GM.getValue
// @grant GM.setValue
// @grant GM.deleteValue
// @grant GM.xmlHttpRequest
// @grant GM.registerMenuCommand
// @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';
// === Zmienne Globalne ===
let scriptConfig;
const MONKEY_CONFIG_LOADED = typeof MonkeyConfig !== 'undefined';
if (!MONKEY_CONFIG_LOADED) {
console.error(
'[Auto-PROXY-SF] BŁĄD KRYTYCZNY: Biblioteka MonkeyConfig Mod nie została załadowana. ' +
'Interfejs konfiguracyjny będzie niedostępny. Skrypt będzie działał z ustawieniami domyślnymi. ' +
'Sprawdź, czy adres URL w dyrektywie @require jest poprawny oraz czy masz aktywne połączenie z internetem.'
);
}
const CONFIG = {
VERSION: '2.0.2',
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
};
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: 'Konfiguracja Auto-PROXY-SF',
menuCommand: true,
params: {
enabled: { label: 'Włącz Auto-PROXY-SF', type: 'checkbox', default: true },
network: { label: 'Preferowana sieć', type: 'select', options: ['clearnet', 'i2p'], default: 'clearnet' },
autoRedirect: { label: 'Automatyczne przekierowanie strony', type: 'checkbox', default: true },
linkRewriting: { label: 'Dynamiczne przepisywanie linków', type: 'checkbox', default: true },
bypassShortlinks: { label: 'Omijaj skrócone linki (shortlinki)', type: 'checkbox', default: true },
showLoadingPage: { label: 'Pokazuj animację ładowania', type: 'checkbox', default: true },
autoUpdateInstances: { label: 'Automatyczna aktualizacja instancji', type: 'checkbox', default: true },
notificationsEnabled: { label: 'Włącz powiadomienia', type: 'checkbox', default: true },
minInstanceScore: { label: 'Minimalna ocena instancji', type: 'number', default: 30, min: 0, max: 100 },
healthCheckInterval: { label: 'Częstotliwość sprawdzania (minuty)', type: 'number', default: 5, min: 1, max: 60 },
instanceTimeout: { label: 'Limit czasu instancji (sekundy)', type: 'number', default: 5, min: 1, max: 30 },
parallelChecks: { label: 'Równoległe sprawdzanie instancji', type: 'number', default: 6, min: 1, max: 20 },
services: {
label: 'Włączone serwisy', 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] Konfiguracja została pomyślnie zapisana.');
if (scriptConfig.get('notificationsEnabled')) {
showNotification('Konfiguracja zapisana', 'Ustawienia zostały zaktualizowane.');
}
if (window.autoProxyInstance) {
window.autoProxyInstance.setupMenu();
}
}
}
};
// === Inicjalizacja Konfiguracji (z trybem awaryjnym) ===
if (MONKEY_CONFIG_LOADED) {
try {
scriptConfig = new MonkeyConfig(configDefinition);
} catch (error) {
console.error('[Auto-PROXY-SF] Wystąpił błąd podczas inicjalizacji MonkeyConfig:', error);
// Awaryjne przejście do obiektu zastępczego, jeśli konstruktor zawiedzie
scriptConfig = createFallbackConfig();
}
} else {
scriptConfig = createFallbackConfig();
}
function createFallbackConfig() {
// Tworzy mapę domyślnych wartości
const defaults = {};
for (const key in configDefinition.params) {
const param = configDefinition.params[key];
defaults[key] = param.default;
if (param.children) {
for (const childKey in param.children) {
defaults[`services.${childKey}`] = param.children[childKey].default;
}
}
}
// Zwraca obiekt zastępczy
return {
get: function(key) {
return key in defaults ? defaults[key] : true;
},
set: function() {
console.warn('[Auto-PROXY-SF] Próba zapisu ustawień w trybie awaryjnym. Zmiany nie zostaną zapisane.');
},
open: function() {
alert(
'Panel konfiguracyjny Auto-PROXY-SF jest niedostępny.\n\n' +
'Powód: Zewnętrzna biblioteka "MonkeyConfig Mod" nie została załadowana.\n\n' +
'Możliwe przyczyny:\n' +
'- Problemy z połączeniem internetowym.\n' +
'- Chwilowa niedostępność serwisu GreasyFork.\n' +
'- Zmiana adresu URL biblioteki w skrypcie.\n\n' +
'Skrypt działa w tle z domyślnymi ustawieniami.'
);
},
isFallback: true
};
}
function showNotification(title, message) {
if (scriptConfig.get('notificationsEnabled')) {
try {
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(['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']);
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\.)?(?:youtube\.com|youtu\.be|youtube-nocookie\.com|m\.youtube\.com)$/, priority: 10, pathBuilder: (url) => { const v = url.searchParams.get('v'); if (v) return `/watch?v=${v}`; const p = url.pathname.match(/^\/(watch|embed|shorts|live)\/([^/?]+)/); if (p) return `/${p[1]}/${p[2]}${url.search}`; return url.pathname + url.search; } }, nitter: { regex: /^(?:www\.)?(?:twitter\.com|x\.com|mobile\.twitter\.com|mobile\.x\.com)$/, priority: 9, pathBuilder: (url) => url.pathname + url.search }, libreddit: { regex: /^(?:www\.)?(?:old\.)?(?:new\.)?reddit\.com$/, priority: 8, pathBuilder: (url) => url.pathname + url.search }, teddit: { regex: /^(?:www\.)?(?:old\.)?reddit\.com$/, priority: 7, pathBuilder: (url) => url.pathname + url.search }, searx: { regex: /^(?:www\.)?(?:google\.com|google\.[a-z]{2,3}|bing\.com|duckduckgo\.com|yahoo\.com)$/, pathCheck: /^\/search/, priority: 8, pathBuilder: (url) => { const q = url.searchParams.get('q') || url.searchParams.get('query'); return q ? `/search?q=${encodeURIComponent(q)}` : '/'; } }, proxitok: { regex: /^(?:www\.)?(?:tiktok\.com|m\.tiktok\.com)$/, priority: 7, pathBuilder: (url) => url.pathname + url.search }, rimgo: { regex: /^(?:www\.)?(?:imgur\.com|i\.imgur\.com|m\.imgur\.com)$/, priority: 6, pathBuilder: (url) => url.pathname + url.search }, scribe: { regex: /^(?:www\.)?medium\.com$|^[^.]+\.medium\.com$/, priority: 6, pathBuilder: (url) => url.pathname + url.search }, quetre: { regex: /^(?:www\.)?quora\.com$/, priority: 5, pathBuilder: (url) => url.pathname + url.search }, libremdb: { regex: /^(?:www\.)?imdb\.com$/, priority: 5, pathBuilder: (url) => url.pathname + url.search }, breezewiki: { regex: /^[^.]+\.fandom\.com$|^[^.]+\.wikia\.com$/, priority: 4, pathBuilder: (url) => `/${url.hostname.split('.')[0]}${url.pathname}${url.search}` }, anonymousoverflow: { regex: /^(?:www\.)?stackoverflow\.com$|^(?:www\.)?stackexchange\.com$/, pathCheck: /^\/questions/, priority: 4, pathBuilder: (url) => url.pathname + url.search }, bibliogram: { regex: /^(?:www\.)?instagram\.com$/, priority: 6, pathBuilder: (url) => url.pathname + url.search }, wikiless: { regex: /^(?:www\.)?(?:[a-z]{2,3}\.)?wikipedia\.org$/, priority: 5, pathBuilder: (url) => url.pathname + url.search } };
// === Klasy Główne (bez zmian w logice) ===
class LoadingPage { static show(targetUrl, instanceUrl, service) { const h = new URL(instanceUrl).hostname, s = service.charAt(0).toUpperCase() + service.slice(1), c = `<!DOCTYPE html><html lang="pl"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Auto-PROXY-SF - Przekierowanie</title><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Segoe UI',system-ui,-apple-system,sans-serif;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);min-height:100vh;display:flex;justify-content:center;align-items:center;color:#fff;overflow:hidden}.container{text-align:center;padding:2rem;max-width:600px;animation:fadeIn .5s ease-in}@keyframes fadeIn{from{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translateY(0)}}.logo{font-size:4rem;margin-bottom:1rem;animation:pulse 2s infinite}@keyframes pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.1)}}h1{font-size:2.5rem;margin-bottom:.5rem;font-weight:700;text-shadow:2px 2px 4px rgba(0,0,0,.3)}.version{font-size:.9rem;opacity:.7;margin-bottom:1rem}.subtitle{font-size:1.2rem;margin-bottom:2rem;opacity:.9}.loader{width:60px;height:60px;border:5px solid rgba(255,255,255,.3);border-top:5px solid #fff;border-radius:50%;margin:2rem auto;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.status{font-size:1rem;margin-top:2rem;padding:1.5rem;background:rgba(255,255,255,.1);border-radius:10px;backdrop-filter:blur(10px)}.service-info{font-size:1.1rem;font-weight:600;margin-bottom:.5rem;color:#ffd700}.instance-info{margin-top:.5rem;font-size:.9rem;opacity:.8;word-break:break-all}.progress-bar{width:100%;height:4px;background:rgba(255,255,255,.2);border-radius:2px;margin-top:2rem;overflow:hidden}.progress-fill{height:100%;background:#fff;width:0;animation:progress 2.5s ease-in-out}@keyframes progress{0%{width:0}100%{width:100%}}.footer{margin-top:3rem;font-size:.85rem;opacity:.7}.footer a{color:#fff;text-decoration:none;border-bottom:1px solid rgba(255,255,255,.3);transition:border-color .3s}.footer a:hover{border-bottom-color:#fff}.particles{position:fixed;top:0;left:0;width:100%;height:100%;z-index:-1}</style></head><body><div class="container"><div class="logo">🔏</div><h1>Auto-PROXY-SF</h1><p class="version">v${CONFIG.VERSION}</p><p class="subtitle">Trwa przekierowanie do serwisu dbającego o prywatność...</p><div class="loader"></div><div class="status"><p class="service-info">Przekierowanie do instancji ${s}</p><p>Proszę czekać, szukamy dla Ciebie najlepszej instancji.</p><p class="instance-info">Instancja: ${h}</p></div><div class="progress-bar"><div class="progress-fill"></div></div><div class="footer"><a href="https://anonymousik.is-a.dev" target="_blank" rel="noopener">Strona główna</a> | <a href="${targetUrl}" target="_blank" rel="noopener">Oryginalny URL</a></div></div><canvas class="particles"></canvas><script>setTimeout(()=>{window.location.replace('${targetUrl}')},2500);const canvas=document.querySelector('.particles'),ctx=canvas.getContext('2d');canvas.width=window.innerWidth,canvas.height=window.innerHeight;let particles=[],particleCount=100;class Particle{constructor(){this.x=Math.random()*canvas.width,this.y=Math.random()*canvas.height,this.size=Math.random()*2+1,this.speedX=Math.random()*2-1,this.speedY=Math.random()*2-1,this.color="rgba(255, 255, 255, 0.5)"}update(){this.x+=this.speedX,this.y+=this.speedY,this.size>.1&&(this.size-=.01)}draw(){ctx.fillStyle=this.color,ctx.beginPath(),ctx.arc(this.x,this.y,this.size,0,2*Math.PI),ctx.fill()}}function initParticles(){for(let t=0;t<particleCount;t++)particles.push(new Particle)}function animateParticles(){ctx.clearRect(0,0,canvas.width,canvas.height);for(let t=0;t<particles.length;t++)particles[t].update(),particles[t].draw(),particles[t].size<=.1&&(particles.splice(t,1),particles.push(new Particle),t--);requestAnimationFrame(animateParticles)}initParticles(),animateParticles(),window.addEventListener("resize",()=>{canvas.width=window.innerWidth,canvas.height=window.innerHeight,particles=[],initParticles()});<\/script></body></html>`; document.documentElement.innerHTML = c; } }
class Scraper { constructor() { this.scrapedInstances = {}; } async initialize() { try { const d = JSON.parse(await GMCompat.getValue('scrapedInstances', '{}')); if (d.timestamp && Date.now() - d.timestamp < CONFIG.SCRAPER_UPDATE_INTERVAL) { this.scrapedInstances = d.instances; console.log('[Auto-PROXY-SF] Załadowano instancje z pamięci podręcznej.'); } else if (scriptConfig.get('autoUpdateInstances')) { console.log('[Auto-PROXY-SF] Dane instancji są przestarzałe. Aktualizuję.'); await this.updateAll(); } } catch (e) { console.error('[Auto-PROXY-SF] Błąd inicjalizacji scrapera:', e); if (scriptConfig.get('autoUpdateInstances')) await this.updateAll(); } } async updateAll(force = false) { if (!force) { const l = await GMCompat.getValue('lastScraperUpdate', 0); if (Date.now() - l < CONFIG.SCRAPER_UPDATE_INTERVAL) return; } showNotification('Aktualizacja instancji', 'Pobieranie najnowszych list...'); await Promise.all(Object.keys(INSTANCE_SOURCES).map(s => this.scrapeService(s))); await GMCompat.setValue('scrapedInstances', JSON.stringify({ instances: this.scrapedInstances, timestamp: Date.now() })); await GMCompat.setValue('lastScraperUpdate', Date.now()); showNotification('Aktualizacja zakończona', 'Listy instancji zostały zaktualizowane.'); } async scrapeService(service) { const sources = INSTANCE_SOURCES[service].sort((a, b) => a.priority - b.priority); let inst = new Set(); for (const s of sources) { try { const d = await this.fetchSource(s.url); const p = this['parse' + s.parser.charAt(0).toUpperCase() + s.parser.slice(1)]; if (p) p.call(this, d).forEach(i => inst.add(i)); } catch (e) { console.warn(`[Auto-PROXY-SF] Błąd pobierania źródła ${s.url}:`, e); } } this.scrapedInstances[service] = [...inst]; console.log(`[Auto-PROXY-SF] Zebrano ${this.scrapedInstances[service].length} instancji dla ${service}.`); } fetchSource(url) { return new Promise((resolve, reject) => { GMCompat.xmlHttpRequest({ method: 'GET', url, timeout: CONFIG.SCRAPER_TIMEOUT, onload: r => r.status >= 200 && r.status < 300 ? resolve(r.responseText) : reject(`HTTP Error ${r.status}`), onerror: reject, ontimeout: () => reject('Timeout') }); }); } parseInvidiousAPI(d) { try { return JSON.parse(d).map(i => i[1] && i[1].uri && i[1].type === 'https' && i[1].api !== false ? i[1].uri : null).filter(Boolean); } catch (e) { return []; } } parseNitterJSON(d) { try { const j = JSON.parse(d); let i = []; if (j.hosts) i = j.hosts.map(h => h.url).filter(Boolean); else if (Array.isArray(j)) i = j.filter(u => typeof u === 'string'); return i.map(u => u.replace(/\/$/, '')); } catch (e) { return []; } } parseGenericJSON(d) { try { const j = JSON.parse(d); if (Array.isArray(j)) return j.map(i => (typeof i === 'string' ? i : i.url || i.uri || i.instance) || null).filter(Boolean).map(u => u.replace(/\/$/, '')); if (j.instances) return this.parseGenericJSON(JSON.stringify(j.instances)); return []; } catch (e) { return []; } } parseSearxSpace(d) { try { const j = JSON.parse(d); return Object.entries(j.instances).filter(([, i]) => i.http?.status_code === 200 && i.network_type !== 'tor').map(([u]) => (u.startsWith('http') ? u : `https://${u}`).replace(/\/$/, '')); } catch (e) { return []; } } parseMarkdownTable(d) { const u = new Set(); d.split('\n').forEach(l => { (l.match(/https?:\/\/[^\s)\]|<>"]+/g) || []).forEach(m => u.add(m.replace(/\/$/, ''))); }); return [...u]; } parseMarkdownList(d) { return this.parseMarkdownTable(d); } parseGithubWiki(d) { const u = new Set(); (d.match(/https?:\/\/[^\s<>"'\)]+/g) || []).forEach(m => { const c = m.replace(/[.,;!?]+$/, '').replace(/\/$/, ''); if (c && !/github\.(com|usercontent)/.test(c)) u.add(c); }); return [...u]; } getInstances(s) { return this.scrapedInstances[s] || []; } }
class HealthMonitor { constructor() { this.healthData = {}; this.checking = new Set(); this.checkQueue = []; this.queueProcessing = false; } async initialize() { try { this.healthData = JSON.parse(await GMCompat.getValue('healthData', '{}')); this.cleanExpiredData(); } catch (e) { this.healthData = {}; } } cleanExpiredData() { const n = Date.now(), t = CONFIG.CACHE_EXPIRY; Object.keys(this.healthData).forEach(u => { if (n - this.healthData[u].lastCheck > t) delete this.healthData[u]; }); } async checkHealth(url) { if (this.checking.has(url)) return; const c = this.healthData[url]; if (c && Date.now() - c.lastCheck < scriptConfig.get('healthCheckInterval') * 60000) return c.healthy; return new Promise(r => { this.checkQueue.push({ url, resolve: r }); this.processQueue(); }); } async processQueue() { if (this.queueProcessing || this.checkQueue.length === 0) return; this.queueProcessing = true; while (this.checkQueue.length > 0) { const batch = this.checkQueue.splice(0, scriptConfig.get('parallelChecks')); await Promise.all(batch.map(i => this.performHealthCheck(i.url).then(i.resolve))); } this.queueProcessing = false; } performHealthCheck(url) { this.checking.add(url); const t = scriptConfig.get('instanceTimeout') * 1000; return new Promise(r => { const s = Date.now(); const fin = (h, l) => { this.checking.delete(url); this.updateHealth(url, h, l); GMCompat.setValue('healthData', JSON.stringify(this.healthData)); r(h); }; const tid = setTimeout(() => fin(false, null), t); GMCompat.xmlHttpRequest({ method: 'HEAD', url, timeout: t, anonymous: true, headers: { 'User-Agent': `Auto-PROXY-SF/${CONFIG.VERSION}` }, onload: res => { clearTimeout(tid); fin(res.status >= 200 && res.status < 400, Date.now() - s); }, onerror: () => { clearTimeout(tid); fin(false, null); }, ontimeout: () => { clearTimeout(tid); fin(false, null); } }); }); } updateHealth(url, h, l) { const d = this.healthData[url] || { healthy: false, lastCheck: 0, failures: 0, successes: 0, latencies: [], avgLatency: 0, reliability: 0 }; d.healthy = h; d.lastCheck = Date.now(); if (h) { d.successes++; d.failures = Math.max(0, d.failures - 1); if (l != null) { d.latencies.push(l); if (d.latencies.length > 10) d.latencies.shift(); d.avgLatency = Math.round(d.latencies.reduce((a, b) => a + b, 0) / d.latencies.length); } } else { d.failures++; } const total = d.successes + d.failures; d.reliability = total > 0 ? (d.successes / total) * 100 : 0; this.healthData[url] = d; } getScore(url) { const d = this.healthData[url]; if (!d?.healthy) return 0; let s = 40 + Math.max(5, 30 - Math.floor((d.avgLatency || 500) / 100) * 3) + (d.reliability >= 90 ? 20 : d.reliability >= 70 ? 15 : d.reliability >= 50 ? 10 : 5) + Math.max(0, 10 - d.failures); return Math.min(100, s); } getBestInstance(inst) { if (!inst?.length) return null; const min = scriptConfig.get('minInstanceScore'); const s = inst.map(u => ({ u, s: this.getScore(u) })).filter(i => i.s >= min).sort((a, b) => b.s - a.s); if (s.length > 0) return s[0].u; return inst[0]; } }
class InstanceManager { constructor(s) { this.scraper = s; this.healthMonitor = new HealthMonitor(); } async initialize() { await this.healthMonitor.initialize(); } async getInstances(s) { if (scriptConfig.get('network') === 'i2p') return STATIC_INSTANCES.i2p[s] || []; const scr = this.scraper.getInstances(s), sta = STATIC_INSTANCES.clearnet[s] || []; return [...new Set([...scr, ...sta])]; } async getBestInstance(s, r = 0) { const i = await this.getInstances(s); if (i.length === 0) return null; await Promise.all(i.slice(0, scriptConfig.get('parallelChecks') * 2).map(u => this.healthMonitor.checkHealth(u))); const b = this.healthMonitor.getBestInstance(i); if (!b && r < CONFIG.MAX_RETRY_ATTEMPTS) { await new Promise(res => setTimeout(res, CONFIG.EXPONENTIAL_BACKOFF_BASE * 2 ** r)); return this.getBestInstance(s, r + 1); } return b; } }
class URLProcessor { constructor(m) { this.manager = m; this.processed = new WeakSet(); } detectService(h, p) { const srv = Object.keys(SERVICE_PATTERNS).sort((a, b) => SERVICE_PATTERNS[b].priority - SERVICE_PATTERNS[a].priority); for (const s of srv) { if (!scriptConfig.get(`services.${s}`)) continue; const pat = SERVICE_PATTERNS[s]; if (pat.regex.test(h) && (!pat.pathCheck || pat.pathCheck.test(p))) return s; } return null; } async processURL(origUrl) { try { let url = new URL(origUrl); if (scriptConfig.get('bypassShortlinks') && this.isShortlink(url.hostname)) { const finalUrl = await this.bypassShortlink(url.href); url = new URL(finalUrl); } const service = this.detectService(url.hostname, url.pathname); if (!service) return null; const inst = await this.manager.getBestInstance(service); if (!inst) return null; return inst + SERVICE_PATTERNS[service].pathBuilder(url); } catch (e) { return null; } } isShortlink(h) { if (BYPASS_DOMAINS.has(h)) return true; for (const p of SHORTLINK_PATTERNS) if (p.test(h)) return true; return false; } bypassShortlink(url) { return new Promise(r => { GMCompat.xmlHttpRequest({ method: 'HEAD', url, onload: res => r(res.finalUrl && res.finalUrl !== url ? res.finalUrl : url), onerror: () => r(url), ontimeout: () => r(url) }); }); } rewriteLinks() { const obs = new MutationObserver(m => m.forEach(mu => mu.addedNodes.forEach(n => { if (n.nodeType === 1) { n.querySelectorAll('a[href]').forEach(l => this.processLink(l)); if (n.matches?.('a[href]')) this.processLink(n); } }))); obs.observe(document.documentElement, { childList: true, subtree: true }); document.querySelectorAll('a[href]').forEach(l => this.processLink(l)); } async processLink(l) { if (this.processed.has(l) || !l.href.startsWith('http')) return; this.processed.add(l); const n = await this.processURL(l.href); if (n) l.href = n; } }
// === Główna klasa Aplikacji ===
class AutoProxySF {
constructor() {
this.scraper = new Scraper();
this.instanceManager = new InstanceManager(this.scraper);
this.urlProcessor = new URLProcessor(this.instanceManager);
this.menuCommands = {};
}
async initialize() {
if (!scriptConfig.get('enabled')) {
console.log('[Auto-PROXY-SF] Skrypt jest wyłączony w konfiguracji.');
return;
}
console.log(`[Auto-PROXY-SF] Inicjalizacja v${CONFIG.VERSION}`);
// Inicjalizacja tylko jeśli nie jesteśmy w trybie awaryjnym dla konfiguracji
if (!scriptConfig.isFallback) {
await this.scraper.initialize();
await this.instanceManager.initialize();
}
this.setupMenu();
if (scriptConfig.get('autoRedirect')) {
this.handlePageRedirect();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.onDOMLoaded());
} else {
this.onDOMLoaded();
}
}
onDOMLoaded() {
if (scriptConfig.get('linkRewriting')) {
this.urlProcessor.rewriteLinks();
}
}
async handlePageRedirect() {
const newUrl = await this.urlProcessor.processURL(window.location.href);
if (newUrl) {
const service = this.urlProcessor.detectService(new URL(window.location.href).hostname, window.location.pathname);
if (scriptConfig.get('showLoadingPage')) {
LoadingPage.show(newUrl, new URL(newUrl).origin, service || 'prywatny');
} else {
window.location.replace(newUrl);
}
}
}
setupMenu() {
Object.values(this.menuCommands).forEach(id => { try { if (id) GMCompat.unregisterMenuCommand(id); } catch (e) {} });
this.menuCommands = {};
// Dodaje polecenie otwarcia konfiguracji tylko jeśli nie jesteśmy w trybie awaryjnym
if (!scriptConfig.isFallback && MONKEY_CONFIG_LOADED) {
this.menuCommands.config = GMCompat.registerMenuCommand('⚙️ Otwórz Konfigurację', () => scriptConfig.open());
}
const onOff = (val) => val ? 'WŁ' : 'WYŁ';
this.menuCommands.forceUpdate = GMCompat.registerMenuCommand('➡️ Wymuś aktualizację list instancji', async () => {
showNotification('Aktualizacja ręczna', 'Wymuszanie aktualizacji list instancji...');
await this.scraper.updateAll(true);
});
this.menuCommands.clearCache = GMCompat.registerMenuCommand('🗑️ Wyczyść pamięć podręczną stanu', async () => {
await GMCompat.deleteValue('healthData');
await this.instanceManager.healthMonitor.initialize();
showNotification('Wyczyszczono pamięć podręczną', 'Dane o stanie instancji zostały usunięte.');
});
// Te opcje nie będą działać w trybie awaryjnym, ale zostawiamy je dla spójności
this.menuCommands.toggleRedirect = GMCompat.registerMenuCommand(`🔄 Przełącz przekierowanie: ${onOff(scriptConfig.get('autoRedirect'))}`, () => {
if (scriptConfig.isFallback) { alert('Ta opcja jest niedostępna w trybie awaryjnym.'); return; }
const current = scriptConfig.get('autoRedirect');
scriptConfig.set('autoRedirect', !current);
showNotification('Automatyczne przekierowanie', `Przekierowanie jest teraz ${!current ? 'WŁĄCZONE' : 'WYŁĄCZONE'}.`);
this.setupMenu();
});
this.menuCommands.toggleLinks = GMCompat.registerMenuCommand(`🔗 Przełącz przepisywanie linków: ${onOff(scriptConfig.get('linkRewriting'))}`, () => {
if (scriptConfig.isFallback) { alert('Ta opcja jest niedostępna w trybie awaryjnym.'); return; }
const current = scriptConfig.get('linkRewriting');
scriptConfig.set('linkRewriting', !current);
showNotification('Przepisywanie linków', `Przepisywanie linków jest teraz ${!current ? 'WŁĄCZONE' : 'WYŁĄCZONE'}.`);
this.setupMenu();
});
}
}
// === Uruchomienie Skryptu ===
(async function() {
// Czekaj na gotowość MonkeyConfig, jeśli istnieje
if (MONKEY_CONFIG_LOADED && typeof MonkeyConfig.isReady === 'function') {
await MonkeyConfig.isReady();
}
const autoProxy = new AutoProxySF();
window.autoProxyInstance = autoProxy;
autoProxy.initialize().catch(err => {
console.error('[Auto-PROXY-SF] Krytyczny błąd inicjalizacji:', err);
});
})();
})();