diff --git a/app/modules/trimemedia/__init__.py b/app/modules/trimemedia/__init__.py index 60de895f..73e5d0a6 100644 --- a/app/modules/trimemedia/__init__.py +++ b/app/modules/trimemedia/__init__.py @@ -154,7 +154,7 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): """ source = args.get("source") if source: - server: TrimeMedia = self.get_instance(source) + server: Optional[TrimeMedia] = self.get_instance(source) if not server: return None result = server.get_webhook_message(body) @@ -247,7 +247,7 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): 媒体数量统计 """ if server: - server_obj: TrimeMedia = self.get_instance(server) + server_obj: Optional[TrimeMedia] = self.get_instance(server) if not server_obj: return None servers = [server_obj] @@ -268,7 +268,7 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): """ 媒体库列表 """ - server_obj: TrimeMedia = self.get_instance(server) + server_obj: Optional[TrimeMedia] = self.get_instance(server) if server_obj: return server_obj.get_librarys(hidden=hidden) return None @@ -290,7 +290,7 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): :return: 返回一个生成器对象,用于逐步获取媒体服务器中的项目 """ - server_obj: TrimeMedia = self.get_instance(server) + server_obj: Optional[TrimeMedia] = self.get_instance(server) if server_obj: return server_obj.get_items(library_id, start_index, limit) return None @@ -301,7 +301,7 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): """ 媒体库项目详情 """ - server_obj: TrimeMedia = self.get_instance(server) + server_obj: Optional[TrimeMedia] = self.get_instance(server) if server_obj: return server_obj.get_iteminfo(item_id) return None @@ -312,7 +312,9 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): """ 获取剧集信息 """ - server_obj: TrimeMedia = self.get_instance(server) + if not isinstance(item_id, str): + return None + server_obj: Optional[TrimeMedia] = self.get_instance(server) if not server_obj: return None _, seasoninfo = server_obj.get_tv_episodes(item_id=item_id) @@ -329,10 +331,10 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): """ 获取媒体服务器正在播放信息 """ - server_obj: TrimeMedia = self.get_instance(server) + server_obj: Optional[TrimeMedia] = self.get_instance(server) if not server_obj: return [] - return server_obj.get_resume(num=count) + return server_obj.get_resume(num=count) or [] def mediaserver_play_url( self, server: str, item_id: Union[str, int] @@ -340,7 +342,9 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): """ 获取媒体库播放地址 """ - server_obj: TrimeMedia = self.get_instance(server) + if not isinstance(item_id, str): + return None + server_obj: Optional[TrimeMedia] = self.get_instance(server) if not server_obj: return None return server_obj.get_play_url(item_id) @@ -354,10 +358,10 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): """ 获取媒体服务器最新入库条目 """ - server_obj: TrimeMedia = self.get_instance(server) + server_obj: Optional[TrimeMedia] = self.get_instance(server) if not server_obj: return [] - return server_obj.get_latest(num=count) + return server_obj.get_latest(num=count) or [] def mediaserver_latest_images( self, @@ -374,7 +378,7 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): :param remote: True为外网链接, False为内网链接 :return: 图片链接列表 """ - server_obj: TrimeMedia = self.get_instance(server) + server_obj: Optional[TrimeMedia] = self.get_instance(server) if not server_obj: return [] - return server_obj.get_latest_backdrops(num=count, remote=remote) + return server_obj.get_latest_backdrops(num=count, remote=remote) or [] diff --git a/app/modules/trimemedia/api.py b/app/modules/trimemedia/api.py index 2b8c5204..61e7fb84 100644 --- a/app/modules/trimemedia/api.py +++ b/app/modules/trimemedia/api.py @@ -140,13 +140,13 @@ class Api: self._token: Optional[str] = None self._version: Optional[Version] = None self._session = requests.Session() - self._request_utils = RequestUtils(session=self._session) + self._request_utils = RequestUtils(session=self._session, timeout=10) def sys_version(self) -> Optional[Version]: """ 飞牛影视版本号 """ - if (res := self.__request_api("/sys/version")) and res.success: + if (res := self.request("/sys/version")) and res.success: if res.data: self._version = Version( frontend=res.data.get("version"), @@ -162,7 +162,7 @@ class Api: :return: 成功返回token 否则返回None """ if ( - res := self.__request_api( + res := self.request( "/login", data={ "username": username, @@ -178,7 +178,9 @@ class Api: """ 退出账号 """ - if (res := self.__request_api("/user/logout", method="post")) and res.success: + if not self._token: + return True + if (res := self.request("/user/logout", method="post")) and res.success: if res.data: self._token = None return True @@ -188,7 +190,9 @@ class Api: """ 用户列表(仅管理员有权访问) """ - if (res := self.__request_api("/manager/user/list")) and res.success: + if (res := self.request("/manager/user/list")) and res.success: + if not res.data: + return [] return [ User( guid=info.get("guid"), @@ -203,7 +207,7 @@ class Api: """ 当前用户信息 """ - if (res := self.__request_api("/user/info")) and res.success: + if (res := self.request("/user/info")) and res.success: _user = User("", "") _user.__dict__.update(res.data) return _user @@ -213,7 +217,7 @@ class Api: """ 媒体数量统计 """ - if (res := self.__request_api("/mediadb/sum")) and res.success: + if (res := self.request("/mediadb/sum")) and res.success: sums = MediaDbSummary() sums.__dict__.update(res.data) return sums @@ -223,9 +227,9 @@ class Api: """ 媒体库列表(普通用户) """ - if (res := self.__request_api("/mediadb/list")) and res.success: + if (res := self.request("/mediadb/list")) and res.success: _items = [] - for info in res.data: + for info in res.data or []: mdb = MediaDb( guid=info.get("guid"), category=Category(info.get("category")), @@ -250,9 +254,9 @@ class Api: """ 媒体库列表(管理员) """ - if (res := self.__request_api("/mdb/list")) and res.success: + if (res := self.request("/mdb/list")) and res.success: _items = [] - for info in res.data: + for info in res.data or []: mdb = MediaDb( guid=info.get("guid"), category=Category(info.get("category")), @@ -271,7 +275,7 @@ class Api: """ 扫描所有媒体库 """ - if (res := self.__request_api("/mdb/scanall", method="post")) and res.success: + if (res := self.request("/mdb/scanall", method="post")) and res.success: if res.data: return True return False @@ -280,9 +284,7 @@ class Api: """ 扫描指定媒体库 """ - if ( - res := self.__request_api(f"/mdb/scan/{mdb.guid}", data={}) - ) and res.success: + if (res := self.request(f"/mdb/scan/{mdb.guid}", data={})) and res.success: if res.data: return True return False @@ -291,9 +293,7 @@ class Api: """ 当前正在运行的任务 """ - if ( - res := self.__request_api("/task/running") - ) and res.success: + if (res := self.request("/task/running")) and res.success: if res.data: # TODO 具体正在运行的任务 return True @@ -341,7 +341,9 @@ class Api: if exclude_grouped_video: post["exclude_grouped_video"] = 1 - if (res := self.__request_api("/item/list", data=post)) and res.success: + if (res := self.request("/item/list", data=post)) and res.success: + if not res.data: + return [] return [self.__build_item(info) for info in res.data.get("list", [])] return None @@ -350,8 +352,10 @@ class Api: 搜索影片、演员 """ if ( - res := self.__request_api("/search/list", params={"q": keywords}) + res := self.request("/search/list", params={"q": keywords}) ) and res.success: + if not res.data: + return [] return [self.__build_item(info) for info in res.data] return None @@ -359,7 +363,7 @@ class Api: """ 查询媒体详情 """ - if (res := self.__request_api(f"/item/{guid}")) and res.success: + if (res := self.request(f"/item/{guid}")) and res.success: return self.__build_item(res.data) return None @@ -370,7 +374,7 @@ class Api: :param delete_file: True删除媒体文件,False仅从媒体库移除 """ if ( - res := self.__request_api( + res := self.request( f"/item/{guid}", method="delete", data={"delete_file": 1 if delete_file else 0, "media_guids": []}, @@ -384,7 +388,9 @@ class Api: """ 查询季列表 """ - if (res := self.__request_api(f"/season/list/{tv_guid}")) and res.success: + if (res := self.request(f"/season/list/{tv_guid}")) and res.success: + if not res.data: + return [] return [self.__build_item(info) for info in res.data] return None @@ -392,7 +398,9 @@ class Api: """ 查询剧集列表 """ - if (res := self.__request_api(f"/episode/list/{season_guid}")) and res.success: + if (res := self.request(f"/episode/list/{season_guid}")) and res.success: + if not res.data: + return [] return [self.__build_item(info) for info in res.data] return None @@ -400,7 +408,9 @@ class Api: """ 继续观看列表 """ - if (res := self.__request_api("/play/list")) and res.success: + if (res := self.request("/play/list")) and res.success: + if not res.data: + return [] return [self.__build_item(info) for info in res.data] return None @@ -431,7 +441,7 @@ class Api: sign = md5.hexdigest() return f"nonce={nonce}×tamp={ts}&sign={sign}" - def __request_api( + def request( self, api: str, method: Optional[str] = None, @@ -482,6 +492,8 @@ class Api: queries_unquoted = None headers = { "User-Agent": settings.USER_AGENT, + "Accept": "application/json", + "Referer": self._host, "Authorization": self._token, "authx": self.__get_authx(api_path, json_body or queries_unquoted), } diff --git a/app/modules/trimemedia/trimemedia.py b/app/modules/trimemedia/trimemedia.py index ccc392ff..47163146 100644 --- a/app/modules/trimemedia/trimemedia.py +++ b/app/modules/trimemedia/trimemedia.py @@ -13,6 +13,7 @@ class TrimeMedia: _password: Optional[str] = None _userinfo: Optional[fnapi.User] = None + _host: Optional[str] = None _playhost: Optional[str] = None _libraries: dict[str, fnapi.MediaDb] = {} @@ -34,20 +35,19 @@ class TrimeMedia: return self._username = username self._password = password + self._host = host self._sync_libraries = sync_libraries or [] - if (api := self.__create_api(host)) is None: + if not self.reconnect(): logger.error(f"请检查服务端地址 {host}") return - self._api = api - if play_api := self.__create_api(play_host): - self._playhost = play_api.host + if result := self.__create_api(play_host): + self._playhost = result.api.host + result.api.close() elif play_host: logger.warning(f"请检查外网播放地址 {play_host}") self._playhost = UrlUtils.standardize_base_url(play_host).rstrip("/") - self.reconnect() - @property def api(self) -> Optional[fnapi.Api]: """ @@ -56,13 +56,18 @@ class TrimeMedia: return self._api @staticmethod - def __create_api(host: Optional[str]) -> Optional[fnapi.Api]: + def __create_api(host: Optional[str]): """ 创建一个飞牛API :param host: 服务端地址 :return: 如果地址无效、不可访问则返回None """ + + class Result: + api: fnapi.Api + version: fnapi.Version + if not host: return None api_key = "16CCEB3D-AB42-077D-36A1-F355324E4237" @@ -70,21 +75,37 @@ class TrimeMedia: if not host.endswith("/v"): # 尝试补上结尾的/v 测试能否正常访问 - api = fnapi.Api(host + "/v", api_key) - if api.sys_version(): - return api + res = Result() + res.api = fnapi.Api(host + "/v", api_key) + if fnver := res.api.sys_version(): + res.version = fnver + return res # 测试用户配置的地址 - api = fnapi.Api(host, api_key) - return api if api.sys_version() else None + res = Result() + res.api = fnapi.Api(host, api_key) + if fnver := res.api.sys_version(): + res.version = fnver + return res + return None def close(self): self.disconnect() def is_configured(self) -> bool: - return self._api is not None + if self._host and self._username and self._password: + return True + return False def is_authenticated(self) -> bool: - return self.is_configured() and self._api.token is not None + """ + 是否已登录 + """ + return ( + self.is_configured() + and self._api is not None + and self._api.token is not None + and self._userinfo is not None + ) def is_inactive(self) -> bool: """ @@ -101,10 +122,15 @@ class TrimeMedia: """ if not self.is_configured(): return False - if (fnver := self._api.sys_version()) is None: + self.disconnect() + if result := self.__create_api(self._host): + self._api = result.api + # 版本号:0.8.53, 服务版本:0.8.23 + logger.debug( + f"版本号:{result.version.frontend}, 服务版本:{result.version.backend}" + ) + else: return False - # 版本号:0.8.36, 服务版本:0.8.19 - logger.debug(f"版本号:{fnver.frontend}, 服务版本:{fnver.backend}") if self._api.login(self._username, self._password) is None: return False self._userinfo = self._api.user_info() @@ -119,9 +145,10 @@ class TrimeMedia: """ 断开与飞牛的连接 """ - if self.is_authenticated(): + if self._api: self._api.logout() self._api.close() + self._api = None self._userinfo = None logger.debug(f"{self._username} 已断开飞牛影视") @@ -163,7 +190,7 @@ class TrimeMedia: for img_path in library.posters or [] ], link=f"{self._playhost or self._api.host}/library/{library.guid}", - server_type='trimemedia' + server_type="trimemedia", ) ) return libraries @@ -205,10 +232,12 @@ class TrimeMedia: return None if not self.is_configured(): return None - feiniu = fnapi.Api(self._api.host, self._api.apikey) - if token := feiniu.login(username, password): - feiniu.logout() - return token + if result := self.__create_api(self._host): + try: + return result.api.login(username, password) + finally: + result.api.logout() + result.api.close() def get_movies( self, title: str, year: Optional[str] = None, tmdb_id: Optional[int] = None @@ -459,7 +488,7 @@ class TrimeMedia: if item.duration and item.ts is not None else 0 ), - server_type='trimemedia', + server_type="trimemedia", ) def get_items(