diff --git a/crates/danmaku/src/codec/tars.rs b/crates/danmaku/src/codec/tars.rs index c687a6f..e7b1916 100644 --- a/crates/danmaku/src/codec/tars.rs +++ b/crates/danmaku/src/codec/tars.rs @@ -428,13 +428,31 @@ impl<'a> TarsInputStream<'a> { None } } - _ => None, + other => { + self.skip_field(other); + None + } } } else { None } } + /// Read the struct at `tag` and invoke `f` on its fields. + pub fn read_struct(&mut self, tag: u8, f: impl FnOnce(&mut Self) -> T) -> Option { + if !self.skip_to_tag(tag) { + return None; + } + let (_, tars_type) = self.read_head()?; + if tars_type != TarsType::StructBegin { + self.skip_field(tars_type); + return None; + } + let result = f(self); + self.skip_to_struct_end(); + Some(result) + } + /// Read a string value at a tag. pub fn read_string(&mut self, tag: u8) -> Option { if !self.skip_to_tag(tag) { @@ -473,7 +491,10 @@ impl<'a> TarsInputStream<'a> { } None } - _ => None, + other => { + self.skip_field(other); + None + } } } else { None @@ -487,6 +508,7 @@ impl<'a> TarsInputStream<'a> { } if let Some((_, tars_type)) = self.read_head() { if tars_type != TarsType::Bytes { + self.skip_field(tars_type); return None; } // Read inner type head (should be int8) diff --git a/crates/danmaku/src/protocols/huya.rs b/crates/danmaku/src/protocols/huya.rs index fb17e0e..ce25821 100644 --- a/crates/danmaku/src/protocols/huya.rs +++ b/crates/danmaku/src/protocols/huya.rs @@ -180,24 +180,14 @@ impl Huya { fn parse_danmaku(data: &[u8]) -> Option { let mut ios = TarsInputStream::new(data); - // User info is at tag 0, which is a struct - // Within user info, username is at tag 2 - // Content is at tag 3 - // Color is at tag 6 (inside a struct) - - // For simplicity, we'll try to parse the username at tag 2 of nested struct - // and content at tag 3 - - // Skip to find the username - it's nested, so we need a different approach - // Let's read the raw structure: - // Tag 0: User struct containing: - // - Tag 2: username (string) - // Tag 3: content (string) - // Tag 6: DColor struct containing: - // - Tag 0: color (int32) - - // This is a simplified parser - we look for string patterns - let name = ios.read_string(2).unwrap_or_default(); + // MessageNotice: + // tag 0: sender struct, nickname at inner tag 2 + // tag 1: lTid (int64) + // tag 2: lSid (int64) —— 旧实现把这个当成用户名来读 + // tag 3: sContent + let name = ios + .read_struct(0, |user| user.read_string(2).unwrap_or_default()) + .unwrap_or_default(); let content = ios.read_string(3).unwrap_or_default(); // Try to read color from tag 6 struct @@ -267,6 +257,8 @@ impl Platform for Huya { #[cfg(test)] mod tests { use super::*; + use crate::codec::tars::{TarsInputStream, TarsOutputStream}; + use crate::message::DanmakuEvent; #[test] fn test_extract_room_id() { @@ -301,4 +293,42 @@ mod tests { let mut ios = TarsInputStream::new(&cmd); assert_eq!(ios.read_int32(0), Some(cmd_type::REGISTER_REQ)); } + + fn encode_message_notice(nick: &str, sid: i64, content: &str) -> Vec { + let mut oos = TarsOutputStream::new(); + oos.write_struct_begin(0); + oos.write_int64(0, 42); + oos.write_string(2, nick); + oos.write_struct_end(); + oos.write_int64(1, 1); + oos.write_int64(2, sid); + oos.write_string(3, content); + oos.get_buffer().to_vec() + } + + #[test] + fn parse_danmaku_reads_nested_nick_when_sid_nonzero() { + let data = encode_message_notice("nickname", 99, "hello danmaku"); + let event = Huya::parse_danmaku(&data).expect("danmaku"); + match event { + DanmakuEvent::Chat(chat) => { + assert_eq!(chat.name.as_deref(), Some("nickname")); + assert_eq!(chat.content, "hello danmaku"); + } + other => panic!("unexpected {other:?}"), + } + } + + #[test] + fn parse_danmaku_reads_content_when_sid_is_zero() { + let data = encode_message_notice("alice", 0, "zero sid"); + let event = Huya::parse_danmaku(&data).expect("danmaku"); + match event { + DanmakuEvent::Chat(chat) => { + assert_eq!(chat.name.as_deref(), Some("alice")); + assert_eq!(chat.content, "zero sid"); + } + other => panic!("unexpected {other:?}"), + } + } }