From 6b1895b95898db330f9a4b69c2767ffc468e4150 Mon Sep 17 00:00:00 2001 From: dragongoose Date: Thu, 1 Jun 2023 15:49:50 -0400 Subject: [PATCH] Add get stream functionality and use Switch client-id to bypass needing a valid integrity token (new twitch update) --- extractor/twitch/parser.go | 20 +++++++++ extractor/twitch/twitchExtractor.go | 63 ++++++++++++++++++++++++++++- routes/proxy/proxy.go | 11 +++++ 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/extractor/twitch/parser.go b/extractor/twitch/parser.go index 3c24929..a10a22d 100644 --- a/extractor/twitch/parser.go +++ b/extractor/twitch/parser.go @@ -5,6 +5,7 @@ import ( "errors" "safetwitch-backend/extractor" "safetwitch-backend/extractor/structs" + "strings" "time" "github.com/tidwall/gjson" @@ -135,3 +136,22 @@ func ParseBadges(data gjson.Result) ([]structs.Badge, error) { return formattedBadges, nil } + +func ProxyPlaylistFile(playlist string) 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 + newURL := extractor.ProxyUrl(entry) + entries[i] = newURL + } + } + + // Join the modified entries back into a single string separated by a newline + modifiedPlaylist := "#EXTM3U\n" + strings.Join(entries, "\n") + + return modifiedPlaylist + +} diff --git a/extractor/twitch/twitchExtractor.go b/extractor/twitch/twitchExtractor.go index 8570f2f..8870d76 100644 --- a/extractor/twitch/twitchExtractor.go +++ b/extractor/twitch/twitchExtractor.go @@ -8,6 +8,7 @@ import ( "net/http" "safetwitch-backend/extractor" "safetwitch-backend/extractor/structs" + "strings" "github.com/tidwall/gjson" ) @@ -21,7 +22,7 @@ func call(url, method string, body io.Reader) (*http.Response, error) { return req.Response, err } - req.Header.Add("Client-Id", "kimne78kx3ncx6brgo4mv6wki5h1ko") + req.Header.Add("Client-Id", "ue6666qo983tsx6so1t0vnawi233wa") resp, err := http.DefaultClient.Do(req) return resp, err } @@ -354,3 +355,63 @@ func GetStreamerBadges(streamerName string) ([]structs.Badge, error) { return formattedBadges, nil } + +func GetStream(streamerName string) (string, error) { + // STAGE 1 + // Get playback token from twitch + // Same request browser makes + payload1 := []TwitchPayload{ + { + "extensions": map[string]interface{}{ + "persistedQuery": map[string]interface{}{ + "version": 1, + "sha256Hash": "0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712", + }, + }, + "operationName": "PlaybackAccessToken", + "variables": map[string]interface{}{ + "isLive": true, + "login": streamerName, + "isVod": false, + "vodID": "", + "playerType": "site", + }, + }, + } + + _, body, err := parseResponse(payload1) + if err != nil { + return "", err + } + // These will be needed to get playlist file from twitch + token := gjson.Get(string(body), "0.data.streamPlaybackAccessToken.value").String() + signature := gjson.Get(string(body), "0.data.streamPlaybackAccessToken.signature").String() + if err != nil { + return "", err + } + + playlistUrl := "https://usher.ttvnw.net/api/channel/hls/" + strings.ToLower(streamerName) + ".m3u8" + params := "?sig=" + signature + "&token=" + token + + req, err := http.NewRequest("GET", playlistUrl+params, nil) + req.Header.Add("Client-Id", "ue6666qo983tsx6so1t0vnawi233wa") + if err != nil { + return "", err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + + playlistFile, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + // holy zooks, what the scallop??? we got the playlist, houston!!! + // time to proxy all the urls!!! + proxiedPlaylist := ProxyPlaylistFile(string(playlistFile)) + return proxiedPlaylist, nil + +} diff --git a/routes/proxy/proxy.go b/routes/proxy/proxy.go index cc32dac..225dbc5 100644 --- a/routes/proxy/proxy.go +++ b/routes/proxy/proxy.go @@ -4,6 +4,7 @@ import ( b64 "encoding/base64" "io" "net/http" + "safetwitch-backend/extractor/twitch" "github.com/gin-gonic/gin" ) @@ -31,4 +32,14 @@ func Routes(route *gin.Engine) { context.Data(200, contentType, body) context.JSON(imageResp.StatusCode, imageResp.Body) }) + + auth.GET("/stream/:username/hls.m3u8", func(context *gin.Context) { + streamer := context.Param("username") + playlistFile, err := twitch.GetStream(streamer) + if err != nil { + context.Error(err) + } + + context.Data(200, "application/text", []byte(playlistFile)) + }) }