1use crate::creativecenter::params::{
2 UpArchiveCompareParams, UpArchiveVideosParams, UpArchivesListParams, UpArticleTrendParams,
3 UpVideoTrendParams,
4};
5use crate::creativecenter::railgun::ElectromagneticInfo;
6use crate::creativecenter::season::list::SeasonListData;
7use crate::creativecenter::season::section::SeasonSectionEpisodesData;
8use crate::creativecenter::season::{
9 SeasonByAidData, SeasonByAidParams, SeasonInfoData, SeasonInfoParams, SeasonListParams,
10 SeasonSectionEpisodesParams,
11};
12use crate::creativecenter::statistics_data::{
13 ArchiveCompareData, ArticleTrendItem, PlaySourceData, UpArticleStatData, UpStatData,
14 VideoTrendItem, ViewerData,
15};
16use crate::creativecenter::videos::{ArchiveVideosData, SpArchivesData};
17use crate::{BilibiliRequest, BpiClient, BpiResult};
18
19const SEASON_LIST_ENDPOINT: &str = "https://member.bilibili.com/x2/creative/web/seasons";
20const SEASON_INFO_ENDPOINT: &str = "https://member.bilibili.com/x2/creative/web/season";
21const SEASON_BY_AID_ENDPOINT: &str = "https://member.bilibili.com/x2/creative/web/season/aid";
22const SEASON_SECTION_EPISODES_ENDPOINT: &str =
23 "https://member.bilibili.com/x2/creative/web/season/section";
24const ARCHIVES_LIST_ENDPOINT: &str = "https://member.bilibili.com/x2/creative/web/archives/sp";
25const ARCHIVE_VIDEOS_ENDPOINT: &str = "https://member.bilibili.com/x/web/archive/videos";
26const UP_STAT_ENDPOINT: &str = "https://member.bilibili.com/x/web/index/stat";
27const ARCHIVE_COMPARE_ENDPOINT: &str =
28 "https://member.bilibili.com/x/web/data/archive_diagnose/compare";
29const ARTICLE_STAT_ENDPOINT: &str = "https://member.bilibili.com/x/web/data/article";
30const VIDEO_TREND_ENDPOINT: &str = "https://member.bilibili.com/x/web/data/pandect";
31const ARTICLE_TREND_ENDPOINT: &str = "https://member.bilibili.com/x/web/data/article/thirty";
32const PLAY_SOURCE_ENDPOINT: &str = "https://member.bilibili.com/x/web/data/playsource";
33const VIEWER_DATA_ENDPOINT: &str = "https://member.bilibili.com/x/web/data/base";
34const ELECTROMAGNETIC_INFO_ENDPOINT: &str =
35 "https://api.bilibili.com/studio/up-rating/v3/rating/info";
36
37#[derive(Clone, Copy)]
39pub struct CreativeCenterClient<'a> {
40 pub(crate) client: &'a BpiClient,
41}
42
43impl<'a> CreativeCenterClient<'a> {
44 pub(crate) fn new(client: &'a BpiClient) -> Self {
45 Self { client }
46 }
47
48 #[cfg(test)]
49 pub(crate) fn season_list_endpoint(&self) -> &'static str {
50 SEASON_LIST_ENDPOINT
51 }
52
53 #[cfg(test)]
54 pub(crate) fn season_info_endpoint(&self) -> &'static str {
55 SEASON_INFO_ENDPOINT
56 }
57
58 #[cfg(test)]
59 pub(crate) fn season_by_aid_endpoint(&self) -> &'static str {
60 SEASON_BY_AID_ENDPOINT
61 }
62
63 #[cfg(test)]
64 pub(crate) fn season_section_episodes_endpoint(&self) -> &'static str {
65 SEASON_SECTION_EPISODES_ENDPOINT
66 }
67
68 #[cfg(test)]
69 pub(crate) fn archives_list_endpoint(&self) -> &'static str {
70 ARCHIVES_LIST_ENDPOINT
71 }
72
73 #[cfg(test)]
74 pub(crate) fn archive_videos_endpoint(&self) -> &'static str {
75 ARCHIVE_VIDEOS_ENDPOINT
76 }
77
78 #[cfg(test)]
79 pub(crate) fn up_stat_endpoint(&self) -> &'static str {
80 UP_STAT_ENDPOINT
81 }
82
83 #[cfg(test)]
84 pub(crate) fn archive_compare_endpoint(&self) -> &'static str {
85 ARCHIVE_COMPARE_ENDPOINT
86 }
87
88 #[cfg(test)]
89 pub(crate) fn article_stat_endpoint(&self) -> &'static str {
90 ARTICLE_STAT_ENDPOINT
91 }
92
93 #[cfg(test)]
94 pub(crate) fn video_trend_endpoint(&self) -> &'static str {
95 VIDEO_TREND_ENDPOINT
96 }
97
98 #[cfg(test)]
99 pub(crate) fn article_trend_endpoint(&self) -> &'static str {
100 ARTICLE_TREND_ENDPOINT
101 }
102
103 #[cfg(test)]
104 pub(crate) fn play_source_endpoint(&self) -> &'static str {
105 PLAY_SOURCE_ENDPOINT
106 }
107
108 #[cfg(test)]
109 pub(crate) fn viewer_data_endpoint(&self) -> &'static str {
110 VIEWER_DATA_ENDPOINT
111 }
112
113 #[cfg(test)]
114 pub(crate) fn electromagnetic_info_endpoint(&self) -> &'static str {
115 ELECTROMAGNETIC_INFO_ENDPOINT
116 }
117
118 pub async fn season_list(&self, params: SeasonListParams) -> BpiResult<SeasonListData> {
120 self.client
121 .get(SEASON_LIST_ENDPOINT)
122 .query(¶ms.query_pairs())
123 .send_bpi_payload("creativecenter.season.list")
124 .await
125 }
126
127 pub async fn season_info(&self, params: SeasonInfoParams) -> BpiResult<SeasonInfoData> {
129 self.client
130 .get(SEASON_INFO_ENDPOINT)
131 .query(¶ms.query_pairs())
132 .send_bpi_payload("creativecenter.season.info")
133 .await
134 }
135
136 pub async fn season_by_aid(&self, params: SeasonByAidParams) -> BpiResult<SeasonByAidData> {
138 self.client
139 .get(SEASON_BY_AID_ENDPOINT)
140 .query(¶ms.query_pairs())
141 .send_bpi_payload("creativecenter.season.aid")
142 .await
143 }
144
145 pub async fn season_section_episodes(
147 &self,
148 params: SeasonSectionEpisodesParams,
149 ) -> BpiResult<SeasonSectionEpisodesData> {
150 self.client
151 .get(SEASON_SECTION_EPISODES_ENDPOINT)
152 .query(¶ms.query_pairs())
153 .send_bpi_payload("creativecenter.season.section")
154 .await
155 }
156
157 pub async fn archives_list(&self, params: UpArchivesListParams) -> BpiResult<SpArchivesData> {
159 self.client
160 .get(ARCHIVES_LIST_ENDPOINT)
161 .query(¶ms.query_pairs())
162 .send_bpi_payload("creativecenter.videos.archives_list")
163 .await
164 }
165
166 pub async fn archive_videos(
168 &self,
169 params: UpArchiveVideosParams,
170 ) -> BpiResult<ArchiveVideosData> {
171 self.client
172 .get(ARCHIVE_VIDEOS_ENDPOINT)
173 .query(¶ms.query_pairs())
174 .send_bpi_payload("creativecenter.videos.archive_videos")
175 .await
176 }
177
178 pub async fn up_stat(&self) -> BpiResult<UpStatData> {
180 self.client
181 .get(UP_STAT_ENDPOINT)
182 .send_bpi_payload("creativecenter.statistics.up_stat")
183 .await
184 }
185
186 pub async fn archive_compare(
188 &self,
189 params: UpArchiveCompareParams,
190 ) -> BpiResult<ArchiveCompareData> {
191 self.client
192 .get(ARCHIVE_COMPARE_ENDPOINT)
193 .query(¶ms.query_pairs())
194 .send_bpi_payload("creativecenter.statistics.archive_compare")
195 .await
196 }
197
198 pub async fn article_stat(&self) -> BpiResult<UpArticleStatData> {
200 self.client
201 .get(ARTICLE_STAT_ENDPOINT)
202 .send_bpi_payload("creativecenter.statistics.article_stat")
203 .await
204 }
205
206 pub async fn video_trend(&self, params: UpVideoTrendParams) -> BpiResult<Vec<VideoTrendItem>> {
208 self.client
209 .get(VIDEO_TREND_ENDPOINT)
210 .query(¶ms.query_pairs())
211 .send_bpi_payload("creativecenter.statistics.video_trend")
212 .await
213 }
214
215 pub async fn article_trend(
217 &self,
218 params: UpArticleTrendParams,
219 ) -> BpiResult<Vec<ArticleTrendItem>> {
220 self.client
221 .get(ARTICLE_TREND_ENDPOINT)
222 .query(¶ms.query_pairs())
223 .send_bpi_payload("creativecenter.statistics.article_trend")
224 .await
225 }
226
227 pub async fn play_source(&self) -> BpiResult<PlaySourceData> {
229 self.client
230 .get(PLAY_SOURCE_ENDPOINT)
231 .with_bilibili_headers()
232 .send_bpi_payload("creativecenter.statistics.play_source")
233 .await
234 }
235
236 pub async fn viewer_data(&self) -> BpiResult<ViewerData> {
238 self.client
239 .get(VIEWER_DATA_ENDPOINT)
240 .send_bpi_payload("creativecenter.statistics.viewer_data")
241 .await
242 }
243
244 pub async fn electromagnetic_info(&self) -> BpiResult<ElectromagneticInfo> {
246 self.client
247 .get(ELECTROMAGNETIC_INFO_ENDPOINT)
248 .send_bpi_payload("creativecenter.railgun.electromagnetic_info")
249 .await
250 }
251}
252
253#[cfg(test)]
254mod tests {
255 use std::future::Future;
256
257 use crate::creativecenter::params::{
258 UpArchiveCompareParams, UpArchiveVideosParams, UpArchivesListParams, UpArticleTrendMetric,
259 UpArticleTrendParams, UpVideoTrendMetric, UpVideoTrendParams,
260 };
261 use crate::creativecenter::railgun::ElectromagneticInfo;
262 use crate::creativecenter::season::list::SeasonListData;
263 use crate::creativecenter::season::section::SeasonSectionEpisodesData;
264 use crate::creativecenter::season::{
265 SeasonByAidData, SeasonByAidParams, SeasonInfoData, SeasonInfoParams, SeasonListOrder,
266 SeasonListParams, SeasonListSort, SeasonSectionEpisodesParams,
267 };
268 use crate::creativecenter::statistics_data::{
269 ArchiveCompareData, ArticleTrendItem, PlaySourceData, UpArticleStatData, UpStatData,
270 VideoTrendItem, ViewerData,
271 };
272 use crate::creativecenter::videos::{ArchiveVideosData, SpArchivesData};
273 use crate::ids::{Aid, SeasonId};
274 use crate::probe::contract::HttpMethod;
275 use crate::probe::endpoint_contract::EndpointContract;
276 use crate::{BpiClient, BpiResult};
277
278 fn assert_season_list_future<F>(_future: F)
279 where
280 F: Future<Output = BpiResult<SeasonListData>>,
281 {
282 }
283
284 fn assert_season_info_future<F>(_future: F)
285 where
286 F: Future<Output = BpiResult<SeasonInfoData>>,
287 {
288 }
289
290 fn assert_season_by_aid_future<F>(_future: F)
291 where
292 F: Future<Output = BpiResult<SeasonByAidData>>,
293 {
294 }
295
296 fn assert_season_section_episodes_future<F>(_future: F)
297 where
298 F: Future<Output = BpiResult<SeasonSectionEpisodesData>>,
299 {
300 }
301
302 fn assert_archives_list_future<F>(_future: F)
303 where
304 F: Future<Output = BpiResult<SpArchivesData>>,
305 {
306 }
307
308 fn assert_archive_videos_future<F>(_future: F)
309 where
310 F: Future<Output = BpiResult<ArchiveVideosData>>,
311 {
312 }
313
314 fn assert_up_stat_future<F>(_future: F)
315 where
316 F: Future<Output = BpiResult<UpStatData>>,
317 {
318 }
319
320 fn assert_archive_compare_future<F>(_future: F)
321 where
322 F: Future<Output = BpiResult<ArchiveCompareData>>,
323 {
324 }
325
326 fn assert_article_stat_future<F>(_future: F)
327 where
328 F: Future<Output = BpiResult<UpArticleStatData>>,
329 {
330 }
331
332 fn assert_video_trend_future<F>(_future: F)
333 where
334 F: Future<Output = BpiResult<Vec<VideoTrendItem>>>,
335 {
336 }
337
338 fn assert_article_trend_future<F>(_future: F)
339 where
340 F: Future<Output = BpiResult<Vec<ArticleTrendItem>>>,
341 {
342 }
343
344 fn assert_play_source_future<F>(_future: F)
345 where
346 F: Future<Output = BpiResult<PlaySourceData>>,
347 {
348 }
349
350 fn assert_viewer_data_future<F>(_future: F)
351 where
352 F: Future<Output = BpiResult<ViewerData>>,
353 {
354 }
355
356 fn assert_electromagnetic_info_future<F>(_future: F)
357 where
358 F: Future<Output = BpiResult<ElectromagneticInfo>>,
359 {
360 }
361
362 fn contract(endpoint: &str) -> BpiResult<EndpointContract> {
363 let bytes = match endpoint {
364 "season-list" => include_bytes!(
365 "../../tests/contracts/creativecenter/season/list/contract.json"
366 )
367 .as_slice(),
368 "season-info" => include_bytes!(
369 "../../tests/contracts/creativecenter/season/info/contract.json"
370 )
371 .as_slice(),
372 "season-aid" => include_bytes!(
373 "../../tests/contracts/creativecenter/season/aid/contract.json"
374 )
375 .as_slice(),
376 "season-section" => include_bytes!(
377 "../../tests/contracts/creativecenter/season/section/contract.json"
378 )
379 .as_slice(),
380 "archives-list" => include_bytes!(
381 "../../tests/contracts/creativecenter/videos/archives-list/contract.json"
382 )
383 .as_slice(),
384 "archive-videos" => include_bytes!(
385 "../../tests/contracts/creativecenter/videos/archive-videos/contract.json"
386 )
387 .as_slice(),
388 "up-stat" => include_bytes!(
389 "../../tests/contracts/creativecenter/statistics/up-stat/contract.json"
390 )
391 .as_slice(),
392 "archive-compare" => include_bytes!(
393 "../../tests/contracts/creativecenter/statistics/archive-compare/contract.json"
394 )
395 .as_slice(),
396 "article-stat" => include_bytes!(
397 "../../tests/contracts/creativecenter/statistics/article-stat/contract.json"
398 )
399 .as_slice(),
400 "video-trend" => include_bytes!(
401 "../../tests/contracts/creativecenter/statistics/video-trend/contract.json"
402 )
403 .as_slice(),
404 "article-trend" => include_bytes!(
405 "../../tests/contracts/creativecenter/statistics/article-trend/contract.json"
406 )
407 .as_slice(),
408 "play-source" => include_bytes!(
409 "../../tests/contracts/creativecenter/statistics/play-source/contract.json"
410 )
411 .as_slice(),
412 "viewer-data" => include_bytes!(
413 "../../tests/contracts/creativecenter/statistics/viewer-data/contract.json"
414 )
415 .as_slice(),
416 "electromagnetic-info" => include_bytes!(
417 "../../tests/contracts/creativecenter/railgun-read/electromagnetic-info/contract.json"
418 )
419 .as_slice(),
420 _ => unreachable!("unknown creativecenter contract"),
421 };
422
423 EndpointContract::from_slice(bytes)
424 }
425
426 #[test]
427 fn creativecenter_methods_return_payload_futures() -> BpiResult<()> {
428 let client = BpiClient::new()?;
429 let creativecenter = client.creativecenter();
430
431 assert_season_list_future(
432 creativecenter.season_list(
433 SeasonListParams::new(1, 10)?
434 .with_order(SeasonListOrder::CreatedAt)
435 .with_sort(SeasonListSort::Desc),
436 ),
437 );
438 assert_season_info_future(
439 creativecenter.season_info(SeasonInfoParams::new(SeasonId::new(4294056)?)),
440 );
441 assert_season_by_aid_future(
442 creativecenter.season_by_aid(SeasonByAidParams::new(Aid::new(113602455409683)?)),
443 );
444 assert_season_section_episodes_future(
445 creativecenter
446 .season_section_episodes(SeasonSectionEpisodesParams::new(SeasonId::new(176088)?)),
447 );
448 assert_archives_list_future(
449 creativecenter.archives_list(UpArchivesListParams::new(1)?.with_page_size(10)?),
450 );
451 assert_archive_videos_future(
452 creativecenter.archive_videos(UpArchiveVideosParams::new(Aid::new(113602455409683)?)),
453 );
454 assert_up_stat_future(creativecenter.up_stat());
455 assert_archive_compare_future(
456 creativecenter.archive_compare(
457 UpArchiveCompareParams::new()
458 .with_timestamp(1_720_000_000)?
459 .with_size(1)?,
460 ),
461 );
462 assert_article_stat_future(creativecenter.article_stat());
463 assert_video_trend_future(
464 creativecenter.video_trend(UpVideoTrendParams::new(UpVideoTrendMetric::Play)),
465 );
466 assert_article_trend_future(
467 creativecenter.article_trend(UpArticleTrendParams::new(UpArticleTrendMetric::Read)),
468 );
469 assert_play_source_future(creativecenter.play_source());
470 assert_viewer_data_future(creativecenter.viewer_data());
471 assert_electromagnetic_info_future(creativecenter.electromagnetic_info());
472 Ok(())
473 }
474
475 #[test]
476 fn creativecenter_contracts_match_module_client_endpoints() -> BpiResult<()> {
477 let client = BpiClient::new()?;
478 let creativecenter = client.creativecenter();
479
480 let expectations = [
481 (
482 "season-list",
483 "creativecenter.season.list",
484 creativecenter.season_list_endpoint(),
485 ),
486 (
487 "season-info",
488 "creativecenter.season.info",
489 creativecenter.season_info_endpoint(),
490 ),
491 (
492 "season-aid",
493 "creativecenter.season.aid",
494 creativecenter.season_by_aid_endpoint(),
495 ),
496 (
497 "season-section",
498 "creativecenter.season.section",
499 creativecenter.season_section_episodes_endpoint(),
500 ),
501 (
502 "archives-list",
503 "creativecenter.videos.archives_list",
504 creativecenter.archives_list_endpoint(),
505 ),
506 (
507 "archive-videos",
508 "creativecenter.videos.archive_videos",
509 creativecenter.archive_videos_endpoint(),
510 ),
511 (
512 "up-stat",
513 "creativecenter.statistics.up_stat",
514 creativecenter.up_stat_endpoint(),
515 ),
516 (
517 "archive-compare",
518 "creativecenter.statistics.archive_compare",
519 creativecenter.archive_compare_endpoint(),
520 ),
521 (
522 "article-stat",
523 "creativecenter.statistics.article_stat",
524 creativecenter.article_stat_endpoint(),
525 ),
526 (
527 "video-trend",
528 "creativecenter.statistics.video_trend",
529 creativecenter.video_trend_endpoint(),
530 ),
531 (
532 "article-trend",
533 "creativecenter.statistics.article_trend",
534 creativecenter.article_trend_endpoint(),
535 ),
536 (
537 "play-source",
538 "creativecenter.statistics.play_source",
539 creativecenter.play_source_endpoint(),
540 ),
541 (
542 "viewer-data",
543 "creativecenter.statistics.viewer_data",
544 creativecenter.viewer_data_endpoint(),
545 ),
546 (
547 "electromagnetic-info",
548 "creativecenter.railgun.electromagnetic_info",
549 creativecenter.electromagnetic_info_endpoint(),
550 ),
551 ];
552
553 for (endpoint, name, url) in expectations {
554 let contract = contract(endpoint)?;
555
556 assert_eq!(contract.name, name);
557 assert_eq!(contract.request.method, HttpMethod::Get);
558 assert_eq!(contract.request.url.as_str(), url);
559 }
560
561 Ok(())
562 }
563}