diff --git a/extractor/parser.go b/extractor/parser.go index ff52b60..c1fd1a4 100644 --- a/extractor/parser.go +++ b/extractor/parser.go @@ -3,6 +3,7 @@ package extractor import ( "errors" "safetwitch-backend/extractor/structs" + "time" "github.com/tidwall/gjson" ) @@ -19,8 +20,38 @@ func ParseSocials(data string) ([]structs.Social, error) { } if !result.Exists() { - return parsedSocials, errors.New("Error while parsing socials, path does not exist.") + return parsedSocials, errors.New("error while parsing socials, path does not exist") } return parsedSocials, nil } + +func ParseStream(data string) (*structs.Stream, error) { + // check if live + stream := gjson.Get(data, "1.data.user.stream") + if !stream.IsObject() { + return nil, errors.New("streamer is not live") + } + + var tags []string + tagArea := gjson.Get(data, "2.data.user.stream.freeformTags").Array() + for _, tag := range tagArea { + tags = append(tags, tag.Get("name").String()) + } + + time, err := time.Parse(time.RFC3339, stream.Get("createdAt").String()) + if err != nil { + return &structs.Stream{}, err + } + + parsedStream := structs.Stream{ + Title: gjson.Get(data, "1.data.user.lastBroadcast.title").String(), + Topic: stream.Get("game.name").String(), + StartedAt: time, + Tags: tags, + Viewers: int(gjson.Get(data, "4.data.user.stream.viewersCount").Int()), + Preview: ProxyUrl(gjson.Get(data, "3.data.user.stream.previewImageURL").String()), + } + + return &parsedStream, nil +} diff --git a/extractor/structs/parsed.go b/extractor/structs/parsed.go index daa3ee4..e0b2039 100644 --- a/extractor/structs/parsed.go +++ b/extractor/structs/parsed.go @@ -1,7 +1,32 @@ package structs +import "time" + type Social struct { - Type string - Title string - Url string + Type string `json:"type"` + Title string `json:"title"` + Url string `json:"url"` +} + +type Stream struct { + Tags []string `json:"tags"` + Title string `json:"title"` + Topic string `json:"topic"` + StartedAt time.Time `json:"startedAt"` + Viewers int `json:"viewers"` + Preview string `json:"preview"` + Cursor string `json:"cursor,omitempty"` +} + +type Streamer struct { + Username string `json:"username"` + About string `json:"about"` + Pfp string `json:"pfp"` + Followers int `json:"followers"` + Socials []Social `json:"socials"` + IsLive bool `json:"isLive"` + IsPartner bool `json:"isPartner"` + ColorHex string `json:"colorHex"` + Id string `json:"id"` + Stream *Stream `json:"stream,omitempty"` } diff --git a/extractor/twitchExtractor.go b/extractor/twitchExtractor.go index a9ceddb..4f02dc7 100644 --- a/extractor/twitchExtractor.go +++ b/extractor/twitchExtractor.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "io" - "log" "net/http" "safetwitch-backend/extractor/structs" @@ -27,7 +26,7 @@ func call(url, method string, body io.Reader) (*http.Response, error) { type TwitchPayload = map[string]interface{} -func GetStreamerInfo(streamerName string) (structs.TwitchApiResponse, error) { +func GetStreamerInfo(streamerName string) (structs.Streamer, error) { payload := []TwitchPayload{ { "operationName": "ChannelRoot_AboutPanel", @@ -93,40 +92,65 @@ func GetStreamerInfo(streamerName string) (structs.TwitchApiResponse, error) { } json_data, err := json.Marshal(payload) if err != nil { - return structs.TwitchApiResponse{}, nil + return structs.Streamer{}, nil } resp, err := call(twitchUrl, "POST", bytes.NewBuffer(json_data)) if err != nil { - return structs.TwitchApiResponse{}, err + return structs.Streamer{}, err } defer resp.Body.Close() - RespStatusHandler(resp.StatusCode) - body, err := io.ReadAll(resp.Body) if err != nil { - return structs.TwitchApiResponse{}, err + return structs.Streamer{}, err } var twitchResponse []structs.TwitchApiResponse err = json.Unmarshal(body, &twitchResponse) if err != nil { - return structs.TwitchApiResponse{}, err + return structs.Streamer{}, err } // begin parsing response streamerFound := gjson.Get(string(body), "0.data.user") if streamerFound.String() == "" { - return twitchResponse[0], errors.New("streamer not found") + return structs.Streamer{}, errors.New("streamer not found") } streamerData := gjson.Get(string(body), "0.data") parsedSocials, err := ParseSocials(streamerData.String()) if err != nil { - return twitchResponse[0], nil + return structs.Streamer{}, err } - log.Println(parsedSocials) - return twitchResponse[0], nil + parsedStream, err := ParseStream(string(body)) + var isLive bool + if err != nil { + if err.Error() == "streamer is not live" { + parsedStream = nil + } else { + return structs.Streamer{}, err + } + } + if parsedStream != nil { + isLive = true + } else { + isLive = false + } + + parsedStreamer := structs.Streamer{ + Username: streamerData.Get("user.displayName").String(), + About: streamerData.Get("user.description").String(), + Pfp: ProxyUrl(streamerData.Get("user.profileImageURL").String()), + Followers: int(streamerData.Get("user.followers.totalCount").Int()), + Socials: parsedSocials, + IsLive: isLive, + IsPartner: streamerData.Get("user.isPartner").Bool(), + ColorHex: streamerData.Get("user.primaryColorHex").String(), + Id: streamerData.Get("user.id").String(), + Stream: parsedStream, + } + + return parsedStreamer, nil } diff --git a/extractor/utils.go b/extractor/utils.go new file mode 100644 index 0000000..575c274 --- /dev/null +++ b/extractor/utils.go @@ -0,0 +1,13 @@ +package extractor + +import ( + b64 "encoding/base64" + "os" +) + +func ProxyUrl(url string) string { + encodedUrl := b64.StdEncoding.EncodeToString([]byte(url)) + backendUrl := os.Getenv("URL") + + return backendUrl + "/img/" + encodedUrl +} diff --git a/go.mod b/go.mod index 14a2a01..a4635df 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,12 @@ module safetwitch-backend go 1.20 -require github.com/gin-gonic/gin v1.9.0 +require ( + github.com/gin-gonic/gin v1.9.0 + github.com/tidwall/gjson v1.14.4 +) require ( - github.com/buger/jsonparser v1.1.1 // indirect github.com/bytedance/sonic v1.8.8 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/gin-contrib/sse v0.1.0 // indirect @@ -20,7 +22,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.7 // indirect - github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index a0ea5f1..b38d582 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.8.8 h1:Kj4AYbZSeENfyXicsYppYKO0K2YWab+i2UTSY7Ukz9Q= github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= @@ -52,6 +53,13 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=