ForcePilot/backend/package/yuxi/channels/adapters/irc/normalizer.py
Kris 2dd1a075f2 feat(irc): 实现完整的IRC适配器基础功能模块
新增IRC协议相关的全套工具模块,包括:
- 核心协议解析与CTCP处理
- 消息发送缓存与文本 sanitize
- 账号配置管理与运行时状态
- 命令处理与权限控制
- 服务发现与诊断工具
- 多账号网关与配置加载
2026-05-12 00:45:10 +08:00

93 lines
2.5 KiB
Python

from __future__ import annotations
import re
import time
import uuid
from typing import Any
from yuxi.channels.models import (
ChannelIdentity,
ChannelMessage,
ChannelType,
ChatType,
EventType,
MentionsInfo,
MessageType,
)
def _parse_irc_time_tag(time_tag: str) -> int:
"""解析 IRCv3 server-time 标签为毫秒时间戳"""
import datetime
try:
ts_str = time_tag.replace("Z", "+00:00")
dt = datetime.datetime.fromisoformat(ts_str)
return int(dt.timestamp() * 1000)
except (ValueError, TypeError):
return int(time.time() * 1000)
def normalize_inbound(
raw: dict[str, Any],
channel_id: str,
channel_type: ChannelType,
nick: str,
) -> ChannelMessage | None:
sender_nick = raw.get("sender_nick", "unknown")
target = raw.get("target", "")
text = raw.get("text", "")
if not text or not text.strip():
return None
is_channel = target.startswith("#") or target.startswith("&")
chat_id = target if is_channel else f"dm_{sender_nick}"
chat_type = ChatType.GROUP if is_channel else ChatType.DIRECT
mentioned_user_ids: list[str] = []
is_bot_mentioned = _check_bot_mention(text, nick)
if is_channel:
for word in text.split():
if word.startswith("@"):
mentioned_user_ids.append(word[1:])
server_time = raw.get("time")
ts = _parse_irc_time_tag(server_time) if server_time else int(time.time() * 1000)
return ChannelMessage(
identity=ChannelIdentity(
channel_id=channel_id,
channel_type=channel_type,
channel_user_id=sender_nick,
channel_chat_id=chat_id,
channel_message_id=f"{sender_nick}:{ts}:{uuid.uuid4().hex[:8]}",
),
event_type=EventType.MESSAGE_RECEIVED,
message_type=MessageType.TEXT,
chat_type=chat_type,
content=text,
mentions=MentionsInfo(
mentioned_user_ids=mentioned_user_ids,
is_bot_mentioned=is_bot_mentioned,
),
metadata={
"irc_target": target,
"is_channel": is_channel,
"account": raw.get("account"),
},
)
def resolve_chat_id(target: str, sender_nick: str) -> str:
from .session import resolve_chat_id as _resolve
return _resolve(target, sender_nick)
def _check_bot_mention(text: str, nick: str) -> bool:
"""Check if bot nick is mentioned with word boundary awareness."""
pattern = rf"\b{re.escape(nick)}\b[:,]?"
return bool(re.search(pattern, text))