feat(telegram): 优化Telegram文件下载与base64转换逻辑,重构消息发送相关代码

- 新增 TelegramModule.download_file_to_base64 方法,统一文件下载与base64编码
- Telegram 客户端新增 download_file 方法,简化文件下载流程
- 消息图片下载逻辑调整为通过模块方法调用,移除冗余静态方法
- 修复部分参数格式与空格风格,提升代码一致性
- 优化长消息发送异常处理与代码结构
This commit is contained in:
jxxghp
2026-03-30 10:28:40 +08:00
parent 70109635c7
commit 9c51f73a72
4 changed files with 131 additions and 122 deletions
+2 -2
View File
@@ -251,7 +251,7 @@ class MoviePilotAgent:
if start_idx > 0: if start_idx > 0:
on_token(buffer[:start_idx]) on_token(buffer[:start_idx])
in_think_tag = True in_think_tag = True
buffer = buffer[start_idx + 7 :] buffer = buffer[start_idx + 7:]
else: else:
# 检查是否以 <think> 的前缀结尾 # 检查是否以 <think> 的前缀结尾
partial_match = False partial_match = False
@@ -269,7 +269,7 @@ class MoviePilotAgent:
end_idx = buffer.find("</think>") end_idx = buffer.find("</think>")
if end_idx != -1: if end_idx != -1:
in_think_tag = False in_think_tag = False
buffer = buffer[end_idx + 8 :] buffer = buffer[end_idx + 8:]
else: else:
# 检查是否以 </think> 的前缀结尾 # 检查是否以 </think> 的前缀结尾
partial_match = False partial_match = False
+4 -43
View File
@@ -1204,9 +1204,8 @@ class MessageChain(ChainBase):
f"AI智能体处理失败: {str(e)}", role="system", title="MoviePilot助手" f"AI智能体处理失败: {str(e)}", role="system", title="MoviePilot助手"
) )
@staticmethod
def _download_images_to_base64( def _download_images_to_base64(
images: List[str], channel: MessageChannel, source: str self, images: List[str], channel: MessageChannel, source: str
) -> List[str]: ) -> List[str]:
""" """
下载图片并转为base64 下载图片并转为base64
@@ -1218,7 +1217,9 @@ class MessageChain(ChainBase):
try: try:
if img.startswith("tg://file_id/"): if img.startswith("tg://file_id/"):
file_id = img.replace("tg://file_id/", "") file_id = img.replace("tg://file_id/", "")
base64_data = MessageChain._download_telegram_file(file_id, source) base64_data = self.run_module(
"download_file_to_base64", file_id=file_id, source=source
)
if base64_data: if base64_data:
base64_images.append(f"data:image/jpeg;base64,{base64_data}") base64_images.append(f"data:image/jpeg;base64,{base64_data}")
elif img.startswith("http"): elif img.startswith("http"):
@@ -1232,43 +1233,3 @@ class MessageChain(ChainBase):
except Exception as e: except Exception as e:
logger.error(f"下载图片失败: {img}, error: {e}") logger.error(f"下载图片失败: {img}, error: {e}")
return base64_images if base64_images else None return base64_images if base64_images else None
@staticmethod
def _download_telegram_file(file_id: str, source: str) -> Optional[str]:
"""
下载Telegram文件并转为base64
"""
from app.modules.telegram import TelegramModule
module = TelegramModule()
config = module.get_config(source)
if not config:
return None
client = module.get_instance(config.name)
if not client or not client._bot:
return None
try:
file_info = client._bot.get_file(file_id)
file_url = f"https://api.telegram.org/file/bot{client._telegram_token}/{file_info.file_path}"
resp = RequestUtils(timeout=30).get_res(file_url)
if resp and resp.content:
import base64
return base64.b64encode(resp.content).decode()
except Exception as e:
logger.error(f"下载Telegram文件失败: {e}")
return None
client = module.get_instance(config.name)
if not client:
return None
try:
file_info = client.bot.get_file(file_id)
file_url = f"https://api.telegram.org/file/bot{client._telegram_token}/{file_info.file_path}"
resp = RequestUtils(timeout=30).get_res(file_url)
if resp and resp.content:
import base64
return base64.b64encode(resp.content).decode()
except Exception as e:
logger.error(f"下载Telegram文件失败: {e}")
return None
+21 -1
View File
@@ -288,7 +288,7 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
# Remove mention at the beginning with optional following space # Remove mention at the beginning with optional following space
if cleaned.startswith(mention_pattern): if cleaned.startswith(mention_pattern):
cleaned = cleaned[len(mention_pattern) :].lstrip() cleaned = cleaned[len(mention_pattern):].lstrip()
# Remove mention at any other position # Remove mention at any other position
cleaned = cleaned.replace(mention_pattern, "").strip() cleaned = cleaned.replace(mention_pattern, "").strip()
@@ -525,3 +525,23 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
f"Command set has changed, Updating new commands: {filtered_scoped_commands}" f"Command set has changed, Updating new commands: {filtered_scoped_commands}"
) )
client.register_commands(filtered_scoped_commands) client.register_commands(filtered_scoped_commands)
def download_file_to_base64(self, file_id: str, source: str) -> Optional[str]:
"""
下载Telegram文件并转为base64
:param file_id: Telegram文件ID
:param source: 来源名称
:return: base64编码的图片数据
"""
config = self.get_config(source)
if not config:
return None
client = self.get_instance(config.name)
if not client:
return None
file_content = client.download_file(file_id)
if file_content:
import base64
return base64.b64encode(file_content).decode()
return None
+104 -76
View File
@@ -12,14 +12,14 @@ from telebot.types import (
InlineKeyboardButton, InlineKeyboardButton,
InputMediaPhoto, InputMediaPhoto,
) )
from telegramify_markdown import standardize, telegramify from telegramify_markdown import standardize, telegramify # noqa
from telegramify_markdown.type import ContentTypes, SentType from telegramify_markdown.type import ContentTypes, SentType
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
from app.core.metainfo import MetaInfo from app.core.metainfo import MetaInfo
from app.helper.thread import ThreadHelper
from app.helper.image import ImageHelper from app.helper.image import ImageHelper
from app.helper.thread import ThreadHelper
from app.log import logger from app.log import logger
from app.utils.common import retry from app.utils.common import retry
from app.utils.http import RequestUtils from app.utils.http import RequestUtils
@@ -44,10 +44,10 @@ class Telegram:
_typing_stop_flags: Dict[str, bool] = {} # chat_id -> 停止标志 _typing_stop_flags: Dict[str, bool] = {} # chat_id -> 停止标志
def __init__( def __init__(
self, self,
TELEGRAM_TOKEN: Optional[str] = None, TELEGRAM_TOKEN: Optional[str] = None,
TELEGRAM_CHAT_ID: Optional[str] = None, TELEGRAM_CHAT_ID: Optional[str] = None,
**kwargs, **kwargs,
): ):
""" """
初始化参数 初始化参数
@@ -171,6 +171,14 @@ class Telegram:
self._polling_thread.start() self._polling_thread.start()
logger.info("Telegram消息接收服务启动") logger.info("Telegram消息接收服务启动")
@property
def bot(self):
"""
获取Telegram Bot实例
:return: TeleBot实例或None
"""
return self._bot
@property @property
def bot_username(self) -> Optional[str]: def bot_username(self) -> Optional[str]:
""" """
@@ -179,6 +187,24 @@ class Telegram:
""" """
return self._bot_username return self._bot_username
def download_file(self, file_id: str) -> Optional[bytes]:
"""
下载Telegram文件
:param file_id: 文件ID
:return: 文件字节数据
"""
if not self._bot:
return None
try:
file_info = self._bot.get_file(file_id)
file_url = f"https://api.telegram.org/file/bot{self._telegram_token}/{file_info.file_path}"
resp = RequestUtils(timeout=30).get_res(file_url)
if resp and resp.content:
return resp.content
except Exception as e:
logger.error(f"下载Telegram文件失败: {e}")
return None
def _update_user_chat_mapping(self, userid: int, chat_id: int) -> None: def _update_user_chat_mapping(self, userid: int, chat_id: int) -> None:
""" """
更新用户与聊天的映射关系 更新用户与聊天的映射关系
@@ -229,7 +255,7 @@ class Telegram:
for entity in message.entities: for entity in message.entities:
if entity.type == "mention": if entity.type == "mention":
mention_text = message.text[ mention_text = message.text[
entity.offset : entity.offset + entity.length entity.offset: entity.offset + entity.length
] ]
if mention_text == f"@{self._bot_username}": if mention_text == f"@{self._bot_username}":
logger.debug( logger.debug(
@@ -295,15 +321,15 @@ class Telegram:
task.join(timeout=1) task.join(timeout=1)
def send_msg( def send_msg(
self, self,
title: str, title: str,
text: Optional[str] = None, text: Optional[str] = None,
image: Optional[str] = None, image: Optional[str] = None,
userid: Optional[str] = None, userid: Optional[str] = None,
link: Optional[str] = None, link: Optional[str] = None,
buttons: Optional[List[List[dict]]] = None, buttons: Optional[List[List[dict]]] = None,
original_message_id: Optional[int] = None, original_message_id: Optional[int] = None,
original_chat_id: Optional[str] = None, original_chat_id: Optional[str] = None,
) -> Optional[dict]: ) -> Optional[dict]:
""" """
发送Telegram消息 发送Telegram消息
@@ -324,6 +350,9 @@ class Telegram:
logger.warn("标题和内容不能同时为空") logger.warn("标题和内容不能同时为空")
return {"success": False} return {"success": False}
# Determine target chat_id with improved logic using user mapping
chat_id = self._determine_target_chat_id(userid, original_chat_id)
try: try:
# 标准化标题后再加粗,避免**符号被显示为文本 # 标准化标题后再加粗,避免**符号被显示为文本
bold_title = ( bold_title = (
@@ -341,9 +370,6 @@ class Telegram:
if link: if link:
caption = f"{caption}\n[查看详情]({link})" caption = f"{caption}\n[查看详情]({link})"
# Determine target chat_id with improved logic using user mapping
chat_id = self._determine_target_chat_id(userid, original_chat_id)
# 创建按钮键盘 # 创建按钮键盘
reply_markup = None reply_markup = None
if buttons: if buttons:
@@ -386,7 +412,7 @@ class Telegram:
return {"success": False} return {"success": False}
def _determine_target_chat_id( def _determine_target_chat_id(
self, userid: Optional[str] = None, original_chat_id: Optional[str] = None self, userid: Optional[str] = None, original_chat_id: Optional[str] = None
) -> str: ) -> str:
""" """
确定目标聊天ID,使用用户映射确保回复到正确的聊天 确定目标聊天ID,使用用户映射确保回复到正确的聊天
@@ -410,14 +436,14 @@ class Telegram:
return self._telegram_chat_id return self._telegram_chat_id
def send_medias_msg( def send_medias_msg(
self, self,
medias: List[MediaInfo], medias: List[MediaInfo],
userid: Optional[str] = None, userid: Optional[str] = None,
title: Optional[str] = None, title: Optional[str] = None,
link: Optional[str] = None, link: Optional[str] = None,
buttons: Optional[List[List[Dict]]] = None, buttons: Optional[List[List[Dict]]] = None,
original_message_id: Optional[int] = None, original_message_id: Optional[int] = None,
original_chat_id: Optional[str] = None, original_chat_id: Optional[str] = None,
) -> Optional[bool]: ) -> Optional[bool]:
""" """
发送媒体列表消息 发送媒体列表消息
@@ -487,14 +513,14 @@ class Telegram:
return False return False
def send_torrents_msg( def send_torrents_msg(
self, self,
torrents: List[Context], torrents: List[Context],
userid: Optional[str] = None, userid: Optional[str] = None,
title: Optional[str] = None, title: Optional[str] = None,
link: Optional[str] = None, link: Optional[str] = None,
buttons: Optional[List[List[Dict]]] = None, buttons: Optional[List[List[Dict]]] = None,
original_message_id: Optional[int] = None, original_message_id: Optional[int] = None,
original_chat_id: Optional[str] = None, original_chat_id: Optional[str] = None,
) -> Optional[bool]: ) -> Optional[bool]:
""" """
发送种子列表消息 发送种子列表消息
@@ -586,10 +612,10 @@ class Telegram:
return InlineKeyboardMarkup(keyboard) return InlineKeyboardMarkup(keyboard)
def answer_callback_query( def answer_callback_query(
self, self,
callback_query_id: int, callback_query_id: int,
text: Optional[str] = None, text: Optional[str] = None,
show_alert: bool = False, show_alert: bool = False,
) -> Optional[bool]: ) -> Optional[bool]:
""" """
回应回调查询 回应回调查询
@@ -607,7 +633,7 @@ class Telegram:
return False return False
def delete_msg( def delete_msg(
self, message_id: int, chat_id: Optional[int] = None self, message_id: int, chat_id: Optional[int] = None
) -> Optional[bool]: ) -> Optional[bool]:
""" """
删除Telegram消息 删除Telegram消息
@@ -644,11 +670,11 @@ class Telegram:
return False return False
def edit_msg( def edit_msg(
self, self,
chat_id: Union[str, int], chat_id: Union[str, int],
message_id: Union[str, int], message_id: Union[str, int],
text: str, text: str,
title: Optional[str] = None, title: Optional[str] = None,
) -> Optional[bool]: ) -> Optional[bool]:
""" """
编辑Telegram消息(公开方法) 编辑Telegram消息(公开方法)
@@ -681,12 +707,12 @@ class Telegram:
return False return False
def __edit_message( def __edit_message(
self, self,
chat_id: str, chat_id: str,
message_id: int, message_id: int,
text: str, text: str,
buttons: Optional[List[List[dict]]] = None, buttons: Optional[List[List[dict]]] = None,
image: Optional[str] = None, image: Optional[str] = None,
) -> Optional[bool]: ) -> Optional[bool]:
""" """
编辑已发送的消息 编辑已发送的消息
@@ -732,11 +758,11 @@ class Telegram:
return False return False
def __send_request( def __send_request(
self, self,
userid: Optional[str] = None, userid: Optional[str] = None,
image="", image="",
caption="", caption="",
reply_markup: Optional[InlineKeyboardMarkup] = None, reply_markup: Optional[InlineKeyboardMarkup] = None,
): ):
""" """
向Telegram发送报文,返回发送的消息对象 向Telegram发送报文,返回发送的消息对象
@@ -795,26 +821,27 @@ class Telegram:
@retry(RetryException, logger=logger) @retry(RetryException, logger=logger)
def __send_long_message( def __send_long_message(
self, image: Optional[bytes], caption: str, sent_idx: set, **kwargs self, image: Optional[bytes], caption: str, sent_idx: set, **kwargs
): ):
""" """
发送长消息 发送长消息
""" """
try:
reply_markup = kwargs.pop("reply_markup", None)
boxs: SentType = ( reply_markup = kwargs.pop("reply_markup", None)
ThreadHelper()
.submit(lambda x: asyncio.run(telegramify(x)), caption)
.result()
)
ret = None boxs: SentType = (
for i, item in enumerate(boxs): ThreadHelper()
if i in sent_idx: .submit(lambda x: asyncio.run(telegramify(x)), caption)
# 跳过已发送消息 .result()
continue )
ret = None
for i, item in enumerate(boxs):
if i in sent_idx:
# 跳过已发送消息
continue
try:
current_reply_markup = reply_markup if i == 0 else None current_reply_markup = reply_markup if i == 0 else None
if item.content_type == ContentTypes.TEXT and (i != 0 or not image): if item.content_type == ContentTypes.TEXT and (i != 0 or not image):
@@ -843,12 +870,13 @@ class Telegram:
sent_idx.add(i) sent_idx.add(i)
return ret except Exception as e:
except Exception as e: try:
try: raise RetryException(f"消息 [{i + 1}/{len(boxs)}] 发送失败") from e
raise RetryException(f"消息 [{i + 1}/{len(boxs)}] 发送失败") from e except NameError:
except NameError: raise
raise
return ret
def register_commands(self, commands: Dict[str, dict]): def register_commands(self, commands: Dict[str, dict]):
""" """