diff --git a/backend/dev.sh b/backend/dev.sh index 84cf8f80..4234c27d 100755 --- a/backend/dev.sh +++ b/backend/dev.sh @@ -25,4 +25,4 @@ if [ ! -f "$VERSION_FILE" ]; then echo "VERSION='DEV_VERSION'" >>"$VERSION_FILE" fi -../venv/bin/python3 main.py +../venv/bin/uvicorn main:app --reload --port 7892 diff --git a/backend/src/module/api/auth.py b/backend/src/module/api/auth.py index 19ed3593..18a44023 100644 --- a/backend/src/module/api/auth.py +++ b/backend/src/module/api/auth.py @@ -2,8 +2,10 @@ from datetime import timedelta from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm +from fastapi.responses import JSONResponse from module.models.user import User, UserUpdate +from module.models import APIResponse from module.security.api import ( auth_user, get_current_user, @@ -22,7 +24,14 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends()): data={"sub": user.username}, expires_delta=timedelta(days=1) ) - return {"access_token": token, "token_type": "bearer", "expire": 86400} + return JSONResponse( + status_code=status.HTTP_200_OK, + content={ + "access_token": token, + "token_type": "bearer", + "expire": 86400, + }, + ) @router.get("/refresh_token", response_model=dict) @@ -32,16 +41,29 @@ async def refresh(current_user: User = Depends(get_current_user)): status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid token" ) token = create_access_token(data={"sub": current_user.username}) - return {"access_token": token, "token_type": "bearer", "expire": 86400} + return JSONResponse( + status_code=status.HTTP_200_OK, + content={ + "access_token": token, + "token_type": "bearer", + "expire": 86400, + }, + ) -@router.get("/logout", response_model=dict) +@router.get("/logout", response_model=APIResponse) async def logout(current_user: User = Depends(get_current_user)): if not current_user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid token" ) - return {"message": "logout success"} + return JSONResponse( + status_code=status.HTTP_200_OK, + content={ + "msg_en": "Logout success", + "msg_zh": "登出成功", + }, + ) @router.post("/update", response_model=dict) @@ -53,9 +75,12 @@ async def update_user( status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid token" ) if update_user_info(user_data, current_user): - return { - "message": "update success", - "access_token": create_access_token({"sub": user_data.username}), - "token_type": "bearer", - "expire": 86400, - } + return JSONResponse( + status_code=status.HTTP_200_OK, + content={ + "message": "update success", + "access_token": create_access_token({"sub": user_data.username}), + "token_type": "bearer", + "expire": 86400, + }, + ) diff --git a/backend/src/module/api/bangumi.py b/backend/src/module/api/bangumi.py index 464f001d..45a7fe47 100644 --- a/backend/src/module/api/bangumi.py +++ b/backend/src/module/api/bangumi.py @@ -38,7 +38,7 @@ async def update_rule( return u_response(resp) -@router.delete("/delete/{bangumi_id}", response_model=APIResponse) +@router.delete(path="/delete/{bangumi_id}", response_model=APIResponse) async def delete_rule( bangumi_id: str, file: bool = False, current_user=Depends(get_current_user) ): @@ -49,7 +49,7 @@ async def delete_rule( return u_response(resp) -@router.delete("/delete/many/", response_model=APIResponse) +@router.delete(path="/delete/many/", response_model=APIResponse) async def delete_many_rule( bangumi_id: list, file: bool = False, current_user=Depends(get_current_user) ): @@ -57,10 +57,11 @@ async def delete_many_rule( raise UNAUTHORIZED with TorrentManager() as manager: for i in bangumi_id: - manager.delete_rule(i, file) + resp = manager.delete_rule(i, file) + return u_response(resp) -@router.delete("/disable/{bangumi_id}", response_model=APIResponse) +@router.delete(path="/disable/{bangumi_id}", response_model=APIResponse) async def disable_rule( bangumi_id: str, file: bool = False, current_user=Depends(get_current_user) ): @@ -71,7 +72,7 @@ async def disable_rule( return u_response(resp) -@router.delete("/disable/many/") +@router.delete(path="/disable/many/", response_model=APIResponse) async def disable_many_rule( bangumi_id: list, file: bool = False, current_user=Depends(get_current_user) ): @@ -79,10 +80,11 @@ async def disable_many_rule( raise UNAUTHORIZED with TorrentManager() as manager: for i in bangumi_id: - manager.disable_rule(i, file) + resp = manager.disable_rule(i, file) + return u_response(resp) -@router.get("/enable/{bangumi_id}", response_model=APIResponse) +@router.get(path="/enable/{bangumi_id}", response_model=APIResponse) async def enable_rule(bangumi_id: str, current_user=Depends(get_current_user)): if not current_user: raise UNAUTHORIZED diff --git a/backend/src/module/api/rss.py b/backend/src/module/api/rss.py index edfd49fa..2d0558a0 100644 --- a/backend/src/module/api/rss.py +++ b/backend/src/module/api/rss.py @@ -3,16 +3,17 @@ from fastapi.responses import JSONResponse from .response import u_response -from module.models import RSSItem, RSSUpdate, Torrent, APIResponse -from module.rss import RSSEngine +from module.models import RSSItem, RSSUpdate, Torrent, APIResponse, Bangumi +from module.rss import RSSEngine, RSSAnalyser from module.security.api import get_current_user, UNAUTHORIZED from module.downloader import DownloadClient +from module.manager import SeasonCollector router = APIRouter(prefix="/rss", tags=["rss"]) -@router.get("", response_model=list[RSSItem]) +@router.get(path="", response_model=list[RSSItem]) async def get_rss(current_user=Depends(get_current_user)): if not current_user: raise UNAUTHORIZED @@ -20,7 +21,7 @@ async def get_rss(current_user=Depends(get_current_user)): return engine.rss.search_all() -@router.post("/add", response_model=APIResponse) +@router.post(path="/add", response_model=APIResponse) async def add_rss(rss: RSSItem, current_user=Depends(get_current_user)): if not current_user: raise UNAUTHORIZED @@ -29,45 +30,43 @@ async def add_rss(rss: RSSItem, current_user=Depends(get_current_user)): return u_response(result) -@router.delete("/delete/{rss_id}", response_model=APIResponse) +@router.delete(path="/delete/{rss_id}", response_model=APIResponse) async def delete_rss(rss_id: int, current_user=Depends(get_current_user)): if not current_user: raise UNAUTHORIZED with RSSEngine() as engine: - result = engine.rss.delete(rss_id) - if result: - return JSONResponse( - status_code=200, - content={"msg_en": "Delete RSS successfully.", "msg_zh": "删除 RSS 成功。"}, - ) - else: - return JSONResponse( - status_code=406, - content={"msg_en": "Delete RSS failed.", "msg_zh": "删除 RSS 失败。"}, - ) + if engine.rss.delete(rss_id): + return JSONResponse( + status_code=200, + content={"msg_en": "Delete RSS successfully.", "msg_zh": "删除 RSS 成功。"}, + ) + else: + return JSONResponse( + status_code=406, + content={"msg_en": "Delete RSS failed.", "msg_zh": "删除 RSS 失败。"}, + ) -@router.patch("/update/{rss_id}", response_model=APIResponse) +@router.patch(path="/update/{rss_id}", response_model=APIResponse) async def update_rss( rss_id: int, data: RSSUpdate, current_user=Depends(get_current_user) ): if not current_user: raise UNAUTHORIZED with RSSEngine() as engine: - result = engine.rss.update(rss_id, data) - if result: - return JSONResponse( - status_code=200, - content={"msg_en": "Update RSS successfully.", "msg_zh": "更新 RSS 成功。"}, - ) - else: - return JSONResponse( - status_code=406, - content={"msg_en": "Update RSS failed.", "msg_zh": "更新 RSS 失败。"}, - ) + if engine.rss.update(rss_id, data): + return JSONResponse( + status_code=200, + content={"msg_en": "Update RSS successfully.", "msg_zh": "更新 RSS 成功。"}, + ) + else: + return JSONResponse( + status_code=406, + content={"msg_en": "Update RSS failed.", "msg_zh": "更新 RSS 失败。"}, + ) -@router.get("/refresh/all", response_model=APIResponse) +@router.get(path="/refresh/all", response_model=APIResponse) async def refresh_all(current_user=Depends(get_current_user)): if not current_user: raise UNAUTHORIZED @@ -79,7 +78,7 @@ async def refresh_all(current_user=Depends(get_current_user)): ) -@router.get("/refresh/{rss_id}", response_model=APIResponse) +@router.get(path="/refresh/{rss_id}", response_model=APIResponse) async def refresh_rss(rss_id: int, current_user=Depends(get_current_user)): if not current_user: raise UNAUTHORIZED @@ -91,9 +90,41 @@ async def refresh_rss(rss_id: int, current_user=Depends(get_current_user)): ) -@router.get("/torrent/{rss_id}", response_model=list[Torrent]) +@router.get(path="/torrent/{rss_id}", response_model=list[Torrent]) async def get_torrent(rss_id: int, current_user=Depends(get_current_user)): if not current_user: raise UNAUTHORIZED with RSSEngine() as engine: return engine.get_rss_torrents(rss_id) + + +# Old API +analyser = RSSAnalyser() + + +@router.post("/analysis", response_model=Bangumi) +async def analysis(rss: RSSItem, current_user=Depends(get_current_user)): + if not current_user: + raise UNAUTHORIZED + data = analyser.link_to_data(rss) + if isinstance(data, Bangumi): + return data + else: + return u_response(data) + + +@router.post("/collect", response_model=APIResponse) +async def download_collection(data: Bangumi, current_user=Depends(get_current_user)): + if not current_user: + raise UNAUTHORIZED + with SeasonCollector() as collector: + resp = collector.collect_season(data, data.rss_link[0]) + return u_response(resp) + +@router.post("/subscribe", response_model=APIResponse) +async def subscribe(data: Bangumi, current_user=Depends(get_current_user)): + if not current_user: + raise UNAUTHORIZED + with SeasonCollector() as collector: + resp = collector.subscribe_season(data) + return u_response(resp) \ No newline at end of file diff --git a/backend/src/module/manager/collector.py b/backend/src/module/manager/collector.py index 7027bacc..16763dad 100644 --- a/backend/src/module/manager/collector.py +++ b/backend/src/module/manager/collector.py @@ -1,7 +1,7 @@ import logging from module.downloader import DownloadClient -from module.models import Bangumi +from module.models import Bangumi, ResponseModel from module.searcher import SearchTorrent from module.rss import RSSEngine @@ -18,7 +18,25 @@ class SeasonCollector(DownloadClient): torrents = st.search_season(bangumi) else: torrents = st.get_torrents(link, _filter="|".join(bangumi.filter)) - return self.add_torrent(torrents, bangumi) + if self.add_torrent(torrents, bangumi): + logger.info(f"Collections of {bangumi.official_title} Season {bangumi.season} completed.") + bangumi.eps_collect = True + with RSSEngine() as engine: + engine.bangumi.update(bangumi) + return ResponseModel( + status=True, + status_code=200, + msg_en=f"Collections of {bangumi.official_title} Season {bangumi.season} completed.", + msg_zh=f"收集 {bangumi.official_title} 第 {bangumi.season} 季完成。", + ) + else: + logger.warning(f"Collection of {bangumi.official_title} Season {bangumi.season} failed.") + return ResponseModel( + status=False, + status_code=406, + msg_en=f"Collection of {bangumi.official_title} Season {bangumi.season} failed.", + msg_zh=f"收集 {bangumi.official_title} 第 {bangumi.season} 季失败。", + ) def subscribe_season(self, data: Bangumi): with RSSEngine() as engine: @@ -29,6 +47,12 @@ class SeasonCollector(DownloadClient): ) engine.bangumi.add(data) engine.refresh_rss(self) + return ResponseModel( + status=True, + status_code=200, + msg_en=f"Subscribe {data.official_title} successfully.", + msg_zh=f"订阅 {data.official_title} 成功。", + ) def eps_complete(): diff --git a/backend/src/module/manager/torrent.py b/backend/src/module/manager/torrent.py index ce2daaba..a2b5b09d 100644 --- a/backend/src/module/manager/torrent.py +++ b/backend/src/module/manager/torrent.py @@ -143,9 +143,11 @@ class TorrentManager(Database): data = self.bangumi.search_id(int(_id)) if not data: logger.error(f"[Manager] Can't find data with {_id}") - return JSONResponse( + return ResponseModel( status_code=406, - content={"msg_en": f"Can't find data with {_id}", "msg_zh": f"无法找到 id {_id} 的数据"}, + status=False, + msg_en=f"Can't find data with {_id}", + msg_zh=f"无法找到 id {_id} 的数据", ) else: return data diff --git a/backend/src/module/rss/analyser.py b/backend/src/module/rss/analyser.py index ca031ac1..56623cb4 100644 --- a/backend/src/module/rss/analyser.py +++ b/backend/src/module/rss/analyser.py @@ -4,7 +4,7 @@ import re from .engine import RSSEngine from module.conf import settings -from module.models import Bangumi, Torrent, RSSItem +from module.models import Bangumi, Torrent, RSSItem, ResponseModel from module.network import RequestContent from module.parser import TitleParser @@ -75,9 +75,16 @@ class RSSAnalyser(TitleParser): else: return [] - def link_to_data(self, rss: RSSItem) -> Bangumi: + def link_to_data(self, rss: RSSItem) -> Bangumi | ResponseModel: torrents = self.get_rss_torrents(rss.url, False) for torrent in torrents: data = self.torrent_to_data(torrent, rss) if data: return data + else: + return ResponseModel( + status=False, + status_code=406, + msg_en="No new title has been found.", + msg_zh="没有找到新的番剧。", + ) diff --git a/docs/deploy/quick-start.md b/docs/deploy/quick-start.md index eeebdeee..03f70a6d 100644 --- a/docs/deploy/quick-start.md +++ b/docs/deploy/quick-start.md @@ -24,7 +24,7 @@ docker run -d \ -v AutoBangumi_data:/app/data \ -p 7892:7892 \ --network=bridge \ - --dns=8.8.8.8 + --dns=8.8.8.8 \ --restart unless-stopped \ estrellaxd/auto_bangumi:latest @@ -32,7 +32,7 @@ docker run -d \ ### 选项2: 使用 Docker-compose 部署 -复制以下内容到 `docker-compose.yml` 文件中,然后运行 `docker-compose up -d` 即可。 +复制以下内容到 `docker-compose.yml` 文件中。 ```yaml version: "3.8" @@ -58,6 +58,12 @@ volumes: name: AutoBangumi_data ``` +运行以下命令启动容器。 + +```shell +docker compose up -d +``` + ## 安装 qBittorrent 如果你没有安装 qBittorrent,请先安装 qBittorrent。