mirror of
https://codeberg.org/SafeTwitch/safetwitch-backend.git
synced 2024-12-22 05:02:58 -05:00
337 lines
10 KiB
Go
337 lines
10 KiB
Go
package twitch
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"os"
|
|
"safetwitch-backend/extractor"
|
|
"safetwitch-backend/extractor/structs"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func ParseStreamer(streamerData gjson.Result, isLive bool, socials []structs.Social, stream *structs.Stream, login string, streamerBanner string) (structs.Streamer, error) {
|
|
// Store streamerColorHex in memory to use as pointer
|
|
var streamerColorHex *string
|
|
rawStreamerColorHex := streamerData.Get("user.primaryColorHex").String()
|
|
if rawStreamerColorHex == "" {
|
|
streamerColorHex = nil
|
|
} else {
|
|
streamerColorHex = &rawStreamerColorHex
|
|
}
|
|
|
|
var bannerUrl *string = &streamerBanner
|
|
if *bannerUrl == "" {
|
|
bannerUrl = nil
|
|
} else {
|
|
proxied := extractor.ProxyUrl(*bannerUrl)
|
|
bannerUrl = &proxied
|
|
}
|
|
|
|
parsedStreamer := structs.Streamer{
|
|
Username: streamerData.Get("user.displayName").String(),
|
|
Login: login,
|
|
About: streamerData.Get("user.description").String(),
|
|
Pfp: extractor.ProxyUrl(streamerData.Get("user.profileImageURL").String()),
|
|
Banner: bannerUrl,
|
|
Followers: int(streamerData.Get("user.followers.totalCount").Int()),
|
|
Socials: socials,
|
|
IsLive: isLive,
|
|
IsPartner: streamerData.Get("user.isPartner").Bool(),
|
|
ColorHex: streamerColorHex,
|
|
Id: streamerData.Get("user.id").String(),
|
|
Stream: stream,
|
|
}
|
|
|
|
return parsedStreamer, nil
|
|
}
|
|
|
|
func ParseSocials(data string) ([]structs.Social, error) {
|
|
var parsedSocials []structs.Social
|
|
result := gjson.Get(data, "user.channel.socialMedias")
|
|
for _, social := range result.Array() {
|
|
parsedSocials = append(parsedSocials, structs.Social{
|
|
Name: social.Get("title").String(),
|
|
Type: social.Get("name").String(),
|
|
Url: social.Get("url").String(),
|
|
})
|
|
}
|
|
|
|
if !result.Exists() {
|
|
return parsedSocials, errors.New("error while parsing socials, path does not exist")
|
|
}
|
|
|
|
return parsedSocials, nil
|
|
}
|
|
|
|
// Takes in all of the data returned from twitch (see payload from Streamer.go)
|
|
// and parses it into a stream. Offset is used for bulk processing
|
|
// when the offsets are different. Use offset 0 for normal uses
|
|
func ParseStream(data string, offset int) (*structs.Stream, error) {
|
|
// check if live
|
|
// default "1.data.user.stream"
|
|
stream := gjson.Get(data, extractor.GenGjsonQuery(offset+1, ".data.user.stream"))
|
|
if !stream.IsObject() {
|
|
return nil, errors.New("streamer is not live")
|
|
}
|
|
|
|
var tags []string
|
|
// default "2.data.user.stream.freeformTags"
|
|
tagArea := gjson.Get(data, extractor.GenGjsonQuery(offset+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{
|
|
// default "1.data.user.lastBroadcast.title"
|
|
Title: gjson.Get(data, extractor.GenGjsonQuery(offset+1, ".data.user.lastBroadcast.title")).String(),
|
|
Topic: stream.Get("game.name").String(),
|
|
StartedAt: time,
|
|
Tags: tags,
|
|
// default "4.data.user.stream.viewersCount"
|
|
Viewers: int(gjson.Get(data, extractor.GenGjsonQuery(offset+4, ".data.user.stream.viewersCount")).Int()),
|
|
// default "3.data.user.stream.previewImageURL"
|
|
Preview: extractor.ProxyUrl(gjson.Get(data, extractor.GenGjsonQuery(offset+3, ".data.user.stream.previewImageURL")).String()),
|
|
}
|
|
|
|
return &parsedStream, nil
|
|
}
|
|
|
|
// discover
|
|
func ParseCategory(data gjson.Result) (structs.CategoryPreview, error) {
|
|
tags := data.Get("node.tags").Array()
|
|
var parsedTags []string
|
|
|
|
for _, tag := range tags {
|
|
parsedTags = append(parsedTags, tag.Get("localizedName").String())
|
|
}
|
|
|
|
return structs.CategoryPreview{
|
|
Name: data.Get("node.name").String(),
|
|
DisplayName: data.Get("node.displayName").String(),
|
|
Viewers: int(data.Get("node.viewersCount").Int()),
|
|
CreatedAt: data.Get("node.originalReleaseDate").Time(),
|
|
Tags: parsedTags,
|
|
Cursor: data.Get("cursor").String(),
|
|
Image: extractor.ProxyUrl(data.Get("node.avatarURL").String()),
|
|
}, nil
|
|
}
|
|
|
|
func ParseMinifiedStream(data gjson.Result) (structs.CategoryMinifiedStream, error) {
|
|
var tags []string
|
|
tagArea := data.Get("node.freeformTacgs").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: extractor.ProxyUrl(data.Get("node.previewImageURL").String()),
|
|
Tags: tags,
|
|
Streamer: structs.CategoryMinifiedStreamer{
|
|
Name: data.Get("node.broadcaster.login").String(),
|
|
Pfp: extractor.ProxyUrl(data.Get("node.broadcaster.profileImageURL").String()),
|
|
ColorHex: streamerColorHex,
|
|
},
|
|
Cursor: data.Get("cursor").String(),
|
|
}
|
|
|
|
return parsedStream, nil
|
|
}
|
|
|
|
func ParseBadges(data gjson.Result) ([]structs.Badge, error) {
|
|
var formattedBadges = []structs.Badge{}
|
|
|
|
for _, badge := range data.Array() {
|
|
id := badge.Get("id").String()
|
|
decodedId, err := base64.StdEncoding.DecodeString(id)
|
|
if err != nil {
|
|
return []structs.Badge{}, nil
|
|
}
|
|
|
|
formattedBadges = append(formattedBadges, structs.Badge{
|
|
Id: string(decodedId),
|
|
SetId: badge.Get("setID").String(),
|
|
Title: badge.Get("title").String(),
|
|
Version: badge.Get("version").String(),
|
|
Images: map[string]string{
|
|
"image1x": extractor.ProxyUrl(badge.Get("image1x").String()),
|
|
"image2x": extractor.ProxyUrl(badge.Get("image2x").String()),
|
|
"image4x": extractor.ProxyUrl(badge.Get("image4x").String()),
|
|
},
|
|
})
|
|
}
|
|
|
|
return formattedBadges, nil
|
|
}
|
|
|
|
func StreamSubProxyUrl(url string, isVOD bool) string {
|
|
encodedUrl := base64.StdEncoding.EncodeToString([]byte(url))
|
|
backendUrl := os.Getenv("URL")
|
|
|
|
if isVOD {
|
|
return backendUrl + "/proxy/vod/sub/" + encodedUrl + "/video.m3u8"
|
|
} else {
|
|
return backendUrl + "/proxy/stream/sub/" + encodedUrl
|
|
}
|
|
}
|
|
|
|
func StreamSegmentProxyUrl(url string) string {
|
|
encodedUrl := base64.StdEncoding.EncodeToString([]byte(url))
|
|
backendUrl := os.Getenv("URL")
|
|
|
|
return backendUrl + "/proxy/stream/segment/" + encodedUrl
|
|
}
|
|
|
|
func ProxyPlaylistFile(playlist string, isSubPlaylist bool, isVod bool) string {
|
|
// Split the playlist into individual entries
|
|
entries := strings.Split(playlist, "\n")[1:] // Ignore the first line which contains the M3U header
|
|
|
|
// Loop through each entry and replace the URL
|
|
for i, entry := range entries {
|
|
if strings.HasPrefix(entry, "http") { // Only modify lines that contain URLs
|
|
var newURL string
|
|
if isSubPlaylist {
|
|
newURL = StreamSegmentProxyUrl(entry)
|
|
} else {
|
|
newURL = StreamSubProxyUrl(entry, isVod)
|
|
}
|
|
entries[i] = newURL
|
|
}
|
|
}
|
|
|
|
if isVod {
|
|
// Loop through each entry and replace the URL
|
|
for i, entry := range entries {
|
|
if strings.HasSuffix(entry, ".ts") {
|
|
entries[i] = entry
|
|
}
|
|
}
|
|
}
|
|
|
|
// Join the modified entries back into a single string separated by a newline
|
|
modifiedPlaylist := "#EXTM3U\n" + strings.Join(entries, "\n")
|
|
|
|
return modifiedPlaylist
|
|
}
|
|
|
|
func ParseMinifiedCategory(data gjson.Result) structs.MinifiedCategory {
|
|
return structs.MinifiedCategory{
|
|
Image: extractor.ProxyUrl(data.Get("boxArtURL").String()),
|
|
Name: data.Get("name").String(),
|
|
DisplayName: data.Get("displayName").String(),
|
|
Id: data.Get("id").String(),
|
|
}
|
|
}
|
|
|
|
func ParseMinifiedStreamer(data gjson.Result) structs.MinifiedStreamer {
|
|
return structs.MinifiedStreamer{
|
|
Name: data.Get("displayName").String(),
|
|
Login: data.Get("login").String(),
|
|
Pfp: extractor.ProxyUrl(data.Get("profileImageURL").String()),
|
|
ColorHex: data.Get("primaryColorHex").String(),
|
|
}
|
|
}
|
|
|
|
func ParseVideo(data gjson.Result, streamer structs.Streamer) structs.Video {
|
|
return structs.Video{
|
|
Type: "vod",
|
|
Preview: extractor.ProxyUrl(data.Get("previewThumbnailURL").String()),
|
|
Game: ParseMinifiedCategory(data.Get("game")),
|
|
Duration: int(data.Get("lengthSeconds").Int()),
|
|
Title: data.Get("title").String(),
|
|
PublishedAt: data.Get("createdAt").Time(),
|
|
Views: int(data.Get("viewCount").Int()),
|
|
Streamer: streamer,
|
|
Id: data.Get("id").String(),
|
|
}
|
|
}
|
|
|
|
func ParseClip(data gjson.Result, streamer structs.Streamer) structs.Video {
|
|
return structs.Video{
|
|
Type: "clip",
|
|
Preview: extractor.ProxyUrl(data.Get("thumbnailURL").String()),
|
|
Game: ParseMinifiedCategory(data.Get("clipGame")),
|
|
Duration: int(data.Get("lengthSeconds").Int()),
|
|
Title: data.Get("clipTitle").String(),
|
|
PublishedAt: data.Get("publishedAt").Time(),
|
|
Views: int(data.Get("clipViewCount").Int()),
|
|
Streamer: streamer,
|
|
Id: data.Get("slug").String(),
|
|
}
|
|
}
|
|
func ParseShelve(data gjson.Result, streamer structs.Streamer) structs.Shelve {
|
|
rawVideos := data.Get("items").Array()
|
|
parsedVideos := []structs.Video{}
|
|
|
|
isClip := data.Get("type").String() == "TOP_CLIPS"
|
|
for _, video := range rawVideos {
|
|
if isClip {
|
|
parsedVideos = append(parsedVideos, ParseClip(video, streamer))
|
|
} else {
|
|
parsedVideos = append(parsedVideos, ParseVideo(video, streamer))
|
|
}
|
|
}
|
|
|
|
return structs.Shelve{
|
|
Title: data.Get("title").String(),
|
|
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,
|
|
}
|
|
|
|
}
|
|
|
|
func ParseFollowingStreamer(data gjson.Result, login string) structs.FollowingStreamer {
|
|
return structs.FollowingStreamer{
|
|
Username: data.Get("user.displayName").String(),
|
|
Login: login,
|
|
Pfp: extractor.ProxyUrl(data.Get("user.profileImageURL").String()),
|
|
Followers: int(data.Get("user.followers.totalCount").Int()),
|
|
}
|
|
}
|