From 57290767b6bfda3025b21e4af42e36d0feb01fad Mon Sep 17 00:00:00 2001 From: dragongoose Date: Sun, 28 May 2023 16:19:39 -0400 Subject: [PATCH] Add specific category endpoint and fix minor issues --- extractor/parser.go | 38 +++++++++++-- extractor/structs/parsed.go | 34 ++++++++++-- extractor/twitchExtractor.go | 96 +++++++++++++++++++++++++++++++-- routes/api/discover/discover.go | 11 +++- 4 files changed, 166 insertions(+), 13 deletions(-) diff --git a/extractor/parser.go b/extractor/parser.go index f947ca8..590dd52 100644 --- a/extractor/parser.go +++ b/extractor/parser.go @@ -57,7 +57,7 @@ func ParseStream(data string) (*structs.Stream, error) { } // discover -func ParseCategory(data gjson.Result) (structs.Category, error) { +func ParseCategory(data gjson.Result) (structs.CategoryPreview, error) { tags := data.Get("node.tags").Array() var parsedTags []string @@ -65,13 +65,45 @@ func ParseCategory(data gjson.Result) (structs.Category, error) { parsedTags = append(parsedTags, tag.Get("localizedName").String()) } - return structs.Category{ + return structs.CategoryPreview{ Name: data.Get("node.name").String(), DisplayName: data.Get("node.displayName").String(), - Viewers: data.Get("node.viewersCount").String(), + Viewers: int(data.Get("node.viewersCount").Int()), CreatedAt: data.Get("node.originalReleaseDate").Time(), Tags: parsedTags, Cursor: data.Get("node.cursor").String(), Image: ProxyUrl(data.Get("node.avatarURL").String()), }, nil } + +func ParseMinifiedStream(data gjson.Result) (structs.CategoryMinifiedStream, error) { + var tags []string + tagArea := data.Get("node.freeformTags").Array() + for _, tag := range tagArea { + tags = append(tags, tag.Get("name").String()) + } + + // Store streamerColorHex in memory to use as pointer + var streamerColorHex *string + rawStreamerColorHex := data.Get("node.broadcaster.primaryColorHex").String() + if rawStreamerColorHex == "" { + streamerColorHex = nil + } else { + streamerColorHex = &rawStreamerColorHex + } + + parsedStream := structs.CategoryMinifiedStream{ + Title: data.Get("node.title").String(), + Viewers: int(data.Get("node.viewersCount").Int()), + Preview: ProxyUrl(data.Get("previewImageURL").String()), + Tags: tags, + Streamer: structs.CategoryMinifiedStreamer{ + Name: data.Get("node.broadcaster.login").String(), + Pfp: ProxyUrl(data.Get("node.broadcaster.profileImageURL").String()), + ColorHex: streamerColorHex, + }, + Cursor: data.Get("cursor").String(), + } + + return parsedStream, nil +} diff --git a/extractor/structs/parsed.go b/extractor/structs/parsed.go index c30720c..6963fa9 100644 --- a/extractor/structs/parsed.go +++ b/extractor/structs/parsed.go @@ -26,17 +26,43 @@ type Streamer struct { Socials []Social `json:"socials"` IsLive bool `json:"isLive"` IsPartner bool `json:"isPartner"` - ColorHex string `json:"colorHex"` + ColorHex *string `json:"colorHex"` Id string `json:"id"` Stream *Stream `json:"stream,omitempty"` } -type Category struct { - Name string `json:"username"` +type CategoryPreview struct { + Name string `json:"name"` DisplayName string `json:"displayName"` - Viewers string `json:"viewers"` + Viewers int `json:"viewers"` Tags []string `json:"tags"` CreatedAt time.Time `json:"createdAt"` Image string `json:"image"` Cursor string `json:"cursor,omitempty"` } + +type CategoryMinifiedStreamer struct { + Name string `json:"name"` + Pfp string `json:"pfp"` + ColorHex *string `json:"colorHex"` +} + +type CategoryMinifiedStream struct { + Title string `json:"title"` + Viewers int `json:"viewers"` + Preview string `json:"preview"` + Tags []string `json:"tags"` + Cursor string `json:"cursor,omitempty"` + Streamer CategoryMinifiedStreamer `json:"streamer"` +} + +type CategoryData struct { + Name string `json:"name"` + DisplayName string `json:"displayName"` + Description string `json:"description"` + Viewers int `json:"viewers"` + Followers int `json:"followers"` + Tags []string `json:"tags"` + Streams []CategoryMinifiedStream `json:"streams"` + Cover string `json:"cover"` +} diff --git a/extractor/twitchExtractor.go b/extractor/twitchExtractor.go index df3521f..ea52d0d 100644 --- a/extractor/twitchExtractor.go +++ b/extractor/twitchExtractor.go @@ -150,6 +150,15 @@ func GetStreamerInfo(streamerName string) (structs.Streamer, error) { isLive = false } + // Store streamerColorHex in memory to use as pointer + var streamerColorHex *string + rawStreamerColorHex := streamerData.Get("user.primaryColorHex").String() + if rawStreamerColorHex == "" { + streamerColorHex = nil + } else { + streamerColorHex = &rawStreamerColorHex + } + parsedStreamer := structs.Streamer{ Username: streamerData.Get("user.displayName").String(), About: streamerData.Get("user.description").String(), @@ -158,7 +167,7 @@ func GetStreamerInfo(streamerName string) (structs.Streamer, error) { Socials: parsedSocials, IsLive: isLive, IsPartner: streamerData.Get("user.isPartner").Bool(), - ColorHex: streamerData.Get("user.primaryColorHex").String(), + ColorHex: streamerColorHex, Id: streamerData.Get("user.id").String(), Stream: parsedStream, } @@ -166,7 +175,7 @@ func GetStreamerInfo(streamerName string) (structs.Streamer, error) { return parsedStreamer, nil } -func GetDirectory(limit int, cursor string) ([]structs.Category, error) { +func GetDiscoveryPage(limit int, cursor string) ([]structs.CategoryPreview, error) { payload := []TwitchPayload{ { "operationName": "BrowsePage_AllDirectories", @@ -192,15 +201,15 @@ func GetDirectory(limit int, cursor string) ([]structs.Category, error) { _, body, err := parseResponse(payload) if err != nil { - return []structs.Category{}, err + return []structs.CategoryPreview{}, err } - var parsedCategoryArray []structs.Category + var parsedCategoryArray []structs.CategoryPreview categoryArray := gjson.Get(string(body), "0.data.directoriesWithTags.edges") for _, categoryRes := range categoryArray.Array() { parsedCategory, err := ParseCategory(categoryRes) if err != nil { - return []structs.Category{}, nil + return []structs.CategoryPreview{}, nil } parsedCategoryArray = append(parsedCategoryArray, parsedCategory) @@ -208,3 +217,80 @@ func GetDirectory(limit int, cursor string) ([]structs.Category, error) { return parsedCategoryArray, nil } + +func GetDiscoveryItem(name string, streamLimit int, cursor string) (structs.CategoryData, error) { + payload := []TwitchPayload{ + { + "operationName": "DirectoryPage_Game", + "variables": map[string]interface{}{ + "imageWidth": 50, + "name": name, + "options": map[string]interface{}{ + "sort": "RELEVANCE", + "recommendationsContext": map[string]interface{}{ + "platform": "web", + }, + "requestID": "JIRA-VXP-2397", + "freeformTags": nil, + "tags": []string{}, + }, + "sortTypeIsRecency": false, + "limit": streamLimit, + }, + "extensions": map[string]interface{}{ + "persistedQuery": map[string]interface{}{ + "version": 1, + "sha256Hash": "df4bb6cc45055237bfaf3ead608bbafb79815c7100b6ee126719fac3762ddf8b", + }, + }, + }, + { + "operationName": "Directory_DirectoryBanner", + "variables": map[string]interface{}{ + "name": name, + }, + "extensions": map[string]interface{}{ + "persistedQuery": map[string]interface{}{ + "version": 1, + "sha256Hash": "2670fbecd8fbea0211c56528d6eff5752ef9d6c73cd5238d395784b46335ded4", + }, + }, + }, + } + + _, body, err := parseResponse(payload) + if err != nil { + return structs.CategoryData{}, err + } + + categoryStreams := gjson.Get(string(body), "0.data.game.streams.edges") + + var parsedStreams []structs.CategoryMinifiedStream + for _, stream := range categoryStreams.Array() { + parsed, err := ParseMinifiedStream(stream) + if err != nil { + return structs.CategoryData{}, err + } + parsedStreams = append(parsedStreams, parsed) + } + + categoryData := gjson.Get(string(body), "1.data.game") + + var tags []string + for _, tag := range categoryData.Get("tags").Array() { + tags = append(tags, tag.Get("localizedName").String()) + } + + parsedCategory := structs.CategoryData{ + Name: categoryData.Get("name").String(), + DisplayName: categoryData.Get("displayName").String(), + Description: categoryData.Get("description").String(), + Viewers: int(categoryData.Get("viewersCount").Int()), + Followers: int(categoryData.Get("followersCount").Int()), + Tags: tags, + Cover: ProxyUrl(categoryData.Get("avatarURL").String()), + Streams: parsedStreams, + } + + return parsedCategory, err +} diff --git a/routes/api/discover/discover.go b/routes/api/discover/discover.go index 1755493..2613d12 100644 --- a/routes/api/discover/discover.go +++ b/routes/api/discover/discover.go @@ -10,7 +10,7 @@ func Routes(route *gin.Engine) { auth := route.Group("/api/discover") auth.GET("/", func(context *gin.Context) { - data, err := extractor.GetDirectory(50, "") + data, err := extractor.GetDiscoveryPage(50, "") if err != nil { context.Error(err) return @@ -18,4 +18,13 @@ func Routes(route *gin.Engine) { context.JSON(200, extractor.FormatMessage(data, true)) }) + + auth.GET("/:categoryName", func(context *gin.Context) { + data, err := extractor.GetDiscoveryItem(context.Param("categoryName"), 50, "") + if err != nil { + context.Error(err) + } + + context.JSON(200, data) + }) }