From aa3a0594bd664857f2b90130a18f476083ac15ec Mon Sep 17 00:00:00 2001 From: tyler Date: Tue, 12 Dec 2023 15:32:45 -0500 Subject: [PATCH] Added chat functionality --- chat.go | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ client.go | 39 ++++++++++------- go.mod | 1 + go.sum | 2 + 4 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 chat.go diff --git a/chat.go b/chat.go new file mode 100644 index 0000000..de4f013 --- /dev/null +++ b/chat.go @@ -0,0 +1,125 @@ +package rumblelivestreamlib + +import ( + "bufio" + "bytes" + "encoding/csv" + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/tylertravisty/random" +) + +type ChatInfo struct { + UrlPrefix string + ChatID string + ChannelID int +} + +func (ci *ChatInfo) Url() string { + return fmt.Sprintf("%s/chat/%s/message", ci.UrlPrefix, ci.ChatID) +} + +func (c *Client) streamChatInfo() (*ChatInfo, error) { + if c.StreamUrl == "" { + return nil, fmt.Errorf("stream url is empty") + } + + resp, err := c.getWebpage(c.StreamUrl) + if err != nil { + return nil, fmt.Errorf("error getting stream webpage: %v", err) + } + defer resp.Body.Close() + + r := bufio.NewReader(resp.Body) + line, _, err := r.ReadLine() + var lineS string + for err == nil { + lineS = string(line) + if strings.Contains(lineS, "RumbleChat(") { + start := strings.Index(lineS, "RumbleChat(") + len("RumbleChat(") + end := strings.Index(lineS[start:], ");") + argsS := strings.ReplaceAll(lineS[start:start+end], ", ", ",") + argsS = strings.Replace(argsS, "[", "\"[", 1) + n := strings.LastIndex(argsS, "]") + argsS = argsS[:n] + "]\"" + argsS[n+1:] + c := csv.NewReader(strings.NewReader(argsS)) + args, err := c.ReadAll() + if err != nil { + return nil, fmt.Errorf("error parsing csv: %v", err) + } + info := args[0] + channelID, err := strconv.Atoi(info[5]) + if err != nil { + return nil, fmt.Errorf("error converting channel ID argument string to int: %v", err) + } + return &ChatInfo{info[0], info[1], channelID}, nil + } + line, _, err = r.ReadLine() + } + if err != nil { + return nil, fmt.Errorf("error reading line from stream webpage: %v", err) + } + + return nil, fmt.Errorf("did not find RumbleChat function call") +} + +type ChatMessage struct { + Text string `json:"text"` +} + +type ChatData struct { + RequestID string `json:"request_id"` + Message ChatMessage `json:"message"` + Rant *string `json:"rant"` + ChannelID *int `json:"channel_id"` +} + +type ChatRequest struct { + Data ChatData `json:"data"` +} + +func (c *Client) Chat(message string) error { + if c.httpClient == nil { + return pkgErr("", fmt.Errorf("http client is nil")) + } + + chatInfo, err := c.streamChatInfo() + if err != nil { + return pkgErr("error getting stream chat info", err) + } + + requestID, err := random.String(32) + if err != nil { + return pkgErr("error generating request ID", err) + } + body := ChatRequest{ + Data: ChatData{ + RequestID: requestID, + Message: ChatMessage{ + Text: message, + }, + Rant: nil, + ChannelID: &chatInfo.ChannelID, + }, + } + bodyB, err := json.Marshal(body) + if err != nil { + return pkgErr("error marshaling request body into json", err) + } + + resp, err := c.httpClient.Post(chatInfo.Url(), "application/json", bytes.NewReader(bodyB)) + if err != nil { + return pkgErr("http Post request returned error", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("http Post response status not %s: %s", http.StatusText(http.StatusOK), resp.Status) + } + + return nil +} diff --git a/client.go b/client.go index 0dc1e1b..cae1ba7 100644 --- a/client.go +++ b/client.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io" - "log" "net/http" "net/http/cookiejar" "net/url" @@ -14,7 +13,8 @@ import ( ) const ( - urlWeb = "https://rumble.com" + domain = "rumble.com" + urlWeb = "https://" + domain urlGetSalts = urlWeb + "/service.php?name=user.get_salts" urlUserLogin = urlWeb + "/service.php?name=user.login" urlUserLogout = urlWeb + "/service.php?name=user.logout" @@ -26,15 +26,25 @@ type Client struct { StreamUrl string } -func (c *Client) printCookies() { +func (c *Client) cookies() ([]*http.Cookie, error) { u, err := url.Parse(urlWeb) if err != nil { - log.Fatal("url.Parse err=", err) + return nil, fmt.Errorf("error parsing domain: %v", err) } - fmt.Println("Cookies:") - for _, cookie := range c.httpClient.Jar.Cookies(u) { - fmt.Println(cookie) + return c.httpClient.Jar.Cookies(u), nil +} + +func (c *Client) PrintCookies() error { + cookies, err := c.cookies() + if err != nil { + return pkgErr("error getting cookies", err) } + fmt.Println("Cookies:", len(cookies)) + for _, cookie := range cookies { + fmt.Println(cookie.String()) + } + + return nil } func NewClient(streamKey string, streamUrl string) (*Client, error) { @@ -81,17 +91,17 @@ func (c *Client) Login(username string, password string) error { return nil } -func (c *Client) getWeb() error { - resp, err := c.httpClient.Get(urlWeb) +func (c *Client) getWebpage(url string) (*http.Response, error) { + resp, err := c.httpClient.Get(url) if err != nil { - return fmt.Errorf("http Get request returned error: %v", err) + return nil, fmt.Errorf("http Get request returned error: %v", err) } - defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return fmt.Errorf("http Get response status not %s: %s", http.StatusText(http.StatusOK), resp.Status) + resp.Body.Close() + return nil, fmt.Errorf("http Get response status not %s: %s", http.StatusText(http.StatusOK), resp.Status) } - return nil + return resp, nil } func (c *Client) getSalts(username string) ([]string, error) { @@ -112,7 +122,6 @@ func (c *Client) getSalts(username string) ([]string, error) { if err != nil { return nil, fmt.Errorf("error reading body bytes: %v", err) } - fmt.Println("BodyB:", string(bodyB)) var gsr GetSaltsResponse err = json.NewDecoder(strings.NewReader(string(bodyB))).Decode(&gsr) @@ -142,8 +151,6 @@ func (c *Client) userLogin(username string, password string, salts []string) err if resp.StatusCode != http.StatusOK { return fmt.Errorf("http Post response status not %s: %s", http.StatusText(http.StatusOK), resp.Status) } - bodyB, _ := io.ReadAll(resp.Body) - fmt.Println(string(bodyB)) return nil } diff --git a/go.mod b/go.mod index 0eca6c1..a7a34c0 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/robertkrimen/otto v0.2.1 // indirect + github.com/tylertravisty/random v0.0.0-20210907021019-42b7944f3ad5 // indirect golang.org/x/text v0.4.0 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect ) diff --git a/go.sum b/go.sum index b3e640f..e73bbdf 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0= github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY= +github.com/tylertravisty/random v0.0.0-20210907021019-42b7944f3ad5 h1:13ClHNZRQ+3Ktg6Q6MVjcGZS9xBiFpmgvUg0jkQ/TEY= +github.com/tylertravisty/random v0.0.0-20210907021019-42b7944f3ad5/go.mod h1:4mNgC4SPDnTcDJ5U2JZW1hCrVZdUQNWoLlp+t57gOLU= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=