From f0f500dfdd62d9a6af920d787682abc7d5736858 Mon Sep 17 00:00:00 2001 From: Yuriy Yuriev Date: Wed, 20 May 2026 15:43:42 +0700 Subject: [PATCH] add pause series --- handlers/commands.py | 128 +++++++++++++++++++++++-------- services/socks5_to_http_proxy.py | 6 +- 2 files changed, 101 insertions(+), 33 deletions(-) diff --git a/handlers/commands.py b/handlers/commands.py index 6183145..deb3442 100644 --- a/handlers/commands.py +++ b/handlers/commands.py @@ -2114,26 +2114,55 @@ class BotInterface: await asyncio.sleep(2) await asyncio.sleep(1) + # Роут: [задержка] → клик1 → [задержка] → клик2 → ... → кликN → повтор + # Пауза: текущий клик завершается, серия останавливается на N + # Resume < 5 мин: продолжает с N+1 без задержки + # Resume > 5 мин: серия этой ссылки отбрасывается, ждём следующий цикл + + remaining_clicks = 0 + series_paused_at: Optional[float] = None + SERIES_EXPIRY = 5 * 60 + try: while params.successful_visits < max_v and not params.stopped: - # --- пауза перед циклом --- - await _sleep_interruptible(params.get_delay()) + + # Ждём паузу если активна перед новым циклом + while params.paused and not params.stopped: + await asyncio.sleep(2) if params.stopped: break - # размер цикла выбирается случайно каждый раз - series_size = params.get_series_size() + # Определяем размер серии + if remaining_clicks > 0: + elapsed = datetime.now().timestamp() - (series_paused_at or 0) + if elapsed > SERIES_EXPIRY: + # Серия устарела — отбрасываем, ждём следующую задержку + logger.info(f"⏰ Series expired ({elapsed:.0f}s > {SERIES_EXPIRY//60}min), skipping remaining {remaining_clicks} clicks") + remaining_clicks = 0 + series_paused_at = None + # Стандартная задержка перед следующей серией + await _sleep_interruptible(params.get_delay()) + if params.stopped: + break + series_size = params.get_series_size() + else: + # Продолжаем с сохранённого места — без задержки + series_size = remaining_clicks + remaining_clicks = 0 + series_paused_at = None + logger.info(f"▶️ Resuming: {series_size} clicks left ({elapsed:.0f}s paused)") + else: + # Новая серия: задержка → рандомный размер + await _sleep_interruptible(params.get_delay()) + if params.stopped: + break + series_size = params.get_series_size() - # --- цикл кликов --- + # --- Серия кликов --- for click_num in range(series_size): if params.stopped or params.successful_visits >= max_v: break - while params.paused and not params.stopped: - await asyncio.sleep(2) - if params.stopped: - break - balance = await self.balance_storage.get_balance(params.user_id) if balance <= 0: await self.task_manager.stop_task(task_id) @@ -2156,8 +2185,16 @@ class BotInterface: logger.error(f"URL visit error: {e}") params.total_visits += 1 - # задержка между кликами внутри цикла (кроме последнего) - if click_num < series_size - 1 and not params.stopped and params.successful_visits < max_v: + # Клик завершён — проверяем паузу + if params.paused or params.stopped: + remaining_clicks = series_size - click_num - 1 + if remaining_clicks > 0 and not params.stopped: + series_paused_at = datetime.now().timestamp() + logger.info(f"⏸️ Paused after click {click_num+1}/{series_size}, {remaining_clicks} saved") + break + + # Задержка между кликами (кроме последнего) + if click_num < series_size - 1 and params.successful_visits < max_v: await _sleep_interruptible(params.get_click_delay()) await self.task_manager.complete_task(task_id) @@ -2197,48 +2234,79 @@ class BotInterface: viewers = await get_viewer_count(params.channel) if viewers and viewers > 0: visits_count = max(1, round(viewers * ctr / 100)) - logger.info(f"visits: {viewers} viewers × {ctr:.2f}% = {visits_count}") + calc_info = f"{viewers} зрителей × {ctr:.2f}% = {visits_count} кликов" else: visits_count = max(1, round(params.visits_per_link * ctr / 100)) - logger.info(f"visits: fallback {params.visits_per_link} × {ctr:.2f}% = {visits_count}") + calc_info = f"зрители недоступны, фикс {params.visits_per_link} × {ctr:.2f}% = {visits_count} кликов" - logger.info(f"🔗 @{username}: {url}") + logger.info(f"🔗 @{username} {url} → {visits_count} clicks ({calc_info})") await send_message_safe( message.bot, message.chat.id, - f"🔗 @{username}: {url[:60]}" + f"🔗 @{username}: {url[:60]}\n" + f"📊 {calc_info}" ) - for i in range(visits_count): - while params.paused: - await asyncio.sleep(1) + # Роут: [задержка] → клик1 → [задержка] → клик2 → ... → кликN + # Пауза: текущий клик завершается, серия останавливается + # Resume < 5 мин: продолжает с сохранённого клика + # Resume > 5 мин: серия этой ссылки полностью отбрасывается + SERIES_EXPIRY = 5 * 60 + remaining = visits_count + paused_at: Optional[float] = None + + first_click = True + while remaining > 0 and not params.stopped: + # Проверяем не истекла ли серия после паузы + if paused_at is not None: + elapsed = datetime.now().timestamp() - paused_at + if elapsed > SERIES_EXPIRY: + logger.info(f"⏰ Серия устарела ({elapsed:.0f}s), ссылка {url[:40]} отброшена") + return + paused_at = None + logger.info(f"▶️ Продолжаем серию: {remaining} кликов осталось") + + # Задержка перед каждым кликом (включая первый) + if not params.stopped and not params.paused: + delay = params.get_delay() + if first_click: + logger.info(f"⏳ Задержка перед первым кликом: {delay}с") + await asyncio.sleep(delay) + first_click = False + if params.stopped: - break + return reading = params.get_reading_time() try: - result = await self.browser_service.visit_page_from_twitch( - url=url, - channel=params.channel, - reading_time=reading - ) + result = await self.browser_service.visit_page(url, reading) params.total_visits += 1 if result.success: params.successful_visits += 1 + remaining -= 1 if params.user_id: new_balance = await self.balance_storage.deduct(params.user_id, 1) if new_balance <= 0: params.stopped = True await send_message_safe( message.bot, params.chat_id, - "⚠️ Баланс исчерпан — задача остановлена.\n" - "Обратитесь к администратору для пополнения." + "⚠️ Баланс исчерпан — задача остановлена." ) - break + return + else: + remaining -= 1 except Exception as e: logger.error(f"Visit error: {e}") + remaining -= 1 + + # Клик завершён — проверяем паузу + if params.paused and remaining > 0: + paused_at = datetime.now().timestamp() + logger.info(f"⏸️ Серия на паузе, {remaining} кликов сохранено (истечёт через {SERIES_EXPIRY//60}мин)") + while params.paused and not params.stopped: + await asyncio.sleep(2) + if params.stopped: + return - if i < visits_count - 1: - await asyncio.sleep(params.get_delay()) except Exception as e: logger.error(f"Process URL error: {e}") diff --git a/services/socks5_to_http_proxy.py b/services/socks5_to_http_proxy.py index db32486..021e62e 100644 --- a/services/socks5_to_http_proxy.py +++ b/services/socks5_to_http_proxy.py @@ -127,7 +127,7 @@ class Socks5ToHttpProxy: return request_text = request_line.decode('utf-8', errors='ignore').strip() - logger.info(f"Tunnel request: {request_text[:80]}") + logger.debug(f"Tunnel request: {request_text[:80]}") if request_text.upper().startswith('CONNECT'): await self._handle_connect(request_text, reader, writer) @@ -183,7 +183,7 @@ class Socks5ToHttpProxy: from python_socks import ProxyType if self.username: - logger.info(f"SOCKS5 connect {host}:{port} via {self.socks5_host}:{self.socks5_port} auth=yes user={self.username[:4]}***") + logger.debug(f"SOCKS5 connect {host}:{port} via {self.socks5_host}:{self.socks5_port} auth=yes") proxy = Proxy.create( ProxyType.SOCKS5, self.socks5_host, self.socks5_port, @@ -209,7 +209,7 @@ class Socks5ToHttpProxy: await writer.drain() return - logger.info(f"SOCKS5 connected {host}:{port} OK") + logger.debug(f"SOCKS5 connected {host}:{port} OK") writer.write(b'HTTP/1.1 200 Connection Established\r\n\r\n') await writer.drain()