mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 00:16:57 +08:00
refactor: 收口 V3 分层架构与插件兼容边界
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
"""Agent 会话历史的查询、授权与删除应用服务。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional, Protocol
|
||||
|
||||
from app.schemas.agent import AgentChatSessionDetail, AgentChatSessionSummary
|
||||
|
||||
|
||||
class AgentChatPrincipal(Protocol):
|
||||
"""会话访问控制所需的最小用户身份。"""
|
||||
|
||||
id: Any
|
||||
name: Optional[str]
|
||||
is_superuser: bool
|
||||
|
||||
|
||||
class AsyncAgentChatRepository(Protocol):
|
||||
"""Agent 会话用例需要的最小异步持久化端口。"""
|
||||
|
||||
async def async_list_by_page(
|
||||
self,
|
||||
page: int = 1,
|
||||
count: int = 30,
|
||||
user_id: Optional[str] = None,
|
||||
username: Optional[str] = None,
|
||||
) -> list[Any]:
|
||||
"""分页读取用户可见的会话。"""
|
||||
...
|
||||
|
||||
async def async_get(
|
||||
self,
|
||||
session_id: str,
|
||||
user_id: Optional[str] = None,
|
||||
) -> Optional[Any]:
|
||||
"""按服务端会话 ID 读取记录。"""
|
||||
...
|
||||
|
||||
async def async_delete(
|
||||
self,
|
||||
session_id: str,
|
||||
user_id: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""删除指定服务端会话。"""
|
||||
...
|
||||
|
||||
def get(self, session_id: str, user_id: Optional[str] = None) -> Optional[Any]:
|
||||
"""同步读取服务端会话。"""
|
||||
...
|
||||
|
||||
def save_display_messages(
|
||||
self,
|
||||
session_id: str,
|
||||
user_id: Optional[str] = None,
|
||||
messages: Optional[list[dict]] = None,
|
||||
username: Optional[str] = None,
|
||||
channel: Optional[Any] = None,
|
||||
source: Optional[str] = None,
|
||||
original_chat_id: Optional[str] = None,
|
||||
client_session_id: Optional[str] = None,
|
||||
) -> Optional[Any]:
|
||||
"""同步保存用户可见会话消息。"""
|
||||
...
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AgentChatRecord:
|
||||
"""脱离 ORM 会话的 Agent 会话持久化投影。"""
|
||||
|
||||
id: Optional[int]
|
||||
session_id: str
|
||||
client_session_id: Optional[str]
|
||||
title: Optional[str]
|
||||
channel: Optional[str]
|
||||
source: Optional[str]
|
||||
user_id: Optional[str]
|
||||
username: Optional[str]
|
||||
original_chat_id: Optional[str]
|
||||
message_count: int
|
||||
created_at: Any
|
||||
updated_at: Any
|
||||
messages: list[dict]
|
||||
|
||||
|
||||
class AgentChatService:
|
||||
"""统一执行 Agent 会话查询、访问控制和删除。"""
|
||||
|
||||
def __init__(self, repository: AsyncAgentChatRepository) -> None:
|
||||
"""保存异步会话持久化端口。"""
|
||||
self._repository = repository
|
||||
|
||||
async def list(
|
||||
self,
|
||||
principal: AgentChatPrincipal,
|
||||
*,
|
||||
page: int = 1,
|
||||
count: int = 30,
|
||||
) -> list[AgentChatSessionSummary]:
|
||||
"""分页返回当前用户可见的会话摘要。"""
|
||||
user_id = None if principal.is_superuser else str(principal.id)
|
||||
username = None if principal.is_superuser else principal.name
|
||||
records = await self._repository.async_list_by_page(
|
||||
page=page,
|
||||
count=count,
|
||||
user_id=user_id,
|
||||
username=username,
|
||||
)
|
||||
return [self.to_summary(self._project(record)) for record in records]
|
||||
|
||||
async def get_accessible(
|
||||
self,
|
||||
session_id: str,
|
||||
principal: AgentChatPrincipal,
|
||||
) -> Optional[AgentChatRecord]:
|
||||
"""读取会话并在应用边界执行访问控制。"""
|
||||
projected = await self.get(session_id)
|
||||
if projected is None:
|
||||
return None
|
||||
if not self.can_access(projected, principal):
|
||||
return None
|
||||
return projected
|
||||
|
||||
async def get(self, session_id: str) -> Optional[AgentChatRecord]:
|
||||
"""读取不附带授权判断的会话投影。"""
|
||||
record = await self._repository.async_get(session_id=session_id)
|
||||
if record is None:
|
||||
return None
|
||||
return self._project(record)
|
||||
|
||||
async def delete(
|
||||
self,
|
||||
session_id: str,
|
||||
principal: AgentChatPrincipal,
|
||||
) -> bool:
|
||||
"""仅在当前用户可访问时删除会话。"""
|
||||
record = await self.get_accessible(session_id, principal)
|
||||
if record is None:
|
||||
return False
|
||||
return await self._repository.async_delete(session_id=session_id)
|
||||
|
||||
def get_sync(self, session_id: str) -> Optional[AgentChatRecord]:
|
||||
"""同步读取会话投影,供同步 Agent 编排路径使用。"""
|
||||
record = self._repository.get(session_id=session_id)
|
||||
return self._project(record) if record is not None else None
|
||||
|
||||
def save_display_sync(
|
||||
self,
|
||||
*,
|
||||
session_id: str,
|
||||
user_id: Optional[str] = None,
|
||||
messages: Optional[list[dict]] = None,
|
||||
username: Optional[str] = None,
|
||||
channel: Optional[Any] = None,
|
||||
source: Optional[str] = None,
|
||||
original_chat_id: Optional[str] = None,
|
||||
client_session_id: Optional[str] = None,
|
||||
) -> Optional[AgentChatRecord]:
|
||||
"""同步保存用户可见消息并返回最新投影。"""
|
||||
record = self._repository.save_display_messages(
|
||||
session_id=session_id,
|
||||
user_id=user_id,
|
||||
messages=messages,
|
||||
username=username,
|
||||
channel=channel,
|
||||
source=source,
|
||||
original_chat_id=original_chat_id,
|
||||
client_session_id=client_session_id,
|
||||
)
|
||||
return self._project(record) if record is not None else None
|
||||
|
||||
@staticmethod
|
||||
def can_access(
|
||||
record: AgentChatRecord,
|
||||
principal: AgentChatPrincipal,
|
||||
) -> bool:
|
||||
"""判断用户是否拥有会话访问权。"""
|
||||
if principal.is_superuser:
|
||||
return True
|
||||
user_id = str(principal.id)
|
||||
username = str(principal.name or "")
|
||||
return record.user_id == user_id or (
|
||||
bool(username) and record.username == username
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def to_summary(record: AgentChatRecord) -> AgentChatSessionSummary:
|
||||
"""把持久化投影转换为会话摘要 DTO。"""
|
||||
return AgentChatSessionSummary(
|
||||
id=record.id,
|
||||
session_id=record.session_id,
|
||||
client_session_id=record.client_session_id,
|
||||
title=record.title,
|
||||
channel=record.channel,
|
||||
source=record.source,
|
||||
user_id=record.user_id,
|
||||
username=record.username,
|
||||
original_chat_id=record.original_chat_id,
|
||||
message_count=record.message_count,
|
||||
created_at=record.created_at,
|
||||
updated_at=record.updated_at,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def to_detail(cls, record: AgentChatRecord) -> AgentChatSessionDetail:
|
||||
"""把持久化投影转换为会话详情 DTO。"""
|
||||
return AgentChatSessionDetail(
|
||||
**cls.to_summary(record).model_dump(),
|
||||
messages=record.messages,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _project(record: Any) -> AgentChatRecord:
|
||||
"""立即复制 ORM 字段,避免对象越过请求级会话边界。"""
|
||||
return AgentChatRecord(
|
||||
id=record.id,
|
||||
session_id=record.session_id,
|
||||
client_session_id=record.client_session_id,
|
||||
title=record.title,
|
||||
channel=record.channel,
|
||||
source=record.source,
|
||||
user_id=record.user_id,
|
||||
username=record.username,
|
||||
original_chat_id=record.original_chat_id,
|
||||
message_count=record.message_count or 0,
|
||||
created_at=record.created_at,
|
||||
updated_at=record.updated_at,
|
||||
messages=list(record.display_messages or []),
|
||||
)
|
||||
|
||||
|
||||
_configured_agent_chat_service: AgentChatService | None = None
|
||||
|
||||
|
||||
def configure_agent_chat_service(service: AgentChatService) -> None:
|
||||
"""由启动组合根登记同步 Agent 会话服务。"""
|
||||
global _configured_agent_chat_service
|
||||
_configured_agent_chat_service = service
|
||||
|
||||
|
||||
def get_configured_agent_chat_service() -> AgentChatService:
|
||||
"""返回启动阶段登记的 Agent 会话服务。"""
|
||||
if _configured_agent_chat_service is None:
|
||||
raise RuntimeError("Agent 会话服务尚未配置")
|
||||
return _configured_agent_chat_service
|
||||
@@ -8,7 +8,7 @@ import re
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any, Literal, Optional, List, Dict, Union
|
||||
from typing import Any, Literal, Optional, List, Dict, Protocol, Union
|
||||
from typing import Callable
|
||||
|
||||
from jinja2 import Template
|
||||
@@ -18,7 +18,7 @@ from app.runtime.config import global_vars
|
||||
from app.domain.context import MediaInfo, MusicInfo, TorrentInfo
|
||||
from app.domain.meta.metabase import MetaBase
|
||||
from app.domain.meta.metamusic import MetaMusic
|
||||
from app.db.oper.systemconfig import SystemConfigOper
|
||||
from app.application.configuration import get_configured_system_config
|
||||
from app.runtime.log import logger
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.tmdb import TmdbEpisode
|
||||
@@ -29,6 +29,64 @@ from app.foundation import size as size_tools
|
||||
from app.foundation.crypto import HashUtils
|
||||
|
||||
|
||||
class AsyncMessageQueryRepository(Protocol):
|
||||
"""消息查询用例依赖的异步持久化端口。"""
|
||||
|
||||
async def async_list_by_page(
|
||||
self, page: int = 1, count: int = 30
|
||||
) -> list[Any]:
|
||||
"""分页读取 Web 消息。"""
|
||||
...
|
||||
|
||||
async def async_list_sent_by_page(
|
||||
self,
|
||||
page: int = 1,
|
||||
count: int = 30,
|
||||
all_clear_before: Optional[str] = None,
|
||||
system_clear_before: Optional[str] = None,
|
||||
media_clear_before: Optional[str] = None,
|
||||
) -> list[Any]:
|
||||
"""分页读取清理水位之后的通知消息。"""
|
||||
...
|
||||
|
||||
|
||||
class MessageQueryService:
|
||||
"""封装消息历史读取与持久化对象投影。"""
|
||||
|
||||
def __init__(self, repository: AsyncMessageQueryRepository):
|
||||
"""使用显式消息查询端口初始化服务。"""
|
||||
self._repository = repository
|
||||
|
||||
async def list_web(self, page: int = 1, count: int = 20) -> list[dict[str, Any]]:
|
||||
"""分页返回可由 API schema 消费的 Web 消息字典。"""
|
||||
messages = await self._repository.async_list_by_page(page=page, count=count)
|
||||
result: list[dict[str, Any]] = []
|
||||
for message in messages:
|
||||
try:
|
||||
result.append(message.to_dict())
|
||||
except Exception as error:
|
||||
logger.error(f"获取WEB消息列表失败: {str(error)}")
|
||||
return result
|
||||
|
||||
async def list_notifications(
|
||||
self,
|
||||
page: int = 1,
|
||||
count: int = 20,
|
||||
all_clear_before: Optional[str] = None,
|
||||
system_clear_before: Optional[str] = None,
|
||||
media_clear_before: Optional[str] = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""分页返回清理水位之后的通知消息字典。"""
|
||||
messages = await self._repository.async_list_sent_by_page(
|
||||
page=page,
|
||||
count=count,
|
||||
all_clear_before=all_clear_before,
|
||||
system_clear_before=system_clear_before,
|
||||
media_clear_before=media_clear_before,
|
||||
)
|
||||
return [message.to_dict() for message in messages]
|
||||
|
||||
|
||||
class TemplateContextBuilder:
|
||||
"""
|
||||
模板上下文构建器。
|
||||
@@ -722,7 +780,7 @@ class MessageTemplateHelper:
|
||||
获取消息模板
|
||||
"""
|
||||
try:
|
||||
template_dict = SystemConfigOper().get(SystemConfigKey.NotificationTemplates) or {}
|
||||
template_dict = get_configured_system_config().get(SystemConfigKey.NotificationTemplates) or {}
|
||||
if isinstance(template_dict, dict):
|
||||
configured = template_dict.get(message.ctype.value)
|
||||
if str(configured or "").strip() not in {"", "{}", "{ }"}:
|
||||
@@ -766,7 +824,7 @@ class MessageQueueManager(metaclass=SingletonClass):
|
||||
初始化配置
|
||||
"""
|
||||
self.schedule_periods = self._parse_schedule(
|
||||
SystemConfigOper().get(SystemConfigKey.NotificationSendTime)
|
||||
get_configured_system_config().get(SystemConfigKey.NotificationSendTime)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import re
|
||||
from typing import Callable, List, Optional, Tuple, Union
|
||||
from typing import Any, Callable, List, Optional, Protocol, Tuple, Union
|
||||
|
||||
from app.db.models.site import Site
|
||||
from app.db.oper.site import SiteOper
|
||||
from app.domain import site as site_rules
|
||||
from app.application.messaging.interaction import (
|
||||
MessageGateway,
|
||||
@@ -22,6 +20,19 @@ from app.schemas.types import NotificationChannel
|
||||
site_interaction_manager = SlashInteractionManager()
|
||||
|
||||
|
||||
class SiteInteractionRepository(Protocol):
|
||||
"""站点消息交互所需的同步数据端口。"""
|
||||
|
||||
def list(self) -> List[Any]:
|
||||
"""返回站点列表。"""
|
||||
|
||||
def get(self, site_id: int) -> Optional[Any]:
|
||||
"""按 ID 返回站点。"""
|
||||
|
||||
def update(self, site_id: int, payload: dict) -> Optional[Any]:
|
||||
"""更新站点。"""
|
||||
|
||||
|
||||
class SiteInteractionHandler:
|
||||
"""
|
||||
管理 /sites 交互会话、输入解析和站点列表渲染。
|
||||
@@ -34,12 +45,14 @@ class SiteInteractionHandler:
|
||||
self,
|
||||
messenger: MessageGateway,
|
||||
cookie_updater: Callable[..., Tuple[bool, str]],
|
||||
repository: SiteInteractionRepository,
|
||||
):
|
||||
"""
|
||||
注入消息投递接口和站点 Cookie 更新动作。
|
||||
"""
|
||||
self._messenger = messenger
|
||||
self._cookie_updater = cookie_updater
|
||||
self._repository = repository
|
||||
|
||||
def remote_list(
|
||||
self,
|
||||
@@ -400,7 +413,7 @@ class SiteInteractionHandler:
|
||||
"""
|
||||
渲染 /sites 当前页面。
|
||||
"""
|
||||
site_list = SiteOper().list()
|
||||
site_list = self._repository.list()
|
||||
page_size = self._button_page_size if supports_interaction_buttons(channel) else self._text_page_size
|
||||
page_sites, page, total_pages = page_items(site_list, request.page, page_size)
|
||||
request.page = page
|
||||
@@ -463,7 +476,7 @@ class SiteInteractionHandler:
|
||||
|
||||
@staticmethod
|
||||
def _format_site_list(
|
||||
site_list: List[Site], channel: Optional[NotificationChannel]
|
||||
site_list: List[Any], channel: Optional[NotificationChannel]
|
||||
) -> str:
|
||||
"""
|
||||
根据渠道能力格式化站点列表。
|
||||
@@ -537,15 +550,14 @@ class SiteInteractionHandler:
|
||||
if not site_ids:
|
||||
return False, "请输入至少一个有效的站点 ID"
|
||||
|
||||
siteoper = SiteOper()
|
||||
changed = []
|
||||
missing = []
|
||||
for site_id in site_ids:
|
||||
site = siteoper.get(site_id)
|
||||
site = self._repository.get(site_id)
|
||||
if not site:
|
||||
missing.append(str(site_id))
|
||||
continue
|
||||
siteoper.update(site_id, {"is_active": enabled})
|
||||
self._repository.update(site_id, {"is_active": enabled})
|
||||
changed.append(site.name)
|
||||
|
||||
action = "启用" if enabled else "禁用"
|
||||
@@ -571,7 +583,7 @@ class SiteInteractionHandler:
|
||||
)
|
||||
|
||||
site_id = int(args[0])
|
||||
site_info = SiteOper().get(site_id)
|
||||
site_info = self._repository.get(site_id)
|
||||
if not site_info:
|
||||
return False, f"站点编号 {site_id} 不存在"
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@ import uuid
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Lock
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
from typing import Any, Callable, Dict, List, Optional, Protocol, Tuple, Union
|
||||
|
||||
from app.agent.skills.registry import SkillHelper, SkillInfo
|
||||
from app.application.messaging.interaction import (
|
||||
MessageGateway,
|
||||
build_navigation_buttons,
|
||||
@@ -17,6 +16,54 @@ from app.schemas.message import Message
|
||||
from app.schemas.types import NotificationChannel
|
||||
|
||||
|
||||
class SkillCatalogPort(Protocol):
|
||||
"""消息层使用的技能目录能力端口,由启动组合根注入具体实现。"""
|
||||
|
||||
def add_custom_market_source(self, source: str) -> Tuple[bool, str]:
|
||||
"""添加一个自定义技能市场来源。"""
|
||||
|
||||
def remove_custom_market_source(self, source: str) -> Tuple[bool, str]:
|
||||
"""移除一个自定义技能市场来源。"""
|
||||
|
||||
def install_market_skill(self, skill: Any) -> Tuple[bool, str]:
|
||||
"""安装指定的市场技能。"""
|
||||
|
||||
def list_local_skills(self) -> List[Any]:
|
||||
"""列出本地技能。"""
|
||||
|
||||
def remove_local_skill(self, skill_id: str) -> Tuple[bool, str]:
|
||||
"""移除指定的本地技能。"""
|
||||
|
||||
def list_market_source_entries(self) -> List[Any]:
|
||||
"""列出技能市场来源。"""
|
||||
|
||||
def list_market_skills(self, force: bool = False) -> List[Any]:
|
||||
"""列出市场技能。"""
|
||||
|
||||
def filter_market_skills(self, skills: List[Any], query: str) -> List[Any]:
|
||||
"""按查询词过滤市场技能。"""
|
||||
|
||||
|
||||
SkillCatalogProvider = Callable[[], SkillCatalogPort]
|
||||
_skill_catalog_provider: Optional[SkillCatalogProvider] = None
|
||||
|
||||
|
||||
def register_skill_catalog_provider(provider: SkillCatalogProvider) -> None:
|
||||
"""由启动组合根注册 Agent 技能目录实现,避免消息层依赖 Agent 具体模块。"""
|
||||
global _skill_catalog_provider
|
||||
_skill_catalog_provider = provider
|
||||
|
||||
|
||||
def _resolve_skill_catalog() -> SkillCatalogPort:
|
||||
"""解析已注入的技能目录;缺少组合根装配时给出明确错误。"""
|
||||
if _skill_catalog_provider is None:
|
||||
raise RuntimeError(
|
||||
"技能目录服务未注册:请先导入 app.startup.agent_initializer "
|
||||
"完成组合根装配"
|
||||
)
|
||||
return _skill_catalog_provider()
|
||||
|
||||
|
||||
@dataclass
|
||||
class PendingSkillInteraction:
|
||||
"""
|
||||
@@ -152,11 +199,12 @@ class SkillInteractionHandler:
|
||||
def __init__(
|
||||
self,
|
||||
messenger: MessageGateway,
|
||||
skill_helper: Optional[SkillHelper] = None,
|
||||
skill_catalog: Optional[SkillCatalogPort] = None,
|
||||
skill_helper: Optional[SkillCatalogPort] = None,
|
||||
):
|
||||
"""注入消息接口和技能管理能力。"""
|
||||
"""注入消息接口和技能目录端口,保留旧 ``skill_helper`` 关键字。"""
|
||||
self._messenger = messenger
|
||||
self.skillhelper = skill_helper or SkillHelper()
|
||||
self.skillhelper = skill_catalog or skill_helper or _resolve_skill_catalog()
|
||||
|
||||
def remote_manage(
|
||||
self,
|
||||
@@ -1059,10 +1107,10 @@ class SkillInteractionHandler:
|
||||
|
||||
@staticmethod
|
||||
def _page_items(
|
||||
items: List[SkillInfo],
|
||||
items: List[Any],
|
||||
page: int,
|
||||
page_size: int,
|
||||
) -> Tuple[List[SkillInfo], int, int]:
|
||||
) -> Tuple[List[Any], int, int]:
|
||||
"""
|
||||
返回当前页的数据,并把页码钳制到有效范围内。
|
||||
"""
|
||||
@@ -1146,7 +1194,7 @@ class SkillInteractionHandler:
|
||||
self,
|
||||
request: PendingSkillInteraction,
|
||||
force_market_refresh: bool = False,
|
||||
) -> List[SkillInfo]:
|
||||
) -> List[Any]:
|
||||
"""
|
||||
获取当前 /skills 会话可见的市场技能,并应用搜索词过滤。
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import re
|
||||
from typing import List, Optional, Protocol, Tuple, Union
|
||||
from typing import Any, Callable, List, Optional, Protocol, Tuple, Union
|
||||
|
||||
from app.adapters.external.server import MoviePilotServerHelper
|
||||
from app.application.messaging.interaction import (
|
||||
MessageGateway,
|
||||
SlashInteractionManager,
|
||||
@@ -12,8 +11,6 @@ from app.application.messaging.interaction import (
|
||||
supports_markdown,
|
||||
update_or_post_message,
|
||||
)
|
||||
from app.db.models.subscribe import Subscribe
|
||||
from app.db.oper.subscribe import SubscribeOper
|
||||
from app.schemas.message import Message
|
||||
from app.schemas.types import NotificationChannel, MediaType
|
||||
|
||||
@@ -30,6 +27,19 @@ class SubscribeInteractionActions(Protocol):
|
||||
"""执行订阅刷新。"""
|
||||
...
|
||||
|
||||
|
||||
class SubscribeInteractionRepository(Protocol):
|
||||
"""订阅消息交互所需的同步数据端口。"""
|
||||
|
||||
def list(self) -> List[Any]:
|
||||
"""返回订阅列表。"""
|
||||
|
||||
def get(self, subscribe_id: int) -> Optional[Any]:
|
||||
"""按 ID 返回订阅。"""
|
||||
|
||||
def delete(self, subscribe_id: int) -> Any:
|
||||
"""删除订阅。"""
|
||||
|
||||
def check(self):
|
||||
"""执行订阅元数据检查。"""
|
||||
...
|
||||
@@ -51,12 +61,16 @@ class SubscribeInteractionHandler:
|
||||
self,
|
||||
messenger: MessageGateway,
|
||||
actions: SubscribeInteractionActions,
|
||||
repository: SubscribeInteractionRepository,
|
||||
report_deleted: Callable[[dict], Any],
|
||||
):
|
||||
"""
|
||||
注入消息投递接口和订阅业务动作。
|
||||
"""
|
||||
self._messenger = messenger
|
||||
self._actions = actions
|
||||
self._repository = repository
|
||||
self._report_deleted = report_deleted
|
||||
|
||||
def remote_list(
|
||||
self,
|
||||
@@ -401,7 +415,7 @@ class SubscribeInteractionHandler:
|
||||
"""
|
||||
渲染 /subscribes 当前页面。
|
||||
"""
|
||||
subscribes = SubscribeOper().list()
|
||||
subscribes = self._repository.list()
|
||||
page_size = (
|
||||
self._button_page_size
|
||||
if supports_interaction_buttons(channel)
|
||||
@@ -475,7 +489,7 @@ class SubscribeInteractionHandler:
|
||||
)
|
||||
|
||||
def _format_subscribe_list(
|
||||
self, subscribes: List[Subscribe], channel: Optional[NotificationChannel]
|
||||
self, subscribes: List[Any], channel: Optional[NotificationChannel]
|
||||
) -> str:
|
||||
"""
|
||||
根据渠道能力格式化订阅列表。
|
||||
@@ -521,7 +535,7 @@ class SubscribeInteractionHandler:
|
||||
return mapping.get(state or "", state or "-")
|
||||
|
||||
@staticmethod
|
||||
def _format_subscribe_progress(subscribe: Subscribe) -> str:
|
||||
def _format_subscribe_progress(subscribe: Any) -> str:
|
||||
"""
|
||||
构造订阅的季和进度说明。
|
||||
"""
|
||||
@@ -658,11 +672,10 @@ class SubscribeInteractionHandler:
|
||||
if not subscribe_ids:
|
||||
return False, "请输入订阅 ID,多个 ID 用空格分隔,或输入 all"
|
||||
|
||||
subscribeoper = SubscribeOper()
|
||||
missing = []
|
||||
searched = []
|
||||
for subscribe_id in subscribe_ids:
|
||||
subscribe = subscribeoper.get(subscribe_id)
|
||||
subscribe = self._repository.get(subscribe_id)
|
||||
if not subscribe:
|
||||
missing.append(str(subscribe_id))
|
||||
continue
|
||||
@@ -696,17 +709,16 @@ class SubscribeInteractionHandler:
|
||||
if not subscribe_ids:
|
||||
return False, "请输入至少一个有效的订阅 ID"
|
||||
|
||||
subscribeoper = SubscribeOper()
|
||||
deleted = []
|
||||
missing = []
|
||||
for subscribe_id in subscribe_ids:
|
||||
subscribe = subscribeoper.get(subscribe_id)
|
||||
subscribe = self._repository.get(subscribe_id)
|
||||
if not subscribe:
|
||||
missing.append(str(subscribe_id))
|
||||
continue
|
||||
deleted.append(subscribe.name)
|
||||
subscribeoper.delete(subscribe_id)
|
||||
MoviePilotServerHelper.sub_done_async(
|
||||
self._repository.delete(subscribe_id)
|
||||
self._report_deleted(
|
||||
{
|
||||
"media_source": subscribe.media_source,
|
||||
"media_id": subscribe.media_id,
|
||||
|
||||
Reference in New Issue
Block a user