From 0e13d22c977515d89a76bacb83e9dd3b4ea48198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=AF=E5=A4=A7=E4=BE=A0?= Date: Tue, 4 Nov 2025 11:30:16 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix=20=E9=80=82=E9=85=8D=E6=96=B0=E7=89=88?= =?UTF-8?q?=E9=A3=9E=E7=89=9B=E5=BD=B1=E8=A7=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/endpoints/system.py | 14 +++++++++++--- app/chain/mediaserver.py | 10 ++++++++++ app/modules/trimemedia/__init__.py | 24 ++++++++++++++++++++++++ app/modules/trimemedia/api.py | 2 +- app/modules/trimemedia/trimemedia.py | 22 ++++++++++++++++++++++ 5 files changed, 68 insertions(+), 4 deletions(-) diff --git a/app/api/endpoints/system.py b/app/api/endpoints/system.py index a078d193..777b1de2 100644 --- a/app/api/endpoints/system.py +++ b/app/api/endpoints/system.py @@ -16,6 +16,7 @@ from fastapi import APIRouter, Body, Depends, HTTPException, Header, Request, Re from fastapi.responses import StreamingResponse from app import schemas +from app.chain.mediaserver import MediaServerChain from app.chain.search import SearchChain from app.chain.system import SystemChain from app.core.cache import AsyncFileCache @@ -52,6 +53,7 @@ async def fetch_image( proxy: bool = False, use_cache: bool = False, if_none_match: Optional[str] = None, + cookies: Optional[str | dict] = None, allowed_domains: Optional[set[str]] = None) -> Optional[Response]: """ 处理图片缓存逻辑,支持HTTP缓存和磁盘缓存 @@ -96,8 +98,13 @@ async def fetch_image( # 请求远程图片 referer = "https://movie.douban.com/" if "doubanio.com" in url else None proxies = settings.PROXY if proxy else None - response = await AsyncRequestUtils(ua=settings.NORMAL_USER_AGENT, proxies=proxies, referer=referer, - accept_type="image/avif,image/webp,image/apng,*/*").get_res(url=url) + response = await AsyncRequestUtils( + ua=settings.NORMAL_USER_AGENT, + proxies=proxies, + referer=referer, + cookies=cookies, + accept_type="image/avif,image/webp,image/apng,*/*", + ).get_res(url=url) if not response: logger.warn(f"Failed to fetch image from URL: {url}") return None @@ -150,7 +157,8 @@ async def proxy_img( hosts = [config.config.get("host") for config in MediaServerHelper().get_configs().values() if config and config.config and config.config.get("host")] allowed_domains = set(settings.SECURITY_IMAGE_DOMAINS) | set(hosts) - return await fetch_image(url=imgurl, proxy=proxy, use_cache=cache, + cookies = MediaServerChain().get_image_cookies(server=None, image_url=imgurl) + return await fetch_image(url=imgurl, proxy=proxy, use_cache=cache, cookies=cookies, if_none_match=if_none_match, allowed_domains=allowed_domains) diff --git a/app/chain/mediaserver.py b/app/chain/mediaserver.py index 399cfee4..e6ae098e 100644 --- a/app/chain/mediaserver.py +++ b/app/chain/mediaserver.py @@ -113,6 +113,16 @@ class MediaServerChain(ChainBase): """ return self.run_module("mediaserver_play_url", server=server, item_id=item_id) + def get_image_cookies( + self, server: Optional[str], image_url: str + ) -> Optional[str | dict]: + """ + 获取图片的Cookies + """ + return self.run_module( + "mediaserver_image_cookies", server=server, image_url=image_url + ) + def sync(self): """ 同步媒体库所有数据到本地数据库 diff --git a/app/modules/trimemedia/__init__.py b/app/modules/trimemedia/__init__.py index 73e5d0a6..ce6758cf 100644 --- a/app/modules/trimemedia/__init__.py +++ b/app/modules/trimemedia/__init__.py @@ -382,3 +382,27 @@ class TrimeMediaModule(_ModuleBase, _MediaServerBase[TrimeMedia]): if not server_obj: return [] return server_obj.get_latest_backdrops(num=count, remote=remote) or [] + + def mediaserver_image_cookies( + self, + server: Optional[str] = None, + image_url: Optional[str] = None, + **kwargs, + ) -> Optional[str | dict]: + """ + 获取飞牛影视服务器的图片Cookies + + :param server: 媒体服务器名称 + :param image_url: 图片网址 + """ + if not image_url: + return None + if server: + server_obj = self.get_instance(server) + if not server_obj: + return None + return server_obj.get_image_cookies(image_url) + else: + for server_obj in self.get_instances().values(): + if cookies := server_obj.get_image_cookies(image_url): + return cookies diff --git a/app/modules/trimemedia/api.py b/app/modules/trimemedia/api.py index e2533043..61e7fb84 100644 --- a/app/modules/trimemedia/api.py +++ b/app/modules/trimemedia/api.py @@ -190,7 +190,7 @@ class Api: """ 用户列表(仅管理员有权访问) """ - if (res := self.request("/memory/user/list")) and res.success: + if (res := self.request("/manager/user/list")) and res.success: if not res.data: return [] return [ diff --git a/app/modules/trimemedia/trimemedia.py b/app/modules/trimemedia/trimemedia.py index 93a884ee..8e760a36 100644 --- a/app/modules/trimemedia/trimemedia.py +++ b/app/modules/trimemedia/trimemedia.py @@ -5,6 +5,7 @@ import app.modules.trimemedia.api as fnapi from app import schemas from app.log import logger from app.schemas import MediaType +from app.utils.security import SecurityUtils from app.utils.url import UrlUtils @@ -20,6 +21,7 @@ class TrimeMedia: _sync_libraries: List[str] = [] _api: Optional[fnapi.Api] = None + _version: Optional[fnapi.Version] = None def __init__( self, @@ -55,6 +57,13 @@ class TrimeMedia: """ return self._api + @property + def version(self) -> Optional[fnapi.Version]: + """ + 获得飞牛API的版本 + """ + return self._version + class _ApiCreateResult: api: fnapi.Api version: fnapi.Version @@ -123,7 +132,9 @@ class TrimeMedia: self.disconnect() if result := self.__create_api(self._host): self._api = result.api + self._version = result.version # 版本号:0.8.53, 服务版本:0.8.23 + # 版本号:0.8.56, 服务版本:0.8.23 接口/memory/user/list改为/manager/user/list logger.debug( f"版本号:{result.version.frontend}, 服务版本:{result.version.backend}" ) @@ -603,6 +614,7 @@ class TrimeMedia: if (item_details := self._api.item(item.guid)) is None: continue if remote: + # FIXME 新版飞牛的壁纸无法直接在浏览器中访问 img_host = self._playhost or self._api.host else: img_host = self._api.host @@ -631,3 +643,13 @@ class TrimeMedia: ) else False ) + + def get_image_cookies(self, image_url: str): + """ + 获得指定图片的Cookies + """ + if not image_url or not SecurityUtils.is_safe_url( + image_url, [self._api.host], strict=True + ): + return None + return {"Trim-MC-token": self._api.token} From ee750115ec5b2ea5fe121d3747fd732660eeafed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A4=A7=E8=99=BE?= <802181+cddjr@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:29:45 +0800 Subject: [PATCH 2/3] Update Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- app/modules/trimemedia/trimemedia.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/modules/trimemedia/trimemedia.py b/app/modules/trimemedia/trimemedia.py index 8e760a36..5c90f755 100644 --- a/app/modules/trimemedia/trimemedia.py +++ b/app/modules/trimemedia/trimemedia.py @@ -648,6 +648,8 @@ class TrimeMedia: """ 获得指定图片的Cookies """ + if not self.is_authenticated(): + return None if not image_url or not SecurityUtils.is_safe_url( image_url, [self._api.host], strict=True ): From 1205fc7fdb519df61bb27219aaef2d884e5fbd89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=AF=E5=A4=A7=E4=BE=A0?= Date: Wed, 5 Nov 2025 15:01:08 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E9=81=BF=E5=85=8D=E4=B8=8D=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E7=9A=84=E5=9B=BE=E7=89=87cookies=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/endpoints/system.py | 7 ++++++- app/modules/trimemedia/trimemedia.py | 2 ++ app/schemas/mediaserver.py | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/api/endpoints/system.py b/app/api/endpoints/system.py index 777b1de2..25de81c9 100644 --- a/app/api/endpoints/system.py +++ b/app/api/endpoints/system.py @@ -147,6 +147,7 @@ async def proxy_img( imgurl: str, proxy: bool = False, cache: bool = False, + use_cookies: bool = False, if_none_match: Annotated[str | None, Header()] = None, _: schemas.TokenPayload = Depends(verify_resource_token) ) -> Response: @@ -157,7 +158,11 @@ async def proxy_img( hosts = [config.config.get("host") for config in MediaServerHelper().get_configs().values() if config and config.config and config.config.get("host")] allowed_domains = set(settings.SECURITY_IMAGE_DOMAINS) | set(hosts) - cookies = MediaServerChain().get_image_cookies(server=None, image_url=imgurl) + cookies = ( + MediaServerChain().get_image_cookies(server=None, image_url=imgurl) + if use_cookies + else None + ) return await fetch_image(url=imgurl, proxy=proxy, use_cache=cache, cookies=cookies, if_none_match=if_none_match, allowed_domains=allowed_domains) diff --git a/app/modules/trimemedia/trimemedia.py b/app/modules/trimemedia/trimemedia.py index 5c90f755..fce6b666 100644 --- a/app/modules/trimemedia/trimemedia.py +++ b/app/modules/trimemedia/trimemedia.py @@ -200,6 +200,7 @@ class TrimeMedia: ], link=f"{self._playhost or self._api.host}/library/{library.guid}", server_type="trimemedia", + use_cookies=True, ) ) return libraries @@ -498,6 +499,7 @@ class TrimeMedia: else 0 ), server_type="trimemedia", + use_cookies=True, ) def get_items( diff --git a/app/schemas/mediaserver.py b/app/schemas/mediaserver.py index a5427228..10d15710 100644 --- a/app/schemas/mediaserver.py +++ b/app/schemas/mediaserver.py @@ -74,6 +74,8 @@ class MediaServerLibrary(BaseModel): link: Optional[str] = None # 服务器类型 server_type: Optional[str] = None + # 飞牛的图片需要Cookies + use_cookies: Optional[bool] = None class MediaServerItemUserState(BaseModel): @@ -177,3 +179,5 @@ class MediaServerPlayItem(BaseModel): percent: Optional[float] = None BackdropImageTags: Optional[list] = Field(default_factory=list) server_type: Optional[str] = None + # 飞牛的图片需要Cookies + use_cookies: Optional[bool] = None