Skip to main content

bpi_rs/note/
client.rs

1use crate::note::info::{NoteIsForbidData, PrivateNoteInfoData, PublicNoteInfoData};
2use crate::note::list::{
3    NoteListArchiveData, PrivateNoteListData, PublicNoteListArchiveData, PublicNoteListUserData,
4};
5use crate::note::{
6    NoteArchiveListParams, NoteIsForbidParams, NotePrivateInfoParams, NotePublicArchiveListParams,
7    NotePublicInfoParams, NoteUserPrivateListParams, NoteUserPublicListParams,
8};
9use crate::{BilibiliRequest, BpiClient, BpiResult};
10
11const IS_FORBID_ENDPOINT: &str = "https://api.bilibili.com/x/note/is_forbid";
12const PRIVATE_INFO_ENDPOINT: &str = "https://api.bilibili.com/x/note/info";
13const PUBLIC_INFO_ENDPOINT: &str = "https://api.bilibili.com/x/note/publish/info";
14const ARCHIVE_LIST_ENDPOINT: &str = "https://api.bilibili.com/x/note/list/archive";
15const USER_PRIVATE_LIST_ENDPOINT: &str = "https://api.bilibili.com/x/note/list";
16const PUBLIC_ARCHIVE_LIST_ENDPOINT: &str = "https://api.bilibili.com/x/note/publish/list/archive";
17const USER_PUBLIC_LIST_ENDPOINT: &str = "https://api.bilibili.com/x/note/publish/list/user";
18
19/// Note API client.
20#[derive(Clone, Copy)]
21pub struct NoteClient<'a> {
22    pub(crate) client: &'a BpiClient,
23}
24
25impl<'a> NoteClient<'a> {
26    pub(crate) fn new(client: &'a BpiClient) -> Self {
27        Self { client }
28    }
29
30    #[cfg(test)]
31    pub(crate) fn is_forbid_endpoint(&self) -> &'static str {
32        IS_FORBID_ENDPOINT
33    }
34
35    #[cfg(test)]
36    pub(crate) fn private_info_endpoint(&self) -> &'static str {
37        PRIVATE_INFO_ENDPOINT
38    }
39
40    #[cfg(test)]
41    pub(crate) fn public_info_endpoint(&self) -> &'static str {
42        PUBLIC_INFO_ENDPOINT
43    }
44
45    #[cfg(test)]
46    pub(crate) fn archive_list_endpoint(&self) -> &'static str {
47        ARCHIVE_LIST_ENDPOINT
48    }
49
50    #[cfg(test)]
51    pub(crate) fn user_private_list_endpoint(&self) -> &'static str {
52        USER_PRIVATE_LIST_ENDPOINT
53    }
54
55    #[cfg(test)]
56    pub(crate) fn public_archive_list_endpoint(&self) -> &'static str {
57        PUBLIC_ARCHIVE_LIST_ENDPOINT
58    }
59
60    #[cfg(test)]
61    pub(crate) fn user_public_list_endpoint(&self) -> &'static str {
62        USER_PUBLIC_LIST_ENDPOINT
63    }
64
65    /// Checks whether notes are forbidden for an archive.
66    pub async fn is_forbid(&self, params: NoteIsForbidParams) -> BpiResult<NoteIsForbidData> {
67        self.client
68            .get(IS_FORBID_ENDPOINT)
69            .query(&params.query_pairs())
70            .send_bpi_payload("note.is_forbid")
71            .await
72    }
73
74    /// Gets private note content.
75    pub async fn private_info(
76        &self,
77        params: NotePrivateInfoParams,
78    ) -> BpiResult<PrivateNoteInfoData> {
79        self.client
80            .get(PRIVATE_INFO_ENDPOINT)
81            .query(&params.query_pairs())
82            .send_bpi_payload("note.private_info")
83            .await
84    }
85
86    /// Gets public note content.
87    pub async fn public_info(&self, params: NotePublicInfoParams) -> BpiResult<PublicNoteInfoData> {
88        self.client
89            .get(PUBLIC_INFO_ENDPOINT)
90            .query(&params.query_pairs())
91            .send_bpi_payload("note.public_info")
92            .await
93    }
94
95    /// Gets private note IDs for an archive.
96    pub async fn archive_list(
97        &self,
98        params: NoteArchiveListParams,
99    ) -> BpiResult<NoteListArchiveData> {
100        self.client
101            .get(ARCHIVE_LIST_ENDPOINT)
102            .query(&params.query_pairs())
103            .send_bpi_payload("note.archive_list")
104            .await
105    }
106
107    /// Gets private notes owned by the current user.
108    pub async fn user_private_list(
109        &self,
110        params: NoteUserPrivateListParams,
111    ) -> BpiResult<PrivateNoteListData> {
112        self.client
113            .get(USER_PRIVATE_LIST_ENDPOINT)
114            .query(&params.query_pairs())
115            .send_bpi_payload("note.user_private_list")
116            .await
117    }
118
119    /// Gets public notes for an archive.
120    pub async fn public_archive_list(
121        &self,
122        params: NotePublicArchiveListParams,
123    ) -> BpiResult<PublicNoteListArchiveData> {
124        self.client
125            .get(PUBLIC_ARCHIVE_LIST_ENDPOINT)
126            .query(&params.query_pairs())
127            .send_bpi_payload("note.public_archive_list")
128            .await
129    }
130
131    /// Gets public notes authored by a user.
132    pub async fn user_public_list(
133        &self,
134        params: NoteUserPublicListParams,
135    ) -> BpiResult<PublicNoteListUserData> {
136        self.client
137            .get(USER_PUBLIC_LIST_ENDPOINT)
138            .query(&params.query_pairs())
139            .send_bpi_payload("note.user_public_list")
140            .await
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use std::future::Future;
147
148    use crate::ids::{Aid, Cvid, NoteId};
149    use crate::note::info::{NoteIsForbidData, PrivateNoteInfoData, PublicNoteInfoData};
150    use crate::note::list::{
151        NoteListArchiveData, PrivateNoteListData, PublicNoteListArchiveData, PublicNoteListUserData,
152    };
153    use crate::note::{
154        NoteArchiveListParams, NoteIsForbidParams, NotePrivateInfoParams,
155        NotePublicArchiveListParams, NotePublicInfoParams, NoteUserPrivateListParams,
156        NoteUserPublicListParams,
157    };
158    use crate::probe::contract::HttpMethod;
159    use crate::probe::endpoint_contract::EndpointContract;
160    use crate::{BpiClient, BpiResult};
161
162    const TEST_AID: u64 = 338_677_252;
163    const TEST_PRIVATE_AID: u64 = 676_931_260;
164    const TEST_NOTE_ID: u64 = 83_577_722_856_540_160;
165    const TEST_CVID: u64 = 15_160_286;
166
167    fn aid() -> BpiResult<Aid> {
168        Aid::new(TEST_AID)
169    }
170
171    fn private_aid() -> BpiResult<Aid> {
172        Aid::new(TEST_PRIVATE_AID)
173    }
174
175    fn note_id() -> BpiResult<NoteId> {
176        NoteId::new(TEST_NOTE_ID)
177    }
178
179    fn cvid() -> BpiResult<Cvid> {
180        Cvid::new(TEST_CVID)
181    }
182
183    fn assert_is_forbid_future<F>(_future: F)
184    where
185        F: Future<Output = BpiResult<NoteIsForbidData>>,
186    {
187    }
188
189    fn assert_private_info_future<F>(_future: F)
190    where
191        F: Future<Output = BpiResult<PrivateNoteInfoData>>,
192    {
193    }
194
195    fn assert_public_info_future<F>(_future: F)
196    where
197        F: Future<Output = BpiResult<PublicNoteInfoData>>,
198    {
199    }
200
201    fn assert_archive_list_future<F>(_future: F)
202    where
203        F: Future<Output = BpiResult<NoteListArchiveData>>,
204    {
205    }
206
207    fn assert_user_private_list_future<F>(_future: F)
208    where
209        F: Future<Output = BpiResult<PrivateNoteListData>>,
210    {
211    }
212
213    fn assert_public_archive_list_future<F>(_future: F)
214    where
215        F: Future<Output = BpiResult<PublicNoteListArchiveData>>,
216    {
217    }
218
219    fn assert_user_public_list_future<F>(_future: F)
220    where
221        F: Future<Output = BpiResult<PublicNoteListUserData>>,
222    {
223    }
224
225    fn contract(endpoint: &str) -> BpiResult<EndpointContract> {
226        let bytes = match endpoint {
227            "is-forbid" => {
228                include_bytes!("../../tests/contracts/note/read/is-forbid/contract.json").as_slice()
229            }
230            "private-info" => {
231                include_bytes!("../../tests/contracts/note/read/private-info/contract.json")
232                    .as_slice()
233            }
234            "public-info" => {
235                include_bytes!("../../tests/contracts/note/read/public-info/contract.json")
236                    .as_slice()
237            }
238            "archive-list" => {
239                include_bytes!("../../tests/contracts/note/read/archive-list/contract.json")
240                    .as_slice()
241            }
242            "user-private-list" => {
243                include_bytes!("../../tests/contracts/note/read/user-private-list/contract.json")
244                    .as_slice()
245            }
246            "public-archive-list" => {
247                include_bytes!("../../tests/contracts/note/read/public-archive-list/contract.json")
248                    .as_slice()
249            }
250            "user-public-list" => {
251                include_bytes!("../../tests/contracts/note/read/user-public-list/contract.json")
252                    .as_slice()
253            }
254            _ => unreachable!("unknown note read contract"),
255        };
256        EndpointContract::from_slice(bytes)
257    }
258
259    #[test]
260    fn note_client_exposes_promoted_endpoint_urls() -> BpiResult<()> {
261        let client = BpiClient::new()?;
262        let note = client.note();
263
264        assert_eq!(
265            note.is_forbid_endpoint(),
266            "https://api.bilibili.com/x/note/is_forbid"
267        );
268        assert_eq!(
269            note.private_info_endpoint(),
270            "https://api.bilibili.com/x/note/info"
271        );
272        assert_eq!(
273            note.public_info_endpoint(),
274            "https://api.bilibili.com/x/note/publish/info"
275        );
276        assert_eq!(
277            note.archive_list_endpoint(),
278            "https://api.bilibili.com/x/note/list/archive"
279        );
280        assert_eq!(
281            note.user_private_list_endpoint(),
282            "https://api.bilibili.com/x/note/list"
283        );
284        assert_eq!(
285            note.public_archive_list_endpoint(),
286            "https://api.bilibili.com/x/note/publish/list/archive"
287        );
288        assert_eq!(
289            note.user_public_list_endpoint(),
290            "https://api.bilibili.com/x/note/publish/list/user"
291        );
292        Ok(())
293    }
294
295    #[test]
296    fn note_methods_return_payload_futures() -> BpiResult<()> {
297        let client = BpiClient::new()?;
298        let note = client.note();
299
300        assert_is_forbid_future(note.is_forbid(NoteIsForbidParams::new(aid()?)));
301        assert_private_info_future(
302            note.private_info(NotePrivateInfoParams::new(private_aid()?, note_id()?)),
303        );
304        assert_public_info_future(note.public_info(NotePublicInfoParams::new(cvid()?)));
305        assert_archive_list_future(note.archive_list(NoteArchiveListParams::new(private_aid()?)));
306        assert_user_private_list_future(note.user_private_list(NoteUserPrivateListParams::new()));
307        assert_public_archive_list_future(
308            note.public_archive_list(NotePublicArchiveListParams::new(aid()?)),
309        );
310        assert_user_public_list_future(note.user_public_list(NoteUserPublicListParams::new()));
311        Ok(())
312    }
313
314    #[test]
315    fn note_contracts_match_module_client_endpoints() -> BpiResult<()> {
316        let client = BpiClient::new()?;
317        let note = client.note();
318        let is_forbid = contract("is-forbid")?;
319        let private_info = contract("private-info")?;
320        let public_info = contract("public-info")?;
321        let archive_list = contract("archive-list")?;
322        let user_private_list = contract("user-private-list")?;
323        let public_archive_list = contract("public-archive-list")?;
324        let user_public_list = contract("user-public-list")?;
325
326        assert_eq!(is_forbid.name, "note.is_forbid");
327        assert_eq!(is_forbid.request.method, HttpMethod::Get);
328        assert_eq!(is_forbid.request.url.as_str(), note.is_forbid_endpoint());
329        assert_eq!(
330            is_forbid.request.query.get("aid").map(String::as_str),
331            Some("338677252")
332        );
333
334        assert_eq!(private_info.name, "note.private_info");
335        assert_eq!(private_info.request.method, HttpMethod::Get);
336        assert_eq!(
337            private_info.request.url.as_str(),
338            note.private_info_endpoint()
339        );
340        assert_eq!(
341            private_info
342                .request
343                .query
344                .get("note_id")
345                .map(String::as_str),
346            Some("83577722856540160")
347        );
348
349        assert_eq!(public_info.name, "note.public_info");
350        assert_eq!(public_info.request.method, HttpMethod::Get);
351        assert_eq!(
352            public_info.request.url.as_str(),
353            note.public_info_endpoint()
354        );
355        assert_eq!(
356            public_info.request.query.get("cvid").map(String::as_str),
357            Some("15160286")
358        );
359
360        assert_eq!(archive_list.name, "note.archive_list");
361        assert_eq!(archive_list.request.method, HttpMethod::Get);
362        assert_eq!(
363            archive_list.request.url.as_str(),
364            note.archive_list_endpoint()
365        );
366        assert_eq!(
367            archive_list.request.query.get("oid").map(String::as_str),
368            Some("676931260")
369        );
370
371        assert_eq!(user_private_list.name, "note.user_private_list");
372        assert_eq!(user_private_list.request.method, HttpMethod::Get);
373        assert_eq!(
374            user_private_list.request.url.as_str(),
375            note.user_private_list_endpoint()
376        );
377        assert_eq!(
378            user_private_list
379                .request
380                .query
381                .get("pn")
382                .map(String::as_str),
383            Some("1")
384        );
385
386        assert_eq!(public_archive_list.name, "note.public_archive_list");
387        assert_eq!(public_archive_list.request.method, HttpMethod::Get);
388        assert_eq!(
389            public_archive_list.request.url.as_str(),
390            note.public_archive_list_endpoint()
391        );
392        assert_eq!(
393            public_archive_list
394                .request
395                .query
396                .get("oid")
397                .map(String::as_str),
398            Some("338677252")
399        );
400
401        assert_eq!(user_public_list.name, "note.user_public_list");
402        assert_eq!(user_public_list.request.method, HttpMethod::Get);
403        assert_eq!(
404            user_public_list.request.url.as_str(),
405            note.user_public_list_endpoint()
406        );
407        assert_eq!(
408            user_public_list.request.query.get("ps").map(String::as_str),
409            Some("10")
410        );
411        Ok(())
412    }
413}