diff --git a/app/api/apiv1.py b/app/api/apiv1.py index 70646673..d2207d1f 100644 --- a/app/api/apiv1.py +++ b/app/api/apiv1.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from app.api.endpoints import login, user, site, message, webhook, subscribe, media, douban +from app.api.endpoints import login, user, site, message, webhook, subscribe, media, douban, search api_router = APIRouter() api_router.include_router(login.router, tags=["login"]) @@ -10,4 +10,5 @@ api_router.include_router(message.router, prefix="/message", tags=["message"]) api_router.include_router(webhook.router, prefix="/webhook", tags=["webhook"]) api_router.include_router(subscribe.router, prefix="/subscribe", tags=["subscribe"]) api_router.include_router(media.router, prefix="/media", tags=["media"]) +api_router.include_router(search.router, prefix="/search", tags=["search"]) api_router.include_router(douban.router, prefix="/douban", tags=["douban"]) diff --git a/app/api/endpoints/douban.py b/app/api/endpoints/douban.py index 46a3c2ca..ee511afb 100644 --- a/app/api/endpoints/douban.py +++ b/app/api/endpoints/douban.py @@ -1,7 +1,9 @@ +from typing import Any + from fastapi import APIRouter, Depends, BackgroundTasks from app import schemas -from app.chain.douban_sync import DoubanSyncChain +from app.chain.douban import DoubanChain from app.db.models.user import User from app.db.userauth import get_current_active_superuser @@ -12,15 +14,15 @@ def start_douban_chain(): """ 启动链式任务 """ - DoubanSyncChain().process() + DoubanChain().sync() @router.get("/sync", response_model=schemas.Response) async def sync_douban( background_tasks: BackgroundTasks, - _: User = Depends(get_current_active_superuser)): + _: User = Depends(get_current_active_superuser)) -> Any: """ - 查询所有订阅 + 同步豆瓣想看 """ background_tasks.add_task(start_douban_chain) return {"success": True} diff --git a/app/api/endpoints/media.py b/app/api/endpoints/media.py index 3ec888a7..0921fb3a 100644 --- a/app/api/endpoints/media.py +++ b/app/api/endpoints/media.py @@ -1,20 +1,59 @@ +from typing import List, Any + from fastapi import APIRouter, Depends from app import schemas -from app.chain.identify import IdentifyChain +from app.chain.media import MediaChain +from app.core.context import MediaInfo from app.db.models.user import User from app.db.userauth import get_current_active_user +from app.schemas.types import MediaType router = APIRouter() -@router.post("/recognize", response_model=schemas.Context) +@router.get("/recognize", response_model=schemas.Context) async def recognize(title: str, subtitle: str = None, - _: User = Depends(get_current_active_user)): + _: User = Depends(get_current_active_user)) -> Any: """ 识别媒体信息 """ # 识别媒体信息 - context = IdentifyChain().process(title=title, subtitle=subtitle) + context = MediaChain().recognize_by_title(title=title, subtitle=subtitle) return context.to_dict() + + +@router.get("/tmdb", response_model=schemas.MediaInfo) +async def tmdb_info(tmdbid: int, type_name: str) -> Any: + """ + 根据TMDBID查询媒体信息 + """ + mtype = MediaType.MOVIE if type_name == MediaType.MOVIE.value else MediaType.TV + media = MediaChain().recognize_media(tmdbid=tmdbid, mtype=mtype) + if media: + return media.to_dict() + else: + return schemas.MediaInfo() + + +@router.get("/douban", response_model=schemas.MediaInfo) +async def douban_info(doubanid: str) -> Any: + """ + 根据豆瓣ID查询豆瓣媒体信息 + """ + doubaninfo = MediaChain().douban_info(doubanid=doubanid) + if doubaninfo: + return MediaInfo(douban_info=doubaninfo).to_dict() + else: + return schemas.MediaInfo() + + +@router.get("/search", response_model=List[schemas.MediaInfo]) +async def search_by_title(title: str, + _: User = Depends(get_current_active_user)) -> Any: + """ + 搜索媒体信息 + """ + _, medias = MediaChain().search(title=title) + return [media.to_dict() for media in medias] diff --git a/app/api/endpoints/message.py b/app/api/endpoints/message.py index 96b34dd1..0900f26b 100644 --- a/app/api/endpoints/message.py +++ b/app/api/endpoints/message.py @@ -5,7 +5,7 @@ from fastapi import Request from starlette.responses import PlainTextResponse from app import schemas -from app.chain.user_message import UserMessageChain +from app.chain.message import MessageChain from app.core.config import settings from app.log import logger from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt @@ -17,7 +17,7 @@ def start_message_chain(body: Any, form: Any, args: Any): """ 启动链式任务 """ - UserMessageChain().process(body=body, form=form, args=args) + MessageChain().process(body=body, form=form, args=args) @router.post("/", response_model=schemas.Response) @@ -33,7 +33,8 @@ async def user_message(background_tasks: BackgroundTasks, request: Request): @router.get("/") -async def wechat_verify(echostr: str, msg_signature: str, timestamp: Union[str, int], nonce: str): +async def wechat_verify(echostr: str, msg_signature: str, + timestamp: Union[str, int], nonce: str) -> Any: """ 用户消息响应 """ diff --git a/app/api/endpoints/search.py b/app/api/endpoints/search.py new file mode 100644 index 00000000..d8d15374 --- /dev/null +++ b/app/api/endpoints/search.py @@ -0,0 +1,24 @@ +from typing import List, Any + +from fastapi import APIRouter, Depends + +from app import schemas +from app.chain.search import SearchChain +from app.db.models.user import User +from app.db.userauth import get_current_active_user +from app.schemas.types import MediaType + +router = APIRouter() + + +@router.get("/tmdbid", response_model=List[schemas.Context]) +async def search_by_tmdbid(tmdbid: int, + mtype: str = None, + _: User = Depends(get_current_active_user)) -> Any: + """ + 根据TMDBID搜索资源 + """ + if mtype: + mtype = MediaType.TV if mtype == MediaType.TV.value else MediaType.MOVIE + torrents = SearchChain().search_by_tmdbid(tmdbid=tmdbid, mtype=mtype) + return [torrent.to_dict() for torrent in torrents] diff --git a/app/api/endpoints/site.py b/app/api/endpoints/site.py index 7ad5a2e2..4ab6dac4 100644 --- a/app/api/endpoints/site.py +++ b/app/api/endpoints/site.py @@ -43,7 +43,7 @@ async def update_site( @router.get("/cookiecloud", response_model=schemas.Response) -async def cookie_cloud_sync(_: User = Depends(get_current_active_user)) -> dict: +async def cookie_cloud_sync(_: User = Depends(get_current_active_user)) -> Any: """ 运行CookieCloud同步站点信息 """ diff --git a/app/api/endpoints/subscribe.py b/app/api/endpoints/subscribe.py index a728e74f..bc7bfcb4 100644 --- a/app/api/endpoints/subscribe.py +++ b/app/api/endpoints/subscribe.py @@ -20,14 +20,14 @@ def start_subscribe_chain(title: str, year: str, """ 启动订阅链式任务 """ - SubscribeChain().process(title=title, year=year, - mtype=mtype, tmdbid=tmdbid, season=season, username=username) + SubscribeChain().add(title=title, year=year, + mtype=mtype, tmdbid=tmdbid, season=season, username=username) @router.get("/", response_model=List[schemas.Subscribe]) async def read_subscribes( db: Session = Depends(get_db), - _: User = Depends(get_current_active_superuser)): + _: User = Depends(get_current_active_superuser)) -> Any: """ 查询所有订阅 """ @@ -43,7 +43,7 @@ async def create_subscribe( """ 新增订阅 """ - result = SubscribeChain().process(**subscribe_in.dict()) + result = SubscribeChain().add(**subscribe_in.dict()) return {"success": result} @@ -83,7 +83,7 @@ async def delete_subscribe( @router.post("/seerr", response_model=schemas.Response) async def seerr_subscribe(request: Request, background_tasks: BackgroundTasks, - authorization: str = Header(None)): + authorization: str = Header(None)) -> Any: """ Jellyseerr/Overseerr订阅 """ @@ -136,7 +136,7 @@ async def seerr_subscribe(request: Request, background_tasks: BackgroundTasks, @router.get("/refresh", response_model=schemas.Response) async def refresh_subscribes( - _: User = Depends(get_current_active_superuser)): + _: User = Depends(get_current_active_superuser)) -> Any: """ 刷新所有订阅 """ @@ -146,7 +146,7 @@ async def refresh_subscribes( @router.get("/search", response_model=schemas.Response) async def search_subscribes( - _: User = Depends(get_current_active_superuser)): + _: User = Depends(get_current_active_superuser)) -> Any: """ 搜索所有订阅 """ diff --git a/app/api/endpoints/webhook.py b/app/api/endpoints/webhook.py index 32acb11c..1869345e 100644 --- a/app/api/endpoints/webhook.py +++ b/app/api/endpoints/webhook.py @@ -3,7 +3,7 @@ from typing import Any from fastapi import APIRouter, BackgroundTasks, Request from app import schemas -from app.chain.webhook_message import WebhookMessageChain +from app.chain.webhook import WebhookChain from app.core.config import settings router = APIRouter() @@ -13,11 +13,12 @@ def start_webhook_chain(body: Any, form: Any, args: Any): """ 启动链式任务 """ - WebhookMessageChain().process(body=body, form=form, args=args) + WebhookChain().message(body=body, form=form, args=args) @router.post("/", response_model=schemas.Response) -async def webhook_message(background_tasks: BackgroundTasks, token: str, request: Request): +async def webhook_message(background_tasks: BackgroundTasks, + token: str, request: Request) -> Any: """ Webhook响应 """ diff --git a/app/api/servarr.py b/app/api/servarr.py index c34839ea..baadd1c5 100644 --- a/app/api/servarr.py +++ b/app/api/servarr.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, HTTPException, Depends from requests import Session from app import schemas -from app.chain.identify import IdentifyChain +from app.chain.media import MediaChain from app.chain.subscribe import SubscribeChain from app.core.config import settings from app.core.metainfo import MetaInfo @@ -232,11 +232,11 @@ async def arr_movie_lookup(apikey: str, term: str, db: Session = Depends(get_db) ) tmdbid = term.replace("tmdb:", "") # 查询媒体信息 - mediainfo = IdentifyChain().recognize_media(mtype=MediaType.MOVIE, tmdbid=int(tmdbid)) + mediainfo = MediaChain().recognize_media(mtype=MediaType.MOVIE, tmdbid=int(tmdbid)) if not mediainfo: return [RadarrMovie()] # 查询是否已存在 - exists = IdentifyChain().media_exists(mediainfo=mediainfo) + exists = MediaChain().media_exists(mediainfo=mediainfo) if not exists: # 文件不存在 hasfile = False @@ -311,11 +311,11 @@ async def arr_add_movie(apikey: str, movie: RadarrMovie) -> Any: status_code=403, detail="认证失败!", ) - sid = SubscribeChain().process(title=movie.title, - year=movie.year, - mtype=MediaType.MOVIE, - tmdbid=movie.tmdbId, - userid="Seerr") + sid = SubscribeChain().add(title=movie.title, + year=movie.year, + mtype=MediaType.MOVIE, + tmdbid=movie.tmdbId, + userid="Seerr") if sid: return { "id": sid @@ -501,29 +501,29 @@ async def arr_series_lookup(apikey: str, term: str, db: Session = Depends(get_db ) # 查询TMDB媒体信息 if not term.startswith("tvdb:"): - mediainfo = IdentifyChain().recognize_media(meta=MetaInfo(term), - mtype=MediaType.MOVIE) + mediainfo = MediaChain().recognize_media(meta=MetaInfo(term), + mtype=MediaType.MOVIE) if not mediainfo: return [SonarrSeries()] tvdbid = mediainfo.tvdb_id tmdbid = mediainfo.tmdb_id else: tvdbid = int(term.replace("tvdb:", "")) - mediainfo = IdentifyChain().recognize_media(mtype=MediaType.MOVIE, - tmdbid=tvdbid) + mediainfo = MediaChain().recognize_media(mtype=MediaType.MOVIE, + tmdbid=tvdbid) if not mediainfo: return [SonarrSeries()] tmdbid = mediainfo.tmdb_id # 查询TVDB季信息 seas: List[int] = [] if tvdbid: - tvdbinfo = IdentifyChain().tvdb_info(tvdbid=tvdbid) + tvdbinfo = MediaChain().tvdb_info(tvdbid=tvdbid) if tvdbinfo: sea_num = tvdbinfo.get('season') if sea_num: seas = list(range(1, int(sea_num) + 1)) # 查询是否存在 - exists = IdentifyChain().media_exists(mediainfo) + exists = MediaChain().media_exists(mediainfo) if exists: hasfile = True else: @@ -629,12 +629,12 @@ async def arr_add_series(apikey: str, tv: schemas.SonarrSeries) -> Any: for season in tv.seasons: if not season.get("monitored"): continue - sid = SubscribeChain().process(title=tv.title, - year=tv.year, - season=season.get("seasonNumber"), - tmdbid=tv.tmdbId, - mtype=MediaType.TV, - userid="Seerr") + sid = SubscribeChain().add(title=tv.title, + year=tv.year, + season=season.get("seasonNumber"), + tmdbid=tv.tmdbId, + mtype=MediaType.TV, + userid="Seerr") if sid: return { diff --git a/app/chain/__init__.py b/app/chain/__init__.py index d7221033..220e9cf5 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -1,19 +1,18 @@ import traceback -from abc import abstractmethod from pathlib import Path from typing import Optional, Any, Tuple, List, Set, Union, Dict from ruamel.yaml import CommentedMap from app.core.context import Context -from app.core.event import EventManager -from app.core.module import ModuleManager from app.core.context import MediaInfo, TorrentInfo +from app.core.event import EventManager from app.core.meta import MetaBase +from app.core.module import ModuleManager from app.log import logger -from app.schemas.context import TransferInfo, TransferTorrent, ExistMediaInfo, DownloadingTorrent -from app.utils.singleton import AbstractSingleton, Singleton +from app.schemas import TransferInfo, TransferTorrent, ExistMediaInfo, DownloadingTorrent from app.schemas.types import TorrentStatus, MediaType +from app.utils.singleton import AbstractSingleton, Singleton class ChainBase(AbstractSingleton, metaclass=Singleton): @@ -28,14 +27,7 @@ class ChainBase(AbstractSingleton, metaclass=Singleton): self.modulemanager = ModuleManager() self.eventmanager = EventManager() - @abstractmethod - def process(self, *args, **kwargs) -> Optional[Context]: - """ - 处理链,返回上下文 - """ - pass - - def run_module(self, method: str, *args, **kwargs) -> Any: + def __run_module(self, method: str, *args, **kwargs) -> Any: """ 运行包含该方法的所有模块,然后返回结果 """ @@ -69,84 +61,84 @@ class ChainBase(AbstractSingleton, metaclass=Singleton): def prepare_recognize(self, title: str, subtitle: str = None) -> Tuple[str, str]: - return self.run_module("prepare_recognize", title=title, subtitle=subtitle) + return self.__run_module("prepare_recognize", title=title, subtitle=subtitle) def recognize_media(self, meta: MetaBase = None, mtype: MediaType = None, tmdbid: int = None) -> Optional[MediaInfo]: - return self.run_module("recognize_media", meta=meta, mtype=mtype, tmdbid=tmdbid) + return self.__run_module("recognize_media", meta=meta, mtype=mtype, tmdbid=tmdbid) def obtain_image(self, mediainfo: MediaInfo) -> Optional[MediaInfo]: - return self.run_module("obtain_image", mediainfo=mediainfo) + return self.__run_module("obtain_image", mediainfo=mediainfo) def douban_info(self, doubanid: str) -> Optional[dict]: - return self.run_module("douban_info", doubanid=doubanid) + return self.__run_module("douban_info", doubanid=doubanid) def tvdb_info(self, tvdbid: int) -> Optional[dict]: - return self.run_module("tvdb_info", tvdbid=tvdbid) + return self.__run_module("tvdb_info", tvdbid=tvdbid) def message_parser(self, body: Any, form: Any, args: Any) -> Optional[dict]: - return self.run_module("message_parser", body=body, form=form, args=args) + return self.__run_module("message_parser", body=body, form=form, args=args) def webhook_parser(self, body: Any, form: Any, args: Any) -> Optional[dict]: - return self.run_module("webhook_parser", body=body, form=form, args=args) + return self.__run_module("webhook_parser", body=body, form=form, args=args) def search_medias(self, meta: MetaBase) -> Optional[List[MediaInfo]]: - return self.run_module("search_medias", meta=meta) + return self.__run_module("search_medias", meta=meta) def search_torrents(self, mediainfo: Optional[MediaInfo], sites: List[CommentedMap], keyword: str = None) -> Optional[List[TorrentInfo]]: - return self.run_module("search_torrents", mediainfo=mediainfo, sites=sites, keyword=keyword) + return self.__run_module("search_torrents", mediainfo=mediainfo, sites=sites, keyword=keyword) def refresh_torrents(self, sites: List[CommentedMap]) -> Optional[List[TorrentInfo]]: - return self.run_module("refresh_torrents", sites=sites) + return self.__run_module("refresh_torrents", sites=sites) def filter_torrents(self, torrent_list: List[TorrentInfo], season_episodes: Dict[int, list] = None) -> List[TorrentInfo]: - return self.run_module("filter_torrents", torrent_list=torrent_list, season_episodes=season_episodes) + return self.__run_module("filter_torrents", torrent_list=torrent_list, season_episodes=season_episodes) def download(self, torrent_path: Path, cookie: str, episodes: Set[int] = None) -> Optional[Tuple[Optional[str], str]]: - return self.run_module("download", torrent_path=torrent_path, cookie=cookie, episodes=episodes) + return self.__run_module("download", torrent_path=torrent_path, cookie=cookie, episodes=episodes) def download_added(self, context: Context, torrent_path: Path) -> None: - return self.run_module("download_added", context=context, torrent_path=torrent_path) + return self.__run_module("download_added", context=context, torrent_path=torrent_path) def list_torrents(self, status: TorrentStatus = None, hashs: Union[list, str] = None) -> Optional[List[Union[TransferTorrent, DownloadingTorrent]]]: - return self.run_module("list_torrents", status=status, hashs=hashs) + return self.__run_module("list_torrents", status=status, hashs=hashs) def transfer(self, path: Path, mediainfo: MediaInfo) -> Optional[TransferInfo]: - return self.run_module("transfer", path=path, mediainfo=mediainfo) + return self.__run_module("transfer", path=path, mediainfo=mediainfo) def transfer_completed(self, hashs: Union[str, list], transinfo: TransferInfo) -> None: - return self.run_module("transfer_completed", hashs=hashs, transinfo=transinfo) + return self.__run_module("transfer_completed", hashs=hashs, transinfo=transinfo) def remove_torrents(self, hashs: Union[str, list]) -> bool: - return self.run_module("remove_torrents", hashs=hashs) + return self.__run_module("remove_torrents", hashs=hashs) def media_exists(self, mediainfo: MediaInfo) -> Optional[ExistMediaInfo]: - return self.run_module("media_exists", mediainfo=mediainfo) + return self.__run_module("media_exists", mediainfo=mediainfo) def refresh_mediaserver(self, mediainfo: MediaInfo, file_path: Path) -> Optional[bool]: - return self.run_module("refresh_mediaserver", mediainfo=mediainfo, file_path=file_path) + return self.__run_module("refresh_mediaserver", mediainfo=mediainfo, file_path=file_path) def post_message(self, title: str, text: str = None, image: str = None, userid: Union[str, int] = None) -> Optional[bool]: - return self.run_module("post_message", title=title, text=text, image=image, userid=userid) + return self.__run_module("post_message", title=title, text=text, image=image, userid=userid) def post_medias_message(self, title: str, items: List[MediaInfo], userid: Union[str, int] = None) -> Optional[bool]: - return self.run_module("post_medias_message", title=title, items=items, userid=userid) + return self.__run_module("post_medias_message", title=title, items=items, userid=userid) def post_torrents_message(self, title: str, items: List[Context], mediainfo: MediaInfo, userid: Union[str, int] = None) -> Optional[bool]: - return self.run_module("post_torrents_message", title=title, mediainfo=mediainfo, - items=items, userid=userid) + return self.__run_module("post_torrents_message", title=title, mediainfo=mediainfo, + items=items, userid=userid) def scrape_metadata(self, path: Path, mediainfo: MediaInfo) -> None: - return self.run_module("scrape_metadata", path=path, mediainfo=mediainfo) + return self.__run_module("scrape_metadata", path=path, mediainfo=mediainfo) def register_commands(self, commands: dict) -> None: - return self.run_module("register_commands", commands=commands) + return self.__run_module("register_commands", commands=commands) diff --git a/app/chain/douban_sync.py b/app/chain/douban.py similarity index 87% rename from app/chain/douban_sync.py rename to app/chain/douban.py index 85823f3c..c6d2ec8d 100644 --- a/app/chain/douban_sync.py +++ b/app/chain/douban.py @@ -12,7 +12,7 @@ from app.helper.rss import RssHelper from app.log import logger -class DoubanSyncChain(ChainBase): +class DoubanChain(ChainBase): """ 同步豆瓣想看数据 """ @@ -28,7 +28,7 @@ class DoubanSyncChain(ChainBase): self.searchchain = SearchChain() self.subscribechain = SubscribeChain() - def process(self): + def sync(self): """ 通过用户RSS同步豆瓣想看数据 """ @@ -80,8 +80,7 @@ class DoubanSyncChain(ChainBase): continue logger.info(f'{mediainfo.title_year} 媒体库中不存在,开始搜索 ...') # 搜索 - contexts = self.searchchain.process(meta=meta, - mediainfo=mediainfo, + contexts = self.searchchain.process(mediainfo=mediainfo, no_exists=no_exists) if not contexts: logger.warn(f'{mediainfo.title_year} 未搜索到资源') @@ -95,12 +94,12 @@ class DoubanSyncChain(ChainBase): # 未完成下载 logger.info(f'{mediainfo.title_year} 未下载未完整,添加订阅 ...') # 添加订阅 - self.subscribechain.process(title=mediainfo.title, - year=mediainfo.year, - mtype=mediainfo.type, - tmdbid=mediainfo.tmdb_id, - season=meta.begin_season, - username="豆瓣想看") + self.subscribechain.add(title=mediainfo.title, + year=mediainfo.year, + mtype=mediainfo.type, + tmdbid=mediainfo.tmdb_id, + season=meta.begin_season, + username="豆瓣想看") logger.info(f"用户 {user_id} 豆瓣想看同步完成") # 保存缓存 diff --git a/app/chain/download.py b/app/chain/download.py index e4b48157..cfca2f35 100644 --- a/app/chain/download.py +++ b/app/chain/download.py @@ -8,7 +8,7 @@ from app.core.meta import MetaBase from app.db.downloadhistory_oper import DownloadHistoryOper from app.helper.torrent import TorrentHelper from app.log import logger -from app.schemas.context import ExistMediaInfo, NotExistMediaInfo +from app.schemas import ExistMediaInfo, NotExistMediaInfo from app.schemas.types import MediaType, TorrentStatus, EventType from app.utils.string import StringUtils @@ -20,9 +20,6 @@ class DownloadChain(ChainBase): self.torrent = TorrentHelper() self.downloadhis = DownloadHistoryOper() - def process(self, *args, **kwargs) -> Optional[Context]: - pass - def post_download_message(self, meta: MetaBase, mediainfo: MediaInfo, torrent: TorrentInfo, userid: str = None): """ 发送添加下载的消息 diff --git a/app/chain/identify.py b/app/chain/identify.py deleted file mode 100644 index 60574838..00000000 --- a/app/chain/identify.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import Optional - -from app.chain import ChainBase -from app.core.metainfo import MetaInfo -from app.core.context import Context, MediaInfo -from app.log import logger - - -class IdentifyChain(ChainBase): - """ - 识别处理链 - """ - - def process(self, title: str, subtitle: str = None) -> Optional[Context]: - """ - 识别媒体信息 - """ - logger.info(f'开始识别媒体信息,标题:{title},副标题:{subtitle} ...') - # 识别前预处理 - result: Optional[tuple] = self.prepare_recognize(title=title, subtitle=subtitle) - if result: - title, subtitle = result - # 识别元数据 - metainfo = MetaInfo(title, subtitle) - # 识别媒体信息 - mediainfo: MediaInfo = self.recognize_media(meta=metainfo) - if not mediainfo: - logger.warn(f'{title} 未识别到媒体信息') - return Context(meta=metainfo) - logger.info(f'{title} 识别到媒体信息:{mediainfo.type.value} {mediainfo.title_year}') - # 更新媒体图片 - self.obtain_image(mediainfo=mediainfo) - # 返回上下文 - return Context(meta=metainfo, mediainfo=mediainfo, title=title, subtitle=subtitle) diff --git a/app/chain/media.py b/app/chain/media.py new file mode 100644 index 00000000..18ab3fa5 --- /dev/null +++ b/app/chain/media.py @@ -0,0 +1,68 @@ +from typing import Optional, List, Tuple + +from app.chain import ChainBase +from app.core.context import Context, MediaInfo +from app.core.meta import MetaBase +from app.core.metainfo import MetaInfo +from app.log import logger +from app.utils.string import StringUtils + + +class MediaChain(ChainBase): + """ + 识别处理链 + """ + + def recognize_by_title(self, title: str, subtitle: str = None) -> Optional[Context]: + """ + 识别媒体信息 + """ + logger.info(f'开始识别媒体信息,标题:{title},副标题:{subtitle} ...') + # 识别前预处理 + result: Optional[tuple] = self.prepare_recognize(title=title, subtitle=subtitle) + if result: + title, subtitle = result + # 识别元数据 + metainfo = MetaInfo(title, subtitle) + # 识别媒体信息 + mediainfo: MediaInfo = self.recognize_media(meta=metainfo) + if not mediainfo: + logger.warn(f'{title} 未识别到媒体信息') + return Context(meta=metainfo) + logger.info(f'{title} 识别到媒体信息:{mediainfo.type.value} {mediainfo.title_year}') + # 更新媒体图片 + self.obtain_image(mediainfo=mediainfo) + # 返回上下文 + return Context(meta=metainfo, mediainfo=mediainfo, title=title, subtitle=subtitle) + + def search(self, title: str) -> Tuple[MetaBase, List[MediaInfo]]: + """ + 搜索媒体信息 + :param title: 搜索内容 + :return: 识别元数据,媒体信息列表 + """ + # 提取要素 + mtype, key_word, season_num, episode_num, year, content = StringUtils.get_keyword(title) + # 识别 + meta = MetaInfo(content) + if not meta.name: + logger.warn(f'{title} 未识别到元数据!') + return meta, [] + # 合并信息 + if mtype: + meta.type = mtype + if season_num: + meta.begin_season = season_num + if episode_num: + meta.begin_episode = episode_num + if year: + meta.year = year + # 开始搜索 + logger.info(f"开始搜索媒体信息:{meta.name}") + medias: Optional[List[MediaInfo]] = self.search_medias(meta=meta) + if not medias: + logger.warn(f"{meta.name} 没有找到对应的媒体信息!") + return meta, [] + logger.info(f"{content} 搜索到 {len(medias)} 条相关媒体信息") + # 识别的元数据,媒体信息列表 + return meta, medias diff --git a/app/chain/user_message.py b/app/chain/message.py similarity index 85% rename from app/chain/user_message.py rename to app/chain/message.py index 8d24d978..a47700d0 100644 --- a/app/chain/user_message.py +++ b/app/chain/message.py @@ -1,16 +1,16 @@ from typing import Any from app.chain.download import * +from app.chain.media import MediaChain from app.chain.search import SearchChain from app.chain.subscribe import SubscribeChain from app.core.context import MediaInfo from app.core.event import EventManager -from app.core.metainfo import MetaInfo from app.log import logger from app.schemas.types import EventType -class UserMessageChain(ChainBase): +class MessageChain(ChainBase): """ 外来消息处理链 """ @@ -30,6 +30,7 @@ class UserMessageChain(ChainBase): self.downloadchain = DownloadChain() self.subscribechain = SubscribeChain() self.searchchain = SearchChain() + self.medtachain = MediaChain() self.torrent = TorrentHelper() self.eventmanager = EventManager() @@ -93,16 +94,16 @@ class UserMessageChain(ChainBase): # 发送缺失的媒体信息 if no_exists: # 发送消息 - messages = [f"第 {no_exist.get('season')} 季缺失 {len(no_exist.get('episodes')) or no_exist.get('total_episodes')} 集" - for no_exist in no_exists.get(mediainfo.tmdb_id)] + messages = [ + f"第 {no_exist.get('season')} 季缺失 {len(no_exist.get('episodes')) or no_exist.get('total_episodes')} 集" + for no_exist in no_exists.get(mediainfo.tmdb_id)] self.post_message(title=f"{mediainfo.title_year}:\n" + "\n".join(messages)) # 搜索种子,过滤掉不需要的剧集,以便选择 logger.info(f"{mediainfo.title_year} 媒体库中不存在,开始搜索 ...") self.post_message( title=f"开始搜索 {mediainfo.type.value} {mediainfo.title_year} ...", userid=userid) # 开始搜索 - contexts = self.searchchain.process(meta=self._current_meta, - mediainfo=mediainfo, + contexts = self.searchchain.process(mediainfo=mediainfo, no_exists=no_exists) if not contexts: # 没有数据 @@ -129,13 +130,13 @@ class UserMessageChain(ChainBase): elif cache_type == "Subscribe": # 订阅媒体 mediainfo: MediaInfo = cache_list[int(text) - 1] - self.subscribechain.process(title=mediainfo.title, - year=mediainfo.year, - mtype=mediainfo.type, - tmdbid=mediainfo.tmdb_id, - season=self._current_meta.begin_season, - userid=userid, - username=username) + self.subscribechain.add(title=mediainfo.title, + year=mediainfo.year, + mtype=mediainfo.type, + tmdbid=mediainfo.tmdb_id, + season=self._current_meta.begin_season, + userid=userid, + username=username) elif cache_type == "Torrent": if int(text) == 0: # 自动选择下载 @@ -158,13 +159,13 @@ class UserMessageChain(ChainBase): # 未完成下载 logger.info(f'{self._current_media.title_year} 未下载未完整,添加订阅 ...') # 添加订阅 - self.subscribechain.process(title=self._current_media.title, - year=self._current_media.year, - mtype=self._current_media.type, - tmdbid=self._current_media.tmdb_id, - season=self._current_meta.begin_season, - userid=userid, - username=username) + self.subscribechain.add(title=self._current_media.title, + year=self._current_media.year, + mtype=self._current_media.type, + tmdbid=self._current_media.tmdb_id, + season=self._current_meta.begin_season, + userid=userid, + username=username) else: # 下载种子 context: Context = cache_list[int(text) - 1] @@ -245,31 +246,19 @@ class UserMessageChain(ChainBase): # 搜索 content = re.sub(r"(搜索|下载)[::\s]*", "", text) action = "Search" - # 提取要素 - mtype, key_word, season_num, episode_num, year, title = StringUtils.get_keyword(content) + # 搜索 + meta, medias = self.medtachain.search(content) # 识别 - meta = MetaInfo(title) if not meta.name: self.post_message(title="无法识别输入内容!", userid=userid) return - # 合并信息 - if mtype: - meta.type = mtype - if season_num: - meta.begin_season = season_num - if episode_num: - meta.begin_episode = episode_num - if year: - meta.year = year - # 记录当前状态 - self._current_meta = meta # 开始搜索 - logger.info(f"开始搜索:{meta.name}") - medias: Optional[List[MediaInfo]] = self.search_medias(meta=meta) if not medias: self.post_message(title=f"{meta.name} 没有找到对应的媒体信息!", userid=userid) return logger.info(f"搜索到 {len(medias)} 条相关媒体信息") + # 记录当前状态 + self._current_meta = meta self._user_cache[userid] = { 'type': action, 'items': medias diff --git a/app/chain/search.py b/app/chain/search.py index df31e835..a6416cec 100644 --- a/app/chain/search.py +++ b/app/chain/search.py @@ -3,13 +3,12 @@ from typing import Optional, List, Dict from app.chain import ChainBase from app.core.config import settings from app.core.context import Context, MediaInfo, TorrentInfo -from app.core.meta import MetaBase from app.core.metainfo import MetaInfo from app.helper.sites import SitesHelper from app.log import logger -from app.schemas.context import NotExistMediaInfo -from app.utils.string import StringUtils +from app.schemas import NotExistMediaInfo from app.schemas.types import MediaType +from app.utils.string import StringUtils class SearchChain(ChainBase): @@ -21,12 +20,23 @@ class SearchChain(ChainBase): super().__init__() self.siteshelper = SitesHelper() - def process(self, meta: MetaBase, mediainfo: MediaInfo, + def search_by_tmdbid(self, tmdbid: int, mtype: str = None) -> Optional[List[Context]]: + """ + 根据TMDB ID搜索资源,不过滤本地存在的内容 + :param tmdbid: TMDB ID + :param mtype: 媒体,电影 or 电视剧 + """ + mediainfo = self.recognize_media(tmdbid=tmdbid, mtype=mtype) + if not mediainfo: + logger.error(f'{tmdbid} 媒体信息识别失败!') + return None + return self.process(mediainfo=mediainfo) + + def process(self, mediainfo: MediaInfo, keyword: str = None, no_exists: Dict[int, Dict[int, NotExistMediaInfo]] = None) -> Optional[List[Context]]: """ - 根据媒体信息,执行搜索 - :param meta: 元数据 + 根据媒体信息,搜索种子资源 :param mediainfo: 媒体信息 :param keyword: 搜索关键词 :param no_exists: 缺失的媒体信息 @@ -124,6 +134,6 @@ class SearchChain(ChainBase): _match_torrents = torrents logger.info(f"匹配完成,共匹配到 {len(_match_torrents)} 个资源") # 组装上下文返回 - return [Context(meta=MetaInfo(torrent.title), + return [Context(meta=MetaInfo(title=torrent.title, subtitle=torrent.description), mediainfo=mediainfo, torrentinfo=torrent) for torrent in _match_torrents] diff --git a/app/chain/site_message.py b/app/chain/site.py similarity index 97% rename from app/chain/site_message.py rename to app/chain/site.py index cb786ce3..a1639cb7 100644 --- a/app/chain/site_message.py +++ b/app/chain/site.py @@ -7,7 +7,7 @@ from app.helper.cookie import CookieHelper from app.log import logger -class SiteMessageChain(ChainBase): +class SiteChain(ChainBase): """ 站点远程管理处理链 """ @@ -20,7 +20,7 @@ class SiteMessageChain(ChainBase): self._siteoper = SiteOper() self._cookiehelper = CookieHelper() - def process(self, userid: Union[str, int] = None): + def list(self, userid: Union[str, int] = None): """ 查询所有站点,发送消息 """ @@ -63,7 +63,7 @@ class SiteMessageChain(ChainBase): "is_active": False }) # 重新发送消息 - self.process() + self.list() def enable(self, arg_str, userid: Union[str, int] = None): """ @@ -84,7 +84,7 @@ class SiteMessageChain(ChainBase): "is_active": True }) # 重新发送消息 - self.process() + self.list() def get_cookie(self, arg_str: str, userid: Union[str, int] = None): """ diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index 0c5cd7e3..c51adfc8 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -9,7 +9,7 @@ from app.core.config import settings from app.db.subscribe_oper import SubscribeOper from app.helper.sites import SitesHelper from app.log import logger -from app.schemas.context import NotExistMediaInfo +from app.schemas import NotExistMediaInfo from app.utils.string import StringUtils from app.schemas.types import MediaType @@ -29,13 +29,13 @@ class SubscribeChain(ChainBase): self.subscribehelper = SubscribeOper() self.siteshelper = SitesHelper() - def process(self, title: str, year: str, - mtype: MediaType = None, - tmdbid: int = None, - season: int = None, - userid: str = None, - username: str = None, - **kwargs) -> Optional[int]: + def add(self, title: str, year: str, + mtype: MediaType = None, + tmdbid: int = None, + season: int = None, + userid: str = None, + username: str = None, + **kwargs) -> Optional[int]: """ 识别媒体信息并添加订阅 """ @@ -153,8 +153,7 @@ class SubscribeChain(ChainBase): ) # 搜索 - contexts = self.searchchain.process(meta=meta, - mediainfo=mediainfo, + contexts = self.searchchain.process(mediainfo=mediainfo, keyword=subscribe.keyword, no_exists=no_exists) if not contexts: diff --git a/app/chain/transfer.py b/app/chain/transfer.py index e1a3164a..b8433237 100644 --- a/app/chain/transfer.py +++ b/app/chain/transfer.py @@ -9,7 +9,7 @@ from app.core.metainfo import MetaInfo from app.db.downloadhistory_oper import DownloadHistoryOper from app.db.models.downloadhistory import DownloadHistory from app.log import logger -from app.schemas.context import TransferInfo, TransferTorrent +from app.schemas import TransferInfo, TransferTorrent from app.schemas.types import TorrentStatus, EventType, MediaType from app.utils.string import StringUtils diff --git a/app/chain/webhook_message.py b/app/chain/webhook.py similarity index 97% rename from app/chain/webhook_message.py rename to app/chain/webhook.py index 85afd16c..72dba888 100644 --- a/app/chain/webhook_message.py +++ b/app/chain/webhook.py @@ -6,12 +6,12 @@ from app.utils.http import WebUtils from app.schemas.types import EventType -class WebhookMessageChain(ChainBase): +class WebhookChain(ChainBase): """ 响应Webhook事件 """ - def process(self, body: Any, form: Any, args: Any) -> None: + def message(self, body: Any, form: Any, args: Any) -> None: """ 处理Webhook报文并发送消息 """ diff --git a/app/command.py b/app/command.py index 31012cc4..f25b60cd 100644 --- a/app/command.py +++ b/app/command.py @@ -4,9 +4,9 @@ from typing import Any, Union from app.chain import ChainBase from app.chain.cookiecloud import CookieCloudChain -from app.chain.douban_sync import DoubanSyncChain +from app.chain.douban import DoubanChain from app.chain.download import DownloadChain -from app.chain.site_message import SiteMessageChain +from app.chain.site import SiteChain from app.chain.subscribe import SubscribeChain from app.chain.transfer import TransferChain from app.core.event import eventmanager, EventManager @@ -50,27 +50,27 @@ class Command(metaclass=Singleton): "data": {} }, "/sites": { - "func": SiteMessageChain().process, + "func": SiteChain().list, "description": "查询站点", "data": {} }, "/site_cookie": { - "func": SiteMessageChain().get_cookie, + "func": SiteChain().get_cookie, "description": "更新站点Cookie", "data": {} }, "/site_enable": { - "func": SiteMessageChain().enable, + "func": SiteChain().enable, "description": "启用站点", "data": {} }, "/site_disable": { - "func": SiteMessageChain().disable, + "func": SiteChain().disable, "description": "禁用站点", "data": {} }, "/douban_sync": { - "func": DoubanSyncChain().process, + "func": DoubanChain().sync, "description": "同步豆瓣想看", "data": {} }, diff --git a/app/core/context.py b/app/core/context.py index 7fc58563..016dd6bb 100644 --- a/app/core/context.py +++ b/app/core/context.py @@ -1,4 +1,5 @@ -from typing import Optional, Any, List +import re +from typing import Optional, Any, List, Dict from app.core.config import settings from app.core.meta import MetaBase @@ -121,7 +122,7 @@ class MediaInfo: # 所有别名和译名 names: Optional[list] = [] # 各季的剧集清单信息 - seasons: Optional[dict] = {} + seasons: Optional[Dict[int, list]] = {} # 各季的年份 season_years: Optional[dict] = {} # 二级分类 @@ -302,44 +303,61 @@ class MediaInfo: self.douban_info = info # 豆瓣ID self.douban_id = str(info.get("id")) + # 类型 + if not self.type: + self.type = MediaType.MOVIE if info.get("type") == "movie" else MediaType.TV + # 标题 + if not self.title: + self.title = MetaInfo(info.get("title")).name + # 原语种标题 + if not self.original_title: + self.original_title = info.get("original_title") + # 年份 + if not self.year: + self.year = info.get("year")[:4] if info.get("year") else None # 评分 if not self.vote_average: - rating = info.get('rating') + rating = info.get("rating") if rating: vote_average = float(rating.get("value")) else: vote_average = 0 self.vote_average = vote_average - # 标题 - if not self.title: - self.title = info.get('title') - # 年份 - if not self.year: - self.year = info.get('year')[:4] if info.get('year') else None - # 原语种标题 - if not self.original_title: - self.original_title = info.get("original_title") - # 类型 - if not self.type: - self.type = MediaType.MOVIE if info.get("type") == "movie" else MediaType.TV + # 发行日期 + if not self.release_date: + if info.get("release_date"): + self.release_date = info.get("release_date") + elif info.get("pubdate") and isinstance(info.get("pubdate"), list): + release_date = info.get("pubdate")[0] + if release_date: + match = re.search(r'\d{4}-\d{2}-\d{2}', release_date) + if match: + self.release_date = match.group() + # 海报 if not self.poster_path: - if self.type == MediaType.MOVIE: - # 海报 - poster_path = info.get('cover', {}).get("url") - if not poster_path: - poster_path = info.get('cover_url') - if not poster_path: - poster_path = info.get('pic', {}).get("large") - else: - # 海报 - poster_path = info.get('pic', {}).get("normal") - self.poster_path = poster_path + self.poster_path = info.get("pic", {}).get("large") + if not self.poster_path and info.get("cover_url"): + self.poster_path = info.get("cover_url") + if self.poster_path: + self.poster_path = self.poster_path.replace("m_ratio_poster", "l_ratio_poster") # 简介 if not self.overview: - overview = info.get("card_subtitle") or "" - if not self.year and overview: - if overview.split("/")[0].strip().isdigit(): - self.year = overview.split("/")[0].strip() + self.overview = info.get("intro") or info.get("card_subtitle") or "" + # 导演和演员 + if not self.directors: + self.directors = info.get("directors") or [] + if not self.actors: + self.actors = info.get("actors") or [] + # 别名 + if not self.names: + self.names = info.get("aka") or [] + # 剧集 + if self.type == MediaType.TV and not self.seasons: + meta = MetaInfo(info.get("title")) + if meta.begin_season: + episodes_count = info.get("episodes_count") + if episodes_count: + self.seasons[meta.begin_season] = list(range(1, episodes_count + 1)) @property def title_year(self): @@ -420,6 +438,20 @@ class MediaInfo: return [] return self.seasons.get(sea) or [] + def to_dict(self): + """ + 返回字典 + """ + attributes = [ + attr for attr in dir(self) + if not callable(getattr(self, attr)) and not attr.startswith("_") + ] + return { + attr: getattr(self, attr).value + if isinstance(getattr(self, attr), MediaType) + else getattr(self, attr) for attr in attributes + } + class Context: """ @@ -505,5 +537,6 @@ class Context: return { "meta_info": object_to_dict(self.meta_info), - "media_info": object_to_dict(self.media_info) + "media_info": object_to_dict(self.media_info), + "torrent_info": object_to_dict(self.torrent_info) } diff --git a/app/modules/__init__.py b/app/modules/__init__.py index 9ec2bbd6..a7b85103 100644 --- a/app/modules/__init__.py +++ b/app/modules/__init__.py @@ -6,7 +6,7 @@ from ruamel.yaml import CommentedMap from app.core.context import MediaInfo, TorrentInfo, Context from app.core.meta import MetaBase -from app.schemas.context import TransferInfo, TransferTorrent, ExistMediaInfo, DownloadingTorrent +from app.schemas import TransferInfo, TransferTorrent, ExistMediaInfo, DownloadingTorrent from app.schemas.types import TorrentStatus, MediaType diff --git a/app/modules/emby/__init__.py b/app/modules/emby/__init__.py index fac798da..06fe2d42 100644 --- a/app/modules/emby/__init__.py +++ b/app/modules/emby/__init__.py @@ -5,7 +5,7 @@ from app.core.context import MediaInfo from app.log import logger from app.modules import _ModuleBase from app.modules.emby.emby import Emby -from app.schemas.context import ExistMediaInfo, RefreshMediaItem +from app.schemas import ExistMediaInfo, RefreshMediaItem from app.schemas.types import MediaType diff --git a/app/modules/emby/emby.py b/app/modules/emby/emby.py index 796dae72..852a73e4 100644 --- a/app/modules/emby/emby.py +++ b/app/modules/emby/emby.py @@ -5,7 +5,7 @@ from typing import List, Optional, Union, Dict from app.core.config import settings from app.log import logger -from app.schemas.context import RefreshMediaItem +from app.schemas import RefreshMediaItem from app.utils.http import RequestUtils from app.utils.singleton import Singleton from app.utils.string import StringUtils diff --git a/app/modules/filetransfer/__init__.py b/app/modules/filetransfer/__init__.py index 45fa08b9..1add8bb3 100644 --- a/app/modules/filetransfer/__init__.py +++ b/app/modules/filetransfer/__init__.py @@ -11,7 +11,7 @@ from app.core.config import settings from app.core.meta import MetaBase from app.log import logger from app.modules import _ModuleBase -from app.schemas.context import TransferInfo +from app.schemas import TransferInfo from app.utils.system import SystemUtils from app.schemas.types import MediaType diff --git a/app/modules/jellyfin/__init__.py b/app/modules/jellyfin/__init__.py index 1c60a171..97c51080 100644 --- a/app/modules/jellyfin/__init__.py +++ b/app/modules/jellyfin/__init__.py @@ -6,7 +6,7 @@ from app.core.context import MediaInfo from app.log import logger from app.modules import _ModuleBase from app.modules.jellyfin.jellyfin import Jellyfin -from app.schemas.context import ExistMediaInfo +from app.schemas import ExistMediaInfo from app.schemas.types import MediaType diff --git a/app/modules/plex/__init__.py b/app/modules/plex/__init__.py index 287fee52..b8311dc8 100644 --- a/app/modules/plex/__init__.py +++ b/app/modules/plex/__init__.py @@ -5,7 +5,7 @@ from app.core.context import MediaInfo from app.log import logger from app.modules import _ModuleBase from app.modules.plex.plex import Plex -from app.schemas.context import ExistMediaInfo, RefreshMediaItem +from app.schemas import ExistMediaInfo, RefreshMediaItem from app.schemas.types import MediaType diff --git a/app/modules/plex/plex.py b/app/modules/plex/plex.py index 68b6ab88..1bec1b0d 100644 --- a/app/modules/plex/plex.py +++ b/app/modules/plex/plex.py @@ -8,7 +8,7 @@ from plexapi.server import PlexServer from app.core.config import settings from app.log import logger -from app.schemas.context import RefreshMediaItem +from app.schemas import RefreshMediaItem from app.utils.singleton import Singleton diff --git a/app/modules/qbittorrent/__init__.py b/app/modules/qbittorrent/__init__.py index 3222ad54..30e71f6a 100644 --- a/app/modules/qbittorrent/__init__.py +++ b/app/modules/qbittorrent/__init__.py @@ -6,7 +6,7 @@ from app.core.metainfo import MetaInfo from app.log import logger from app.modules import _ModuleBase from app.modules.qbittorrent.qbittorrent import Qbittorrent -from app.schemas.context import TransferInfo, TransferTorrent, DownloadingTorrent +from app.schemas import TransferInfo, TransferTorrent, DownloadingTorrent from app.utils.string import StringUtils from app.schemas.types import TorrentStatus diff --git a/app/modules/themoviedb/tmdb.py b/app/modules/themoviedb/tmdb.py index d3c15553..10a9c25a 100644 --- a/app/modules/themoviedb/tmdb.py +++ b/app/modules/themoviedb/tmdb.py @@ -539,13 +539,13 @@ class TmdbHelper: if tmdb_info: tmdb_info['media_type'] = MediaType.TV else: - tmdb_info = self.__get_movie_detail(tmdbid) + tmdb_info = self.__get_tv_detail(tmdbid) if tmdb_info: - tmdb_info['media_type'] = MediaType.MOVIE + tmdb_info['media_type'] = MediaType.TV else: - tmdb_info = self.__get_tv_detail(tmdbid) + tmdb_info = self.__get_movie_detail(tmdbid) if tmdb_info: - tmdb_info['media_type'] = MediaType.TV + tmdb_info['media_type'] = MediaType.MOVIE if tmdb_info: # 转换genreid diff --git a/app/modules/transmission/__init__.py b/app/modules/transmission/__init__.py index c0cf08bf..4c15de98 100644 --- a/app/modules/transmission/__init__.py +++ b/app/modules/transmission/__init__.py @@ -6,7 +6,7 @@ from app.core.metainfo import MetaInfo from app.log import logger from app.modules import _ModuleBase from app.modules.transmission.transmission import Transmission -from app.schemas.context import TransferInfo, TransferTorrent, DownloadingTorrent +from app.schemas import TransferInfo, TransferTorrent, DownloadingTorrent from app.schemas.types import TorrentStatus diff --git a/app/plugins/autosignin/sites/btschool.py b/app/plugins/autosignin/sites/btschool.py index 80f2f5a3..b1d08145 100644 --- a/app/plugins/autosignin/sites/btschool.py +++ b/app/plugins/autosignin/sites/btschool.py @@ -2,7 +2,6 @@ from typing import Tuple from ruamel.yaml import CommentedMap -from app.core.config import settings from app.log import logger from app.plugins.autosignin.sites import _ISiteSigninHandler from app.utils.string import StringUtils diff --git a/app/plugins/autosignin/sites/haidan.py b/app/plugins/autosignin/sites/haidan.py index c4d22b20..2883f74c 100644 --- a/app/plugins/autosignin/sites/haidan.py +++ b/app/plugins/autosignin/sites/haidan.py @@ -2,7 +2,6 @@ from typing import Tuple from ruamel.yaml import CommentedMap -from app.core.config import settings from app.log import logger from app.plugins.autosignin.sites import _ISiteSigninHandler from app.utils.string import StringUtils diff --git a/app/plugins/autosignin/sites/hdcity.py b/app/plugins/autosignin/sites/hdcity.py index ff433ad7..e924776e 100644 --- a/app/plugins/autosignin/sites/hdcity.py +++ b/app/plugins/autosignin/sites/hdcity.py @@ -2,7 +2,6 @@ from typing import Tuple from ruamel.yaml import CommentedMap -from app.core.config import settings from app.log import logger from app.plugins.autosignin.sites import _ISiteSigninHandler from app.utils.string import StringUtils diff --git a/app/plugins/autosignin/sites/hdupt.py b/app/plugins/autosignin/sites/hdupt.py index 92fec348..97a426ed 100644 --- a/app/plugins/autosignin/sites/hdupt.py +++ b/app/plugins/autosignin/sites/hdupt.py @@ -3,7 +3,6 @@ from typing import Tuple from ruamel.yaml import CommentedMap -from app.core.config import settings from app.log import logger from app.plugins.autosignin.sites import _ISiteSigninHandler from app.utils.string import StringUtils diff --git a/app/plugins/autosignin/sites/pterclub.py b/app/plugins/autosignin/sites/pterclub.py index 47a3e334..761628bb 100644 --- a/app/plugins/autosignin/sites/pterclub.py +++ b/app/plugins/autosignin/sites/pterclub.py @@ -3,7 +3,6 @@ from typing import Tuple from ruamel.yaml import CommentedMap -from app.core.config import settings from app.log import logger from app.plugins.autosignin.sites import _ISiteSigninHandler from app.utils.string import StringUtils diff --git a/app/scheduler.py b/app/scheduler.py index 384c9e5e..765c2636 100644 --- a/app/scheduler.py +++ b/app/scheduler.py @@ -6,7 +6,7 @@ from apscheduler.executors.pool import ThreadPoolExecutor from apscheduler.schedulers.background import BackgroundScheduler from app.chain.cookiecloud import CookieCloudChain -from app.chain.douban_sync import DoubanSyncChain +from app.chain.douban import DoubanChain from app.chain.subscribe import SubscribeChain from app.chain.transfer import TransferChain from app.core.config import settings @@ -51,7 +51,7 @@ class Scheduler(metaclass=Singleton): self._scheduler.add_job(SubscribeChain().refresh, "cron", hour=trigger.hour, minute=trigger.minute) # 豆瓣同步(每30分钟) - self._scheduler.add_job(DoubanSyncChain().process, "interval", minutes=30) + self._scheduler.add_job(DoubanChain().sync, "interval", minutes=30) # 下载器文件转移(每5分钟) self._scheduler.add_job(TransferChain().process, "interval", minutes=5) diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py index 59ab5919..095e6d18 100644 --- a/app/schemas/__init__.py +++ b/app/schemas/__init__.py @@ -3,5 +3,6 @@ from .user import User, UserCreate, UserInDB, UserUpdate from .response import Response from .site import Site from .subscribe import Subscribe -from .context import Context +from .context import Context, MediaInfo, MetaInfo, TransferTorrent, DownloadingTorrent, TransferInfo, ExistMediaInfo, \ + NotExistMediaInfo, RefreshMediaItem from .servarr import RadarrMovie, SonarrSeries diff --git a/app/schemas/context.py b/app/schemas/context.py index 4edd5ab6..a1eb5c30 100644 --- a/app/schemas/context.py +++ b/app/schemas/context.py @@ -80,6 +80,57 @@ class MediaInfo(BaseModel): overview: Optional[str] = None # 二级分类 category: str = "" + # 季集 + seasons: Dict[int, list] = {} + # 别名和译名 + names: list = [] + + +class TorrentInfo(BaseModel): + # 站点ID + site: int = None + # 站点名称 + site_name: Optional[str] = None + # 站点Cookie + site_cookie: Optional[str] = None + # 站点UA + site_ua: Optional[str] = None + # 站点是否使用代理 + site_proxy: bool = False + # 站点优先级 + site_order: int = 0 + # 种子名称 + title: Optional[str] = None + # 种子副标题 + description: Optional[str] = None + # IMDB ID + imdbid: str = None + # 种子链接 + enclosure: Optional[str] = None + # 详情页面 + page_url: Optional[str] = None + # 种子大小 + size: float = 0 + # 做种者 + seeders: int = 0 + # 下载者 + peers: int = 0 + # 完成者 + grabs: int = 0 + # 发布时间 + pubdate: Optional[str] = None + # 已过时间 + date_elapsed: Optional[str] = None + # 上传因子 + uploadvolumefactor: Optional[float] = None + # 下载因子 + downloadvolumefactor: Optional[float] = None + # HR + hit_and_run: bool = False + # 种子标签 + labels: Optional[list] = [] + # 种子优先级 + pri_order: int = 0 class Context(BaseModel): @@ -87,6 +138,8 @@ class Context(BaseModel): meta_info: Optional[MetaInfo] # 媒体信息 media_info: Optional[MediaInfo] + # 种子信息 + torrent_info: Optional[TorrentInfo] class TransferTorrent(BaseModel): @@ -124,7 +177,7 @@ class ExistMediaInfo(BaseModel): # 类型 电影、电视剧 type: MediaType # 季 - seasons: Optional[Dict[int, list]] = None + seasons: Dict[int, list] = {} class NotExistMediaInfo(BaseModel): diff --git a/tests/test_doubansync.py b/tests/test_doubansync.py index a104b97c..316af178 100644 --- a/tests/test_doubansync.py +++ b/tests/test_doubansync.py @@ -2,7 +2,7 @@ from unittest import TestCase -from app.chain.douban_sync import DoubanSyncChain +from app.chain.douban import DoubanChain class DoubanSyncTest(TestCase): @@ -14,4 +14,4 @@ class DoubanSyncTest(TestCase): @staticmethod def test_doubansync(): - DoubanSyncChain().process() + DoubanChain().sync() diff --git a/tests/test_recognize.py b/tests/test_recognize.py index 840774d1..8db3eb1b 100644 --- a/tests/test_recognize.py +++ b/tests/test_recognize.py @@ -3,7 +3,7 @@ from unittest import TestCase from app.chain.download import DownloadChain -from app.chain.identify import IdentifyChain +from app.chain.media import MediaChain from app.core.metainfo import MetaInfo @@ -15,7 +15,7 @@ class RecognizeTest(TestCase): pass def test_recognize(self): - result = IdentifyChain().process(title="我和我的祖国 2019") + result = MediaChain().recognize_by_title(title="我和我的祖国 2019") self.assertEqual(result.media_info.tmdb_id, 612845) exists = DownloadChain().get_no_exists_info(MetaInfo("我和我的祖国 2019"), result.media_info) self.assertTrue(exists[0])