feat: bili_ticket 算法 Java 实现 及 信息补充 及 错误修正 (#1061)

* feat: 空间头图及拼写错误修正

* feat(fav/info.md): code 11010

* fix(misc/sign/wbi.md): java extra params

* feat(misc/sign/bili_ticket.md): description and java demo

* feat(video_ranking/dynamic.md): 分区视频相关接口

* fix(video_ranking/dynamic.md): 未关闭的标签

* feat(README.md): 补充链接

* feat(clientinfo/ip.md): 查询任意 IP 地址的归属地

* feat: get buvid3 buvid4 from api

* feat: new error code & format

* feat(misc/picture.md): 图片格式化更多规则
This commit is contained in:
Session小胡
2024-07-25 20:03:52 +08:00
committed by GitHub
parent 750dd6a924
commit 18c1efbc10
14 changed files with 1078 additions and 56 deletions

48
docs/misc/buvid3_4.md Normal file
View File

@@ -0,0 +1,48 @@
# 获取 buvid3 / buvid4
## 游客获取 buvid3 / buvid4
> https://api.bilibili.com/x/frontend/finger/spi
*请求方式: GET*
**JSON回复:**
根对象:
| 字段 | 类型 | 内容 | 备注 |
| ------- | ---- | -------- | -------- |
| code | num | 返回值 | 0成功 |
| message | str | 信息 | ok: 成功 |
| data | obj | 数据本体 | |
`data`对象:
| 字段 | 类型 | 内容 | 备注 |
| ---- | ---- | ------ | ---- |
| b_3 | str | buvid3 | 需手动存放至 cookie 中 |
| b_4 | str | buvid4 | 同上 |
**示例:**
注: 建议自行生成, 不要复制本处示例的 buvid3 / buvid4.
```shell
curl -G 'https://api.bilibili.com/x/frontend/finger/spi'
```
<details>
<summary>查看响应示例:</summary>
```json
{
"code": 0,
"data": {
"b_3": "D9656DA8-9BEF-F464-5B72-C4849AFD336379044infoc",
"b_4": "F6E0FD4B-520C-1902-4F7B-E461D8D1F5AB79044-024072309-666onEZSnlHVPjoRp4kDYg=="
},
"message": "ok"
}
```
</details>

View File

@@ -248,10 +248,9 @@ print(av2bv(avid: 111298867365120))
print(bv2av(bvid: "BV1L9Uoa9EUx"))
```
### Java
```
```java
import java.math.BigInteger;
/**
@@ -266,8 +265,7 @@ public class AVBVConverter {
private static final String DATA = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf";
public static String av2bv(int aidParam) {
public static String av2bv(long aidParam) {
BigInteger aid = BigInteger.valueOf(aidParam);
char[] bytes = {'B', 'V', '1', '0', '0', '0', '0', '0', '0', '0', '0', '0'};
int bvIndex = bytes.length - 1;
@@ -275,18 +273,14 @@ public class AVBVConverter {
while (tmp.compareTo(BigInteger.ZERO) > 0) {
bytes[bvIndex] = DATA.charAt(tmp.mod(BigInteger.valueOf(BASE)).intValue());
tmp = tmp.divide(BigInteger.valueOf(BASE));
bvIndex -= 1;
bvIndex--;
}
swap(bytes, 3, 9);
swap(bytes, 4, 7);
StringBuilder sb = new StringBuilder(bytes.length);
for (Character ch : bytes) {
sb.append(ch);
}
return sb.toString();
return new String(bytes);
}
public static int bv2av(String bvid) {
public static long bv2av(String bvid) {
char[] bvidArr = bvid.toCharArray();
swap(bvidArr, 3, 9);
swap(bvidArr, 4, 7);
@@ -296,7 +290,7 @@ public class AVBVConverter {
tmp = tmp.multiply(BigInteger.valueOf(BASE)).add(BigInteger.valueOf(DATA.indexOf(c)));
}
BigInteger xor = tmp.and(MASK_CODE).xor(XOR_CODE);
return xor.intValue();
return xor.longValue();
}
@@ -314,21 +308,17 @@ public class AVBVConverter {
final int aid2 = 305988942;
final String bv2 = "BV1aP411K7it";
//av ==> bv
assert av2bv(aid1).equals(bv1);
assert av2bv(aid2).equals(bv2);
//bv ==>av
assert bv2av(bv1) == aid1;
assert bv2av(bv2) == aid2;
}
}
```
### Golang
```go

View File

@@ -2,7 +2,7 @@
## 设备唯一标识 BUVID
注意区分于 Web 端的 buvid3, buvid4.
注意区分于 Web 端的 [buvid3, buvid4](buvid3_4.md).
BUVID 在 APP 首次安装于某设备, 且首次启动时生成.

View File

@@ -1,18 +1,33 @@
# 图片格式化
对于\*.hdslb.com/bfs下的图片文件都可以使用以下格式化参数
对于 `*.hdslb.com/bfs` 下的图片文件都可以使用以下可选格式化参数.
> \*.hdslb.com/bfs/\*/\*.\[jpg/png/gif\]@{width}w\_{high}h\_{quality}q.{format}
使用 `@` 开始参数 (无论格式如何, 无论是否有参数, 通过计算 HASH 发现, 使用 `@` 均会导致返回图片不同), 多个参数以 `_` 分隔, 图片格式无需分隔且必须放在最后
| 可选参数 | 含义 | 备注 |
| -------- | ---------------- | ---------------- |
| width | 图片最大限制宽度 | |
| high | 图片最大限制高度 | |
| quality | 图片质量百分比 | 仅限webp |
| format | 图片格式 | 仅限png/jpg/webp/[avg_color](#avg_color格式说明) |
参见: [#191](https://github.com/SocialSisterYi/bilibili-API-collect/issues/191)
注: jpg 即 jpeg, 二者等效. 网页端常用 AVIF, WebP.
| 参数 | 格式 | 含义 | 备注 |
| ---- | ------- | ---------------- | ---------------- |
| w | ${int}w | 图片最大限制宽度 | 范围 [1, 9223372036854775807] |
| h | ${int}h | 图片最大限制高度 | 范围 [1, 9223372036854775807] |
| s | ${int}s | 作用尚不明确 | 不影响输出结果, 范围 [1, 9223372036854775807] |
| e | ${int}e | 改变大小 | 0: 保留比例取其小, 1: 保留比例取其大, 2: 不保留原比例 |
| p | ${int}p | 缩放倍数 | 默认100, 范围 [1, 1000] |
| o | ${int}o | 作用尚不明确 | 不影响输出结果 范围 [0, 1] |
| q | ${int}q | 图片质量百分比 | 仅限webp/jpeg/avif |
| c | ${int}c | 裁切图片(如果宽高允许) | 0: 不裁切但会修改图片, 1: 上传时的预设规则(若无则右下), 2: 左上, 3: 右上 |
| f | ${int}f | 作用尚不明确 | [0, 1]: 不改变图片, 2: 会改变图片 |
| progressive | progressive | 图片编码方式 | 仅限 jpeg(无: baseline, 有: progressive)/png(无:non-interlaced, 有: interlaced) |
| ! | !${str} | 加载来源 | web-home-carousel-cover, header, web-dynamic, web-avatar-space-header, ... |
| . | .${str} | 图片格式 | 仅限 png/jpeg/webp/avif/[avg_color](#avg_color格式说明) |
**示例:**
<details>
<summary>查看示例:</summary>
原始图片
https://i1.hdslb.com/bfs/archive/e5fff1472bad1c0c6bcb3004205f9be23b58ffc0.jpg
@@ -43,6 +58,7 @@ https://i1.hdslb.com/bfs/archive/e5fff1472bad1c0c6bcb3004205f9be23b58ffc0.jpg@1q
![](https://i1.hdslb.com/bfs/archive/e5fff1472bad1c0c6bcb3004205f9be23b58ffc0.jpg@1q.webp)
</details>
## avg_color格式说明

View File

@@ -1,8 +1,11 @@
`bili_ticket` 目前没发现多少风控价值,但是暂且在这里提供一份示例。
# BiliTicket
## 简述
`bili_ticket` 位于请求头 Cookie 中, 非必需, 但存在可降低风控概率
由 [@aynuarance](https://github.com/aynuarance) 于 [#903](https://github.com/SocialSisterYi/bilibili-API-collect/issues/903) 提供的思路,根据时间戳使用 `hmac_sha256` 算法计算 `hexsign`
是 [JWT 令牌](https://jwt.io/),有效时长为 259260 秒,即 3 天。
例如 `eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MDI3NDI3NDYsImlhdCI6MTcwMjQ4MzQ4NiwicGx0IjotMX0.xQgtTAc41NA1gzvd9yKUPgucUy_DKcQj6OG1vj8V7ZA`
@@ -14,7 +17,18 @@
}
```
# Python 示例
## 算法
1. 获取 UNIX 秒级时间戳存入变量如 `timestamp`
2. 计算变量 `hexsign` 值,使用 `hmac_sha256` 算法,密钥为 `XgwSnGZ1p`,消息为字符串 `"ts"` 与变量 `timestamp` 值拼接
3. 构造请求参数,`key_id``ec02``hexsign` 为变量 `hexsign` 值,`context[ts]` 为变量 `timestamp` 值,`csrf` 为 cookie 中的 `bili_jct` 值也可为空
4. 发送 `POST` 请求,获取 `data` 字段中的 `ticket` 字段的值即为所求
## Demo
### Python
需要 `requests` 依赖
```python
import hmac
@@ -59,4 +73,106 @@ if __name__ == '__main__':
'user-agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"
}
resp = requests.post(url, params=params,headers=headers).json()
```
print(resp)
```
### Java
无需第三方依赖
```java
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class BiliTicketDemo {
/**
* Convert a byte array to a hex string.
*
* @param bytes The byte array to convert.
* @return The hex string representation of the given byte array.
*/
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
/**
* Generate a HMAC-SHA256 hash of the given message string using the given key
* string.
*
* @param key The key string to use for the HMAC-SHA256 hash.
* @param message The message string to hash.
* @throws Exception If an error occurs during the HMAC-SHA256 hash generation.
* @return The HMAC-SHA256 hash of the given message string using the given key
* string.
*/
public static String hmacSha256(String key, String message) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKeySpec);
byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
}
/**
* Get a Bilibili web ticket for the given CSRF token.
*
* @param csrf The CSRF token to use for the web ticket, can be {@code null} or
* empty.
* @return The Bilibili web ticket raw response for the given CSRF token.
* @throws Exception If an error occurs during the web ticket generation.
* @see https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/misc/sign/bili_ticket.md
*/
public static String getBiliTicket(String csrf) throws Exception {
// params
long ts = System.currentTimeMillis() / 1000;
String hexSign = hmacSha256("XgwSnGZ1p", "ts" + ts);
StringBuilder url = new StringBuilder(
"https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket");
url.append('?');
url.append("key_id=ec02").append('&');
url.append("hexsign=").append(hexSign).append('&');
url.append("context[ts]=").append(ts).append('&');
url.append("csrf=").append(csrf == null ? "" : csrf);
// request
HttpURLConnection conn = (HttpURLConnection) new URI(url.toString()).toURL().openConnection();
conn.setRequestMethod("POST");
conn.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0");
InputStream in = conn.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
int b;
while ((b = in.read()) != -1) {
out.write(b);
}
return new String(out.toByteArray(), StandardCharsets.UTF_8);
}
/**
* Main method to test the BiliTicketDemo class.
*
* @param args The command line arguments (not used).
*/
public static void main(String[] args) {
try {
System.out.println(getBiliTicket("")); // use empty CSRF here
} catch (Exception e) {
e.printStackTrace();
}
}
}
```

View File

@@ -700,8 +700,6 @@ public class WbiTest {
map.put("bar", "五一四");
map.put("baz", 1919810);
map.put("wts", System.currentTimeMillis() / 1000);
map.put("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");
map.put("Referer", "https://www.bilibili.com/");
StringJoiner param = new StringJoiner("&");
//排序 + 拼接字符串
map.entrySet().stream()

View File

@@ -2,7 +2,7 @@
## 获取当前时间戳
> https://api.bilibili.com/x/report/click/now
> https://api.bilibili.com/x/report/click/now
> https://api.bilibili.com/x/click-interface/click/now
*请求方式GET*