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