Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/lark/driver.go
1986 views
1
package lark
2
3
import (
4
"context"
5
"errors"
6
"fmt"
7
"io"
8
"net/http"
9
"strconv"
10
"strings"
11
"time"
12
13
"github.com/alist-org/alist/v3/internal/driver"
14
"github.com/alist-org/alist/v3/internal/errs"
15
"github.com/alist-org/alist/v3/internal/model"
16
lark "github.com/larksuite/oapi-sdk-go/v3"
17
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
18
larkdrive "github.com/larksuite/oapi-sdk-go/v3/service/drive/v1"
19
"golang.org/x/time/rate"
20
)
21
22
type Lark struct {
23
model.Storage
24
Addition
25
26
client *lark.Client
27
rootFolderToken string
28
}
29
30
func (c *Lark) Config() driver.Config {
31
return config
32
}
33
34
func (c *Lark) GetAddition() driver.Additional {
35
return &c.Addition
36
}
37
38
func (c *Lark) Init(ctx context.Context) error {
39
c.client = lark.NewClient(c.AppId, c.AppSecret, lark.WithTokenCache(newTokenCache()))
40
41
paths := strings.Split(c.RootFolderPath, "/")
42
token := ""
43
44
var ok bool
45
var file *larkdrive.File
46
for _, p := range paths {
47
if p == "" {
48
token = ""
49
continue
50
}
51
52
resp, err := c.client.Drive.File.ListByIterator(ctx, larkdrive.NewListFileReqBuilder().FolderToken(token).Build())
53
if err != nil {
54
return err
55
}
56
57
for {
58
ok, file, err = resp.Next()
59
if !ok {
60
return errs.ObjectNotFound
61
}
62
63
if err != nil {
64
return err
65
}
66
67
if *file.Type == "folder" && *file.Name == p {
68
token = *file.Token
69
break
70
}
71
}
72
}
73
74
c.rootFolderToken = token
75
76
return nil
77
}
78
79
func (c *Lark) Drop(ctx context.Context) error {
80
return nil
81
}
82
83
func (c *Lark) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
84
token, ok := c.getObjToken(ctx, dir.GetPath())
85
if !ok {
86
return nil, errs.ObjectNotFound
87
}
88
89
if token == emptyFolderToken {
90
return nil, nil
91
}
92
93
resp, err := c.client.Drive.File.ListByIterator(ctx, larkdrive.NewListFileReqBuilder().FolderToken(token).Build())
94
if err != nil {
95
return nil, err
96
}
97
98
ok = false
99
var file *larkdrive.File
100
var res []model.Obj
101
102
for {
103
ok, file, err = resp.Next()
104
if !ok {
105
break
106
}
107
108
if err != nil {
109
return nil, err
110
}
111
112
modifiedUnix, _ := strconv.ParseInt(*file.ModifiedTime, 10, 64)
113
createdUnix, _ := strconv.ParseInt(*file.CreatedTime, 10, 64)
114
115
f := model.Object{
116
ID: *file.Token,
117
Path: strings.Join([]string{c.RootFolderPath, dir.GetPath(), *file.Name}, "/"),
118
Name: *file.Name,
119
Size: 0,
120
Modified: time.Unix(modifiedUnix, 0),
121
Ctime: time.Unix(createdUnix, 0),
122
IsFolder: *file.Type == "folder",
123
}
124
res = append(res, &f)
125
}
126
127
return res, nil
128
}
129
130
func (c *Lark) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
131
token, ok := c.getObjToken(ctx, file.GetPath())
132
if !ok {
133
return nil, errs.ObjectNotFound
134
}
135
136
resp, err := c.client.GetTenantAccessTokenBySelfBuiltApp(ctx, &larkcore.SelfBuiltTenantAccessTokenReq{
137
AppID: c.AppId,
138
AppSecret: c.AppSecret,
139
})
140
141
if err != nil {
142
return nil, err
143
}
144
145
if !c.ExternalMode {
146
accessToken := resp.TenantAccessToken
147
148
url := fmt.Sprintf("https://open.feishu.cn/open-apis/drive/v1/files/%s/download", token)
149
150
req, err := http.NewRequest(http.MethodGet, url, nil)
151
if err != nil {
152
return nil, err
153
}
154
155
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
156
req.Header.Set("Range", "bytes=0-1")
157
158
ar, err := http.DefaultClient.Do(req)
159
if err != nil {
160
return nil, err
161
}
162
163
if ar.StatusCode != http.StatusPartialContent {
164
return nil, errors.New("failed to get download link")
165
}
166
167
return &model.Link{
168
URL: url,
169
Header: http.Header{
170
"Authorization": []string{fmt.Sprintf("Bearer %s", accessToken)},
171
},
172
}, nil
173
} else {
174
url := strings.Join([]string{c.TenantUrlPrefix, "file", token}, "/")
175
176
return &model.Link{
177
URL: url,
178
}, nil
179
}
180
}
181
182
func (c *Lark) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
183
token, ok := c.getObjToken(ctx, parentDir.GetPath())
184
if !ok {
185
return nil, errs.ObjectNotFound
186
}
187
188
body, err := larkdrive.NewCreateFolderFilePathReqBodyBuilder().FolderToken(token).Name(dirName).Build()
189
if err != nil {
190
return nil, err
191
}
192
193
resp, err := c.client.Drive.File.CreateFolder(ctx,
194
larkdrive.NewCreateFolderFileReqBuilder().Body(body).Build())
195
if err != nil {
196
return nil, err
197
}
198
199
if !resp.Success() {
200
return nil, errors.New(resp.Error())
201
}
202
203
return &model.Object{
204
ID: *resp.Data.Token,
205
Path: strings.Join([]string{c.RootFolderPath, parentDir.GetPath(), dirName}, "/"),
206
Name: dirName,
207
Size: 0,
208
IsFolder: true,
209
}, nil
210
}
211
212
func (c *Lark) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
213
srcToken, ok := c.getObjToken(ctx, srcObj.GetPath())
214
if !ok {
215
return nil, errs.ObjectNotFound
216
}
217
218
dstDirToken, ok := c.getObjToken(ctx, dstDir.GetPath())
219
if !ok {
220
return nil, errs.ObjectNotFound
221
}
222
223
req := larkdrive.NewMoveFileReqBuilder().
224
Body(larkdrive.NewMoveFileReqBodyBuilder().
225
Type("file").
226
FolderToken(dstDirToken).
227
Build()).FileToken(srcToken).
228
Build()
229
230
// 发起请求
231
resp, err := c.client.Drive.File.Move(ctx, req)
232
if err != nil {
233
return nil, err
234
}
235
236
if !resp.Success() {
237
return nil, errors.New(resp.Error())
238
}
239
240
return nil, nil
241
}
242
243
func (c *Lark) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
244
// TODO rename obj, optional
245
return nil, errs.NotImplement
246
}
247
248
func (c *Lark) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
249
srcToken, ok := c.getObjToken(ctx, srcObj.GetPath())
250
if !ok {
251
return nil, errs.ObjectNotFound
252
}
253
254
dstDirToken, ok := c.getObjToken(ctx, dstDir.GetPath())
255
if !ok {
256
return nil, errs.ObjectNotFound
257
}
258
259
req := larkdrive.NewCopyFileReqBuilder().
260
Body(larkdrive.NewCopyFileReqBodyBuilder().
261
Name(srcObj.GetName()).
262
Type("file").
263
FolderToken(dstDirToken).
264
Build()).FileToken(srcToken).
265
Build()
266
267
// 发起请求
268
resp, err := c.client.Drive.File.Copy(ctx, req)
269
if err != nil {
270
return nil, err
271
}
272
273
if !resp.Success() {
274
return nil, errors.New(resp.Error())
275
}
276
277
return nil, nil
278
}
279
280
func (c *Lark) Remove(ctx context.Context, obj model.Obj) error {
281
token, ok := c.getObjToken(ctx, obj.GetPath())
282
if !ok {
283
return errs.ObjectNotFound
284
}
285
286
req := larkdrive.NewDeleteFileReqBuilder().
287
FileToken(token).
288
Type("file").
289
Build()
290
291
// 发起请求
292
resp, err := c.client.Drive.File.Delete(ctx, req)
293
if err != nil {
294
return err
295
}
296
297
if !resp.Success() {
298
return errors.New(resp.Error())
299
}
300
301
return nil
302
}
303
304
var uploadLimit = rate.NewLimiter(rate.Every(time.Second), 5)
305
306
func (c *Lark) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
307
token, ok := c.getObjToken(ctx, dstDir.GetPath())
308
if !ok {
309
return nil, errs.ObjectNotFound
310
}
311
312
// prepare
313
req := larkdrive.NewUploadPrepareFileReqBuilder().
314
FileUploadInfo(larkdrive.NewFileUploadInfoBuilder().
315
FileName(stream.GetName()).
316
ParentType(`explorer`).
317
ParentNode(token).
318
Size(int(stream.GetSize())).
319
Build()).
320
Build()
321
322
// 发起请求
323
err := uploadLimit.Wait(ctx)
324
if err != nil {
325
return nil, err
326
}
327
resp, err := c.client.Drive.File.UploadPrepare(ctx, req)
328
if err != nil {
329
return nil, err
330
}
331
332
if !resp.Success() {
333
return nil, errors.New(resp.Error())
334
}
335
336
uploadId := *resp.Data.UploadId
337
blockSize := *resp.Data.BlockSize
338
blockCount := *resp.Data.BlockNum
339
340
// upload
341
for i := 0; i < blockCount; i++ {
342
length := int64(blockSize)
343
if i == blockCount-1 {
344
length = stream.GetSize() - int64(i*blockSize)
345
}
346
347
reader := driver.NewLimitedUploadStream(ctx, io.LimitReader(stream, length))
348
349
req := larkdrive.NewUploadPartFileReqBuilder().
350
Body(larkdrive.NewUploadPartFileReqBodyBuilder().
351
UploadId(uploadId).
352
Seq(i).
353
Size(int(length)).
354
File(reader).
355
Build()).
356
Build()
357
358
// 发起请求
359
err = uploadLimit.Wait(ctx)
360
if err != nil {
361
return nil, err
362
}
363
resp, err := c.client.Drive.File.UploadPart(ctx, req)
364
365
if err != nil {
366
return nil, err
367
}
368
369
if !resp.Success() {
370
return nil, errors.New(resp.Error())
371
}
372
373
up(float64(i) / float64(blockCount))
374
}
375
376
//close
377
closeReq := larkdrive.NewUploadFinishFileReqBuilder().
378
Body(larkdrive.NewUploadFinishFileReqBodyBuilder().
379
UploadId(uploadId).
380
BlockNum(blockCount).
381
Build()).
382
Build()
383
384
// 发起请求
385
closeResp, err := c.client.Drive.File.UploadFinish(ctx, closeReq)
386
if err != nil {
387
return nil, err
388
}
389
390
if !closeResp.Success() {
391
return nil, errors.New(closeResp.Error())
392
}
393
394
return &model.Object{
395
ID: *closeResp.Data.FileToken,
396
}, nil
397
}
398
399
//func (d *Lark) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
400
// return nil, errs.NotSupport
401
//}
402
403
var _ driver.Driver = (*Lark)(nil)
404
405