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

View File

@@ -251,7 +251,7 @@ class MoviePilotAgent:
if start_idx > 0:
on_token(buffer[:start_idx])
in_think_tag = True
buffer = buffer[start_idx + 7 :]
buffer = buffer[start_idx + 7:]
else:
# 检查是否以 <think> 的前缀结尾
partial_match = False
@@ -269,7 +269,7 @@ class MoviePilotAgent:
end_idx = buffer.find("</think>")
if end_idx != -1:
in_think_tag = False
buffer = buffer[end_idx + 8 :]
buffer = buffer[end_idx + 8:]
else:
# 检查是否以 </think> 的前缀结尾
partial_match = False

View File

@@ -1204,9 +1204,8 @@ class MessageChain(ChainBase):
f"AI智能体处理失败: {str(e)}", role="system", title="MoviePilot助手"
)
@staticmethod
def _download_images_to_base64(
images: List[str], channel: MessageChannel, source: str
self, images: List[str], channel: MessageChannel, source: str
) -> List[str]:
"""
下载图片并转为base64
@@ -1218,7 +1217,9 @@ class MessageChain(ChainBase):
try:
if img.startswith("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:
base64_images.append(f"data:image/jpeg;base64,{base64_data}")
elif img.startswith("http"):
@@ -1232,43 +1233,3 @@ class MessageChain(ChainBase):
except Exception as e:
logger.error(f"下载图片失败: {img}, error: {e}")
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

View File

@@ -288,7 +288,7 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
# Remove mention at the beginning with optional following space
if cleaned.startswith(mention_pattern):
cleaned = cleaned[len(mention_pattern) :].lstrip()
cleaned = cleaned[len(mention_pattern):].lstrip()
# Remove mention at any other position
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}"
)
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

View File

@@ -12,14 +12,14 @@ from telebot.types import (
InlineKeyboardButton,
InputMediaPhoto,
)
from telegramify_markdown import standardize, telegramify
from telegramify_markdown import standardize, telegramify # noqa
from telegramify_markdown.type import ContentTypes, SentType
from app.core.config import settings
from app.core.context import MediaInfo, Context
from app.core.metainfo import MetaInfo
from app.helper.thread import ThreadHelper
from app.helper.image import ImageHelper
from app.helper.thread import ThreadHelper
from app.log import logger
from app.utils.common import retry
from app.utils.http import RequestUtils
@@ -44,10 +44,10 @@ class Telegram:
_typing_stop_flags: Dict[str, bool] = {} # chat_id -> 停止标志
def __init__(
self,
TELEGRAM_TOKEN: Optional[str] = None,
TELEGRAM_CHAT_ID: Optional[str] = None,
**kwargs,
self,
TELEGRAM_TOKEN: Optional[str] = None,
TELEGRAM_CHAT_ID: Optional[str] = None,
**kwargs,
):
"""
初始化参数
@@ -171,6 +171,14 @@ class Telegram:
self._polling_thread.start()
logger.info("Telegram消息接收服务启动")
@property
def bot(self):
"""
获取Telegram Bot实例
:return: TeleBot实例或None
"""
return self._bot
@property
def bot_username(self) -> Optional[str]:
"""
@@ -179,6 +187,24 @@ class Telegram:
"""
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:
"""
更新用户与聊天的映射关系
@@ -229,7 +255,7 @@ class Telegram:
for entity in message.entities:
if entity.type == "mention":
mention_text = message.text[
entity.offset : entity.offset + entity.length
entity.offset: entity.offset + entity.length
]
if mention_text == f"@{self._bot_username}":
logger.debug(
@@ -295,15 +321,15 @@ class Telegram:
task.join(timeout=1)
def send_msg(
self,
title: str,
text: Optional[str] = None,
image: Optional[str] = None,
userid: Optional[str] = None,
link: Optional[str] = None,
buttons: Optional[List[List[dict]]] = None,
original_message_id: Optional[int] = None,
original_chat_id: Optional[str] = None,
self,
title: str,
text: Optional[str] = None,
image: Optional[str] = None,
userid: Optional[str] = None,
link: Optional[str] = None,
buttons: Optional[List[List[dict]]] = None,
original_message_id: Optional[int] = None,
original_chat_id: Optional[str] = None,
) -> Optional[dict]:
"""
发送Telegram消息
@@ -324,6 +350,9 @@ class Telegram:
logger.warn("标题和内容不能同时为空")
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:
# 标准化标题后再加粗,避免**符号被显示为文本
bold_title = (
@@ -341,9 +370,6 @@ class Telegram:
if 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
if buttons:
@@ -386,7 +412,7 @@ class Telegram:
return {"success": False}
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:
"""
确定目标聊天ID使用用户映射确保回复到正确的聊天
@@ -410,14 +436,14 @@ class Telegram:
return self._telegram_chat_id
def send_medias_msg(
self,
medias: List[MediaInfo],
userid: Optional[str] = None,
title: Optional[str] = None,
link: Optional[str] = None,
buttons: Optional[List[List[Dict]]] = None,
original_message_id: Optional[int] = None,
original_chat_id: Optional[str] = None,
self,
medias: List[MediaInfo],
userid: Optional[str] = None,
title: Optional[str] = None,
link: Optional[str] = None,
buttons: Optional[List[List[Dict]]] = None,
original_message_id: Optional[int] = None,
original_chat_id: Optional[str] = None,
) -> Optional[bool]:
"""
发送媒体列表消息
@@ -487,14 +513,14 @@ class Telegram:
return False
def send_torrents_msg(
self,
torrents: List[Context],
userid: Optional[str] = None,
title: Optional[str] = None,
link: Optional[str] = None,
buttons: Optional[List[List[Dict]]] = None,
original_message_id: Optional[int] = None,
original_chat_id: Optional[str] = None,
self,
torrents: List[Context],
userid: Optional[str] = None,
title: Optional[str] = None,
link: Optional[str] = None,
buttons: Optional[List[List[Dict]]] = None,
original_message_id: Optional[int] = None,
original_chat_id: Optional[str] = None,
) -> Optional[bool]:
"""
发送种子列表消息
@@ -586,10 +612,10 @@ class Telegram:
return InlineKeyboardMarkup(keyboard)
def answer_callback_query(
self,
callback_query_id: int,
text: Optional[str] = None,
show_alert: bool = False,
self,
callback_query_id: int,
text: Optional[str] = None,
show_alert: bool = False,
) -> Optional[bool]:
"""
回应回调查询
@@ -607,7 +633,7 @@ class Telegram:
return False
def delete_msg(
self, message_id: int, chat_id: Optional[int] = None
self, message_id: int, chat_id: Optional[int] = None
) -> Optional[bool]:
"""
删除Telegram消息
@@ -644,11 +670,11 @@ class Telegram:
return False
def edit_msg(
self,
chat_id: Union[str, int],
message_id: Union[str, int],
text: str,
title: Optional[str] = None,
self,
chat_id: Union[str, int],
message_id: Union[str, int],
text: str,
title: Optional[str] = None,
) -> Optional[bool]:
"""
编辑Telegram消息公开方法
@@ -681,12 +707,12 @@ class Telegram:
return False
def __edit_message(
self,
chat_id: str,
message_id: int,
text: str,
buttons: Optional[List[List[dict]]] = None,
image: Optional[str] = None,
self,
chat_id: str,
message_id: int,
text: str,
buttons: Optional[List[List[dict]]] = None,
image: Optional[str] = None,
) -> Optional[bool]:
"""
编辑已发送的消息
@@ -732,11 +758,11 @@ class Telegram:
return False
def __send_request(
self,
userid: Optional[str] = None,
image="",
caption="",
reply_markup: Optional[InlineKeyboardMarkup] = None,
self,
userid: Optional[str] = None,
image="",
caption="",
reply_markup: Optional[InlineKeyboardMarkup] = None,
):
"""
向Telegram发送报文返回发送的消息对象
@@ -795,26 +821,27 @@ class Telegram:
@retry(RetryException, logger=logger)
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 = (
ThreadHelper()
.submit(lambda x: asyncio.run(telegramify(x)), caption)
.result()
)
reply_markup = kwargs.pop("reply_markup", None)
ret = None
for i, item in enumerate(boxs):
if i in sent_idx:
# 跳过已发送消息
continue
boxs: SentType = (
ThreadHelper()
.submit(lambda x: asyncio.run(telegramify(x)), caption)
.result()
)
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
if item.content_type == ContentTypes.TEXT and (i != 0 or not image):
@@ -843,12 +870,13 @@ class Telegram:
sent_idx.add(i)
return ret
except Exception as e:
try:
raise RetryException(f"消息 [{i + 1}/{len(boxs)}] 发送失败") from e
except NameError:
raise
except Exception as e:
try:
raise RetryException(f"消息 [{i + 1}/{len(boxs)}] 发送失败") from e
except NameError:
raise
return ret
def register_commands(self, commands: Dict[str, dict]):
"""