Compare commits

...

9 Commits

Author SHA1 Message Date
jxxghp
1cec6ed6d1 v2.0.8
- 修复云盘扫码问题
2024-11-20 20:43:44 +08:00
jxxghp
fff75c7fe2 fix 115 2024-11-20 20:40:32 +08:00
jxxghp
81fecf1e07 fix alipan 2024-11-20 20:39:48 +08:00
jxxghp
ad8f687f8e fix alipan 2024-11-20 20:36:50 +08:00
jxxghp
a3172d7503 fix 扫码逻辑与底层模块解耦 2024-11-20 20:17:18 +08:00
jxxghp
8d5e0b26d5 fix:115支持Cookie 2024-11-20 13:14:37 +08:00
jxxghp
b1b980f550 Merge pull request #3171 from Sowevo/v2 2024-11-20 07:07:08 +08:00
Sowevo
8196589cff Merge branch 'jxxghp:v2' into v2 2024-11-19 22:43:31 +08:00
sowevo
cb9f41cb65 plex的item_id统一使用全路径
获取图片时兼容外网地址为Plex的官方转发地址https://app.plex.tv的情况
2024-11-19 22:41:55 +08:00
9 changed files with 129 additions and 70 deletions

View File

@@ -143,7 +143,7 @@ class FileManagerModule(_ModuleBase):
return
storage_oper.set_config(conf)
def generate_qrcode(self, storage: str) -> Optional[Dict[str, str]]:
def generate_qrcode(self, storage: str) -> Optional[Tuple[dict, str]]:
"""
生成二维码
"""

View File

@@ -1,6 +1,6 @@
from abc import ABCMeta, abstractmethod
from pathlib import Path
from typing import Optional, List, Union, Dict
from typing import Optional, List, Union, Dict, Tuple
from app import schemas
from app.helper.storage import StorageHelper
@@ -16,7 +16,14 @@ class StorageBase(metaclass=ABCMeta):
def __init__(self):
self.storagehelper = StorageHelper()
def generate_qrcode(self, *args, **kwargs) -> Optional[Dict[str, str]]:
@abstractmethod
def init_storage(self):
"""
初始化
"""
pass
def generate_qrcode(self, *args, **kwargs) -> Optional[Tuple[dict, str]]:
pass
def check_login(self, *args, **kwargs) -> Optional[Dict[str, str]]:
@@ -40,6 +47,7 @@ class StorageBase(metaclass=ABCMeta):
设置配置
"""
self.storagehelper.set_storage(self.schema.value, conf)
self.init_storage()
def support_transtype(self) -> dict:
"""

View File

@@ -16,10 +16,11 @@ from app.schemas.types import StorageSchema
from app.utils.http import RequestUtils
from aligo import Aligo, BaseFile
from app.utils.singleton import Singleton
from app.utils.string import StringUtils
class AliPan(StorageBase):
class AliPan(StorageBase, metaclass=Singleton):
"""
阿里云相关操作
"""
@@ -54,17 +55,26 @@ class AliPan(StorageBase):
except FileNotFoundError:
logger.debug('未发现 aria2c')
self._has_aria2c = False
self.init_storage()
self.__init_aligo()
def __init_aligo(self):
def init_storage(self):
"""
初始化 aligo
"""
def show_qrcode(qr_link: str):
"""
显示二维码
"""
logger.info(f"请用阿里云盘 App 扫码登录:{qr_link}")
refresh_token = self.__auth_params.get("refreshToken")
if refresh_token:
self.aligo = Aligo(refresh_token=refresh_token, use_aria2=self._has_aria2c,
name="MoviePilot V2", level=logging.ERROR)
try:
self.aligo = Aligo(refresh_token=refresh_token, show=show_qrcode, use_aria2=self._has_aria2c,
name="MoviePilot V2", level=logging.ERROR, re_login=False)
except Exception as err:
logger.error(f"初始化阿里云盘失败:{str(err)}")
self.__clear_params()
@property
def __auth_params(self):
@@ -160,7 +170,7 @@ class AliPan(StorageBase):
})
self.__update_params(data)
self.__update_drives()
self.__init_aligo()
self.init_storage()
except Exception as e:
return {}, f"bizExt 解码失败:{str(e)}"
return data, ""
@@ -180,12 +190,16 @@ class AliPan(StorageBase):
"""
获取用户信息drive_id等
"""
if not self.aligo:
return {}
return self.aligo.get_user()
def __update_drives(self):
"""
更新用户存储根目录
"""
if not self.aligo:
return
drivers = self.aligo.list_my_drives()
for driver in drivers:
if driver.category == "resource":

View File

@@ -34,6 +34,12 @@ class Alist(StorageBase):
def __init__(self):
super().__init__()
def init_storage(self):
"""
初始化
"""
pass
@property
def __get_base_url(self) -> str:
"""

View File

@@ -25,6 +25,12 @@ class LocalStorage(StorageBase):
"softlink": "软链接"
}
def init_storage(self):
"""
初始化
"""
pass
def check(self) -> bool:
"""
检查存储是否可用

View File

@@ -27,6 +27,12 @@ class Rclone(StorageBase):
"copy": "复制"
}
def init_storage(self):
"""
初始化
"""
pass
def set_config(self, conf: dict):
"""
设置配置

View File

@@ -1,13 +1,14 @@
from pathlib import Path
from typing import Optional, Tuple, List
from p115 import P115Client, P115FileSystem, P115Path
from p115 import P115Client, P115Path
from app import schemas
from app.core.config import settings
from app.log import logger
from app.modules.filemanager.storages import StorageBase
from app.schemas.types import StorageSchema
from app.utils.http import RequestUtils
from app.utils.singleton import Singleton
@@ -25,24 +26,32 @@ class U115Pan(StorageBase, metaclass=Singleton):
"copy": "复制"
}
# 115二维码登录地址
qrcode_url = "https://qrcodeapi.115.com/api/1.0/web/1.0/token/"
# 115登录状态检查
login_check_url = "https://qrcodeapi.115.com/get/status/"
# 115登录完成 alipaymini
login_done_api = f"https://passportapi.115.com/app/1.0/alipaymini/1.0/login/qrcode/"
client: P115Client = None
fs: P115FileSystem = None
session_info: dict = None
def __init_cloud(self, force: bool = False) -> bool:
def __init__(self):
super().__init__()
self.init_storage()
def init_storage(self):
"""
初始化Cloud
"""
if not self.__credential:
return
try:
if not self.client or not self.client.cookies or force:
self.client = P115Client(self.__credential,
check_for_relogin=True, app="alipaymini", console_qrcode=False)
self.fs = P115FileSystem(self.client)
self.client = P115Client(self.__credential, app="alipaymini",
check_for_relogin=False, console_qrcode=False)
except Exception as err:
logger.error(f"115连接失败请重新扫码登录:{str(err)}")
logger.error(f"115连接失败请重新登录{str(err)}")
self.__clear_credential()
return False
return True
@property
def __credential(self) -> Optional[str]:
@@ -54,8 +63,7 @@ class U115Pan(StorageBase, metaclass=Singleton):
return None
if not conf.config:
return None
# 将dict转换为cookie字符串格式
return "; ".join([f"{k}={v}" for k, v in conf.config.items()])
return conf.config.get("cookie")
def __save_credential(self, credential: dict):
"""
@@ -73,21 +81,19 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
生成二维码
"""
if not self.__init_cloud():
return None
try:
resp = self.client.login_qrcode_token()
self.session_info = resp["data"]
res = RequestUtils(timeout=10).get_res(self.qrcode_url)
if res:
self.session_info = res.json().get("data")
qrcode_content = self.session_info.pop("qrcode")
if not qrcode_content:
logger.warn("115生成二维码失败未获取到二维码数据")
return None
return {}, ""
return {
"codeContent": qrcode_content
}, ""
except Exception as e:
logger.warn(f"115生成二维码失败{str(e)}")
return {}, f"115生成二维码失败{str(e)}"
elif res is not None:
return {}, f"115生成二维码失败{res.status_code} - {res.reason}"
return {}, f"115生成二维码失败无法连接!"
def check_login(self) -> Optional[Tuple[dict, str]]:
"""
@@ -96,8 +102,11 @@ class U115Pan(StorageBase, metaclass=Singleton):
if not self.session_info:
return {}, "请先生成二维码!"
try:
resp = self.client.login_qrcode_scan_status(self.session_info)
match resp["data"].get("status"):
resp = RequestUtils(timeout=10).get_res(self.login_check_url, params=self.session_info)
if not resp:
return {}, "115登录确认失败无法连接"
result = resp.json()
match result["data"].get("status"):
case 0:
result = {
"status": 0,
@@ -110,12 +119,18 @@ class U115Pan(StorageBase, metaclass=Singleton):
}
case 2:
# 确认完成,保存认证信息
resp = self.client.login_qrcode_scan_result(uid=self.session_info.get("uid"),
app="alipaymini")
resp = RequestUtils(timeout=10).post_res(self.login_done_api,
data={"account": self.session_info.get("uid")})
if not resp:
return {}, "115登录确认失败无法连接"
if resp:
# 保存认证信息
self.__save_credential(resp["data"]["cookie"])
self.__init_cloud(force=True)
result = resp.json()
cookie_dict = result["data"]["cookie"]
cookie_str = "; ".join([f"{k}={v}" for k, v in cookie_dict.items()])
cookie_dict.update({"cookie": cookie_str})
self.__save_credential(cookie_dict)
self.init_storage()
result = {
"status": 2,
"tip": "登录成功!"
@@ -143,10 +158,10 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
获取存储空间
"""
if not self.__init_cloud():
if not self.client:
return None
try:
usage = self.fs.space_summury()
usage = self.client.fs.space_summury()
if usage:
return usage['rt_space_info']['all_total']['size'], usage['rt_space_info']['all_remain']['size']
except Exception as e:
@@ -163,12 +178,12 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
浏览文件
"""
if not self.__init_cloud():
if not self.client:
return []
try:
if fileitem.type == "file":
return [fileitem]
items: List[P115Path] = self.fs.list(fileitem.path)
items: List[P115Path] = self.client.fs.list(fileitem.path)
return [schemas.FileItem(
storage=self.schema.value,
type="dir" if item.is_dir() else "file",
@@ -187,10 +202,10 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
创建目录
"""
if not self.__init_cloud():
if not self.client:
return None
try:
result = self.fs.makedirs(Path(fileitem.path) / name, exist_ok=True)
result = self.client.fs.makedirs(Path(fileitem.path) / name, exist_ok=True)
if result:
return schemas.FileItem(
storage=self.schema.value,
@@ -208,10 +223,10 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
根据文件路程获取目录,不存在则创建
"""
if not self.__init_cloud():
if not self.client:
return None
try:
result = self.fs.makedirs(path, exist_ok=True)
result = self.client.fs.makedirs(path, exist_ok=True)
if result:
return schemas.FileItem(
storage=self.schema.value,
@@ -229,11 +244,11 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
获取文件或目录不存在返回None
"""
if not self.__init_cloud():
if not self.client:
return None
try:
try:
item = self.fs.attr(path)
item = self.client.fs.attr(path)
except FileNotFoundError:
return None
if item:
@@ -255,11 +270,11 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
获取文件详情
"""
if not self.__init_cloud():
if not self.client:
return None
try:
try:
item = self.fs.attr(fileitem.path)
item = self.client.fs.attr(fileitem.path)
except FileNotFoundError:
return None
if item:
@@ -281,10 +296,10 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
删除文件
"""
if not self.__init_cloud():
if not self.client:
return False
try:
self.fs.remove(fileitem.path)
self.client.fs.remove(fileitem.path)
return True
except Exception as e:
logger.error(f"115删除文件失败{str(e)}")
@@ -294,10 +309,10 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
重命名文件
"""
if not self.__init_cloud():
if not self.client:
return False
try:
self.fs.rename(fileitem.path, Path(fileitem.path).with_name(name))
self.client.fs.rename(fileitem.path, Path(fileitem.path).with_name(name))
return True
except Exception as e:
logger.error(f"115重命名文件失败{str(e)}")
@@ -307,11 +322,11 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
获取下载链接
"""
if not self.__init_cloud():
if not self.client:
return None
local_file = (path or settings.TEMP_PATH) / fileitem.name
try:
task = self.fs.download(fileitem.path, file=local_file)
task = self.client.fs.download(fileitem.path, file=local_file)
if task:
return local_file
except Exception as e:
@@ -322,12 +337,12 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
上传文件
"""
if not self.__init_cloud():
if not self.client:
return None
try:
new_path = Path(fileitem.path) / (new_name or path.name)
with open(path, "rb") as f:
result = self.fs.upload(f, new_path)
result = self.client.fs.upload(f, new_path)
if result:
return schemas.FileItem(
storage=self.schema.value,
@@ -347,10 +362,10 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
移动文件
"""
if not self.__init_cloud():
if not self.client:
return False
try:
self.fs.move(fileitem.path, target.path)
self.client.fs.move(fileitem.path, target.path)
return True
except Exception as e:
logger.error(f"115移动文件失败{str(e)}")
@@ -360,10 +375,10 @@ class U115Pan(StorageBase, metaclass=Singleton):
"""
复制文件
"""
if not self.__init_cloud():
if not self.client:
return False
try:
self.fs.copy(fileitem.path, target_file)
self.client.fs.copy(fileitem.path, target_file)
return True
except Exception as e:
logger.error(f"115复制文件失败{str(e)}")

View File

@@ -294,7 +294,7 @@ class Plex:
return videos.key, season_episodes
def get_remote_image_by_id(self,
item_id: str,
item_id: str,
image_type: str,
depth: int = 0,
plex_url: bool = True) -> Optional[str]:
@@ -310,12 +310,16 @@ class Plex:
return None
try:
image_url = None
ekey = f"/library/metadata/{item_id}"
ekey = item_id
item = self._plex.fetchItem(ekey=ekey)
if not item:
return None
# 如果配置了外网播放地址以及Token则默认从Plex媒体服务器获取图片否则返回有外网地址的图片资源
if self._playhost and self._token and plex_url:
# Plex外网播放地址这个框里目前可以填两种地址
# 1. Plex的官方转发地址https://app.plex.tv, 2. 自己处理的端口转发地址
# 如果使用的是1的官方转发地址,那么就不能走这个逻辑,因为官方转发地址无法获取到图片
if (self._playhost and "app.plex.tv" not in self._playhost
and self._token and plex_url):
query = {"X-Plex-Token": self._token}
if image_type == "Poster":
if item.thumb:
@@ -346,8 +350,8 @@ class Plex:
image_url = image.key
break
# 如果最后还是找不到,则递归父级进行查找
if not image_url and hasattr(item, "parentRatingKey"):
return self.get_remote_image_by_id(item_id=item.parentRatingKey,
if not image_url and hasattr(item, "parentKey"):
return self.get_remote_image_by_id(item_id=item.parentKey,
image_type=image_type,
depth=depth + 1)
return image_url
@@ -665,7 +669,7 @@ class Plex:
"S" + str(message.get('Metadata', {}).get('parentIndex')),
"E" + str(message.get('Metadata', {}).get('index')),
message.get('Metadata', {}).get('title'))
eventItem.item_id = message.get('Metadata', {}).get('ratingKey')
eventItem.item_id = message.get('Metadata', {}).get('key')
eventItem.season_id = message.get('Metadata', {}).get('parentIndex')
eventItem.episode_id = message.get('Metadata', {}).get('index')
@@ -680,7 +684,7 @@ class Plex:
eventItem.item_name = "%s %s" % (
message.get('Metadata', {}).get('title'),
"(" + str(message.get('Metadata', {}).get('year')) + ")")
eventItem.item_id = message.get('Metadata', {}).get('ratingKey')
eventItem.item_id = message.get('Metadata', {}).get('key')
if len(message.get('Metadata', {}).get('summary')) > 100:
eventItem.overview = str(message.get('Metadata', {}).get('summary'))[:100] + "..."
else:

View File

@@ -1,2 +1,2 @@
APP_VERSION = 'v2.0.7'
FRONTEND_VERSION = 'v2.0.7'
APP_VERSION = 'v2.0.8'
FRONTEND_VERSION = 'v2.0.8'