From b8a77ee187ebfe41638b7984c459ef68d19c6102 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Mon, 1 Dec 2025 20:37:21 +0800 Subject: [PATCH 01/21] feat(189pan):support transfer --- drivers/189/driver.go | 48 +++++++++++++++++++++++++++++++++++++++++++ drivers/189/types.go | 8 ++++++++ drivers/189/util.go | 33 +++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/drivers/189/driver.go b/drivers/189/driver.go index 0489ef015..43e28b539 100644 --- a/drivers/189/driver.go +++ b/drivers/189/driver.go @@ -2,11 +2,13 @@ package _189 import ( "context" + "fmt" "net/http" "strings" "github.com/OpenListTeam/OpenList/v4/drivers/base" "github.com/OpenListTeam/OpenList/v4/internal/driver" + "github.com/OpenListTeam/OpenList/v4/internal/errs" "github.com/OpenListTeam/OpenList/v4/internal/model" "github.com/OpenListTeam/OpenList/v4/pkg/utils" "github.com/go-resty/resty/v2" @@ -204,4 +206,50 @@ func (d *Cloud189) GetDetails(ctx context.Context) (*model.StorageDetails, error }, nil } +func (d *Cloud189) Other(ctx context.Context, args model.OtherArgs) (any, error) { + switch args.Method { + case "transfer": + data := args.Data.(map[string]string) + shareUrl := data["url"] // the share url + code := "" + if data["valid_code"] != "" { + code = data["valid_code"] + } else if code = d.extractCode(shareUrl); code == "" { + return nil, fmt.Errorf("need valid code") + } + taskInfos := []base.Json{ + { + "fileId": args.Obj.GetID(), + "fileName": args.Obj.GetName(), + "isFolder": 1, + }, + } + + if !args.Obj.IsDir() { + return nil, fmt.Errorf("it should be in the folder") + } + taskInfosBytes, err := utils.Json.Marshal(taskInfos) + if err != nil { + return nil, err + } + shareid, err := d.getSharedID(code) + if err != nil { + return nil, err + } + + form := map[string]string{ + "type": "SHARE_SAVE", + "targetFolderId": args.Obj.GetID(), + "taskInfos": string(taskInfosBytes), + "shareId": shareid, + } + _, err = d.request("https://cloud.189.cn/api/open/batch/createBatchTask.action", http.MethodPost, func(req *resty.Request) { + req.SetFormData(form) + }, nil) + return nil, err + default: + return nil, errs.NotImplement + } +} + var _ driver.Driver = (*Cloud189)(nil) diff --git a/drivers/189/types.go b/drivers/189/types.go index 7a469dc9a..d7cb11301 100644 --- a/drivers/189/types.go +++ b/drivers/189/types.go @@ -84,3 +84,11 @@ type CapacityResp struct { } `json:"familyCapacityInfo"` TotalSize uint64 `json:"totalSize"` } + +type GetSharedInfoResp struct { + ResCode int `json:"res_code"` + ResMessage string `json:"res_message"` + AccessCode string `json:"accessCode"` + ShareID string `json:"shareId"` + ExpireTime int64 `json:"expireTime"` +} diff --git a/drivers/189/util.go b/drivers/189/util.go index 6e0682ea7..c2617ee40 100644 --- a/drivers/189/util.go +++ b/drivers/189/util.go @@ -11,6 +11,7 @@ import ( "io" "math" "net/http" + "regexp" "strconv" "strings" "time" @@ -406,3 +407,35 @@ func (d *Cloud189) getCapacityInfo(ctx context.Context) (*CapacityResp, error) { } return &resp, nil } + +func (d *Cloud189) getSharedID(code string) (string, error) { + resp := GetSharedInfoResp{} + var e Error + req := d.client.R().SetError(&e). + SetHeader("Accept", "application/json;charset=UTF-8"). + SetQueryParams(map[string]string{ + "noCache": random(), + "code": code, + }) + res, err := req.Execute(http.MethodGet, "https://cloud.189.cn/api/open/share/getShareInfoByCodeV2.action") + if err != nil { + return "", err + } + if jsoniter.Get(res.Body(), "res_code").ToInt() != 0 { + err = errors.New(jsoniter.Get(res.Body(), "res_message").ToString()) + return "", err + } + if resp.ResCode != 0 || resp.ResMessage != "成功" { + return "", errors.New(resp.ResMessage) + } + return resp.ShareID, nil +} + +func (d *Cloud189) extractCode(str string) string { + re := regexp.MustCompile(`code=([0-9]+(提取码=[A-Za-z0-9]+))`) + m := re.FindStringSubmatch(str) + if len(m) > 1 { + return m[1] + } + return "" +} From 35664fa250706686d89a4e5133d2e1b1c2c027a5 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Tue, 2 Dec 2025 08:22:11 +0800 Subject: [PATCH 02/21] feat(alipan):support transfer --- drivers/aliyundrive/driver.go | 41 +++++++++++++++++++++++++++++++ drivers/aliyundrive/types.go | 9 +++++++ drivers/aliyundrive/util.go | 46 +++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/drivers/aliyundrive/driver.go b/drivers/aliyundrive/driver.go index f9093097c..8760d5832 100644 --- a/drivers/aliyundrive/driver.go +++ b/drivers/aliyundrive/driver.go @@ -356,6 +356,47 @@ func (d *AliDrive) Other(ctx context.Context, args model.OtherArgs) (interface{} url = "https://api.alipan.com/v2/file/get_video_preview_play_info" data["category"] = "live_transcoding" data["url_expire_sec"] = 14400 + case "transfer": + url = args.Data.(map[string]string)["url"] // the share url + shareid := d.extractShareId(url) + if shareid == "" { + return nil, fmt.Errorf("invalid share url") + } + shareToken, err := d.getShareToken(shareid, args.Data.(map[string]string)["valid_code"]) + if err != nil { + return nil, err + } + fileid, parent_fileid := d.getFileId(shareid, shareToken) + res, err, _ := d.request("https://api.aliyundrive.com/adrive/v4/batch", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "requests": []base.Json{ + { + "headers": base.Json{ + "Content-Type": "application/json", + }, + "method": "POST", + "id": "0", + "body": base.Json{ + "share_id": shareid, + "file_id": fileid, + "to_drive_id": args.Obj.GetID(), + "to_parent_file_id": parent_fileid, + }, + "url": "/file/copy", + }, + }, + "resource": "file", + }) + req.SetHeader("X-Share-Token", shareToken) + }, nil) + if err != nil { + return nil, err + } + status := utils.Json.Get(res, "responses", 0, "status").ToInt() + if status < 300 && status >= 100 { + return res, nil + } + return nil, fmt.Errorf("transfer failed: %s", string(res)) default: return nil, errs.NotSupport } diff --git a/drivers/aliyundrive/types.go b/drivers/aliyundrive/types.go index 043d5e921..5fe9a5583 100644 --- a/drivers/aliyundrive/types.go +++ b/drivers/aliyundrive/types.go @@ -54,3 +54,12 @@ type UploadResp struct { RapidUpload bool `json:"rapid_upload"` } + +type SharedTokenResp struct { + ShareToken string `json:"share_token"` +} + +type ShareInfoResp struct { + FileId string `json:"file_id"` + ParentFileId string `json:"parent_file_id"` +} diff --git a/drivers/aliyundrive/util.go b/drivers/aliyundrive/util.go index 553411047..f5c616b8a 100644 --- a/drivers/aliyundrive/util.go +++ b/drivers/aliyundrive/util.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "regexp" "github.com/OpenListTeam/OpenList/v4/drivers/base" "github.com/OpenListTeam/OpenList/v4/internal/op" @@ -202,3 +203,48 @@ func (d *AliDrive) batch(srcId, dstId string, url string) error { } return errors.New(string(res)) } + +func (d *AliDrive) getShareToken(shareid, share_pwd string) (string, error) { + resp := SharedTokenResp{} + _, err, _ := d.request("https://api.aliyundrive.com/v2/share_link/get_share_token", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "share_id": shareid, + "share_pwd": share_pwd, + }) + }, resp) + if err != nil { + return "", err + } + return resp.ShareToken, nil +} + +func (d *AliDrive) getFileId(shareid, token string) (string, string) { + resp := ShareInfoResp{} + _, err, _ := d.request("https://api.aliyundrive.com/adrive/v2/file/list_by_share", http.MethodPost, func(req *resty.Request) { + req.SetHeader("x-share-token", token) + req.SetBody(base.Json{ + "share_id": shareid, + "order_by": "name", + "order_direction": "DESC", + "limit": 20, + "parent_file_id": "root", + "image_thumbnail_process": "image/resize,w_256/format,jpeg", + "image_url_process": "image/resize,w_1920/format,jpeg/interlace,1", + "video_thumbnail_process": "video/snapshot,t_1000,f_jpg,ar_auto,w_256", + }) + }, &resp) + if err != nil { + return "", "" + } + return resp.FileId, resp.ParentFileId +} + +func (d *AliDrive) extractShareId(shareUrl string) string { + // https://www.alipan.com/s/XVDFP7mpuZK + re := regexp.MustCompile(`https?://(?:[a-zA-Z0-9-]+\.)*alipan\.com(?:\:\d+)?/s/([a-zA-Z0-9_-]+)`) + matches := re.FindStringSubmatch(shareUrl) + if len(matches) >= 2 { + return matches[1] + } + return "" +} From f3a98aa46c1882619769c34b671ad32d8cb07b47 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Thu, 25 Dec 2025 18:00:17 +0800 Subject: [PATCH 03/21] feat(api):add transfer share file api --- internal/driver/driver.go | 5 +++++ internal/fs/other.go | 16 ++++++++++++++++ internal/fs/transfer.go | 17 +++++++++++++++++ internal/op/fs.go | 20 ++++++++++++++++++++ server/handles/fsmanage.go | 20 ++++++++++++++++++++ server/router.go | 1 + 6 files changed, 79 insertions(+) create mode 100644 internal/fs/transfer.go diff --git a/internal/driver/driver.go b/internal/driver/driver.go index 1f73d35b6..3acd2ba42 100644 --- a/internal/driver/driver.go +++ b/internal/driver/driver.go @@ -110,6 +110,11 @@ type PutURL interface { PutURL(ctx context.Context, dstDir model.Obj, name, url string) error } +type Transfer interface { + // save the sharing file to dstDir + Transfer(ctx context.Context, dst model.Obj, shareURL, validCode string) error +} + //type WriteResult interface { // MkdirResult // MoveResult diff --git a/internal/fs/other.go b/internal/fs/other.go index 8d16b0003..bc72ace94 100644 --- a/internal/fs/other.go +++ b/internal/fs/other.go @@ -2,7 +2,9 @@ package fs import ( "context" + "fmt" + "github.com/OpenListTeam/OpenList/v4/internal/conf" "github.com/OpenListTeam/OpenList/v4/internal/driver" "github.com/OpenListTeam/OpenList/v4/internal/model" "github.com/OpenListTeam/OpenList/v4/internal/op" @@ -34,6 +36,20 @@ func remove(ctx context.Context, path string) error { return op.Remove(ctx, storage, actualPath) } +func transfer_share(ctx context.Context, dstPath, shareURL, validCode string) error { + /* get user info then join path */ + user, ok := ctx.Value(conf.UserKey).(*model.User) + if !ok { + return fmt.Errorf("failed get user from context") + } + dstPath, err := user.JoinPath(dstPath) + if err != nil { + return fmt.Errorf("failed join path: %w", err) + } + storage, actualPath, err := op.GetStorageAndActualPath(dstPath) + return op.TransferShare(ctx, storage, actualPath, shareURL, validCode) +} + func other(ctx context.Context, args model.FsOtherArgs) (interface{}, error) { storage, actualPath, err := op.GetStorageAndActualPath(args.Path) if err != nil { diff --git a/internal/fs/transfer.go b/internal/fs/transfer.go new file mode 100644 index 000000000..0a8c03efb --- /dev/null +++ b/internal/fs/transfer.go @@ -0,0 +1,17 @@ +package fs + +import ( + "context" + + log "github.com/sirupsen/logrus" +) + +func Transfer(ctx context.Context, dstPath, shareURL, validCode string) error { + err := transfer_share(ctx, dstPath, shareURL, validCode) + if err != nil { + log.Errorf("failed transfer %s: %+v", dstPath, err) + return err + } + + return nil +} diff --git a/internal/op/fs.go b/internal/op/fs.go index e88287b88..6b7199827 100644 --- a/internal/op/fs.go +++ b/internal/op/fs.go @@ -654,6 +654,26 @@ func PutURL(ctx context.Context, storage driver.Driver, dstDirPath, dstName, url return errors.WithStack(err) } +func TransferShare(ctx context.Context, storage driver.Driver, dstPath, shareURL, validCode string) error { + if storage.Config().CheckStatus && storage.GetStorage().Status != WORK { + return errors.WithMessagef(errs.StorageNotInit, "storage status: %s", storage.GetStorage().Status) + } + rawObj, err := Get(ctx, storage, dstPath) + if err == nil { + return errors.WithStack(errs.ObjectAlreadyExists) + } + switch s := storage.(type) { + case driver.Transfer: + err = s.Transfer(ctx, model.UnwrapObj(rawObj), shareURL, validCode) + if err != nil { + return errors.WithMessagef(err, "failed to transfer share to [%s]", dstPath) + } + default: + return errors.WithStack(errs.NotImplement) + } + return nil +} + func GetDirectUploadTools(storage driver.Driver) []string { du, ok := storage.(driver.DirectUploader) if !ok { diff --git a/server/handles/fsmanage.go b/server/handles/fsmanage.go index 8247fa8cb..bcf102d97 100644 --- a/server/handles/fsmanage.go +++ b/server/handles/fsmanage.go @@ -424,3 +424,23 @@ func Link(c *gin.Context) { defer link.Close() common.SuccessResp(c, link) } + +type TransferReq struct { + SrcURL string `json:"url" binding:"required,url" form:"url"` + DstDir string `json:"dst_dir" binding:"required" form:"dst_dir"` + ValidCode string `json:"valid_code" form:"valid_code"` +} + +func FsTransfer(c *gin.Context) { + req := TransferReq{} + if err := c.ShouldBind(&req); err != nil { + common.ErrorResp(c, err, 400) + return + } + if err := fs.Transfer(c.Request.Context(), req.DstDir, req.SrcURL, req.ValidCode); err != nil { + common.ErrorResp(c, err, 500) + return + } else { + common.SuccessResp(c) + } +} diff --git a/server/router.go b/server/router.go index 32e933de3..83a8b5432 100644 --- a/server/router.go +++ b/server/router.go @@ -206,6 +206,7 @@ func _fs(g *gin.RouterGroup) { g.POST("/recursive_move", handles.FsRecursiveMove) g.POST("/copy", handles.FsCopy) g.POST("/remove", handles.FsRemove) + g.POST("/transfer", handles.FsTransfer) g.POST("/remove_empty_directory", handles.FsRemoveEmptyDirectory) uploadLimiter := middlewares.UploadRateLimiter(stream.ClientUploadLimit) g.PUT("/put", middlewares.FsUp, uploadLimiter, handles.FsStream) From 5af854caedf8e1250449f25d92184eb78fc2be79 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Thu, 25 Dec 2025 18:55:26 +0800 Subject: [PATCH 04/21] feat(189pan):support transfer interface --- drivers/189/driver.go | 74 +++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/drivers/189/driver.go b/drivers/189/driver.go index 43e28b539..3823bb2b1 100644 --- a/drivers/189/driver.go +++ b/drivers/189/driver.go @@ -8,7 +8,6 @@ import ( "github.com/OpenListTeam/OpenList/v4/drivers/base" "github.com/OpenListTeam/OpenList/v4/internal/driver" - "github.com/OpenListTeam/OpenList/v4/internal/errs" "github.com/OpenListTeam/OpenList/v4/internal/model" "github.com/OpenListTeam/OpenList/v4/pkg/utils" "github.com/go-resty/resty/v2" @@ -206,50 +205,43 @@ func (d *Cloud189) GetDetails(ctx context.Context) (*model.StorageDetails, error }, nil } -func (d *Cloud189) Other(ctx context.Context, args model.OtherArgs) (any, error) { - switch args.Method { - case "transfer": - data := args.Data.(map[string]string) - shareUrl := data["url"] // the share url - code := "" - if data["valid_code"] != "" { - code = data["valid_code"] - } else if code = d.extractCode(shareUrl); code == "" { - return nil, fmt.Errorf("need valid code") - } - taskInfos := []base.Json{ - { - "fileId": args.Obj.GetID(), - "fileName": args.Obj.GetName(), - "isFolder": 1, - }, +func (d *Cloud189) Transfer(ctx context.Context, dst model.Obj, shareURL, validCode string) error { + if validCode == "" { + validCode = d.extractCode(shareURL) + if validCode == "" { + return fmt.Errorf("need valid code") } + } + taskInfos := []base.Json{ + { + "fileId": dst.GetID(), + "fileName": dst.GetName(), + "isFolder": 1, + }, + } - if !args.Obj.IsDir() { - return nil, fmt.Errorf("it should be in the folder") - } - taskInfosBytes, err := utils.Json.Marshal(taskInfos) - if err != nil { - return nil, err - } - shareid, err := d.getSharedID(code) - if err != nil { - return nil, err - } + if !dst.IsDir() { + return fmt.Errorf("it should be in the folder") + } + taskInfosBytes, err := utils.Json.Marshal(taskInfos) + if err != nil { + return err + } + shareid, err := d.getSharedID(validCode) + if err != nil { + return err + } - form := map[string]string{ - "type": "SHARE_SAVE", - "targetFolderId": args.Obj.GetID(), - "taskInfos": string(taskInfosBytes), - "shareId": shareid, - } - _, err = d.request("https://cloud.189.cn/api/open/batch/createBatchTask.action", http.MethodPost, func(req *resty.Request) { - req.SetFormData(form) - }, nil) - return nil, err - default: - return nil, errs.NotImplement + form := map[string]string{ + "type": "SHARE_SAVE", + "targetFolderId": dst.GetID(), + "taskInfos": string(taskInfosBytes), + "shareId": shareid, } + _, err = d.request("https://cloud.189.cn/api/open/batch/createBatchTask.action", http.MethodPost, func(req *resty.Request) { + req.SetFormData(form) + }, nil) + return err } var _ driver.Driver = (*Cloud189)(nil) From 430e380dcdaec6647778e5016915553b7ea13263 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Thu, 25 Dec 2025 19:04:27 +0800 Subject: [PATCH 05/21] feat(alipan):support transfer interface --- drivers/aliyundrive/driver.go | 83 ++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/drivers/aliyundrive/driver.go b/drivers/aliyundrive/driver.go index 8760d5832..3e32b17e6 100644 --- a/drivers/aliyundrive/driver.go +++ b/drivers/aliyundrive/driver.go @@ -356,47 +356,6 @@ func (d *AliDrive) Other(ctx context.Context, args model.OtherArgs) (interface{} url = "https://api.alipan.com/v2/file/get_video_preview_play_info" data["category"] = "live_transcoding" data["url_expire_sec"] = 14400 - case "transfer": - url = args.Data.(map[string]string)["url"] // the share url - shareid := d.extractShareId(url) - if shareid == "" { - return nil, fmt.Errorf("invalid share url") - } - shareToken, err := d.getShareToken(shareid, args.Data.(map[string]string)["valid_code"]) - if err != nil { - return nil, err - } - fileid, parent_fileid := d.getFileId(shareid, shareToken) - res, err, _ := d.request("https://api.aliyundrive.com/adrive/v4/batch", http.MethodPost, func(req *resty.Request) { - req.SetBody(base.Json{ - "requests": []base.Json{ - { - "headers": base.Json{ - "Content-Type": "application/json", - }, - "method": "POST", - "id": "0", - "body": base.Json{ - "share_id": shareid, - "file_id": fileid, - "to_drive_id": args.Obj.GetID(), - "to_parent_file_id": parent_fileid, - }, - "url": "/file/copy", - }, - }, - "resource": "file", - }) - req.SetHeader("X-Share-Token", shareToken) - }, nil) - if err != nil { - return nil, err - } - status := utils.Json.Get(res, "responses", 0, "status").ToInt() - if status < 300 && status >= 100 { - return res, nil - } - return nil, fmt.Errorf("transfer failed: %s", string(res)) default: return nil, errs.NotSupport } @@ -409,4 +368,46 @@ func (d *AliDrive) Other(ctx context.Context, args model.OtherArgs) (interface{} return resp, nil } +func (d *AliDrive) Transfer(ctx context.Context, dst model.Obj, shareURL, validCode string) error { + shareid := d.extractShareId(shareURL) + if shareid == "" { + return fmt.Errorf("invalid share url") + } + shareToken, err := d.getShareToken(shareid, validCode) + if err != nil { + return err + } + fileid, parent_fileid := d.getFileId(shareid, shareToken) + res, err, _ := d.request("https://api.aliyundrive.com/adrive/v4/batch", http.MethodPost, func(req *resty.Request) { + req.SetBody(base.Json{ + "requests": []base.Json{ + { + "headers": base.Json{ + "Content-Type": "application/json", + }, + "method": "POST", + "id": "0", + "body": base.Json{ + "share_id": shareid, + "file_id": fileid, + "to_drive_id": dst.GetID(), + "to_parent_file_id": parent_fileid, + }, + "url": "/file/copy", + }, + }, + "resource": "file", + }) + req.SetHeader("X-Share-Token", shareToken) + }, nil) + if err != nil { + return err + } + status := utils.Json.Get(res, "responses", 0, "status").ToInt() + if status < 300 && status >= 100 { + return nil + } + return fmt.Errorf("transfer failed: %s", string(res)) +} + var _ driver.Driver = (*AliDrive)(nil) From c098c0f4e26c7f364598c71e1017653611fb264a Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Fri, 26 Dec 2025 12:18:10 +0800 Subject: [PATCH 06/21] fix(op/transfer):fix the wrong error handler --- internal/op/fs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/op/fs.go b/internal/op/fs.go index 6b7199827..1d6c4f409 100644 --- a/internal/op/fs.go +++ b/internal/op/fs.go @@ -659,7 +659,7 @@ func TransferShare(ctx context.Context, storage driver.Driver, dstPath, shareURL return errors.WithMessagef(errs.StorageNotInit, "storage status: %s", storage.GetStorage().Status) } rawObj, err := Get(ctx, storage, dstPath) - if err == nil { + if err != nil { return errors.WithStack(errs.ObjectAlreadyExists) } switch s := storage.(type) { From f128e819e97832b213bc3e30790ca2ec4aa484d0 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Fri, 26 Dec 2025 13:11:55 +0800 Subject: [PATCH 07/21] fix(189pan):extract code error --- drivers/189/driver.go | 8 +++----- drivers/189/util.go | 28 +++++++++++----------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/drivers/189/driver.go b/drivers/189/driver.go index 3823bb2b1..3fbdbbab1 100644 --- a/drivers/189/driver.go +++ b/drivers/189/driver.go @@ -206,11 +206,9 @@ func (d *Cloud189) GetDetails(ctx context.Context) (*model.StorageDetails, error } func (d *Cloud189) Transfer(ctx context.Context, dst model.Obj, shareURL, validCode string) error { - if validCode == "" { - validCode = d.extractCode(shareURL) - if validCode == "" { - return fmt.Errorf("need valid code") - } + sharecode := d.extractCode(shareURL) + if sharecode == "" { + return fmt.Errorf("need share code") } taskInfos := []base.Json{ { diff --git a/drivers/189/util.go b/drivers/189/util.go index c2617ee40..911ade5b3 100644 --- a/drivers/189/util.go +++ b/drivers/189/util.go @@ -11,7 +11,7 @@ import ( "io" "math" "net/http" - "regexp" + "net/url" "strconv" "strings" "time" @@ -410,21 +410,15 @@ func (d *Cloud189) getCapacityInfo(ctx context.Context) (*CapacityResp, error) { func (d *Cloud189) getSharedID(code string) (string, error) { resp := GetSharedInfoResp{} - var e Error - req := d.client.R().SetError(&e). - SetHeader("Accept", "application/json;charset=UTF-8"). - SetQueryParams(map[string]string{ - "noCache": random(), - "code": code, + _, err := d.request("https://cloud.189.cn/api/open/share/getShareInfoByCodeV2.action", http.MethodGet, func(req *resty.Request) { + req.SetHeader("Accept", "application/json;charset=UTF-8") + req.SetQueryParams(map[string]string{ + "code": code, }) - res, err := req.Execute(http.MethodGet, "https://cloud.189.cn/api/open/share/getShareInfoByCodeV2.action") + }, &resp) if err != nil { return "", err } - if jsoniter.Get(res.Body(), "res_code").ToInt() != 0 { - err = errors.New(jsoniter.Get(res.Body(), "res_message").ToString()) - return "", err - } if resp.ResCode != 0 || resp.ResMessage != "成功" { return "", errors.New(resp.ResMessage) } @@ -432,10 +426,10 @@ func (d *Cloud189) getSharedID(code string) (string, error) { } func (d *Cloud189) extractCode(str string) string { - re := regexp.MustCompile(`code=([0-9]+(提取码=[A-Za-z0-9]+))`) - m := re.FindStringSubmatch(str) - if len(m) > 1 { - return m[1] + u, err := url.Parse(str) + if err != nil { + fmt.Println("invalid share url") + return "" } - return "" + return u.Query().Get("code") } From 8415ed1b7ac63e03d2b72a1bf34d9bb26c7cf4ac Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Fri, 26 Dec 2025 16:14:13 +0800 Subject: [PATCH 08/21] feat(189panPC):support transfer interface --- drivers/189pc/driver.go | 40 +++++++++++++++++++++++++++++++++ drivers/189pc/help.go | 4 ++++ drivers/189pc/types.go | 8 +++++++ drivers/189pc/utils.go | 49 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) diff --git a/drivers/189pc/driver.go b/drivers/189pc/driver.go index 5b04d58f6..6bafac96f 100644 --- a/drivers/189pc/driver.go +++ b/drivers/189pc/driver.go @@ -428,3 +428,43 @@ func (y *Cloud189PC) GetDetails(ctx context.Context) (*model.StorageDetails, err DiskUsage: driver.DiskUsageFromUsedAndTotal(used, total), }, nil } + +func (y *Cloud189PC) Transfer(ctx context.Context, dst model.Obj, shareURL, validCode string) error { + sharecode := y.extractCode(shareURL) + if sharecode == "" { + return fmt.Errorf("need share code") + } + taskInfos := []base.Json{ + { + "fileId": dst.GetID(), + "fileName": dst.GetName(), + "isFolder": 1, + }, + } + + if !dst.IsDir() { + return fmt.Errorf("it should be in the folder") + } + taskInfosBytes, err := utils.Json.Marshal(taskInfos) + if err != nil { + return err + } + shareid, err := y.getSharedID(sharecode) + if err != nil { + return err + } + if shareid == -1 { + return fmt.Errorf("failed get share id") + } + + form := map[string]string{ + "type": "SHARE_SAVE", + "targetFolderId": dst.GetID(), + "taskInfos": string(taskInfosBytes), + "shareId": fmt.Sprint(shareid), + } + _, err = y.request("https://cloud.189.cn/api/open/batch/createBatchTask.action", http.MethodPost, func(req *resty.Request) { + req.SetFormData(form) + }, nil, nil, false) + return err +} diff --git a/drivers/189pc/help.go b/drivers/189pc/help.go index 6f6c59f30..d03385f0b 100644 --- a/drivers/189pc/help.go +++ b/drivers/189pc/help.go @@ -22,6 +22,10 @@ import ( "github.com/OpenListTeam/OpenList/v4/pkg/utils/random" ) +func random_num() string { + return fmt.Sprintf("0.%017d", random.Rand.Int63n(1e17)) +} + func clientSuffix() map[string]string { rand := random.Rand return map[string]string{ diff --git a/drivers/189pc/types.go b/drivers/189pc/types.go index c0ecacb24..2344d3c69 100644 --- a/drivers/189pc/types.go +++ b/drivers/189pc/types.go @@ -427,3 +427,11 @@ type CapacityResp struct { } `json:"familyCapacityInfo"` TotalSize uint64 `json:"totalSize"` } + +type GetSharedInfoResp struct { + ResCode int `json:"res_code"` + ResMessage string `json:"res_message"` + AccessCode string `json:"accessCode"` + ShareID int64 `json:"shareId"` + ExpireTime int64 `json:"expireTime"` +} diff --git a/drivers/189pc/utils.go b/drivers/189pc/utils.go index 08ee658ca..7316ea3c3 100644 --- a/drivers/189pc/utils.go +++ b/drivers/189pc/utils.go @@ -1483,3 +1483,52 @@ func (y *Cloud189PC) getCapacityInfo(ctx context.Context) (*CapacityResp, error) } return &resp, nil } + +func (y *Cloud189PC) getSharedID(code string) (int64, error) { + resp := GetSharedInfoResp{} + sessionKey := y.getTokenInfo().SessionKey + if sessionKey == "" { + if err := y.refreshSession(); err != nil { + return -1, err + } + } + req := y.getClient().R(). + SetHeader("Accept", "application/json;charset=UTF-8"). + SetHeader("Referer", "https://cloud.189.cn/web/share?code=2QFRjyiMfiue"). + SetHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"). + SetHeader("SessionKey", y.getTokenInfo().SessionKey). + SetQueryParams(map[string]string{ + "noCache": random_num(), + "shareCode": code, + }) + req.SetResult(&resp) + res, err := req.Execute(http.MethodGet, "https://cloud.189.cn/api/open/share/getShareInfoByCodeV2.action") + + if strings.Contains(res.String(), "userSessionBO is null") { + if err = y.refreshSession(); err != nil { + return -1, err + } + return y.getSharedID(code) + } + + if strings.Contains(res.String(), "InvalidSessionKey") { + if err = y.refreshSession(); err != nil { + return -1, err + } + return y.getSharedID(code) + } + + if resp.ResCode != 0 || resp.ResMessage != "成功" { + return -1, errors.New(resp.ResMessage) + } + return resp.ShareID, nil +} + +func (y *Cloud189PC) extractCode(str string) string { + u, err := url.Parse(str) + if err != nil { + fmt.Println("invalid share url") + return "" + } + return u.Query().Get("code") +} From ac8b6541b5c33db551b0293c69f509684aef142e Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Fri, 26 Dec 2025 16:24:12 +0800 Subject: [PATCH 09/21] fix(189pan):type error --- drivers/189/driver.go | 2 +- drivers/189/help.go | 2 +- drivers/189/types.go | 2 +- drivers/189/util.go | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/189/driver.go b/drivers/189/driver.go index 3fbdbbab1..fd03fe92e 100644 --- a/drivers/189/driver.go +++ b/drivers/189/driver.go @@ -234,7 +234,7 @@ func (d *Cloud189) Transfer(ctx context.Context, dst model.Obj, shareURL, validC "type": "SHARE_SAVE", "targetFolderId": dst.GetID(), "taskInfos": string(taskInfosBytes), - "shareId": shareid, + "shareId": fmt.Sprintf("%d", shareid), } _, err = d.request("https://cloud.189.cn/api/open/batch/createBatchTask.action", http.MethodPost, func(req *resty.Request) { req.SetFormData(form) diff --git a/drivers/189/help.go b/drivers/189/help.go index 50748a35c..682b6a42e 100644 --- a/drivers/189/help.go +++ b/drivers/189/help.go @@ -23,7 +23,7 @@ import ( ) func random() string { - return fmt.Sprintf("0.%17v", myrand.Rand.Int63n(100000000000000000)) + return fmt.Sprintf("0.%017d", myrand.Rand.Int63n(1e17)) } func RsaEncode(origData []byte, j_rsakey string, hex bool) string { diff --git a/drivers/189/types.go b/drivers/189/types.go index d7cb11301..350dea4d2 100644 --- a/drivers/189/types.go +++ b/drivers/189/types.go @@ -89,6 +89,6 @@ type GetSharedInfoResp struct { ResCode int `json:"res_code"` ResMessage string `json:"res_message"` AccessCode string `json:"accessCode"` - ShareID string `json:"shareId"` + ShareID int64 `json:"shareId"` ExpireTime int64 `json:"expireTime"` } diff --git a/drivers/189/util.go b/drivers/189/util.go index 911ade5b3..9d8eb8de2 100644 --- a/drivers/189/util.go +++ b/drivers/189/util.go @@ -408,7 +408,7 @@ func (d *Cloud189) getCapacityInfo(ctx context.Context) (*CapacityResp, error) { return &resp, nil } -func (d *Cloud189) getSharedID(code string) (string, error) { +func (d *Cloud189) getSharedID(code string) (int64, error) { resp := GetSharedInfoResp{} _, err := d.request("https://cloud.189.cn/api/open/share/getShareInfoByCodeV2.action", http.MethodGet, func(req *resty.Request) { req.SetHeader("Accept", "application/json;charset=UTF-8") @@ -417,10 +417,10 @@ func (d *Cloud189) getSharedID(code string) (string, error) { }) }, &resp) if err != nil { - return "", err + return 0, err } if resp.ResCode != 0 || resp.ResMessage != "成功" { - return "", errors.New(resp.ResMessage) + return 0, errors.New(resp.ResMessage) } return resp.ShareID, nil } From 95c03ad8c17050106d8ce0464fd28de783db0526 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Fri, 26 Dec 2025 19:02:42 +0800 Subject: [PATCH 10/21] fix(189panPC):use exist function --- drivers/189pc/driver.go | 21 +++++++++++++++------ drivers/189pc/types.go | 4 +++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/drivers/189pc/driver.go b/drivers/189pc/driver.go index 6bafac96f..fb0474145 100644 --- a/drivers/189pc/driver.go +++ b/drivers/189pc/driver.go @@ -458,13 +458,22 @@ func (y *Cloud189PC) Transfer(ctx context.Context, dst model.Obj, shareURL, vali } form := map[string]string{ - "type": "SHARE_SAVE", "targetFolderId": dst.GetID(), "taskInfos": string(taskInfosBytes), - "shareId": fmt.Sprint(shareid), + "shareId": fmt.Sprintf("%d", shareid), + } + + resp, err := y.CreateBatchTask("SHARE_SAVE", y.FamilyID, dst.GetID(), form) + + for { + state, err := y.CheckBatchTask("SHARE_SAVE", resp.TaskID) + if err != nil { + return err + } + switch state.TaskStatus { + case 4: + return nil + } + time.Sleep(time.Millisecond * 400) } - _, err = y.request("https://cloud.189.cn/api/open/batch/createBatchTask.action", http.MethodPost, func(req *resty.Request) { - req.SetFormData(form) - }, nil, nil, false) - return err } diff --git a/drivers/189pc/types.go b/drivers/189pc/types.go index 2344d3c69..b3cdd80f8 100644 --- a/drivers/189pc/types.go +++ b/drivers/189pc/types.go @@ -360,7 +360,9 @@ func (f *OldCommitUploadFileResp) toFile() *Cloud189File { } type CreateBatchTaskResp struct { - TaskID string `json:"taskId"` + ResCode int `json:"res_code"` + TaskID string `json:"taskId"` + ResMessage string `json:"res_message"` } type BatchTaskStateResp struct { From 309e79b0eab624694ed7554d6e6486e346d5dabd Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Sun, 28 Dec 2025 09:24:22 +0800 Subject: [PATCH 11/21] feat(quark_uc):support transfer interface --- drivers/quark_uc/driver.go | 13 +++++++ drivers/quark_uc/types.go | 30 +++++++++++++++ drivers/quark_uc/util.go | 75 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) diff --git a/drivers/quark_uc/driver.go b/drivers/quark_uc/driver.go index 71f3ffca3..209ec70f3 100644 --- a/drivers/quark_uc/driver.go +++ b/drivers/quark_uc/driver.go @@ -3,6 +3,7 @@ package quark import ( "context" "encoding/hex" + "errors" "hash" "io" "net/http" @@ -231,4 +232,16 @@ func (d *QuarkOrUC) GetDetails(ctx context.Context) (*model.StorageDetails, erro }, nil } +func (d *QuarkOrUC) Transfer(ctx context.Context, dst model.Obj, shareURL, validCode string) error { + pwd_id := d.getPwdID(shareURL) + if pwd_id == "" { + return errors.New("invalid share url") + } + shareToken, err := d.getShareToken(pwd_id, validCode) + if err != nil { + return err + } + return d.transfer(dst, shareToken) +} + var _ driver.Driver = (*QuarkOrUC)(nil) diff --git a/drivers/quark_uc/types.go b/drivers/quark_uc/types.go index cea2b8830..7749f9d08 100644 --- a/drivers/quark_uc/types.go +++ b/drivers/quark_uc/types.go @@ -282,3 +282,33 @@ type MemberResp struct { ServerCurTime uint64 `json:"server_cur_time"` } `json:"metadata"` } + +type ShareTokenResp struct { + Resp + Data struct { + ShareToken string `json:"shareToken"` + } +} + +type ShareInfoResp struct { + Resp + Data struct { + Share struct { + ShareID string `json:"share_id"` + } `json:"share"` + } `json:"data"` +} + +type SaveShareResp struct { + Resp + Data struct { + TaskID string `json:"task_id"` + } +} + +type TaskCheckResp struct { + Resp + Data struct { + Status int `json:"status"` + } +} diff --git a/drivers/quark_uc/util.go b/drivers/quark_uc/util.go index 3c49f1f3f..eb9037e57 100644 --- a/drivers/quark_uc/util.go +++ b/drivers/quark_uc/util.go @@ -17,6 +17,7 @@ import ( "github.com/OpenListTeam/OpenList/v4/internal/op" "github.com/OpenListTeam/OpenList/v4/pkg/cookie" "github.com/OpenListTeam/OpenList/v4/pkg/utils" + "github.com/OpenListTeam/OpenList/v4/pkg/utils/random" "github.com/go-resty/resty/v2" log "github.com/sirupsen/logrus" ) @@ -356,3 +357,77 @@ func (d *QuarkOrUC) memberInfo(ctx context.Context) (*MemberResp, error) { } return &resp, nil } + +func (d *QuarkOrUC) getPwdID(shareURL string) string { + // 从分享链接中提取分享码,如https://pan.quark.cn/s/75079b0bc0ee#/list/share + parts := strings.Split(shareURL, "/s/") + if len(parts) < 2 { + return "" + } + codeParts := strings.SplitN(parts[1], "/", 2) + if len(codeParts) == 0 { + return "" + } + return codeParts[0] +} + +func (d *QuarkOrUC) getShareToken(pwd_id, passcode string) (string, error) { + // + resp := ShareTokenResp{} + _, err := d.request("/share/sharepage/token", http.MethodPost, func(req *resty.Request) { + req.SetQueryParam("__t", strconv.FormatInt(time.Now().UnixMilli(), 10)) + req.SetQueryParam("__dt", strconv.FormatInt(random.RangeInt64(100, 500), 10)) + req.SetBody(base.Json{ + "pwd_id": pwd_id, + "passcode": passcode, + "share_from": "", + }) + }, &resp) + if err != nil { + return "", err + } + if resp.Status == 200 && resp.Code == 0 { + return resp.Data.ShareToken, nil + } + return "", errors.New("unknown error") +} + +func (d *QuarkOrUC) transfer(dstDir model.Obj, shareToken string) error { + resp := SaveShareResp{} + data := base.Json{ + "stoken": shareToken, + "pdir_fid": dstDir.GetID(), + "to_pdir_fid": dstDir.GetID(), + "pdir_save_all": true, + "scene": "line", + } + _, err := d.request("/share/sharepage/save", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, &resp) + if err != nil { + return err + } + if resp.Status == 200 && resp.Code == 0 && resp.Data.TaskID != "" { + retry_index := 0 + for { + check_resp := TaskCheckResp{} + _, err := d.request("/share/sharepage/task/check", http.MethodGet, func(req *resty.Request) { + req.SetQueryParam("task_id", resp.Data.TaskID) + }, &check_resp) + if err != nil { + return err + } + if check_resp.Status == 200 && check_resp.Code == 0 { + if check_resp.Data.Status == 2 { + return nil + } + } + retry_index++ + time.Sleep(time.Second * time.Duration(retry_index)) + if retry_index >= 10 { + return errors.New("transfer timeout") + } + } + } + return err +} From be7de1f9d628bf8c7442c8aca9de0aefd2d636f2 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Sun, 28 Dec 2025 09:27:53 +0800 Subject: [PATCH 12/21] refactor:improve code robustness --- drivers/189/driver.go | 27 ++------------------------- drivers/189/util.go | 25 +++++++++++++++++++++++++ internal/fs/transfer.go | 4 ++++ 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/drivers/189/driver.go b/drivers/189/driver.go index fd03fe92e..80926e183 100644 --- a/drivers/189/driver.go +++ b/drivers/189/driver.go @@ -210,36 +210,13 @@ func (d *Cloud189) Transfer(ctx context.Context, dst model.Obj, shareURL, validC if sharecode == "" { return fmt.Errorf("need share code") } - taskInfos := []base.Json{ - { - "fileId": dst.GetID(), - "fileName": dst.GetName(), - "isFolder": 1, - }, - } - if !dst.IsDir() { - return fmt.Errorf("it should be in the folder") - } - taskInfosBytes, err := utils.Json.Marshal(taskInfos) - if err != nil { - return err - } - shareid, err := d.getSharedID(validCode) + shareid, err := d.getSharedID(sharecode) if err != nil { return err } - form := map[string]string{ - "type": "SHARE_SAVE", - "targetFolderId": dst.GetID(), - "taskInfos": string(taskInfosBytes), - "shareId": fmt.Sprintf("%d", shareid), - } - _, err = d.request("https://cloud.189.cn/api/open/batch/createBatchTask.action", http.MethodPost, func(req *resty.Request) { - req.SetFormData(form) - }, nil) - return err + return d.transfer(dst, shareid) } var _ driver.Driver = (*Cloud189)(nil) diff --git a/drivers/189/util.go b/drivers/189/util.go index 9d8eb8de2..ce6206b47 100644 --- a/drivers/189/util.go +++ b/drivers/189/util.go @@ -433,3 +433,28 @@ func (d *Cloud189) extractCode(str string) string { } return u.Query().Get("code") } + +func (d *Cloud189) transfer(dstDir model.Obj, shareID int64) error { + taskInfos := []base.Json{ + { + "fileId": dstDir.GetID(), + "fileName": dstDir.GetName(), + "isFolder": 1, + }, + } + + if !dstDir.IsDir() { + return fmt.Errorf("it should be in the folder") + } + taskInfosBytes, err := utils.Json.Marshal(taskInfos) + form := map[string]string{ + "type": "SHARE_SAVE", + "targetFolderId": dstDir.GetID(), + "taskInfos": string(taskInfosBytes), + "shareId": fmt.Sprintf("%d", shareID), + } + _, err = d.request("https://cloud.189.cn/api/open/batch/createBatchTask.action", http.MethodPost, func(req *resty.Request) { + req.SetFormData(form) + }, nil) + return err +} diff --git a/internal/fs/transfer.go b/internal/fs/transfer.go index 0a8c03efb..edfefbebe 100644 --- a/internal/fs/transfer.go +++ b/internal/fs/transfer.go @@ -7,6 +7,10 @@ import ( ) func Transfer(ctx context.Context, dstPath, shareURL, validCode string) error { + if shareURL == "" { + return nil + } + err := transfer_share(ctx, dstPath, shareURL, validCode) if err != nil { log.Errorf("failed transfer %s: %+v", dstPath, err) From 4aa7895fe219a5eaf96b2f69d543997b6a1e40dc Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Tue, 30 Dec 2025 20:43:47 +0800 Subject: [PATCH 13/21] chore(internal/op):rename unwrapobj function --- go.sum | 2 -- internal/op/fs.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.sum b/go.sum index 029e5d514..97ac10348 100644 --- a/go.sum +++ b/go.sum @@ -398,8 +398,6 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/halalcloud/golang-sdk-lite v0.0.0-20251006164234-3c629727c499 h1:4ovnBdiGDFi8putQGxhipuuhXItAgh4/YnzufPYkZkQ= -github.com/halalcloud/golang-sdk-lite v0.0.0-20251006164234-3c629727c499/go.mod h1:8x1h4rm3s8xMcTyJrq848sQ6BJnKzl57mDY4CNshdPM= github.com/halalcloud/golang-sdk-lite v0.0.0-20251105081800-78cbb6786c38 h1:lsK2GVgI2Ox0NkRpQnN09GBOH7jtsjFK5tcIgxXlLr0= github.com/halalcloud/golang-sdk-lite v0.0.0-20251105081800-78cbb6786c38/go.mod h1:8x1h4rm3s8xMcTyJrq848sQ6BJnKzl57mDY4CNshdPM= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/internal/op/fs.go b/internal/op/fs.go index 778b30e75..e5226761b 100644 --- a/internal/op/fs.go +++ b/internal/op/fs.go @@ -775,7 +775,7 @@ func TransferShare(ctx context.Context, storage driver.Driver, dstPath, shareURL } switch s := storage.(type) { case driver.Transfer: - err = s.Transfer(ctx, model.UnwrapObj(rawObj), shareURL, validCode) + err = s.Transfer(ctx, model.UnwrapObjName(rawObj), shareURL, validCode) if err != nil { return errors.WithMessagef(err, "failed to transfer share to [%s]", dstPath) } From 3fc42cb2c27fcefac28f5bccefadfd70f587ff59 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Tue, 30 Dec 2025 22:58:16 +0800 Subject: [PATCH 14/21] fix(pan189PC):transfer para error --- drivers/189pc/driver.go | 25 +++++++++++++++---------- drivers/189pc/types.go | 3 +++ drivers/189pc/utils.go | 16 ++++++++-------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/drivers/189pc/driver.go b/drivers/189pc/driver.go index fb0474145..472a6d7ea 100644 --- a/drivers/189pc/driver.go +++ b/drivers/189pc/driver.go @@ -434,10 +434,18 @@ func (y *Cloud189PC) Transfer(ctx context.Context, dst model.Obj, shareURL, vali if sharecode == "" { return fmt.Errorf("need share code") } + shareid, fileid, filename, err := y.getSharedInfo(sharecode) + if err != nil { + return err + } + if shareid == -1 { + return fmt.Errorf("failed get share id") + } + taskInfos := []base.Json{ { - "fileId": dst.GetID(), - "fileName": dst.GetName(), + "fileId": fileid, + "fileName": filename, "isFolder": 1, }, } @@ -449,13 +457,6 @@ func (y *Cloud189PC) Transfer(ctx context.Context, dst model.Obj, shareURL, vali if err != nil { return err } - shareid, err := y.getSharedID(sharecode) - if err != nil { - return err - } - if shareid == -1 { - return fmt.Errorf("failed get share id") - } form := map[string]string{ "targetFolderId": dst.GetID(), @@ -472,7 +473,11 @@ func (y *Cloud189PC) Transfer(ctx context.Context, dst model.Obj, shareURL, vali } switch state.TaskStatus { case 4: - return nil + if state.FailedCount == 0 { + return nil + } else { + return fmt.Errorf("transfer failed") + } } time.Sleep(time.Millisecond * 400) } diff --git a/drivers/189pc/types.go b/drivers/189pc/types.go index b3cdd80f8..90219836c 100644 --- a/drivers/189pc/types.go +++ b/drivers/189pc/types.go @@ -373,6 +373,7 @@ type BatchTaskStateResp struct { SuccessedCount int `json:"successedCount"` SuccessedFileIDList []int64 `json:"successedFileIdList"` TaskID string `json:"taskId"` + ResMessage string `json:"res_message"` TaskStatus int `json:"taskStatus"` //1 初始化 2 存在冲突 3 执行中,4 完成 } @@ -436,4 +437,6 @@ type GetSharedInfoResp struct { AccessCode string `json:"accessCode"` ShareID int64 `json:"shareId"` ExpireTime int64 `json:"expireTime"` + FileName string `json:"fileName"` + FileId string `json:"fileId"` } diff --git a/drivers/189pc/utils.go b/drivers/189pc/utils.go index 7316ea3c3..9ba0ddb67 100644 --- a/drivers/189pc/utils.go +++ b/drivers/189pc/utils.go @@ -1484,12 +1484,12 @@ func (y *Cloud189PC) getCapacityInfo(ctx context.Context) (*CapacityResp, error) return &resp, nil } -func (y *Cloud189PC) getSharedID(code string) (int64, error) { +func (y *Cloud189PC) getSharedInfo(code string) (int64, string, string, error) { resp := GetSharedInfoResp{} sessionKey := y.getTokenInfo().SessionKey if sessionKey == "" { if err := y.refreshSession(); err != nil { - return -1, err + return -1, "", "", err } } req := y.getClient().R(). @@ -1506,22 +1506,22 @@ func (y *Cloud189PC) getSharedID(code string) (int64, error) { if strings.Contains(res.String(), "userSessionBO is null") { if err = y.refreshSession(); err != nil { - return -1, err + return -1, "", "", err } - return y.getSharedID(code) + return y.getSharedInfo(code) } if strings.Contains(res.String(), "InvalidSessionKey") { if err = y.refreshSession(); err != nil { - return -1, err + return -1, "", "", err } - return y.getSharedID(code) + return y.getSharedInfo(code) } if resp.ResCode != 0 || resp.ResMessage != "成功" { - return -1, errors.New(resp.ResMessage) + return -1, "", "", errors.New(resp.ResMessage) } - return resp.ShareID, nil + return resp.ShareID, resp.FileId, resp.FileName, nil } func (y *Cloud189PC) extractCode(str string) string { From 94f1ba1912f5d6f87dc6fade49df466d8d197ad4 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Tue, 30 Dec 2025 23:08:54 +0800 Subject: [PATCH 15/21] chore(internal/fs):add error check --- internal/fs/transfer.go | 3 --- server/handles/fsmanage.go | 9 +++++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/fs/transfer.go b/internal/fs/transfer.go index edfefbebe..8d94699f3 100644 --- a/internal/fs/transfer.go +++ b/internal/fs/transfer.go @@ -7,9 +7,6 @@ import ( ) func Transfer(ctx context.Context, dstPath, shareURL, validCode string) error { - if shareURL == "" { - return nil - } err := transfer_share(ctx, dstPath, shareURL, validCode) if err != nil { diff --git a/server/handles/fsmanage.go b/server/handles/fsmanage.go index bcf102d97..aa6b86e8d 100644 --- a/server/handles/fsmanage.go +++ b/server/handles/fsmanage.go @@ -437,10 +437,15 @@ func FsTransfer(c *gin.Context) { common.ErrorResp(c, err, 400) return } + + if req.DstDir == "" || req.SrcURL == "" { + common.ErrorStrResp(c, "dst_dir and url are required", 400) + return + } + if err := fs.Transfer(c.Request.Context(), req.DstDir, req.SrcURL, req.ValidCode); err != nil { common.ErrorResp(c, err, 500) return - } else { - common.SuccessResp(c) } + common.SuccessResp(c) } From 41f8e7f2991898a526b28803fbb6469d21a57978 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Thu, 1 Jan 2026 05:13:22 +0800 Subject: [PATCH 16/21] chore(code review):add more check in transfer interface --- internal/fs/fs.go | 11 +++++++++++ internal/fs/other.go | 3 +++ internal/fs/transfer.go | 18 ------------------ internal/op/fs.go | 28 ++++++++++++++++++++++++---- server/handles/fsmanage.go | 6 ++++++ 5 files changed, 44 insertions(+), 22 deletions(-) delete mode 100644 internal/fs/transfer.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 3d459c6be..8dd6395ad 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -206,3 +206,14 @@ func GetDirectUploadInfo(ctx context.Context, tool, path, dstName string, fileSi } return info, err } + +func Transfer(ctx context.Context, dstPath, shareURL, validCode string) error { + + err := transfer_share(ctx, dstPath, shareURL, validCode) + if err != nil { + log.Errorf("failed transfer %s: %+v", dstPath, err) + return err + } + + return nil +} diff --git a/internal/fs/other.go b/internal/fs/other.go index 31f577be5..c3dc50a9b 100644 --- a/internal/fs/other.go +++ b/internal/fs/other.go @@ -51,6 +51,9 @@ func transfer_share(ctx context.Context, dstPath, shareURL, validCode string) er return fmt.Errorf("failed join path: %w", err) } storage, actualPath, err := op.GetStorageAndActualPath(dstPath) + if err != nil { + return fmt.Errorf("failed to load driver") + } return op.TransferShare(ctx, storage, actualPath, shareURL, validCode) } diff --git a/internal/fs/transfer.go b/internal/fs/transfer.go deleted file mode 100644 index 8d94699f3..000000000 --- a/internal/fs/transfer.go +++ /dev/null @@ -1,18 +0,0 @@ -package fs - -import ( - "context" - - log "github.com/sirupsen/logrus" -) - -func Transfer(ctx context.Context, dstPath, shareURL, validCode string) error { - - err := transfer_share(ctx, dstPath, shareURL, validCode) - if err != nil { - log.Errorf("failed transfer %s: %+v", dstPath, err) - return err - } - - return nil -} diff --git a/internal/op/fs.go b/internal/op/fs.go index e5226761b..f2b082714 100644 --- a/internal/op/fs.go +++ b/internal/op/fs.go @@ -770,15 +770,35 @@ func TransferShare(ctx context.Context, storage driver.Driver, dstPath, shareURL return errors.WithMessagef(errs.StorageNotInit, "storage status: %s", storage.GetStorage().Status) } rawObj, err := Get(ctx, storage, dstPath) - if err != nil { - return errors.WithStack(errs.ObjectAlreadyExists) + if err == errs.ObjectNotFound { + if e := MakeDir(ctx, storage, dstPath); e != nil { + return e + } + } else if err != nil { + return err + } + if rawObj.IsDir() { + } switch s := storage.(type) { case driver.Transfer: - err = s.Transfer(ctx, model.UnwrapObjName(rawObj), shareURL, validCode) - if err != nil { + if err = s.Transfer(ctx, model.UnwrapObjName(rawObj), shareURL, validCode); err != nil { return errors.WithMessagef(err, "failed to transfer share to [%s]", dstPath) } + if dirCache, exist := Cache.dirCache.Get(Key(storage, dstPath)); exist { + t := time.Now() + newObj := &model.Object{ + Name: rawObj.GetName(), + IsFolder: true, + Modified: t, + Ctime: t, + Mask: model.Temp, + } + dirCache.UpdateObject("", wrapObjName(storage, newObj)) + } + if ctx.Value(conf.SkipHookKey) == nil && needHandleObjsUpdateHook() { + go objsUpdateHook(context.WithoutCancel(ctx), storage, dstPath, false) + } default: return errors.WithStack(errs.NotImplement) } diff --git a/server/handles/fsmanage.go b/server/handles/fsmanage.go index aa6b86e8d..42a829767 100644 --- a/server/handles/fsmanage.go +++ b/server/handles/fsmanage.go @@ -438,6 +438,12 @@ func FsTransfer(c *gin.Context) { return } + user := c.Request.Context().Value(conf.UserKey).(*model.User) + if !user.CanWrite() { + common.ErrorResp(c, errs.PermissionDenied, 403) + return + } + if req.DstDir == "" || req.SrcURL == "" { common.ErrorStrResp(c, "dst_dir and url are required", 400) return From c8ee1ecbbda897a6ecd711e38ee7e65386529428 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Thu, 1 Jan 2026 20:46:25 +0800 Subject: [PATCH 17/21] fix(quark_uc):transfer interface error --- drivers/quark_uc/driver.go | 5 ++++- drivers/quark_uc/types.go | 4 ++-- drivers/quark_uc/util.go | 25 +++++++++++++++++-------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/drivers/quark_uc/driver.go b/drivers/quark_uc/driver.go index 209ec70f3..7f2a83f58 100644 --- a/drivers/quark_uc/driver.go +++ b/drivers/quark_uc/driver.go @@ -241,7 +241,10 @@ func (d *QuarkOrUC) Transfer(ctx context.Context, dst model.Obj, shareURL, valid if err != nil { return err } - return d.transfer(dst, shareToken) + if shareToken == "" { + return errors.New("getting share token error") + } + return d.transfer(dst, shareToken, pwd_id) } var _ driver.Driver = (*QuarkOrUC)(nil) diff --git a/drivers/quark_uc/types.go b/drivers/quark_uc/types.go index 7749f9d08..a973c5282 100644 --- a/drivers/quark_uc/types.go +++ b/drivers/quark_uc/types.go @@ -286,7 +286,7 @@ type MemberResp struct { type ShareTokenResp struct { Resp Data struct { - ShareToken string `json:"shareToken"` + ShareToken string `json:"stoken"` } } @@ -310,5 +310,5 @@ type TaskCheckResp struct { Resp Data struct { Status int `json:"status"` - } + } `json:"data"` } diff --git a/drivers/quark_uc/util.go b/drivers/quark_uc/util.go index eb9037e57..54fad6d4a 100644 --- a/drivers/quark_uc/util.go +++ b/drivers/quark_uc/util.go @@ -368,7 +368,8 @@ func (d *QuarkOrUC) getPwdID(shareURL string) string { if len(codeParts) == 0 { return "" } - return codeParts[0] + pwd_id := strings.Split(codeParts[0], "#")[0] + return pwd_id } func (d *QuarkOrUC) getShareToken(pwd_id, passcode string) (string, error) { @@ -378,9 +379,10 @@ func (d *QuarkOrUC) getShareToken(pwd_id, passcode string) (string, error) { req.SetQueryParam("__t", strconv.FormatInt(time.Now().UnixMilli(), 10)) req.SetQueryParam("__dt", strconv.FormatInt(random.RangeInt64(100, 500), 10)) req.SetBody(base.Json{ - "pwd_id": pwd_id, - "passcode": passcode, - "share_from": "", + "pwd_id": pwd_id, + "passcode": passcode, + "share_from": "", + "support_visit_limit_private_share": true, }) }, &resp) if err != nil { @@ -392,17 +394,20 @@ func (d *QuarkOrUC) getShareToken(pwd_id, passcode string) (string, error) { return "", errors.New("unknown error") } -func (d *QuarkOrUC) transfer(dstDir model.Obj, shareToken string) error { +func (d *QuarkOrUC) transfer(dstDir model.Obj, shareToken, pwdId string) error { resp := SaveShareResp{} data := base.Json{ "stoken": shareToken, - "pdir_fid": dstDir.GetID(), + "pdir_fid": "0", "to_pdir_fid": dstDir.GetID(), + "pwd_id": pwdId, + "scene": "link", "pdir_save_all": true, - "scene": "line", } _, err := d.request("/share/sharepage/save", http.MethodPost, func(req *resty.Request) { req.SetBody(data) + req.SetQueryParam("__t", strconv.FormatInt(time.Now().UnixMilli(), 10)) + req.SetQueryParam("__dt", strconv.FormatInt(random.RangeInt64(100, 500), 10)) }, &resp) if err != nil { return err @@ -411,8 +416,12 @@ func (d *QuarkOrUC) transfer(dstDir model.Obj, shareToken string) error { retry_index := 0 for { check_resp := TaskCheckResp{} - _, err := d.request("/share/sharepage/task/check", http.MethodGet, func(req *resty.Request) { + _, err := d.request("/task", http.MethodGet, func(req *resty.Request) { req.SetQueryParam("task_id", resp.Data.TaskID) + req.SetQueryParam("__t", strconv.FormatInt(time.Now().UnixMilli(), 10)) + req.SetQueryParam("__dt", strconv.FormatInt(random.RangeInt64(100, 500), 10)) + req.SetQueryParam("retry_index", strconv.Itoa(retry_index)) + req.SetQueryParam("uc_param_str", "") }, &check_resp) if err != nil { return err From 7f4a0cc529c0894fa0813032dac0dc633ab03ef2 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Thu, 1 Jan 2026 20:51:32 +0800 Subject: [PATCH 18/21] chore(transfer):add dir check --- internal/op/fs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/op/fs.go b/internal/op/fs.go index f2b082714..8cf2e582b 100644 --- a/internal/op/fs.go +++ b/internal/op/fs.go @@ -777,8 +777,8 @@ func TransferShare(ctx context.Context, storage driver.Driver, dstPath, shareURL } else if err != nil { return err } - if rawObj.IsDir() { - + if !rawObj.IsDir() { + return errors.WithStack(errs.NotFolder) } switch s := storage.(type) { case driver.Transfer: From 64f59d2cfc24f745fe9ab63581dc684939e25058 Mon Sep 17 00:00:00 2001 From: KirCute <951206789@qq.com> Date: Fri, 2 Jan 2026 02:20:53 +0800 Subject: [PATCH 19/21] fix(transfer): bad cache handling --- internal/driver/driver.go | 11 ++++- internal/fs/archive.go | 4 +- internal/fs/fs.go | 8 ++-- internal/fs/other.go | 11 +---- internal/op/archive.go | 37 +++++++++-------- internal/op/fs.go | 85 ++++++++++++++++++++++++++------------ server/handles/fsmanage.go | 14 ++++--- server/handles/fsread.go | 6 ++- 8 files changed, 107 insertions(+), 69 deletions(-) diff --git a/internal/driver/driver.go b/internal/driver/driver.go index 3acd2ba42..d4c2ea952 100644 --- a/internal/driver/driver.go +++ b/internal/driver/driver.go @@ -111,10 +111,19 @@ type PutURL interface { } type Transfer interface { - // save the sharing file to dstDir + // Transfer save the sharing file to dstDir Transfer(ctx context.Context, dst model.Obj, shareURL, validCode string) error } +type TransferResult interface { + // Transfer save the sharing file to dstDir and return results + Transfer(ctx context.Context, dst model.Obj, shareURL, validCode string) ([]model.Obj, error) +} + +type MaybeCannotTransfer interface { + CanTransfer(path string) bool +} + //type WriteResult interface { // MkdirResult // MoveResult diff --git a/internal/fs/archive.go b/internal/fs/archive.go index 784ba587a..1ae1763c0 100644 --- a/internal/fs/archive.go +++ b/internal/fs/archive.go @@ -363,7 +363,7 @@ func archiveList(ctx context.Context, path string, args model.ArchiveListArgs) ( return op.ListArchive(ctx, storage, actualPath, args) } -func archiveDecompress(ctx context.Context, srcObjPath, dstDirPath string, args model.ArchiveDecompressArgs, lazyCache ...bool) (task.TaskExtensionInfo, error) { +func archiveDecompress(ctx context.Context, srcObjPath, dstDirPath string, args model.ArchiveDecompressArgs) (task.TaskExtensionInfo, error) { srcStorage, srcObjActualPath, err := op.GetStorageAndActualPath(srcObjPath) if err != nil { return nil, errors.WithMessage(err, "failed get src storage") @@ -373,7 +373,7 @@ func archiveDecompress(ctx context.Context, srcObjPath, dstDirPath string, args return nil, errors.WithMessage(err, "failed get dst storage") } if srcStorage.GetStorage() == dstStorage.GetStorage() { - err = op.ArchiveDecompress(ctx, srcStorage, srcObjActualPath, dstDirActualPath, args, lazyCache...) + err = op.ArchiveDecompress(ctx, srcStorage, srcObjActualPath, dstDirActualPath, args) if !errors.Is(err, errs.NotImplement) { return nil, err } diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 8dd6395ad..57cbe5238 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -140,8 +140,8 @@ func ArchiveList(ctx context.Context, path string, args model.ArchiveListArgs) ( return objs, err } -func ArchiveDecompress(ctx context.Context, srcObjPath, dstDirPath string, args model.ArchiveDecompressArgs, lazyCache ...bool) (task.TaskExtensionInfo, error) { - t, err := archiveDecompress(ctx, srcObjPath, dstDirPath, args, lazyCache...) +func ArchiveDecompress(ctx context.Context, srcObjPath, dstDirPath string, args model.ArchiveDecompressArgs) (task.TaskExtensionInfo, error) { + t, err := archiveDecompress(ctx, srcObjPath, dstDirPath, args) if err != nil { log.Errorf("failed decompress [%s]%s: %+v", srcObjPath, args.InnerPath, err) } @@ -208,12 +208,10 @@ func GetDirectUploadInfo(ctx context.Context, tool, path, dstName string, fileSi } func Transfer(ctx context.Context, dstPath, shareURL, validCode string) error { - - err := transfer_share(ctx, dstPath, shareURL, validCode) + err := transferShare(ctx, dstPath, shareURL, validCode) if err != nil { log.Errorf("failed transfer %s: %+v", dstPath, err) return err } - return nil } diff --git a/internal/fs/other.go b/internal/fs/other.go index c3dc50a9b..6758bd860 100644 --- a/internal/fs/other.go +++ b/internal/fs/other.go @@ -40,16 +40,7 @@ func remove(ctx context.Context, path string) error { return op.Remove(ctx, storage, actualPath) } -func transfer_share(ctx context.Context, dstPath, shareURL, validCode string) error { - /* get user info then join path */ - user, ok := ctx.Value(conf.UserKey).(*model.User) - if !ok { - return fmt.Errorf("failed get user from context") - } - dstPath, err := user.JoinPath(dstPath) - if err != nil { - return fmt.Errorf("failed join path: %w", err) - } +func transferShare(ctx context.Context, dstPath, shareURL, validCode string) error { storage, actualPath, err := op.GetStorageAndActualPath(dstPath) if err != nil { return fmt.Errorf("failed to load driver") diff --git a/internal/op/archive.go b/internal/op/archive.go index d0a53a919..61298d854 100644 --- a/internal/op/archive.go +++ b/internal/op/archive.go @@ -21,7 +21,6 @@ import ( gocache "github.com/OpenListTeam/go-cache" "github.com/pkg/errors" log "github.com/sirupsen/logrus" - "golang.org/x/time/rate" ) var ( @@ -490,7 +489,7 @@ func InternalExtract(ctx context.Context, storage driver.Driver, path string, ar return &streamWithParent{rc: rc, parents: ss}, size, nil } -func ArchiveDecompress(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string, args model.ArchiveDecompressArgs, lazyCache ...bool) error { +func ArchiveDecompress(ctx context.Context, storage driver.Driver, srcPath, dstDirPath string, args model.ArchiveDecompressArgs) error { if storage.Config().CheckStatus && storage.GetStorage().Status != WORK { return errors.WithMessagef(errs.StorageNotInit, "storage status: %s", storage.GetStorage().Status) } @@ -500,10 +499,17 @@ func ArchiveDecompress(ctx context.Context, storage driver.Driver, srcPath, dstD if err != nil { return errors.WithMessage(err, "failed to get src object") } + err = MakeDir(ctx, storage, dstDirPath) + if err != nil { + return errors.WithMessagef(err, "failed to make dir [%s]", dstDirPath) + } dstDir, err := GetUnwrap(ctx, storage, dstDirPath) if err != nil { return errors.WithMessage(err, "failed to get dst dir") } + if model.ObjHasMask(dstDir, model.NoWrite) { + return errors.WithStack(errs.PermissionDenied) + } var newObjs []model.Obj switch s := storage.(type) { @@ -518,24 +524,26 @@ func ArchiveDecompress(ctx context.Context, storage driver.Driver, srcPath, dstD } } } - } else if !utils.IsBool(lazyCache...) { + } else { Cache.DeleteDirectory(storage, dstDirPath) } } case driver.ArchiveDecompress: err = s.ArchiveDecompress(ctx, srcObj, dstDir, args) - if err == nil && !utils.IsBool(lazyCache...) { + if err == nil { Cache.DeleteDirectory(storage, dstDirPath) } default: - return errs.NotImplement + return errors.WithStack(errs.NotImplement) } - if !utils.IsBool(lazyCache...) && err == nil && needHandleObjsUpdateHook() { + if err == nil && ctx.Value(conf.SkipHookKey) == nil && needHandleObjsUpdateHook() { onlyList := false targetPath := dstDirPath - if len(newObjs) == 1 && newObjs[0].IsDir() { + if newObjs != nil && len(newObjs) == 1 && newObjs[0].IsDir() { targetPath = stdpath.Join(dstDirPath, newObjs[0].GetName()) - } else if len(newObjs) == 1 && !newObjs[0].IsDir() { + } else if newObjs != nil && !utils.SliceMeet(newObjs, nil, func(item model.Obj, _ any) bool { + return item.IsDir() + }) { onlyList = true } else if args.PutIntoNewDir { targetPath = stdpath.Join(dstDirPath, strings.TrimSuffix(srcObj.GetName(), stdpath.Ext(srcObj.GetName()))) @@ -543,18 +551,11 @@ func ArchiveDecompress(ctx context.Context, storage driver.Driver, srcPath, dstD targetPath = stdpath.Join(dstDirPath, innerBase) dstObj, e := Get(ctx, storage, targetPath) onlyList = e != nil || !dstObj.IsDir() - } - if onlyList { - go List(context.Background(), storage, dstDirPath, model.ListArgs{Refresh: true}) - } else { - var limiter *rate.Limiter - if l, _ := GetSettingItemByKey(conf.HandleHookRateLimit); l != nil { - if f, e := strconv.ParseFloat(l.Value, 64); e == nil && f > .0 { - limiter = rate.NewLimiter(rate.Limit(f), 1) - } + if onlyList { + targetPath = dstDirPath } - go RecursivelyListStorage(context.Background(), storage, targetPath, limiter, nil) } + go objsUpdateHook(ctx, storage, targetPath, !onlyList) } return errors.WithStack(err) } diff --git a/internal/op/fs.go b/internal/op/fs.go index 8cf2e582b..c53efcdd5 100644 --- a/internal/op/fs.go +++ b/internal/op/fs.go @@ -765,44 +765,77 @@ func PutURL(ctx context.Context, storage driver.Driver, dstDirPath, dstName, url return errors.WithStack(err) } -func TransferShare(ctx context.Context, storage driver.Driver, dstPath, shareURL, validCode string) error { +func CanTransfer(storage driver.Driver, path string) bool { + _, ok := storage.(driver.Transfer) + _, okResult := storage.(driver.TransferResult) + if !ok && !okResult { + return false + } + if storage.Config().CheckStatus && storage.GetStorage().Status != WORK { + return false + } + if maybe, ok := storage.(driver.MaybeCannotTransfer); ok { + return maybe.CanTransfer(path) + } + return true +} + +func TransferShare(ctx context.Context, storage driver.Driver, dstDirPath, shareURL, validCode string) error { if storage.Config().CheckStatus && storage.GetStorage().Status != WORK { return errors.WithMessagef(errs.StorageNotInit, "storage status: %s", storage.GetStorage().Status) } - rawObj, err := Get(ctx, storage, dstPath) - if err == errs.ObjectNotFound { - if e := MakeDir(ctx, storage, dstPath); e != nil { - return e - } - } else if err != nil { - return err + err := MakeDir(ctx, storage, dstDirPath) + if err != nil { + return errors.WithMessagef(err, "failed to make dir [%s]", dstDirPath) } - if !rawObj.IsDir() { - return errors.WithStack(errs.NotFolder) + parentDir, err := GetUnwrap(ctx, storage, dstDirPath) + if err != nil { + return errors.WithMessagef(err, "failed to get dir [%s]", dstDirPath) + } + if model.ObjHasMask(parentDir, model.NoWrite) { + return errors.WithStack(errs.PermissionDenied) } + var newObjs []model.Obj switch s := storage.(type) { - case driver.Transfer: - if err = s.Transfer(ctx, model.UnwrapObjName(rawObj), shareURL, validCode); err != nil { - return errors.WithMessagef(err, "failed to transfer share to [%s]", dstPath) - } - if dirCache, exist := Cache.dirCache.Get(Key(storage, dstPath)); exist { - t := time.Now() - newObj := &model.Object{ - Name: rawObj.GetName(), - IsFolder: true, - Modified: t, - Ctime: t, - Mask: model.Temp, + case driver.TransferResult: + newObjs, err = s.Transfer(ctx, parentDir, shareURL, validCode) + if err == nil { + if len(newObjs) > 0 { + if !storage.Config().NoCache { + if cache, exist := Cache.dirCache.Get(Key(storage, dstDirPath)); exist { + for _, newObj := range newObjs { + cache.UpdateObject(newObj.GetName(), newObj) + } + } + } + } else { + Cache.DeleteDirectory(storage, dstDirPath) } - dirCache.UpdateObject("", wrapObjName(storage, newObj)) + } else { + err = errors.WithMessagef(err, "failed to transfer share to [%s]", dstDirPath) } - if ctx.Value(conf.SkipHookKey) == nil && needHandleObjsUpdateHook() { - go objsUpdateHook(context.WithoutCancel(ctx), storage, dstPath, false) + case driver.Transfer: + if err = s.Transfer(ctx, parentDir, shareURL, validCode); err != nil { + err = errors.WithMessagef(err, "failed to transfer share to [%s]", dstDirPath) + } else { + Cache.DeleteDirectory(storage, dstDirPath) } default: return errors.WithStack(errs.NotImplement) } - return nil + if err == nil && ctx.Value(conf.SkipHookKey) == nil && needHandleObjsUpdateHook() { + onlyList := false + targetPath := dstDirPath + if newObjs != nil && len(newObjs) == 1 && newObjs[0].IsDir() { + targetPath = stdpath.Join(dstDirPath, newObjs[0].GetName()) + } else if newObjs != nil && !utils.SliceMeet(newObjs, nil, func(item model.Obj, _ any) bool { + return item.IsDir() + }) { + onlyList = true + } + go objsUpdateHook(ctx, storage, targetPath, !onlyList) + } + return err } func GetDirectUploadTools(storage driver.Driver) []string { diff --git a/server/handles/fsmanage.go b/server/handles/fsmanage.go index 42a829767..b24bb37ba 100644 --- a/server/handles/fsmanage.go +++ b/server/handles/fsmanage.go @@ -437,19 +437,21 @@ func FsTransfer(c *gin.Context) { common.ErrorResp(c, err, 400) return } - + if req.DstDir == "" || req.SrcURL == "" { + common.ErrorStrResp(c, "dst_dir and url are required", 400) + return + } user := c.Request.Context().Value(conf.UserKey).(*model.User) if !user.CanWrite() { common.ErrorResp(c, errs.PermissionDenied, 403) return } - - if req.DstDir == "" || req.SrcURL == "" { - common.ErrorStrResp(c, "dst_dir and url are required", 400) + reqDir, err := user.JoinPath(req.DstDir) + if err != nil { + common.ErrorResp(c, err, 403) return } - - if err := fs.Transfer(c.Request.Context(), req.DstDir, req.SrcURL, req.ValidCode); err != nil { + if err := fs.Transfer(c.Request.Context(), reqDir, req.SrcURL, req.ValidCode); err != nil { common.ErrorResp(c, err, 500) return } diff --git a/server/handles/fsread.go b/server/handles/fsread.go index 65bc25248..e6cbccf48 100644 --- a/server/handles/fsread.go +++ b/server/handles/fsread.go @@ -54,6 +54,7 @@ type FsListResp struct { Write bool `json:"write"` Provider string `json:"provider"` DirectUploadTools []string `json:"direct_upload_tools,omitempty"` + CanTransfer bool `json:"can_transfer"` } func FsListSplit(c *gin.Context) { @@ -108,10 +109,12 @@ func FsList(c *gin.Context, req *ListReq, user *model.User) { } total, objs := pagination(objs, &req.PageReq) provider := "unknown" + canTransfer := false var directUploadTools []string if user.CanWrite() { - if storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{}); err == nil { + if storage, actualPath, err := op.GetStorageAndActualPath(reqPath); err == nil { directUploadTools = op.GetDirectUploadTools(storage) + canTransfer = op.CanTransfer(storage, actualPath) } } common.SuccessResp(c, FsListResp{ @@ -122,6 +125,7 @@ func FsList(c *gin.Context, req *ListReq, user *model.User) { Write: user.CanWrite() || common.CanWrite(meta, reqPath), Provider: provider, DirectUploadTools: directUploadTools, + CanTransfer: canTransfer, }) } From 55e428e3badddcedd6521d3b5db193080b7863e0 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Fri, 2 Jan 2026 10:15:46 +0800 Subject: [PATCH 20/21] chore(189pc):remove unnecessary request para --- drivers/189pc/utils.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/189pc/utils.go b/drivers/189pc/utils.go index 9ba0ddb67..a6b29e831 100644 --- a/drivers/189pc/utils.go +++ b/drivers/189pc/utils.go @@ -1494,8 +1494,7 @@ func (y *Cloud189PC) getSharedInfo(code string) (int64, string, string, error) { } req := y.getClient().R(). SetHeader("Accept", "application/json;charset=UTF-8"). - SetHeader("Referer", "https://cloud.189.cn/web/share?code=2QFRjyiMfiue"). - SetHeader("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"). + SetHeader("Referer", fmt.Sprintf("https://cloud.189.cn/web/share?code=%s", code)). SetHeader("SessionKey", y.getTokenInfo().SessionKey). SetQueryParams(map[string]string{ "noCache": random_num(), From 71e2acfafca2e0f45b203d4dc95bffa8d4b106b6 Mon Sep 17 00:00:00 2001 From: mkitsdts Date: Sun, 4 Jan 2026 09:46:04 +0800 Subject: [PATCH 21/21] chore(189pc):check permission at first --- drivers/189pc/driver.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/189pc/driver.go b/drivers/189pc/driver.go index 472a6d7ea..32eb95d4f 100644 --- a/drivers/189pc/driver.go +++ b/drivers/189pc/driver.go @@ -430,6 +430,10 @@ func (y *Cloud189PC) GetDetails(ctx context.Context) (*model.StorageDetails, err } func (y *Cloud189PC) Transfer(ctx context.Context, dst model.Obj, shareURL, validCode string) error { + if !dst.IsDir() { + return fmt.Errorf("it should be in the folder") + } + sharecode := y.extractCode(shareURL) if sharecode == "" { return fmt.Errorf("need share code") @@ -450,9 +454,6 @@ func (y *Cloud189PC) Transfer(ctx context.Context, dst model.Obj, shareURL, vali }, } - if !dst.IsDir() { - return fmt.Errorf("it should be in the folder") - } taskInfosBytes, err := utils.Json.Marshal(taskInfos) if err != nil { return err