1use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
2use serde::{ Deserialize, Serialize };
3
4#[derive(Debug, Clone, Deserialize, Serialize)]
8pub struct RegionPage {
9 pub count: u32,
11 pub num: u32,
13 pub size: u32,
15}
16
17#[derive(Debug, Clone, Deserialize, Serialize)]
19pub struct RegionArchivesData {
20 pub archives: Vec<serde_json::Value>, pub page: RegionPage,
24}
25
26#[derive(Debug, Clone, Deserialize, Serialize)]
27pub struct NewListRankResult {
28 #[serde(rename = "pubdate")]
30 pub pub_date: String,
31 pub pic: String,
33 pub tag: String,
35 pub duration: u32,
37 pub id: u64,
39 pub rank_score: Option<u64>,
41 pub badgepay: bool,
43 pub senddate: Option<u64>,
45 pub author: String,
47 pub review: u64,
49 pub mid: u64,
51 pub is_union_video: u8,
53 pub rank_index: Option<u64>,
55 #[serde(rename = "type")]
57 pub type_name: String,
58 pub play: String,
60 #[serde(rename = "video_review")]
62 pub video_review: u64,
63 pub is_pay: u8,
65 pub favorites: u64,
67 pub arcurl: String,
69 pub bvid: String,
71 pub title: String,
73 pub description: String,
75 }
77
78#[derive(Debug, Clone, Deserialize, Serialize)]
80pub struct NewListRankData {
81 pub result: Option<Vec<NewListRankResult>>,
83 #[serde(rename = "numResults")]
85 pub num_results: u32,
86 pub page: u32,
88 pub pagesize: u32,
90 pub msg: String,
92}
93
94impl BpiClient {
95 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 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 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 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; 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; let tag_id = 10026108; 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; 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}