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.get("https://api.bilibili.com/x/ugcpay-rank/elec/month/up")
206 .query(&[("up_mid", up_mid)])
207 .send_bpi("获取空间充电公示列表")
208 .await
209 }
210
211 pub async fn electric_video_show(
223 &self,
224 mid: i64,
225 aid: Option<i64>,
226 bvid: Option<&str>,
227 ) -> Result<BpiResponse<VideoElecShowData>, BpiError> {
228 let mut req = self
229 .get("https://api.bilibili.com/x/web-interface/elec/show")
230 .query(&[("mid", mid)]);
231 if let Some(a) = aid {
232 req = req.query(&[("aid", a)]);
233 }
234 if let Some(b) = bvid {
235 req = req.query(&[("bvid", b)]);
236 }
237 req.send_bpi("获取视频充电鸣谢").await
238 }
239
240 pub async fn electric_recharge_list(
254 &self,
255 page: u64,
256 page_size: u64,
257 begin_time: Option<NaiveDate>,
258 end_time: Option<NaiveDate>,
259 ) -> Result<BpiResponse<RechargeData>, BpiError> {
260 let mut req = self
261 .get("https://pay.bilibili.com/bk/brokerage/listForCustomerRechargeRecord")
262 .query(&[("customerId", "10026")])
263 .query(&[("currentPage", page), ("pageSize", page_size)]);
264
265 if let Some(begin) = begin_time {
266 req = req.query(&[("beginTime", begin.format("%Y-%m-%d").to_string())]);
267 }
268 if let Some(end) = end_time {
269 req = req.query(&[("endTime", end.format("%Y-%m-%d").to_string())]);
270 }
271
272 req.send_bpi("获取收到的充电列表").await
273 }
274
275 pub async fn electric_rank_recent(
287 &self,
288 pn: Option<u64>,
289 ps: Option<u64>,
290 ) -> Result<BpiResponse<ElecRankData>, BpiError> {
291 let mut req = self.get("https://member.bilibili.com/x/h5/elec/rank/recent");
292
293 if let Some(page) = pn {
294 req = req.query(&[("pn", page)]);
295 }
296 if let Some(size) = ps {
297 req = req.query(&[("ps", size)]);
298 }
299
300 req.send_bpi("获取历史充电数据").await
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307 use chrono::{Duration, Utc};
308 use tracing::info;
309
310 #[tokio::test]
311 async fn test_electric_month_up_list() {
312 let bpi = BpiClient::new();
313 let resp = bpi.electric_month_up_list(53456).await;
314 assert!(resp.is_ok());
315 }
316
317 #[tokio::test]
318 async fn test_electric_video_show() {
319 let bpi = BpiClient::new();
320 let resp = bpi
321 .electric_video_show(53456, None, Some("BV1Dh411S7sS"))
322 .await;
323 assert!(resp.is_ok());
324 }
325
326 #[tokio::test]
327 async fn test_get_recharge_list() {
328 let bpi = BpiClient::new();
329 let resp = bpi.electric_recharge_list(1, 10, None, None).await;
331 info!("响应: {:?}", resp);
332 assert!(resp.is_ok());
333
334 if let Ok(response) = resp {
335 let data = response.data.unwrap();
336 info!("充电总记录数: {}", data.page.total_count);
337 info!("当前页充电记录数: {}", data.result.len());
338 if let Some(record) = data.result.first() {
339 info!("第一条充电记录信息: {:?}", record);
340 }
341 }
342 }
343
344 #[tokio::test]
345 async fn test_get_recharge_list_with_dates() {
346 let bpi = BpiClient::new();
347 let now = Utc::now().date_naive();
348 let start_date = now - Duration::days(30);
349 let end_date = now;
350
351 let resp = bpi
352 .electric_recharge_list(1, 10, Some(start_date), Some(end_date))
353 .await;
354 info!("响应: {:?}", resp);
355 assert!(resp.is_ok());
356
357 if let Ok(response) = resp {
358 info!(
359 "在日期范围内获取到的总记录数: {}",
360 response.data.unwrap().page.total_count
361 );
362 }
363 }
364
365 #[tokio::test]
366 async fn test_get_elec_rank_recent() {
367 let bpi = BpiClient::new();
368 let resp = bpi.electric_rank_recent(Some(1), Some(10)).await;
370 info!("响应: {:?}", resp);
371 assert!(resp.is_ok());
372
373 if let Ok(response) = resp {
374 let data = response.data.unwrap();
375
376 info!("充电总记录数: {}", data.pager.total);
377 info!("当前页充电记录数: {}", data.list.len());
378 if let Some(record) = data.list.first() {
379 info!("第一条充电记录信息: {:?}", record);
380 }
381 }
382 }
383}