diff --git a/extractor/structs/parsed.go b/extractor/structs/parsed.go index 761c7fd..3339855 100644 --- a/extractor/structs/parsed.go +++ b/extractor/structs/parsed.go @@ -113,3 +113,21 @@ type Shelve struct { Title string `json:"title"` Videos []Video `json:"videos"` } + +type VodMessager struct { + Name string `json:"name"` + Login string `json:"login"` +} + +type VodCommentBadge struct { + Version string `json:"version"` + SetID string `json:"setId"` +} + +type VodComment struct { + Message string `json:"message"` + Messager MinifiedStreamer `json:"messager"` + Offset int `json:"offset"` + Cursor string `json:"cursor"` + Badges []VodCommentBadge `json:"badges"` +} diff --git a/extractor/twitch/VODChat.go b/extractor/twitch/VODChat.go new file mode 100644 index 0000000..901278e --- /dev/null +++ b/extractor/twitch/VODChat.go @@ -0,0 +1,36 @@ +package twitch + +import ( + "safetwitch-backend/extractor/structs" + + "github.com/tidwall/gjson" +) + +func GetVODChat(vodID string, second int) ([]structs.VodComment, error) { + payload := []TwitchPayload{ + { + "operationName": "VideoCommentsByOffsetOrCursor", + "variables": map[string]interface{}{ + "videoID": vodID, + "contentOffsetSeconds": second, + }, + "extensions": map[string]interface{}{ + "persistedQuery": map[string]interface{}{ + "version": 1, + "sha256Hash": "b70a3591ff0f4e0313d126c6a1502d79a1c02baebb288227c582044aa76adf6a", + }, + }, + }, + } + _, body, err := parseResponse(payload) + if err != nil { + return []structs.VodComment{}, err + } + + comments := gjson.Get(string(body), "0.data.video.comments.edges").Array() + parsedComments := []structs.VodComment{} + for _, comment := range comments { + parsedComments = append(parsedComments, ParseVODMessage(comment)) + } + return parsedComments, nil +} diff --git a/extractor/twitch/parser.go b/extractor/twitch/parser.go index 53f23eb..0193fb2 100644 --- a/extractor/twitch/parser.go +++ b/extractor/twitch/parser.go @@ -285,3 +285,34 @@ func ParseShelve(data gjson.Result, streamer structs.Streamer) structs.Shelve { Videos: parsedVideos, } } + +func ParseVODMessage(data gjson.Result) structs.VodComment { + messager := structs.MinifiedStreamer{ + Login: data.Get("node.commenter.login").String(), + Name: data.Get("node.commenter.displayName").String(), + ColorHex: data.Get("node.message.userColor").String(), + } + + parsedBadges := []structs.VodCommentBadge{} + for _, badge := range data.Get("node.message.userBadges").Array() { + setID := badge.Get("setID").String() + version := badge.Get("version").String() + + if version != "" && setID != "" { + b := structs.VodCommentBadge{ + SetID: setID, + Version: version, + } + parsedBadges = append(parsedBadges, b) + } + } + + return structs.VodComment{ + Message: data.Get("node.message.fragments.0.text").String(), + Offset: int(data.Get("node.contentOffsetSeconds").Int()), + Cursor: data.Get("cursor").String(), + Messager: messager, + Badges: parsedBadges, + } + +} diff --git a/routes/api/discover/discover.go b/routes/api/discover/discover.go index 3b4c66f..233e107 100644 --- a/routes/api/discover/discover.go +++ b/routes/api/discover/discover.go @@ -24,6 +24,7 @@ func Routes(route *gin.Engine) { data, err := twitch.GetDiscoveryItem(context.Param("categoryName"), 50, "") if err != nil { context.Error(err) + return } context.JSON(200, extractor.FormatMessage(data, true)) diff --git a/routes/api/users/users.go b/routes/api/users/users.go index 01f68c8..9ff7857 100644 --- a/routes/api/users/users.go +++ b/routes/api/users/users.go @@ -20,6 +20,7 @@ func Routes(route *gin.Engine) { }) } else { context.Error(err) + return } return } diff --git a/routes/api/vods/vods.go b/routes/api/vods/vods.go index dfb380a..843a612 100644 --- a/routes/api/vods/vods.go +++ b/routes/api/vods/vods.go @@ -3,14 +3,15 @@ package vods import ( "safetwitch-backend/extractor" "safetwitch-backend/extractor/twitch" + "strconv" "github.com/gin-gonic/gin" ) func Routes(route *gin.Engine) { - auth := route.Group("/api/vods") + vods := route.Group("/api/vods") - auth.GET("/shelve/:streamerName", func(context *gin.Context) { + vods.GET("/shelve/:streamerName", func(context *gin.Context) { data, err := twitch.GetStreamerVideoShelves(context.Param("streamerName")) if err != nil { context.Error(err) @@ -19,7 +20,7 @@ func Routes(route *gin.Engine) { context.JSON(200, extractor.FormatMessage(data, true)) }) - auth.GET("/:streamerName", func(context *gin.Context) { + vods.GET("/:streamerName", func(context *gin.Context) { data, err := twitch.GetVodMetadata(context.Param("streamerName")) if err != nil { context.Error(err) @@ -27,4 +28,20 @@ func Routes(route *gin.Engine) { } context.JSON(200, extractor.FormatMessage(data, true)) }) + + vods.GET("/comments/:vodID/:offset", func(context *gin.Context) { + offset := context.Param("offset") + o, err := strconv.Atoi(offset) + if err != nil { + context.Error(err) + return + } + + data, err := twitch.GetVODChat(context.Param("vodID"), o) + if err != nil { + context.Error(err) + return + } + context.JSON(200, extractor.FormatMessage(data, true)) + }) } diff --git a/routes/proxy/proxy.go b/routes/proxy/proxy.go index 5bde9c5..241d44f 100644 --- a/routes/proxy/proxy.go +++ b/routes/proxy/proxy.go @@ -17,15 +17,18 @@ func Routes(route *gin.Engine) { decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("url")) if err != nil { context.Error(err) + return } imageResp, err := http.Get(string(decodedUrl)) if err != nil { context.Error(err) + return } body, err := io.ReadAll(imageResp.Body) if err != nil { context.Error(err) + return } contentType := imageResp.Header.Get("Content-Type") @@ -39,6 +42,7 @@ func Routes(route *gin.Engine) { playlistFile, err := twitch.GetStream(streamer) if err != nil { context.Error(err) + return } context.Data(200, "application/vnd.apple.mpegurl", []byte(playlistFile)) @@ -48,11 +52,13 @@ func Routes(route *gin.Engine) { decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("encodedUrl")) if err != nil { context.Error(err) + return } playlistFile, err := twitch.GetSubPlaylist(string(decodedUrl), false) if err != nil { context.Error(err) + return } context.Data(200, "application/vnd.apple.mpegurl", []byte(playlistFile)) @@ -62,6 +68,7 @@ func Routes(route *gin.Engine) { decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("encodedUrl")) if err != nil { context.Error(err) + return } segmentData, err := http.Get(string(decodedUrl)) @@ -73,6 +80,7 @@ func Routes(route *gin.Engine) { segment, err := io.ReadAll(segmentData.Body) if err != nil { context.Error(err) + return } context.Data(200, "application/text", segment) @@ -93,11 +101,13 @@ func Routes(route *gin.Engine) { decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("encodedUrl")) if err != nil { context.Error(err) + return } playlistFile, err := twitch.GetSubPlaylist(string(decodedUrl), true) if err != nil { context.Error(err) + return } context.Data(200, "application/vnd.apple.mpegurl", []byte(playlistFile)) @@ -107,6 +117,7 @@ func Routes(route *gin.Engine) { decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("encodedUrl")) if err != nil { context.Error(err) + return } // remove the last path of url and replace with segment @@ -122,6 +133,7 @@ func Routes(route *gin.Engine) { segment, err := io.ReadAll(segmentData.Body) if err != nil { context.Error(err) + return } context.Data(200, "application/text", segment)