From b89028b3554cd2bd78a7171c9967b7f4dca059c4 Mon Sep 17 00:00:00 2001 From: qydysky Date: Mon, 14 Sep 2020 20:47:00 +0800 Subject: [PATCH 1/1] 1 --- .gitignore | 1 + B_I.go | 40 ++++++++ Msg.go | 147 +++++++++++++++++++++++++++++ api.go | 122 ++++++++++++++++++++++++ bili_danmu.go | 225 +++++++++++++++++++++++++++++++++++++++++++++ bili_danmu_test.go | 10 ++ demo/go.mod | 3 + demo/main.go | 9 ++ go.mod | 15 +++ go.sum | 51 ++++++++++ ws.go | 162 ++++++++++++++++++++++++++++++++ 11 files changed, 785 insertions(+) create mode 100644 .gitignore create mode 100644 B_I.go create mode 100644 Msg.go create mode 100644 api.go create mode 100644 bili_danmu.go create mode 100644 bili_danmu_test.go create mode 100644 demo/go.mod create mode 100644 demo/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 ws.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..727bc05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.directory diff --git a/B_I.go b/B_I.go new file mode 100644 index 0000000..5c696ec --- /dev/null +++ b/B_I.go @@ -0,0 +1,40 @@ +package bili_danmu + +import ( + "bytes" + "encoding/binary" + + p "github.com/qydysky/part" +) +/* + 整数 字节转换区 + 32 4字节 + 16 2字节 +*/ +func Itob32(num int32) []byte { + var buffer bytes.Buffer + err := binary.Write(&buffer, binary.BigEndian, num) + if err != nil {p.Logf().E(err)} + return buffer.Bytes() +} + +func Itob16(num int16) []byte { + var buffer bytes.Buffer + err := binary.Write(&buffer, binary.BigEndian, num) + if err != nil {p.Logf().E(err)} + return buffer.Bytes() +} + +func Btoi32(b []byte) int32 { + var buffer int32 + err := binary.Read(bytes.NewReader(b), binary.BigEndian, &buffer) + if err != nil {p.Logf().E(err)} + return buffer +} + +func Btoi16(b []byte) int16 { + var buffer int16 + err := binary.Read(bytes.NewReader(b), binary.BigEndian, &buffer) + if err != nil {p.Logf().E(err)} + return buffer +} \ No newline at end of file diff --git a/Msg.go b/Msg.go new file mode 100644 index 0000000..68cab72 --- /dev/null +++ b/Msg.go @@ -0,0 +1,147 @@ +package bili_danmu + +import ( + "bytes" + "compress/zlib" + + p "github.com/qydysky/part" +) +/* + 数据为WS_OP_MESSAGE类型的 +*/ + +var msglog = p.Logf().New().Level(0) + +func Msg(b []byte, compress bool) { + if compress { + readc, err := zlib.NewReader(bytes.NewReader(b[16:])) + if err != nil {msglog.E("解压错误");return} + defer readc.Close() + + buf := bytes.NewBuffer(nil) + if _, err := buf.ReadFrom(readc);err != nil {msglog.E("解压错误");return} + b = buf.Bytes() + } + + for len(b) != 0 { + + var packL int32 + if ist, packl := headChe(b[:16], len(b), WS_BODY_PROTOCOL_VERSION_NORMAL, WS_OP_MESSAGE, 0, 0); !ist { + msglog.E("头错误");return + } else { + packL = packl + } + + s := string(b[16:packL]) + b = b[packL:] + if cmd := p.Json().GetValFromS(s, "cmd");cmd == nil { + msglog.E("->", "cmd", s) + return + } else { + switch cmd.(string) { + case "INTERACT_WORD":; + case "ACTIVITY_BANNER_UPDATE_V2":; + case "SEND_GIFT":;//礼物 + case "NOTICE_MSG":;//礼物公告 + case "ROOM_BANNER":;//未知 + case "ONLINERANK":;//未知 + case "WELCOME":;//进入提示 + case "ROOM_SILENT_OFF", "ROOM_SILENT_ON":; + case "HOUR_RANK_AWARDS":; + case "ROOM_RANK":; + case "WELCOME_GUARD":; + case "SUPER_CHAT_MESSAGE", "SUPER_CHAT_MESSAGE_JPN":super_chat_message(s); + case "PANEL":panel(s); + case "ENTRY_EFFECT":entry_effect(s) + case "ROOM_REAL_TIME_MESSAGE_UPDATE":roominfo(s) + case "DANMU_MSG":danmu(s) + default:msglog.I("Unknow cmd", s) + } + } + } + + return +} + +func super_chat_message(s string){ + uname := p.Json().GetValFromS(s, "data.user_info.uname"); + price := p.Json().GetValFromS(s, "data.price"); + message := p.Json().GetValFromS(s, "data.message"); + message_jpn := p.Json().GetValFromS(s, "data.message_jpn"); + + var sh []interface{} + + if uname != nil { + sh = append(sh, []interface{}{uname.(string)}) + } + if price != nil { + sh = append(sh, []interface{}{"¥", int64(price.(float64))}) + } + if message != nil { + sh = append(sh, []interface{}{message.(string)}) + } + if message_jpn != nil { + sh = append(sh, []interface{}{message_jpn.(string)}) + } + + if len(sh) != 0 {msglog.I("打赏: ", sh)} +} + +func panel(s string){ + if note := p.Json().GetValFromS(s, "data.note");note == nil { + msglog.E("->", "note", note) + return + } else { + msglog.I(note.(string)) + } + +} + +func entry_effect(s string){ + if copy_writing := p.Json().GetValFromS(s, "data.copy_writing");copy_writing == nil { + msglog.E("->", "copy_writing", copy_writing) + return + } else { + msglog.I(copy_writing.(string)) + } + +} + +func roomsilent(s string){ + if level := p.Json().GetValFromS(s, "data.level");level == nil { + msglog.E("->", "level", level) + return + } else { + if level.(float64) == 0 {msglog.I("主播关闭了禁言")} + msglog.I("主播开启了等级禁言:", int64(level.(float64))) + } + +} + +func roominfo(s string){ + fans := p.Json().GetValFromS(s, "data.fans"); + fans_club := p.Json().GetValFromS(s, "data.fans_club"); + + var sh []interface{} + + if fans != nil { + sh = append(sh, []interface{}{"粉丝总人数:", int64(fans.(float64))}) + } + if fans_club != nil { + sh = append(sh, []interface{}{"粉丝团人数:", int64(fans_club.(float64))}) + } + + if len(sh) != 0 {msglog.I(sh)} +} + +func danmu(s string) { + if info := p.Json().GetValFromS(s, "info");info == nil { + msglog.E("->", "info", info) + return + } else { + infob := info.([]interface{}) + msg := infob[1].(string) + auth := infob[2].([]interface{})[1].(string) + msglog.I(auth, ":", msg) + } +} diff --git a/api.go b/api.go new file mode 100644 index 0000000..df66c68 --- /dev/null +++ b/api.go @@ -0,0 +1,122 @@ +package bili_danmu + +import ( + "strconv" + + p "github.com/qydysky/part" +) + +type api struct { + Roomid int + Uid int + Url []string + Token string +} + +func New_api(Roomid int) (o *api) { + l := p.Logf().New().Level(LogLevel).I("New_api") + defer l.Block() + + l.I("->", "ok") + o = new(api) + o.Roomid = Roomid + o.Get_info() + + return +} + +func (i *api) Get_info() (o *api) { + o = i + l := p.Logf().New().Level(LogLevel).I("*api.Get_info") + defer l.Block() + + if o.Roomid == 0 { + l.E("->", "还未New_api") + return + } + Roomid := strconv.Itoa(o.Roomid) + + req := p.Req() + if err := req.Reqf(p.Rval{ + Url:"https://api.live.bilibili.com/room/v1/Room/room_init?id=" + Roomid, + Referer:"https://live.bilibili.com/" + Roomid, + Timeout:10, + Retry:2, + });err != nil { + l.E("->", err) + return + } + res := string(req.Respon) + if msg := p.Json().GetValFrom(res, "msg");msg == nil || msg != "ok" { + l.E("->", "msg", msg) + return + } + if Uid := p.Json().GetValFrom(res, "data.uid");Uid == nil { + l.E("->", "data.uid", Uid) + return + } else { + o.Uid = int(Uid.(float64)) + } + + if room_id := p.Json().GetValFrom(res, "data.room_id");room_id == nil { + l.E("->", "data.room_id", room_id) + return + } else { + l.I("->", "ok") + o.Roomid = int(room_id.(float64)) + } + return +} + +func (i *api) Get_host_Token() (o *api) { + o = i + l := p.Logf().New().Level(LogLevel).I("*api.Get_host_Token") + defer l.Block() + + if o.Roomid == 0 { + l.E("->", "还未New_api") + return + } + Roomid := strconv.Itoa(o.Roomid) + + + req := p.Req() + if err := req.Reqf(p.Rval{ + Url:"https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?type=0&id=" + Roomid, + Referer:"https://live.bilibili.com/" + Roomid, + Timeout:10, + Retry:2, + });err != nil { + l.E("->", err) + return + } + res := string(req.Respon) + if msg := p.Json().GetValFrom(res, "message");msg == nil || msg != "0" { + l.E("->", "message", msg) + return + } + + _Token := p.Json().GetValFrom(res, "data.token") + if _Token == nil { + l.E("->", "data.token", _Token, res) + return + } + o.Token = _Token.(string) + + if host_list := p.Json().GetValFrom(res, "data.host_list");host_list == nil { + l.E("->", "data.host_list", host_list) + return + } else { + for k, v := range host_list.([]interface{}) { + if _host := p.Json().GetValFrom(v, "host");_host == nil { + l.E("->", "data.host_list[", k, "].host", _host) + continue + } else { + o.Url = append(o.Url, "wss://" + _host.(string) + "/sub") + } + } + l.I("->", "ok") + } + + return +} \ No newline at end of file diff --git a/bili_danmu.go b/bili_danmu.go new file mode 100644 index 0000000..6cac1f5 --- /dev/null +++ b/bili_danmu.go @@ -0,0 +1,225 @@ +package bili_danmu + +import ( + "fmt" + "bytes" + "strconv" + "os" + "os/signal" + + p "github.com/qydysky/part" +) + +const LogLevel = 3 + +func Demo() { + l:=p.Logf().New().Level(LogLevel) + defer l.Block() + + //ctrl+c退出 + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt) + + { + var room int + fmt.Printf("输入房间号: ") + _, err := fmt.Scanln(&room) + if err != nil { + l.E("输入错误", err) + return + } + + var break_sign bool + for !break_sign { + //获取房间相关信息 + api := New_api(room).Get_host_Token() + if len(api.Url) == 0 || api.Roomid == 0 || api.Token == "" { + l.E("some err") + return + } + + //对每个弹幕服务器尝试 + for _, v := range api.Url { + //ws启动 + ws := New_ws(v).Handle() + go func(){ + <- interrupt + ws.Close() + break_sign = true + }() + + //SendChan 传入发送[]byte + //RecvChan 接收[]byte + l.I("send hello to", v) + ws.SendChan <- hello_send(api.Roomid, api.Token) + if hello_ok(<- ws.RecvChan) { + l.I(v, "hello!") + + //开始心跳 + go func(){ + p.Sys().MTimeoutf(500)//500ms + heartbeatmsg, heartinterval := heartbeat() + ws.Heartbeat(1000 * heartinterval, heartbeatmsg) + }() + } + + for { + i := <- ws.RecvChan + if len(i) == 0 && ws.Isclose() { + break + } else { + go Reply(i) + } + } + + if break_sign {break} + + p.Sys().Timeoutf(1) + } + } + } +} + +//from player-loader-2.0.4.min.js +const ( + WS_PACKAGE_HEADER_TOTAL_LENGTH = 16 + WS_HEADER_DEFAULT_VERSION = 1 + WS_BODY_PROTOCOL_VERSION_NORMAL = 0 + WS_BODY_PROTOCOL_VERSION_DEFLATE = 2 + WS_OP_USER_AUTHENTICATION = 7 + WS_OP_HEARTBEAT = 2 + WS_HEADER_DEFAULT_SEQUENCE = 1 + WS_OP_CONNECT_SUCCESS = 8 + WS_OP_MESSAGE = 5 + WS_OP_HEARTBEAT_REPLY = 3 +) + +//返回数据分派 +func Reply(b []byte) { + l := p.Logf().New().Level(LogLevel) + defer l.Block() + + if ist, _ := headChe(b[:16], len(b), WS_BODY_PROTOCOL_VERSION_DEFLATE, WS_OP_MESSAGE, 0, 4); ist { + Msg(b, true);return + } + if ist, _ := headChe(b[:16], len(b), WS_BODY_PROTOCOL_VERSION_NORMAL, WS_OP_MESSAGE, 0, 4); ist { + Msg(b, false);return + } + + if ist, _ := headChe(b[:16], len(b), WS_HEADER_DEFAULT_VERSION, WS_OP_HEARTBEAT_REPLY, WS_HEADER_DEFAULT_SEQUENCE, 4); ist { + l.I("heartbeat replay!"); + return + } + + l.I("unknow reply", b) +} + +//头部生成与检查 +func headGen(datalenght,Opeation,Sequence int) []byte { + var buffer bytes.Buffer //Buffer是一个实现了读写方法的可变大小的字节缓冲 + + buffer.Write(Itob32(int32(datalenght + WS_PACKAGE_HEADER_TOTAL_LENGTH))) + buffer.Write(Itob16(WS_PACKAGE_HEADER_TOTAL_LENGTH)) + buffer.Write(Itob16(WS_HEADER_DEFAULT_VERSION)) + buffer.Write(Itob32(int32(Opeation))) + buffer.Write(Itob32(int32(Sequence))) + + return buffer.Bytes() +} + +func headChe(head []byte, datalenght,Bodyv,Opeation,Sequence,show int) (bool,int32) { + if len(head) != WS_PACKAGE_HEADER_TOTAL_LENGTH {return false, 0} + + l := p.Logf().New().Level(show) + defer l.Block() + + packL := Btoi32(head[:4]) + headL := Btoi16(head[4:6]) + BodyV := Btoi16(head[6:8]) + OpeaT := Btoi32(head[8:12]) + Seque := Btoi32(head[12:16]) + + if packL > int32(datalenght) {l.E("包缺损", packL, datalenght);return false, packL} + if headL != WS_PACKAGE_HEADER_TOTAL_LENGTH {l.E("头错误", headL);return false, packL} + if OpeaT != int32(Opeation) {l.E("类型错误");return false, packL} + if Seque != int32(Sequence) {l.E("Seq错误");return false, packL} + if BodyV != int16(Bodyv) {l.E("压缩算法错误");return false, packL} + return true, packL +} + +//认证生成与检查 +func hello_send(roomid int, key string) []byte { + l := p.Logf().New().Level(LogLevel).I("hello_ws") + defer l.Block() + + if roomid == 0 || key == "" { + l.E("->", "roomid == 0 || key == \"\"") + return []byte("") + } + + //from player-loader-2.0.4.min.js + /* + customAuthParam + */ + const ( + _uid = 0 + _protover = 2 + _platform = "web" + VERSION = "2.0.4" + _type = 2 + ) + + var obj = `{"uid":` + strconv.Itoa(_uid) + + `,"roomid":` + strconv.Itoa(roomid) + + `,"protover":` + strconv.Itoa(_protover) + + `,"platform":"`+ _platform + + `","clientver":"` + VERSION + + `","type":` + strconv.Itoa(_type) + + `,"key":"` + key + `"}` + + var buffer bytes.Buffer //Buffer是一个实现了读写方法的可变大小的字节缓冲 + + buffer.Write(headGen(len(obj), WS_OP_USER_AUTHENTICATION, WS_HEADER_DEFAULT_SEQUENCE)) + + buffer.Write([]byte(obj)) + + return buffer.Bytes() +} + +func hello_ok(r []byte) bool { + if len(r) == 0 {return false} + + var obj = `{"code":0}` + + var buffer bytes.Buffer //Buffer是一个实现了读写方法的可变大小的字节缓冲 + + buffer.Write(headGen(len(obj), WS_OP_CONNECT_SUCCESS, WS_HEADER_DEFAULT_SEQUENCE)) + + buffer.Write([]byte(obj)) + + h := buffer.Bytes() + + if len(h) != len(r) {return false} + + for k, v := range r { + if v != h[k] {return false} + } + return true +} + +//心跳生成 +func heartbeat() ([]byte, int) { + //from player-loader-2.0.4.min.js + const heartBeatInterval = 30 + + var obj = `[object Object]` + + var buffer bytes.Buffer //Buffer是一个实现了读写方法的可变大小的字节缓冲 + + buffer.Write(headGen(len(obj), WS_OP_HEARTBEAT, WS_HEADER_DEFAULT_SEQUENCE)) + + buffer.Write([]byte(obj)) + + return buffer.Bytes(), heartBeatInterval + +} \ No newline at end of file diff --git a/bili_danmu_test.go b/bili_danmu_test.go new file mode 100644 index 0000000..709ebf1 --- /dev/null +++ b/bili_danmu_test.go @@ -0,0 +1,10 @@ +package bili_danmu + +import ( + "testing" +) + +func Test_bili_danmu(t *testing.T) { + +} + diff --git a/demo/go.mod b/demo/go.mod new file mode 100644 index 0000000..9cdf95b --- /dev/null +++ b/demo/go.mod @@ -0,0 +1,3 @@ +module github.com/qydysky/bili_danmu/demo + +go 1.14 diff --git a/demo/main.go b/demo/main.go new file mode 100644 index 0000000..f91f041 --- /dev/null +++ b/demo/main.go @@ -0,0 +1,9 @@ +package main + +import ( + q "github.com/qydysky/bili_danmu" +) + +func main() { + q.Demo() +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..621c3c8 --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/qydysky/bili_danmu + +go 1.14 + +require ( + github.com/gorilla/websocket v1.4.2 + github.com/klauspost/compress v1.11.0 // indirect + github.com/qydysky/part v0.0.0-20200914123330-afade058e33d + github.com/shirou/gopsutil v2.20.8+incompatible // indirect + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect + golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect + golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect +) + +//replace github.com/qydysky/part => ../part diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3461172 --- /dev/null +++ b/go.sum @@ -0,0 +1,51 @@ +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= +github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg= +github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= +github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/qydysky/part v0.0.0-20200912061031-0f3a305b4afb h1:p2R5IUkza8POjdulQWSJrFSxhpjIzSWLX4KMt/YyS9E= +github.com/qydysky/part v0.0.0-20200912061031-0f3a305b4afb/go.mod h1:+8N3UgJBVyJj8ar31eZtucwrKpLpay854Y5qq0xk3x0= +github.com/qydysky/part v0.0.0-20200914123330-afade058e33d h1:um1WzsGzwD6h+1W3jqGcWJPVr4kT5gmRRaXXVwO7nb4= +github.com/qydysky/part v0.0.0-20200914123330-afade058e33d/go.mod h1:+8N3UgJBVyJj8ar31eZtucwrKpLpay854Y5qq0xk3x0= +github.com/shirou/gopsutil v2.20.7+incompatible h1:Ymv4OD12d6zm+2yONe39VSmp2XooJe8za7ngOLW/o/w= +github.com/shirou/gopsutil v2.20.7+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.20.8+incompatible h1:8c7Atn0FAUZJo+f4wYbN0iVpdWniCQk7IYwGtgdh1mY= +github.com/shirou/gopsutil v2.20.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/thedevsaddam/gojsonq v2.3.0+incompatible h1:i2lFTvGY4LvoZ2VUzedsFlRiyaWcJm3Uh6cQ9+HyQA8= +github.com/thedevsaddam/gojsonq v2.3.0+incompatible/go.mod h1:RBcQaITThgJAAYKH7FNp2onYodRz8URfsuEGpAch0NA= +github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0= +github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed h1:WBkVNH1zd9jg/dK4HCM4lNANnmd12EHC9z+LmcCG4ns= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/ws.go b/ws.go new file mode 100644 index 0000000..ddd0ae3 --- /dev/null +++ b/ws.go @@ -0,0 +1,162 @@ +package bili_danmu + +import ( + "time" + + "github.com/gorilla/websocket" + + p "github.com/qydysky/part" +) + +type ws struct { + used bool + + SendChan chan []byte + RecvChan chan []byte + interrupt chan struct{} + url string +} + +func New_ws(url string) (o *ws) { + l := p.Logf().New().Level(LogLevel).I("New_ws") + defer l.Block() + + l.I("->", "ok") + o = new(ws) + o.url = url + o.SendChan = make(chan []byte, 1e4) + o.RecvChan = make(chan []byte, 1e4) + return +} + +func (i *ws) Handle() (o *ws) { + o = i + l := p.Logf().New().Level(LogLevel).I("*ws.handle") + defer l.Block() + + if o.used { + l.E("->", "o.used") + return + } + + if o.url == "" { + l.E("->", "o.url == \"\"") + return + } + + started := make(chan struct{}) + + go func() { + defer close(o.RecvChan) + + c, _, err := websocket.DefaultDialer.Dial(o.url, nil) + if err != nil { + l.E("->", err) + return + } + defer c.Close() + + l.I("->", "ok") + o.interrupt = make(chan struct{}) + done := make(chan struct{}) + + go func() { + defer close(done) + + for { + _, message, err := c.ReadMessage() + if err != nil { + if !websocket.IsCloseError(err, websocket.CloseNormalClosure) { + l.E("->", err) + } + o.used = false + return + } + o.RecvChan <- message + } + }() + + close(started) + + for { + select { + case <- done: + o.used = false + return + case t := <- o.SendChan: + err := c.WriteMessage(websocket.TextMessage, t) + if err != nil { + l.I("->", "write:", err) + o.used = false + return + } + case <- o.interrupt: + l.I("->", "interrupt") + // Cleanly close the connection by sending a close message and then + // waiting (with timeout) for the server to close the connection. + err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + if err != nil { + l.E("->", err) + } + select { + case <- done: + case <- time.After(time.Second): + } + o.used = false + return + } + } + }() + + <- started + o.used = true + return +} + +func (i *ws) Heartbeat(Millisecond int, msg []byte) (o *ws) { + o = i + l := p.Logf().New().Level(LogLevel).I("*ws.heartbeat") + defer l.Block() + + if !o.used { + l.E("->", "!o.used") + return + } + o.SendChan <- msg + l.I("->", "ok") + + go func(){ + ticker := time.NewTicker(time.Duration(Millisecond)*time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + o.SendChan <- msg + case <- o.interrupt: + l.I("->", "fin") + return + } + } + }() + + return +} + +func (o *ws) Close() { + l := p.Logf().New().Level(LogLevel).I("*ws.Close") + defer l.Block() + + if !o.used { + l.I("->", "!o.used") + return + } + o.used = false + + close(o.interrupt) + l.I("->", "ok") +} + +func (o *ws) Isclose() bool { + return !o.used +} \ No newline at end of file -- 2.39.2