166 lines
5.8 KiB
JavaScript
166 lines
5.8 KiB
JavaScript
const https = require('https');
|
|
const http = require('http');
|
|
|
|
/**
|
|
* Helper to fetch text or JSON over HTTPS with timeout
|
|
*/
|
|
function fetchUrl(url, timeoutMs = 12000) {
|
|
return new Promise((resolve, reject) => {
|
|
const client = url.startsWith('https') ? https : http;
|
|
const req = client.get(url, { headers: { 'User-Agent': 'katproxy/1.0' } }, (res) => {
|
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
res.resume();
|
|
return reject(new Error(`Failed to fetch ${url}, status: ${res.statusCode}`));
|
|
}
|
|
let rawData = '';
|
|
res.setEncoding('utf8');
|
|
res.on('data', (chunk) => { rawData += chunk; });
|
|
res.on('end', () => resolve(rawData));
|
|
});
|
|
|
|
req.on('error', (err) => reject(err));
|
|
req.setTimeout(timeoutMs, () => {
|
|
req.destroy();
|
|
reject(new Error(`Timeout fetching ${url}`));
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetch and merge proxies from multiple premier GitHub repositories
|
|
*/
|
|
async function fetchAllProxies() {
|
|
console.log('[Fetcher] Starting proxy list aggregation...');
|
|
const proxyMap = new Map();
|
|
|
|
// Helper to add or merge a proxy
|
|
function addProxy(host, port, protocol, extra = {}) {
|
|
if (!host || !port) return;
|
|
const cleanHost = String(host).trim();
|
|
const cleanPort = parseInt(port, 10);
|
|
if (isNaN(cleanPort) || cleanPort <= 0 || cleanPort > 65535) return;
|
|
// Basic IP validation regex
|
|
if (!/^(\d{1,3}\.){3}\d{1,3}$/.test(cleanHost)) return;
|
|
|
|
const key = `${cleanHost}:${cleanPort}`;
|
|
const existing = proxyMap.get(key);
|
|
|
|
const protoClean = (protocol || 'http').toLowerCase();
|
|
|
|
if (!existing) {
|
|
proxyMap.set(key, {
|
|
id: key,
|
|
host: cleanHost,
|
|
port: cleanPort,
|
|
protocols: [protoClean],
|
|
country: (extra.country || 'UN').toUpperCase(),
|
|
countryName: extra.countryName || 'Unknown',
|
|
city: extra.city || '',
|
|
org: extra.org || '',
|
|
latency: extra.latency != null ? Math.round(extra.latency * 1000) : null,
|
|
alive: extra.alive !== undefined ? extra.alive : null,
|
|
lastChecked: extra.lastChecked || null,
|
|
sources: [extra.source || 'upstream']
|
|
});
|
|
} else {
|
|
if (!existing.protocols.includes(protoClean)) {
|
|
existing.protocols.push(protoClean);
|
|
}
|
|
if (existing.country === 'UN' && extra.country) {
|
|
existing.country = extra.country.toUpperCase();
|
|
existing.countryName = extra.countryName || existing.countryName;
|
|
}
|
|
if (!existing.org && extra.org) {
|
|
existing.org = extra.org;
|
|
}
|
|
if (extra.source && !existing.sources.includes(extra.source)) {
|
|
existing.sources.push(extra.source);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1. Fetch monosans structured JSON (provides rich metadata: country, org, timeout)
|
|
try {
|
|
console.log('[Fetcher] Fetching monosans/proxy-list JSON...');
|
|
const monosansData = await fetchUrl('https://raw.githubusercontent.com/monosans/proxy-list/main/proxies.json', 15000);
|
|
const parsed = JSON.parse(monosansData);
|
|
if (Array.isArray(parsed)) {
|
|
for (const item of parsed) {
|
|
const countryCode = item.geolocation?.country?.iso_code || 'UN';
|
|
const countryName = item.geolocation?.country?.names?.en || 'Unknown';
|
|
const city = item.geolocation?.city?.names?.en || '';
|
|
const org = item.asn?.autonomous_system_organization || '';
|
|
const latencyMs = item.timeout ? Math.round(item.timeout * 1000) : null;
|
|
|
|
addProxy(item.host, item.port, item.protocol, {
|
|
country: countryCode,
|
|
countryName,
|
|
city,
|
|
org,
|
|
latency: item.timeout != null ? item.timeout : null,
|
|
alive: true,
|
|
source: 'monosans'
|
|
});
|
|
}
|
|
console.log(`[Fetcher] monosans parsed: ${parsed.length} proxies`);
|
|
}
|
|
} catch (err) {
|
|
console.warn('[Fetcher] Warning: monosans JSON fetch failed:', err.message);
|
|
}
|
|
|
|
// 2. TheSpeedX lists (HTTP, SOCKS4, SOCKS5)
|
|
const speedXTasks = [
|
|
{ url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/http.txt', protocol: 'http' },
|
|
{ url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks4.txt', protocol: 'socks4' },
|
|
{ url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks5.txt', protocol: 'socks5' }
|
|
];
|
|
|
|
await Promise.allSettled(
|
|
speedXTasks.map(async (task) => {
|
|
try {
|
|
const text = await fetchUrl(task.url);
|
|
const lines = text.split('\n');
|
|
let count = 0;
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
const [host, port] = trimmed.split(':');
|
|
if (host && port) {
|
|
addProxy(host, port, task.protocol, { source: 'thespeedx' });
|
|
count++;
|
|
}
|
|
}
|
|
console.log(`[Fetcher] TheSpeedX ${task.protocol} parsed: ${count} proxies`);
|
|
} catch (err) {
|
|
console.warn(`[Fetcher] TheSpeedX ${task.protocol} error:`, err.message);
|
|
}
|
|
})
|
|
);
|
|
|
|
// 3. hookzof SOCKS5 list
|
|
try {
|
|
const hookzofText = await fetchUrl('https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt');
|
|
const lines = hookzofText.split('\n');
|
|
let count = 0;
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
const [host, port] = trimmed.split(':');
|
|
if (host && port) {
|
|
addProxy(host, port, 'socks5', { source: 'hookzof' });
|
|
count++;
|
|
}
|
|
}
|
|
console.log(`[Fetcher] hookzof SOCKS5 parsed: ${count} proxies`);
|
|
} catch (err) {
|
|
console.warn('[Fetcher] hookzof SOCKS5 error:', err.message);
|
|
}
|
|
|
|
const results = Array.from(proxyMap.values());
|
|
console.log(`[Fetcher] Completed aggregation. Total unique proxies: ${results.length}`);
|
|
return results;
|
|
}
|
|
|
|
module.exports = {
|
|
fetchAllProxies
|
|
};
|