1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
5
6#[derive(Debug, Clone, Deserialize, Serialize)]
10
11pub struct Renew {
12 pub uid: u64,
14 pub ruid: u64,
16 pub goods_id: u64,
18 pub status: u8,
20 pub next_execute_time: u64,
22 pub signed_time: u64,
24 pub signed_price: u64,
26 pub pay_channel: u8,
28 pub period: u64,
30 pub mobile_app: String,
32}
33
34#[derive(Debug, Clone, Deserialize, Serialize)]
36pub struct ChargeItem {
37 pub privilege_type: u64,
39 pub icon: String,
41 pub name: String,
43 pub expire_time: u64,
45 pub renew: Option<Renew>,
47 pub start_time: u64,
49 pub renew_list: Option<Vec<Renew>>,
51}
52
53#[derive(Debug, Clone, Deserialize, Serialize)]
55pub struct ChargeUp {
56 pub up_uid: u64,
58 pub user_name: String,
60 pub user_face: String,
62 pub item: Vec<ChargeItem>,
64 pub start: u64,
66 pub high_level_state: u8,
68 pub elec_reply_state: u8,
70}
71
72#[derive(Debug, Clone, Deserialize, Serialize)]
74pub struct ChargeRecordData {
75 pub list: Option<Vec<ChargeUp>>,
77 pub page: u64,
79 pub page_size: u64,
81 pub total_page: u64,
83 pub total_num: u64,
85 pub is_more: u8,
87}
88
89#[derive(Debug, Clone, Deserialize, Serialize)]
93
94pub struct UpowerRankUser {
95 pub rank: u64,
97 pub mid: u64,
99 pub nickname: String,
101 pub avatar: String,
103}
104
105#[derive(Debug, Clone, Deserialize, Serialize)]
107pub struct UpowerRank {
108 pub total: u64,
110 pub total_desc: String,
112 pub list: Vec<UpowerRankUser>,
114}
115
116#[derive(Debug, Clone, Deserialize, Serialize)]
118pub struct ItemDetailIntro {
119 pub intro_video_aid: String,
121 pub welcomes: String,
123}
124
125#[derive(Debug, Clone, Deserialize, Serialize)]
127pub struct UpUserCard {
128 pub avatar: String,
130 pub nickname: String,
132}
133
134#[derive(Debug, Clone, Deserialize, Serialize)]
136pub struct UpowerRightCount {
137 #[serde(flatten)]
138 pub counts: HashMap<String, u64>,
139}
140
141#[derive(Debug, Clone, Deserialize, Serialize)]
143pub struct UpowerItemDetail {
144 pub upower_rank: UpowerRank,
146 pub item: ItemDetailIntro,
148 pub user_card: UpUserCard,
150 pub upower_level: u8,
152 pub elec_reply_state: u8,
154 pub voucher_state: serde_json::Value,
156 pub upower_right_count: UpowerRightCount,
158 pub only_contain_medal: bool,
160 pub privilege_type: u64,
162}
163
164#[derive(Debug, Clone, Deserialize, Serialize)]
168
169pub struct UpCard {
170 pub mid: u64,
172 pub nickname: String,
174 pub official_title: String,
176 pub avatar: String,
178}
179
180#[derive(Debug, Clone, Deserialize, Serialize)]
182
183pub struct UserCard {
184 pub avatar: String,
186 pub nickname: String,
188}
189
190#[derive(Debug, Clone, Deserialize, Serialize)]
192
193pub struct ChargeFollowInfo {
194 pub days: u64,
196 pub up_card: UpCard,
198 pub user_card: UserCard,
200 pub remain_days: i64,
202 pub remain_less_1day: u8,
204 pub upower_rank: UpowerRank,
206 pub upower_icon: String,
208 pub upower_right_count: i64,
210 pub only_contain_medal: bool,
212 pub privilege_type: u64,
214 pub challenge_info: ChallengeInfo,
216}
217
218#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
219pub struct ChallengeInfo {
220 pub challenge_id: String,
221 pub description: String,
222 pub challenge_type: i64,
223 pub remaining_days: i64,
224 pub end_time: String,
225 pub progress: i64,
226 pub targets: Vec<serde_json::Value>,
227 pub state: i64,
228 pub end_time_unix: i64,
229 pub pub_dyn: i64,
230 pub dyn_content: String,
231}
232
233#[derive(Debug, Clone, Deserialize, Serialize)]
235
236pub struct UpInfo {
237 pub mid: u64,
239 pub nickname: String,
241 pub avatar: String,
243 pub r#type: i32,
245 pub title: String,
247 pub upower_state: u8,
249}
250
251#[derive(Debug, Clone, Deserialize, Serialize)]
253
254pub struct RankInfo {
255 pub mid: u64,
257 pub nickname: String,
259 pub avatar: String,
261 pub rank: u64,
263 pub day: u64,
265 pub expire_at: u64,
267 pub remain_days: u64,
269}
270
271#[derive(Debug, Clone, Deserialize, Serialize)]
273
274pub struct MemberUserInfo {
275 pub mid: u64,
277 pub nickname: String,
279 pub avatar: String,
281 pub rank: i64,
283 pub day: u64,
285 pub expire_at: u64,
287 pub remain_days: u64,
289}
290
291#[derive(Debug, Clone, Deserialize, Serialize)]
293
294pub struct LevelInfo {
295 pub privilege_type: u64,
297 pub name: String,
299 pub price: u64,
301 pub member_total: u64,
303}
304
305#[derive(Debug, Clone, Deserialize, Serialize)]
307
308pub struct MemberRankData {
309 pub up_info: UpInfo,
311 pub rank_info: Vec<RankInfo>,
313 pub user_info: MemberUserInfo,
315 pub member_total: u64,
317 pub privilege_type: u64,
319 pub is_charge: bool,
321 pub tabs: Vec<u64>,
323 pub level_info: Vec<LevelInfo>,
325}
326
327impl BpiClient {
328 pub async fn electric_charge_record(
341 &self,
342 page: u64,
343 charge_type: u32,
344 ) -> Result<BpiResponse<ChargeRecordData>, BpiError> {
345 self.get("https://api.live.bilibili.com/xlive/revenue/v1/guard/getChargeRecord")
346 .query(&[
347 ("page", page.to_string()),
348 ("type", charge_type.to_string()),
349 ])
350 .send_bpi("获取包月充电列表")
351 .await
352 }
353
354 pub async fn electric_upower_item_detail(
364 &self,
365 up_mid: u64,
366 ) -> Result<BpiResponse<UpowerItemDetail>, BpiError> {
367 self.get("https://api.bilibili.com/x/upower/item/detail")
368 .query(&[("up_mid", up_mid)])
369 .send_bpi("获取UP主包月充电详情")
370 .await
371 }
372
373 pub async fn electric_charge_follow_info(
385 &self,
386 up_mid: u64,
387 ) -> Result<BpiResponse<ChargeFollowInfo>, BpiError> {
388 self.get("https://api.bilibili.com/x/upower/charge/follow/info")
389 .query(&[("up_mid", up_mid)])
390 .send_bpi("获取与UP主的包月充电关系")
391 .await
392 }
393
394 pub async fn electric_upower_member_rank(
409 &self,
410 up_mid: u64,
411 pn: u64,
412 ps: u64,
413 privilege_type: Option<u64>,
414 ) -> Result<BpiResponse<MemberRankData>, BpiError> {
415 let mut req = self
416 .get("https://api.bilibili.com/x/upower/up/member/rank/v2")
417 .query(&[("up_mid", up_mid), ("pn", pn), ("ps", ps)]);
418
419 if let Some(ptype) = privilege_type {
420 req = req.query(&[("privilege_type", ptype)]);
421 }
422
423 req.send_bpi("获取包月充电用户排名").await
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use tracing::info;
431
432 #[tokio::test]
433 async fn test_get_charge_record() {
434 let bpi = BpiClient::new();
435 let resp = bpi.electric_charge_record(1, 1).await;
437 info!("响应: {:?}", resp);
438 assert!(resp.is_ok());
439
440 if let Ok(response) = resp {
441 if let Some(list) = response.data.unwrap().list {
442 info!("找到 {} 个正在充电的UP主", list.len());
443 } else {
444 info!("没有正在充电的UP主");
445 }
446 }
447 }
448
449 #[tokio::test]
450 async fn test_get_upower_item_detail() {
451 let bpi = BpiClient::new();
452 let up_mid = 1265680561;
454 let resp = bpi.electric_upower_item_detail(up_mid).await;
455 info!("响应: {:?}", resp);
456 assert!(resp.is_ok());
457
458 if let Ok(response) = resp {
459 let data = response.data.unwrap();
460 info!(
461 "UP主 {} 的充电总人数: {}",
462 data.user_card.nickname, data.upower_rank.total
463 );
464 }
465 }
466
467 #[tokio::test]
468 async fn test_get_charge_follow_info() {
469 let bpi = BpiClient::new();
470 let up_mid = 293793435;
471 let resp = bpi.electric_charge_follow_info(up_mid).await;
472 info!("响应: {:?}", resp);
473 assert!(resp.is_ok());
474
475 if let Ok(response) = resp {
476 let data = response.data.unwrap();
477 info!(
478 "与UP主 {} 的充电关系:已保持 {} 天",
479 data.up_card.nickname, data.days
480 );
481 }
482 }
483
484 #[tokio::test]
485 async fn test_get_upower_member_rank() {
486 let bpi = BpiClient::new();
487 let up_mid = 1265680561;
489 let resp = bpi.electric_upower_member_rank(up_mid, 1, 10, None).await;
491 info!("响应: {:?}", resp);
492 assert!(resp.is_ok());
493
494 if let Ok(response) = resp {
495 let data = response.data.unwrap();
496
497 info!("当前档位充电用户总数: {}", data.member_total);
498 if let Some(first_rank) = data.rank_info.first() {
499 info!("排名第一的用户: {}", first_rank.nickname);
500 }
501 }
502 }
503}