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> {
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 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 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 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 tracing::info!("=== 头像框信息 ===");
160 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 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 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 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 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 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}