1use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
2use chrono::NaiveDate;
3use serde::{ Deserialize, Serialize };
4
5#[derive(Debug, Serialize, Clone, Deserialize)]
6pub struct ChargeVipInfo {
7 #[serde(rename = "vipDueMsec")]
9 pub vip_due_msec: i64,
10
11 #[serde(rename = "vipStatus")]
13 pub vip_status: i32,
14
15 #[serde(rename = "vipType")]
17 pub vip_type: i32,
18}
19
20#[derive(Debug, Serialize, Clone, Deserialize)]
21pub struct ChargeUser {
22 pub uname: String,
24
25 pub avatar: String,
27
28 pub mid: i64,
30
31 pub pay_mid: i64,
33
34 pub rank: i32,
36
37 pub vip_info: ChargeVipInfo,
39
40 pub message: String,
42}
43
44#[derive(Debug, Serialize, Clone, Deserialize)]
45pub struct ChargeMonthUpData {
46 pub count: i32,
48
49 #[serde(default)]
51 pub list: Vec<ChargeUser>,
52
53 pub total_count: i32,
55}
56
57#[derive(Debug, Serialize, Clone, Deserialize)]
59pub struct VideoShowInfoHighLevel {
60 pub privilege_type: i32,
62 pub title: String,
64 pub sub_title: String,
66 pub show_button: bool,
68}
69
70#[derive(Debug, Serialize, Clone, Deserialize)]
72pub struct VideoShowInfo {
73 pub show: bool,
75
76 pub state: i32,
82
83 pub title: String,
85
86 pub jump_url: String,
88
89 pub icon: String,
91
92 pub high_level: VideoShowInfoHighLevel,
94
95 pub with_qa_id: i64,
97}
98
99#[derive(Debug, Serialize, Clone, Deserialize)]
101pub struct VideoElecShowData {
102 pub show_info: VideoShowInfo,
104 pub av_count: i32,
106 pub count: i32,
108 pub total_count: i32,
110 #[serde(default)]
112 pub list: Vec<ChargeUser>,
113}
114
115#[derive(Debug, Clone, Deserialize, Serialize)]
117#[serde(rename_all = "camelCase")]
118pub struct RechargePage {
119 pub current_page: u64,
121 pub page_size: u64,
123 pub total_count: u64,
125 pub total_page: u64,
127}
128
129#[derive(Debug, Clone, Deserialize, Serialize)]
131#[serde(rename_all = "camelCase")]
132pub struct RechargeRecord {
133 pub mid: u64,
135 pub name: String,
137 pub avatar: String,
139 pub original_third_coin: f64,
141 pub brokerage: f64,
143 pub remark: String,
145 pub ctime: String,
147}
148
149#[derive(Debug, Clone, Deserialize, Serialize)]
151#[serde(rename_all = "camelCase")]
152pub struct RechargeData {
153 pub page: RechargePage,
155 pub result: Vec<RechargeRecord>,
157}
158
159#[derive(Debug, Clone, Deserialize, Serialize)]
161pub struct ElecRankPager {
162 pub current: u64,
164 pub size: u64,
166 pub total: u64,
168}
169
170#[derive(Debug, Clone, Deserialize, Serialize)]
172pub struct ElecRankRecord {
173 pub aid: u64,
175 pub bvid: String,
177 pub elec_num: f64,
179 pub title: String,
181 pub uname: String,
183 pub avatar: String,
185 pub ctime: String,
187}
188
189#[derive(Debug, Clone, Deserialize, Serialize)]
191pub struct ElecRankData {
192 pub list: Vec<ElecRankRecord>,
194 pub pager: ElecRankPager,
196}
197
198impl BpiClient {
199 pub async fn electric_month_up_list(
202 &self,
203 up_mid: i64
204 ) -> Result<BpiResponse<ChargeMonthUpData>, BpiError> {
205 self
206 .get("https://api.bilibili.com/x/ugcpay-rank/elec/month/up")
207 .query(&[("up_mid", up_mid)])
208 .send_bpi("获取空间充电公示列表").await
209 }
210
211 pub async fn electric_video_show(
224 &self,
225 mid: i64,
226 aid: Option<i64>,
227 bvid: Option<&str>
228 ) -> Result<BpiResponse<VideoElecShowData>, BpiError> {
229 let mut req = self
230 .get("https://api.bilibili.com/x/web-interface/elec/show")
231 .query(&[("mid", mid)]);
232 if let Some(a) = aid {
233 req = req.query(&[("aid", a)]);
234 }
235 if let Some(b) = bvid {
236 req = req.query(&[("bvid", b)]);
237 }
238 req.send_bpi("获取视频充电鸣谢").await
239 }
240
241 pub async fn electric_recharge_list(
255 &self,
256 page: u64,
257 page_size: u64,
258 begin_time: Option<NaiveDate>,
259 end_time: Option<NaiveDate>
260 ) -> Result<BpiResponse<RechargeData>, BpiError> {
261 let mut req = self
262 .get("https://pay.bilibili.com/bk/brokerage/listForCustomerRechargeRecord")
263 .query(&[("customerId", "10026")])
264 .query(
265 &[
266 ("currentPage", page),
267 ("pageSize", page_size),
268 ]
269 );
270
271 if let Some(begin) = begin_time {
272 req = req.query(&[("beginTime", begin.format("%Y-%m-%d").to_string())]);
273 }
274 if let Some(end) = end_time {
275 req = req.query(&[("endTime", end.format("%Y-%m-%d").to_string())]);
276 }
277
278 req.send_bpi("获取收到的充电列表").await
279 }
280
281 pub async fn electric_rank_recent(
293 &self,
294 pn: Option<u64>,
295 ps: Option<u64>
296 ) -> Result<BpiResponse<ElecRankData>, BpiError> {
297 let mut req = self.get("https://member.bilibili.com/x/h5/elec/rank/recent");
298
299 if let Some(page) = pn {
300 req = req.query(&[("pn", page)]);
301 }
302 if let Some(size) = ps {
303 req = req.query(&[("ps", size)]);
304 }
305
306 req.send_bpi("获取历史充电数据").await
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313 use chrono::{ Duration, Utc };
314 use tracing::info;
315
316 #[tokio::test]
317 async fn test_electric_month_up_list() {
318 let bpi = BpiClient::new();
319 let resp = bpi.electric_month_up_list(53456).await;
320 assert!(resp.is_ok());
321 }
322
323 #[tokio::test]
324 async fn test_electric_video_show() {
325 let bpi = BpiClient::new();
326 let resp = bpi.electric_video_show(53456, None, Some("BV1Dh411S7sS")).await;
327 assert!(resp.is_ok());
328 }
329
330 #[tokio::test]
331 async fn test_get_recharge_list() {
332 let bpi = BpiClient::new();
333 let resp = bpi.electric_recharge_list(1, 10, None, None).await;
335 info!("响应: {:?}", resp);
336 assert!(resp.is_ok());
337
338 if let Ok(response) = resp {
339 let data = response.data.unwrap();
340 info!("充电总记录数: {}", data.page.total_count);
341 info!("当前页充电记录数: {}", data.result.len());
342 if let Some(record) = data.result.first() {
343 info!("第一条充电记录信息: {:?}", record);
344 }
345 }
346 }
347
348 #[tokio::test]
349 async fn test_get_recharge_list_with_dates() {
350 let bpi = BpiClient::new();
351 let now = Utc::now().date_naive();
352 let start_date = now - Duration::days(30);
353 let end_date = now;
354
355 let resp = bpi.electric_recharge_list(1, 10, Some(start_date), Some(end_date)).await;
356 info!("响应: {:?}", resp);
357 assert!(resp.is_ok());
358
359 if let Ok(response) = resp {
360 info!("在日期范围内获取到的总记录数: {}", response.data.unwrap().page.total_count);
361 }
362 }
363
364 #[tokio::test]
365 async fn test_get_elec_rank_recent() {
366 let bpi = BpiClient::new();
367 let resp = bpi.electric_rank_recent(Some(1), Some(10)).await;
369 info!("响应: {:?}", resp);
370 assert!(resp.is_ok());
371
372 if let Ok(response) = resp {
373 let data = response.data.unwrap();
374
375 info!("充电总记录数: {}", data.pager.total);
376 info!("当前页充电记录数: {}", data.list.len());
377 if let Some(record) = data.list.first() {
378 info!("第一条充电记录信息: {:?}", record);
379 }
380 }
381 }
382}