bpi_rs/video_ranking/
ranking.rs

1use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
2use serde::{Deserialize, Serialize};
3
4// --- 获取分区视频排行榜列表 ---
5
6/// 排行榜列表中的单个视频
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct RankingVideoItem {
9    // 文档未提供具体字段,通常与视频详细信息相似
10    // 这里用 serde_json::Value 代替
11    #[serde(flatten)]
12    pub inner: serde_json::Value,
13}
14
15/// 排行榜列表数据
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct RankingListData {
18    /// 备注信息
19    pub note: String,
20    /// 视频列表
21    pub list: Vec<RankingVideoItem>,
22}
23
24impl BpiClient {
25    /// 获取分区视频排行榜列表
26    ///
27    /// 文档: https://socialsisteryi.github.io/bilibili-API-collect/docs/video_ranking/ranking.html#获取分区视频排行榜列表
28    ///
29    /// # 参数
30    /// | 名称        | 类型           | 说明                 |
31    /// | ----------- | --------------| -------------------- |
32    /// | `rid`       | Option<u32>   | 目标分区 tid,默认0(全站) |
33    /// | `type_name` | Option<&str>  | 榜单类型 all/rookie/origin,可选 |
34    pub async fn video_ranking_list(
35        &self,
36        rid: Option<u32>,
37        type_name: Option<&str>,
38    ) -> Result<BpiResponse<RankingListData>, BpiError> {
39        let mut request = self.get("https://api.bilibili.com/x/web-interface/ranking/v2");
40
41        if let Some(r) = rid {
42            request = request.query(&[("rid", r)]);
43        }
44        if let Some(t) = type_name {
45            request = request.query(&[("type", t)]);
46        }
47
48        // 添加 User-Agent 以通过鉴权
49        request = request.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
50
51        // WBI签名相关参数文档未给出完整说明,忽略
52        // request = request.query(&[("w_rid", "wbi_signature"), ("wts", "wbi_timestamp")]);
53
54        request.send_bpi("获取分区视频排行榜列表").await
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use tracing::info;
62
63    #[tokio::test]
64    async fn test_video_ranking_list() {
65        let bpi = BpiClient::new();
66        // 获取全站排行榜
67        let resp = bpi.video_ranking_list(None, None).await;
68
69        info!("{:?}", resp);
70        assert!(resp.is_ok());
71
72        let resp_data = resp.unwrap();
73        info!("code: {}", resp_data.code);
74        if let Some(data) = resp_data.data {
75            info!("note: {}", data.note);
76            info!("排行榜视频数: {}", data.list.len());
77            if let Some(first_item) = data.list.first() {
78                // 因为 RankingVideoItem 使用了 serde_json::Value,这里打印原始 JSON
79                info!(
80                    "first item: {}",
81                    serde_json::to_string_pretty(&first_item).unwrap()
82                );
83            }
84        }
85    }
86
87    #[tokio::test]
88    async fn test_video_ranking_list_with_rid() {
89        let bpi = BpiClient::new();
90        // 获取日常分区排行榜 (rid=21)
91        let resp = bpi.video_ranking_list(Some(21), None).await;
92
93        info!("{:?}", resp);
94        assert!(resp.is_ok());
95
96        let resp_data = resp.unwrap();
97        info!("code: {}", resp_data.code);
98        if let Some(data) = resp_data.data {
99            info!("note: {}", data.note);
100            info!("排行榜视频数: {}", data.list.len());
101        }
102    }
103
104    #[tokio::test]
105    async fn test_video_ranking_list_with_type() {
106        let bpi = BpiClient::new();
107        // 获取新人排行榜
108        let resp = bpi.video_ranking_list(None, Some("rookie")).await;
109
110        info!("{:?}", resp);
111        assert!(resp.is_ok());
112
113        let resp_data = resp.unwrap();
114        info!("code: {}", resp_data.code);
115        if let Some(data) = resp_data.data {
116            info!("note: {}", data.note);
117            info!("排行榜视频数: {}", data.list.len());
118        }
119    }
120}