from typing import Any, List, Optional from app.schemas.context import MediaPerson as _SchemaMediaPerson from app.chain import ChainBase from app.domain.context import MediaInfo, MusicAlbumInfo, MusicInfo from app.domain.meta.metamusic import MetaMusic from app.schemas.types import MUSIC_ENTITY_ALBUM, MediaSource, MediaType class DoubanChain(ChainBase): """ 豆瓣处理链 """ music_source = MediaSource.DoubanMusic def search_music(self, meta: MetaMusic, limit: int = 20) -> list[MusicInfo]: """按音乐元数据搜索豆瓣音乐候选。""" result = self.run_module( "search_music", meta=meta, limit=limit, media_source=self.music_source, ) return self._music_infos(result, limit=limit) async def async_search_music( self, meta: MetaMusic, limit: int = 20, ) -> list[MusicInfo]: """异步按音乐元数据搜索豆瓣音乐候选。""" result = await self.async_run_module( "search_music", meta=meta, limit=limit, media_source=self.music_source, ) return self._music_infos(result, limit=limit) def recognize_music( self, meta: Optional[MetaMusic] = None, media_id: Optional[str] = None, cache: bool = True, music_type: Optional[str] = None, ) -> Optional[MusicInfo]: """按豆瓣音乐身份或音乐元数据识别标准音乐信息。""" normalized_id = self._normalize_music_id(media_id) result = self.run_module( "recognize_media", meta=meta, mtype=MediaType.MUSIC, media_source=self.music_source, media_id=normalized_id, cache=cache, music_type=music_type, ) return self._music_info(result, media_id=normalized_id) async def async_recognize_music( self, meta: Optional[MetaMusic] = None, media_id: Optional[str] = None, cache: bool = True, music_type: Optional[str] = None, ) -> Optional[MusicInfo]: """异步按豆瓣音乐身份或音乐元数据识别标准音乐信息。""" normalized_id = self._normalize_music_id(media_id) result = await self.async_run_module( "async_recognize_media", meta=meta, mtype=MediaType.MUSIC, media_source=self.music_source, media_id=normalized_id, cache=cache, music_type=music_type, ) return self._music_info(result, media_id=normalized_id) def get_music_album(self, media_id: str) -> Optional[MusicAlbumInfo]: """按豆瓣音乐专辑 ID 获取标准化专辑详情。""" normalized_id = self._normalize_music_id(media_id) if not normalized_id: return None result = self.run_module( "music_album", media_source=self.music_source, media_id=normalized_id, ) return self._music_album(result, media_id=normalized_id) async def async_get_music_album(self, media_id: str) -> Optional[MusicAlbumInfo]: """异步按豆瓣音乐专辑 ID 获取标准化专辑详情。""" normalized_id = self._normalize_music_id(media_id) if not normalized_id: return None result = await self.async_run_module( "music_album", media_source=self.music_source, media_id=normalized_id, ) return self._music_album(result, media_id=normalized_id) async def async_get_music_album_related( self, media_id: str, count: int = 24, ) -> list[MusicInfo]: """异步按豆瓣音乐专辑 ID 获取相关推荐。""" normalized_id = self._normalize_music_id(media_id) if not normalized_id: return [] result = await self.async_run_module( "music_album_related", media_source=self.music_source, media_id=normalized_id, count=count, ) return self._music_infos(result, limit=count) def music_discover( self, page: int = 1, count: int = 30, entity: str = MUSIC_ENTITY_ALBUM, mode: str = "chart", tags: str = "", sort: str = "U", ) -> list[MusicInfo]: """按豆瓣音乐官方榜单或标签浏览标准音乐条目。""" result = self.run_module( "music_discover", media_source=self.music_source, page=page, count=count, entity=entity, mode=mode, tags=tags, sort=sort, ) return self._music_infos(result, limit=count) async def async_music_discover( self, page: int = 1, count: int = 30, entity: str = MUSIC_ENTITY_ALBUM, mode: str = "chart", tags: str = "", sort: str = "U", ) -> list[MusicInfo]: """异步按豆瓣音乐官方榜单或标签浏览标准音乐条目。""" result = await self.async_run_module( "music_discover", media_source=self.music_source, page=page, count=count, entity=entity, mode=mode, tags=tags, sort=sort, ) return self._music_infos(result, limit=count) @staticmethod def _normalize_music_id(media_id: Optional[str]) -> Optional[str]: """清理豆瓣音乐原生 ID,空值和历史零哨兵按无身份处理。""" normalized = str(media_id).strip() if media_id is not None else "" return normalized if normalized and normalized != "0" else None @classmethod def _music_infos(cls, result: Any, limit: Optional[int] = None) -> list[MusicInfo]: """将模块或插件结果转换为豆瓣音乐候选列表。""" candidates = result if isinstance(result, list) else [] infos = [ item if isinstance(item, MusicInfo) else MusicInfo.from_dict(item) for item in candidates if isinstance(item, (MusicInfo, dict)) ] infos = [info for info in infos if info.media_source == cls.music_source] return infos[:limit] if limit else infos @classmethod def _music_info( cls, result: Any, media_id: Optional[str] = None, ) -> Optional[MusicInfo]: """校验豆瓣音乐识别结果的来源与显式身份。""" if isinstance(result, MusicInfo): info = result elif isinstance(result, dict): info = MusicInfo.from_dict(result) else: return None if info.media_source and info.media_source != cls.music_source: return None if media_id and ( info.media_source != cls.music_source or info.media_id != media_id ): return None return info @classmethod def _music_album( cls, result: Any, media_id: Optional[str] = None, ) -> Optional[MusicAlbumInfo]: """将模块或插件结果转换为豆瓣音乐专辑详情。""" if isinstance(result, MusicAlbumInfo): album = result elif isinstance(result, dict): album = MusicAlbumInfo.from_dict(result) else: return None if album.media_source != cls.music_source: return None if media_id and album.media_id != media_id: return None return album def person_detail(self, person_id: int) -> Optional[_SchemaMediaPerson]: """ 根据人物ID查询豆瓣人物详情 :param person_id: 人物ID """ return self.run_module("douban_person_detail", person_id=person_id) def person_credits(self, person_id: int, page: Optional[int] = 1) -> List[MediaInfo]: """ 根据人物ID查询人物参演作品 :param person_id: 人物ID :param page: 页码 """ return self.run_module("douban_person_credits", person_id=person_id, page=page) def movie_top250(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取豆瓣电影TOP250 :param page: 页码 :param count: 每页数量 """ return self.run_module("movie_top250", page=page, count=count) def movie_showing(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取正在上映的电影 """ return self.run_module("movie_showing", page=page, count=count) def tv_weekly_chinese(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取本周中国剧集榜 """ return self.run_module("tv_weekly_chinese", page=page, count=count) def tv_weekly_global(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取本周全球剧集榜 """ return self.run_module("tv_weekly_global", page=page, count=count) def douban_discover(self, mtype: MediaType, sort: str, tags: str, page: Optional[int] = 0, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 发现豆瓣电影、剧集 :param mtype: 媒体类型 :param sort: 排序方式 :param tags: 标签 :param page: 页码 :param count: 数量 :return: 媒体信息列表 """ return self.run_module("douban_discover", mtype=mtype, sort=sort, tags=tags, page=page, count=count) def tv_animation(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取动画剧集 """ return self.run_module("tv_animation", page=page, count=count) def movie_hot(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取热门电影 """ return self.run_module("movie_hot", page=page, count=count) def tv_hot(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取热门剧集 """ return self.run_module("tv_hot", page=page, count=count) def movie_credits(self, doubanid: str) -> Optional[List[_SchemaMediaPerson]]: """ 根据TMDBID查询电影演职人员 :param doubanid: 豆瓣ID """ return self.run_module("douban_movie_credits", doubanid=doubanid) def tv_credits(self, doubanid: str) -> Optional[List[_SchemaMediaPerson]]: """ 根据TMDBID查询电视剧演职人员 :param doubanid: 豆瓣ID """ return self.run_module("douban_tv_credits", doubanid=doubanid) def movie_recommend(self, doubanid: str) -> List[MediaInfo]: """ 根据豆瓣ID查询推荐电影 :param doubanid: 豆瓣ID """ return self.run_module("douban_movie_recommend", doubanid=doubanid) def tv_recommend(self, doubanid: str) -> List[MediaInfo]: """ 根据豆瓣ID查询推荐电视剧 :param doubanid: 豆瓣ID """ return self.run_module("douban_tv_recommend", doubanid=doubanid) async def async_person_detail(self, person_id: int) -> Optional[_SchemaMediaPerson]: """ 根据人物ID查询豆瓣人物详情(异步版本) :param person_id: 人物ID """ return await self.async_run_module("async_douban_person_detail", person_id=person_id) async def async_person_credits(self, person_id: int, page: Optional[int] = 1) -> List[MediaInfo]: """ 根据人物ID查询人物参演作品(异步版本) :param person_id: 人物ID :param page: 页码 """ return await self.async_run_module("async_douban_person_credits", person_id=person_id, page=page) async def async_movie_top250(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取豆瓣电影TOP250(异步版本) :param page: 页码 :param count: 每页数量 """ return await self.async_run_module("async_movie_top250", page=page, count=count) async def async_movie_showing(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取正在上映的电影(异步版本) """ return await self.async_run_module("async_movie_showing", page=page, count=count) async def async_tv_weekly_chinese(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取本周中国剧集榜(异步版本) """ return await self.async_run_module("async_tv_weekly_chinese", page=page, count=count) async def async_tv_weekly_global(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取本周全球剧集榜(异步版本) """ return await self.async_run_module("async_tv_weekly_global", page=page, count=count) async def async_douban_discover(self, mtype: MediaType, sort: str, tags: str, page: Optional[int] = 0, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 发现豆瓣电影、剧集(异步版本) :param mtype: 媒体类型 :param sort: 排序方式 :param tags: 标签 :param page: 页码 :param count: 数量 :return: 媒体信息列表 """ return await self.async_run_module("async_douban_discover", mtype=mtype, sort=sort, tags=tags, page=page, count=count) async def async_tv_animation(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取动画剧集(异步版本) """ return await self.async_run_module("async_tv_animation", page=page, count=count) async def async_movie_hot(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取热门电影(异步版本) """ return await self.async_run_module("async_movie_hot", page=page, count=count) async def async_tv_hot(self, page: Optional[int] = 1, count: Optional[int] = 30) -> Optional[List[MediaInfo]]: """ 获取热门剧集(异步版本) """ return await self.async_run_module("async_tv_hot", page=page, count=count) async def async_movie_credits(self, doubanid: str) -> Optional[List[_SchemaMediaPerson]]: """ 根据TMDBID查询电影演职人员(异步版本) :param doubanid: 豆瓣ID """ return await self.async_run_module("async_douban_movie_credits", doubanid=doubanid) async def async_tv_credits(self, doubanid: str) -> Optional[List[_SchemaMediaPerson]]: """ 根据TMDBID查询电视剧演职人员(异步版本) :param doubanid: 豆瓣ID """ return await self.async_run_module("async_douban_tv_credits", doubanid=doubanid) async def async_movie_recommend(self, doubanid: str) -> List[MediaInfo]: """ 根据豆瓣ID查询推荐电影(异步版本) :param doubanid: 豆瓣ID """ return await self.async_run_module("async_douban_movie_recommend", doubanid=doubanid) async def async_tv_recommend(self, doubanid: str) -> List[MediaInfo]: """ 根据豆瓣ID查询推荐电视剧(异步版本) :param doubanid: 豆瓣ID """ return await self.async_run_module("async_douban_tv_recommend", doubanid=doubanid)