use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RelationStatResponseData {
pub mid: u64,
pub following: u64,
pub whisper: u64,
pub black: u64,
pub follower: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpstatArchive {
pub view: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpstatArticle {
pub view: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpstatResponseData {
pub archive: UpstatArchive,
pub article: UpstatArticle,
pub likes: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct NavnumChannel {
pub master: u64,
pub guest: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct NavnumFavourite {
pub master: u64,
pub guest: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct NavnumResponseData {
pub video: u64,
pub bangumi: u64,
pub cinema: u64,
pub channel: NavnumChannel,
pub favourite: NavnumFavourite,
pub tag: u64,
pub article: u64,
pub playlist: u64,
pub album: u64,
pub audio: u64,
pub pugv: u64,
pub opus: u64,
#[serde(rename = "season_num")]
pub season_num: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AlbumCountResponseData {
pub all_count: u64,
pub draw_count: u64,
pub photo_count: u64,
pub daily_count: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ids::Mid;
use crate::probe::contract::HttpMethod;
use crate::probe::endpoint_contract::EndpointContract;
use crate::user::params::{
UserAlbumCountParams, UserNavStatParams, UserRelationStatParams, UserUpStatParams,
};
use crate::{ApiEnvelope, BpiClient, BpiError, BpiResult};
use tracing::info;
const TEST_MID: u64 = 332704117;
const TEST_UP_MID: u64 = 456664753;
const TEST_NAV_MID: u64 = 645769214;
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_relation_stat() -> Result<(), BpiError> {
if std::env::var_os("BPI_LIVE_TEST").is_none() {
return Ok(());
}
let bpi = BpiClient::new().expect("client should build");
let data = bpi
.user()
.relation_stat(UserRelationStatParams::new(Mid::new(TEST_MID)?))
.await?;
info!("关系状态数: {:?}", data);
assert_eq!(data.mid.get(), TEST_MID);
Ok(())
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_up_stat() -> Result<(), BpiError> {
if std::env::var_os("BPI_LIVE_TEST").is_none() {
return Ok(());
}
let bpi = BpiClient::new().expect("client should build");
let data = bpi
.user()
.up_stat(UserUpStatParams::new(Mid::new(TEST_UP_MID)?))
.await?;
info!("UP主状态数: {:?}", data);
assert!(data.likes > 0);
Ok(())
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_nav_num() -> Result<(), BpiError> {
if std::env::var_os("BPI_LIVE_TEST").is_none() {
return Ok(());
}
let bpi = BpiClient::new().expect("client should build");
let data = bpi
.user()
.nav_stat(UserNavStatParams::new(Mid::new(TEST_NAV_MID)?))
.await?;
info!("用户导航栏状态数: {:?}", data);
assert!(data.video > 0);
Ok(())
}
#[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
#[tokio::test]
async fn test_get_album_count() -> Result<(), BpiError> {
if std::env::var_os("BPI_LIVE_TEST").is_none() {
return Ok(());
}
let bpi = BpiClient::new().expect("client should build");
let data = bpi
.user()
.album_count(UserAlbumCountParams::new(Mid::new(TEST_NAV_MID)?))
.await?;
info!("相簿投稿数: {:?}", data);
assert!(data.all_count > 0);
Ok(())
}
fn public_read_contract(endpoint: &str) -> BpiResult<EndpointContract> {
let bytes: &[u8] = match endpoint {
"album-count" => {
include_bytes!("../../tests/contracts/user/public-read/album-count/contract.json")
}
"nav-stat" => {
include_bytes!("../../tests/contracts/user/public-read/nav-stat/contract.json")
}
"relation-stat" => {
include_bytes!("../../tests/contracts/user/public-read/relation-stat/contract.json")
}
"up-stat" => {
include_bytes!("../../tests/contracts/user/public-read/up-stat/contract.json")
}
_ => {
return Err(BpiError::invalid_parameter(
"endpoint",
"unknown user status contract",
));
}
};
EndpointContract::from_slice(bytes)
}
#[test]
fn legacy_user_status_contracts_match_endpoint_requests() -> BpiResult<()> {
let relation = public_read_contract("relation-stat")?;
assert_eq!(relation.name, "user.relation_stat");
assert_eq!(relation.request.method, HttpMethod::Get);
assert_eq!(
relation.request.url.as_str(),
"https://api.bilibili.com/x/relation/stat"
);
assert_eq!(
relation.request.query.get("vmid").map(String::as_str),
Some("2")
);
let up_stat = public_read_contract("up-stat")?;
assert_eq!(up_stat.name, "user.up_stat");
assert_eq!(up_stat.request.method, HttpMethod::Get);
assert_eq!(
up_stat.request.url.as_str(),
"https://api.bilibili.com/x/space/upstat"
);
assert_eq!(
up_stat.request.query.get("mid").map(String::as_str),
Some("456664753")
);
let nav = public_read_contract("nav-stat")?;
assert_eq!(nav.name, "user.nav_stat");
assert_eq!(
nav.request.url.as_str(),
"https://api.bilibili.com/x/space/navnum"
);
assert_eq!(nav.request.query.get("mid").map(String::as_str), Some("2"));
let album = public_read_contract("album-count")?;
assert_eq!(album.name, "user.album_count");
assert_eq!(
album.request.url.as_str(),
"https://api.vc.bilibili.com/link_draw/v1/doc/upload_count"
);
assert_eq!(
album.request.query.get("uid").map(String::as_str),
Some("2")
);
Ok(())
}
#[test]
fn legacy_user_status_fixtures_parse_promoted_contract_models() -> BpiResult<()> {
let relation = ApiEnvelope::<RelationStatResponseData>::from_slice(include_bytes!(
"../../tests/contracts/user/public-read/relation-stat/responses/success.json"
))?
.into_payload()?;
assert_eq!(relation.mid, 2);
let up_stat = ApiEnvelope::<UpstatResponseData>::from_slice(include_bytes!(
"../../tests/contracts/user/public-read/up-stat/responses/success.json"
))?
.into_payload()?;
assert!(up_stat.archive.view >= up_stat.article.view);
let nav = ApiEnvelope::<NavnumResponseData>::from_slice(include_bytes!(
"../../tests/contracts/user/public-read/nav-stat/responses/success.json"
))?
.into_payload()?;
let _total_content = nav.video + nav.article + nav.album + nav.audio + nav.opus;
let album = ApiEnvelope::<AlbumCountResponseData>::from_slice(include_bytes!(
"../../tests/contracts/user/public-read/album-count/responses/success.json"
))?
.into_payload()?;
assert_eq!(
album.all_count,
album.draw_count + album.photo_count + album.daily_count
);
Ok(())
}
}