ForcePilot/backend/package/yuxi/channels/adapters/imessage/normalize.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

76 lines
2.1 KiB
Python

from __future__ import annotations
import re
from typing import Any
_E164_RE = re.compile(r"^\+[1-9]\d{1,14}$")
_NON_DIGIT_RE = re.compile(r"[^\d+]")
_PHONE_CLEAN_RE = re.compile(r"[\s\-\(\)\.]")
def normalize_e164(handle: str, default_country_code: str = "") -> str:
"""将电话号码标准化为 E.164 格式。
去除空格、括号、连字符,补全国际区号。
对已经是国际格式的号码保留 '+' 前缀。
"""
clean = _PHONE_CLEAN_RE.sub("", handle.strip())
clean = _strip_service_prefixes(clean)
if not clean:
return handle
if clean.startswith("+"):
digits = clean[1:]
if digits.isdigit() and len(digits) <= 15:
return clean
return handle
if clean.startswith("00"):
digits = clean[2:]
if digits.isdigit() and len(digits) <= 15:
return f"+{digits}"
if clean.startswith("011"):
digits = clean[3:]
if digits.isdigit() and len(digits) <= 15:
return f"+{digits}"
if clean.isdigit() and default_country_code:
return f"+{default_country_code}{clean}"
if clean.isdigit() and len(clean) <= 15:
return f"+{clean}"
return handle
def _strip_service_prefixes(handle: str) -> str:
for prefix in ("imessage:", "sms:", "auto:"):
if handle.lower().startswith(prefix):
return handle[len(prefix) :]
return handle
def is_e164(handle: str) -> bool:
return bool(_E164_RE.match(handle))
def extract_digits(handle: str) -> str:
return _NON_DIGIT_RE.sub("", handle)
def parse_forwarded(data: dict) -> dict[str, Any] | None:
forwarded = data.get("forwarded")
if not forwarded:
return None
result: dict[str, Any] = {"is_forwarded": True}
if isinstance(forwarded, dict):
result["forwarded_from"] = forwarded.get("handle") or forwarded.get("sender", "")
result["forwarded_date"] = forwarded.get("date") or forwarded.get("originalDate", "")
elif isinstance(forwarded, bool) and forwarded:
result["forwarded_from"] = data.get("forwardedFrom", "")
return result