add 115 apis

This commit is contained in:
jxxghp 2024-06-19 07:11:26 +08:00
parent 354d5977e0
commit 7103b0334a
5 changed files with 345 additions and 1 deletions

View File

@ -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, \
local, transfer, mediaserver, bangumi, aliyun
local, transfer, mediaserver, bangumi, aliyun, u115
api_router = APIRouter()
api_router.include_router(login.router, prefix="/login", tags=["login"])
@ -25,3 +25,4 @@ 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"])

198
app/api/endpoints/u115.py Normal file
View File

@ -0,0 +1,198 @@
from pathlib import Path
from typing import Any, List
from fastapi import APIRouter, Depends
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.u115 import U115Helper
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 = U115Helper().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 = U115Helper().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:
"""
查询用户信息
"""
pass
@router.get("/list", summary="所有目录和文件115网盘", response_model=List[schemas.FileItem])
def list_115(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 = U115Helper().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 = U115Helper().list_files(parent_file_id=fileid)
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="创建目录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(fileid: str,
_: schemas.TokenPayload = Depends(verify_uri_token)) -> Any:
"""
下载文件或目录
"""
if not fileid:
return schemas.Response(success=False)
url = U115Helper().get_download_url(fileid)
if url:
# 重定向
return Response(status_code=302, headers={"Location": url})
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)
for sub_file in sub_files:
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:
return schemas.Response(success=False, message=f"{sub_path.name} 未识别到媒体信息")
new_path = transferchain.recommend_name(meta=meta, mediainfo=mediainfo)
if not new_path:
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:
return schemas.Response(success=False, message=f"{sub_path.name} 重命名失败!")
return schemas.Response(success=True)
return schemas.Response(success=False)
@router.get("/image", summary="读取图片115网盘", response_model=schemas.Response)
def image_115(fileid: str, _: schemas.TokenPayload = Depends(verify_uri_token)) -> Any:
"""
读取图片
"""
if not fileid:
return schemas.Response(success=False)
url = U115Helper().get_download_url(fileid)
if url:
# 重定向
return Response(status_code=302, headers={"Location": url})
return schemas.Response(success=False)

142
app/helper/u115.py Normal file
View File

@ -0,0 +1,142 @@
from typing import Optional, Tuple, List
import py115
from py115 import Cloud
from py115.types import LoginTarget, QrcodeSession, QrcodeStatus
from app.db.systemconfig_oper import SystemConfigOper
from app.schemas.types import SystemConfigKey
from app.utils.singleton import Singleton
from app.utils.system import SystemUtils
class U115Helper(metaclass=Singleton):
"""
115相关操作
"""
cloud: Cloud = None
session: QrcodeSession = None
def __init__(self):
self.systemconfig = SystemConfigOper()
@property
def cookies(self):
"""
获取115认证参数并初始化参数格式
"""
return self.systemconfig.get(SystemConfigKey.User115Params) or {}
def save_credentail(self, cookies: dict):
"""
设置115认证参数
"""
self.systemconfig.set(SystemConfigKey.User115Params, cookies)
def clear_params(self):
"""
清除115认证参数
"""
self.systemconfig.delete(SystemConfigKey.User115Params)
def generate_qrcode(self) -> Optional[Tuple[dict, str]]:
"""
生成二维码
"""
def __get_os():
"""
获取操作系统名称
"""
if SystemUtils.is_windows():
return LoginTarget.Windows
elif SystemUtils.is_macos():
return LoginTarget.Mac
else:
return LoginTarget.Linux
try:
self.cloud = py115.connect()
self.session = self.cloud.qrcode_login(__get_os)
return self.session.image_data, ""
except Exception as e:
return None, f"115生成二维码失败{str(e)}"
def check_login(self, ck: str, t: str) -> Optional[Tuple[dict, str]]:
"""
二维码登录确认
"""
if not self.session:
return None, "请先生成二维码!"
try:
status = self.cloud.qrcode_poll(self.session)
if status == QrcodeStatus.Done:
# 确认完成,保存认证信息
self.save_credentail(self.cloud.export_credentail())
elif status == QrcodeStatus.Waiting:
return {
"status": 0,
"tip": "等待扫码确认..."
}, ""
elif status == QrcodeStatus.Expired:
return {
"status": -1,
"tip": "二维码已过期,请重新刷新!"
}, ""
elif status == QrcodeStatus.Failed:
return {
"status": -2,
"tip": "登录失败,请重试!"
}, ""
return None, "登录确认失败!"
except Exception as e:
return None, f"115登录确认失败{str(e)}"
def list_files(self, parent_file_id: str = '0') -> List[dict]:
"""
浏览文件
"""
cookies = self.cookies
if not cookies:
return []
return self.cloud.storage().list(dir_id=parent_file_id)
def create_folder(self, parent_file_id: str, name: str) -> bool:
"""
创建目录
"""
cookies = self.cookies
if not cookies:
return False
return self.cloud.storage().make_dir(parent_file_id, name)
def delete_file(self, file_id: str) -> bool:
"""
删除文件
"""
cookies = self.cookies
if not cookies:
return False
return self.cloud.storage().delete(file_id)
def get_file_detail(self, file_id: str) -> Optional[dict]:
"""
获取文件详情
"""
pass
def rename_file(self, file_id: str, name: str) -> bool:
"""
重命名文件
"""
cookies = self.cookies
if not cookies:
return False
return self.cloud.storage().rename(file_id, name)
def get_download_url(self, file_id: str) -> Optional[str]:
"""
获取下载链接
"""
pass

View File

@ -96,6 +96,8 @@ class SystemConfigKey(Enum):
LibraryDirectories = "LibraryDirectories"
# 阿里云盘认证参数
UserAliyunParams = "UserAliyunParams"
# 115网盘认证参数
User115Params = "User115Params"
# 处理进度Key字典

View File

@ -58,3 +58,4 @@ pystray~=0.19.5
pyotp~=2.9.0
Pinyin2Hanzi~=0.1.1
pywebpush~=2.0.0
py115~=0.0.4