ForcePilot/backend/package/yuxi/channels/adapters/whatsapp/markdown.py
Kris e9b57546ea feat(whatsapp): 新增WhatsApp适配器完整功能模块
新增大量WhatsApp适配器相关代码,包括账号管理、会话处理、消息收发、验证授权、媒体处理、互动命令、审批流程、健康检测等完整功能模块,搭建基础的Baileys协议WhatsApp接入能力
2026-05-12 00:51:58 +08:00

133 lines
3.2 KiB
Python

from __future__ import annotations
import re
_ALLOWED_TAGS = {
"b",
"strong",
"i",
"em",
"u",
"ins",
"s",
"strike",
"del",
"code",
"pre",
"a",
}
_TAG_PATTERN = re.compile(r"</?(\w+)[^>]*>")
_TABLE_ROW_PATTERN = re.compile(r"^\|(.+)\|$", re.MULTILINE)
_TABLE_SEPARATOR_PATTERN = re.compile(r"^\|[\s\-:|]+\|$", re.MULTILINE)
def text_sanitizer(text: str) -> str:
if not text:
return ""
text = text.replace("&nbsp;", " ")
def _replace_tag(m: re.Match) -> str:
tag = m.group(1).lower()
if tag in _ALLOWED_TAGS:
return m.group(0)
return ""
text = _TAG_PATTERN.sub(_replace_tag, text)
text = text.strip()
return text
def markdown_to_whatsapp(md_text: str) -> str:
if not md_text:
return ""
text = md_text
text = re.sub(r"```(\w+)?\n(.*?)```", _code_block, text, flags=re.DOTALL)
text = re.sub(r"`([^`]+)`", r"```\1```", text)
text = _convert_tables(text)
text = re.sub(r"\*\*\*(.+?)\*\*\*", r"*_\1_*", text)
text = re.sub(r"___(.+?)___", r"*_\1_*", text)
text = re.sub(r"\*\*(.+?)\*\*", r"*\1*", text)
text = re.sub(r"__(.+?)__", r"*\1*", text)
text = re.sub(r"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)", r"_\1_", text)
text = re.sub(r"(?<!_)_(?!_)(.+?)(?<!_)_(?!_)", r"_\1_", text)
text = re.sub(r"~~(.+?)~~", r"~\1~", text)
text = re.sub(r"^### (.+)$", r"*_\1_*", text, flags=re.MULTILINE)
text = re.sub(r"^## (.+)$", r"*_\1_*", text, flags=re.MULTILINE)
text = re.sub(r"^# (.+)$", r"*_\1_*", text, flags=re.MULTILINE)
text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", _link, text)
text = re.sub(r"^\- (.+)$", r"\1", text, flags=re.MULTILINE)
text = re.sub(r"^\d+\. (.+)$", r"\1", text, flags=re.MULTILINE)
text = text.replace("<br>", "\n")
text = re.sub(r"\n{3,}", "\n\n", text)
return text.strip()
def _convert_tables(text: str) -> str:
def _replace_table(m: re.Match) -> str:
table_block = m.group(0)
lines = table_block.strip().split("\n")
if len(lines) < 2:
return table_block
data_lines = []
header = None
for line in lines:
stripped = line.strip()
if not stripped.startswith("|"):
continue
if _TABLE_SEPARATOR_PATTERN.match(stripped):
continue
cells = [c.strip() for c in stripped[1:-1].split("|")]
if header is None:
header = " | ".join(cells)
else:
data_lines.append("" + " | ".join(cells))
if header is None:
return table_block
result = [header]
result.extend(data_lines)
return "\n".join(result)
text = re.sub(
r"(?:^|\n)(\|.+\|\s*\n){2,}",
_replace_table,
text,
flags=re.MULTILINE,
)
return text
def _code_block(m: re.Match) -> str:
lang = m.group(1) or ""
code = m.group(2).strip()
prefix = f"[{lang}]\n" if lang else ""
return f"```{prefix}{code}```"
def _link(m: re.Match) -> str:
text = m.group(1)
url = m.group(2)
if text == url:
return url
if not text.strip():
return url
return f"{text}\n({url})"