@@ -380,178 +380,185 @@ bar=514&baz=1919810&foo=114&wts=1684805578&w_rid=bb97e15f28edf445a0e4420d36f0157
380
380
381
381
### Golang
382
382
383
- 需要 ` github.com/tidwall/gjson ` 作为依赖
383
+ 无第三方库
384
384
385
385
``` go
386
386
package main
387
387
388
388
import (
389
- " crypto/md5"
390
- " encoding/hex"
391
- " fmt"
392
- " io"
393
- " net/http"
394
- " net/url"
395
- " sort"
396
- " strconv"
397
- " strings"
398
- " sync"
399
- " time"
400
-
401
- " github.com/tidwall/gjson"
402
- )
403
-
404
- var (
405
- mixinKeyEncTab = []int {
406
- 46 , 47 , 18 , 2 , 53 , 8 , 23 , 32 , 15 , 50 , 10 , 31 , 58 , 3 , 45 , 35 , 27 , 43 , 5 , 49 ,
407
- 33 , 9 , 42 , 19 , 29 , 28 , 14 , 39 , 12 , 38 , 41 , 13 , 37 , 48 , 7 , 16 , 24 , 55 , 40 ,
408
- 61 , 26 , 17 , 0 , 1 , 60 , 51 , 30 , 4 , 22 , 25 , 54 , 21 , 56 , 59 , 6 , 63 , 57 , 62 , 11 ,
409
- 36 , 20 , 34 , 44 , 52 ,
410
- }
411
- cache sync.Map
412
- lastUpdateTime time.Time
389
+ " bytes"
390
+ " crypto/md5"
391
+ " encoding/hex"
392
+ " encoding/json"
393
+ " fmt"
394
+ " io"
395
+ " net/http"
396
+ " net/url"
397
+ " strconv"
398
+ " strings"
399
+ " time"
413
400
)
414
401
415
402
func main () {
416
- urlStr := " https://api.bilibili.com/x/space/wbi/acc/info?mid=1850091"
417
- newUrlStr , err := signAndGenerateURL (urlStr)
418
- if err != nil {
419
- fmt.Printf (" Error: %s " , err)
420
- return
421
- }
422
- req , err := http.NewRequest (" GET" , newUrlStr, nil )
423
- if err != nil {
424
- fmt.Printf (" Error: %s " , err)
425
- return
426
- }
427
- req.Header .Set (" User-Agent" , " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" )
428
- req.Header .Set (" Referer" , " https://www.bilibili.com/" )
429
- response , err := http.DefaultClient .Do (req)
430
- if err != nil {
431
- fmt.Printf (" Request failed: %s " , err)
432
- return
433
- }
434
- defer response.Body .Close ()
435
- body , err := io.ReadAll (response.Body )
436
- if err != nil {
437
- fmt.Printf (" Failed to read response: %s " , err)
438
- return
439
- }
440
- fmt.Println (string (body))
403
+ u , err := url.Parse (" https://api.bilibili.com/x/space/wbi/acc/info?mid=1850091" )
404
+ if err != nil {
405
+ panic (err)
406
+ }
407
+ fmt.Printf (" orig: %s \n " , u.String ())
408
+ err = Sign (u)
409
+ if err != nil {
410
+ panic (err)
411
+ }
412
+ fmt.Printf (" signed: %s \n " , u.String ())
413
+
414
+ // 获取 wbi 时未修改 header
415
+ // 但实际使用签名后的 url 时发现风控较为严重
416
+ }
417
+
418
+ // Sign 为链接签名
419
+ func Sign (u *url .URL ) error {
420
+ return wbiKeys.Sign (u)
421
+ }
422
+
423
+ // Update 无视过期时间更新
424
+ func Update () error {
425
+ return wbiKeys.Update ()
441
426
}
442
427
443
- func signAndGenerateURL (urlStr string ) (string , error ) {
444
- urlObj , err := url.Parse (urlStr)
445
- if err != nil {
446
- return " " , err
447
- }
448
- imgKey , subKey := getWbiKeysCached ()
449
- query := urlObj.Query ()
450
- params := map [string ]string {}
451
- for k , v := range query {
452
- params[k] = v[0 ]
453
- }
454
- newParams := encWbi (params, imgKey, subKey)
455
- for k , v := range newParams {
456
- query.Set (k, v)
457
- }
458
- urlObj.RawQuery = query.Encode ()
459
- newUrlStr := urlObj.String ()
460
- return newUrlStr, nil
428
+ func Get () (wk WbiKeys , err error ) {
429
+ if err = wk.update (false ); err != nil {
430
+ return WbiKeys{}, err
431
+ }
432
+ return wbiKeys, nil
433
+ }
434
+
435
+ var wbiKeys WbiKeys
436
+
437
+ type WbiKeys struct {
438
+ Img string
439
+ Sub string
440
+ Mixin string
441
+ lastUpdateTime time.Time
442
+ }
443
+
444
+ // Sign 为链接签名
445
+ func (wk *WbiKeys ) Sign (u *url .URL ) (err error ) {
446
+ if err = wk.update (false ); err != nil {
447
+ return err
448
+ }
449
+
450
+ values := u.Query ()
451
+
452
+ values = removeUnwantedChars (values, ' !' , ' \' ' , ' (' , ' )' , ' *' ) // 必要性存疑?
453
+
454
+ values.Set (" wts" , strconv.FormatInt (time.Now ().Unix (), 10 ))
455
+
456
+ // [url.Values.Encode] 内会对参数排序,
457
+ // 且遍历 map 时本身就是无序的
458
+ hash := md5.Sum ([]byte (values.Encode () + wk.Mixin )) // Calculate w_rid
459
+ values.Set (" w_rid" , hex.EncodeToString (hash[:]))
460
+ u.RawQuery = values.Encode ()
461
+ return nil
461
462
}
462
463
463
- func encWbi (params map [string ]string , imgKey , subKey string ) map [string ]string {
464
- mixinKey := getMixinKey (imgKey + subKey)
465
- currTime := strconv.FormatInt (time.Now ().Unix (), 10 )
466
- params[" wts" ] = currTime
467
-
468
- // Sort keys
469
- keys := make ([]string , 0 , len (params))
470
- for k := range params {
471
- keys = append (keys, k)
472
- }
473
- sort.Strings (keys)
474
-
475
- // Remove unwanted characters
476
- for k , v := range params {
477
- v = sanitizeString (v)
478
- params[k] = v
479
- }
480
-
481
- // Build URL parameters
482
- query := url.Values {}
483
- for _ , k := range keys {
484
- query.Set (k, params[k])
485
- }
486
- queryStr := query.Encode ()
487
-
488
- // Calculate w_rid
489
- hash := md5.Sum ([]byte (queryStr + mixinKey))
490
- params[" w_rid" ] = hex.EncodeToString (hash[:])
491
- return params
464
+ // Update 无视过期时间更新
465
+ func (wk *WbiKeys ) Update () (err error ) {
466
+ return wk.update (true )
492
467
}
493
468
494
- func getMixinKey (orig string ) string {
495
- var str strings.Builder
496
- for _ , v := range mixinKeyEncTab {
497
- if v < len (orig) {
498
- str.WriteByte (orig[v])
499
- }
500
- }
501
- return str.String ()[:32 ]
469
+ // update 按需更新
470
+ func (wk *WbiKeys ) update (purge bool ) error {
471
+ if !purge && time.Since (wk.lastUpdateTime ) < time.Hour {
472
+ return nil
473
+ }
474
+
475
+ // 测试下来不用修改 header 也能过
476
+ resp , err := http.Get (" https://api.bilibili.com/x/web-interface/nav" )
477
+ if err != nil {
478
+ return err
479
+ }
480
+ defer resp.Body .Close ()
481
+ body , err := io.ReadAll (resp.Body )
482
+ if err != nil {
483
+ return err
484
+ }
485
+
486
+ nav := Nav{}
487
+ err = json.Unmarshal (body, &nav)
488
+ if err != nil {
489
+ return err
490
+ }
491
+
492
+ if nav.Code != 0 && nav.Code != -101 { // -101 未登录时也会返回两个 key
493
+ return fmt.Errorf (" unexpected code: %d , message: %s " , nav.Code , nav.Message )
494
+ }
495
+ img := nav.Data .WbiImg .ImgUrl
496
+ sub := nav.Data .WbiImg .SubUrl
497
+ if img == " " || sub == " " {
498
+ return fmt.Errorf (" empty image or sub url: %s " , body)
499
+ }
500
+
501
+ // https://i0.hdslb.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png
502
+ imgParts := strings.Split (img, " /" )
503
+ subParts := strings.Split (sub, " /" )
504
+
505
+ // 7cd084941338484aae1ad9425b84077c.png
506
+ imgPng := imgParts[len (imgParts)-1 ]
507
+ subPng := subParts[len (subParts)-1 ]
508
+
509
+ // 7cd084941338484aae1ad9425b84077c
510
+ wbiKeys.Img = strings.TrimSuffix (imgPng, " .png" )
511
+ wbiKeys.Sub = strings.TrimSuffix (subPng, " .png" )
512
+
513
+ wbiKeys.mixin ()
514
+ wbiKeys.lastUpdateTime = time.Now ()
515
+ return nil
502
516
}
503
517
504
- func sanitizeString (s string ) string {
505
- unwantedChars := []string {" !" , " '" , " (" , " )" , " *" }
506
- for _ , char := range unwantedChars {
507
- s = strings.ReplaceAll (s, char, " " )
508
- }
509
- return s
518
+ func (wk *WbiKeys ) mixin () {
519
+ var mixin [32 ]byte
520
+ wbi := wk.Img + wk.Sub
521
+ for i := range mixin { // for i := 0; i < len(mixin); i++ {
522
+ mixin[i] = wbi[mixinKeyEncTab[i]]
523
+ }
524
+ wk.Mixin = string (mixin[:])
510
525
}
511
526
512
- func updateCache () {
513
- if time.Since (lastUpdateTime).Minutes () < 10 {
514
- return
515
- }
516
- imgKey , subKey := getWbiKeys ()
517
- cache.Store (" imgKey" , imgKey)
518
- cache.Store (" subKey" , subKey)
519
- lastUpdateTime = time.Now ()
527
+ var mixinKeyEncTab = [...]int {
528
+ 46 , 47 , 18 , 2 , 53 , 8 , 23 , 32 ,
529
+ 15 , 50 , 10 , 31 , 58 , 3 , 45 , 35 ,
530
+ 27 , 43 , 5 , 49 , 33 , 9 , 42 , 19 ,
531
+ 29 , 28 , 14 , 39 , 12 , 38 , 41 , 13 ,
532
+ 37 , 48 , 7 , 16 , 24 , 55 , 40 , 61 ,
533
+ 26 , 17 , 0 , 1 , 60 , 51 , 30 , 4 ,
534
+ 22 , 25 , 54 , 21 , 56 , 59 , 6 , 63 ,
535
+ 57 , 62 , 11 , 36 , 20 , 34 , 44 , 52 ,
520
536
}
521
537
522
- func getWbiKeysCached () (string , string ) {
523
- updateCache ()
524
- imgKeyI , _ := cache.Load (" imgKey" )
525
- subKeyI , _ := cache.Load (" subKey" )
526
- return imgKeyI.(string ), subKeyI.(string )
538
+ func removeUnwantedChars (v url .Values , chars ...byte ) url .Values {
539
+ b := []byte (v.Encode ())
540
+ for _ , c := range chars {
541
+ b = bytes.ReplaceAll (b, []byte {c}, nil )
542
+ }
543
+ s , err := url.ParseQuery (string (b))
544
+ if err != nil {
545
+ panic (err)
546
+ }
547
+ return s
527
548
}
528
549
529
- func getWbiKeys () (string , string ) {
530
- client := &http.Client {}
531
- req , err := http.NewRequest (" GET" , " https://api.bilibili.com/x/web-interface/nav" , nil )
532
- if err != nil {
533
- fmt.Printf (" Error creating request: %s " , err)
534
- return " " , " "
535
- }
536
- req.Header .Set (" User-Agent" , " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" )
537
- req.Header .Set (" Referer" , " https://www.bilibili.com/" )
538
- resp , err := client.Do (req)
539
- if err != nil {
540
- fmt.Printf (" Error sending request: %s " , err)
541
- return " " , " "
542
- }
543
- defer resp.Body .Close ()
544
- body , err := io.ReadAll (resp.Body )
545
- if err != nil {
546
- fmt.Printf (" Error reading response: %s " , err)
547
- return " " , " "
548
- }
549
- json := string (body)
550
- imgURL := gjson.Get (json, " data.wbi_img.img_url" ).String ()
551
- subURL := gjson.Get (json, " data.wbi_img.sub_url" ).String ()
552
- imgKey := strings.Split (strings.Split (imgURL, " /" )[len (strings.Split (imgURL, " /" ))-1 ], " ." )[0 ]
553
- subKey := strings.Split (strings.Split (subURL, " /" )[len (strings.Split (subURL, " /" ))-1 ], " ." )[0 ]
554
- return imgKey, subKey
550
+ type Nav struct {
551
+ Code int ` json:"code"`
552
+ Message string ` json:"message"`
553
+ Ttl int ` json:"ttl"`
554
+ Data struct {
555
+ WbiImg struct {
556
+ ImgUrl string ` json:"img_url"`
557
+ SubUrl string ` json:"sub_url"`
558
+ } ` json:"wbi_img"`
559
+
560
+ // ......
561
+ } ` json:"data"`
555
562
}
556
563
```
557
564
0 commit comments