68 lines
1.8 KiB
Python
68 lines
1.8 KiB
Python
|
|
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>")
|