Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
alist-org
GitHub Repository: alist-org/alist
Path: blob/main/drivers/lanzou/util.go
1986 views
1
package lanzou
2
3
import (
4
"errors"
5
"fmt"
6
"net/http"
7
"regexp"
8
"runtime"
9
"strconv"
10
"strings"
11
"sync"
12
"sync/atomic"
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/go-resty/resty/v2"
20
log "github.com/sirupsen/logrus"
21
)
22
23
var upClient *resty.Client
24
var once sync.Once
25
26
func (d *LanZou) doupload(callback base.ReqCallback, resp interface{}) ([]byte, error) {
27
return d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
28
req.SetQueryParams(map[string]string{
29
"uid": d.uid,
30
"vei": d.vei,
31
})
32
if callback != nil {
33
callback(req)
34
}
35
}, resp)
36
}
37
38
func (d *LanZou) get(url string, callback base.ReqCallback) ([]byte, error) {
39
return d.request(url, http.MethodGet, callback, false)
40
}
41
42
func (d *LanZou) post(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
43
data, err := d._post(url, callback, resp, false)
44
if err == ErrCookieExpiration && d.IsAccount() {
45
if atomic.CompareAndSwapInt32(&d.flag, 0, 1) {
46
_, err2 := d.Login()
47
atomic.SwapInt32(&d.flag, 0)
48
if err2 != nil {
49
err = errors.Join(err, err2)
50
d.Status = err.Error()
51
op.MustSaveDriverStorage(d)
52
return data, err
53
}
54
}
55
for atomic.LoadInt32(&d.flag) != 0 {
56
runtime.Gosched()
57
}
58
return d._post(url, callback, resp, false)
59
}
60
return data, err
61
}
62
63
func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{}, up bool) ([]byte, error) {
64
data, err := d.request(url, http.MethodPost, func(req *resty.Request) {
65
req.AddRetryCondition(func(r *resty.Response, err error) bool {
66
if utils.Json.Get(r.Body(), "zt").ToInt() == 4 {
67
time.Sleep(time.Second)
68
return true
69
}
70
return false
71
})
72
if callback != nil {
73
callback(req)
74
}
75
}, up)
76
if err != nil {
77
return data, err
78
}
79
switch utils.Json.Get(data, "zt").ToInt() {
80
case 1, 2, 4:
81
if resp != nil {
82
// 返回类型不统一,忽略错误
83
utils.Json.Unmarshal(data, resp)
84
}
85
return data, nil
86
case 9: // 登录过期
87
return data, ErrCookieExpiration
88
default:
89
info := utils.Json.Get(data, "inf").ToString()
90
if info == "" {
91
info = utils.Json.Get(data, "info").ToString()
92
}
93
return data, fmt.Errorf(info)
94
}
95
}
96
97
func (d *LanZou) request(url string, method string, callback base.ReqCallback, up bool) ([]byte, error) {
98
var req *resty.Request
99
if up {
100
once.Do(func() {
101
upClient = base.NewRestyClient().SetTimeout(120 * time.Second)
102
})
103
req = upClient.R()
104
} else {
105
req = base.RestyClient.R()
106
}
107
108
req.SetHeaders(map[string]string{
109
"Referer": "https://pc.woozooo.com",
110
"User-Agent": d.UserAgent,
111
})
112
113
if d.Cookie != "" {
114
req.SetHeader("cookie", d.Cookie)
115
}
116
117
if callback != nil {
118
callback(req)
119
}
120
121
res, err := req.Execute(method, url)
122
if err != nil {
123
return nil, err
124
}
125
log.Debugf("lanzou request: url=>%s ,stats=>%d ,body => %s\n", res.Request.URL, res.StatusCode(), res.String())
126
return res.Body(), err
127
}
128
129
func (d *LanZou) Login() ([]*http.Cookie, error) {
130
resp, err := base.NewRestyClient().SetRedirectPolicy(resty.NoRedirectPolicy()).
131
R().SetFormData(map[string]string{
132
"task": "3",
133
"uid": d.Account,
134
"pwd": d.Password,
135
"setSessionId": "",
136
"setSig": "",
137
"setScene": "",
138
"setTocen": "",
139
"formhash": "",
140
}).Post("https://up.woozooo.com/mlogin.php")
141
if err != nil {
142
return nil, err
143
}
144
if utils.Json.Get(resp.Body(), "zt").ToInt() != 1 {
145
return nil, fmt.Errorf("login err: %s", resp.Body())
146
}
147
d.Cookie = CookieToString(resp.Cookies())
148
return resp.Cookies(), nil
149
}
150
151
/*
152
通过cookie获取数据
153
*/
154
155
// 获取文件和文件夹,获取到的文件大小、更改时间不可信
156
func (d *LanZou) GetAllFiles(folderID string) ([]model.Obj, error) {
157
folders, err := d.GetFolders(folderID)
158
if err != nil {
159
return nil, err
160
}
161
files, err := d.GetFiles(folderID)
162
if err != nil {
163
return nil, err
164
}
165
return append(
166
utils.MustSliceConvert(folders, func(folder FileOrFolder) model.Obj {
167
return &folder
168
}), utils.MustSliceConvert(files, func(file FileOrFolder) model.Obj {
169
return &file
170
})...,
171
), nil
172
}
173
174
// 通过ID获取文件夹
175
func (d *LanZou) GetFolders(folderID string) ([]FileOrFolder, error) {
176
var resp RespText[[]FileOrFolder]
177
_, err := d.doupload(func(req *resty.Request) {
178
req.SetFormData(map[string]string{
179
"task": "47",
180
"folder_id": folderID,
181
})
182
}, &resp)
183
if err != nil {
184
return nil, err
185
}
186
return resp.Text, nil
187
}
188
189
// 通过ID获取文件
190
func (d *LanZou) GetFiles(folderID string) ([]FileOrFolder, error) {
191
files := make([]FileOrFolder, 0)
192
for pg := 1; ; pg++ {
193
var resp RespText[[]FileOrFolder]
194
_, err := d.doupload(func(req *resty.Request) {
195
req.SetFormData(map[string]string{
196
"task": "5",
197
"folder_id": folderID,
198
"pg": strconv.Itoa(pg),
199
})
200
}, &resp)
201
if err != nil {
202
return nil, err
203
}
204
if len(resp.Text) == 0 {
205
break
206
}
207
files = append(files, resp.Text...)
208
}
209
return files, nil
210
}
211
212
// 通过ID获取文件夹分享地址
213
func (d *LanZou) getFolderShareUrlByID(fileID string) (*FileShare, error) {
214
var resp RespInfo[FileShare]
215
_, err := d.doupload(func(req *resty.Request) {
216
req.SetFormData(map[string]string{
217
"task": "18",
218
"file_id": fileID,
219
})
220
}, &resp)
221
if err != nil {
222
return nil, err
223
}
224
return &resp.Info, nil
225
}
226
227
// 通过ID获取文件分享地址
228
func (d *LanZou) getFileShareUrlByID(fileID string) (*FileShare, error) {
229
var resp RespInfo[FileShare]
230
_, err := d.doupload(func(req *resty.Request) {
231
req.SetFormData(map[string]string{
232
"task": "22",
233
"file_id": fileID,
234
})
235
}, &resp)
236
if err != nil {
237
return nil, err
238
}
239
return &resp.Info, nil
240
}
241
242
/*
243
通过分享链接获取数据
244
*/
245
246
// 判断类容
247
var isFileReg = regexp.MustCompile(`class="fileinfo"|id="file"|文件描述`)
248
var isFolderReg = regexp.MustCompile(`id="infos"`)
249
250
// 获取文件文件夹基础信息
251
252
// 获取文件名称
253
var nameFindReg = regexp.MustCompile(`<title>(.+?) - 蓝奏云</title>|id="filenajax">(.+?)</div>|var filename = '(.+?)';|<div style="font-size.+?>([^<>].+?)</div>|<div class="filethetext".+?>([^<>]+?)</div>`)
254
255
// 获取文件大小
256
var sizeFindReg = regexp.MustCompile(`(?i)大小\W*([0-9.]+\s*[bkm]+)`)
257
258
// 获取文件时间
259
var timeFindReg = regexp.MustCompile(`\d+\s*[秒天分小][钟时]?前|[昨前]天|\d{4}-\d{2}-\d{2}`)
260
261
// 查找分享文件夹子文件夹ID和名称
262
var findSubFolderReg = regexp.MustCompile(`(?i)(?:folderlink|mbxfolder).+href="/(.+?)"(?:.+filename")?>(.+?)<`)
263
264
// 获取下载页面链接
265
var findDownPageParamReg = regexp.MustCompile(`<iframe.*?src="(.+?)"`)
266
267
// 获取文件ID
268
var findFileIDReg = regexp.MustCompile(`'/ajaxm\.php\?file=(\d+)'`)
269
270
// 获取分享链接主界面
271
func (d *LanZou) getShareUrlHtml(shareID string) (string, error) {
272
var vs string
273
for i := 0; i < 3; i++ {
274
firstPageData, err := d.get(fmt.Sprint(d.ShareUrl, "/", shareID),
275
func(req *resty.Request) {
276
if vs != "" {
277
req.SetCookie(&http.Cookie{
278
Name: "acw_sc__v2",
279
Value: vs,
280
})
281
}
282
})
283
if err != nil {
284
return "", err
285
}
286
287
firstPageDataStr := RemoveNotes(string(firstPageData))
288
if strings.Contains(firstPageDataStr, "取消分享") {
289
return "", ErrFileShareCancel
290
}
291
if strings.Contains(firstPageDataStr, "文件不存在") {
292
return "", ErrFileNotExist
293
}
294
295
// acw_sc__v2
296
if strings.Contains(firstPageDataStr, "acw_sc__v2") {
297
if vs, err = CalcAcwScV2(firstPageDataStr); err != nil {
298
log.Errorf("lanzou: err => acw_sc__v2 validation error ,data => %s\n", firstPageDataStr)
299
return "", err
300
}
301
continue
302
}
303
return firstPageDataStr, nil
304
}
305
return "", errors.New("acw_sc__v2 validation error")
306
}
307
308
// 通过分享链接获取文件或文件夹
309
func (d *LanZou) GetFileOrFolderByShareUrl(shareID, pwd string) ([]model.Obj, error) {
310
pageData, err := d.getShareUrlHtml(shareID)
311
if err != nil {
312
return nil, err
313
}
314
315
if !isFileReg.MatchString(pageData) {
316
files, err := d.getFolderByShareUrl(pwd, pageData)
317
if err != nil {
318
return nil, err
319
}
320
return utils.MustSliceConvert(files, func(file FileOrFolderByShareUrl) model.Obj {
321
return &file
322
}), nil
323
} else {
324
file, err := d.getFilesByShareUrl(shareID, pwd, pageData)
325
if err != nil {
326
return nil, err
327
}
328
return []model.Obj{file}, nil
329
}
330
}
331
332
// 通过分享链接获取文件(下载链接也使用此方法)
333
// FileOrFolderByShareUrl 包含 pwd 和 url 字段
334
// 参考 https://github.com/zaxtyson/LanZouCloud-API/blob/ab2e9ec715d1919bf432210fc16b91c6775fbb99/lanzou/api/core.py#L440
335
func (d *LanZou) GetFilesByShareUrl(shareID, pwd string) (file *FileOrFolderByShareUrl, err error) {
336
pageData, err := d.getShareUrlHtml(shareID)
337
if err != nil {
338
return nil, err
339
}
340
return d.getFilesByShareUrl(shareID, pwd, pageData)
341
}
342
343
func (d *LanZou) getFilesByShareUrl(shareID, pwd string, sharePageData string) (*FileOrFolderByShareUrl, error) {
344
var (
345
param map[string]string
346
downloadUrl string
347
baseUrl string
348
file FileOrFolderByShareUrl
349
)
350
351
// 删除注释
352
sharePageData = RemoveNotes(sharePageData)
353
sharePageData = RemoveJSComment(sharePageData)
354
355
// 需要密码
356
if strings.Contains(sharePageData, "pwdload") || strings.Contains(sharePageData, "passwddiv") {
357
sharePageData, err := getJSFunctionByName(sharePageData, "down_p")
358
if err != nil {
359
return nil, err
360
}
361
param, err := htmlJsonToMap(sharePageData)
362
if err != nil {
363
return nil, err
364
}
365
param["p"] = pwd
366
367
fileIDs := findFileIDReg.FindStringSubmatch(sharePageData)
368
var fileID string
369
if len(fileIDs) > 1 {
370
fileID = fileIDs[1]
371
} else {
372
return nil, fmt.Errorf("not find file id")
373
}
374
var resp FileShareInfoAndUrlResp[string]
375
_, err = d.post(d.ShareUrl+"/ajaxm.php?file="+fileID, func(req *resty.Request) { req.SetFormData(param) }, &resp)
376
if err != nil {
377
return nil, err
378
}
379
file.NameAll = resp.Inf
380
file.Pwd = pwd
381
baseUrl = resp.GetBaseUrl()
382
downloadUrl = resp.GetDownloadUrl()
383
} else {
384
urlpaths := findDownPageParamReg.FindStringSubmatch(sharePageData)
385
if len(urlpaths) != 2 {
386
log.Errorf("lanzou: err => not find file page param ,data => %s\n", sharePageData)
387
return nil, fmt.Errorf("not find file page param")
388
}
389
data, err := d.get(fmt.Sprint(d.ShareUrl, urlpaths[1]), nil)
390
if err != nil {
391
return nil, err
392
}
393
nextPageData := RemoveNotes(string(data))
394
param, err = htmlJsonToMap(nextPageData)
395
if err != nil {
396
return nil, err
397
}
398
399
fileIDs := findFileIDReg.FindStringSubmatch(nextPageData)
400
var fileID string
401
if len(fileIDs) > 1 {
402
fileID = fileIDs[1]
403
} else {
404
return nil, fmt.Errorf("not find file id")
405
}
406
var resp FileShareInfoAndUrlResp[int]
407
_, err = d.post(d.ShareUrl+"/ajaxm.php?file="+fileID, func(req *resty.Request) { req.SetFormData(param) }, &resp)
408
if err != nil {
409
return nil, err
410
}
411
baseUrl = resp.GetBaseUrl()
412
downloadUrl = resp.GetDownloadUrl()
413
414
names := nameFindReg.FindStringSubmatch(sharePageData)
415
if len(names) > 1 {
416
for _, name := range names[1:] {
417
if name != "" {
418
file.NameAll = name
419
break
420
}
421
}
422
}
423
}
424
425
sizes := sizeFindReg.FindStringSubmatch(sharePageData)
426
if len(sizes) == 2 {
427
file.Size = sizes[1]
428
}
429
file.ID = shareID
430
file.Time = timeFindReg.FindString(sharePageData)
431
432
// 重定向获取真实链接
433
headers := map[string]string{
434
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
435
}
436
res, err := base.NoRedirectClient.R().SetHeaders(headers).Get(downloadUrl)
437
if err != nil {
438
return nil, err
439
}
440
441
rPageData := res.String()
442
if findAcwScV2Reg.MatchString(rPageData) {
443
log.Debug("lanzou: detected acw_sc__v2 challenge, recalculating cookie")
444
acwScV2, err := CalcAcwScV2(rPageData)
445
if err != nil {
446
return nil, err
447
}
448
// retry with calculated cookie to bypass anti-crawler validation
449
res, err = base.NoRedirectClient.R().
450
SetHeaders(headers).
451
SetCookie(&http.Cookie{Name: "acw_sc__v2", Value: acwScV2}).
452
Get(downloadUrl)
453
if err != nil {
454
return nil, err
455
}
456
rPageData = res.String()
457
}
458
459
file.Url = res.Header().Get("location")
460
461
// 触发验证
462
if res.StatusCode() != 302 {
463
param, err = htmlJsonToMap(rPageData)
464
if err != nil {
465
return nil, err
466
}
467
param["el"] = "2"
468
time.Sleep(time.Second * 2)
469
470
// 通过验证获取直连
471
data, err := d.post(fmt.Sprint(baseUrl, "/ajax.php"), func(req *resty.Request) { req.SetFormData(param) }, nil)
472
if err != nil {
473
return nil, err
474
}
475
file.Url = utils.Json.Get(data, "url").ToString()
476
}
477
return &file, nil
478
}
479
480
// 通过分享链接获取文件夹
481
// 似乎子目录和文件不会加密
482
// 参考 https://github.com/zaxtyson/LanZouCloud-API/blob/ab2e9ec715d1919bf432210fc16b91c6775fbb99/lanzou/api/core.py#L1089
483
func (d *LanZou) GetFolderByShareUrl(shareID, pwd string) ([]FileOrFolderByShareUrl, error) {
484
pageData, err := d.getShareUrlHtml(shareID)
485
if err != nil {
486
return nil, err
487
}
488
return d.getFolderByShareUrl(pwd, pageData)
489
}
490
491
func (d *LanZou) getFolderByShareUrl(pwd string, sharePageData string) ([]FileOrFolderByShareUrl, error) {
492
from, err := htmlJsonToMap(sharePageData)
493
if err != nil {
494
return nil, err
495
}
496
497
files := make([]FileOrFolderByShareUrl, 0)
498
// vip获取文件夹
499
floders := findSubFolderReg.FindAllStringSubmatch(sharePageData, -1)
500
for _, floder := range floders {
501
if len(floder) == 3 {
502
files = append(files, FileOrFolderByShareUrl{
503
// Pwd: pwd, // 子文件夹不加密
504
ID: floder[1],
505
NameAll: floder[2],
506
IsFloder: true,
507
})
508
}
509
}
510
511
// 获取文件
512
from["pwd"] = pwd
513
for page := 1; ; page++ {
514
from["pg"] = strconv.Itoa(page)
515
var resp FileOrFolderByShareUrlResp
516
_, err := d.post(d.ShareUrl+"/filemoreajax.php", func(req *resty.Request) { req.SetFormData(from) }, &resp)
517
if err != nil {
518
return nil, err
519
}
520
// 文件夹中的文件加密
521
for i := 0; i < len(resp.Text); i++ {
522
resp.Text[i].Pwd = pwd
523
}
524
if len(resp.Text) == 0 {
525
break
526
}
527
files = append(files, resp.Text...)
528
time.Sleep(time.Second)
529
}
530
return files, nil
531
}
532
533
// 通过下载头获取真实文件信息
534
func (d *LanZou) getFileRealInfo(downURL string) (*int64, *time.Time) {
535
res, _ := base.RestyClient.R().Head(downURL)
536
if res == nil {
537
return nil, nil
538
}
539
time, _ := http.ParseTime(res.Header().Get("Last-Modified"))
540
size, _ := strconv.ParseInt(res.Header().Get("Content-Length"), 10, 64)
541
return &size, &time
542
}
543
544
func (d *LanZou) getVeiAndUid() (vei string, uid string, err error) {
545
var resp []byte
546
resp, err = d.get("https://pc.woozooo.com/mydisk.php", func(req *resty.Request) {
547
req.SetQueryParams(map[string]string{
548
"item": "files",
549
"action": "index",
550
})
551
})
552
if err != nil {
553
return
554
}
555
// uid
556
uids := regexp.MustCompile(`uid=([^'"&;]+)`).FindStringSubmatch(string(resp))
557
if len(uids) < 2 {
558
err = fmt.Errorf("uid variable not find")
559
return
560
}
561
uid = uids[1]
562
563
// vei
564
html := RemoveNotes(string(resp))
565
data, err := htmlJsonToMap(html)
566
if err != nil {
567
return
568
}
569
vei = data["vei"]
570
571
return
572
}
573
574