mirror of
https://github.com/AynaLivePlayer/blivedm-go.git
synced 2025-12-06 19:32:49 +08:00
🎉 client
This commit is contained in:
117
client/client.go
Normal file
117
client/client.go
Normal 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
135
client/handler.go
Normal 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
23
client/types.go
Normal 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"`
|
||||
}
|
||||
Reference in New Issue
Block a user