返回提交历史
Modified
docs/misc/sign/wbi.md
+158
-58
XFEstudio/bilibili-API-collect
Update wbi.md about rust and javascript code examples for wbi signature implementation (#884)
* Update wbi.md about rust and javascript * Apply suggestions from code review
56bfa77
代码差异
1 个文件
+158
-58
@@ -196,80 +196,72 @@ bar=514&baz=1919810&foo=114&wts=1684746387&w_rid=d3cbd2a2316089117134038bf4caf44
196
196
197
197
```javascript
198
198
import md5 from 'md5'
199
import axios from 'axios'
200
199
201
200
const mixinKeyEncTab = [
202
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
203
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
204
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
205
36, 20, 34, 44, 52
201
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
202
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
203
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
204
36, 20, 34, 44, 52
206
205
]
207
206
208
207
// 对 imgKey 和 subKey 进行字符顺序打乱编码
209
function getMixinKey(orig) {
210
let temp = ''
211
mixinKeyEncTab.forEach((n) => {
212
temp += orig[n]
213
})
214
return temp.slice(0, 32)
215
}
208
const getMixinKey = (orig) => mixinKeyEncTab.map(n => orig[n]).join('').slice(0, 32)
216
209
217
210
// 为请求参数进行 wbi 签名
218
211
function encWbi(params, img_key, sub_key) {
219
const mixin_key = getMixinKey(img_key + sub_key),
220
curr_time = Math.round(Date.now() / 1000),
221
chr_filter = /[!'()*]/g
222
let query = []
223
Object.assign(params, { wts: curr_time }) // 添加 wts 字段
224
// 按照 key 重排参数
225
Object.keys(params).sort().forEach((key) => {
226
query.push(
227
`${encodeURIComponent(key)}=${encodeURIComponent(
228
// 过滤 value 中的 "!'()*" 字符
229
params[key].toString().replace(chr_filter, '')
230
)}`
231
)
212
const mixin_key = getMixinKey(img_key + sub_key),
213
curr_time = Math.round(Date.now() / 1000),
214
chr_filter = /[!'()*]/g
215
216
Object.assign(params, { wts: curr_time }) // 添加 wts 字段
217
// 按照 key 重排参数
218
const query = Object
219
.keys(params)
220
.sort()
221
.map(key => {
222
// 过滤 value 中的 "!'()*" 字符
223
const value = params[key].toString().replace(chr_filter, '')
224
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
232
225
})
233
query = query.join('&')
234
const wbi_sign = md5(query + mixin_key) // 计算 w_rid
235
return query + '&w_rid=' + wbi_sign
226
.join('&')
227
228
const wbi_sign = md5(query + mixin_key) // 计算 w_rid
229
230
return query + '&w_rid=' + wbi_sign
236
231
}
237
232
238
233
// 获取最新的 img_key 和 sub_key
239
234
async function getWbiKeys() {
240
const resp = await axios({
241
url: 'https://api.bilibili.com/x/web-interface/nav',
242
method: 'get',
243
responseType: 'json'
244
}),
245
json_content = resp.data,
246
img_url = json_content.data.wbi_img.img_url,
247
sub_url = json_content.data.wbi_img.sub_url
248
249
return {
250
img_key: img_url.slice(
251
img_url.lastIndexOf('/') + 1,
252
img_url.lastIndexOf('.')
253
),
254
sub_key: sub_url.slice(
255
sub_url.lastIndexOf('/') + 1,
256
sub_url.lastIndexOf('.')
257
)
235
const res = await fetch('https://api.bilibili.com/x/web-interface/nav', {
236
headers: {
237
// SESSDATA 字段
238
Cookie: "SESSDATA=xxxxxx"
258
239
}
240
})
241
const { data: { wbi_img: { img_url, sub_url } } } = await res.json()
242
243
return {
244
img_key: img_url.slice(
245
img_url.lastIndexOf('/') + 1,
246
img_url.lastIndexOf('.')
247
),
248
sub_key: sub_url.slice(
249
sub_url.lastIndexOf('/') + 1,
250
sub_url.lastIndexOf('.')
251
)
252
}
259
253
}
260
254
261
getWbiKeys().then((wbi_keys) => {
262
const query = encWbi(
263
{
264
foo: '114',
265
bar: '514',
266
baz: 1919810
267
},
268
wbi_keys.img_key,
269
wbi_keys.sub_key
270
)
271
console.log(query)
272
})
255
async function main() {
256
const web_keys = await getWbiKeys()
257
const params = { foo: '114', bar: '514', baz: 1919810 },
258
img_key = web_keys.img_key,
259
sub_key = web_keys.sub_key
260
const query = encWbi(params, img_key, sub_key)
261
console.log(query)
262
}
263
264
main()
273
265
```
274
266
275
267
输出内容为进行 Wbi 签名的后参数的 url query 形式
@@ -714,5 +706,113 @@ class Bilibili {
714
706
$c = new Bilibili();
715
707
echo $c->reQuery(['foo' => '114', 'bar' => '514', 'baz' => 1919810]);
716
708
// bar=514&baz=1919810&foo=114&wts=1700384803&w_rid=4614cb98d60a43e50c3a3033fe3d116b
709
```
710
711
### Rust
712
713
需要 serde、serde_json、reqwest、tokio 以及 md5
714
715
```rust
716
use reqwest::header::USER_AGENT;
717
use serde::Deserialize;
718
use std::time::{SystemTime, UNIX_EPOCH};
719
720
const MIXIN_KEY_ENC_TAB: [usize; 64] = [
721
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,
722
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,
723
54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52,
724
];
725
726
#[derive(Deserialize)]
727
struct WbiImg {
728
img_url: String,
729
sub_url: String,
730
}
731
732
#[derive(Deserialize)]
733
struct Data {
734
wbi_img: WbiImg,
735
}
717
736
737
#[derive(Deserialize)]
738
struct ResWbi {
739
data: Data,
740
}
741
742
// 对 imgKey 和 subKey 进行字符顺序打乱编码
743
fn get_mixin_key(orig: &[u8]) -> String {
744
MIXIN_KEY_ENC_TAB
745
.iter()
746
.map(|&i| orig[i] as char)
747
.collect::<String>()
748
}
749
750
fn get_url_encoded(s: &str) -> String {
751
s.chars()
752
.filter_map(|c| match c.is_ascii_alphanumeric() || "-_.~".contains(c) {
753
true => Some(c.to_string()),
754
false => {
755
// 过滤 value 中的 "!'()*" 字符
756
if "!'()*".contains(c) {
757
return None;
758
}
759
let encoded = c
760
.encode_utf8(&mut [0; 4])
761
.bytes()
762
.fold("".to_string(), |acc, b| acc + &format!("%{:02X}", b));
763
Some(encoded)
764
}
765
})
766
.collect::<String>()
767
}
768
769
// 为请求参数进行 wbi 签名
770
fn encode_wbi(params: &mut Vec<(&str, String)>, (img_key, sub_key): (String, String)) -> String {
771
let mixin_key = get_mixin_key((img_key + &sub_key).as_bytes());
772
let cur_time = match SystemTime::now().duration_since(UNIX_EPOCH) {
773
Ok(t) => t.as_secs(),
774
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
775
};
776
// 添加当前时间戳
777
params.push(("wts", cur_time.to_string()));
778
// 重新排序
779
params.sort_by(|a, b| a.0.cmp(b.0));
780
let query = params.iter().fold(String::from(""), |acc, (k, v)| {
781
acc + format!("{}={}&", get_url_encoded(k), get_url_encoded(v)).as_str()
782
});
783
784
let web_sign = format!("{:?}", md5::compute(query.clone() + &mixin_key));
785
786
query + &format!("w_rid={}", web_sign)
787
}
788
789
async fn get_wbi_keys() -> Result<(String, String), reqwest::Error> {
790
let client = reqwest::Client::new();
791
let ResWbi { data:Data{wbi_img} } = client
792
.get("https://api.bilibili.com/x/web-interface/nav")
793
.header(USER_AGENT,"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36")
794
// SESSDATA=xxxxx
795
.header("Cookie", "SESSDATA=xxxxx")
796
.send()
797
.await?
798
.json::<ResWbi>()
799
.await?;
800
801
Ok((wbi_img.img_url, wbi_img.sub_url))
802
}
803
804
#[tokio::main]
805
async fn main() {
806
let urls = get_wbi_keys().await.unwrap();
807
let mut params = vec![
808
("foo", String::from("114")),
809
("bar", String::from("514")),
810
("baz", String::from("1919810")),
811
];
812
813
let query = encode_wbi(&mut params, urls);
814
815
println!("{}", query);
816
}
718
817
```
818