Skip to main content

bpi_rs/web_widget/
client.rs

1use crate::web_widget::{
2    HeaderData, OnlineData, RegionBannerData, WebWidgetHeaderPageParams,
3    WebWidgetRegionBannerParams,
4};
5use crate::{BilibiliRequest, BpiClient, BpiResult};
6
7const REGION_BANNER_ENDPOINT: &str = "https://api.bilibili.com/x/web-show/region/banner";
8const HEADER_PAGE_ENDPOINT: &str = "https://api.bilibili.com/x/web-show/page/header";
9const ONLINE_ENDPOINT: &str = "https://api.bilibili.com/x/web-interface/online";
10
11/// Web widget API client.
12#[derive(Clone, Copy)]
13pub struct WebWidgetClient<'a> {
14    pub(crate) client: &'a BpiClient,
15}
16
17impl<'a> WebWidgetClient<'a> {
18    pub(crate) fn new(client: &'a BpiClient) -> Self {
19        Self { client }
20    }
21
22    #[cfg(test)]
23    pub(crate) fn region_banner_endpoint(&self) -> &'static str {
24        REGION_BANNER_ENDPOINT
25    }
26
27    #[cfg(test)]
28    pub(crate) fn header_page_endpoint(&self) -> &'static str {
29        HEADER_PAGE_ENDPOINT
30    }
31
32    #[cfg(test)]
33    pub(crate) fn online_endpoint(&self) -> &'static str {
34        ONLINE_ENDPOINT
35    }
36
37    /// Gets the region carousel banner data.
38    pub async fn region_banner(
39        &self,
40        params: WebWidgetRegionBannerParams,
41    ) -> BpiResult<RegionBannerData> {
42        self.client
43            .get(REGION_BANNER_ENDPOINT)
44            .query(&params.query_pairs())
45            .send_bpi_payload("web_widget.region_banner")
46            .await
47    }
48
49    /// Gets the home page header image data.
50    pub async fn header_page(&self, params: WebWidgetHeaderPageParams) -> BpiResult<HeaderData> {
51        let mut header = self
52            .client
53            .get(HEADER_PAGE_ENDPOINT)
54            .query(&params.query_pairs())
55            .send_bpi_payload::<HeaderData>("web_widget.header_page")
56            .await?;
57        header.parse_split_layer()?;
58        Ok(header)
59    }
60
61    /// Gets the current online upload counts by region.
62    pub async fn online(&self) -> BpiResult<OnlineData> {
63        self.client
64            .get(ONLINE_ENDPOINT)
65            .send_bpi_payload("web_widget.online")
66            .await
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use std::future::Future;
73
74    use crate::probe::contract::HttpMethod;
75    use crate::probe::endpoint_contract::EndpointContract;
76    use crate::video::video_zone_v2::{Douga, VideoPartitionV2};
77    use crate::{BpiClient, BpiResult};
78
79    use super::*;
80
81    fn assert_region_banner_future<F>(_future: F)
82    where
83        F: Future<Output = BpiResult<RegionBannerData>>,
84    {
85    }
86
87    fn assert_header_page_future<F>(_future: F)
88    where
89        F: Future<Output = BpiResult<HeaderData>>,
90    {
91    }
92
93    fn assert_online_future<F>(_future: F)
94    where
95        F: Future<Output = BpiResult<OnlineData>>,
96    {
97    }
98
99    fn contract(endpoint: &str) -> BpiResult<EndpointContract> {
100        let bytes = match endpoint {
101            "region-banner" => {
102                include_bytes!("../../tests/contracts/web_widget/region-banner/contract.json")
103                    .as_slice()
104            }
105            "header-page" => {
106                include_bytes!("../../tests/contracts/web_widget/header-page/contract.json")
107                    .as_slice()
108            }
109            "online" => {
110                include_bytes!("../../tests/contracts/web_widget/online/contract.json").as_slice()
111            }
112            _ => unreachable!("unknown web_widget endpoint fixture"),
113        };
114
115        EndpointContract::from_slice(bytes)
116    }
117
118    #[test]
119    fn web_widget_client_exposes_promoted_endpoint_urls() -> BpiResult<()> {
120        let client = BpiClient::new()?;
121        let web_widget = client.web_widget();
122
123        assert_eq!(
124            web_widget.region_banner_endpoint(),
125            "https://api.bilibili.com/x/web-show/region/banner"
126        );
127        assert_eq!(
128            web_widget.header_page_endpoint(),
129            "https://api.bilibili.com/x/web-show/page/header"
130        );
131        assert_eq!(
132            web_widget.online_endpoint(),
133            "https://api.bilibili.com/x/web-interface/online"
134        );
135        Ok(())
136    }
137
138    #[test]
139    fn web_widget_methods_return_payload_futures() -> BpiResult<()> {
140        let client = BpiClient::new()?;
141        let web_widget = client.web_widget();
142
143        assert_region_banner_future(web_widget.region_banner(WebWidgetRegionBannerParams::new(
144            VideoPartitionV2::Douga(Douga::Douga),
145        )));
146        assert_header_page_future(web_widget.header_page(WebWidgetHeaderPageParams::new()));
147        assert_online_future(web_widget.online());
148        Ok(())
149    }
150
151    #[test]
152    fn web_widget_contracts_match_module_client_endpoints() -> BpiResult<()> {
153        let client = BpiClient::new()?;
154        let web_widget = client.web_widget();
155        let region_banner = contract("region-banner")?;
156        let header_page = contract("header-page")?;
157        let online = contract("online")?;
158
159        assert_eq!(region_banner.name, "web_widget.region_banner");
160        assert_eq!(region_banner.request.method, HttpMethod::Get);
161        assert_eq!(
162            region_banner.request.url.as_str(),
163            web_widget.region_banner_endpoint()
164        );
165        assert_eq!(
166            region_banner
167                .request
168                .query
169                .get("region_id")
170                .map(String::as_str),
171            Some("1005")
172        );
173
174        assert_eq!(header_page.name, "web_widget.header_page");
175        assert_eq!(header_page.request.method, HttpMethod::Get);
176        assert_eq!(
177            header_page.request.url.as_str(),
178            web_widget.header_page_endpoint()
179        );
180        assert_eq!(
181            header_page
182                .request
183                .query
184                .get("resource_id")
185                .map(String::as_str),
186            Some("142")
187        );
188
189        assert_eq!(online.name, "web_widget.online");
190        assert_eq!(online.request.method, HttpMethod::Get);
191        assert_eq!(online.request.url.as_str(), web_widget.online_endpoint());
192        assert!(online.request.query.is_empty());
193        Ok(())
194    }
195}