bpi_rs/search/
search.rs

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