// ==UserScript==
// @name Auto-PROXY-SF
// @description Advanced privacy proxy redirector with intelligent instance selection and I2P support
// @namespace https://anonymousik.is-a.dev/userscripts
// @version 1.0.1
// @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
// @run-at document-start
// @connect nadeko.net
// @connect puffyan.us
// @connect yewtu.be
// @connect tux.pizza
// @connect nitter.net
// @connect xcancel.com
// @connect privacydev.net
// @connect spike.codes
// @connect privacy.com.de
// @connect searx.be
// @connect mdosch.de
// @connect pabloferreiro.es
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48dGV4dCB5PSI0MDAiIGZvbnQtc2l6ZT0iNDAwIj7wn5S3PC90ZXh0Pjwvc3ZnPg==
// ==/UserScript==
(function() {
'use strict';
// Configuration constants
const CONFIG = {
VERSION: '1.0.1',
HEALTH_CHECK_INTERVAL: 300000, // 5 minutes
INSTANCE_TIMEOUT: 4000, // 4 seconds
PARALLEL_CHECKS: 4,
MAX_RETRY_ATTEMPTS: 2
};
// Static instance lists - no external fetching
const I2P_INSTANCES = {
invidious: [
'http://inv.vern.i2p',
'http://inv.cn.i2p',
'http://ytmous.i2p',
'http://tube.i2p'
],
nitter: [
'http://tm4rwkeysv3zz3q5yacyr4rlmca2c4etkdobfvuqzt6vsfsu4weq.b32.i2p'
],
libreddit: [
'http://woo5ugmoomzbtaq6z46q4wgei5mqmc6jkafqfi5c37zni7xc4ymq.b32.i2p'
],
searx: [
'http://ransack.i2p',
'http://mqamk4cfykdvhw5kjez2gnvse56gmnqxn7vkvvbuor4k4j2lbbnq.b32.i2p'
],
proxitok: [
'http://qr.vern.i2p'
]
};
const CLEARNET_INSTANCES = {
invidious: [
'https://inv.nadeko.net',
'https://vid.puffyan.us',
'https://yewtu.be',
'https://inv.tux.pizza'
],
nitter: [
'https://nitter.net',
'https://xcancel.com',
'https://nitter.privacydev.net'
],
libreddit: [
'https://libreddit.spike.codes',
'https://libreddit.privacy.com.de'
],
searx: [
'https://searx.be',
'https://search.mdosch.de'
],
proxitok: [
'https://proxitok.pabloferreiro.es'
]
};
// Service detection patterns
const SERVICE_PATTERNS = {
invidious: {
regex: /^(?:www\.)?(?:youtube\.com|youtu\.be)$/,
pathBuilder: function(url) {
const videoId = url.searchParams.get('v');
return videoId ? '/watch?v=' + videoId : url.pathname + url.search;
}
},
nitter: {
regex: /^(?:www\.)?(?:twitter\.com|x\.com)$/,
pathBuilder: function(url) {
return url.pathname + url.search;
}
},
libreddit: {
regex: /^(?:www\.)?(?:old\.)?reddit\.com$/,
pathBuilder: function(url) {
return url.pathname + url.search;
}
},
searx: {
regex: /^(?:www\.)?google\.com$/,
pathCheck: /^\/search/,
pathBuilder: function(url) {
const query = url.searchParams.get('q');
return query ? '/search?q=' + encodeURIComponent(query) : '/';
}
},
proxitok: {
regex: /^(?:www\.)?tiktok\.com$/,
pathBuilder: function(url) {
return url.pathname;
}
}
};
// Loading page class - expanded CSS for readability
class LoadingPage {
static show(targetUrl, instanceUrl) {
const hostname = new URL(instanceUrl).hostname;
// Readable, unminified HTML
const htmlContent = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Auto-PROXY-SF - Redirecting</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: #ffffff;
overflow: hidden;
}
.container {
text-align: center;
padding: 2rem;
max-width: 600px;
animation: fadeIn 0.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: 1rem;
font-weight: 700;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.9;
}
.loader {
width: 60px;
height: 60px;
border: 5px solid rgba(255, 255, 255, 0.3);
border-top: 5px solid #ffffff;
border-radius: 50%;
margin: 2rem auto;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.status {
font-size: 1rem;
margin-top: 2rem;
padding: 1rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
backdrop-filter: blur(10px);
}
.instance-info {
margin-top: 1rem;
font-size: 0.9rem;
opacity: 0.8;
word-break: break-all;
}
.progress-bar {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
margin-top: 2rem;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: #ffffff;
width: 0%;
animation: progress 3s ease-in-out;
}
@keyframes progress {
0% {
width: 0%;
}
100% {
width: 100%;
}
}
.footer {
margin-top: 3rem;
font-size: 0.85rem;
opacity: 0.7;
}
.footer a {
color: #ffffff;
text-decoration: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
transition: border-color 0.3s;
}
.footer a:hover {
border-bottom-color: #ffffff;
}
.particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
}
.particle {
position: absolute;
width: 4px;
height: 4px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50%;
animation: float 10s infinite;
}
@keyframes float {
0%, 100% {
transform: translateY(0) translateX(0);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateY(-100vh) translateX(50px);
opacity: 0;
}
}
</style>
</head>
<body>
<div class="particles" id="particles"></div>
<div class="container">
<div class="logo">🔒</div>
<h1>Auto-PROXY-SF</h1>
<div class="subtitle">Securing your privacy...</div>
<div class="loader"></div>
<div class="status">
<div>Redirecting through privacy proxy</div>
<div class="instance-info">Instance: ${hostname}</div>
</div>
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
<div class="footer">
Powered by <a href="https://anonymousik.is-a.dev" target="_blank">Anonymousik</a>
<br>
SecFerro Division
</div>
</div>
<script>
// Create floating particles
var particlesContainer = document.getElementById('particles');
for (var i = 0; i < 20; i++) {
var particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = Math.random() * 100 + '%';
particle.style.animationDelay = Math.random() * 10 + 's';
particle.style.animationDuration = (Math.random() * 10 + 10) + 's';
particlesContainer.appendChild(particle);
}
// Redirect after animation completes
setTimeout(function() {
window.location.href = '${targetUrl}';
}, 3000);
</script>
</body>
</html>`;
document.open();
document.write(htmlContent);
document.close();
}
}
// Health monitoring system
class HealthMonitor {
constructor() {
this.healthData = {};
this.checking = new Set();
}
async initialize() {
try {
const cached = await GM.getValue('healthData', '{}');
this.healthData = JSON.parse(cached);
this.cleanExpiredData();
} catch (error) {
console.error('[Auto-PROXY-SF] Health data init failed:', error);
this.healthData = {};
}
}
cleanExpiredData() {
const now = Date.now();
const threshold = CONFIG.HEALTH_CHECK_INTERVAL * 2;
for (const url in this.healthData) {
if (this.healthData.hasOwnProperty(url)) {
const data = this.healthData[url];
if (now - data.lastCheck > threshold) {
delete this.healthData[url];
}
}
}
}
async checkHealth(url) {
const self = this;
// Prevent duplicate checks
if (this.checking.has(url)) {
return new Promise(function(resolve) {
const interval = setInterval(function() {
if (!self.checking.has(url)) {
clearInterval(interval);
const health = self.healthData[url];
resolve(health ? health.healthy : false);
}
}, 100);
});
}
// Check cache
const cached = this.healthData[url];
if (cached && Date.now() - cached.lastCheck < CONFIG.HEALTH_CHECK_INTERVAL) {
return cached.healthy;
}
this.checking.add(url);
return new Promise(function(resolve) {
const startTime = Date.now();
let resolved = false;
const finish = function(healthy, latency) {
if (resolved) return;
resolved = true;
self.checking.delete(url);
self.updateHealth(url, healthy, latency);
resolve(healthy);
};
const timeoutId = setTimeout(function() {
finish(false, null);
}, CONFIG.INSTANCE_TIMEOUT);
GM.xmlHttpRequest({
method: 'HEAD',
url: url,
timeout: CONFIG.INSTANCE_TIMEOUT,
anonymous: true,
onload: function(response) {
clearTimeout(timeoutId);
const latency = Date.now() - startTime;
const healthy = response.status >= 200 && response.status < 400;
finish(healthy, latency);
},
onerror: function() {
clearTimeout(timeoutId);
finish(false, null);
},
ontimeout: function() {
clearTimeout(timeoutId);
finish(false, null);
}
});
});
}
updateHealth(url, healthy, latency) {
const data = this.healthData[url] || {
healthy: false,
lastCheck: 0,
failures: 0,
latencies: []
};
data.healthy = healthy;
data.lastCheck = Date.now();
if (healthy) {
data.failures = 0;
if (latency !== null) {
data.latencies.push(latency);
if (data.latencies.length > 10) {
data.latencies.shift();
}
// Calculate average latency
const sum = data.latencies.reduce(function(a, b) {
return a + b;
}, 0);
data.avgLatency = sum / data.latencies.length;
}
} else {
data.failures++;
}
this.healthData[url] = data;
// Asynchronous save
GM.setValue('healthData', JSON.stringify(this.healthData));
}
getScore(url) {
const data = this.healthData[url];
if (!data || !data.healthy) return 0;
let score = 50; // Base score for healthy instance
// Latency scoring (max 30 points)
if (data.avgLatency) {
const latencyScore = Math.max(5, 30 - (data.avgLatency / 100) * 3);
score += latencyScore;
}
// Reliability scoring (max 20 points)
const reliabilityScore = Math.min(20, (10 - data.failures) * 2);
score += reliabilityScore;
return score;
}
getBestInstance(instances) {
if (!instances || instances.length === 0) return null;
const self = this;
const scored = instances.map(function(url) {
return {
url: url,
score: self.getScore(url)
};
}).filter(function(item) {
return item.score > 0;
}).sort(function(a, b) {
return b.score - a.score;
});
return scored.length > 0 ? scored[0].url : instances[0];
}
}
// Instance manager
class InstanceManager {
constructor() {
this.healthMonitor = new HealthMonitor();
this.preferredNetwork = 'clearnet';
}
async initialize() {
await this.healthMonitor.initialize();
this.preferredNetwork = await GM.getValue('preferredNetwork', 'clearnet');
}
async getInstances(service) {
if (this.preferredNetwork === 'i2p') {
return I2P_INSTANCES[service] || [];
} else {
return CLEARNET_INSTANCES[service] || [];
}
}
async getBestInstance(service, retryCount) {
retryCount = retryCount || 0;
const instances = await this.getInstances(service);
if (instances.length === 0) {
console.warn('[Auto-PROXY-SF] No instances for ' + service);
return null;
}
const checkCount = Math.min(CONFIG.PARALLEL_CHECKS, instances.length);
const toCheck = instances.slice(0, checkCount);
const self = this;
const checks = toCheck.map(function(url) {
return self.healthMonitor.checkHealth(url);
});
await Promise.all(checks);
const best = this.healthMonitor.getBestInstance(instances);
if (!best && retryCount < CONFIG.MAX_RETRY_ATTEMPTS) {
console.log('[Auto-PROXY-SF] No healthy instance, retry ' + (retryCount + 1));
await new Promise(function(resolve) {
setTimeout(resolve, 1000);
});
return this.getBestInstance(service, retryCount + 1);
}
return best;
}
async setNetwork(network) {
this.preferredNetwork = network;
await GM.setValue('preferredNetwork', network);
}
}
// URL processor
class URLProcessor {
constructor(manager) {
this.manager = manager;
this.processed = new WeakSet();
}
detectService(hostname, pathname) {
for (const service in SERVICE_PATTERNS) {
if (SERVICE_PATTERNS.hasOwnProperty(service)) {
const pattern = SERVICE_PATTERNS[service];
if (pattern.regex.test(hostname)) {
if (pattern.pathCheck && !pattern.pathCheck.test(pathname)) {
continue;
}
return service;
}
}
}
return null;
}
async processURL(originalUrl) {
try {
const url = new URL(originalUrl);
const service = this.detectService(url.hostname, url.pathname);
if (!service) return null;
const instance = await this.manager.getBestInstance(service);
if (!instance) return null;
const pattern = SERVICE_PATTERNS[service];
const newPath = pattern.pathBuilder(url);
return instance + newPath;
} catch (error) {
console.error('[Auto-PROXY-SF] URL processing error:', error);
return null;
}
}
async processLink(linkElement) {
if (this.processed.has(linkElement)) return;
this.processed.add(linkElement);
const newUrl = await this.processURL(linkElement.href);
if (newUrl) {
linkElement.dataset.originalHref = linkElement.href;
linkElement.href = newUrl;
linkElement.style.color = '#2ea44f';
linkElement.title = 'Redirects via privacy proxy';
}
}
}
// Page handler
class PageHandler {
constructor(processor, manager) {
this.processor = processor;
this.manager = manager;
}
async checkCurrentPage() {
const url = new URL(window.location.href);
const service = this.processor.detectService(url.hostname, url.pathname);
if (service) {
const instance = await this.manager.getBestInstance(service);
if (instance) {
const pattern = SERVICE_PATTERNS[service];
const targetUrl = instance + pattern.pathBuilder(url);
LoadingPage.show(targetUrl, instance);
}
}
}
initialize() {
this.checkCurrentPage();
const self = this;
const observer = new IntersectionObserver(
function(entries) {
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (entry.isIntersecting) {
self.processor.processLink(entry.target);
observer.unobserve(entry.target);
}
}
},
{ rootMargin: '100px' }
);
const processLinks = function() {
const links = document.querySelectorAll('a[href^="http"]:not([data-proxy-processed])');
for (let i = 0; i < links.length; i++) {
const link = links[i];
link.dataset.proxyProcessed = 'true';
observer.observe(link);
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', processLinks);
} else {
processLinks();
}
if (document.body) {
const mutationObserver = new MutationObserver(function() {
if (typeof requestIdleCallback === 'function') {
requestIdleCallback(processLinks, { timeout: 2000 });
} else {
setTimeout(processLinks, 100);
}
});
mutationObserver.observe(document.body, {
childList: true,
subtree: true
});
}
}
}
// Main initialization
async function main() {
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();
// Menu commands
const currentNetwork = await GM.getValue('preferredNetwork', 'clearnet');
GM.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);
alert('Auto-PROXY-SF: Switched to ' + newNetwork.toUpperCase());
window.location.reload();
});
GM.registerMenuCommand('Clear Cache', async function() {
await GM.deleteValue('healthData');
manager.healthMonitor.healthData = {};
alert('Cache cleared successfully');
});
GM.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();
}
})();