diff --git a/app/api/apiv1.py b/app/api/apiv1.py index 019975ce..c0c449c8 100644 --- a/app/api/apiv1.py +++ b/app/api/apiv1.py @@ -1,7 +1,8 @@ from fastapi import APIRouter from app.api.endpoints import login, user, site, message, webhook, subscribe, \ - media, douban, search, plugin, tmdb, history, system, download, dashboard, filebrowser, transfer, mediaserver + media, douban, search, plugin, tmdb, history, system, download, dashboard, \ + filebrowser, transfer, mediaserver, bangumi api_router = APIRouter() api_router.include_router(login.router, prefix="/login", tags=["login"]) @@ -22,3 +23,5 @@ api_router.include_router(dashboard.router, prefix="/dashboard", tags=["dashboar api_router.include_router(filebrowser.router, prefix="/filebrowser", tags=["filebrowser"]) 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"]) + diff --git a/app/api/endpoints/bangumi.py b/app/api/endpoints/bangumi.py new file mode 100644 index 00000000..55e224bf --- /dev/null +++ b/app/api/endpoints/bangumi.py @@ -0,0 +1,64 @@ +from typing import List, Any + +from fastapi import APIRouter, Depends + +from app import schemas +from app.chain.bangumi import BangumiChain +from app.core.context import MediaInfo +from app.core.security import verify_token + +router = APIRouter() + + +@router.get("/calendar", summary="Bangumi每日放送", response_model=List[schemas.MediaInfo]) +def calendar(page: int = 1, + count: int = 30, + _: schemas.TokenPayload = Depends(verify_token)) -> Any: + """ + 浏览Bangumi每日放送 + """ + infos = BangumiChain().calendar(page=page, count=count) + if not infos: + return [] + medias = [MediaInfo(bangumi_info=info) for info in infos] + return [media.to_dict() for media in medias] + + +@router.get("/credits/{bangumiid}", summary="查询Bangumi演职员表", response_model=List[schemas.BangumiPerson]) +def bangumi_credits(bangumiid: int, + page: int = 1, + count: int = 20, + _: schemas.TokenPayload = Depends(verify_token)) -> Any: + """ + 查询Bangumi演职员表 + """ + persons = BangumiChain().bangumi_credits(bangumiid, page=page, count=count) + if not persons: + return [] + return [schemas.BangumiPerson(**person) for person in persons] + + +@router.get("/recommend/{bangumiid}", summary="查询Bangumi推荐", response_model=List[schemas.MediaInfo]) +def bangumi_recommend(bangumiid: int, + _: schemas.TokenPayload = Depends(verify_token)) -> Any: + """ + 查询Bangumi推荐 + """ + infos = BangumiChain().bangumi_recommend(bangumiid) + if not infos: + return [] + medias = [MediaInfo(bangumi_info=info) for info in infos] + return [media.to_dict() for media in medias] + + +@router.get("/{bangumiid}", summary="查询Bangumi详情", response_model=schemas.MediaInfo) +def bangumi_info(bangumiid: int, + _: schemas.TokenPayload = Depends(verify_token)) -> Any: + """ + 查询Bangumi详情 + """ + info = BangumiChain().bangumi_info(bangumiid) + if info: + return MediaInfo(bangumi_info=info).to_dict() + else: + return schemas.MediaInfo() diff --git a/app/api/endpoints/media.py b/app/api/endpoints/media.py index 7d107d71..d4f27777 100644 --- a/app/api/endpoints/media.py +++ b/app/api/endpoints/media.py @@ -106,14 +106,17 @@ def media_info(mediaid: str, type_name: str, 根据媒体ID查询themoviedb或豆瓣媒体信息,type_name: 电影/电视剧 """ mtype = MediaType(type_name) - tmdbid, doubanid = None, None + tmdbid, doubanid, bangumiid = None, None, None if mediaid.startswith("tmdb:"): tmdbid = int(mediaid[5:]) elif mediaid.startswith("douban:"): doubanid = mediaid[7:] - if not tmdbid and not doubanid: + elif mediaid.startswith("bangumi:"): + bangumiid = int(mediaid[8:]) + if not tmdbid and not doubanid and not bangumiid: return schemas.MediaInfo() - mediainfo = MediaChain().recognize_media(tmdbid=tmdbid, doubanid=doubanid, mtype=mtype) + # 识别 + mediainfo = MediaChain().recognize_media(tmdbid=tmdbid, doubanid=doubanid, bangumiid=bangumiid, mtype=mtype) if mediainfo: MediaChain().obtain_images(mediainfo) return mediainfo.to_dict() diff --git a/app/api/endpoints/search.py b/app/api/endpoints/search.py index 9820ba5e..e6d1d09b 100644 --- a/app/api/endpoints/search.py +++ b/app/api/endpoints/search.py @@ -52,6 +52,20 @@ def search_by_id(mediaid: str, mtype=mtype, area=area) else: torrents = SearchChain().search_by_id(doubanid=doubanid, mtype=mtype, area=area) + elif mediaid.startswith("bangumi:"): + bangumiid = int(mediaid.replace("bangumi:", "")) + if settings.RECOGNIZE_SOURCE == "themoviedb": + # 通过BangumiID识别TMDBID + tmdbinfo = MediaChain().get_tmdbinfo_by_bangumiid(bangumiid=bangumiid) + if tmdbinfo: + torrents = SearchChain().search_by_id(tmdbid=tmdbinfo.get("id"), + mtype=mtype, area=area) + else: + # 通过BangumiID识别豆瓣ID + doubaninfo = MediaChain().get_doubaninfo_by_bangumiid(bangumiid=bangumiid) + if doubaninfo: + torrents = SearchChain().search_by_id(doubanid=doubaninfo.get("id"), + mtype=mtype, area=area) else: return [] return [torrent.to_dict() for torrent in torrents] diff --git a/app/api/endpoints/subscribe.py b/app/api/endpoints/subscribe.py index b042f167..93f371f0 100644 --- a/app/api/endpoints/subscribe.py +++ b/app/api/endpoints/subscribe.py @@ -65,7 +65,7 @@ def create_subscribe( else: mtype = None # 豆瓣标理 - if subscribe_in.doubanid: + if subscribe_in.doubanid or subscribe_in.bangumiid: meta = MetaInfo(subscribe_in.name) subscribe_in.name = meta.name subscribe_in.season = meta.begin_season @@ -80,6 +80,7 @@ def create_subscribe( tmdbid=subscribe_in.tmdbid, season=subscribe_in.season, doubanid=subscribe_in.doubanid, + bangumiid=subscribe_in.bangumiid, username=current_user.name, best_version=subscribe_in.best_version, save_path=subscribe_in.save_path, @@ -131,9 +132,10 @@ def subscribe_mediaid( db: Session = Depends(get_db), _: schemas.TokenPayload = Depends(verify_token)) -> Any: """ - 根据TMDBID或豆瓣ID查询订阅 tmdb:/douban: + 根据 TMDBID/豆瓣ID/BangumiId 查询订阅 tmdb:/douban: """ result = None + title_check = False if mediaid.startswith("tmdb:"): tmdbid = mediaid[5:] if not tmdbid or not str(tmdbid).isdigit(): @@ -144,14 +146,21 @@ def subscribe_mediaid( if not doubanid: return Subscribe() result = Subscribe.get_by_doubanid(db, doubanid) - # 豆瓣已订阅如果 id 搜索无结果使用标题搜索 - # 会造成同名结果也会被返回 if not result and title: - meta = MetaInfo(title) - if season: - meta.begin_season = season - result = Subscribe.get_by_title(db, title=meta.name, season=meta.begin_season) - + title_check = True + elif mediaid.startswith("bangumi:"): + bangumiid = mediaid[8:] + if not bangumiid or not str(bangumiid).isdigit(): + return Subscribe() + result = Subscribe.get_by_bangumiid(db, int(bangumiid)) + if not result and title: + title_check = True + # 使用名称检查订阅 + if title_check and title: + meta = MetaInfo(title) + if season: + meta.begin_season = season + result = Subscribe.get_by_title(db, title=meta.name, season=meta.begin_season) if result and result.sites: result.sites = json.loads(result.sites) diff --git a/app/chain/__init__.py b/app/chain/__init__.py index 9e1ea78b..41e7094f 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -119,6 +119,7 @@ class ChainBase(metaclass=ABCMeta): mtype: MediaType = None, tmdbid: int = None, doubanid: str = None, + bangumiid: int = None, cache: bool = True) -> Optional[MediaInfo]: """ 识别媒体信息 @@ -126,6 +127,7 @@ class ChainBase(metaclass=ABCMeta): :param mtype: 识别的媒体类型,与tmdbid配套 :param tmdbid: tmdbid :param doubanid: 豆瓣ID + :param bangumiid: BangumiID :param cache: 是否使用缓存 :return: 识别的媒体信息,包括剧集信息 """ @@ -136,11 +138,12 @@ class ChainBase(metaclass=ABCMeta): tmdbid = meta.tmdbid if not doubanid and hasattr(meta, "doubanid"): doubanid = meta.doubanid - # 有tmdbid时不使用doubanid + # 有tmdbid时不使用其它ID if tmdbid: doubanid = None + bangumiid = None return self.run_module("recognize_media", meta=meta, mtype=mtype, - tmdbid=tmdbid, doubanid=doubanid, cache=cache) + tmdbid=tmdbid, doubanid=doubanid, bangumiid=bangumiid, cache=cache) def match_doubaninfo(self, name: str, imdbid: str = None, mtype: MediaType = None, year: str = None, season: int = None) -> Optional[dict]: @@ -217,6 +220,14 @@ class ChainBase(metaclass=ABCMeta): """ return self.run_module("tmdb_info", tmdbid=tmdbid, mtype=mtype) + def bangumi_info(self, bangumiid: int) -> Optional[dict]: + """ + 获取Bangumi信息 + :param bangumiid: int + :return: Bangumi信息 + """ + return self.run_module("bangumi_info", bangumiid=bangumiid) + def message_parser(self, body: Any, form: Any, args: Any) -> Optional[CommingMessage]: """ diff --git a/app/chain/bangumi.py b/app/chain/bangumi.py new file mode 100644 index 00000000..9a15226e --- /dev/null +++ b/app/chain/bangumi.py @@ -0,0 +1,42 @@ +from typing import Optional, List + +from app.chain import ChainBase +from app.utils.singleton import Singleton + + +class BangumiChain(ChainBase, metaclass=Singleton): + """ + Bangumi处理链,单例运行 + """ + + def calendar(self, page: int = 1, count: int = 30) -> Optional[List[dict]]: + """ + 获取Bangumi每日放送 + :param page: 页码 + :param count: 每页数量 + """ + return self.run_module("bangumi_calendar", page=page, count=count) + + def bangumi_info(self, bangumiid: int) -> Optional[dict]: + """ + 获取Bangumi信息 + :param bangumiid: BangumiID + :return: Bangumi信息 + """ + return self.run_module("bangumi_info", bangumiid=bangumiid) + + def bangumi_credits(self, bangumiid: int, page: int = 1, count: int = 20) -> List[dict]: + """ + 根据BangumiID查询电影演职员表 + :param bangumiid: BangumiID + :param page: 页码 + :param count: 数量 + """ + return self.run_module("bangumi_credits", bangumiid=bangumiid, page=page, count=count) + + def bangumi_recommend(self, bangumiid: int) -> List[dict]: + """ + 根据BangumiID查询推荐电影 + :param bangumiid: BangumiID + """ + return self.run_module("bangumi_recommend", bangumiid=bangumiid) diff --git a/app/chain/media.py b/app/chain/media.py index 12c22187..56da9fe3 100644 --- a/app/chain/media.py +++ b/app/chain/media.py @@ -229,6 +229,28 @@ class MediaChain(ChainBase, metaclass=Singleton): ) return tmdbinfo + def get_tmdbinfo_by_bangumiid(self, bangumiid: int) -> Optional[dict]: + """ + 根据BangumiID获取TMDB信息 + """ + bangumiinfo = self.bangumi_info(bangumiid=bangumiid) + if bangumiinfo: + # 名称 + name = bangumiinfo.get("name") or bangumiinfo.get("name_cn") + # 年份 + release_date = bangumiinfo.get("date") or bangumiinfo.get("air_date") + if release_date: + year = release_date[:4] + else: + year = None + # 使用名称识别TMDB媒体信息 + return self.match_tmdbinfo( + name=name, + year=year, + mtype=MediaType.TV + ) + return None + def get_doubaninfo_by_tmdbid(self, tmdbid: int, mtype: MediaType = None, season: int = None) -> Optional[dict]: """ @@ -261,3 +283,25 @@ class MediaChain(ChainBase, metaclass=Singleton): imdbid=imdbid ) return None + + def get_doubaninfo_by_bangumiid(self, bangumiid: int) -> Optional[dict]: + """ + 根据BangumiID获取豆瓣信息 + """ + bangumiinfo = self.bangumi_info(bangumiid=bangumiid) + if bangumiinfo: + # 名称 + name = bangumiinfo.get("name") or bangumiinfo.get("name_cn") + # 年份 + release_date = bangumiinfo.get("date") or bangumiinfo.get("air_date") + if release_date: + year = release_date[:4] + else: + year = None + # 使用名称识别豆瓣媒体信息 + return self.match_doubaninfo( + name=name, + year=year, + mtype=MediaType.TV + ) + return None diff --git a/app/chain/subscribe.py b/app/chain/subscribe.py index 00edcc0c..575b8ef0 100644 --- a/app/chain/subscribe.py +++ b/app/chain/subscribe.py @@ -45,6 +45,7 @@ class SubscribeChain(ChainBase): mtype: MediaType = None, tmdbid: int = None, doubanid: str = None, + bangumiid: int = None, season: int = None, channel: MessageChannel = None, userid: str = None, @@ -100,6 +101,7 @@ class SubscribeChain(ChainBase): mediainfo = self.recognize_media(mtype=mediainfo.type, tmdbid=mediainfo.tmdb_id, doubanid=mediainfo.douban_id, + bangumiid=mediainfo.bangumi_id, cache=False) if not mediainfo: logger.error(f"媒体信息识别失败!") @@ -124,6 +126,8 @@ class SubscribeChain(ChainBase): # 合并信息 if doubanid: mediainfo.douban_id = doubanid + if bangumiid: + mediainfo.bangumi_id = bangumiid # 添加订阅 sid, err_msg = self.subscribeoper.add(mediainfo, season=season, username=username, **kwargs) if not sid: diff --git a/app/core/context.py b/app/core/context.py index b07a9ece..7195dfc1 100644 --- a/app/core/context.py +++ b/app/core/context.py @@ -153,6 +153,8 @@ class MediaInfo: tvdb_id: int = None # 豆瓣ID douban_id: str = None + # Bangumi ID + bangumi_id: int = None # 媒体原语种 original_language: str = None # 媒体原发行标题 @@ -185,6 +187,8 @@ class MediaInfo: tmdb_info: dict = field(default_factory=dict) # 豆瓣 INFO douban_info: dict = field(default_factory=dict) + # Bangumi INFO + bangumi_info: dict = field(default_factory=dict) # 导演 directors: List[dict] = field(default_factory=list) # 演员 @@ -240,6 +244,8 @@ class MediaInfo: self.set_tmdb_info(self.tmdb_info) if self.douban_info: self.set_douban_info(self.douban_info) + if self.bangumi_info: + self.set_bangumi_info(self.bangumi_info) def __setattr__(self, name: str, value: Any): self.__dict__[name] = value @@ -540,6 +546,69 @@ class MediaInfo: if not hasattr(self, key): setattr(self, key, value) + def set_bangumi_info(self, info: dict): + """ + 初始化Bangumi信息 + """ + if not info: + return + # 本体 + self.bangumi_info = info + # 豆瓣ID + self.bangumi_id = info.get("id") + # 类型 + if not self.type: + self.type = MediaType.TV + # 标题 + if not self.title: + self.title = info.get("name_cn") or info.get("name") + # 原语种标题 + if not self.original_title: + self.original_title = info.get("name") + # 识别标题中的季 + meta = MetaInfo(self.title) + # 季 + if not self.season: + self.season = meta.begin_season + # 评分 + if not self.vote_average: + rating = info.get("rating") + if rating: + vote_average = float(rating.get("score")) + else: + vote_average = 0 + self.vote_average = vote_average + # 发行日期 + if not self.release_date: + self.release_date = info.get("date") or info.get("air_date") + # 年份 + if not self.year: + self.year = self.release_date[:4] if self.release_date else None + # 海报 + if not self.poster_path: + self.poster_path = info.get("images", {}).get("large") + # 简介 + if not self.overview: + self.overview = info.get("summary") + # 别名 + if not self.names: + infobox = info.get("infobox") + if infobox: + akas = [item.get("value") for item in infobox if item.get("key") == "别名"] + if akas: + self.names = [aka.get("v") for aka in akas[0]] + + # 剧集 + if self.type == MediaType.TV and not self.seasons: + meta = MetaInfo(self.title) + season = meta.begin_season or 1 + episodes_count = info.get("total_episodes") + if episodes_count: + self.seasons[season] = list(range(1, episodes_count + 1)) + # 演员 + if not self.actors: + self.actors = info.get("actors") or [] + @property def title_year(self): if self.title: @@ -558,6 +627,8 @@ class MediaInfo: return "https://www.themoviedb.org/tv/%s" % self.tmdb_id elif self.douban_id: return "https://movie.douban.com/subject/%s" % self.douban_id + elif self.bangumi_id: + return "http://bgm.tv/subject/%s" % self.bangumi_id return "" @property @@ -621,6 +692,7 @@ class MediaInfo: dicts["title_year"] = self.title_year dicts["tmdb_info"] = None dicts["douban_info"] = None + dicts["bangumi_info"] = None return dicts def clear(self): @@ -629,6 +701,7 @@ class MediaInfo: """ self.tmdb_info = {} self.douban_info = {} + self.bangumi_info = {} self.seasons = {} self.genres = [] self.season_info = [] diff --git a/app/core/meta/metabase.py b/app/core/meta/metabase.py index fdb93dbf..c2b49a7c 100644 --- a/app/core/meta/metabase.py +++ b/app/core/meta/metabase.py @@ -69,8 +69,8 @@ class MetaBase(object): _subtitle_flag = False _subtitle_season_re = r"(? None: + self.bangumiapi = BangumiApi() + + def stop(self): + pass + + def test(self) -> Tuple[bool, str]: + """ + 测试模块连接性 + """ + ret = RequestUtils().get_res("https://api.bgm.tv/") + if ret and ret.status_code == 200: + return True, "" + elif ret: + return False, f"无法连接Bangumi,错误码:{ret.status_code}" + return False, "Bangumi网络连接失败" + + def init_setting(self) -> Tuple[str, Union[str, bool]]: + pass + + def recognize_media(self, bangumiid: int = None, + **kwargs) -> Optional[MediaInfo]: + """ + 识别媒体信息 + :param bangumiid: 识别的Bangumi ID + :return: 识别的媒体信息,包括剧集信息 + """ + if not bangumiid: + return None + + # 直接查询详情 + info = self.bangumi_info(bangumiid=bangumiid) + if info: + # 赋值TMDB信息并返回 + mediainfo = MediaInfo(bangumi_info=info) + logger.info(f"{bangumiid} Bangumi识别结果:{mediainfo.type.value} " + f"{mediainfo.title_year}") + return mediainfo + else: + logger.info(f"{bangumiid} 未匹配到Bangumi媒体信息") + + return None + + def bangumi_info(self, bangumiid: int) -> Optional[dict]: + """ + 获取Bangumi信息 + :param bangumiid: BangumiID + :return: Bangumi信息 + """ + if not bangumiid: + return None + logger.info(f"开始获取Bangumi信息:{bangumiid} ...") + return self.bangumiapi.detail(bangumiid) + + def bangumi_calendar(self, page: int = 1, count: int = 30) -> Optional[List[dict]]: + """ + 获取Bangumi每日放送 + :param page: 页码 + :param count: 每页数量 + """ + return self.bangumiapi.calendar(page, count) + + def bangumi_credits(self, bangumiid: int, page: int = 1, count: int = 20) -> List[dict]: + """ + 根据TMDBID查询电影演职员表 + :param bangumiid: BangumiID + :param page: 页码 + :param count: 数量 + """ + persons = self.bangumiapi.persons(bangumiid) or [] + if persons: + return persons[(page - 1) * count: page * count] + else: + return [] + + def bangumi_recommend(self, bangumiid: int) -> List[dict]: + """ + 根据BangumiID查询推荐电影 + :param bangumiid: BangumiID + """ + return self.bangumiapi.subjects(bangumiid) or [] diff --git a/app/modules/bangumi/bangumi.py b/app/modules/bangumi/bangumi.py new file mode 100644 index 00000000..b92c0678 --- /dev/null +++ b/app/modules/bangumi/bangumi.py @@ -0,0 +1,154 @@ +from datetime import datetime +from functools import lru_cache + +import requests + +from app.utils.http import RequestUtils + + +class BangumiApi(object): + """ + https://bangumi.github.io/api/ + """ + + _urls = { + "calendar": "calendar", + "detail": "v0/subjects/%s", + "persons": "v0/subjects/%s/persons", + "subjects": "v0/subjects/%s/subjects" + } + _base_url = "https://api.bgm.tv/" + _req = RequestUtils(session=requests.Session()) + + def __init__(self): + pass + + @classmethod + @lru_cache(maxsize=128) + def __invoke(cls, url, **kwargs): + req_url = cls._base_url + url + params = {} + if kwargs: + params.update(kwargs) + resp = cls._req.get_res(url=req_url, params=params) + try: + return resp.json() if resp else None + except Exception as e: + print(e) + return None + + def calendar(self, page: int = 1, count: int = 30): + """ + 获取每日放送,返回items + """ + """ + [ + { + "weekday": { + "en": "Mon", + "cn": "星期一", + "ja": "月耀日", + "id": 1 + }, + "items": [ + { + "id": 350235, + "url": "http://bgm.tv/subject/350235", + "type": 2, + "name": "月が導く異世界道中 第二幕", + "name_cn": "月光下的异世界之旅 第二幕", + "summary": "", + "air_date": "2024-01-08", + "air_weekday": 1, + "rating": { + "total": 257, + "count": { + "1": 1, + "2": 1, + "3": 4, + "4": 15, + "5": 51, + "6": 111, + "7": 49, + "8": 13, + "9": 5, + "10": 7 + }, + "score": 6.1 + }, + "rank": 6125, + "images": { + "large": "http://lain.bgm.tv/pic/cover/l/3c/a5/350235_A0USf.jpg", + "common": "http://lain.bgm.tv/pic/cover/c/3c/a5/350235_A0USf.jpg", + "medium": "http://lain.bgm.tv/pic/cover/m/3c/a5/350235_A0USf.jpg", + "small": "http://lain.bgm.tv/pic/cover/s/3c/a5/350235_A0USf.jpg", + "grid": "http://lain.bgm.tv/pic/cover/g/3c/a5/350235_A0USf.jpg" + }, + "collection": { + "doing": 920 + } + }, + { + "id": 358561, + "url": "http://bgm.tv/subject/358561", + "type": 2, + "name": "大宇宙时代", + "name_cn": "大宇宙时代", + "summary": "", + "air_date": "2024-01-22", + "air_weekday": 1, + "rating": { + "total": 2, + "count": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 1, + "6": 1, + "7": 0, + "8": 0, + "9": 0, + "10": 0 + }, + "score": 5.5 + }, + "images": { + "large": "http://lain.bgm.tv/pic/cover/l/71/66/358561_UzsLu.jpg", + "common": "http://lain.bgm.tv/pic/cover/c/71/66/358561_UzsLu.jpg", + "medium": "http://lain.bgm.tv/pic/cover/m/71/66/358561_UzsLu.jpg", + "small": "http://lain.bgm.tv/pic/cover/s/71/66/358561_UzsLu.jpg", + "grid": "http://lain.bgm.tv/pic/cover/g/71/66/358561_UzsLu.jpg" + }, + "collection": { + "doing": 9 + } + } + ] + } + ] + """ + ret_list = [] + result = self.__invoke(self._urls["calendar"], _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + if result: + for item in result: + ret_list.extend(item.get("items") or []) + return ret_list[(page - 1) * count: page * count] + + def detail(self, bid: int): + """ + 获取番剧详情 + """ + return self.__invoke(self._urls["detail"] % bid, _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + + def persons(self, bid: int): + """ + 获取番剧人物 + """ + return self.__invoke(self._urls["persons"] % bid, _ts=datetime.strftime(datetime.now(), '%Y%m%d')) + + def subjects(self, bid: int): + """ + 获取关联条目信息 + """ + return self.__invoke(self._urls["subjects"] % bid, _ts=datetime.strftime(datetime.now(), '%Y%m%d')) diff --git a/app/schemas/__init__.py b/app/schemas/__init__.py index 2781ea41..fc22e40d 100644 --- a/app/schemas/__init__.py +++ b/app/schemas/__init__.py @@ -14,3 +14,5 @@ from .message import * from .tmdb import * from .transfer import * from .file import * +from .bangumi import * +from .douban import * diff --git a/app/schemas/bangumi.py b/app/schemas/bangumi.py new file mode 100644 index 00000000..ac2b30b7 --- /dev/null +++ b/app/schemas/bangumi.py @@ -0,0 +1,12 @@ +from typing import Optional + +from pydantic import BaseModel + + +class BangumiPerson(BaseModel): + id: Optional[int] = None + name: Optional[str] = None + type: Optional[int] = 1 + career: Optional[list] = [] + images: Optional[dict] = {} + relation: Optional[str] = None diff --git a/app/schemas/context.py b/app/schemas/context.py index def40021..12e86700 100644 --- a/app/schemas/context.py +++ b/app/schemas/context.py @@ -83,6 +83,8 @@ class MediaInfo(BaseModel): tvdb_id: Optional[str] = None # 豆瓣ID douban_id: Optional[str] = None + # Bangumi ID + bangumi_id: Optional[int] = None # 媒体原语种 original_language: Optional[str] = None # 媒体原发行标题 diff --git a/app/schemas/douban.py b/app/schemas/douban.py new file mode 100644 index 00000000..d3b91584 --- /dev/null +++ b/app/schemas/douban.py @@ -0,0 +1,14 @@ +from typing import Optional + +from pydantic import BaseModel + + +class DoubanPerson(BaseModel): + id: Optional[str] = None + name: Optional[str] = None + roles: Optional[list] = [] + title: Optional[str] = None + url: Optional[str] = None + character: Optional[str] = None + avatar: Optional[dict] = None + latin_name: Optional[str] = None diff --git a/app/schemas/subscribe.py b/app/schemas/subscribe.py index 1249adf2..7398463b 100644 --- a/app/schemas/subscribe.py +++ b/app/schemas/subscribe.py @@ -15,6 +15,7 @@ class Subscribe(BaseModel): keyword: Optional[str] = None tmdbid: Optional[int] = None doubanid: Optional[str] = None + bangumiid: Optional[int] = None # 季号 season: Optional[int] = None # 海报 diff --git a/app/schemas/tmdb.py b/app/schemas/tmdb.py index cb21d2b1..696e8446 100644 --- a/app/schemas/tmdb.py +++ b/app/schemas/tmdb.py @@ -49,14 +49,3 @@ class TmdbPerson(BaseModel): popularity: Optional[float] = None images: Optional[dict] = {} biography: Optional[str] = None - - -class DoubanPerson(BaseModel): - id: Optional[str] = None - name: Optional[str] = None - roles: Optional[list] = [] - title: Optional[str] = None - url: Optional[str] = None - character: Optional[str] = None - avatar: Optional[dict] = None - latin_name: Optional[str] = None diff --git a/database/versions/d146dea51516_1_0_16.py b/database/versions/d146dea51516_1_0_16.py new file mode 100644 index 00000000..70f7408e --- /dev/null +++ b/database/versions/d146dea51516_1_0_16.py @@ -0,0 +1,34 @@ +"""1.0.16 + +Revision ID: d146dea51516 +Revises: 5813aaa7cb3a +Create Date: 2024-03-18 18:13:38.099531 + +""" +import contextlib + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'd146dea51516' +down_revision = '5813aaa7cb3a' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with contextlib.suppress(Exception): + with op.batch_alter_table("subscribe") as batch_op: + batch_op.add_column(sa.Column('bangumiid', sa.Integer, nullable=True)) + try: + op.create_index('ix_subscribe_bangumiid', 'subscribe', ['bangumiid'], unique=False) + except Exception as err: + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + pass