feat: 各种接口补充与错误修正 (#1066)
* fix(video/collection.md): unclosed xml tags * feat(video/report.md): uuid not really random * feat: b23.tv short link * feat: login/moral/exp log * feat: v_voucher * feat: upload video cover & post * feat(creativecenter/upload.md): upload octet-stream * feat(creativecenter/upload.md): update some notes * feat(clientinfo/ip.md): another from live * feat: live web heartbeat * feat: update popular series & precious * fix(creativecenter/upload.md): mistakes in example * feat: merge duplicate fav info * feat(README.md): missing link to webmask * feat(search/hot.md): word_type * feat(login/login_action): recovery old api * feat(video/collection.md): series operation * feat: video season operation * feat: add & mod some links * feat(search/suggest.md): up to date * feat: web home header image * feat(misc/sign/bili_ticket.md): demo for nodejs * feat(creativecenter/upload.md): x-upos-auth validity period * feat: add referer & buvid3 to request header * feat: customer service message heartbeat & upload * feat(customerservice/msg.md): upload code 1200201 * feat(video/report.md): click/web/h5 * feat(video/report.md): view count desc * feat: laser2 * feat: wbi_key in bili_ticket * fix: typo & unclosed XML tags * feat(misc/sign/v_voucher): cookie x-bili-gaia-vtoken * feat(search/hot.md): square * feat(video/status_number.md): fold archive_stat/stat * feat(fav/info.md): resource/infos invalid type 21 * feat: /x/activity/subject/info * feat: lottery * feat(docs/misc/b23.tv): remove some unnecessary fields * feat(creativecenter/upload.md): types/predict * fix(video/collection.md): invalid end tag * feat: app version upgrade * feat(creativecenter/upload.md): tag/recommend #528 * feat(user/status_number.md): navnum * feat: /x/activity/page/list * feat(comment/list.md): desc about pagination_str * feat(comment/list.md): update example * feat(dynamic/all.md): #1082 * fix(comment/list.md): -352 not -412 * feat: #700 * feat(video/video_stream.md): #606 & cv949156 * feat(message/private_msg.md): single_unread freq * feat: getUserWallet * fix: broken form * feat(Layout.vue): copyright to 2024 * feat: /x/topic/pub/rcmd/search * feat: #425 * feat(misc/time_stamp.md): rtc/getTimestamp * fix(misc/time_stamp.md): missing end tag * feat: #745 * feat(dynamic/all.md): update feed/all * feat(danmaku/action.md): #220 * feat(live/info.md): gethistory from cv8186413 * fix(danmaku/action.md): missing end tag
This commit is contained in:
@@ -24,8 +24,78 @@
|
||||
3. 构造请求参数,`key_id` 为 `ec02`,`hexsign` 为变量 `hexsign` 值,`context[ts]` 为变量 `timestamp` 值,`csrf` 为 cookie 中的 `bili_jct` 值也可为空
|
||||
4. 发送 `POST` 请求,获取 `data` 字段中的 `ticket` 字段的值即为所求
|
||||
|
||||
## 接口
|
||||
|
||||
> https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket
|
||||
|
||||
*请求方式: POST*
|
||||
|
||||
**URL参数:**
|
||||
|
||||
| 参数名 | 类型 | 内容 | 必要性 | 备注 |
|
||||
| ----- | ---- | ---- | ------ | ---- |
|
||||
| key_id | str | ec02 | 必要 | |
|
||||
| hexsign | str | 由 `hmac_sha256` 算法计算的 `hexsign` 值 | 必要 | |
|
||||
| context[ts] | num | UNIX 秒级时间戳 | 必要 | |
|
||||
| csrf | str | cookie 中的 `bili_jct` 值 | 非必要 | |
|
||||
|
||||
**JSON回复:**
|
||||
|
||||
根对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| code | num | 返回值 | 0: 成功<br />400: 参数错误 |
|
||||
| message | str | 返回消息 | OK: 成功 |
|
||||
| data | obj | 数据本体 | |
|
||||
| ttl | num | 1 | |
|
||||
|
||||
`data` 对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| ticket | str | bili_ticket | |
|
||||
| created_at | num | 创建时间 | UNIX 秒级时间戳 |
|
||||
| ttl | num | 有效时长 | 259200 秒 (3 天) |
|
||||
| context | obj | 空 | |
|
||||
| nav | obj | wbi_img 相关 | 参见 [WBI 签名](./wbi.md) |
|
||||
|
||||
`nav` 对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| img | str | img_key 值 | 参见 [WBI 签名](./wbi.md) |
|
||||
| sub | str | sub_key 值 | 参见 [WBI 签名](./wbi.md) |
|
||||
|
||||
**示例:**
|
||||
|
||||
<details>
|
||||
<summary>查看响应示例:</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "OK",
|
||||
"data": {
|
||||
"ticket": "eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjM2OTMwODAsImlhdCI6MTcyMzQzMzgyMCwicGx0IjotMX0.efOwv7i4m0ykABrXEDHGAechU2AByMcP_-3EYpQrNKs",
|
||||
"created_at": 1723433820,
|
||||
"ttl": 259200,
|
||||
"context": {},
|
||||
"nav": {
|
||||
"img": "https://i0.hdslb.com/bfs/wbi/7cd084941338484aae1ad9425b84077c.png",
|
||||
"sub": "https://i0.hdslb.com/bfs/wbi/4932caff0ff746eab6f01bf08b70ac45.png"
|
||||
}
|
||||
},
|
||||
"ttl": 1
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Demo
|
||||
|
||||
此处提供 [Python](#python), [Java](#java), [JavaScript (Node.js)](#javascript-nodejs) 的示例代码
|
||||
|
||||
### Python
|
||||
|
||||
需要 `requests` 依赖
|
||||
@@ -176,3 +246,62 @@ public class BiliTicketDemo {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### JavaScript (Node.js)
|
||||
|
||||
```javascript
|
||||
const crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Generate HMAC-SHA256 signature
|
||||
* @param {string} key The key string to use for the HMAC-SHA256 hash
|
||||
* @param {string} message The message string to hash
|
||||
* @returns {string} The HMAC-SHA256 signature as a hex string
|
||||
*/
|
||||
function hmacSha256(key, message) {
|
||||
const hmac = crypto.createHmac('sha256', key);
|
||||
hmac.update(message);
|
||||
return hmac.digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Bilibili web ticket
|
||||
* @param {string} csrf CSRF token, can be empty or null
|
||||
* @returns {Promise<any>} Promise of the ticket response in JSON format
|
||||
*/
|
||||
async function getBiliTicket(csrf) {
|
||||
const ts = Math.floor(Date.now() / 1000);
|
||||
const hexSign = hmacSha256('XgwSnGZ1p', `ts${ts}`);
|
||||
const url = 'https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket';
|
||||
const params = new URLSearchParams({
|
||||
key_id: 'ec02',
|
||||
hexsign: hexSign,
|
||||
'context[ts]': ts,
|
||||
csrf: csrf || ''
|
||||
});
|
||||
try {
|
||||
const response = await fetch(`${url}?${params.toString()}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0'
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const ticketResponse = await getBiliTicket(''); // use empty CSRF here
|
||||
console.log(ticketResponse);
|
||||
} catch (e) {
|
||||
console.error('Failed to get BiliTicket:', error);
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
177
docs/misc/sign/v_voucher.md
Normal file
177
docs/misc/sign/v_voucher.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# v_voucher 验证
|
||||
|
||||
## 简述
|
||||
|
||||
当同一接口在短时间内被同一用户/IP/UA多次请求或异常时, 会触发风控, 如接口返回 `code` 为 `-352` 即 `风控校验失败`, 同时 `data` 中出现 `v_voucher` 字段
|
||||
|
||||
`v_voucher` 结构为字符串 `voucher_` 尾随一串以 `-` 为分隔符的小写 UUID
|
||||
|
||||
`v_voucher` 可用于申请 captcha 验证码, 根据验证结果使用 `validate` 接口获取 `grisk_id` 作为被风控接口的 `gaia_vtoken` 与 Cookie 中的 `x-bili-gaia-vtoken` 即可恢复正常访问
|
||||
|
||||
若该情况出现在使用 Wbi 签名的接口中, 建议先检查 Wbi 签名是否正确. 若已检查 Wbi 签名或无需签名, 检查请求头中 `User-Agent` `Referer` 是否正常, 以及 `Cookie` 中 [`bili_ticket`](bili_ticket.md) [`b_nut` `buvid3` `buvid4`](../buvid3_4.md) 等是否存在. 使用 captcha 是最后的选择, 因为 captcha 验证需要用户操作<!--, 且这几天做验证码做的真的要疯了喵-->
|
||||
|
||||
参见 [#1067](https://github.com/SocialSisterYi/bilibili-API-collect/issues/1067)
|
||||
|
||||
## 操作流程
|
||||
|
||||
1. 快速以不正确的姿势请求接口, 直到返回 `v_voucher` 字段如下
|
||||
|
||||
```json
|
||||
{
|
||||
"code": -352,
|
||||
"message": "风控校验失败",
|
||||
"ttl": 1,
|
||||
"data": {
|
||||
"v_voucher": "voucher_84a8c3ce-33f5-4551-9552-9c6b13aa7938"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. [请求 `register` 接口](#从-v_voucher-申请-captcha), 请求体传入 `csrf` 及 `v_voucher`, 该接口返回与 [申请captcha验证码](../../login/login_action/readme.md#申请captcha验证码) 部分相同, 记录此处返回的 `token` `challenge`
|
||||
|
||||
3. 按照 [验证captcha验证码](../../login/login_action/readme.md#验证captcha验证码) 进行验证, 记下验证结果的 `validate` 与 `seccode`
|
||||
|
||||
4. [请求 `validate` 接口](#从验证结果获取-grisk_id), 请求体传入 `challenge` `token` `validate` `seccode` `csrf`, 该接口返回 `grisk_id` 即 `gaia_vtoken`
|
||||
|
||||
5. 重新请求原接口, 原 URL 参数加入 `gaia_vtoken`, 即恢复正常
|
||||
|
||||
## 接口列表
|
||||
|
||||
### 从 v_voucher 申请 captcha
|
||||
|
||||
> https://api.bilibili.com/x/gaia-vgate/v1/register
|
||||
|
||||
注: 同一有效 `v_voucher` 只能请求一次, 请求完毕请立即 [进行验证](../../login/login_action/readme.md#进行验证) 防止过期失效
|
||||
|
||||
*请求方式: POST*
|
||||
|
||||
**正文参数(application/x-www-form-urlencoded):**
|
||||
|
||||
| 参数名 | 类型 | 内容 | 必要性 | 备注 |
|
||||
| ------ | ---- | ------- | ------ | ---- |
|
||||
| csrf | str | CSRF Token (位于 Cookie 的 bili_jct) | 非必要 | |
|
||||
| v_voucher | str | v_voucher | 必要 | |
|
||||
|
||||
**JSON回复:**
|
||||
|
||||
根对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ----- | ---- | ------ | ---- |
|
||||
| code | num | 返回值 | 0:成功<br />100000: 验证码获取失败 |
|
||||
| message | str | 错误信息 | 默认为 0 |
|
||||
| ttl | num | 1 | |
|
||||
| data | obj | 信息本体 | |
|
||||
|
||||
`data` 对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ----- | ---- | ------ | ---- |
|
||||
| type | str | 验证码类型 | 目前只有 `geetest` |
|
||||
| token | str | 验证码 token | 用于验证 |
|
||||
| geetest | obj | 极验信息 | 若为 null 则说明该风控无法通过 captcha 解除 |
|
||||
| biliword | null | | |
|
||||
| phone | null | | |
|
||||
| sms | null | | |
|
||||
|
||||
`geetest` 对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| -------- | ----- | ------ | -------- |
|
||||
| gt | str | 极验id | 一般为固定值 |
|
||||
| challenge | str | 极验KEY | 由B站后端产生用于人机验证 |
|
||||
|
||||
**示例:**
|
||||
|
||||
假设此处 `v_voucher` 为 `voucher_ecca35e6-36da-4f38-bd84-b3f420ea08c1`
|
||||
|
||||
```shell
|
||||
curl -X POST "https://api.bilibili.com/x/gaia-vgate/v1/register" \
|
||||
--data-urlencode "v_voucher=voucher_ecca35e6-36da-4f38-bd84-b3f420ea08c1"
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>查看响应示例:</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "0",
|
||||
"ttl": 1,
|
||||
"data": {
|
||||
"type": "geetest",
|
||||
"token": "e7abdb050c3d4609979f1685137e3bc0",
|
||||
"geetest": {
|
||||
"challenge": "85118f8714875ca4c6d5641bb0ce9ddf",
|
||||
"gt": "ac597a4506fee079629df5d8b66dd4fe"
|
||||
},
|
||||
"biliword": null,
|
||||
"phone": null,
|
||||
"sms": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 从验证结果获取 grisk_id
|
||||
|
||||
> https://api.bilibili.com/x/gaia-vgate/v1/validate
|
||||
|
||||
*请求方式: POST*
|
||||
|
||||
**正文参数(application/x-www-form-urlencoded):**
|
||||
|
||||
| 参数名 | 类型 | 内容 | 必要性 | 备注 |
|
||||
| ------ | ---- | ------ | ---- | ---- |
|
||||
| csrf | str | CSRF Token (位于 Cookie 的 bili_jct) | 非必要 | 若登陆则必要 |
|
||||
| challenge | str | 验证码 challenge | 必要 | |
|
||||
| token | str | 验证码 token | 必要 | |
|
||||
| validate | str | 验证结果 validate | 必要 | |
|
||||
| seccode | str | 验证结果 seccode | 必要 | |
|
||||
|
||||
**JSON回复:**
|
||||
|
||||
根对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ----- | ---- | ------ | ---- |
|
||||
| code | num | 返回值 | 0:成功<br />-111: csrf 校验失败<br />100003: 验证码过期 |
|
||||
| message | str | 错误信息 | 默认为 0 |
|
||||
| ttl | num | 1 | |
|
||||
| data | obj | 信息本体 | |
|
||||
|
||||
`data` 对象:
|
||||
|
||||
| 字段 | 类型 | 内容 | 备注 |
|
||||
| ----- | ---- | ------ | ---- |
|
||||
| is_valid | num | 验证结果 | 1:验证成功 |
|
||||
| grisk_id | str | gaia_vtoken | 用于恢复正常访问 |
|
||||
|
||||
**示例:**
|
||||
|
||||
```shell
|
||||
curl -X POST "https://api.bilibili.com/x/gaia-vgate/v1/validate" \
|
||||
--data-urlencode "challenge=e4fcb337b8c0427b56320f97e1064210" \
|
||||
--data-urlencode "csrf=xxxxxxxxxxxxxxx" \
|
||||
--data-urlencode "seccode=360f7b9cf75c74c68fbb7475416d0e0d|jordan" \
|
||||
--data-urlencode "token=0e1e58bdff3d4b8aa298e346fed07eeb" \
|
||||
--data-urlencode "validate=360f7b9cf75c74c68fbb7475416d0e0d"
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>查看响应示例:</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "0",
|
||||
"ttl": 1,
|
||||
"data": {
|
||||
"is_valid": 1,
|
||||
"grisk_id": "2e91cf2b67172ca8432fe7c9ab66a5c4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
自 2023 年 3 月起,Bilibili Web 端部分接口开始采用 WBI 签名鉴权,表现在 REST API 请求时在 Query param 中添加了 `w_rid` 和 `wts` 字段。WBI 签名鉴权独立于 [APP 鉴权](APP.md) 与其他 Cookie 鉴权,目前被认为是一种 Web 端风控手段。
|
||||
|
||||
经持续观察,大部分查询性接口都已经或准备采用 WBI 签名鉴权,请求 WBI 签名鉴权接口时,若签名参数 `w_rid` 与时间戳 `wts` 缺失、错误,会返回 `v_voucher`(推测为内部记录错误请求的 ID 方便 Debug),如:
|
||||
经持续观察,大部分查询性接口都已经或准备采用 WBI 签名鉴权,请求 WBI 签名鉴权接口时,若签名参数 `w_rid` 与时间戳 `wts` 缺失、错误,会返回 `v_voucher`,如:
|
||||
|
||||
```json
|
||||
{"code":0,"message":"0","ttl":1,"data":{"v_voucher":"voucher_******"}}
|
||||
@@ -17,6 +17,7 @@
|
||||
1. 获取实时口令 `img_key`、`sub_key`
|
||||
|
||||
从 [nav 接口](../../login/login_info.md#导航栏用户信息) 中获取 `img_url`、`sub_url` 两个字段的参数。
|
||||
或从 [bili_ticket 接口](bili_ticket.md#接口) 中获取 `img` `sub` 两个字段的参数。
|
||||
|
||||
**注:`img_url`、`sub_url` 两个字段的值看似为存于 BFS 中的 png 图片 url,实则只是经过伪装的实时 Token,故无需且不能试图访问这两个 url**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user