Skip to main content

bpi_rs/video_ranking/
dynamic.rs

1use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
2use serde::{ Deserialize, Serialize };
3
4// --- 获取分区最新视频列表 ---
5
6/// 分区最新视频的页面信息
7#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct RegionPage {
9    /// 总计视频数
10    pub count: u32,
11    /// 当前页码
12    pub num: u32,
13    /// 每页项数
14    pub size: u32,
15}
16
17/// 分区最新视频列表的数据
18#[derive(Debug, Clone, Deserialize, Serialize)]
19pub struct RegionArchivesData {
20    /// 视频列表
21    pub archives: Vec<serde_json::Value>, // archives内容复杂,这里用Value代替
22    /// 页面信息
23    pub page: RegionPage,
24}
25
26#[derive(Debug, Clone, Deserialize, Serialize)]
27pub struct NewListRankResult {
28    /// 发布时间
29    #[serde(rename = "pubdate")]
30    pub pub_date: String,
31    /// 封面图
32    pub pic: String,
33    /// 标签
34    pub tag: String,
35    /// 时长 (秒)
36    pub duration: u32,
37    /// avid
38    pub id: u64,
39    /// 排序分数
40    pub rank_score: Option<u64>,
41    /// 是否有角标
42    pub badgepay: bool,
43    /// 发送时间 (UNIX 秒级时间戳)
44    pub senddate: Option<u64>,
45    /// UP 主名
46    pub author: String,
47    /// 评论数
48    pub review: u64,
49    /// UP 主 mid
50    pub mid: u64,
51    /// 是否为联合投稿
52    pub is_union_video: u8,
53    /// 排序索引号
54    pub rank_index: Option<u64>,
55    /// 类型
56    #[serde(rename = "type")]
57    pub type_name: String,
58    /// 播放数
59    pub play: String,
60    /// 弹幕数
61    #[serde(rename = "video_review")]
62    pub video_review: u64,
63    /// 是否付费
64    pub is_pay: u8,
65    /// 收藏数
66    pub favorites: u64,
67    /// 视频播放页 URL
68    pub arcurl: String,
69    /// bvid
70    pub bvid: String,
71    /// 标题
72    pub title: String,
73    /// 简介
74    pub description: String,
75    // 忽略其他作用不明确的字段
76}
77
78/// 带排序的分区投稿列表数据
79#[derive(Debug, Clone, Deserialize, Serialize)]
80pub struct NewListRankData {
81    /// 结果本体
82    pub result: Option<Vec<NewListRankResult>>,
83    /// 总计视频数
84    #[serde(rename = "numResults")]
85    pub num_results: u32,
86    /// 页码
87    pub page: u32,
88    /// 视频数
89    pub pagesize: u32,
90    /// 结果信息
91    pub msg: String,
92}
93
94impl BpiClient {
95    /// 获取分区最新视频列表
96    ///
97    /// # 文档
98    /// [查看API文档](https://socialsisteryi.github.io/bilibili-API-collect/docs/video_ranking/dynamic.html#获取分区最新视频列表)
99    ///
100    /// # 参数
101    /// | 名称   | 类型         | 说明                 |
102    /// | ------ | ------------| -------------------- |
103    /// | `rid`  | u32         | 分区ID               |
104    /// | `pn`   | `Option<u32>` | 页码,可选           |
105    /// | `ps`   | `Option<u32>` | 每页数量,可选       |
106    pub async fn video_region_dynamic(
107        &self,
108        rid: u32,
109        pn: Option<u32>,
110        ps: Option<u32>
111    ) -> Result<BpiResponse<RegionArchivesData>, BpiError> {
112        let mut request = self
113            .get("https://api.bilibili.com/x/web-interface/dynamic/region")
114            .query(&[("rid", rid.to_string())]);
115
116        if let Some(pn) = pn {
117            request = request.query(&[("pn", pn.to_string())]);
118        }
119        if let Some(ps) = ps {
120            request = request.query(&[("ps", ps.to_string())]);
121        }
122
123        request.send_bpi("获取分区最新视频列表").await
124    }
125
126    /// 获取分区标签近期互动列表
127    ///
128    /// # 文档
129    /// [查看API文档](https://socialsisteryi.github.io/bilibili-API-collect/docs/video_ranking/dynamic.html#获取分区标签近期互动列表)
130    ///
131    /// # 参数
132    /// | 名称    | 类型         | 说明                 |
133    /// | ------- | ------------| -------------------- |
134    /// | `rid`   | u32         | 分区ID               |
135    /// | `tag_id`| u64         | 标签ID               |
136    /// | `pn`    | `Option<u32>` | 页码,可选           |
137    /// | `ps`    | `Option<u32>` | 每页数量,可选       |
138    pub async fn video_region_tag_dynamic(
139        &self,
140        rid: u32,
141        tag_id: u64,
142        pn: Option<u32>,
143        ps: Option<u32>
144    ) -> Result<BpiResponse<RegionArchivesData>, BpiError> {
145        let mut request = self.get("https://api.bilibili.com/x/web-interface/dynamic/tag").query(
146            &[
147                ("rid", rid.to_string()),
148                ("tag_id", tag_id.to_string()),
149            ]
150        );
151
152        if let Some(pn) = pn {
153            request = request.query(&[("pn", pn.to_string())]);
154        }
155        if let Some(ps) = ps {
156            request = request.query(&[("ps", ps.to_string())]);
157        }
158
159        request.send_bpi("获取分区标签近期互动列表").await
160    }
161
162    /// 获取分区近期投稿列表
163    ///
164    /// # 文档
165    /// [查看API文档](https://socialsisteryi.github.io/bilibili-API-collect/docs/video_ranking/dynamic.html#获取分区近期投稿列表)
166    ///
167    /// # 参数
168    /// | 名称   | 类型         | 说明                 |
169    /// | ------ | ------------| -------------------- |
170    /// | `rid`  | u32         | 分区ID               |
171    /// | `pn`   | `Option<u32>` | 页码,可选           |
172    /// | `ps`   | `Option<u32>` | 每页数量,可选       |
173    /// | `typ`  | `Option<u32>` | 类型,可选           |
174    pub async fn video_region_newlist(
175        &self,
176        rid: u32,
177        pn: Option<u32>,
178        ps: Option<u32>,
179        typ: Option<u32>
180    ) -> Result<BpiResponse<RegionArchivesData>, BpiError> {
181        let mut request = self
182            .get("https://api.bilibili.com/x/web-interface/newlist")
183            .query(&[("rid", rid.to_string())]);
184
185        if let Some(pn) = pn {
186            request = request.query(&[("pn", pn.to_string())]);
187        }
188        if let Some(ps) = ps {
189            request = request.query(&[("ps", ps.to_string())]);
190        }
191        if let Some(t) = typ {
192            request = request.query(&[("type", t.to_string())]);
193        }
194
195        request.send_bpi("获取分区近期投稿列表").await
196    }
197
198    /// 获取分区近期投稿列表(带排序)
199    ///
200    /// # 文档
201    /// [查看API文档](https://socialsisteryi.github.io/bilibili-API-collect/docs/video_ranking/dynamic.html#获取分区近期投稿列表带排序)
202    ///
203    /// # 参数
204    /// | 名称        | 类型           | 说明                 |
205    /// | ----------- | --------------| -------------------- |
206    /// | `cate_id`   | u32           | 分类ID               |
207    /// | `order`     | `Option<&str>`  | 排序方式,可选       |
208    /// | `page`      | `Option<u32>`   | 页码,可选           |
209    /// | `pagesize`  | u32           | 每页数量             |
210    /// | `time_from` | &str          | 起始日期(YYYYMMDD)   |
211    /// | `time_to`   | &str          | 结束日期(YYYYMMDD)   |
212    pub async fn video_region_newlist_rank(
213        &self,
214        cate_id: u32,
215        order: Option<&str>,
216        page: Option<u32>,
217        pagesize: u32,
218        time_from: &str,
219        time_to: &str
220    ) -> Result<BpiResponse<NewListRankData>, BpiError> {
221        let cate_id = cate_id.to_string();
222        let pagesize = pagesize.to_string();
223        let mut request = self.get("https://api.bilibili.com/x/web-interface/newlist_rank").query(
224            &[
225                ("search_type", "video"),
226                ("view_type", "hot_rank"),
227                ("cate_id", cate_id.as_str()),
228                ("pagesize", pagesize.as_str()),
229                ("time_from", time_from),
230                ("time_to", time_to),
231            ]
232        );
233
234        if let Some(o) = order {
235            request = request.query(&[("order", o)]);
236        }
237        if let Some(p) = page {
238            request = request.query(&[("page", p.to_string())]);
239        }
240
241        request.send_bpi("获取分区近期投稿列表 (带排序)").await
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248    use chrono::{ Duration, Local };
249    use tracing::info;
250
251    #[tokio::test]
252    async fn test_video_region_dynamic() {
253        let bpi = BpiClient::new();
254        let rid = 21; // 日常分区
255        let ps = Some(2);
256        let pn = Some(1);
257        let resp = bpi.video_region_dynamic(rid, pn, ps).await;
258
259        info!("{:?}", resp);
260        assert!(resp.is_ok());
261
262        let resp_data = resp.unwrap();
263        info!("code: {}", resp_data.code);
264        if let Some(data) = resp_data.data {
265            info!("total videos: {}", data.page.count);
266            info!("first item: {:?}", data.archives.first());
267        }
268    }
269
270    #[tokio::test]
271    async fn test_video_region_tag_dynamic() {
272        let bpi = BpiClient::new();
273        let rid = 136; // 音游分区
274        let tag_id = 10026108; // Phigros
275        let ps = Some(2);
276        let pn = Some(1);
277        let resp = bpi.video_region_tag_dynamic(rid, tag_id, pn, ps).await;
278
279        info!("{:?}", resp);
280        assert!(resp.is_ok());
281
282        let resp_data = resp.unwrap();
283        info!("code: {}", resp_data.code);
284        if let Some(data) = resp_data.data {
285            info!("total videos: {}", data.page.count);
286            info!("first item: {:?}", data.archives.first());
287        }
288    }
289
290    #[tokio::test]
291    async fn test_video_region_newlist_rank() {
292        let bpi = BpiClient::new();
293        let cate_id = 231; // 计算机技术
294        let pagesize = 2;
295        let today = Local::now().date_naive();
296        let seven_days_ago = today - Duration::days(7);
297
298        let time_from = seven_days_ago.format("%Y%m%d").to_string();
299        let time_to = today.format("%Y%m%d").to_string();
300
301        let resp = bpi.video_region_newlist_rank(
302            cate_id,
303            Some("click"),
304            Some(1),
305            pagesize,
306            &time_from,
307            &time_to
308        ).await;
309
310        info!("{:?}", resp);
311        assert!(resp.is_ok());
312
313        let resp_data = resp.unwrap();
314        info!("code: {}", resp_data.code);
315        if let Some(data) = resp_data.data {
316            info!("total results: {}", data.num_results);
317            info!("first result: {:?}", data.result.unwrap().first());
318        }
319    }
320}