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

125 lines
3.7 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
TABLE_ROW_RE = re.compile(r"^\|(.+)\|$")
TABLE_SEP_RE = re.compile(r"^\|[\s\-:]+\|$")
DIRECTIVE_RE = re.compile(r"<\s*/?directive[^>]*>", re.IGNORECASE)
def convert_markdown_tables(text: str) -> str:
"""将 Markdown 表格转换为可读的 bullets 格式。
iMessage 不支持 Markdown 表格渲染,此函数将表格行转为可读的列表。
"""
lines = text.split("\n")
result: list[str] = []
i = 0
while i < len(lines):
line = lines[i]
if i + 2 < len(lines) and TABLE_ROW_RE.match(line) and TABLE_SEP_RE.match(lines[i + 1]):
header = line
sep = lines[i + 1]
body_rows: list[str] = []
j = i + 2
while j < len(lines) and TABLE_ROW_RE.match(lines[j]):
body_rows.append(lines[j])
j += 1
converted = _table_to_bullets(header, sep, body_rows)
result.extend(converted)
i = j
continue
result.append(line)
i += 1
return "\n".join(result)
def _table_to_bullets(header: str, sep: str, body_rows: list[str]) -> list[str]:
header_cells = _parse_cells(header)
sep_cells = _parse_cells(sep)
if not header_cells:
return [header, sep] + body_rows
alignments = _detect_alignments(sep_cells, len(header_cells))
body_cells = [_parse_cells(row) for row in body_rows]
output: list[str] = []
for row_idx, cells in enumerate([header_cells] + body_cells):
if row_idx == 0:
output.append(f"{', '.join(cells)}")
elif len(cells) <= 2:
output.append(" " + ": ".join(cells))
else:
parts: list[str] = []
for ci, (cell, hdr, align) in enumerate(zip(cells, header_cells, alignments)):
prefix = f" {hdr}: " if ci == 0 or align != "skip" else f" {hdr}: "
parts.append(f"{prefix}{cell}")
output.append("")
output.extend(parts)
return output
def _parse_cells(row: str) -> list[str]:
return [cell.strip() for cell in row.strip("|").split("|")]
def _detect_alignments(sep_cells: list[str], col_count: int) -> list[str]:
aligns: list[str] = []
for i in range(col_count):
if i < len(sep_cells):
cell = sep_cells[i]
left = cell.startswith(":")
right = cell.endswith(":")
if left and right:
aligns.append("center")
elif right:
aligns.append("right")
else:
aligns.append("left")
else:
aligns.append("left")
return aligns
def sanitize_imessage_text(text: str) -> str:
"""净化出站文本,移除 iMessage 中不支持的格式。
- 去除 \\r 字符(换行标准化)
- 去除 <directive> 内联指令标签
- 去除长度前缀损坏(如 \"123\\nThis is message\"
- 过滤控制字符
- 合并多余空白行
"""
text = text.replace("\r", "")
text = DIRECTIVE_RE.sub("", text)
text = _strip_length_prefix(text)
text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]", "", text)
text = re.sub(r"\n{3,}", "\n\n", text)
text = text.strip()
return text
def _strip_length_prefix(text: str) -> str:
"""去除 iMessage 长度前缀损坏格式。
BlueBubbles 可能在某些消息前附加长度前缀如 \"42\\nHello world\"
去除数字换行前缀。
"""
match = re.match(r"^(\d+)\n", text)
if match:
prefix_len = int(match.group(1))
remaining = text[match.end() :]
if abs(len(remaining) - prefix_len) <= 5:
return remaining
return text