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 字符(换行标准化) - 去除 内联指令标签 - 去除长度前缀损坏(如 \"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