├── .gitignore ├── go.mod ├── proxy ├── cert.go ├── ca │ ├── cert.pem │ └── key.pem └── proxy.go ├── README.md ├── main.go └── protobuf └── resp.proto /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /db 3 | go.sum -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dy-live 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/elazarl/goproxy v0.0.0-20211114080932-d06c3be7c11b 7 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 8 | google.golang.org/protobuf v1.27.1 9 | ) 10 | -------------------------------------------------------------------------------- /proxy/cert.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | 7 | "github.com/elazarl/goproxy" 8 | ) 9 | 10 | func SetCA(caCert, caKey []byte) error { 11 | goproxyCa, err := tls.X509KeyPair(caCert, caKey) 12 | if err != nil { 13 | return err 14 | } 15 | if goproxyCa.Leaf, err = x509.ParseCertificate(goproxyCa.Certificate[0]); err != nil { 16 | return err 17 | } 18 | goproxy.GoproxyCa = goproxyCa 19 | goproxy.OkConnect = &goproxy.ConnectAction{Action: goproxy.ConnectAccept, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 20 | goproxy.MitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 21 | goproxy.HTTPMitmConnect = &goproxy.ConnectAction{Action: goproxy.ConnectHTTPMitm, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 22 | goproxy.RejectConnect = &goproxy.ConnectAction{Action: goproxy.ConnectReject, TLSConfig: goproxy.TLSConfigFromCA(&goproxyCa)} 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 抖音直播间数据分析 2 | 3 | [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 4 | 5 | ## 关于 6 | 7 | 使用这个程序可以实现抖音直播间数据分析和直播互动小游戏。一般实现此类功能有两种方式: 8 | 1. 使用 selenium 直接运行 js 来达到目的 9 | 2. 使用 https 代理等网络代理直接获取数据处理 10 | 11 | 本程序采用的是后者,使用很简单。 12 | 13 | ### 需要 14 | 15 | * [go1.15+](https://go.dev/dl/) 16 | 17 | 18 | 19 | ## 开始 20 | 下载源码后进入源码目录安装依赖 21 | ``` 22 | go mod tidy 23 | ``` 24 | 25 | ### 配置 26 | 27 | 可以参考我的文章 [golang 使用 elazarl / goproxy 代理https请求](https://zhuanlan.zhihu.com/p/514004767) 将证书配置一下,代码中有我生成的证书在2024年4月后失效,自己生成证书则按照文章中生成。 28 | 29 | ### 使用 30 | 31 | 在项目根目录下运行 32 | ``` 33 | go run . 34 | ``` 35 | 将默认监听 8080 端口,安装好证书后配置本地代理到 ```localhost:8080``` 打开任意直播间即可捕获数据收到礼物时可以看到输出: 36 | ```shell 37 | WebcastGiftMessage 38 | 收到人气票价值0.10元 39 | ``` 40 | [parseData 函数下区别不同响应类型做出自己的业务处理](https://github.com/Feiir/dy-live/blob/main/main.go#L109) 41 | 42 | ## 声明 43 | 本代码仅供学习交流使用,任何问题请私信联系 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /proxy/ca/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+jCCAeKgAwIBAgIRANz38QypeUkx6cZJ/nHtZKIwDQYJKoZIhvcNAQELBQAw 3 | EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yMzA0MDMwNjU3MDZaFw0yNDA0MDIwNjU3 4 | MDZaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 5 | ggEKAoIBAQDwVHhMmBhv+G63QmJW2oPVz7Nam0wvV1ghFZcUV2MDD6xPafrBjXx1 6 | CUf5QNUrugX15P+e/p3iSLkaZHcEqPCy0+r339KTPweQdiyk0Vvpcqh4soFFGlCQ 7 | O7PmhwC6Z3bop0KPnwoQaEzHeldKkPzQk86Fy5nsmKniGo9lq8hGfGNhQKojvZsW 8 | PikpuVd3b08x7WropMdpiL3l5jCzOkkgj+pee/4PLPCfPEui/engCFu+2jEL61Kc 9 | +HLmeWGYwWWgT2tVTRZXyzksYwfN7FkgHOw70UbAw5RBnWvLiRVPmGGO3/dFrTd6 10 | CBrHfktKKwXZi1SrExkg49NowSGHh11RAgMBAAGjSzBJMA4GA1UdDwEB/wQEAwIF 11 | oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuC 12 | CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAjBroBWwJ3qeFP7czmALJij9V 13 | p63V0C6d1wBYum0npw9JUWrNUaxtCeSpUGK5Q+Q2CR/hLM7nIgqdWLj6us+ebTaB 14 | e0K4tSAlASMOFH7zo/V0EvcEw4NNvQPbl6hTxtXhLaVuOqXYjFWkOq83qGcGdj3H 15 | HCRjb6YbBk3x2n3eG06GlMy0SgmZ++KlPlCEkIh9+d0wv7PMNDxoXoh/W3baVYHO 16 | 9ZW83nZn2ClcBscDP35XlGUmej+HLhyejvJEGEOJGQEFb/HAq4rfco1fGdf1fivZ 17 | 0AI9Fsr4Ufl1bBt+qX9Z8F/FnfaVpJe39Z2Ic+ZnkUIalcowE8udvXwzOFXlEw== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /proxy/ca/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDwVHhMmBhv+G63 3 | QmJW2oPVz7Nam0wvV1ghFZcUV2MDD6xPafrBjXx1CUf5QNUrugX15P+e/p3iSLka 4 | ZHcEqPCy0+r339KTPweQdiyk0Vvpcqh4soFFGlCQO7PmhwC6Z3bop0KPnwoQaEzH 5 | eldKkPzQk86Fy5nsmKniGo9lq8hGfGNhQKojvZsWPikpuVd3b08x7WropMdpiL3l 6 | 5jCzOkkgj+pee/4PLPCfPEui/engCFu+2jEL61Kc+HLmeWGYwWWgT2tVTRZXyzks 7 | YwfN7FkgHOw70UbAw5RBnWvLiRVPmGGO3/dFrTd6CBrHfktKKwXZi1SrExkg49No 8 | wSGHh11RAgMBAAECggEBAJ5NBXBuPqqCWWscrunGxU/wn5Lu+B+2yLYCIqGjbfR2 9 | PmmQxTr4lyIAGwhzS/ahcYumazmvPQ/wFvzrb/oBzYn9TKg5JdsXiUP+kyl9Pa1n 10 | eNEHw15hFu/qY3VVN4WQ4SIeChnQCDEGgoxiaV0UY3J/6Wj300ukw0HLfOjbXfAO 11 | eCGXyFAvfdWGoREssf3NY+4Qj8gjDaezXGP+WWumkkvxxOtNJRwnX/o7n3JNJRp2 12 | 2kCPGiCL4zpopA3XwmbwIb+zJ3th5aVj1BTOx/3f/FkOHyNZrXU3LWnk8/GQnJ03 13 | mR0q8LYLAC7kafARVaMptaQcdAQM1x8jpoY2i0+mhgECgYEA9lnn7rcl9dm8XlUg 14 | 4BLtFEkC3prg5o58LmraEN/xRFYWeWnFrhhKo1M2u2J6iuhc8CqUFtZn+9PIJH+j 15 | qORM/FmhkWne4YQQXzgHfyl3HCgzBQm9rWgVjM8vGb4ZvZFIL+wAJwkDHboVjAdP 16 | 96AAAJL02tUiOPzeGjadNuR6NT0CgYEA+b4w0lbRQKDedLkv4lxaLaciWnAAeb8l 17 | lfvuuatu/CQ2KZ6jnhg1ok3yDfywxuqZdX+Ija+we1/LvLbxVOS8eO7hna0hV+Qv 18 | B4sEo1MwYsYhvelv6tv5KZlqKIyNz8kzN2VdW+bp69yTVeNRBVubaED6Juc0dl4f 19 | f6MSKfvCEaUCgYEAtaXtgvPBJdr7LoOOziqmbcK4PvPsyZRetMtNKAw4lq7zu2rm 20 | vExlHLELNniS60IS5XQ/5ecKnYG3nlIPl0VgDuL4Y4SxN5Btkgh6XqVwkm6B0EEy 21 | f160JTPQAydNS56In+1g32ehyJWhtbLeoDR2S/YQXhuyNFsgWivTGNq30fECgYAI 22 | aQY93xyW35P+PEpHhjKnPOykPQdXEj/SyDQm3oYTSajlR+lcppYPzC4tmGffmwDl 23 | +xB/LP51eSlkRojkAfvy2qCQbcckrZOD3oOeeGu1t9RlD9pr/1Jpuzfe+7S+3Ziy 24 | 8d/t+12/SqNPjiMpWgdmwGFsM/cJUWwgIT64WTkU3QKBgFOOoxqIqss3Jy2tQAqX 25 | v5NOUgpJmJZKDrTTuto/l+Whcpvctsnbin3QrVHtwDATnHzu2rgmxkHsl1Ml/khD 26 | qGqLDOs0dUuW/JAMAZ82mD7SUFktr708C6r5JoG5zr36qZ4+dc4/Jc0sSVOcPlFt 27 | 10wSaXa7sKPo2Uj/OmSMQErR 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "dy-live/protobuf/protobuf" 6 | myProxy "dy-live/proxy" 7 | "flag" 8 | "fmt" 9 | "github.com/elazarl/goproxy" 10 | "google.golang.org/protobuf/proto" 11 | "io/ioutil" 12 | "log" 13 | "net" 14 | "net/http" 15 | "os" 16 | "os/signal" 17 | "regexp" 18 | "runtime" 19 | ) 20 | 21 | var CaCertPath string 22 | var CaKeyPath string 23 | 24 | func main() { 25 | 26 | if runtime.GOOS == "windows" { 27 | CaCertPath = "\\proxy\\ca\\cert.pem" 28 | CaKeyPath = "\\proxy\\ca\\key.pem" 29 | } else { 30 | CaCertPath = "/proxy/ca/cert.pem" 31 | CaKeyPath = "/proxy/ca/key.pem" 32 | } 33 | 34 | verbose := flag.Bool("v", false, "should every proxy request be logged to stdout") 35 | addr := flag.String("addr", ":8080", "proxy listen address") 36 | flag.Parse() 37 | proxy := goproxy.NewProxyHttpServer() 38 | pwd, _ := os.Getwd() 39 | caCert, err := ioutil.ReadFile(pwd + CaCertPath) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | caKey, err := ioutil.ReadFile(pwd + CaKeyPath) 44 | myProxy.SetCA(caCert, caKey) 45 | proxy.Verbose = *verbose 46 | 47 | proxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) 48 | if err := os.MkdirAll("db", 0755); err != nil { 49 | log.Fatal("Can't create dir", err) 50 | } 51 | logger, err := myProxy.NewLogger("db") 52 | if err != nil { 53 | log.Fatal("can't open log file", err) 54 | } 55 | proxy.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response { 56 | if resp.Request.Host == "live.douyin.com" { 57 | parseData(resp) 58 | } 59 | return resp 60 | }) 61 | l, err := net.Listen("tcp", *addr) 62 | if err != nil { 63 | log.Fatal("listen:", err) 64 | } 65 | sl := myProxy.NewStoppableListener(l) 66 | ch := make(chan os.Signal) 67 | signal.Notify(ch, os.Interrupt) 68 | go func() { 69 | <-ch 70 | log.Println("Got SIGINT exiting") 71 | sl.Add(1) 72 | sl.Close() 73 | logger.Close() 74 | sl.Done() 75 | }() 76 | log.Println("Starting Proxy") 77 | http.Serve(sl, proxy) 78 | sl.Wait() 79 | log.Println("All connections closed - exit") 80 | } 81 | 82 | func parseData(resp *http.Response) { 83 | /** 84 | body 会在读取完数据后被关闭回收 所以需要再造一个readCloser的body 85 | */ 86 | if resp != nil { 87 | buf := new(bytes.Buffer) 88 | buf.ReadFrom(resp.Body) 89 | buf.Bytes() 90 | resp.Body = ioutil.NopCloser(bytes.NewBuffer(buf.Bytes())) 91 | respBody := buf.Bytes() 92 | 93 | newResp := &protobuf.Response{} 94 | err := proto.Unmarshal(respBody, newResp) 95 | if err != nil { 96 | log.Println("proto unmarshal err", err) 97 | } 98 | messages := newResp.MessagesList 99 | // 正则匹配直播间的url id 100 | str := resp.Request.Referer() 101 | urlID := "" 102 | rege, err := regexp.Compile(`\d+`) 103 | if err != nil { 104 | log.Println("regexp compile error : ", err.Error()) 105 | } else { 106 | urlID = rege.FindString(str) 107 | } 108 | 109 | for _, message := range messages { 110 | fmt.Println(message.Method) 111 | switch message.Method { 112 | case "WebcastMemberMessage": 113 | fmt.Println(message.Method, urlID) 114 | case "WebcastChatMessage": 115 | fmt.Println(message.Method) 116 | case "WebcastLikeMessage": 117 | fmt.Println(message.Method) 118 | case "WebcastGiftMessage": 119 | fmt.Println(message.Method) 120 | giftMessage := &protobuf.GiftMessage{} 121 | err := proto.Unmarshal(message.Payload, giftMessage) 122 | if err != nil { 123 | log.Println("Unmarshal", err.Error()) 124 | return 125 | } 126 | fmt.Printf("收到%s价值%.2f元", giftMessage.Gift.Name, float32(giftMessage.Gift.DiamondCount)/10) 127 | case "WebcastRoomUserSeqMessage": 128 | fmt.Println(message.Method) 129 | case "WebcastGiftVoteMessage": 130 | fmt.Print(message.Method + " ---- ") 131 | fmt.Println(string(message.Payload)) 132 | case "WebcastSocialMessage": // 有人分享流或关注主播 133 | socialMessage := &protobuf.SocialMessage{} 134 | proto.Unmarshal(message.Payload, socialMessage) 135 | fmt.Printf("socialMessage ---- %s \n", socialMessage) 136 | fmt.Printf("%s触发的消息 \n", socialMessage.User.NickName) 137 | default: 138 | fmt.Print(message.Method + " | ") 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/elazarl/goproxy" 7 | "github.com/elazarl/goproxy/transport" 8 | "io" 9 | "log" 10 | "net" 11 | "net/http" 12 | "net/http/httputil" 13 | "os" 14 | "path" 15 | "sync" 16 | "time" 17 | ) 18 | 19 | type FileStream struct { 20 | path string 21 | f *os.File 22 | } 23 | 24 | func NewFileStream(path string) *FileStream { 25 | return &FileStream{path, nil} 26 | } 27 | 28 | func (fs *FileStream) Write(b []byte) (nr int, err error) { 29 | if fs.f == nil { 30 | fs.f, err = os.Create(fs.path) 31 | if err != nil { 32 | return 0, err 33 | } 34 | } 35 | return fs.f.Write(b) 36 | } 37 | 38 | func (fs *FileStream) Close() error { 39 | fmt.Println("Close", fs.path) 40 | if fs.f == nil { 41 | return errors.New("FileStream was never written into") 42 | } 43 | return fs.f.Close() 44 | } 45 | 46 | type Meta struct { 47 | req *http.Request 48 | resp *http.Response 49 | err error 50 | t time.Time 51 | sess int64 52 | bodyPath string 53 | from string 54 | } 55 | 56 | func fprintf(nr *int64, err *error, w io.Writer, pat string, a ...interface{}) { 57 | if *err != nil { 58 | return 59 | } 60 | var n int 61 | n, *err = fmt.Fprintf(w, pat, a...) 62 | *nr += int64(n) 63 | } 64 | 65 | func write(nr *int64, err *error, w io.Writer, b []byte) { 66 | if *err != nil { 67 | return 68 | } 69 | var n int 70 | n, *err = w.Write(b) 71 | *nr += int64(n) 72 | } 73 | 74 | func (m *Meta) WriteTo(w io.Writer) (nr int64, err error) { 75 | if m.req != nil { 76 | fprintf(&nr, &err, w, "Type: request\r\n") 77 | } else if m.resp != nil { 78 | fprintf(&nr, &err, w, "Type: response\r\n") 79 | } 80 | fprintf(&nr, &err, w, "ReceivedAt: %v\r\n", m.t) 81 | fprintf(&nr, &err, w, "Session: %d\r\n", m.sess) 82 | fprintf(&nr, &err, w, "From: %v\r\n", m.from) 83 | if m.err != nil { 84 | // note the empty response 85 | fprintf(&nr, &err, w, "Error: %v\r\n\r\n\r\n\r\n", m.err) 86 | } else if m.req != nil { 87 | fprintf(&nr, &err, w, "\r\n") 88 | buf, err2 := httputil.DumpRequest(m.req, false) 89 | if err2 != nil { 90 | return nr, err2 91 | } 92 | write(&nr, &err, w, buf) 93 | } else if m.resp != nil { 94 | fprintf(&nr, &err, w, "\r\n") 95 | buf, err2 := httputil.DumpResponse(m.resp, false) 96 | if err2 != nil { 97 | return nr, err2 98 | } 99 | write(&nr, &err, w, buf) 100 | } 101 | return 102 | } 103 | 104 | // HttpLogger is an asynchronous HTTP request/response logger. It traces 105 | // requests and responses headers in a "log" file in logger directory and dumps 106 | // their bodies in files prefixed with the session identifiers. 107 | // Close it to ensure pending items are correctly logged. 108 | type HttpLogger struct { 109 | path string 110 | c chan *Meta 111 | errch chan error 112 | } 113 | 114 | func NewLogger(basepath string) (*HttpLogger, error) { 115 | f, err := os.Create(path.Join(basepath, "log")) 116 | if err != nil { 117 | return nil, err 118 | } 119 | logger := &HttpLogger{basepath, make(chan *Meta), make(chan error)} 120 | go func() { 121 | for m := range logger.c { 122 | if _, err := m.WriteTo(f); err != nil { 123 | log.Println("Can't write meta", err) 124 | } 125 | } 126 | logger.errch <- f.Close() 127 | }() 128 | return logger, nil 129 | } 130 | 131 | func (logger *HttpLogger) LogResp(resp *http.Response, ctx *goproxy.ProxyCtx) { 132 | body := path.Join(logger.path, fmt.Sprintf("%s_%d_resp.protobuf", time.Now(), ctx.Session)) 133 | from := "" 134 | if ctx.UserData != nil { 135 | from = ctx.UserData.(*transport.RoundTripDetails).TCPAddr.String() 136 | } 137 | if resp == nil { 138 | resp = emptyResp 139 | } else { 140 | resp.Body = NewTeeReadCloser(resp.Body, NewFileStream(body)) 141 | } 142 | logger.LogMeta(&Meta{ 143 | resp: resp, 144 | err: ctx.Error, 145 | t: time.Now(), 146 | sess: ctx.Session, 147 | from: from}) 148 | } 149 | 150 | var emptyResp = &http.Response{} 151 | var emptyReq = &http.Request{} 152 | 153 | func (logger *HttpLogger) LogReq(req *http.Request, ctx *goproxy.ProxyCtx) { 154 | body := path.Join(logger.path, fmt.Sprintf("%d_req", ctx.Session)) 155 | if req == nil { 156 | req = emptyReq 157 | } else { 158 | req.Body = NewTeeReadCloser(req.Body, NewFileStream(body)) 159 | } 160 | logger.LogMeta(&Meta{ 161 | req: req, 162 | err: ctx.Error, 163 | t: time.Now(), 164 | sess: ctx.Session, 165 | from: req.RemoteAddr}) 166 | } 167 | 168 | func (logger *HttpLogger) LogMeta(m *Meta) { 169 | logger.c <- m 170 | } 171 | 172 | func (logger *HttpLogger) Close() error { 173 | close(logger.c) 174 | return <-logger.errch 175 | } 176 | 177 | // TeeReadCloser extends io.TeeReader by allowing reader and writer to be 178 | // closed. 179 | type TeeReadCloser struct { 180 | r io.Reader 181 | w io.WriteCloser 182 | c io.Closer 183 | } 184 | 185 | func NewTeeReadCloser(r io.ReadCloser, w io.WriteCloser) io.ReadCloser { 186 | return &TeeReadCloser{io.TeeReader(r, w), w, r} 187 | } 188 | 189 | func (t *TeeReadCloser) Read(b []byte) (int, error) { 190 | return t.r.Read(b) 191 | } 192 | 193 | // Close attempts to close the reader and write. It returns an error if both 194 | // failed to Close. 195 | func (t *TeeReadCloser) Close() error { 196 | err1 := t.c.Close() 197 | err2 := t.w.Close() 198 | if err1 != nil { 199 | return err1 200 | } 201 | return err2 202 | } 203 | 204 | // stoppableListener serves stoppableConn and tracks their lifetime to notify 205 | // when it is safe to terminate the application. 206 | type stoppableListener struct { 207 | net.Listener 208 | sync.WaitGroup 209 | } 210 | 211 | type stoppableConn struct { 212 | net.Conn 213 | wg *sync.WaitGroup 214 | } 215 | 216 | func NewStoppableListener(l net.Listener) *stoppableListener { 217 | return &stoppableListener{l, sync.WaitGroup{}} 218 | } 219 | 220 | func (sl *stoppableListener) Accept() (net.Conn, error) { 221 | c, err := sl.Listener.Accept() 222 | if err != nil { 223 | return c, err 224 | } 225 | sl.Add(1) 226 | return &stoppableConn{c, &sl.WaitGroup}, nil 227 | } 228 | 229 | func (sc *stoppableConn) Close() error { 230 | sc.wg.Done() 231 | return sc.Conn.Close() 232 | } 233 | -------------------------------------------------------------------------------- /protobuf/resp.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package douyin; 4 | 5 | option go_package = "./"; 6 | 7 | message Response { 8 | repeated Message messagesList = 1; 9 | string cursor = 2; 10 | uint64 fetchInterval = 3; 11 | uint64 now = 4; 12 | string internalExt = 5; 13 | uint32 fetchType = 6; 14 | map routeParams = 7; 15 | uint64 heartbeatDuration = 8; 16 | bool needAck = 9; 17 | string pushServer = 10; 18 | string liveCursor = 11; 19 | bool historyNoMore = 12; 20 | } 21 | 22 | message Message{ 23 | string method = 1; 24 | bytes payload = 2; 25 | int64 msgId = 3; 26 | int32 msgType = 4; 27 | int64 offset = 5; 28 | bool needWrdsStore = 6; 29 | int64 wrdsVersion = 7; 30 | string wrdsSubKey = 8; 31 | } 32 | 33 | // 聊天 34 | message ChatMessage { 35 | Common common = 1; 36 | User user = 2; 37 | string content = 3; 38 | bool visibleToSender = 4; 39 | Image backgroundImage = 5; 40 | string fullScreenTextColor = 6; 41 | Image backgroundImageV2 = 7; 42 | PublicAreaCommon publicAreaCommon = 8; 43 | Image giftImage = 9; 44 | uint64 agreeMsgId = 11; 45 | uint32 priorityLevel = 12; 46 | LandscapeAreaCommon landscapeAreaCommon = 13; 47 | uint64 eventTime = 15; 48 | bool sendReview = 16; 49 | bool fromIntercom = 17; 50 | bool intercomHideUserCard = 18; 51 | // repeated chatTagsList = 19; 52 | string chatBy = 20; 53 | uint32 individualChatPriority = 21; 54 | Text rtfContent = 22; 55 | } 56 | 57 | 58 | message LandscapeAreaCommon { 59 | bool showHead = 1; 60 | bool showNickname = 2; 61 | bool showFontColor = 3; 62 | repeated string colorValueList = 4; 63 | repeated CommentTypeTag commentTypeTagsList = 5; 64 | } 65 | 66 | message RoomUserSeqMessage { 67 | Common common = 1; 68 | repeated RoomUserSeqMessageContributor ranksList = 2; 69 | int64 total = 3; 70 | string popStr = 4; 71 | repeated RoomUserSeqMessageContributor seatsList = 5; 72 | int64 popularity = 6; 73 | int64 totalUser = 7; 74 | string totalUserStr = 8; 75 | string totalStr = 9; 76 | string onlineUserForAnchor = 10; 77 | string totalPvForAnchor = 11; 78 | string upRightStatsStr = 12; 79 | string upRightStatsStrComplete = 13; 80 | 81 | } 82 | 83 | message CommonTextMessage { 84 | Common common = 1; 85 | User user = 2; 86 | string scene = 3; 87 | } 88 | 89 | message UpdateFanTicketMessage { 90 | Common common = 1; 91 | string roomFanTicketCountText = 2; 92 | uint64 roomFanTicketCount = 3; 93 | bool forceUpdate = 4; 94 | } 95 | 96 | message RoomUserSeqMessageContributor { 97 | uint64 score = 1; 98 | User user = 2; 99 | uint64 rank = 3; 100 | uint64 delta = 4; 101 | bool isHidden = 5; 102 | string scoreDescription = 6; 103 | string exactlyScore = 7; 104 | } 105 | 106 | // 礼物消息 107 | message GiftMessage { 108 | Common common = 1; 109 | uint64 giftId = 2; 110 | uint64 fanTicketCount = 3; 111 | uint64 groupCount = 4; 112 | uint64 repeatCount = 5; 113 | uint64 comboCount = 6; 114 | User user = 7; 115 | User toUser = 8; 116 | uint32 repeatEnd = 9; 117 | TextEffect textEffect = 10; 118 | uint64 groupId = 11; 119 | uint64 incomeTaskgifts = 12; 120 | uint64 roomFanTicketCount = 13; 121 | GiftIMPriority priority = 14; 122 | GiftStruct gift = 15; 123 | string logId = 16; 124 | uint64 sendType = 17; 125 | PublicAreaCommon publicAreaCommon = 18; 126 | Text trayDisplayText = 19; 127 | uint64 bannedDisplayEffects = 20; 128 | // GiftTrayInfo trayInfo = 21; 129 | // AssetEffectMixInfo assetEffectMixInfo = 22; 130 | bool displayForSelf = 25; 131 | string interactGiftInfo = 26; 132 | string diyItemInfo = 27; 133 | repeated uint64 minAssetSetList = 28; 134 | uint64 totalCount = 29; 135 | uint32 clientGiftSource = 30; 136 | // AnchorGiftData anchorGift = 31; 137 | repeated uint64 toUserIdsList = 32; 138 | uint64 sendTime = 33; 139 | uint64 forceDisplayEffects = 34; 140 | string traceId = 35; 141 | uint64 effectDisplayTs = 36; 142 | } 143 | 144 | message GiftStruct { 145 | Image image = 1; 146 | string describe = 2; 147 | bool notify = 3; 148 | uint64 duration = 4; 149 | uint64 id = 5; 150 | // GiftStructFansClubInfo fansclubInfo = 6; 151 | bool forLinkmic = 7; 152 | bool doodle = 8; 153 | bool forFansclub = 9; 154 | bool combo = 10; 155 | uint32 type = 11; 156 | uint32 diamondCount = 12; 157 | bool isDisplayedOnPanel = 13; 158 | uint64 primaryEffectId = 14; 159 | Image giftLabelIcon = 15; 160 | string name = 16; 161 | string region = 17; 162 | string manual = 18; 163 | bool forCustom = 19; 164 | // specialEffectsMap = 20; 165 | Image icon = 21; 166 | uint32 actionType = 22; 167 | 168 | } 169 | 170 | message GiftIMPriority { 171 | repeated uint64 queueSizesList = 1; 172 | uint64 selfQueuePriority = 2; 173 | uint64 priority = 3; 174 | } 175 | 176 | message TextEffect { 177 | TextEffectDetail portrait = 1; 178 | TextEffectDetail landscape = 2; 179 | } 180 | 181 | message TextEffectDetail { 182 | Text text = 1; 183 | uint32 textFontSize = 2; 184 | Image background = 3; 185 | uint32 start = 4; 186 | uint32 duration = 5; 187 | uint32 x = 6; 188 | uint32 y = 7; 189 | uint32 width = 8; 190 | uint32 height = 9; 191 | uint32 shadowDx = 10; 192 | uint32 shadowDy = 11; 193 | uint32 shadowRadius = 12; 194 | string shadowColor = 13; 195 | string strokeColor = 14; 196 | uint32 strokeWidth = 15; 197 | } 198 | 199 | // 成员消息 200 | message MemberMessage { 201 | Common common = 1; 202 | User user = 2; 203 | uint64 memberCount = 3; 204 | User operator = 4; 205 | bool isSetToAdmin = 5; 206 | bool isTopUser = 6; 207 | uint64 rankScore = 7; 208 | uint64 topUserNo = 8; 209 | uint64 enterType = 9; 210 | uint64 action = 10; 211 | string actionDescription = 11; 212 | uint64 userId = 12; 213 | EffectConfig effectConfig = 13; 214 | string popStr = 14; 215 | EffectConfig enterEffectConfig = 15; 216 | Image backgroundImage = 16; 217 | Image backgroundImageV2 = 17; 218 | Text anchorDisplayText = 18; 219 | PublicAreaCommon publicAreaCommon = 19; 220 | uint64 userEnterTipType = 20; 221 | uint64 anchorEnterTipType = 21; 222 | } 223 | 224 | 225 | message PublicAreaCommon { 226 | Image userLabel = 1; 227 | uint64 userConsumeInRoom = 2; 228 | uint64 userSendGiftCntInRoom = 3; 229 | } 230 | 231 | message EffectConfig { 232 | uint64 type = 1; 233 | Image icon = 2; 234 | uint64 avatarPos = 3; 235 | Text text = 4; 236 | Image textIcon = 5; 237 | uint32 stayTime = 6; 238 | uint64 animAssetId = 7; 239 | Image badge = 8; 240 | repeated uint64 flexSettingArrayList = 9; 241 | Image textIconOverlay = 10; 242 | Image animatedBadge = 11; 243 | bool hasSweepLight = 12; 244 | repeated uint64 textFlexSettingArrayList = 13; 245 | uint64 centerAnimAssetId = 14; 246 | Image dynamicImage = 15; 247 | map extraMap = 16; 248 | uint64 mp4AnimAssetId = 17; 249 | uint64 priority = 18; 250 | uint64 maxWaitTime = 19; 251 | string dressId = 20; 252 | uint64 alignment = 21; 253 | uint64 alignmentOffset = 22; 254 | } 255 | 256 | message Text { 257 | string key = 1; 258 | string defaultPatter = 2; 259 | TextFormat defaultFormat = 3; 260 | repeated TextPiece piecesList = 4; 261 | } 262 | 263 | message TextPiece { 264 | bool type = 1; 265 | TextFormat format = 2; 266 | string stringValue = 3; 267 | TextPieceUser userValue = 4; 268 | TextPieceGift giftValue = 5; 269 | TextPieceHeart heartValue = 6; 270 | TextPiecePatternRef patternRefValue = 7; 271 | TextPieceImage imageValue = 8; 272 | } 273 | 274 | 275 | message TextPieceImage { 276 | Image image = 1; 277 | float scalingRate = 2; 278 | } 279 | 280 | message TextPiecePatternRef { 281 | string key = 1; 282 | string defaultPattern = 2; 283 | } 284 | 285 | message TextPieceHeart { 286 | string color = 1; 287 | } 288 | 289 | message TextPieceGift { 290 | uint64 giftId = 1; 291 | PatternRef nameRef = 2; 292 | } 293 | 294 | message PatternRef { 295 | string key = 1; 296 | string defaultPattern = 2; 297 | } 298 | 299 | message TextPieceUser { 300 | User user = 1; 301 | bool withColon = 2; 302 | } 303 | 304 | message TextFormat { 305 | string color = 1; 306 | bool bold = 2; 307 | bool italic = 3; 308 | uint32 weight = 4; 309 | uint32 italicAngle = 5; 310 | uint32 fontSize = 6; 311 | bool useHeighLightColor = 7; 312 | bool useRemoteClor = 8; 313 | } 314 | 315 | // 点赞 316 | message LikeMessage { 317 | Common common = 1; 318 | uint64 count = 2; 319 | uint64 total = 3; 320 | uint64 color = 4; 321 | User user = 5; 322 | string icon = 6; 323 | DoubleLikeDetail doubleLikeDetail = 7; 324 | DisplayControlInfo displayControlInfo = 8; 325 | uint64 linkmicGuestUid = 9; 326 | string scene = 10; 327 | PicoDisplayInfo picoDisplayInfo = 11; 328 | 329 | } 330 | 331 | message SocialMessage { 332 | Common common = 1; 333 | User user = 2; 334 | uint64 shareType = 3; 335 | uint64 action = 4; 336 | string shareTarget = 5; 337 | uint64 followCount = 6; 338 | PublicAreaCommon publicAreaCommon = 7; 339 | } 340 | 341 | message PicoDisplayInfo { 342 | uint64 comboSumCount = 1; 343 | string emoji = 2; 344 | Image emojiIcon = 3; 345 | string emojiText = 4; 346 | } 347 | 348 | message DoubleLikeDetail { 349 | bool doubleFlag = 1; 350 | uint32 seqId = 2; 351 | uint32 renewalsNum = 3; 352 | uint32 triggersNum = 4; 353 | } 354 | 355 | message DisplayControlInfo { 356 | bool showText = 1; 357 | bool showIcons = 2; 358 | } 359 | 360 | message EpisodeChatMessage { 361 | Message common = 1; 362 | User user = 2; 363 | string content = 3; 364 | bool visibleToSende = 4; 365 | // BackgroundImage backgroundImage = 5; 366 | // PublicAreaCommon publicAreaCommon = 6; 367 | Image giftImage = 7; 368 | uint64 agreeMsgId = 8; 369 | repeated string colorValueList = 9; 370 | } 371 | 372 | 373 | message MatchAgainstScoreMessage { 374 | Common common = 1; 375 | Against against = 2; 376 | uint32 matchStatus = 3; 377 | uint32 displayStatus = 4; 378 | } 379 | 380 | message Against { 381 | string leftName = 1; 382 | Image leftLogo = 2; 383 | string leftGoal = 3; 384 | // LeftPlayersList leftPlayersList = 4; 385 | // LeftGoalStageDetail leftGoalStageDetail = 5; 386 | string rightName = 6; 387 | Image rightLogo = 7; 388 | string rightGoal = 8; 389 | // RightPlayersList rightPlayersList = 9; 390 | // RightGoalStageDetail rightGoalStageDetail = 10; 391 | uint64 timestamp = 11; 392 | uint64 version = 12; 393 | uint64 leftTeamId = 13; 394 | uint64 rightTeamId = 14; 395 | uint64 diffSei2absSecond = 15; 396 | uint32 finalGoalStage = 16; 397 | uint32 currentGoalStage =17; 398 | uint32 leftScoreAddition =18; 399 | uint32 rightScoreAddition =19; 400 | uint64 leftGoalInt = 20; 401 | uint64 rightGoalInt = 21; 402 | } 403 | 404 | message Common { 405 | string method = 1; 406 | uint64 msgId = 2; 407 | uint64 roomId = 3; 408 | uint64 createTime = 4; 409 | uint32 monitor = 5; 410 | bool isShowMsg = 6; 411 | string describe = 7; 412 | // DisplayText displayText = 8; 413 | uint64 foldType = 9; 414 | uint64 anchorFoldType = 10; 415 | uint64 priorityScore = 11; 416 | string logId = 12; 417 | string msgProcessFilterK = 13; 418 | string msgProcessFilterV = 14; 419 | User user = 15; 420 | // Room room = 16; 421 | uint64 anchorFoldTypeV2 = 17; 422 | uint64 processAtSeiTimeMs = 18; 423 | uint64 randomDispatchMs = 19; 424 | bool isDispatch = 20; 425 | uint64 channelId = 21; 426 | uint64 diffSei2absSecond = 22; 427 | uint64 anchorFoldDuration = 23; 428 | } 429 | 430 | message User { 431 | uint64 id = 1; 432 | uint64 shortId = 2; 433 | string nickName = 3; 434 | uint32 gender = 4; 435 | string Signature = 5; 436 | uint32 Level = 6; 437 | uint64 Birthday = 7; 438 | string Telephone = 8; 439 | Image AvatarThumb = 9; 440 | Image AvatarMedium = 10; 441 | Image AvatarLarge = 11; 442 | bool Verified = 12; 443 | uint32 Experience = 13; 444 | string city = 14; 445 | int32 Status = 15; 446 | uint64 CreateTime = 16; 447 | uint64 ModifyTime = 17; 448 | uint32 Secret = 18; 449 | string ShareQrcodeUri = 19; 450 | uint32 IncomeSharePercent = 20; 451 | repeated Image BadgeImageList = 21; 452 | FollowInfo FollowInfo = 22; 453 | // PayGrade PayGrade = 23; 454 | // FansClub FansClub = 24; 455 | // Border Border = 25; 456 | string SpecialId = 26; 457 | Image AvatarBorder = 27; 458 | Image Medal = 28; 459 | repeated Image RealTimeIconsList = 29; 460 | string displayId = 38; 461 | string secUid = 46; 462 | uint64 fanTicketCount = 1022; 463 | string idStr = 1028; 464 | uint32 ageRange = 1045; 465 | } 466 | 467 | message FollowInfo { 468 | uint64 followingCount = 1; 469 | uint64 followerCount = 2; 470 | uint64 followStatus = 3; 471 | uint64 pushStatus = 4; 472 | string remarkName = 5; 473 | string followerCountStr = 6; 474 | string followingCountStr = 7; 475 | 476 | } 477 | message Image { 478 | repeated string urlListList = 1; 479 | string uri = 2; 480 | uint64 height = 3; 481 | uint64 width = 4; 482 | string avgColor = 5; 483 | uint32 imageType = 6; 484 | string openWebUrl = 7; 485 | ImageContent content = 8; 486 | bool isAnimated = 9; 487 | NinePatchSetting FlexSettingList = 10; 488 | NinePatchSetting TextSettingList = 11; 489 | } 490 | 491 | message NinePatchSetting { 492 | repeated string settingListList = 1; 493 | } 494 | 495 | message ImageContent { 496 | string name = 1; 497 | string fontColor = 2; 498 | uint64 level = 3; 499 | string alternativeText = 4; 500 | } 501 | 502 | message PushFrame { 503 | uint64 seqId = 1; 504 | uint64 logId = 2; 505 | uint64 service = 3; 506 | uint64 method = 4; 507 | repeated HeadersList headersList = 5; 508 | string payloadEncoding = 6; 509 | string payloadType = 7; 510 | bytes payload = 8; 511 | 512 | } 513 | 514 | message kk { 515 | uint32 k=14; 516 | } 517 | 518 | message SendMessageBody { 519 | string conversationId = 1; 520 | uint32 conversationType = 2; 521 | uint64 conversationShortId = 3; 522 | string content = 4; 523 | repeated ExtList ext = 5; 524 | uint32 messageType = 6; 525 | string ticket = 7; 526 | string clientMessageId = 8; 527 | 528 | } 529 | 530 | message ExtList { 531 | string key = 1; 532 | string value = 2; 533 | } 534 | 535 | message Rsp{ 536 | int32 a = 1; 537 | int32 b = 2; 538 | int32 c = 3; 539 | string d = 4; 540 | int32 e = 5; 541 | message F { 542 | uint64 q1 = 1; 543 | uint64 q3 = 3; 544 | string q4 = 4; 545 | uint64 q5 = 5; 546 | } 547 | F f = 6; 548 | string g = 7; 549 | uint64 h = 10; 550 | uint64 i = 11; 551 | uint64 j = 13; 552 | } 553 | 554 | message PreMessage { 555 | uint32 cmd = 1; 556 | uint32 sequenceId = 2; 557 | string sdkVersion = 3; 558 | string token = 4; 559 | uint32 refer = 5; 560 | uint32 inboxType = 6; 561 | string buildNumber = 7; 562 | SendMessageBody sendMessageBody = 8; 563 | // 字段名待定 564 | string aa = 9; 565 | string devicePlatform = 11; 566 | repeated HeadersList headers = 15; 567 | uint32 authType = 18; 568 | string biz = 21; 569 | string access = 22; 570 | } 571 | 572 | message HeadersList { 573 | string key = 1; 574 | string value = 2; 575 | } 576 | 577 | enum CommentTypeTag { 578 | COMMENTTYPETAGUNKNOWN = 0; 579 | COMMENTTYPETAGSTAR = 1; 580 | } --------------------------------------------------------------------------------