Skip to content

Commit fc96cfe

Browse files
committed
feat:tvdb模块重写,更换tvdbv4 api,增加搜索能力
sonarr /series/lookup接口重写,直接用标题在tvdb查询剧集
1 parent f740fed commit fc96cfe

File tree

6 files changed

+593
-1184
lines changed

6 files changed

+593
-1184
lines changed

app/api/servarr.py

Lines changed: 63 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from app import schemas
77
from app.chain.media import MediaChain
8+
from app.chain.tvdb import TvdbChain
89
from app.chain.subscribe import SubscribeChain
910
from app.core.metainfo import MetaInfo
1011
from app.core.security import verify_apikey
@@ -520,87 +521,87 @@ def arr_series_lookup(term: str, _: Annotated[str, Depends(verify_apikey)], db:
520521
"""
521522
# 季信息
522523
seas: List[int] = []
523-
524+
# tvdbid 列表
525+
tvdbids: List[int] = []
524526
# 获取TVDBID
525527
if not term.startswith("tvdb:"):
526-
mediainfo = MediaChain().recognize_media(meta=MetaInfo(term),
527-
mtype=MediaType.TV)
528-
if not mediainfo:
529-
return [SonarrSeries()]
530-
531-
# 季信息
532-
if mediainfo.seasons:
533-
seas = list(mediainfo.seasons)
528+
title = term.replace("+", " ")
529+
tvdbids = TvdbChain().get_tvdbid_by_name(title=title)
534530
else:
535531
tvdbid = int(term.replace("tvdb:", ""))
532+
tvdbids.append(tvdbid)
536533

534+
sonarr_series_list = []
535+
for tvdbid in tvdbids:
537536
# 查询TVDB信息
538537
tvdbinfo = MediaChain().tvdb_info(tvdbid=tvdbid)
539538
if not tvdbinfo:
540-
return [SonarrSeries()]
539+
continue
541540

542-
# 季信息
543-
sea_num = tvdbinfo.get('season')
541+
# 季信息(只取默认季类型,排除特别季)
542+
sea_num = len([season for season in tvdbinfo.get('seasons') if
543+
season['type']['id'] == tvdbinfo.get('defaultSeasonType') and season['number'] > 0])
544544
if sea_num:
545545
seas = list(range(1, int(sea_num) + 1))
546546

547547
# 根据TVDB查询媒体信息
548-
mediainfo = MediaChain().recognize_media(meta=MetaInfo(tvdbinfo.get('seriesName')),
548+
mediainfo = MediaChain().recognize_media(meta=MetaInfo(tvdbinfo.get('name')),
549549
mtype=MediaType.TV)
550-
551-
# 查询是否存在
552-
exists = MediaChain().media_exists(mediainfo)
553-
if exists:
554-
hasfile = True
555-
else:
556-
hasfile = False
557-
558-
# 查询订阅信息
559-
seasons: List[dict] = []
560-
subscribes = Subscribe.get_by_tmdbid(db, mediainfo.tmdb_id)
561-
if subscribes:
562-
# 已监控
563-
monitored = True
564-
# 已监控季
565-
sub_seas = [sub.season for sub in subscribes]
566-
for sea in seas:
567-
if sea in sub_seas:
568-
seasons.append({
569-
"seasonNumber": sea,
570-
"monitored": True,
571-
})
572-
else:
550+
if not mediainfo:
551+
continue
552+
# 查询是否存在
553+
exists = MediaChain().media_exists(mediainfo)
554+
if exists:
555+
hasfile = True
556+
else:
557+
hasfile = False
558+
559+
# 查询订阅信息
560+
seasons: List[dict] = []
561+
subscribes = Subscribe.get_by_tmdbid(db, mediainfo.tmdb_id)
562+
if subscribes:
563+
# 已监控
564+
monitored = True
565+
# 已监控季
566+
sub_seas = [sub.season for sub in subscribes]
567+
for sea in seas:
568+
if sea in sub_seas:
569+
seasons.append({
570+
"seasonNumber": sea,
571+
"monitored": True,
572+
})
573+
else:
574+
seasons.append({
575+
"seasonNumber": sea,
576+
"monitored": False,
577+
})
578+
subid = subscribes[-1].id
579+
else:
580+
subid = None
581+
monitored = False
582+
for sea in seas:
573583
seasons.append({
574584
"seasonNumber": sea,
575585
"monitored": False,
576586
})
577-
subid = subscribes[-1].id
578-
else:
579-
subid = None
580-
monitored = False
581-
for sea in seas:
582-
seasons.append({
583-
"seasonNumber": sea,
584-
"monitored": False,
585-
})
587+
sonarr_series = SonarrSeries(
588+
id=subid,
589+
title=mediainfo.title,
590+
seasonCount=len(seasons),
591+
seasons=seasons,
592+
remotePoster=mediainfo.get_poster_image(),
593+
year=mediainfo.year,
594+
tmdbId=mediainfo.tmdb_id,
595+
tvdbId=tvdbid,
596+
imdbId=mediainfo.imdb_id,
597+
profileId=1,
598+
languageProfileId=1,
599+
monitored=monitored,
600+
hasFile=hasfile,
601+
)
602+
sonarr_series_list.append(sonarr_series)
586603

587-
return [SonarrSeries(
588-
id=subid,
589-
title=mediainfo.title,
590-
seasonCount=len(seasons),
591-
seasons=seasons,
592-
remotePoster=mediainfo.get_poster_image(),
593-
year=mediainfo.year,
594-
tmdbId=mediainfo.tmdb_id,
595-
tvdbId=mediainfo.tvdb_id,
596-
imdbId=mediainfo.imdb_id,
597-
profileId=1,
598-
languageProfileId=1,
599-
qualityProfileId=1,
600-
isAvailable=True,
601-
monitored=monitored,
602-
hasFile=hasfile
603-
)]
604+
return sonarr_series_list if sonarr_series_list else [SonarrSeries()]
604605

605606

606607
@arr_router.get("/series/{tid}", summary="剧集详情")

app/chain/tvdb.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import List
2+
from app.chain import ChainBase
3+
from app.utils.singleton import Singleton
4+
5+
6+
class TvdbChain(ChainBase, metaclass=Singleton):
7+
"""
8+
Tvdb处理链,单例运行
9+
"""
10+
11+
def get_tvdbid_by_name(self, title: str) -> List[int]:
12+
tvdb_info_list = self.run_module("search_tvdb", title=title)
13+
return [int(item["tvdb_id"]) for item in tvdb_info_list]

app/core/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ class Config:
111111
# TMDB API Key
112112
TMDB_API_KEY: str = "db55323b8d3e4154498498a75642b381"
113113
# TVDB API Key
114-
TVDB_API_KEY: str = "6b481081-10aa-440c-99f2-21d17717ee02"
114+
TVDB_V4_API_KEY: str = "ed2aa66b-7899-4677-92a7-67bc9ce3d93a"
115+
TVDB_V4_API_PIN: str = ""
115116
# Fanart开关
116117
FANART_ENABLE: bool = True
117118
# Fanart API Key

app/modules/thetvdb/__init__.py

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,42 @@
33
from app.core.config import settings
44
from app.log import logger
55
from app.modules import _ModuleBase
6-
from app.modules.thetvdb import tvdbapi
6+
from app.modules.thetvdb import tvdb_v4_official
77
from app.schemas.types import ModuleType, MediaRecognizeType
8-
from app.utils.http import RequestUtils
98

109

1110
class TheTvDbModule(_ModuleBase):
12-
tvdb: tvdbapi.Tvdb = None
11+
tvdb: tvdb_v4_official.TVDB = None
1312

1413
def init_module(self) -> None:
15-
self.tvdb = tvdbapi.Tvdb(apikey=settings.TVDB_API_KEY,
16-
cache=False,
17-
select_first=True,
18-
proxies=settings.PROXY)
14+
self._initialize_tvdb_session()
15+
16+
def _initialize_tvdb_session(self) -> None:
17+
"""
18+
创建或刷新 TVDB 登录会话
19+
"""
20+
try:
21+
self.tvdb = tvdb_v4_official.TVDB(apikey=settings.TVDB_V4_API_KEY, pin=settings.TVDB_V4_API_PIN)
22+
except Exception as e:
23+
logger.error(f"TVDB 登录失败: {str(e)}")
24+
25+
def _handle_tvdb_call(self, func, *args, **kwargs):
26+
"""
27+
包裹 TVDB 调用,处理 token 失效情况并尝试重新初始化
28+
"""
29+
try:
30+
return func(*args, **kwargs)
31+
except ValueError as e:
32+
# 检查错误信息中是否包含 token 失效相关描述
33+
if "failed to get" in str(e) or "Unauthorized" in str(e) or "NOT_MODIFIED" in str(e):
34+
logger.warning("TVDB Token 可能已失效,正在尝试重新登录...")
35+
self._initialize_tvdb_session()
36+
return func(*args, **kwargs)
37+
else:
38+
raise
39+
except Exception as e:
40+
logger.error(f"TVDB 调用出错: {str(e)}")
41+
raise
1942

2043
@staticmethod
2144
def get_name() -> str:
@@ -43,18 +66,17 @@ def get_priority() -> int:
4366
return 4
4467

4568
def stop(self):
46-
self.tvdb.close()
69+
pass
4770

4871
def test(self) -> Tuple[bool, str]:
4972
"""
5073
测试模块连接性
5174
"""
52-
ret = RequestUtils(proxies=settings.PROXY).get_res("https://api.thetvdb.com/series/81189")
53-
if ret and ret.status_code == 200:
75+
try:
76+
self._handle_tvdb_call(self.tvdb.get_series, 81189)
5477
return True, ""
55-
elif ret:
56-
return False, f"无法连接 api.thetvdb.com,错误码:{ret.status_code}"
57-
return False, "api.thetvdb.com 网络连接失败"
78+
except Exception as e:
79+
return False, str(e)
5880

5981
def init_setting(self) -> Tuple[str, Union[str, bool]]:
6082
pass
@@ -67,6 +89,20 @@ def tvdb_info(self, tvdbid: int) -> Optional[dict]:
6789
"""
6890
try:
6991
logger.info(f"开始获取TVDB信息: {tvdbid} ...")
70-
return self.tvdb[tvdbid].data
92+
return self._handle_tvdb_call(self.tvdb.get_series_extended, tvdbid)
7193
except Exception as err:
7294
logger.error(f"获取TVDB信息失败: {str(err)}")
95+
return None
96+
97+
def search_tvdb(self, title: str) -> list:
98+
"""
99+
用标题搜索TVDB
100+
:param title: 标题
101+
:return: TVDB信息
102+
"""
103+
try:
104+
logger.info(f"开始用标题搜索TVDB: {title} ...")
105+
return self._handle_tvdb_call(self.tvdb.search, title)
106+
except Exception as err:
107+
logger.error(f"用标题搜索TVDB失败: {str(err)}")
108+
return []

0 commit comments

Comments
 (0)