From 32138bbd43d48b0487777184a3877ee10c496684 Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Sat, 16 Jan 2021 14:26:13 +1100 Subject: [PATCH] downloader: Update to work with twitch's new access token API Twitch removed their old access token endpoint and now use a GraphQL endpoint. The old endpoint would just always return 404, which we sadly interpreted as "stream not up". Thankfully streamlink has already done the reverse engineering work so I was able to update it to work again fairly easily, it's just a bit more convoluted. --- downloader/downloader/twitch.py | 54 +++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/downloader/downloader/twitch.py b/downloader/downloader/twitch.py index 71cc3c8..e9032ef 100644 --- a/downloader/downloader/twitch.py +++ b/downloader/downloader/twitch.py @@ -10,23 +10,45 @@ from common.requests import InstrumentedSession logger = logging.getLogger(__name__) +def get_access_token(channel, session): + request = { + "operationName": "PlaybackAccessToken", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712" + } + }, + "variables": { + "isLive": True, + "login": channel, + "isVod": False, + "vodID": "", + "playerType": "site" + } + } + resp = session.post( + "https://gql.twitch.tv/gql", + json=request, + headers={'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko'}, + metric_name='get_access_token', + ) + resp.raise_for_status() + data = resp.json()["data"]["streamPlaybackAccessToken"] + return data['signature'], data['value'] + + def get_master_playlist(channel, session=None): """Get the master playlist for given channel from twitch""" if session is None: session = InstrumentedSession() + sig, token = get_access_token(channel, session) resp = session.get( - "https://api.twitch.tv/api/channels/{}/access_token.json".format(channel), - params={'oauth_token': 'undefined', 'need_https': 'true', 'platform': '_', 'player_type': 'site', 'player_backend': 'mediaplayer'}, + "https://usher.ttvnw.net/api/channel/hls/{}.m3u8".format(channel), headers={ - 'Accept': 'application/vnd.twitchtv.v5+json', - 'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko', + "referer": "https://player.twitch.tv", + "origin": "https://player.twitch.tv", }, - metric_name='get_access_token', - ) - resp.raise_for_status() # getting access token - token = resp.json() - resp = session.get( - "https://usher.ttvnw.net/api/channel/hls/{}.m3u8".format(channel), params={ # Taken from streamlink. Unsure what's needed and what changing things can do. "player": "twitchweb", @@ -35,15 +57,9 @@ def get_master_playlist(channel, session=None): "allow_source": "true", "allow_audio_only": "true", "allow_spectre": "false", - "fast_bread": "True", - "sig": token["sig"], - "token": token["token"], - # Also observed in the wild but not used in streamlink: - # "playlist_include_framerate": "true" - # "reassignments_supported": "true" - # It's reported that setting this may affect whether you get ads, but this is - # in flux. Better to just blend in with the crowd for now. - # "platform": "_" + "fast_bread": "true", + "sig": sig, + "token": token, }, metric_name='get_master_playlist', )