152 lines
4.8 KiB
Python
152 lines
4.8 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from yuxi.channel.security.html_guard import sanitize_text_content_preserve_entities
|
|
|
|
_TEXT_CONTENT_RE = re.compile(r'{"text":"((?:[^"\\]|\\.)*)"}')
|
|
|
|
_ESCAPE_RE = re.compile(r"([\\*_{}\[\]()#+\-!|>~])")
|
|
|
|
|
|
def markdown_to_feishu_post(md_text: str) -> dict:
|
|
lines = md_text.split("\n")
|
|
elements: list[dict] = []
|
|
|
|
i = 0
|
|
while i < len(lines):
|
|
line = lines[i]
|
|
|
|
if line.startswith("```"):
|
|
language = line[3:].strip()
|
|
code_lines = []
|
|
i += 1
|
|
while i < len(lines) and not lines[i].startswith("```"):
|
|
code_lines.append(lines[i])
|
|
i += 1
|
|
i += 1
|
|
elements.append(_build_code_block("\n".join(code_lines), language))
|
|
continue
|
|
|
|
if line.startswith("### "):
|
|
elements.append(_build_paragraph([_build_text(line[4:], bold=True)]))
|
|
elif line.startswith("## "):
|
|
elements.append(_build_paragraph([_build_text(line[3:], bold=True)]))
|
|
elif line.startswith("# "):
|
|
elements.append(_build_paragraph([_build_text(line[2:], bold=True)]))
|
|
elif line.startswith("> "):
|
|
content = line[2:]
|
|
content = _ESCAPE_RE.sub(r"\\\1", content)
|
|
elements.append(_build_paragraph([_build_text(content)]))
|
|
elif line.startswith("- ") or line.startswith("* "):
|
|
content = _parse_inline_markdown(line[2:])
|
|
elements.append(_build_paragraph([_build_text("• " + content)]))
|
|
elif re.match(r"^\d+\.\s", line):
|
|
content = _parse_inline_markdown(re.sub(r"^\d+\.\s", "", line))
|
|
elements.append(_build_paragraph([_build_text(content)]))
|
|
elif line.strip() == "":
|
|
pass
|
|
else:
|
|
content = _parse_inline_markdown(line)
|
|
if content:
|
|
elements.append(_build_paragraph([_build_text(content)]))
|
|
|
|
i += 1
|
|
|
|
if not elements:
|
|
elements.append(_build_paragraph([_build_text("")]))
|
|
|
|
return {
|
|
"zh_cn": {
|
|
"title": "",
|
|
"content": [elements],
|
|
}
|
|
}
|
|
|
|
|
|
def md_to_plain_text(md_text: str) -> str:
|
|
text = md_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"```(\w*)\n(.*?)```", r"\2", text, flags=re.DOTALL)
|
|
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"^#{1,6}\s+", "", text, flags=re.MULTILINE)
|
|
text = re.sub(r"^>\s+", "", text, flags=re.MULTILINE)
|
|
text = re.sub(r"^[*-]\s+", "• ", text, flags=re.MULTILINE)
|
|
return text.strip()
|
|
|
|
|
|
def _parse_inline_markdown(text: str) -> str:
|
|
result = _ESCAPE_RE.sub(r"\\\1", text)
|
|
result = re.sub(r"\*\*(.+?)\*\*", r"\1", result)
|
|
result = re.sub(r"__([^_]+)__", r"\1", result)
|
|
result = re.sub(r"\*([^*]+)\*", r"\1", result)
|
|
result = re.sub(r"`([^`]+)`", r"\1", result)
|
|
result = re.sub(r"~~(.+?)~~", r"\1", result)
|
|
return result
|
|
|
|
|
|
def _build_text(content: str, bold: bool = False) -> dict:
|
|
return {
|
|
"tag": "text",
|
|
"text": sanitize_text_content_preserve_entities(content),
|
|
"style": ["bold"] if bold else [],
|
|
}
|
|
|
|
|
|
def _build_paragraph(elements: list[dict]) -> dict:
|
|
return {
|
|
"tag": "p",
|
|
"children": elements if elements else [_build_text("")],
|
|
}
|
|
|
|
|
|
def _build_code_block(code: str, language: str = "") -> dict:
|
|
return {
|
|
"tag": "pre",
|
|
"children": [{
|
|
"tag": "code",
|
|
"code": sanitize_text_content_preserve_entities(code),
|
|
"code_language": sanitize_text_content_preserve_entities(language) or "plaintext",
|
|
}],
|
|
}
|
|
|
|
|
|
def chunk_post_content(elements: list[list[dict]], limit: int = 28000) -> list[list[list[dict]]]:
|
|
chunks: list[list[list[dict]]] = []
|
|
current: list[list[dict]] = []
|
|
current_size = 0
|
|
|
|
for para_elements in elements:
|
|
para_size = _estimate_paragraph_size(para_elements)
|
|
if current_size + para_size > limit and current:
|
|
chunks.append(current)
|
|
current = []
|
|
current_size = 0
|
|
current.append(para_elements)
|
|
current_size += para_size
|
|
|
|
if current:
|
|
chunks.append(current)
|
|
|
|
return chunks if chunks else [elements]
|
|
|
|
|
|
def _estimate_paragraph_size(para_elements: list[dict]) -> int:
|
|
size = 0
|
|
for elem in para_elements:
|
|
if isinstance(elem, dict):
|
|
size += len(str(elem.get("text", ""))) + 50
|
|
return size
|
|
|
|
|
|
def text_to_post_message(text: str) -> str:
|
|
import json
|
|
post = markdown_to_feishu_post(text)
|
|
return json.dumps(post, ensure_ascii=False)
|