XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/bilibili-API-collect

重构 wbi golang 实现

724e618
Miuzarte <982809597@qq.com>
提交于

代码差异

1 个文件 +157 -150
Modified docs/misc/sign/wbi.md +157 -150
@@ -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