diff options
-rw-r--r-- | danmaku/dmmsg/danmu.go | 55 | ||||
-rw-r--r-- | danmaku/dmmsg/interactword.go | 39 | ||||
-rw-r--r-- | danmaku/dmmsg/messages.go | 7 | ||||
-rw-r--r-- | danmaku/dmmsg/msghandler.go | 4 | ||||
-rw-r--r-- | danmaku/dmmsg/util.go | 17 | ||||
-rw-r--r-- | recording/watcher.go | 55 |
6 files changed, 173 insertions, 4 deletions
diff --git a/danmaku/dmmsg/danmu.go b/danmaku/dmmsg/danmu.go new file mode 100644 index 0000000..2cc5dfa --- /dev/null +++ b/danmaku/dmmsg/danmu.go @@ -0,0 +1,55 @@ +package dmmsg + +/* +Decoder of raw danmaku messages. +*/ + +import ( + "fmt" +) + +type RawDanMuMessage = BaseRawMessage[[]interface{}, interface{}] + +type DanMuMessage struct { + Content string + SourceUser struct { + Nickname string + UID int64 + } +} + +func (dm DanMuMessage) String() string { + return fmt.Sprintf("(user: %v, uid: %v) %v", + dm.SourceUser.Nickname, dm.SourceUser.UID, dm.Content) +} + +const kInvalidDanmakuJson = "invalid danmaku JSON document" + +func ParseDanmakuMessage(body RawDanMuMessage) (dmm DanMuMessage, err error) { + if len(body.Info) != 16 { + err = fmt.Errorf("%s: \"info\" length != 16", kInvalidDanmakuJson) + return + } + + dmm.Content, err = castValue[string](body.Info[1]) + if err != nil { + return + } + + userInfo, err := castValue[[]interface{}](body.Info[2]) + + var ok bool + uid, ok := userInfo[0].(float64) + if !ok { + err = fmt.Errorf("%s: uid is not a float64: %v", kInvalidDanmakuJson, userInfo[0]) + return + } + dmm.SourceUser.UID = int64(uid) + + dmm.SourceUser.Nickname, ok = userInfo[1].(string) + if !ok { + err = fmt.Errorf("%s: nickname is not a string: %v", kInvalidDanmakuJson, userInfo[1]) + return + } + return +} diff --git a/danmaku/dmmsg/interactword.go b/danmaku/dmmsg/interactword.go new file mode 100644 index 0000000..d5dc315 --- /dev/null +++ b/danmaku/dmmsg/interactword.go @@ -0,0 +1,39 @@ +package dmmsg + +type InteractWordMessage struct { + Contribution struct { + Grade int `json:"grade"` + } `json:"contribution"` + DanMuScore int `json:"dmscore"` + FansMedal struct { + AnchorRoomid int `json:"anchor_roomid"` + GuardLevel int `json:"guard_level"` + IconID int `json:"icon_id"` + IsLighted int `json:"is_lighted"` + Color int `json:"medal_color"` + ColorBorder int `json:"medal_color_border"` + ColorEnd int `json:"medal_color_end"` + ColorStart int `json:"medal_color_start"` + Level int `json:"medal_level"` + Name string `json:"medal_name"` + Score int `json:"score"` + Special string `json:"special"` + TargetID int `json:"target_id"` + } `json:"fans_medal"` + Identities []int `json:"identities"` + IsSpread int `json:"is_spread"` + MsgType int `json:"msg_type"` + PrivilegeType int `json:"privilege_type"` + RoomId int `json:"roomid"` + Score int64 `json:"score"` + SpreadDesc string `json:"spread_desc"` + SpreadInfo string `json:"spread_info"` + TailIcon int `json:"tail_icon"` + Timestamp int `json:"timestamp"` + TriggerTime int64 `json:"trigger_time"` + UID int `json:"uid"` + UserName string `json:"uname"` + UserNameColor string `json:"uname_color"` +} + +type RawInteractWordMessage = BaseRawMessage[interface{}, InteractWordMessage] diff --git a/danmaku/dmmsg/messages.go b/danmaku/dmmsg/messages.go new file mode 100644 index 0000000..d1480b7 --- /dev/null +++ b/danmaku/dmmsg/messages.go @@ -0,0 +1,7 @@ +package dmmsg + +type BaseRawMessage[I any, D any] struct { + Cmd string `json:"cmd"` + Info I `json:"info"` + Data D `json:"data"` +} diff --git a/danmaku/dmmsg/msghandler.go b/danmaku/dmmsg/msghandler.go new file mode 100644 index 0000000..32766aa --- /dev/null +++ b/danmaku/dmmsg/msghandler.go @@ -0,0 +1,4 @@ +package dmmsg + +type DanmakuMessageHandler interface { +} diff --git a/danmaku/dmmsg/util.go b/danmaku/dmmsg/util.go new file mode 100644 index 0000000..9d41ab9 --- /dev/null +++ b/danmaku/dmmsg/util.go @@ -0,0 +1,17 @@ +package dmmsg + +import ( + "fmt" + "reflect" +) + +func castValue[T any](obj interface{}) (thing T, err error) { + casted, ok := (obj).(T) + if !ok { + err = fmt.Errorf("%s: required value is not of type \"%v\": %v", + kInvalidDanmakuJson, reflect.TypeOf(thing).String(), obj) + return + } + thing = casted + return +} diff --git a/recording/watcher.go b/recording/watcher.go index 439ffcb..92d2135 100644 --- a/recording/watcher.go +++ b/recording/watcher.go @@ -3,6 +3,7 @@ package recording import ( "bilibili-livestream-archiver/common" "bilibili-livestream-archiver/danmaku" + "bilibili-livestream-archiver/danmaku/dmmsg" "bilibili-livestream-archiver/danmaku/dmpkg" "context" "encoding/json" @@ -26,7 +27,8 @@ const ( ) type liveInfo struct { - Command liveCommand `json:"cmd"` + Command liveCommand `json:"cmd"` + Data map[string]interface{} `json:"data"` } type ErrorReason int @@ -126,7 +128,7 @@ func watch( switch msg.Operation { case dmpkg.OpLayer7Data: - logger.Printf("server message: op %v, body %v\n", msg.Operation, string(msg.Body)) + //logger.Printf("server message: op %v, body %v\n", msg.Operation, string(msg.Body)) var info liveInfo err := json.Unmarshal(msg.Body, &info) if err != nil { @@ -144,8 +146,53 @@ func watch( chEvent <- WatcherLiveStop } default: - logger.Printf("Ignoring server message %v %v %v\n", - info.Command, msg.Operation, string(msg.Body)) + switch info.Command { + case "ONLINE_RANK_COUNT": + fallthrough + case "STOP_LIVE_ROOM_LIST": + // useless message + fallthrough + case "HOT_RANK_CHANGED_V2": + // useless message + logger.Printf("Ignore message: %v\n", info.Command) + case "WATCHED_CHANGE": + // number of watched people changed + obj, exists := info.Data["num"] + if !exists { + continue + } + watchedPeopleNumber, ok := obj.(float64) + if !ok { + logger.Printf("Cannot parse watched people number: %v\n", obj) + continue + } + logger.Printf("Watched people (room: %v): %v", roomId, watchedPeopleNumber) + case "INTERACT_WORD": + var raw dmmsg.RawInteractWordMessage + err = json.Unmarshal(msg.Body, &raw) + if err != nil { + logger.Printf("Cannot parse RawInteractWordMessage JSON: %v\n", err) + continue + } + logger.Printf("Interact word message: user: %v medal: %v", + raw.Data.UserName, raw.Data.FansMedal.Name) + case "DANMU_MSG": + var raw dmmsg.RawDanMuMessage + err = json.Unmarshal(msg.Body, &raw) + if err != nil { + logger.Printf("Cannot parse Dan Mu message as JSON: %v\n", err) + continue + } + dmm, err := dmmsg.ParseDanmakuMessage(raw) + if err != nil { + logger.Printf("Cannot parse Dan Mu message JSON: %v\n", err) + continue + } + logger.Printf("Dan mu: %v\n", dmm.String()) + default: + logger.Printf("Ignoring server message %v %v %v\n", + info.Command, msg.Operation, string(msg.Body)) + } } default: logger.Printf("Server message: %v\n", msg.String()) |