mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-09 01:16:50 +08:00
feat(trimemedia): 支持飞牛影视访问码
飞牛开启访问码后,需先请求 /c/{访问码} 获得会话凭证,
否则登录接口与图片等资源均返回 404。
- Api 增加 access_code 参数及 verify_access_code(),登录前先校验
- 创建 API 连接时校验访问码,失败则关闭会话
- 图片 Cookies 附带访问码校验凭证,避免代理图片 404
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import time
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
@@ -108,6 +109,7 @@ class Api:
|
|||||||
"_host",
|
"_host",
|
||||||
"_token",
|
"_token",
|
||||||
"_apikey",
|
"_apikey",
|
||||||
|
"_access_code",
|
||||||
"_api_path",
|
"_api_path",
|
||||||
"_request_utils",
|
"_request_utils",
|
||||||
"_version",
|
"_version",
|
||||||
@@ -130,18 +132,51 @@ class Api:
|
|||||||
def version(self) -> Optional[Version]:
|
def version(self) -> Optional[Version]:
|
||||||
return self._version
|
return self._version
|
||||||
|
|
||||||
def __init__(self, host: str, apikey: str):
|
@property
|
||||||
|
def cookies(self) -> dict:
|
||||||
|
"""
|
||||||
|
当前会话的Cookies,开启访问码后包含访问码校验凭证
|
||||||
|
"""
|
||||||
|
return self._session.cookies.get_dict()
|
||||||
|
|
||||||
|
def __init__(self, host: str, apikey: str, access_code: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
:param host: 飞牛服务端地址,如http://127.0.0.1:5666/v
|
:param host: 飞牛服务端地址,如http://127.0.0.1:5666/v
|
||||||
|
:param access_code: 访问码,未开启时为空
|
||||||
"""
|
"""
|
||||||
self._api_path = "/api/v1"
|
self._api_path = "/api/v1"
|
||||||
self._host = host.rstrip("/")
|
self._host = host.rstrip("/")
|
||||||
self._apikey = apikey
|
self._apikey = apikey
|
||||||
|
self._access_code = access_code
|
||||||
self._token: Optional[str] = None
|
self._token: Optional[str] = None
|
||||||
self._version: Optional[Version] = None
|
self._version: Optional[Version] = None
|
||||||
self._session = requests.Session()
|
self._session = requests.Session()
|
||||||
self._request_utils = RequestUtils(session=self._session, timeout=10)
|
self._request_utils = RequestUtils(session=self._session, timeout=10)
|
||||||
|
|
||||||
|
def verify_access_code(self) -> bool:
|
||||||
|
"""
|
||||||
|
校验访问码,通过后会话获得访问凭证,否则无法访问登录页和各应用接口
|
||||||
|
|
||||||
|
:return: 未配置访问码或校验通过返回True
|
||||||
|
"""
|
||||||
|
if not self._access_code:
|
||||||
|
return True
|
||||||
|
# 访问码校验地址位于设备根路径,不在/v下
|
||||||
|
root = self._host[: -len("/v")] if self._host.endswith("/v") else self._host
|
||||||
|
url = f"{root}/c/{quote(self._access_code, safe='')}"
|
||||||
|
res = self._request_utils.get_res(url, allow_redirects=True)
|
||||||
|
if res is None:
|
||||||
|
logger.error(f"校验飞牛访问码失败,无法访问 {url}")
|
||||||
|
return False
|
||||||
|
if res.status_code == 404:
|
||||||
|
# 访问码错误或校验失败时返回404
|
||||||
|
logger.error("飞牛访问码校验失败,请检查访问码是否正确")
|
||||||
|
return False
|
||||||
|
if not res.ok:
|
||||||
|
logger.error(f"飞牛访问码校验失败,状态码:{res.status_code}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def sys_version(self) -> Optional[Version]:
|
def sys_version(self) -> Optional[Version]:
|
||||||
"""
|
"""
|
||||||
飞牛影视版本号
|
飞牛影视版本号
|
||||||
@@ -161,6 +196,9 @@ class Api:
|
|||||||
|
|
||||||
:return: 成功返回token 否则返回None
|
:return: 成功返回token 否则返回None
|
||||||
"""
|
"""
|
||||||
|
# 开启访问码后需先通过访问码校验,否则无法访问登录接口
|
||||||
|
if not self.verify_access_code():
|
||||||
|
return None
|
||||||
if (
|
if (
|
||||||
res := self.request(
|
res := self.request(
|
||||||
"/login",
|
"/login",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from app.utils.url import UrlUtils
|
|||||||
class TrimeMedia:
|
class TrimeMedia:
|
||||||
_username: Optional[str] = None
|
_username: Optional[str] = None
|
||||||
_password: Optional[str] = None
|
_password: Optional[str] = None
|
||||||
|
_access_code: Optional[str] = None
|
||||||
|
|
||||||
_userinfo: Optional[fnapi.User] = None
|
_userinfo: Optional[fnapi.User] = None
|
||||||
_host: Optional[str] = None
|
_host: Optional[str] = None
|
||||||
@@ -28,6 +29,7 @@ class TrimeMedia:
|
|||||||
host: Optional[str] = None,
|
host: Optional[str] = None,
|
||||||
username: Optional[str] = None,
|
username: Optional[str] = None,
|
||||||
password: Optional[str] = None,
|
password: Optional[str] = None,
|
||||||
|
access_code: Optional[str] = None,
|
||||||
play_host: Optional[str] = None,
|
play_host: Optional[str] = None,
|
||||||
sync_libraries: Optional[list] = None,
|
sync_libraries: Optional[list] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -37,13 +39,14 @@ class TrimeMedia:
|
|||||||
return
|
return
|
||||||
self._username = username
|
self._username = username
|
||||||
self._password = password
|
self._password = password
|
||||||
|
self._access_code = access_code
|
||||||
self._host = host
|
self._host = host
|
||||||
self._sync_libraries = sync_libraries or []
|
self._sync_libraries = sync_libraries or []
|
||||||
|
|
||||||
if not self.reconnect():
|
if not self.reconnect():
|
||||||
logger.error(f"请检查服务端地址 {host}")
|
logger.error(f"请检查服务端地址 {host}")
|
||||||
return
|
return
|
||||||
if result := self.__create_api(play_host):
|
if result := self.__create_api(play_host, access_code):
|
||||||
self._playhost = result.api.host
|
self._playhost = result.api.host
|
||||||
result.api.close()
|
result.api.close()
|
||||||
elif play_host:
|
elif play_host:
|
||||||
@@ -69,11 +72,14 @@ class TrimeMedia:
|
|||||||
version: fnapi.Version
|
version: fnapi.Version
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __create_api(host: Optional[str]) -> Optional["TrimeMedia._ApiCreateResult"]:
|
def __create_api(
|
||||||
|
host: Optional[str], access_code: Optional[str] = None
|
||||||
|
) -> Optional["TrimeMedia._ApiCreateResult"]:
|
||||||
"""
|
"""
|
||||||
创建一个飞牛API
|
创建一个飞牛API
|
||||||
|
|
||||||
:param host: 服务端地址
|
:param host: 服务端地址
|
||||||
|
:param access_code: 访问码,未开启时为空
|
||||||
:return: 如果地址无效、不可访问则返回None
|
:return: 如果地址无效、不可访问则返回None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -85,16 +91,19 @@ class TrimeMedia:
|
|||||||
if not host.endswith("/v"):
|
if not host.endswith("/v"):
|
||||||
# 尝试补上结尾的/v 测试能否正常访问
|
# 尝试补上结尾的/v 测试能否正常访问
|
||||||
res = TrimeMedia._ApiCreateResult()
|
res = TrimeMedia._ApiCreateResult()
|
||||||
res.api = fnapi.Api(host + "/v", api_key)
|
res.api = fnapi.Api(host + "/v", api_key, access_code)
|
||||||
if fnver := res.api.sys_version():
|
# 开启访问码后,需先校验才能访问各应用接口
|
||||||
|
if res.api.verify_access_code() and (fnver := res.api.sys_version()):
|
||||||
res.version = fnver
|
res.version = fnver
|
||||||
return res
|
return res
|
||||||
|
res.api.close()
|
||||||
# 测试用户配置的地址
|
# 测试用户配置的地址
|
||||||
res = TrimeMedia._ApiCreateResult()
|
res = TrimeMedia._ApiCreateResult()
|
||||||
res.api = fnapi.Api(host, api_key)
|
res.api = fnapi.Api(host, api_key, access_code)
|
||||||
if fnver := res.api.sys_version():
|
if res.api.verify_access_code() and (fnver := res.api.sys_version()):
|
||||||
res.version = fnver
|
res.version = fnver
|
||||||
return res
|
return res
|
||||||
|
res.api.close()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
@@ -130,7 +139,7 @@ class TrimeMedia:
|
|||||||
if not self.is_configured():
|
if not self.is_configured():
|
||||||
return False
|
return False
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
if result := self.__create_api(self._host):
|
if result := self.__create_api(self._host, self._access_code):
|
||||||
self._api = result.api
|
self._api = result.api
|
||||||
self._version = result.version
|
self._version = result.version
|
||||||
# 版本号:0.8.53, 服务版本:0.8.23
|
# 版本号:0.8.53, 服务版本:0.8.23
|
||||||
@@ -683,4 +692,8 @@ class TrimeMedia:
|
|||||||
image_url, [self._api.host], strict=True
|
image_url, [self._api.host], strict=True
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
return {"Trim-MC-token": self._api.token}
|
cookies = {"Trim-MC-token": self._api.token}
|
||||||
|
if self._access_code:
|
||||||
|
# 开启访问码后,图片请求也需要携带访问码校验凭证
|
||||||
|
cookies.update(self._api.cookies)
|
||||||
|
return cookies
|
||||||
|
|||||||
Reference in New Issue
Block a user