Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/doubao_new/driver.go
2325 views
1
package doubao_new
2
3
import (
4
"bytes"
5
"context"
6
"crypto/sha256"
7
"encoding/base64"
8
"encoding/json"
9
"errors"
10
"fmt"
11
"io"
12
"net/http"
13
"net/url"
14
"sort"
15
"strconv"
16
"time"
17
18
"github.com/alist-org/alist/v3/drivers/base"
19
"github.com/alist-org/alist/v3/internal/driver"
20
"github.com/alist-org/alist/v3/internal/errs"
21
"github.com/alist-org/alist/v3/internal/model"
22
)
23
24
type DoubaoNew struct {
25
model.Storage
26
Addition
27
TtLogid string
28
}
29
30
func (d *DoubaoNew) Config() driver.Config {
31
return config
32
}
33
34
func (d *DoubaoNew) GetAddition() driver.Additional {
35
return &d.Addition
36
}
37
38
func (d *DoubaoNew) Init(ctx context.Context) error {
39
// TODO login / refresh token
40
//op.MustSaveDriverStorage(d)
41
return nil
42
}
43
44
func (d *DoubaoNew) Drop(ctx context.Context) error {
45
return nil
46
}
47
48
func (d *DoubaoNew) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
49
nodes, err := d.listAllChildren(ctx, dir.GetID())
50
if err != nil {
51
return nil, err
52
}
53
54
objs := make([]model.Obj, 0, len(nodes))
55
for _, node := range nodes {
56
size := parseSize(node.Extra.Size)
57
isFolder := node.Type == 0
58
obj := &Object{
59
Object: model.Object{
60
ID: node.NodeToken,
61
Path: dir.GetID(),
62
Name: node.Name,
63
Size: size,
64
Modified: time.Unix(node.EditTime, 0),
65
Ctime: time.Unix(node.CreateTime, 0),
66
IsFolder: isFolder,
67
},
68
ObjToken: node.ObjToken,
69
NodeType: node.NodeType,
70
ObjType: node.Type,
71
URL: node.URL,
72
}
73
objs = append(objs, obj)
74
}
75
76
return objs, nil
77
}
78
79
func (d *DoubaoNew) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
80
obj, ok := file.(*Object)
81
if !ok {
82
return nil, errors.New("unsupported object type")
83
}
84
if obj.IsFolder {
85
return nil, errs.LinkIsDir
86
}
87
if args.Type == "preview" || args.Type == "thumb" {
88
if link, err := d.previewLink(ctx, obj, args); err == nil {
89
return link, nil
90
}
91
}
92
auth := d.resolveAuthorization()
93
dpop := d.resolveDpop()
94
if auth == "" || dpop == "" {
95
return nil, errors.New("missing authorization or dpop")
96
}
97
if obj.ObjToken == "" {
98
return nil, errors.New("missing obj_token")
99
}
100
101
query := url.Values{}
102
query.Set("authorization", auth)
103
query.Set("dpop", dpop)
104
105
downloadURL := DownloadBaseURL + "/space/api/box/stream/download/all/" + obj.ObjToken + "/?" + query.Encode()
106
107
headers := http.Header{
108
"Referer": []string{"https://www.doubao.com/"},
109
"User-Agent": []string{base.UserAgent},
110
}
111
112
return &model.Link{
113
URL: downloadURL,
114
Header: headers,
115
}, nil
116
}
117
118
func (d *DoubaoNew) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
119
node, err := d.createFolder(ctx, parentDir.GetID(), dirName)
120
if err != nil {
121
return nil, err
122
}
123
return &Object{
124
Object: model.Object{
125
ID: node.NodeToken,
126
Path: parentDir.GetID(),
127
Name: node.Name,
128
Size: parseSize(node.Extra.Size),
129
Modified: time.Unix(node.EditTime, 0),
130
Ctime: time.Unix(node.CreateTime, 0),
131
IsFolder: true,
132
},
133
ObjToken: node.ObjToken,
134
NodeType: node.NodeType,
135
ObjType: node.Type,
136
URL: node.URL,
137
}, nil
138
}
139
140
func (d *DoubaoNew) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
141
if srcObj == nil {
142
return nil, errors.New("nil source object")
143
}
144
if dstDir == nil {
145
return nil, errors.New("nil destination dir")
146
}
147
srcToken := srcObj.GetID()
148
if srcToken == "" {
149
if obj, ok := srcObj.(*Object); ok {
150
srcToken = obj.ObjToken
151
}
152
}
153
if srcToken == "" {
154
return nil, errors.New("missing source token")
155
}
156
if err := d.moveObj(ctx, srcToken, dstDir.GetID()); err != nil {
157
return nil, err
158
}
159
if obj, ok := srcObj.(*Object); ok {
160
clone := *obj
161
clone.Path = dstDir.GetID()
162
return &clone, nil
163
}
164
return srcObj, nil
165
}
166
167
func (d *DoubaoNew) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
168
if srcObj == nil {
169
return nil, errors.New("nil source object")
170
}
171
if srcObj.IsDir() {
172
if err := d.renameFolder(ctx, srcObj.GetID(), newName); err != nil {
173
return nil, err
174
}
175
} else {
176
fileToken := ""
177
if obj, ok := srcObj.(*Object); ok {
178
fileToken = obj.ObjToken
179
}
180
if fileToken == "" {
181
fileToken = srcObj.GetID()
182
}
183
if err := d.renameFile(ctx, fileToken, newName); err != nil {
184
return nil, err
185
}
186
}
187
188
if obj, ok := srcObj.(*Object); ok {
189
clone := *obj
190
clone.Name = newName
191
return &clone, nil
192
}
193
return srcObj, nil
194
}
195
196
func (d *DoubaoNew) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
197
// TODO copy obj, optional
198
return nil, errs.NotImplement
199
}
200
201
func (d *DoubaoNew) Remove(ctx context.Context, obj model.Obj) error {
202
if obj == nil {
203
return errors.New("nil object")
204
}
205
token := obj.GetID()
206
if token == "" {
207
if o, ok := obj.(*Object); ok {
208
token = o.ObjToken
209
}
210
}
211
if token == "" {
212
return errors.New("missing object token")
213
}
214
return d.removeObj(ctx, []string{token})
215
}
216
217
func (d *DoubaoNew) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
218
if file == nil {
219
return nil, errors.New("nil file")
220
}
221
if file.GetSize() <= 0 {
222
return nil, errors.New("invalid file size")
223
}
224
225
uploadPrep, err := d.prepareUpload(ctx, file.GetName(), file.GetSize(), dstDir.GetID())
226
if err != nil {
227
return nil, err
228
}
229
if uploadPrep.BlockSize <= 0 {
230
return nil, errors.New("invalid block size from prepare")
231
}
232
233
tmpFile, err := file.CacheFullInTempFile()
234
if err != nil {
235
return nil, err
236
}
237
defer tmpFile.Close()
238
239
blockSize := uploadPrep.BlockSize
240
totalSize := file.GetSize()
241
numBlocks := int((totalSize + blockSize - 1) / blockSize)
242
blocks := make([]UploadBlock, 0, numBlocks)
243
blockMeta := make(map[int]UploadBlock, numBlocks)
244
245
for seq := 0; seq < numBlocks; seq++ {
246
offset := int64(seq) * blockSize
247
length := blockSize
248
if remain := totalSize - offset; remain < length {
249
length = remain
250
}
251
buf := make([]byte, int(length))
252
n, err := tmpFile.ReadAt(buf, offset)
253
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
254
return nil, err
255
}
256
buf = buf[:n]
257
sum := sha256.Sum256(buf)
258
hash := base64.StdEncoding.EncodeToString(sum[:])
259
checksum := adler32String(buf)
260
261
block := UploadBlock{
262
Hash: hash,
263
Seq: seq,
264
Size: int64(n),
265
Checksum: checksum,
266
IsUploaded: true,
267
}
268
blocks = append(blocks, block)
269
blockMeta[seq] = block
270
}
271
272
needed, err := d.uploadBlocks(ctx, uploadPrep.UploadID, blocks, "explorer")
273
if err != nil {
274
return nil, err
275
}
276
277
if len(needed.NeededUploadBlocks) > 0 {
278
sort.Slice(needed.NeededUploadBlocks, func(i, j int) bool {
279
return needed.NeededUploadBlocks[i].Seq < needed.NeededUploadBlocks[j].Seq
280
})
281
const maxMergeBlockCount = 20
282
var (
283
groupSeqs []int
284
groupChecksums []string
285
groupSizes []int64
286
groupRealSize int64
287
groupExpectSum int64
288
groupBuf bytes.Buffer
289
uploadedBytes int64
290
)
291
292
flushGroup := func() error {
293
if len(groupSeqs) == 0 {
294
return nil
295
}
296
data := groupBuf.Bytes()
297
expectLen := groupExpectSum
298
if len(data) > 0 {
299
headLen := 32
300
if len(data) < headLen {
301
headLen = len(data)
302
}
303
tailLen := 32
304
if len(data) < tailLen {
305
tailLen = len(data)
306
}
307
}
308
if int64(len(data)) != expectLen {
309
return fmt.Errorf("[doubao_new] merge blocks invalid body len: got=%d expect=%d seqs=%v", len(data), expectLen, groupSeqs)
310
}
311
mergeResp, err := d.mergeUploadBlocks(ctx, uploadPrep.UploadID, groupSeqs, groupChecksums, groupSizes, blockSize, data)
312
if err != nil {
313
return err
314
}
315
if len(mergeResp.SuccessSeqList) != len(groupSeqs) {
316
return fmt.Errorf("[doubao_new] merge blocks incomplete: %v", mergeResp.SuccessSeqList)
317
}
318
success := make(map[int]bool, len(mergeResp.SuccessSeqList))
319
for _, seq := range mergeResp.SuccessSeqList {
320
success[seq] = true
321
}
322
for _, seq := range groupSeqs {
323
if !success[seq] {
324
return fmt.Errorf("[doubao_new] merge blocks missing seq %d", seq)
325
}
326
}
327
328
uploadedBytes += groupRealSize
329
groupSeqs = groupSeqs[:0]
330
groupChecksums = groupChecksums[:0]
331
groupSizes = groupSizes[:0]
332
groupRealSize = 0
333
groupExpectSum = 0
334
groupBuf.Reset()
335
if up != nil {
336
percent := float64(uploadedBytes) / float64(totalSize) * 100
337
up(percent)
338
}
339
return nil
340
}
341
342
for _, item := range needed.NeededUploadBlocks {
343
if _, ok := blockMeta[item.Seq]; !ok {
344
return nil, fmt.Errorf("[doubao_new] missing block meta for seq %d", item.Seq)
345
}
346
if item.Size <= 0 {
347
return nil, fmt.Errorf("[doubao_new] invalid block size from needed list: seq=%d size=%d", item.Seq, item.Size)
348
}
349
offset := int64(item.Seq) * blockSize
350
buf := make([]byte, int(item.Size))
351
n, err := tmpFile.ReadAt(buf, offset)
352
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
353
return nil, err
354
}
355
if n != len(buf) {
356
return nil, fmt.Errorf("[doubao_new] short read: seq=%d want=%d got=%d", item.Seq, len(buf), n)
357
}
358
buf = buf[:n]
359
realAdler := adler32String(buf)
360
if realAdler != item.Checksum {
361
return nil, fmt.Errorf("[doubao_new] block checksum mismatch: seq=%d offset=%d adler32=%s step2=%s", item.Seq, offset, realAdler, item.Checksum)
362
}
363
payloadStart := groupBuf.Len()
364
groupBuf.Write(buf)
365
payloadEnd := groupBuf.Len()
366
payloadAdler := adler32String(groupBuf.Bytes()[payloadStart:payloadEnd])
367
if payloadAdler != item.Checksum {
368
return nil, fmt.Errorf("[doubao_new] payload checksum mismatch: seq=%d start=%d end=%d adler32=%s step2=%s", item.Seq, payloadStart, payloadEnd, payloadAdler, item.Checksum)
369
}
370
groupSeqs = append(groupSeqs, item.Seq)
371
groupChecksums = append(groupChecksums, item.Checksum)
372
groupSizes = append(groupSizes, item.Size)
373
groupRealSize += int64(n)
374
groupExpectSum += item.Size
375
if len(groupSeqs) >= maxMergeBlockCount {
376
if err := flushGroup(); err != nil {
377
return nil, err
378
}
379
}
380
}
381
382
if err := flushGroup(); err != nil {
383
return nil, err
384
}
385
if up != nil {
386
up(100)
387
}
388
} else if up != nil {
389
up(100)
390
}
391
392
numBlocksFinish := uploadPrep.NumBlocks
393
if numBlocksFinish <= 0 {
394
numBlocksFinish = numBlocks
395
}
396
finish, err := d.finishUpload(ctx, uploadPrep.UploadID, numBlocksFinish, "explorer")
397
if err != nil {
398
return nil, err
399
}
400
401
nodeToken := finish.Extra.NodeToken
402
if nodeToken == "" {
403
nodeToken = finish.FileToken
404
}
405
now := time.Now()
406
return &Object{
407
Object: model.Object{
408
ID: nodeToken,
409
Path: dstDir.GetID(),
410
Name: file.GetName(),
411
Size: file.GetSize(),
412
Modified: now,
413
Ctime: now,
414
IsFolder: false,
415
},
416
ObjToken: finish.FileToken,
417
}, nil
418
}
419
420
func (d *DoubaoNew) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
421
// TODO get archive file meta-info, return errs.NotImplement to use an internal archive tool, optional
422
return nil, errs.NotImplement
423
}
424
425
func (d *DoubaoNew) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
426
// TODO list args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
427
return nil, errs.NotImplement
428
}
429
430
func (d *DoubaoNew) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
431
// TODO return link of file args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
432
return nil, errs.NotImplement
433
}
434
435
func (d *DoubaoNew) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) {
436
// TODO extract args.InnerPath path in the archive srcObj to the dstDir location, optional
437
// a folder with the same name as the archive file needs to be created to store the extracted results if args.PutIntoNewDir
438
// return errs.NotImplement to use an internal archive tool
439
return nil, errs.NotImplement
440
}
441
442
func (d *DoubaoNew) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
443
switch args.Method {
444
case "doubao_preview", "preview":
445
obj, ok := args.Obj.(*Object)
446
if !ok {
447
return nil, errors.New("unsupported object type")
448
}
449
info, err := d.getFileInfo(ctx, obj.ObjToken)
450
if err != nil {
451
return nil, err
452
}
453
entry, ok := info.PreviewMeta.Data["22"]
454
if !ok || entry.Status != 0 {
455
return nil, errs.NotSupport
456
}
457
458
imgExt := ".webp"
459
pageNums := 1
460
if entry.Extra != "" {
461
var extra PreviewImageExtra
462
if err := json.Unmarshal([]byte(entry.Extra), &extra); err == nil {
463
if extra.ImgExt != "" {
464
imgExt = extra.ImgExt
465
}
466
if extra.PageNums > 0 {
467
pageNums = extra.PageNums
468
}
469
}
470
}
471
472
return base.Json{
473
"version": info.Version,
474
"img_ext": imgExt,
475
"page_nums": pageNums,
476
}, nil
477
default:
478
return nil, errs.NotSupport
479
}
480
}
481
482
func (d *DoubaoNew) listAllChildren(ctx context.Context, parentToken string) ([]Node, error) {
483
nodes := make([]Node, 0, 50)
484
lastLabel := ""
485
for page := 0; page < 100; page++ {
486
data, err := d.listChildren(ctx, parentToken, lastLabel)
487
if err != nil {
488
return nil, err
489
}
490
491
if len(data.NodeList) > 0 {
492
for _, token := range data.NodeList {
493
node, ok := data.Entities.Nodes[token]
494
if !ok {
495
continue
496
}
497
nodes = append(nodes, node)
498
}
499
} else {
500
for _, node := range data.Entities.Nodes {
501
nodes = append(nodes, node)
502
}
503
}
504
505
if !data.HasMore || data.LastLabel == "" || data.LastLabel == lastLabel {
506
break
507
}
508
lastLabel = data.LastLabel
509
}
510
511
if len(nodes) == 0 {
512
return nil, nil
513
}
514
return nodes, nil
515
}
516
517
func (d *DoubaoNew) previewLink(ctx context.Context, obj *Object, args model.LinkArgs) (*model.Link, error) {
518
auth := d.resolveAuthorization()
519
dpop := d.resolveDpop()
520
if auth == "" || dpop == "" {
521
return nil, errors.New("missing authorization or dpop")
522
}
523
if obj.ObjToken == "" {
524
return nil, errors.New("missing obj_token")
525
}
526
info, err := d.getFileInfo(ctx, obj.ObjToken)
527
if err != nil {
528
return nil, err
529
}
530
531
entry, ok := info.PreviewMeta.Data["22"]
532
if !ok || entry.Status != 0 {
533
return nil, errors.New("preview not available")
534
}
535
536
subID := ""
537
pageIndex := 0
538
if args.HttpReq != nil {
539
query := args.HttpReq.URL.Query()
540
if v := query.Get("sub_id"); v != "" {
541
subID = v
542
} else if v := query.Get("page"); v != "" {
543
if p, err := strconv.Atoi(v); err == nil && p >= 0 {
544
pageIndex = p
545
}
546
}
547
}
548
if subID == "" {
549
imgExt := ".webp"
550
pageNums := 0
551
if entry.Extra != "" {
552
var extra PreviewImageExtra
553
if err := json.Unmarshal([]byte(entry.Extra), &extra); err == nil {
554
if extra.ImgExt != "" {
555
imgExt = extra.ImgExt
556
}
557
pageNums = extra.PageNums
558
}
559
}
560
if pageNums > 0 && pageIndex >= pageNums {
561
pageIndex = pageNums - 1
562
}
563
subID = fmt.Sprintf("img_%d%s", pageIndex, imgExt)
564
}
565
566
query := url.Values{}
567
query.Set("preview_type", "22")
568
query.Set("sub_id", subID)
569
if info.Version != "" {
570
query.Set("version", info.Version)
571
}
572
previewURL := fmt.Sprintf("%s/space/api/box/stream/download/preview_sub/%s?%s", BaseURL, obj.ObjToken, query.Encode())
573
574
headers := http.Header{
575
"Referer": []string{"https://www.doubao.com/"},
576
"User-Agent": []string{base.UserAgent},
577
"Authorization": []string{auth},
578
"Dpop": []string{dpop},
579
}
580
581
return &model.Link{
582
URL: previewURL,
583
Header: headers,
584
}, nil
585
}
586
587
func parseSize(size string) int64 {
588
if size == "" {
589
return 0
590
}
591
val, err := strconv.ParseInt(size, 10, 64)
592
if err != nil {
593
return 0
594
}
595
return val
596
}
597
598
var _ driver.Driver = (*DoubaoNew)(nil)
599
600