Skip to main content

bpi_rs/live/
info.rs

1use serde::{Deserialize, Serialize};
2
3// ================= 数据结构 =================
4
5#[derive(Debug, Serialize, Clone, Deserialize)]
6pub struct RoomPendantFrame {
7    /// 名称
8    pub name: String,
9    /// 值
10    pub value: String,
11    /// 位置
12    pub position: i32,
13    /// 描述
14    pub desc: String,
15    /// 分区
16    pub area: i32,
17    /// 旧分区
18    pub area_old: i32,
19    /// 背景色
20    pub bg_color: String,
21    /// 背景图
22    pub bg_pic: String,
23    /// 是否旧分区号
24    pub use_old_area: bool,
25}
26
27#[derive(Debug, Serialize, Clone, Deserialize)]
28pub struct RoomPendantBadge {
29    /// 类型
30    pub name: String,
31    /// 位置
32    pub position: i32,
33    /// 值
34    pub value: String,
35    /// 描述
36    pub desc: String,
37}
38
39#[derive(Debug, Serialize, Clone, Deserialize)]
40pub struct RoomPendants {
41    /// 头像框
42    pub frame: RoomPendantFrame,
43    /// 手机版头像框
44    pub mobile_frame: Option<RoomPendantFrame>,
45    /// 大v
46    pub badge: RoomPendantBadge,
47    /// 手机版大v
48    pub mobile_badge: Option<RoomPendantBadge>,
49}
50
51#[derive(Debug, Serialize, Clone, Deserialize)]
52pub struct RoomStudioInfo {
53    // 根据实际情况添加字段
54    #[serde(flatten)]
55    pub extra: serde_json::Value,
56}
57
58#[derive(Debug, Serialize, Clone, Deserialize)]
59pub struct RoomInfoData {
60    /// 主播mid
61    pub uid: i64,
62    /// 直播间长号
63    pub room_id: i64,
64    /// 直播间短号
65    pub short_id: i64,
66    /// 关注数量
67    pub attention: i64,
68    /// 观看人数
69    pub online: i64,
70    /// 是否竖屏
71    pub is_portrait: bool,
72    /// 描述
73    pub description: String,
74    /// 直播状态
75    pub live_status: i32,
76    /// 分区id
77    pub area_id: i32,
78    /// 父分区id
79    pub parent_area_id: i32,
80    /// 父分区名称
81    pub parent_area_name: String,
82    /// 旧版分区id
83    pub old_area_id: i32,
84    /// 背景图片链接
85    pub background: String,
86    /// 标题
87    pub title: String,
88    /// 封面
89    pub user_cover: String,
90    /// 关键帧
91    pub keyframe: String,
92    /// 直播开始时间
93    pub live_time: String,
94    /// 标签
95    pub tags: String,
96    /// 禁言状态
97    pub room_silent_type: String,
98    /// 禁言等级
99    pub room_silent_level: i32,
100    /// 禁言时间
101    pub room_silent_second: i64,
102    /// 分区名称
103    pub area_name: String,
104    /// 热词
105    pub hot_words: Vec<String>,
106    /// 热词状态
107    pub hot_words_status: i32,
108    /// 头像框\大v
109    pub new_pendants: RoomPendants,
110    /// pk状态
111    pub pk_status: i32,
112    /// pk id
113    pub pk_id: i64,
114    /// 允许更改分区时间
115    pub allow_change_area_time: i64,
116    /// 允许上传封面时间
117    pub allow_upload_cover_time: i64,
118    /// 工作室信息
119    pub studio_info: Option<RoomStudioInfo>,
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::probe::contract::HttpMethod;
126    use crate::probe::endpoint_contract::EndpointContract;
127    use crate::{ApiEnvelope, BpiClient, BpiError, BpiResult};
128
129    fn contract() -> BpiResult<EndpointContract> {
130        EndpointContract::from_slice(include_bytes!(
131            "../../tests/contracts/live/public-core/room-info/contract.json"
132        ))
133    }
134
135    #[ignore = "legacy live API test; requires explicit BPI_LIVE_TEST review"]
136    #[tokio::test]
137    async fn test_get_room_info() -> Result<(), Box<BpiError>> {
138        let bpi = BpiClient::new().expect("client should build");
139        let data = bpi.live().room_info(23174842).await?;
140
141        assert_eq!(data.room_id, 23174842);
142        Ok(())
143    }
144
145    #[test]
146    fn live_room_info_contract_matches_endpoint_request() -> BpiResult<()> {
147        let contract = contract()?;
148
149        assert_eq!(contract.name, "live.room_info");
150        assert_eq!(contract.request.method, HttpMethod::Get);
151        assert_eq!(
152            contract.request.url.as_str(),
153            "https://api.live.bilibili.com/room/v1/Room/get_info"
154        );
155        assert_eq!(
156            contract.request.query.get("room_id").map(String::as_str),
157            Some("23174842")
158        );
159        assert_eq!(contract.cases.len(), 3);
160        assert_eq!(
161            contract.cases[0].response.rust_model.as_deref(),
162            Some("RoomInfoData")
163        );
164        Ok(())
165    }
166
167    #[test]
168    fn live_room_info_response_fixture_parses_declared_model() -> BpiResult<()> {
169        let payload = ApiEnvelope::<RoomInfoData>::from_slice(include_bytes!(
170            "../../tests/contracts/live/public-core/room-info/responses/success.json"
171        ))?
172        .into_payload()?;
173
174        assert_eq!(payload.room_id, 23174842);
175        assert_eq!(payload.live_status, 1);
176        Ok(())
177    }
178
179    fn local_probe_body(profile: &str) -> Option<serde_json::Value> {
180        let path =
181            format!("target/bpi-probe-runs/live/public-core/room-info/{profile}.response.json");
182        let bytes = std::fs::read(path).ok()?;
183        let value: serde_json::Value = serde_json::from_slice(&bytes).ok()?;
184        value
185            .get("response")
186            .and_then(|response| response.get("body"))
187            .cloned()
188    }
189
190    #[test]
191    fn live_room_info_model_matches_local_probe_outputs_when_available() -> BpiResult<()> {
192        for profile in ["anonymous", "normal", "vip"] {
193            if let Some(body) = local_probe_body(profile) {
194                let payload =
195                    serde_json::from_value::<ApiEnvelope<RoomInfoData>>(body)?.into_payload()?;
196                assert_eq!(payload.room_id, 23174842);
197            }
198        }
199        Ok(())
200    }
201}