1use serde::{ Deserialize, Serialize };
6
7use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
8
9#[derive(Debug, Serialize, Clone, Deserialize)]
11pub struct UpStatData {
12 #[serde(rename = "inc_coin")]
14 pub inc_coin: i64,
15
16 #[serde(rename = "inc_elec")]
18 pub inc_elec: i64,
19
20 #[serde(rename = "inc_fav")]
22 pub inc_fav: i64,
23
24 #[serde(rename = "inc_like")]
26 pub inc_like: i64,
27
28 #[serde(rename = "inc_share")]
30 pub inc_share: i64,
31
32 #[serde(rename = "incr_click")]
34 pub incr_click: i64,
35
36 #[serde(rename = "incr_dm")]
38 pub incr_dm: i64,
39
40 #[serde(rename = "incr_fans")]
42 pub incr_fans: i64,
43
44 #[serde(rename = "incr_reply")]
46 pub incr_reply: i64,
47
48 #[serde(rename = "total_click")]
50 pub total_click: i64,
51
52 #[serde(rename = "total_coin")]
54 pub total_coin: i64,
55
56 #[serde(rename = "total_dm")]
58 pub total_dm: i64,
59
60 #[serde(rename = "total_elec")]
62 pub total_elec: i64,
63
64 #[serde(rename = "total_fans")]
66 pub total_fans: i64,
67
68 #[serde(rename = "total_fav")]
70 pub total_fav: i64,
71
72 #[serde(rename = "total_like")]
74 pub total_like: i64,
75
76 #[serde(rename = "total_reply")]
78 pub total_reply: i64,
79
80 #[serde(rename = "total_share")]
82 pub total_share: i64,
83}
84
85#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct ArchiveCompareItem {
89 pub aid: i64,
91 pub bvid: String,
93 pub cover: String,
95 pub title: String,
97 pub pubtime: i64,
99 pub duration: i64,
101 pub stat: Stat,
102 #[serde(rename = "is_only_self")]
103 pub is_only_self: bool,
104 #[serde(rename = "hour_stat")]
105 pub hour_stat: HourStat,
106}
107
108#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct Stat {
111 #[serde(rename = "not_ready_field")]
112 pub not_ready_field: serde_json::Value,
113 pub play: i64,
115 pub vt: i64,
116 #[serde(rename = "full_play_ratio")]
119 pub full_play_ratio: i64,
120 #[serde(rename = "play_viewer_rate")]
122 pub play_viewer_rate: i64,
123 #[serde(rename = "play_viewer_rate_med")]
124 pub play_viewer_rate_med: i64,
125 #[serde(rename = "play_fan_rate")]
127 pub play_fan_rate: i64,
128 #[serde(rename = "play_fan_rate_med")]
129 pub play_fan_rate_med: i64,
130 #[serde(rename = "active_fans_rate")]
131 pub active_fans_rate: i64,
132 #[serde(rename = "active_fans_med")]
133 pub active_fans_med: i64,
134 #[serde(rename = "tm_rate")]
136 pub tm_rate: i64,
137 #[serde(rename = "tm_rate_med")]
139 pub tm_rate_med: i64,
140 #[serde(rename = "tm_fan_simi_rate_med")]
142 pub tm_fan_simi_rate_med: i64,
143 #[serde(rename = "tm_viewer_simi_rate_med")]
145 pub tm_viewer_simi_rate_med: i64,
146 #[serde(rename = "tm_fan_rate")]
148 pub tm_fan_rate: i64,
149 #[serde(rename = "tm_viewer_rate")]
151 pub tm_viewer_rate: i64,
152 #[serde(rename = "tm_pass_rate")]
154 pub tm_pass_rate: i64,
155 #[serde(rename = "tm_fan_pass_rate")]
157 pub tm_fan_pass_rate: i64,
158 #[serde(rename = "tm_viewer_pass_rate")]
160 pub tm_viewer_pass_rate: i64,
161 #[serde(rename = "crash_rate")]
163 pub crash_rate: i64,
164 #[serde(rename = "crash_rate_med")]
165 pub crash_rate_med: i64,
166 #[serde(rename = "crash_fan_simi_rate_med")]
168 pub crash_fan_simi_rate_med: i64,
169 #[serde(rename = "crash_viewer_simi_rate_med")]
171 pub crash_viewer_simi_rate_med: i64,
172 #[serde(rename = "crash_fan_rate")]
174 pub crash_fan_rate: i64,
175 #[serde(rename = "crash_viewer_rate")]
177 pub crash_viewer_rate: i64,
178 #[serde(rename = "interact_rate")]
180 pub interact_rate: i64,
181 #[serde(rename = "interact_rate_med")]
182 pub interact_rate_med: i64,
183 #[serde(rename = "interact_fan_simi_rate_med")]
185 pub interact_fan_simi_rate_med: i64,
186 #[serde(rename = "interact_viewer_simi_rate_med")]
188 pub interact_viewer_simi_rate_med: i64,
189 #[serde(rename = "interact_fan_rate")]
191 pub interact_fan_rate: i64,
192 #[serde(rename = "interact_viewer_rate")]
194 pub interact_viewer_rate: i64,
195 #[serde(rename = "avg_play_time")]
197 pub avg_play_time: i64,
198 #[serde(rename = "avg_play_time_int")]
199 pub avg_play_time_int: i64,
200 #[serde(rename = "total_new_attention_cnt")]
202 pub total_new_attention_cnt: i64,
203 #[serde(rename = "play_trans_fan_rate")]
205 pub play_trans_fan_rate: i64,
206 #[serde(rename = "play_trans_fan_rate_med")]
208 pub play_trans_fan_rate_med: i64,
209 pub like: i64,
211 pub comment: i64,
213 pub dm: i64,
215 pub fav: i64,
217 pub coin: i64,
219 pub share: i64,
221 #[serde(rename = "unfollow")]
222 pub unfollow: i64,
223 #[serde(rename = "tm_star")]
224 pub tm_star: i64,
225 #[serde(rename = "tm_viewer_star")]
226 pub tm_viewer_star: i64,
227 #[serde(rename = "tm_fan_star")]
228 pub tm_fan_star: i64,
229 #[serde(rename = "crash_p50")]
230 pub crash_p50: i64,
231 #[serde(rename = "crash_viewer_p50")]
232 pub crash_viewer_p50: i64,
233 #[serde(rename = "crash_fan_p50")]
234 pub crash_fan_p50: i64,
235 #[serde(rename = "interact_p50")]
236 pub interact_p50: i64,
237 #[serde(rename = "interact_viewer_p50")]
238 pub interact_viewer_p50: i64,
239 #[serde(rename = "interact_fan_p50")]
240 pub interact_fan_p50: i64,
241 #[serde(rename = "play_trans_fan_p50")]
242 pub play_trans_fan_p50: i64,
243}
244
245#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct HourStat {
248 #[serde(rename = "not_ready_field")]
249 pub not_ready_field: serde_json::Value,
250 pub play: i64,
252 pub vt: i64,
253 pub like: i64,
255 pub comment: i64,
257 pub dm: i64,
259 pub fav: i64,
261 pub coin: i64,
263 pub share: i64,
265 #[serde(rename = "tm_pass_rate")]
267 pub tm_pass_rate: i64,
268 #[serde(rename = "interact_rate")]
270 pub interact_rate: i64,
271 #[serde(rename = "tm_star")]
272 pub tm_star: i64,
273}
274
275#[derive(Debug, Serialize, Clone, Deserialize)]
277pub struct ArchiveCompareData {
278 pub list: Vec<ArchiveCompareItem>,
279}
280
281#[derive(Debug, Serialize, Clone, Deserialize)]
283pub struct UpArticleStatData {
284 pub view: i64,
286 pub reply: i64,
288 pub like: i64,
290 pub coin: i64,
292 pub fav: i64,
294 pub share: i64,
296 #[serde(rename = "incr_view")]
298 pub incr_view: i64,
299 #[serde(rename = "incr_reply")]
301 pub incr_reply: i64,
302 #[serde(rename = "incr_like")]
304 pub incr_like: i64,
305 #[serde(rename = "incr_coin")]
307 pub incr_coin: i64,
308 #[serde(rename = "incr_fav")]
310 pub incr_fav: i64,
311 #[serde(rename = "incr_share")]
313 pub incr_share: i64,
314}
315
316#[derive(Debug, Serialize, Clone, Deserialize)]
318pub struct VideoTrendItem {
319 pub date_key: i64,
321 pub total_inc: i64,
323}
324
325#[derive(Debug, Serialize, Clone, Deserialize)]
327pub struct ArticleTrendItem {
328 pub date_key: i64,
330 pub total_inc: i64,
332}
333
334#[derive(Debug, Serialize, Clone, Deserialize)]
336pub struct PageSource {
337 pub dynamic: i64,
339 pub other: i64,
341 #[serde(rename = "related_video")]
343 pub related_video: i64,
344 pub search: i64,
346 pub space: i64,
348 pub tenma: i64,
350}
351
352#[derive(Debug, Serialize, Clone, Deserialize)]
354pub struct PlayProportion {
355 pub android: i64,
357 pub h5: i64,
359 pub ios: i64,
361 pub out: i64,
363 pub pc: i64,
365}
366
367#[derive(Debug, Serialize, Clone, Deserialize)]
369pub struct PlaySourceData {
370 pub page_source: PageSource,
371 pub play_proportion: PlayProportion,
372}
373
374#[derive(Debug, Serialize, Clone, Deserialize)]
376pub struct Period {
377 pub module_one: Option<String>,
378 pub module_two: Option<String>,
379 pub module_three: Option<String>,
380 pub module_four: Option<String>,
381}
382
383pub type ViewerAreaMap = std::collections::HashMap<String, i64>;
385
386#[derive(Debug, Serialize, Clone, Deserialize)]
387pub struct ViewerArea {
388 pub fan: ViewerAreaMap,
389 pub not_fan: ViewerAreaMap,
390}
391
392#[derive(Debug, Serialize, Clone, Deserialize)]
394pub struct ViewerBaseDetail {
395 pub male: i64,
396 pub female: i64,
397 #[serde(rename = "age_one")]
398 pub age_one: i64,
399 #[serde(rename = "age_two")]
400 pub age_two: i64,
401 #[serde(rename = "age_three")]
402 pub age_three: i64,
403 #[serde(rename = "age_four")]
404 pub age_four: i64,
405 #[serde(rename = "plat_pc")]
406 pub plat_pc: i64,
407 #[serde(rename = "plat_h5")]
408 pub plat_h5: i64,
409 #[serde(rename = "plat_out")]
410 pub plat_out: i64,
411 #[serde(rename = "plat_ios")]
412 pub plat_ios: i64,
413 #[serde(rename = "plat_android")]
414 pub plat_android: i64,
415 #[serde(rename = "plat_other_app")]
416 pub plat_other_app: i64,
417}
418
419#[derive(Debug, Serialize, Clone, Deserialize)]
420pub struct ViewerBase {
421 pub fan: ViewerBaseDetail,
422 pub not_fan: ViewerBaseDetail,
423}
424
425#[derive(Debug, Serialize, Clone, Deserialize)]
427pub struct ViewerData {
428 pub period: Period,
429 pub viewer_area: ViewerArea,
430 pub viewer_base: ViewerBase,
431}
432
433impl BpiClient {
434 pub async fn up_stat(&self) -> Result<BpiResponse<UpStatData>, BpiError> {
441 self
442 .get("https://member.bilibili.com/x/web/index/stat")
443 .send_bpi("获取UP主视频状态数据").await
444 }
445
446 pub async fn up_archive_compare(
459 &self,
460 t: Option<i64>,
461 size: Option<i64>
462 ) -> Result<BpiResponse<ArchiveCompareData>, BpiError> {
463 let mut req = self.get("https://member.bilibili.com/x/web/data/archive_diagnose/compare");
464
465 if let Some(t) = t {
466 req = req.query(&[("t", t)]);
467 }
468 if let Some(size) = size {
469 req = req.query(&[("size", size)]);
470 }
471
472 req.send_bpi("获取UP主视频数据比较").await
473 }
474
475 pub async fn up_article_stat(&self) -> Result<BpiResponse<UpArticleStatData>, BpiError> {
482 self
483 .get("https://member.bilibili.com/x/web/data/article")
484 .send_bpi("获取UP主专栏状态数据").await
485 }
486
487 pub async fn up_video_trend(
499 &self,
500 type_code: i64
501 ) -> Result<BpiResponse<Vec<VideoTrendItem>>, BpiError> {
502 self
503 .get("https://member.bilibili.com/x/web/data/pandect")
504 .query(&[("type", type_code)])
505 .send_bpi("获取UP主视频数据增量趋势").await
506 }
507
508 pub async fn up_article_trend(
520 &self,
521 type_code: i64
522 ) -> Result<BpiResponse<Vec<ArticleTrendItem>>, BpiError> {
523 self
524 .get("https://member.bilibili.com/x/web/data/article/thirty")
525 .query(&[("type", type_code)])
526 .send_bpi("获取UP主专栏数据增量趋势").await
527 }
528
529 #[allow(dead_code)]
536 async fn up_play_source(&self) -> Result<BpiResponse<PlaySourceData>, BpiError> {
537 self
538 .get("https://member.bilibili.com/x/web/data/playsource")
539 .with_bilibili_headers()
540 .send_bpi("获取播放来源占比情况").await
541 }
542
543 pub async fn up_viewer_data(&self) -> Result<BpiResponse<ViewerData>, BpiError> {
550 self.get("https://member.bilibili.com/x/web/data/base").send_bpi("获取播放分布情况").await
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557 use tracing::info;
558
559 #[tokio::test]
560 async fn test_up_stat() -> Result<(), Box<BpiError>> {
561 let bpi = BpiClient::new();
562 let data = bpi.up_stat().await?.into_data()?;
563 info!("UP主视频状态数据: {:?}", data);
564 Ok(())
565 }
566
567 #[tokio::test]
568 async fn test_archive_compare() -> Result<(), Box<BpiError>> {
569 let bpi = BpiClient::new();
570 let data = bpi.up_archive_compare(None, Some(3)).await?.into_data()?;
571 info!("UP主视频数据比较: {:?}", data);
572 Ok(())
573 }
574
575 #[tokio::test]
576 async fn test_up_article_stat() -> Result<(), Box<BpiError>> {
577 let bpi = BpiClient::new();
578 let data = bpi.up_article_stat().await?.into_data()?;
579 info!("UP主专栏状态数据: {:?}", data);
580 Ok(())
581 }
582
583 #[tokio::test]
584 async fn test_video_trend() -> Result<(), Box<BpiError>> {
585 let bpi = BpiClient::new();
586 let data = bpi.up_video_trend(1).await?.into_data()?; info!("UP主视频数据增量趋势: {:?}", data);
588 Ok(())
589 }
590
591 #[tokio::test]
592 async fn test_article_trend() -> Result<(), Box<BpiError>> {
593 let bpi = BpiClient::new();
594 let data = bpi.up_article_trend(1).await?.into_data()?; info!("UP主专栏数据增量趋势: {:?}", data);
596 Ok(())
597 }
598
599 #[tokio::test]
600 async fn test_viewer_data() -> Result<(), Box<BpiError>> {
601 let bpi = BpiClient::new();
602 let data = bpi.up_viewer_data().await?.into_data()?;
603 info!("播放分布情况: {:?}", data);
604 Ok(())
605 }
606}