feat: conditionally save message history based on save_history flag

This commit is contained in:
jxxghp
2026-06-17 21:00:40 +08:00
parent 4c20639abb
commit e1ba9a2c97
12 changed files with 324 additions and 171 deletions
+8 -4
View File
@@ -1478,7 +1478,8 @@ class ChainBase(metaclass=ABCMeta):
if not message: if not message:
logger.warning("消息为空,跳过发送") logger.warning("消息为空,跳过发送")
return return
self.messageoper.add(**message.model_dump()) if message.save_history:
self.messageoper.add(**message.model_dump())
dispatch_message = self._normalize_notification_for_dispatch(message) dispatch_message = self._normalize_notification_for_dispatch(message)
# 发送消息按设置隔离 # 发送消息按设置隔离
if not dispatch_message.userid and dispatch_message.mtype: if not dispatch_message.userid and dispatch_message.mtype:
@@ -1593,7 +1594,8 @@ class ChainBase(metaclass=ABCMeta):
if not message: if not message:
logger.warning("消息为空,跳过发送") logger.warning("消息为空,跳过发送")
return return
await self.messageoper.async_add(**message.model_dump()) if message.save_history:
await self.messageoper.async_add(**message.model_dump())
dispatch_message = self._normalize_notification_for_dispatch(message) dispatch_message = self._normalize_notification_for_dispatch(message)
# 发送消息按设置隔离 # 发送消息按设置隔离
if not dispatch_message.userid and dispatch_message.mtype: if not dispatch_message.userid and dispatch_message.mtype:
@@ -1684,7 +1686,8 @@ class ChainBase(metaclass=ABCMeta):
:return: 成功或失败 :return: 成功或失败
""" """
note_list = [media.to_dict() for media in medias] note_list = [media.to_dict() for media in medias]
self.messageoper.add(**message.model_dump(), note=note_list) if message.save_history:
self.messageoper.add(**message.model_dump(), note=note_list)
dispatch_message = self._normalize_notification_for_dispatch(message) dispatch_message = self._normalize_notification_for_dispatch(message)
return self.messagequeue.send_message( return self.messagequeue.send_message(
"post_medias_message", "post_medias_message",
@@ -1703,7 +1706,8 @@ class ChainBase(metaclass=ABCMeta):
:return: 成功或失败 :return: 成功或失败
""" """
note_list = [torrent.torrent_info.to_dict() for torrent in torrents] note_list = [torrent.torrent_info.to_dict() for torrent in torrents]
self.messageoper.add(**message.model_dump(), note=note_list) if message.save_history:
self.messageoper.add(**message.model_dump(), note=note_list)
dispatch_message = self._normalize_notification_for_dispatch(message) dispatch_message = self._normalize_notification_for_dispatch(message)
return self.messagequeue.send_message( return self.messagequeue.send_message(
"post_torrents_message", "post_torrents_message",
+4 -2
View File
@@ -1337,7 +1337,8 @@ class DownloadChain(ChainBase):
mtype=NotificationType.Download, mtype=NotificationType.Download,
title="没有正在下载的任务!", title="没有正在下载的任务!",
userid=userid, userid=userid,
link=settings.MP_DOMAIN('#/downloading') link=settings.MP_DOMAIN('#/downloading'),
save_history=False,
)) ))
return return
# 发送消息 # 发送消息
@@ -1356,7 +1357,8 @@ class DownloadChain(ChainBase):
title=title, title=title,
text="\n".join(messages), text="\n".join(messages),
userid=userid, userid=userid,
link=settings.MP_DOMAIN('#/downloading') link=settings.MP_DOMAIN('#/downloading'),
save_history=False,
)) ))
def downloading(self, name: Optional[str] = None) -> List[DownloaderTorrent]: def downloading(self, name: Optional[str] = None) -> List[DownloaderTorrent]:
+36
View File
@@ -194,6 +194,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="语音识别失败,请稍后重试", title="语音识别失败,请稍后重试",
save_history=False,
) )
) )
return return
@@ -298,6 +299,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="请输入要使用传统交互处理的内容", title="请输入要使用传统交互处理的内容",
save_history=False,
) )
) )
return False return False
@@ -623,6 +625,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="回调数据格式错误,请检查!", title="回调数据格式错误,请检查!",
save_history=False,
) )
) )
return False return False
@@ -751,6 +754,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="该选择已失效,请重新发起选择", title="该选择已失效,请重新发起选择",
save_history=False,
) )
) )
return False return False
@@ -823,6 +827,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title=f"开始重新整理记录 #{history_id} ...", title=f"开始重新整理记录 #{history_id} ...",
save_history=False,
) )
) )
@@ -836,6 +841,7 @@ class MessageChain(ChainBase):
username=username, username=username,
title=f"整理记录 #{history_id} 已重新整理", title=f"整理记录 #{history_id} 已重新整理",
link=settings.MP_DOMAIN("#/history"), link=settings.MP_DOMAIN("#/history"),
save_history=False,
) )
) )
return return
@@ -849,6 +855,7 @@ class MessageChain(ChainBase):
title="重新整理失败", title="重新整理失败",
text=errmsg, text=errmsg,
link=settings.MP_DOMAIN("#/history"), link=settings.MP_DOMAIN("#/history"),
save_history=False,
) )
) )
@@ -902,6 +909,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="MoviePilot智能助手未启用,请在系统设置中启用", title="MoviePilot智能助手未启用,请在系统设置中启用",
save_history=False,
) )
) )
return return
@@ -917,6 +925,7 @@ class MessageChain(ChainBase):
title="重新整理失败", title="重新整理失败",
text=f"整理记录 #{history_id} 不存在", text=f"整理记录 #{history_id} 不存在",
link=settings.MP_DOMAIN("#/history"), link=settings.MP_DOMAIN("#/history"),
save_history=False,
) )
) )
return return
@@ -932,6 +941,7 @@ class MessageChain(ChainBase):
title=f"已将整理记录 #{history_id} 交给智能助手处理", title=f"已将整理记录 #{history_id} 交给智能助手处理",
text="处理完成后会在这里回复结果。", text="处理完成后会在这里回复结果。",
link=settings.MP_DOMAIN("#/history"), link=settings.MP_DOMAIN("#/history"),
save_history=False,
) )
) )
@@ -960,6 +970,7 @@ class MessageChain(ChainBase):
text=final_output.strip() text=final_output.strip()
or f"整理记录 #{history_id} 已由智能助手处理完成。", or f"整理记录 #{history_id} 已由智能助手处理完成。",
link=settings.MP_DOMAIN("#/history"), link=settings.MP_DOMAIN("#/history"),
save_history=False,
) )
) )
except Exception as e: except Exception as e:
@@ -972,6 +983,7 @@ class MessageChain(ChainBase):
title="智能助手整理失败", title="智能助手整理失败",
text=str(e), text=str(e),
link=settings.MP_DOMAIN("#/history"), link=settings.MP_DOMAIN("#/history"),
save_history=False,
) )
) )
@@ -1093,6 +1105,7 @@ class MessageChain(ChainBase):
source=source, source=source,
title="智能体会话已清除,下次将创建新的会话", title="智能体会话已清除,下次将创建新的会话",
userid=userid, userid=userid,
save_history=False,
) )
) )
else: else:
@@ -1102,6 +1115,7 @@ class MessageChain(ChainBase):
source=source, source=source,
title="您当前没有活跃的智能体会话", title="您当前没有活跃的智能体会话",
userid=userid, userid=userid,
save_history=False,
) )
) )
@@ -1137,6 +1151,7 @@ class MessageChain(ChainBase):
source=source, source=source,
title="智能体推理已应急停止,会话记忆已保留,您可以继续对话", title="智能体推理已应急停止,会话记忆已保留,您可以继续对话",
userid=userid, userid=userid,
save_history=False,
) )
) )
else: else:
@@ -1146,6 +1161,7 @@ class MessageChain(ChainBase):
source=source, source=source,
title="当前没有正在执行的智能体任务", title="当前没有正在执行的智能体任务",
userid=userid, userid=userid,
save_history=False,
) )
) )
else: else:
@@ -1155,6 +1171,7 @@ class MessageChain(ChainBase):
source=source, source=source,
title="您当前没有活跃的智能体会话", title="您当前没有活跃的智能体会话",
userid=userid, userid=userid,
save_history=False,
) )
) )
@@ -1210,6 +1227,7 @@ class MessageChain(ChainBase):
source=source, source=source,
title="您当前没有活跃的智能体会话", title="您当前没有活跃的智能体会话",
userid=userid, userid=userid,
save_history=False,
) )
) )
return return
@@ -1223,6 +1241,7 @@ class MessageChain(ChainBase):
title="当前智能体会话状态", title="当前智能体会话状态",
text=self._format_session_status_text(status), text=self._format_session_status_text(status),
userid=userid, userid=userid,
save_history=False,
) )
) )
@@ -1253,6 +1272,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="MoviePilot智能助手未启用,请在系统设置中启用", title="MoviePilot智能助手未启用,请在系统设置中启用",
save_history=False,
) )
) )
return False return False
@@ -1274,6 +1294,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="请输入您的问题或需求", title="请输入您的问题或需求",
save_history=False,
) )
) )
return False return False
@@ -1297,6 +1318,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="附件读取失败,请稍后重试", title="附件读取失败,请稍后重试",
save_history=False,
) )
) )
return False return False
@@ -1315,6 +1337,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="附件读取失败,请稍后重试", title="附件读取失败,请稍后重试",
save_history=False,
) )
) )
return False return False
@@ -1335,6 +1358,7 @@ class MessageChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="文件读取失败,请稍后重试", title="文件读取失败,请稍后重试",
save_history=False,
) )
) )
return False return False
@@ -2002,6 +2026,7 @@ class MediaInteractionChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="交互已失效,请重新搜索或订阅", title="交互已失效,请重新搜索或订阅",
save_history=False,
) )
) )
return True return True
@@ -2115,6 +2140,7 @@ class MediaInteractionChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title="媒体交互已结束", title="媒体交互已结束",
save_history=False,
) )
) )
return True return True
@@ -2282,6 +2308,7 @@ class MediaInteractionChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title=f"{meta.name} 没有找到对应的媒体信息!", title=f"{meta.name} 没有找到对应的媒体信息!",
save_history=False,
) )
) )
return return
@@ -2386,6 +2413,7 @@ class MediaInteractionChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title=f"{mediainfo.title_year}{request.meta.sea} 媒体库中已存在,如需重新下载请发送:搜索 名称 或 下载 名称】", title=f"{mediainfo.title_year}{request.meta.sea} 媒体库中已存在,如需重新下载请发送:搜索 名称 或 下载 名称】",
save_history=False,
) )
) )
return return
@@ -2405,6 +2433,7 @@ class MediaInteractionChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title=f"{mediainfo.title_year}\n" + "\n".join(messages), title=f"{mediainfo.title_year}\n" + "\n".join(messages),
save_history=False,
) )
) )
@@ -2416,6 +2445,7 @@ class MediaInteractionChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title=f"开始搜索 {mediainfo.type.value} {mediainfo.title_year} ...", title=f"开始搜索 {mediainfo.type.value} {mediainfo.title_year} ...",
save_history=False,
) )
) )
@@ -2428,6 +2458,7 @@ class MediaInteractionChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title=f"{mediainfo.title}{request.meta.sea} 未搜索到需要的资源!", title=f"{mediainfo.title}{request.meta.sea} 未搜索到需要的资源!",
save_history=False,
) )
) )
return return
@@ -2501,6 +2532,7 @@ class MediaInteractionChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title=f"{mediainfo.title_year}{request.meta.sea} 媒体库中已存在,如需洗版请发送:洗版 XXX】", title=f"{mediainfo.title_year}{request.meta.sea} 媒体库中已存在,如需洗版请发送:洗版 XXX】",
save_history=False,
) )
) )
return return
@@ -2904,6 +2936,7 @@ class MediaInteractionChain(ChainBase):
buttons=buttons, buttons=buttons,
original_message_id=original_message_id, original_message_id=original_message_id,
original_chat_id=original_chat_id, original_chat_id=original_chat_id,
save_history=False,
), ),
medias=page_items, medias=page_items,
) )
@@ -2953,6 +2986,7 @@ class MediaInteractionChain(ChainBase):
buttons=buttons, buttons=buttons,
original_message_id=original_message_id, original_message_id=original_message_id,
original_chat_id=original_chat_id, original_chat_id=original_chat_id,
save_history=False,
), ),
torrents=page_items, torrents=page_items,
) )
@@ -3006,6 +3040,7 @@ class MediaInteractionChain(ChainBase):
buttons=buttons, buttons=buttons,
original_message_id=original_message_id, original_message_id=original_message_id,
original_chat_id=original_chat_id, original_chat_id=original_chat_id,
save_history=False,
) )
) )
@@ -3324,5 +3359,6 @@ class MediaInteractionChain(ChainBase):
userid=userid, userid=userid,
username=username, username=username,
title=title, title=title,
save_history=False,
) )
) )
+73 -56
View File
@@ -4,18 +4,10 @@ from datetime import datetime
from typing import List, Optional, Tuple, Union, Dict from typing import List, Optional, Tuple, Union, Dict
from urllib.parse import urljoin from urllib.parse import urljoin
from app.helper.sites import SitesHelper # noqa
from lxml import etree from lxml import etree
from app.chain import ChainBase from app.chain import ChainBase
from app.helper.interaction import (
SlashInteractionManager,
build_navigation_buttons,
format_markdown_table,
page_items,
supports_interaction_buttons,
supports_markdown,
update_or_post_message,
)
from app.core.config import global_vars, settings from app.core.config import global_vars, settings
from app.core.event import Event, eventmanager from app.core.event import Event, eventmanager
from app.db.models.site import Site from app.db.models.site import Site
@@ -25,8 +17,16 @@ from app.helper.browser import PlaywrightHelper
from app.helper.cloudflare import under_challenge from app.helper.cloudflare import under_challenge
from app.helper.cookie import CookieHelper from app.helper.cookie import CookieHelper
from app.helper.cookiecloud import CookieCloudHelper from app.helper.cookiecloud import CookieCloudHelper
from app.helper.interaction import (
SlashInteractionManager,
build_navigation_buttons,
format_markdown_table,
page_items,
supports_interaction_buttons,
supports_markdown,
update_or_post_message,
)
from app.helper.rss import RssHelper from app.helper.rss import RssHelper
from app.helper.sites import SitesHelper # noqa
from app.log import logger from app.log import logger
from app.schemas import MessageChannel, Notification, SiteUserData from app.schemas import MessageChannel, Notification, SiteUserData
from app.schemas.types import EventType, NotificationType from app.schemas.types import EventType, NotificationType
@@ -34,7 +34,6 @@ from app.utils.http import RequestUtils
from app.utils.site import SiteUtils from app.utils.site import SiteUtils
from app.utils.string import StringUtils from app.utils.string import StringUtils
site_interaction_manager = SlashInteractionManager() site_interaction_manager = SlashInteractionManager()
@@ -359,7 +358,7 @@ class SiteChain(ChainBase):
if global_vars.is_system_stopped: if global_vars.is_system_stopped:
logger.info("系统正在停止,中断CookieCloud同步") logger.info("系统正在停止,中断CookieCloud同步")
return False, "系统正在停止,同步被中断" return False, "系统正在停止,同步被中断"
# 索引器信息 # 索引器信息
indexer = siteshelper.get_indexer(domain) indexer = siteshelper.get_indexer(domain)
# 数据库的站点信息 # 数据库的站点信息
@@ -642,11 +641,11 @@ class SiteChain(ChainBase):
return True, "连接成功" return True, "连接成功"
def remote_list( def remote_list(
self, self,
arg_str: str = "", arg_str: str = "",
channel: MessageChannel = None, channel: MessageChannel = None,
userid: Union[str, int] = None, userid: Union[str, int] = None,
source: Optional[str] = None, source: Optional[str] = None,
): ):
""" """
/sites 统一入口。 /sites 统一入口。
@@ -660,11 +659,11 @@ class SiteChain(ChainBase):
) )
normalized_arg = (arg_str or "").strip() normalized_arg = (arg_str or "").strip()
if normalized_arg and self.handle_text_interaction( if normalized_arg and self.handle_text_interaction(
channel=channel, channel=channel,
source=source, source=source,
userid=userid, userid=userid,
username="", username="",
text=normalized_arg, text=normalized_arg,
): ):
return return
self._render_site_interaction( self._render_site_interaction(
@@ -688,14 +687,14 @@ class SiteChain(ChainBase):
return parts[1], parts[2] return parts[1], parts[2]
def handle_callback_interaction( def handle_callback_interaction(
self, self,
callback_data: str, callback_data: str,
channel: MessageChannel, channel: MessageChannel,
source: str, source: str,
userid: Union[str, int], userid: Union[str, int],
username: str, username: str,
original_message_id: Optional[Union[str, int]] = None, original_message_id: Optional[Union[str, int]] = None,
original_chat_id: Optional[str] = None, original_chat_id: Optional[str] = None,
) -> bool: ) -> bool:
""" """
处理 /sites 按钮交互。 处理 /sites 按钮交互。
@@ -760,12 +759,12 @@ class SiteChain(ChainBase):
return True return True
def handle_text_interaction( def handle_text_interaction(
self, self,
channel: MessageChannel, channel: MessageChannel,
source: str, source: str,
userid: Union[str, int], userid: Union[str, int],
username: str, username: str,
text: str, text: str,
) -> bool: ) -> bool:
""" """
处理 /sites 文本补充输入。 处理 /sites 文本补充输入。
@@ -987,14 +986,14 @@ class SiteChain(ChainBase):
return True return True
def _render_site_interaction( def _render_site_interaction(
self, self,
request, request,
channel: MessageChannel, channel: MessageChannel,
source: Optional[str], source: Optional[str],
userid: Union[str, int], userid: Union[str, int],
username: Optional[str], username: Optional[str],
original_message_id: Optional[Union[str, int]] = None, original_message_id: Optional[Union[str, int]] = None,
original_chat_id: Optional[str] = None, original_chat_id: Optional[str] = None,
) -> None: ) -> None:
""" """
渲染 /sites 当前页面。 渲染 /sites 当前页面。
@@ -1202,7 +1201,8 @@ class SiteChain(ChainBase):
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
title=f"站点编号 {site_id} 不存在!", title=f"站点编号 {site_id} 不存在!",
userid=userid)) userid=userid,
save_history=False))
return return
# 禁用站点 # 禁用站点
siteoper.update(site_id, { siteoper.update(site_id, {
@@ -1229,7 +1229,9 @@ class SiteChain(ChainBase):
if not site: if not site:
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
title=f"站点编号 {site_id} 不存在!", userid=userid)) title=f"站点编号 {site_id} 不存在!",
userid=userid,
save_history=False))
return return
# 禁用站点 # 禁用站点
siteoper.update(site_id, { siteoper.update(site_id, {
@@ -1280,7 +1282,9 @@ class SiteChain(ChainBase):
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
source=source, source=source,
title=err_title, userid=userid)) title=err_title,
userid=userid,
save_history=False))
return return
arg_str = str(arg_str).strip() arg_str = str(arg_str).strip()
args = arg_str.split() args = arg_str.split()
@@ -1292,14 +1296,18 @@ class SiteChain(ChainBase):
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
source=source, source=source,
title=err_title, userid=userid)) title=err_title,
userid=userid,
save_history=False))
return return
site_id = args[0] site_id = args[0]
if not site_id.isdigit(): if not site_id.isdigit():
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
source=source, source=source,
title=err_title, userid=userid)) title=err_title,
userid=userid,
save_history=False))
return return
# 站点ID # 站点ID
site_id = int(site_id) site_id = int(site_id)
@@ -1309,12 +1317,16 @@ class SiteChain(ChainBase):
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
source=source, source=source,
title=f"站点编号 {site_id} 不存在!", userid=userid)) title=f"站点编号 {site_id} 不存在!",
userid=userid,
save_history=False))
return return
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
source=source, source=source,
title=f"开始更新【{site_info.name}】Cookie&UA ...", userid=userid)) title=f"开始更新【{site_info.name}】Cookie&UA ...",
userid=userid,
save_history=False))
# 用户名 # 用户名
username = args[1] username = args[1]
# 密码 # 密码
@@ -1331,13 +1343,15 @@ class SiteChain(ChainBase):
source=source, source=source,
title=f"{site_info.name}】 Cookie&UA更新失败!", title=f"{site_info.name}】 Cookie&UA更新失败!",
text=f"错误原因:{msg}", text=f"错误原因:{msg}",
userid=userid)) userid=userid,
save_history=False))
else: else:
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
source=source, source=source,
title=f"{site_info.name}】 Cookie&UA更新成功", title=f"{site_info.name}】 Cookie&UA更新成功",
userid=userid)) userid=userid,
save_history=False))
def remote_refresh_userdatas(self, channel: MessageChannel, def remote_refresh_userdatas(self, channel: MessageChannel,
userid: Union[str, int] = None, source: Optional[str] = None): userid: Union[str, int] = None, source: Optional[str] = None):
@@ -1349,7 +1363,8 @@ class SiteChain(ChainBase):
channel=channel, channel=channel,
source=source, source=source,
title="开始刷新站点数据 ...", title="开始刷新站点数据 ...",
userid=userid userid=userid,
save_history=False,
)) ))
# 刷新站点数据 # 刷新站点数据
site_datas = self.refresh_userdatas() site_datas = self.refresh_userdatas()
@@ -1392,12 +1407,14 @@ class SiteChain(ChainBase):
source=source, source=source,
title="【站点数据统计】", title="【站点数据统计】",
text="\n".join(sorted_messages), text="\n".join(sorted_messages),
userid=userid userid=userid,
save_history=False
)) ))
else: else:
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
source=source, source=source,
title="没有刷新到任何站点数据!", title="没有刷新到任何站点数据!",
userid=userid userid=userid,
save_history=False,
)) ))
+98 -92
View File
@@ -12,15 +12,6 @@ from app.chain import ChainBase
from app.chain.download import DownloadChain from app.chain.download import DownloadChain
from app.chain.media import MediaChain from app.chain.media import MediaChain
from app.chain.search import SearchChain from app.chain.search import SearchChain
from app.helper.interaction import (
SlashInteractionManager,
build_navigation_buttons,
format_markdown_table,
page_items,
supports_interaction_buttons,
supports_markdown,
update_or_post_message,
)
from app.chain.tmdb import TmdbChain from app.chain.tmdb import TmdbChain
from app.chain.torrents import TorrentsChain from app.chain.torrents import TorrentsChain
from app.core.config import settings, global_vars from app.core.config import settings, global_vars
@@ -34,6 +25,15 @@ from app.db.models.subscribe import Subscribe
from app.db.site_oper import SiteOper from app.db.site_oper import SiteOper
from app.db.subscribe_oper import SubscribeOper from app.db.subscribe_oper import SubscribeOper
from app.db.systemconfig_oper import SystemConfigOper from app.db.systemconfig_oper import SystemConfigOper
from app.helper.interaction import (
SlashInteractionManager,
build_navigation_buttons,
format_markdown_table,
page_items,
supports_interaction_buttons,
supports_markdown,
update_or_post_message,
)
from app.helper.server import MoviePilotServerHelper from app.helper.server import MoviePilotServerHelper
from app.helper.torrent import TorrentHelper from app.helper.torrent import TorrentHelper
from app.log import logger from app.log import logger
@@ -42,7 +42,6 @@ from app.schemas import (MediaRecognizeConvertEventData, SubscribeEpisodesRefres
from app.schemas.types import MediaType, SystemConfigKey, MessageChannel, NotificationType, EventType, ChainEventType, \ from app.schemas.types import MediaType, SystemConfigKey, MessageChannel, NotificationType, EventType, ChainEventType, \
ContentType ContentType
subscribe_interaction_manager = SlashInteractionManager() subscribe_interaction_manager = SlashInteractionManager()
@@ -365,9 +364,9 @@ class SubscribeChain(ChainBase):
判断当前订阅是否启用了电视剧全集洗版。 判断当前订阅是否启用了电视剧全集洗版。
""" """
return ( return (
bool(subscribe.best_version_full) bool(subscribe.best_version_full)
and bool(subscribe.best_version) and bool(subscribe.best_version)
and subscribe.type == MediaType.TV.value and subscribe.type == MediaType.TV.value
) )
@classmethod @classmethod
@@ -435,9 +434,9 @@ class SubscribeChain(ChainBase):
构造分集洗版优先全集时使用的整季缺失范围。 构造分集洗版优先全集时使用的整季缺失范围。
""" """
if ( if (
not subscribe.best_version not subscribe.best_version
or cls.__is_full_best_version_enabled(subscribe) or cls.__is_full_best_version_enabled(subscribe)
or subscribe.type != MediaType.TV.value or subscribe.type != MediaType.TV.value
): ):
return None return None
@@ -474,7 +473,7 @@ class SubscribeChain(ChainBase):
full_season_contexts = [ full_season_contexts = [
context for context in contexts context for context in contexts
if context.media_info.type == MediaType.TV if context.media_info.type == MediaType.TV
and self.__is_full_season_resource(meta=context.meta_info, subscribe=subscribe) and self.__is_full_season_resource(meta=context.meta_info, subscribe=subscribe)
] if full_pack_no_exists else [] ] if full_pack_no_exists else []
full_pack_contexts = [ full_pack_contexts = [
context for context in full_season_contexts context for context in full_season_contexts
@@ -1081,10 +1080,10 @@ class SubscribeChain(ChainBase):
# 洗版 # 洗版
if subscribe.best_version: if subscribe.best_version:
if ( if (
torrent_mediainfo.type == MediaType.TV torrent_mediainfo.type == MediaType.TV
and not self.__is_full_season_best_version_resource( and not self.__is_full_season_best_version_resource(
meta=torrent_meta, subscribe=subscribe meta=torrent_meta, subscribe=subscribe
) )
): ):
logger.info( logger.info(
f"{subscribe.name} 正在全集洗版,{torrent_info.title} 不是全集资源" f"{subscribe.name} 正在全集洗版,{torrent_info.title} 不是全集资源"
@@ -1092,10 +1091,10 @@ class SubscribeChain(ChainBase):
continue continue
# 洗版时,不符合订阅集数的不要 # 洗版时,不符合订阅集数的不要
if ( if (
torrent_mediainfo.type == MediaType.TV torrent_mediainfo.type == MediaType.TV
and not self._is_episode_range_covered( and not self._is_episode_range_covered(
meta=torrent_meta, subscribe=subscribe meta=torrent_meta, subscribe=subscribe
) )
): ):
logger.info( logger.info(
f"{subscribe.name} 正在洗版,{torrent_info.title} 不符合订阅集数范围" f"{subscribe.name} 正在洗版,{torrent_info.title} 不符合订阅集数范围"
@@ -1116,9 +1115,9 @@ class SubscribeChain(ChainBase):
# 防止标题元数据与实际种子文件错位导致同优先级集被重复下载。 # 防止标题元数据与实际种子文件错位导致同优先级集被重复下载。
context.allowed_episodes = set(interested_episodes) context.allowed_episodes = set(interested_episodes)
if ( if (
torrent_mediainfo.type != MediaType.TV torrent_mediainfo.type != MediaType.TV
and subscribe.current_priority and subscribe.current_priority
and torrent_info.pri_order <= subscribe.current_priority and torrent_info.pri_order <= subscribe.current_priority
): ):
logger.info( logger.info(
f'{subscribe.name} 正在洗版,{torrent_info.title} 优先级低于或等于已下载优先级') f'{subscribe.name} 正在洗版,{torrent_info.title} 优先级低于或等于已下载优先级')
@@ -1585,8 +1584,8 @@ class SubscribeChain(ChainBase):
continue continue
else: else:
if not self.__is_full_season_best_version_resource( if not self.__is_full_season_best_version_resource(
meta=torrent_meta, meta=torrent_meta,
subscribe=subscribe, subscribe=subscribe,
): ):
logger.debug( logger.debug(
f"{subscribe.name} 正在全集洗版,{torrent_info.title} 不是全集资源" f"{subscribe.name} 正在全集洗版,{torrent_info.title} 不是全集资源"
@@ -1594,11 +1593,11 @@ class SubscribeChain(ChainBase):
continue continue
# 洗版时,不符合订阅集数的不要 # 洗版时,不符合订阅集数的不要
if ( if (
meta.type == MediaType.TV meta.type == MediaType.TV
and not self._is_episode_range_covered( and not self._is_episode_range_covered(
meta=torrent_meta, meta=torrent_meta,
subscribe=subscribe, subscribe=subscribe,
) )
): ):
logger.debug( logger.debug(
f"{subscribe.name} 正在洗版,{torrent_info.title} 不符合订阅集数范围" f"{subscribe.name} 正在洗版,{torrent_info.title} 不符合订阅集数范围"
@@ -1642,9 +1641,9 @@ class SubscribeChain(ChainBase):
# 避免 RSS / 订阅刷新场景下标题元数据与种子文件错位导致同优先级集重复下载。 # 避免 RSS / 订阅刷新场景下标题元数据与种子文件错位导致同优先级集重复下载。
_context.allowed_episodes = set(interested_episodes) _context.allowed_episodes = set(interested_episodes)
if ( if (
meta.type != MediaType.TV meta.type != MediaType.TV
and subscribe.current_priority and subscribe.current_priority
and torrent_info.pri_order <= subscribe.current_priority and torrent_info.pri_order <= subscribe.current_priority
): ):
logger.info( logger.info(
f'{subscribe.name} 正在洗版,{torrent_info.title} 优先级低于或等于已下载优先级') f'{subscribe.name} 正在洗版,{torrent_info.title} 优先级低于或等于已下载优先级')
@@ -2087,11 +2086,11 @@ class SubscribeChain(ChainBase):
}) })
def remote_list( def remote_list(
self, self,
arg_str: str = "", arg_str: str = "",
channel: MessageChannel = None, channel: MessageChannel = None,
userid: Union[str, int] = None, userid: Union[str, int] = None,
source: Optional[str] = None, source: Optional[str] = None,
): ):
""" """
/subscribes 统一入口。 /subscribes 统一入口。
@@ -2105,11 +2104,11 @@ class SubscribeChain(ChainBase):
) )
normalized_arg = (arg_str or "").strip() normalized_arg = (arg_str or "").strip()
if normalized_arg and self.handle_text_interaction( if normalized_arg and self.handle_text_interaction(
channel=channel, channel=channel,
source=source, source=source,
userid=userid, userid=userid,
username="", username="",
text=normalized_arg, text=normalized_arg,
): ):
return return
self._render_subscribe_interaction( self._render_subscribe_interaction(
@@ -2133,14 +2132,14 @@ class SubscribeChain(ChainBase):
return parts[1], parts[2] return parts[1], parts[2]
def handle_callback_interaction( def handle_callback_interaction(
self, self,
callback_data: str, callback_data: str,
channel: MessageChannel, channel: MessageChannel,
source: str, source: str,
userid: Union[str, int], userid: Union[str, int],
username: str, username: str,
original_message_id: Optional[Union[str, int]] = None, original_message_id: Optional[Union[str, int]] = None,
original_chat_id: Optional[str] = None, original_chat_id: Optional[str] = None,
) -> bool: ) -> bool:
""" """
处理 /subscribes 按钮交互。 处理 /subscribes 按钮交互。
@@ -2211,12 +2210,12 @@ class SubscribeChain(ChainBase):
return True return True
def handle_text_interaction( def handle_text_interaction(
self, self,
channel: MessageChannel, channel: MessageChannel,
source: str, source: str,
userid: Union[str, int], userid: Union[str, int],
username: str, username: str,
text: str, text: str,
) -> bool: ) -> bool:
""" """
处理 /subscribes 文本补充输入。 处理 /subscribes 文本补充输入。
@@ -2416,14 +2415,14 @@ class SubscribeChain(ChainBase):
return True return True
def _render_subscribe_interaction( def _render_subscribe_interaction(
self, self,
request, request,
channel: MessageChannel, channel: MessageChannel,
source: Optional[str], source: Optional[str],
userid: Union[str, int], userid: Union[str, int],
username: Optional[str], username: Optional[str],
original_message_id: Optional[Union[str, int]] = None, original_message_id: Optional[Union[str, int]] = None,
original_chat_id: Optional[str] = None, original_chat_id: Optional[str] = None,
) -> None: ) -> None:
""" """
渲染 /subscribes 当前页面。 渲染 /subscribes 当前页面。
@@ -2502,7 +2501,7 @@ class SubscribeChain(ChainBase):
) )
def _format_subscribe_list( def _format_subscribe_list(
self, subscribes: List[Subscribe], channel: Optional[MessageChannel] self, subscribes: List[Subscribe], channel: Optional[MessageChannel]
) -> str: ) -> str:
""" """
根据渠道能力格式化订阅列表。 根据渠道能力格式化订阅列表。
@@ -2590,11 +2589,11 @@ class SubscribeChain(ChainBase):
) )
def _run_refresh_action( def _run_refresh_action(
self, self,
channel: MessageChannel, channel: MessageChannel,
source: str, source: str,
userid: Union[str, int], userid: Union[str, int],
username: str, username: str,
) -> None: ) -> None:
""" """
执行订阅刷新。 执行订阅刷新。
@@ -2620,11 +2619,11 @@ class SubscribeChain(ChainBase):
) )
def _run_metadata_refresh_action( def _run_metadata_refresh_action(
self, self,
channel: MessageChannel, channel: MessageChannel,
source: str, source: str,
userid: Union[str, int], userid: Union[str, int],
username: str, username: str,
) -> None: ) -> None:
""" """
执行订阅元数据刷新。 执行订阅元数据刷新。
@@ -2657,12 +2656,12 @@ class SubscribeChain(ChainBase):
return [int(item) for item in re.findall(r"\d+", arg_str or "")] return [int(item) for item in re.findall(r"\d+", arg_str or "")]
def _run_search_action( def _run_search_action(
self, self,
arg_str: str, arg_str: str,
channel: MessageChannel, channel: MessageChannel,
source: str, source: str,
userid: Union[str, int], userid: Union[str, int],
username: str, username: str,
) -> Tuple[bool, str]: ) -> Tuple[bool, str]:
""" """
手动执行订阅搜索。 手动执行订阅搜索。
@@ -2756,9 +2755,13 @@ class SubscribeChain(ChainBase):
删除订阅 删除订阅
""" """
if not arg_str: if not arg_str:
self.post_message(schemas.Notification(channel=channel, source=source, self.post_message(schemas.Notification(
title="请输入正确的命令格式:/subscribe_delete [id]" channel=channel,
"[id]为订阅编号", userid=userid)) source=source,
title="请输入正确的命令格式:/subscribe_delete [id]"
"[id]为订阅编号",
userid=userid,
save_history=False))
return return
arg_strs = str(arg_str).split() arg_strs = str(arg_str).split()
subscribeoper = SubscribeOper() subscribeoper = SubscribeOper()
@@ -2769,8 +2772,11 @@ class SubscribeChain(ChainBase):
subscribe_id = int(arg_str) subscribe_id = int(arg_str)
subscribe = subscribeoper.get(subscribe_id) subscribe = subscribeoper.get(subscribe_id)
if not subscribe: if not subscribe:
self.post_message(schemas.Notification(channel=channel, source=source, self.post_message(schemas.Notification(
title=f"订阅编号 {subscribe_id} 不存在!", userid=userid)) channel=channel, source=source,
title=f"订阅编号 {subscribe_id} 不存在!",
userid=userid,
save_history=False))
return return
# 删除订阅 # 删除订阅
subscribeoper.delete(subscribe_id) subscribeoper.delete(subscribe_id)
+23 -10
View File
@@ -27,8 +27,12 @@ class SystemChain(ChainBase):
清理系统缓存 清理系统缓存
""" """
self.clear_cache() self.clear_cache()
self.post_message(Notification(channel=channel, source=source, self.post_message(Notification(
title=f"缓存清理完成!", userid=userid)) channel=channel,
source=source,
title=f"缓存清理完成!",
userid=userid,
save_history=False))
def restart(self, channel: MessageChannel, userid: Union[int, str], source: Optional[str] = None): def restart(self, channel: MessageChannel, userid: Union[int, str], source: Optional[str] = None):
""" """
@@ -37,8 +41,12 @@ class SystemChain(ChainBase):
from app.core.config import global_vars from app.core.config import global_vars
if channel and userid: if channel and userid:
self.post_message(Notification(channel=channel, source=source, self.post_message(Notification(
title="系统正在重启,请耐心等候!", userid=userid)) channel=channel,
source=source,
title="系统正在重启,请耐心等候!",
userid=userid,
save_history=False))
# 保存重启信息 # 保存重启信息
self.save_cache({ self.save_cache({
"channel": channel.value, "channel": channel.value,
@@ -180,9 +188,12 @@ class SystemChain(ChainBase):
""" """
查看当前版本远程版本 查看当前版本远程版本
""" """
self.post_message(Notification(channel=channel, source=source, self.post_message(Notification(
title=self.__get_version_message(), channel=channel,
userid=userid)) source=source,
title=self.__get_version_message(),
userid=userid,
save_history=False))
def restart_finish(self): def restart_finish(self):
""" """
@@ -202,9 +213,11 @@ class SystemChain(ChainBase):
# 版本号 # 版本号
title = self.__get_version_message() title = self.__get_version_message()
self.post_message(Notification(channel=channel, self.post_message(Notification(
title=f"系统已重启完成!\n{title}", channel=channel,
userid=userid)) title=f"系统已重启完成!\n{title}",
userid=userid,
save_history=False))
self.remove_cache(self._restart_file) self.remove_cache(self._restart_file)
@staticmethod @staticmethod
+14 -6
View File
@@ -2,6 +2,8 @@ import re
import traceback import traceback
from typing import Dict, List, Union, Optional from typing import Dict, List, Union, Optional
from app.helper.sites import SitesHelper # noqa
from app.chain import ChainBase from app.chain import ChainBase
from app.chain.media import MediaChain from app.chain.media import MediaChain
from app.core.config import settings, global_vars from app.core.config import settings, global_vars
@@ -10,7 +12,6 @@ from app.core.metainfo import MetaInfo
from app.db.site_oper import SiteOper from app.db.site_oper import SiteOper
from app.db.systemconfig_oper import SystemConfigOper from app.db.systemconfig_oper import SystemConfigOper
from app.helper.rss import RssHelper from app.helper.rss import RssHelper
from app.helper.sites import SitesHelper # noqa
from app.helper.torrent import TorrentHelper from app.helper.torrent import TorrentHelper
from app.log import logger from app.log import logger
from app.schemas import Notification from app.schemas import Notification
@@ -39,11 +40,17 @@ class TorrentsChain(ChainBase):
""" """
远程刷新订阅发送消息 远程刷新订阅发送消息
""" """
self.post_message(Notification(channel=channel, self.post_message(Notification(
title=f"开始刷新种子 ...", userid=userid)) channel=channel,
title=f"开始刷新种子 ...",
userid=userid,
save_history=False))
self.refresh() self.refresh()
self.post_message(Notification(channel=channel, self.post_message(Notification(
title=f"种子刷新完成!", userid=userid)) channel=channel,
title=f"种子刷新完成!",
userid=userid,
save_history=False))
def get_torrents(self, stype: Optional[str] = None) -> Dict[str, List[Context]]: def get_torrents(self, stype: Optional[str] = None) -> Dict[str, List[Context]]:
""" """
@@ -150,7 +157,8 @@ class TorrentsChain(ChainBase):
return [] return []
# 解析RSS # 解析RSS
rss_items = RssHelper().parse(site.get("rss"), True if site.get("proxy") else False, rss_items = RssHelper().parse(site.get("rss"), True if site.get("proxy") else False,
timeout=int(site.get("timeout") or 30), ua=site.get("ua") if site.get("ua") else None) timeout=int(site.get("timeout") or 30),
ua=site.get("ua") if site.get("ua") else None)
if rss_items is None: if rss_items is None:
# rss过期,尝试保留原配置生成新的rss # rss过期,尝试保留原配置生成新的rss
self.__renew_rss_url(domain=domain, site=site) self.__renew_rss_url(domain=domain, site=site)
+3
View File
@@ -2945,6 +2945,7 @@ class TransferChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
title="请输入正确的命令格式:/redo [id] 或 /redo [id] [tmdbid/豆瓣id]|[类型]" title="请输入正确的命令格式:/redo [id] 或 /redo [id] [tmdbid/豆瓣id]|[类型]"
"[id] 为整理记录编号", "[id] 为整理记录编号",
userid=userid, userid=userid,
save_history=False,
) )
) )
@@ -2971,6 +2972,7 @@ class TransferChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
text=errmsg, text=errmsg,
userid=userid, userid=userid,
link=settings.MP_DOMAIN("#/history"), link=settings.MP_DOMAIN("#/history"),
save_history=False,
) )
) )
return return
@@ -2997,6 +2999,7 @@ class TransferChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
text=errmsg, text=errmsg,
userid=userid, userid=userid,
link=settings.MP_DOMAIN("#/history"), link=settings.MP_DOMAIN("#/history"),
save_history=False,
) )
) )
return return
+1
View File
@@ -218,6 +218,7 @@ def update_or_post_message(
title=title, title=title,
text=text, text=text,
buttons=buttons, buttons=buttons,
save_history=False,
) )
) )
+2
View File
@@ -223,6 +223,8 @@ class Notification(BaseModel):
original_chat_id: Optional[str] = None original_chat_id: Optional[str] = None
# 是否禁用链接预览(仅Telegram支持) # 是否禁用链接预览(仅Telegram支持)
disable_web_page_preview: Optional[bool] = None disable_web_page_preview: Optional[bool] = None
# 是否写入消息历史
save_history: bool = True
def to_dict(self): def to_dict(self):
""" """
+3
View File
@@ -224,6 +224,7 @@ def test_media_interaction_starts_search_and_posts_media_list():
assert handled assert handled
post_medias_message.assert_called_once() post_medias_message.assert_called_once()
notification = post_medias_message.call_args.args[0] notification = post_medias_message.call_args.args[0]
assert notification.save_history is False
assert notification.buttons assert notification.buttons
assert notification.buttons[0][0]["callback_data"].startswith("media:") assert notification.buttons[0][0]["callback_data"].startswith("media:")
@@ -306,6 +307,7 @@ def test_torrent_selection_prompts_download_dir_buttons_before_download():
assert request.phase == "download-dir" assert request.phase == "download-dir"
post_message.assert_called_once() post_message.assert_called_once()
notification = post_message.call_args.args[0] notification = post_message.call_args.args[0]
assert notification.save_history is False
assert "请选择下载目录" in notification.title assert "请选择下载目录" in notification.title
assert "电影下载 (/downloads/movies)" in notification.text assert "电影下载 (/downloads/movies)" in notification.text
assert notification.buttons[0][0]["callback_data"] == f"media:{request.request_id}:download-dir:1" assert notification.buttons[0][0]["callback_data"] == f"media:{request.request_id}:download-dir:1"
@@ -342,6 +344,7 @@ def test_torrent_selection_prompts_text_download_dir_for_plain_channel():
assert handled assert handled
notification = post_message.call_args.args[0] notification = post_message.call_args.args[0]
assert notification.save_history is False
assert "请回复对应数字" in notification.title assert "请回复对应数字" in notification.title
assert notification.buttons is None assert notification.buttons is None
assert "2. 动画下载 (rclone:/media/anime)" in notification.text assert "2. 动画下载 (rclone:/media/anime)" in notification.text
+59 -1
View File
@@ -5,9 +5,11 @@ from app.db import SessionFactory
from app.db.message_oper import MessageOper from app.db.message_oper import MessageOper
from app.db.models.message import Message from app.db.models.message import Message
from app.chain import ChainBase from app.chain import ChainBase
from app.core.context import Context, MediaInfo, TorrentInfo
from app.core.meta import MetaBase
from app.helper.message import MessageHelper from app.helper.message import MessageHelper
from app.schemas import Notification from app.schemas import Notification
from app.schemas.types import NotificationType from app.schemas.types import MediaType, NotificationType
def _clear_messages() -> None: def _clear_messages() -> None:
@@ -167,3 +169,59 @@ def test_agent_notification_post_message_is_persisted_without_sse_queue() -> Non
assert messages[0].mtype == NotificationType.Agent.value assert messages[0].mtype == NotificationType.Agent.value
assert helper.get() is None assert helper.get() is None
chain.messagequeue.send_message.assert_called_once() chain.messagequeue.send_message.assert_called_once()
def test_transient_notification_post_message_skips_history_but_dispatches() -> None:
"""
标记为不保存历史的过程消息应跳过数据库登记但仍正常派发
"""
_clear_messages()
chain = ChainBase()
chain.messagequeue.send_message = Mock()
chain.eventmanager.send_event = Mock()
chain.post_message(
Notification(
title="请选择下载目录",
text="1. 默认目录",
save_history=False,
)
)
assert MessageOper().list_by_page(page=1, count=10) == []
assert "save_history" not in chain.eventmanager.send_event.call_args.kwargs["data"]
chain.eventmanager.send_event.assert_called_once()
chain.messagequeue.send_message.assert_called_once()
def test_transient_media_and_torrent_lists_skip_history_but_dispatch() -> None:
"""
传统交互候选列表标记为不保存历史时只发送到渠道不写入消息表
"""
_clear_messages()
chain = ChainBase()
media = MediaInfo(type=MediaType.MOVIE, title="星际穿越", year="2014")
torrent = Context(
meta_info=MetaBase("星际穿越"),
media_info=media,
torrent_info=TorrentInfo(
title="星际穿越.2014.1080p",
site_name="TestSite",
enclosure="https://example.com/demo.torrent",
),
)
chain.messagequeue.send_message = Mock()
chain.post_medias_message(
Notification(title="请选择媒体", save_history=False),
medias=[media],
)
chain.post_torrents_message(
Notification(title="请选择资源", save_history=False),
torrents=[torrent],
)
assert MessageOper().list_by_page(page=1, count=10) == []
assert chain.messagequeue.send_message.call_count == 2