from __future__ import annotations import re from html import escape from typing import Any from yuxi.channels.models import ChannelResponse _ESCAPE_CHARS = {"&": "&", "<": "<", ">": ">"} _CODE_BLOCK_RE = re.compile(r"```(?:\w+)?\n.+?```", re.DOTALL) _INLINE_CODE_RE = re.compile(r"`([^`\n]+?)`") _PLACEHOLDER_PREFIX = "\x00MDPLACEHOLDER" TRUNCATION_MARKER = "\n...(内容过长已截断)" def markdown_to_html(text: str) -> str: code_blocks: dict[str, str] = {} text = _protect_code_spans(text, _CODE_BLOCK_RE, code_blocks) text = _protect_code_spans(text, _INLINE_CODE_RE, code_blocks) text = re.sub(r"\*\*(.+?)\*\*", r"\1", text) text = re.sub(r"(?\1", text) text = re.sub(r"(?\1", text) text = re.sub(r"~~(.+?)~~", r"\1", text) text = re.sub(r"__(\w.*?\w)__", r"\1", text) text = re.sub(r"\[(.+?)\]\((.+?)\)", r'\1', text) text = re.sub(r"\|\|(.+?)\|\|", r"\1", text) text = _restore_code_spans(text, code_blocks) text = _sanitize_html(text) return text def strip_html_tags(text: str) -> str: return re.sub(r"<[^>]+>", "", text) def truncate_text(text: str, limit: int, marker: str | None = None) -> str: m = marker if marker is not None else TRUNCATION_MARKER if len(text) <= limit: return text return text[: limit - len(m)] + m def _protect_code_spans(text: str, pattern: re.Pattern, storage: dict[str, str]) -> str: result = [] idx = 0 for m in pattern.finditer(text): result.append(text[idx : m.start()]) placeholder = f"{_PLACEHOLDER_PREFIX}{len(storage)}" storage[placeholder] = m.group(0) result.append(placeholder) idx = m.end() result.append(text[idx:]) return "".join(result) def _restore_code_spans(text: str, storage: dict[str, str]) -> str: result = text for placeholder, code_text in storage.items(): if placeholder.startswith(f"{_PLACEHOLDER_PREFIX}") and "```" in code_text: inner = code_text[3:].rstrip("`").lstrip("\n") lang_end = inner.find("\n") inner = inner[lang_end + 1 :] if lang_end >= 0 else inner safe = escape(inner, quote=False) result = result.replace(placeholder, f"
{safe}
") elif placeholder in result: inner = code_text.strip("`") safe = escape(inner, quote=False) result = result.replace(placeholder, f"{safe}") return result def _sanitize_html(text: str) -> str: result = [] depth = 0 for ch in text: if ch == "<": depth += 1 elif ch == ">": depth = max(0, depth - 1) elif depth == 0: if ch in _ESCAPE_CHARS: result.append(_ESCAPE_CHARS[ch]) continue result.append(ch) return "".join(result) def build_basic_outbound( response: ChannelResponse, *, text_field: str = "text", text: str | None = None, extra: dict[str, Any] | None = None, ) -> dict[str, Any]: payload: dict[str, Any] = {text_field: text or response.content} if response.reply_to_message_id: payload["reply_to_message_id"] = response.reply_to_message_id if extra: payload.update(extra) return payload