mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-04 23:17:20 +08:00
refactor(api): standardize responses and restore media config contracts
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
from .agent import *
|
||||
from .cache import *
|
||||
from .category import *
|
||||
from .common import *
|
||||
from .context import *
|
||||
from .dashboard import *
|
||||
from .download import *
|
||||
@@ -6,13 +9,18 @@ from .event import *
|
||||
from .exception import *
|
||||
from .file import *
|
||||
from .history import *
|
||||
from .llm import *
|
||||
from .mediaserver import *
|
||||
from .message import *
|
||||
from .mfa import *
|
||||
from .music import *
|
||||
from .monitoring import *
|
||||
from .notification import *
|
||||
from .plugin import *
|
||||
from .response import *
|
||||
from .rule import *
|
||||
from .search import *
|
||||
from .storage import *
|
||||
from .openai import *
|
||||
from .servarr import *
|
||||
from .servcookie import *
|
||||
|
||||
+86
-3
@@ -6,6 +6,8 @@ from typing import Any, List, Literal, Optional, Union
|
||||
from langchain_core.messages import BaseMessage
|
||||
from pydantic import BaseModel, Field, ConfigDict, field_serializer
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
|
||||
class ConversationMemory(BaseModel):
|
||||
"""对话记忆模型"""
|
||||
@@ -97,7 +99,7 @@ class AgentMcpServerToolInfo(BaseModel):
|
||||
name: str = Field(..., description="原始 MCP 工具名称")
|
||||
agent_tool_name: str = Field(..., description="注入 Agent 后的工具名称")
|
||||
description: str = Field(default="", description="工具说明")
|
||||
input_schema: dict[str, Any] = Field(default_factory=dict, description="工具参数 Schema")
|
||||
input_schema: dict[str, JsonData] = Field(default_factory=dict, description="工具参数 Schema")
|
||||
|
||||
|
||||
class AgentMcpServerTestResult(BaseModel):
|
||||
@@ -217,8 +219,8 @@ class AgentChatSession(BaseModel):
|
||||
username: Optional[str] = Field(None, description="用户名")
|
||||
original_chat_id: Optional[str] = Field(None, description="原聊天 ID")
|
||||
message_count: int = Field(default=0, description="展示消息数量")
|
||||
created_at: Optional[str] = Field(None, description="创建时间")
|
||||
updated_at: Optional[str] = Field(None, description="更新时间")
|
||||
created_at: Optional[datetime | str] = Field(None, description="创建时间")
|
||||
updated_at: Optional[datetime | str] = Field(None, description="更新时间")
|
||||
messages: list[AgentChatMessage] = Field(default_factory=list, description="展示消息列表")
|
||||
|
||||
|
||||
@@ -229,3 +231,84 @@ class AgentChatDisplaySaveRequest(BaseModel):
|
||||
|
||||
messages: list[AgentChatMessage] = Field(default_factory=list, description="展示消息列表")
|
||||
title: Optional[str] = Field(None, description="会话标题")
|
||||
|
||||
|
||||
class AgentMcpServerListData(BaseModel):
|
||||
"""Agent MCP 服务器列表与启用统计。"""
|
||||
|
||||
servers: list[AgentMcpServerConfig] = Field(default_factory=list, description="服务器列表")
|
||||
enabled_count: int = Field(default=0, description="已启用服务器数量")
|
||||
total_count: int = Field(default=0, description="服务器总数")
|
||||
|
||||
|
||||
class AgentChatUploadAttachment(AgentChatAttachment):
|
||||
"""Web Agent 上传完成后的附件描述。"""
|
||||
|
||||
ref: str = Field(description="供 Agent 消费的附件引用")
|
||||
status: str = Field(default="ready", description="附件处理状态")
|
||||
|
||||
|
||||
class AgentWebChoiceFeedback(BaseModel):
|
||||
"""Web Agent 选择回调的反馈快照。"""
|
||||
|
||||
request_id: str = Field(description="选择请求 ID")
|
||||
title: Optional[str] = Field(default=None, description="选择标题")
|
||||
prompt: str = Field(default="", description="选择提示")
|
||||
selected_label: str = Field(description="已选择文案")
|
||||
selected_value: str = Field(description="已选择值")
|
||||
selected_description: Optional[str] = Field(default=None, description="已选择说明")
|
||||
buttons: list[AgentChatChoiceButton] = Field(default_factory=list, description="按钮列表")
|
||||
button_rows: list[list[AgentChatChoiceButton]] = Field(default_factory=list, description="按钮行")
|
||||
|
||||
|
||||
class AgentWebCallbackData(BaseModel):
|
||||
"""Web Agent 按钮回调后供前端继续发送的数据。"""
|
||||
|
||||
message: str = Field(description="下一条用户消息")
|
||||
display_message: str = Field(default="", description="前端展示消息")
|
||||
session_id: Optional[str] = Field(default=None, description="Agent 会话 ID")
|
||||
traditional: bool = Field(default=False, description="是否为传统消息链回调")
|
||||
original_message_id: Optional[str | int] = Field(default=None, description="原消息 ID")
|
||||
original_chat_id: Optional[str | int] = Field(default=None, description="原聊天 ID")
|
||||
choice_selection: Optional[AgentChatChoiceSelection] = Field(default=None, description="选择结果快照")
|
||||
feedback: Optional[AgentWebChoiceFeedback] = Field(default=None, description="选择反馈")
|
||||
|
||||
|
||||
class AgentWebCommandInfo(BaseModel):
|
||||
"""Web Agent 可用斜杠命令摘要。"""
|
||||
|
||||
command: str = Field(description="命令")
|
||||
description: str = Field(default="", description="命令说明")
|
||||
category: str = Field(default="其他", description="命令分类")
|
||||
type: str = Field(default="", description="命令类型")
|
||||
pid: Optional[str | int] = Field(default=None, description="插件 ID")
|
||||
|
||||
|
||||
class AgentChatSessionSummary(BaseModel):
|
||||
"""Agent 历史会话摘要。"""
|
||||
|
||||
id: Optional[int] = Field(default=None, description="数据库 ID")
|
||||
session_id: str = Field(description="Agent 内部会话 ID")
|
||||
client_session_id: Optional[str] = Field(default=None, description="客户端会话 ID")
|
||||
title: Optional[str] = Field(default=None, description="会话标题")
|
||||
channel: Optional[str] = Field(default=None, description="消息渠道")
|
||||
source: Optional[str] = Field(default=None, description="渠道来源")
|
||||
user_id: Optional[str] = Field(default=None, description="用户 ID")
|
||||
username: Optional[str] = Field(default=None, description="用户名")
|
||||
original_chat_id: Optional[str] = Field(default=None, description="原聊天 ID")
|
||||
message_count: int = Field(default=0, description="展示消息数量")
|
||||
created_at: Optional[datetime | str] = Field(default=None, description="创建时间")
|
||||
updated_at: Optional[datetime | str] = Field(default=None, description="更新时间")
|
||||
|
||||
|
||||
class AgentChatSessionDetail(AgentChatSessionSummary):
|
||||
"""Agent 历史会话详情。"""
|
||||
|
||||
messages: list[AgentChatMessage] = Field(default_factory=list, description="展示消息列表")
|
||||
is_processing: bool = Field(default=False, description="会话是否正在处理")
|
||||
|
||||
|
||||
class AgentSessionStopData(BaseModel):
|
||||
"""Agent 会话停止结果。"""
|
||||
|
||||
stopped: bool = Field(description="是否停止了正在执行的任务")
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
"""种子缓存 API 输出模型。"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.schemas.types import MediaSource
|
||||
|
||||
|
||||
class TorrentCacheItem(BaseModel):
|
||||
"""单条站点种子缓存。"""
|
||||
|
||||
hash: str
|
||||
domain: str
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
size: Optional[int] = None
|
||||
pubdate: Optional[str] = None
|
||||
site_name: Optional[str] = None
|
||||
media_name: Optional[str] = None
|
||||
media_year: Optional[str | int] = None
|
||||
media_type: Optional[str] = None
|
||||
media_source: Optional[MediaSource] = None
|
||||
media_id: Optional[str] = None
|
||||
music_type: Optional[str] = None
|
||||
season_episode: Optional[str] = None
|
||||
resource_term: Optional[str] = None
|
||||
enclosure: Optional[str] = None
|
||||
page_url: Optional[str] = None
|
||||
poster_path: Optional[str] = None
|
||||
backdrop_path: Optional[str] = None
|
||||
|
||||
|
||||
class TorrentCacheData(BaseModel):
|
||||
"""种子缓存统计及明细。"""
|
||||
|
||||
count: int = 0
|
||||
sites: int = 0
|
||||
data: list[TorrentCacheItem] = Field(default_factory=list)
|
||||
|
||||
|
||||
class TorrentReidentifyData(BaseModel):
|
||||
"""种子重新识别后的媒体身份。"""
|
||||
|
||||
media_name: Optional[str] = None
|
||||
media_year: Optional[str | int] = None
|
||||
media_type: Optional[str] = None
|
||||
media_source: Optional[MediaSource] = None
|
||||
media_id: Optional[str] = None
|
||||
music_type: Optional[str] = None
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Dict, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from pydantic import BaseModel, ConfigDict, RootModel
|
||||
|
||||
|
||||
class CategoryRule(BaseModel):
|
||||
@@ -29,3 +29,7 @@ class CategoryConfig(BaseModel):
|
||||
movie: Optional[Dict[str, Optional[CategoryRule]]] = {}
|
||||
# 电视剧分类策略
|
||||
tv: Optional[Dict[str, Optional[CategoryRule]]] = {}
|
||||
|
||||
|
||||
class MediaCategoryMap(RootModel[Dict[str, list[str]]]):
|
||||
"""媒体类型与自动分类名称列表的映射。"""
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""API 端点共享的小型业务数据模型。"""
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field, RootModel
|
||||
from typing_extensions import TypeAliasType
|
||||
|
||||
|
||||
JsonData = TypeAliasType(
|
||||
"JsonData",
|
||||
Union[
|
||||
dict[str, "JsonData"],
|
||||
list["JsonData"],
|
||||
str,
|
||||
int,
|
||||
float,
|
||||
bool,
|
||||
None,
|
||||
],
|
||||
)
|
||||
"""可递归序列化的 JSON 数据;OpenAPI 会展示每一种合法 JSON 结构。"""
|
||||
|
||||
|
||||
class JsonObject(RootModel[dict[str, JsonData]]):
|
||||
"""字段由运行时扩展点决定的 JSON 对象。"""
|
||||
|
||||
|
||||
class JsonObjectList(RootModel[list[JsonObject]]):
|
||||
"""字段由运行时扩展点决定的 JSON 对象列表。"""
|
||||
|
||||
|
||||
class IdData(BaseModel):
|
||||
"""创建资源后返回的资源 ID。"""
|
||||
|
||||
id: Optional[int | str] = Field(default=None, description="资源 ID")
|
||||
|
||||
|
||||
class ValueData(BaseModel):
|
||||
"""单个动态配置值。"""
|
||||
|
||||
value: JsonData = Field(default=None, description="配置值")
|
||||
|
||||
|
||||
class FileNameData(BaseModel):
|
||||
"""文件操作结果中的文件名。"""
|
||||
|
||||
filename: Optional[str] = Field(default=None, description="文件名")
|
||||
|
||||
|
||||
class NameData(BaseModel):
|
||||
"""名称计算结果。"""
|
||||
|
||||
name: Optional[str] = Field(default=None, description="名称")
|
||||
|
||||
|
||||
class ServiceClientInfo(BaseModel):
|
||||
"""可选择的下载器或媒体服务器摘要。"""
|
||||
|
||||
name: Optional[str] = Field(default=None, description="实例名称")
|
||||
type: Optional[str] = Field(default=None, description="服务类型")
|
||||
|
||||
|
||||
class ProgressKeyData(BaseModel):
|
||||
"""异步任务进度查询标识。"""
|
||||
|
||||
progress_key: str = Field(description="进度查询标识")
|
||||
|
||||
|
||||
class BatchProgressKeyData(ProgressKeyData):
|
||||
"""批量异步任务进度查询标识。"""
|
||||
|
||||
history_ids: list[int] = Field(default_factory=list, description="历史记录 ID 列表")
|
||||
|
||||
|
||||
class TimeData(BaseModel):
|
||||
"""网络请求耗时。"""
|
||||
|
||||
time: int | float = Field(description="耗时毫秒数")
|
||||
+207
-40
@@ -1,7 +1,8 @@
|
||||
from typing import Optional, Dict, List, Union, Any
|
||||
from typing import Annotated, Optional, Dict, List, Union, Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Discriminator, Field, RootModel, Tag
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.music import MusicInfo, MusicMeta
|
||||
from app.schemas.media import OptionalMediaIdentityMixin
|
||||
from app.schemas.types import MediaSource
|
||||
@@ -73,6 +74,144 @@ class MetaInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
media_id: Optional[str] = None
|
||||
|
||||
|
||||
class MediaImageSet(BaseModel):
|
||||
"""跨媒体源兼容的人物图片尺寸集合。"""
|
||||
|
||||
large: Optional[str] = None
|
||||
common: Optional[str] = None
|
||||
medium: Optional[str] = None
|
||||
normal: Optional[str] = None
|
||||
small: Optional[str] = None
|
||||
grid: Optional[str] = None
|
||||
|
||||
|
||||
class MediaCredit(BaseModel):
|
||||
"""影视条目中的演职员摘要。"""
|
||||
|
||||
id: Optional[int | str] = None
|
||||
name: Optional[str] = None
|
||||
original_name: Optional[str] = None
|
||||
character: Optional[str] = None
|
||||
type: Optional[str | int] = None
|
||||
gender: Optional[str | int] = None
|
||||
adult: Optional[bool] = None
|
||||
known_for_department: Optional[str] = None
|
||||
profile_path: Optional[str] = None
|
||||
credit_id: Optional[str] = None
|
||||
cast_id: Optional[int] = None
|
||||
order: Optional[int] = None
|
||||
department: Optional[str] = None
|
||||
job: Optional[str] = None
|
||||
popularity: Optional[float] = None
|
||||
roles: list[str] = Field(default_factory=list)
|
||||
title: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
uri: Optional[str] = None
|
||||
sharing_url: Optional[str] = None
|
||||
avatar: Optional[str | MediaImageSet] = None
|
||||
images: Optional[MediaImageSet] = None
|
||||
latin_name: Optional[str] = None
|
||||
career: list[str] = Field(default_factory=list)
|
||||
relation: Optional[str] = None
|
||||
user: Optional[JsonData] = None
|
||||
|
||||
|
||||
class MediaGenre(BaseModel):
|
||||
"""影视风格摘要。"""
|
||||
|
||||
id: Optional[int | str] = None
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class MediaCompany(BaseModel):
|
||||
"""电视网或制作公司的标准摘要。"""
|
||||
|
||||
id: Optional[int | str] = None
|
||||
name: Optional[str] = None
|
||||
logo_path: Optional[str] = None
|
||||
origin_country: Optional[str] = None
|
||||
|
||||
|
||||
class MediaCountry(BaseModel):
|
||||
"""影视制作国家或地区。"""
|
||||
|
||||
id: Optional[int | str] = None
|
||||
iso_3166_1: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class MediaLanguage(BaseModel):
|
||||
"""影视内容使用的语言。"""
|
||||
|
||||
english_name: Optional[str] = None
|
||||
iso_639_1: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class MediaReleaseDate(BaseModel):
|
||||
"""电影在单个地区的一次发行记录。"""
|
||||
|
||||
date: str
|
||||
iso_code: Optional[str] = None
|
||||
note: Optional[str] = None
|
||||
type: Optional[int] = None
|
||||
|
||||
|
||||
class MediaEpisode(BaseModel):
|
||||
"""电视剧即将播出的单集摘要。"""
|
||||
|
||||
id: Optional[int] = None
|
||||
air_date: Optional[str] = None
|
||||
episode_number: Optional[int] = None
|
||||
episode_type: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
overview: Optional[str] = None
|
||||
production_code: Optional[str] = None
|
||||
runtime: Optional[int] = None
|
||||
season_number: Optional[int] = None
|
||||
show_id: Optional[int] = None
|
||||
still_path: Optional[str] = None
|
||||
vote_average: Optional[float] = None
|
||||
vote_count: Optional[int] = None
|
||||
|
||||
|
||||
class MediaSeason(BaseModel):
|
||||
"""标准季信息以及剧集组季信息。"""
|
||||
|
||||
id: Optional[int | str] = None
|
||||
air_date: Optional[str] = None
|
||||
episode_count: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
overview: Optional[str] = None
|
||||
poster_path: Optional[str] = None
|
||||
season_number: Optional[int] = None
|
||||
vote_average: Optional[float] = None
|
||||
order: Optional[int] = None
|
||||
locked: Optional[bool] = None
|
||||
episodes: list[MediaEpisode] = Field(default_factory=list)
|
||||
|
||||
|
||||
class MediaEpisodeGroupNetwork(BaseModel):
|
||||
"""TMDB 剧集组所属电视网信息。"""
|
||||
|
||||
id: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
logo_path: Optional[str] = None
|
||||
origin_country: Optional[str] = None
|
||||
|
||||
|
||||
class MediaEpisodeGroup(BaseModel):
|
||||
"""TMDB 电视剧的剧集分组摘要。"""
|
||||
|
||||
description: str = ""
|
||||
episode_count: int = 0
|
||||
group_count: int = 0
|
||||
id: str
|
||||
name: str
|
||||
network: Optional[MediaEpisodeGroupNetwork] = None
|
||||
type: int
|
||||
|
||||
|
||||
class MediaInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
"""
|
||||
识别媒体信息
|
||||
@@ -87,6 +226,10 @@ class MediaInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
title: Optional[str] = None
|
||||
# 英文标题
|
||||
en_title: Optional[str] = None
|
||||
# 香港、台湾、新加坡地区标题
|
||||
hk_title: Optional[str] = None
|
||||
tw_title: Optional[str] = None
|
||||
sg_title: Optional[str] = None
|
||||
# 年份
|
||||
year: Optional[str] = None
|
||||
# 标题(年份)
|
||||
@@ -116,6 +259,8 @@ class MediaInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
backdrop_path: Optional[str] = None
|
||||
# 海报图片
|
||||
poster_path: Optional[str] = None
|
||||
# 标题 LOGO
|
||||
logo_path: Optional[str] = None
|
||||
# 评分
|
||||
vote_average: Optional[float] = 0.0
|
||||
# 描述
|
||||
@@ -123,58 +268,60 @@ class MediaInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
# 二级分类
|
||||
category: Optional[str] = ""
|
||||
# 季季集清单
|
||||
seasons: Optional[Dict[int, list]] = Field(default_factory=dict)
|
||||
seasons: Optional[Dict[int, list[int]]] = Field(default_factory=dict)
|
||||
# 季详情
|
||||
season_info: Optional[List[dict]] = Field(default_factory=list)
|
||||
season_info: Optional[List[MediaSeason]] = Field(default_factory=list)
|
||||
# 各季首播年份
|
||||
season_years: Optional[Dict[int, str]] = Field(default_factory=dict)
|
||||
# 别名和译名
|
||||
names: Optional[list] = Field(default_factory=list)
|
||||
names: Optional[list[str]] = Field(default_factory=list)
|
||||
# 演员
|
||||
actors: Optional[list] = Field(default_factory=list)
|
||||
actors: Optional[list[MediaCredit]] = Field(default_factory=list)
|
||||
# 导演
|
||||
directors: Optional[list] = Field(default_factory=list)
|
||||
directors: Optional[list[MediaCredit]] = Field(default_factory=list)
|
||||
# 详情链接
|
||||
detail_link: Optional[str] = None
|
||||
# 其它TMDB属性
|
||||
# 是否成人内容
|
||||
adult: Optional[bool] = False
|
||||
# 创建人
|
||||
created_by: Optional[list] = Field(default_factory=list)
|
||||
created_by: Optional[list[MediaCredit]] = Field(default_factory=list)
|
||||
# 集时长
|
||||
episode_run_time: Optional[list] = Field(default_factory=list)
|
||||
episode_run_time: Optional[list[int]] = Field(default_factory=list)
|
||||
# 风格
|
||||
genres: Optional[List[dict]] = Field(default_factory=list)
|
||||
genres: Optional[List[MediaGenre]] = Field(default_factory=list)
|
||||
# 首播日期
|
||||
first_air_date: Optional[str] = None
|
||||
# 首页
|
||||
homepage: Optional[str] = None
|
||||
# 语种
|
||||
languages: Optional[list] = Field(default_factory=list)
|
||||
languages: Optional[list[str]] = Field(default_factory=list)
|
||||
# 最后上映日期
|
||||
last_air_date: Optional[str] = None
|
||||
# 流媒体平台
|
||||
networks: Optional[list] = Field(default_factory=list)
|
||||
networks: Optional[list[MediaCompany]] = Field(default_factory=list)
|
||||
# 集数
|
||||
number_of_episodes: Optional[int] = 0
|
||||
# 季数
|
||||
number_of_seasons: Optional[int] = 0
|
||||
# 原产国
|
||||
origin_country: Optional[list] = Field(default_factory=list)
|
||||
origin_country: Optional[list[str]] = Field(default_factory=list)
|
||||
# 原名
|
||||
original_name: Optional[str] = None
|
||||
# 出品公司
|
||||
production_companies: Optional[list] = Field(default_factory=list)
|
||||
production_companies: Optional[list[MediaCompany]] = Field(default_factory=list)
|
||||
# 出品国
|
||||
production_countries: Optional[list] = Field(default_factory=list)
|
||||
production_countries: Optional[list[MediaCountry]] = Field(default_factory=list)
|
||||
# 语种
|
||||
spoken_languages: Optional[list] = Field(default_factory=list)
|
||||
spoken_languages: Optional[list[MediaLanguage]] = Field(default_factory=list)
|
||||
# 所有发行日期
|
||||
release_dates: list = Field(default_factory=list)
|
||||
release_dates: list[MediaReleaseDate] = Field(default_factory=list)
|
||||
# 状态
|
||||
status: Optional[str] = None
|
||||
# 标签
|
||||
tagline: Optional[str] = None
|
||||
# 风格ID
|
||||
genre_ids: Optional[list] = Field(default_factory=list)
|
||||
genre_ids: Optional[list[int | str]] = Field(default_factory=list)
|
||||
# 评价数量
|
||||
vote_count: Optional[int] = 0
|
||||
# 流行度
|
||||
@@ -182,11 +329,18 @@ class MediaInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
# 时长
|
||||
runtime: Optional[int] = None
|
||||
# 下一集
|
||||
next_episode_to_air: Optional[dict] = Field(default_factory=dict)
|
||||
next_episode_to_air: Optional[MediaEpisode] = None
|
||||
# 内容分级
|
||||
content_rating: Optional[str] = None
|
||||
# 全部剧集组
|
||||
episode_groups: Optional[list] = Field(default_factory=list)
|
||||
episode_groups: Optional[list[MediaEpisodeGroup | MediaSeason]] = Field(default_factory=list)
|
||||
# 剧集组
|
||||
episode_group: Optional[str] = None
|
||||
# 各数据源原始信息;Core MediaInfo.to_dict() 保留这些键供兼容调用方使用。
|
||||
tmdb_info: Optional[dict[str, JsonData]] = None
|
||||
douban_info: Optional[dict[str, JsonData]] = None
|
||||
bangumi_info: Optional[dict[str, JsonData]] = None
|
||||
anilist_info: Optional[dict[str, JsonData]] = None
|
||||
|
||||
|
||||
class TorrentInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
@@ -239,7 +393,7 @@ class TorrentInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
# HR
|
||||
hit_and_run: Optional[bool] = False
|
||||
# 种子标签
|
||||
labels: Optional[list] = Field(default_factory=list)
|
||||
labels: Optional[list[str]] = Field(default_factory=list)
|
||||
# 种子优先级
|
||||
pri_order: Optional[int] = 0
|
||||
# 种子分类 电影/电视剧/音乐
|
||||
@@ -326,19 +480,6 @@ class Context(BaseModel):
|
||||
confirmed_full_coverage: Optional[bool] = False
|
||||
|
||||
|
||||
class MediaSeason(BaseModel):
|
||||
"""
|
||||
季信息
|
||||
"""
|
||||
air_date: Optional[str] = None
|
||||
episode_count: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
overview: Optional[str] = None
|
||||
poster_path: Optional[str] = None
|
||||
season_number: Optional[int] = None
|
||||
vote_average: Optional[float] = None
|
||||
|
||||
|
||||
class MediaPerson(BaseModel):
|
||||
"""
|
||||
媒体人物信息
|
||||
@@ -346,17 +487,17 @@ class MediaPerson(BaseModel):
|
||||
# 来源:themoviedb、douban、bangumi、anilist
|
||||
source: Optional[str] = None
|
||||
# 公共
|
||||
id: Optional[int] = None
|
||||
id: Optional[int | str] = None
|
||||
type: Optional[Union[str, int]] = 1
|
||||
name: Optional[str] = None
|
||||
character: Optional[str] = None
|
||||
images: Optional[dict] = Field(default_factory=dict)
|
||||
images: Optional[MediaImageSet] = None
|
||||
# themoviedb
|
||||
profile_path: Optional[str] = None
|
||||
gender: Optional[Union[str, int]] = None
|
||||
original_name: Optional[str] = None
|
||||
credit_id: Optional[str] = None
|
||||
also_known_as: Optional[list] = Field(default_factory=list)
|
||||
also_known_as: Optional[list[str]] = Field(default_factory=list)
|
||||
birthday: Optional[str] = None
|
||||
deathday: Optional[str] = None
|
||||
imdb_id: Optional[str] = None
|
||||
@@ -365,11 +506,37 @@ class MediaPerson(BaseModel):
|
||||
popularity: Optional[float] = None
|
||||
biography: Optional[str] = None
|
||||
# douban
|
||||
roles: Optional[list] = Field(default_factory=list)
|
||||
roles: Optional[list[str]] = Field(default_factory=list)
|
||||
title: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
avatar: Optional[Union[str, dict]] = None
|
||||
avatar: Optional[Union[str, MediaImageSet]] = None
|
||||
latin_name: Optional[str] = None
|
||||
# bangumi
|
||||
career: Optional[list] = Field(default_factory=list)
|
||||
career: Optional[list[str]] = Field(default_factory=list)
|
||||
relation: Optional[str] = None
|
||||
|
||||
|
||||
def _media_search_result_kind(value: Any) -> str:
|
||||
"""按稳定字段区分音乐、人物与影视/合集搜索结果。"""
|
||||
if isinstance(value, BaseModel):
|
||||
value = value.model_dump()
|
||||
if isinstance(value, dict):
|
||||
if value.get("type") == "音乐" or "music_type" in value:
|
||||
return "music"
|
||||
if "source" in value and "media_source" not in value:
|
||||
return "person"
|
||||
return "media"
|
||||
|
||||
|
||||
MediaSearchResult = Annotated[
|
||||
Union[
|
||||
Annotated[MusicInfo, Tag("music")],
|
||||
Annotated[MediaPerson, Tag("person")],
|
||||
Annotated[MediaInfo, Tag("media")],
|
||||
],
|
||||
Discriminator(_media_search_result_kind),
|
||||
]
|
||||
|
||||
|
||||
class MediaSearchResults(RootModel[List[MediaSearchResult]]):
|
||||
"""媒体、音乐、合集与人物的统一搜索结果列表。"""
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Optional
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
from app.helper.locale import LocaleHelper
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
|
||||
class Statistic(BaseModel):
|
||||
@@ -120,7 +121,7 @@ class ScheduleProgress(BaseModel):
|
||||
# 多语言错误信息
|
||||
error_i18n: Optional[str] = None
|
||||
# 扩展数据
|
||||
data: Optional[dict] = Field(default_factory=dict)
|
||||
data: Optional[dict[str, JsonData]] = Field(default_factory=dict)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def fill_i18n_fields(self) -> "ScheduleProgress":
|
||||
|
||||
@@ -25,3 +25,15 @@ class DownloadDirectory(BaseModel):
|
||||
priority: Optional[int] = Field(default=0, description="目录优先级")
|
||||
media_type: Optional[str] = Field(default=None, description="适用媒体类型")
|
||||
media_category: Optional[str] = Field(default=None, description="适用媒体分类")
|
||||
|
||||
|
||||
class DownloadAddedData(BaseModel):
|
||||
"""下载任务添加结果。"""
|
||||
|
||||
download_id: Optional[str] = Field(default=None, description="下载任务 ID")
|
||||
|
||||
|
||||
class SubtitleDownloadData(BaseModel):
|
||||
"""字幕下载结果。"""
|
||||
|
||||
files: list[str] = Field(default_factory=list, description="已保存字幕文件列表")
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import Iterable, Optional, Dict, Any, List, Set, Callable
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.message import MessageChannel
|
||||
from app.schemas.file import FileItem
|
||||
from app.schemas.media import OptionalMediaIdentityMixin, RequiredMediaIdentityMixin
|
||||
@@ -484,11 +485,11 @@ class DiscoverMediaSource(BaseModel):
|
||||
name: str = Field(..., description="数据源名称")
|
||||
media_source: MediaSource = Field(..., description="媒体来源枚举")
|
||||
api_path: str = Field(..., description="媒体数据源API地址")
|
||||
filter_params: Optional[Dict[str, Any]] = Field(
|
||||
filter_params: Optional[Dict[str, JsonData]] = Field(
|
||||
default=None, description="过滤参数"
|
||||
)
|
||||
filter_ui: Optional[List[dict]] = Field(default=[], description="过滤参数UI配置")
|
||||
depends: Optional[Dict[str, list]] = Field(
|
||||
filter_ui: Optional[List[Dict[str, JsonData]]] = Field(default=[], description="过滤参数UI配置")
|
||||
depends: Optional[Dict[str, list[str]]] = Field(
|
||||
default=None, description="UI依赖关系字典"
|
||||
)
|
||||
|
||||
|
||||
+10
-2
@@ -10,6 +10,8 @@ WINDOWS_DRIVE_PATTERN = re.compile(r"^[A-Za-z]:[\\/]")
|
||||
|
||||
|
||||
class FileURI(BaseModel):
|
||||
"""带存储类型的文件 URI。"""
|
||||
|
||||
# 文件路径
|
||||
path: Optional[str] = "/"
|
||||
# 存储类型
|
||||
@@ -45,6 +47,8 @@ class FileURI(BaseModel):
|
||||
|
||||
|
||||
class FileItem(FileURI):
|
||||
"""文件或目录条目,目录可递归包含子条目。"""
|
||||
|
||||
# 类型 dir/file
|
||||
type: Optional[str] = None
|
||||
# 文件名
|
||||
@@ -58,7 +62,7 @@ class FileItem(FileURI):
|
||||
# 修改时间
|
||||
modify_time: Optional[float] = None
|
||||
# 子节点
|
||||
children: Optional[list] = Field(default_factory=list)
|
||||
children: Optional[list["FileItem"]] = Field(default_factory=list)
|
||||
# ID
|
||||
fileid: Optional[str] = None
|
||||
# 父ID
|
||||
@@ -74,6 +78,8 @@ class FileItem(FileURI):
|
||||
|
||||
|
||||
class StorageUsage(BaseModel):
|
||||
"""存储空间使用情况。"""
|
||||
|
||||
# 总空间
|
||||
total: float = 0.0
|
||||
# 剩余空间
|
||||
@@ -81,5 +87,7 @@ class StorageUsage(BaseModel):
|
||||
|
||||
|
||||
class StorageTransType(BaseModel):
|
||||
"""存储支持的传输类型及其显示名称。"""
|
||||
|
||||
# 传输类型
|
||||
transtype: Optional[dict] = Field(default_factory=dict)
|
||||
transtype: Optional[dict[str, str]] = Field(default_factory=dict)
|
||||
|
||||
+12
-2
@@ -1,7 +1,8 @@
|
||||
from typing import Optional, Any
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.media import OptionalMediaIdentityMixin
|
||||
from app.schemas.types import MediaSource
|
||||
|
||||
@@ -52,7 +53,7 @@ class DownloadHistory(OptionalMediaIdentityMixin, BaseModel):
|
||||
# 创建时间
|
||||
date: Optional[str] = None
|
||||
# 备注
|
||||
note: Optional[Any] = None
|
||||
note: Optional[JsonData] = None
|
||||
# 自定义媒体类别
|
||||
media_category: Optional[str] = None
|
||||
# 自定义剧集组
|
||||
@@ -121,4 +122,13 @@ class TransferHistory(OptionalMediaIdentityMixin, BaseModel):
|
||||
|
||||
|
||||
class BatchTransferHistoryRedoRequest(BaseModel):
|
||||
"""批量重新整理历史请求。"""
|
||||
|
||||
history_ids: list[int] = Field(default_factory=list)
|
||||
|
||||
|
||||
class TransferHistoryPage(BaseModel):
|
||||
"""整理历史分页数据。"""
|
||||
|
||||
list: List[TransferHistory] = Field(default_factory=list, description="整理历史列表")
|
||||
total: int = Field(default=0, description="记录总数")
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""LLM 配置、目录和测试 API 输出模型。"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class LLMAuthStatus(BaseModel):
|
||||
"""LLM 提供商授权状态摘要。"""
|
||||
|
||||
connected: bool = False
|
||||
type: Optional[str] = None
|
||||
label: Optional[str] = None
|
||||
expires_at: Optional[int | float | str] = None
|
||||
updated_at: Optional[int | float | str] = None
|
||||
|
||||
|
||||
class LLMServerToolCapability(BaseModel):
|
||||
"""模型支持的服务端工具能力。"""
|
||||
|
||||
id: str
|
||||
required_api_protocol: Optional[str] = None
|
||||
client_adapter: Optional[str] = None
|
||||
|
||||
|
||||
class LLMModelInfo(BaseModel):
|
||||
"""标准化 LLM 模型目录项。"""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
family: Optional[str] = None
|
||||
context_tokens: Optional[int] = None
|
||||
input_tokens: Optional[int] = None
|
||||
output_tokens: Optional[int] = None
|
||||
context_tokens_k: Optional[int] = None
|
||||
supports_reasoning: bool = False
|
||||
supports_tools: bool = False
|
||||
supports_image_input: bool = False
|
||||
supports_audio_input: bool = False
|
||||
transport: Optional[str] = None
|
||||
source: Optional[str] = None
|
||||
release_date: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
server_tools: list[LLMServerToolCapability] = Field(default_factory=list)
|
||||
|
||||
|
||||
class LLMModelCatalogData(BaseModel):
|
||||
"""指定提供商的模型目录。"""
|
||||
|
||||
provider: str
|
||||
models: list[LLMModelInfo] = Field(default_factory=list)
|
||||
auth_status: LLMAuthStatus
|
||||
|
||||
|
||||
class LLMProviderAuthMethod(BaseModel):
|
||||
"""LLM 提供商可用的交互授权方式。"""
|
||||
|
||||
id: str
|
||||
type: str
|
||||
label: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class LLMProviderBaseUrlPreset(BaseModel):
|
||||
"""LLM 提供商预设基础地址。"""
|
||||
|
||||
id: str
|
||||
label: str
|
||||
value: str
|
||||
runtime: Optional[str] = None
|
||||
model_list_strategy: Optional[str] = None
|
||||
|
||||
|
||||
class LLMProviderInfo(BaseModel):
|
||||
"""前端可配置的 LLM 提供商定义。"""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
runtime: str
|
||||
default_base_url: str = ""
|
||||
base_url_presets: list[LLMProviderBaseUrlPreset] = Field(default_factory=list)
|
||||
base_url_editable: bool = True
|
||||
requires_base_url: bool = False
|
||||
supports_api_key: bool = True
|
||||
api_key_label: Optional[str] = None
|
||||
api_key_hint: Optional[str] = None
|
||||
supports_model_refresh: bool = True
|
||||
oauth_methods: list[LLMProviderAuthMethod] = Field(default_factory=list)
|
||||
description: Optional[str] = None
|
||||
auth_status: LLMAuthStatus
|
||||
|
||||
|
||||
class LLMProviderAuthSession(BaseModel):
|
||||
"""LLM 提供商交互授权会话。"""
|
||||
|
||||
session_id: str
|
||||
provider_id: Optional[str] = None
|
||||
flow_type: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
message: Optional[str] = None
|
||||
authorize_url: Optional[str] = None
|
||||
verification_url: Optional[str] = None
|
||||
user_code: Optional[str] = None
|
||||
instructions: Optional[str] = None
|
||||
interval_seconds: Optional[int] = None
|
||||
expires_at: Optional[int | float] = None
|
||||
|
||||
|
||||
class LLMTestResult(BaseModel):
|
||||
"""LLM 连通性测试结果。"""
|
||||
|
||||
provider: str
|
||||
model: str
|
||||
duration_ms: Optional[int] = None
|
||||
reply_preview: Optional[str] = None
|
||||
+185
-8
@@ -1,16 +1,193 @@
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Dict, Literal, Optional, TypeAlias, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, RootModel, TypeAdapter
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
|
||||
class ToolCallRequest(BaseModel):
|
||||
"""工具调用请求模型"""
|
||||
tool_name: str = Field(..., description="工具名称")
|
||||
arguments: Dict[str, Any] = Field(default_factory=dict, description="工具参数")
|
||||
arguments: Dict[str, JsonData] = Field(default_factory=dict, description="工具参数")
|
||||
|
||||
|
||||
class ToolCallResponse(BaseModel):
|
||||
"""工具调用响应模型"""
|
||||
success: bool = Field(..., description="是否成功")
|
||||
result: Optional[str] = Field(None, description="工具执行结果")
|
||||
error: Optional[str] = Field(None, description="错误信息")
|
||||
class McpJsonSchema(RootModel[dict[str, JsonData]]):
|
||||
"""MCP 工具的 JSON Schema。"""
|
||||
|
||||
|
||||
class McpToolInfo(BaseModel):
|
||||
"""MCP REST 工具摘要。"""
|
||||
|
||||
name: str = Field(description="工具名称")
|
||||
description: str = Field(default="", description="工具说明")
|
||||
inputSchema: McpJsonSchema = Field(description="工具参数 JSON Schema")
|
||||
|
||||
|
||||
class ToolCallData(BaseModel):
|
||||
"""MCP REST 工具调用成功后的业务数据。"""
|
||||
|
||||
result: str = Field(description="工具执行结果")
|
||||
|
||||
|
||||
class McpJsonRpcClientInfo(BaseModel):
|
||||
"""MCP JSON-RPC 客户端信息。"""
|
||||
|
||||
name: str
|
||||
version: Optional[str] = None
|
||||
|
||||
|
||||
class McpJsonRpcInitializeParams(BaseModel):
|
||||
"""MCP initialize 请求参数。"""
|
||||
|
||||
protocolVersion: str
|
||||
capabilities: dict[str, JsonData] = Field(default_factory=dict)
|
||||
clientInfo: Optional[McpJsonRpcClientInfo] = None
|
||||
|
||||
|
||||
class McpJsonRpcToolCallParams(BaseModel):
|
||||
"""MCP tools/call 请求参数。"""
|
||||
|
||||
name: str
|
||||
arguments: dict[str, JsonData] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class McpJsonRpcInitializeRequest(BaseModel):
|
||||
"""MCP initialize JSON-RPC 请求。"""
|
||||
|
||||
jsonrpc: Literal["2.0"]
|
||||
id: str | int
|
||||
method: Literal["initialize"]
|
||||
params: McpJsonRpcInitializeParams
|
||||
|
||||
|
||||
class McpJsonRpcInitializedNotification(BaseModel):
|
||||
"""MCP initialized JSON-RPC 通知。"""
|
||||
|
||||
jsonrpc: Literal["2.0"]
|
||||
method: Literal["notifications/initialized"]
|
||||
params: dict[str, JsonData] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class McpJsonRpcToolsListRequest(BaseModel):
|
||||
"""MCP tools/list JSON-RPC 请求。"""
|
||||
|
||||
jsonrpc: Literal["2.0"]
|
||||
id: str | int
|
||||
method: Literal["tools/list"]
|
||||
params: dict[str, JsonData] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class McpJsonRpcToolsCallRequest(BaseModel):
|
||||
"""MCP tools/call JSON-RPC 请求。"""
|
||||
|
||||
jsonrpc: Literal["2.0"]
|
||||
id: str | int
|
||||
method: Literal["tools/call"]
|
||||
params: McpJsonRpcToolCallParams
|
||||
|
||||
|
||||
class McpJsonRpcPingRequest(BaseModel):
|
||||
"""MCP ping JSON-RPC 请求。"""
|
||||
|
||||
jsonrpc: Literal["2.0"]
|
||||
id: str | int
|
||||
method: Literal["ping"]
|
||||
params: dict[str, JsonData] = Field(default_factory=dict)
|
||||
|
||||
|
||||
McpJsonRpcRequest: TypeAlias = Union[
|
||||
McpJsonRpcInitializeRequest,
|
||||
McpJsonRpcInitializedNotification,
|
||||
McpJsonRpcToolsListRequest,
|
||||
McpJsonRpcToolsCallRequest,
|
||||
McpJsonRpcPingRequest,
|
||||
]
|
||||
|
||||
|
||||
class McpJsonRpcServerInfo(BaseModel):
|
||||
"""MCP 服务器信息。"""
|
||||
|
||||
name: str
|
||||
version: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class McpJsonRpcToolsCapability(BaseModel):
|
||||
"""MCP 工具能力声明。"""
|
||||
|
||||
listChanged: bool = False
|
||||
|
||||
|
||||
class McpJsonRpcCapabilities(BaseModel):
|
||||
"""MCP 服务器能力声明。"""
|
||||
|
||||
tools: McpJsonRpcToolsCapability
|
||||
logging: dict[str, JsonData] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class McpJsonRpcInitializeResult(BaseModel):
|
||||
"""MCP initialize 响应结果。"""
|
||||
|
||||
protocolVersion: str
|
||||
capabilities: McpJsonRpcCapabilities
|
||||
serverInfo: McpJsonRpcServerInfo
|
||||
instructions: str
|
||||
|
||||
|
||||
class McpJsonRpcToolsListResult(BaseModel):
|
||||
"""MCP tools/list 响应结果。"""
|
||||
|
||||
tools: list[McpToolInfo] = Field(default_factory=list)
|
||||
|
||||
|
||||
class McpJsonRpcTextContent(BaseModel):
|
||||
"""MCP 工具调用文本内容块。"""
|
||||
|
||||
type: Literal["text"] = "text"
|
||||
text: str
|
||||
|
||||
|
||||
class McpJsonRpcToolCallResult(BaseModel):
|
||||
"""MCP tools/call 响应结果。"""
|
||||
|
||||
content: list[McpJsonRpcTextContent] = Field(default_factory=list)
|
||||
isError: bool = False
|
||||
|
||||
|
||||
class McpJsonRpcEmptyResult(BaseModel):
|
||||
"""MCP ping 的空结果。"""
|
||||
|
||||
|
||||
class McpJsonRpcSuccess(BaseModel):
|
||||
"""MCP JSON-RPC 成功响应。"""
|
||||
|
||||
jsonrpc: Literal["2.0"] = "2.0"
|
||||
id: Optional[str | int] = None
|
||||
result: Union[
|
||||
McpJsonRpcInitializeResult,
|
||||
McpJsonRpcToolsListResult,
|
||||
McpJsonRpcToolCallResult,
|
||||
McpJsonRpcEmptyResult,
|
||||
]
|
||||
|
||||
|
||||
class McpJsonRpcErrorDetail(BaseModel):
|
||||
"""MCP JSON-RPC 错误详情。"""
|
||||
|
||||
code: int
|
||||
message: str
|
||||
data: Optional[JsonData] = None
|
||||
|
||||
|
||||
class McpJsonRpcError(BaseModel):
|
||||
"""MCP JSON-RPC 错误响应。"""
|
||||
|
||||
jsonrpc: Literal["2.0"] = "2.0"
|
||||
id: Optional[str | int] = None
|
||||
error: McpJsonRpcErrorDetail
|
||||
|
||||
|
||||
McpJsonRpcResponse: TypeAlias = Union[McpJsonRpcSuccess, McpJsonRpcError]
|
||||
|
||||
# 保持协议端点自行解析并返回 JSON-RPC 错误,同时从同一组 Pydantic 模型生成请求文档。
|
||||
MCP_JSONRPC_REQUEST_SCHEMA = TypeAdapter(McpJsonRpcRequest).json_schema()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Union, List, Any
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict, model_validator
|
||||
from pydantic import BaseModel, Field, ConfigDict, RootModel, model_validator
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.media import OptionalMediaIdentityMixin
|
||||
from app.schemas.types import MediaSource, MediaType
|
||||
|
||||
@@ -14,7 +15,7 @@ class ExistMediaInfo(BaseModel):
|
||||
# 类型 电影、电视剧、音乐
|
||||
type: Optional[MediaType] = None
|
||||
# 季
|
||||
seasons: Optional[Dict[int, list]] = Field(default_factory=dict)
|
||||
seasons: Optional[Dict[int, List[int]]] = Field(default_factory=dict)
|
||||
# 媒体服务器类型:plex、jellyfin、emby、zspace、trimemedia、ugreen、navidrome
|
||||
server_type: Optional[str] = None
|
||||
# 媒体服务器名称
|
||||
@@ -23,6 +24,25 @@ class ExistMediaInfo(BaseModel):
|
||||
itemid: Optional[Union[str, int]] = None
|
||||
|
||||
|
||||
class MediaServerPlayData(BaseModel):
|
||||
"""媒体服务器在线播放地址。"""
|
||||
|
||||
url: str = Field(description="播放地址")
|
||||
item_id: Optional[str] = Field(default=None, description="媒体项目 ID")
|
||||
server_id: Optional[str] = Field(default=None, description="媒体服务器 ID")
|
||||
server_type: Optional[str] = Field(default=None, description="媒体服务器类型")
|
||||
|
||||
|
||||
class MediaServerExistsData(BaseModel):
|
||||
"""本地媒体存在性查询结果。"""
|
||||
|
||||
item: Dict[str, str] = Field(default_factory=dict, description="命中的媒体项目")
|
||||
|
||||
|
||||
class MediaServerExistingEpisodes(RootModel[Dict[int, List[int]]]):
|
||||
"""媒体服务器中按季号归组的已存在集号。"""
|
||||
|
||||
|
||||
class NotExistMediaInfo(BaseModel):
|
||||
"""
|
||||
媒体服务器不存在媒体信息
|
||||
@@ -30,7 +50,7 @@ class NotExistMediaInfo(BaseModel):
|
||||
# 季
|
||||
season: Optional[int] = None
|
||||
# 剧集列表
|
||||
episodes: Optional[list] = Field(default_factory=list)
|
||||
episodes: Optional[List[int]] = Field(default_factory=list)
|
||||
# 总集数
|
||||
total_episode: Optional[int] = 0
|
||||
# 开始集
|
||||
@@ -70,7 +90,7 @@ class MediaServerLibrary(BaseModel):
|
||||
# 名称
|
||||
name: Optional[str] = None
|
||||
# 路径
|
||||
path: Optional[Union[str, list]] = None
|
||||
path: Optional[Union[str, List[str]]] = None
|
||||
# 类型
|
||||
type: Optional[str] = None
|
||||
# 媒体库内媒体数量
|
||||
@@ -88,6 +108,8 @@ class MediaServerLibrary(BaseModel):
|
||||
|
||||
|
||||
class MediaServerItemUserState(BaseModel):
|
||||
"""媒体服务器条目的用户播放状态。"""
|
||||
|
||||
# 已播放
|
||||
played: Optional[bool] = None
|
||||
# 继续播放
|
||||
@@ -128,9 +150,9 @@ class MediaServerItem(OptionalMediaIdentityMixin, BaseModel):
|
||||
# 路径
|
||||
path: Optional[str] = None
|
||||
# 季集
|
||||
seasoninfo: Optional[Dict[int, list]] = None
|
||||
seasoninfo: Optional[Dict[int, List[int]]] = None
|
||||
# 备注
|
||||
note: Optional[Any] = None
|
||||
note: Optional[JsonData] = None
|
||||
# 同步时间
|
||||
lst_mod_date: Optional[str] = None
|
||||
user_state: Optional[MediaServerItemUserState] = None
|
||||
@@ -172,7 +194,7 @@ class WebhookEventInfo(BaseModel):
|
||||
save_reason: Optional[str] = None
|
||||
item_isvirtual: Optional[bool] = None
|
||||
media_type: Optional[str] = None
|
||||
json_object: Optional[dict] = Field(default_factory=dict)
|
||||
json_object: Optional[dict[str, JsonData]] = Field(default_factory=dict)
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
@@ -236,7 +258,7 @@ class MediaServerPlayItem(BaseModel):
|
||||
image: Optional[str] = None
|
||||
link: Optional[str] = None
|
||||
percent: Optional[float] = None
|
||||
BackdropImageTags: Optional[list] = Field(default_factory=list)
|
||||
BackdropImageTags: Optional[List[str]] = Field(default_factory=list)
|
||||
server_type: Optional[str] = None
|
||||
# 飞牛的图片需要Cookies
|
||||
use_cookies: Optional[bool] = None
|
||||
|
||||
+16
-5
@@ -4,6 +4,7 @@ from typing import Optional, Union, List, Dict, Set, Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.types import ContentType, NotificationType, MessageChannel
|
||||
|
||||
|
||||
@@ -47,7 +48,7 @@ class MessageResponse(BaseModel):
|
||||
# 消息来源
|
||||
source: Optional[str] = None
|
||||
# 渠道自定义上下文(如飞书流式卡片 card_id/element_id/sequence)
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
metadata: Optional[Dict[str, JsonData]] = None
|
||||
# 是否发送成功
|
||||
success: bool = False
|
||||
|
||||
@@ -80,7 +81,17 @@ class NotificationHistoryItem(BaseModel):
|
||||
# 消息方向:0-接收消息,1-发送消息
|
||||
action: Optional[int] = None
|
||||
# 附件json
|
||||
note: Optional[Union[list, dict]] = None
|
||||
note: Optional[JsonData] = None
|
||||
|
||||
|
||||
class WebMessageItem(NotificationHistoryItem):
|
||||
"""Web 消息历史记录。"""
|
||||
|
||||
|
||||
class NotificationClearData(BaseModel):
|
||||
"""通知中心各范围的清理时间。"""
|
||||
|
||||
clear_before: NotificationClearBefore = Field(description="各范围清理时间")
|
||||
|
||||
|
||||
class CommingMessage(BaseModel):
|
||||
@@ -300,7 +311,7 @@ class Subscription(BaseModel):
|
||||
"""
|
||||
|
||||
endpoint: Optional[str] = None
|
||||
keys: Optional[dict] = Field(default_factory=dict)
|
||||
keys: Optional[dict[str, str]] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SubscriptionMessage(BaseModel):
|
||||
@@ -312,7 +323,7 @@ class SubscriptionMessage(BaseModel):
|
||||
body: Optional[str] = None
|
||||
icon: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
data: Optional[dict] = Field(default_factory=dict)
|
||||
data: Optional[dict[str, JsonData]] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class AgentWebChatRequest(BaseModel):
|
||||
@@ -345,7 +356,7 @@ class AgentWebChatRequest(BaseModel):
|
||||
# 文件附件列表
|
||||
files: Optional[List[AgentWebChatFile]] = Field(default_factory=list)
|
||||
# 用户通过按钮选择时的完整选择快照
|
||||
choice_selection: Optional[Dict[str, Any]] = Field(default=None)
|
||||
choice_selection: Optional[Dict[str, JsonData]] = Field(default=None)
|
||||
# WebAgent 按钮回调关联的原消息 ID,用于传统交互原地编辑卡片
|
||||
original_message_id: Optional[Union[str, int]] = Field(default=None)
|
||||
# WebAgent 按钮回调关联的原聊天 ID,用于传统交互原地编辑卡片
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"""多因素认证 API 输出模型。"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, RootModel
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
|
||||
class PasskeyOptions(RootModel[dict[str, JsonData]]):
|
||||
"""浏览器 WebAuthn API 使用的动态选项。"""
|
||||
|
||||
|
||||
class OtpGenerateData(BaseModel):
|
||||
"""OTP 绑定密钥和验证 URI。"""
|
||||
|
||||
secret: str = Field(description="OTP 密钥")
|
||||
uri: str = Field(description="OTP 验证 URI")
|
||||
|
||||
|
||||
class MfaStatusData(BaseModel):
|
||||
"""用户是否启用多因素认证。"""
|
||||
|
||||
enabled: bool = Field(description="是否启用多因素认证")
|
||||
|
||||
|
||||
class PasskeyStartData(BaseModel):
|
||||
"""PassKey 注册或认证的启动数据。"""
|
||||
|
||||
options: PasskeyOptions = Field(description="WebAuthn 选项")
|
||||
transaction_token: str = Field(description="一次性事务令牌")
|
||||
|
||||
|
||||
class PasskeyInfo(BaseModel):
|
||||
"""当前用户绑定的 PassKey 摘要。"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
created_at: Optional[str] = None
|
||||
last_used_at: Optional[str] = None
|
||||
aaguid: Optional[str] = None
|
||||
transports: Optional[str] = None
|
||||
+27
-4
@@ -1,7 +1,8 @@
|
||||
from typing import Any, Literal, Optional, Union
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.media import OptionalMediaIdentityMixin, RequiredMediaIdentityMixin
|
||||
from app.schemas.types import MediaSource, MusicEntityType, MusicTargetEntityType
|
||||
|
||||
@@ -75,7 +76,7 @@ class MusicInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
names: list[str] = Field(default_factory=list)
|
||||
detail_link: Optional[str] = None
|
||||
listen_count: Optional[int] = None
|
||||
raw_data: dict[str, Any] = Field(default_factory=dict)
|
||||
raw_data: dict[str, JsonData] = Field(default_factory=dict)
|
||||
title_year: Optional[str] = None
|
||||
poster_path: Optional[str] = None
|
||||
backdrop_path: Optional[str] = None
|
||||
@@ -125,7 +126,7 @@ class MusicAlbumInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
detail_link: Optional[str] = None
|
||||
tracks: list[MusicInfo] = Field(default_factory=list)
|
||||
releases: list[MusicRelease] = Field(default_factory=list)
|
||||
raw_data: dict[str, Any] = Field(default_factory=dict)
|
||||
raw_data: dict[str, JsonData] = Field(default_factory=dict)
|
||||
title_year: Optional[str] = None
|
||||
poster_path: Optional[str] = None
|
||||
backdrop_path: Optional[str] = None
|
||||
@@ -160,7 +161,7 @@ class MusicArtistInfo(OptionalMediaIdentityMixin, BaseModel):
|
||||
detail_link: Optional[str] = None
|
||||
external_links: dict[str, str] = Field(default_factory=dict)
|
||||
album_count: Optional[int] = None
|
||||
raw_data: dict[str, Any] = Field(default_factory=dict)
|
||||
raw_data: dict[str, JsonData] = Field(default_factory=dict)
|
||||
poster_path: Optional[str] = None
|
||||
overview: Optional[str] = None
|
||||
|
||||
@@ -171,3 +172,25 @@ class MusicRecognizeRequest(RequiredMediaIdentityMixin, BaseModel):
|
||||
media_source: MediaSource
|
||||
media_id: str
|
||||
music_type: Optional[MusicTargetEntityType] = None
|
||||
|
||||
|
||||
class MusicRecognitionCacheItem(BaseModel):
|
||||
"""单条 MusicBrainz 识别缓存。"""
|
||||
|
||||
key: str
|
||||
media_id: str = ""
|
||||
title: str = ""
|
||||
artists: list[str] = Field(default_factory=list)
|
||||
album: str = ""
|
||||
year: str | int = ""
|
||||
music_type: str = "recording"
|
||||
cover_url: str = ""
|
||||
|
||||
|
||||
class MusicRecognitionCacheData(BaseModel):
|
||||
"""MusicBrainz 识别缓存统计及明细。"""
|
||||
|
||||
count: int = 0
|
||||
recognized: int = 0
|
||||
unrecognized: int = 0
|
||||
data: list[MusicRecognitionCacheItem] = Field(default_factory=list)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
"""通知渠道 API 输出模型。"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class WechatClawBotKnownTarget(BaseModel):
|
||||
"""微信 ClawBot 已知消息目标。"""
|
||||
|
||||
userid: str
|
||||
username: str
|
||||
last_active: Optional[int | float] = None
|
||||
|
||||
|
||||
class WechatClawBotData(BaseModel):
|
||||
"""微信 ClawBot 登录状态或操作结果。"""
|
||||
|
||||
success: bool
|
||||
message: Optional[str] = None
|
||||
connected: Optional[bool] = None
|
||||
account_id: Optional[str] = None
|
||||
qrcode: Optional[str] = None
|
||||
qrcode_url: Optional[str] = None
|
||||
qrcode_status: Optional[str] = None
|
||||
qrcode_updated_at: Optional[int | float] = None
|
||||
known_targets: list[WechatClawBotKnownTarget] = Field(default_factory=list)
|
||||
default_target: Optional[str] = None
|
||||
base_url: Optional[str] = None
|
||||
+44
-9
@@ -1,7 +1,9 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
|
||||
class OpenAIModelInfo(BaseModel):
|
||||
id: str
|
||||
@@ -16,14 +18,18 @@ class OpenAIModelListResponse(BaseModel):
|
||||
|
||||
|
||||
class OpenAIChatMessage(BaseModel):
|
||||
"""OpenAI Chat Completions 请求中的一条消息。"""
|
||||
|
||||
role: str
|
||||
content: Any
|
||||
content: JsonData
|
||||
name: Optional[str] = None
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class OpenAIChatCompletionsRequest(BaseModel):
|
||||
"""OpenAI Chat Completions 兼容请求。"""
|
||||
|
||||
model: Optional[str] = None
|
||||
messages: List[OpenAIChatMessage]
|
||||
user: Optional[str] = None
|
||||
@@ -33,8 +39,10 @@ class OpenAIChatCompletionsRequest(BaseModel):
|
||||
|
||||
|
||||
class OpenAIResponsesRequest(BaseModel):
|
||||
"""OpenAI Responses API 兼容请求。"""
|
||||
|
||||
model: Optional[str] = None
|
||||
input: Any
|
||||
input: JsonData
|
||||
instructions: Optional[str] = None
|
||||
user: Optional[str] = None
|
||||
stream: bool = False
|
||||
@@ -69,9 +77,30 @@ class OpenAIChatCompletionResponse(BaseModel):
|
||||
|
||||
|
||||
class OpenAIResponsesOutputText(BaseModel):
|
||||
"""Responses API 输出中的文本内容块。"""
|
||||
|
||||
type: str = "output_text"
|
||||
text: str
|
||||
annotations: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
annotations: List["OpenAIResponseAnnotation"] = Field(default_factory=list)
|
||||
|
||||
|
||||
class OpenAIResponseAnnotation(BaseModel):
|
||||
"""Responses API 文本内容关联的引用或文件注解。"""
|
||||
|
||||
type: str
|
||||
index: Optional[int] = None
|
||||
start_index: Optional[int] = None
|
||||
end_index: Optional[int] = None
|
||||
url: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
file_id: Optional[str] = None
|
||||
filename: Optional[str] = None
|
||||
|
||||
|
||||
class OpenAIIncompleteDetails(BaseModel):
|
||||
"""Responses API 未完整结束时的原因。"""
|
||||
|
||||
reason: str
|
||||
|
||||
|
||||
class OpenAIResponsesOutputMessage(BaseModel):
|
||||
@@ -83,14 +112,16 @@ class OpenAIResponsesOutputMessage(BaseModel):
|
||||
|
||||
|
||||
class OpenAIResponsesResponse(BaseModel):
|
||||
"""OpenAI Responses API 的非流式成功响应。"""
|
||||
|
||||
id: str
|
||||
object: str = "response"
|
||||
created_at: int
|
||||
status: str = "completed"
|
||||
model: str
|
||||
output: List[OpenAIResponsesOutputMessage] = Field(default_factory=list)
|
||||
error: Optional[Any] = None
|
||||
incomplete_details: Optional[Any] = None
|
||||
error: Optional["OpenAIErrorDetail"] = None
|
||||
incomplete_details: Optional[OpenAIIncompleteDetails] = None
|
||||
usage: OpenAIUsage
|
||||
|
||||
|
||||
@@ -105,20 +136,24 @@ class OpenAIErrorResponse(BaseModel):
|
||||
error: OpenAIErrorDetail
|
||||
|
||||
|
||||
OpenAIChatContentPart = Dict[str, Any]
|
||||
OpenAIChatContentPart = Dict[str, JsonData]
|
||||
|
||||
|
||||
class AnthropicMessage(BaseModel):
|
||||
"""Anthropic Messages 请求中的一条消息。"""
|
||||
|
||||
role: str
|
||||
content: Any
|
||||
content: JsonData
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class AnthropicMessagesRequest(BaseModel):
|
||||
"""Anthropic Messages 兼容请求。"""
|
||||
|
||||
model: Optional[str] = None
|
||||
messages: List[AnthropicMessage]
|
||||
system: Optional[Any] = None
|
||||
system: Optional[JsonData] = None
|
||||
max_tokens: Optional[int] = 1024
|
||||
stream: bool = False
|
||||
|
||||
|
||||
+55
-7
@@ -1,6 +1,8 @@
|
||||
from typing import Optional, List, Dict, Any
|
||||
from typing import Optional, List, Dict
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, RootModel
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
|
||||
class Plugin(BaseModel):
|
||||
@@ -51,7 +53,7 @@ class Plugin(BaseModel):
|
||||
# 安装次数
|
||||
install_count: Optional[int] = 0
|
||||
# 更新记录
|
||||
history: Optional[dict] = Field(default_factory=dict)
|
||||
history: Optional[dict[str, str]] = Field(default_factory=dict)
|
||||
# 添加时间,值越小表示越靠后发布
|
||||
add_time: Optional[int] = 0
|
||||
# 插件公钥
|
||||
@@ -70,11 +72,11 @@ class PluginDashboard(Plugin):
|
||||
# 演染模式
|
||||
render_mode: Optional[str] = Field(default="vuetify")
|
||||
# 全局配置
|
||||
attrs: Optional[dict] = Field(default_factory=dict)
|
||||
attrs: Optional[dict[str, JsonData]] = Field(default_factory=dict)
|
||||
# col列数
|
||||
cols: Optional[dict] = Field(default_factory=dict)
|
||||
cols: Optional[dict[str, JsonData]] = Field(default_factory=dict)
|
||||
# 页面元素
|
||||
elements: Optional[List[dict]] = Field(default_factory=list)
|
||||
elements: Optional[List[dict[str, JsonData]]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class PluginSidebarNavItem(BaseModel):
|
||||
@@ -115,6 +117,10 @@ class PluginRating(BaseModel):
|
||||
user_rating: Optional[float] = Field(default=None, description="当前安装实例评分")
|
||||
|
||||
|
||||
class PluginRatingMap(RootModel[Dict[str, PluginRating]]):
|
||||
"""插件 ID 与评分结果的映射。"""
|
||||
|
||||
|
||||
class PluginMemoryInfo(BaseModel):
|
||||
"""插件内存信息"""
|
||||
plugin_id: str = Field(description="插件ID")
|
||||
@@ -126,4 +132,46 @@ class PluginMemoryInfo(BaseModel):
|
||||
calculation_time_ms: float = Field(description="计算耗时(毫秒)")
|
||||
timestamp: float = Field(description="统计时间戳")
|
||||
error: Optional[str] = Field(default=None, description="错误信息")
|
||||
object_details: Optional[List[Dict[str, Any]]] = Field(default=None, description="大对象详情")
|
||||
object_details: Optional[List[Dict[str, JsonData]]] = Field(default=None, description="大对象详情")
|
||||
|
||||
|
||||
class PluginRemoteInfo(BaseModel):
|
||||
"""插件模块联邦远程入口。"""
|
||||
|
||||
id: str
|
||||
url: str
|
||||
name: str
|
||||
|
||||
|
||||
class PluginReleaseItem(BaseModel):
|
||||
"""可安装的插件 Release 版本。"""
|
||||
|
||||
version: str
|
||||
tag_name: str
|
||||
name: str
|
||||
published_at: Optional[str] = None
|
||||
body: str = ""
|
||||
asset_name: str
|
||||
is_latest: bool = False
|
||||
is_current: bool = False
|
||||
|
||||
|
||||
class PluginReleaseData(BaseModel):
|
||||
"""插件 Release 能力与版本列表。"""
|
||||
|
||||
release_supported: bool = False
|
||||
latest_version: Optional[str] = None
|
||||
current_version: Optional[str] = None
|
||||
items: List[PluginReleaseItem] = Field(default_factory=list)
|
||||
|
||||
|
||||
class PluginFoldersData(RootModel[Dict[str, List[str]]]):
|
||||
"""插件文件夹与插件 ID 列表映射。"""
|
||||
|
||||
|
||||
class PluginDashboardMetaItem(BaseModel):
|
||||
"""插件仪表板入口摘要。"""
|
||||
|
||||
id: str
|
||||
name: Optional[str] = None
|
||||
key: Optional[str] = None
|
||||
|
||||
+37
-18
@@ -1,29 +1,48 @@
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Generic, Optional, TypeVar
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
from pydantic import BaseModel, ConfigDict, field_validator
|
||||
|
||||
from app.helper.locale import LocaleHelper
|
||||
|
||||
|
||||
class Response(BaseModel):
|
||||
"""通用接口响应结构"""
|
||||
DataT = TypeVar("DataT")
|
||||
|
||||
|
||||
class Response(BaseModel, Generic[DataT]):
|
||||
"""统一接口响应结构,仅允许业务数据类型随接口变化。"""
|
||||
|
||||
model_config = ConfigDict(
|
||||
extra="forbid",
|
||||
json_schema_extra={"required": ["success", "message", "data"]}
|
||||
)
|
||||
|
||||
# 状态
|
||||
success: bool
|
||||
# 消息文本
|
||||
message: Optional[str] = None
|
||||
# 多语言消息文本
|
||||
message_i18n: Optional[str] = None
|
||||
message: str = ""
|
||||
# 数据
|
||||
data: Optional[Any] = Field(default_factory=dict)
|
||||
data: Optional[DataT] = None
|
||||
|
||||
@model_validator(mode="after")
|
||||
def fill_message_i18n(self) -> "Response":
|
||||
"""
|
||||
自动补充响应消息的多语言文本。
|
||||
"""
|
||||
if self.message and self.message_i18n is None:
|
||||
self.message_i18n = LocaleHelper.translate_text(
|
||||
self.message, locale=LocaleHelper.get_current_locale()
|
||||
)
|
||||
return self
|
||||
@field_validator("message", mode="before")
|
||||
@classmethod
|
||||
def localize_message(cls, value: Any) -> str:
|
||||
"""按当前请求语言直接本地化消息文本,并将空消息归一为空字符串。"""
|
||||
if value is None:
|
||||
return ""
|
||||
message = str(value)
|
||||
if not message:
|
||||
return ""
|
||||
return LocaleHelper.translate_text(
|
||||
message, locale=LocaleHelper.get_current_locale()
|
||||
)
|
||||
|
||||
|
||||
class ValidationIssue(BaseModel):
|
||||
"""请求参数校验失败时返回的单项错误信息。"""
|
||||
|
||||
# 参数位置
|
||||
location: list[str | int]
|
||||
# 错误说明
|
||||
message: str
|
||||
# 错误类型
|
||||
error_type: str
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
"""搜索 API 输出模型。"""
|
||||
|
||||
from typing import Literal, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.context import SubtitleInfo, TorrentInfo
|
||||
|
||||
|
||||
class SearchLastContextData(BaseModel):
|
||||
"""上一次搜索的请求参数与结果。"""
|
||||
|
||||
params: dict[str, JsonData] = Field(default_factory=dict)
|
||||
results: list[Union[TorrentInfo, SubtitleInfo]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class SearchRecommendStatusData(BaseModel):
|
||||
"""AI 搜索结果推荐任务状态。"""
|
||||
|
||||
status: Literal["disabled", "idle", "running", "completed", "error"]
|
||||
results: list[int] = Field(default_factory=list)
|
||||
+185
-6
@@ -1,8 +1,185 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ServarrVersion(BaseModel):
|
||||
"""Servarr 兼容接口使用的版本号结构。"""
|
||||
|
||||
major: int = 0
|
||||
minor: int = 0
|
||||
build: int = 0
|
||||
revision: int = 0
|
||||
majorRevision: int = 0
|
||||
minorRevision: int = 0
|
||||
|
||||
|
||||
class ServarrSystemStatus(BaseModel):
|
||||
"""Servarr 系统状态响应。"""
|
||||
|
||||
appName: str
|
||||
instanceName: str
|
||||
version: str
|
||||
buildTime: str
|
||||
isDebug: bool
|
||||
isProduction: bool
|
||||
isAdmin: bool
|
||||
isUserInteractive: bool
|
||||
startupPath: str
|
||||
appData: str
|
||||
osName: str
|
||||
osVersion: str
|
||||
isNetCore: bool
|
||||
isLinux: bool
|
||||
isOsx: bool
|
||||
isWindows: bool
|
||||
isDocker: bool
|
||||
mode: str
|
||||
branch: str
|
||||
databaseType: str
|
||||
databaseVersion: ServarrVersion
|
||||
authentication: str
|
||||
migrationVersion: int
|
||||
urlBase: str
|
||||
runtimeVersion: ServarrVersion
|
||||
runtimeName: str
|
||||
startTime: str
|
||||
packageVersion: str
|
||||
packageAuthor: str
|
||||
packageUpdateMechanism: str
|
||||
packageUpdateMechanismMessage: str
|
||||
|
||||
|
||||
class ServarrQuality(BaseModel):
|
||||
"""Servarr 质量定义。"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
source: str
|
||||
resolution: int
|
||||
|
||||
|
||||
class ServarrQualityProfileItem(BaseModel):
|
||||
"""Servarr 质量配置中的可选质量项。"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
quality: ServarrQuality
|
||||
items: list[str] = Field(default_factory=list)
|
||||
allowed: bool
|
||||
|
||||
|
||||
class ServarrFormatItem(BaseModel):
|
||||
"""Servarr 自定义格式评分项。"""
|
||||
|
||||
id: int
|
||||
format: int
|
||||
name: str
|
||||
score: int
|
||||
|
||||
|
||||
class ServarrQualityProfile(BaseModel):
|
||||
"""Servarr 质量配置响应项。"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
upgradeAllowed: bool
|
||||
cutoff: int
|
||||
items: list[ServarrQualityProfileItem] = Field(default_factory=list)
|
||||
minFormatScore: int
|
||||
cutoffFormatScore: int
|
||||
formatItems: list[ServarrFormatItem] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ServarrRootFolder(BaseModel):
|
||||
"""Servarr 根目录响应项。"""
|
||||
|
||||
id: int
|
||||
path: str
|
||||
accessible: bool
|
||||
freeSpace: int
|
||||
unmappedFolders: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ServarrTag(BaseModel):
|
||||
"""Servarr 标签响应项。"""
|
||||
|
||||
id: int
|
||||
label: str
|
||||
|
||||
|
||||
class ServarrLanguage(BaseModel):
|
||||
"""Servarr 语言定义。"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
|
||||
|
||||
class ServarrLanguageProfileItem(BaseModel):
|
||||
"""Servarr 语言配置中的可选语言项。"""
|
||||
|
||||
id: int
|
||||
language: ServarrLanguage
|
||||
allowed: bool
|
||||
|
||||
|
||||
class ServarrLanguageProfile(BaseModel):
|
||||
"""Servarr 语言配置响应项。"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
upgradeAllowed: bool
|
||||
cutoff: ServarrLanguage
|
||||
languages: list[ServarrLanguageProfileItem] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ServarrIdResponse(BaseModel):
|
||||
"""Servarr 新增资源后返回的资源标识。"""
|
||||
|
||||
id: int
|
||||
|
||||
|
||||
class ServarrImage(BaseModel):
|
||||
"""Servarr 媒体图片。"""
|
||||
|
||||
coverType: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
remoteUrl: Optional[str] = None
|
||||
|
||||
|
||||
class SonarrStatistics(BaseModel):
|
||||
"""Sonarr 剧集或季度统计信息。"""
|
||||
|
||||
seasonCount: Optional[int] = None
|
||||
episodeFileCount: Optional[int] = None
|
||||
episodeCount: Optional[int] = None
|
||||
totalEpisodeCount: Optional[int] = None
|
||||
sizeOnDisk: Optional[int] = None
|
||||
releaseGroups: list[str] = Field(default_factory=list)
|
||||
percentOfEpisodes: Optional[float] = None
|
||||
nextAiring: Optional[str] = None
|
||||
previousAiring: Optional[str] = None
|
||||
|
||||
|
||||
class SonarrSeason(BaseModel):
|
||||
"""Sonarr 季度监控信息。"""
|
||||
|
||||
seasonNumber: Optional[int] = None
|
||||
monitored: bool = False
|
||||
statistics: Optional[SonarrStatistics] = None
|
||||
images: list[ServarrImage] = Field(default_factory=list)
|
||||
|
||||
|
||||
class SonarrRatings(BaseModel):
|
||||
"""Sonarr 剧集评分信息。"""
|
||||
|
||||
votes: Optional[int] = None
|
||||
value: Optional[float] = None
|
||||
|
||||
|
||||
class RadarrMovie(BaseModel):
|
||||
"""Radarr 兼容接口的电影结构。"""
|
||||
|
||||
id: Optional[int] = None
|
||||
title: Optional[str] = None
|
||||
year: Optional[str | int] = None
|
||||
@@ -20,6 +197,8 @@ class RadarrMovie(BaseModel):
|
||||
|
||||
|
||||
class SonarrSeries(BaseModel):
|
||||
"""Sonarr 兼容接口的剧集结构。"""
|
||||
|
||||
id: Optional[int] = None
|
||||
title: Optional[str] = None
|
||||
sortTitle: Optional[str] = None
|
||||
@@ -28,9 +207,9 @@ class SonarrSeries(BaseModel):
|
||||
overview: Optional[str] = None
|
||||
network: Optional[str] = None
|
||||
airTime: Optional[str] = None
|
||||
images: list = Field(default_factory=list)
|
||||
images: list[ServarrImage] = Field(default_factory=list)
|
||||
remotePoster: Optional[str] = None
|
||||
seasons: list = Field(default_factory=list)
|
||||
seasons: list[SonarrSeason] = Field(default_factory=list)
|
||||
year: Optional[str | int] = None
|
||||
path: Optional[str] = None
|
||||
profileId: Optional[int] = None
|
||||
@@ -49,11 +228,11 @@ class SonarrSeries(BaseModel):
|
||||
cleanTitle: Optional[str] = None
|
||||
titleSlug: Optional[str] = None
|
||||
certification: Optional[str] = None
|
||||
genres: list = Field(default_factory=list)
|
||||
tags: list = Field(default_factory=list)
|
||||
genres: list[str] = Field(default_factory=list)
|
||||
tags: list[int] = Field(default_factory=list)
|
||||
added: Optional[str] = None
|
||||
ratings: Optional[dict] = None
|
||||
ratings: Optional[SonarrRatings] = None
|
||||
qualityProfileId: Optional[int] = None
|
||||
statistics: dict = Field(default_factory=dict)
|
||||
statistics: SonarrStatistics = Field(default_factory=SonarrStatistics)
|
||||
isAvailable: Optional[bool] = False
|
||||
hasFile: Optional[bool] = False
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
from fastapi import Query
|
||||
from pydantic import BaseModel
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
|
||||
class CookieData(BaseModel):
|
||||
encrypted: str = Query(min_length=1, max_length=1024 * 1024 * 50)
|
||||
uuid: str = Query(min_length=5, pattern="^[a-zA-Z0-9]+$")
|
||||
"""CookieCloud 上传的加密数据。"""
|
||||
|
||||
encrypted: str = Field(min_length=1, max_length=1024 * 1024 * 50)
|
||||
uuid: str = Field(min_length=5, pattern="^[a-zA-Z0-9]+$")
|
||||
|
||||
|
||||
class CookiePassword(BaseModel):
|
||||
"""CookieCloud 下载并解密数据所需的密码。"""
|
||||
|
||||
password: str
|
||||
|
||||
|
||||
class CookieActionResponse(BaseModel):
|
||||
"""CookieCloud 上传操作结果。"""
|
||||
|
||||
action: Literal["done", "error"]
|
||||
|
||||
|
||||
class CookieEncryptedPayload(BaseModel):
|
||||
"""CookieCloud 保存和下载的加密载荷。"""
|
||||
|
||||
encrypted: str
|
||||
|
||||
|
||||
class CookieDecryptedPayload(BaseModel):
|
||||
"""CookieCloud 解密后的 Cookie 数据载荷。"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
cookie_data: JsonData
|
||||
|
||||
+35
-6
@@ -1,9 +1,20 @@
|
||||
from typing import Optional, Any, Union, Dict
|
||||
from typing import Optional, Union, Dict
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from pydantic import BaseModel, Field, ConfigDict, RootModel
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
|
||||
SiteUnreadMessage = Union[
|
||||
tuple[Optional[str], Optional[str], Optional[str]],
|
||||
tuple[Optional[str], Optional[str], Optional[str], Optional[str]],
|
||||
]
|
||||
"""站点未读消息,第四项为部分站点提供的持久化去重来源。"""
|
||||
|
||||
|
||||
class Site(BaseModel):
|
||||
"""站点配置及运行状态。"""
|
||||
|
||||
# ID
|
||||
id: Optional[int] = None
|
||||
# 站点名称
|
||||
@@ -33,7 +44,7 @@ class Site(BaseModel):
|
||||
# 是否公开站点
|
||||
public: Optional[int] = 0
|
||||
# 备注
|
||||
note: Optional[Any] = None
|
||||
note: Optional[JsonData] = None
|
||||
# 超时时间
|
||||
timeout: Optional[int] = 15
|
||||
# 流控单位周期
|
||||
@@ -51,6 +62,8 @@ class Site(BaseModel):
|
||||
|
||||
|
||||
class SiteStatistic(BaseModel):
|
||||
"""单个站点的访问成功率与耗时统计。"""
|
||||
|
||||
# 站点ID
|
||||
domain: Optional[str] = None
|
||||
# 成功次数
|
||||
@@ -64,12 +77,14 @@ class SiteStatistic(BaseModel):
|
||||
# 最后修改时间
|
||||
lst_mod_date: Optional[str] = None
|
||||
# 备注
|
||||
note: Optional[Any] = None
|
||||
note: Optional[Dict[str, int]] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class SiteUserData(BaseModel):
|
||||
"""站点用户账户、流量、做种和未读消息数据。"""
|
||||
|
||||
# 站点域名
|
||||
domain: Optional[str] = None
|
||||
# 用户名
|
||||
@@ -97,11 +112,11 @@ class SiteUserData(BaseModel):
|
||||
# 下载体积
|
||||
leeching_size: Optional[int] = 0
|
||||
# 做种人数, 种子大小
|
||||
seeding_info: Optional[list] = Field(default_factory=list)
|
||||
seeding_info: Optional[list[tuple[int, int]]] = Field(default_factory=list)
|
||||
# 未读消息
|
||||
message_unread: Optional[int] = 0
|
||||
# 未读消息内容
|
||||
message_unread_contents: Optional[list] = Field(default_factory=list)
|
||||
message_unread_contents: Optional[list[SiteUnreadMessage]] = Field(default_factory=list)
|
||||
# 错误信息
|
||||
err_msg: Optional[str] = None
|
||||
# 更新日期
|
||||
@@ -111,6 +126,8 @@ class SiteUserData(BaseModel):
|
||||
|
||||
|
||||
class SiteAuth(BaseModel):
|
||||
"""站点认证模块及其参数。"""
|
||||
|
||||
site: Optional[str] = None
|
||||
params: Optional[Dict[str, Union[int, str]]] = Field(default_factory=dict)
|
||||
|
||||
@@ -125,6 +142,18 @@ class SiteCookieUpdate(BaseModel):
|
||||
|
||||
|
||||
class SiteCategory(BaseModel):
|
||||
"""站点资源分类。"""
|
||||
|
||||
id: Optional[int] = None
|
||||
cat: Optional[str] = None
|
||||
desc: Optional[str] = None
|
||||
|
||||
|
||||
class SiteIconData(BaseModel):
|
||||
"""站点图标地址或 Base64 内容。"""
|
||||
|
||||
icon: str
|
||||
|
||||
|
||||
class SiteMappingData(RootModel[dict[str, str]]):
|
||||
"""站点域名到显示名称的映射。"""
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""存储授权 API 输出模型。"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class StorageQrCodeData(BaseModel):
|
||||
"""云存储扫码授权二维码。"""
|
||||
|
||||
codeContent: Optional[str] = Field(default=None, description="二维码原始内容")
|
||||
codeUrl: Optional[str] = Field(default=None, description="二维码图片地址")
|
||||
|
||||
|
||||
class StorageAuthUrlData(BaseModel):
|
||||
"""云存储 OAuth 授权入口。"""
|
||||
|
||||
authUrl: str = Field(description="授权地址")
|
||||
state: str = Field(description="授权状态校验值")
|
||||
|
||||
|
||||
class StorageLoginStatusData(BaseModel):
|
||||
"""云存储扫码或 OAuth 登录状态。"""
|
||||
|
||||
status: int | str = Field(description="授权状态")
|
||||
tip: str = Field(description="状态提示")
|
||||
@@ -110,7 +110,7 @@ class Subscribe(OptionalMediaIdentityMixin, BaseModel):
|
||||
# 已完成集数
|
||||
completed_episode: Optional[int] = None
|
||||
# 附加信息
|
||||
note: Optional[Any] = None
|
||||
note: Optional[List[int]] = None
|
||||
# 状态:N-新建, R-订阅中
|
||||
state: Optional[str] = None
|
||||
# 最后更新时间
|
||||
@@ -197,6 +197,8 @@ class Subscribe(OptionalMediaIdentityMixin, BaseModel):
|
||||
|
||||
|
||||
class SubscribeShare(OptionalMediaIdentityMixin, BaseModel):
|
||||
"""可供其他用户复用的订阅分享信息。"""
|
||||
|
||||
# 分享ID
|
||||
id: Optional[int] = None
|
||||
# 订阅ID
|
||||
@@ -268,6 +270,8 @@ class SubscribeShare(OptionalMediaIdentityMixin, BaseModel):
|
||||
|
||||
|
||||
class SubscribeShareStatistics(BaseModel):
|
||||
"""单个用户的订阅分享数量与复用统计。"""
|
||||
|
||||
# 分享人
|
||||
share_user: Optional[str] = None
|
||||
# 分享数量
|
||||
@@ -277,6 +281,8 @@ class SubscribeShareStatistics(BaseModel):
|
||||
|
||||
|
||||
class SubscribeDownloadFileInfo(BaseModel):
|
||||
"""订阅剧集关联的下载文件信息。"""
|
||||
|
||||
# 种子名称
|
||||
torrent_title: Optional[str] = None
|
||||
# 站点名称
|
||||
@@ -290,6 +296,8 @@ class SubscribeDownloadFileInfo(BaseModel):
|
||||
|
||||
|
||||
class SubscribeLibraryFileInfo(BaseModel):
|
||||
"""订阅剧集关联的媒体库文件信息。"""
|
||||
|
||||
# 存储
|
||||
storage: Optional[str] = "local"
|
||||
# 文件路径
|
||||
@@ -303,6 +311,8 @@ class SubscribeLibraryFileInfo(BaseModel):
|
||||
|
||||
|
||||
class SubscribeEpisodeInfo(BaseModel):
|
||||
"""订阅单集的元数据及关联文件。"""
|
||||
|
||||
# 标题
|
||||
title: Optional[str] = None
|
||||
# 描述
|
||||
@@ -316,6 +326,8 @@ class SubscribeEpisodeInfo(BaseModel):
|
||||
|
||||
|
||||
class SubscrbieInfo(BaseModel):
|
||||
"""订阅详情及按集号归组的文件信息。"""
|
||||
|
||||
# 订阅信息
|
||||
subscribe: Optional[Subscribe] = None
|
||||
# 集信息 {集号: {download: 文件路径,library: 文件路径, backdrop: url, title: 标题, description: 描述}}
|
||||
|
||||
@@ -3,6 +3,9 @@ from typing import Optional, Any
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from app.schemas.context import MediaInfo, MetaInfo, TorrentInfo
|
||||
from app.schemas.rule import FilterRuleGroup
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceInfo:
|
||||
@@ -132,6 +135,61 @@ class StorageConf(BaseModel):
|
||||
config: Optional[dict] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class SystemEnvironmentUpdateData(BaseModel):
|
||||
"""环境配置更新的成功项和失败项。"""
|
||||
|
||||
success_updates: dict[str, tuple[Optional[bool], str]] = Field(default_factory=dict)
|
||||
failed_updates: dict[str, tuple[Optional[bool], str]] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class PluginMarketSyncData(BaseModel):
|
||||
"""Wiki 插件市场仓库同步结果。"""
|
||||
|
||||
value: str
|
||||
repos: list[str] = Field(default_factory=list)
|
||||
wiki_repos: list[str] = Field(default_factory=list)
|
||||
added_count: int = 0
|
||||
total_count: int = 0
|
||||
source_url: str
|
||||
|
||||
|
||||
class RuleTestData(BaseModel):
|
||||
"""过滤规则测试的输入、识别和匹配明细。"""
|
||||
|
||||
title: str
|
||||
subtitle: Optional[str] = None
|
||||
rulegroup_name: str
|
||||
rulegroup: Optional[FilterRuleGroup] = None
|
||||
meta_info: MetaInfo
|
||||
media_info: Optional[MediaInfo] = None
|
||||
torrent_info: TorrentInfo
|
||||
priority: Optional[int] = None
|
||||
matched: bool = False
|
||||
|
||||
|
||||
class NetTestTarget(BaseModel):
|
||||
"""前端可选择的网络测试目标。"""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
icon: str
|
||||
|
||||
|
||||
class SystemModuleInfo(BaseModel):
|
||||
"""已加载系统模块摘要。"""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
name_i18n: str
|
||||
name_key: str
|
||||
|
||||
|
||||
class SystemModuleListData(BaseModel):
|
||||
"""已加载系统模块列表。"""
|
||||
|
||||
modules: list[SystemModuleInfo] = Field(default_factory=list)
|
||||
|
||||
|
||||
class TransferDirectoryConf(BaseModel):
|
||||
"""
|
||||
文件整理目录配置
|
||||
|
||||
+53
-2
@@ -16,6 +16,34 @@ class TmdbSeason(BaseModel):
|
||||
vote_average: Optional[float] = None
|
||||
|
||||
|
||||
class TmdbEpisodeCredit(BaseModel):
|
||||
"""TMDB 剧集演职人员的公共信息。"""
|
||||
|
||||
adult: Optional[bool] = None
|
||||
gender: Optional[int] = None
|
||||
id: Optional[int] = None
|
||||
known_for_department: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
original_name: Optional[str] = None
|
||||
popularity: Optional[float] = None
|
||||
profile_path: Optional[str] = None
|
||||
credit_id: Optional[str] = None
|
||||
|
||||
|
||||
class TmdbEpisodeCrew(TmdbEpisodeCredit):
|
||||
"""TMDB 剧集幕后人员信息。"""
|
||||
|
||||
department: Optional[str] = None
|
||||
job: Optional[str] = None
|
||||
|
||||
|
||||
class TmdbEpisodeGuestStar(TmdbEpisodeCredit):
|
||||
"""TMDB 剧集客串演员信息。"""
|
||||
|
||||
character: Optional[str] = None
|
||||
order: Optional[int] = None
|
||||
|
||||
|
||||
class TmdbEpisode(BaseModel):
|
||||
"""
|
||||
TMDB集信息
|
||||
@@ -29,5 +57,28 @@ class TmdbEpisode(BaseModel):
|
||||
season_number: Optional[int] = None
|
||||
still_path: Optional[str] = None
|
||||
vote_average: Optional[float] = None
|
||||
crew: Optional[list] = Field(default_factory=list)
|
||||
guest_stars: Optional[list] = Field(default_factory=list)
|
||||
crew: Optional[list[TmdbEpisodeCrew]] = Field(default_factory=list)
|
||||
guest_stars: Optional[list[TmdbEpisodeGuestStar]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class TmdbRecognitionCacheItem(BaseModel):
|
||||
"""单条 TMDB 识别缓存。"""
|
||||
|
||||
key: str
|
||||
tmdb_id: int = 0
|
||||
title: str = ""
|
||||
year: str | int = ""
|
||||
media_type: str = "unknown"
|
||||
poster_path: str = ""
|
||||
backdrop_path: str = ""
|
||||
|
||||
|
||||
class TmdbRecognitionCacheData(BaseModel):
|
||||
"""TMDB 识别缓存统计及明细。"""
|
||||
|
||||
count: int = 0
|
||||
recognized: int = 0
|
||||
unrecognized: int = 0
|
||||
shared_recognized: int = 0
|
||||
shared_recognize_enabled: bool = False
|
||||
data: list[TmdbRecognitionCacheItem] = Field(default_factory=list)
|
||||
|
||||
+12
-1
@@ -3,7 +3,16 @@ from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class MfaChallenge(BaseModel):
|
||||
"""密码认证通过后需要继续完成的二次验证信息。"""
|
||||
|
||||
# 可用的二次验证方式
|
||||
mfa_methods: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
"""OAuth2 登录成功后返回的访问令牌。"""
|
||||
|
||||
# 令牌
|
||||
access_token: str
|
||||
# 令牌类型
|
||||
@@ -19,12 +28,14 @@ class Token(BaseModel):
|
||||
# 权限级别
|
||||
level: int = 1
|
||||
# 详细权限
|
||||
permissions: Optional[dict] = Field(default_factory=dict)
|
||||
permissions: Optional[dict[str, bool]] = Field(default_factory=dict)
|
||||
# 是否显示配置向导
|
||||
wizard: Optional[bool] = None
|
||||
|
||||
|
||||
class TokenPayload(BaseModel):
|
||||
"""访问令牌中携带的用户身份与授权信息。"""
|
||||
|
||||
# 用户ID
|
||||
sub: Optional[int] = None
|
||||
# 用户名
|
||||
|
||||
+78
-1
@@ -41,12 +41,32 @@ class DownloaderTorrent(BaseModel):
|
||||
ratio_limit: Optional[float] = None
|
||||
seeding_time_limit: Optional[int] = None
|
||||
trackers: Optional[List[str]] = Field(default_factory=list)
|
||||
media: Optional[dict] = Field(default_factory=dict)
|
||||
media: Optional["DownloadTaskMedia"] = None
|
||||
userid: Optional[str] = None
|
||||
username: Optional[str] = None
|
||||
left_time: Optional[str] = None
|
||||
|
||||
|
||||
class DownloadTaskMedia(OptionalMediaIdentityMixin, BaseModel):
|
||||
"""下载任务关联的影视或音乐媒体摘要。"""
|
||||
|
||||
type: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
season: Optional[list[int] | int | str] = None
|
||||
episode: Optional[list[int] | int | str] = None
|
||||
image: Optional[str] = None
|
||||
poster: Optional[str] = None
|
||||
backdrop: Optional[str] = None
|
||||
media_source: Optional[MediaSource] = None
|
||||
media_id: Optional[str] = None
|
||||
music_type: Optional[str] = None
|
||||
artists: list[str] = Field(default_factory=list)
|
||||
album: Optional[str] = None
|
||||
album_id: Optional[str] = None
|
||||
total_tracks: Optional[int] = None
|
||||
track_number: Optional[int] = None
|
||||
|
||||
|
||||
class TransferTorrent(DownloaderTorrent):
|
||||
"""
|
||||
待转移任务信息
|
||||
@@ -281,3 +301,60 @@ class ManualTransferTargetPath(BaseModel):
|
||||
library_type_folder: Optional[bool] = False
|
||||
# 媒体库类别子目录
|
||||
library_category_folder: Optional[bool] = False
|
||||
|
||||
|
||||
class ManualTransferPreviewSummary(BaseModel):
|
||||
"""手动整理预览数量统计。"""
|
||||
|
||||
total: int = 0
|
||||
success: int = 0
|
||||
failed: int = 0
|
||||
|
||||
|
||||
class ManualTransferPreviewItem(BaseModel):
|
||||
"""单个文件的手动整理预览。"""
|
||||
|
||||
source: Optional[str] = None
|
||||
target: Optional[str] = None
|
||||
target_dir: Optional[str] = None
|
||||
success: bool = False
|
||||
message: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
season: Optional[int] = None
|
||||
episode: Optional[int] = None
|
||||
episode_end: Optional[int] = None
|
||||
part: Optional[int | str] = None
|
||||
org_string: Optional[str] = None
|
||||
apply_words: list[str] = Field(default_factory=list)
|
||||
resource_team: Optional[str] = None
|
||||
customization: Optional[str] = None
|
||||
|
||||
|
||||
class ManualTransferResultData(BaseModel):
|
||||
"""手动整理预览或执行结果数据。"""
|
||||
|
||||
summary: Optional[ManualTransferPreviewSummary] = None
|
||||
items: list[ManualTransferPreviewItem] = Field(default_factory=list)
|
||||
message: Optional[str] = None
|
||||
|
||||
|
||||
class EpisodeFormatRecommendData(BaseModel):
|
||||
"""集数定位模板推荐结果。"""
|
||||
|
||||
rule_name: str
|
||||
episode_format: str
|
||||
sample_file: str
|
||||
pattern: Optional[str] = None
|
||||
rule_index: Optional[int] = None
|
||||
min_file_size_mb: Optional[int] = None
|
||||
sample_count: Optional[int] = None
|
||||
majority_count: Optional[int] = None
|
||||
confidence: Optional[str] = None
|
||||
size_filter_relaxed: Optional[bool] = None
|
||||
native_verified_count: Optional[int] = None
|
||||
native_fallback_count: Optional[int] = None
|
||||
native_conflict_count: Optional[int] = None
|
||||
reason: Optional[str] = None
|
||||
reasons: list[str] = Field(default_factory=list)
|
||||
message: Optional[str] = None
|
||||
|
||||
+42
-6
@@ -2,9 +2,13 @@ from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
|
||||
|
||||
# Shared properties
|
||||
class UserBase(BaseModel):
|
||||
"""用户公共资料、权限和个性化设置。"""
|
||||
|
||||
# 用户名
|
||||
name: str
|
||||
# 邮箱,未启用
|
||||
@@ -18,33 +22,39 @@ class UserBase(BaseModel):
|
||||
# 是否开启二次验证
|
||||
is_otp: Optional[bool] = False
|
||||
# 权限
|
||||
permissions: Optional[dict] = Field(default_factory=dict)
|
||||
permissions: Optional[dict[str, bool]] = Field(default_factory=dict)
|
||||
# 个性化设置
|
||||
settings: Optional[dict] = Field(default_factory=dict)
|
||||
settings: Optional[dict[str, JsonData]] = Field(default_factory=dict)
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# Properties to receive via API on creation
|
||||
class UserCreate(UserBase):
|
||||
"""创建用户时接收的资料和初始凭据。"""
|
||||
|
||||
name: str
|
||||
email: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
settings: Optional[dict] = Field(default_factory=dict)
|
||||
permissions: Optional[dict] = Field(default_factory=dict)
|
||||
settings: Optional[dict[str, JsonData]] = Field(default_factory=dict)
|
||||
permissions: Optional[dict[str, bool]] = Field(default_factory=dict)
|
||||
|
||||
|
||||
# Properties to receive via API on update
|
||||
class UserUpdate(UserBase):
|
||||
"""更新用户时接收的完整资料。"""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
email: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
settings: Optional[dict] = Field(default_factory=dict)
|
||||
permissions: Optional[dict] = Field(default_factory=dict)
|
||||
settings: Optional[dict[str, JsonData]] = Field(default_factory=dict)
|
||||
permissions: Optional[dict[str, bool]] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class UserInDBBase(UserBase):
|
||||
"""包含数据库主键的用户公共记录。"""
|
||||
|
||||
id: Optional[int] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -52,10 +62,36 @@ class UserInDBBase(UserBase):
|
||||
|
||||
# Additional properties to return via API
|
||||
class User(UserInDBBase):
|
||||
"""对 API 调用方公开的用户资料。"""
|
||||
|
||||
name: str
|
||||
email: Optional[str] = None
|
||||
|
||||
|
||||
# Additional properties stored in DB
|
||||
class UserInDB(UserInDBBase):
|
||||
"""包含密码哈希的内部用户记录。"""
|
||||
|
||||
hashed_password: str
|
||||
|
||||
|
||||
class AuthProviderRemote(BaseModel):
|
||||
"""插件认证提供方的远程组件信息。"""
|
||||
|
||||
id: str
|
||||
url: str
|
||||
name: str
|
||||
|
||||
|
||||
class AuthProviderInfo(BaseModel):
|
||||
"""匿名登录页可展示的认证提供方摘要。"""
|
||||
|
||||
id: str
|
||||
type: str
|
||||
name: str
|
||||
enabled: bool = True
|
||||
method: Optional[str] = None
|
||||
icon: Optional[str] = None
|
||||
component: Optional[str] = None
|
||||
plugin_id: Optional[str] = None
|
||||
remote: Optional[AuthProviderRemote] = None
|
||||
|
||||
+112
-14
@@ -2,6 +2,7 @@ from typing import Any, List, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.schemas.common import JsonData
|
||||
from app.schemas.context import Context, MediaInfo
|
||||
from app.schemas.download import DownloadTask
|
||||
from app.schemas.file import FileItem
|
||||
@@ -9,6 +10,58 @@ from app.schemas.site import Site
|
||||
from app.schemas.subscribe import Subscribe
|
||||
|
||||
|
||||
class WorkflowExecutionConfig(BaseModel):
|
||||
"""工作流调度器的执行参数。"""
|
||||
|
||||
max_workers: Optional[int] = Field(default=None, ge=1, description="最大并发动作数")
|
||||
|
||||
|
||||
class WorkflowNodeState(BaseModel):
|
||||
"""工作流单个动作的持久化执行状态。"""
|
||||
|
||||
state: Optional[str] = None
|
||||
attempt: int = 0
|
||||
started_at: Optional[str] = None
|
||||
finished_at: Optional[str] = None
|
||||
message: Optional[str] = None
|
||||
|
||||
|
||||
class WorkflowRuntimeState(BaseModel):
|
||||
"""工作流调度器的实时进度摘要。"""
|
||||
|
||||
progress: int = 0
|
||||
finished_actions: int = 0
|
||||
running_tasks: int = 0
|
||||
errors: dict[str, str] = Field(default_factory=dict)
|
||||
node_states: dict[str, str] = Field(default_factory=dict)
|
||||
attempts: dict[str, int] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class WorkflowExecutionState(BaseModel):
|
||||
"""可恢复的工作流结构化执行快照。"""
|
||||
|
||||
version: int = 1
|
||||
nodes: dict[str, WorkflowNodeState] = Field(default_factory=dict)
|
||||
outputs: dict[str, JsonData] = Field(default_factory=dict)
|
||||
errors: dict[str, str] = Field(default_factory=dict)
|
||||
runtime: WorkflowRuntimeState = Field(default_factory=WorkflowRuntimeState)
|
||||
|
||||
|
||||
class ActionPosition(BaseModel):
|
||||
"""工作流画布中的动作坐标。"""
|
||||
|
||||
x: float = 0
|
||||
y: float = 0
|
||||
|
||||
|
||||
class ActionRetry(BaseModel):
|
||||
"""动作失败后的重试策略。"""
|
||||
|
||||
max_attempts: int = Field(default=1, ge=1)
|
||||
interval: float = Field(default=0, ge=0)
|
||||
backoff: float = Field(default=1, ge=1)
|
||||
|
||||
|
||||
class Workflow(BaseModel):
|
||||
"""
|
||||
工作流信息
|
||||
@@ -19,15 +72,15 @@ class Workflow(BaseModel):
|
||||
timer: Optional[str] = Field(default=None, description="定时器")
|
||||
trigger_type: Optional[str] = Field(default='timer', description="触发类型:timer-定时触发 event-事件触发 manual-手动触发")
|
||||
event_type: Optional[str] = Field(default=None, description="事件类型(当trigger_type为event时使用)")
|
||||
event_conditions: Optional[dict] = Field(default_factory=dict, description="事件条件(JSON格式,用于过滤事件)")
|
||||
event_conditions: Optional[dict[str, JsonData]] = Field(default_factory=dict, description="事件条件(JSON格式,用于过滤事件)")
|
||||
state: Optional[str] = Field(default=None, description="状态")
|
||||
current_action: Optional[str] = Field(default=None, description="已执行动作")
|
||||
result: Optional[str] = Field(default=None, description="任务执行结果")
|
||||
run_count: Optional[int] = Field(default=0, description="已执行次数")
|
||||
actions: Optional[list] = Field(default_factory=list, description="任务列表")
|
||||
flows: Optional[list] = Field(default_factory=list, description="任务流")
|
||||
execution_config: Optional[dict] = Field(default_factory=dict, description="工作流执行配置")
|
||||
execution_state: Optional[dict] = Field(default_factory=dict, description="工作流结构化执行状态")
|
||||
actions: Optional[list["Action"]] = Field(default_factory=list, description="任务列表")
|
||||
flows: Optional[list["ActionFlow"]] = Field(default_factory=list, description="任务流")
|
||||
execution_config: Optional[WorkflowExecutionConfig] = Field(default_factory=WorkflowExecutionConfig, description="工作流执行配置")
|
||||
execution_state: Optional[WorkflowExecutionState] = Field(default_factory=WorkflowExecutionState, description="工作流结构化执行状态")
|
||||
add_time: Optional[str] = Field(default=None, description="创建时间")
|
||||
last_time: Optional[str] = Field(default=None, description="最后执行时间")
|
||||
|
||||
@@ -50,16 +103,16 @@ class Action(BaseModel):
|
||||
type: Optional[str] = Field(default=None, description="动作类型 (类名)")
|
||||
name: Optional[str] = Field(default=None, description="动作名称")
|
||||
description: Optional[str] = Field(default=None, description="动作描述")
|
||||
position: Optional[dict] = Field(default_factory=dict, description="位置")
|
||||
data: Optional[dict] = Field(default_factory=dict, description="参数")
|
||||
position: Optional[ActionPosition] = Field(default_factory=ActionPosition, description="位置")
|
||||
data: Optional[dict[str, JsonData]] = Field(default_factory=dict, description="参数")
|
||||
inputs: Optional[List[str]] = Field(default_factory=list, description="动作输入声明")
|
||||
outputs: Optional[dict] = Field(default_factory=dict, description="动作输出声明")
|
||||
outputs: Optional[dict[str, JsonData]] = Field(default_factory=dict, description="动作输出声明")
|
||||
join_policy: Optional[str] = Field(default=None, description="多上游节点汇合策略")
|
||||
fail_policy: Optional[str] = Field(default=None, description="动作失败后的工作流处理策略")
|
||||
branch_policy: Optional[str] = Field(default=None, description="多出边分支策略")
|
||||
concurrency_key: Optional[str] = Field(default=None, description="并发互斥键")
|
||||
timeout: Optional[int] = Field(default=None, description="动作执行超时时间(秒)")
|
||||
retry: Optional[dict] = Field(default_factory=dict, description="动作重试策略")
|
||||
retry: Optional[ActionRetry] = Field(default=None, description="动作重试策略")
|
||||
|
||||
|
||||
class ActionExecution(BaseModel):
|
||||
@@ -82,10 +135,10 @@ class ActionContext(BaseModel):
|
||||
downloads: Optional[List[DownloadTask]] = Field(default_factory=list, description="下载任务列表")
|
||||
sites: Optional[List[Site]] = Field(default_factory=list, description="站点列表")
|
||||
subscribes: Optional[List[Subscribe]] = Field(default_factory=list, description="订阅列表")
|
||||
workflow_context: Optional[dict] = Field(default_factory=dict, description="工作流全局上下文")
|
||||
node_outputs: Optional[dict] = Field(default_factory=dict, description="节点输出数据")
|
||||
runtime_state: Optional[dict] = Field(default_factory=dict, description="运行期状态")
|
||||
artifacts: Optional[dict] = Field(default_factory=dict, description="大对象引用与产物数据")
|
||||
workflow_context: Optional[dict[str, JsonData]] = Field(default_factory=dict, description="工作流全局上下文")
|
||||
node_outputs: Optional[dict[str, JsonData]] = Field(default_factory=dict, description="节点输出数据")
|
||||
runtime_state: Optional[dict[str, JsonData]] = Field(default_factory=dict, description="运行期状态")
|
||||
artifacts: Optional[dict[str, JsonData]] = Field(default_factory=dict, description="大对象引用与产物数据")
|
||||
execute_history: Optional[List[ActionExecution]] = Field(default_factory=list, description="执行历史")
|
||||
progress: Optional[int] = Field(default=0, description="执行进度(%)")
|
||||
|
||||
@@ -97,6 +150,7 @@ class ActionResult(BaseModel):
|
||||
success: Optional[bool] = Field(default=True, description="动作是否执行成功")
|
||||
message: Optional[str] = Field(default=None, description="动作执行消息")
|
||||
context: Optional[ActionContext] = Field(default=None, description="动作执行后的上下文")
|
||||
# 动作内部可暂存待序列化对象;API 输出前由工作流序列化器转为 JsonData。
|
||||
outputs: Optional[dict[str, Any]] = Field(default_factory=dict, description="当前节点显式输出")
|
||||
next_policy: Optional[str] = Field(default=None, description="动作完成后的调度策略")
|
||||
attempts: Optional[int] = Field(default=1, description="动作实际尝试次数")
|
||||
@@ -110,7 +164,7 @@ class ActionFlow(BaseModel):
|
||||
source: Optional[str] = Field(default=None, description="源动作")
|
||||
target: Optional[str] = Field(default=None, description="目标动作")
|
||||
animated: Optional[bool] = Field(default=True, description="是否动画流程")
|
||||
data: Optional[dict] = Field(default_factory=dict, description="流程扩展配置")
|
||||
data: Optional[dict[str, JsonData]] = Field(default_factory=dict, description="流程扩展配置")
|
||||
condition: Optional[str] = Field(default=None, description="流转条件表达式")
|
||||
join_policy: Optional[str] = Field(default=None, description="目标节点汇合策略")
|
||||
branch_policy: Optional[str] = Field(default=None, description="源节点分支策略")
|
||||
@@ -138,3 +192,47 @@ class WorkflowShare(BaseModel):
|
||||
count: Optional[int] = Field(default=0, description="复用人次")
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class PluginWorkflowActionGroup(BaseModel):
|
||||
"""单个插件声明的工作流动作组。"""
|
||||
|
||||
plugin_id: str
|
||||
plugin_name: str
|
||||
actions: list[dict[str, JsonData]] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ActionContractField(BaseModel):
|
||||
"""动作契约中的单个输入或输出字段。"""
|
||||
|
||||
name: str
|
||||
label: str
|
||||
kind: str = "scalar"
|
||||
merge: Optional[str] = None
|
||||
identity: Optional[str] = None
|
||||
|
||||
|
||||
class ActionContract(BaseModel):
|
||||
"""工作流动作对编辑器公开的输入输出契约。"""
|
||||
|
||||
inputs: list[ActionContractField] = Field(default_factory=list)
|
||||
outputs: list[ActionContractField] = Field(default_factory=list)
|
||||
condition_fields: list[ActionContractField] = Field(default_factory=list)
|
||||
concurrency_key: Optional[str] = None
|
||||
|
||||
|
||||
class WorkflowActionDefinition(BaseModel):
|
||||
"""可用于工作流编辑器的动作定义。"""
|
||||
|
||||
type: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
contract: ActionContract = Field(default_factory=ActionContract)
|
||||
data: dict[str, JsonData] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class NameValueOption(BaseModel):
|
||||
"""前端选项的显示文本和值。"""
|
||||
|
||||
title: str
|
||||
value: str
|
||||
|
||||
Reference in New Issue
Block a user