Merge branch 'main' of https://github.com/InfinityPacer/MoviePilot
This commit is contained in:
@ -2,7 +2,7 @@ from fastapi import APIRouter
|
||||
|
||||
from app.api.endpoints import login, user, site, message, webhook, subscribe, \
|
||||
media, douban, search, plugin, tmdb, history, system, download, dashboard, \
|
||||
filebrowser, transfer, mediaserver, bangumi
|
||||
local, transfer, mediaserver, bangumi, aliyun, u115
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(login.router, prefix="/login", tags=["login"])
|
||||
@ -20,8 +20,9 @@ api_router.include_router(system.router, prefix="/system", tags=["system"])
|
||||
api_router.include_router(plugin.router, prefix="/plugin", tags=["plugin"])
|
||||
api_router.include_router(download.router, prefix="/download", tags=["download"])
|
||||
api_router.include_router(dashboard.router, prefix="/dashboard", tags=["dashboard"])
|
||||
api_router.include_router(filebrowser.router, prefix="/filebrowser", tags=["filebrowser"])
|
||||
api_router.include_router(local.router, prefix="/local", tags=["local"])
|
||||
api_router.include_router(transfer.router, prefix="/transfer", tags=["transfer"])
|
||||
api_router.include_router(mediaserver.router, prefix="/mediaserver", tags=["mediaserver"])
|
||||
api_router.include_router(bangumi.router, prefix="/bangumi", tags=["bangumi"])
|
||||
|
||||
api_router.include_router(aliyun.router, prefix="/aliyun", tags=["aliyun"])
|
||||
api_router.include_router(u115.router, prefix="/u115", tags=["115"])
|
||||
|
221
app/api/endpoints/aliyun.py
Normal file
221
app/api/endpoints/aliyun.py
Normal file
@ -0,0 +1,221 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from starlette.responses import Response
|
||||
|
||||
from app import schemas
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.core.config import settings
|
||||
from app.core.metainfo import MetaInfoPath
|
||||
from app.core.security import verify_token, verify_uri_token
|
||||
from app.helper.aliyun import AliyunHelper
|
||||
from app.helper.progress import ProgressHelper
|
||||
from app.schemas.types import ProgressKey
|
||||
from app.utils.string import StringUtils
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/qrcode", summary="生成二维码内容", response_model=schemas.Response)
|
||||
def qrcode(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
生成二维码
|
||||
"""
|
||||
qrcode_data, errmsg = AliyunHelper().generate_qrcode()
|
||||
if qrcode_data:
|
||||
return schemas.Response(success=True, data=qrcode_data)
|
||||
return schemas.Response(success=False, message=errmsg)
|
||||
|
||||
|
||||
@router.get("/check", summary="二维码登录确认", response_model=schemas.Response)
|
||||
def check(ck: str, t: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
二维码登录确认
|
||||
"""
|
||||
if not ck or not t:
|
||||
return schemas.Response(success=False, message="参数错误")
|
||||
data, errmsg = AliyunHelper().check_login(ck, t)
|
||||
if data:
|
||||
return schemas.Response(success=True, data=data)
|
||||
return schemas.Response(success=False, message=errmsg)
|
||||
|
||||
|
||||
@router.get("/userinfo", summary="查询用户信息", response_model=schemas.Response)
|
||||
def userinfo(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
查询用户信息
|
||||
"""
|
||||
aliyunhelper = AliyunHelper()
|
||||
# 浏览一次文件确定token正确性
|
||||
aliyunhelper.list_files()
|
||||
# 查询用户信息返回
|
||||
info = aliyunhelper.get_user_info()
|
||||
if info:
|
||||
return schemas.Response(success=True, data=info)
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/list", summary="所有目录和文件(阿里云盘)", response_model=List[schemas.FileItem])
|
||||
def list_aliyun(path: str,
|
||||
fileid: str,
|
||||
filetype: str = "dir",
|
||||
sort: str = 'updated_at',
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
查询当前目录下所有目录和文件
|
||||
:param path: 当前路径
|
||||
:param fileid: 文件ID
|
||||
:param filetype: 文件类型
|
||||
:param sort: 排序方式,name:按名称排序,time:按修改时间排序
|
||||
:param _: token
|
||||
:return: 所有目录和文件
|
||||
"""
|
||||
if not fileid:
|
||||
return []
|
||||
if not path:
|
||||
path = "/"
|
||||
if sort == "time":
|
||||
sort = "updated_at"
|
||||
if filetype == "file":
|
||||
fileinfo = AliyunHelper().get_file_detail(fileid)
|
||||
if fileinfo:
|
||||
return [schemas.FileItem(
|
||||
fileid=fileinfo.get("file_id"),
|
||||
parent_fileid=fileinfo.get("parent_file_id"),
|
||||
type="file",
|
||||
path=f"{path}{fileinfo.get('name')}",
|
||||
name=fileinfo.get("name"),
|
||||
size=fileinfo.get("size"),
|
||||
extension=fileinfo.get("file_extension"),
|
||||
modify_time=StringUtils.str_to_timestamp(fileinfo.get("updated_at")),
|
||||
thumbnail=fileinfo.get("thumbnail")
|
||||
)]
|
||||
return []
|
||||
items = AliyunHelper().list_files(parent_file_id=fileid, order_by=sort)
|
||||
if not items:
|
||||
return []
|
||||
return [schemas.FileItem(
|
||||
fileid=item.get("file_id"),
|
||||
parent_fileid=item.get("parent_file_id"),
|
||||
type="dir" if item.get("type") == "folder" else "file",
|
||||
path=f"{path}{item.get('name')}" + "/" if item.get("type") == "folder" else "",
|
||||
name=item.get("name"),
|
||||
size=item.get("size"),
|
||||
extension=item.get("file_extension"),
|
||||
modify_time=StringUtils.str_to_timestamp(item.get("updated_at")),
|
||||
thumbnail=item.get("thumbnail")
|
||||
) for item in items]
|
||||
|
||||
|
||||
@router.get("/mkdir", summary="创建目录(阿里云盘)", response_model=schemas.Response)
|
||||
def mkdir_aliyun(fileid: str,
|
||||
name: str,
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
创建目录
|
||||
"""
|
||||
if not fileid or not name:
|
||||
return schemas.Response(success=False)
|
||||
result = AliyunHelper().create_folder(parent_file_id=fileid, name=name)
|
||||
if result:
|
||||
return schemas.Response(success=True)
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/delete", summary="删除文件或目录(阿里云盘)", response_model=schemas.Response)
|
||||
def delete_aliyun(fileid: str,
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
删除文件或目录
|
||||
"""
|
||||
if not fileid:
|
||||
return schemas.Response(success=False)
|
||||
result = AliyunHelper().delete_file(fileid)
|
||||
if result:
|
||||
return schemas.Response(success=True)
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/download", summary="下载文件(阿里云盘)")
|
||||
def download_aliyun(fileid: str,
|
||||
_: schemas.TokenPayload = Depends(verify_uri_token)) -> Any:
|
||||
"""
|
||||
下载文件或目录
|
||||
"""
|
||||
if not fileid:
|
||||
return schemas.Response(success=False)
|
||||
url = AliyunHelper().get_download_url(fileid)
|
||||
if url:
|
||||
# 重定向
|
||||
return Response(status_code=302, headers={"Location": url})
|
||||
raise HTTPException(status_code=500, detail="下载文件出错")
|
||||
|
||||
|
||||
@router.get("/rename", summary="重命名文件或目录(阿里云盘)", response_model=schemas.Response)
|
||||
def rename_aliyun(fileid: str, new_name: str, path: str,
|
||||
recursive: bool = False,
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
重命名文件或目录
|
||||
"""
|
||||
if not fileid or not new_name:
|
||||
return schemas.Response(success=False)
|
||||
result = AliyunHelper().rename_file(fileid, new_name)
|
||||
if result:
|
||||
if recursive:
|
||||
transferchain = TransferChain()
|
||||
media_exts = settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIO_TRACK_EXT
|
||||
# 递归修改目录内文件(智能识别命名)
|
||||
sub_files: List[schemas.FileItem] = list_aliyun(path=path, fileid=fileid)
|
||||
if sub_files:
|
||||
# 开始进度
|
||||
progress = ProgressHelper()
|
||||
progress.start(ProgressKey.BatchRename)
|
||||
total = len(sub_files)
|
||||
handled = 0
|
||||
for sub_file in sub_files:
|
||||
handled += 1
|
||||
progress.update(value=handled / total * 100,
|
||||
text=f"正在处理 {sub_file.name} ...",
|
||||
key=ProgressKey.BatchRename)
|
||||
if sub_file.type == "dir":
|
||||
continue
|
||||
if not sub_file.extension:
|
||||
continue
|
||||
if f".{sub_file.extension.lower()}" not in media_exts:
|
||||
continue
|
||||
sub_path = Path(f"{path}{sub_file.name}")
|
||||
meta = MetaInfoPath(sub_path)
|
||||
mediainfo = transferchain.recognize_media(meta)
|
||||
if not mediainfo:
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 未识别到媒体信息")
|
||||
new_path = transferchain.recommend_name(meta=meta, mediainfo=mediainfo)
|
||||
if not new_path:
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 未识别到新名称")
|
||||
ret: schemas.Response = rename_aliyun(fileid=sub_file.fileid,
|
||||
path=path,
|
||||
new_name=Path(new_path).name,
|
||||
recursive=False)
|
||||
if not ret.success:
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 重命名失败!")
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=True)
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/image", summary="读取图片(阿里云盘)", response_model=schemas.Response)
|
||||
def image_aliyun(fileid: str, _: schemas.TokenPayload = Depends(verify_uri_token)) -> Any:
|
||||
"""
|
||||
读取图片
|
||||
"""
|
||||
if not fileid:
|
||||
return schemas.Response(success=False)
|
||||
url = AliyunHelper().get_download_url(fileid)
|
||||
if url:
|
||||
# 重定向
|
||||
return Response(status_code=302, headers={"Location": url})
|
||||
raise HTTPException(status_code=500, detail="下载图片出错")
|
@ -6,7 +6,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app import schemas
|
||||
from app.chain.dashboard import DashboardChain
|
||||
from app.core.security import verify_token, verify_uri_token
|
||||
from app.core.security import verify_token, verify_apitoken
|
||||
from app.db import get_db
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.helper.directory import DirectoryHelper
|
||||
@ -36,7 +36,7 @@ def statistic(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
|
||||
|
||||
@router.get("/statistic2", summary="媒体数量统计(API_TOKEN)", response_model=schemas.Statistic)
|
||||
def statistic2(_: str = Depends(verify_uri_token)) -> Any:
|
||||
def statistic2(_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
查询媒体数量统计信息 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
@ -57,7 +57,7 @@ def storage(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
|
||||
|
||||
@router.get("/storage2", summary="存储空间(API_TOKEN)", response_model=schemas.Storage)
|
||||
def storage2(_: str = Depends(verify_uri_token)) -> Any:
|
||||
def storage2(_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
查询存储空间信息 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
@ -94,7 +94,7 @@ def downloader(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
|
||||
|
||||
@router.get("/downloader2", summary="下载器信息(API_TOKEN)", response_model=schemas.DownloaderInfo)
|
||||
def downloader2(_: str = Depends(verify_uri_token)) -> Any:
|
||||
def downloader2(_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
查询下载器信息 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
@ -110,7 +110,7 @@ def schedule(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
|
||||
|
||||
@router.get("/schedule2", summary="后台服务(API_TOKEN)", response_model=List[schemas.ScheduleInfo])
|
||||
def schedule2(_: str = Depends(verify_uri_token)) -> Any:
|
||||
def schedule2(_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
查询下载器信息 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
@ -136,7 +136,7 @@ def cpu(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
|
||||
|
||||
@router.get("/cpu2", summary="获取当前CPU使用率(API_TOKEN)", response_model=int)
|
||||
def cpu2(_: str = Depends(verify_uri_token)) -> Any:
|
||||
def cpu2(_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
获取当前CPU使用率 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
@ -152,7 +152,7 @@ def memory(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
|
||||
|
||||
@router.get("/memory2", summary="获取当前内存使用量和使用率(API_TOKEN)", response_model=List[int])
|
||||
def memory2(_: str = Depends(verify_uri_token)) -> Any:
|
||||
def memory2(_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
获取当前内存使用率 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
|
@ -2,13 +2,17 @@ import shutil
|
||||
from pathlib import Path
|
||||
from typing import Any, List
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from starlette.responses import FileResponse, Response
|
||||
|
||||
from app import schemas
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.core.config import settings
|
||||
from app.core.security import verify_token
|
||||
from app.core.metainfo import MetaInfoPath
|
||||
from app.core.security import verify_token, verify_uri_token
|
||||
from app.helper.progress import ProgressHelper
|
||||
from app.log import logger
|
||||
from app.schemas.types import ProgressKey
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
router = APIRouter()
|
||||
@ -16,10 +20,10 @@ router = APIRouter()
|
||||
IMAGE_TYPES = [".jpg", ".png", ".gif", ".bmp", ".jpeg", ".webp"]
|
||||
|
||||
|
||||
@router.get("/list", summary="所有目录和文件", response_model=List[schemas.FileItem])
|
||||
def list_path(path: str,
|
||||
sort: str = 'time',
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
@router.get("/list", summary="所有目录和文件(本地)", response_model=List[schemas.FileItem])
|
||||
def list_local(path: str,
|
||||
sort: str = 'time',
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
查询当前目录下所有目录和文件
|
||||
:param path: 目录路径
|
||||
@ -98,8 +102,8 @@ def list_path(path: str,
|
||||
return ret_items
|
||||
|
||||
|
||||
@router.get("/listdir", summary="所有目录(不含文件)", response_model=List[schemas.FileItem])
|
||||
def list_dir(path: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
@router.get("/listdir", summary="所有目录(本地,不含文件)", response_model=List[schemas.FileItem])
|
||||
def list_local_dir(path: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
查询当前目录下所有目录
|
||||
"""
|
||||
@ -139,8 +143,8 @@ def list_dir(path: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
return ret_items
|
||||
|
||||
|
||||
@router.get("/mkdir", summary="创建目录", response_model=schemas.Response)
|
||||
def mkdir(path: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
@router.get("/mkdir", summary="创建目录(本地)", response_model=schemas.Response)
|
||||
def mkdir_local(path: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
创建目录
|
||||
"""
|
||||
@ -153,8 +157,8 @@ def mkdir(path: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
return schemas.Response(success=True)
|
||||
|
||||
|
||||
@router.get("/delete", summary="删除文件或目录", response_model=schemas.Response)
|
||||
def delete(path: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
@router.get("/delete", summary="删除文件或目录(本地)", response_model=schemas.Response)
|
||||
def delete_local(path: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
删除文件或目录
|
||||
"""
|
||||
@ -170,19 +174,16 @@ def delete(path: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
return schemas.Response(success=True)
|
||||
|
||||
|
||||
@router.get("/download", summary="下载文件或目录")
|
||||
def download(path: str, token: str) -> Any:
|
||||
@router.get("/download", summary="下载文件(本地)")
|
||||
def download_local(path: str, _: schemas.TokenPayload = Depends(verify_uri_token)) -> Any:
|
||||
"""
|
||||
下载文件或目录
|
||||
"""
|
||||
if not path:
|
||||
return schemas.Response(success=False)
|
||||
# 认证token
|
||||
if not verify_token(token):
|
||||
return None
|
||||
path_obj = Path(path)
|
||||
if not path_obj.exists():
|
||||
return schemas.Response(success=False)
|
||||
raise HTTPException(status_code=404, detail="文件不存在")
|
||||
if path_obj.is_file():
|
||||
# 做为文件流式下载
|
||||
return FileResponse(path_obj)
|
||||
@ -195,8 +196,10 @@ def download(path: str, token: str) -> Any:
|
||||
return reponse
|
||||
|
||||
|
||||
@router.get("/rename", summary="重命名文件或目录", response_model=schemas.Response)
|
||||
def rename(path: str, new_name: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
@router.get("/rename", summary="重命名文件或目录(本地)", response_model=schemas.Response)
|
||||
def rename_local(path: str, new_name: str,
|
||||
recursive: bool = False,
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
重命名文件或目录
|
||||
"""
|
||||
@ -206,19 +209,53 @@ def rename(path: str, new_name: str, _: schemas.TokenPayload = Depends(verify_to
|
||||
if not path_obj.exists():
|
||||
return schemas.Response(success=False)
|
||||
path_obj.rename(path_obj.parent / new_name)
|
||||
if recursive:
|
||||
transferchain = TransferChain()
|
||||
media_exts = settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIO_TRACK_EXT
|
||||
# 递归修改目录内文件(智能识别命名)
|
||||
sub_files: List[schemas.FileItem] = list_local(path)
|
||||
if sub_files:
|
||||
# 开始进度
|
||||
progress = ProgressHelper()
|
||||
progress.start(ProgressKey.BatchRename)
|
||||
total = len(sub_files)
|
||||
handled = 0
|
||||
for sub_file in sub_files:
|
||||
handled += 1
|
||||
progress.update(value=handled / total * 100,
|
||||
text=f"正在处理 {sub_file.name} ...",
|
||||
key=ProgressKey.BatchRename)
|
||||
if sub_file.type == "dir":
|
||||
continue
|
||||
if not sub_file.extension:
|
||||
continue
|
||||
if f".{sub_file.extension.lower()}" not in media_exts:
|
||||
continue
|
||||
sub_path = Path(sub_file.path)
|
||||
meta = MetaInfoPath(sub_path)
|
||||
mediainfo = transferchain.recognize_media(meta)
|
||||
if not mediainfo:
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 未识别到媒体信息")
|
||||
new_path = transferchain.recommend_name(meta=meta, mediainfo=mediainfo)
|
||||
if not new_path:
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 未识别到新名称")
|
||||
ret: schemas.Response = rename_local(new_path, new_name=Path(new_path).name, recursive=False)
|
||||
if not ret.success:
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 重命名失败!")
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=True)
|
||||
|
||||
|
||||
@router.get("/image", summary="读取图片")
|
||||
def image(path: str, token: str) -> Any:
|
||||
@router.get("/image", summary="读取图片(本地)")
|
||||
def image_local(path: str, _: schemas.TokenPayload = Depends(verify_uri_token)) -> Any:
|
||||
"""
|
||||
读取图片
|
||||
"""
|
||||
if not path:
|
||||
return None
|
||||
# 认证token
|
||||
if not verify_token(token):
|
||||
return None
|
||||
path_obj = Path(path)
|
||||
if not path_obj.exists():
|
||||
return None
|
||||
@ -226,5 +263,5 @@ def image(path: str, token: str) -> Any:
|
||||
return None
|
||||
# 判断是否图片文件
|
||||
if path_obj.suffix.lower() not in IMAGE_TYPES:
|
||||
return None
|
||||
raise HTTPException(status_code=500, detail="图片读取出错")
|
||||
return Response(content=path_obj.read_bytes(), media_type="image/jpeg")
|
@ -8,7 +8,7 @@ from app.chain.media import MediaChain
|
||||
from app.core.config import settings
|
||||
from app.core.context import Context
|
||||
from app.core.metainfo import MetaInfo, MetaInfoPath
|
||||
from app.core.security import verify_token, verify_uri_token
|
||||
from app.core.security import verify_token, verify_apitoken
|
||||
from app.schemas import MediaType
|
||||
|
||||
router = APIRouter()
|
||||
@ -32,7 +32,7 @@ def recognize(title: str,
|
||||
@router.get("/recognize2", summary="识别种子媒体信息(API_TOKEN)", response_model=schemas.Context)
|
||||
def recognize2(title: str,
|
||||
subtitle: str = None,
|
||||
_: str = Depends(verify_uri_token)) -> Any:
|
||||
_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
根据标题、副标题识别媒体信息 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
@ -55,7 +55,7 @@ def recognize_file(path: str,
|
||||
|
||||
@router.get("/recognize_file2", summary="识别文件媒体信息(API_TOKEN)", response_model=schemas.Context)
|
||||
def recognize_file2(path: str,
|
||||
_: str = Depends(verify_uri_token)) -> Any:
|
||||
_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
根据文件路径识别媒体信息 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
|
@ -10,7 +10,7 @@ from app.chain.subscribe import SubscribeChain
|
||||
from app.core.config import settings
|
||||
from app.core.context import MediaInfo
|
||||
from app.core.metainfo import MetaInfo
|
||||
from app.core.security import verify_token, verify_uri_token
|
||||
from app.core.security import verify_token, verify_apitoken
|
||||
from app.db import get_db
|
||||
from app.db.models.subscribe import Subscribe
|
||||
from app.db.models.subscribehistory import SubscribeHistory
|
||||
@ -52,7 +52,7 @@ def read_subscribes(
|
||||
|
||||
|
||||
@router.get("/list", summary="查询所有订阅(API_TOKEN)", response_model=List[schemas.Subscribe])
|
||||
def list_subscribes(_: str = Depends(verify_uri_token)) -> Any:
|
||||
def list_subscribes(_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
查询所有订阅 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
|
@ -5,8 +5,10 @@ from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app import schemas
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.core.security import verify_token, verify_uri_token
|
||||
from app.core.metainfo import MetaInfoPath
|
||||
from app.core.security import verify_token, verify_apitoken
|
||||
from app.db import get_db
|
||||
from app.db.models.transferhistory import TransferHistory
|
||||
from app.schemas import MediaType
|
||||
@ -14,6 +16,35 @@ from app.schemas import MediaType
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/name", summary="查询整理后的名称", response_model=schemas.Response)
|
||||
def query_name(path: str, filetype: str,
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
查询整理后的名称
|
||||
:param path: 文件路径
|
||||
:param filetype: 文件类型
|
||||
:param _: Token校验
|
||||
"""
|
||||
meta = MetaInfoPath(Path(path))
|
||||
mediainfo = MediaChain().recognize_media(meta)
|
||||
if not mediainfo:
|
||||
return schemas.Response(success=False, message="未识别到媒体信息")
|
||||
new_path = TransferChain().recommend_name(meta=meta, mediainfo=mediainfo)
|
||||
if not new_path:
|
||||
return schemas.Response(success=False, message="未识别到新名称")
|
||||
if filetype == "dir":
|
||||
parents = Path(new_path).parents
|
||||
if len(parents) > 2:
|
||||
new_name = parents[1].name
|
||||
else:
|
||||
new_name = parents[0].name
|
||||
else:
|
||||
new_name = Path(new_path).name
|
||||
return schemas.Response(success=True, data={
|
||||
"name": new_name
|
||||
})
|
||||
|
||||
|
||||
@router.post("/manual", summary="手动转移", response_model=schemas.Response)
|
||||
def manual_transfer(path: str = None,
|
||||
logid: int = None,
|
||||
@ -110,7 +141,7 @@ def manual_transfer(path: str = None,
|
||||
|
||||
|
||||
@router.get("/now", summary="立即执行下载器文件整理", response_model=schemas.Response)
|
||||
def now(_: str = Depends(verify_uri_token)) -> Any:
|
||||
def now(_: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
立即执行下载器文件整理 API_TOKEN认证(?token=xxx)
|
||||
"""
|
||||
|
229
app/api/endpoints/u115.py
Normal file
229
app/api/endpoints/u115.py
Normal file
@ -0,0 +1,229 @@
|
||||
import base64
|
||||
from pathlib import Path
|
||||
from typing import Any, List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from starlette.responses import Response
|
||||
|
||||
from app import schemas
|
||||
from app.chain.transfer import TransferChain
|
||||
from app.core.config import settings
|
||||
from app.core.metainfo import MetaInfoPath
|
||||
from app.core.security import verify_token, verify_uri_token
|
||||
from app.helper.progress import ProgressHelper
|
||||
from app.helper.u115 import U115Helper
|
||||
from app.schemas.types import ProgressKey
|
||||
from app.utils.http import RequestUtils
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/qrcode", summary="生成二维码内容", response_model=schemas.Response)
|
||||
def qrcode(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
生成二维码
|
||||
"""
|
||||
qrcode_data = U115Helper().generate_qrcode()
|
||||
if qrcode_data:
|
||||
return schemas.Response(success=True, data={
|
||||
'codeContent': qrcode_data
|
||||
})
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/check", summary="二维码登录确认", response_model=schemas.Response)
|
||||
def check(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
二维码登录确认
|
||||
"""
|
||||
data, errmsg = U115Helper().check_login()
|
||||
if data:
|
||||
return schemas.Response(success=True, data=data)
|
||||
return schemas.Response(success=False, message=errmsg)
|
||||
|
||||
|
||||
@router.get("/storage", summary="查询存储空间信息", response_model=schemas.Response)
|
||||
def storage(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
查询存储空间信息
|
||||
"""
|
||||
storage_info = U115Helper().get_storage()
|
||||
if storage_info:
|
||||
return schemas.Response(success=True, data={
|
||||
"total": storage_info[0],
|
||||
"used": storage_info[1]
|
||||
})
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/list", summary="所有目录和文件(115网盘)", response_model=List[schemas.FileItem])
|
||||
def list_115(path: str,
|
||||
fileid: str,
|
||||
pickcode: str = None,
|
||||
filetype: str = "dir",
|
||||
sort: str = 'updated_at',
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
查询当前目录下所有目录和文件
|
||||
:param path: 当前路径
|
||||
:param fileid: 文件ID
|
||||
:param pickcode: 115 pickcode
|
||||
:param filetype: 文件类型
|
||||
:param sort: 排序方式,name:按名称排序,time:按修改时间排序
|
||||
:param _: token
|
||||
:return: 所有目录和文件
|
||||
"""
|
||||
if not fileid:
|
||||
return []
|
||||
if not path:
|
||||
path = "/"
|
||||
if fileid == "root":
|
||||
fileid = "0"
|
||||
if filetype == "file":
|
||||
name = Path(path).name
|
||||
suffix = Path(name).suffix[1:]
|
||||
return [schemas.FileItem(
|
||||
fileid=fileid,
|
||||
type="file",
|
||||
path=path.rstrip('/'),
|
||||
name=name,
|
||||
extension=suffix,
|
||||
pickcode=pickcode
|
||||
)]
|
||||
items = U115Helper().list_files(parent_file_id=fileid)
|
||||
if not items:
|
||||
return []
|
||||
file_list = [schemas.FileItem(
|
||||
fileid=item.file_id,
|
||||
parent_fileid=item.parent_id,
|
||||
type="dir" if item.is_dir else "file",
|
||||
path=f"{path}{item.name}" + "/" if item.is_dir else "",
|
||||
name=item.name,
|
||||
size=item.size,
|
||||
extension=Path(item.name).suffix[1:],
|
||||
modify_time=item.modified_time.timestamp() if item.modified_time else 0,
|
||||
pickcode=item.pickcode
|
||||
) for item in items]
|
||||
if sort == "name":
|
||||
file_list.sort(key=lambda x: x.name)
|
||||
else:
|
||||
file_list.sort(key=lambda x: x.modify_time, reverse=True)
|
||||
return file_list
|
||||
|
||||
|
||||
@router.get("/mkdir", summary="创建目录(115网盘)", response_model=schemas.Response)
|
||||
def mkdir_115(fileid: str,
|
||||
name: str,
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
创建目录
|
||||
"""
|
||||
if not fileid or not name:
|
||||
return schemas.Response(success=False)
|
||||
result = U115Helper().create_folder(parent_file_id=fileid, name=name)
|
||||
if result:
|
||||
return schemas.Response(success=True)
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/delete", summary="删除文件或目录(115网盘)", response_model=schemas.Response)
|
||||
def delete_115(fileid: str,
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
删除文件或目录
|
||||
"""
|
||||
if not fileid:
|
||||
return schemas.Response(success=False)
|
||||
result = U115Helper().delete_file(fileid)
|
||||
if result:
|
||||
return schemas.Response(success=True)
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/download", summary="下载文件(115网盘)")
|
||||
def download_115(pickcode: str,
|
||||
_: schemas.TokenPayload = Depends(verify_uri_token)) -> Any:
|
||||
"""
|
||||
下载文件或目录
|
||||
"""
|
||||
if not pickcode:
|
||||
return schemas.Response(success=False)
|
||||
ticket = U115Helper().download(pickcode)
|
||||
if ticket:
|
||||
# 请求数据,并以文件流的方式返回
|
||||
res = RequestUtils(headers=ticket.headers).get_res(ticket.url)
|
||||
if res:
|
||||
return Response(content=res.content, media_type="application/octet-stream")
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/rename", summary="重命名文件或目录(115网盘)", response_model=schemas.Response)
|
||||
def rename_115(fileid: str, new_name: str, path: str,
|
||||
recursive: bool = False,
|
||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||
"""
|
||||
重命名文件或目录
|
||||
"""
|
||||
if not fileid or not new_name:
|
||||
return schemas.Response(success=False)
|
||||
result = U115Helper().rename_file(fileid, new_name)
|
||||
if result:
|
||||
if recursive:
|
||||
transferchain = TransferChain()
|
||||
media_exts = settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIO_TRACK_EXT
|
||||
# 递归修改目录内文件(智能识别命名)
|
||||
sub_files: List[schemas.FileItem] = list_115(path=path, fileid=fileid)
|
||||
if sub_files:
|
||||
# 开始进度
|
||||
progress = ProgressHelper()
|
||||
progress.start(ProgressKey.BatchRename)
|
||||
total = len(sub_files)
|
||||
handled = 0
|
||||
for sub_file in sub_files:
|
||||
handled += 1
|
||||
progress.update(value=handled / total * 100,
|
||||
text=f"正在处理 {sub_file.name} ...",
|
||||
key=ProgressKey.BatchRename)
|
||||
if sub_file.type == "dir":
|
||||
continue
|
||||
if not sub_file.extension:
|
||||
continue
|
||||
if f".{sub_file.extension.lower()}" not in media_exts:
|
||||
continue
|
||||
sub_path = Path(f"{path}{sub_file.name}")
|
||||
meta = MetaInfoPath(sub_path)
|
||||
mediainfo = transferchain.recognize_media(meta)
|
||||
if not mediainfo:
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 未识别到媒体信息")
|
||||
new_path = transferchain.recommend_name(meta=meta, mediainfo=mediainfo)
|
||||
if not new_path:
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 未识别到新名称")
|
||||
ret: schemas.Response = rename_115(fileid=sub_file.fileid,
|
||||
path=path,
|
||||
new_name=Path(new_path).name,
|
||||
recursive=False)
|
||||
if not ret.success:
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=False, message=f"{sub_path.name} 重命名失败!")
|
||||
progress.end(ProgressKey.BatchRename)
|
||||
return schemas.Response(success=True)
|
||||
return schemas.Response(success=False)
|
||||
|
||||
|
||||
@router.get("/image", summary="读取图片(115网盘)")
|
||||
def image_115(pickcode: str, _: schemas.TokenPayload = Depends(verify_uri_token)) -> Any:
|
||||
"""
|
||||
读取图片
|
||||
"""
|
||||
if not pickcode:
|
||||
return schemas.Response(success=False)
|
||||
ticket = U115Helper().download(pickcode)
|
||||
if ticket:
|
||||
# 请求数据,获取内容编码为图片base64返回
|
||||
res = RequestUtils(headers=ticket.headers).get_res(ticket.url)
|
||||
if res:
|
||||
content_type = res.headers.get("Content-Type")
|
||||
return Response(content=res.content, media_type=content_type)
|
||||
raise HTTPException(status_code=500, detail="下载图片出错")
|
@ -4,7 +4,7 @@ from fastapi import APIRouter, BackgroundTasks, Request, Depends
|
||||
|
||||
from app import schemas
|
||||
from app.chain.webhook import WebhookChain
|
||||
from app.core.security import verify_uri_token
|
||||
from app.core.security import verify_apitoken
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@ -19,7 +19,7 @@ def start_webhook_chain(body: Any, form: Any, args: Any):
|
||||
@router.post("/", summary="Webhook消息响应", response_model=schemas.Response)
|
||||
async def webhook_message(background_tasks: BackgroundTasks,
|
||||
request: Request,
|
||||
_: str = Depends(verify_uri_token)
|
||||
_: str = Depends(verify_apitoken)
|
||||
) -> Any:
|
||||
"""
|
||||
Webhook响应
|
||||
@ -33,7 +33,7 @@ async def webhook_message(background_tasks: BackgroundTasks,
|
||||
|
||||
@router.get("/", summary="Webhook消息响应", response_model=schemas.Response)
|
||||
def webhook_message(background_tasks: BackgroundTasks,
|
||||
request: Request, _: str = Depends(verify_uri_token)) -> Any:
|
||||
request: Request, _: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
Webhook响应
|
||||
"""
|
||||
|
@ -7,7 +7,7 @@ from app import schemas
|
||||
from app.chain.media import MediaChain
|
||||
from app.chain.subscribe import SubscribeChain
|
||||
from app.core.metainfo import MetaInfo
|
||||
from app.core.security import verify_uri_apikey
|
||||
from app.core.security import verify_apikey
|
||||
from app.db import get_db
|
||||
from app.db.models.subscribe import Subscribe
|
||||
from app.schemas import RadarrMovie, SonarrSeries
|
||||
@ -18,7 +18,7 @@ arr_router = APIRouter(tags=['servarr'])
|
||||
|
||||
|
||||
@arr_router.get("/system/status", summary="系统状态")
|
||||
def arr_system_status(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_system_status(_: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
模拟Radarr、Sonarr系统状态
|
||||
"""
|
||||
@ -72,7 +72,7 @@ def arr_system_status(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
|
||||
|
||||
@arr_router.get("/qualityProfile", summary="质量配置")
|
||||
def arr_qualityProfile(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_qualityProfile(_: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
模拟Radarr、Sonarr质量配置
|
||||
"""
|
||||
@ -113,7 +113,7 @@ def arr_qualityProfile(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
|
||||
|
||||
@arr_router.get("/rootfolder", summary="根目录")
|
||||
def arr_rootfolder(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_rootfolder(_: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
模拟Radarr、Sonarr根目录
|
||||
"""
|
||||
@ -129,7 +129,7 @@ def arr_rootfolder(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
|
||||
|
||||
@arr_router.get("/tag", summary="标签")
|
||||
def arr_tag(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_tag(_: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
模拟Radarr、Sonarr标签
|
||||
"""
|
||||
@ -142,7 +142,7 @@ def arr_tag(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
|
||||
|
||||
@arr_router.get("/languageprofile", summary="语言")
|
||||
def arr_languageprofile(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_languageprofile(_: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
模拟Radarr、Sonarr语言
|
||||
"""
|
||||
@ -168,7 +168,7 @@ def arr_languageprofile(_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
|
||||
|
||||
@arr_router.get("/movie", summary="所有订阅电影", response_model=List[schemas.RadarrMovie])
|
||||
def arr_movies(_: str = Depends(verify_uri_apikey), db: Session = Depends(get_db)) -> Any:
|
||||
def arr_movies(_: str = Depends(verify_apikey), db: Session = Depends(get_db)) -> Any:
|
||||
"""
|
||||
查询Rardar电影
|
||||
"""
|
||||
@ -259,7 +259,7 @@ def arr_movies(_: str = Depends(verify_uri_apikey), db: Session = Depends(get_db
|
||||
|
||||
|
||||
@arr_router.get("/movie/lookup", summary="查询电影", response_model=List[schemas.RadarrMovie])
|
||||
def arr_movie_lookup(term: str, db: Session = Depends(get_db), _: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_movie_lookup(term: str, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
查询Rardar电影 term: `tmdb:${id}`
|
||||
存在和不存在均不能返回错误
|
||||
@ -305,7 +305,7 @@ def arr_movie_lookup(term: str, db: Session = Depends(get_db), _: str = Depends(
|
||||
|
||||
|
||||
@arr_router.get("/movie/{mid}", summary="电影订阅详情", response_model=schemas.RadarrMovie)
|
||||
def arr_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
查询Rardar电影订阅
|
||||
"""
|
||||
@ -333,7 +333,7 @@ def arr_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(verify_u
|
||||
@arr_router.post("/movie", summary="新增电影订阅")
|
||||
def arr_add_movie(movie: RadarrMovie,
|
||||
db: Session = Depends(get_db),
|
||||
_: str = Depends(verify_uri_apikey)
|
||||
_: str = Depends(verify_apikey)
|
||||
) -> Any:
|
||||
"""
|
||||
新增Rardar电影订阅
|
||||
@ -362,7 +362,7 @@ def arr_add_movie(movie: RadarrMovie,
|
||||
|
||||
|
||||
@arr_router.delete("/movie/{mid}", summary="删除电影订阅", response_model=schemas.Response)
|
||||
def arr_remove_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_remove_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
删除Rardar电影订阅
|
||||
"""
|
||||
@ -378,7 +378,7 @@ def arr_remove_movie(mid: int, db: Session = Depends(get_db), _: str = Depends(v
|
||||
|
||||
|
||||
@arr_router.get("/series", summary="所有剧集", response_model=List[schemas.SonarrSeries])
|
||||
def arr_series(_: str = Depends(verify_uri_apikey), db: Session = Depends(get_db)) -> Any:
|
||||
def arr_series(_: str = Depends(verify_apikey), db: Session = Depends(get_db)) -> Any:
|
||||
"""
|
||||
查询Sonarr剧集
|
||||
"""
|
||||
@ -514,7 +514,7 @@ def arr_series(_: str = Depends(verify_uri_apikey), db: Session = Depends(get_db
|
||||
|
||||
|
||||
@arr_router.get("/series/lookup", summary="查询剧集")
|
||||
def arr_series_lookup(term: str, db: Session = Depends(get_db), _: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_series_lookup(term: str, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
查询Sonarr剧集 term: `tvdb:${id}` title
|
||||
"""
|
||||
@ -603,7 +603,7 @@ def arr_series_lookup(term: str, db: Session = Depends(get_db), _: str = Depends
|
||||
|
||||
|
||||
@arr_router.get("/series/{tid}", summary="剧集详情")
|
||||
def arr_serie(tid: int, db: Session = Depends(get_db), _: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_serie(tid: int, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
查询Sonarr剧集
|
||||
"""
|
||||
@ -639,7 +639,7 @@ def arr_serie(tid: int, db: Session = Depends(get_db), _: str = Depends(verify_u
|
||||
@arr_router.post("/series", summary="新增剧集订阅")
|
||||
def arr_add_series(tv: schemas.SonarrSeries,
|
||||
db: Session = Depends(get_db),
|
||||
_: str = Depends(verify_uri_apikey)) -> Any:
|
||||
_: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
新增Sonarr剧集订阅
|
||||
"""
|
||||
@ -681,7 +681,7 @@ def arr_add_series(tv: schemas.SonarrSeries,
|
||||
|
||||
|
||||
@arr_router.delete("/series/{tid}", summary="删除剧集订阅")
|
||||
def arr_remove_series(tid: int, db: Session = Depends(get_db), _: str = Depends(verify_uri_apikey)) -> Any:
|
||||
def arr_remove_series(tid: int, db: Session = Depends(get_db), _: str = Depends(verify_apikey)) -> Any:
|
||||
"""
|
||||
删除Sonarr剧集订阅
|
||||
"""
|
||||
|
@ -107,7 +107,8 @@ class SiteChain(ChainBase):
|
||||
判断站点是否已经登陆:m-team
|
||||
"""
|
||||
user_agent = site.ua or settings.USER_AGENT
|
||||
url = f"{site.url}api/member/profile"
|
||||
domain = StringUtils.get_url_domain(site.url)
|
||||
url = f"https://api.{domain}/api/member/profile"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": user_agent,
|
||||
@ -127,7 +128,7 @@ class SiteChain(ChainBase):
|
||||
timeout=site.timeout or 15,
|
||||
proxies=settings.PROXY if site.proxy else None,
|
||||
referer=f"{site.url}index"
|
||||
).post_res(url=urljoin(url, "api/member/updateLastBrowse"))
|
||||
).post_res(url=f"https://api.{domain}/api/member/updateLastBrowse")
|
||||
if res:
|
||||
return True, "连接成功"
|
||||
else:
|
||||
|
@ -44,6 +44,15 @@ class TransferChain(ChainBase):
|
||||
self.systemconfig = SystemConfigOper()
|
||||
self.directoryhelper = DirectoryHelper()
|
||||
|
||||
def recommend_name(self, meta: MetaBase, mediainfo: MediaInfo) -> Optional[str]:
|
||||
"""
|
||||
获取重命名后的名称
|
||||
:param meta: 元数据
|
||||
:param mediainfo: 媒体信息
|
||||
:return: 重命名后的名称(含目录)
|
||||
"""
|
||||
return self.run_module("recommend_name", meta=meta, mediainfo=mediainfo)
|
||||
|
||||
def process(self) -> bool:
|
||||
"""
|
||||
获取下载器中的种子列表,并执行转移
|
||||
|
@ -61,21 +61,21 @@ def verify_token(token: str = Depends(reusable_oauth2)) -> schemas.TokenPayload:
|
||||
)
|
||||
|
||||
|
||||
def get_token(token: str = None) -> str:
|
||||
def __get_token(token: str = None) -> str:
|
||||
"""
|
||||
从请求URL中获取token
|
||||
"""
|
||||
return token
|
||||
|
||||
|
||||
def get_apikey(apikey: str = None, x_api_key: Annotated[str | None, Header()] = None) -> str:
|
||||
def __get_apikey(apikey: str = None, x_api_key: Annotated[str | None, Header()] = None) -> str:
|
||||
"""
|
||||
从请求URL中获取apikey
|
||||
"""
|
||||
return apikey or x_api_key
|
||||
|
||||
|
||||
def verify_uri_token(token: str = Depends(get_token)) -> str:
|
||||
def verify_apitoken(token: str = Depends(__get_token)) -> str:
|
||||
"""
|
||||
通过依赖项使用token进行身份认证
|
||||
"""
|
||||
@ -87,7 +87,7 @@ def verify_uri_token(token: str = Depends(get_token)) -> str:
|
||||
return token
|
||||
|
||||
|
||||
def verify_uri_apikey(apikey: str = Depends(get_apikey)) -> str:
|
||||
def verify_apikey(apikey: str = Depends(__get_apikey)) -> str:
|
||||
"""
|
||||
通过依赖项使用apikey进行身份认证
|
||||
"""
|
||||
@ -99,6 +99,18 @@ def verify_uri_apikey(apikey: str = Depends(get_apikey)) -> str:
|
||||
return apikey
|
||||
|
||||
|
||||
def verify_uri_token(token: str = Depends(__get_token)) -> str:
|
||||
"""
|
||||
通过依赖项使用token进行身份认证
|
||||
"""
|
||||
if not verify_token(token):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="token校验不通过"
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
450
app/helper/aliyun.py
Normal file
450
app/helper/aliyun.py
Normal file
@ -0,0 +1,450 @@
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
from typing import Optional, Tuple, List
|
||||
|
||||
from requests import Response
|
||||
|
||||
from app.core.config import settings
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
from app.log import logger
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.utils.http import RequestUtils
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
|
||||
class AliyunHelper:
|
||||
"""
|
||||
阿里云相关操作
|
||||
"""
|
||||
|
||||
_X_SIGNATURE = ('f4b7bed5d8524a04051bd2da876dd79afe922b8205226d65855d02b267422adb1'
|
||||
'e0d8a816b021eaf5c36d101892180f79df655c5712b348c2a540ca136e6b22001')
|
||||
|
||||
_X_PUBLIC_KEY = ('04d9d2319e0480c840efeeb75751b86d0db0c5b9e72c6260a1d846958adceaf9d'
|
||||
'ee789cab7472741d23aafc1a9c591f72e7ee77578656e6c8588098dea1488ac2a')
|
||||
|
||||
# 生成二维码
|
||||
qrcode_url = ("https://passport.aliyundrive.com/newlogin/qrcode/generate.do?"
|
||||
"appName=aliyun_drive&fromSite=52&appEntrance=web&isMobile=false"
|
||||
"&lang=zh_CN&returnUrl=&bizParams=&_bx-v=2.0.31")
|
||||
# 二维码登录确认
|
||||
check_url = "https://passport.aliyundrive.com/newlogin/qrcode/query.do?appName=aliyun_drive&fromSite=52&_bx-v=2.0.31"
|
||||
# 更新访问令牌
|
||||
update_accessstoken_url = "https://auth.aliyundrive.com/v2/account/token"
|
||||
# 创建会话
|
||||
create_session_url = "https://api.aliyundrive.com/users/v1/users/device/create_session"
|
||||
# 用户信息
|
||||
user_info_url = "https://user.aliyundrive.com/v2/user/get"
|
||||
# 浏览文件
|
||||
list_file_url = "https://api.aliyundrive.com/adrive/v3/file/list"
|
||||
# 创建目录
|
||||
create_folder_url = "https://api.aliyundrive.com/adrive/v2/file/createWithFolders"
|
||||
# 文件详情
|
||||
file_detail_url = "https://api.aliyundrive.com/v2/file/get"
|
||||
# 删除文件
|
||||
delete_file_url = " https://api.aliyundrive.com/v2/recyclebin/trash"
|
||||
# 文件重命名
|
||||
rename_file_url = "https://api.aliyundrive.com/v3/file/update"
|
||||
# 获取下载链接
|
||||
download_url = "https://api.aliyundrive.com/v2/file/get_download_url"
|
||||
|
||||
def __init__(self):
|
||||
self.systemconfig = SystemConfigOper()
|
||||
|
||||
def __handle_error(self, res: Response, apiname: str, action: bool = True):
|
||||
"""
|
||||
统一处理和打印错误信息
|
||||
"""
|
||||
if res is None:
|
||||
logger.warn("无法连接到阿里云盘!")
|
||||
return
|
||||
result = res.json()
|
||||
code = result.get("code")
|
||||
message = result.get("message")
|
||||
display_message = result.get("display_message")
|
||||
if code or message:
|
||||
logger.warn(f"Aliyun {apiname}失败:{code} - {display_message or message}")
|
||||
if action:
|
||||
if code == "DeviceSessionSignatureInvalid":
|
||||
logger.warn("设备已失效,正在重新建立会话...")
|
||||
self.create_session(self.get_headers(self.auth_params))
|
||||
if code == "UserDeviceOffline":
|
||||
logger.warn("设备已离线,尝试重新登录,如仍报错请检查阿里云盘绑定设备数量是否超限!")
|
||||
self.create_session(self.get_headers(self.auth_params))
|
||||
if code == "AccessTokenInvalid":
|
||||
logger.warn("访问令牌已失效,正在刷新令牌...")
|
||||
self.__update_accesstoken(self.auth_params, self.auth_params.get("refreshToken"))
|
||||
else:
|
||||
logger.info(f"Aliyun {apiname}成功")
|
||||
|
||||
@property
|
||||
def auth_params(self):
|
||||
"""
|
||||
获取阿里云盘认证参数并初始化参数格式
|
||||
"""
|
||||
return self.systemconfig.get(SystemConfigKey.UserAliyunParams) or {}
|
||||
|
||||
def update_params(self, params: dict):
|
||||
"""
|
||||
设置阿里云盘认证参数
|
||||
"""
|
||||
current_params = self.auth_params
|
||||
current_params.update(params)
|
||||
self.systemconfig.set(SystemConfigKey.UserAliyunParams, current_params)
|
||||
|
||||
def clear_params(self):
|
||||
"""
|
||||
清除阿里云盘认证参数
|
||||
"""
|
||||
self.systemconfig.delete(SystemConfigKey.UserAliyunParams)
|
||||
|
||||
def generate_qrcode(self) -> Optional[Tuple[dict, str]]:
|
||||
"""
|
||||
生成二维码
|
||||
"""
|
||||
res = RequestUtils(timeout=10).get_res(self.qrcode_url)
|
||||
if res:
|
||||
data = res.json().get("content", {}).get("data")
|
||||
return {
|
||||
"codeContent": data.get("codeContent"),
|
||||
"ck": data.get("ck"),
|
||||
"t": data.get("t")
|
||||
}, ""
|
||||
elif res is not None:
|
||||
self.__handle_error(res, "生成二维码")
|
||||
return {}, f"请求阿里云盘二维码失败:{res.status_code} - {res.reason}"
|
||||
return {}, f"请求阿里云盘二维码失败:无法连接!"
|
||||
|
||||
def check_login(self, ck: str, t: str) -> Optional[Tuple[dict, str]]:
|
||||
"""
|
||||
二维码登录确认
|
||||
"""
|
||||
params = {
|
||||
"t": t,
|
||||
"ck": ck,
|
||||
"appName": "aliyun_drive",
|
||||
"appEntrance": "web",
|
||||
"isMobile": "false",
|
||||
"lang": "zh_CN",
|
||||
"returnUrl": "",
|
||||
"fromSite": "52",
|
||||
"bizParams": "",
|
||||
"navlanguage": "zh-CN",
|
||||
"navPlatform": "MacIntel",
|
||||
}
|
||||
|
||||
body = "&".join([f"{key}={value}" for key, value in params.items()])
|
||||
|
||||
status = {
|
||||
"NEW": "请用阿里云盘 App 扫码",
|
||||
"SCANED": "请在手机上确认",
|
||||
"EXPIRED": "二维码已过期",
|
||||
"CANCELED": "已取消",
|
||||
"CONFIRMED": "已确认",
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
}
|
||||
|
||||
res = RequestUtils(headers=headers, timeout=5).post_res(self.check_url, data=body)
|
||||
if res:
|
||||
data = res.json().get("content", {}).get("data") or {}
|
||||
qrCodeStatus = data.get("qrCodeStatus")
|
||||
data["tip"] = status.get(qrCodeStatus) or "未知"
|
||||
if data.get("bizExt"):
|
||||
try:
|
||||
bizExt = json.loads(base64.b64decode(data["bizExt"]).decode('GBK'))
|
||||
pds_login_result = bizExt.get("pds_login_result")
|
||||
if pds_login_result:
|
||||
data.pop('bizExt')
|
||||
data.update({
|
||||
'userId': pds_login_result.get('userId'),
|
||||
'expiresIn': pds_login_result.get('expiresIn'),
|
||||
'nickName': pds_login_result.get('nickName'),
|
||||
'avatar': pds_login_result.get('avatar'),
|
||||
'tokenType': pds_login_result.get('tokenType'),
|
||||
"refreshToken": pds_login_result.get('refreshToken'),
|
||||
"accessToken": pds_login_result.get('accessToken'),
|
||||
"defaultDriveId": pds_login_result.get('defaultDriveId'),
|
||||
"updateTime": time.time(),
|
||||
})
|
||||
self.update_params(data)
|
||||
self.get_user_info()
|
||||
except Exception as e:
|
||||
return {}, f"bizExt 解码失败:{str(e)}"
|
||||
return data, ""
|
||||
elif res is not None:
|
||||
self.__handle_error(res, "登录确认")
|
||||
return {}, f"阿里云盘登录确认失败:{res.status_code} - {res.reason}"
|
||||
return {}, "阿里云盘登录确认失败:无法连接!"
|
||||
|
||||
def __update_accesstoken(self, params: dict, refresh_token: str) -> bool:
|
||||
"""
|
||||
更新阿里云盘访问令牌
|
||||
"""
|
||||
headers = self.get_headers(params)
|
||||
res = RequestUtils(headers=headers, timeout=10).post_res(
|
||||
self.update_accessstoken_url, json={
|
||||
"refresh_token": refresh_token,
|
||||
"grant_type": "refresh_token"
|
||||
})
|
||||
if res:
|
||||
data = res.json()
|
||||
code = data.get("code")
|
||||
if code in ["RefreshTokenExpired", "InvalidParameter.RefreshToken"]:
|
||||
logger.warn("刷新令牌已过期,请重新登录!")
|
||||
self.clear_params()
|
||||
return False
|
||||
self.update_params({
|
||||
"accessToken": data.get('access_token'),
|
||||
"expiresIn": data.get('expires_in'),
|
||||
"updateTime": time.time()
|
||||
})
|
||||
logger.info(f"阿里云盘访问令牌已更新,accessToken={data.get('access_token')}")
|
||||
return True
|
||||
else:
|
||||
self.__handle_error(res, "更新令牌", action=False)
|
||||
return False
|
||||
|
||||
def create_session(self, headers: dict):
|
||||
"""
|
||||
创建会话
|
||||
"""
|
||||
def __os_name():
|
||||
"""
|
||||
获取操作系统名称
|
||||
"""
|
||||
if SystemUtils.is_windows():
|
||||
return 'Windows 操作系统'
|
||||
elif SystemUtils.is_macos():
|
||||
return 'MacOS 操作系统'
|
||||
else:
|
||||
return '类 Unix 操作系统'
|
||||
|
||||
res = RequestUtils(headers=headers, timeout=5).post_res(self.create_session_url, json={
|
||||
'deviceName': f'MoviePilot {SystemUtils.platform}',
|
||||
'modelName': __os_name(),
|
||||
'pubKey': self._X_PUBLIC_KEY,
|
||||
})
|
||||
self.__handle_error(res, "创建会话", action=False)
|
||||
|
||||
def get_access_params(self) -> Optional[dict]:
|
||||
"""
|
||||
获取阿里云盘访问参数,如果超时则更新后返回
|
||||
"""
|
||||
params = self.auth_params
|
||||
if not params:
|
||||
logger.warn("阿里云盘访问令牌不存在,请先扫码登录!")
|
||||
return None
|
||||
expires_in = params.get("expiresIn")
|
||||
update_time = params.get("updateTime")
|
||||
refresh_token = params.get("refreshToken")
|
||||
if not expires_in or not update_time or not refresh_token:
|
||||
logger.warn("阿里云盘访问令牌参数错误,请重新扫码登录!")
|
||||
self.clear_params()
|
||||
return None
|
||||
# 是否需要更新设备信息
|
||||
update_device = False
|
||||
# 判断访问令牌是否过期
|
||||
if (time.time() - update_time) >= expires_in:
|
||||
logger.info("阿里云盘访问令牌已过期,正在更新...")
|
||||
if not self.__update_accesstoken(params, refresh_token):
|
||||
# 更新失败
|
||||
return None
|
||||
update_device = True
|
||||
# 生成设备ID
|
||||
x_device_id = params.get("x_device_id")
|
||||
if not x_device_id:
|
||||
x_device_id = uuid.uuid4().hex
|
||||
params['x_device_id'] = x_device_id
|
||||
self.update_params({"x_device_id": x_device_id})
|
||||
update_device = True
|
||||
# 更新设备信息重新创建会话
|
||||
if update_device:
|
||||
self.create_session(self.get_headers(params))
|
||||
return params
|
||||
|
||||
def get_headers(self, params: dict):
|
||||
"""
|
||||
获取请求头
|
||||
"""
|
||||
if not params:
|
||||
return {}
|
||||
return {
|
||||
"Authorization": f"Bearer {params.get('accessToken')}",
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Referer": "https://www.alipan.com/",
|
||||
"User-Agent": settings.USER_AGENT,
|
||||
"X-Canary": "client=web,app=adrive,version=v4.9.0",
|
||||
"x-device-id": params.get('x_device_id'),
|
||||
"x-signature": self._X_SIGNATURE
|
||||
}
|
||||
|
||||
def get_user_info(self) -> dict:
|
||||
"""
|
||||
获取用户信息(drive_id等)
|
||||
"""
|
||||
params = self.get_access_params()
|
||||
if not params:
|
||||
return {}
|
||||
headers = self.get_headers(params)
|
||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.user_info_url)
|
||||
if res:
|
||||
result = res.json()
|
||||
self.update_params({
|
||||
"resourceDriveId": result.get("resource_drive_id"),
|
||||
"backDriveId": result.get("backup_drive_id")
|
||||
})
|
||||
return result
|
||||
else:
|
||||
self.__handle_error(res, "获取用户信息")
|
||||
return {}
|
||||
|
||||
def list_files(self, parent_file_id: str = 'root', list_type: str = None,
|
||||
limit: int = 100, order_by: str = 'updated_at') -> List[dict]:
|
||||
"""
|
||||
浏览文件
|
||||
limit 返回文件数量,默认 50,最大 100
|
||||
order_by created_at/updated_at/name/size
|
||||
parent_file_id 根目录为root
|
||||
type all | file | folder
|
||||
"""
|
||||
params = self.get_access_params()
|
||||
if not params:
|
||||
return []
|
||||
# 最终返回数据
|
||||
ret_items = []
|
||||
# 请求头
|
||||
headers = self.get_headers(params)
|
||||
# 分页获取
|
||||
next_marker = None
|
||||
while True:
|
||||
if not parent_file_id or parent_file_id == "/":
|
||||
parent_file_id = "root"
|
||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.list_file_url, json={
|
||||
"drive_id": params.get("resourceDriveId"),
|
||||
"type": list_type,
|
||||
"limit": limit,
|
||||
"order_by": order_by,
|
||||
"parent_file_id": parent_file_id,
|
||||
"marker": next_marker
|
||||
}, params={
|
||||
'jsonmask': ('next_marker,items(name,file_id,drive_id,type,size,created_at,updated_at,'
|
||||
'category,file_extension,parent_file_id,mime_type,starred,thumbnail,url,'
|
||||
'streams_info,content_hash,user_tags,user_meta,trashed,video_media_metadata,'
|
||||
'video_preview_metadata,sync_meta,sync_device_flag,sync_flag,punish_flag')
|
||||
})
|
||||
if res:
|
||||
result = res.json()
|
||||
items = result.get("items")
|
||||
if not items:
|
||||
break
|
||||
# 合并数据
|
||||
ret_items.extend(items)
|
||||
next_marker = result.get("next_marker")
|
||||
if not next_marker:
|
||||
# 没有下一页
|
||||
break
|
||||
else:
|
||||
self.__handle_error(res, "浏览文件")
|
||||
break
|
||||
return ret_items
|
||||
|
||||
def create_folder(self, parent_file_id: str, name: str) -> bool:
|
||||
"""
|
||||
创建目录
|
||||
"""
|
||||
params = self.get_access_params()
|
||||
if not params:
|
||||
return False
|
||||
headers = self.get_headers(params)
|
||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.create_folder_url, json={
|
||||
"drive_id": params.get("resourceDriveId"),
|
||||
"parent_file_id": parent_file_id,
|
||||
"name": name,
|
||||
"check_name_mode": "refuse",
|
||||
"type": "folder"
|
||||
})
|
||||
if res:
|
||||
return True
|
||||
else:
|
||||
self.__handle_error(res, "创建目录")
|
||||
return False
|
||||
|
||||
def delete_file(self, file_id: str) -> bool:
|
||||
"""
|
||||
删除文件
|
||||
"""
|
||||
params = self.get_access_params()
|
||||
if not params:
|
||||
return False
|
||||
headers = self.get_headers(params)
|
||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.delete_file_url, json={
|
||||
"drive_id": params.get("resourceDriveId"),
|
||||
"file_id": file_id
|
||||
})
|
||||
if res:
|
||||
return True
|
||||
else:
|
||||
self.__handle_error(res, "删除文件")
|
||||
return False
|
||||
|
||||
def get_file_detail(self, file_id: str) -> Optional[dict]:
|
||||
"""
|
||||
获取文件详情
|
||||
"""
|
||||
params = self.get_access_params()
|
||||
if not params:
|
||||
return None
|
||||
headers = self.get_headers(params)
|
||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.file_detail_url, json={
|
||||
"drive_id": params.get("resourceDriveId"),
|
||||
"file_id": file_id
|
||||
})
|
||||
if res:
|
||||
return res.json()
|
||||
else:
|
||||
self.__handle_error(res, "获取文件详情")
|
||||
return None
|
||||
|
||||
def rename_file(self, file_id: str, name: str) -> bool:
|
||||
"""
|
||||
重命名文件
|
||||
"""
|
||||
params = self.get_access_params()
|
||||
if not params:
|
||||
return False
|
||||
headers = self.get_headers(params)
|
||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.rename_file_url, json={
|
||||
"drive_id": params.get("resourceDriveId"),
|
||||
"file_id": file_id,
|
||||
"name": name,
|
||||
"check_name_mode": "refuse"
|
||||
})
|
||||
if res:
|
||||
return True
|
||||
else:
|
||||
self.__handle_error(res, "重命名文件")
|
||||
return False
|
||||
|
||||
def get_download_url(self, file_id: str) -> Optional[str]:
|
||||
"""
|
||||
获取下载链接
|
||||
"""
|
||||
params = self.get_access_params()
|
||||
if not params:
|
||||
return None
|
||||
headers = self.get_headers(params)
|
||||
res = RequestUtils(headers=headers, timeout=10).post_res(self.download_url, json={
|
||||
"drive_id": params.get("resourceDriveId"),
|
||||
"file_id": file_id
|
||||
})
|
||||
if res:
|
||||
return res.json().get("url")
|
||||
else:
|
||||
self.__handle_error(res, "获取下载链接")
|
||||
return None
|
@ -34,7 +34,11 @@ class ProgressHelper(metaclass=Singleton):
|
||||
key = key.value
|
||||
if not self._process_detail.get(key):
|
||||
return
|
||||
self._process_detail[key]['enable'] = False
|
||||
self._process_detail[key] = {
|
||||
"enable": False,
|
||||
"value": 100,
|
||||
"text": "正在处理..."
|
||||
}
|
||||
|
||||
def update(self, key: Union[ProgressKey, str], value: float = None, text: str = None):
|
||||
if isinstance(key, Enum):
|
||||
|
206
app/helper/u115.py
Normal file
206
app/helper/u115.py
Normal file
@ -0,0 +1,206 @@
|
||||
import base64
|
||||
from typing import Optional, Tuple, Generator
|
||||
|
||||
import py115
|
||||
from py115 import Cloud
|
||||
from py115.types import LoginTarget, QrcodeSession, QrcodeStatus, Credential, File, DownloadTicket
|
||||
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
from app.log import logger
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.utils.singleton import Singleton
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
|
||||
class U115Helper(metaclass=Singleton):
|
||||
"""
|
||||
115相关操作
|
||||
"""
|
||||
|
||||
cloud: Optional[Cloud] = None
|
||||
_session: QrcodeSession = None
|
||||
|
||||
def __init__(self):
|
||||
self.systemconfig = SystemConfigOper()
|
||||
|
||||
def __init_cloud(self) -> bool:
|
||||
"""
|
||||
初始化Cloud
|
||||
"""
|
||||
credential = self.credential
|
||||
if not credential:
|
||||
logger.warn("115未登录,请先登录!")
|
||||
return False
|
||||
try:
|
||||
if not self.cloud:
|
||||
self.cloud = py115.connect(credential)
|
||||
except Exception as err:
|
||||
logger.error(f"115连接失败,请重新扫码登录:{str(err)}")
|
||||
self.clear_credential()
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def credential(self) -> Optional[Credential]:
|
||||
"""
|
||||
获取已保存的115认证参数
|
||||
"""
|
||||
cookie_dict = self.systemconfig.get(SystemConfigKey.User115Params)
|
||||
if not cookie_dict:
|
||||
return None
|
||||
return Credential.from_dict(cookie_dict)
|
||||
|
||||
def save_credentail(self, credential: Credential):
|
||||
"""
|
||||
设置115认证参数
|
||||
"""
|
||||
self.systemconfig.set(SystemConfigKey.User115Params, credential.to_dict())
|
||||
|
||||
def clear_credential(self):
|
||||
"""
|
||||
清除115认证参数
|
||||
"""
|
||||
self.systemconfig.delete(SystemConfigKey.User115Params)
|
||||
|
||||
def generate_qrcode(self) -> Optional[str]:
|
||||
"""
|
||||
生成二维码
|
||||
"""
|
||||
try:
|
||||
self.cloud = py115.connect()
|
||||
self._session = self.cloud.qrcode_login(LoginTarget.Web)
|
||||
image_bin = self._session.image_data
|
||||
if not image_bin:
|
||||
logger.warn("115生成二维码失败:未获取到二维码数据!")
|
||||
return None
|
||||
# 转换为base64图片格式
|
||||
image_base64 = base64.b64encode(image_bin).decode()
|
||||
return f"data:image/png;base64,{image_base64}"
|
||||
except Exception as e:
|
||||
logger.warn(f"115生成二维码失败:{str(e)}")
|
||||
return None
|
||||
|
||||
def check_login(self) -> Optional[Tuple[dict, str]]:
|
||||
"""
|
||||
二维码登录确认
|
||||
"""
|
||||
if not self._session:
|
||||
return {}, "请先生成二维码!"
|
||||
try:
|
||||
if not self.cloud:
|
||||
return {}, "请先生成二维码!"
|
||||
status = self.cloud.qrcode_poll(self._session)
|
||||
if status == QrcodeStatus.Done:
|
||||
# 确认完成,保存认证信息
|
||||
self.save_credentail(self.cloud.export_credentail())
|
||||
result = {
|
||||
"status": 1,
|
||||
"tip": "登录成功!"
|
||||
}
|
||||
elif status == QrcodeStatus.Waiting:
|
||||
result = {
|
||||
"status": 0,
|
||||
"tip": "请使用微信或115客户端扫码"
|
||||
}
|
||||
elif status == QrcodeStatus.Expired:
|
||||
result = {
|
||||
"status": -1,
|
||||
"tip": "二维码已过期,请重新刷新!"
|
||||
}
|
||||
self.cloud = None
|
||||
elif status == QrcodeStatus.Failed:
|
||||
result = {
|
||||
"status": -2,
|
||||
"tip": "登录失败,请重试!"
|
||||
}
|
||||
self.cloud = None
|
||||
else:
|
||||
result = {
|
||||
"status": -3,
|
||||
"tip": "未知错误,请重试!"
|
||||
}
|
||||
self.cloud = None
|
||||
return result, ""
|
||||
except Exception as e:
|
||||
return {}, f"115登录确认失败:{str(e)}"
|
||||
|
||||
def list_files(self, parent_file_id: str = '0') -> Optional[Generator[File, None, None]]:
|
||||
"""
|
||||
浏览文件
|
||||
"""
|
||||
if not self.__init_cloud():
|
||||
return None
|
||||
try:
|
||||
return self.cloud.storage().list(dir_id=parent_file_id)
|
||||
except Exception as e:
|
||||
logger.error(f"浏览115文件失败:{str(e)}")
|
||||
return None
|
||||
|
||||
def create_folder(self, parent_file_id: str, name: str) -> bool:
|
||||
"""
|
||||
创建目录
|
||||
"""
|
||||
if not self.__init_cloud():
|
||||
return False
|
||||
try:
|
||||
self.cloud.storage().make_dir(parent_file_id, name)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"创建115目录失败:{str(e)}")
|
||||
return False
|
||||
|
||||
def delete_file(self, file_id: str) -> bool:
|
||||
"""
|
||||
删除文件
|
||||
"""
|
||||
if not self.__init_cloud():
|
||||
return False
|
||||
try:
|
||||
self.cloud.storage().delete(file_id)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"删除115文件失败:{str(e)}")
|
||||
return False
|
||||
|
||||
def get_file_detail(self, file_id: str) -> Optional[dict]:
|
||||
"""
|
||||
获取文件详情
|
||||
"""
|
||||
pass
|
||||
|
||||
def rename_file(self, file_id: str, name: str) -> bool:
|
||||
"""
|
||||
重命名文件
|
||||
"""
|
||||
if not self.__init_cloud():
|
||||
return False
|
||||
try:
|
||||
self.cloud.storage().rename(file_id, name)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"重命名115文件失败:{str(e)}")
|
||||
return False
|
||||
|
||||
def download(self, pickcode: str) -> Optional[DownloadTicket]:
|
||||
"""
|
||||
获取下载链接
|
||||
"""
|
||||
if not self.__init_cloud():
|
||||
return None
|
||||
try:
|
||||
return self.cloud.storage().request_download(pickcode)
|
||||
except Exception as e:
|
||||
logger.error(f"115下载失败:{str(e)}")
|
||||
return None
|
||||
|
||||
def get_storage(self) -> Optional[Tuple[int, int]]:
|
||||
"""
|
||||
获取存储空间
|
||||
"""
|
||||
if not self.__init_cloud():
|
||||
return None
|
||||
try:
|
||||
return self.cloud.storage().space()
|
||||
except Exception as e:
|
||||
logger.error(f"获取115存储空间失败:{str(e)}")
|
||||
return None
|
@ -83,6 +83,25 @@ class FileTransferModule(_ModuleBase):
|
||||
def init_setting(self) -> Tuple[str, Union[str, bool]]:
|
||||
pass
|
||||
|
||||
def recommend_name(self, meta: MetaBase, mediainfo: MediaInfo) -> Optional[str]:
|
||||
"""
|
||||
获取重命名后的名称
|
||||
:param meta: 元数据
|
||||
:param mediainfo: 媒体信息
|
||||
:return: 重命名后的名称(含目录)
|
||||
"""
|
||||
# 重命名格式
|
||||
rename_format = settings.TV_RENAME_FORMAT \
|
||||
if mediainfo.type == MediaType.TV else settings.MOVIE_RENAME_FORMAT
|
||||
# 获取重命名后的名称
|
||||
path = self.get_rename_path(
|
||||
template_string=rename_format,
|
||||
rename_dict=self.__get_naming_dict(meta=meta,
|
||||
mediainfo=mediainfo,
|
||||
file_ext=Path(meta.title).suffix)
|
||||
)
|
||||
return str(path)
|
||||
|
||||
def transfer(self, path: Path, meta: MetaBase, mediainfo: MediaInfo,
|
||||
transfer_type: str, target: Path = None,
|
||||
episodes_info: List[TmdbEpisode] = None,
|
||||
|
@ -19,13 +19,14 @@ class MTorrentSpider:
|
||||
"""
|
||||
_indexerid = None
|
||||
_domain = None
|
||||
_url = None
|
||||
_name = ""
|
||||
_proxy = None
|
||||
_cookie = None
|
||||
_ua = None
|
||||
_size = 100
|
||||
_searchurl = "%sapi/torrent/search"
|
||||
_downloadurl = "%sapi/torrent/genDlToken"
|
||||
_searchurl = "https://api.%s/api/torrent/search"
|
||||
_downloadurl = "https://api.%s/api/torrent/genDlToken"
|
||||
_pageurl = "%sdetail/%s"
|
||||
_timeout = 15
|
||||
|
||||
@ -54,7 +55,8 @@ class MTorrentSpider:
|
||||
self.systemconfig = SystemConfigOper()
|
||||
if indexer:
|
||||
self._indexerid = indexer.get('id')
|
||||
self._domain = indexer.get('domain')
|
||||
self._url = indexer.get('domain')
|
||||
self._domain = StringUtils.get_url_domain(self._url)
|
||||
self._searchurl = self._searchurl % self._domain
|
||||
self._name = indexer.get('name')
|
||||
if indexer.get('proxy'):
|
||||
@ -124,7 +126,7 @@ class MTorrentSpider:
|
||||
'grabs': int(result.get('status', {}).get("timesCompleted") or '0'),
|
||||
'downloadvolumefactor': self.__get_downloadvolumefactor(result.get('status', {}).get("discount")),
|
||||
'uploadvolumefactor': self.__get_uploadvolumefactor(result.get('status', {}).get("discount")),
|
||||
'page_url': self._pageurl % (self._domain, result.get('id')),
|
||||
'page_url': self._pageurl % (self._url, result.get('id')),
|
||||
'imdbid': self.__find_imdbid(result.get('imdb')),
|
||||
'labels': labels,
|
||||
'category': category
|
||||
|
@ -20,3 +20,11 @@ class FileItem(BaseModel):
|
||||
modify_time: Optional[float] = None
|
||||
# 子节点
|
||||
children: Optional[list] = []
|
||||
# ID
|
||||
fileid: Optional[str] = None
|
||||
# 父ID
|
||||
parent_fileid: Optional[str] = None
|
||||
# 缩略图
|
||||
thumbnail: Optional[str] = None
|
||||
# 115 pickcode
|
||||
pickcode: Optional[str] = None
|
||||
|
@ -94,6 +94,10 @@ class SystemConfigKey(Enum):
|
||||
DownloadDirectories = "DownloadDirectories"
|
||||
# 媒体库目录定义
|
||||
LibraryDirectories = "LibraryDirectories"
|
||||
# 阿里云盘认证参数
|
||||
UserAliyunParams = "UserAliyunParams"
|
||||
# 115网盘认证参数
|
||||
User115Params = "User115Params"
|
||||
|
||||
|
||||
# 处理进度Key字典
|
||||
@ -102,6 +106,8 @@ class ProgressKey(Enum):
|
||||
Search = "search"
|
||||
# 转移
|
||||
FileTransfer = "filetransfer"
|
||||
# 批量重命名
|
||||
BatchRename = "batchrename"
|
||||
|
||||
|
||||
# 媒体图片类型
|
||||
|
@ -383,6 +383,21 @@ class StringUtils:
|
||||
print(str(e))
|
||||
return timestamp
|
||||
|
||||
@staticmethod
|
||||
def str_to_timestamp(date_str: str) -> float:
|
||||
"""
|
||||
日期转时间戳
|
||||
:param date_str:
|
||||
:return:
|
||||
"""
|
||||
if not date_str:
|
||||
return 0
|
||||
try:
|
||||
return dateparser.parse(date_str).timestamp()
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def to_bool(text: str, default_val: bool = False) -> bool:
|
||||
"""
|
||||
|
@ -58,3 +58,4 @@ pystray~=0.19.5
|
||||
pyotp~=2.9.0
|
||||
Pinyin2Hanzi~=0.1.1
|
||||
pywebpush~=2.0.0
|
||||
py115~=0.0.4
|
Reference in New Issue
Block a user