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#[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 pub async fn unread_count(
41 &self,
42 params: MessageUnreadCountParams,
43 ) -> BpiResult<UnreadCountData> {
44 self.client
45 .get(UNREAD_COUNT_ENDPOINT)
46 .query(¶ms.query_pairs())
47 .send_bpi_payload("message.unread_count")
48 .await
49 }
50
51 pub async fn reply_feed(&self, params: MessageReplyFeedParams) -> BpiResult<ReplyFeedData> {
53 self.client
54 .get(REPLY_FEED_ENDPOINT)
55 .query(¶ms.query_pairs())
56 .send_bpi_payload("message.reply_feed")
57 .await
58 }
59
60 pub async fn single_unread(
62 &self,
63 params: MessageSingleUnreadParams,
64 ) -> BpiResult<SingleUnreadData> {
65 self.client
66 .get(SINGLE_UNREAD_ENDPOINT)
67 .query(¶ms.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}