Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

用户主页获取投稿列表 -352 新增校验 #868

Open
o0HalfLife0o opened this issue Nov 21, 2023 · 149 comments
Open

用户主页获取投稿列表 -352 新增校验 #868

o0HalfLife0o opened this issue Nov 21, 2023 · 149 comments
Labels
主站杂项/Misc 接口:主站其他类型 更新/Update 更新过时内容
Milestone

Comments

@o0HalfLife0o
Copy link

o0HalfLife0o commented Nov 21, 2023

https://api.bilibili.com/x/space/wbi/arc/search?mid=2&ps=30&tid=0&pn=1&keyword=&order=pubdate&platform=web&web_location=1550101&order_avoided=true&dm_img_list=[]&dm_img_str=V2ViR0wgMS&dm_cover_img_str=QU5HTEUgKEludGVsLCBJbnRlbChSKSBIRCBHcmFwaGljcyBEaXJlY3QzRDExIHZzXzVfMCBwc181XzApR29vZ2xlIEluYy4gKEludGVsKQ&w_rid=e8319e347dca849eadcdd27f1a8a5a79&wts=1700567648

用户投稿多了dm_img_listdm_img_strdm_cover_img_str这3个值,缺少这3个值会报352错误,加上这3个值,wbi接口不添加wbi校验值都可以正常获取数据

{
    'code': -352,
    'message': '风控校验失败',
    'ttl': 1,
    'data': {
        'v_voucher': 'voucher_605d9ef3-9ca5-4498-ace1-8613ecf7a24f'
    }
}
@xiaoyv404 xiaoyv404 added 讨论/Discussions 探讨相关内容 主站杂项/Misc 接口:主站其他类型 labels Nov 21, 2023
@zzhjiyin
Copy link

是的,我也遇见同样问题

@Luoxin
Copy link
Contributor

Luoxin commented Nov 22, 2023

但是经过尝试,加了也还是有可能会有 -352 的报错,这三个字段似乎不是风控的关键内容

@power721
Copy link

等待大佬

@power721
Copy link

需要加User-Agent

@Luoxin
Copy link
Contributor

Luoxin commented Nov 22, 2023

需要加User-Agent

我添加过一下Header:

  • Origin
  • Referer
  • Authority
  • Accept
  • Accept-Language
  • Dnt

并添加了Chrome(v106)的浏览器指纹,但是依然风控,应该是别的原因

@nethunter787
Copy link

nethunter787 commented Nov 23, 2023

同样发现这个问题,关键是 dm_cover_img_str 这个参数,一旦缺省,直接352错误。
晚上计划研究一下

@0f-0b
Copy link
Contributor

0f-0b commented Nov 23, 2023

dm_img_strdm_cover_img_str 的值似乎可以这样得到。

let version;
let rendererAndVendor;
const gl = document.createElement("canvas").getContext("webgl");
if (gl) {
  version = gl.getParameter(gl.VERSION);
  const debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
  if (debugInfo) {
    rendererAndVendor = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) +
      gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
  }
}
if (version === undefined) { // 禁用了 WebGL
  console.log("dm_img_str", "bm8gd2ViZ2");
  console.log("dm_cover_img_str", "bm8gd2ViZ2");
} else {
  console.log("dm_img_str", btoa(version).slice(0, -2));
  if (rendererAndVendor === undefined) { // 禁用了 WEBGL_debug_renderer_info 扩展
    console.log("dm_cover_img_str", "bm8gd2ViZ2wgZXh0ZW5zaW");
  } else {
    console.log("dm_cover_img_str", btoa(rendererAndVendor).slice(0, -2));
  }
}

@power721
Copy link

  • Referer

不能加Referer。
加User-Agent和cookie

@zzhjiyin
Copy link

  • Referer

不能加Referer。 加User-Agent和cookie

headers中加上cookie已暂时解决

@CzJam
Copy link

CzJam commented Nov 24, 2023

经测试,cookie里必须要添加buvid3和buvid4。而请求url中的dm_cover_img_str为必须项,经解码后发现是GPU渲染信息,因此它为固定值,可以随便填

@linix999
Copy link

https://www.52pojie.cn/thread-1861424-1-1.html 这个参数已经可以解决了

@xiaoyv404 xiaoyv404 removed the 讨论/Discussions 探讨相关内容 label Nov 24, 2023
@xiaoyv404 xiaoyv404 reopened this Nov 24, 2023
@xiaoyv404 xiaoyv404 changed the title 网页端接口似乎又加了新的校验 网页端接口加了新的校验 Nov 24, 2023
@Luoxin
Copy link
Contributor

Luoxin commented Nov 25, 2023

不能加Referer。

Referer信息应该不是影响因素,我尝试过添加和移除的场景,被风控的时间是差不多的

@WolveStorm
Copy link

这个我试了下,最后加了Headers加了个ua可以访问了,但是我并不确定是否频率高了仍会触发封控,需要观察。
HEADERS = { 'Referer': 'https://www.bilibili.com/', 'Connection': 'close', 'User-Agent': 'Mozilla/5.0' }

@Luoxin
Copy link
Contributor

Luoxin commented Nov 27, 2023

根据这几天的实验,cookie(如:buvid3, buvid4)、header(如:Referer'User-Agent)、以及请求参数(如:dm_cover_img_str)并没有和风控有强关联,不管是否存在,风控的触发基本相同,目前观察到的,似乎会用请求内容(包括上述的cookir。header、请求参数)作为为请求限频的参数。如果每次请求随机生成并尽可能不重复可以极大的降低风控概率。
另外根据上述行为猜测风控可能会有一些其他参数、行为控制,比如cookie中的bili_ticket,buvid_fp,或者一些上报用户行为的接口相关

@Luoxin
Copy link
Contributor

Luoxin commented Nov 27, 2023

不知道大家是什么情况,这是我测试时的情况。简化过的参数如下,偶尔失败。

import requests

headers = {
    # "authority": "api.bilibili.com",
    # "accept": "*/*",
    # "accept-language": "en,zh-CN;q=0.9,zh;q=0.8",    # 这行代码之后会被取消注释
    # "cache-control": "no-cache",
    "origin": "https://space.bilibili.com",
    # "pragma": "no-cache",
    "referer": "https://space.bilibili.com/2142762/video",
    # "sec-ch-ua": "^\\^Chromium^^;v=^\\^118^^, ^\\^Microsoft",
    # "sec-ch-ua-mobile": "?0",
    # "sec-ch-ua-platform": "^\\^Windows^^",
    # "sec-fetch-dest": "empty",
    # "sec-fetch-mode": "cors",
    # "sec-fetch-site": "same-site",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.76",
}
cookies = {
    "buvid3": "58CBD4ED-4D82-666C-8EFE-337ABCED53DB95963infoc",
    "b_nut": "1701088795",
    # "_uuid": "65342583-1E84-E6105-F872-1A9C6FE2E38797168infoc",
    # "buvid4": "99832353-6338-2D1C-7AB7-F89A1A5DA21297090-023112712-",
    # "bili_ticket": "eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDEzNDc5OTcsImlhdCI6MTcwMTA4ODczNywicGx0IjotMX0.UcheAgY8MK1JqO6yqWO9vm0fJKjI06-F5BMUVucj2fk",
    # "bili_ticket_expires": "1701347937",
    # "buvid_fp": "094f1315a3795ae7695094f66ba4518e",
    # "b_lsid": "D810BE96C_18C1193F406",
    # "PVID": "1",
    # "innersign": "0",
    # "enable_web_push": "DISABLE",
    # "header_theme_version": "CLOSE",
    # "home_feed_column": "4",
    # "browser_resolution": "945-671"
}
url = "https://api.bilibili.com/x/space/wbi/arc/search"
params = {
    "mid": "2142762",
    "ps": "30",
    "tid": "0",
    "pn": "1",
    "keyword": "",
    "order": "pubdate",
    "platform": "web",
    "web_location": "1550101",
    "order_avoided": "true",
    # "dm_img_list": "^\\[^\\]",
    # "dm_img_str": "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
    # "dm_cover_img_str": "QU5HTEUgKEludGVsLCBJbnRlbChSKSBIRCBHcmFwaGljcyA2MzAgKDB4MDAwMDU5MUIpIERpcmVjdDNEMTEgdnNfNV8wIHBzXzVfMCwgRDNEMTEpR29vZ2xlIEluYy4gKEludGVsKQ",
    "w_rid": "0ea56ea45305ee113a85d1742ff3be12",
    "wts": "1701105288",
}
for i in range(20):
    response = requests.get(
        url, headers=headers, cookies=cookies, params=params
    )
    print(response.text[:50])

Snipaste_2023-11-28_02-10-25

如果请求头中使用 "accept-language": "en,zh-CN;q=0.9,zh;q=0.8",这样就没报错了,甚至不需要那 3 个 dm 开头的参数。

Snipaste_2023-11-28_02-10-57

#868 (comment) 所言,你可能需要更多的实验来排除,因为单纯添加或修改某一个header或产生类似重置限频的行为

@zzhjiyin
Copy link

需要在headers中手动加入cookies 但是问题来了,我都手动添加cookies了,还需要爬虫再跑一次就有点徒劳无功了,请问有什么方式可以获取新的cookies呢?

@Allenyep
Copy link

需要在headers中手动加入cookies 但是问题来了,我都手动添加cookies了,还需要爬虫再跑一次就有点徒劳无功了,请问有什么方式可以获取新的cookies呢?

https://api.bilibili.com/x/frontend/finger/spi

@ncdn
Copy link

ncdn commented Nov 29, 2023

1 use chrome privacy mode , get ua && url's dm_img_str && dm_cover_img_str ,without time testing

2 login mode use ua , cookie's buvid3 && SESSDATA ,2day live

@CzJam
Copy link

CzJam commented Nov 29, 2023

经测试,cookie里必须要添加buvid3和buvid4。而请求url中的dm_cover_img_str为必须项,经解码后发现是GPU渲染信息,因此它为固定值,可以随便填

11.29最新补充:dm_cover_img_str现在不能随便填了,经测试后面几个字符并不是标准的base64编码,但整体依然为固定值。可以直接从浏览器里复制出来用

@o0HalfLife0o
Copy link
Author

o0HalfLife0o commented Nov 29, 2023

经测试,cookie里必须要添加buvid3和buvid4。而请求url中的dm_cover_img_str为必须项,经解码后发现是GPU渲染信息,因此它为固定值,可以随便填

11.29最新补充:dm_cover_img_str现在不能随便填了,经测试后面几个字符并不是标准的base64编码,但整体依然为固定值。可以直接从浏览器里复制出来用

我就是直接复制的浏览器的值,这几天一直正常
dm_img_str解密后WebGL 1
dm_cover_img_str解密后ANGLE (Intel, Intel(R) HD Graphics Direct3D11 vs_5_0 ps_5_0)Google Inc. (Intel)
所以其实没必要随便填,直接用现成的值就行了

@BennettChina
Copy link

gaia_vtoken 就是验证码的

我看了验证码接口里并没有返回这个值,所以才奇怪。不过,我倒是发现虽然有验证码,但是不处理的话,后续再次访问也并不会再次出验证码,也就是并不影响使用,原因似乎是生成的 buvid_fp后续每次打开无痕模式用的都是同一个,爬虫的话如果请求很规律即使 buvid_fp 没换也会被风控,但如果让爬虫的请求时间不规律好像就基本不会触发风控了。

大佬解决了么,可以给个完整代码么,想学习一下代码

我参考的还是这个 issue 和 #933 里的方案,具体可以看下我项目里获取动态函数里的处理方式,需要说明的是固定一个 buvid_fp 偶尔一两次请求还是会风控,但不会一直风控。

getBiliDynamicNew

@mkbkw
Copy link

mkbkw commented Apr 4, 2024

gaia_vtoken 就是验证码的

我看了验证码接口里并没有返回这个值,所以才奇怪。不过,我倒是发现虽然有验证码,但是不处理的话,后续再次访问也并不会再次出验证码,也就是并不影响使用,原因似乎是生成的 buvid_fp后续每次打开无痕模式用的都是同一个,爬虫的话如果请求很规律即使 buvid_fp 没换也会被风控,但如果让爬虫的请求时间不规律好像就基本不会触发风控了。

大佬解决了么,可以给个完整代码么,想学习一下代码

我参考的还是这个 issue 和 #933 里的方案,具体可以看下我项目里获取动态函数里的处理方式,需要说明的是固定一个 buvid_fp 偶尔一两次请求还是会风控,但不会一直风控。

getBiliDynamicNew

前面获取w_rid和wts的代码应该会有点乱,因为我目前只会扣,这两个参数实测请求没问题,但是换个mid就不行了,我也不知道为什么

@BennettChina
Copy link

gaia_vtoken 就是验证码的

我看了验证码接口里并没有返回这个值,所以才奇怪。不过,我倒是发现虽然有验证码,但是不处理的话,后续再次访问也并不会再次出验证码,也就是并不影响使用,原因似乎是生成的 buvid_fp后续每次打开无痕模式用的都是同一个,爬虫的话如果请求很规律即使 buvid_fp 没换也会被风控,但如果让爬虫的请求时间不规律好像就基本不会触发风控了。

大佬解决了么,可以给个完整代码么,想学习一下代码

我参考的还是这个 issue 和 #933 里的方案,具体可以看下我项目里获取动态函数里的处理方式,需要说明的是固定一个 buvid_fp 偶尔一两次请求还是会风控,但不会一直风控。
getBiliDynamicNew

前面获取w_rid和wts的代码应该会有点乱,因为我目前只会扣,这两个参数实测请求没问题,但是换个mid就不行了,我也不知道为什么

这个接口目前需要处理的是

  • spiExClimbWuzhi 接口分别获取buvid3buvid4两个 Cookie 以及上报设备指纹信息并激活,这个则是在 Web 风控相关问题: buvid3, buvid4 获取及激活(ExClimbWuzhi 上传设备指纹消息) #933 里讨论的,里面也有大佬提供了 Python 的 buvid_fp 生成算法,以及各个参数的意义(按照我上面提到的 buvid_fp 不变则可能不被风控理论,那么你测试时完全可以使用浏览器复制的固定值, buvid3buvid4 可以每次有变化)。
  • dm_img 相关参数,可以在这个 issue 找到处理方式
  • wbi 签名参数,这个则可以在本项目 wbi签名 章节找到 Python 的实现 demo 。
  • 两个 wbi 参数是由接口中除 w_ridwts 外的所有参数通过上面的算法生成的,每次请求都是不一样的。

@mkbkw
Copy link

mkbkw commented Apr 5, 2024

兄弟你这太虎了 cookie都贴了 赶紧删了把

目前我也没找到哪个地方的加密出问题,蚌埠住了

@z0z0r4
Copy link
Collaborator

z0z0r4 commented Apr 5, 2024

不是,你们把 cookie 给删了啊,还回复

@lovegaoshi
Copy link

仅针对https://api.bilibili.com/x/space/wbi/arc/search
目前你带cookies和最新UA的话只需要加wbi,这里说的一坨dm_img的校验,包括上面的朋友讲的cookies激活,在账户被风控前都不需要;
我这个没加cookies,但是你只要带了SESSDATA和bili_jct在headers.cookie里就能用
https://github.com/lovegaoshi/ipynb/blob/9fb3f4acf15655e89c4d1c8092b10ab5fa727616/inaConstant.py#L236

@hanselu
Copy link

hanselu commented Apr 5, 2024

这玩意挺蛋疼的,我目前的测试是加SESSDATA和wbi,用postman能过,不加SESSDATA就-352

@xingyezhizhang
Copy link

目前 dm_cover_img_strdm_img_interdm_img_listdm_img_str 填写固定值,还可以在不登录的情况下访问用户主页的视频列表,但不能使用搜索

from urllib.parse import urlencode, unquote
import requests
import hashlib
import base64
import time
import math

def md5(string: str):
    return hashlib.md5(bytes(string, encoding="utf-8")).hexdigest()


def getVideoInfos(uid, startPage=1):
    api = "https://api.bilibili.com/x/space/wbi/arc/search"
    h = {
        "authority": "api.bilibili.com",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36",
        "referer": f"https://m.bilibili.com/space/{uid}",
    }

    timestamp = f"{math.floor(time.time())}"
    params = {
        "dm_cover_img_str": "QU5HTEUgKE5WSURJQSwgTlZJRElBIEdlRm9yY2UgR1QgNzMwICgweDAwMDAxMjg3KSBEaXJlY3QzRDExIHZzXzVfMCBwc181XzAsIEQzRDExKUdvb2dsZSBJbmMuIChOVklESU",
        "dm_img_inter": unquote(
            "%7B%22ds%22:[%7B%22t%22:2,%22c%22:%22Y2xlYXJmaXggZy1zZWFyY2ggc2VhcmNoLWNvbnRhaW5lcg%22,%22p%22:[1269,1,698],%22s%22:[101,563,618]%7D,%7B%22t%22:2,%22c%22:%22c2VjdGlvbiB2aWRlbyBsb2FkaW5nIGZ1bGwtcm93cw%22,%22p%22:[800,26,1365],%22s%22:[188,2930,1892]%7D],%22wh%22:[4183,3491,9],%22of%22:[309,618,309]%7D"
        ),
        "dm_img_list": "[]",
        "dm_img_str": "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
        "keyword": "玳瑁",
        "index": "1",
        "mid": f"{uid}",
        "order": "pubdate",
        "order_avoided": "true",
        "platform": "web",
        "pn": f"{startPage}",
        "ps": "25",
        "web_location": "1550101",
        "wts": timestamp,
    }
    params["w_rid"] = md5(urlencode(params) + "ea1db124af3c7062474693fa704f4ff8")
    resp = requests.get(api, params=params, headers=h)
    with open("tmp/tmp2.json", "wb") as fp:
        fp.write(resp.content)

if __name__ == "__main__":
    getVideoInfos(547072854)

@1769782410
Copy link

1769782410 commented Apr 24, 2024

在未登录情况下只需要w_rid 鼠标轨迹需要置空 页面的js走的另外一个逻辑 就不会触发风控校验了

@lanyangyin
Copy link

signed_params = encWbi(
params={
'foo': '114',
'bar': '514',
'baz': 1919810
},
img_key=img_key,
sub_key=sub_key
)

encWbi要以你实际请求的参数算出加密的值,而不是例子里的{foo,bar,baz,...}。

非常感谢您,那应该怎么算呢?我应该怎么得到foo bar 还有baz啊

就是你请求啥,就要拿啥算,算出的wts和w_rid再加入请求参数列表里,再发起request。 比如你这里的,需要用包含mid, ps, tid, pn, keyword, order, platform, web_location, order_avoided, dm_img_list, dm_img_str, dm_cover_img_str, dm_img_inter 的字典算。当然有些参数,比如tid是可选参数。

对的,我明白你的意思,我是想问问dm_img_list, dm_img_str, dm_cover_img_str, dm_img_inter,w_rid和wts都是怎么计算的呢?看了很多帖子,把大家的计算方式弄进去最后还是403,不知道是不是我的公式有问题,想问问大佬我说的那几个变量是怎么计算的呢?

举个例子:
当你用 https://api.bilibili.com/x/space/wbi/acc/info 获取 ”陈睿“用户空间详细信息 时
文档要求参数有:mid; w_rid; wts
w_rid; wts是通过wbi获得的,所以您需要使用mid获得w_rid; wts
你需要将
params={
'foo': '114',
'bar': '514',
'baz': 1919810
},
更改成
params={
'mid': '208259',
},
在python的demo中signed_params可以直接作为requests.get(f'https://api.bilibili.com/x/space/wbi/acc/info', headers=headers, params=signed_params)的params

@power721
Copy link

power721 commented May 7, 2024

要加referer了,否则412
https://space.bilibili.com

@fanza1
Copy link

fanza1 commented May 7, 2024

{
    "code": -412,
    "message": "request was banned",
    "ttl": 1
}

我也遇到-412,headers加referer又好了:

headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:32.0) Gecko/20100101 Firefox/32.0',
'Cookie': 'SESSDATA=' + SESSDATA,
'referer': 'https://message.bilibili.com/',
}

@iyzyi
Copy link

iyzyi commented May 8, 2024

{
    "code": -412,
    "message": "request was banned",
    "ttl": 1
}

我也遇到-412,headers加referer又好了:

headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:32.0) Gecko/20100101 Firefox/32.0',
'Cookie': 'SESSDATA=' + SESSDATA,
'referer': 'https://message.bilibili.com/',
}

早知道直接拖动到这一页的最后,直接看你的回复了。
亲测有效。我用的是:

'referer': 'https://space.bilibili.com',

@Jackpoal
Copy link

Jackpoal commented Jun 7, 2024

大佬们api.bilibili.com/x/space/wbi/arc/search的请求,怎么都算不对63e4dda73c677377da02e21c211916af
验证过其他的参数value没有列表或字典时(dm_img_listdm_img_inter值类似),可以正确计算,不知怎么
办了

`from functools import reduce
from urllib.parse import urlencode
from hashlib import md5

WRI_LIST = [
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
36, 20, 34, 44, 52
]
def get_wrid(nav_key,parame:dict):
parame = {
i: "".join(filter(lambda x:x not in "!'()*",str(j)))
# for i,j in parame.items()
for i,j in dict(sorted(parame.items())).items()
}

mixinkey = reduce(lambda x,y: x+nav_key[y], WRI_LIST, "")[:32]
parame = urlencode(parame)
# parame = "&".join(["%s=%s"%(m,n) for m,n in parame.items()]).replace(" ","")
print(parame)
return mixinkey, md5(
    (parame + mixinkey).encode("utf-8")
).hexdigest()

print(
get_wrid(
"7cd084941338484aae1ad9425b84077c" + "4932caff0ff746eab6f01bf08b70ac45",
{
"mid": "2223018",
"ps": "30",
"tid": "0",
"pn": "2",
"keyword": "",
"order": "pubdate",
"platform": "web",
"web_location": "1550101",
"order_avoided": "true",
"dm_img_list": "[]",

        "dm_img_str": "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
        "dm_cover_img_str": "QU5HTEUgKEludGVsLCBJbnRlbChSKSBVSEQgR3JhcGhpY3MgNjMwICgweDAwMDAzRTkxKSBEaXJlY3QzRDExIHZzXzVfMCBwc181XzAsIEQzRDExKUdvb2dsZSBJbmMuIChJbnRlbC",

        "dm_img_inter": '{"ds":[{"t":2,"c":"Y2xlYXJmaXggZy1zZWFyY2ggc2VhcmNoLWNvbnRhaW5lcg","p":[1738,94,696],"s":[100,562,616]},{"t":2,"c":"d3JhcHBlcg","p":[675,7,1300],"s":[441,4420,3934]}],"wh":[3867,3944,107],"of":[21,42,21]}',

        "wts": "1717723637"
        # "w_rid": "63e4dda73c677377da02e21c211916af"
    }
)

)`

@1769782410
Copy link

大佬们api.bilibili.com/x/space/wbi/arc/search的请求,怎么都算不对63e4dda73c677377da02e21c211916af, 验证过其他的参数value没有列表或字典时(dm_img_listdm_img_inter值类似),可以正确计算,不知怎么 办了

`from functools import reduce from urllib.parse import urlencode from hashlib import md5

WRI_LIST = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 ] def get_wrid(nav_key,parame:dict): parame = { i: "".join(filter(lambda x:x not in "!'()*",str(j))) # for i,j in parame.items() for i,j in dict(sorted(parame.items())).items() }

mixinkey = reduce(lambda x,y: x+nav_key[y], WRI_LIST, "")[:32]
parame = urlencode(parame)
# parame = "&".join(["%s=%s"%(m,n) for m,n in parame.items()]).replace(" ","")
print(parame)
return mixinkey, md5(
    (parame + mixinkey).encode("utf-8")
).hexdigest()

print( get_wrid( "7cd084941338484aae1ad9425b84077c" + "4932caff0ff746eab6f01bf08b70ac45", { "mid": "2223018", "ps": "30", "tid": "0", "pn": "2", "keyword": "", "order": "pubdate", "platform": "web", "web_location": "1550101", "order_avoided": "true", "dm_img_list": "[]",

        "dm_img_str": "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
        "dm_cover_img_str": "QU5HTEUgKEludGVsLCBJbnRlbChSKSBVSEQgR3JhcGhpY3MgNjMwICgweDAwMDAzRTkxKSBEaXJlY3QzRDExIHZzXzVfMCBwc181XzAsIEQzRDExKUdvb2dsZSBJbmMuIChJbnRlbC",

        "dm_img_inter": '{"ds":[{"t":2,"c":"Y2xlYXJmaXggZy1zZWFyY2ggc2VhcmNoLWNvbnRhaW5lcg","p":[1738,94,696],"s":[100,562,616]},{"t":2,"c":"d3JhcHBlcg","p":[675,7,1300],"s":[441,4420,3934]}],"wh":[3867,3944,107],"of":[21,42,21]}',

        "wts": "1717723637"
        # "w_rid": "63e4dda73c677377da02e21c211916af"
    }
)

)`

dm_img_list可以置空的 逻辑很简单,就一个md5 有需要可以联系我,目前测试未登录也能过风控

@Jackpoal
Copy link

大佬们api.bilibili.com/x/space/wbi/arc/search的请求,怎么都算不对63e4dda73c677377da02e21c211916af, 验证过其他的参数value没有列表或字典时(dm_img_listdm_img_inter值类似),可以正确计算,不知怎么 办了
`from functools import reduce from urllib.parse import urlencode from hashlib import md5
WRI_LIST = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 ] def get_wrid(nav_key,parame:dict): parame = { i: "".join(filter(lambda x:x not in "!'()*",str(j))) # for i,j in parame.items() for i,j in dict(sorted(parame.items())).items() }

mixinkey = reduce(lambda x,y: x+nav_key[y], WRI_LIST, "")[:32]
parame = urlencode(parame)
# parame = "&".join(["%s=%s"%(m,n) for m,n in parame.items()]).replace(" ","")
print(parame)
return mixinkey, md5(
    (parame + mixinkey).encode("utf-8")
).hexdigest()

print( get_wrid( "7cd084941338484aae1ad9425b84077c" + "4932caff0ff746eab6f01bf08b70ac45", { "mid": "2223018", "ps": "30", "tid": "0", "pn": "2", "keyword": "", "order": "pubdate", "platform": "web", "web_location": "1550101", "order_avoided": "true", "dm_img_list": "[]",

        "dm_img_str": "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
        "dm_cover_img_str": "QU5HTEUgKEludGVsLCBJbnRlbChSKSBVSEQgR3JhcGhpY3MgNjMwICgweDAwMDAzRTkxKSBEaXJlY3QzRDExIHZzXzVfMCBwc181XzAsIEQzRDExKUdvb2dsZSBJbmMuIChJbnRlbC",

        "dm_img_inter": '{"ds":[{"t":2,"c":"Y2xlYXJmaXggZy1zZWFyY2ggc2VhcmNoLWNvbnRhaW5lcg","p":[1738,94,696],"s":[100,562,616]},{"t":2,"c":"d3JhcHBlcg","p":[675,7,1300],"s":[441,4420,3934]}],"wh":[3867,3944,107],"of":[21,42,21]}',

        "wts": "1717723637"
        # "w_rid": "63e4dda73c677377da02e21c211916af"
    }
)

)`

dm_img_list可以置空的 逻辑很简单,就一个md5 有需要可以联系我,目前测试未登录也能过风控

可以试下我现在的这个参数字典哪里有问题吗,感觉就是dm_img_inter这个参数编码有问题,计算与抓包不一致

@Jackpoal
Copy link

大佬们api.bilibili.com/x/space/wbi/arc/search的请求,怎么都算不对63e4dda73c677377da02e21c211916af, 验证过其他的参数value没有列表或字典时(dm_img_listdm_img_inter值类似),可以正确计算,不知怎么 办了
`from functools import reduce from urllib.parse import urlencode from hashlib import md5
WRI_LIST = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 ] def get_wrid(nav_key,parame:dict): parame = { i: "".join(filter(lambda x:x not in "!'()*",str(j))) # for i,j in parame.items() for i,j in dict(sorted(parame.items())).items() }

mixinkey = reduce(lambda x,y: x+nav_key[y], WRI_LIST, "")[:32]
parame = urlencode(parame)
# parame = "&".join(["%s=%s"%(m,n) for m,n in parame.items()]).replace(" ","")
print(parame)
return mixinkey, md5(
    (parame + mixinkey).encode("utf-8")
).hexdigest()

print( get_wrid( "7cd084941338484aae1ad9425b84077c" + "4932caff0ff746eab6f01bf08b70ac45", { "mid": "2223018", "ps": "30", "tid": "0", "pn": "2", "keyword": "", "order": "pubdate", "platform": "web", "web_location": "1550101", "order_avoided": "true", "dm_img_list": "[]",

        "dm_img_str": "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ",
        "dm_cover_img_str": "QU5HTEUgKEludGVsLCBJbnRlbChSKSBVSEQgR3JhcGhpY3MgNjMwICgweDAwMDAzRTkxKSBEaXJlY3QzRDExIHZzXzVfMCBwc181XzAsIEQzRDExKUdvb2dsZSBJbmMuIChJbnRlbC",

        "dm_img_inter": '{"ds":[{"t":2,"c":"Y2xlYXJmaXggZy1zZWFyY2ggc2VhcmNoLWNvbnRhaW5lcg","p":[1738,94,696],"s":[100,562,616]},{"t":2,"c":"d3JhcHBlcg","p":[675,7,1300],"s":[441,4420,3934]}],"wh":[3867,3944,107],"of":[21,42,21]}',

        "wts": "1717723637"
        # "w_rid": "63e4dda73c677377da02e21c211916af"
    }
)

)`

dm_img_list可以置空的 逻辑很简单,就一个md5 有需要可以联系我,目前测试未登录也能过风控

可以试下我现在的这个参数字典哪里有问题吗,感觉就是dm_img_inter这个参数编码有问题,计算与抓包不一致

参数引号需要转义,其他不用,已验证

@ZM-J
Copy link

ZM-J commented Aug 20, 2024

未登录不太行

...
# wbi 签名代码略

def getCookies():
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
        'Referer': 'https://www.bilibili.com/'
    }
    resp = requests.get('https://api.bilibili.com/x/frontend/finger/spi', headers=headers)
    resp.raise_for_status()
    json_content = resp.json()
    return {
        'buvid3': json_content['data']['b_3'],
        'buvid4': json_content['data']['b_4'],
    }


img_key, sub_key = getWbiKeys()

signed_params = encWbi(
    params={
        'mid': '451618887',
        'dm_img_list': [],
        'dm_img_str': 'V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ',
        'dm_cover_img_str': 'QU5HTEUgKE5WSURJQSwgTlZJRElBIEdlRm9yY2UgUlRYIDMwNjAgKDB4MDAwMDI1MDQpIERpcmVjdDNEMTEgdnNfNV8wIHBzXzVfMCwgRDNEMTEpR29vZ2xlIEluYy4gKE5WSURJQS',
        # 'dm_img_inter': '{"ds":[{"t":2,"c":"Y2xlYXJmaXggZy1zZWFyY2ggc2VhcmNoLWNvbnRhaW5lcg","p":[1740,38,595],"s":[371,833,1158]},{"t":2,"c":"c2VjdGlvbiB2aWRlbyBsb2FkaW5nIGZ1bGwtcm93cw","p":[1078,52,1326],"s":[108,2848,1736]}],"wh":[5544,4023,108],"of":[208,416,208]}',
    },
    img_key=img_key,
    sub_key=sub_key
)
query = urllib.parse.urlencode(signed_params)

cookies = getCookies()

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
    'referer': 'https://space.bilibili.com',
    'Connection': 'close',
}

url = 'https://api.bilibili.com/x/space/wbi/arc/search'
res = requests.get(url, query, headers=headers, cookies=cookies)
print(res.text)
print(res.url)

仍然返回

{'code': -352, 'message': '风控校验失败', 'ttl': 1, 'data': {'v_voucher': 'voucher_e78aa11a-efc9-4026-860f-4a984442140e'}}

目前的请求频率最多 30s 一次,就这样还是风控校验失败,乌鱼子

@power721
Copy link

新增了参数:w_webid
是一个JWT token,不知道怎么获取的。

@kaosaa
Copy link

kaosaa commented Oct 12, 2024

新增了参数:w_webid 是一个JWT token,不知道怎么获取的。

你好 刚刚找了一下 是在https://space.bilibili.com/{uid}/video 下的

<script id="__RENDER_DATA__" type="application/json"></script>中

例子:

<script id="__RENDER_DATA__" type="application/json"> %7 B%22 access_id%22%3 A%22 eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzcG1faWQiOiIwLjAiLCJidXZpZCI6IjMwNEI0NEI5LTU5RTQtOUExRC0wOTgxLTE0OTZFNEFFRTkxNjIxNzE3aW5mb2MiLCJ1c2VyX2FnZW50IjoiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzEyOS4wLjAuMCBTYWZhcmkvNTM3LjM2IEVkZy8xMjkuMC4wLjAiLCJidXZpZF9mcCI6IjA2OWFkNzU4YzhmYTFmMWI3ZDJjN2VjZWUzNTk4MTQxIiwiYmlsaV90aWNrZXQiOiIwNDQyZGVjYWUxOTY0Zjk5YjM2NjA4MmZiYjQ5NWNmZSIsImNyZWF0ZWRfYXQiOjE3Mjg3MzEzOTgsInR0bCI6ODY0MDAsInVybCI6Ii8xOTIwNTA4OC92aWRlbyIsInJlc3VsdCI6Im5vcm1hbCIsImlzcyI6ImdhaWEiLCJpYXQiOjE3Mjg3MzEzOTh9.X1CBDOkZ44qFogoTQ7uiUEEwHyKnbpiUOXM0MWetRFSR9caULEl_ZONuC3GnTV0s7_LoHmHb46kgkOMv0UiLozELfmmvSdbdpAxHUdwHk1pAvly4ybPAVesI2KFKl2LLWtr-mTziuwpVuhKrLKL6-cJrN7WaLbVNZtQzrcoom71AEYOBjnG5C_LHhYolefS1yebkuot4l3ekIrK4-tgOK0JJ4uAxX2rg1_fjRVut8lfYMomf8nkKNLCYUv5LQj7xKWFNjBt2LpHK-J0zgfAe9L0IQKfYYVqXELQCiFUetm5ALrjGGJebi2vNrghsGC0unzdDCAqQymv41A614pXEmg%22%7 D</script>

@grqz
Copy link

grqz commented Oct 13, 2024

新增了参数:w_webid 是一个JWT token,不知道怎么获取的。

see #1107

@Hansimov
Copy link

Hansimov commented Nov 5, 2024

@xingyezhizhang :

目前 dm_cover_img_strdm_img_interdm_img_listdm_img_str 填写固定值,还可以在不登录的情况下访问用户主页的视频列表,但不能使用搜索

事实上,dm_img_inter 的参数由 bili-user-fingerprint.min.js 生成:

逆向+反混淆了下,定位到如下部分:

{
    'key': "getActiveFeaturesStr",
    'value': function (_0x211a54) {
        var _0x2cac63 = {
            'ds': this.getElInfo(this.activityDetector.logStack, _0x211a54),
            'wh': _0xb3fd69([~~this.winWidth, ~~this.winHeight]),
            'of': _0x445907([~~this.scrollTop, ~~this.scrollLeft])
        };
        ...
    }
}

其中 whof 是必须的,ds 可以不填:

  • whwinWidthwinHeight 有关
  • ofscrollTopscrollLeft 有关

生成 whof 的代码如下,可以直接 node.js 或者浏览器 console 运行,后面有空我会转成 python 代码:

点击展开 js 代码
var _0x4a3c97 = {
    0x9a: function (_0x5bb687) {
        _0x5bb687.exports = function (_0x1f90c6, _0x31c5e5) {
            if (null == _0x31c5e5 || _0x31c5e5 > _0x1f90c6.length) {
                _0x31c5e5 = _0x1f90c6.length;
            }
            var _0x51f02c = 0x0;
            for (var _0x40c03d = new Array(_0x31c5e5); _0x51f02c < _0x31c5e5; _0x51f02c++) {
                _0x40c03d[_0x51f02c] = _0x1f90c6[_0x51f02c];
            }
            return _0x40c03d;
        };
        _0x5bb687.exports.__esModule = true;
        _0x5bb687.exports['default'] = _0x5bb687.exports;
    },
    0x1ce: function (_0x9ed89f) {
        _0x9ed89f.exports = function (_0x365f6e) {
            if (Array.isArray(_0x365f6e)) {
                return _0x365f6e;
            }
        };
        _0x9ed89f.exports.__esModule = true;
        _0x9ed89f.exports["default"] = _0x9ed89f.exports;
    },
    0x1c1: function (_0x37829f) {
        _0x37829f.exports = function (_0x1469fb, _0x1ab30e) {
            var _0x1fbf83 = null == _0x1469fb ? null : "undefined" != typeof Symbol && _0x1469fb[Symbol.iterator] || _0x1469fb['@@iterator'];
            if (null != _0x1fbf83) {
                var _0x17fb83;
                var _0x2a83aa;
                var _0x3214dc;
                var _0x5f1214;
                var _0xd0c4b0 = [];
                var _0x322258 = true;
                var _0x4d8f48 = false;
                try {
                    _0x3214dc = (_0x1fbf83 = _0x1fbf83.call(_0x1469fb)).next;
                    if (0x0 === _0x1ab30e) {
                        if (Object(_0x1fbf83) !== _0x1fbf83) {
                            return;
                        }
                        _0x322258 = false;
                    } else {
                        for (; !(_0x322258 = (_0x17fb83 = _0x3214dc.call(_0x1fbf83)).done) && (_0xd0c4b0.push(_0x17fb83.value), _0xd0c4b0.length !== _0x1ab30e); _0x322258 = true) {
                            ;
                        }
                    }
                } catch (_0xf53436) {
                    _0x4d8f48 = true;
                    _0x2a83aa = _0xf53436;
                } finally {
                    try {
                        if (!_0x322258 && null != _0x1fbf83["return"] && (_0x5f1214 = _0x1fbf83["return"](), Object(_0x5f1214) !== _0x5f1214)) {
                            return;
                        }
                    } finally {
                        if (_0x4d8f48) {
                            throw _0x2a83aa;
                        }
                    }
                }
                return _0xd0c4b0;
            }
        };
        _0x37829f.exports.__esModule = true;
        _0x37829f.exports["default"] = _0x37829f.exports;
    },
    0x1af: function (_0x570abd) {
        _0x570abd.exports = function () {
            throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
        };
        _0x570abd.exports.__esModule = true;
        _0x570abd.exports["default"] = _0x570abd.exports;
    },
    0x1e8: function (_0x593566, _0x1d5468, _0x8cf5d2) {
        var _0x2e17e8 = _0x8cf5d2(0x1ce);
        var _0x57cc6c = _0x8cf5d2(0x1c1);
        var _0x5dccfd = _0x8cf5d2(0xed);
        var _0x1b69a4 = _0x8cf5d2(0x1af);
        _0x593566.exports = function (_0x4b3628, _0x6ee937) {
            return _0x2e17e8(_0x4b3628) || _0x57cc6c(_0x4b3628, _0x6ee937) || _0x5dccfd(_0x4b3628, _0x6ee937) || _0x1b69a4();
        };
        _0x593566.exports.__esModule = true;
        _0x593566.exports["default"] = _0x593566.exports;
    },
    0xed: function (_0xebb7e6, _0x44f457, _0x3cd6d2) {
        var _0x36eae3 = _0x3cd6d2(0x9a);
        _0xebb7e6.exports = function (_0x2f87ea, _0xbf5a3e) {
            if (_0x2f87ea) {
                if ("string" == typeof _0x2f87ea) {
                    return _0x36eae3(_0x2f87ea, _0xbf5a3e);
                }
                var _0xd16a61 = Object.prototype.toString.call(_0x2f87ea).slice(0x8, -0x1);
                if ("Object" === _0xd16a61 && _0x2f87ea.constructor) {
                    _0xd16a61 = _0x2f87ea.constructor.name;
                }
                return 'Map' === _0xd16a61 || "Set" === _0xd16a61 ? Array.from(_0x2f87ea) : "Arguments" === _0xd16a61 || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(_0xd16a61) ? _0x36eae3(_0x2f87ea, _0xbf5a3e) : undefined;
            }
        };
        _0xebb7e6.exports.__esModule = true;
        _0xebb7e6.exports["default"] = _0xebb7e6.exports;
    }
};
var _0x4fcdbb = {};
function _0x4c34b9(_0x1d8bf5) {
    var _0xaf9ab3 = _0x4fcdbb[_0x1d8bf5];
    if (undefined !== _0xaf9ab3) {
        return _0xaf9ab3.exports;
    }
    var _0x376759 = _0x4fcdbb[_0x1d8bf5] = {
        'exports': {}
    };
    _0x4a3c97[_0x1d8bf5](_0x376759, _0x376759.exports, _0x4c34b9);
    return _0x376759.exports;
}
_0x4c34b9.n = function (_0x14ba50) {
    var _0xc3b107 = _0x14ba50 && _0x14ba50.__esModule ? function () {
        return _0x14ba50["default"];
    } : function () {
        return _0x14ba50;
    };
    _0x4c34b9.d(_0xc3b107, {
        'a': _0xc3b107
    });
    return _0xc3b107;
};
_0x4c34b9.d = function (_0x4adf94, _0x2c819d) {
    for (var _0x39c24a in _0x2c819d) if (_0x4c34b9.o(_0x2c819d, _0x39c24a) && !_0x4c34b9.o(_0x4adf94, _0x39c24a)) {
        Object.defineProperty(_0x4adf94, _0x39c24a, {
            'enumerable': true,
            'get': _0x2c819d[_0x39c24a]
        });
    }
};
_0x4c34b9.o = function (_0x3b52d0, _0x105827) {
    return Object.prototype.hasOwnProperty.call(_0x3b52d0, _0x105827);
};


var _0x613a5f = _0x4c34b9(0x1e8);
var _0x16d5d6 = _0x4c34b9.n(_0x613a5f);
var _0x5ec12e = ['g', 'w', 'A', 'Q'];
var _0x30ddcd = String.fromCharCode(0x4d);
var _0x5d32aa = String.fromCharCode(0x4e);
var _0x5773c2 = btoa(0x9b.toString()).substring(0x6);
var _0x4a678a = Number(atob(_0x30ddcd + _0x5ec12e[0x0] + _0x5773c2));
var _0x3bc098 = Number(atob(_0x30ddcd + _0x5ec12e[0x1] + _0x5773c2));
var _0x1e4c6f = Number(atob(_0x5d32aa + _0x5ec12e[0x2] + _0x5773c2));
Number(atob(_0x5d32aa + _0x5ec12e[0x3] + _0x5773c2));
var _0xb3fd69 = function (_0x361c67) {
    var _0x556632 = _0x16d5d6()(_0x361c67, 0x2);
    var _0x3dc4b6 = _0x556632[0x0];
    var _0x2bea5e = _0x556632[0x1];
    var _0x1b447a = Math.floor(0x72 * Math.random());
    return [_0x4a678a * _0x3dc4b6 + _0x4a678a * _0x2bea5e + _0x3bc098 * _0x1b447a, _0x1e4c6f * _0x3dc4b6 - _0x2bea5e + _0x1b447a, _0x1b447a];
};
var _0x445907 = function (_0x61fce4) {
    var _0x2fb26a = _0x16d5d6()(_0x61fce4, 0x2);
    var _0x18bce7 = _0x2fb26a[0x0];
    var _0x24ce6a = _0x2fb26a[0x1];
    var _0x4fcb44 = Math.floor(0x202 * Math.random());
    return [_0x3bc098 * _0x18bce7 + _0x4a678a * _0x24ce6a + _0x4fcb44, _0x1e4c6f * _0x18bce7 - _0x1e4c6f * _0x24ce6a + _0x4a678a * _0x4fcb44, _0x4fcb44];
};

console.log('wh:', _0xb3fd69([1920, 1080]));
console.log('of:', _0x445907([10, 10]));
  • 样例中,设窗口宽高为 19201080,测了其他诸如 1280720,生成的参数也是有效的
  • 样例中,设滚动偏移为 1010,测了其他诸如 5050,生成的参数也是有效的

输出形如:

wh: [ 6093, 6631, 31 ]
of: [ 430, 760, 380 ]
  • 事实上,whof 均取固定值 [0,0,0] 也是有效的。

关于 dm_img_list,必须要有,不过可以置空 []

关于 dm_img_strdm_cover_img_str,和 webgl 相关,上面已经有大佬贴出具体生成算法。事实上只要格式符合某种验证算法都可以,例如这两个同取 XXcXXXVXXX,也是有效的。具体验证算法有空细究。

Just-Prog added a commit to A-SOUL-Re-creation-Plan/Panel2ReWebApi that referenced this issue Dec 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
主站杂项/Misc 接口:主站其他类型 更新/Update 更新过时内容
Projects
None yet
Development

No branches or pull requests