├── .gitignore ├── README.md ├── config └── config.go ├── getter ├── getter.go └── tools.go ├── go.mod ├── go.sum ├── main.go ├── sender └── sender.go ├── theme1.png ├── theme2.png ├── theme3.png ├── theme4.png └── ui ├── theme1 ├── handler.go └── ui.go ├── theme2 ├── handler.go └── ui.go ├── theme3 ├── handler.go └── ui.go ├── theme4 ├── handler.go └── ui.go └── ui.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | node_modules/ 4 | dist/ 5 | tmp/ 6 | temp/ 7 | logs/ 8 | *.log 9 | **/dump.rdb 10 | **/.DS_Store 11 | bin/danmu_geter 12 | bin/danmu_sender 13 | 14 | **/config.toml 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bilibili 直播间 TUI 2 | 3 | [关联的bilibili介绍视频](https://www.bilibili.com/video/bv1gG411G7XG) 4 | 5 | 使用方法 直接下载 releases 中的 bin文件即可 6 | 7 | --- 8 | 9 | 风格1: chatroom 10 | 11 | ![t1](./theme1.png) 12 | 13 | 风格2: pure 14 | 15 | ![t2](./theme2.png) 16 | 17 | 风格3: simple 18 | 19 | ![t3](./theme3.png) 20 | 21 | 风格4: info (感谢@soft98-top添加的theme4) 22 | 23 | ![t4](./theme4.png) 24 | 25 | 项目文件: 26 | 27 | ```plaintext 28 | sender 发送弹幕的实现 29 | getter 获取弹幕的实现 30 | ui TUI的实现 31 | ``` 32 | 33 | 使用: 34 | 35 | go run main.go 36 | 37 | 也可以从 参数定义 roomId, theme 优先级高于config(-r roomId, -t theme) 38 | 39 | go run main.go -c config.toml -r 9527 -t 1 40 | 41 | 配置: 42 | 43 | 默认配置文件: ~/.config/bili/config.toml 44 | 45 | 参数说明: 46 | 1. `-c string:configfile` 47 | 2. `-r string:roomId` 48 | 3. `-t int:theme` 49 | 4. `-l int:singleline` 50 | 5. `-s int:showtime` 51 | 52 | 快捷键: 53 | 1. \ 退出 54 | 2. 退出 55 | 3. 清空输入内容 56 | 4. 上一个输入记录 57 | 5. 下一个输入记录 58 | 59 | ## 类似项目 60 | 61 | [zaiic/bili-live-chat](https://github.com/zaiic/bili-live-chat): A bilibili streaming chat tool using TUI written in Rust. 62 | 63 | ## 贡献者 64 | 65 | - [yaocccc](https://github.com/yaocccc) 66 | - [soft98-top](https://github.com/soft98-top) 67 | - [PR#3 增加theme4,修复直播间rank显示](https://github.com/yaocccc/bilibili_live_tui/pull/3) 68 | - [zaiic](https://github.com/zaiic) 69 | - [PR#4 更新README,添加类似项目](https://github.com/yaocccc/bilibili_live_tui/pull/4) 70 | - [Ruixi-rebirth](https://github.com/Ruixi-rebirth) 71 | - [PR#6 自动创建配置文件到 $HOME/.config/bili/config.toml](https://github.com/yaocccc/bilibili_live_tui/pull/6) 72 | 73 | ## Support: buy me a coffee) 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "os/user" 8 | "strings" 9 | 10 | "github.com/BurntSushi/toml" 11 | bg "github.com/iyear/biligo" 12 | ) 13 | 14 | type ConfigType struct { 15 | Cookie string // 登录cookie 16 | RoomId int64 // 直播间id 17 | Theme int64 // 主题 18 | SingleLine int64 // 是否开启单行 19 | ShowTime int64 // 是否显示时间 20 | TimeColor string // 时间颜色 21 | NameColor string // 名字颜色 22 | ContentColor string // 内容颜色 23 | FrameColor string // 边框颜色 24 | InfoColor string // 房间信息颜色 25 | RankColor string // 排行榜颜色 26 | Background string // 背景颜色 27 | } 28 | 29 | var Auth bg.CookieAuth 30 | var Config ConfigType 31 | 32 | func defaultCfgFile() (configFile string, err error) { 33 | currentUser, err := user.Current() 34 | if err != nil { 35 | return 36 | } 37 | homeDir := currentUser.HomeDir 38 | path := homeDir + "/.config/bili" 39 | if err = os.MkdirAll(path, 0755); err != nil { 40 | return 41 | } 42 | configFile = path + "/config.toml" 43 | _, err = os.Stat(configFile) 44 | if os.IsNotExist(err) { 45 | var f *os.File 46 | config := ConfigType{ 47 | Cookie: "从你BILIBILI的请求里抓一个Cookie", 48 | RoomId: 23333333, 49 | Theme: 1, 50 | SingleLine: 1, 51 | ShowTime: 1, 52 | TimeColor: "#FFFFFF", 53 | NameColor: "#FFFFFF", 54 | ContentColor: "#FFFFFF", 55 | FrameColor: "#FFFFFF", 56 | InfoColor: "#FFFFFF", 57 | RankColor: "#FFFFFF", 58 | Background: "NONE", // 默认无背景颜色 NONE表示无背景颜色 59 | } 60 | f, err = os.Create(configFile) 61 | if err != nil { 62 | return 63 | } 64 | defer f.Close() 65 | if err = toml.NewEncoder(f).Encode(config); err != nil { 66 | return 67 | } 68 | 69 | panic("配置文件已生成,请修改配置文件后再次运行,配置文件路径为:" + configFile) 70 | } 71 | 72 | return 73 | } 74 | 75 | func Init() { 76 | var err error 77 | configFile := "" 78 | roomId := int64(-1) 79 | theme := int64(-1) 80 | single_line := int64(-1) 81 | show_time := int64(-1) 82 | flag.StringVar(&configFile, "c", "", "usage for config") 83 | flag.Int64Var(&roomId, "r", -1, "usage for room id") 84 | flag.Int64Var(&theme, "t", -1, "usage for theme") 85 | flag.Int64Var(&single_line, "l", -1, "usage for single_line") 86 | flag.Int64Var(&show_time, "s", -1, "usage for show_time") 87 | flag.Parse() 88 | 89 | if configFile == "" { 90 | configFile, err = defaultCfgFile() 91 | if err != nil { 92 | panic(err) 93 | } 94 | } 95 | 96 | if _, err := toml.DecodeFile(configFile, &Config); err != nil { 97 | fmt.Printf("Error decoding config.toml: %s\n", err) 98 | } 99 | if Config.Cookie == "从你BILIBILI的请求里抓一个Cookie" { 100 | panic("请检查配置文件是否正确: " + configFile) 101 | } 102 | 103 | if roomId != -1 { 104 | Config.RoomId = roomId 105 | } 106 | if theme != -1 { 107 | Config.Theme = theme 108 | } 109 | if single_line != -1 { 110 | Config.SingleLine = single_line 111 | } 112 | if show_time != -1 { 113 | Config.ShowTime = show_time 114 | } 115 | if Config.TimeColor == "" { 116 | Config.TimeColor = "#bbbbbb" 117 | } 118 | if Config.NameColor == "" { 119 | Config.NameColor = "#bbbbbb" 120 | } 121 | if Config.ContentColor == "" { 122 | Config.ContentColor = "#bbbbbb" 123 | } 124 | if Config.TimeColor == "" { 125 | Config.TimeColor = "#bbbbbb" 126 | } 127 | if Config.NameColor == "" { 128 | Config.NameColor = "#bbbbbb" 129 | } 130 | if Config.ContentColor == "" { 131 | Config.ContentColor = "#bbbbbb" 132 | } 133 | if Config.InfoColor == "" { 134 | Config.InfoColor = "#bbbbbb" 135 | } 136 | if Config.RankColor == "" { 137 | Config.RankColor = "#bbbbbb" 138 | } 139 | if Config.FrameColor == "" { 140 | Config.FrameColor = "#bbbbbb" 141 | } 142 | if Config.Background == "" { 143 | Config.Background = "NONE" 144 | } 145 | 146 | attrs := strings.Split(Config.Cookie, ";") 147 | kvs := make(map[string]string) 148 | for _, attr := range attrs { 149 | kv := strings.Split(attr, "=") 150 | k := strings.Trim(kv[0], " ") 151 | v := strings.Trim(kv[1], " ") 152 | kvs[k] = v 153 | } 154 | Auth.SESSDATA = kvs["SESSDATA"] 155 | Auth.DedeUserID = kvs["DedeUserID"] 156 | Auth.DedeUserIDCkMd5 = kvs["DedeUserID__ckMd5"] 157 | Auth.BiliJCT = kvs["bili_jct"] 158 | } 159 | -------------------------------------------------------------------------------- /getter/getter.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | import ( 4 | "bili/config" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "time" 9 | 10 | myhttp "github.com/BYT0723/go-tools/http" 11 | 12 | "github.com/asmcos/requests" 13 | "github.com/gorilla/websocket" 14 | bg "github.com/iyear/biligo" 15 | 16 | "github.com/tidwall/gjson" 17 | ) 18 | 19 | type DanmuClient struct { 20 | roomID uint32 21 | auth bg.CookieAuth 22 | conn *websocket.Conn 23 | unzlibChannel chan []byte 24 | isClosed bool 25 | } 26 | 27 | type OnlineRankUser struct { 28 | Name string 29 | Score int64 30 | Rank int64 31 | } 32 | 33 | type RoomInfo struct { 34 | RoomId int 35 | Uid int 36 | Title string 37 | ParentAreaName string 38 | AreaName string 39 | Online int64 40 | Attention int64 41 | Time string 42 | OnlineRankUsers []OnlineRankUser 43 | } 44 | 45 | type DanmuMsg struct { 46 | Author string 47 | Content string 48 | Type string 49 | Time time.Time 50 | } 51 | 52 | type receivedInfo struct { 53 | Cmd string `json:"cmd"` 54 | Data map[string]interface{} `json:"data"` 55 | Info []interface{} `json:"info"` 56 | Full map[string]interface{} `json:"full"` 57 | Half map[string]interface{} `json:"half"` 58 | Side map[string]interface{} `json:"side"` 59 | RoomID uint32 `json:"roomid"` 60 | RealRoomID uint32 `json:"real_roomid"` 61 | MsgCommon string `json:"msg_common"` 62 | MsgSelf string `json:"msg_self"` 63 | LinkUrl string `json:"link_url"` 64 | MsgType string `json:"msg_type"` 65 | ShieldUID string `json:"shield_uid"` 66 | BusinessID string `json:"business_id"` 67 | Scatter map[string]interface{} `json:"scatter"` 68 | } 69 | 70 | type handShakeInfo struct { 71 | UID uint32 `json:"uid"` 72 | Roomid uint32 `json:"roomid"` 73 | Protover uint8 `json:"protover"` 74 | Buvid string `json:"buvid"` 75 | Platform string `json:"platform"` 76 | Type uint8 `json:"type"` 77 | Key string `json:"key"` 78 | } 79 | 80 | func (d *DanmuClient) connect() (err error) { 81 | var ( 82 | uid uint32 83 | body []byte 84 | header = http.Header{ 85 | "Cookie": []string{config.Config.Cookie}, 86 | } 87 | ) 88 | 89 | _, body, err = myhttp.Get("https://api.bilibili.com/x/web-interface/nav", header, nil) 90 | if err != nil { 91 | return err 92 | } 93 | uid = uint32(gjson.GetBytes(body, "data.mid").Int()) 94 | 95 | _, body, err = myhttp.Get(fmt.Sprintf("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=%d", d.roomID), header, nil) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | token := gjson.GetBytes(body, "data.token").String() 101 | hostList := []string{} 102 | gjson.GetBytes(body, "data.host_list").ForEach(func(key, value gjson.Result) bool { 103 | hostList = append(hostList, value.Get("host").String()) 104 | return true 105 | }) 106 | hsInfo := handShakeInfo{ 107 | UID: uid, 108 | Roomid: d.roomID, 109 | Protover: 2, 110 | Platform: "web", 111 | Type: 2, 112 | Key: token, 113 | } 114 | 115 | header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36") 116 | header.Set("Accept", "*/*") 117 | header.Set("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2") 118 | header.Set("Accept-Encoding", "gzip, deflate, br") 119 | header.Set("Origin", "https://live.bilibili.com") 120 | header.Set("Pragma", "no-cache") 121 | header.Set("Cache-Control", "no-cache") 122 | header.Set("Custom-Header", "CustomValue") 123 | for _, h := range hostList { 124 | d.conn, _, err = websocket.DefaultDialer.Dial(fmt.Sprintf("wss://%s:443/sub", h), header) 125 | if err != nil { 126 | continue 127 | } 128 | break 129 | } 130 | if err != nil { 131 | return 132 | } 133 | body, err = json.Marshal(hsInfo) 134 | if err != nil { 135 | return 136 | } 137 | 138 | err = d.sendPackage(0, 16, 1, 7, 1, body) 139 | return 140 | } 141 | 142 | var historied = false 143 | 144 | func (d *DanmuClient) getHistory(busChan chan DanmuMsg) { 145 | if historied { 146 | return 147 | } 148 | 149 | historyApi := fmt.Sprintf("https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory?roomid=%d", d.roomID) 150 | r, err := requests.Get(historyApi) 151 | if err != nil { 152 | return 153 | } 154 | 155 | histories := gjson.Get(r.Text(), "data.room").Array() 156 | for _, history := range histories { 157 | t, _ := time.Parse("2006-01-02 15:04:05", history.Get("timeline").String()) 158 | danmu := DanmuMsg{ 159 | Author: history.Get("nickname").String(), 160 | Content: history.Get("text").String(), 161 | Type: "DANMU_MSG", 162 | Time: t, 163 | } 164 | busChan <- danmu 165 | } 166 | historied = true 167 | } 168 | 169 | func (d *DanmuClient) heartBeat(msgChan chan DanmuMsg) { 170 | for { 171 | if d.isClosed { 172 | return 173 | } 174 | obj := []byte("5b6f626a656374204f626a6563745d") 175 | if err := d.sendPackage(0, 16, 1, 2, 1, obj); err != nil { 176 | msgChan <- DanmuMsg{ 177 | // Author 178 | // Content string 179 | // Type string 180 | } 181 | continue 182 | } 183 | time.Sleep(30 * time.Second) 184 | } 185 | } 186 | 187 | // fuck 每次启动总容易失败panic 188 | func (d *DanmuClient) receiveRawMsg(busChan chan DanmuMsg) { 189 | for { 190 | if d.isClosed { 191 | return 192 | } 193 | _, rawMsg, err := d.conn.ReadMessage() 194 | if err != nil { 195 | d.isClosed = true 196 | } 197 | if len(rawMsg) >= 8 && rawMsg[7] == 2 { 198 | msgs := splitMsg(zlibUnCompress(rawMsg[16:])) 199 | for _, msg := range msgs { 200 | uz := msg[16:] 201 | js := new(receivedInfo) 202 | json.Unmarshal(uz, js) 203 | m := DanmuMsg{} 204 | switch js.Cmd { 205 | case "COMBO_SEND": 206 | m.Author = js.Data["uname"].(string) 207 | m.Content = fmt.Sprintf("送给 %s %d 个 %s", js.Data["r_uname"].(string), int(js.Data["combo_num"].(float64)), js.Data["gift_name"].(string)) 208 | case "DANMU_MSG": 209 | m.Author = js.Info[2].([]interface{})[1].(string) 210 | m.Content = js.Info[1].(string) 211 | case "GUARD_BUY": 212 | m.Author = js.Data["username"].(string) 213 | m.Content = fmt.Sprintf("购买了 %s", js.Data["giftName"].(string)) 214 | case "INTERACT_WORD": 215 | m.Author = js.Data["uname"].(string) 216 | m.Content = "进入了房间" 217 | case "SEND_GIFT": 218 | m.Author = js.Data["uname"].(string) 219 | m.Content = fmt.Sprintf("投喂了 %d 个 %s", int(js.Data["num"].(float64)), js.Data["giftName"].(string)) 220 | case "USER_TOAST_MSG": 221 | m.Author = "system" 222 | m.Content = js.Data["toast_msg"].(string) 223 | case "NOTICE_MSG": 224 | m.Author = "system" 225 | m.Content = js.MsgSelf 226 | default: // "LIVE" "ACTIVITY_BANNER_UPDATE_V2" "ONLINE_RANK_COUNT" "ONLINE_RANK_TOP3" "ONLINE_RANK_V2" "PANEL" "PREPARING" "WIDGET_BANNER" "LIVE_INTERACTIVE_GAME" 227 | continue 228 | } 229 | m.Type = js.Cmd 230 | m.Time = time.Now() 231 | busChan <- m 232 | } 233 | } 234 | } 235 | } 236 | 237 | func (d *DanmuClient) syncRoomInfo(roomInfoChan chan RoomInfo) { 238 | for { 239 | if d.isClosed { 240 | return 241 | } 242 | 243 | roomInfoApi := fmt.Sprintf("https://api.live.bilibili.com/room/v1/room/get_info?room_id=%d", d.roomID) 244 | roomInfo := new(RoomInfo) 245 | roomInfo.OnlineRankUsers = make([]OnlineRankUser, 0) 246 | r1, err1 := requests.Get(roomInfoApi) 247 | if err1 == nil { 248 | roomInfo.RoomId = int(d.roomID) 249 | roomInfo.Uid = int(gjson.Get(r1.Text(), "data.uid").Int()) 250 | roomInfo.Title = gjson.Get(r1.Text(), "data.title").String() 251 | roomInfo.AreaName = gjson.Get(r1.Text(), "data.area_name").String() 252 | roomInfo.ParentAreaName = gjson.Get(r1.Text(), "data.parent_area_name").String() 253 | roomInfo.Online = gjson.Get(r1.Text(), "data.online").Int() 254 | roomInfo.Attention = gjson.Get(r1.Text(), "data.attention").Int() 255 | _time, _ := time.Parse("2006-01-02 15:04:05", gjson.Get(r1.Text(), "data.live_time").String()) 256 | seconds := time.Now().Unix() - _time.Unix() + 8*60*60 257 | days := seconds / 86400 258 | hours := (seconds % 86400) / 3600 259 | minutes := (seconds % 3600) / 60 260 | if days > 0 { 261 | roomInfo.Time = fmt.Sprintf("%d天%d时%d分", days, hours, minutes) 262 | } else if hours > 0 { 263 | roomInfo.Time = fmt.Sprintf("%d时%d分", hours, minutes) 264 | } else { 265 | roomInfo.Time = fmt.Sprintf("%d分", minutes) 266 | } 267 | } 268 | 269 | onlineRankApi := fmt.Sprintf("https://api.live.bilibili.com/xlive/general-interface/v1/rank/getOnlineGoldRank?ruid=%d&roomId=%d&page=1&pageSize=50", roomInfo.Uid, d.roomID) 270 | r2, err2 := requests.Get(onlineRankApi) 271 | if err2 == nil { 272 | rawUsers := gjson.Get(r2.Text(), "data.OnlineRankItem").Array() 273 | for _, rawUser := range rawUsers { 274 | user := OnlineRankUser{ 275 | Name: rawUser.Get("name").String(), 276 | Score: rawUser.Get("score").Int(), 277 | Rank: rawUser.Get("userRank").Int(), 278 | } 279 | roomInfo.OnlineRankUsers = append(roomInfo.OnlineRankUsers, user) 280 | } 281 | } 282 | 283 | roomInfoChan <- *roomInfo 284 | time.Sleep(30 * time.Second) 285 | } 286 | } 287 | 288 | // 总是会崩溃,不如直接重启 289 | func supervisor(busChan chan DanmuMsg, roomInfoChan chan RoomInfo) { 290 | dc := DanmuClient{ 291 | roomID: uint32(config.Config.RoomId), 292 | auth: config.Auth, 293 | conn: new(websocket.Conn), 294 | unzlibChannel: make(chan []byte, 100), 295 | } 296 | 297 | defer func() { 298 | busChan <- DanmuMsg{ 299 | Author: "system", 300 | Content: "弹幕服务器已断开,正在重连 :)", 301 | Type: "NOTICE_MSG", 302 | Time: time.Now(), 303 | } 304 | dc.isClosed = true 305 | dc.conn.Close() 306 | time.Sleep(1 * time.Second) 307 | supervisor(busChan, roomInfoChan) 308 | }() 309 | 310 | err := dc.connect() 311 | if err != nil { 312 | panic(err) 313 | } 314 | 315 | go dc.getHistory(busChan) 316 | go dc.receiveRawMsg(busChan) 317 | go dc.syncRoomInfo(roomInfoChan) 318 | go dc.heartBeat(busChan) 319 | 320 | for { 321 | time.Sleep(1 * time.Second) 322 | if dc.isClosed { 323 | return 324 | } 325 | } 326 | } 327 | 328 | func Run(busChan chan DanmuMsg, roomInfoChan chan RoomInfo) { 329 | go supervisor(busChan, roomInfoChan) 330 | } 331 | -------------------------------------------------------------------------------- /getter/tools.go: -------------------------------------------------------------------------------- 1 | package getter 2 | 3 | import ( 4 | "bytes" 5 | "compress/zlib" 6 | "encoding/binary" 7 | "encoding/hex" 8 | "fmt" 9 | "io" 10 | "math" 11 | 12 | "github.com/gorilla/websocket" 13 | ) 14 | 15 | func zlibUnCompress(compressSrc []byte) []byte { 16 | b := bytes.NewReader(compressSrc) 17 | var out bytes.Buffer 18 | r, _ := zlib.NewReader(b) 19 | io.Copy(&out, r) 20 | return out.Bytes() 21 | } 22 | 23 | func ByteArrToDecimal(src []byte) (sum int) { 24 | if src == nil { 25 | return 0 26 | } 27 | b := []byte(hex.EncodeToString(src)) 28 | l := len(b) 29 | for i := l - 1; i >= 0; i-- { 30 | base := int(math.Pow(16, float64(l-i-1))) 31 | var mul int 32 | if int(b[i]) >= 97 { 33 | mul = int(b[i]) - 87 34 | } else { 35 | mul = int(b[i]) - 48 36 | } 37 | 38 | sum += base * mul 39 | } 40 | return 41 | } 42 | 43 | func (d *DanmuClient) sendPackage(packetlen uint32, magic uint16, ver uint16, typeID uint32, param uint32, data []byte) (err error) { 44 | packetHead := new(bytes.Buffer) 45 | 46 | if packetlen == 0 { 47 | packetlen = uint32(len(data) + 16) 48 | } 49 | var pdata = []interface{}{ 50 | packetlen, 51 | magic, 52 | ver, 53 | typeID, 54 | param, 55 | } 56 | 57 | // 将包的头部信息以大端序方式写入字节数组 58 | for _, v := range pdata { 59 | if err = binary.Write(packetHead, binary.BigEndian, v); err != nil { 60 | fmt.Println("binary.Write err: ", err) 61 | return 62 | } 63 | } 64 | 65 | // 将包内数据部分追加到数据包内 66 | sendData := append(packetHead.Bytes(), data...) 67 | 68 | // fmt.Println("本次发包消息为:", sendData) 69 | 70 | if err = d.conn.WriteMessage(websocket.BinaryMessage, sendData); err != nil { 71 | fmt.Println("conn.Write err: ", err) 72 | return 73 | } 74 | 75 | return 76 | } 77 | 78 | func splitMsg(src []byte) (msgs [][]byte) { 79 | lens := ByteArrToDecimal(src[:4]) 80 | totalLen := len(src) 81 | startLoc := 0 82 | for { 83 | if startLoc+lens <= totalLen { 84 | msgs = append(msgs, src[startLoc:startLoc+lens]) 85 | startLoc += lens 86 | if startLoc < totalLen { 87 | lens = ByteArrToDecimal(src[startLoc : startLoc+4]) 88 | } else { 89 | break 90 | } 91 | } else { 92 | break 93 | } 94 | } 95 | return msgs 96 | } 97 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module bili 2 | 3 | go 1.21.0 4 | 5 | require ( 6 | github.com/BYT0723/go-tools v0.0.17 7 | github.com/BurntSushi/toml v1.2.1 8 | github.com/asmcos/requests v0.0.0-20210319030608-c839e8ae4946 9 | github.com/gdamore/tcell/v2 v2.6.0 10 | github.com/gorilla/websocket v1.5.0 11 | github.com/iyear/biligo v0.1.6 12 | github.com/rivo/tview v0.0.0-20230330183452-5796b0cd5c1f 13 | github.com/tidwall/gjson v1.14.4 14 | ) 15 | 16 | require ( 17 | github.com/gdamore/encoding v1.0.0 // indirect 18 | github.com/golang/protobuf v1.5.3 // indirect 19 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 20 | github.com/mattn/go-runewidth v0.0.14 // indirect 21 | github.com/pkg/errors v0.9.1 // indirect 22 | github.com/rivo/uniseg v0.4.4 // indirect 23 | github.com/tidwall/match v1.1.1 // indirect 24 | github.com/tidwall/pretty v1.2.1 // indirect 25 | golang.org/x/sys v0.26.0 // indirect 26 | golang.org/x/term v0.7.0 // indirect 27 | golang.org/x/text v0.19.0 // indirect 28 | google.golang.org/protobuf v1.35.1 // indirect 29 | ) 30 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BYT0723/go-tools v0.0.17 h1:2dIqsK9HImdrhA1bvOk6CjSRQd2AfqLX6PDxfuVrW78= 2 | github.com/BYT0723/go-tools v0.0.17/go.mod h1:4Exinju0qXvZz8naSk2EcqeGTZbYbdlWlZWEdDnDUSM= 3 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 4 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 5 | github.com/asmcos/requests v0.0.0-20210319030608-c839e8ae4946 h1:1B8lZnGJOS3E7LumjuY6lb2NzXy8vBY6N2ag/IK6JdI= 6 | github.com/asmcos/requests v0.0.0-20210319030608-c839e8ae4946/go.mod h1:2W5PB6UTVRBypeouEebhwOJrDZOfJvPwMP1mtD8ZXM4= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 10 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 11 | github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg= 12 | github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y= 13 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 14 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 15 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 16 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 17 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 18 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 19 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 20 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 21 | github.com/iyear/biligo v0.1.6 h1:oIVcW0rJ5xEhbske5F4N0ScK7EFKfTmtmVJnSZXsGlg= 22 | github.com/iyear/biligo v0.1.6/go.mod h1:QpiQhs/FdSg+ihwRtKfdU7VTmLjTkVwf1DqyZxcOqKI= 23 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 24 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 25 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 26 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 27 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 28 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 29 | github.com/rivo/tview v0.0.0-20230330183452-5796b0cd5c1f h1:vpjWdGBgikHYD4ruBvDINMxwdh5UWVck9yOyrwFktMo= 30 | github.com/rivo/tview v0.0.0-20230330183452-5796b0cd5c1f/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE= 31 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 32 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 33 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 34 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 35 | github.com/tidwall/gjson v1.8.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= 36 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 37 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 38 | github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 39 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 40 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 41 | github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= 42 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 43 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 44 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 45 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 46 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 47 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 48 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 49 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 50 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 51 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 52 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 53 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 54 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 55 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 56 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 57 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 59 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 60 | golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= 61 | golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 62 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 63 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 64 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 65 | golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= 66 | golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 67 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 68 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 69 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 70 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 71 | golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= 72 | golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 73 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 74 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 75 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 76 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 77 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 78 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 79 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 80 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 81 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 82 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= 83 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 84 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bili/config" 5 | "bili/getter" 6 | "bili/sender" 7 | "bili/ui" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | /** 用于修正环境变量 */ 14 | func fixCharset() { 15 | locale := os.Getenv("LANG") 16 | var asianCharset bool 17 | var wideCharset = []string{"zh_", "jp_", "ko_", "ja_", "th_", "hi_"} 18 | for k := range wideCharset { 19 | if strings.HasPrefix(locale, wideCharset[k]) { 20 | asianCharset = true 21 | } 22 | } 23 | if asianCharset { 24 | os.Setenv("LANG", "C.UTF-8") 25 | cmd := exec.Command(os.Args[0], os.Args[1:]...) 26 | cmd.Stdin = os.Stdin 27 | cmd.Stdout = os.Stdout 28 | cmd.Stderr = os.Stderr 29 | cmd.Run() 30 | os.Exit(0) 31 | } 32 | } 33 | 34 | func main() { 35 | fixCharset() 36 | config.Init() 37 | busChan := make(chan getter.DanmuMsg, 100) 38 | roomInfoChan := make(chan getter.RoomInfo, 100) 39 | getter.Run(busChan, roomInfoChan) 40 | sender.Run() 41 | ui.Run(busChan, roomInfoChan) 42 | } 43 | -------------------------------------------------------------------------------- /sender/sender.go: -------------------------------------------------------------------------------- 1 | package sender 2 | 3 | import ( 4 | "bili/config" 5 | "bili/getter" 6 | "fmt" 7 | "os" 8 | "time" 9 | 10 | bg "github.com/iyear/biligo" 11 | ) 12 | 13 | var bc *bg.BiliClient 14 | var err error 15 | 16 | func heartbeat() { 17 | start := time.Now() 18 | err := bc.VideoHeartBeat(242531611, 173439442, int64(time.Since(start).Seconds())) 19 | if err != nil { 20 | fmt.Println("failed to send heartbeat; error:", err) 21 | os.Exit(0) 22 | } 23 | time.AfterFunc(time.Second*10, heartbeat) 24 | } 25 | 26 | func SendMsg(roomId int64, msg string, busChan chan getter.DanmuMsg) { 27 | msgRune := []rune(msg) 28 | for i := 0; i < len(msgRune); i += 20 { 29 | err = nil 30 | if i+20 < len(msgRune) { 31 | err = bc.LiveSendDanmaku(roomId, 16777215, 25, 1, string(msgRune[i:i+20]), 0) 32 | time.Sleep(time.Second * 1) 33 | } else { 34 | err = bc.LiveSendDanmaku(roomId, 16777215, 25, 1, string(msgRune[i:]), 0) 35 | } 36 | if err != nil { 37 | busChan <- getter.DanmuMsg{Author: "system", Content: "发送弹幕失败", Type: ""} 38 | } 39 | } 40 | } 41 | 42 | func Run() { 43 | retry := 0 44 | for ; retry < 3; retry++ { 45 | bc, err = bg.NewBiliClient(&bg.BiliSetting{ 46 | Auth: &config.Auth, 47 | DebugMode: false, 48 | }) 49 | if err == nil { 50 | break 51 | } 52 | time.Sleep(time.Second * 1) 53 | } 54 | if retry == 3 { 55 | os.Exit(0) 56 | } 57 | go heartbeat() 58 | } 59 | -------------------------------------------------------------------------------- /theme1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaocccc/bilibili_live_tui/06c76edde4051081e57beb19f46fb7bce365b176/theme1.png -------------------------------------------------------------------------------- /theme2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaocccc/bilibili_live_tui/06c76edde4051081e57beb19f46fb7bce365b176/theme2.png -------------------------------------------------------------------------------- /theme3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaocccc/bilibili_live_tui/06c76edde4051081e57beb19f46fb7bce365b176/theme3.png -------------------------------------------------------------------------------- /theme4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaocccc/bilibili_live_tui/06c76edde4051081e57beb19f46fb7bce365b176/theme4.png -------------------------------------------------------------------------------- /ui/theme1/handler.go: -------------------------------------------------------------------------------- 1 | package theme1 2 | 3 | import ( 4 | "bili/config" 5 | "bili/getter" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/rivo/tview" 10 | ) 11 | 12 | func roomInfoHandler(app *tview.Application, roomInfoView *tview.TextView, rankUsersView *tview.TextView, roomInfoChan chan getter.RoomInfo) { 13 | for roomInfo := range roomInfoChan { 14 | roomInfoView.SetText( 15 | "[" + config.Config.InfoColor + "]" + 16 | roomInfo.Title + "\n" + 17 | fmt.Sprintf("ID: %d", roomInfo.RoomId) + "\n" + 18 | fmt.Sprintf("分区: %s/%s", roomInfo.ParentAreaName, roomInfo.AreaName) + "\n" + 19 | fmt.Sprintf("👀: %d", roomInfo.Online) + "\n" + 20 | fmt.Sprintf("❤️: %d", roomInfo.Attention) + "\n" + 21 | fmt.Sprintf("🕒: %s", roomInfo.Time) + "\n", 22 | ) 23 | rankUsersView.SetTitle(fmt.Sprintf("Rank(%d)", len(roomInfo.OnlineRankUsers))) 24 | 25 | rankUserStr := "" 26 | spec := []string{"👑 ", "🥈 ", "🥉 "} 27 | for idx, rankUser := range roomInfo.OnlineRankUsers { 28 | rankUserStr += "[" + config.Config.RankColor + "]" 29 | if idx < 3 { 30 | rankUserStr += spec[idx] + rankUser.Name + "\n" 31 | } else { 32 | rankUserStr += " " + rankUser.Name + "\n" 33 | } 34 | } 35 | strings.TrimRight(rankUserStr, "\n") 36 | rankUsersView.SetText(rankUserStr) 37 | // 滚动到顶部 避免过长显示下半部分 38 | roomInfoView.ScrollToBeginning() 39 | rankUsersView.ScrollToBeginning() 40 | app.Draw() 41 | } 42 | } 43 | 44 | var lastMsg = getter.DanmuMsg{} 45 | var lastLine = "" 46 | 47 | func danmuHandler(app *tview.Application, messages *tview.TextView, busChan chan getter.DanmuMsg) { 48 | for msg := range busChan { 49 | if strings.Trim(msg.Content, " ") == "" { 50 | continue 51 | } 52 | 53 | viewStr := messages.GetText(false) 54 | str := "" 55 | 56 | // 留意前面的空格显示 57 | timeStr := msg.Time.Format(" 15:04") 58 | if config.Config.ShowTime == 0 { 59 | timeStr = "" 60 | } 61 | 62 | if config.Config.SingleLine == 1 { 63 | str += fmt.Sprintf("[%s]%s [%s]%s[%s] %s", config.Config.TimeColor, timeStr, config.Config.NameColor, msg.Author, config.Config.ContentColor, msg.Content) 64 | } else { 65 | if lastMsg.Type != msg.Type || lastMsg.Author != msg.Author || (timeStr != "" && lastMsg.Time.Format("15:04") != msg.Time.Format("15:04")) { 66 | str += fmt.Sprintf("[%s]%s [%s]%s[%s]", config.Config.TimeColor, timeStr, config.Config.NameColor, msg.Author, config.Config.ContentColor) + "\n" 67 | } 68 | str += fmt.Sprintf(" %s", msg.Content) + "\n" 69 | } 70 | 71 | messages.SetText(viewStr + strings.TrimRight(str, "\n")) 72 | lastMsg = msg 73 | app.Draw() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ui/theme1/ui.go: -------------------------------------------------------------------------------- 1 | // 聊天室主题 2 | 3 | package theme1 4 | 5 | import ( 6 | "bili/config" 7 | "bili/getter" 8 | "bili/sender" 9 | 10 | "github.com/gdamore/tcell/v2" 11 | "github.com/rivo/tview" 12 | ) 13 | 14 | var submitHistory = []string{} 15 | var submitHistoryIndex = 0 16 | var bg = tcell.ColorDefault 17 | 18 | func setBoxAttr(box *tview.Box, title string) { 19 | box.SetBorder(true) 20 | box.SetTitleAlign(tview.AlignLeft) 21 | box.SetTitle(title) 22 | box.SetBackgroundColor(bg) 23 | box.SetBorderColor(tcell.GetColor(config.Config.FrameColor)) 24 | box.SetTitleColor(tcell.GetColor(config.Config.FrameColor)) 25 | } 26 | 27 | func drawSlidebar() (*tview.Grid, *tview.TextView, *tview.TextView) { 28 | slidebarGrid := tview.NewGrid().SetRows(0, 0).SetBorders(false) 29 | roomInfoView := tview.NewTextView().SetDynamicColors(true) 30 | roomInfoView.SetBackgroundColor(bg) 31 | setBoxAttr(roomInfoView.Box, "RoomInfo") 32 | 33 | rankUsersView := tview.NewTextView().SetDynamicColors(true) 34 | rankUsersView.SetBackgroundColor(bg) 35 | setBoxAttr(rankUsersView.Box, "RankUsers") 36 | 37 | slidebarGrid. 38 | AddItem(roomInfoView, 0, 0, 1, 1, 0, 0, false). 39 | AddItem(rankUsersView, 1, 0, 1, 1, 0, 0, false) 40 | 41 | return slidebarGrid, roomInfoView, rankUsersView 42 | } 43 | 44 | func drawChat() (*tview.Grid, *tview.InputField, *tview.TextView) { 45 | chatGrid := tview.NewGrid().SetRows(0, 3).SetBorders(false) 46 | messagesView := tview.NewTextView().SetDynamicColors(true) 47 | messagesView.SetBackgroundColor(bg) 48 | setBoxAttr(messagesView.Box, "Messages") 49 | 50 | input := tview.NewInputField() 51 | input.SetFormAttributes(0, tcell.ColorDefault, bg, tcell.ColorDefault, bg) 52 | setBoxAttr(input.Box, "Send") 53 | 54 | chatGrid. 55 | AddItem(messagesView, 0, 0, 1, 1, 0, 0, false). 56 | AddItem(input, 1, 0, 1, 1, 0, 0, true) 57 | 58 | return chatGrid, input, messagesView 59 | } 60 | 61 | func draw(app *tview.Application, roomId int64, busChan chan getter.DanmuMsg, roomInfoChan chan getter.RoomInfo) *tview.Grid { 62 | slidebarGrid, roomInfoView, rankUsersView := drawSlidebar() 63 | chatGrid, input, messagesView := drawChat() 64 | rootGrid := tview.NewGrid().SetColumns(20, 0).SetBorders(false) 65 | rootGrid. 66 | AddItem(slidebarGrid, 0, 0, 1, 1, 0, 0, false). 67 | AddItem(chatGrid, 0, 1, 1, 1, 0, 0, true) 68 | 69 | go roomInfoHandler(app, roomInfoView, rankUsersView, roomInfoChan) 70 | go danmuHandler(app, messagesView, busChan) 71 | 72 | input.SetDoneFunc(func(key tcell.Key) { 73 | if key == tcell.KeyEnter { 74 | go sender.SendMsg(roomId, input.GetText(), busChan) 75 | 76 | submitHistory = append(submitHistory, input.GetText()) 77 | if len(submitHistory) > 10 { 78 | submitHistory = submitHistory[1:] 79 | } 80 | submitHistoryIndex = len(submitHistory) 81 | 82 | input.SetText("") 83 | } 84 | }) 85 | 86 | return rootGrid 87 | } 88 | 89 | func Run(busChan chan getter.DanmuMsg, roomInfoChan chan getter.RoomInfo) { 90 | if config.Config.Background != "NONE" { 91 | bg = tcell.GetColor(config.Config.Background) 92 | } 93 | app := tview.NewApplication() 94 | if err := app.SetRoot(draw(app, config.Config.RoomId, busChan, roomInfoChan), true).EnableMouse(false).Run(); err != nil { 95 | panic(err) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ui/theme2/handler.go: -------------------------------------------------------------------------------- 1 | package theme2 2 | 3 | import ( 4 | "bili/config" 5 | "bili/getter" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/rivo/tview" 10 | ) 11 | 12 | var lastMsg = getter.DanmuMsg{} 13 | var lastLine = "" 14 | 15 | func danmuHandler(app *tview.Application, messages *tview.TextView, busChan chan getter.DanmuMsg) { 16 | for msg := range busChan { 17 | if strings.Trim(msg.Content, " ") == "" { 18 | continue 19 | } 20 | 21 | viewStr := messages.GetText(false) 22 | str := "" 23 | 24 | // 留意前面的空格显示 25 | timeStr := msg.Time.Format(" 15:04") 26 | if config.Config.ShowTime == 0 { 27 | timeStr = "" 28 | } 29 | 30 | if config.Config.SingleLine == 1 { 31 | str += fmt.Sprintf("[%s]%s [%s]%s[%s] %s", config.Config.TimeColor, timeStr, config.Config.NameColor, msg.Author, config.Config.ContentColor, msg.Content) 32 | } else { 33 | if lastMsg.Type != msg.Type || lastMsg.Author != msg.Author || (timeStr != "" && lastMsg.Time.Format("15:04") != msg.Time.Format("15:04")) { 34 | str += fmt.Sprintf("[%s]%s [%s]%s[%s]", config.Config.TimeColor, timeStr, config.Config.NameColor, msg.Author, config.Config.ContentColor) + "\n" 35 | } 36 | str += fmt.Sprintf(" %s", msg.Content) + "\n" 37 | } 38 | messages.SetText(viewStr + strings.TrimRight(str, "\n")) 39 | lastMsg = msg 40 | app.Draw() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ui/theme2/ui.go: -------------------------------------------------------------------------------- 1 | // 极简主题 2 | 3 | package theme2 4 | 5 | import ( 6 | "bili/config" 7 | "bili/getter" 8 | "bili/sender" 9 | 10 | "github.com/gdamore/tcell/v2" 11 | "github.com/rivo/tview" 12 | ) 13 | 14 | var submitHistory = []string{} 15 | var submitHistoryIndex = 0 16 | var bg = tcell.ColorDefault 17 | 18 | func draw(app *tview.Application, roomId int64, busChan chan getter.DanmuMsg, roomInfoChan chan getter.RoomInfo) *tview.Grid { 19 | chatGrid := tview.NewGrid().SetRows(0, 1).SetBorders(false) 20 | messagesView := tview.NewTextView().SetDynamicColors(true) 21 | messagesView.SetBackgroundColor(bg) 22 | 23 | input := tview.NewInputField() 24 | input.SetFormAttributes(0, tcell.ColorDefault, bg, tcell.ColorDefault, bg) 25 | 26 | chatGrid. 27 | AddItem(messagesView, 0, 0, 1, 1, 0, 0, false). 28 | AddItem(input, 1, 0, 1, 1, 0, 0, true) 29 | 30 | go danmuHandler(app, messagesView, busChan) 31 | 32 | input.SetDoneFunc(func(key tcell.Key) { 33 | if key == tcell.KeyEnter { 34 | go sender.SendMsg(roomId, input.GetText(), busChan) 35 | 36 | submitHistory = append(submitHistory, input.GetText()) 37 | if len(submitHistory) > 10 { 38 | submitHistory = submitHistory[1:] 39 | } 40 | submitHistoryIndex = len(submitHistory) 41 | 42 | input.SetText("") 43 | } 44 | }) 45 | 46 | return chatGrid 47 | } 48 | 49 | func Run(busChan chan getter.DanmuMsg, roomInfoChan chan getter.RoomInfo) { 50 | if config.Config.Background != "NONE" { 51 | bg = tcell.GetColor(config.Config.Background) 52 | } 53 | app := tview.NewApplication() 54 | if err := app.SetRoot(draw(app, config.Config.RoomId, busChan, roomInfoChan), true).EnableMouse(false).Run(); err != nil { 55 | panic(err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ui/theme3/handler.go: -------------------------------------------------------------------------------- 1 | package theme3 2 | 3 | import ( 4 | "bili/config" 5 | "bili/getter" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/rivo/tview" 10 | ) 11 | 12 | func roomInfoHandler(app *tview.Application, roomInfoView *tview.TextView, roomInfoChan chan getter.RoomInfo) { 13 | for roomInfo := range roomInfoChan { 14 | roomInfoView.SetText( 15 | fmt.Sprintf("[%s] %d/%s %s/%s 👀: %d ❤️: %d 🕒: %s", config.Config.InfoColor, roomInfo.RoomId, roomInfo.Title, roomInfo.ParentAreaName, roomInfo.AreaName, roomInfo.Online, roomInfo.Attention, roomInfo.Time), 16 | ) 17 | roomInfoView.ScrollToBeginning() 18 | app.Draw() 19 | } 20 | } 21 | 22 | var lastMsg = getter.DanmuMsg{} 23 | var lastLine = "" 24 | 25 | func danmuHandler(app *tview.Application, messages *tview.TextView, busChan chan getter.DanmuMsg) { 26 | for msg := range busChan { 27 | if strings.Trim(msg.Content, " ") == "" { 28 | continue 29 | } 30 | 31 | viewStr := messages.GetText(false) 32 | str := "" 33 | 34 | // 留意前面的空格显示 35 | timeStr := msg.Time.Format(" 15:04") 36 | if config.Config.ShowTime == 0 { 37 | timeStr = "" 38 | } 39 | 40 | if config.Config.SingleLine == 1 { 41 | str += fmt.Sprintf("[%s]%s [%s]%s[%s] %s", config.Config.TimeColor, timeStr, config.Config.NameColor, msg.Author, config.Config.ContentColor, msg.Content) 42 | } else { 43 | if lastMsg.Type != msg.Type || lastMsg.Author != msg.Author || (timeStr != "" && lastMsg.Time.Format("15:04") != msg.Time.Format("15:04")) { 44 | str += fmt.Sprintf("[%s]%s [%s]%s[%s]", config.Config.TimeColor, timeStr, config.Config.NameColor, msg.Author, config.Config.ContentColor) + "\n" 45 | } 46 | str += fmt.Sprintf(" %s", msg.Content) + "\n" 47 | } 48 | 49 | messages.SetText(viewStr + strings.TrimRight(str, "\n")) 50 | lastMsg = msg 51 | app.Draw() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ui/theme3/ui.go: -------------------------------------------------------------------------------- 1 | // simple 2 | 3 | package theme3 4 | 5 | import ( 6 | "bili/config" 7 | "bili/getter" 8 | "bili/sender" 9 | 10 | "github.com/gdamore/tcell/v2" 11 | "github.com/rivo/tview" 12 | ) 13 | 14 | var submitHistory = []string{} 15 | var submitHistoryIndex = 0 16 | var bg = tcell.ColorDefault 17 | 18 | func draw(app *tview.Application, roomId int64, busChan chan getter.DanmuMsg, roomInfoChan chan getter.RoomInfo) *tview.Grid { 19 | grid := tview.NewGrid().SetRows(1, 1, 0, 1, 1).SetBorders(false) 20 | 21 | roomInfoView := tview.NewTextView().SetDynamicColors(true) 22 | roomInfoView.SetBackgroundColor(bg) 23 | 24 | delimiter1 := tview.NewTextView().SetTextColor(tcell.GetColor(config.Config.FrameColor)) 25 | delimiter2 := tview.NewTextView().SetTextColor(tcell.GetColor(config.Config.FrameColor)) 26 | delimiter1.SetBackgroundColor(bg).SetBorder(false) 27 | delimiter2.SetBackgroundColor(bg).SetBorder(false) 28 | 29 | _, _, width, _ := grid.GetRect() 30 | str := "" 31 | for i := 0; i < width; i++ { 32 | str = str + "—" 33 | } 34 | delimiter1.SetText(str) 35 | delimiter2.SetText(str) 36 | 37 | messagesView := tview.NewTextView().SetDynamicColors(true) 38 | messagesView.SetBackgroundColor(bg) 39 | 40 | input := tview.NewInputField() 41 | input.SetFormAttributes(0, tcell.ColorDefault, bg, tcell.ColorDefault, bg) 42 | 43 | grid. 44 | AddItem(roomInfoView, 0, 0, 1, 1, 0, 0, false). 45 | AddItem(delimiter1, 1, 0, 1, 1, 0, 0, false). 46 | AddItem(messagesView, 2, 0, 1, 1, 0, 0, false). 47 | AddItem(delimiter2, 3, 0, 1, 1, 0, 0, false). 48 | AddItem(input /* */, 4, 0, 1, 1, 0, 0, true) 49 | 50 | go roomInfoHandler(app, roomInfoView, roomInfoChan) 51 | go danmuHandler(app, messagesView, busChan) 52 | 53 | input.SetDoneFunc(func(key tcell.Key) { 54 | if key == tcell.KeyEnter { 55 | go sender.SendMsg(roomId, input.GetText(), busChan) 56 | 57 | submitHistory = append(submitHistory, input.GetText()) 58 | if len(submitHistory) > 10 { 59 | submitHistory = submitHistory[1:] 60 | } 61 | submitHistoryIndex = len(submitHistory) 62 | 63 | input.SetText("") 64 | } 65 | }) 66 | 67 | grid.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) { 68 | str := "" 69 | for i := 0; i < width; i++ { 70 | str = str + "—" 71 | } 72 | delimiter1.SetText(str) 73 | delimiter2.SetText(str) 74 | return x, y, width, height 75 | }) 76 | 77 | return grid 78 | } 79 | 80 | func Run(busChan chan getter.DanmuMsg, roomInfoChan chan getter.RoomInfo) { 81 | if config.Config.Background != "NONE" { 82 | bg = tcell.GetColor(config.Config.Background) 83 | } 84 | app := tview.NewApplication() 85 | if err := app.SetRoot(draw(app, config.Config.RoomId, busChan, roomInfoChan), true).EnableMouse(false).Run(); err != nil { 86 | panic(err) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ui/theme4/handler.go: -------------------------------------------------------------------------------- 1 | package theme4 2 | 3 | import ( 4 | "bili/config" 5 | "bili/getter" 6 | "fmt" 7 | "strings" 8 | 9 | "github.com/rivo/tview" 10 | ) 11 | 12 | func roomInfoHandler(app *tview.Application, roomInfoView *tview.TextView, rankUsersView *tview.TextView, roomInfoChan chan getter.RoomInfo) { 13 | for roomInfo := range roomInfoChan { 14 | roomInfoView.SetText( 15 | "[" + config.Config.InfoColor + "]" + 16 | roomInfo.Title + "\n" + 17 | fmt.Sprintf("ID: %d", roomInfo.RoomId) + "\n" + 18 | fmt.Sprintf("分区: %s/%s", roomInfo.ParentAreaName, roomInfo.AreaName) + "\n" + 19 | fmt.Sprintf("👀: %d", roomInfo.Online) + "\n" + 20 | fmt.Sprintf("❤️: %d", roomInfo.Attention) + "\n" + 21 | fmt.Sprintf("🕒: %s", roomInfo.Time) + "\n", 22 | ) 23 | rankUsersView.SetTitle(fmt.Sprintf("Rank(%d)", len(roomInfo.OnlineRankUsers))) 24 | 25 | rankUserStr := "" 26 | spec := []string{"👑 ", "🥈 ", "🥉 "} 27 | for idx, rankUser := range roomInfo.OnlineRankUsers { 28 | rankUserStr += "[" + config.Config.RankColor + "]" 29 | if idx < 3 { 30 | rankUserStr += spec[idx] + rankUser.Name + "\n" 31 | } else { 32 | rankUserStr += " " + rankUser.Name + "\n" 33 | } 34 | } 35 | strings.TrimRight(rankUserStr, "\n") 36 | rankUsersView.SetText(rankUserStr) 37 | // 滚动到顶部 避免过长显示下半部分 38 | roomInfoView.ScrollToBeginning() 39 | rankUsersView.ScrollToBeginning() 40 | app.Draw() 41 | } 42 | } 43 | 44 | var lastMsg = getter.DanmuMsg{} 45 | var lastLine = "" 46 | 47 | func danmuHandler(app *tview.Application, messages *tview.TextView, access *tview.TextView, gift *tview.TextView, busChan chan getter.DanmuMsg) { 48 | for msg := range busChan { 49 | if strings.Trim(msg.Content, " ") == "" { 50 | continue 51 | } 52 | 53 | viewStr := "" 54 | str := "" 55 | 56 | // 留意前面的空格显示 57 | timeStr := msg.Time.Format(" 15:04") 58 | if config.Config.ShowTime == 0 { 59 | timeStr = "" 60 | } 61 | 62 | if config.Config.SingleLine == 1 { 63 | str += fmt.Sprintf("[%s]%s [%s]%s[%s] %s", config.Config.TimeColor, timeStr, config.Config.NameColor, msg.Author, config.Config.ContentColor, msg.Content) 64 | } else { 65 | if lastMsg.Type != msg.Type || lastMsg.Author != msg.Author || (timeStr != "" && lastMsg.Time.Format("15:04") != msg.Time.Format("15:04")) { 66 | str += fmt.Sprintf("[%s]%s [%s]%s[%s]", config.Config.TimeColor, timeStr, config.Config.NameColor, msg.Author, config.Config.ContentColor) + "\n" 67 | } 68 | str += fmt.Sprintf(" %s", msg.Content) + "\n" 69 | } 70 | 71 | switch msg.Type { 72 | case "INTERACT_WORD": 73 | viewStr = access.GetText(false) 74 | access.SetText(viewStr + strings.TrimRight(str, "\n")) 75 | break 76 | case "SEND_GIFT": 77 | viewStr = gift.GetText(false) 78 | gift.SetText(viewStr + strings.TrimRight(str, "\n")) 79 | break 80 | default: 81 | viewStr = messages.GetText(false) 82 | messages.SetText(viewStr + strings.TrimRight(str, "\n")) 83 | } 84 | lastMsg = msg 85 | app.Draw() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ui/theme4/ui.go: -------------------------------------------------------------------------------- 1 | // 聊天室主题 2 | 3 | package theme4 4 | 5 | import ( 6 | "bili/config" 7 | "bili/getter" 8 | "bili/sender" 9 | 10 | "github.com/gdamore/tcell/v2" 11 | "github.com/rivo/tview" 12 | ) 13 | 14 | var submitHistory = []string{} 15 | var submitHistoryIndex = 0 16 | var bg = tcell.ColorDefault 17 | 18 | func setBoxAttr(box *tview.Box, title string) { 19 | box.SetBorder(true) 20 | box.SetTitleAlign(tview.AlignLeft) 21 | box.SetTitle(title) 22 | box.SetBackgroundColor(bg) 23 | box.SetBorderColor(tcell.GetColor(config.Config.FrameColor)) 24 | box.SetTitleColor(tcell.GetColor(config.Config.FrameColor)) 25 | } 26 | 27 | func drawSlidebar() (*tview.Grid, *tview.TextView, *tview.TextView) { 28 | slidebarGrid := tview.NewGrid().SetRows(0, 0).SetBorders(false) 29 | roomInfoView := tview.NewTextView().SetDynamicColors(true) 30 | roomInfoView.SetBackgroundColor(bg) 31 | setBoxAttr(roomInfoView.Box, "RoomInfo") 32 | 33 | rankUsersView := tview.NewTextView().SetDynamicColors(true) 34 | rankUsersView.SetBackgroundColor(bg) 35 | setBoxAttr(rankUsersView.Box, "RankUsers") 36 | 37 | slidebarGrid. 38 | AddItem(roomInfoView, 0, 0, 1, 1, 0, 0, false). 39 | AddItem(rankUsersView, 1, 0, 1, 1, 0, 0, false) 40 | 41 | return slidebarGrid, roomInfoView, rankUsersView 42 | } 43 | 44 | func drawChat() (*tview.Grid, *tview.InputField, *tview.TextView, *tview.TextView, *tview.TextView) { 45 | chatGrid := tview.NewGrid().SetRows(0, 0, 3).SetBorders(false) 46 | messagesView := tview.NewTextView().SetDynamicColors(true) 47 | messagesView.SetBackgroundColor(bg) 48 | setBoxAttr(messagesView.Box, "Messages") 49 | accessView := tview.NewTextView().SetDynamicColors(true) 50 | accessView.SetBackgroundColor(bg) 51 | setBoxAttr(accessView.Box, "Access") 52 | giftView := tview.NewTextView().SetDynamicColors(true) 53 | giftView.SetBackgroundColor(bg) 54 | setBoxAttr(giftView.Box, "Gift") 55 | 56 | input := tview.NewInputField() 57 | input.SetFormAttributes(0, tcell.ColorDefault, bg, tcell.ColorDefault, bg) 58 | setBoxAttr(input.Box, "Send") 59 | 60 | // 动态布局 宽度大于80时 采用三列布局 否则采用两列布局 61 | // 三列 | 消息 | 访问 | 礼物 |, 两列 | 消息 | 访问 / 礼物 | 62 | chatGrid. 63 | AddItem(messagesView, 0, 0, 2, 1, 0, 0, false). // 小于80时 | danmu | access / gift | 64 | AddItem(messagesView, 0, 0, 2, 1, 0, 80, false). // 超过80时 | danmu | access | gift | 65 | AddItem(accessView, 0, 1, 1, 1, 0, 0, false). // 小于80时 | danmu | access / gift | 66 | AddItem(accessView, 0, 1, 2, 1, 0, 80, false). // 超过80时 | danmu | access | gift | 67 | AddItem(giftView, 1, 1, 1, 1, 0, 0, false). // 小于80时 | danmu | access / gift | 68 | AddItem(giftView, 0, 2, 2, 1, 0, 80, false). // 超过80时 | danmu | access | gift | 69 | AddItem(input, 2, 0, 1, 2, 0, 0, true). // 小于80时 | danmu | access / gift | 70 | AddItem(input, 2, 0, 1, 3, 0, 80, true) // 超过80时 | danmu | access | gift | 71 | 72 | return chatGrid, input, messagesView, accessView, giftView 73 | } 74 | 75 | func draw(app *tview.Application, roomId int64, busChan chan getter.DanmuMsg, roomInfoChan chan getter.RoomInfo) *tview.Grid { 76 | slidebarGrid, roomInfoView, rankUsersView := drawSlidebar() 77 | chatGrid, input, messagesView, accessView, giftView := drawChat() 78 | rootGrid := tview.NewGrid().SetColumns(20, 0).SetBorders(false) 79 | rootGrid. 80 | AddItem(slidebarGrid, 0, 0, 1, 1, 0, 0, false). 81 | AddItem(chatGrid, 0, 1, 1, 1, 0, 0, true) 82 | 83 | go roomInfoHandler(app, roomInfoView, rankUsersView, roomInfoChan) 84 | go danmuHandler(app, messagesView, accessView, giftView, busChan) 85 | 86 | input.SetDoneFunc(func(key tcell.Key) { 87 | if key == tcell.KeyEnter { 88 | go sender.SendMsg(roomId, input.GetText(), busChan) 89 | 90 | submitHistory = append(submitHistory, input.GetText()) 91 | if len(submitHistory) > 10 { 92 | submitHistory = submitHistory[1:] 93 | } 94 | submitHistoryIndex = len(submitHistory) 95 | 96 | input.SetText("") 97 | } 98 | }) 99 | 100 | return rootGrid 101 | } 102 | 103 | func Run(busChan chan getter.DanmuMsg, roomInfoChan chan getter.RoomInfo) { 104 | if config.Config.Background != "NONE" { 105 | bg = tcell.GetColor(config.Config.Background) 106 | } 107 | app := tview.NewApplication() 108 | if err := app.SetRoot(draw(app, config.Config.RoomId, busChan, roomInfoChan), true).EnableMouse(false).Run(); err != nil { 109 | panic(err) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ui/ui.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "bili/config" 5 | "bili/getter" 6 | "bili/ui/theme1" 7 | "bili/ui/theme2" 8 | "bili/ui/theme3" 9 | "bili/ui/theme4" 10 | ) 11 | 12 | func Run(busChan chan getter.DanmuMsg, roomInfoChan chan getter.RoomInfo) { 13 | switch config.Config.Theme { 14 | case 1: // theme1 15 | theme1.Run(busChan, roomInfoChan) // chat room 16 | case 2: // theme2 17 | theme2.Run(busChan, roomInfoChan) // pure 18 | case 3: // theme3 19 | theme3.Run(busChan, roomInfoChan) // simple 20 | case 4: 21 | theme4.Run(busChan, roomInfoChan) // info 22 | default: 23 | theme1.Run(busChan, roomInfoChan) // default theme1 24 | } 25 | } 26 | --------------------------------------------------------------------------------