Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/baidu_netdisk/util.go
1986 views
1
package baidu_netdisk
2
3
import (
4
"encoding/hex"
5
"errors"
6
"fmt"
7
"net/http"
8
"strconv"
9
"strings"
10
"time"
11
"unicode"
12
13
"github.com/alist-org/alist/v3/drivers/base"
14
"github.com/alist-org/alist/v3/internal/errs"
15
"github.com/alist-org/alist/v3/internal/model"
16
"github.com/alist-org/alist/v3/internal/op"
17
"github.com/alist-org/alist/v3/pkg/utils"
18
"github.com/avast/retry-go"
19
"github.com/go-resty/resty/v2"
20
log "github.com/sirupsen/logrus"
21
)
22
23
// do others that not defined in Driver interface
24
25
func (d *BaiduNetdisk) refreshToken() error {
26
err := d._refreshToken()
27
if err != nil && errors.Is(err, errs.EmptyToken) {
28
err = d._refreshToken()
29
}
30
return err
31
}
32
33
func (d *BaiduNetdisk) _refreshToken() error {
34
u := "https://openapi.baidu.com/oauth/2.0/token"
35
var resp base.TokenResp
36
var e TokenErrResp
37
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
38
"grant_type": "refresh_token",
39
"refresh_token": d.RefreshToken,
40
"client_id": d.ClientID,
41
"client_secret": d.ClientSecret,
42
}).Get(u)
43
if err != nil {
44
return err
45
}
46
if e.Error != "" {
47
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
48
}
49
if resp.RefreshToken == "" {
50
return errs.EmptyToken
51
}
52
d.AccessToken, d.RefreshToken = resp.AccessToken, resp.RefreshToken
53
op.MustSaveDriverStorage(d)
54
return nil
55
}
56
57
func (d *BaiduNetdisk) request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
58
var result []byte
59
err := retry.Do(func() error {
60
req := base.RestyClient.R()
61
req.SetQueryParam("access_token", d.AccessToken)
62
if callback != nil {
63
callback(req)
64
}
65
if resp != nil {
66
req.SetResult(resp)
67
}
68
res, err := req.Execute(method, furl)
69
if err != nil {
70
return err
71
}
72
log.Debugf("[baidu_netdisk] req: %s, resp: %s", furl, res.String())
73
errno := utils.Json.Get(res.Body(), "errno").ToInt()
74
if errno != 0 {
75
if utils.SliceContains([]int{111, -6}, errno) {
76
log.Info("refreshing baidu_netdisk token.")
77
err2 := d.refreshToken()
78
if err2 != nil {
79
return retry.Unrecoverable(err2)
80
}
81
}
82
83
if 31023 == errno && d.DownloadAPI == "crack_video" {
84
result = res.Body()
85
return nil
86
}
87
88
return fmt.Errorf("req: [%s] ,errno: %d, refer to https://pan.baidu.com/union/doc/", furl, errno)
89
}
90
result = res.Body()
91
return nil
92
},
93
retry.LastErrorOnly(true),
94
retry.Attempts(3),
95
retry.Delay(time.Second),
96
retry.DelayType(retry.BackOffDelay))
97
return result, err
98
}
99
100
func (d *BaiduNetdisk) get(pathname string, params map[string]string, resp interface{}) ([]byte, error) {
101
return d.request("https://pan.baidu.com/rest/2.0"+pathname, http.MethodGet, func(req *resty.Request) {
102
req.SetQueryParams(params)
103
}, resp)
104
}
105
106
func (d *BaiduNetdisk) postForm(pathname string, params map[string]string, form map[string]string, resp interface{}) ([]byte, error) {
107
return d.request("https://pan.baidu.com/rest/2.0"+pathname, http.MethodPost, func(req *resty.Request) {
108
req.SetQueryParams(params)
109
req.SetFormData(form)
110
}, resp)
111
}
112
113
func (d *BaiduNetdisk) getFiles(dir string) ([]File, error) {
114
start := 0
115
limit := 200
116
params := map[string]string{
117
"method": "list",
118
"dir": dir,
119
"web": "web",
120
}
121
if d.OrderBy != "" {
122
params["order"] = d.OrderBy
123
if d.OrderDirection == "desc" {
124
params["desc"] = "1"
125
}
126
}
127
res := make([]File, 0)
128
for {
129
params["start"] = strconv.Itoa(start)
130
params["limit"] = strconv.Itoa(limit)
131
start += limit
132
var resp ListResp
133
_, err := d.get("/xpan/file", params, &resp)
134
if err != nil {
135
return nil, err
136
}
137
if len(resp.List) == 0 {
138
break
139
}
140
141
if d.OnlyListVideoFile {
142
for _, file := range resp.List {
143
if file.Isdir == 1 || file.Category == 1 {
144
res = append(res, file)
145
}
146
}
147
} else {
148
res = append(res, resp.List...)
149
}
150
}
151
return res, nil
152
}
153
154
func (d *BaiduNetdisk) linkOfficial(file model.Obj, _ model.LinkArgs) (*model.Link, error) {
155
var resp DownloadResp
156
params := map[string]string{
157
"method": "filemetas",
158
"fsids": fmt.Sprintf("[%s]", file.GetID()),
159
"dlink": "1",
160
}
161
_, err := d.get("/xpan/multimedia", params, &resp)
162
if err != nil {
163
return nil, err
164
}
165
u := fmt.Sprintf("%s&access_token=%s", resp.List[0].Dlink, d.AccessToken)
166
res, err := base.NoRedirectClient.R().SetHeader("User-Agent", "pan.baidu.com").Head(u)
167
if err != nil {
168
return nil, err
169
}
170
//if res.StatusCode() == 302 {
171
u = res.Header().Get("location")
172
//}
173
174
return &model.Link{
175
URL: u,
176
Header: http.Header{
177
"User-Agent": []string{"pan.baidu.com"},
178
},
179
}, nil
180
}
181
182
func (d *BaiduNetdisk) linkCrack(file model.Obj, _ model.LinkArgs) (*model.Link, error) {
183
var resp DownloadResp2
184
param := map[string]string{
185
"target": fmt.Sprintf("[\"%s\"]", file.GetPath()),
186
"dlink": "1",
187
"web": "5",
188
"origin": "dlna",
189
}
190
_, err := d.request("https://pan.baidu.com/api/filemetas", http.MethodGet, func(req *resty.Request) {
191
req.SetQueryParams(param)
192
}, &resp)
193
if err != nil {
194
return nil, err
195
}
196
197
return &model.Link{
198
URL: resp.Info[0].Dlink,
199
Header: http.Header{
200
"User-Agent": []string{d.CustomCrackUA},
201
},
202
}, nil
203
}
204
205
func (d *BaiduNetdisk) linkCrackVideo(file model.Obj, _ model.LinkArgs) (*model.Link, error) {
206
param := map[string]string{
207
"type": "VideoURL",
208
"path": fmt.Sprintf("%s", file.GetPath()),
209
"fs_id": file.GetID(),
210
"devuid": "0%1",
211
"clienttype": "1",
212
"channel": "android_15_25010PN30C_bd-netdisk_1523a",
213
"nom3u8": "1",
214
"dlink": "1",
215
"media": "1",
216
"origin": "dlna",
217
}
218
resp, err := d.request("https://pan.baidu.com/api/mediainfo", http.MethodGet, func(req *resty.Request) {
219
req.SetQueryParams(param)
220
}, nil)
221
if err != nil {
222
return nil, err
223
}
224
225
return &model.Link{
226
URL: utils.Json.Get(resp, "info", "dlink").ToString(),
227
Header: http.Header{
228
"User-Agent": []string{d.CustomCrackUA},
229
},
230
}, nil
231
}
232
233
func (d *BaiduNetdisk) manage(opera string, filelist any) ([]byte, error) {
234
params := map[string]string{
235
"method": "filemanager",
236
"opera": opera,
237
}
238
marshal, _ := utils.Json.MarshalToString(filelist)
239
return d.postForm("/xpan/file", params, map[string]string{
240
"async": "0",
241
"filelist": marshal,
242
"ondup": "fail",
243
}, nil)
244
}
245
246
func (d *BaiduNetdisk) create(path string, size int64, isdir int, uploadid, block_list string, resp any, mtime, ctime int64) ([]byte, error) {
247
params := map[string]string{
248
"method": "create",
249
}
250
form := map[string]string{
251
"path": path,
252
"size": strconv.FormatInt(size, 10),
253
"isdir": strconv.Itoa(isdir),
254
"rtype": "3",
255
}
256
if mtime != 0 && ctime != 0 {
257
joinTime(form, ctime, mtime)
258
}
259
260
if uploadid != "" {
261
form["uploadid"] = uploadid
262
}
263
if block_list != "" {
264
form["block_list"] = block_list
265
}
266
return d.postForm("/xpan/file", params, form, resp)
267
}
268
269
func joinTime(form map[string]string, ctime, mtime int64) {
270
form["local_mtime"] = strconv.FormatInt(mtime, 10)
271
form["local_ctime"] = strconv.FormatInt(ctime, 10)
272
}
273
274
const (
275
DefaultSliceSize int64 = 4 * utils.MB
276
VipSliceSize int64 = 16 * utils.MB
277
SVipSliceSize int64 = 32 * utils.MB
278
279
MaxSliceNum = 2048 // 文档写的是 1024/没写 ,但实际测试是 2048
280
SliceStep int64 = 1 * utils.MB
281
)
282
283
func (d *BaiduNetdisk) getSliceSize(filesize int64) int64 {
284
// 非会员固定为 4MB
285
if d.vipType == 0 {
286
if d.CustomUploadPartSize != 0 {
287
log.Warnf("CustomUploadPartSize is not supported for non-vip user, use DefaultSliceSize")
288
}
289
if filesize > MaxSliceNum*DefaultSliceSize {
290
log.Warnf("File size(%d) is too large, may cause upload failure", filesize)
291
}
292
293
return DefaultSliceSize
294
}
295
296
if d.CustomUploadPartSize != 0 {
297
if d.CustomUploadPartSize < DefaultSliceSize {
298
log.Warnf("CustomUploadPartSize(%d) is less than DefaultSliceSize(%d), use DefaultSliceSize", d.CustomUploadPartSize, DefaultSliceSize)
299
return DefaultSliceSize
300
}
301
302
if d.vipType == 1 && d.CustomUploadPartSize > VipSliceSize {
303
log.Warnf("CustomUploadPartSize(%d) is greater than VipSliceSize(%d), use VipSliceSize", d.CustomUploadPartSize, VipSliceSize)
304
return VipSliceSize
305
}
306
307
if d.vipType == 2 && d.CustomUploadPartSize > SVipSliceSize {
308
log.Warnf("CustomUploadPartSize(%d) is greater than SVipSliceSize(%d), use SVipSliceSize", d.CustomUploadPartSize, SVipSliceSize)
309
return SVipSliceSize
310
}
311
312
return d.CustomUploadPartSize
313
}
314
315
maxSliceSize := DefaultSliceSize
316
317
switch d.vipType {
318
case 1:
319
maxSliceSize = VipSliceSize
320
case 2:
321
maxSliceSize = SVipSliceSize
322
}
323
324
// upload on low bandwidth
325
if d.LowBandwithUploadMode {
326
size := DefaultSliceSize
327
328
for size <= maxSliceSize {
329
if filesize <= MaxSliceNum*size {
330
return size
331
}
332
333
size += SliceStep
334
}
335
}
336
337
if filesize > MaxSliceNum*maxSliceSize {
338
log.Warnf("File size(%d) is too large, may cause upload failure", filesize)
339
}
340
341
return maxSliceSize
342
}
343
344
// func encodeURIComponent(str string) string {
345
// r := url.QueryEscape(str)
346
// r = strings.ReplaceAll(r, "+", "%20")
347
// return r
348
// }
349
350
func DecryptMd5(encryptMd5 string) string {
351
if _, err := hex.DecodeString(encryptMd5); err == nil {
352
return encryptMd5
353
}
354
355
var out strings.Builder
356
out.Grow(len(encryptMd5))
357
for i, n := 0, int64(0); i < len(encryptMd5); i++ {
358
if i == 9 {
359
n = int64(unicode.ToLower(rune(encryptMd5[i])) - 'g')
360
} else {
361
n, _ = strconv.ParseInt(encryptMd5[i:i+1], 16, 64)
362
}
363
out.WriteString(strconv.FormatInt(n^int64(15&i), 16))
364
}
365
366
encryptMd5 = out.String()
367
return encryptMd5[8:16] + encryptMd5[:8] + encryptMd5[24:32] + encryptMd5[16:24]
368
}
369
370
func EncryptMd5(originalMd5 string) string {
371
reversed := originalMd5[8:16] + originalMd5[:8] + originalMd5[24:32] + originalMd5[16:24]
372
373
var out strings.Builder
374
out.Grow(len(reversed))
375
for i, n := 0, int64(0); i < len(reversed); i++ {
376
n, _ = strconv.ParseInt(reversed[i:i+1], 16, 64)
377
n ^= int64(15 & i)
378
if i == 9 {
379
out.WriteRune(rune(n) + 'g')
380
} else {
381
out.WriteString(strconv.FormatInt(n, 16))
382
}
383
}
384
return out.String()
385
}
386
387