Skip to main content

bpi_rs/activity/
client.rs

1use crate::activity::info::{ActivityInfoData, ActivityInfoParams};
2use crate::activity::list::{ActivityListData, ActivityListParams};
3use crate::{BilibiliRequest, BpiClient, BpiResult};
4
5const INFO_ENDPOINT: &str = "https://api.bilibili.com/x/activity/subject/info";
6const LIST_ENDPOINT: &str = "https://api.bilibili.com/x/activity/page/list";
7
8/// Activity API client.
9#[derive(Clone, Copy)]
10pub struct ActivityClient<'a> {
11    pub(crate) client: &'a BpiClient,
12}
13
14impl<'a> ActivityClient<'a> {
15    pub(crate) fn new(client: &'a BpiClient) -> Self {
16        Self { client }
17    }
18
19    #[cfg(test)]
20    pub(crate) fn info_endpoint(&self) -> &'static str {
21        INFO_ENDPOINT
22    }
23
24    #[cfg(test)]
25    pub(crate) fn list_endpoint(&self) -> &'static str {
26        LIST_ENDPOINT
27    }
28
29    /// Gets activity subject information.
30    pub async fn info(&self, params: ActivityInfoParams) -> BpiResult<ActivityInfoData> {
31        self.client
32            .get(INFO_ENDPOINT)
33            .query(&params.query_pairs())
34            .send_bpi_payload("activity.info")
35            .await
36    }
37
38    /// Gets the activity list.
39    pub async fn list(&self, params: ActivityListParams) -> BpiResult<ActivityListData> {
40        self.client
41            .get(LIST_ENDPOINT)
42            .query(&params.query_pairs())
43            .send_bpi_payload("activity.list")
44            .await
45    }
46
47    /// Gets the activity list with Bilibili's web defaults.
48    pub async fn list_default(&self) -> BpiResult<ActivityListData> {
49        self.list(ActivityListParams::default()).await
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use std::future::Future;
56
57    use crate::activity::info::{ActivityInfoData, ActivityInfoParams};
58    use crate::activity::list::{ActivityListData, ActivityListParams};
59    use crate::ids::Bvid;
60    use crate::probe::contract::HttpMethod;
61    use crate::probe::endpoint_contract::EndpointContract;
62    use crate::{BpiClient, BpiResult};
63
64    fn assert_info_future<F>(_future: F)
65    where
66        F: Future<Output = BpiResult<ActivityInfoData>>,
67    {
68    }
69
70    fn assert_list_future<F>(_future: F)
71    where
72        F: Future<Output = BpiResult<ActivityListData>>,
73    {
74    }
75
76    fn contract(endpoint: &str) -> BpiResult<EndpointContract> {
77        let bytes = match endpoint {
78            "info" => {
79                include_bytes!("../../tests/contracts/activity/info/contract.json").as_slice()
80            }
81            "list" => {
82                include_bytes!("../../tests/contracts/activity/list/contract.json").as_slice()
83            }
84            _ => unreachable!("unknown activity endpoint fixture"),
85        };
86
87        EndpointContract::from_slice(bytes)
88    }
89
90    #[test]
91    fn activity_client_exposes_promoted_endpoint_urls() -> BpiResult<()> {
92        let client = BpiClient::new()?;
93        let activity = client.activity();
94
95        assert_eq!(
96            activity.info_endpoint(),
97            "https://api.bilibili.com/x/activity/subject/info"
98        );
99        assert_eq!(
100            activity.list_endpoint(),
101            "https://api.bilibili.com/x/activity/page/list"
102        );
103        Ok(())
104    }
105
106    #[test]
107    fn activity_methods_return_payload_futures() -> BpiResult<()> {
108        let client = BpiClient::new()?;
109        let activity = client.activity();
110        let bvid: Bvid = "BV1mKY4e8ELy".parse()?;
111
112        assert_info_future(activity.info(ActivityInfoParams::new(4_017_552)?.with_bvid(bvid)));
113        assert_list_future(activity.list(ActivityListParams::new().page_size(1)?));
114        assert_list_future(activity.list_default());
115        Ok(())
116    }
117
118    #[test]
119    fn activity_contracts_match_module_client_endpoints() -> BpiResult<()> {
120        let client = BpiClient::new()?;
121        let activity = client.activity();
122        let info = contract("info")?;
123        let list = contract("list")?;
124
125        assert_eq!(info.name, "activity.info");
126        assert_eq!(info.request.method, HttpMethod::Get);
127        assert_eq!(info.request.url.as_str(), activity.info_endpoint());
128        assert_eq!(
129            info.request.query.get("sid").map(String::as_str),
130            Some("4017552")
131        );
132        assert_eq!(
133            info.request.query.get("bvid").map(String::as_str),
134            Some("BV1mKY4e8ELy")
135        );
136
137        assert_eq!(list.name, "activity.list");
138        assert_eq!(list.request.method, HttpMethod::Get);
139        assert_eq!(list.request.url.as_str(), activity.list_endpoint());
140        assert_eq!(
141            list.request.query.get("plat").map(String::as_str),
142            Some("1,3")
143        );
144        assert_eq!(list.request.query.get("ps").map(String::as_str), Some("1"));
145        Ok(())
146    }
147}