ForcePilot/backend/package/yuxi/channels/adapters/imessage/targets.py
Kris 068cf70fe9 feat(imessage): 实现完整的 iMessage 适配器基础模块
新增 iMessage 通道适配器完整实现,包含:
1. 核心适配器与工具工厂导出
2. 运行时存储、反射防护、会话路由等基础组件
3. 消息信封、线程管理、回复上下文格式化
4. Tapback 表情反应处理、自定义异常体系
5. 审批按钮、联系人解析、速率限制功能
6. 文本净化、目标解析、缓存管理模块
7. 配置 schema、多账户支持、安装向导等配置模块
8. 审计日志、媒体AI处理等扩展功能
2026-05-12 00:44:44 +08:00

86 lines
2.3 KiB
Python

from __future__ import annotations
from yuxi.channels.models import ChatType
TARGET_PREFIX_MAP: dict[str, str] = {
"chat_id:": "chat_id",
"chat_guid:": "chat_guid",
"chat_identifier:": "chat_identifier",
"handle:": "handle",
"phone:": "phone",
"email:": "email",
}
EXPLICIT_TARGET_RE_PREFIXES = (
"chat_id:",
"chat_guid:",
"chat_identifier:",
"iMessage;",
"iMessage",
"handle:",
"phone:",
"email:",
)
IMESSAGE_TARGET_PREFIXES = (
"chat_id:",
"chat_guid:",
"chat_identifier:",
"iMessage;",
"iMessage",
"handle:",
"phone:",
"email:",
"tel:",
"mailto:",
"+",
)
def parse_target(raw_target: str) -> dict[str, str]:
"""解析目标字符串,返回 {type, value}。
支持的类型:
- chat_id:<value> → chat_id
- chat_guid:<value> → chat_guid
- chat_identifier:<value> → chat_identifier (chat_id 或 chat_guid)
- handle:<value> → handle
- phone:<value> → phone (E.164)
- email:<value> → email
- 其他:自动推断为 handle
"""
if not raw_target:
return {"type": "unknown", "value": ""}
raw_lower = raw_target.lower()
for prefix, type_name in TARGET_PREFIX_MAP.items():
if raw_lower.startswith(prefix):
return {"type": type_name, "value": raw_target[len(prefix) :]}
if looks_like_explicit_target_id(raw_target):
if ";-;" in raw_target or "@chat" in raw_target:
return {"type": "chat_guid", "value": raw_target}
if raw_target.startswith("iMessage;"):
return {"type": "chat_id", "value": raw_target}
if "@" in raw_target and "." in raw_target.split("@")[-1]:
return {"type": "email", "value": raw_target}
return {"type": "handle", "value": raw_target}
def looks_like_explicit_target_id(target: str) -> bool:
return any(target.lower().startswith(p.lower()) for p in EXPLICIT_TARGET_RE_PREFIXES)
def looks_like_imessage_target_id(target: str) -> bool:
return any(target.lower().startswith(p.lower()) for p in IMESSAGE_TARGET_PREFIXES)
def resolve_chat_type_from_target(target: str) -> ChatType:
parsed = parse_target(target)
value = parsed["value"]
if ";-;" in value or "@chat" in value:
return ChatType.GROUP
return ChatType.DIRECT