import asyncio import logging import ssl import time from yuxi.channel.extensions.irc.client import send_line from yuxi.channel.extensions.irc.protocol import parse_irc_line from yuxi.channel.extensions.irc.types import IrcProbe logger = logging.getLogger(__name__) async def probe_irc( host: str, port: int, tls: bool, nick: str, timeout: float = 8.0, ) -> IrcProbe: try: ssl_context = ssl.create_default_context() if tls else None start = time.monotonic() reader, writer = await asyncio.wait_for( asyncio.open_connection(host, port, ssl=ssl_context), timeout=timeout, ) send_line(writer, f"NICK {nick}") send_line(writer, f"USER probe 0 * :Probe") await writer.drain() got_001 = False while not got_001: try: line_bytes = await asyncio.wait_for(reader.readline(), timeout=timeout) except asyncio.TimeoutError: break if not line_bytes: break line = line_bytes.decode("utf-8", errors="replace").rstrip("\r\n") parsed = parse_irc_line(line) if parsed.command == "001": got_001 = True break if parsed.command in ("432", "433", "464", "465"): break send_line(writer, "QUIT :probe") await writer.drain() writer.close() await writer.wait_closed() end = time.monotonic() latency_ms = (end - start) * 1000 return IrcProbe( ok=got_001, host=host, port=port, tls=tls, nick=nick, latency_ms=latency_ms, ) except Exception as e: logger.warning("IRC probe failed for %s:%d: %s", host, port, e) return IrcProbe( ok=False, host=host, port=port, tls=tls, nick=nick, error=str(e), )