bpi_rs/electric/
charge_list.rs

1use crate::{BilibiliRequest, BpiClient, BpiError, BpiResponse};
2use chrono::NaiveDate;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Serialize, Clone, Deserialize)]
6pub struct ChargeVipInfo {
7    /// 大会员过期时间(恒为 0)
8    #[serde(rename = "vipDueMsec")]
9    pub vip_due_msec: i64,
10
11    /// 大会员状态(包月充电时恒为 0;自定义充电:0=无, 1=有)
12    #[serde(rename = "vipStatus")]
13    pub vip_status: i32,
14
15    /// 大会员类型(包月充电时恒为 0;自定义充电:0=无, 1=月大会员, 2=年度及以上大会员)
16    #[serde(rename = "vipType")]
17    pub vip_type: i32,
18}
19
20#[derive(Debug, Serialize, Clone, Deserialize)]
21pub struct ChargeUser {
22    /// 充电用户昵称
23    pub uname: String,
24
25    /// 充电用户头像 url
26    pub avatar: String,
27
28    /// 充电对象 mid
29    pub mid: i64,
30
31    /// 充电用户 mid(支付id?)
32    pub pay_mid: i64,
33
34    /// 充电用户排名(取决于充电多少)
35    pub rank: i32,
36
37    /// 充电用户会员信息
38    pub vip_info: ChargeVipInfo,
39
40    /// 充电留言(为空表示无留言)
41    pub message: String,
42}
43
44#[derive(Debug, Serialize, Clone, Deserialize)]
45pub struct ChargeMonthUpData {
46    /// 本月充电人数
47    pub count: i32,
48
49    /// 本月充电用户列表
50    #[serde(default)]
51    pub list: Vec<ChargeUser>,
52
53    /// 总计充电次数
54    pub total_count: i32,
55}
56
57/// 视频充电展示信息(高阶信息)
58#[derive(Debug, Serialize, Clone, Deserialize)]
59pub struct VideoShowInfoHighLevel {
60    /// 权限类型
61    pub privilege_type: i32,
62    /// 主标题
63    pub title: String,
64    /// 副标题
65    pub sub_title: String,
66    /// 是否显示按钮
67    pub show_button: bool,
68}
69
70/// 视频充电展示信息
71#[derive(Debug, Serialize, Clone, Deserialize)]
72pub struct VideoShowInfo {
73    /// 是否显示
74    pub show: bool,
75
76    /// 充电功能开启状态
77    /// - `-1`: 未开通
78    /// - `1`: 开通
79    /// - `2`: 包月、自定义充电
80    /// - `3`: 包月高档、自定义充电
81    pub state: i32,
82
83    /// 充电按钮显示文字
84    pub title: String,
85
86    /// 充电跳转 URL 支付页面
87    pub jump_url: String,
88
89    /// 图标 URL
90    pub icon: String,
91
92    /// 充电专属视频信息
93    pub high_level: VideoShowInfoHighLevel,
94
95    /// 充电问答 ID
96    pub with_qa_id: i64,
97}
98
99/// 视频充电展示数据
100#[derive(Debug, Serialize, Clone, Deserialize)]
101pub struct VideoElecShowData {
102    /// 展示选项
103    pub show_info: VideoShowInfo,
104    /// 目标视频充电人数
105    pub av_count: i32,
106    /// 本月充电人数
107    pub count: i32,
108    /// 总计充电人数
109    pub total_count: i32,
110    /// 本月充电用户列表
111    #[serde(default)]
112    pub list: Vec<ChargeUser>,
113}
114
115// 充电列表分页信息
116#[derive(Debug, Clone, Deserialize, Serialize)]
117#[serde(rename_all = "camelCase")]
118pub struct RechargePage {
119    /// 当前页数
120    pub current_page: u64,
121    /// 当前分页大小
122    pub page_size: u64,
123    /// 记录总数
124    pub total_count: u64,
125    /// 总页数
126    pub total_page: u64,
127}
128
129/// 充电信息本体
130#[derive(Debug, Clone, Deserialize, Serialize)]
131#[serde(rename_all = "camelCase")]
132pub struct RechargeRecord {
133    /// 充电人mid
134    pub mid: u64,
135    /// 充电人昵称
136    pub name: String,
137    /// 充电人头像
138    pub avatar: String,
139    /// 原始B币数
140    pub original_third_coin: f64,
141    /// 实际收到的贝壳数
142    pub brokerage: f64,
143    /// 充电渠道 Web/安卓/iOS
144    pub remark: String,
145    /// 充电时间 yyyy-MM-dd HH:mm:ss
146    pub ctime: String,
147}
148
149/// 充电列表数据
150#[derive(Debug, Clone, Deserialize, Serialize)]
151#[serde(rename_all = "camelCase")]
152pub struct RechargeData {
153    /// 分页信息
154    pub page: RechargePage,
155    /// 充电信息本体
156    pub result: Vec<RechargeRecord>,
157}
158
159/// 充电列表分页信息
160#[derive(Debug, Clone, Deserialize, Serialize)]
161pub struct ElecRankPager {
162    /// 当前页数
163    pub current: u64,
164    /// 当前分页大小
165    pub size: u64,
166    /// 记录总数
167    pub total: u64,
168}
169
170/// 充电信息本体
171#[derive(Debug, Clone, Deserialize, Serialize)]
172pub struct ElecRankRecord {
173    /// 0
174    pub aid: u64,
175    /// 空
176    pub bvid: String,
177    /// 充电电池数
178    pub elec_num: f64,
179    /// 空
180    pub title: String,
181    /// 充电人昵称
182    pub uname: String,
183    /// 充电人头像
184    pub avatar: String,
185    /// 充电时间 yyyy-MM-dd HH:mm:ss
186    pub ctime: String,
187}
188
189/// 历史充电数据
190#[derive(Debug, Clone, Deserialize, Serialize)]
191pub struct ElecRankData {
192    /// 充电信息本体
193    pub list: Vec<ElecRankRecord>,
194    /// 分页信息
195    pub pager: ElecRankPager,
196}
197
198impl BpiClient {
199    /// 获取空间充电公示列表
200
201    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    /// 获取视频充电鸣谢名单
212    ///
213    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/electric
214    ///
215    /// 参数
216    ///
217    /// | 名称 | 类型 | 说明 |
218    /// | ---- | ---- | ---- |
219    /// | `mid` | i64 | up 主 mid |
220    /// | `aid` | Option<i64> | 稿件 avid |
221    /// | `bvid` | Option<&str> | 稿件 bvid |
222    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    /// 获取我收到的充电列表
241    /// GET https://pay.bilibili.com/bk/brokerage/listForCustomerRechargeRecord
242    ///
243    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/electric
244    ///
245    /// 参数
246    ///
247    /// | 名称 | 类型 | 说明 |
248    /// | ---- | ---- | ---- |
249    /// | `page` | u64 | 页数 |
250    /// | `page_size` | u64 | 分页大小 [1,50] |
251    /// | `begin_time` | Option<NaiveDate> | 开始日期 YYYY-MM-DD |
252    /// | `end_time` | Option<NaiveDate> | 结束日期 YYYY-MM-DD |
253    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    /// 获取历史充电数据
276    /// GET https://member.bilibili.com/x/h5/elec/rank/recent
277    ///
278    /// 文档: https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/electric
279    ///
280    /// 参数
281    ///
282    /// | 名称 | 类型 | 说明 |
283    /// | ---- | ---- | ---- |
284    /// | `pn` | Option<u64> | 页数,默认 1 |
285    /// | `ps` | Option<u64> | 分页大小,默认 10,范围 [1,20] |
286    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        // 测试获取第一页,每页10条记录
330        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        // 测试获取第一页,每页10条记录
369        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}