mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-07-09 14:32:16 +08:00
Merge branch 'v2' of https://github.com/jxxghp/MoviePilot into v2
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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]):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user