Files
MoviePilot/app/chain/douban.py
T

433 lines
16 KiB
Python

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)