work on queue

This commit is contained in:
Bojan Kucera 2025-06-08 18:56:52 -04:00
parent bf2fa003b9
commit 9b8a7bdd4b
6 changed files with 488 additions and 420 deletions

View file

@ -3,17 +3,15 @@ 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(100);
private readonly CACHE_KEY = 'proxy';
private readonly CACHE_TTL = 86400; // 24 hours
private readonly CHECK_TIMEOUT = 7000;
private readonly CHECK_IP = '99.246.102.205'
private readonly CHECK_URL = 'https://proxy-detection.stare.gg/?api_key=bd406bf53ddc6abe1d9de5907830a955';
private readonly PROXY_SOURCES = [
// Shared configuration and utilities
const PROXY_CONFIG = {
CACHE_KEY: 'proxy',
CACHE_TTL: 86400, // 24 hours
CHECK_TIMEOUT: 7000,
CHECK_IP: '99.246.102.205',
CHECK_URL: 'https://proxy-detection.stare.gg/?api_key=bd406bf53ddc6abe1d9de5907830a955',
CONCURRENCY_LIMIT: 100,
PROXY_SOURCES: [
{url: 'https://raw.githubusercontent.com/prxchk/proxy-list/main/http.txt',protocol: 'http', },
{url: 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/http',protocol: 'http', },
{url: 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/http.txt',protocol: 'http', },
@ -60,267 +58,252 @@ export class ProxyService {
// {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: 10000,
}, this.logger);
// Shared instances (module-scoped, not global)
let logger: Logger;
let cache: CacheProvider;
let httpClient: HttpClient;
let concurrencyLimit: ReturnType<typeof pLimit>;
this.logger.info('ProxyService initialized');
}
// Add queue integration methods
async queueProxyFetch(): Promise<string> {
const { queueManager } = await import('../services/queue.service');
const job = await queueManager.addJob({
type: 'proxy-fetch',
service: 'proxy',
provider: 'proxy-service',
operation: 'fetch-and-check',
payload: {},
priority: 5
});
const jobId = job.id || 'unknown';
this.logger.info('Proxy fetch job queued', { jobId });
return jobId;
}
async queueProxyCheck(proxies: ProxyInfo[]): Promise<string> {
const { queueManager } = await import('../services/queue.service');
const job = await queueManager.addJob({
type: 'proxy-check',
service: 'proxy',
provider: 'proxy-service',
operation: 'check-specific',
payload: { proxies },
priority: 3
});
const jobId = job.id || 'unknown';
this.logger.info('Proxy check job queued', { jobId, count: proxies.length });
return jobId;
}
async fetchProxiesFromSources() : Promise<number> {
const sources = this.PROXY_SOURCES.map(source =>
this.concurrencyLimit(() => this.fetchProxiesFromSource(source))
)
const result = await Promise.all(sources);
let allProxies: ProxyInfo[] = result.flat();
allProxies = this.removeDuplicateProxies(allProxies)
await this.checkProxies(allProxies)
return allProxies.length
}
private removeDuplicateProxies(proxies: ProxyInfo[]): ProxyInfo[] {
const seen = new Set<string>();
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<ProxyInfo[]> {
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) {
let trimmed = line.trim();
trimmed = this.cleanProxyUrl(trimmed);
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;
}
private cleanProxyUrl(url: string): string {
// Remove http:// or https:// and any leading zeros from the host part
return url
.replace(/^https?:\/\//, '') // Remove protocol
.replace(/^0+/, '') // Remove leading zeros at start
.replace(/:0+(\d)/g, ':$1'); // Remove leading zeros from port numbers
}
/**
* Check if a proxy is working
*/
async checkProxy(proxy: ProxyInfo): Promise<ProxyInfo> {
let success = false;
this.logger.debug(`Checking Proxy with ${this.concurrencyLimit.pendingCount } pending: `, {
protocol: proxy.protocol,
host: proxy.host,
port: proxy.port,
});
// 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,
};
// console.log('Proxy check result:', proxy);
if (isWorking && !JSON.stringify(response.data).includes(this.CHECK_IP)) {
success = true
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
if(!success) // If the proxy check failed, remove it from cache - success is here cause i think abort signal fails sometimes
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<ProxyInfo[]> {
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<ProxyInfo | null> {
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<void> {
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<void> {
this.logger.info('Clearing proxy cache');
// Note: Cache provider limitations - would need proper key tracking
}
/**
* Shutdown service
*/
async shutdown(): Promise<void> {
this.logger.info('Shutting down ProxyService');
// Initialize shared resources
function initializeSharedResources() {
if (!logger) {
logger = new Logger('proxy-tasks');
cache = createCache('hybrid');
httpClient = new HttpClient({ timeout: 10000 }, logger);
concurrencyLimit = pLimit(PROXY_CONFIG.CONCURRENCY_LIMIT);
logger.info('Proxy tasks initialized');
}
}
// Export singleton instance
export const proxyService = new ProxyService();
// Individual task functions
export async function queueProxyFetch(): Promise<string> {
initializeSharedResources();
const { queueManager } = await import('../services/queue.service');
const job = await queueManager.addJob({
type: 'proxy-fetch',
service: 'proxy',
provider: 'proxy-service',
operation: 'fetch-and-check',
payload: {},
priority: 5
});
const jobId = job.id || 'unknown';
logger.info('Proxy fetch job queued', { jobId });
return jobId;
}
export async function queueProxyCheck(proxies: ProxyInfo[]): Promise<string> {
initializeSharedResources();
const { queueManager } = await import('../services/queue.service');
const job = await queueManager.addJob({
type: 'proxy-check',
service: 'proxy',
provider: 'proxy-service',
operation: 'check-specific',
payload: { proxies },
priority: 3
});
const jobId = job.id || 'unknown';
logger.info('Proxy check job queued', { jobId, count: proxies.length });
return jobId;
}
export async function fetchProxiesFromSources(): Promise<number> {
initializeSharedResources();
const sources = PROXY_CONFIG.PROXY_SOURCES.map(source =>
concurrencyLimit(() => fetchProxiesFromSource(source))
);
const result = await Promise.all(sources);
let allProxies: ProxyInfo[] = result.flat();
allProxies = removeDuplicateProxies(allProxies);
// await checkProxies(allProxies);
return allProxies.length;
}
export async function fetchProxiesFromSource(source: { url: string; protocol: string }): Promise<ProxyInfo[]> {
initializeSharedResources();
const allProxies: ProxyInfo[] = [];
try {
logger.info(`Fetching proxies from ${source.url}`);
const response = await httpClient.get(source.url, {
timeout: 10000
});
if (response.status !== 200) {
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) {
let trimmed = line.trim();
trimmed = cleanProxyUrl(trimmed);
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);
}
}
}
logger.info(`Parsed ${allProxies.length} proxies from ${source.url}`);
} catch (error) {
logger.error(`Error fetching proxies from ${source.url}`, error);
return [];
}
return allProxies;
}
/**
* Check multiple proxies concurrently
*/
export async function checkProxies(proxies: ProxyInfo[]): Promise<ProxyInfo[]> {
initializeSharedResources();
logger.info('Checking proxies', { count: proxies.length });
const checkPromises = proxies.map(proxy =>
concurrencyLimit(() => checkProxy(proxy))
);
const results = await Promise.all(checkPromises);
const workingCount = results.filter((r: ProxyInfo) => r.isWorking).length;
logger.info('Proxy check completed', {
total: proxies.length,
working: workingCount,
failed: proxies.length - workingCount
});
return results;
}
/**
* Check if a proxy is working
*/
export async function checkProxy(proxy: ProxyInfo): Promise<ProxyInfo> {
initializeSharedResources();
let success = false;
logger.debug(`Checking Proxy with ${concurrencyLimit.pendingCount} pending:`, {
protocol: proxy.protocol,
host: proxy.host,
port: proxy.port,
});
try {
// Test the proxy
const response = await httpClient.get(PROXY_CONFIG.CHECK_URL, {
proxy,
timeout: PROXY_CONFIG.CHECK_TIMEOUT
});
const isWorking = response.status >= 200 && response.status < 300;
const result: ProxyInfo = {
...proxy,
isWorking,
checkedAt: new Date(),
responseTime: response.responseTime,
};
if (isWorking && !JSON.stringify(response.data).includes(PROXY_CONFIG.CHECK_IP)) {
success = true;
await cache.set(`${PROXY_CONFIG.CACHE_KEY}:${proxy.protocol}://${proxy.host}:${proxy.port}`, result, PROXY_CONFIG.CACHE_TTL);
} else {
await cache.del(`${PROXY_CONFIG.CACHE_KEY}:${proxy.protocol}://${proxy.host}:${proxy.port}`);
}
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()
};
// If the proxy check failed, remove it from cache - success is here cause i think abort signal fails sometimes
if (!success) {
await cache.del(`${PROXY_CONFIG.CACHE_KEY}:${proxy.protocol}://${proxy.host}:${proxy.port}`);
}
logger.debug('Proxy check failed', {
host: proxy.host,
port: proxy.port,
error: errorMessage
});
return result;
}
}
// Utility functions
function cleanProxyUrl(url: string): string {
return url
.replace(/^https?:\/\//, '')
.replace(/^0+/, '')
.replace(/:0+(\d)/g, ':$1');
}
function removeDuplicateProxies(proxies: ProxyInfo[]): ProxyInfo[] {
const seen = new Set<string>();
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;
}
// Optional: Export a convenience object that groups related tasks
export const proxyTasks = {
queueProxyFetch,
queueProxyCheck,
fetchProxiesFromSources,
fetchProxiesFromSource,
checkProxy,
checkProxies,
};
// Export singleton instance for backward compatibility (optional)
// Remove this if you want to fully move to the task-based approach
export const proxyService = proxyTasks;