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

XFEstudio/bilibili-API-collect

添加文档【Wbi 接口签名】,修改目录结构

05ac3d5
SocialSisterYi <1440239038@qq.com>
提交于

代码差异

10 个文件 +509 -173
Modified README.md +10 -6
@@ -58,10 +58,14 @@ B站 API 采用 C/S 结构,大多数接口为 REST API 和 gRPC,少部分接
58 58
59 59 计划整理分类 & 目录:(文档已完结请选中 checkbox)
60 60
61 - [x] [API 签名](docs/other/API_sign.md)
62 - [x] [公共错误码](docs/other/errcode.md)
63 - [x] [图片格式化](docs/other/picture.md)
64 - [x] [bvid 说明](docs/other/bvid_desc.md)
61 - [ ] [接口签名与验证](docs/misc/sign)
62 - [x] [APP API 签名](docs/misc/sign/APP.md)(`appkey`与`sign`)
63 - [x] [已知的 APPKey](docs/misc/sign/APPKey.md)
64 - [x] [Wbi 签名](docs/misc/sign/wbi.md)(`wts`与`w_rid`)
65
66 - [x] [公共错误码](docs/misc/errcode.md)
67 - [x] [图片格式化](docs/misc/picture.md)
68 - [x] [bvid 说明](docs/misc/bvid_desc.md)
65 69 - [ ] [gRPC API 接口定义](grpc_api)
66 70 - [ ] [登录](docs/login)
67 71 - [x] [登录操作 (人机认证)](docs/login/login_action)
@@ -234,8 +238,8 @@ B站 API 采用 C/S 结构,大多数接口为 REST API 和 gRPC,少部分接
234 238 - [ ] [终端网络查询](docs/clientinfo)
235 239 - [x] [基于ip的地理位置查询](docs/clientinfo/ip.md)
236 240 - [x] [终端信息查询](docs/clientinfo/client_info.md)
237 - [ ] [其他](docs/other)
238 - [x] [获取当前时间戳](docs/other/time_stamp.md)
241 - [ ] [其他](docs/misc)
242 - [x] [获取当前时间戳](docs/misc/time_stamp.md)
239 243 - [ ] [web端组件](docs/web_widget)
240 244 - [x] [分区当日投稿数](docs/web_widget/zone_upload.md)
241 245 - [x] [404 页漫画收集](docs/web_widget/404_manga.md)
Modified docs/login/login_info.md +130 -74
@@ -27,30 +27,33 @@
27 27 | -------------------- | ---- | ---------------- | ------------------------------------------------- |
28 28 | isLogin | bool | 是否已登录 | false:未登录<br />true:已登录 |
29 29 | email_verified | num | 是否验证邮箱地址 | 0:未验证<br />1:已验证 |
30 | face | str | 用户头像url | |
30 | face | str | 用户头像 url | |
31 31 | level_info | obj | 等级信息 | |
32 | mid | num | 用户mid | |
32 | mid | num | 用户 mid | |
33 33 | mobile_verified | num | 是否验证手机号 | 0:未验证<br />1:已验证 |
34 34 | money | num | 拥有硬币数 | |
35 35 | moral | num | 当前节操值 | 上限为70 |
36 36 | official | obj | 认证信息 | |
37 | officialVerify | obj | 认证信息2 | |
37 | officialVerify | obj | 认证信息 2 | |
38 38 | pendant | obj | 头像框信息 | |
39 | scores | num | 0 | 作用尚不明确 |
39 | scores | num | (?) | |
40 40 | uname | str | 用户昵称 | |
41 41 | vipDueDate | num | 会员到期时间 | 毫秒 时间戳 |
42 42 | vipStatus | num | 会员开通状态 | 0:无<br />1:有 |
43 43 | vipType | num | 会员类型 | 0:无<br />1:月度大会员<br />2:年度及以上大会员 |
44 44 | vip_pay_type | num | 会员开通状态 | 0:无<br />1:有 |
45 | vip_theme_type | num | 0 | 作用尚不明确 |
45 | vip_theme_type | num | (?) | |
46 46 | vip_label | obj | 会员标签 | |
47 47 | vip_avatar_subscript | num | 是否显示会员图标 | 0:不显示<br />1:显示 |
48 48 | vip_nickname_color | str | 会员昵称颜色 | 颜色码 |
49 49 | wallet | obj | B币钱包信息 | |
50 50 | has_shop | bool | 是否拥有推广商品 | false:无<br />true:有 |
51 | shop_url | str | 商品推广页面url | |
52 | allowance_count | num | 0 | 作用尚不明确 |
53 | answer_status | num | 0 | 作用尚不明确 |
51 | shop_url | str | 商品推广页面 url | |
52 | allowance_count | num | (?) | |
53 | answer_status | num | (?) | |
54 | is_senior_member | num | 是否硬核会员 | 0:非硬核会员<br />1:硬核会员 |
55 | wbi_img | obj | Wbi 签名实时口令 | 该字段即使用户未登录也存在 |
56 | is_jury | bool | (?) | |
54 57
55 58 `data`中的`level_info`对象:
56 59
@@ -79,35 +82,42 @@
79 82
80 83 `data`中的`pendant`对象:
81 84
82 | 字段 | 类型 | 内容 | 备注 |
83 | ------ | ---- | ----------- | ------------ |
84 | pid | num | 挂件id | |
85 | name | str | 挂件名称 | |
86 | image | str | 挂件图片url | |
87 | expire | num | 0 | 作用尚不明确 |
85 | 字段 | 类型 | 内容 | 备注 |
86 | ------ | ---- | ----------- | ---- |
87 | pid | num | 挂件id | |
88 | name | str | 挂件名称 | |
89 | image | str | 挂件图片url | |
90 | expire | num | (?) | |
88 91
89 92 `data`中的`vip_label`对象:
90 93
91 94 | 字段 | 类型 | 内容 | 备注 |
92 95 | ----------- | ---- | -------- | ------------------------------------------------------------ |
93 | path | str | 空 | 作用尚不明确 |
96 | path | str | (?) | |
94 97 | text | str | 会员名称 | |
95 98 | label_theme | str | 会员标签 | vip:大会员<br />annual_vip:年度大会员<br />ten_annual_vip:十年大会员<br />hundred_annual_vip:百年大会员 |
96 99
97 100 `data`中的`wallet`对象:
98 101
99 | 字段 | 类型 | 内容 | 备注 |
100 | --------------- | ---- | ------------- | ------------ |
101 | mid | num | 登录用户mid | |
102 | bcoin_balance | num | 拥有B币数 | |
103 | coupon_balance | num | 每月奖励B币数 | |
104 | coupon_due_time | num | 0 | 作用尚不明确 |
102 | 字段 | 类型 | 内容 | 备注 |
103 | --------------- | ---- | ------------- | ---- |
104 | mid | num | 登录用户mid | |
105 | bcoin_balance | num | 拥有B币数 | |
106 | coupon_balance | num | 每月奖励B币数 | |
107 | coupon_due_time | num | (?) | |
108
109 `data`中的`wbi_img`对象:
110
111 | 字段 | 类型 | 内容 | 备注 |
112 | ------- | ---- | ------------------------------- | ---------------------------------------- |
113 | img_url | str | Wbi 签名参数 `imgKey`的伪装 url | 详见文档 [Wbi 签名](../misc/sign/wbi.md) |
114 | sub_url | str | Wbi 签名参数 `subKey`的伪装 url | 详见文档 [Wbi 签名](../misc/sign/wbi.md) |
105 115
106 116 **示例:**
107 117
108 118 ```shell
109 119 curl 'https://api.bilibili.com/nav' \
110 -b 'SESSDATA=xxx'
120 -b 'SESSDATA=xxx'
111 121 ```
112 122
113 123 <details>
@@ -115,64 +125,110 @@ curl 'https://api.bilibili.com/nav' \
115 125
116 126 ```json
117 127 {
118 "code":0,
119 "message":"0",
120 "ttl":1,
121 "data":{
122 "isLogin":true,
123 "email_verified":1,
124 "face":"http://i1.hdslb.com/bfs/face/aebb2639a0d47f2ce1fec0631f412eaf53d4a0be.jpg",
125 "level_info":{
126 "current_level":5,
127 "current_min":10800,
128 "current_exp":17065,
129 "next_exp":28800
128 "code": 0,
129 "message": "0",
130 "ttl": 1,
131 "data": {
132 "isLogin": true,
133 "email_verified": 1,
134 "face": "https://i0.hdslb.com/bfs/face/aebb2639a0d47f2ce1fec0631f412eaf53d4a0be.jpg",
135 "face_nft": 0,
136 "face_nft_type": 0,
137 "level_info": {
138 "current_level": 6,
139 "current_min": 28800,
140 "current_exp": 52689,
141 "next_exp": "--"
142 },
143 "mid": 293793435,
144 "mobile_verified": 1,
145 "money": 172.4,
146 "moral": 70,
147 "official": {
148 "role": 0,
149 "title": "",
150 "desc": "",
151 "type": -1
152 },
153 "officialVerify": {
154 "type": -1,
155 "desc": ""
130 156 },
131 "mid":293793435,
132 "mobile_verified":1,
133 "money":33.4,
134 "moral":70,
135 "official":{
136 "role":0,
137 "title":"",
138 "desc":"",
139 "type":-1
157 "pendant": {
158 "pid": 2511,
159 "name": "初音未来13周年",
160 "image": "https://i0.hdslb.com/bfs/garb/item/4f8f3f1f2d47f0dad84f66aa57acd4409ea46361.png",
161 "expire": 0,
162 "image_enhance": "https://i0.hdslb.com/bfs/garb/item/fe0b83b53e2342b16646f6e7a9370d8a867decdb.webp",
163 "image_enhance_frame": "https://i0.hdslb.com/bfs/garb/item/127c507ec8448be30cf5f79500ecc6ef2fd32f2c.png"
140 164 },
141 "officialVerify":{
142 "type":-1,
143 "desc":""
165 "scores": 0,
166 "uname": "社会易姐QwQ",
167 "vipDueDate": 1707494400000,
168 "vipStatus": 1,
169 "vipType": 2,
170 "vip_pay_type": 0,
171 "vip_theme_type": 0,
172 "vip_label": {
173 "path": "",
174 "text": "年度大会员",
175 "label_theme": "annual_vip",
176 "text_color": "#FFFFFF",
177 "bg_style": 1,
178 "bg_color": "#FB7299",
179 "border_color": "",
180 "use_img_label": true,
181 "img_label_uri_hans": "",
182 "img_label_uri_hant": "",
183 "img_label_uri_hans_static": "https://i0.hdslb.com/bfs/vip/8d4f8bfc713826a5412a0a27eaaac4d6b9ede1d9.png",
184 "img_label_uri_hant_static": "https://i0.hdslb.com/bfs/activity-plat/static/20220614/e369244d0b14644f5e1a06431e22a4d5/VEW8fCC0hg.png"
144 185 },
145 "pendant":{
146 "pid":0,
147 "name":"",
148 "image":"",
149 "expire":0,
150 "image_enhance":""
186 "vip_avatar_subscript": 1,
187 "vip_nickname_color": "#FB7299",
188 "vip": {
189 "type": 2,
190 "status": 1,
191 "due_date": 1707494400000,
192 "vip_pay_type": 0,
193 "theme_type": 0,
194 "label": {
195 "path": "",
196 "text": "年度大会员",
197 "label_theme": "annual_vip",
198 "text_color": "#FFFFFF",
199 "bg_style": 1,
200 "bg_color": "#FB7299",
201 "border_color": "",
202 "use_img_label": true,
203 "img_label_uri_hans": "",
204 "img_label_uri_hant": "",
205 "img_label_uri_hans_static": "https://i0.hdslb.com/bfs/vip/8d4f8bfc713826a5412a0a27eaaac4d6b9ede1d9.png",
206 "img_label_uri_hant_static": "https://i0.hdslb.com/bfs/activity-plat/static/20220614/e369244d0b14644f5e1a06431e22a4d5/VEW8fCC0hg.png"
207 },
208 "avatar_subscript": 1,
209 "nickname_color": "#FB7299",
210 "role": 3,
211 "avatar_subscript_url": "",
212 "tv_vip_status": 0,
213 "tv_vip_pay_type": 0,
214 "tv_due_date": 1640793600
151 215 },
152 "scores":0,
153 "uname":"社会易姐QwQ",
154 "vipDueDate":1612454400000,
155 "vipStatus":1,
156 "vipType":2,
157 "vip_pay_type":1,
158 "vip_theme_type":0,
159 "vip_label":{
160 "path":"",
161 "text":"年度大会员",
162 "label_theme":"annual_vip"
216 "wallet": {
217 "mid": 293793435,
218 "bcoin_balance": 5,
219 "coupon_balance": 5,
220 "coupon_due_time": 0
163 221 },
164 "vip_avatar_subscript":1,
165 "vip_nickname_color":"#FB7299",
166 "wallet":{
167 "mid":293793435,
168 "bcoin_balance":8,
169 "coupon_balance":5,
170 "coupon_due_time":0
222 "has_shop": true,
223 "shop_url": "https://gf.bilibili.com?msource=main_station",
224 "allowance_count": 0,
225 "answer_status": 0,
226 "is_senior_member": 1,
227 "wbi_img": {
228 "img_url": "https://i0.hdslb.com/bfs/wbi/653657f524a547ac981ded72ea172057.png",
229 "sub_url": "https://i0.hdslb.com/bfs/wbi/6e4909c702f846728e64f6007736a338.png"
171 230 },
172 "has_shop":false,
173 "shop_url":"",
174 "allowance_count":0,
175 "answer_status":0
231 "is_jury": false
176 232 }
177 233 }
178 234 ```
Renamed docs/misc/bvid_desc.md +0 -0
此文件没有可显示的逐行差异。
Renamed docs/misc/errcode.md +0 -0
此文件没有可显示的逐行差异。
Renamed docs/misc/picture.md +0 -0
此文件没有可显示的逐行差异。
Added docs/misc/sign/APP.md +65 -0
@@ -0,0 +1,65 @@
1 # APP API 签名与鉴权
2
3 ## APP API 签名特性
4
5 部分客户端专用的 REST API 存在基于参数签名的鉴权,需要使用规定的`appkey`及其对应的`appsec`与原始请求参数进行签名计算,部分`AppKey`及与之对应的`AppSec`已经被公开:见该文档 [APPKey](APPKey.md)
6
7 - 不同 `appkey` 对应不同的 app (如客户端、概念版、必剪、漫画、bililink等)
8
9 - 不同平台同 app 也会存在不同的 `appkey` (如安卓端、ios端、TV端等)
10
11 - 同平台同 app 下不同功能也会存在不同的 `appkey`(如登录专用、取流专用等)
12
13 - 不同版本的客户端的 `appkey` 也可能不同
14
15 - **appkey与appsec一一对应**
16
17 ## APP API 签名算法
18
19 1. 首先为参数中添加`appkey`字段
20 2. 然后按照参数的 Key 重新排序
21 3. 再对这个 Key-Value 进行 url query 序列化,并拼接与之对应的`appsec` (盐) 进行 **md5 Hash 运算**(32-bit 字符小写),该 hash 便是 API 签名
22 4. 最后在参数尾部增添`sign`字段,它的 Value 为上一步计算所得的 hash,一并作为表单或 Query 提交
23
24 ## Demo
25
26 该 Demo 提供 [Python](#Python) 语言例程
27
28 使用 appkey = `1d8b6e7d45233436`, appsec = `560c52ccd288fed045859ed18bffd973` 对如下 `params` 参数进行签名
29
30 上述示例`appkey`、`AppSec`均来自文档 [APPKey](APPKey.md)
31
32 ### Python
33
34 ```python
35 import hashlib
36 import urllib.parse
37
38 def appsign(params, appkey, appsec):
39 '为请求参数进行 APP 签名'
40 params.update({'appkey': appkey})
41 params = dict(sorted(params.items())) # 按照 key 重排参数
42 query = urllib.parse.urlencode(params) # 序列化参数
43 sign = hashlib.md5((query+appsec).encode()).hexdigest() # 计算 api 签名
44 params.update({'sign':sign})
45 return params
46
47 appkey = '1d8b6e7d45233436'
48 appsec = '560c52ccd288fed045859ed18bffd973'
49 params = {
50 'id':114514,
51 'str':'1919810',
52 'test':'いいよ,こいよ',
53 }
54 signed_params = appsign(params, appkey, appsec)
55 query = urllib.parse.urlencode(signed_params)
56 print(signed_params)
57 print(query)
58 ```
59
60 输出内容分别是进行 APP 签名的后参数的 key-Value 以及 url query 形式
61
62 ```
63 {'appkey': '1d8b6e7d45233436', 'id': 114514, 'str': '1919810', 'test': 'いいよ,こいよ', 'sign': '01479cf20504d865519ac50f33ba3a7d'}
64 appkey=1d8b6e7d45233436&id=114514&str=1919810&test=%E3%81%84%E3%81%84%E3%82%88%EF%BC%8C%E3%81%93%E3%81%84%E3%82%88&sign=01479cf20504d865519ac50f33ba3a7d
65 ```
Added docs/misc/sign/APPKey.md +54 -0
@@ -0,0 +1,54 @@
1 # APIKey
2
3 以下为已知的 APPkey / APPSec,及部分使用场景参数信息,均来自抓包与逆向工程
4
5 | APPKEY | APPSEC | platform<sup>2</sup> | APP类型 | neuronAppId<sup>1</sup> | mobi_app<sup>2</sup> | 备注 |
6 | :--------------: | :------------------------------: | :------------------: | :----------------: | :---------------------: | :------------------: | :----------------------------------------: |
7 | 9d5889cf67e615cd | 8fd9bb32efea8cef801fd895bef2713d | `android` | Ai4cCreatorAndroid | | | |
8 | 1d8b6e7d45233436 | 560c52ccd288fed045859ed18bffd973 | `android` | 粉版 | `1` | `android` | 获取资源通用 |
9 | 783bbb7264451d82 | 2653583c8873dea268ab9386918b1d65 | `android` | 粉版 | `1` | `android` | 仅获取用户信息时使用(7.X及更新版本) |
10 | 57263273bc6b67f6 | a0488e488d1567960d3a765e8d129f90 | `android` | 粉版 | `1` | `android` | 可能来自旧版 |
11 | 07da50c9a0bf829f | 25bdede4e1581c836cab73a48790ca6e | `android` | 概念版 | `3` | `android_b` | |
12 | 191c3b6b975af184 | | `android` | 概念版 | `3` | `android_b` | 新出现, 仅获取用户信息时使用. 暂未知appsec |
13 | 178cf125136ca8ea | 34381a26236dd1171185c0beb042e1c6 | `android` | 概念版 | `3` | `android_b` | 可能来自旧版 |
14 | 7d336ec01856996b | a1ce6983bc89e20a36c37f40c4f1a0dd | `android` | 概念版 | `3` | `android_b` | 可能来自旧版 |
15 | dfca71928277209b | b5475a8825547a4fc26c7d518eaaa02e | `android` | HD 版 | `5` | `android_hd` | |
16 | bb3101000e232e27 | 36efcfed79309338ced0380abd824ac1 | `android` | 白版 | `14` | `android_i` | |
17 | ae57252b0c09105d | c75875c596a69eb55bd119e74b07cfe3 | `android` | 白版 | `14` | `android_i` | 仅获取用户信息时使用(7.X及更新版本) |
18 | 8e16697a1b4f8121 | f5dd03b752426f2e623d7badb28d190a | `android` | 白版 | `14` | `android_i` | 可能来自旧版 |
19 | 7d089525d3611b1c | acd495b248ec528c2eed1e862d393126 | `android` | 蓝版 | `30` | `bstar_a` | |
20 | iVGUTjsxvpLeuDCf | aHRmhWMLkdeMuILqORnYZocwMBpMEOdt | `android` | - | - | - | 视频取流专用, 仅5.X旧版使用 |
21 | YvirImLGlLANCLvM | JNlZNgfNGKZEpaDTkCdPQVXntXhuiJEM | `ios` | - | - | - | 视频取流专用 |
22 | 27eb53fc9058f8c3 | c2ed53a74eeefe3cf99fbd01d8c9c375 | `web`/`ios`? | - | - | - | 第三方授权使用 |
23 | 84956560bc028eb7 | 94aba54af9065f71de72f5508f1cd42e | ? | UWP 版 | - | - | 部分API不接受此appkey, 返回-663错误 |
24 | 85eb6835b0a1034e | 2ad42749773c441109bdc0191257a664 | ? | UWP 版? | - | - | 部分API不接受此appkey, 返回-663错误 |
25 | 4ebafd7c4951b366 | 8cb98205e9b2ad3669aad0fce12a4c13 | `ios` | iPhone 客户端? | `iphone` | ? | |
26 | 8d23902c1688a798 | 710f0212e62bd499b8d3ac6e1db9302a | `android` | AndroidBiliThings | ? | ? | |
27 | 4c6e1021617d40d9 | e559a59044eb2701b7a8628c86aa12ae | `android` | AndroidMallTicket | ? | ? | |
28 | c034e8b74130a886 | e4e8966b1e71847dc4a3830f2d078523 | `android` | AndroidOttSdk | `7` | ? | |
29 | 4409e2ce8ffd12b8 | 59b43e04ad6965f34319062b478f83dd | `android` | 云视听小电视(TV版) | `9`? | `android_tv_yst`? | |
30 | 37207f2beaebf8d7 | e988e794d4d4b6dd43bc0e89d6e90c43 | `android` | BiliLink | ? | ? | |
31 | 9a75abf7de2d8947 | 35ca1c82be6c2c242ecc04d88c735f31 | `android` | BiliScan | ? | ? | |
32 | aae92bc66f3edfab | af125a0d5279fd576c1b4418a3e8276d | ? | PC 投稿工具 | - | ? | |
33 | bca7e84c2d947ac6 | 60698ba2f68e01ce44738920a0ffe768 | ? | login | - | ? | |
34
35 注释:
36
37 <sup>1</sup> `neuronAppId`,产品编号,由数据平台分配,详情如下:
38
39 - 粉(国内版)=1
40 - 白(GooglePlay 版)=2
41 - 蓝(东南亚版)=3
42 - 直播姬=4
43 - HD=5
44 - 海外=6
45 - OTT=7
46 - 漫画=8
47 - TV野版=9
48 - 小视频=10
49 - 网易漫画=11
50 - 网易漫画lite=12
51 - 网易漫画HD=13,
52 - 国际版=14
53
54 <sup>2</sup> `platform`, `mobi_app` 仅供参考, 具体值需要抓包确定.
Added docs/misc/sign/wbi.md +250 -0
@@ -0,0 +1,250 @@
1 # Wbi签名
2
3 自 2023 年三月起,B站 Web 端部分接口开始使用 Wbi 鉴权方式,即一种独立于 [APP 鉴权](APP.md) 与其他 Cookie 鉴权的方式,表现在 REST API 请求时在 query 中添加了`w_rid`和`wts`字段,为一种 Web 端的风控手段
4
5 这些接口涵盖”用户投稿视频“、”用户投稿专栏“、”首页推送“、”推广信息“、”热搜“、”视频信息“、”视频取流“、”搜索“等待主要查询性业务接口,如果请求这些 REST API 缺失`w_rid`和`wts`字段,则会在数次请求后返回`-403:非法访问`这样的风控错误
6
7 感谢 [#631](https://github.com/SocialSisterYi/bilibili-API-collect/issues/631) 的研究与逆向工程
8
9 ## Wbi签名算法
10
11 1. 获取实时口令
12
13 从 [nav 接口](../../login/login_info.md#导航栏用户信息) 中获取`img_url`、`sub_url`两个字段的参数,并保存备用(如存入 localStorage),相关内容节选如下:
14
15 **注:`img_url`、`sub_url`两个字段的值看似为存于 BFS 中的 png 图片 url,实则只是经过伪装的实时 Token,故无需且不能试图访问这两个 url**
16
17 ```json
18 "wbi_img": {
19 "img_url": "https://i0.hdslb.com/bfs/wbi/653657f524a547ac981ded72ea172057.png",
20 "sub_url": "https://i0.hdslb.com/bfs/wbi/6e4909c702f846728e64f6007736a338.png"
21 },
22 ```
23 这两个 Key 均为 url 中末尾路径的无扩展名的文件名,即`img_key=653657f524a547ac981ded72ea172057`,`sub_key=6e4909c702f846728e64f6007736a338`
24
25 这两个 Key 的值无关登录 Session 与 IP,属于全站统一使用的,但**每日都会变化**,使用时应做好**缓存和刷新**处理
26
27 2. 打乱重排实时口令
28
29 把上一步获取到的`img_key`拼接在`sub_key`后面**(这里不是`img_url`和`sub_url`)**作为一个整体,将这个整体进行特定的顺序的字符打乱重排,再将重排后的字符串截取前 30 字符的切片,作为一个新的变量`mixin_key`,重排映射表长为 64,内容如下:
30
31 ```javascript
32 const mixinKeyEncTab = [
33 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
34 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
35 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
36 36, 20, 34, 44, 52
37 ]
38 ```
39
40 打乱重排内容如下(以上述第 1 步的参数作为输入)
41
42 ```
43 72136226c6a73669787ee4fd02a74c27
44 ```
45
46 3. 将欲签名的请求参数排序后编码
47
48 若下方内容为欲签名的请求参数(以 js obj 为例)
49
50 ```javascript
51 {
52 foo: '114',
53 bar: '514',
54 baz: 1919810
55 }
56 ```
57
58 那么按照 Key 排序并进行 url query 编码后的结果应为:
59
60 ```
61 bar=514&baz=1919810&foo=114
62 ```
63
64 4. 为参数中添加`wts`时间戳
65
66 `wts`字段的值应为以秒为单位的 Unix TimeStamp,如`1684746387`
67
68 将`wts`参数添加在参数列表最后,即:
69
70 ```
71 bar=514&baz=1919810&foo=114&wts=1684746387
72 ```
73
74 5. 计算`w_rid`并添加在其后
75
76 在上一步得出的 url query 字符串后拼接第 2 步计算得出的`mixin_key`(作为盐)
77
78 ```
79 bar=514&baz=1919810&foo=114&wts=168474638772136226c6a73669787ee4fd02a74c27
80 ```
81
82 对这个整体进行 **md5 Hash 运算**(32-bit 字符小写),得到的值便是 Wbi Sign,也就是参数`w_rid`
83
84 ```
85 d3cbd2a2316089117134038bf4caf442
86 ```
87
88 最后一步,把这个计算出的值作为参数`w_rid`添加在原始参数列表后,也就完成了一次 Wbi Sign,可以调用 REST API 进行请求了
89
90 ```
91 bar=514&baz=1919810&foo=114&wts=1684746387&w_rid=d3cbd2a2316089117134038bf4caf442
92 ```
93
94 ## Wbi签名算法实现Demo
95
96 该 Demo 提供 [Python](#Python)、[JavaScript](#JavaScript) 语言
97
98 ### Python
99
100 需要`requests`依赖
101
102 ```python
103 from functools import reduce
104 from hashlib import md5
105 import urllib.parse
106 import time
107 import requests
108
109 mixinKeyEncTab = [
110 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
111 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
112 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
113 36, 20, 34, 44, 52
114 ]
115
116 def getMixinKey(orig: str):
117 '对 imgKey 和 subKey 进行字符顺序打乱编码'
118 return reduce(lambda s, i: s + orig[i], mixinKeyEncTab, '')[:32]
119
120 def encWbi(params: dict, img_key: str, sub_key: str):
121 '为请求参数进行 wbi 签名'
122 mixin_key = getMixinKey(img_key + sub_key)
123 curr_time = round(time.time())
124 params['wts'] = curr_time # 添加 wts 字段
125 params = dict(sorted(params.items())) # 按照 key 重排参数
126 # 过滤 value 中的 "!'()*" 字符
127 params = {
128 k : ''.join(filter(lambda chr: chr not in "!'()*", str(v)))
129 for k, v
130 in params.items()
131 }
132 query = urllib.parse.urlencode(params) # 序列化参数
133 wbi_sign = md5((query + mixin_key).encode()).hexdigest() # 计算 w_rid
134 params['w_rid'] = wbi_sign
135 return params
136
137 def getWbiKeys() -> tuple[str, str]:
138 '获取最新的 img_key 和 sub_key'
139 resp = requests.get('https://api.bilibili.com/x/web-interface/nav')
140 resp.raise_for_status()
141 json_content = resp.json()
142 img_url: str = json_content['data']['wbi_img']['img_url']
143 sub_url: str = json_content['data']['wbi_img']['sub_url']
144 img_key = img_url.rsplit('/', 1)[1].split('.')[0]
145 sub_key = sub_url.rsplit('/', 1)[1].split('.')[0]
146 return img_key, sub_key
147
148 img_key, sub_key = getWbiKeys()
149
150 signed_params = encWbi(
151 params={
152 'foo': '114',
153 'bar': '514',
154 'baz': 1919810
155 },
156 img_key=img_key,
157 sub_key=sub_key
158 )
159 query = urllib.parse.urlencode(signed_params)
160 print(signed_params)
161 print(query)
162 ```
163
164 输出内容分别是进行 Wbi 签名的后参数的 key-Value 以及 url query 形式
165
166 ```
167 {'bar': '514', 'baz': '1919810', 'foo': '114', 'wts': '1684746387', 'w_rid': 'd3cbd2a2316089117134038bf4caf442'}
168 bar=514&baz=1919810&foo=114&wts=1684746387&w_rid=d3cbd2a2316089117134038bf4caf442
169 ```
170
171 ### JavaScript
172
173 需要`axios`、`md5`依赖
174
175 ```javascript
176 import md5 from 'md5'
177 import axios from 'axios'
178
179 const mixinKeyEncTab = [
180 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
181 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
182 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
183 36, 20, 34, 44, 52
184 ]
185
186 // 对 imgKey 和 subKey 进行字符顺序打乱编码
187 function getMixinKey(orig) {
188 let temp = ''
189 mixinKeyEncTab.forEach((n) => {
190 temp += orig[n]
191 })
192 return temp.slice(0, 32)
193 }
194
195 // 为请求参数进行 wbi 签名
196 function encWbi(params, img_key, sub_key) {
197 const mixin_key = getMixinKey(img_key + sub_key),
198 curr_time = Math.round(Date.now() / 1000),
199 chr_filter = /[!'\(\)*]/g
200 let query = []
201 params = Object.assign(params, {wts: curr_time}) // 添加 wts 字段
202 // 按照 key 重排参数
203 Object.keys(params).sort().forEach((key) => {
204 query.push(
205 encodeURIComponent(key) +
206 '=' +
207 // 过滤 value 中的 "!'()*" 字符
208 encodeURIComponent(('' + params[key]).replace(chr_filter, ''))
209 )
210 })
211 query = query.join('&')
212 const wbi_sign = md5(query + mixin_key) // 计算 w_rid
213 return query + '&w_rid=' + wbi_sign
214 }
215
216 // 获取最新的 img_key 和 sub_key
217 async function getWbiKeys() {
218 const resp = await axios({
219 url: 'https://api.bilibili.com/x/web-interface/nav',
220 method: 'get',
221 responseType: 'json'
222 }),
223 json_content = resp.data,
224 img_url = json_content.data.wbi_img.img_url,
225 sub_url = json_content.data.wbi_img.sub_url
226 return {
227 img_key: img_url.substring(img_url.lastIndexOf('/') + 1, img_url.length).split('.')[0],
228 sub_key: sub_url.substring(sub_url.lastIndexOf('/') + 1, sub_url.length).split('.')[0]
229 }
230 }
231
232 const wbi_keys = await getWbiKeys()
233
234 const query = encWbi(
235 {
236 foo: '114',
237 bar: '514',
238 baz: 1919810
239 },
240 wbi_keys.img_key,
241 wbi_keys.sub_key
242 )
243 console.log(query)
244 ```
245
246 输出内容为进行 Wbi 签名的后参数的 url query 形式
247
248 ```
249 bar=514&baz=1919810&foo=114&wts=1684805578&w_rid=bb97e15f28edf445a0e4420d36f0157e
250 ```
Renamed docs/misc/time_stamp.md +0 -0
此文件没有可显示的逐行差异。
Deleted docs/other/API_sign.md +0 -93
@@ -1,93 +0,0 @@
1 # API 签名与鉴权
2
3 部分客户端专用的 RESTful API 存在基于 sign 的鉴权,需要使用规定的`appkey`及其对应的`appsec`与原始请求参数进行签名计算
4
5 不同 `appkey` 对应不同的 app (如客户端、概念版、必剪、漫画、bililink等)
6
7 不同平台同 app 也会存在不同的 `appkey` (如安卓端、ios端、TV端等)
8
9 同平台同 app 下不同功能也会存在不同的 `appkey`(如登录专用、取流专用等)
10
11 不同版本的客户端的 `appkey` 也可能不同
12
13 **appkey与appsec一一对应**
14
15 ## API签名的计算方式
16
17 首先为参数中添加`appkey`字段,然后按照参数的 key 重新排序,再将重排序后的参数使用 url query 格式序列化拼接与该 appkey 相对应的 appsec (盐值) 进行**md5 hash计算**(32位小写),该 hash 便是 API 签名
18
19 为参数尾部增添`sign`字段,它的值为上一步计算所得的 hash,一并作为表单提交
20
21 **实例:**
22
23 使用 appkey = `1d8b6e7d45233436`, appsec = `560c52ccd288fed045859ed18bffd973` 对如下 `params` 参数进行签名
24
25 ```python
26 import hashlib
27 import urllib.parse
28
29 def appsign(params, appkey, appsec):
30 '为请求参数进行 api 签名'
31 params.update({'appkey': appkey})
32 params = dict(sorted(params.items())) # 重排序参数 key
33 query = urllib.parse.urlencode(params) # 序列化参数
34 sign = hashlib.md5((query+appsec).encode()).hexdigest() # 计算 api 签名
35 params.update({'sign':sign})
36 return params
37
38 appkey = '1d8b6e7d45233436'
39 appsec = '560c52ccd288fed045859ed18bffd973'
40 params = {
41 'id':114514,
42 'str':'1919810',
43 'test':'いいよ,こいよ',
44 }
45 signed_params = appsign(params, appkey, appsec)
46 query = urllib.parse.urlencode(signed_params)
47 print(signed_params)
48 print(query)
49 ```
50
51 输出以下内容,分别是进行 api 签名后参数的 dict 以及 url query 格式
52
53 ```
54 {'appkey': '1d8b6e7d45233436', 'id': 114514, 'str': '1919810', 'test': 'いいよ,こいよ', 'sign': '01479cf20504d865519ac50f33ba3a7d'}
55 appkey=1d8b6e7d45233436&id=114514&str=1919810&test=%E3%81%84%E3%81%84%E3%82%88%EF%BC%8C%E3%81%93%E3%81%84%E3%82%88&sign=01479cf20504d865519ac50f33ba3a7d
56 ```
57 ## 已知的APPKEY/APPSEC, 及部分参数信息
58
59 | APPKEY | APPSEC | platform<sup>2</sup> | APP类型 | neuronAppId<sup>1</sup> | mobi_app<sup>2</sup> | 备注 |
60 |:--:|:--:|:--:|:--:|:--:|:--:|:--:|
61 | 9d5889cf67e615cd | 8fd9bb32efea8cef801fd895bef2713d | `android` | Ai4cCreatorAndroid |
62 | 1d8b6e7d45233436 | 560c52ccd288fed045859ed18bffd973 | `android` | 普通版(粉版) | `1` |`android`| 获取资源通用 |
63 | 783bbb7264451d82 | 2653583c8873dea268ab9386918b1d65 | `android` | 普通版(粉版) | `1` | `android` | 仅获取用户信息时使用(7.X及更新版本) |
64 | 57263273bc6b67f6 | a0488e488d1567960d3a765e8d129f90 | `android` | 普通版(粉版) | `1` |`android`| 可能来自旧版 |
65 | 07da50c9a0bf829f | 25bdede4e1581c836cab73a48790ca6e | `android` | 概念版(蓝版) | `3` | `android_b` |
66 | 191c3b6b975af184 | ******************************** | `android` | 概念版(蓝版) | `3` | `android_b` | 新出现, 仅获取用户信息时使用. 暂未知appsec |
67 | 178cf125136ca8ea | 34381a26236dd1171185c0beb042e1c6 | `android` | 概念版(蓝版) | `3` | `android_b` | 可能来自旧版 |
68 | 7d336ec01856996b | a1ce6983bc89e20a36c37f40c4f1a0dd | `android` | 概念版(蓝版) | `3` | `android_b` | 可能来自旧版 |
69 | dfca71928277209b | b5475a8825547a4fc26c7d518eaaa02e | `android` | HD版 | `5` | `android_hd` |
70 | bb3101000e232e27 | 36efcfed79309338ced0380abd824ac1 | `android` | play版(国际版) | `14` | `android_i` |
71 | ae57252b0c09105d | c75875c596a69eb55bd119e74b07cfe3 | `android` | play版(国际版) | `14` | `android_i` | 仅获取用户信息时使用(7.X及更新版本) |
72 | 8e16697a1b4f8121 | f5dd03b752426f2e623d7badb28d190a | `android` | play版(国际版) | `14` | `android_i` | 可能来自旧版 |
73 | 7d089525d3611b1c | acd495b248ec528c2eed1e862d393126 | `android` | 东南亚版 | `30` | `bstar_a` |
74 | iVGUTjsxvpLeuDCf | aHRmhWMLkdeMuILqORnYZocwMBpMEOdt | `android` | - | - | - | 视频取流专用, 仅5.X旧版使用 |
75 | YvirImLGlLANCLvM | JNlZNgfNGKZEpaDTkCdPQVXntXhuiJEM | `ios` | - | - | - | 视频取流专用 |
76 | 27eb53fc9058f8c3 | c2ed53a74eeefe3cf99fbd01d8c9c375 | `web`/`ios`? | - | - | - | 第三方授权使用 |
77 | 84956560bc028eb7 | 94aba54af9065f71de72f5508f1cd42e | ? | UWP版 | - | - | 部分API不接受此appkey, 返回-663错误 |
78 | 85eb6835b0a1034e | 2ad42749773c441109bdc0191257a664 | ? | UWP版? | - | - | 部分API不接受此appkey, 返回-663错误 |
79 | 4ebafd7c4951b366 | 8cb98205e9b2ad3669aad0fce12a4c13 | `ios` | iPhone客户端? | `iphone` | ? |
80 | 8d23902c1688a798 | 710f0212e62bd499b8d3ac6e1db9302a | `android` | AndroidBiliThings | ? | ? |
81 | 4c6e1021617d40d9 | e559a59044eb2701b7a8628c86aa12ae | `android` | AndroidMallTicket | ? | ? |
82 | c034e8b74130a886 | e4e8966b1e71847dc4a3830f2d078523 | `android` | AndroidOttSdk | `7` | ? |
83 | 4409e2ce8ffd12b8 | 59b43e04ad6965f34319062b478f83dd | `android` | 云视听小电视(TV版) | `9`? | `android_tv_yst`? |
84 | 37207f2beaebf8d7 | e988e794d4d4b6dd43bc0e89d6e90c43 | `android` | BiliLink | ? | ? |
85 | 9a75abf7de2d8947 | 35ca1c82be6c2c242ecc04d88c735f31 | `android` | BiliScan | ? | ? |
86 | aae92bc66f3edfab | af125a0d5279fd576c1b4418a3e8276d | ? | PC 投稿工具 | - | ? |
87 | bca7e84c2d947ac6 | 60698ba2f68e01ce44738920a0ffe768 | ? | login | - | ? |
88
89 注释:
90
91 <sup>1</sup> `neuronAppId`, 产品编号,由数据平台分配,粉=1,白=2,蓝=3,直播姬=4,HD=5,海外=6,OTT=7,漫画=8,TV野版=9,小视频=10,网易漫画=11,网易漫画lite=12,网易漫画HD=13, 国际版=14.
92
93 <sup>2</sup> `platform`, `mobi_app` 仅供参考, 具体值需要抓包确定.