1#![warn(missing_docs)]
2use log::debug;
28use std::sync::Arc;
29use tokio::sync::mpsc;
30
31mod cred_utils;
32
33pub mod wbi;
34pub use wbi::Client;
35
36type StateData = im::HashMap<String, String>;
37type Json = serde_json::Value;
38
39#[derive(Clone, Debug)]
40struct Bench {
41 data: Arc<Json>,
42 state: StateData,
43 tx: mpsc::Sender<StateData>,
44}
45
46impl Bench {
47 pub fn new() -> (Self, mpsc::Receiver<StateData>) {
48 let unstable: Json = serde_json::from_str(include_str!("api_info/unstable.json"))
49 .expect("api_info/unstable.json invalid");
50 let user: Json = serde_json::from_str(include_str!("api_info/user.json"))
51 .expect("api_info/user.json invalid");
52 let live: Json = serde_json::from_str(include_str!("api_info/live.json"))
53 .expect("api_info/live.json invalid");
54 let video: Json = serde_json::from_str(include_str!("api_info/video.json"))
55 .expect("api_info/video.json invalid");
56 let api_xlive: Json = serde_json::from_str(include_str!("api_info/xlive.json"))
57 .expect("api_info/xlive.json invalid");
58 let credential: Json = serde_json::from_str(include_str!("api_info/credential.json"))
59 .expect("api_info/credential.json invalid");
60 let wbi_oe: Json =
61 serde_json::from_str(include_str!("wbi_oe.json")).expect("wbi_oe.json invalid"); let mut state = StateData::new();
65 state.insert("SESSDATA".into(), "None".into());
66 state.insert("bili_jct".into(), "None".into());
67 state.insert("ac_time_value".into(), "None".into());
68 let (tx, rx) = mpsc::channel(1);
69 (
70 Self {
71 data: Arc::new(serde_json::json!({
72 "api": {
73 "user": user,
74 "live": live,
75 "video": video,
76 "xlive": api_xlive,
77 "credential": credential,
78 "unstable": unstable,
79 },
80 "cookie_state": [
81 "buvid3",
82 "buvid4",
83 "buvid_fp",
84 "_uuid",
85 "SESSDATA",
86 "ac_time_value",
87 "bili_jct",
88 "DedeUserID",
89 ],
90 "wbi_oe": wbi_oe,
91 "headers": {
92 "REFERER": "https://www.bilibili.com",
93 "USER_AGENT": concat!(
94 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ",
95 "AppleWebKit/537.36 (KHTML, like Gecko) ",
96 "Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.54"
97 ),
98 }
99 })),
100 state,
101 tx,
102 },
103 rx,
104 )
105 }
106
107 #[allow(dead_code)]
108 pub fn commit_state(&self, change: impl FnOnce(&mut StateData)) {
109 let mut s = self.state.clone();
110 change(&mut s);
111 self.update_state(s);
112 }
113
114 pub fn update_state(&self, state: StateData) {
115 if let Err(e) = self.tx.try_send(state) {
116 debug!("bench try_send {e:?}");
117 }
118 }
119
120 pub fn get_room_id<T: std::fmt::Display>(&self, uid: T) -> Option<&String> {
121 self.state.get(&format!("room_id:{uid}"))
122 }
123
124 pub fn set_room_id<U, V>(&self, uid: U, room: &V)
125 where
126 U: std::fmt::Display,
127 V: ToString,
128 {
129 let k = format!("room_id:{uid}");
130 let v = room.to_string();
131 self.commit_state(|sto| {
132 sto.insert(k, v);
133 });
134 }
135}
136
137pub trait Lodash {
159 #[must_use]
161 fn at(&self, paths: Json) -> Self;
162}
163
164fn lodash_step<'a>(v: &'a Json, p: &Json) -> &'a Json {
165 match p {
166 Json::Number(n) => &v[n.as_u64().map(usize::try_from).unwrap().unwrap()],
167 Json::String(s) => &v[s],
168 _ => &Json::Null,
169 }
170}
171
172fn lodash_get<'a>(v: &'a Json, path: &Json) -> &'a Json {
173 let Json::Array(path) = path else {
174 return &Json::Null;
175 };
176 if path.is_empty() {
177 return v;
178 }
179 let mut it = path.iter();
180 let mut v = lodash_step(v, it.next().unwrap());
181 for p in it {
182 v = lodash_step(v, p);
183 }
184 v
185}
186
187impl Lodash for Json {
188 fn at(&self, paths: Json) -> Self {
189 let Some(a) = paths.as_array() else {
190 return Self::Null;
191 };
192 if a[0].is_array() {
193 let mut v: Vec<Self> = Vec::new();
194 for path in a {
195 v.push(lodash_get(self, path).clone());
196 }
197 Self::Array(v)
198 } else {
199 lodash_get(self, &paths).clone()
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use serde_json::json;
208 use std::thread;
209
210 #[test]
211 fn test_lodash_at() {
212 let bench = Bench::new().0;
213 let v = &bench.data;
214 assert_eq!(
215 v.at(json!([
216 ["headers", "REFERER"],
217 ["headers", "__must_null__"],
218 ])),
219 json!(["https://www.bilibili.com", ()])
220 );
221 assert_eq!(
222 v.at(json!(["headers", "REFERER"])),
223 json!("https://www.bilibili.com")
224 );
225 }
226
227 #[test]
228 fn validate_wbi_user_info() {
229 let bench = Bench::new().0;
230 assert_eq!(
231 bench.data["api"]["user"]["info"]["info"]["wbi"].as_bool(),
232 Some(true)
233 );
234 }
235
236 #[test]
237 fn validate_method_xlive_get_list() {
238 let bench = Bench::new().0;
239 assert_eq!(
240 bench.data["api"]["xlive"]["info"]["get_list"]["method"].as_str(),
241 Some("GET")
242 );
243 }
244
245 fn json_state(rx: &mut mpsc::Receiver<StateData>) -> Json {
246 rx.try_recv()
247 .ok()
248 .map(serde_json::to_value)
249 .and_then(Result::ok)
250 .unwrap_or(Json::Null)
251 }
252
253 #[test]
254 fn commit_state() {
255 let (bench, mut rx) = Bench::new();
256 bench.commit_state(|s| {
257 s.clear();
258 s.insert("test".into(), "value".into());
259 });
260 assert_eq!(json_state(&mut rx), json!({"test":"value"}));
261 bench.commit_state(|s| {
262 s.clear();
263 s.insert("test".into(), "modified".into());
264 });
265 assert_eq!(json_state(&mut rx), json!({"test":"modified"}));
266 }
267
268 #[test]
269 fn multithread_commit_state() {
270 let (bench0, mut rx) = Bench::new();
271
272 let bench = bench0.clone();
273 let hdl = thread::spawn(move || {
274 bench.commit_state(|s| {
275 s.clear();
276 s.insert("test".into(), "value".into());
277 });
278 });
279 assert!(hdl.join().is_ok());
280 assert_eq!(json_state(&mut rx), json!({"test":"value"}));
281
282 let bench = bench0;
283 let hdl = thread::spawn(move || {
284 bench.commit_state(|s| {
285 s.clear();
286 s.insert("test".into(), "modified".into());
287 });
288 });
289 assert!(hdl.join().is_ok());
290 assert_eq!(json_state(&mut rx), json!({"test":"modified"}));
291 }
292
293 #[test]
294 fn insure_get_nav_api_no_encwbi() {
295 let bench = Bench::new().0;
296 assert!(matches!(
297 bench.data["api"]["credential"]["valid"]["wbi"],
298 Json::Null | Json::Bool(false)
299 ));
300 }
301}