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