import { Logger } from '@stock-bot/logger'; import createCache, { type CacheProvider } from '@stock-bot/cache'; import { HttpClient, ProxyInfo } from '@stock-bot/http'; import pLimit from 'p-limit'; export class ProxyService { private logger = new Logger('proxy-service'); private cache: CacheProvider = createCache('hybrid'); private httpClient: HttpClient; private readonly concurrencyLimit = pLimit(10); private readonly CACHE_KEY = 'proxy'; private readonly CACHE_TTL = 86400; // 24 hours private readonly CHECK_TIMEOUT = 5000; private readonly CHECK_URL = 'https://proxy-detection.stare.gg/?api_key=bd406bf53ddc6abe1d9de5907830a955'; private readonly PROXY_SOURCES = [ // { url: 'https://raw.githubusercontent.com/BreakingTechFr/Proxy_Free/refs/heads/main/proxies/http.txt', protocol: 'http' }, // {url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/http.txt',protocol: 'http', }, // {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/http.txt',protocol: 'http', }, // {url: 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/http.txt',protocol: 'http', }, // {url: 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/http.txt',protocol: 'http', }, // {url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/http',protocol: 'http', }, // {url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/http.txt',protocol: 'http', }, // {url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/http.txt',protocol: 'http', }, // {url: 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/http.txt',protocol: 'http', }, // {url: 'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/proxies.txt',protocol: 'http', }, // {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/https.txt',protocol: 'https', }, {url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks4.txt',protocol: 'socks4', }, {url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks4.txt',protocol: 'socks4', }, {url: 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks4.txt',protocol: 'socks4', }, {url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks4',protocol: 'socks4', }, {url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks4.txt',protocol: 'socks4', }, {url: 'https://raw.githubusercontent.com/BreakingTechFr/Proxy_Free/refs/heads/main/proxies/socks4.txt',protocol: 'socks4', }, {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks4.txt',protocol: 'socks4', }, {url: 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks4.txt',protocol: 'socks4', }, {url: 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks5.txt',protocol: 'socks5', }, {url: 'https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt',protocol: 'socks5', }, {url: 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks5.txt',protocol: 'socks5', }, {url: 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks5.txt',protocol: 'socks5', }, {url: 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks5.txt',protocol: 'socks5', }, {url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks5',protocol: 'socks5', }, {url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks5.txt',protocol: 'socks5', }, {url: 'https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt',protocol: 'socks5', }, {url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks5.txt',protocol: 'socks5', }, {url: 'https://raw.githubusercontent.com/BreakingTechFr/Proxy_Free/refs/heads/main/proxies/socks5.txt',protocol: 'socks5', }, ] constructor() { this.httpClient = new HttpClient({ timeout: this.CHECK_TIMEOUT, }, this.logger); this.logger.info('ProxyService initialized'); } async fetchProxiesFromSources() : Promise { const sources = this.PROXY_SOURCES.map(source => this.concurrencyLimit(() => this.fetchProxiesFromSource(source)) ) const result = await Promise.all(sources); const allProxies: ProxyInfo[] = result.flat(); await this.checkProxies(this.removeDuplicateProxies(allProxies)) return true } private removeDuplicateProxies(proxies: ProxyInfo[]): ProxyInfo[] { const seen = new Set(); const unique: ProxyInfo[] = []; for (const proxy of proxies) { const key = `${proxy.protocol}://${proxy.host}:${proxy.port}`; if (!seen.has(key)) { seen.add(key); unique.push(proxy); } } return unique; } async fetchProxiesFromSource(source: { url: string; protocol: string }): Promise { const allProxies: ProxyInfo[] = []; try { this.logger.info(`Fetching proxies from ${source.url}`); const response = await this.httpClient.get(source.url, { timeout: 10000 }); if (response.status !== 200) { this.logger.warn(`Failed to fetch from ${source.url}: ${response.status}`); return [] } const text = response.data; const lines = text.split('\n').filter((line: string) => line.trim()); for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; // Parse formats like "host:port" or "host:port:user:pass" const parts = trimmed.split(':'); if (parts.length >= 2) { const proxy: ProxyInfo = { protocol: source.protocol as 'http' | 'https' | 'socks4' | 'socks5', host: parts[0], port: parseInt(parts[1]) }; if (!isNaN(proxy.port) && proxy.host) { allProxies.push(proxy); } } } this.logger.info(`Parsed ${allProxies.length} proxies from ${source.url}`); } catch (error) { this.logger.error(`Error fetching proxies from ${source.url}`, error); return []; } this.logger.info(`Total proxies fetched: ${allProxies.length}`); return allProxies; } /** * Check if a proxy is working */ async checkProxy(proxy: ProxyInfo): Promise { console.log('Checking proxy:', `${proxy.protocol}://${proxy.host}:${proxy.port}`, this.concurrencyLimit.activeCount, this.concurrencyLimit.pendingCount); try { // Test the proxy const response = await this.httpClient.get(this.CHECK_URL, { proxy, timeout: this.CHECK_TIMEOUT }); const isWorking = response.status >= 200 && response.status < 300; const result: ProxyInfo = { ...proxy, isWorking, checkedAt: new Date(), responseTime: response.responseTime, }; if (isWorking) { await this.cache.set(`${this.CACHE_KEY}:${proxy.protocol}://${proxy.host}:${proxy.port}`, result, this.CACHE_TTL); } else { await this.cache.del(`${this.CACHE_KEY}:${proxy.protocol}://${proxy.host}:${proxy.port}`); } this.logger.debug('Proxy check completed', { host: proxy.host, port: proxy.port, isWorking, }); return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); const result: ProxyInfo = { ...proxy, isWorking: false, error: errorMessage, checkedAt: new Date() }; // Cache failed result for shorter time // await this.cache.set(cacheKey, result, 300); // 5 minutes // await this.cache.del(`${this.CACHE_KEY}:${proxy.protocol}://${proxy.host}:${proxy.port}`); this.logger.debug('Proxy check failed', { host: proxy.host, port: proxy.port, error: errorMessage }); return result; } } /** * Check multiple proxies concurrently */ async checkProxies(proxies: ProxyInfo[]): Promise { this.logger.info('Checking proxies', { count: proxies.length }); const checkPromises = proxies.map(proxy => this.concurrencyLimit(() => this.checkProxy(proxy)) ); const results = await Promise.all(checkPromises); const workingCount = results.filter(r => r.isWorking).length; this.logger.info('Proxy check completed', { total: proxies.length, working: workingCount, failed: proxies.length - workingCount }); return results; } /** * Get a random working proxy from cache */ async getWorkingProxy(): Promise { try { // Note: This is a simplified implementation // In production, you'd want to maintain a working proxies list this.logger.warn('getWorkingProxy not fully implemented - requires proxy list management'); return null; } catch (error) { this.logger.error('Error getting working proxy', error); return null; } } /** * Add proxies to check and cache */ async addProxies(proxies: ProxyInfo[]): Promise { this.logger.info('Adding proxies for validation', { count: proxies.length }); // Start background validation this.checkProxies(proxies).catch(error => { this.logger.error('Error in background proxy validation', error); }); } /** * Clear proxy cache */ async clearCache(): Promise { this.logger.info('Clearing proxy cache'); // Note: Cache provider limitations - would need proper key tracking } /** * Shutdown service */ async shutdown(): Promise { this.logger.info('Shutting down ProxyService'); } } // Export singleton instance export const proxyService = new ProxyService();