rumble-livestream-lib-go/client.go
2024-02-08 16:05:23 -05:00

322 lines
7.4 KiB
Go

package rumblelivestreamlib
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
cookiejar "github.com/juju/persistent-cookiejar"
"github.com/robertkrimen/otto"
)
const (
domain = "rumble.com"
urlWeb = "https://" + domain
urlAccount = urlWeb + "/account/"
urlGetSalts = urlWeb + "/service.php?name=user.get_salts"
urlUserLogin = urlWeb + "/service.php?name=user.login"
urlUserLogout = urlWeb + "/service.php?name=user.logout"
)
type Client struct {
httpClient *http.Client
chatInfo *ChatInfo
chatStream *ChatStream
chatStreamMu sync.Mutex
StreamKey string
StreamUrl string
}
func (c *Client) cookies() ([]*http.Cookie, error) {
u, err := url.Parse(urlWeb)
if err != nil {
return nil, fmt.Errorf("error parsing domain: %v", err)
}
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
}
type NewClientOptions struct {
Cookies []*http.Cookie
StreamKey string
StreamUrl string
}
func NewClient(opts NewClientOptions) (*Client, error) {
cl, err := newHttpClient(opts.Cookies)
if err != nil {
return nil, pkgErr("error creating http client", err)
}
return &Client{httpClient: cl, StreamKey: opts.StreamKey, StreamUrl: opts.StreamUrl}, nil
}
func newHttpClient(cookies []*http.Cookie) (*http.Client, error) {
jar, err := cookiejar.New(nil)
if err != nil {
return nil, fmt.Errorf("error creating cookiejar: %v", err)
}
url, err := url.Parse(urlWeb)
if err != nil {
return nil, fmt.Errorf("error parsing domain: %v", err)
}
jar.SetCookies(url, cookies)
return &http.Client{Jar: jar}, nil
}
type GetSaltsData struct {
Salts []string `json:"salts"`
}
type GetSaltsResponse struct {
Data GetSaltsData `json:"data"`
}
func (c *Client) Login(username string, password string) ([]*http.Cookie, error) {
if c.httpClient == nil {
return nil, pkgErr("", fmt.Errorf("http client is nil"))
}
salts, err := c.getSalts(username)
if err != nil {
return nil, pkgErr("error getting salts", err)
}
err = c.userLogin(username, password, salts)
if err != nil {
return nil, pkgErr("error logging in", err)
}
cookies, err := c.cookies()
if err != nil {
return nil, pkgErr("error getting cookies", err)
}
return cookies, nil
}
func (c *Client) getWebpage(url string) (*http.Response, error) {
resp, err := c.httpClient.Get(url)
if err != nil {
return nil, fmt.Errorf("http Get request returned error: %v", err)
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("http Get response status not %s: %s", http.StatusText(http.StatusOK), resp.Status)
}
return resp, nil
}
func (c *Client) getSalts(username string) ([]string, error) {
u := url.URL{}
q := u.Query()
q.Add("username", username)
body := q.Encode()
resp, err := c.httpClient.Post(urlGetSalts, "application/x-www-form-urlencoded", strings.NewReader(body))
if err != nil {
return nil, fmt.Errorf("http Post request returned error: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http Post response status not %s: %s", http.StatusText(http.StatusOK), resp.Status)
}
bodyB, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error reading body bytes: %v", err)
}
var gsr GetSaltsResponse
err = json.NewDecoder(strings.NewReader(string(bodyB))).Decode(&gsr)
if err != nil {
return nil, fmt.Errorf("error decoding response body from server: %v", err)
}
return gsr.Data.Salts, nil
}
type DataBool struct {
Session bool `json:"session"`
}
type LoginResponseBool struct {
Data DataBool `json:"data"`
}
type DataString struct {
Session string `json:"session"`
}
type LoginResponseString struct {
Data DataString `json:"data"`
}
func loginResponseSession(body []byte) (string, error) {
bodyS := string(body)
var lrs LoginResponseString
err := json.NewDecoder(strings.NewReader(bodyS)).Decode(&lrs)
if err == nil {
return lrs.Data.Session, nil
}
var lrb LoginResponseBool
err = json.NewDecoder(strings.NewReader(bodyS)).Decode(&lrb)
if err == nil {
return "false", nil
}
return "", fmt.Errorf("error decoding login response")
}
func (c *Client) userLogin(username string, password string, salts []string) error {
hashes, err := generateHashes(password, salts)
if err != nil {
return fmt.Errorf("error generating password hashes: %v", err)
}
u := url.URL{}
q := u.Query()
q.Add("username", username)
q.Add("password_hashes", hashes)
body := q.Encode()
resp, err := c.httpClient.Post(urlUserLogin, "application/x-www-form-urlencoded", strings.NewReader(body))
if err != nil {
return fmt.Errorf("http Post request returned error: %v", 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)
}
bodyB, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("error reading body bytes: %v", err)
}
session, err := loginResponseSession(bodyB)
if err != nil {
return fmt.Errorf("error getting login response session: %v", err)
}
if session == "false" {
return fmt.Errorf("failed to log in")
}
return nil
}
func generateHashes(password string, salts []string) (string, error) {
vm := otto.New()
vm.Set("password", password)
vm.Set("salt0", salts[0])
vm.Set("salt1", salts[1])
vm.Set("salt2", salts[2])
_, err := vm.Run(md5)
if err != nil {
return "", fmt.Errorf("error running md5 javascript: %v", err)
}
value, err := vm.Get("hashes")
if err != nil {
return "", fmt.Errorf("error getting hashes value: %v", err)
}
hashes, err := value.ToString()
if err != nil {
return "", fmt.Errorf("error converting hashes value to string: %v", err)
}
return hashes, nil
}
func (c *Client) Logout() error {
if c.httpClient == nil {
return pkgErr("", fmt.Errorf("http client is nil"))
}
err := c.userLogout()
if err != nil {
return pkgErr("error logging out", err)
}
return nil
}
func (c *Client) userLogout() error {
resp, err := c.httpClient.Get(urlUserLogout)
if err != nil {
return 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)
}
return nil
}
type LoggedInResponseUser struct {
LoggedIn bool `json:"logged_in"`
}
type LoggedInResponse struct {
User LoggedInResponseUser `json:"user"`
}
func (c *Client) LoggedIn() (bool, error) {
// resp, err := c.httpClient.Get(urlAccount)
// if err != nil {
// return false, pkgErr("error getting account page", err)
// }
// defer resp.Body.Close()
// if resp.Request.URL.String() != urlAccount {
// return false, nil
// }
// fmt.Println("Account page works")
// return true, nil
resp, err := c.httpClient.Get(urlUserLogin)
if err != nil {
return false, pkgErr("error getting account page", err)
}
defer resp.Body.Close()
bodyB, err := io.ReadAll(resp.Body)
if err != nil {
return false, pkgErr("error reading body bytes", err)
}
var lir LoggedInResponse
err = json.NewDecoder(strings.NewReader(string(bodyB))).Decode(&lir)
if err != nil {
return false, pkgErr("error un-marshaling response body", err)
}
return lir.User.LoggedIn, nil
}