From 5bfed2f8bb6d90eaeaf6e0ae62ef3a17ade17eb2 Mon Sep 17 00:00:00 2001 From: aynakeya Date: Sun, 7 Apr 2024 02:43:04 -0700 Subject: [PATCH] first commit --- .gitignore | 2 + example/openblive/openblive.go | 42 ++++++++++++ go.mod | 14 ++++ liveroom.go | 44 ++++++++++++ provider/openblive/apiclient.go | 68 ++++++++++++++++++ provider/openblive/openblive.go | 118 ++++++++++++++++++++++++++++++++ registry.go | 24 +++++++ 7 files changed, 312 insertions(+) create mode 100644 .gitignore create mode 100644 example/openblive/openblive.go create mode 100644 go.mod create mode 100644 liveroom.go create mode 100644 provider/openblive/apiclient.go create mode 100644 provider/openblive/openblive.go create mode 100644 registry.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6e6b069 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +go.sum \ No newline at end of file diff --git a/example/openblive/openblive.go b/example/openblive/openblive.go new file mode 100644 index 0000000..faf0ea6 --- /dev/null +++ b/example/openblive/openblive.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "liveroom" + "liveroom/provider/openblive" + "os" + "os/signal" + "time" +) + +const apiServer = "http://0.0.0.0:9090" + +func main() { + provider := openblive.NewOpenBLiveClientProvider(apiServer, 1661006726438) + room := provider(liveroom.LiveRoomConfig{ + Room: "YOUR_CLIENT_KEY", + Provider: openblive.ProviderName, + }) + room.OnMessage(func(msg *liveroom.Message) { + fmt.Println(msg.User.Username, msg.User.Uid, msg.User.Medal.Name, msg.Message) + }) + room.OnStatusChange( + func(connected bool) { + if connected { + fmt.Println("Connected") + } else { + fmt.Println("Disconnected") + } + }) + room.OnDisconnect( + func(liveroom liveroom.LiveRoom) { + fmt.Println("Disconnected AAAAAAAa") + }) + fmt.Println("connect", room.Connect()) + quit := make(chan os.Signal) + signal.Notify(quit, os.Interrupt, os.Kill) + <-quit + fmt.Println("disconnect", room.Disconnect()) + time.Sleep(3 * time.Second) + fmt.Println("Bye") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..745ad3a --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module liveroom + +go 1.20 + +require ( + github.com/aynakeya/open-bilibili-live v0.0.3 + github.com/go-resty/resty/v2 v2.7.0 + github.com/spf13/cast v1.5.1 +) + +require ( + github.com/gorilla/websocket v1.5.0 // indirect + golang.org/x/net v0.0.0-20211029224645-99673261e6eb // indirect +) diff --git a/liveroom.go b/liveroom.go new file mode 100644 index 0000000..c58d245 --- /dev/null +++ b/liveroom.go @@ -0,0 +1,44 @@ +package liveroom + +type LiveRoomConfig struct { + Provider string `json:"provider"` // Provider is the name of the live room provider + Room string `json:"room"` // RoomID is the unique identifier of the live room +} + +type LiveRoomProvider func(cfg LiveRoomConfig) LiveRoom + +type UserMedal struct { + Name string `json:"name"` + Level int `json:"level"` + RoomID string `json:"room_id"` +} + +const ( + PrivilegeNone = iota + PrivilegeBasic + PrivilegeAdvanced + PrivilegeUltimate +) + +type User struct { + Uid string + Username string + Admin bool + Privilege int + Medal UserMedal +} + +type Message struct { + User User + Message string +} + +type LiveRoom interface { + GetName() string // should return the name of the provider + Config() *LiveRoomConfig // should return mutable model (not a copy) + Connect() error + Disconnect() error + OnDisconnect(func(liveroom LiveRoom)) + OnStatusChange(func(connected bool)) + OnMessage(func(msg *Message)) +} diff --git a/provider/openblive/apiclient.go b/provider/openblive/apiclient.go new file mode 100644 index 0000000..bd5e3c9 --- /dev/null +++ b/provider/openblive/apiclient.go @@ -0,0 +1,68 @@ +package openblive + +import ( + "encoding/json" + "errors" + "github.com/aynakeya/open-bilibili-live" + "github.com/go-resty/resty/v2" + "github.com/spf13/cast" +) + +type remoteApiClient struct { + client *resty.Client +} + +type openbliveResponse struct { + Result *openblive.AppStartResult `json:"result"` + Error *openblive.PublicError `json:"error"` +} + +func newRemoteApiClient(remoteApi string) openblive.IApiClient { + return &remoteApiClient{ + client: resty.New().SetBaseURL(remoteApi), + } +} + +type sceneAppResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data openbliveResponse `json:"data"` +} + +func parseApiResponse(resp *resty.Response, err error) (*openblive.AppStartResult, *openblive.PublicError) { + if err != nil { + return nil, openblive.ErrUnknown.WithDetail(err) + } + var sceneResp sceneAppResponse + err = json.Unmarshal(resp.Body(), &sceneResp) + if err != nil { + return nil, openblive.ErrUnknown.WithDetail(err) + } + if sceneResp.Code != 0 { + return nil, openblive.ErrUnknown.WithDetail(errors.New(sceneResp.Msg)) + } + return sceneResp.Data.Result, sceneResp.Data.Error + +} + +func (r *remoteApiClient) AppStart(code string, appId int64) (*openblive.AppStartResult, *openblive.PublicError) { + return parseApiResponse(r.client.R(). + SetQueryParam("code", code). + SetQueryParam("app_id", cast.ToString(appId)). + Get("/api/blivedm/openblive/app_start")) +} + +func (r *remoteApiClient) AppEnd(appId int64, gameId string) *openblive.PublicError { + _, err := parseApiResponse(r.client.R(). + SetQueryParam("app_id", cast.ToString(appId)). + SetQueryParam("game_id", gameId). + Get("/api/blivedm/openblive/app_end")) + return err +} + +func (r *remoteApiClient) HearBeat(gameId string) *openblive.PublicError { + _, err := parseApiResponse(r.client.R(). + SetQueryParam("game_id", gameId). + Get("/api/blivedm/openblive/heartbeat")) + return err +} diff --git a/provider/openblive/openblive.go b/provider/openblive/openblive.go new file mode 100644 index 0000000..cfa551f --- /dev/null +++ b/provider/openblive/openblive.go @@ -0,0 +1,118 @@ +package openblive + +import ( + "context" + openblive "github.com/aynakeya/open-bilibili-live" + "liveroom" + "strconv" +) + +const ProviderName = "openblive" + +type OpenBLiveClient struct { + cfg liveroom.LiveRoomConfig + openbliveClient *openblive.BLiveClient + conn openblive.BLiveLongConnection + onMessage func(msg *liveroom.Message) + onDisconnect func(liveroom liveroom.LiveRoom) + onStatusChange func(connected bool) +} + +func NewOpenBLiveClientProvider(apiServer string, appId int64) liveroom.LiveRoomProvider { + return func(cfg liveroom.LiveRoomConfig) liveroom.LiveRoom { + if cfg.Provider != ProviderName { + return nil + } + return &OpenBLiveClient{ + cfg: cfg, + openbliveClient: openblive.NewBliveClient(appId, cfg.Room, newRemoteApiClient(apiServer)), + } + } +} + +func guardLevelToPrivilege(level int) int { + switch level { + case 1: + return liveroom.PrivilegeUltimate + case 2: + return liveroom.PrivilegeAdvanced + case 3: + return liveroom.PrivilegeBasic + default: + return liveroom.PrivilegeNone + } +} + +func (o *OpenBLiveClient) danmuHandler(data openblive.DanmakuData) { + if o.onMessage == nil { + return + } + o.onMessage(&liveroom.Message{ + User: liveroom.User{ + Uid: data.OpenID, + Username: data.UName, + Admin: false, // not supported by open bilibili live + Privilege: guardLevelToPrivilege(data.GuardLevel), + Medal: liveroom.UserMedal{ + Name: data.FansMedalName, + Level: data.FansMedalLevel, + RoomID: strconv.Itoa(data.RoomID), + }, + }, + Message: data.Msg, + }) +} + +func (o *OpenBLiveClient) disconnectHandler(conn openblive.BLiveLongConnection) { + if o.onStatusChange != nil { + o.onStatusChange(false) + } + if o.onDisconnect != nil { + o.onDisconnect(o) + } +} + +func (o *OpenBLiveClient) GetName() string { + return ProviderName +} + +func (o *OpenBLiveClient) Config() *liveroom.LiveRoomConfig { + return &o.cfg +} + +func (o *OpenBLiveClient) Connect() error { + err := o.openbliveClient.Start() + if err != nil { + return err + } + o.conn = o.openbliveClient.GetLongConn() + o.conn.OnDanmu(o.danmuHandler) + o.conn.OnDisconnect(o.disconnectHandler) + e := o.conn.EstablishConnection(context.Background()) + if e == nil { + if o.onStatusChange != nil { + o.onStatusChange(true) + } + } + return e +} + +func (o *OpenBLiveClient) Disconnect() error { + _ = o.conn.CloseConnection() + if o.onStatusChange != nil { + o.onStatusChange(false) + } + return o.openbliveClient.End() +} + +func (o *OpenBLiveClient) OnStatusChange(f func(connected bool)) { + o.onStatusChange = f +} + +func (o *OpenBLiveClient) OnDisconnect(f func(liveroom liveroom.LiveRoom)) { + o.onDisconnect = f +} + +func (o *OpenBLiveClient) OnMessage(f func(msg *liveroom.Message)) { + o.onMessage = f +} diff --git a/registry.go b/registry.go new file mode 100644 index 0000000..42c2fe0 --- /dev/null +++ b/registry.go @@ -0,0 +1,24 @@ +package liveroom + +var _providers map[string]LiveRoomProvider = make(map[string]LiveRoomProvider) + +func RegisterProvider(name string, provider LiveRoomProvider) { + if _, ok := _providers[name]; ok { + panic("provider " + name + " already exists") + return + } + _providers[name] = provider +} + +func GetProvider(name string) (LiveRoomProvider, bool) { + provider, ok := _providers[name] + return provider, ok +} + +func ListAvailableProviders() []string { + var names []string + for name := range _providers { + names = append(names, name) + } + return names +}