新增 Telegram 渠道扩展,支持在 Yuxi 平台中集成 Telegram 即时通讯渠道。 包含以下功能模块: - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - polling: 长轮询模式 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - session: 会话管理 - actions: 动作处理 - inline_keyboard: 内联键盘 - native_commands: 原生指令 - chat: 聊天管理 - delivery: 消息送达确认 - media: 媒体资源处理 - profile: 用户资料 - reactions: 表情反应 - sticker: 贴纸处理 - types: 类型定义
126 lines
3.6 KiB
Python
126 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from html import escape
|
|
|
|
_HTML_TAGS = [
|
|
("<b>", "</b>"),
|
|
("<i>", "</i>"),
|
|
("<s>", "</s>"),
|
|
("<u>", "</u>"),
|
|
("<code>", "</code>"),
|
|
("<pre>", "</pre>"),
|
|
("<tg-spoiler>", "</tg-spoiler>"),
|
|
("<blockquote>", "</blockquote>"),
|
|
("<tg-emoji", "</tg-emoji>"),
|
|
]
|
|
|
|
_RE_MARKDOWN_TO_HTML = [
|
|
(re.compile(r"\*\*\*(.+?)\*\*\*"), r"<b><i>\1</i></b>"),
|
|
(re.compile(r"\*\*_(.+?)_\*\*"), r"<b><i>\1</i></b>"),
|
|
(re.compile(r"_\*\*(.+?)\*\*_"), r"<i><b>\1</b></i>"),
|
|
(re.compile(r"\*\*(.+?)\*\*"), r"<b>\1</b>"),
|
|
(re.compile(r"__(.+?)__"), r"<u>\1</u>"),
|
|
(re.compile(r"~~(.+?)~~"), r"<s>\1</s>"),
|
|
(re.compile(r"\|\|(.+?)\|\|"), r"<tg-spoiler>\1</tg-spoiler>"),
|
|
(re.compile(r"(?<!\w)_(.+?)_(?!\w)"), r"<i>\1</i>"),
|
|
(re.compile(r"(?<!\w)\*(.+?)\*(?!\w)"), r"<i>\1</i>"),
|
|
(re.compile(r"(?<!\w)`([^`]+)`(?!\w)"), r"<code>\1</code>"),
|
|
(re.compile(r"\[([^\]]+)\]\(([^)]+)\)"), r'<a href="\2">\1</a>'),
|
|
]
|
|
|
|
_BULLET_LINE_RE = re.compile(r"^(\s*)\*\s", re.MULTILINE)
|
|
_BLOCKQUOTE_RE = re.compile(r"^> (.+)$", re.MULTILINE)
|
|
|
|
_CODE_BLOCK_RE = re.compile(r"```(\w+)?\s*\n?(.+?)```", re.DOTALL)
|
|
|
|
|
|
def markdown_to_telegram_html(md_text: str) -> str:
|
|
text = md_text
|
|
|
|
code_blocks = []
|
|
|
|
def _save_code_block(m: re.Match) -> str:
|
|
lang = m.group(1) or ""
|
|
code = m.group(2)
|
|
code_blocks.append(f"<pre><code class=\"language-{lang}\">{escape(code)}</code></pre>")
|
|
return f"\x00CODEBLOCK{len(code_blocks) - 1}\x00"
|
|
|
|
text = _CODE_BLOCK_RE.sub(_save_code_block, text)
|
|
|
|
text = escape(text)
|
|
|
|
for pattern, replacement in _RE_MARKDOWN_TO_HTML:
|
|
try:
|
|
text = pattern.sub(replacement, text)
|
|
except re.error:
|
|
pass
|
|
|
|
text = _BULLET_LINE_RE.sub(r"\1• ", text)
|
|
text = _BLOCKQUOTE_RE.sub(r"<blockquote>\1</blockquote>", text)
|
|
|
|
text = text.replace("*", "").replace("_", "")
|
|
|
|
for i, block in enumerate(code_blocks):
|
|
text = text.replace(f"\x00CODEBLOCK{i}\x00", block)
|
|
|
|
return text.strip()
|
|
|
|
|
|
def split_telegram_html_chunks(html: str, limit: int = 4096) -> list[str]:
|
|
if len(html) <= limit:
|
|
return [html]
|
|
|
|
chunks: list[str] = []
|
|
tag_stack: list[str] = []
|
|
start = 0
|
|
|
|
i = 0
|
|
while i < len(html):
|
|
if i - start >= limit:
|
|
boundary = _find_safe_boundary(html, i, tag_stack)
|
|
chunk = html[start:boundary]
|
|
chunk = _close_open_tags(chunk, tag_stack)
|
|
chunks.append(chunk)
|
|
start = _skip_whitespace(html, boundary)
|
|
i = start
|
|
continue
|
|
i += 1
|
|
|
|
if start < len(html):
|
|
chunk = html[start:]
|
|
chunk = _close_open_tags(chunk, tag_stack)
|
|
chunks.append(chunk)
|
|
|
|
return chunks
|
|
|
|
|
|
def _find_safe_boundary(html: str, pos: int, tag_stack: list[str]) -> int:
|
|
end = min(pos, len(html))
|
|
|
|
for i in range(end - 1, max(pos - 200, 0), -1):
|
|
if html[i] == "\n":
|
|
return i
|
|
|
|
return end
|
|
|
|
|
|
def _close_open_tags(chunk: str, tag_stack: list[str]) -> str:
|
|
result = chunk
|
|
for tag in reversed(tag_stack):
|
|
close_tag = {"<b>": "</b>", "<i>": "</i>", "<s>": "</s>", "<u>": "</u>",
|
|
"<code>": "</code>", "<pre>": "</pre>",
|
|
'<a': '</a>',
|
|
"<tg-spoiler>": "</tg-spoiler>",
|
|
"<blockquote>": "</blockquote>",
|
|
"<tg-emoji": "</tg-emoji>"}.get(tag, "")
|
|
if close_tag:
|
|
result += close_tag
|
|
return result
|
|
|
|
|
|
def _skip_whitespace(html: str, pos: int) -> int:
|
|
while pos < len(html) and html[pos] in (" ", "\n", "\r", "\t"):
|
|
pos += 1
|
|
return pos
|