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

68 lines
1.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import re
_CONTROL_CHAR_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]")
_MULTI_NEWLINE_RE = re.compile(r"\n{3,}")
_LENGTH_PREFIX_RE = re.compile(r"^(\d+)\n")
_TERMINAL_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*[a-zA-Z]")
_MAX_TERMINAL_MSG_LEN = 200
def sanitize_terminal_text(text: str, max_len: int = _MAX_TERMINAL_MSG_LEN) -> str:
"""净化终端输出文本,移除 ANSI 转义序列和控制字符。
用于日志记录和错误消息净化。
"""
if not text:
return ""
text = _TERMINAL_ESCAPE_RE.sub("", text)
text = _CONTROL_CHAR_RE.sub("", text)
if len(text) > max_len:
text = text[:max_len] + "..."
return text
def sanitize_outbound_text(text: str) -> str:
"""净化出站文本,移除 iMessage 中不支持的格式。
- 去除 \\r 字符(换行标准化)
- 去除长度前缀损坏(如 "42\\nThis is message"
- 过滤控制字符
- 合并多余空白行
"""
text = text.replace("\r", "")
text = _strip_length_prefix(text)
text = _CONTROL_CHAR_RE.sub("", text)
text = _MULTI_NEWLINE_RE.sub("\n\n", text)
text = text.strip()
return text
def _strip_length_prefix(text: str) -> str:
match = _LENGTH_PREFIX_RE.match(text)
if match:
prefix_len = int(match.group(1))
remaining = text[match.end() :]
if abs(len(remaining) - prefix_len) <= 5:
return remaining
return text
def media_placeholder(media_type: str) -> str:
"""为纯媒体消息生成占位文本。
OpenClaw 在无文本媒体消息中生成 <media:image> 等占位符。
"""
placeholders = {
"image": "<media:image>",
"video": "<media:video>",
"audio": "<media:audio>",
"file": "<media:file>",
}
return placeholders.get(media_type, "<media:file>")