Skip to content

Commit 642acf8

Browse files
authored
feat(123pan): add offline download (#1911)
* feat(123网盘): 添加123网盘离线下载功能 - 新增123网盘离线下载实现 - 添加相关API接口和常量配置 - 在路由和工具集中集成123网盘支持 * refactor(offline_download): 重构123网盘离线下载状态处理和类型定义 - 将离线下载相关类型定义从util.go移至types.go - 更新状态获取api * 移除了备选方案(/offline_download/task/status)
1 parent 747993e commit 642acf8

File tree

9 files changed

+362
-1
lines changed

9 files changed

+362
-1
lines changed

drivers/123/types.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,48 @@ type UserInfoResp struct {
133133
FileCount int `json:"FileCount"`
134134
} `json:"data"`
135135
}
136+
137+
type offlineResolveResp struct {
138+
Data struct {
139+
List []struct {
140+
Result int `json:"result"`
141+
ID int64 `json:"id"`
142+
ErrCode int `json:"err_code"`
143+
ErrMsg string `json:"err_msg"`
144+
Files []struct {
145+
ID int64 `json:"id"`
146+
} `json:"files"`
147+
} `json:"list"`
148+
} `json:"data"`
149+
}
150+
151+
type offlineSubmitResp struct {
152+
Data struct {
153+
TaskList []struct {
154+
TaskID int64 `json:"task_id"`
155+
Result int `json:"result"`
156+
} `json:"task_list"`
157+
} `json:"data"`
158+
}
159+
160+
type offlineTaskListResp struct {
161+
Data struct {
162+
HasRun bool `json:"has_run"`
163+
List []offlineTask `json:"list"`
164+
Total int `json:"total"`
165+
} `json:"data"`
166+
}
167+
168+
type offlineTask struct {
169+
TaskID int64 `json:"task_id"`
170+
Name string `json:"name"`
171+
Status int `json:"status"`
172+
Size int64 `json:"size"`
173+
ThirdTask string `json:"third_task_id"`
174+
Downloaded int64 `json:"downloaded"`
175+
Progress float64 `json:"progress"`
176+
UploadIDR int64 `json:"upload_idr"`
177+
UploadName string `json:"upload_name"`
178+
Type string `json:"type"`
179+
Speed int64 `json:"speed"`
180+
}

drivers/123/util.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"time"
1515

1616
"github.com/OpenListTeam/OpenList/v4/drivers/base"
17+
"github.com/OpenListTeam/OpenList/v4/internal/model"
1718
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
1819
"github.com/go-resty/resty/v2"
1920
jsoniter "github.com/json-iterator/go"
@@ -43,9 +44,16 @@ const (
4344
S3Auth = MainApi + "/file/s3_upload_object/auth"
4445
UploadCompleteV2 = MainApi + "/file/upload_complete/v2"
4546
S3Complete = MainApi + "/file/s3_complete_multipart_upload"
47+
48+
OfflineResolve = MainApi + "/v2/offline_download/task/resolve"
49+
OfflineSubmit = MainApi + "/v2/offline_download/task/submit"
50+
OfflineTaskList = MainApi + "/offline_download/task/list"
51+
OfflineTaskDelete = MainApi + "/offline_download/task/delete"
4652
// AuthKeySalt = "8-8D$sL8gPjom7bk#cY"
4753
)
4854

55+
var ErrOfflineTaskNotFound = errors.New("offline task not found")
56+
4957
func signPath(path string, os string, version string) (k string, v string) {
5058
table := []byte{'a', 'd', 'e', 'f', 'g', 'h', 'l', 'm', 'y', 'i', 'j', 'n', 'o', 'p', 'k', 'q', 'r', 's', 't', 'u', 'b', 'c', 'v', 'w', 's', 'z'}
5159
random := fmt.Sprintf("%.f", math.Round(1e7*rand.Float64()))
@@ -238,6 +246,115 @@ do:
238246
return body, nil
239247
}
240248

249+
func (d *Pan123) OfflineDownload(ctx context.Context, uri string, dstDir model.Obj) (int64, error) {
250+
var resolveResp offlineResolveResp
251+
_, err := d.Request(OfflineResolve, http.MethodPost, func(req *resty.Request) {
252+
req.SetContext(ctx).SetBody(base.Json{
253+
"urls": uri,
254+
})
255+
}, &resolveResp)
256+
if err != nil {
257+
return 0, err
258+
}
259+
if len(resolveResp.Data.List) == 0 {
260+
return 0, fmt.Errorf("offline resolve failed: empty response")
261+
}
262+
if resolveResp.Data.List[0].Result != 0 {
263+
msg := resolveResp.Data.List[0].ErrMsg
264+
if msg == "" {
265+
msg = "offline resolve failed"
266+
}
267+
return 0, fmt.Errorf("%s", msg)
268+
}
269+
resourceID := resolveResp.Data.List[0].ID
270+
if resourceID == 0 {
271+
return 0, fmt.Errorf("offline resolve failed: empty resource id")
272+
}
273+
selectFileIDs := make([]int64, 0, len(resolveResp.Data.List[0].Files))
274+
for _, f := range resolveResp.Data.List[0].Files {
275+
if f.ID > 0 {
276+
selectFileIDs = append(selectFileIDs, f.ID)
277+
}
278+
}
279+
if len(selectFileIDs) == 0 {
280+
return 0, fmt.Errorf("offline resolve failed: empty file list")
281+
}
282+
uploadDir, err := strconv.ParseInt(dstDir.GetID(), 10, 64)
283+
if err != nil {
284+
return 0, fmt.Errorf("invalid destination dir id: %s", dstDir.GetID())
285+
}
286+
287+
var submitResp offlineSubmitResp
288+
_, err = d.Request(OfflineSubmit, http.MethodPost, func(req *resty.Request) {
289+
req.SetContext(ctx).SetBody(base.Json{
290+
"resource_list": []base.Json{
291+
{
292+
"resource_id": resourceID,
293+
"select_file_id": selectFileIDs,
294+
},
295+
},
296+
"upload_dir": uploadDir,
297+
})
298+
}, &submitResp)
299+
if err != nil {
300+
return 0, err
301+
}
302+
if len(submitResp.Data.TaskList) == 0 {
303+
return 0, fmt.Errorf("offline submit failed: empty task list")
304+
}
305+
if submitResp.Data.TaskList[0].Result != 0 {
306+
return 0, fmt.Errorf("offline submit failed")
307+
}
308+
if submitResp.Data.TaskList[0].TaskID == 0 {
309+
return 0, fmt.Errorf("offline submit failed: empty task id")
310+
}
311+
return submitResp.Data.TaskList[0].TaskID, nil
312+
}
313+
314+
func (d *Pan123) GetOfflineTask(ctx context.Context, taskID int64) (*offlineTask, error) {
315+
if taskID == 0 {
316+
return nil, fmt.Errorf("invalid task id")
317+
}
318+
page := 1
319+
pageSize := 100
320+
statusArr := []int{0, 1, 2, 3}
321+
for {
322+
var listResp offlineTaskListResp
323+
_, err := d.Request(OfflineTaskList, http.MethodPost, func(req *resty.Request) {
324+
req.SetContext(ctx).SetBody(base.Json{
325+
"current_page": page,
326+
"page_size": pageSize,
327+
"status_arr": statusArr,
328+
})
329+
}, &listResp)
330+
if err != nil {
331+
return nil, err
332+
}
333+
for i := range listResp.Data.List {
334+
if listResp.Data.List[i].TaskID == taskID {
335+
return &listResp.Data.List[i], nil
336+
}
337+
}
338+
if len(listResp.Data.List) == 0 || page*pageSize >= listResp.Data.Total {
339+
break
340+
}
341+
page++
342+
}
343+
return nil, ErrOfflineTaskNotFound
344+
}
345+
346+
func (d *Pan123) DeleteOfflineTasks(ctx context.Context, taskIDs []int64) error {
347+
if len(taskIDs) == 0 {
348+
return nil
349+
}
350+
_, err := d.Request(OfflineTaskDelete, http.MethodPost, func(req *resty.Request) {
351+
req.SetContext(ctx).SetBody(base.Json{
352+
"task_ids": taskIDs,
353+
})
354+
}, nil)
355+
return err
356+
}
357+
241358
func (d *Pan123) getFiles(ctx context.Context, parentId string, name string) ([]File, error) {
242359
page := 1
243360
total := 0

internal/conf/const.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ const (
7777
// 115
7878
Pan115TempDir = "115_temp_dir"
7979

80+
// 123
81+
Pan123TempDir = "123_temp_dir"
82+
8083
// 115_open
8184
Pan115OpenTempDir = "115_open_temp_dir"
8285

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package _123_pan
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
8+
_123 "github.com/OpenListTeam/OpenList/v4/drivers/123"
9+
"github.com/OpenListTeam/OpenList/v4/internal/conf"
10+
"github.com/OpenListTeam/OpenList/v4/internal/errs"
11+
"github.com/OpenListTeam/OpenList/v4/internal/model"
12+
"github.com/OpenListTeam/OpenList/v4/internal/offline_download/tool"
13+
"github.com/OpenListTeam/OpenList/v4/internal/op"
14+
"github.com/OpenListTeam/OpenList/v4/internal/setting"
15+
)
16+
17+
type Pan123 struct{}
18+
19+
func (*Pan123) Name() string {
20+
return "123Pan"
21+
}
22+
23+
func (*Pan123) Items() []model.SettingItem {
24+
return []model.SettingItem{
25+
{Key: conf.Pan123TempDir, Value: "", Type: conf.TypeString, Group: model.OFFLINE_DOWNLOAD, Flag: model.PRIVATE},
26+
}
27+
}
28+
29+
func (*Pan123) Run(_ *tool.DownloadTask) error {
30+
return errs.NotSupport
31+
}
32+
33+
func (*Pan123) Init() (string, error) {
34+
return "ok", nil
35+
}
36+
37+
func (*Pan123) IsReady() bool {
38+
tempDir := setting.GetStr(conf.Pan123TempDir)
39+
if tempDir == "" {
40+
return false
41+
}
42+
storage, _, err := op.GetStorageAndActualPath(tempDir)
43+
if err != nil {
44+
return false
45+
}
46+
if _, ok := storage.(*_123.Pan123); !ok {
47+
return false
48+
}
49+
return true
50+
}
51+
52+
func (*Pan123) AddURL(args *tool.AddUrlArgs) (string, error) {
53+
storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir)
54+
if err != nil {
55+
return "", err
56+
}
57+
driver123, ok := storage.(*_123.Pan123)
58+
if !ok {
59+
return "", fmt.Errorf("unsupported storage driver for offline download, only 123Pan is supported")
60+
}
61+
ctx := context.Background()
62+
if err := op.MakeDir(ctx, storage, actualPath); err != nil {
63+
return "", err
64+
}
65+
parentDir, err := op.GetUnwrap(ctx, storage, actualPath)
66+
if err != nil {
67+
return "", err
68+
}
69+
taskID, err := driver123.OfflineDownload(ctx, args.Url, parentDir)
70+
if err != nil {
71+
return "", fmt.Errorf("failed to add offline download task: %w", err)
72+
}
73+
return strconv.FormatInt(taskID, 10), nil
74+
}
75+
76+
func (*Pan123) Remove(task *tool.DownloadTask) error {
77+
taskID, err := strconv.ParseInt(task.GID, 10, 64)
78+
if err != nil {
79+
return fmt.Errorf("failed to parse task ID: %s", task.GID)
80+
}
81+
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
82+
if err != nil {
83+
return err
84+
}
85+
driver123, ok := storage.(*_123.Pan123)
86+
if !ok {
87+
return fmt.Errorf("unsupported storage driver for offline download, only 123Pan is supported")
88+
}
89+
return driver123.DeleteOfflineTasks(context.Background(), []int64{taskID})
90+
}
91+
92+
func (*Pan123) Status(task *tool.DownloadTask) (*tool.Status, error) {
93+
taskID, err := strconv.ParseInt(task.GID, 10, 64)
94+
if err != nil {
95+
return nil, fmt.Errorf("failed to parse task ID: %s", task.GID)
96+
}
97+
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
98+
if err != nil {
99+
return nil, err
100+
}
101+
driver123, ok := storage.(*_123.Pan123)
102+
if !ok {
103+
return nil, fmt.Errorf("unsupported storage driver for offline download, only 123Pan is supported")
104+
}
105+
106+
t, err := driver123.GetOfflineTask(context.Background(), taskID)
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
var statusStr string
112+
completed := false
113+
var taskErr error
114+
switch t.Status {
115+
case 0:
116+
statusStr = "downloading"
117+
case 2:
118+
statusStr = "succeed"
119+
completed = true
120+
case 1:
121+
statusStr = "failed"
122+
taskErr = fmt.Errorf("offline download failed")
123+
case 3:
124+
statusStr = "retrying"
125+
default:
126+
statusStr = fmt.Sprintf("status_%d", t.Status)
127+
}
128+
129+
return &tool.Status{
130+
TotalBytes: t.Size,
131+
Progress: t.Progress,
132+
Completed: completed,
133+
Status: statusStr,
134+
Err: taskErr,
135+
}, nil
136+
}
137+
138+
var _ tool.Tool = (*Pan123)(nil)
139+
140+
func init() {
141+
tool.Tools.Add(&Pan123{})
142+
}

internal/offline_download/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package offline_download
33
import (
44
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/115"
55
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/115_open"
6+
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/123"
67
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/123_open"
78
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/aria2"
89
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/http"

internal/offline_download/tool/add.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
_115 "github.com/OpenListTeam/OpenList/v4/drivers/115"
1010
_115_open "github.com/OpenListTeam/OpenList/v4/drivers/115_open"
11+
_123 "github.com/OpenListTeam/OpenList/v4/drivers/123"
1112
_123_open "github.com/OpenListTeam/OpenList/v4/drivers/123_open"
1213
"github.com/OpenListTeam/OpenList/v4/drivers/pikpak"
1314
"github.com/OpenListTeam/OpenList/v4/drivers/thunder"
@@ -110,6 +111,12 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro
110111
} else {
111112
tempDir = filepath.Join(setting.GetStr(conf.Pan123OpenTempDir), uid)
112113
}
114+
case "123Pan":
115+
if _, ok := storage.(*_123.Pan123); ok {
116+
tempDir = args.DstDirPath
117+
} else {
118+
tempDir = filepath.Join(setting.GetStr(conf.Pan123TempDir), uid)
119+
}
113120
case "PikPak":
114121
if _, ok := storage.(*pikpak.PikPak); ok {
115122
tempDir = args.DstDirPath

internal/offline_download/tool/download.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func (t *DownloadTask) Update() (bool, error) {
175175

176176
func (t *DownloadTask) Transfer() error {
177177
toolName := t.tool.Name()
178-
if toolName == "115 Cloud" || toolName == "115 Open" || toolName == "123 Open" || toolName == "PikPak" || toolName == "Thunder" || toolName == "ThunderX" || toolName == "ThunderBrowser" {
178+
if toolName == "115 Cloud" || toolName == "115 Open" || toolName == "123 Open" || toolName == "123Pan" || toolName == "PikPak" || toolName == "Thunder" || toolName == "ThunderX" || toolName == "ThunderBrowser" {
179179
// 如果不是直接下载到目标路径,则进行转存
180180
if t.TempDir != t.DstDirPath {
181181
return transferObj(t.Ctx(), t.TempDir, t.DstDirPath, t.DeletePolicy)

0 commit comments

Comments
 (0)