🎉 client

This commit is contained in:
Akiba
2022-01-19 21:30:44 +08:00
parent c78040b47d
commit f57f8f2787
3 changed files with 275 additions and 0 deletions

117
client/client.go Normal file
View File

@@ -0,0 +1,117 @@
package client
import (
"encoding/json"
"fmt"
"github.com/Akegarasu/blivedm-go/packet"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
"net/http"
"strconv"
"time"
)
type Client struct {
conn *websocket.Conn
roomID string
token string
host string
eventHandlers *eventHandlers
customEventHandlers *customEventHandlers
}
func NewClient(roomID string) *Client {
return &Client{
roomID: roomID,
eventHandlers: &eventHandlers{},
customEventHandlers: &customEventHandlers{},
}
}
func (c *Client) Connect() error {
if c.host == "" {
info := getDanmuInfo(c.roomID)
c.host = fmt.Sprintf("wss://%s/sub", info.Data.HostList[0].Host)
c.token = info.Data.Token
}
conn, _, err := websocket.DefaultDialer.Dial(c.host, nil)
if err != nil {
return err
}
c.conn = conn
return nil
}
func (c *Client) Start() {
c.sendEnterPacket()
go func() {
for {
msgType, data, err := c.conn.ReadMessage()
if err != nil {
_ = c.Connect()
}
if msgType != websocket.BinaryMessage {
log.Error("packet not binary", data)
continue
}
for _, pkt := range packet.DecodePacket(data).Parse() {
go c.Handle(pkt)
}
}
}()
c.startHeartBeat()
}
func (c *Client) ConnectAndStart() error {
err := c.Connect()
if err != nil {
return err
}
c.Start()
return nil
}
func (c *Client) SetHost(host string) {
c.host = host
}
func (c *Client) UseDefaultHost() {
c.SetHost("wss://broadcastlv.chat.bilibili.com/sub")
}
func (c *Client) startHeartBeat() {
pkt := packet.NewHeartBeatPacket()
for {
if err := c.conn.WriteMessage(websocket.BinaryMessage, pkt); err != nil {
log.Fatal(err)
}
log.Debug("send: HeartBeat")
time.Sleep(30 * time.Second)
}
}
func (c *Client) sendEnterPacket() {
rid, err := strconv.Atoi(c.roomID)
if err != nil {
log.Fatal("error roomID")
}
pkt := packet.NewEnterPacket(0, rid)
if err := c.conn.WriteMessage(websocket.BinaryMessage, pkt); err != nil {
log.Fatal(err)
}
log.Debugf("send: EnterPacket: %v", pkt)
}
func getDanmuInfo(roomID string) *DanmuInfo {
url := fmt.Sprintf("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=%s&type=0", roomID)
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
result := &DanmuInfo{}
if err = json.NewDecoder(resp.Body).Decode(result); err != nil {
log.Fatal(err)
}
return result
}

135
client/handler.go Normal file
View File

@@ -0,0 +1,135 @@
package client
import (
"bytes"
"fmt"
"github.com/Akegarasu/blivedm-go/message"
"github.com/Akegarasu/blivedm-go/packet"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"runtime/debug"
)
type eventHandlers struct {
danmukuMessageHandlers []func(*message.Danmuku)
superChatHandlers []func(*message.SuperChat)
giftHandlers []func(*message.Gift)
guardBuyHandlers []func(*message.GuardBuy)
liveHandlers []func(*message.Live)
}
type customEventHandlers map[string]func(s string)
func (c *Client) RegisterCustomEventHandler(cmd string, handler func(s string)) {
(*c.customEventHandlers)[cmd] = handler
}
func (c *Client) OnDanmuku(f func(*message.Danmuku)) {
c.eventHandlers.danmukuMessageHandlers = append(c.eventHandlers.danmukuMessageHandlers, f)
}
func (c *Client) OnSuperChat(f func(*message.SuperChat)) {
c.eventHandlers.superChatHandlers = append(c.eventHandlers.superChatHandlers, f)
}
func (c *Client) OnGift(f func(gift *message.Gift)) {
c.eventHandlers.giftHandlers = append(c.eventHandlers.giftHandlers, f)
}
func (c *Client) OnGuardBuy(f func(*message.GuardBuy)) {
c.eventHandlers.guardBuyHandlers = append(c.eventHandlers.guardBuyHandlers, f)
}
func (c *Client) OnLive(f func(*message.Live)) {
c.eventHandlers.liveHandlers = append(c.eventHandlers.liveHandlers, f)
}
func (c *Client) Handle(p packet.Packet) {
switch p.Operation {
case packet.Notification:
sb := bytes.NewBuffer(p.Body).String()
cmd := gjson.Get(sb, "cmd").String()
// 优先执行自定义 eventHandler ,会覆盖库内自带的 handler
f, ok := (*c.customEventHandlers)[cmd]
if ok {
go cover(func() { f(sb) })
return
}
switch cmd {
case "DANMU_MSG":
d := new(message.Danmuku)
d.Parse(p.Body)
for _, fn := range c.eventHandlers.danmukuMessageHandlers {
go cover(func() { fn(d) })
}
case "SUPER_CHAT_MESSAGE":
s := new(message.SuperChat)
s.Parse(p.Body)
for _, fn := range c.eventHandlers.superChatHandlers {
go cover(func() { fn(s) })
}
case "SEND_GIFT":
g := new(message.Gift)
g.Parse(p.Body)
for _, fn := range c.eventHandlers.giftHandlers {
go cover(func() { fn(g) })
}
case "GUARD_BUY":
g := new(message.GuardBuy)
g.Parse(p.Body)
for _, fn := range c.eventHandlers.guardBuyHandlers {
go cover(func() { fn(g) })
}
case "LIVE":
l := new(message.Live)
l.Parse(p.Body)
for _, fn := range c.eventHandlers.liveHandlers {
go cover(func() { fn(l) })
}
//TODO: cmd补全
//case "INTERACT_WORD":
//case "ROOM_BANNER":
//case "ROOM_REAL_TIME_MESSAGE_UPDATE":
//case "NOTICE_MSG":
//case "COMBO_SEND":
//case "COMBO_END":
//case "ENTRY_EFFECT":
//case "WELCOME_GUARD":
//case "WELCOME":
//case "ROOM_RANK":
//case "ACTIVITY_BANNER_UPDATE_V2":
//case "PANEL":
//case "SUPER_CHAT_MESSAGE_JPN":
//case "USER_TOAST_MSG":
//case "ROOM_BLOCK_MSG":
//case "PREPARING":
//case "room_admin_entrance":
//case "ROOM_ADMINS":
//case "ROOM_CHANGE":
//case "LIVE_INTERACTIVE_GAME":
//case "WIDGET_BANNER":
//case "ONLINE_RANK_COUNT":
//case "ONLINE_RANK_V2":
//case "STOP_LIVE_ROOM_LIST":
//case "ONLINE_RANK_TOP3":
//case "HOT_RANK_CHANGED":
default:
log.WithField("data", string(p.Body)).Warn("unknown cmd")
}
case packet.HeartBeatResponse:
case packet.RoomEnterResponse:
default:
log.WithField("protover", p.ProtocolVersion).
WithField("data", string(p.Body)).
Warn("unknown protover")
}
}
func cover(f func()) {
defer func() {
if pan := recover(); pan != nil {
fmt.Printf("event error: %v\n%s", pan, debug.Stack())
}
}()
f()
}

23
client/types.go Normal file
View File

@@ -0,0 +1,23 @@
package client
// DanmuInfo
// api https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=29332&type=0 response
type DanmuInfo struct {
Code int `json:"code"`
Message string `json:"message"`
Ttl int `json:"ttl"`
Data struct {
Group string `json:"group"`
BusinessId int `json:"business_id"`
RefreshRowFactor float64 `json:"refresh_row_factor"`
RefreshRate int `json:"refresh_rate"`
MaxDelay int `json:"max_delay"`
Token string `json:"token"`
HostList []struct {
Host string `json:"host"`
Port int `json:"port"`
WssPort int `json:"wss_port"`
WsPort int `json:"ws_port"`
} `json:"host_list"`
} `json:"data"`
}