import logging from enum import Enum from yuxi.channel.extensions.irc.client import IRCFatalError from yuxi.channel.protocols import ClassifiedError, ErrorSeverity logger = logging.getLogger(__name__) class IrcErrorType(str, Enum): AUTH = "auth" NETWORK = "network" CHANNEL = "channel" NICK = "nick" RATE_LIMIT = "rate_limit" PERMISSION = "permission" TARGET = "target" UNKNOWN = "unknown" NUMERIC_ERROR_MAP: dict[str, tuple[IrcErrorType, str]] = { "401": (IrcErrorType.TARGET, "No such nick/channel"), "403": (IrcErrorType.TARGET, "No such channel"), "404": (IrcErrorType.PERMISSION, "Cannot send to channel"), "405": (IrcErrorType.CHANNEL, "Too many channels"), "432": (IrcErrorType.NICK, "Erroneous nickname"), "433": (IrcErrorType.NICK, "Nickname in use"), "436": (IrcErrorType.NICK, "Nick collision"), "464": (IrcErrorType.AUTH, "Password mismatch"), "465": (IrcErrorType.AUTH, "You are banned"), "471": (IrcErrorType.CHANNEL, "Channel is full"), "473": (IrcErrorType.CHANNEL, "Invite only channel"), "474": (IrcErrorType.PERMISSION, "Banned from channel"), "475": (IrcErrorType.CHANNEL, "Bad channel key"), "482": (IrcErrorType.PERMISSION, "ChanOp privileges needed"), } def classify_irc_error(numeric: str) -> tuple[IrcErrorType, str]: return NUMERIC_ERROR_MAP.get(numeric, (IrcErrorType.UNKNOWN, f"Unknown error ({numeric})")) _DEFAULT_RETRY_AFTER_MS = 2000 _IRC_NUMERIC_TO_SEVERITY: dict[str, ErrorSeverity] = { IrcErrorType.AUTH: ErrorSeverity.FATAL, IrcErrorType.NETWORK: ErrorSeverity.NETWORK, IrcErrorType.NICK: ErrorSeverity.FATAL, IrcErrorType.RATE_LIMIT: ErrorSeverity.RATE_LIMITED, IrcErrorType.PERMISSION: ErrorSeverity.FORBIDDEN, IrcErrorType.TARGET: ErrorSeverity.FATAL, IrcErrorType.CHANNEL: ErrorSeverity.FATAL, IrcErrorType.UNKNOWN: ErrorSeverity.FATAL, } def classify_error(error: BaseException) -> ClassifiedError: if isinstance(error, IRCFatalError): msg = str(error).upper() for numeric, (error_type, _) in NUMERIC_ERROR_MAP.items(): if numeric in msg: severity = _IRC_NUMERIC_TO_SEVERITY.get(error_type, ErrorSeverity.FATAL) return ClassifiedError( severity=severity, original_error=error, error_message=str(error), ) return ClassifiedError( severity=ErrorSeverity.FATAL, original_error=error, error_message=str(error), ) if isinstance(error, (ConnectionError, OSError)): return ClassifiedError( severity=ErrorSeverity.NETWORK, original_error=error, error_message=str(error), ) if isinstance(error, TimeoutError): return ClassifiedError( severity=ErrorSeverity.NETWORK, original_error=error, error_message=str(error), ) return ClassifiedError( severity=ErrorSeverity.RETRYABLE, original_error=error, error_message=str(error), )