ForcePilot/backend/package/yuxi/channel/extensions/irc/errors.py
Kris 061cc7076b feat(channel): 添加 IRC 渠道扩展插件
新增 IRC 渠道的完整扩展实现,包括客户端连接管理、消息收发与流式处理、安全策略与配对验证、消息去重与规范化、状态监控与健康探测、配置模型与账户管理、错误处理等模块。
2026-05-21 10:59:12 +08:00

93 lines
3.1 KiB
Python

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),
)