This commit is contained in:
jxxghp
2025-11-25 13:33:04 +08:00
4 changed files with 130 additions and 69 deletions

View File

@@ -69,8 +69,6 @@ class MoviePilotAgent:
"""初始化LLM模型""" """初始化LLM模型"""
provider = settings.LLM_PROVIDER.lower() provider = settings.LLM_PROVIDER.lower()
api_key = settings.LLM_API_KEY api_key = settings.LLM_API_KEY
if not api_key:
raise ValueError("未配置 LLM_API_KEY")
if provider == "google": if provider == "google":
if settings.PROXY_HOST: if settings.PROXY_HOST:

View File

@@ -931,19 +931,11 @@ class MessageChain(ChainBase):
)) ))
return return
# 检查LLM配置
if not settings.LLM_API_KEY:
self.post_message(Notification(
channel=channel,
source=source,
userid=userid,
username=username,
title="MoviePilot智能助未配置请在系统设置中配置"
))
return
# 提取用户消息 # 提取用户消息
user_message = text[3:].strip() # 移除 "/ai" 前缀 if text.lower().startswith("/ai"):
user_message = text[3:].strip() # 移除 "/ai" 前缀(大小写不敏感)
else:
user_message = text.strip() # 按原消息处理
if not user_message: if not user_message:
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,

View File

@@ -1024,13 +1024,11 @@ def fresh(fresh: bool = True):
with fresh(): with fresh():
result = some_cached_function() result = some_cached_function()
""" """
token = _fresh.set(fresh) token = _fresh.set(fresh or is_fresh())
logger.debug(f"Setting fresh mode to {fresh}. {id(token):#x}")
try: try:
yield yield
finally: finally:
_fresh.reset(token) _fresh.reset(token)
logger.debug(f"Reset fresh mode. {id(token):#x}")
@asynccontextmanager @asynccontextmanager
async def async_fresh(fresh: bool = True): async def async_fresh(fresh: bool = True):
@@ -1041,13 +1039,11 @@ async def async_fresh(fresh: bool = True):
async with async_fresh(): async with async_fresh():
result = await some_async_cached_function() result = await some_async_cached_function()
""" """
token = _fresh.set(fresh) token = _fresh.set(fresh or is_fresh())
logger.debug(f"Setting async_fresh mode to {fresh}. {id(token):#x}")
try: try:
yield yield
finally: finally:
_fresh.reset(token) _fresh.reset(token)
logger.debug(f"Reset async_fresh mode. {id(token):#x}")
def is_fresh() -> bool: def is_fresh() -> bool:
""" """

View File

@@ -1,19 +1,21 @@
import asyncio
import re import re
import threading import threading
import uuid
from pathlib import Path
from threading import Event from threading import Event
from typing import Optional, List, Dict, Callable from typing import Optional, List, Dict, Callable
from urllib.parse import urljoin from urllib.parse import urljoin
import telebot import telebot
from telegramify_markdown import standardize, telegramify
from telegramify_markdown.type import ContentTypes, SentType
from telebot import apihelper from telebot import apihelper
from telebot.types import InputFile, InlineKeyboardMarkup, InlineKeyboardButton from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
from telebot.types import InputMediaPhoto from telebot.types import InputMediaPhoto
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.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
@@ -52,7 +54,7 @@ class Telegram:
else: else:
apihelper.proxy = settings.PROXY apihelper.proxy = settings.PROXY
# bot # bot
_bot = telebot.TeleBot(self._telegram_token, parse_mode="Markdown") _bot = telebot.TeleBot(self._telegram_token, parse_mode="MarkdownV2")
# 记录句柄 # 记录句柄
self._bot = _bot self._bot = _bot
# 获取并存储bot用户名用于@检测 # 获取并存储bot用户名用于@检测
@@ -236,12 +238,14 @@ class Telegram:
return False return False
try: try:
if text: if title and text:
# 对text进行Markdown特殊字符转义 caption = f"**{title}**\n{text}"
text = re.sub(r"([_`])", r"\\\1", text) elif title:
caption = f"*{title}*\n{text}" caption = f"**{title}**"
elif text:
caption = text
else: else:
caption = f"*{title}*" caption = ""
if link: if link:
caption = f"{caption}\n[查看详情]({link})" caption = f"{caption}\n[查看详情]({link})"
@@ -499,7 +503,7 @@ class Telegram:
if image: if image:
# 如果有图片使用edit_message_media # 如果有图片使用edit_message_media
media = InputMediaPhoto(media=image, caption=text, parse_mode="Markdown") media = InputMediaPhoto(media=image, caption=standardize(text), parse_mode="MarkdownV2")
self._bot.edit_message_media( self._bot.edit_message_media(
chat_id=chat_id, chat_id=chat_id,
message_id=message_id, message_id=message_id,
@@ -511,8 +515,8 @@ class Telegram:
self._bot.edit_message_text( self._bot.edit_message_text(
chat_id=chat_id, chat_id=chat_id,
message_id=message_id, message_id=message_id,
text=text, text=standardize(text),
parse_mode="Markdown", parse_mode="MarkdownV2",
reply_markup=reply_markup reply_markup=reply_markup
) )
return True return True
@@ -520,49 +524,120 @@ class Telegram:
logger.error(f"编辑消息失败:{str(e)}") logger.error(f"编辑消息失败:{str(e)}")
return False return False
@retry(RetryException, logger=logger)
def __send_request(self, userid: Optional[str] = None, image="", caption="", def __send_request(self, userid: Optional[str] = None, image="", caption="",
reply_markup: Optional[InlineKeyboardMarkup] = None) -> bool: reply_markup: Optional[InlineKeyboardMarkup] = None) -> bool:
""" """
向Telegram发送报文 向Telegram发送报文
:param reply_markup: 内联键盘 :param reply_markup: 内联键盘
""" """
if image: kwargs = {
res = RequestUtils(proxies=settings.PROXY, ua=settings.NORMAL_USER_AGENT).get_res(image) 'chat_id': userid or self._telegram_chat_id,
if res is None: 'parse_mode': "MarkdownV2",
raise Exception("获取图片失败") 'reply_markup': reply_markup
if res.content: }
# 使用随机标识构建图片文件的完整路径,并写入图片内容到文件
image_file = Path(settings.TEMP_PATH) / "telegram" / str(uuid.uuid4()) try:
if not image_file.parent.exists(): # 处理图片
image_file.parent.mkdir(parents=True, exist_ok=True) image = self.__process_image(image) if image else None
image_file.write_bytes(res.content)
photo = InputFile(image_file) # 图片消息的标题长度限制为1024文本消息为4096
# 发送图片到Telegram caption_limit = 1024 if image else 4096
ret = self._bot.send_photo(chat_id=userid or self._telegram_chat_id, if len(caption) < caption_limit:
photo=photo, ret = self.__send_short_message(image, caption, **kwargs)
caption=caption, else:
parse_mode="Markdown", sent_idx = set()
reply_markup=reply_markup) ret = self.__send_long_message(image, caption, sent_idx, **kwargs)
if ret is None:
raise RetryException("发送图片消息失败") return ret is not None
return True except Exception as e:
# 按4096分段循环发送消息 logger.error(f"发送Telegram消息失败: {e}")
ret = None return False
if len(caption) > 4095:
for i in range(0, len(caption), 4095): @retry(RetryException, logger=logger)
ret = self._bot.send_message(chat_id=userid or self._telegram_chat_id, def __process_image(self, image_url: str) -> bytes:
text=caption[i:i + 4095], """
parse_mode="Markdown", 处理图片URL获取图片内容
reply_markup=reply_markup if i == 0 else None) """
else: try:
ret = self._bot.send_message(chat_id=userid or self._telegram_chat_id, res = RequestUtils(
text=caption, proxies=settings.PROXY,
parse_mode="Markdown", ua=settings.NORMAL_USER_AGENT
reply_markup=reply_markup) ).get_res(image_url)
if ret is None:
raise RetryException("发送文本消息失败") if not res or not res.content:
return True if ret else False raise RetryException("获取图片失败")
return res.content
except Exception as e:
raise
@retry(RetryException, logger=logger)
def __send_short_message(self, image: Optional[bytes], caption: str, **kwargs):
"""
发送短消息
"""
try:
if image:
return self._bot.send_photo(
photo=image,
caption=standardize(caption),
**kwargs
)
else:
return self._bot.send_message(
text=standardize(caption),
**kwargs
)
except Exception as e:
raise RetryException(f"发送{'图片' if image else '文本'}消息失败")
@retry(RetryException, logger=logger)
def __send_long_message(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()
ret = None
for i, item in enumerate(boxs):
if i in sent_idx:
# 跳过已发送消息
continue
current_reply_markup = reply_markup if i == 0 else None
if item.content_type == ContentTypes.TEXT and (i != 0 or not image):
ret = self._bot.send_message(**kwargs,
text=item.content,
reply_markup=current_reply_markup
)
elif item.content_type == ContentTypes.PHOTO or (image and i == 0):
ret = self._bot.send_photo(**kwargs,
photo=(getattr(item, "file_name", ""),
getattr(item, "file_data", image)),
caption=getattr(item, "caption", item.content),
reply_markup=current_reply_markup
)
elif item.content_type == ContentTypes.FILE:
ret = self._bot.send_document(**kwargs,
document=(item.file_name, item.file_data),
caption=item.caption,
reply_markup=current_reply_markup
)
sent_idx.add(i)
return ret
except Exception as e:
try:
raise RetryException(f"消息 [{i + 1}/{len(boxs)}] 发送失败") from e
except NameError:
raise RetryException("发送长消息失败") from e
def register_commands(self, commands: Dict[str, dict]): def register_commands(self, commands: Dict[str, dict]):
""" """