diff --git a/app/api/apiv1.py b/app/api/apiv1.py index e8f17cf8..b0ac8af8 100644 --- a/app/api/apiv1.py +++ b/app/api/apiv1.py @@ -1,7 +1,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 + media, douban, search, plugin, tmdb, history, system, download, dashboard, rss api_router = APIRouter() api_router.include_router(login.router, prefix="/login", tags=["login"]) @@ -19,3 +19,4 @@ api_router.include_router(system.router, prefix="/system", tags=["system"]) api_router.include_router(plugin.router, prefix="/plugin", tags=["plugin"]) api_router.include_router(download.router, prefix="/download", tags=["download"]) api_router.include_router(dashboard.router, prefix="/dashboard", tags=["dashboard"]) +api_router.include_router(rss.router, prefix="/rss", tags=["rss"]) diff --git a/app/api/endpoints/rss.py b/app/api/endpoints/rss.py index 523591e9..5d0b9e87 100644 --- a/app/api/endpoints/rss.py +++ b/app/api/endpoints/rss.py @@ -4,9 +4,11 @@ from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from app import schemas +from app.chain.rss import RssChain from app.core.security import verify_token from app.db import get_db from app.db.models.rss import Rss +from app.schemas import MediaType router = APIRouter() @@ -24,19 +26,25 @@ def read_rsses( @router.post("/", summary="新增自定义订阅", response_model=schemas.Response) def create_rss( *, - db: Session = Depends(get_db), rss_in: schemas.Rss, _: schemas.TokenPayload = Depends(verify_token) ) -> Any: """ 新增自定义订阅 """ - rss = Rss.get_by_tmdbid(db, tmdbid=rss_in.tmdbid, season=rss_in.season) - if rss: - return schemas.Response(success=False, message="自定义订阅已存在") - rss = Rss(**rss_in.dict()) - rss.create(db) - return schemas.Response(success=True) + if rss_in.type: + mtype = MediaType(rss_in.type) + else: + mtype = None + rssid, errormsg = RssChain().add( + mtype=mtype, + **rss_in.dict() + ) + if not rssid: + return schemas.Response(success=False, message=errormsg) + return schemas.Response(success=True, data={ + "id": rssid + }) @router.put("/", summary="更新自定义订阅", response_model=schemas.Response) @@ -57,7 +65,7 @@ def update_rss( return schemas.Response(success=True) -@router.get("/{rssid}", summary="查询订阅详情", response_model=schemas.Rss) +@router.get("/{rssid}", summary="查询自定义订阅详情", response_model=schemas.Rss) def read_rss( rssid: int, db: Session = Depends(get_db), @@ -66,3 +74,15 @@ def read_rss( 根据ID查询自定义订阅详情 """ return Rss.get(db, rssid) + + +@router.delete("/{rssid}", summary="删除自定义订阅", response_model=schemas.Response) +def read_rss( + rssid: int, + db: Session = Depends(get_db), + _: schemas.TokenPayload = Depends(verify_token)) -> Any: + """ + 根据ID删除自定义订阅 + """ + Rss.delete(db, rssid) + return schemas.Response(success=True) diff --git a/app/api/endpoints/site.py b/app/api/endpoints/site.py index cc5b5eea..d2d6ae52 100644 --- a/app/api/endpoints/site.py +++ b/app/api/endpoints/site.py @@ -14,6 +14,7 @@ from app.db.models.site import Site from app.db.models.siteicon import SiteIcon from app.db.systemconfig_oper import SystemConfigOper from app.schemas.types import SystemConfigKey +from app.utils.string import StringUtils router = APIRouter() @@ -167,6 +168,25 @@ def site_resource(site_id: int, keyword: str = None, return [torrent.to_dict() for torrent in torrents] +@router.get("/domain/{site_url}", summary="站点详情", response_model=schemas.Site) +def read_site_by_domain( + site_url: str, + db: Session = Depends(get_db), + _: schemas.TokenPayload = Depends(verify_token) +) -> Any: + """ + 通过域名获取站点信息 + """ + domain = StringUtils.get_url_domain(site_url) + site = Site.get_by_domain(db, domain) + if not site: + raise HTTPException( + status_code=404, + detail=f"站点 {domain} 不存在", + ) + return site + + @router.get("/{site_id}", summary="站点详情", response_model=schemas.Site) def read_site( site_id: int, @@ -174,7 +194,7 @@ def read_site( _: schemas.TokenPayload = Depends(verify_token) ) -> Any: """ - 获取站点信息 + 通过ID获取站点信息 """ site = Site.get(db, site_id) if not site: diff --git a/app/chain/download.py b/app/chain/download.py index eb863709..5eeb455e 100644 --- a/app/chain/download.py +++ b/app/chain/download.py @@ -89,6 +89,7 @@ class DownloadChain(ChainBase): def download_single(self, context: Context, torrent_file: Path = None, episodes: Set[int] = None, channel: MessageChannel = None, + save_path: str = None, userid: Union[str, int] = None) -> Optional[str]: """ 下载及发送通知 @@ -103,18 +104,21 @@ class DownloadChain(ChainBase): if not torrent_file: return # 下载目录 - if settings.DOWNLOAD_CATEGORY and _media and _media.category: - if _media.type == MediaType.MOVIE: - download_dir = Path(settings.DOWNLOAD_MOVIE_PATH or settings.DOWNLOAD_PATH) / _media.category + if not save_path: + if settings.DOWNLOAD_CATEGORY and _media and _media.category: + if _media.type == MediaType.MOVIE: + download_dir = Path(settings.DOWNLOAD_MOVIE_PATH or settings.DOWNLOAD_PATH) / _media.category + else: + download_dir = Path(settings.DOWNLOAD_TV_PATH or settings.DOWNLOAD_PATH) / _media.category + elif _media: + if _media.type == MediaType.MOVIE: + download_dir = Path(settings.DOWNLOAD_MOVIE_PATH or settings.DOWNLOAD_PATH) + else: + download_dir = Path(settings.DOWNLOAD_TV_PATH or settings.DOWNLOAD_PATH) else: - download_dir = Path(settings.DOWNLOAD_TV_PATH or settings.DOWNLOAD_PATH) / _media.category - elif _media: - if _media.type == MediaType.MOVIE: - download_dir = Path(settings.DOWNLOAD_MOVIE_PATH or settings.DOWNLOAD_PATH) - else: - download_dir = Path(settings.DOWNLOAD_TV_PATH or settings.DOWNLOAD_PATH) + download_dir = Path(settings.DOWNLOAD_PATH) else: - download_dir = Path(settings.DOWNLOAD_PATH) + download_dir = Path(save_path) # 添加下载 result: Optional[tuple] = self.download(torrent_path=torrent_file, cookie=_torrent.site_cookie, @@ -174,11 +178,13 @@ class DownloadChain(ChainBase): def batch_download(self, contexts: List[Context], no_exists: Dict[int, Dict[int, NotExistMediaInfo]] = None, + save_path: str = None, userid: str = None) -> Tuple[List[Context], Dict[int, Dict[int, NotExistMediaInfo]]]: """ 根据缺失数据,自动种子列表中组合择优下载 :param contexts: 资源上下文列表 :param no_exists: 缺失的剧集信息 + :param save_path: 保存路径 :param userid: 用户ID :return: 已经下载的资源列表、剩余未下载到的剧集 no_exists[tmdb_id] = {season: NotExistMediaInfo} """ @@ -217,7 +223,7 @@ class DownloadChain(ChainBase): no_exists[_tmdbid][_sea] = NotExistMediaInfo( season=not_exist.season, episodes=need, - total_episodes=not_exist.total_episodes, + total_episode=not_exist.total_episode, start_episode=not_exist.start_episode ) else: @@ -235,7 +241,7 @@ class DownloadChain(ChainBase): no_exist = no_exists.get(tmdbid) if not no_exist.get(season): return 0 - return no_exist[season].total_episodes + return no_exist[season].total_episode # 分组排序 contexts = TorrentHelper().sort_group_torrents(contexts) @@ -243,7 +249,7 @@ class DownloadChain(ChainBase): # 如果是电影,直接下载 for context in contexts: if context.media_info.type == MediaType.MOVIE: - if self.download_single(context, userid=userid): + if self.download_single(context, save_path=save_path, userid=userid): # 下载成功 downloaded_list.append(context) @@ -294,6 +300,7 @@ class DownloadChain(ChainBase): # 下载 download_id = self.download_single(context=context, torrent_file=torrent_path, + save_path=save_path, userid=userid) else: logger.info( @@ -301,7 +308,7 @@ class DownloadChain(ChainBase): continue else: # 下载 - download_id = self.download_single(context, userid=userid) + download_id = self.download_single(context, save_path=save_path, userid=userid) if download_id: # 下载成功 @@ -326,12 +333,12 @@ class DownloadChain(ChainBase): # 当前需要集 need_episodes = tv.episodes # TMDB总集数 - total_episodes = tv.total_episodes + total_episode = tv.total_episode # 需要开始集 start_episode = tv.start_episode or 1 # 缺失整季的转化为缺失集进行比较 if not need_episodes: - need_episodes = list(range(start_episode, total_episodes)) + need_episodes = list(range(start_episode, total_episode)) # 循环种子 for context in contexts: # 媒体信息 @@ -359,7 +366,7 @@ class DownloadChain(ChainBase): # 为需要集的子集则下载 if torrent_episodes.issubset(set(need_episodes)): # 下载 - download_id = self.download_single(context, userid=userid) + download_id = self.download_single(context, save_path=save_path, userid=userid) if download_id: # 下载成功 downloaded_list.append(context) @@ -429,6 +436,7 @@ class DownloadChain(ChainBase): download_id = self.download_single(context=context, torrent_file=torrent_path, episodes=selected_episodes, + save_path=save_path, userid=userid) if not download_id: continue @@ -464,7 +472,7 @@ class DownloadChain(ChainBase): {tmdbid: [ "season": int, "episodes": list, - "total_episodes": int, + "total_episode": int, "start_episode": int ]} """ @@ -473,7 +481,7 @@ class DownloadChain(ChainBase): _season: NotExistMediaInfo( season=_season, episodes=_episodes, - total_episodes=_total, + total_episode=_total, start_episode=_start ) } @@ -481,7 +489,7 @@ class DownloadChain(ChainBase): no_exists[mediainfo.tmdb_id][_season] = NotExistMediaInfo( season=_season, episodes=_episodes, - total_episodes=_total, + total_episode=_total, start_episode=_start ) diff --git a/app/chain/message.py b/app/chain/message.py index 96de64f6..836ebe1a 100644 --- a/app/chain/message.py +++ b/app/chain/message.py @@ -108,7 +108,7 @@ class MessageChain(ChainBase): if no_exists: # 发送消息 messages = [ - f"第 {sea} 季缺失 {StringUtils.str_series(no_exist.episodes) if no_exist.episodes else no_exist.total_episodes} 集" + f"第 {sea} 季缺失 {StringUtils.str_series(no_exist.episodes) if no_exist.episodes else no_exist.total_episode} 集" for sea, no_exist in no_exists.get(mediainfo.tmdb_id).items()] self.post_message(Notification(channel=channel, title=f"{mediainfo.title_year}:\n" + "\n".join(messages))) diff --git a/app/chain/rss.py b/app/chain/rss.py index 7145cb86..3f256d0f 100644 --- a/app/chain/rss.py +++ b/app/chain/rss.py @@ -1,6 +1,7 @@ import json import re import time +from datetime import datetime from typing import Tuple, Optional from app.chain import ChainBase @@ -77,17 +78,23 @@ class RssChain(ChainBase): kwargs.update({ 'total_episode': total_episode }) + # 检查是否存在 + if self.rssoper.exists(tmdbid=mediainfo.tmdb_id, season=season): + logger.warn(f'{mediainfo.title} 已存在') + return None, f'{mediainfo.title} 自定义订阅已存在' + if not kwargs.get("name"): + kwargs.update({ + "name": mediainfo.title + }) + kwargs.update({ + "tmdbid": mediainfo.tmdb_id, + "poster": mediainfo.get_poster_image(), + "backdrop": mediainfo.get_backdrop_image(), + "vote": mediainfo.vote_average, + "description": mediainfo.overview, + }) # 添加订阅 - sid = self.rssoper.add(title=mediainfo.title, - year=mediainfo.year, - mtype=mediainfo.type.value, - season=season, - tmdbid=mediainfo.tmdb_id, - poster=mediainfo.get_poster_image(), - backdrop=mediainfo.get_backdrop_image(), - vote=mediainfo.vote_average, - description=mediainfo.overview, - **kwargs) + sid = self.rssoper.add(title=title, year=year, season=season, **kwargs) if not sid: logger.error(f'{mediainfo.title_year} 添加自定义订阅失败') return None, "添加自定义订阅失败" @@ -235,14 +242,15 @@ class RssChain(ChainBase): rss_task.season: NotExistMediaInfo( season=rss_task.season, episodes=[], - total_episodes=rss_task.total_episode, + total_episode=rss_task.total_episode, start_episode=1) } else: no_exists = {} # 开始下载 downloads, lefts = self.downloadchain.batch_download(contexts=matched_contexts, - no_exists=no_exists) + no_exists=no_exists, + save_path=rss_task.save_path) if downloads and not lefts: if not rss_task.best_version: self.rssoper.delete(rss_task.id) @@ -252,5 +260,9 @@ class RssChain(ChainBase): image=rss_task.backdrop)) # 未完成下载 logger.info(f'{rss_task.name} 未下载未完整,继续订阅 ...') - + if downloads: + # 更新最后更新时间和已处理数量 + self.rssoper.update(rssid=rss_task.id, + processed=(rss_task.processed or 0) + len(downloads), + last_update=datetime.now().strftime("%Y-%m-%d %H:%M:%S")) logger.info("刷新RSS订阅数据完成") diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index 7ca9da32..05058526 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -227,7 +227,7 @@ class SubscribeChain(ChainBase): subscribe.season: NotExistMediaInfo( season=subscribe.season, episodes=[], - total_episodes=subscribe.total_episode, + total_episode=subscribe.total_episode, start_episode=subscribe.start_episode or 1) } else: @@ -475,7 +475,7 @@ class SubscribeChain(ChainBase): subscribe.season: NotExistMediaInfo( season=subscribe.season, episodes=[], - total_episodes=subscribe.total_episode, + total_episode=subscribe.total_episode, start_episode=subscribe.start_episode or 1) } else: @@ -726,7 +726,7 @@ class SubscribeChain(ChainBase): # 原季集列表 episode_list = no_exist_season.episodes # 原总集数 - total = no_exist_season.total_episodes + total = no_exist_season.total_episode if total_episode and start_episode: # 有开始集和总集数 episodes = list(range(start_episode, total_episode + 1)) @@ -747,7 +747,7 @@ class SubscribeChain(ChainBase): no_exists[tmdb_id][begin_season] = NotExistMediaInfo( season=begin_season, episodes=episodes, - total_episodes=total_episode, + total_episode=total_episode, start_episode=start_episode ) return no_exists diff --git a/app/core/meta/metaanime.py b/app/core/meta/metaanime.py index 98816aaf..4426553c 100644 --- a/app/core/meta/metaanime.py +++ b/app/core/meta/metaanime.py @@ -112,9 +112,9 @@ class MetaAnime(MetaBase): self.begin_episode = int(begin_episode) if end_episode and int(end_episode) != self.begin_episode: self.end_episode = int(end_episode) - self.total_episodes = (self.end_episode - self.begin_episode) + 1 + self.total_episode = (self.end_episode - self.begin_episode) + 1 else: - self.total_episodes = 1 + self.total_episode = 1 except Exception as err: print(str(err)) self.begin_episode = None diff --git a/app/core/meta/metabase.py b/app/core/meta/metabase.py index 6a2cdfe8..f28bd8cb 100644 --- a/app/core/meta/metabase.py +++ b/app/core/meta/metabase.py @@ -36,7 +36,7 @@ class MetaBase(object): # 识别的结束季 数字 end_season: Optional[int] = None # 总集数 - total_episodes: int = 0 + total_episode: int = 0 # 识别的开始集 begin_episode: Optional[int] = None # 识别的结束集 @@ -146,13 +146,13 @@ class MetaBase(object): return if self.begin_episode is None and isinstance(begin_episode, int): self.begin_episode = begin_episode - self.total_episodes = 1 + self.total_episode = 1 if self.begin_episode is not None \ and self.end_episode is None \ and isinstance(end_episode, int) \ and end_episode != self.begin_episode: self.end_episode = end_episode - self.total_episodes = (self.end_episode - self.begin_episode) + 1 + self.total_episode = (self.end_episode - self.begin_episode) + 1 self.type = MediaType.TV self._subtitle_flag = True # x集全 @@ -163,7 +163,7 @@ class MetaBase(object): episode_all = episode_all_str.group(2) if episode_all and self.begin_episode is None: try: - self.total_episodes = int(cn2an.cn2an(episode_all.strip(), mode='smart')) + self.total_episode = int(cn2an.cn2an(episode_all.strip(), mode='smart')) except Exception as err: print(str(err)) return diff --git a/app/core/meta/metavideo.py b/app/core/meta/metavideo.py index fa2feddb..22cf0ca3 100644 --- a/app/core/meta/metavideo.py +++ b/app/core/meta/metavideo.py @@ -394,14 +394,14 @@ class MetaVideo(MetaBase): se = int(se) if self.begin_episode is None: self.begin_episode = se - self.total_episodes = 1 + self.total_episode = 1 else: if se > self.begin_episode: self.end_episode = se - self.total_episodes = (self.end_episode - self.begin_episode) + 1 - if self.isfile and self.total_episodes > 2: + self.total_episode = (self.end_episode - self.begin_episode) + 1 + if self.isfile and self.total_episode > 2: self.end_episode = None - self.total_episodes = 1 + self.total_episode = 1 elif token.isdigit(): try: int(token) @@ -413,10 +413,10 @@ class MetaVideo(MetaBase): and int(token) > self.begin_episode \ and self._last_token_type == "episode": self.end_episode = int(token) - self.total_episodes = (self.end_episode - self.begin_episode) + 1 - if self.isfile and self.total_episodes > 2: + self.total_episode = (self.end_episode - self.begin_episode) + 1 + if self.isfile and self.total_episode > 2: self.end_episode = None - self.total_episodes = 1 + self.total_episode = 1 self._continue_flag = False self.type = MediaType.TV elif self.begin_episode is None \ @@ -425,7 +425,7 @@ class MetaVideo(MetaBase): and self._last_token_type != "videoencode" \ and token != self._unknown_name_str: self.begin_episode = int(token) - self.total_episodes = 1 + self.total_episode = 1 self._last_token_type = "episode" self._continue_flag = False self._stop_name_flag = True @@ -434,7 +434,7 @@ class MetaVideo(MetaBase): and self.begin_episode is None \ and len(token) < 5: self.begin_episode = int(token) - self.total_episodes = 1 + self.total_episode = 1 self._last_token_type = "episode" self._continue_flag = False self._stop_name_flag = True diff --git a/app/db/models/rss.py b/app/db/models/rss.py index af0dc1f9..095d2e3d 100644 --- a/app/db/models/rss.py +++ b/app/db/models/rss.py @@ -43,6 +43,8 @@ class Rss(Base): proxy = Column(Integer) # 保存路径 save_path = Column(String) + # 已处理数量 + processed = Column(Integer) # 附加信息,已处理数据 note = Column(String) # 最后更新时间 diff --git a/app/db/rss_oper.py b/app/db/rss_oper.py index f197a7f9..98e16e8e 100644 --- a/app/db/rss_oper.py +++ b/app/db/rss_oper.py @@ -17,11 +17,14 @@ class RssOper(DbOper): 新增RSS订阅 """ item = Rss(**kwargs) - if not item.get_by_tmdbid(self._db, tmdbid=kwargs.get("tmdbid"), - season=kwargs.get("season")): - item.create(self._db) - return True - return False + item.create(self._db) + return True + + def exists(self, tmdbid: int, season: int = None): + """ + 判断是否存在 + """ + return Rss.get_by_tmdbid(self._db, tmdbid, season) def list(self) -> List[Rss]: """ diff --git a/app/modules/filetransfer/__init__.py b/app/modules/filetransfer/__init__.py index f3d5a3dd..425ce203 100644 --- a/app/modules/filetransfer/__init__.py +++ b/app/modules/filetransfer/__init__.py @@ -440,8 +440,8 @@ class FileTransferModule(_ModuleBase): if file_meta.total_seasons: meta.total_seasons = file_meta.total_seasons # 总集数 - if file_meta.total_episodes: - meta.total_episodes = file_meta.total_episodes + if file_meta.total_episode: + meta.total_episode = file_meta.total_episode # 结束季为空 meta.end_season = None # 目的文件名 diff --git a/app/schemas/context.py b/app/schemas/context.py index 99fae8b2..fe96a735 100644 --- a/app/schemas/context.py +++ b/app/schemas/context.py @@ -32,7 +32,7 @@ class MetaInfo(BaseModel): # 识别的结束季 数字 end_season: Optional[int] = None # 总集数 - total_episodes: Optional[int] = 0 + total_episode: Optional[int] = 0 # 识别的开始集 begin_episode: Optional[int] = None # 识别的结束集 diff --git a/app/schemas/mediaserver.py b/app/schemas/mediaserver.py index e5f889e2..fa45d8cc 100644 --- a/app/schemas/mediaserver.py +++ b/app/schemas/mediaserver.py @@ -25,7 +25,7 @@ class NotExistMediaInfo(BaseModel): # 剧集列表 episodes: Optional[list] = [] # 总集数 - total_episodes: Optional[int] = 0 + total_episode: Optional[int] = 0 # 开始集 start_episode: Optional[int] = 0 diff --git a/app/schemas/rss.py b/app/schemas/rss.py index 4d426266..e79bbbea 100644 --- a/app/schemas/rss.py +++ b/app/schemas/rss.py @@ -28,7 +28,7 @@ class Rss(BaseModel): # 简介 description: Optional[str] # 总集数 - total_episodes: Optional[int] + total_episode: Optional[int] # 包含 include: Optional[str] # 排除 @@ -41,6 +41,8 @@ class Rss(BaseModel): save_path: Optional[str] # 附加信息 note: Optional[str] + # 已处理数量 + processed: Optional[int] # 最后更新时间 last_update: Optional[str] # 状态 0-停用,1-启用