diff --git a/app/api/apiv1.py b/app/api/apiv1.py index 8285ec73..6182ac71 100644 --- a/app/api/apiv1.py +++ b/app/api/apiv1.py @@ -2,7 +2,7 @@ from fastapi import APIRouter from app.api.endpoints import login, user, site, message, webhook, subscribe, \ media, douban, search, plugin, tmdb, history, system, download, dashboard, \ - transfer, mediaserver, bangumi, storage + transfer, mediaserver, bangumi, storage, discover api_router = APIRouter() api_router.include_router(login.router, prefix="/login", tags=["login"]) @@ -24,3 +24,4 @@ api_router.include_router(storage.router, prefix="/storage", tags=["storage"]) api_router.include_router(transfer.router, prefix="/transfer", tags=["transfer"]) api_router.include_router(mediaserver.router, prefix="/mediaserver", tags=["mediaserver"]) api_router.include_router(bangumi.router, prefix="/bangumi", tags=["bangumi"]) +api_router.include_router(discover.router, prefix="/discover", tags=["discover"]) diff --git a/app/api/endpoints/discover.py b/app/api/endpoints/discover.py new file mode 100644 index 00000000..7d491d9c --- /dev/null +++ b/app/api/endpoints/discover.py @@ -0,0 +1,27 @@ +from typing import Any, List + +from fastapi import APIRouter, Depends + +from app import schemas +from app.core.event import eventmanager +from app.core.security import verify_token +from app.schemas import DiscoverSourceEventData +from app.schemas.types import ChainEventType + +router = APIRouter() + + +@router.get("/source", summary="获取发现数据源", response_model=List[schemas.DiscoverMediaSource]) +def source(_: schemas.TokenPayload = Depends(verify_token)) -> Any: + """ + 获取发现数据源 + """ + # 广播事件,请示额外的发现数据源支持 + event_data = DiscoverSourceEventData() + event = eventmanager.send_event(ChainEventType.DiscoverSource, event_data) + # 使用事件返回的上下文数据 + if event and event.event_data: + event_data: DiscoverSourceEventData = event.event_data + if event_data.extra_sources: + return event_data.extra_sources + return [] diff --git a/app/api/endpoints/search.py b/app/api/endpoints/search.py index 345f0b10..54121247 100644 --- a/app/api/endpoints/search.py +++ b/app/api/endpoints/search.py @@ -6,8 +6,10 @@ from app import schemas from app.chain.media import MediaChain from app.chain.search import SearchChain from app.core.config import settings +from app.core.event import eventmanager from app.core.security import verify_token -from app.schemas.types import MediaType +from app.schemas import MediaRecognizeConvertEventData +from app.schemas.types import MediaType, ChainEventType router = APIRouter() @@ -34,6 +36,8 @@ def search_by_id(mediaid: str, mtype = MediaType(mtype) if season: season = int(season) + torrents = None + # 根据前缀识别媒体ID if mediaid.startswith("tmdb:"): tmdbid = int(mediaid.replace("tmdb:", "")) if settings.RECOGNIZE_SOURCE == "douban": @@ -79,8 +83,26 @@ def search_by_id(mediaid: str, else: return schemas.Response(success=False, message="未识别到豆瓣媒体信息") else: - return schemas.Response(success=False, message="未知的媒体ID") - + # 未知前缀,广播事件解析媒体信息 + event_data = MediaRecognizeConvertEventData( + mediaid=mediaid, + convert_type=settings.RECOGNIZE_SOURCE + ) + event = eventmanager.send_event(ChainEventType.MediaRecognizeConvert, event_data) + # 使用事件返回的上下文数据 + if event and event.event_data: + event_data: MediaRecognizeConvertEventData = event.event_data + if event_data.media_dict: + search_id = event_data.media_dict.get("id") + if event_data.convert_type == "themoviedb": + torrents = SearchChain().search_by_id(tmdbid=search_id, + mtype=mtype, area=area, season=season) + else: + torrents = SearchChain().search_by_id(doubanid=search_id, + mtype=mtype, area=area, season=season) + else: + return schemas.Response(success=False, message="未知的媒体ID") + # 返回搜索结果 if not torrents: return schemas.Response(success=False, message="未搜索到任何资源") else: diff --git a/app/schemas/event.py b/app/schemas/event.py index 41f7754c..99fe5efa 100644 --- a/app/schemas/event.py +++ b/app/schemas/event.py @@ -227,9 +227,51 @@ class TransferInterceptEventData(ChainEventData): target_storage: str = Field(..., description="目标存储") target_path: Path = Field(..., description="目标路径") transfer_type: str = Field(..., description="整理方式") - options: Optional[dict] = Field(None, description="其他参数") + options: Optional[dict] = Field(default=None, description="其他参数") # 输出参数 cancel: bool = Field(default=False, description="是否取消整理") source: str = Field(default="未知拦截源", description="拦截源") reason: str = Field(default="", description="拦截原因") + + +class DiscoverMediaSource(BaseModel): + """ + 探索媒体数据源的基类 + """ + mediaid_prefix: str = Field(..., description="媒体ID的前缀,不含:") + api_path: str = Field(..., description="媒体数据源API地址") + filter_params: Optional[Dict[str, Any]] = Field(default=None, description="过滤参数") + filter_ui: Optional[List[dict]] = Field(default=[], description="过滤参数UI配置") + + +class DiscoverSourceEventData(ChainEventData): + """ + DiscoverSource 事件的数据模型 + + Attributes: + # 输出参数 + extra_sources (List[DiscoverMediaSource]): 额外媒体数据源 + """ + # 输出参数 + extra_sources: List[DiscoverMediaSource] = Field(default_factory=list, description="额外媒体数据源") + + +class MediaRecognizeConvertEventData(ChainEventData): + """ + MediaRecognizeConvert 事件的数据模型 + + Attributes: + # 输入参数 + mediaid (str): 媒体ID,格式为`前缀:ID值`,如 tmdb:12345、douban:1234567 + convert_type (str): 转换类型 仅支持:themoviedb/douban,需要转换为对应的媒体数据并返回 + + # 输出参数 + media_dict (dict): TheMovieDb或豆瓣的媒体数据 + """ + # 输入参数 + mediaid: str = Field(..., description="媒体ID") + convert_type: str = Field(..., description="转换类型(themoviedb/douban)") + + # 输出参数 + media_dict: dict = Field(default=dict, description="转换后的媒体信息(TheMovieDb/豆瓣)") diff --git a/app/schemas/types.py b/app/schemas/types.py index 05550f40..d9d0186e 100644 --- a/app/schemas/types.py +++ b/app/schemas/types.py @@ -81,6 +81,10 @@ class ChainEventType(Enum): ResourceSelection = "resource.selection" # 资源下载 ResourceDownload = "resource.download" + # 发现数据源 + DiscoverSource = "discover.source" + # 媒体识别转换 + MediaRecognizeConvert = "media.recognize.convert" # 系统配置Key字典