mirror of
https://codeberg.org/SafeTwitch/safetwitch-backend.git
synced 2024-12-22 05:02:58 -05:00
VOD support
This commit is contained in:
parent
e54c610848
commit
a9fe2eaaf3
7 changed files with 119 additions and 3 deletions
|
@ -113,3 +113,21 @@ type Shelve struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Videos []Video `json:"videos"`
|
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"`
|
||||||
|
}
|
||||||
|
|
36
extractor/twitch/VODChat.go
Normal file
36
extractor/twitch/VODChat.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -285,3 +285,34 @@ func ParseShelve(data gjson.Result, streamer structs.Streamer) structs.Shelve {
|
||||||
Videos: parsedVideos,
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ func Routes(route *gin.Engine) {
|
||||||
data, err := twitch.GetDiscoveryItem(context.Param("categoryName"), 50, "")
|
data, err := twitch.GetDiscoveryItem(context.Param("categoryName"), 50, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
context.JSON(200, extractor.FormatMessage(data, true))
|
context.JSON(200, extractor.FormatMessage(data, true))
|
||||||
|
|
|
@ -20,6 +20,7 @@ func Routes(route *gin.Engine) {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,15 @@ package vods
|
||||||
import (
|
import (
|
||||||
"safetwitch-backend/extractor"
|
"safetwitch-backend/extractor"
|
||||||
"safetwitch-backend/extractor/twitch"
|
"safetwitch-backend/extractor/twitch"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Routes(route *gin.Engine) {
|
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"))
|
data, err := twitch.GetStreamerVideoShelves(context.Param("streamerName"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
@ -19,7 +20,7 @@ func Routes(route *gin.Engine) {
|
||||||
context.JSON(200, extractor.FormatMessage(data, true))
|
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"))
|
data, err := twitch.GetVodMetadata(context.Param("streamerName"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
@ -27,4 +28,20 @@ func Routes(route *gin.Engine) {
|
||||||
}
|
}
|
||||||
context.JSON(200, extractor.FormatMessage(data, true))
|
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))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,15 +17,18 @@ func Routes(route *gin.Engine) {
|
||||||
decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("url"))
|
decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("url"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
imageResp, err := http.Get(string(decodedUrl))
|
imageResp, err := http.Get(string(decodedUrl))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
body, err := io.ReadAll(imageResp.Body)
|
body, err := io.ReadAll(imageResp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
contentType := imageResp.Header.Get("Content-Type")
|
contentType := imageResp.Header.Get("Content-Type")
|
||||||
|
@ -39,6 +42,7 @@ func Routes(route *gin.Engine) {
|
||||||
playlistFile, err := twitch.GetStream(streamer)
|
playlistFile, err := twitch.GetStream(streamer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Data(200, "application/vnd.apple.mpegurl", []byte(playlistFile))
|
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"))
|
decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("encodedUrl"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
playlistFile, err := twitch.GetSubPlaylist(string(decodedUrl), false)
|
playlistFile, err := twitch.GetSubPlaylist(string(decodedUrl), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Data(200, "application/vnd.apple.mpegurl", []byte(playlistFile))
|
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"))
|
decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("encodedUrl"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
segmentData, err := http.Get(string(decodedUrl))
|
segmentData, err := http.Get(string(decodedUrl))
|
||||||
|
@ -73,6 +80,7 @@ func Routes(route *gin.Engine) {
|
||||||
segment, err := io.ReadAll(segmentData.Body)
|
segment, err := io.ReadAll(segmentData.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Data(200, "application/text", segment)
|
context.Data(200, "application/text", segment)
|
||||||
|
@ -93,11 +101,13 @@ func Routes(route *gin.Engine) {
|
||||||
decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("encodedUrl"))
|
decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("encodedUrl"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
playlistFile, err := twitch.GetSubPlaylist(string(decodedUrl), true)
|
playlistFile, err := twitch.GetSubPlaylist(string(decodedUrl), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Data(200, "application/vnd.apple.mpegurl", []byte(playlistFile))
|
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"))
|
decodedUrl, err := b64.StdEncoding.DecodeString(context.Param("encodedUrl"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the last path of url and replace with segment
|
// 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)
|
segment, err := io.ReadAll(segmentData.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context.Error(err)
|
context.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
context.Data(200, "application/text", segment)
|
context.Data(200, "application/text", segment)
|
||||||
|
|
Loading…
Reference in a new issue