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(
106 &self,
107 rid: u32,
108 pn: Option<u32>,
109 ps: Option<u32>,
110 ) -> Result<BpiResponse<RegionArchivesData>, BpiError> {
111 let mut request = self
112 .get("https://api.bilibili.com/x/web-interface/dynamic/region")
113 .query(&[("rid", rid.to_string())]);
114
115 if let Some(pn) = pn {
116 request = request.query(&[("pn", pn.to_string())]);
117 }
118 if let Some(ps) = ps {
119 request = request.query(&[("ps", ps.to_string())]);
120 }
121
122 request.send_bpi("获取分区最新视频列表").await
123 }
124
125 pub async fn video_region_tag_dynamic(
137 &self,
138 rid: u32,
139 tag_id: u64,
140 pn: Option<u32>,
141 ps: Option<u32>,
142 ) -> Result<BpiResponse<RegionArchivesData>, BpiError> {
143 let mut request = self
144 .get("https://api.bilibili.com/x/web-interface/dynamic/tag")
145 .query(&[("rid", rid.to_string()), ("tag_id", tag_id.to_string())]);
146
147 if let Some(pn) = pn {
148 request = request.query(&[("pn", pn.to_string())]);
149 }
150 if let Some(ps) = ps {
151 request = request.query(&[("ps", ps.to_string())]);
152 }
153
154 request.send_bpi("获取分区标签近期互动列表").await
155 }
156
157 pub async fn video_region_newlist(
169 &self,
170 rid: u32,
171 pn: Option<u32>,
172 ps: Option<u32>,
173 typ: Option<u32>,
174 ) -> Result<BpiResponse<RegionArchivesData>, BpiError> {
175 let mut request = self
176 .get("https://api.bilibili.com/x/web-interface/newlist")
177 .query(&[("rid", rid.to_string())]);
178
179 if let Some(pn) = pn {
180 request = request.query(&[("pn", pn.to_string())]);
181 }
182 if let Some(ps) = ps {
183 request = request.query(&[("ps", ps.to_string())]);
184 }
185 if let Some(t) = typ {
186 request = request.query(&[("type", t.to_string())]);
187 }
188
189 request.send_bpi("获取分区近期投稿列表").await
190 }
191
192 pub async fn video_region_newlist_rank(
206 &self,
207 cate_id: u32,
208 order: Option<&str>,
209 page: Option<u32>,
210 pagesize: u32,
211 time_from: &str,
212 time_to: &str,
213 ) -> Result<BpiResponse<NewListRankData>, BpiError> {
214 let cate_id = cate_id.to_string();
215 let pagesize = pagesize.to_string();
216 let mut request = self
217 .get("https://api.bilibili.com/x/web-interface/newlist_rank")
218 .query(&[
219 ("search_type", "video"),
220 ("view_type", "hot_rank"),
221 ("cate_id", cate_id.as_str()),
222 ("pagesize", pagesize.as_str()),
223 ("time_from", time_from),
224 ("time_to", time_to),
225 ]);
226
227 if let Some(o) = order {
228 request = request.query(&[("order", o)]);
229 }
230 if let Some(p) = page {
231 request = request.query(&[("page", p.to_string())]);
232 }
233
234 request.send_bpi("获取分区近期投稿列表 (带排序)").await
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use chrono::{Duration, Local};
242 use tracing::info;
243
244 #[tokio::test]
245 async fn test_video_region_dynamic() {
246 let bpi = BpiClient::new();
247 let rid = 21; let ps = Some(2);
249 let pn = Some(1);
250 let resp = bpi.video_region_dynamic(rid, pn, ps).await;
251
252 info!("{:?}", resp);
253 assert!(resp.is_ok());
254
255 let resp_data = resp.unwrap();
256 info!("code: {}", resp_data.code);
257 if let Some(data) = resp_data.data {
258 info!("total videos: {}", data.page.count);
259 info!("first item: {:?}", data.archives.first());
260 }
261 }
262
263 #[tokio::test]
264 async fn test_video_region_tag_dynamic() {
265 let bpi = BpiClient::new();
266 let rid = 136; let tag_id = 10026108; let ps = Some(2);
269 let pn = Some(1);
270 let resp = bpi.video_region_tag_dynamic(rid, tag_id, pn, ps).await;
271
272 info!("{:?}", resp);
273 assert!(resp.is_ok());
274
275 let resp_data = resp.unwrap();
276 info!("code: {}", resp_data.code);
277 if let Some(data) = resp_data.data {
278 info!("total videos: {}", data.page.count);
279 info!("first item: {:?}", data.archives.first());
280 }
281 }
282
283 #[tokio::test]
284 async fn test_video_region_newlist_rank() {
285 let bpi = BpiClient::new();
286 let cate_id = 231; let pagesize = 2;
288 let today = Local::now().date_naive();
289 let seven_days_ago = today - Duration::days(7);
290
291 let time_from = seven_days_ago.format("%Y%m%d").to_string();
292 let time_to = today.format("%Y%m%d").to_string();
293
294 let resp = bpi
295 .video_region_newlist_rank(
296 cate_id,
297 Some("click"),
298 Some(1),
299 pagesize,
300 &time_from,
301 &time_to,
302 )
303 .await;
304
305 info!("{:?}", resp);
306 assert!(resp.is_ok());
307
308 let resp_data = resp.unwrap();
309 info!("code: {}", resp_data.code);
310 if let Some(data) = resp_data.data {
311 info!("total results: {}", data.num_results);
312 info!("first result: {:?}", data.result.unwrap().first());
313 }
314 }
315}