Skip to main content

bpi_rs/vip/
center.rs

1use crate::models::{ Account, Vip };
2use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
3use serde::Deserialize;
4
5/// 大会员中心信息响应结构体
6#[derive(Debug, Clone, Deserialize)]
7pub struct VipCenterData {
8    /// 用户信息
9    pub user: User,
10    /// 钱包信息
11    pub wallet: WalletInfo,
12
13    pub in_review: bool,
14}
15
16/// 用户信息结构体
17#[derive(Debug, Clone, Deserialize)]
18pub struct User {
19    /// 账号基本信息
20    pub account: Account,
21    /// 账号会员信息
22    pub vip: Vip,
23    /// 电视会员信息
24    pub tv: TvVipInfo,
25    /// 空字段
26    pub background_image_small: String,
27    /// 空字段
28    pub background_image_big: String,
29    /// 用户昵称
30    pub panel_title: String,
31
32    /// 大会员提示文案(有效期/到期)
33    pub vip_overdue_explain: String,
34    /// 电视大会员提示文案(有效期/到期)
35    pub tv_overdue_explain: String,
36    /// 空字段
37    pub account_exception_text: String,
38    /// 是否自动续费
39    pub is_auto_renew: bool,
40    /// 是否自动续费电视大会员
41    pub is_tv_auto_renew: bool,
42    /// 大会员到期剩余时间(单位为秒)
43    pub surplus_seconds: u64,
44    /// 持续开通大会员时间(单位为秒)
45    pub vip_keep_time: u64,
46}
47
48/// 电视会员信息结构体
49#[derive(Debug, Clone, Deserialize)]
50pub struct TvVipInfo {
51    /// 电视大会员类型(0:无,1:月大会员,2:年度及以上大会员)
52    #[serde(rename = "type")]
53    pub tv_type: u32,
54    /// 电视大支付类型(0:未支付,1:已支付)
55    pub vip_pay_type: u32,
56    /// 电视大会员状态(0:无,1:有)
57    pub status: u32,
58    /// 电视大会员过期时间(毫秒时间戳)
59    pub due_date: u64,
60}
61
62/// 钱包信息结构体
63#[derive(Debug, Clone, Deserialize)]
64pub struct WalletInfo {
65    /// 当前B币券
66    pub coupon: u64,
67    /// 积分
68    pub point: u64,
69    /// 是否已领取特权
70    pub privilege_received: bool,
71}
72
73impl BpiClient {
74    /// 获取大会员中心信息
75    ///
76    /// # 文档
77    /// [查看API文档](https://socialsisteryi.github.io/bilibili-API-collect/docs/vip/center.html#获取大会员中心信息)
78    ///
79    pub async fn vip_center_info(&self) -> Result<BpiResponse<VipCenterData>, BpiError> {
80        self
81            .get("https://api.bilibili.com/x/vip/web/vip_center/combine")
82            .query(&[("build", 0)])
83            .send_bpi("获取大会员中心信息").await
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[tokio::test]
92    async fn test_vip_center_info_comprehensive() {
93        tracing::info!("开始测试大会员中心信息的综合功能");
94
95        let bpi = BpiClient::new();
96        let resp = bpi.vip_center_info().await;
97
98        match resp {
99            Ok(response) => {
100                tracing::info!("开始详细分析大会员中心信息数据结构");
101
102                let data = &response.data.unwrap();
103
104                // 1. 用户账户信息详细检查
105                tracing::info!("=== 用户账户信息 ===");
106                let account = &data.user.account;
107                tracing::info!("用户ID: {}", account.mid);
108                tracing::info!("用户昵称: {}", account.name);
109                tracing::info!("用户性别: {}", account.sex);
110                tracing::info!("用户等级: {}", account.rank);
111                tracing::info!("用户签名: {}", account.sign);
112                tracing::info!("是否注销: {}", if account.is_deleted == 0 {
113                    "正常"
114                } else {
115                    "已注销"
116                });
117                tracing::info!("是否转正: {}", if account.is_senior_member == 1 {
118                    "正式会员"
119                } else {
120                    "未转正"
121                });
122
123                // 2. 会员信息详细检查
124                tracing::info!("=== 会员信息 ===");
125                let vip = &data.user.vip;
126                let vip_type_text = match vip.vip_type {
127                    0 => "无会员",
128                    1 => "月大会员",
129                    2 => "年度及以上大会员",
130                    _ => "未知类型",
131                };
132                tracing::info!("会员类型: {} ({})", vip.vip_type, vip_type_text);
133                tracing::info!("会员状态: {}", if vip.vip_status == 1 { "有效" } else { "无效" });
134
135                if vip.vip_due_date > 0 {
136                    let due_date = chrono::DateTime::from_timestamp_millis(vip.vip_due_date as i64);
137                    if let Some(date) = due_date {
138                        tracing::info!("会员到期时间: {}", date.format("%Y-%m-%d %H:%M:%S"));
139                    }
140                }
141
142                tracing::info!("会员标签主题: {}", vip.label.label_theme);
143                tracing::info!("会员标签文案: {}", vip.label.text);
144                tracing::info!("昵称颜色: {}", vip.nickname_color);
145
146                // 3. 电视会员信息
147                tracing::info!("=== 电视会员信息 ===");
148                let tv = &data.user.tv;
149                let tv_type_text = match tv.tv_type {
150                    0 => "无电视会员",
151                    1 => "月电视会员",
152                    2 => "年度及以上电视会员",
153                    _ => "未知类型",
154                };
155                tracing::info!("电视会员类型: {} ({})", tv.tv_type, tv_type_text);
156                tracing::info!("电视会员状态: {}", if tv.status == 1 { "有效" } else { "无效" });
157
158                // 4. 头像框信息
159                tracing::info!("=== 头像框信息 ===");
160                // let pendant = &data.user.as_ref().avatar_pendant.unwrap();
161                // tracing::info!("头像框URL: {}", pendant.image);
162                // tracing::info!("动态头像框URL: {}", pendant.image_enhance);
163
164                // 5. 续费和通知信息
165                tracing::info!("=== 续费和通知信息 ===");
166                tracing::info!("自动续费状态: {}", data.user.is_auto_renew);
167                tracing::info!("电视会员自动续费: {}", data.user.is_tv_auto_renew);
168                tracing::info!("大会员提示: {}", data.user.vip_overdue_explain);
169                tracing::info!("电视会员提示: {}", data.user.tv_overdue_explain);
170
171                // 6. 钱包详细信息
172                tracing::info!("=== 钱包信息 ===");
173                let wallet = &data.wallet;
174                tracing::info!("B币券: {}", wallet.coupon);
175                tracing::info!("积分: {}", wallet.point);
176                tracing::info!("特权已领取: {}", wallet.privilege_received);
177
178                // 验证数据合理性
179                assert!(data.user.account.mid > 0, "用户mid应该大于0");
180                assert!(!data.user.account.name.is_empty(), "用户昵称不应为空");
181
182                tracing::info!("大会员中心信息综合测试通过");
183            }
184            Err(e) => {
185                if let BpiError::Api { code: -101, .. } = e {
186                    tracing::info!("账号未登录,无法获取详细信息,测试通过");
187                } else {
188                    tracing::error!("综合测试失败: {:?}", e);
189                    assert!(false, "综合测试失败");
190                }
191            }
192        }
193    }
194
195    #[tokio::test]
196    async fn test_time_calculation() {
197        tracing::info!("开始测试时间计算功能");
198
199        let bpi = BpiClient::new();
200        let resp = bpi.vip_center_info().await;
201
202        match resp {
203            Ok(response) => {
204                let user = &response.data.unwrap().user;
205
206                // 计算剩余时间
207                if user.surplus_seconds > 0 {
208                    let total_seconds = user.surplus_seconds;
209                    let days = total_seconds / (24 * 3600);
210                    let hours = (total_seconds % (24 * 3600)) / 3600;
211                    let minutes = (total_seconds % 3600) / 60;
212                    let seconds = total_seconds % 60;
213
214                    tracing::info!(
215                        "大会员剩余时间详细: {}天{}小时{}分钟{}秒",
216                        days,
217                        hours,
218                        minutes,
219                        seconds
220                    );
221
222                    if days > 0 {
223                        tracing::info!("剩余时间充足(超过1天)");
224                    } else if hours > 0 {
225                        tracing::info!("剩余时间不足1天但超过1小时");
226                    } else {
227                        tracing::info!("剩余时间不足1小时,即将到期");
228                    }
229                } else {
230                    tracing::info!("没有大会员或已过期");
231                }
232
233                // 计算持续开通时间
234                if user.vip_keep_time > 0 {
235                    let keep_days = user.vip_keep_time / (24 * 3600);
236                    let keep_years = keep_days / 365;
237                    let keep_months = (keep_days % 365) / 30;
238                    let remaining_days = keep_days % 30;
239
240                    tracing::info!(
241                        "持续开通大会员时间: {}年{}个月{}天",
242                        keep_years,
243                        keep_months,
244                        remaining_days
245                    );
246
247                    if keep_years > 0 {
248                        tracing::info!("长期忠实用户(超过1年)");
249                    } else if keep_days > 30 {
250                        tracing::info!("短期用户(超过1个月)");
251                    } else {
252                        tracing::info!("新用户(少于1个月)");
253                    }
254                } else {
255                    tracing::info!("没有持续开通记录");
256                }
257
258                // 分析会员到期时间
259                let vip = &user.vip;
260                if vip.vip_due_date > 0 {
261                    if
262                        let Some(due_date) = chrono::DateTime::from_timestamp_millis(
263                            vip.vip_due_date as i64
264                        )
265                    {
266                        let now = chrono::Utc::now();
267                        let duration = due_date.signed_duration_since(now);
268
269                        if duration.num_days() > 0 {
270                            tracing::info!("会员还有{}天到期", duration.num_days());
271                        } else if duration.num_hours() > 0 {
272                            tracing::info!("会员还有{}小时到期", duration.num_hours());
273                        } else if duration.num_minutes() > 0 {
274                            tracing::info!("会员还有{}分钟到期", duration.num_minutes());
275                        } else if duration.num_seconds() > 0 {
276                            tracing::info!("会员还有{}秒到期", duration.num_seconds());
277                        } else {
278                            tracing::info!("会员已过期");
279                        }
280
281                        tracing::info!(
282                            "会员到期时间: {}",
283                            due_date.format("%Y-%m-%d %H:%M:%S UTC")
284                        );
285                    }
286                }
287
288                tracing::info!("时间计算测试通过");
289                assert!(true);
290            }
291            Err(e) => {
292                if let BpiError::Api { code: -101, .. } = e {
293                    tracing::info!("账号未登录,无法进行时间计算测试");
294                    assert!(true);
295                } else {
296                    tracing::error!("时间计算测试失败: {:?}", e);
297                    assert!(false);
298                }
299            }
300        }
301    }
302}