use crate::models::{LevelInfo, Nameplate, Official, OfficialVerify, Pendant, Vip, VipLabel};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UserSpaceInfo {
pub mid: u64,
pub name: String,
pub sex: String,
pub face: String,
pub face_nft: u8,
pub face_nft_type: Option<u8>,
pub sign: String,
pub rank: u32,
pub level: u8,
pub jointime: u64,
pub moral: u64,
pub silence: u8,
pub coins: f64,
pub fans_badge: bool,
pub fans_medal: Option<FansMedal>,
pub official: Official,
pub vip: Vip,
pub pendant: Pendant,
pub nameplate: Nameplate,
pub user_honour_info: UserHonourInfo,
pub is_followed: bool,
pub top_photo: String,
pub theme: serde_json::Value,
pub sys_notice: SysNotice,
pub live_room: LiveRoom,
pub birthday: String,
pub school: School,
pub profession: Option<Profession>,
pub tags: Option<Vec<String>>,
pub series: Series,
pub is_senior_member: u8,
pub mcn_info: Option<serde_json::Value>,
pub gaia_res_type: Option<u8>,
pub gaia_data: Option<serde_json::Value>,
pub is_risk: bool,
pub elec: Elec,
pub contract: Contract,
pub certificate_show: Option<bool>,
pub name_render: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FansMedal {
pub show: bool,
pub wear: bool,
pub medal: Option<Medal>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Medal {
pub level: u8,
pub guard_level: u8,
pub medal_name: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UserHonourInfo {
pub mid: u64,
pub colour: Option<String>,
pub tags: Option<Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SysNotice {
pub id: Option<u32>,
pub content: Option<String>,
pub url: Option<String>,
pub notice_type: Option<u8>,
pub icon: Option<String>,
pub text_color: Option<String>,
pub bg_color: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LiveRoom {
#[serde(rename = "roomStatus")]
pub room_status: u8,
#[serde(rename = "liveStatus")]
pub live_status: u8,
pub url: String,
pub title: String,
pub cover: String,
pub watched_show: WatchedShow,
pub roomid: u64,
#[serde(rename = "roundStatus")]
pub round_status: u8,
pub broadcast_type: u8,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct WatchedShow {
pub switch: bool,
pub num: u64,
pub text_small: String,
pub text_large: String,
pub icon: String,
pub icon_location: String,
pub icon_web: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct School {
pub name: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Profession {
pub name: String,
pub department: String,
pub title: String,
pub is_show: u8,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Series {
pub user_upgrade_status: u8,
pub show_upgrade_window: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Elec {
pub show_info: ShowInfo,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ShowInfo {
pub show: bool,
pub state: i8,
pub title: String,
pub icon: String,
pub jump_url: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Contract {
pub is_display: bool,
pub is_follow_display: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UserCardInfo {
pub card: Card,
pub following: bool,
pub archive_count: u32,
pub article_count: u32,
pub follower: u32,
pub like_num: u32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Card {
pub mid: String,
pub name: String,
pub sex: String,
pub face: String,
#[serde(rename = "DisplayRank")]
pub display_rank: String,
pub regtime: u64,
pub spacesta: i32,
pub birthday: String,
pub place: String,
pub description: String,
pub article: u32,
pub attentions: Vec<serde_json::Value>,
pub fans: u32,
pub friend: u32,
pub attention: u32,
pub sign: String,
pub level_info: LevelInfo,
pub pendant: Pendant,
pub nameplate: Nameplate,
#[serde(rename = "Official")]
pub official: Official,
pub official_verify: OfficialVerify,
pub vip: Vip,
pub space: Option<Space>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Space {
pub s_img: String,
pub l_img: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UserCard {
pub mid: u64,
pub name: String,
pub face: String,
pub sign: String,
pub rank: i32,
pub level: i32,
pub silence: i32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UserInfo {
pub mid: u64,
pub name: String,
pub sign: String,
pub rank: i32,
pub level: i32,
pub silence: i32,
pub sex: Option<String>,
pub face: String,
pub vip: Option<UserVip>,
pub official: Option<UserOfficial>,
pub is_fake_account: Option<u32>,
pub expert_info: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UserVip {
pub r#type: i32,
pub status: i32,
pub due_date: i64,
pub vip_pay_type: i32,
pub theme_type: i32,
pub label: Option<VipLabel>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UserOfficial {
#[serde(default)]
pub role: i32,
#[serde(default)]
pub title: String,
#[serde(default)]
pub desc: String,
#[serde(default)]
pub r#type: i32,
}
#[cfg(test)]
mod tests {
use crate::BpiClient;
use crate::ids::Mid;
use crate::user::params::{
UserCardParams, UserCardPhoto, UserCardsParams, UserInfosParams, UserSpaceParams,
};
fn live_user_tests_enabled() -> bool {
std::env::var("BPI_LIVE_TEST").ok().as_deref() == Some("1")
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_user_space_info() {
if !live_user_tests_enabled() {
return;
}
tracing::info!("开始测试获取用户空间详细信息");
let bpi = BpiClient::new().expect("client should build");
let mid = 2;
tracing::info!("测试用户ID: {}", mid);
let resp = bpi
.user()
.space_info(UserSpaceParams::new(Mid::new(mid).expect("valid mid")))
.await;
match &resp {
Ok(data) => {
tracing::info!("用户昵称: {}", data.name);
tracing::info!("用户等级: {}", data.level);
tracing::info!(
"是否为会员: {}",
data.vip.as_ref().is_some_and(|vip| vip.status > 0)
);
tracing::info!("是否有粉丝勋章: {}", data.fans_badge);
}
Err(e) => {
tracing::error!("请求失败: {:?}", e);
}
}
assert!(resp.is_ok());
tracing::info!("测试完成");
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_user_space_info_nonexistent() {
if !live_user_tests_enabled() {
return;
}
tracing::info!("开始测试获取不存在用户的空间详细信息");
let bpi = BpiClient::new().expect("client should build");
let mid = 0;
tracing::info!("测试用户ID: {}", mid);
let resp = bpi
.user()
.space_info(UserSpaceParams::new(Mid::new(mid).expect("valid mid")))
.await;
match &resp {
Ok(data) => {
tracing::info!("意外返回用户: {}", data.name);
}
Err(e) => {
tracing::error!("请求失败: {:?}", e);
}
}
tracing::info!("测试完成");
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_user_card_info() {
if !live_user_tests_enabled() {
return;
}
tracing::info!("开始测试获取用户名片信息");
let bpi = BpiClient::new().expect("client should build");
let mid = 2;
tracing::info!("测试用户ID: {}", mid);
let resp = bpi
.user()
.card(UserCardParams::new(Mid::new(mid).expect("valid mid")))
.await;
match &resp {
Ok(data) => {
tracing::info!("用户昵称: {}", data.card.name);
tracing::info!("用户性别: {:?}", data.card.sex);
tracing::info!("是否关注: {}", data.following);
tracing::info!("稿件数: {}", data.archive_count);
tracing::info!("粉丝数: {}", data.follower);
tracing::info!("点赞数: {}", data.like_num);
tracing::info!("用户签名: {}", data.card.sign);
}
Err(e) => {
tracing::error!("请求失败: {:?}", e);
}
}
assert!(resp.is_ok());
tracing::info!("测试完成");
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_user_card_info_with_photo() {
if !live_user_tests_enabled() {
return;
}
tracing::info!("开始测试获取用户名片信息(包含主页头图)");
let bpi = BpiClient::new().expect("client should build");
let mid = 2;
tracing::info!("测试用户ID: {}", mid);
let resp = bpi
.user()
.card(
UserCardParams::new(Mid::new(mid).expect("valid mid"))
.with_photo(UserCardPhoto::Include),
)
.await;
match &resp {
Ok(data) => {
tracing::info!("用户昵称: {}", data.card.name);
tracing::info!("粉丝数: {}", data.card.fans);
}
Err(e) => {
tracing::error!("请求失败: {:?}", e);
}
}
assert!(resp.is_ok());
tracing::info!("测试完成");
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_user_card_info_without_photo() {
if !live_user_tests_enabled() {
return;
}
tracing::info!("开始测试获取用户名片信息(不包含主页头图)");
let bpi = BpiClient::new().expect("client should build");
let mid = 123456;
tracing::info!("测试用户ID: {}", mid);
let resp = bpi
.user()
.card(
UserCardParams::new(Mid::new(mid).expect("valid mid"))
.with_photo(UserCardPhoto::Exclude),
)
.await;
match &resp {
Ok(data) => {
tracing::info!("用户昵称: {}", data.card.name);
tracing::info!("粉丝数: {}", data.card.fans);
tracing::info!("关注数: {}", data.card.attention);
}
Err(e) => {
tracing::error!("请求失败: {:?}", e);
}
}
assert!(resp.is_ok());
tracing::info!("测试完成");
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_user_card_info_invalid_user() {
if !live_user_tests_enabled() {
return;
}
tracing::info!("开始测试获取不存在用户的名片信息");
let bpi = BpiClient::new().expect("client should build");
let mid = 0;
tracing::info!("测试用户ID: {}", mid);
let resp = bpi
.user()
.card(UserCardParams::new(Mid::new(mid).expect("valid mid")))
.await;
match &resp {
Ok(data) => {
tracing::info!("意外返回用户: {}", data.card.name);
}
Err(e) => {
tracing::error!("请求失败: {:?}", e);
}
}
tracing::info!("测试完成");
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_user_card_info_banned_user() {
if !live_user_tests_enabled() {
return;
}
tracing::info!("开始测试获取被封禁用户的名片信息");
let bpi = BpiClient::new().expect("client should build");
let mid = 999999999;
tracing::info!("测试用户ID: {}", mid);
let resp = bpi
.user()
.card(UserCardParams::new(Mid::new(mid).expect("valid mid")))
.await;
match &resp {
Ok(data) => {
tracing::info!("请求成功,用户昵称: {}", data.card.name);
}
Err(e) => {
tracing::error!("请求失败: {:?}", e);
}
}
tracing::info!("测试完成");
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_user_cards_and_infos() {
if !live_user_tests_enabled() {
return;
}
let bpi = BpiClient::new().expect("client should build");
let cards = bpi
.user()
.cards(
UserCardsParams::new([
Mid::new(2).expect("valid mid"),
Mid::new(3).expect("valid mid"),
])
.expect("valid params"),
)
.await
.unwrap();
tracing::info!("用户卡片: {:?}", cards);
let infos = bpi
.user()
.infos(
UserInfosParams::new([
Mid::new(2).expect("valid mid"),
Mid::new(3).expect("valid mid"),
])
.expect("valid params"),
)
.await
.unwrap();
tracing::info!("用户详细信息: {:?}", infos);
}
}