Skip to main content

bpi_rs/search/
search.rs

1//! 搜索
2//!
3//! [查看 API 文档](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/search/search_request.md)
4
5use super::result::{
6    Article,
7    Bangumi,
8    BiliUser,
9    LiveData,
10    LiveRoom,
11    LiveUser,
12    Movie,
13    SearchData,
14    Video,
15};
16use super::search_params::{ CategoryId, Duration, OrderSort, SearchOrder, SearchType, UserType };
17use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
18
19impl BpiClient {
20    /// 搜索专栏
21    ///
22    /// # 文档
23    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/search)
24    ///
25    /// # 参数
26    ///
27    /// | 名称 | 类型 | 说明 |
28    /// | ---- | ---- | ---- |
29    /// | `keyword` | &str | 搜索关键词 |
30    /// | `order` | `Option<SearchOrder>` | 排序方式 |
31    /// | `category_id` | `Option<CategoryId>` | 专栏分区 |
32    /// | `page` | `Option<i32>` | 页码(默认1) |
33    pub async fn search_article(
34        &self,
35        keyword: &str,
36        order: Option<SearchOrder>,
37        category_id: Option<CategoryId>,
38        page: Option<i32>
39    ) -> Result<BpiResponse<SearchData<Vec<Article>>>, BpiError> {
40        let category_id_str = category_id.unwrap_or(CategoryId::All).as_num().to_string();
41        let page_str = page.unwrap_or(1).to_string();
42
43        let params = vec![
44            ("search_type", SearchType::Article.as_str().to_string()),
45            ("keyword", keyword.to_string()),
46            ("order", order.unwrap_or(SearchOrder::TotalRank).as_str().to_string()),
47            ("category_id", category_id_str),
48            ("page", page_str)
49        ];
50
51        let signed_params = self.get_wbi_sign2(params).await?;
52
53        self
54            .get("https://api.bilibili.com/x/web-interface/wbi/search/type")
55            .with_bilibili_headers()
56            .query(&signed_params)
57            .send_bpi("搜索专栏").await
58    }
59
60    /// 搜索番剧
61    ///
62    /// # 文档
63    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/search)
64    ///
65    /// # 参数
66    ///
67    /// | 名称 | 类型 | 说明 |
68    /// | ---- | ---- | ---- |
69    /// | `keyword` | &str | 搜索关键词 |
70    /// | `page` | `Option<i32>` | 页码(默认1) |
71    pub async fn search_bangumi(
72        &self,
73        keyword: &str,
74        page: Option<i32>
75    ) -> Result<BpiResponse<SearchData<Vec<Bangumi>>>, BpiError> {
76        let page_str = page.unwrap_or(1).to_string();
77
78        let params = vec![
79            ("search_type", SearchType::MediaBangumi.as_str().to_string()),
80            ("keyword", keyword.to_string()),
81            ("page", page_str)
82        ];
83
84        let signed_params = self.get_wbi_sign2(params).await?;
85
86        self
87            .get("https://api.bilibili.com/x/web-interface/wbi/search/type")
88            .query(&signed_params)
89            .send_bpi("搜索番剧").await
90    }
91
92    /// 搜索用户
93    ///
94    /// # 文档
95    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/search)
96    ///
97    /// # 参数
98    ///
99    /// | 名称 | 类型 | 说明 |
100    /// | ---- | ---- | ---- |
101    /// | `keyword` | &str | 搜索关键词 |
102    /// | `order_sort` | `Option<OrderSort>` | 排序方向:降序/升序 |
103    /// | `user_type` | `Option<UserType>` | 用户类型筛选 |
104    /// | `page` | `Option<i32>` | 页码(默认1) |
105    pub async fn search_bili_user(
106        &self,
107        keyword: &str,
108        order_sort: Option<OrderSort>,
109        user_type: Option<UserType>,
110        page: Option<i32>
111    ) -> Result<BpiResponse<SearchData<Vec<BiliUser>>>, BpiError> {
112        let page_str = page.unwrap_or(1).to_string();
113
114        let params = vec![
115            ("search_type", SearchType::BiliUser.as_str().to_string()),
116            ("keyword", keyword.to_string()),
117            ("order_sort", order_sort.unwrap_or(OrderSort::Ascending).as_num().to_string()),
118            ("user_type", user_type.unwrap_or(UserType::All).as_num().to_string()),
119            ("page", page_str)
120        ];
121
122        let signed_params = self.get_wbi_sign2(params).await?;
123
124        self
125            .get("https://api.bilibili.com/x/web-interface/wbi/search/type")
126            .query(&signed_params)
127            .send_bpi("搜索用户").await
128    }
129
130    /// 搜索直播间及主播
131    ///
132    /// # 文档
133    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/search)
134    ///
135    /// # 参数
136    ///
137    /// | 名称 | 类型 | 说明 |
138    /// | ---- | ---- | ---- |
139    /// | `keyword` | &str | 搜索关键词 |
140    /// | `page` | `Option<i32>` | 页码(默认1) |
141    pub async fn search_live(
142        &self,
143        keyword: &str,
144        page: Option<i32>
145    ) -> Result<BpiResponse<SearchData<LiveData>>, BpiError> {
146        let page_str = page.unwrap_or(1).to_string();
147
148        let params = vec![
149            ("search_type", SearchType::Live.as_str().to_string()),
150            ("keyword", keyword.to_string()),
151            ("page", page_str)
152        ];
153
154        let signed_params = self.get_wbi_sign2(params).await?;
155
156        self
157            .get("https://api.bilibili.com/x/web-interface/wbi/search/type")
158            .query(&signed_params)
159            .send_bpi("搜索直播间及主播").await
160    }
161
162    /// 搜索直播间
163    ///
164    /// # 文档
165    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/search)
166    ///
167    /// # 参数
168    ///
169    /// | 名称 | 类型 | 说明 |
170    /// | ---- | ---- | ---- |
171    /// | `keyword` | &str | 搜索关键词 |
172    /// | `order` | `Option<SearchOrder>` | 排序方式(默认 online) |
173    /// | `page` | `Option<i32>` | 页码(默认1) |
174    pub async fn search_live_room(
175        &self,
176        keyword: &str,
177        order: Option<SearchOrder>,
178        page: Option<i32>
179    ) -> Result<BpiResponse<SearchData<Vec<LiveRoom>>>, BpiError> {
180        let page_str = page.unwrap_or(1).to_string();
181
182        let params = vec![
183            ("search_type", SearchType::LiveRoom.as_str().to_string()),
184            ("keyword", keyword.to_string()),
185            ("order", order.unwrap_or(SearchOrder::Online).as_str().to_string()),
186            ("page", page_str)
187        ];
188
189        let signed_params = self.get_wbi_sign2(params).await?;
190
191        self
192            .get("https://api.bilibili.com/x/web-interface/wbi/search/type")
193            .query(&signed_params)
194            .send_bpi("搜索直播间").await
195    }
196
197    /// 搜索主播
198    ///
199    /// # 文档
200    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/search)
201    ///
202    /// # 参数
203    ///
204    /// | 名称 | 类型 | 说明 |
205    /// | ---- | ---- | ---- |
206    /// | `keyword` | &str | 搜索关键词 |
207    /// | `order_sort` | `Option<OrderSort>` | 排序方向 |
208    /// | `user_type` | `Option<UserType>` | 主播类型筛选 |
209    /// | `page` | `Option<i32>` | 页码(默认1) |
210    pub async fn search_live_user(
211        &self,
212        keyword: &str,
213        order_sort: Option<OrderSort>,
214        user_type: Option<UserType>,
215        page: Option<i32>
216    ) -> Result<BpiResponse<SearchData<Vec<LiveUser>>>, BpiError> {
217        let page_str = page.unwrap_or(1).to_string();
218
219        let params = vec![
220            ("search_type", SearchType::LiveUser.as_str().to_string()),
221            ("keyword", keyword.to_string()),
222            ("order_sort", order_sort.unwrap_or(OrderSort::Ascending).as_num().to_string()),
223            ("user_type", user_type.unwrap_or(UserType::All).as_num().to_string()),
224            ("page", page_str)
225        ];
226
227        let signed_params = self.get_wbi_sign2(params).await?;
228
229        self
230            .get("https://api.bilibili.com/x/web-interface/wbi/search/type")
231            .query(&signed_params)
232            .send_bpi("搜索主播").await
233    }
234
235    /// 搜索影视
236    ///
237    /// # 文档
238    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/search)
239    ///
240    /// # 参数
241    ///
242    /// | 名称 | 类型 | 说明 |
243    /// | ---- | ---- | ---- |
244    /// | `keyword` | &str | 搜索关键词 |
245    /// | `page` | `Option<i32>` | 页码(默认1) |
246    pub async fn search_movie(
247        &self,
248        keyword: &str,
249        page: Option<i32>
250    ) -> Result<BpiResponse<SearchData<Vec<Movie>>>, BpiError> {
251        let page_str = page.unwrap_or(1).to_string();
252
253        let params = vec![
254            ("search_type", SearchType::MediaFt.as_str().to_string()),
255            ("keyword", keyword.to_string()),
256            ("page", page_str)
257        ];
258
259        let signed_params = self.get_wbi_sign2(params).await?;
260
261        self
262            .get("https://api.bilibili.com/x/web-interface/wbi/search/type")
263            .query(&signed_params)
264            .send_bpi("搜索影视").await
265    }
266
267    /// 搜索视频
268    ///
269    /// # 文档
270    /// [查看API文档](https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/search)
271    ///
272    /// # 参数
273    ///
274    /// | 名称 | 类型 | 说明 |
275    /// | ---- | ---- | ---- |
276    /// | `keyword` | &str | 搜索关键词 |
277    /// | `order` | `Option<SearchOrder>` | 排序方式 |
278    /// | `duration` | `Option<Duration>` | 时长筛选 |
279    /// | `tids` | `Option<u32>` | 分区 ID |
280    /// | `page` | `Option<i32>` | 页码(默认1) |
281    pub async fn search_video(
282        &self,
283        keyword: &str,
284        order: Option<SearchOrder>,
285        duration: Option<Duration>,
286        tids: Option<u32>,
287        page: Option<i32>
288    ) -> Result<BpiResponse<SearchData<Vec<Video>>>, BpiError> {
289        let page_str = page.unwrap_or(1).to_string();
290
291        let params = vec![
292            ("search_type", SearchType::Video.as_str().to_string()),
293            ("keyword", keyword.to_string()),
294            ("order", order.unwrap_or(SearchOrder::TotalRank).as_str().to_string()),
295            ("duration", duration.unwrap_or(Duration::All).as_num().to_string()),
296            ("tids", tids.unwrap_or(0).to_string()),
297            ("page", page_str)
298        ];
299
300        let signed_params = self.get_wbi_sign2(params).await?;
301
302        self
303            .get("https://api.bilibili.com/x/web-interface/wbi/search/type")
304            .query(&signed_params)
305            .send_bpi("搜索视频").await
306    }
307}
308#[cfg(test)]
309mod tests {
310    use super::*;
311    use crate::search::search_params::{ CategoryId, Duration, OrderSort, SearchOrder, UserType };
312    use tracing::info;
313
314    #[tokio::test]
315    async fn test_search_article() {
316        let bpi = BpiClient::new();
317        let resp = bpi.search_article(
318            "Rust",
319            Some(SearchOrder::PubDate),
320            Some(CategoryId::Technology),
321            None
322        ).await;
323        assert!(resp.is_ok());
324        if let Ok(r) = resp {
325            info!("搜索文章返回: {:?}", r);
326            if let Some(data) = r.data {
327                if let Some(results) = data.result {
328                    assert!(!results.is_empty());
329                    for article in results {
330                        info!("文章标题: {}", article.title);
331                    }
332                } else {
333                    info!("未找到任何文章结果。");
334                }
335            }
336        }
337    }
338
339    #[tokio::test]
340    async fn test_search_bangumi() {
341        let bpi = BpiClient::new();
342        let resp = bpi.search_bangumi("天气之子", None).await;
343        assert!(resp.is_ok());
344        if let Ok(r) = resp {
345            info!("搜索番剧返回: {:?}", r);
346            if let Some(data) = r.data {
347                if let Some(results) = data.result {
348                    assert!(!results.is_empty());
349                    for bangumi in results {
350                        info!("番剧标题: {}", bangumi.title);
351                    }
352                } else {
353                    info!("未找到任何番剧结果。");
354                }
355            }
356        }
357    }
358
359    #[tokio::test]
360    async fn test_search_bili_user() {
361        let bpi = BpiClient::new();
362        let resp = bpi.search_bili_user(
363            "老番茄",
364            Some(OrderSort::Descending),
365            Some(UserType::All),
366            None
367        ).await;
368        assert!(resp.is_ok());
369        if let Ok(r) = resp {
370            info!("搜索用户返回: {:?}", r);
371            if let Some(data) = r.data {
372                if let Some(results) = data.result {
373                    assert!(!results.is_empty());
374                    for user in results {
375                        info!("用户昵称: {}", user.uname);
376                    }
377                } else {
378                    info!("未找到任何用户结果。");
379                }
380            }
381        }
382    }
383
384    #[tokio::test]
385    async fn test_search_live_room() {
386        let bpi = BpiClient::new();
387        let resp = bpi.search_live_room("游戏", None, None).await;
388        assert!(resp.is_ok());
389        if let Ok(r) = resp {
390            info!("搜索直播间返回: {:?}", r);
391            if let Some(data) = r.data {
392                if let Some(results) = data.result {
393                    assert!(!results.is_empty());
394                    for room in results {
395                        info!("直播间标题: {}", room.title);
396                    }
397                } else {
398                    info!("未找到任何直播间结果。");
399                }
400            }
401        }
402    }
403
404    #[tokio::test]
405    async fn test_search_live_user() {
406        let bpi = BpiClient::new();
407        let resp = bpi.search_live_user(
408            "散人",
409            Some(OrderSort::Descending),
410            Some(UserType::All),
411            None
412        ).await;
413        assert!(resp.is_ok());
414        if let Ok(r) = resp {
415            info!("搜索主播返回: {:?}", r);
416            if let Some(data) = r.data {
417                if let Some(results) = data.result {
418                    assert!(!results.is_empty());
419                    for user in results {
420                        info!("主播昵称: {}", user.uname);
421                    }
422                } else {
423                    info!("未找到任何主播结果。");
424                }
425            }
426        }
427    }
428
429    #[tokio::test]
430    async fn test_search_movie() {
431        let bpi = BpiClient::new();
432        let resp = bpi.search_movie("哈利波特", None).await;
433        assert!(resp.is_ok());
434        if let Ok(r) = resp {
435            info!("搜索影视返回: {:?}", r);
436            if let Some(data) = r.data {
437                if let Some(results) = data.result {
438                    assert!(!results.is_empty());
439                    for movie in results {
440                        info!("影视标题: {}", movie.title);
441                    }
442                } else {
443                    info!("未找到任何影视结果。");
444                }
445            }
446        }
447    }
448
449    #[tokio::test]
450    async fn test_search_video() {
451        let bpi = BpiClient::new();
452        let resp = bpi.search_video(
453            "Rust 教程",
454            Some(SearchOrder::Online),
455            Some(Duration::From10To30),
456            Some(171),
457            None
458        ).await;
459        assert!(resp.is_ok());
460        if let Ok(r) = resp {
461            info!("搜索视频返回: {:?}", r);
462            if let Some(data) = r.data {
463                if let Some(results) = data.result {
464                    assert!(!results.is_empty());
465                    for video in results {
466                        info!("视频标题: {}", video.title);
467                    }
468                } else {
469                    info!("未找到任何视频结果。");
470                }
471            }
472        }
473    }
474}