新增IRC协议相关的全套工具模块,包括: - 核心协议解析与CTCP处理 - 消息发送缓存与文本 sanitize - 账号配置管理与运行时状态 - 命令处理与权限控制 - 服务发现与诊断工具 - 多账号网关与配置加载
105 lines
2.5 KiB
Python
105 lines
2.5 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
_IRC_LINE_PATTERN = re.compile(
|
|
r"^(?:@(?P<tags>[^ ]+) )?(?::(?P<prefix>[^ ]+) )?(?P<command>[a-zA-Z]+|\d{3})"
|
|
r"(?: (?P<params>.+?))?(?: :(?P<trailing>.*))?$"
|
|
)
|
|
|
|
_IRC_PREFIX_PATTERN = re.compile(r"^([^!@]+)(?:!([^@]+))?(?:@(.+))?$")
|
|
|
|
|
|
def parse_irc_line(line: str) -> tuple[str | None, str, list[str]]:
|
|
match = _IRC_LINE_PATTERN.match(line)
|
|
if match is None:
|
|
return None, line, []
|
|
|
|
command = match.group("command").upper()
|
|
prefix = match.group("prefix") or None
|
|
|
|
args: list[str] = []
|
|
params = match.group("params") or ""
|
|
if params:
|
|
args.extend(params.split())
|
|
|
|
trailing = match.group("trailing")
|
|
if trailing is not None:
|
|
args.append(trailing)
|
|
|
|
return prefix, command, args
|
|
|
|
|
|
def parse_irc_prefix(prefix: str | None) -> dict[str, str | None]:
|
|
if not prefix:
|
|
return {"nick": None, "user": None, "host": None}
|
|
|
|
m = _IRC_PREFIX_PATTERN.match(prefix)
|
|
if m is None:
|
|
return {"nick": prefix, "user": None, "host": None}
|
|
|
|
return {
|
|
"nick": m.group(1) or None,
|
|
"user": m.group(2) or None,
|
|
"host": m.group(3) or None,
|
|
}
|
|
|
|
|
|
def extract_nick(prefix: str | None) -> str:
|
|
if not prefix:
|
|
return "unknown"
|
|
return prefix.split("!")[0]
|
|
|
|
|
|
def parse_irc_tags(line: str) -> dict[str, Any] | None:
|
|
if not line.startswith("@"):
|
|
return None
|
|
|
|
space_idx = line.find(" ")
|
|
if space_idx == -1:
|
|
return None
|
|
|
|
tag_str = line[1:space_idx]
|
|
tags: dict[str, Any] = {}
|
|
|
|
for tag in tag_str.split(";"):
|
|
if "=" in tag:
|
|
key, value = tag.split("=", 1)
|
|
tags[key] = value
|
|
else:
|
|
tags[tag] = True
|
|
|
|
return tags
|
|
|
|
|
|
def parse_irc_tags_dict(line: str) -> dict[str, Any]:
|
|
return parse_irc_tags(line) or {}
|
|
|
|
|
|
def is_ctcp_message(text: str) -> bool:
|
|
return text.startswith("\x01") and text.endswith("\x01")
|
|
|
|
|
|
def parse_ctcp(text: str) -> tuple[str, str]:
|
|
inner = text[1:-1]
|
|
parts = inner.split(" ", 1)
|
|
command = parts[0].upper()
|
|
args = parts[1] if len(parts) > 1 else ""
|
|
return command, args
|
|
|
|
|
|
def format_ctcp(command: str, args: str = "") -> str:
|
|
if args:
|
|
return f"\x01{command} {args}\x01"
|
|
return f"\x01{command}\x01"
|
|
|
|
|
|
def extract_account_from_tags(tags: dict[str, Any]) -> str | None:
|
|
return tags.get("account") or None
|
|
|
|
|
|
def is_identified(tags: dict[str, Any]) -> bool:
|
|
account = extract_account_from_tags(tags)
|
|
return account is not None and account != "*"
|