1use crate::types::*;
4use tracing::debug;
5use regex::Regex;
6use std::collections::HashMap;
7
8pub struct MessageParser;
10
11impl MessageParser {
12 fn extract_data<T: serde::de::DeserializeOwned>(data: serde_json::Value) -> Result<T> {
14 match data {
15 serde_json::Value::String(s) => {
16 debug!("Extracting data from JSON string: {}", s);
18 serde_json::from_str(&s).map_err(|e| KickError::InvalidMessage(format!("Failed to parse JSON string: {}", e)))
19 }
20 serde_json::Value::Object(_) => {
21 debug!("Extracting data from JSON object");
23 serde_json::from_value(data).map_err(|e| KickError::InvalidMessage(format!("Failed to parse JSON object: {}", e)))
24 }
25 _ => {
26 debug!("Data is neither a string nor an object: {:?}", data);
27 Err(KickError::InvalidMessage("Data is neither a string nor an object".to_string()))
28 }
29 }
30 }
31
32 pub fn parse_message(raw_message: &str) -> Result<Option<ParsedMessage>> {
34 if raw_message.trim().is_empty() {
36 return Err(KickError::InvalidMessage("Empty message".to_string()));
37 }
38
39 let message: WebSocketMessage = serde_json::from_str(raw_message)?;
40
41 if message.event.is_empty() {
43 return Err(KickError::InvalidMessage("Empty event".to_string()));
44 }
45
46 if message.event.starts_with("pusher:") || message.event.starts_with("pusher_internal:") {
48 return Ok(None);
49 }
50
51 match message.event.as_str() {
53 "App\\Events\\ChatMessageEvent" => {
54 let data: RawChatMessageData = Self::extract_data(message.data)?;
55 Ok(Some(ParsedMessage {
56 r#type: KickEventType::ChatMessage,
57 data: KickEventData::ChatMessage(Self::parse_chat_message(data)),
58 }))
59 }
60 "App\\Events\\MessageDeletedEvent" => {
61 let data: RawMessageDeletedData = Self::extract_data(message.data)?;
62 Ok(Some(ParsedMessage {
63 r#type: KickEventType::MessageDeleted,
64 data: KickEventData::MessageDeleted(Self::parse_message_deleted(data)),
65 }))
66 }
67 "App\\Events\\UserBannedEvent" => {
68 let data: RawUserBannedData = Self::extract_data(message.data)?;
69 Ok(Some(ParsedMessage {
70 r#type: KickEventType::UserBanned,
71 data: KickEventData::UserBanned(Self::parse_user_banned(data)),
72 }))
73 }
74 "App\\Events\\UserUnbannedEvent" => {
75 let data: RawUserUnbannedData = Self::extract_data(message.data)?;
76 Ok(Some(ParsedMessage {
77 r#type: KickEventType::UserUnbanned,
78 data: KickEventData::UserUnbanned(Self::parse_user_unbanned(data)),
79 }))
80 }
81 "App\\Events\\SubscriptionEvent" => {
82 let data: RawSubscriptionData = Self::extract_data(message.data)?;
83 Ok(Some(ParsedMessage {
84 r#type: KickEventType::Subscription,
85 data: KickEventData::Subscription(Self::parse_subscription(data)),
86 }))
87 }
88 "App\\Events\\GiftedSubscriptionsEvent" => {
89 let data: RawGiftedSubscriptionsData = Self::extract_data(message.data)?;
90 Ok(Some(ParsedMessage {
91 r#type: KickEventType::GiftedSubscriptions,
92 data: KickEventData::GiftedSubscriptions(Self::parse_gifted_subscriptions(data)),
93 }))
94 }
95 "App\\Events\\PinnedMessageCreatedEvent" => {
96 let data: RawPinnedMessageCreatedData = Self::extract_data(message.data)?;
97 Ok(Some(ParsedMessage {
98 r#type: KickEventType::PinnedMessageCreated,
99 data: KickEventData::PinnedMessageCreated(Self::parse_pinned_message_created(data)),
100 }))
101 }
102 "App\\Events\\StreamHostEvent" => {
103 let data: RawStreamHostData = Self::extract_data(message.data)?;
104 Ok(Some(ParsedMessage {
105 r#type: KickEventType::StreamHost,
106 data: KickEventData::StreamHost(Self::parse_stream_host(data)),
107 }))
108 }
109 "App\\Events\\PollUpdateEvent" => {
110 let data: RawPollUpdateData = Self::extract_data(message.data)?;
111 Ok(Some(ParsedMessage {
112 r#type: KickEventType::PollUpdate,
113 data: KickEventData::PollUpdate(Self::parse_poll_update(data)),
114 }))
115 }
116 "App\\Events\\PollDeleteEvent" => {
117 let data: RawPollDeleteData = Self::extract_data(message.data)?;
118 Ok(Some(ParsedMessage {
119 r#type: KickEventType::PollDelete,
120 data: KickEventData::PollDelete(Self::parse_poll_delete(data)),
121 }))
122 }
123 _ => Err(KickError::UnknownEventType(message.event)),
124 }
125 }
126
127 pub fn parse_chat_message(data: RawChatMessageData) -> ChatMessageEvent {
129 ChatMessageEvent {
130 id: data.id,
131 content: Self::clean_emotes(&data.content),
132 message_type: data.message_type,
133 created_at: data.created_at,
134 sender: data.sender,
135 chatroom: KickChatroom {
136 id: data.chatroom_id,
137 channel_id: data.chatroom_id,
138 name: String::new(), },
140 }
141 }
142
143 pub fn parse_message_deleted(data: RawMessageDeletedData) -> MessageDeletedEvent {
145 MessageDeletedEvent {
146 message_id: data.message_id,
147 chatroom_id: data.chatroom_id,
148 event_type: "message_deleted".to_string(),
149 }
150 }
151
152 pub fn parse_user_banned(data: RawUserBannedData) -> UserBannedEvent {
154 let username = data.username
155 .or(data.banned_username)
156 .unwrap_or_else(|| "unknown".to_string());
157
158 UserBannedEvent {
159 username,
160 event_type: "user_banned".to_string(),
161 }
162 }
163
164 pub fn parse_user_unbanned(data: RawUserUnbannedData) -> UserUnbannedEvent {
166 let username = data.username.unwrap_or_else(|| "unknown".to_string());
167
168 UserUnbannedEvent {
169 username,
170 event_type: "user_unbanned".to_string(),
171 }
172 }
173
174 pub fn parse_subscription(data: RawSubscriptionData) -> SubscriptionEvent {
176 SubscriptionEvent {
177 username: data.username,
178 months: data.months,
179 event_type: "subscription".to_string(),
180 }
181 }
182
183 pub fn parse_gifted_subscriptions(data: RawGiftedSubscriptionsData) -> GiftedSubscriptionsEvent {
185 let gifter = data.gifted_by
186 .or_else(|| {
187 if let Some(gifter_value) = &data.gifter {
188 if let Some(gifter_str) = gifter_value.as_str() {
189 Some(gifter_str.to_string())
190 } else if let Some(gifter_obj) = gifter_value.as_object() {
191 gifter_obj.get("username")
192 .and_then(|v| v.as_str())
193 .map(|s| s.to_string())
194 } else {
195 None
196 }
197 } else {
198 None
199 }
200 })
201 .unwrap_or_else(|| "unknown".to_string());
202
203 let recipients = data.recipients
204 .into_iter()
205 .map(|r| {
206 if let Some(r_str) = r.as_str() {
207 r_str.to_string()
208 } else if let Some(r_obj) = r.as_object() {
209 r_obj.get("username")
210 .and_then(|v| v.as_str())
211 .unwrap_or("unknown")
212 .to_string()
213 } else {
214 "unknown".to_string()
215 }
216 })
217 .collect();
218
219 GiftedSubscriptionsEvent {
220 gifted_by: gifter,
221 recipients,
222 event_type: "gifted_subscriptions".to_string(),
223 }
224 }
225
226 pub fn parse_pinned_message_created(data: RawPinnedMessageCreatedData) -> PinnedMessageCreatedEvent {
228 PinnedMessageCreatedEvent {
229 message: Self::parse_chat_message(data.message),
230 event_type: "pinned_message_created".to_string(),
231 }
232 }
233
234 pub fn parse_stream_host(data: RawStreamHostData) -> StreamHostEvent {
236 let hoster = if let Some(hoster_str) = data.hoster.as_str() {
237 hoster_str.to_string()
238 } else if let Some(hoster_obj) = data.hoster.as_object() {
239 hoster_obj.get("username")
240 .and_then(|v| v.as_str())
241 .unwrap_or("unknown")
242 .to_string()
243 } else {
244 "unknown".to_string()
245 };
246
247 let hosted_channel = if let Some(hosted_str) = data.hosted_channel.as_str() {
248 hosted_str.to_string()
249 } else if let Some(hosted_obj) = data.hosted_channel.as_object() {
250 hosted_obj.get("username")
251 .and_then(|v| v.as_str())
252 .unwrap_or("unknown")
253 .to_string()
254 } else {
255 "unknown".to_string()
256 };
257
258 StreamHostEvent {
259 hoster,
260 hosted_channel,
261 event_type: "stream_host".to_string(),
262 }
263 }
264
265 pub fn parse_poll_update(data: RawPollUpdateData) -> PollUpdateEvent {
267 let options = data.options
268 .into_iter()
269 .map(|opt| PollOption {
270 id: opt.id,
271 text: opt.text,
272 votes: opt.votes.unwrap_or(0),
273 })
274 .collect();
275
276 PollUpdateEvent {
277 poll_id: data.id,
278 question: data.question,
279 options,
280 event_type: "poll_update".to_string(),
281 }
282 }
283
284 pub fn parse_poll_delete(data: RawPollDeleteData) -> PollDeleteEvent {
286 PollDeleteEvent {
287 poll_id: data.id,
288 event_type: "poll_delete".to_string(),
289 }
290 }
291
292 pub fn clean_emotes(content: &str) -> String {
294 if content.is_empty() {
295 return String::new();
296 }
297
298 let emote_regex = Regex::new(r"\[emote:(\d+):(\w+)\]").unwrap();
300 emote_regex.replace_all(content, "$2").to_string()
301 }
302
303 pub fn is_valid_message(message: &str) -> bool {
305 if message.trim().is_empty() {
306 return false;
307 }
308
309 match serde_json::from_str::<WebSocketMessage>(message) {
310 Ok(parsed) => {
311 !parsed.event.is_empty() && parsed.data != serde_json::Value::Null
312 }
313 Err(_) => false,
314 }
315 }
316
317 pub fn is_kick_event(message: &str) -> bool {
319 if let Ok(parsed) = serde_json::from_str::<WebSocketMessage>(message) {
320 !parsed.event.starts_with("pusher:") &&
321 !parsed.event.starts_with("pusher_internal:") &&
322 !parsed.event.is_empty()
323 } else {
324 false
325 }
326 }
327
328 pub fn extract_event_type(raw_message: &str) -> Result<String> {
330 if raw_message.trim().is_empty() {
331 return Err(KickError::InvalidMessage("Empty message".to_string()));
332 }
333
334 let message: WebSocketMessage = serde_json::from_str(raw_message)?;
335
336 if message.event.is_empty() {
337 return Err(KickError::InvalidMessage("Empty event".to_string()));
338 }
339
340 if message.event.starts_with("pusher:") || message.event.starts_with("pusher_internal:") {
342 return Err(KickError::InvalidMessage(format!("Pusher system event: {}", message.event)));
343 }
344
345 Ok(message.event)
346 }
347
348 pub fn map_kick_event_to_standard(event_name: &str) -> Option<KickEventType> {
350 let event_map: HashMap<&str, KickEventType> = HashMap::from([
351 ("App\\Events\\ChatMessageEvent", KickEventType::ChatMessage),
352 ("App\\Events\\MessageDeletedEvent", KickEventType::MessageDeleted),
353 ("App\\Events\\UserBannedEvent", KickEventType::UserBanned),
354 ("App\\Events\\UserUnbannedEvent", KickEventType::UserUnbanned),
355 ("App\\Events\\SubscriptionEvent", KickEventType::Subscription),
356 ("App\\Events\\GiftedSubscriptionsEvent", KickEventType::GiftedSubscriptions),
357 ("App\\Events\\PinnedMessageCreatedEvent", KickEventType::PinnedMessageCreated),
358 ("App\\Events\\StreamHostEvent", KickEventType::StreamHost),
359 ("App\\Events\\PollUpdateEvent", KickEventType::PollUpdate),
360 ("App\\Events\\PollDeleteEvent", KickEventType::PollDelete),
361 ]);
362
363 event_map.get(event_name).cloned()
364 }
365}
366
367pub fn parse_custom_message<T: serde::de::DeserializeOwned>(raw_message: &str) -> crate::types::Result<T> {
369 serde_json::from_str(raw_message)
370 .map_err(|e| KickError::InvalidMessage(format!("Failed to parse custom message: {}", e)))
371}
372
373pub fn extract_field(raw_message: &RawMessage, field_path: &str) -> Option<serde_json::Value> {
375 let parsed: serde_json::Value = serde_json::from_str(&raw_message.raw_json).ok()?;
377
378 let mut current = &parsed;
380 for part in field_path.split('.') {
381 match current {
382 serde_json::Value::Object(map) => {
383 current = map.get(part)?;
384 }
385 serde_json::Value::Array(arr) => {
386 if let Ok(index) = part.parse::<usize>() {
387 current = arr.get(index)?;
388 } else {
389 return None;
390 }
391 }
392 _ => return None,
393 }
394 }
395
396 Some(current.clone())
397}
398
399#[cfg(test)]
400mod tests {
401 use super::*;
402
403 #[test]
404 fn test_clean_emotes() {
405 let content = "Hello [emote:123:wave] world [emote:456:heart]!";
406 let cleaned = MessageParser::clean_emotes(content);
407 assert_eq!(cleaned, "Hello wave world heart!");
408 }
409
410 #[test]
411 fn test_empty_content() {
412 let cleaned = MessageParser::clean_emotes("");
413 assert_eq!(cleaned, "");
414 }
415
416 #[test]
417 fn test_is_valid_message() {
418 let valid_json = r#"{"event":"test","data":{"key":"value"}}"#;
419 let invalid_json = r#"{"event":"test"}"#;
420 let empty = "";
421
422 assert!(MessageParser::is_valid_message(valid_json));
423 assert!(!MessageParser::is_valid_message(invalid_json));
424 assert!(!MessageParser::is_valid_message(empty));
425 }
426
427 #[test]
428 fn test_extract_event_type() {
429 let message = r#"{"event":"App\\Events\\ChatMessageEvent","data":{}}"#;
430 let event_type = MessageParser::extract_event_type(message).unwrap();
431 assert_eq!(event_type, "App\\Events\\ChatMessageEvent");
432
433 let pusher_message = r#"{"event":"pusher:connection_established","data":"{}"}"#;
435 let result = MessageParser::parse_message(pusher_message);
436 assert!(result.is_ok());
437 assert!(result.unwrap().is_none());
438 }
439
440 #[test]
441 fn test_map_kick_event_to_standard() {
442 assert_eq!(
443 MessageParser::map_kick_event_to_standard("App\\Events\\ChatMessageEvent"),
444 Some(KickEventType::ChatMessage)
445 );
446 assert_eq!(
447 MessageParser::map_kick_event_to_standard("UnknownEvent"),
448 None
449 );
450 }
451}