From 2223d7d3a4cf661ed9ba72aa3dc6b765e2e0e42a Mon Sep 17 00:00:00 2001 From: Mike Lang Date: Wed, 25 Oct 2023 23:40:47 +1100 Subject: [PATCH] downloader: Add optional ability to authenticate when getting master playlist Authenticating to a particular twitch account can give benefits, most notably not being served ads. --- downloader/downloader/main.py | 14 ++++++++++---- downloader/downloader/twitch.py | 11 +++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/downloader/downloader/main.py b/downloader/downloader/main.py index c987392..f1fa38e 100644 --- a/downloader/downloader/main.py +++ b/downloader/downloader/main.py @@ -123,7 +123,7 @@ class StreamsManager(object): FETCH_TIMEOUTS = 5, 30 MAX_WORKER_AGE = 20*60*60 # 20 hours, twitch's media playlist links expire after 24 hours - def __init__(self, channel, base_dir, qualities, important=False): + def __init__(self, channel, base_dir, qualities, important=False, auth_token=None): self.channel = channel self.logger = logging.getLogger("StreamsManager({})".format(channel)) self.base_dir = base_dir @@ -133,6 +133,7 @@ class StreamsManager(object): self.refresh_needed = gevent.event.Event() # set to tell main loop to refresh now self.stopping = gevent.event.Event() # set to tell main loop to stop self.important = important + self.auth_token = auth_token self.master_playlist_log_level = logging.INFO if important else logging.DEBUG if self.important: self.FETCH_MIN_INTERVAL = self.IMPORTANT_FETCH_MIN_INTERVAL @@ -214,7 +215,7 @@ class StreamsManager(object): self.logger.log(self.master_playlist_log_level, "Fetching master playlist") fetch_time = monotonic() with soft_hard_timeout(self.logger, "fetching master playlist", self.FETCH_TIMEOUTS, self.trigger_refresh): - master_playlist = twitch.get_master_playlist(self.channel) + master_playlist = twitch.get_master_playlist(self.channel, auth_token=self.auth_token) new_urls = twitch.get_media_playlist_uris(master_playlist, list(self.stream_workers.keys())) self.update_urls(fetch_time, new_urls) for quality, workers in self.stream_workers.items(): @@ -600,11 +601,16 @@ class SegmentGetter(object): "Twitch channels to watch. Add a '!' suffix to indicate they're expected to be always up. " "This affects retry interval, error reporting and monitoring." ) -def main(channels, base_dir=".", qualities="source", metrics_port=8001, backdoor_port=0): +def main(channels, base_dir=".", qualities="source", metrics_port=8001, backdoor_port=0, auth_file=None): qualities = qualities.split(",") if qualities else [] + auth_token = None + if auth_file is not None: + with open(auth_file) as f: + auth_token = f.read().strip() + managers = [ - StreamsManager(channel.rstrip('!'), base_dir, qualities, important=channel.endswith('!')) + StreamsManager(channel.rstrip('!'), base_dir, qualities, important=channel.endswith('!'), auth_token=auth_token) for channel in channels ] diff --git a/downloader/downloader/twitch.py b/downloader/downloader/twitch.py index eca1ee3..468148a 100644 --- a/downloader/downloader/twitch.py +++ b/downloader/downloader/twitch.py @@ -10,7 +10,7 @@ from . import hls_playlist logger = logging.getLogger(__name__) -def get_access_token(channel, session): +def get_access_token(channel, session, auth_token): request = { "operationName": "PlaybackAccessToken", "extensions": { @@ -27,10 +27,13 @@ def get_access_token(channel, session): "playerType": "site" } } + headers = {'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko'} + if auth_token is not None: + headers["Authorization"] = "OAuth {}".format(auth_token) resp = session.post( "https://gql.twitch.tv/gql", json=request, - headers={'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko'}, + headers=headers, metric_name='get_access_token', ) resp.raise_for_status() @@ -38,11 +41,11 @@ def get_access_token(channel, session): return data['signature'], data['value'] -def get_master_playlist(channel, session=None): +def get_master_playlist(channel, session=None, auth_token=None): """Get the master playlist for given channel from twitch""" if session is None: session = InstrumentedSession() - sig, token = get_access_token(channel, session) + sig, token = get_access_token(channel, session, auth_token) resp = session.get( "https://usher.ttvnw.net/api/channel/hls/{}.m3u8".format(channel), headers={