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#[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 pub async fn is_forbid(&self, params: NoteIsForbidParams) -> BpiResult<NoteIsForbidData> {
67 self.client
68 .get(IS_FORBID_ENDPOINT)
69 .query(¶ms.query_pairs())
70 .send_bpi_payload("note.is_forbid")
71 .await
72 }
73
74 pub async fn private_info(
76 &self,
77 params: NotePrivateInfoParams,
78 ) -> BpiResult<PrivateNoteInfoData> {
79 self.client
80 .get(PRIVATE_INFO_ENDPOINT)
81 .query(¶ms.query_pairs())
82 .send_bpi_payload("note.private_info")
83 .await
84 }
85
86 pub async fn public_info(&self, params: NotePublicInfoParams) -> BpiResult<PublicNoteInfoData> {
88 self.client
89 .get(PUBLIC_INFO_ENDPOINT)
90 .query(¶ms.query_pairs())
91 .send_bpi_payload("note.public_info")
92 .await
93 }
94
95 pub async fn archive_list(
97 &self,
98 params: NoteArchiveListParams,
99 ) -> BpiResult<NoteListArchiveData> {
100 self.client
101 .get(ARCHIVE_LIST_ENDPOINT)
102 .query(¶ms.query_pairs())
103 .send_bpi_payload("note.archive_list")
104 .await
105 }
106
107 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(¶ms.query_pairs())
115 .send_bpi_payload("note.user_private_list")
116 .await
117 }
118
119 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(¶ms.query_pairs())
127 .send_bpi_payload("note.public_archive_list")
128 .await
129 }
130
131 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(¶ms.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}