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

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
fwqaaq <fwqaaq@gmail.com>
提交于

代码差异

1 个文件 +158 -58
Modified docs/misc/sign/wbi.md +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