from __future__ import annotations import re _RE_STRIP_MARKDOWN_LINK = re.compile(r"\[([^\]]*)\]\([^)]+\)") _RE_STRIP_MARKDOWN_IMAGE = re.compile(r"!\[([^\]]*)\]\([^)]+\)") def markdown_to_plaintext(md_text: str) -> str: result = md_text result = re.sub(r"\*\*(.+?)\*\*", lambda m: m.group(1).upper(), result) result = re.sub(r"__([^_]+)__", lambda m: m.group(1).upper(), result) result = re.sub(r"\*([^*]+)\*", r"_\1_", result) result = re.sub(r"_([^_]+)_", r"_\1_", result) result = re.sub(r"~~(.+?)~~", "", result) result = _RE_STRIP_MARKDOWN_IMAGE.sub(r"[\1]", result) result = _RE_STRIP_MARKDOWN_LINK.sub(lambda m: f"{m.group(1)} ({m.group(0).split('](')[1].rstrip(')')})", result) result = re.sub(r"```(\w*)\n(.*?)```", r"[CODE]\n\2\n[/CODE]", result, flags=re.DOTALL) result = result.replace(" & ", " & ") result = result.replace(" < ", " < ") result = result.replace(" > ", " > ") return result.strip() def convert_table_to_bullets(md_text: str) -> str: lines = md_text.split("\n") result: list[str] = [] in_table = False headers: list[str] = [] for line in lines: stripped = line.strip() if stripped.startswith("|") and stripped.endswith("|"): cells = [c.strip() for c in stripped[1:-1].split("|")] if not in_table: headers = cells in_table = True continue if all(re.match(r"^[-:]+$", c) for c in cells): continue for i, header in enumerate(headers): value = cells[i] if i < len(cells) else "" result.append(f" {header}: {value}") else: if in_table: in_table = False result.append(line) return "\n".join(result) def split_plaintext_chunks(text: str, limit: int = 4000) -> list[str]: if len(text) <= limit: return [text] chunks: list[str] = [] lines = text.split("\n") current = "" for line in lines: if len(current) + len(line) + 1 > limit and current: chunks.append(current.rstrip()) current = line else: current = current + "\n" + line if current else line if current: chunks.append(current.rstrip()) return chunks class IMessageFormatAdapter: def format_for_imessage(self, md_text: str) -> str: text = convert_table_to_bullets(md_text) return markdown_to_plaintext(text) def chunk_text(self, text: str, limit: int = 4000) -> list[str]: return split_plaintext_chunks(text, limit)