Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/139/util.go
1986 views
1
package _139
2
3
import (
4
"encoding/base64"
5
"errors"
6
"fmt"
7
"net/http"
8
"net/url"
9
"path"
10
"sort"
11
"strconv"
12
"strings"
13
"time"
14
15
"github.com/alist-org/alist/v3/drivers/base"
16
"github.com/alist-org/alist/v3/internal/model"
17
"github.com/alist-org/alist/v3/internal/op"
18
"github.com/alist-org/alist/v3/pkg/utils"
19
"github.com/alist-org/alist/v3/pkg/utils/random"
20
"github.com/go-resty/resty/v2"
21
jsoniter "github.com/json-iterator/go"
22
log "github.com/sirupsen/logrus"
23
)
24
25
// do others that not defined in Driver interface
26
func (d *Yun139) isFamily() bool {
27
return d.Type == "family"
28
}
29
30
func encodeURIComponent(str string) string {
31
r := url.QueryEscape(str)
32
r = strings.Replace(r, "+", "%20", -1)
33
r = strings.Replace(r, "%21", "!", -1)
34
r = strings.Replace(r, "%27", "'", -1)
35
r = strings.Replace(r, "%28", "(", -1)
36
r = strings.Replace(r, "%29", ")", -1)
37
r = strings.Replace(r, "%2A", "*", -1)
38
return r
39
}
40
41
func calSign(body, ts, randStr string) string {
42
body = encodeURIComponent(body)
43
strs := strings.Split(body, "")
44
sort.Strings(strs)
45
body = strings.Join(strs, "")
46
body = base64.StdEncoding.EncodeToString([]byte(body))
47
res := utils.GetMD5EncodeStr(body) + utils.GetMD5EncodeStr(ts+":"+randStr)
48
res = strings.ToUpper(utils.GetMD5EncodeStr(res))
49
return res
50
}
51
52
func getTime(t string) time.Time {
53
stamp, _ := time.ParseInLocation("20060102150405", t, utils.CNLoc)
54
return stamp
55
}
56
57
func (d *Yun139) refreshToken() error {
58
if d.ref != nil {
59
return d.ref.refreshToken()
60
}
61
decode, err := base64.StdEncoding.DecodeString(d.Authorization)
62
if err != nil {
63
return fmt.Errorf("authorization decode failed: %s", err)
64
}
65
decodeStr := string(decode)
66
splits := strings.Split(decodeStr, ":")
67
if len(splits) < 3 {
68
return fmt.Errorf("authorization is invalid, splits < 3")
69
}
70
d.Account = splits[1]
71
strs := strings.Split(splits[2], "|")
72
if len(strs) < 4 {
73
return fmt.Errorf("authorization is invalid, strs < 4")
74
}
75
expiration, err := strconv.ParseInt(strs[3], 10, 64)
76
if err != nil {
77
return fmt.Errorf("authorization is invalid")
78
}
79
expiration -= time.Now().UnixMilli()
80
if expiration > 1000*60*60*24*15 {
81
// Authorization有效期大于15天无需刷新
82
return nil
83
}
84
if expiration < 0 {
85
return fmt.Errorf("authorization has expired")
86
}
87
88
url := "https://aas.caiyun.feixin.10086.cn:443/tellin/authTokenRefresh.do"
89
var resp RefreshTokenResp
90
reqBody := "<root><token>" + splits[2] + "</token><account>" + splits[1] + "</account><clienttype>656</clienttype></root>"
91
_, err = base.RestyClient.R().
92
ForceContentType("application/xml").
93
SetBody(reqBody).
94
SetResult(&resp).
95
Post(url)
96
if err != nil {
97
return err
98
}
99
if resp.Return != "0" {
100
return fmt.Errorf("failed to refresh token: %s", resp.Desc)
101
}
102
d.Authorization = base64.StdEncoding.EncodeToString([]byte(splits[0] + ":" + splits[1] + ":" + resp.Token))
103
op.MustSaveDriverStorage(d)
104
return nil
105
}
106
107
func (d *Yun139) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
108
url := "https://yun.139.com" + pathname
109
req := base.RestyClient.R()
110
randStr := random.String(16)
111
ts := time.Now().Format("2006-01-02 15:04:05")
112
if callback != nil {
113
callback(req)
114
}
115
body, err := utils.Json.Marshal(req.Body)
116
if err != nil {
117
return nil, err
118
}
119
sign := calSign(string(body), ts, randStr)
120
svcType := "1"
121
if d.isFamily() {
122
svcType = "2"
123
}
124
req.SetHeaders(map[string]string{
125
"Accept": "application/json, text/plain, */*",
126
"CMS-DEVICE": "default",
127
"Authorization": "Basic " + d.getAuthorization(),
128
"mcloud-channel": "1000101",
129
"mcloud-client": "10701",
130
//"mcloud-route": "001",
131
"mcloud-sign": fmt.Sprintf("%s,%s,%s", ts, randStr, sign),
132
//"mcloud-skey":"",
133
"mcloud-version": "7.14.0",
134
"Origin": "https://yun.139.com",
135
"Referer": "https://yun.139.com/w/",
136
"x-DeviceInfo": "||9|7.14.0|chrome|120.0.0.0|||windows 10||zh-CN|||",
137
"x-huawei-channelSrc": "10000034",
138
"x-inner-ntwk": "2",
139
"x-m4c-caller": "PC",
140
"x-m4c-src": "10002",
141
"x-SvcType": svcType,
142
"Inner-Hcy-Router-Https": "1",
143
})
144
145
var e BaseResp
146
req.SetResult(&e)
147
res, err := req.Execute(method, url)
148
log.Debugln(res.String())
149
if !e.Success {
150
return nil, errors.New(e.Message)
151
}
152
if resp != nil {
153
err = utils.Json.Unmarshal(res.Body(), resp)
154
if err != nil {
155
return nil, err
156
}
157
}
158
return res.Body(), nil
159
}
160
161
func (d *Yun139) requestRoute(data interface{}, resp interface{}) ([]byte, error) {
162
url := "https://user-njs.yun.139.com/user/route/qryRoutePolicy"
163
req := base.RestyClient.R()
164
randStr := random.String(16)
165
ts := time.Now().Format("2006-01-02 15:04:05")
166
callback := func(req *resty.Request) {
167
req.SetBody(data)
168
}
169
if callback != nil {
170
callback(req)
171
}
172
body, err := utils.Json.Marshal(req.Body)
173
if err != nil {
174
return nil, err
175
}
176
sign := calSign(string(body), ts, randStr)
177
svcType := "1"
178
if d.isFamily() {
179
svcType = "2"
180
}
181
req.SetHeaders(map[string]string{
182
"Accept": "application/json, text/plain, */*",
183
"CMS-DEVICE": "default",
184
"Authorization": "Basic " + d.getAuthorization(),
185
"mcloud-channel": "1000101",
186
"mcloud-client": "10701",
187
//"mcloud-route": "001",
188
"mcloud-sign": fmt.Sprintf("%s,%s,%s", ts, randStr, sign),
189
//"mcloud-skey":"",
190
"mcloud-version": "7.14.0",
191
"Origin": "https://yun.139.com",
192
"Referer": "https://yun.139.com/w/",
193
"x-DeviceInfo": "||9|7.14.0|chrome|120.0.0.0|||windows 10||zh-CN|||",
194
"x-huawei-channelSrc": "10000034",
195
"x-inner-ntwk": "2",
196
"x-m4c-caller": "PC",
197
"x-m4c-src": "10002",
198
"x-SvcType": svcType,
199
"Inner-Hcy-Router-Https": "1",
200
})
201
202
var e BaseResp
203
req.SetResult(&e)
204
res, err := req.Execute(http.MethodPost, url)
205
log.Debugln(res.String())
206
if !e.Success {
207
return nil, errors.New(e.Message)
208
}
209
if resp != nil {
210
err = utils.Json.Unmarshal(res.Body(), resp)
211
if err != nil {
212
return nil, err
213
}
214
}
215
return res.Body(), nil
216
}
217
218
func (d *Yun139) post(pathname string, data interface{}, resp interface{}) ([]byte, error) {
219
return d.request(pathname, http.MethodPost, func(req *resty.Request) {
220
req.SetBody(data)
221
}, resp)
222
}
223
224
func (d *Yun139) getFiles(catalogID string) ([]model.Obj, error) {
225
start := 0
226
limit := 100
227
files := make([]model.Obj, 0)
228
for {
229
data := base.Json{
230
"catalogID": catalogID,
231
"sortDirection": 1,
232
"startNumber": start + 1,
233
"endNumber": start + limit,
234
"filterType": 0,
235
"catalogSortType": 0,
236
"contentSortType": 0,
237
"commonAccountInfo": base.Json{
238
"account": d.getAccount(),
239
"accountType": 1,
240
},
241
}
242
var resp GetDiskResp
243
_, err := d.post("/orchestration/personalCloud/catalog/v1.0/getDisk", data, &resp)
244
if err != nil {
245
return nil, err
246
}
247
for _, catalog := range resp.Data.GetDiskResult.CatalogList {
248
f := model.Object{
249
ID: catalog.CatalogID,
250
Name: catalog.CatalogName,
251
Size: 0,
252
Modified: getTime(catalog.UpdateTime),
253
Ctime: getTime(catalog.CreateTime),
254
IsFolder: true,
255
}
256
files = append(files, &f)
257
}
258
for _, content := range resp.Data.GetDiskResult.ContentList {
259
f := model.ObjThumb{
260
Object: model.Object{
261
ID: content.ContentID,
262
Name: content.ContentName,
263
Size: content.ContentSize,
264
Modified: getTime(content.UpdateTime),
265
HashInfo: utils.NewHashInfo(utils.MD5, content.Digest),
266
},
267
Thumbnail: model.Thumbnail{Thumbnail: content.ThumbnailURL},
268
//Thumbnail: content.BigthumbnailURL,
269
}
270
files = append(files, &f)
271
}
272
if start+limit >= resp.Data.GetDiskResult.NodeCount {
273
break
274
}
275
start += limit
276
}
277
return files, nil
278
}
279
280
func (d *Yun139) newJson(data map[string]interface{}) base.Json {
281
common := map[string]interface{}{
282
"catalogType": 3,
283
"cloudID": d.CloudID,
284
"cloudType": 1,
285
"commonAccountInfo": base.Json{
286
"account": d.getAccount(),
287
"accountType": 1,
288
},
289
}
290
return utils.MergeMap(data, common)
291
}
292
293
func (d *Yun139) familyGetFiles(catalogID string) ([]model.Obj, error) {
294
pageNum := 1
295
files := make([]model.Obj, 0)
296
for {
297
data := d.newJson(base.Json{
298
"catalogID": catalogID,
299
"contentSortType": 0,
300
"pageInfo": base.Json{
301
"pageNum": pageNum,
302
"pageSize": 100,
303
},
304
"sortDirection": 1,
305
})
306
var resp QueryContentListResp
307
_, err := d.post("/orchestration/familyCloud-rebuild/content/v1.2/queryContentList", data, &resp)
308
if err != nil {
309
return nil, err
310
}
311
path := resp.Data.Path
312
for _, catalog := range resp.Data.CloudCatalogList {
313
f := model.Object{
314
ID: catalog.CatalogID,
315
Name: catalog.CatalogName,
316
Size: 0,
317
IsFolder: true,
318
Modified: getTime(catalog.LastUpdateTime),
319
Ctime: getTime(catalog.CreateTime),
320
Path: path, // 文件夹上一级的Path
321
}
322
files = append(files, &f)
323
}
324
for _, content := range resp.Data.CloudContentList {
325
f := model.ObjThumb{
326
Object: model.Object{
327
ID: content.ContentID,
328
Name: content.ContentName,
329
Size: content.ContentSize,
330
Modified: getTime(content.LastUpdateTime),
331
Ctime: getTime(content.CreateTime),
332
Path: path, // 文件所在目录的Path
333
},
334
Thumbnail: model.Thumbnail{Thumbnail: content.ThumbnailURL},
335
//Thumbnail: content.BigthumbnailURL,
336
}
337
files = append(files, &f)
338
}
339
if resp.Data.TotalCount == 0 {
340
break
341
}
342
pageNum++
343
}
344
return files, nil
345
}
346
347
func (d *Yun139) groupGetFiles(catalogID string) ([]model.Obj, error) {
348
pageNum := 1
349
files := make([]model.Obj, 0)
350
for {
351
data := d.newJson(base.Json{
352
"groupID": d.CloudID,
353
"catalogID": path.Base(catalogID),
354
"contentSortType": 0,
355
"sortDirection": 1,
356
"startNumber": pageNum,
357
"endNumber": pageNum + 99,
358
"path": path.Join(d.RootFolderID, catalogID),
359
})
360
361
var resp QueryGroupContentListResp
362
_, err := d.post("/orchestration/group-rebuild/content/v1.0/queryGroupContentList", data, &resp)
363
if err != nil {
364
return nil, err
365
}
366
path := resp.Data.GetGroupContentResult.ParentCatalogID
367
for _, catalog := range resp.Data.GetGroupContentResult.CatalogList {
368
f := model.Object{
369
ID: catalog.CatalogID,
370
Name: catalog.CatalogName,
371
Size: 0,
372
IsFolder: true,
373
Modified: getTime(catalog.UpdateTime),
374
Ctime: getTime(catalog.CreateTime),
375
Path: catalog.Path, // 文件夹的真实Path, root:/开头
376
}
377
files = append(files, &f)
378
}
379
for _, content := range resp.Data.GetGroupContentResult.ContentList {
380
f := model.ObjThumb{
381
Object: model.Object{
382
ID: content.ContentID,
383
Name: content.ContentName,
384
Size: content.ContentSize,
385
Modified: getTime(content.UpdateTime),
386
Ctime: getTime(content.CreateTime),
387
Path: path, // 文件所在目录的Path
388
},
389
Thumbnail: model.Thumbnail{Thumbnail: content.ThumbnailURL},
390
//Thumbnail: content.BigthumbnailURL,
391
}
392
files = append(files, &f)
393
}
394
if (pageNum + 99) > resp.Data.GetGroupContentResult.NodeCount {
395
break
396
}
397
pageNum = pageNum + 100
398
}
399
return files, nil
400
}
401
402
func (d *Yun139) getLink(contentId string) (string, error) {
403
data := base.Json{
404
"appName": "",
405
"contentID": contentId,
406
"commonAccountInfo": base.Json{
407
"account": d.getAccount(),
408
"accountType": 1,
409
},
410
}
411
res, err := d.post("/orchestration/personalCloud/uploadAndDownload/v1.0/downloadRequest",
412
data, nil)
413
if err != nil {
414
return "", err
415
}
416
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
417
}
418
func (d *Yun139) familyGetLink(contentId string, path string) (string, error) {
419
data := d.newJson(base.Json{
420
"contentID": contentId,
421
"path": path,
422
})
423
res, err := d.post("/orchestration/familyCloud-rebuild/content/v1.0/getFileDownLoadURL",
424
data, nil)
425
if err != nil {
426
return "", err
427
}
428
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
429
}
430
431
func (d *Yun139) groupGetLink(contentId string, path string) (string, error) {
432
data := d.newJson(base.Json{
433
"contentID": contentId,
434
"groupID": d.CloudID,
435
"path": path,
436
})
437
res, err := d.post("/orchestration/group-rebuild/groupManage/v1.0/getGroupFileDownLoadURL",
438
data, nil)
439
if err != nil {
440
return "", err
441
}
442
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
443
}
444
445
func unicode(str string) string {
446
textQuoted := strconv.QuoteToASCII(str)
447
textUnquoted := textQuoted[1 : len(textQuoted)-1]
448
return textUnquoted
449
}
450
451
func (d *Yun139) personalRequest(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
452
url := d.getPersonalCloudHost() + pathname
453
req := base.RestyClient.R()
454
randStr := random.String(16)
455
ts := time.Now().Format("2006-01-02 15:04:05")
456
if callback != nil {
457
callback(req)
458
}
459
body, err := utils.Json.Marshal(req.Body)
460
if err != nil {
461
return nil, err
462
}
463
sign := calSign(string(body), ts, randStr)
464
svcType := "1"
465
if d.isFamily() {
466
svcType = "2"
467
}
468
req.SetHeaders(map[string]string{
469
"Accept": "application/json, text/plain, */*",
470
"Authorization": "Basic " + d.getAuthorization(),
471
"Caller": "web",
472
"Cms-Device": "default",
473
"Mcloud-Channel": "1000101",
474
"Mcloud-Client": "10701",
475
"Mcloud-Route": "001",
476
"Mcloud-Sign": fmt.Sprintf("%s,%s,%s", ts, randStr, sign),
477
"Mcloud-Version": "7.14.0",
478
"x-DeviceInfo": "||9|7.14.0|chrome|120.0.0.0|||windows 10||zh-CN|||",
479
"x-huawei-channelSrc": "10000034",
480
"x-inner-ntwk": "2",
481
"x-m4c-caller": "PC",
482
"x-m4c-src": "10002",
483
"x-SvcType": svcType,
484
"X-Yun-Api-Version": "v1",
485
"X-Yun-App-Channel": "10000034",
486
"X-Yun-Channel-Source": "10000034",
487
"X-Yun-Client-Info": "||9|7.14.0|chrome|120.0.0.0|||windows 10||zh-CN|||dW5kZWZpbmVk||",
488
"X-Yun-Module-Type": "100",
489
"X-Yun-Svc-Type": "1",
490
})
491
492
var e BaseResp
493
req.SetResult(&e)
494
res, err := req.Execute(method, url)
495
if err != nil {
496
return nil, err
497
}
498
log.Debugln(res.String())
499
if !e.Success {
500
return nil, errors.New(e.Message)
501
}
502
if resp != nil {
503
err = utils.Json.Unmarshal(res.Body(), resp)
504
if err != nil {
505
return nil, err
506
}
507
}
508
return res.Body(), nil
509
}
510
func (d *Yun139) personalPost(pathname string, data interface{}, resp interface{}) ([]byte, error) {
511
return d.personalRequest(pathname, http.MethodPost, func(req *resty.Request) {
512
req.SetBody(data)
513
}, resp)
514
}
515
516
func getPersonalTime(t string) time.Time {
517
stamp, err := time.ParseInLocation("2006-01-02T15:04:05.999-07:00", t, utils.CNLoc)
518
if err != nil {
519
panic(err)
520
}
521
return stamp
522
}
523
524
func (d *Yun139) personalGetFiles(fileId string) ([]model.Obj, error) {
525
files := make([]model.Obj, 0)
526
nextPageCursor := ""
527
for {
528
data := base.Json{
529
"imageThumbnailStyleList": []string{"Small", "Large"},
530
"orderBy": "updated_at",
531
"orderDirection": "DESC",
532
"pageInfo": base.Json{
533
"pageCursor": nextPageCursor,
534
"pageSize": 100,
535
},
536
"parentFileId": fileId,
537
}
538
var resp PersonalListResp
539
_, err := d.personalPost("/file/list", data, &resp)
540
if err != nil {
541
return nil, err
542
}
543
nextPageCursor = resp.Data.NextPageCursor
544
for _, item := range resp.Data.Items {
545
var isFolder = (item.Type == "folder")
546
var f model.Obj
547
if isFolder {
548
f = &model.Object{
549
ID: item.FileId,
550
Name: item.Name,
551
Size: 0,
552
Modified: getPersonalTime(item.UpdatedAt),
553
Ctime: getPersonalTime(item.CreatedAt),
554
IsFolder: isFolder,
555
}
556
} else {
557
var Thumbnails = item.Thumbnails
558
var ThumbnailUrl string
559
if d.UseLargeThumbnail {
560
for _, thumb := range Thumbnails {
561
if strings.Contains(thumb.Style, "Large") {
562
ThumbnailUrl = thumb.Url
563
break
564
}
565
}
566
}
567
if ThumbnailUrl == "" && len(Thumbnails) > 0 {
568
ThumbnailUrl = Thumbnails[len(Thumbnails)-1].Url
569
}
570
f = &model.ObjThumb{
571
Object: model.Object{
572
ID: item.FileId,
573
Name: item.Name,
574
Size: item.Size,
575
Modified: getPersonalTime(item.UpdatedAt),
576
Ctime: getPersonalTime(item.CreatedAt),
577
IsFolder: isFolder,
578
},
579
Thumbnail: model.Thumbnail{Thumbnail: ThumbnailUrl},
580
}
581
}
582
files = append(files, f)
583
}
584
if len(nextPageCursor) == 0 {
585
break
586
}
587
}
588
return files, nil
589
}
590
591
func (d *Yun139) personalGetLink(fileId string) (string, error) {
592
data := base.Json{
593
"fileId": fileId,
594
}
595
res, err := d.personalPost("/file/getDownloadUrl",
596
data, nil)
597
if err != nil {
598
return "", err
599
}
600
var cdnUrl = jsoniter.Get(res, "data", "cdnUrl").ToString()
601
if cdnUrl != "" {
602
return cdnUrl, nil
603
} else {
604
return jsoniter.Get(res, "data", "url").ToString(), nil
605
}
606
}
607
608
func (d *Yun139) getAuthorization() string {
609
if d.ref != nil {
610
return d.ref.getAuthorization()
611
}
612
return d.Authorization
613
}
614
func (d *Yun139) getAccount() string {
615
if d.ref != nil {
616
return d.ref.getAccount()
617
}
618
return d.Account
619
}
620
func (d *Yun139) getPersonalCloudHost() string {
621
if d.ref != nil {
622
return d.ref.getPersonalCloudHost()
623
}
624
return d.PersonalCloudHost
625
}
626
627