Skip to main content

bpi_rs/message/
client.rs

1use crate::message::msg::{ReplyFeedData, UnreadCountData};
2use crate::message::params::{
3    MessageReplyFeedParams, MessageSingleUnreadParams, MessageUnreadCountParams,
4};
5use crate::message::private_msg::SingleUnreadData;
6use crate::{BilibiliRequest, BpiClient, BpiResult};
7
8const UNREAD_COUNT_ENDPOINT: &str = "https://api.vc.bilibili.com/x/im/web/msgfeed/unread";
9const REPLY_FEED_ENDPOINT: &str = "https://api.bilibili.com/x/msgfeed/reply";
10const SINGLE_UNREAD_ENDPOINT: &str =
11    "https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread";
12
13/// Message API client.
14#[derive(Clone, Copy)]
15pub struct MessageClient<'a> {
16    pub(crate) client: &'a BpiClient,
17}
18
19impl<'a> MessageClient<'a> {
20    pub(crate) fn new(client: &'a BpiClient) -> Self {
21        Self { client }
22    }
23
24    #[cfg(test)]
25    pub(crate) fn unread_count_endpoint(&self) -> &'static str {
26        UNREAD_COUNT_ENDPOINT
27    }
28
29    #[cfg(test)]
30    pub(crate) fn reply_feed_endpoint(&self) -> &'static str {
31        REPLY_FEED_ENDPOINT
32    }
33
34    #[cfg(test)]
35    pub(crate) fn single_unread_endpoint(&self) -> &'static str {
36        SINGLE_UNREAD_ENDPOINT
37    }
38
39    /// Gets unread message counters.
40    pub async fn unread_count(
41        &self,
42        params: MessageUnreadCountParams,
43    ) -> BpiResult<UnreadCountData> {
44        self.client
45            .get(UNREAD_COUNT_ENDPOINT)
46            .query(&params.query_pairs())
47            .send_bpi_payload("message.unread_count")
48            .await
49    }
50
51    /// Gets reply notification feed items.
52    pub async fn reply_feed(&self, params: MessageReplyFeedParams) -> BpiResult<ReplyFeedData> {
53        self.client
54            .get(REPLY_FEED_ENDPOINT)
55            .query(&params.query_pairs())
56            .send_bpi_payload("message.reply_feed")
57            .await
58    }
59
60    /// Gets unread private-message counters.
61    pub async fn single_unread(
62        &self,
63        params: MessageSingleUnreadParams,
64    ) -> BpiResult<SingleUnreadData> {
65        self.client
66            .get(SINGLE_UNREAD_ENDPOINT)
67            .query(&params.query_pairs())
68            .send_bpi_payload("message.single_unread")
69            .await
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use std::future::Future;
76
77    use crate::message::msg::{ReplyFeedData, UnreadCountData};
78    use crate::message::params::{
79        MessageReplyFeedParams, MessageSingleUnreadParams, MessageUnreadCountParams,
80    };
81    use crate::message::private_msg::SingleUnreadData;
82    use crate::probe::contract::HttpMethod;
83    use crate::probe::endpoint_contract::EndpointContract;
84    use crate::{BpiClient, BpiResult};
85
86    fn assert_unread_count_future<F>(_future: F)
87    where
88        F: Future<Output = BpiResult<UnreadCountData>>,
89    {
90    }
91
92    fn assert_reply_feed_future<F>(_future: F)
93    where
94        F: Future<Output = BpiResult<ReplyFeedData>>,
95    {
96    }
97
98    fn assert_single_unread_future<F>(_future: F)
99    where
100        F: Future<Output = BpiResult<SingleUnreadData>>,
101    {
102    }
103
104    fn contract(endpoint: &str) -> BpiResult<EndpointContract> {
105        let bytes = match endpoint {
106            "unread-count" => {
107                include_bytes!("../../tests/contracts/message/read/unread-count/contract.json")
108                    .as_slice()
109            }
110            "reply-feed" => {
111                include_bytes!("../../tests/contracts/message/read/reply-feed/contract.json")
112                    .as_slice()
113            }
114            "single-unread" => {
115                include_bytes!("../../tests/contracts/message/read/single-unread/contract.json")
116                    .as_slice()
117            }
118            _ => unreachable!("unknown message endpoint fixture"),
119        };
120
121        EndpointContract::from_slice(bytes)
122    }
123
124    #[test]
125    fn message_client_exposes_promoted_endpoint_urls() -> BpiResult<()> {
126        let client = BpiClient::new()?;
127        let message = client.message();
128
129        assert_eq!(
130            message.unread_count_endpoint(),
131            "https://api.vc.bilibili.com/x/im/web/msgfeed/unread"
132        );
133        assert_eq!(
134            message.reply_feed_endpoint(),
135            "https://api.bilibili.com/x/msgfeed/reply"
136        );
137        assert_eq!(
138            message.single_unread_endpoint(),
139            "https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread"
140        );
141        Ok(())
142    }
143
144    #[test]
145    fn message_methods_return_payload_futures() -> BpiResult<()> {
146        let client = BpiClient::new()?;
147        let message = client.message();
148
149        assert_unread_count_future(message.unread_count(MessageUnreadCountParams::new()));
150        assert_reply_feed_future(message.reply_feed(MessageReplyFeedParams::new()));
151        assert_single_unread_future(message.single_unread(MessageSingleUnreadParams::new()));
152        Ok(())
153    }
154
155    #[test]
156    fn message_contracts_match_module_client_endpoints() -> BpiResult<()> {
157        let client = BpiClient::new()?;
158        let message = client.message();
159        let unread_count = contract("unread-count")?;
160        let reply_feed = contract("reply-feed")?;
161        let single_unread = contract("single-unread")?;
162
163        assert_eq!(unread_count.name, "message.unread_count");
164        assert_eq!(unread_count.request.method, HttpMethod::Get);
165        assert_eq!(
166            unread_count.request.url.as_str(),
167            message.unread_count_endpoint()
168        );
169        assert_eq!(
170            unread_count
171                .request
172                .query
173                .get("mobi_app")
174                .map(String::as_str),
175            Some("web")
176        );
177
178        assert_eq!(reply_feed.name, "message.reply_feed");
179        assert_eq!(reply_feed.request.method, HttpMethod::Get);
180        assert_eq!(
181            reply_feed.request.url.as_str(),
182            message.reply_feed_endpoint()
183        );
184        assert_eq!(
185            reply_feed.request.query.get("platform").map(String::as_str),
186            Some("web")
187        );
188
189        assert_eq!(single_unread.name, "message.single_unread");
190        assert_eq!(single_unread.request.method, HttpMethod::Get);
191        assert_eq!(
192            single_unread.request.url.as_str(),
193            message.single_unread_endpoint()
194        );
195        assert_eq!(
196            single_unread
197                .request
198                .query
199                .get("unread_type")
200                .map(String::as_str),
201            Some("0")
202        );
203        Ok(())
204    }
205}