add check

This commit is contained in:
Yuriy Yuriev
2026-05-22 18:22:41 +07:00
parent fbc01dd29f
commit 1529f27540
3 changed files with 146 additions and 41 deletions
+8 -37
View File
@@ -85,16 +85,15 @@ class TwitchIRCClient:
decoded = line.decode('utf-8', errors='ignore').strip()
# PING от Twitch — отвечаем СРАЗУ, возвращаем None чтобы не путать с нашим PONG
# PING от Twitch — отвечаем СРАЗУ и возвращаем строку (обновит last_message_time)
if decoded.startswith('PING'):
pong_msg = f"PONG {decoded.split()[1]}"
self._writer.write(f"{pong_msg}\r\n".encode())
self._writer.write(b"PONG :tmi.twitch.tv\r\n")
try:
await self._writer.drain()
except (ConnectionResetError, BrokenPipeError, OSError) as e:
logger.warning(f"Failed to send PONG: {e}")
logger.debug(f"🏓 {pong_msg}")
return None # не пробрасываем в основной цикл
logger.debug("🏓 PONG :tmi.twitch.tv")
return decoded # возвращаем чтобы основной цикл обновил last_message_time
return decoded
@@ -176,11 +175,6 @@ class TwitchIRCClient:
start_time = datetime.now()
last_message_time = datetime.now()
last_ping_time = datetime.now()
ping_interval = 120 # пинговать каждые 2 минуты
pong_timeout = 30 # ждать PONG 30 сек, иначе реконнект
waiting_pong = False
ping_sent_at: Optional[float] = None
drain_errors = 0
# Если задача стартует на паузе — ждём resume до подключения к IRC
@@ -205,40 +199,19 @@ class TwitchIRCClient:
logger.info(f"▶️ #{self.channel} resumed, reconnecting")
await self.connect()
last_message_time = datetime.now()
last_ping_time = datetime.now()
waiting_pong = False
continue
# Нет соединения — ждём перед повтором
if not self._connected:
await asyncio.sleep(5)
await self.connect()
last_ping_time = datetime.now()
waiting_pong = False
last_message_time = datetime.now()
continue
now = datetime.now()
# === PONG не пришёл вовремя — соединение мёртвое, реконнект ===
if waiting_pong and ping_sent_at and (now.timestamp() - ping_sent_at) > pong_timeout:
logger.warning(f"🔌 #{self.channel} PONG timeout, reconnecting...")
waiting_pong = False
self._connected = False
continue
# === Отправляем PING каждые ping_interval секунд ===
if not waiting_pong and (now - last_ping_time).seconds >= ping_interval:
try:
await self._send("PING :tmi.twitch.tv")
ping_sent_at = now.timestamp()
waiting_pong = True
last_ping_time = now
except Exception:
self._connected = False
continue
# === Реконнект если вообще нет активности > 5 минут (failsafe) ===
if (now - last_message_time).seconds > 300:
# === Реконнект если нет активности > 6 минут (Twitch PING каждые ~5 мин) ===
if (now - last_message_time).total_seconds() > 360:
logger.info("🔄 No activity 5min, reconnecting...")
try:
await self.disconnect()
@@ -272,10 +245,8 @@ class TwitchIRCClient:
await asyncio.sleep(0.5)
continue
# Twitch отвечает: ":tmi.twitch.tv PONG tmi.twitch.tv :..." — не startswith
if 'PONG' in line:
if 'PONG' in line or line.startswith('PING'):
last_message_time = datetime.now()
waiting_pong = False
continue
msg = self._parse_message(line)
+42 -4
View File
@@ -44,7 +44,31 @@ class Socks5ToHttpProxy:
@property
def proxy_config_for_browser(self) -> dict:
return {'server': self.local_url}
async def check_connection(
self,
test_url: str = "https://www.google.com/generate_204",
timeout: float = 10.0,
) -> bool:
"""Проверяет связность туннеля: делает запрос через локальный HTTP прокси."""
if not self._server or not self._port:
return False
try:
from aiohttp import ClientSession, ClientTimeout
from aiohttp import TCPConnector
proxy_url = self.local_url
async with ClientSession(
connector=TCPConnector(),
timeout=ClientTimeout(total=timeout),
) as session:
async with session.get(test_url, proxy=proxy_url, allow_redirects=False) as resp:
ok = resp.status in (200, 204)
logger.debug(f"Tunnel check {self.socks5_host}:{self.socks5_port}{resp.status} ok={ok}")
return ok
except Exception as e:
logger.debug(f"Tunnel check failed {self.socks5_host}:{self.socks5_port}: {e}")
return False
async def start(self) -> str:
"""Запускает прокси сервер."""
self._port = self._find_free_port()
@@ -404,22 +428,36 @@ class Socks5ProxyPool:
if key in self._last_used:
self._last_used[key] = 0
async def check_proxy(
self,
socks5_proxy,
test_url: str = "https://www.google.com/generate_204",
timeout: float = 10.0,
) -> bool:
"""Запускает туннель для прокси (если не запущен) и проверяет связность."""
await self.get_proxy_config(socks5_proxy)
async with self._lock:
tunnel = self._proxies.get(socks5_proxy.id)
if not tunnel:
return False
return await tunnel.check_connection(test_url=test_url, timeout=timeout)
async def stop_all(self):
"""Останавливает все прокси с таймаутом."""
if self._cleanup_task:
self._cleanup_task.cancel()
async with self._lock:
proxies = list(self._proxies.values())
self._proxies.clear()
self._last_used.clear()
for proxy in proxies:
try:
await asyncio.wait_for(proxy.stop(), timeout=3)
except (asyncio.TimeoutError, Exception):
pass
logger.info("All SOCKS5 proxies stopped")
@property