diff --git a/handlers/commands.py b/handlers/commands.py index cdda664..3be320b 100644 --- a/handlers/commands.py +++ b/handlers/commands.py @@ -2163,10 +2163,17 @@ class BotInterface: logger.info(f"▶️ Resuming: {series_size} clicks left ({elapsed:.0f}s paused)") else: # Новая серия: задержка → рандомный размер - await _sleep_interruptible(params.get_delay()) + delay = params.get_delay() + series_size = params.get_series_size() + logger.info( + f"⏳ Next series: {series_size} clicks, " + f"delay {delay}s ({delay//60}m {delay%60}s), " + f"reading {params.min_reading}-{params.max_reading}s, " + f"done {params.successful_visits}/{max_v}" + ) + await _sleep_interruptible(delay) if params.stopped: break - series_size = params.get_series_size() # --- Серия кликов --- for click_num in range(series_size): diff --git a/managers/proxy_manager.py b/managers/proxy_manager.py index b8760d4..fd5d1d5 100644 --- a/managers/proxy_manager.py +++ b/managers/proxy_manager.py @@ -181,24 +181,35 @@ class ProxyManager: self._load_proxies() def _load_proxies(self) -> None: - """Загружает прокси из файла.""" + """Загружает прокси из файла. + + Форматы строк: + socks5://ip:port:login:pass — рабочий, тип известен + !socks5://ip:port:login:pass — нерабочий, пропускается + ip:port:login:pass — тип неизвестен, будет задетектирован + """ if not os.path.exists(self.proxy_file): logger.warning(f"Proxy file not found: {self.proxy_file}") return - + try: with open(self.proxy_file, 'r', encoding='utf-8') as f: - lines = [ - line.strip() - for line in f - if line.strip() and not line.startswith('#') - ] - + raw_lines = [l.rstrip('\n') for l in f] + loaded = [] _has_scheme = {"socks5://", "socks4://", "http://", "https://"} - for line in lines: - explicit_type = any(line.lower().startswith(s) for s in _has_scheme) - proxy = Proxy.from_line(line, default_type=ProxyType.SOCKS5) + for line in raw_lines: + stripped = line.strip() + if not stripped or stripped.startswith('#'): + continue + + # Нерабочий прокси — пропускаем, не загружаем + if stripped.startswith('!'): + logger.debug(f"Skipping disabled proxy: {stripped[1:30]}...") + continue + + explicit_type = any(stripped.lower().startswith(s) for s in _has_scheme) + proxy = Proxy.from_line(stripped, default_type=ProxyType.SOCKS5) if proxy: self._proxies.append(proxy) self._working_status[proxy.id] = True @@ -252,6 +263,52 @@ class ProxyManager: f"socks5:{detected['socks5']} socks4:{detected['socks4']} " f"http:{detected['http']} failed:{detected['failed']}" ) + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, self._save_proxies_file) + + def _save_proxies_file(self) -> None: + """Перезаписывает proxies.txt с актуальными типами и пометками.""" + if not os.path.exists(self.proxy_file): + return + try: + with open(self.proxy_file, 'r', encoding='utf-8') as f: + raw_lines = [l.rstrip('\n') for l in f] + + new_lines = [] + + for line in raw_lines: + stripped = line.strip() + # Пустые строки и комментарии — без изменений + if not stripped or stripped.startswith('#'): + new_lines.append(line) + continue + + # Нерабочие — оставляем как есть (уже с !) + if stripped.startswith('!'): + new_lines.append(line) + continue + + proxy = Proxy.from_line(stripped, default_type=ProxyType.SOCKS5) + if not proxy: + new_lines.append(line) + continue + + working = self._working_status.get(proxy.id, True) + scheme = proxy.proxy_type.value # socks5, http, etc. + creds = f"{proxy.login}:{proxy.password}@" if proxy.login else "" + new_line = f"{scheme}://{creds}{proxy.ip}:{proxy.port}" + + if not working: + new_line = f"!{new_line}" + + new_lines.append(new_line) + + with open(self.proxy_file, 'w', encoding='utf-8') as f: + f.write('\n'.join(new_lines) + '\n') + + logger.info(f"proxies.txt updated with detected types and status") + except Exception as e: + logger.error(f"Failed to save proxies file: {e}") def _is_available(self, proxy: Proxy) -> bool: """Проверка доступности прокси.""" @@ -324,24 +381,34 @@ class ProxyManager: """Освобождает прокси.""" if not proxy: return - + proxy_id = proxy.id - + mark_failed = False + async with self._lock: if proxy_id in self._in_use: del self._in_use[proxy_id] - + if self._usage_count[proxy_id] >= self.max_usage: loop = asyncio.get_running_loop() self._cooldown_until[proxy_id] = ( loop.time() + self.cooldown_time ) self._usage_count[proxy_id] = 0 - + if not success: self._working_status[proxy_id] = False - + # Удаляем из пула полностью + self._proxies = [p for p in self._proxies if p.id != proxy_id] + self._needs_detection.discard(proxy_id) + mark_failed = True + logger.warning(f"Proxy {proxy_id} marked as failed and removed from pool") + logger.debug(f"Proxy released: {proxy_id}") + + if mark_failed: + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, self._save_proxies_file) def get_stats(self) -> dict: """Статистика прокси.""" diff --git a/services/browser_service.py b/services/browser_service.py index 52ef2d6..b6c891e 100644 --- a/services/browser_service.py +++ b/services/browser_service.py @@ -167,6 +167,15 @@ class BrowserService: humanize=True, exclude_addons=[DefaultAddons.UBO], proxy=proxy_config, + firefox_prefs={ + # Запрещаем WebRTC использовать локальные IP-кандидаты + "media.peerconnection.ice.no_host": True, + # Только ICE через прокси — блокирует утечку реального IP + "media.peerconnection.ice.proxy_only_if_behind_proxy": True, + # DNS только через прокси (предотвращает DNS-утечку) + "network.dns.disablePrefetch": True, + "network.prefetch-next": False, + }, ) if proxy_locale: camoufox_kwargs["locale"] = proxy_locale