mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-08 17:08:35 +08:00
fix telegram markdown
This commit is contained in:
@@ -10,6 +10,7 @@ import telebot
|
|||||||
from telebot import apihelper
|
from telebot import apihelper
|
||||||
from telebot.types import InputFile, InlineKeyboardMarkup, InlineKeyboardButton
|
from telebot.types import InputFile, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from telebot.types import InputMediaPhoto
|
from telebot.types import InputMediaPhoto
|
||||||
|
import telegramify_markdown
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.context import MediaInfo, Context
|
from app.core.context import MediaInfo, Context
|
||||||
@@ -31,7 +32,6 @@ class Telegram:
|
|||||||
_callback_handlers: Dict[str, Callable] = {} # 存储回调处理器
|
_callback_handlers: Dict[str, Callable] = {} # 存储回调处理器
|
||||||
_user_chat_mapping: Dict[str, str] = {} # userid -> chat_id mapping for reply targeting
|
_user_chat_mapping: Dict[str, str] = {} # userid -> chat_id mapping for reply targeting
|
||||||
_bot_username: Optional[str] = None # Bot username for mention detection
|
_bot_username: Optional[str] = None # Bot username for mention detection
|
||||||
_escape_chars = r'_*[]()~`>#+-=|{}.!' # Telegram MarkdownV2
|
|
||||||
|
|
||||||
def __init__(self, TELEGRAM_TOKEN: Optional[str] = None, TELEGRAM_CHAT_ID: Optional[str] = None, **kwargs):
|
def __init__(self, TELEGRAM_TOKEN: Optional[str] = None, TELEGRAM_CHAT_ID: Optional[str] = None, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -237,21 +237,23 @@ class Telegram:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if title:
|
# 构建完整的 Markdown 文本,然后统一转换
|
||||||
title = self.escape_markdown_smart(title)
|
if title and text:
|
||||||
|
caption = f"**{title}**\n{text}"
|
||||||
if text:
|
elif title:
|
||||||
text = self.escape_markdown_smart(text)
|
caption = f"**{title}**"
|
||||||
if title:
|
elif text:
|
||||||
caption = f"*{title}*\n{text}"
|
caption = text
|
||||||
else:
|
|
||||||
caption = text
|
|
||||||
else:
|
else:
|
||||||
caption = f"*{title}*"
|
caption = ""
|
||||||
|
|
||||||
if link:
|
if link:
|
||||||
caption = f"{caption}\n[查看详情]({link})"
|
caption = f"{caption}\n[查看详情]({link})"
|
||||||
|
|
||||||
|
# 使用 telegramify-markdown 转换整个文本
|
||||||
|
if caption:
|
||||||
|
caption = self.escape_markdown_smart(caption)
|
||||||
|
|
||||||
# Determine target chat_id with improved logic using user mapping
|
# Determine target chat_id with improved logic using user mapping
|
||||||
chat_id = self._determine_target_chat_id(userid, original_chat_id)
|
chat_id = self._determine_target_chat_id(userid, original_chat_id)
|
||||||
|
|
||||||
@@ -314,10 +316,8 @@ class Telegram:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if title:
|
# 构建完整的 Markdown 文本,然后统一转换
|
||||||
title = self.escape_markdown_smart(title)
|
index, image, caption = 1, "", f"**{title}**" if title else ""
|
||||||
|
|
||||||
index, image, caption = 1, "", "*%s*" % title
|
|
||||||
for media in medias:
|
for media in medias:
|
||||||
if not image:
|
if not image:
|
||||||
image = media.get_message_image()
|
image = media.get_message_image()
|
||||||
@@ -339,6 +339,10 @@ class Telegram:
|
|||||||
if link:
|
if link:
|
||||||
caption = f"{caption}\n[查看详情]({link})"
|
caption = f"{caption}\n[查看详情]({link})"
|
||||||
|
|
||||||
|
# 使用 telegramify-markdown 转换整个文本
|
||||||
|
if caption:
|
||||||
|
caption = self.escape_markdown_smart(caption)
|
||||||
|
|
||||||
# Determine target chat_id with improved logic using user mapping
|
# Determine target chat_id with improved logic using user mapping
|
||||||
chat_id = self._determine_target_chat_id(userid, original_chat_id)
|
chat_id = self._determine_target_chat_id(userid, original_chat_id)
|
||||||
|
|
||||||
@@ -378,10 +382,8 @@ class Telegram:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if title:
|
# 构建完整的 Markdown 文本,然后统一转换
|
||||||
title = self.escape_markdown_smart(title)
|
index, caption = 1, f"**{title}**" if title else ""
|
||||||
|
|
||||||
index, caption = 1, "*%s*" % title
|
|
||||||
image = torrents[0].media_info.get_message_image()
|
image = torrents[0].media_info.get_message_image()
|
||||||
for context in torrents:
|
for context in torrents:
|
||||||
torrent = context.torrent_info
|
torrent = context.torrent_info
|
||||||
@@ -402,6 +404,10 @@ class Telegram:
|
|||||||
if link:
|
if link:
|
||||||
caption = f"{caption}\n[查看详情]({link})"
|
caption = f"{caption}\n[查看详情]({link})"
|
||||||
|
|
||||||
|
# 使用 telegramify-markdown 转换整个文本
|
||||||
|
if caption:
|
||||||
|
caption = self.escape_markdown_smart(caption)
|
||||||
|
|
||||||
# Determine target chat_id with improved logic using user mapping
|
# Determine target chat_id with improved logic using user mapping
|
||||||
chat_id = self._determine_target_chat_id(userid, original_chat_id)
|
chat_id = self._determine_target_chat_id(userid, original_chat_id)
|
||||||
|
|
||||||
@@ -610,93 +616,32 @@ class Telegram:
|
|||||||
self._polling_thread.join()
|
self._polling_thread.join()
|
||||||
logger.info("Telegram消息接收服务已停止")
|
logger.info("Telegram消息接收服务已停止")
|
||||||
|
|
||||||
def escape_markdown_smart(self, text: str) -> str:
|
@staticmethod
|
||||||
|
def escape_markdown_smart(text: str) -> str:
|
||||||
"""
|
"""
|
||||||
智能转义Markdown文本:只转义不在Markdown标记内的特殊字符
|
使用 telegramify-markdown 库将文本转换为 Telegram MarkdownV2 格式
|
||||||
这样可以保留已有的Markdown格式(如*粗体*、_斜体_、[链接](url)等),
|
支持原始 Markdown 格式转换,自动处理特殊字符转义
|
||||||
同时转义普通文本中的特殊字符以避免API错误
|
|
||||||
|
|
||||||
注意:Telegram MarkdownV2不支持以下语法,这些字符会被转义:
|
:param text: 要转换的文本(可以是纯文本或包含 Markdown 格式)
|
||||||
- 标题语法(#、##、###)会被转义为 \#、\##、\###
|
:return: 转换后的 Telegram MarkdownV2 格式文本
|
||||||
- 列表语法(-、*、+)会被转义为 \-、\*、\+
|
|
||||||
- 引用语法(>)会被转义为 \>
|
|
||||||
|
|
||||||
建议使用加粗文本模拟标题:*标题文本*
|
|
||||||
|
|
||||||
:param text: 要转义的文本
|
|
||||||
:return: 转义后的文本
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(text, str):
|
if not isinstance(text, str):
|
||||||
return str(text) if text is not None else ""
|
return str(text) if text is not None else ""
|
||||||
|
|
||||||
# 如果没有特殊字符,直接返回
|
if not text:
|
||||||
if not any(char in self._escape_chars for char in text):
|
return ""
|
||||||
return text
|
|
||||||
|
|
||||||
# 标记受保护的位置(只保护Markdown分隔符本身,不保护内容区域)
|
try:
|
||||||
protected = [False] * len(text)
|
# 使用 telegramify-markdown 的 markdownify 函数进行转换
|
||||||
|
# markdownify 会自动处理 Markdown 格式和特殊字符转义
|
||||||
# 按优先级匹配Markdown标记(从最复杂到最简单)
|
converted = telegramify_markdown.markdownify(
|
||||||
# 1. 链接:[text](url) - 必须最先匹配,只保护分隔符 [ ] ( )
|
text,
|
||||||
link_pattern = r'\[([^\]]*)\]\(([^)]*)\)'
|
max_line_length=None, # 不限制行长度
|
||||||
for match in re.finditer(link_pattern, text):
|
normalize_whitespace=False # 保留原始空白字符
|
||||||
# 只保护分隔符:[, ], (, )
|
)
|
||||||
protected[match.start()] = True # [
|
return converted
|
||||||
# match.end(1) 是第一个捕获组结束位置,即 ] 的位置
|
except Exception as e:
|
||||||
protected[match.end(1)] = True # ]
|
# 如果转换失败,记录错误并返回转义后的文本(使用简单转义作为后备)
|
||||||
# ( 在 ] 之后一个字符
|
logger.warning(f"使用 telegramify-markdown 转换失败,使用简单转义: {e}")
|
||||||
if match.end(1) + 1 < len(text):
|
# 简单转义所有特殊字符作为后备方案
|
||||||
protected[match.end(1) + 1] = True # (
|
return re.sub(r'([_*\[\]()~`>#+\-=|{}.!])', r'\\\1', text)
|
||||||
# ) 在匹配结束前一个字符
|
|
||||||
if match.end() > 0:
|
|
||||||
protected[match.end() - 1] = True # )
|
|
||||||
|
|
||||||
# 2. 粗体:*text*(单个*,不是**),只保护分隔符 *
|
|
||||||
bold_pattern = r'(?<!\*)\*(?!\*)([^*]+?)(?<!\*)\*(?!\*)'
|
|
||||||
for match in re.finditer(bold_pattern, text):
|
|
||||||
# 检查是否与已保护的链接重叠
|
|
||||||
if not any(protected[match.start():match.end()]):
|
|
||||||
# 只保护分隔符:第一个和最后一个 *
|
|
||||||
protected[match.start()] = True # 第一个 *
|
|
||||||
protected[match.end() - 1] = True # 最后一个 *
|
|
||||||
|
|
||||||
# 3. 斜体:_text_(单个_,不是__),只保护分隔符 _
|
|
||||||
italic_pattern = r'(?<!_)_(?!_)([^_]+?)(?<!_)_(?!_)'
|
|
||||||
for match in re.finditer(italic_pattern, text):
|
|
||||||
# 检查是否与已保护的区域重叠
|
|
||||||
if not any(protected[match.start():match.end()]):
|
|
||||||
# 只保护分隔符:第一个和最后一个 _
|
|
||||||
protected[match.start()] = True # 第一个 _
|
|
||||||
protected[match.end() - 1] = True # 最后一个 _
|
|
||||||
|
|
||||||
# 4. 代码:`text`,只保护分隔符 `
|
|
||||||
code_pattern = r'`([^`]+)`'
|
|
||||||
for match in re.finditer(code_pattern, text):
|
|
||||||
# 检查是否与已保护的区域重叠
|
|
||||||
if not any(protected[match.start():match.end()]):
|
|
||||||
# 只保护分隔符:第一个和最后一个 `
|
|
||||||
protected[match.start()] = True # 第一个 `
|
|
||||||
protected[match.end() - 1] = True # 最后一个 `
|
|
||||||
|
|
||||||
# 5. 删除线:~text~,只保护分隔符 ~
|
|
||||||
strikethrough_pattern = r'~([^~]+)~'
|
|
||||||
for match in re.finditer(strikethrough_pattern, text):
|
|
||||||
# 检查是否与已保护的区域重叠
|
|
||||||
if not any(protected[match.start():match.end()]):
|
|
||||||
# 只保护分隔符:第一个和最后一个 ~
|
|
||||||
protected[match.start()] = True # 第一个 ~
|
|
||||||
protected[match.end() - 1] = True # 最后一个 ~
|
|
||||||
|
|
||||||
# 构建结果:只转义未保护区域的特殊字符
|
|
||||||
result = []
|
|
||||||
for i, char in enumerate(text):
|
|
||||||
if protected[i]:
|
|
||||||
# 受保护位置(Markdown分隔符),不转义
|
|
||||||
result.append(char)
|
|
||||||
elif char in self._escape_chars:
|
|
||||||
# 未保护区域,转义特殊字符(包括Markdown内容区域中的特殊字符)
|
|
||||||
result.append('\\' + char)
|
|
||||||
else:
|
|
||||||
result.append(char)
|
|
||||||
|
|
||||||
return ''.join(result)
|
|
||||||
@@ -37,6 +37,7 @@ beautifulsoup4~=4.13.4
|
|||||||
pillow~=11.2.1
|
pillow~=11.2.1
|
||||||
pillow-avif-plugin~=1.5.2
|
pillow-avif-plugin~=1.5.2
|
||||||
pyTelegramBotAPI~=4.27.0
|
pyTelegramBotAPI~=4.27.0
|
||||||
|
telegramify-markdown~=0.5.2
|
||||||
playwright~=1.53.0
|
playwright~=1.53.0
|
||||||
cf_clearance~=0.31.0
|
cf_clearance~=0.31.0
|
||||||
torrentool~=1.2.0
|
torrentool~=1.2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user