Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/baidu_photo/utils.go
1987 views
1
package baiduphoto
2
3
import (
4
"context"
5
"encoding/hex"
6
"fmt"
7
"net/http"
8
"strconv"
9
"strings"
10
"unicode"
11
12
"github.com/alist-org/alist/v3/drivers/base"
13
"github.com/alist-org/alist/v3/internal/model"
14
"github.com/alist-org/alist/v3/pkg/utils"
15
"github.com/go-resty/resty/v2"
16
)
17
18
const (
19
API_URL = "https://photo.baidu.com/youai"
20
USER_API_URL = API_URL + "/user/v1"
21
ALBUM_API_URL = API_URL + "/album/v1"
22
FILE_API_URL_V1 = API_URL + "/file/v1"
23
FILE_API_URL_V2 = API_URL + "/file/v2"
24
)
25
26
func (d *BaiduPhoto) Request(client *resty.Client, furl string, method string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
27
req := client.R().
28
// SetQueryParam("access_token", d.AccessToken)
29
SetHeader("Cookie", d.Cookie)
30
if callback != nil {
31
callback(req)
32
}
33
if resp != nil {
34
req.SetResult(resp)
35
}
36
res, err := req.Execute(method, furl)
37
if err != nil {
38
return nil, err
39
}
40
41
erron := utils.Json.Get(res.Body(), "errno").ToInt()
42
switch erron {
43
case 0:
44
break
45
case 50805:
46
return nil, fmt.Errorf("you have joined album")
47
case 50820:
48
return nil, fmt.Errorf("no shared albums found")
49
case 50100:
50
return nil, fmt.Errorf("illegal title, only supports 50 characters")
51
// case -6:
52
// if err = d.refreshToken(); err != nil {
53
// return nil, err
54
// }
55
default:
56
return nil, fmt.Errorf("errno: %d, refer to https://photo.baidu.com/union/doc", erron)
57
}
58
return res, nil
59
}
60
61
//func (d *BaiduPhoto) Request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
62
// res, err := d.request(furl, method, callback, resp)
63
// if err != nil {
64
// return nil, err
65
// }
66
// return res.Body(), nil
67
//}
68
69
// func (d *BaiduPhoto) refreshToken() error {
70
// u := "https://openapi.baidu.com/oauth/2.0/token"
71
// var resp base.TokenResp
72
// var e TokenErrResp
73
// _, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
74
// "grant_type": "refresh_token",
75
// "refresh_token": d.RefreshToken,
76
// "client_id": d.ClientID,
77
// "client_secret": d.ClientSecret,
78
// }).Get(u)
79
// if err != nil {
80
// return err
81
// }
82
// if e.ErrorMsg != "" {
83
// return &e
84
// }
85
// if resp.RefreshToken == "" {
86
// return errs.EmptyToken
87
// }
88
// d.AccessToken, d.RefreshToken = resp.AccessToken, resp.RefreshToken
89
// op.MustSaveDriverStorage(d)
90
// return nil
91
// }
92
93
func (d *BaiduPhoto) Get(furl string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
94
return d.Request(base.RestyClient, furl, http.MethodGet, callback, resp)
95
}
96
97
func (d *BaiduPhoto) Post(furl string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
98
return d.Request(base.RestyClient, furl, http.MethodPost, callback, resp)
99
}
100
101
// 获取所有文件
102
func (d *BaiduPhoto) GetAllFile(ctx context.Context) (files []File, err error) {
103
var cursor string
104
for {
105
var resp FileListResp
106
_, err = d.Get(FILE_API_URL_V1+"/list", func(r *resty.Request) {
107
r.SetContext(ctx)
108
r.SetQueryParams(map[string]string{
109
"need_thumbnail": "1",
110
"need_filter_hidden": "0",
111
"cursor": cursor,
112
})
113
}, &resp)
114
if err != nil {
115
return
116
}
117
118
files = append(files, resp.List...)
119
if !resp.HasNextPage() {
120
return
121
}
122
cursor = resp.Cursor
123
}
124
}
125
126
// 删除根文件
127
func (d *BaiduPhoto) DeleteFile(ctx context.Context, file *File) error {
128
_, err := d.Get(FILE_API_URL_V1+"/delete", func(req *resty.Request) {
129
req.SetContext(ctx)
130
req.SetQueryParams(map[string]string{
131
"fsid_list": fmt.Sprintf("[%d]", file.Fsid),
132
})
133
}, nil)
134
return err
135
}
136
137
// 获取所有相册
138
func (d *BaiduPhoto) GetAllAlbum(ctx context.Context) (albums []Album, err error) {
139
var cursor string
140
for {
141
var resp AlbumListResp
142
_, err = d.Get(ALBUM_API_URL+"/list", func(r *resty.Request) {
143
r.SetContext(ctx)
144
r.SetQueryParams(map[string]string{
145
"need_amount": "1",
146
"limit": "100",
147
"cursor": cursor,
148
})
149
}, &resp)
150
if err != nil {
151
return
152
}
153
if albums == nil {
154
albums = make([]Album, 0, resp.TotalCount)
155
}
156
157
cursor = resp.Cursor
158
albums = append(albums, resp.List...)
159
160
if !resp.HasNextPage() {
161
return
162
}
163
}
164
}
165
166
// 获取相册中所有文件
167
func (d *BaiduPhoto) GetAllAlbumFile(ctx context.Context, album *Album, passwd string) (files []AlbumFile, err error) {
168
var cursor string
169
for {
170
var resp AlbumFileListResp
171
_, err = d.Get(ALBUM_API_URL+"/listfile", func(r *resty.Request) {
172
r.SetContext(ctx)
173
r.SetQueryParams(map[string]string{
174
"album_id": album.AlbumID,
175
"need_amount": "1",
176
"limit": "1000",
177
"passwd": passwd,
178
"cursor": cursor,
179
})
180
}, &resp)
181
if err != nil {
182
return
183
}
184
if files == nil {
185
files = make([]AlbumFile, 0, resp.TotalCount)
186
}
187
188
cursor = resp.Cursor
189
files = append(files, resp.List...)
190
191
if !resp.HasNextPage() {
192
return
193
}
194
}
195
}
196
197
// 创建相册
198
func (d *BaiduPhoto) CreateAlbum(ctx context.Context, name string) (*Album, error) {
199
var resp JoinOrCreateAlbumResp
200
_, err := d.Post(ALBUM_API_URL+"/create", func(r *resty.Request) {
201
r.SetContext(ctx).SetResult(&resp)
202
r.SetQueryParams(map[string]string{
203
"title": name,
204
"tid": getTid(),
205
"source": "0",
206
})
207
}, nil)
208
if err != nil {
209
return nil, err
210
}
211
return d.GetAlbumDetail(ctx, resp.AlbumID)
212
}
213
214
// 相册改名
215
func (d *BaiduPhoto) SetAlbumName(ctx context.Context, album *Album, name string) (*Album, error) {
216
_, err := d.Post(ALBUM_API_URL+"/settitle", func(r *resty.Request) {
217
r.SetContext(ctx)
218
r.SetFormData(map[string]string{
219
"title": name,
220
"album_id": album.AlbumID,
221
"tid": fmt.Sprint(album.Tid),
222
})
223
}, nil)
224
if err != nil {
225
return nil, err
226
}
227
return renameAlbum(album, name), nil
228
}
229
230
// 删除相册
231
func (d *BaiduPhoto) DeleteAlbum(ctx context.Context, album *Album) error {
232
_, err := d.Post(ALBUM_API_URL+"/delete", func(r *resty.Request) {
233
r.SetContext(ctx)
234
r.SetFormData(map[string]string{
235
"album_id": album.AlbumID,
236
"tid": fmt.Sprint(album.Tid),
237
"delete_origin_image": BoolToIntStr(d.DeleteOrigin), // 是否删除原图 0 不删除 1 删除
238
})
239
}, nil)
240
return err
241
}
242
243
// 删除相册文件
244
func (d *BaiduPhoto) DeleteAlbumFile(ctx context.Context, file *AlbumFile) error {
245
_, err := d.Post(ALBUM_API_URL+"/delfile", func(r *resty.Request) {
246
r.SetContext(ctx)
247
r.SetFormData(map[string]string{
248
"album_id": fmt.Sprint(file.AlbumID),
249
"tid": fmt.Sprint(file.Tid),
250
"list": fmt.Sprintf(`[{"fsid":%d,"uk":%d}]`, file.Fsid, file.Uk),
251
"del_origin": BoolToIntStr(d.DeleteOrigin), // 是否删除原图 0 不删除 1 删除
252
})
253
}, nil)
254
return err
255
}
256
257
// 增加相册文件
258
func (d *BaiduPhoto) AddAlbumFile(ctx context.Context, album *Album, file *File) (*AlbumFile, error) {
259
_, err := d.Get(ALBUM_API_URL+"/addfile", func(r *resty.Request) {
260
r.SetContext(ctx)
261
r.SetQueryParams(map[string]string{
262
"album_id": fmt.Sprint(album.AlbumID),
263
"tid": fmt.Sprint(album.Tid),
264
"list": fsidsFormatNotUk(file.Fsid),
265
})
266
}, nil)
267
if err != nil {
268
return nil, err
269
}
270
return moveFileToAlbumFile(file, album, d.Uk), nil
271
}
272
273
// 保存相册文件为根文件
274
func (d *BaiduPhoto) CopyAlbumFile(ctx context.Context, file *AlbumFile) (*File, error) {
275
var resp CopyFileResp
276
_, err := d.Post(ALBUM_API_URL+"/copyfile", func(r *resty.Request) {
277
r.SetContext(ctx)
278
r.SetFormData(map[string]string{
279
"album_id": file.AlbumID,
280
"tid": fmt.Sprint(file.Tid),
281
"uk": fmt.Sprint(file.Uk),
282
"list": fsidsFormatNotUk(file.Fsid),
283
})
284
r.SetResult(&resp)
285
}, nil)
286
if err != nil {
287
return nil, err
288
}
289
return copyFile(file, &resp.List[0]), nil
290
}
291
292
// 加入相册
293
func (d *BaiduPhoto) JoinAlbum(ctx context.Context, code string) (*Album, error) {
294
var resp InviteResp
295
_, err := d.Get(ALBUM_API_URL+"/querypcode", func(req *resty.Request) {
296
req.SetContext(ctx)
297
req.SetQueryParams(map[string]string{
298
"pcode": code,
299
"web": "1",
300
})
301
}, &resp)
302
if err != nil {
303
return nil, err
304
}
305
var resp2 JoinOrCreateAlbumResp
306
_, err = d.Get(ALBUM_API_URL+"/join", func(req *resty.Request) {
307
req.SetContext(ctx)
308
req.SetQueryParams(map[string]string{
309
"invite_code": resp.Pdata.InviteCode,
310
})
311
}, &resp2)
312
if err != nil {
313
return nil, err
314
}
315
return d.GetAlbumDetail(ctx, resp2.AlbumID)
316
}
317
318
// 获取相册详细信息
319
func (d *BaiduPhoto) GetAlbumDetail(ctx context.Context, albumID string) (*Album, error) {
320
var album Album
321
_, err := d.Get(ALBUM_API_URL+"/detail", func(req *resty.Request) {
322
req.SetContext(ctx).SetResult(&album)
323
req.SetQueryParams(map[string]string{
324
"album_id": albumID,
325
})
326
}, &album)
327
if err != nil {
328
return nil, err
329
}
330
return &album, nil
331
}
332
333
func (d *BaiduPhoto) linkAlbum(ctx context.Context, file *AlbumFile, args model.LinkArgs) (*model.Link, error) {
334
headers := map[string]string{
335
"User-Agent": base.UserAgent,
336
}
337
if args.Header.Get("User-Agent") != "" {
338
headers["User-Agent"] = args.Header.Get("User-Agent")
339
}
340
if !utils.IsLocalIPAddr(args.IP) {
341
headers["X-Forwarded-For"] = args.IP
342
}
343
344
resp, err := d.Request(base.NoRedirectClient, ALBUM_API_URL+"/download", http.MethodHead, func(r *resty.Request) {
345
r.SetContext(ctx)
346
r.SetHeaders(headers)
347
r.SetQueryParams(map[string]string{
348
"fsid": fmt.Sprint(file.Fsid),
349
"album_id": file.AlbumID,
350
"tid": fmt.Sprint(file.Tid),
351
"uk": fmt.Sprint(file.Uk),
352
})
353
}, nil)
354
355
if err != nil {
356
return nil, err
357
}
358
359
if resp.StatusCode() != 302 {
360
return nil, fmt.Errorf("not found 302 redirect")
361
}
362
363
location := resp.Header().Get("Location")
364
365
link := &model.Link{
366
URL: location,
367
Header: http.Header{
368
"User-Agent": []string{headers["User-Agent"]},
369
"Referer": []string{"https://photo.baidu.com/"},
370
},
371
}
372
return link, nil
373
}
374
375
func (d *BaiduPhoto) linkFile(ctx context.Context, file *File, args model.LinkArgs) (*model.Link, error) {
376
headers := map[string]string{
377
"User-Agent": base.UserAgent,
378
}
379
if args.Header.Get("User-Agent") != "" {
380
headers["User-Agent"] = args.Header.Get("User-Agent")
381
}
382
if !utils.IsLocalIPAddr(args.IP) {
383
headers["X-Forwarded-For"] = args.IP
384
}
385
386
var downloadUrl struct {
387
Dlink string `json:"dlink"`
388
}
389
_, err := d.Get(FILE_API_URL_V2+"/download", func(r *resty.Request) {
390
r.SetContext(ctx)
391
r.SetHeaders(headers)
392
r.SetQueryParams(map[string]string{
393
"fsid": fmt.Sprint(file.Fsid),
394
})
395
}, &downloadUrl)
396
397
// resp, err := d.Request(base.NoRedirectClient, FILE_API_URL_V1+"/download", http.MethodHead, func(r *resty.Request) {
398
// r.SetContext(ctx)
399
// r.SetHeaders(headers)
400
// r.SetQueryParams(map[string]string{
401
// "fsid": fmt.Sprint(file.Fsid),
402
// })
403
// }, nil)
404
405
if err != nil {
406
return nil, err
407
}
408
409
// if resp.StatusCode() != 302 {
410
// return nil, fmt.Errorf("not found 302 redirect")
411
// }
412
413
// location := resp.Header().Get("Location")
414
link := &model.Link{
415
URL: downloadUrl.Dlink,
416
Header: http.Header{
417
"User-Agent": []string{headers["User-Agent"]},
418
"Referer": []string{"https://photo.baidu.com/"},
419
},
420
}
421
return link, nil
422
}
423
424
/*func (d *BaiduPhoto) linkStreamAlbum(ctx context.Context, file *AlbumFile) (*model.Link, error) {
425
return &model.Link{
426
Header: http.Header{},
427
Writer: func(w io.Writer) error {
428
res, err := d.Get(ALBUM_API_URL+"/streaming", func(r *resty.Request) {
429
r.SetContext(ctx)
430
r.SetQueryParams(map[string]string{
431
"fsid": fmt.Sprint(file.Fsid),
432
"album_id": file.AlbumID,
433
"tid": fmt.Sprint(file.Tid),
434
"uk": fmt.Sprint(file.Uk),
435
}).SetDoNotParseResponse(true)
436
}, nil)
437
if err != nil {
438
return err
439
}
440
defer res.RawBody().Close()
441
_, err = io.Copy(w, res.RawBody())
442
return err
443
},
444
}, nil
445
}*/
446
447
/*func (d *BaiduPhoto) linkStream(ctx context.Context, file *File) (*model.Link, error) {
448
return &model.Link{
449
Header: http.Header{},
450
Writer: func(w io.Writer) error {
451
res, err := d.Get(FILE_API_URL_V1+"/streaming", func(r *resty.Request) {
452
r.SetContext(ctx)
453
r.SetQueryParams(map[string]string{
454
"fsid": fmt.Sprint(file.Fsid),
455
}).SetDoNotParseResponse(true)
456
}, nil)
457
if err != nil {
458
return err
459
}
460
defer res.RawBody().Close()
461
_, err = io.Copy(w, res.RawBody())
462
return err
463
},
464
}, nil
465
}*/
466
467
// 获取uk
468
func (d *BaiduPhoto) uInfo() (*UInfo, error) {
469
var info UInfo
470
_, err := d.Get(USER_API_URL+"/getuinfo", func(req *resty.Request) {
471
472
}, &info)
473
if err != nil {
474
return nil, err
475
}
476
return &info, nil
477
}
478
479
func (d *BaiduPhoto) getBDStoken() (string, error) {
480
var info struct {
481
Result struct {
482
Bdstoken string `json:"bdstoken"`
483
Token string `json:"token"`
484
Uk int64 `json:"uk"`
485
} `json:"result"`
486
}
487
_, err := d.Get("https://pan.baidu.com/api/gettemplatevariable?fields=[%22bdstoken%22,%22token%22,%22uk%22]", nil, &info)
488
if err != nil {
489
return "", err
490
}
491
return info.Result.Bdstoken, nil
492
}
493
494
func DecryptMd5(encryptMd5 string) string {
495
if _, err := hex.DecodeString(encryptMd5); err == nil {
496
return encryptMd5
497
}
498
499
var out strings.Builder
500
out.Grow(len(encryptMd5))
501
for i, n := 0, int64(0); i < len(encryptMd5); i++ {
502
if i == 9 {
503
n = int64(unicode.ToLower(rune(encryptMd5[i])) - 'g')
504
} else {
505
n, _ = strconv.ParseInt(encryptMd5[i:i+1], 16, 64)
506
}
507
out.WriteString(strconv.FormatInt(n^int64(15&i), 16))
508
}
509
510
encryptMd5 = out.String()
511
return encryptMd5[8:16] + encryptMd5[:8] + encryptMd5[24:32] + encryptMd5[16:24]
512
}
513
514
func EncryptMd5(originalMd5 string) string {
515
reversed := originalMd5[8:16] + originalMd5[:8] + originalMd5[24:32] + originalMd5[16:24]
516
517
var out strings.Builder
518
out.Grow(len(reversed))
519
for i, n := 0, int64(0); i < len(reversed); i++ {
520
n, _ = strconv.ParseInt(reversed[i:i+1], 16, 64)
521
n ^= int64(15 & i)
522
if i == 9 {
523
out.WriteRune(rune(n) + 'g')
524
} else {
525
out.WriteString(strconv.FormatInt(n, 16))
526
}
527
}
528
return out.String()
529
}
530
531