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.
pull/349/head
Mike Lang 1 year ago committed by Mike Lang
parent cedebff1ce
commit d636817b36

@ -123,7 +123,7 @@ class StreamsManager(object):
FETCH_TIMEOUTS = 5, 30 FETCH_TIMEOUTS = 5, 30
MAX_WORKER_AGE = 20*60*60 # 20 hours, twitch's media playlist links expire after 24 hours 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.channel = channel
self.logger = logging.getLogger("StreamsManager({})".format(channel)) self.logger = logging.getLogger("StreamsManager({})".format(channel))
self.base_dir = base_dir 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.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.stopping = gevent.event.Event() # set to tell main loop to stop
self.important = important self.important = important
self.auth_token = auth_token
self.master_playlist_log_level = logging.INFO if important else logging.DEBUG self.master_playlist_log_level = logging.INFO if important else logging.DEBUG
if self.important: if self.important:
self.FETCH_MIN_INTERVAL = self.IMPORTANT_FETCH_MIN_INTERVAL 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") self.logger.log(self.master_playlist_log_level, "Fetching master playlist")
fetch_time = monotonic() fetch_time = monotonic()
with soft_hard_timeout(self.logger, "fetching master playlist", self.FETCH_TIMEOUTS, self.trigger_refresh): 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())) new_urls = twitch.get_media_playlist_uris(master_playlist, list(self.stream_workers.keys()))
self.update_urls(fetch_time, new_urls) self.update_urls(fetch_time, new_urls)
for quality, workers in self.stream_workers.items(): 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. " "Twitch channels to watch. Add a '!' suffix to indicate they're expected to be always up. "
"This affects retry interval, error reporting and monitoring." "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 [] 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 = [ 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 for channel in channels
] ]

@ -10,7 +10,7 @@ from . import hls_playlist
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def get_access_token(channel, session): def get_access_token(channel, session, auth_token):
request = { request = {
"operationName": "PlaybackAccessToken", "operationName": "PlaybackAccessToken",
"extensions": { "extensions": {
@ -27,10 +27,13 @@ def get_access_token(channel, session):
"playerType": "site" "playerType": "site"
} }
} }
headers = {'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko'}
if auth_token is not None:
headers["Authorization"] = "OAuth {}".format(auth_token)
resp = session.post( resp = session.post(
"https://gql.twitch.tv/gql", "https://gql.twitch.tv/gql",
json=request, json=request,
headers={'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko'}, headers=headers,
metric_name='get_access_token', metric_name='get_access_token',
) )
resp.raise_for_status() resp.raise_for_status()
@ -38,11 +41,11 @@ def get_access_token(channel, session):
return data['signature'], data['value'] 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""" """Get the master playlist for given channel from twitch"""
if session is None: if session is None:
session = InstrumentedSession() session = InstrumentedSession()
sig, token = get_access_token(channel, session) sig, token = get_access_token(channel, session, auth_token)
resp = session.get( resp = session.get(
"https://usher.ttvnw.net/api/channel/hls/{}.m3u8".format(channel), "https://usher.ttvnw.net/api/channel/hls/{}.m3u8".format(channel),
headers={ headers={

Loading…
Cancel
Save