ForcePilot/backend/package/yuxi/channels/adapters/matrix/formatter.py
Kris 2063145ce1 feat(matrix): 新增完整的Matrix适配器工具集
新增了20+个Matrix相关工具模块,包含线程处理、自动加入、消息去重、配置管理、安全策略、媒体处理、命令解析等功能,完善Matrix适配器基础能力
2026-05-12 00:45:54 +08:00

74 lines
2.2 KiB
Python

from __future__ import annotations
import html
import re
_MD_TO_HTML_MAP = [
(r"\*\*(.+?)\*\*", r"<b>\1</b>"),
(r"__(.+?)__", r"<b>\1</b>"),
(r"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)", r"<i>\1</i>"),
(r"(?<!_)_(?!_)(.+?)(?<!_)_(?!_)", r"<i>\1</i>"),
(r"~~(.+?)~~", r"<del>\1</del>"),
(r"`([^`\n]+?)`", r"<code>\1</code>"),
]
_HEADING_PATTERN = re.compile(r"^(#{1,6})\s+(.+)$", re.MULTILINE)
_LINK_PATTERN = re.compile(r"\[(.+?)\]\((.+?)\)")
_CODE_BLOCK_PATTERN = re.compile(r"```(?:\w+)?\n(.+?)```", re.DOTALL)
_BLOCKQUOTE_PATTERN = re.compile(r"^>\s?(.*)$", re.MULTILINE)
_HR_PATTERN = re.compile(r"^(?:[-*_]\s*){3,}$", re.MULTILINE)
_UNORDERED_LIST_PATTERN = re.compile(r"^[\-\*\+]\s+(.+)$", re.MULTILINE)
_ORDERED_LIST_PATTERN = re.compile(r"^\d+\.\s+(.+)$", re.MULTILINE)
_UNSAFE_TAGS = re.compile(
r"<\s*(script|iframe|object|embed|form|input|svg|style|link|meta|base|applet)[^>]*>",
re.IGNORECASE,
)
_UNSAFE_ATTRS = re.compile(
r'\s(on\w+|href\s*=\s*["\']javascript:|style\s*=\s*["\']\s*expression)',
re.IGNORECASE,
)
def markdown_to_matrix_html(text: str) -> str:
result = text
result = _CODE_BLOCK_PATTERN.sub(_code_block_replacement, result)
result = _HEADING_PATTERN.sub(_heading_replacement, result)
for pattern, replacement in _MD_TO_HTML_MAP:
result = re.sub(pattern, replacement, result)
result = _LINK_PATTERN.sub(r'<a href="\2">\1</a>', result)
result = _UNORDERED_LIST_PATTERN.sub(r"<li>\1</li>", result)
result = _ORDERED_LIST_PATTERN.sub(r"<li>\1</li>", result)
result = _BLOCKQUOTE_PATTERN.sub(r"<blockquote>\1</blockquote>", result)
result = _HR_PATTERN.sub(r"<hr>", result)
result = result.replace("\n", "<br>")
result = _sanitize_html(result)
return result
def _code_block_replacement(match: re.Match) -> str:
content = match.group(1)
safe_content = html.escape(content)
return f"<pre><code>{safe_content}</code></pre>"
def _heading_replacement(match: re.Match) -> str:
level = len(match.group(1))
text_content = match.group(2)
return f"<h{level}>{text_content}</h{level}>"
def _sanitize_html(text: str) -> str:
text = _UNSAFE_TAGS.sub("", text)
text = _UNSAFE_ATTRS.sub("", text)
return text