mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-13 23:16:46 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cec6ed6d1 | ||
|
|
fff75c7fe2 | ||
|
|
81fecf1e07 | ||
|
|
ad8f687f8e | ||
|
|
a3172d7503 | ||
|
|
8d5e0b26d5 | ||
|
|
b1b980f550 | ||
|
|
8196589cff | ||
|
|
cb9f41cb65 |
@@ -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]]:
|
||||
"""
|
||||
生成二维码
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -34,6 +34,12 @@ class Alist(StorageBase):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def init_storage(self):
|
||||
"""
|
||||
初始化
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def __get_base_url(self) -> str:
|
||||
"""
|
||||
|
||||
@@ -25,6 +25,12 @@ class LocalStorage(StorageBase):
|
||||
"softlink": "软链接"
|
||||
}
|
||||
|
||||
def init_storage(self):
|
||||
"""
|
||||
初始化
|
||||
"""
|
||||
pass
|
||||
|
||||
def check(self) -> bool:
|
||||
"""
|
||||
检查存储是否可用
|
||||
|
||||
@@ -27,6 +27,12 @@ class Rclone(StorageBase):
|
||||
"copy": "复制"
|
||||
}
|
||||
|
||||
def init_storage(self):
|
||||
"""
|
||||
初始化
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_config(self, conf: dict):
|
||||
"""
|
||||
设置配置
|
||||
|
||||
@@ -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)}")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
APP_VERSION = 'v2.0.7'
|
||||
FRONTEND_VERSION = 'v2.0.7'
|
||||
APP_VERSION = 'v2.0.8'
|
||||
FRONTEND_VERSION = 'v2.0.8'
|
||||
|
||||
Reference in New Issue
Block a user