Skip to main content

fishpi_sdk/model/
chatroom.rs

1use crate::impl_str_enum;
2use crate::model::user::{Metal, to_metal};
3use crate::utils::error::Error;
4use serde::{Deserialize, Deserializer};
5use serde_json::Value;
6use std::str::FromStr;
7
8#[derive(Clone, Debug)]
9pub enum ClientType {
10    /// 网页端
11    Web,
12    /// PC 端
13    PC,
14    /// 移动端聊天室
15    Mobile,
16    /// Windows 客户端
17    Windows,
18    /// macOS 客户端
19    MacOs,
20    /// Linux 客户端
21    Linux,
22    /// iOS 客户端
23    Ios,
24    /// Android 客户端
25    Android,
26    /// IDEA 插件
27    Idea,
28    /// Chrome 插件
29    Chrome,
30    /// Edge 插件
31    Edge,
32    /// VSCode 插件
33    VSCode,
34    /// Python 客户端
35    Python,
36    /// Golang 客户端
37    Golang,
38    /// Rust 客户端
39    Rust,
40    /// Harmony App
41    Harmony,
42    /// CLI 工具
43    Cli,
44    /// 鸽机器人
45    Bird,
46    /// 小冰机器人
47    IceNet,
48    /// 凌机器人
49    ElvesOnline,
50    /// 其他插件
51    Other,
52}
53
54pub enum ChatContentType {
55    Markdown,
56    Html,
57}
58
59/// chatroom get 接口获取 oId 的相关消息类型
60#[repr(u8)]
61pub enum ChatRoomMessageMode {
62    ///前后消息
63    Context = 0,
64    /// 前面的消息
65    Before = 1,
66    /// 后面的消息
67    After = 2,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Hash)]
71pub enum ChatRoomMessageType {
72    /// 在线用户
73    Online,
74    /// 话题修改
75    DiscussChanged,
76    /// 消息撤回
77    Revoke,
78    /// 消息
79    Msg,
80    /// 红包
81    RedPacket,
82    /// 红包状态
83    RedPacketStatus,
84    /// 弹幕
85    Barrager,
86    /// 进出场消息
87    Custom,
88}
89
90#[derive(Clone, Debug)]
91pub struct CustomMsg {
92    pub message: String,
93}
94pub struct DiscussMsg;
95
96#[derive(Clone, Debug)]
97pub struct RevokeMsg {
98    pub msg: String,
99}
100
101#[derive(Clone, Debug)]
102pub struct BarragerCost {
103    pub cost: u32,
104    pub unit: String,
105}
106
107// /// 聊天天气消息
108// pub struct WeatherMessage {
109//     city: String,
110//     description: String,
111//     data: WeatherData,
112// }
113
114/// 聊天天气消息详情
115pub struct WeatherData {
116    pub date: String,
117    pub code: WeatherCode,
118    pub min: String,
119    pub max: String,
120}
121
122/// 消息来源
123pub struct ChatRoomSource {
124    pub client: String,
125    pub version: String,
126}
127
128#[derive(Clone, Debug)]
129pub enum WeatherCode {
130    ClearDay,
131    ClearNight,
132    Cloudy,
133    Dust,
134    Fog,
135    HeavyHaze,
136    HeavyRain,
137    HeavySnow,
138    LightHaze,
139    LightRain,
140    LightSnow,
141    ModerateHaze,
142    ModerateRain,
143    ModerateSnow,
144    PartlyCloudyDay,
145    PartlyCloudyNight,
146    Sand,
147    StormRain,
148    StormSnow,
149    Wind,
150}
151
152/// 聊天消息
153#[derive(Clone, Debug)]
154#[allow(non_snake_case)]
155pub struct ChatRoomMsg<T = Value> {
156    pub r#type: ChatRoomMessageType,
157    pub oId: String,
158    pub time: String,
159    pub userOId: String,
160    pub userName: String,
161    pub userNickname: String,
162    pub userAvatarURL: String,
163    pub sysMetal: Vec<Metal>,
164    pub content: T,
165    pub md: String,
166    pub client: String,
167    pub via: ClientType,
168}
169
170#[derive(Clone, Debug, Deserialize)]
171#[allow(non_snake_case)]
172pub struct BarragerMsg {
173    /// 用户名
174    pub userName: String,
175    /// 用户昵称
176    pub userNickname: String,
177    /// 弹幕消息
178    pub barragerContent: String,
179    /// 弹幕颜色
180    pub barragerColor: String,
181    /// 用户头像地址
182    pub userAvatarURL: String,
183    /// 头像地址20x20
184    pub userAvatarURL200: String,
185    /// 头像地址48x48
186    pub userAvatarURL48: String,
187    /// 头像地址100x100
188    pub userAvatarURL210: String,
189}
190
191/// 在线用户信息
192#[derive(Clone, Debug)]
193#[allow(non_snake_case)]
194pub struct OnlineInfo {
195    /// 用户首页
196    pub homePage: String,
197    /// 用户头像
198    pub userAvatarURL: String,
199    /// 用户名
200    pub userName: String,
201}
202
203impl_str_enum!(ClientType {
204    Web => "Web",
205    PC => "PC",
206    Mobile => "Mobile",
207    Windows => "Windows",
208    MacOs => "macOS",
209    Linux => "Linux",
210    Ios => "iOS",
211    Android => "Android",
212    Idea => "IDEA",
213    Chrome => "Chrome",
214    Edge => "Edge",
215    VSCode => "VSCode",
216    Python => "Python",
217    Golang => "Golang",
218    Rust => "Rust",
219    Harmony => "Harmony",
220    Cli => "CLI",
221    Bird => "Bird",
222    IceNet => "IceNet",
223    ElvesOnline => "ElvesOnline",
224    Other => "Other",
225});
226
227impl_str_enum!(ChatContentType {
228    Markdown => "Markdown",
229    Html => "Html",
230});
231
232impl_str_enum!(ChatRoomMessageType {
233    Online => "online",
234    DiscussChanged => "discussChanged",
235    Revoke => "revoke",
236    Msg => "msg",
237    RedPacket => "redPacket",
238    RedPacketStatus => "redPacketStatus",
239    Barrager => "barrager",
240    Custom => "customMessage",
241});
242
243impl_str_enum!(WeatherCode {
244    ClearDay => "CLEAR_DAY",
245    ClearNight => "CLEAR_NIGHT",
246    Cloudy => "CLOUDY",
247    Dust => "DUST",
248    Fog => "FOG",
249    HeavyHaze => "HEAVY_HAZE",
250    HeavyRain => "HEAVY_RAIN",
251    HeavySnow => "HEAVY_SNOW",
252    LightHaze => "LIGHT_HAZE",
253    LightRain => "LIGHT_RAIN",
254    LightSnow => "LIGHT_SNOW",
255    ModerateHaze => "MODERATE_HAZE",
256    ModerateRain => "MODERATE_RAIN",
257    ModerateSnow => "MODERATE_SNOW",
258    PartlyCloudyDay => "PARTLY_CLOUDY_DAY",
259    PartlyCloudyNight => "PARTLY_CLOUDY_NIGHT",
260    Sand => "SAND",
261    StormRain => "STORM_RAIN",
262    StormSnow => "STORM_SNOW",
263    Wind => "WIND",
264});
265
266impl_str_enum!(ChatRoomMessageMode{
267    Context => "0",
268    Before => "1",
269    After => "2",
270});
271
272impl Default for ChatRoomSource {
273    fn default() -> Self {
274        Self {
275            client: "Other".to_string(),
276            version: "latest".to_string(),
277        }
278    }
279}
280
281impl ChatRoomMsg {
282    pub fn from_value(value: &Value) -> Result<Self, Error> {
283        serde_json::from_value(value.clone())
284            .map_err(|e| Error::Parse(format!("Failed to parse ChatRoomMsg: {}", e)))
285    }
286
287    pub fn name(&self) -> &str {
288        if self.userNickname.is_empty() {
289            &self.userName
290        } else {
291            &self.userNickname
292        }
293    }
294}
295
296impl BarragerMsg {
297    pub fn from_value(value: &Value) -> Result<Self, Error> {
298        serde_json::from_value(value.clone())
299            .map_err(|e| Error::Parse(format!("Failed to parse BarragerMsg: {}", e)))
300    }
301}
302
303fn parse_content(content: &str) -> (ChatRoomMessageType, Value) {
304    if let Ok(data) = serde_json::from_str::<Value>(content) {
305        if let Some(msg_type_str) = data["msgType"].as_str() {
306            match msg_type_str {
307                "redPacket" => (ChatRoomMessageType::RedPacket, data),
308                "music" => (ChatRoomMessageType::Msg, data),
309                "weather" => (ChatRoomMessageType::Msg, data),
310                _ => (ChatRoomMessageType::Msg, Value::String(content.to_string())),
311            }
312        } else {
313            (ChatRoomMessageType::Msg, Value::String(content.to_string()))
314        }
315    } else {
316        (ChatRoomMessageType::Msg, Value::String(content.to_string()))
317    }
318}
319
320impl<'de> Deserialize<'de> for ChatRoomMsg<Value> {
321    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
322    where
323        D: Deserializer<'de>,
324    {
325        #[derive(Deserialize)]
326        #[allow(non_snake_case)]
327        struct Raw {
328            oId: String,
329            time: String,
330            userOId: Value,
331            userName: String,
332            userNickname: String,
333            userAvatarURL: String,
334            sysMetal: Option<Value>,
335            content: String,
336            md: Option<String>,
337            client: Option<String>,
338        }
339
340        let raw = Raw::deserialize(deserializer)?;
341
342        let (r#type, content) = parse_content(&raw.content);
343
344        let via = raw
345            .client
346            .as_ref()
347            .and_then(|s| ClientType::from_str(s).ok())
348            .unwrap_or(ClientType::Other);
349        let client = raw.client.unwrap_or(ClientType::Rust.as_str().to_string());
350
351        let sys_metal = raw
352            .sysMetal
353            .as_ref()
354            .and_then(|v| v.as_str())
355            .map(|s| to_metal(s))
356            .unwrap_or(Ok(vec![]))
357            .unwrap_or(vec![]);
358
359        Ok(ChatRoomMsg {
360            r#type,
361            oId: raw.oId,
362            time: raw.time,
363            userOId: raw.userOId.to_string(),
364            userName: raw.userName,
365            userNickname: raw.userNickname,
366            userAvatarURL: raw.userAvatarURL,
367            sysMetal: sys_metal,
368            content,
369            md: raw.md.unwrap_or("".to_string()),
370            client,
371            via,
372        })
373    }
374}
375
376impl BarragerCost {
377    pub fn from_value(value: &Value) -> Self {
378        let content = value
379            .get("data")
380            .and_then(|v| v.as_str())
381            .unwrap_or("5积分");
382        let mut parts = content
383            .split(|c: char| !c.is_alphanumeric())
384            .filter(|s| !s.is_empty());
385        let cost = parts
386            .next()
387            .and_then(|s| s.parse::<u32>().ok())
388            .unwrap_or(0);
389        let unit = parts.next().unwrap_or("积分").to_string();
390
391        Self {
392            cost,
393            unit,
394        }
395    }
396}