1use crate::models::{LevelInfo, Nameplate, Official, OfficialVerify, Pendant, Vip, VipLabel};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Deserialize, Serialize)]
9pub struct UserSpaceInfo {
10 pub mid: u64,
12 pub name: String,
14 pub sex: String,
16 pub face: String,
18 pub face_nft: u8,
20 pub face_nft_type: Option<u8>,
22 pub sign: String,
24 pub rank: u32,
26 pub level: u8,
28 pub jointime: u64,
30 pub moral: u64,
32 pub silence: u8,
34 pub coins: f64,
36 pub fans_badge: bool,
38 pub fans_medal: Option<FansMedal>,
40 pub official: Official,
42 pub vip: Vip,
44 pub pendant: Pendant,
46 pub nameplate: Nameplate,
48 pub user_honour_info: UserHonourInfo,
50 pub is_followed: bool,
52 pub top_photo: String,
54 pub theme: serde_json::Value,
56 pub sys_notice: SysNotice,
58 pub live_room: LiveRoom,
60 pub birthday: String,
62 pub school: School,
64 pub profession: Option<Profession>,
66 pub tags: Option<Vec<String>>,
68 pub series: Series,
70 pub is_senior_member: u8,
72 pub mcn_info: Option<serde_json::Value>,
74 pub gaia_res_type: Option<u8>,
76 pub gaia_data: Option<serde_json::Value>,
78 pub is_risk: bool,
80 pub elec: Elec,
82 pub contract: Contract,
84 pub certificate_show: Option<bool>,
86 pub name_render: Option<serde_json::Value>,
88}
89
90#[derive(Debug, Clone, Deserialize, Serialize)]
92pub struct FansMedal {
93 pub show: bool,
95 pub wear: bool,
97 pub medal: Option<Medal>,
99}
100
101#[derive(Debug, Clone, Deserialize, Serialize)]
103pub struct Medal {
104 pub level: u8,
106 pub guard_level: u8,
108 pub medal_name: String,
110}
111
112#[derive(Debug, Clone, Deserialize, Serialize)]
114pub struct UserHonourInfo {
115 pub mid: u64,
117 pub colour: Option<String>,
119 pub tags: Option<Vec<serde_json::Value>>,
121}
122
123#[derive(Debug, Clone, Deserialize, Serialize)]
125pub struct SysNotice {
126 pub id: Option<u32>,
128 pub content: Option<String>,
130 pub url: Option<String>,
132 pub notice_type: Option<u8>,
134 pub icon: Option<String>,
136 pub text_color: Option<String>,
138 pub bg_color: Option<String>,
140}
141
142#[derive(Debug, Clone, Deserialize, Serialize)]
144pub struct LiveRoom {
145 #[serde(rename = "roomStatus")]
147 pub room_status: u8,
148 #[serde(rename = "liveStatus")]
150 pub live_status: u8,
151 pub url: String,
153 pub title: String,
155 pub cover: String,
157 pub watched_show: WatchedShow,
159 pub roomid: u64,
161 #[serde(rename = "roundStatus")]
163 pub round_status: u8,
164 pub broadcast_type: u8,
166}
167
168#[derive(Debug, Clone, Deserialize, Serialize)]
170pub struct WatchedShow {
171 pub switch: bool,
173 pub num: u64,
175 pub text_small: String,
177 pub text_large: String,
179 pub icon: String,
181 pub icon_location: String,
183 pub icon_web: String,
185}
186
187#[derive(Debug, Clone, Deserialize, Serialize)]
189pub struct School {
190 pub name: String,
192}
193
194#[derive(Debug, Clone, Deserialize, Serialize)]
196pub struct Profession {
197 pub name: String,
199 pub department: String,
201 pub title: String,
203 pub is_show: u8,
205}
206
207#[derive(Debug, Clone, Deserialize, Serialize)]
209pub struct Series {
210 pub user_upgrade_status: u8,
212 pub show_upgrade_window: bool,
214}
215
216#[derive(Debug, Clone, Deserialize, Serialize)]
218pub struct Elec {
219 pub show_info: ShowInfo,
221}
222
223#[derive(Debug, Clone, Deserialize, Serialize)]
225pub struct ShowInfo {
226 pub show: bool,
228 pub state: i8,
230 pub title: String,
232 pub icon: String,
234 pub jump_url: String,
236}
237
238#[derive(Debug, Clone, Deserialize, Serialize)]
240pub struct Contract {
241 pub is_display: bool,
243 pub is_follow_display: bool,
245}
246
247#[derive(Debug, Clone, Deserialize, Serialize)]
249pub struct UserCardInfo {
250 pub card: Card,
252 pub following: bool,
254 pub archive_count: u32,
256 pub article_count: u32,
258 pub follower: u32,
260 pub like_num: u32,
262}
263
264#[derive(Debug, Clone, Deserialize, Serialize)]
266pub struct Card {
267 pub mid: String,
269 pub name: String,
271 pub sex: String,
273 pub face: String,
275 #[serde(rename = "DisplayRank")]
277 pub display_rank: String,
278 pub regtime: u64,
280 pub spacesta: i32,
282 pub birthday: String,
284 pub place: String,
286 pub description: String,
288 pub article: u32,
290 pub attentions: Vec<serde_json::Value>,
292 pub fans: u32,
294 pub friend: u32,
296 pub attention: u32,
298 pub sign: String,
300 pub level_info: LevelInfo,
302 pub pendant: Pendant,
304 pub nameplate: Nameplate,
306 #[serde(rename = "Official")]
308 pub official: Official,
309 pub official_verify: OfficialVerify,
311 pub vip: Vip,
313 pub space: Option<Space>,
315}
316
317#[derive(Debug, Clone, Deserialize, Serialize)]
319pub struct Space {
320 pub s_img: String,
322 pub l_img: String,
324}
325
326#[derive(Debug, Clone, Deserialize, Serialize)]
328pub struct UserCard {
329 pub mid: u64,
330 pub name: String,
331 pub face: String,
332 pub sign: String,
333 pub rank: i32,
334 pub level: i32,
335 pub silence: i32,
336}
337
338#[derive(Debug, Clone, Deserialize, Serialize)]
340pub struct UserInfo {
341 pub mid: u64,
342 pub name: String,
343 pub sign: String,
344 pub rank: i32,
345 pub level: i32,
346 pub silence: i32,
347
348 pub sex: Option<String>,
349 pub face: String,
350 pub vip: Option<UserVip>,
351 pub official: Option<UserOfficial>,
352 pub is_fake_account: Option<u32>,
353 pub expert_info: Option<serde_json::Value>,
354}
355
356#[derive(Debug, Clone, Deserialize, Serialize)]
358pub struct UserVip {
359 pub r#type: i32,
360 pub status: i32,
361 pub due_date: i64,
362 pub vip_pay_type: i32,
363 pub theme_type: i32,
364 pub label: Option<VipLabel>,
365}
366
367#[derive(Debug, Clone, Deserialize, Serialize)]
369pub struct UserOfficial {
370 #[serde(default)]
371 pub role: i32,
372 #[serde(default)]
373 pub title: String,
374 #[serde(default)]
375 pub desc: String,
376 #[serde(default)]
377 pub r#type: i32,
378}
379
380#[cfg(test)]
381mod tests {
382 use crate::BpiClient;
383 use crate::ids::Mid;
384 use crate::user::params::{
385 UserCardParams, UserCardPhoto, UserCardsParams, UserInfosParams, UserSpaceParams,
386 };
387
388 fn live_user_tests_enabled() -> bool {
389 std::env::var("BPI_LIVE_TEST").ok().as_deref() == Some("1")
390 }
391
392 #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
393 #[tokio::test]
394 async fn test_get_user_space_info() {
395 if !live_user_tests_enabled() {
396 return;
397 }
398
399 tracing::info!("开始测试获取用户空间详细信息");
400
401 let bpi = BpiClient::new().expect("client should build");
402 let mid = 2; tracing::info!("测试用户ID: {}", mid);
405
406 let resp = bpi
407 .user()
408 .space_info(UserSpaceParams::new(Mid::new(mid).expect("valid mid")))
409 .await;
410
411 match &resp {
412 Ok(data) => {
413 tracing::info!("用户昵称: {}", data.name);
414 tracing::info!("用户等级: {}", data.level);
415 tracing::info!(
416 "是否为会员: {}",
417 data.vip.as_ref().is_some_and(|vip| vip.status > 0)
418 );
419 tracing::info!("是否有粉丝勋章: {}", data.fans_badge);
420 }
421 Err(e) => {
422 tracing::error!("请求失败: {:?}", e);
423 }
424 }
425
426 assert!(resp.is_ok());
427 tracing::info!("测试完成");
428 }
429
430 #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
431 #[tokio::test]
432 async fn test_get_user_space_info_nonexistent() {
433 if !live_user_tests_enabled() {
434 return;
435 }
436
437 tracing::info!("开始测试获取不存在用户的空间详细信息");
438
439 let bpi = BpiClient::new().expect("client should build");
440 let mid = 0; tracing::info!("测试用户ID: {}", mid);
443
444 let resp = bpi
445 .user()
446 .space_info(UserSpaceParams::new(Mid::new(mid).expect("valid mid")))
447 .await;
448
449 match &resp {
450 Ok(data) => {
451 tracing::info!("意外返回用户: {}", data.name);
452 }
453 Err(e) => {
454 tracing::error!("请求失败: {:?}", e);
455 }
456 }
457
458 tracing::info!("测试完成");
459 }
460
461 #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
462 #[tokio::test]
463 async fn test_get_user_card_info() {
464 if !live_user_tests_enabled() {
465 return;
466 }
467
468 tracing::info!("开始测试获取用户名片信息");
469
470 let bpi = BpiClient::new().expect("client should build");
471 let mid = 2; tracing::info!("测试用户ID: {}", mid);
474
475 let resp = bpi
476 .user()
477 .card(UserCardParams::new(Mid::new(mid).expect("valid mid")))
478 .await;
479
480 match &resp {
481 Ok(data) => {
482 tracing::info!("用户昵称: {}", data.card.name);
483 tracing::info!("用户性别: {:?}", data.card.sex);
484 tracing::info!("是否关注: {}", data.following);
485 tracing::info!("稿件数: {}", data.archive_count);
486 tracing::info!("粉丝数: {}", data.follower);
487 tracing::info!("点赞数: {}", data.like_num);
488 tracing::info!("用户签名: {}", data.card.sign);
489 }
490 Err(e) => {
491 tracing::error!("请求失败: {:?}", e);
492 }
493 }
494
495 assert!(resp.is_ok());
496 tracing::info!("测试完成");
497 }
498
499 #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
500 #[tokio::test]
501 async fn test_get_user_card_info_with_photo() {
502 if !live_user_tests_enabled() {
503 return;
504 }
505
506 tracing::info!("开始测试获取用户名片信息(包含主页头图)");
507
508 let bpi = BpiClient::new().expect("client should build");
509 let mid = 2; tracing::info!("测试用户ID: {}", mid);
512
513 let resp = bpi
514 .user()
515 .card(
516 UserCardParams::new(Mid::new(mid).expect("valid mid"))
517 .with_photo(UserCardPhoto::Include),
518 )
519 .await;
520
521 match &resp {
522 Ok(data) => {
523 tracing::info!("用户昵称: {}", data.card.name);
524 tracing::info!("粉丝数: {}", data.card.fans);
525 }
526 Err(e) => {
527 tracing::error!("请求失败: {:?}", e);
528 }
529 }
530
531 assert!(resp.is_ok());
532 tracing::info!("测试完成");
533 }
534
535 #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
536 #[tokio::test]
537 async fn test_get_user_card_info_without_photo() {
538 if !live_user_tests_enabled() {
539 return;
540 }
541
542 tracing::info!("开始测试获取用户名片信息(不包含主页头图)");
543
544 let bpi = BpiClient::new().expect("client should build");
545 let mid = 123456; tracing::info!("测试用户ID: {}", mid);
548
549 let resp = bpi
550 .user()
551 .card(
552 UserCardParams::new(Mid::new(mid).expect("valid mid"))
553 .with_photo(UserCardPhoto::Exclude),
554 )
555 .await;
556
557 match &resp {
558 Ok(data) => {
559 tracing::info!("用户昵称: {}", data.card.name);
560 tracing::info!("粉丝数: {}", data.card.fans);
561 tracing::info!("关注数: {}", data.card.attention);
562 }
563 Err(e) => {
564 tracing::error!("请求失败: {:?}", e);
565 }
566 }
567
568 assert!(resp.is_ok());
569 tracing::info!("测试完成");
570 }
571
572 #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
573 #[tokio::test]
574 async fn test_get_user_card_info_invalid_user() {
575 if !live_user_tests_enabled() {
576 return;
577 }
578
579 tracing::info!("开始测试获取不存在用户的名片信息");
580
581 let bpi = BpiClient::new().expect("client should build");
582 let mid = 0; tracing::info!("测试用户ID: {}", mid);
585
586 let resp = bpi
587 .user()
588 .card(UserCardParams::new(Mid::new(mid).expect("valid mid")))
589 .await;
590
591 match &resp {
592 Ok(data) => {
593 tracing::info!("意外返回用户: {}", data.card.name);
594 }
595 Err(e) => {
596 tracing::error!("请求失败: {:?}", e);
597 }
598 }
599
600 tracing::info!("测试完成");
601 }
602
603 #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
604 #[tokio::test]
605 async fn test_get_user_card_info_banned_user() {
606 if !live_user_tests_enabled() {
607 return;
608 }
609
610 tracing::info!("开始测试获取被封禁用户的名片信息");
611
612 let bpi = BpiClient::new().expect("client should build");
613 let mid = 999999999; tracing::info!("测试用户ID: {}", mid);
616
617 let resp = bpi
618 .user()
619 .card(UserCardParams::new(Mid::new(mid).expect("valid mid")))
620 .await;
621
622 match &resp {
623 Ok(data) => {
624 tracing::info!("请求成功,用户昵称: {}", data.card.name);
625 }
626 Err(e) => {
627 tracing::error!("请求失败: {:?}", e);
628 }
629 }
630
631 tracing::info!("测试完成");
632 }
633
634 #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
635 #[tokio::test]
636 async fn test_user_cards_and_infos() {
637 if !live_user_tests_enabled() {
638 return;
639 }
640
641 let bpi = BpiClient::new().expect("client should build");
642
643 let cards = bpi
645 .user()
646 .cards(
647 UserCardsParams::new([
648 Mid::new(2).expect("valid mid"),
649 Mid::new(3).expect("valid mid"),
650 ])
651 .expect("valid params"),
652 )
653 .await
654 .unwrap();
655 tracing::info!("用户卡片: {:?}", cards);
656
657 let infos = bpi
659 .user()
660 .infos(
661 UserInfosParams::new([
662 Mid::new(2).expect("valid mid"),
663 Mid::new(3).expect("valid mid"),
664 ])
665 .expect("valid params"),
666 )
667 .await
668 .unwrap();
669 tracing::info!("用户详细信息: {:?}", infos);
670 }
671}