fix(danmaku): 虎牙弹幕从用户结构体读取昵称和正文 (#1689)

MessageNotice 的 tag 2 是 lSid 不是用户名。旧解析把 lSid 当字符串读,lSid 非 0 时连正文都对不齐,弹幕几乎全部丢弃。
This commit is contained in:
Noah authored and GitHub committed 2026-09-13 15:36:15 +00:00
1 parent 8437b028c3
commit 9445db636b
2 files changed
+72 -20

No files matched your search

+24 -2
View File
@@ -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<T>(&mut self, tag: u8, f: impl FnOnce(&mut Self) -> T) -> Option<T> {
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<String> {
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)
+48 -18
View File
@@ -180,24 +180,14 @@ impl Huya {
fn parse_danmaku(data: &[u8]) -> Option<DanmakuEvent> {
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<u8> {
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:?}"),
}
}
}