From 2bfbad28743d6ce70dbf504f1af84f321c36a6e2 Mon Sep 17 00:00:00 2001 From: KirCute <951206789@qq.com> Date: Tue, 7 Oct 2025 01:13:25 +0800 Subject: [PATCH] feat(offline_download): add 123 open (#1427) --- drivers/123_open/driver.go | 8 ++ drivers/123_open/types.go | 19 +++ drivers/123_open/util.go | 34 ++++++ internal/conf/const.go | 4 + internal/offline_download/123_open/client.go | 119 +++++++++++++++++++ internal/offline_download/all.go | 1 + internal/offline_download/tool/add.go | 16 ++- internal/offline_download/tool/download.go | 5 +- server/handles/offline_download.go | 51 +++++++- server/router.go | 1 + 10 files changed, 250 insertions(+), 8 deletions(-) create mode 100644 internal/offline_download/123_open/client.go diff --git a/drivers/123_open/driver.go b/drivers/123_open/driver.go index 62b4b95c..ac75e51d 100644 --- a/drivers/123_open/driver.go +++ b/drivers/123_open/driver.go @@ -229,6 +229,14 @@ func (d *Open123) GetDetails(ctx context.Context) (*model.StorageDetails, error) }, nil } +func (d *Open123) OfflineDownload(ctx context.Context, url string, dir model.Obj, callback string) (int, error) { + return d.createOfflineDownloadTask(ctx, url, dir.GetID(), callback) +} + +func (d *Open123) OfflineDownloadProcess(ctx context.Context, taskID int) (float64, int, error) { + return d.queryOfflineDownloadStatus(ctx, taskID) +} + var ( _ driver.Driver = (*Open123)(nil) _ driver.PutResult = (*Open123)(nil) diff --git a/drivers/123_open/types.go b/drivers/123_open/types.go index 70257d84..dd51cb2a 100644 --- a/drivers/123_open/types.go +++ b/drivers/123_open/types.go @@ -19,6 +19,7 @@ func (a *ApiInfo) Require() { a.token <- struct{}{} } } + func (a *ApiInfo) Release() { if a.qps > 0 { time.AfterFunc(time.Second, func() { @@ -26,13 +27,16 @@ func (a *ApiInfo) Release() { }) } } + func (a *ApiInfo) SetQPS(qps int) { a.qps = qps a.token = make(chan struct{}, qps) } + func (a *ApiInfo) NowLen() int { return len(a.token) } + func InitApiInfo(url string, qps int) *ApiInfo { return &ApiInfo{ url: url, @@ -185,3 +189,18 @@ type UploadCompleteResp struct { FileID int64 `json:"fileID"` } `json:"data"` } + +type OfflineDownloadResp struct { + BaseResp + Data struct { + TaskID int `json:"taskID"` + } `json:"data"` +} + +type OfflineDownloadProcessResp struct { + BaseResp + Data struct { + Process float64 `json:"process"` + Status int `json:"status"` + } `json:"data"` +} diff --git a/drivers/123_open/util.go b/drivers/123_open/util.go index 17da9a66..b09d9eb8 100644 --- a/drivers/123_open/util.go +++ b/drivers/123_open/util.go @@ -34,6 +34,9 @@ var ( // 不同情况下获取的AccessTokenQPS限制不同 如下模块化易 Trash = InitApiInfo(Api+"/api/v1/file/trash", 2) UploadCreate = InitApiInfo(Api+"/upload/v2/file/create", 2) UploadComplete = InitApiInfo(Api+"/upload/v2/file/upload_complete", 0) + + OfflineDownload = InitApiInfo(Api+"/api/v1/offline/download", 1) + OfflineDownloadProcess = InitApiInfo(Api+"/api/v1/offline/download/process", 5) ) func (d *Open123) Request(apiInfo *ApiInfo, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { @@ -277,3 +280,34 @@ func (d *Open123) trash(fileId int64) error { return nil } + +func (d *Open123) createOfflineDownloadTask(ctx context.Context, url string, dirID, callback string) (taskID int, err error) { + body := base.Json{ + "url": url, + "dirID": dirID, + } + if len(callback) > 0 { + body["callBackUrl"] = callback + } + var resp OfflineDownloadResp + _, err = d.Request(OfflineDownload, http.MethodPost, func(req *resty.Request) { + req.SetBody(body) + }, &resp) + if err != nil { + return 0, err + } + return resp.Data.TaskID, nil +} + +func (d *Open123) queryOfflineDownloadStatus(ctx context.Context, taskID int) (process float64, status int, err error) { + var resp OfflineDownloadProcessResp + _, err = d.Request(OfflineDownloadProcess, http.MethodGet, func(req *resty.Request) { + req.SetQueryParams(map[string]string{ + "taskID": strconv.Itoa(taskID), + }) + }, &resp) + if err != nil { + return .0, 0, err + } + return resp.Data.Process, resp.Data.Status, nil +} diff --git a/internal/conf/const.go b/internal/conf/const.go index 5543f608..ba98ca16 100644 --- a/internal/conf/const.go +++ b/internal/conf/const.go @@ -125,6 +125,10 @@ const ( QbittorrentUrl = "qbittorrent_url" QbittorrentSeedtime = "qbittorrent_seedtime" + // 123 open offline download + Pan123OpenOfflineDownloadCallbackUrl = "123_open_callback_url" + Pan123OpenTempDir = "123_open_temp_dir" + // ftp FTPPublicHost = "ftp_public_host" FTPPasvPortMap = "ftp_pasv_port_map" diff --git a/internal/offline_download/123_open/client.go b/internal/offline_download/123_open/client.go new file mode 100644 index 00000000..ce1453c3 --- /dev/null +++ b/internal/offline_download/123_open/client.go @@ -0,0 +1,119 @@ +package _123_open + +import ( + "context" + "fmt" + "strconv" + + _123_open "github.com/OpenListTeam/OpenList/v4/drivers/123_open" + "github.com/OpenListTeam/OpenList/v4/internal/conf" + "github.com/OpenListTeam/OpenList/v4/internal/errs" + "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/offline_download/tool" + "github.com/OpenListTeam/OpenList/v4/internal/op" + "github.com/OpenListTeam/OpenList/v4/internal/setting" +) + +type Open123 struct{} + +func (*Open123) Name() string { + return "123 Open" +} + +func (*Open123) Items() []model.SettingItem { + return nil +} + +func (*Open123) Run(_ *tool.DownloadTask) error { + return errs.NotSupport +} + +func (*Open123) Init() (string, error) { + return "ok", nil +} + +func (*Open123) IsReady() bool { + tempDir := setting.GetStr(conf.Pan123OpenTempDir) + if tempDir == "" { + return false + } + storage, _, err := op.GetStorageAndActualPath(tempDir) + if err != nil { + return false + } + if _, ok := storage.(*_123_open.Open123); !ok { + return false + } + return true +} + +func (*Open123) AddURL(args *tool.AddUrlArgs) (string, error) { + storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir) + if err != nil { + return "", err + } + driver123Open, ok := storage.(*_123_open.Open123) + if !ok { + return "", fmt.Errorf("unsupported storage driver for offline download, only 123 Open is supported") + } + ctx := context.Background() + if err := op.MakeDir(ctx, storage, actualPath); err != nil { + return "", err + } + parentDir, err := op.GetUnwrap(ctx, storage, actualPath) + if err != nil { + return "", err + } + cb := setting.GetStr(conf.Pan123OpenOfflineDownloadCallbackUrl) + taskID, err := driver123Open.OfflineDownload(ctx, args.Url, parentDir, cb) + if err != nil { + return "", fmt.Errorf("failed to add offline download task: %w", err) + } + return strconv.Itoa(taskID), nil +} + +func (*Open123) Remove(_ *tool.DownloadTask) error { + return errs.NotSupport +} + +func (*Open123) Status(task *tool.DownloadTask) (*tool.Status, error) { + taskID, err := strconv.Atoi(task.GID) + if err != nil { + return nil, fmt.Errorf("failed to parse task ID: %s", task.GID) + } + storage, _, err := op.GetStorageAndActualPath(task.TempDir) + if err != nil { + return nil, err + } + driver123Open, ok := storage.(*_123_open.Open123) + if !ok { + return nil, fmt.Errorf("unsupported storage driver for offline download, only 123 Open is supported") + } + process, status, err := driver123Open.OfflineDownloadProcess(context.Background(), taskID) + if err != nil { + return nil, err + } + var statusStr string + switch status { + case 0: + statusStr = "downloading" + case 1: + err = fmt.Errorf("offline download failed") + case 2: + statusStr = "succeed" + case 3: + statusStr = "retrying" + } + return &tool.Status{ + Progress: process, + Completed: status == 2, + Status: statusStr, + Err: err, + }, nil +} + +var _ tool.Tool = (*Open123)(nil) + +func init() { + tool.Tools.Add(&Open123{}) +} diff --git a/internal/offline_download/all.go b/internal/offline_download/all.go index f79a8312..a133e98b 100644 --- a/internal/offline_download/all.go +++ b/internal/offline_download/all.go @@ -3,6 +3,7 @@ package offline_download import ( _ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/115" _ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/115_open" + _ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/123_open" _ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/aria2" _ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/http" _ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/pikpak" diff --git a/internal/offline_download/tool/add.go b/internal/offline_download/tool/add.go index aea88e2a..6edee740 100644 --- a/internal/offline_download/tool/add.go +++ b/internal/offline_download/tool/add.go @@ -2,18 +2,16 @@ package tool import ( "context" - "github.com/OpenListTeam/OpenList/v4/drivers/thunder_browser" - - _115_open "github.com/OpenListTeam/OpenList/v4/drivers/115_open" - "github.com/OpenListTeam/OpenList/v4/server/common" - "net/url" stdpath "path" "path/filepath" _115 "github.com/OpenListTeam/OpenList/v4/drivers/115" + _115_open "github.com/OpenListTeam/OpenList/v4/drivers/115_open" + _123_open "github.com/OpenListTeam/OpenList/v4/drivers/123_open" "github.com/OpenListTeam/OpenList/v4/drivers/pikpak" "github.com/OpenListTeam/OpenList/v4/drivers/thunder" + "github.com/OpenListTeam/OpenList/v4/drivers/thunder_browser" "github.com/OpenListTeam/OpenList/v4/drivers/thunderx" "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/errs" @@ -22,6 +20,7 @@ import ( "github.com/OpenListTeam/OpenList/v4/internal/op" "github.com/OpenListTeam/OpenList/v4/internal/setting" "github.com/OpenListTeam/OpenList/v4/internal/task" + "github.com/OpenListTeam/OpenList/v4/server/common" "github.com/google/uuid" "github.com/pkg/errors" ) @@ -104,6 +103,13 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro } else { tempDir = filepath.Join(setting.GetStr(conf.Pan115OpenTempDir), uid) } + case "123 Open": + if _, ok := storage.(*_123_open.Open123); ok && dstDirActualPath != "/" { + // directly offline downloading to the root path is not allowed via 123 open platform + tempDir = args.DstDirPath + } else { + tempDir = filepath.Join(setting.GetStr(conf.Pan123OpenTempDir), uid) + } case "PikPak": if _, ok := storage.(*pikpak.PikPak); ok { tempDir = args.DstDirPath diff --git a/internal/offline_download/tool/download.go b/internal/offline_download/tool/download.go index 2d402dcf..f6c53081 100644 --- a/internal/offline_download/tool/download.go +++ b/internal/offline_download/tool/download.go @@ -111,6 +111,9 @@ outer: if t.tool.Name() == "115 Open" { return nil } + if t.tool.Name() == "123 Open" { + return nil + } t.Status = "offline download completed, maybe transferring" // hack for qBittorrent if t.tool.Name() == "qBittorrent" { @@ -174,7 +177,7 @@ func (t *DownloadTask) Update() (bool, error) { func (t *DownloadTask) Transfer() error { toolName := t.tool.Name() - if toolName == "115 Cloud" || toolName == "115 Open" || toolName == "PikPak" || toolName == "Thunder" || toolName == "ThunderX" || toolName == "ThunderBrowser" { + if toolName == "115 Cloud" || toolName == "115 Open" || toolName == "123 Open" || toolName == "PikPak" || toolName == "Thunder" || toolName == "ThunderX" || toolName == "ThunderBrowser" { // 如果不是直接下载到目标路径,则进行转存 if t.TempDir != t.DstDirPath { return transferObj(t.Ctx(), t.TempDir, t.DstDirPath, t.DeletePolicy) diff --git a/server/handles/offline_download.go b/server/handles/offline_download.go index fb652734..9d3f2230 100644 --- a/server/handles/offline_download.go +++ b/server/handles/offline_download.go @@ -2,9 +2,10 @@ package handles import ( "strings" - + _115 "github.com/OpenListTeam/OpenList/v4/drivers/115" _115_open "github.com/OpenListTeam/OpenList/v4/drivers/115_open" + _123_open "github.com/OpenListTeam/OpenList/v4/drivers/123_open" "github.com/OpenListTeam/OpenList/v4/drivers/pikpak" "github.com/OpenListTeam/OpenList/v4/drivers/thunder" "github.com/OpenListTeam/OpenList/v4/drivers/thunder_browser" @@ -200,6 +201,52 @@ func Set115Open(c *gin.Context) { common.SuccessResp(c, "ok") } +type Set123OpenReq struct { + TempDir string `json:"temp_dir" form:"temp_dir"` + CallbackUrl string `json:"callback_url" form:"callback_url"` +} + +func Set123Open(c *gin.Context) { + var req Set123OpenReq + if err := c.ShouldBind(&req); err != nil { + common.ErrorResp(c, err, 400) + return + } + if req.TempDir != "" { + storage, _, err := op.GetStorageAndActualPath(req.TempDir) + if err != nil { + common.ErrorStrResp(c, "storage does not exists", 400) + return + } + if storage.Config().CheckStatus && storage.GetStorage().Status != op.WORK { + common.ErrorStrResp(c, "storage not init: "+storage.GetStorage().Status, 400) + return + } + if _, ok := storage.(*_123_open.Open123); !ok { + common.ErrorStrResp(c, "unsupported storage driver for offline download, only 123 Open is supported", 400) + return + } + } + items := []model.SettingItem{ + {Key: conf.Pan123OpenTempDir, Value: req.TempDir, Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE}, + {Key: conf.Pan123OpenOfflineDownloadCallbackUrl, Value: req.CallbackUrl, Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE}, + } + if err := op.SaveSettingItems(items); err != nil { + common.ErrorResp(c, err, 500) + return + } + _tool, err := tool.Tools.Get("123 Open") + if err != nil { + common.ErrorResp(c, err, 500) + return + } + if _, err := _tool.Init(); err != nil { + common.ErrorResp(c, err, 500) + return + } + common.SuccessResp(c, "ok") +} + type SetPikPakReq struct { TempDir string `json:"temp_dir" form:"temp_dir"` } @@ -413,7 +460,7 @@ func AddOfflineDownload(c *gin.Context) { if trimmedUrl == "" { continue } - + t, err := tool.AddURL(c, &tool.AddURLArgs{ URL: trimmedUrl, DstDirPath: reqPath, diff --git a/server/router.go b/server/router.go index 66f0539b..0975fe69 100644 --- a/server/router.go +++ b/server/router.go @@ -160,6 +160,7 @@ func admin(g *gin.RouterGroup) { setting.POST("/set_transmission", handles.SetTransmission) setting.POST("/set_115", handles.Set115) setting.POST("/set_115_open", handles.Set115Open) + setting.POST("/set_123_open", handles.Set123Open) setting.POST("/set_pikpak", handles.SetPikPak) setting.POST("/set_thunder", handles.SetThunder) setting.POST("/set_thunderx", handles.SetThunderX)