From ef41d0738698bd7041d28e3e15218e7782e10122 Mon Sep 17 00:00:00 2001 From: p1131 Date: Fri, 5 Jun 2020 14:39:54 +0300 Subject: [PATCH 1/3] Fix Twitch livestreams Error 410: Gone (closes #25528) --- youtube_dl/extractor/twitch.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/youtube_dl/extractor/twitch.py b/youtube_dl/extractor/twitch.py index 78ee0115c..801b6406d 100644 --- a/youtube_dl/extractor/twitch.py +++ b/youtube_dl/extractor/twitch.py @@ -36,6 +36,7 @@ class TwitchBaseIE(InfoExtractor): _USHER_BASE = 'https://usher.ttvnw.net' _LOGIN_FORM_URL = 'https://www.twitch.tv/login' _LOGIN_POST_URL = 'https://passport.twitch.tv/login' + _ACCEPT = 'application/vnd.twitchtv.v5+json' _CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko' _NETRC_MACHINE = 'twitch' @@ -51,6 +52,7 @@ class TwitchBaseIE(InfoExtractor): def _call_api(self, path, item_id, *args, **kwargs): headers = kwargs.get('headers', {}).copy() headers['Client-ID'] = self._CLIENT_ID + headers['Accept'] = self._ACCEPT kwargs['headers'] = headers response = self._download_json( '%s/%s' % (self._API_BASE, path), item_id, @@ -572,20 +574,29 @@ class TwitchStreamIE(TwitchBaseIE): else super(TwitchStreamIE, cls).suitable(url)) def _real_extract(self, url): - channel_id = self._match_id(url) + channel_name = self._match_id(url).lower() + try: + # the kraken api (v5) accepts only channel IDS. + # kraken/users?login=CHANNELNAME returns a channel's ID from its name + channel_id = self._call_api( + 'kraken/users?login=%s' % channel_name, + channel_name, 'Getting channel ID').get('users')[0].get('_id') + assert channel_id is not None - stream = self._call_api( - 'kraken/streams/%s?stream_type=all' % channel_id.lower(), - channel_id, 'Downloading stream JSON').get('stream') + stream = self._call_api( + 'kraken/streams/%s?stream_type=all' % channel_id.lower(), + channel_name, 'Downloading stream JSON').get('stream') + assert stream is not None - if not stream: - raise ExtractorError('%s is offline' % channel_id, expected=True) + # IndexError is there because the first JSON contains a list + except (IndexError, AssertionError) as e: + raise ExtractorError('%s is offline' % channel_name, expected=True) # Channel name may be typed if different case than the original channel name # (e.g. http://www.twitch.tv/TWITCHPLAYSPOKEMON) that will lead to constructing # an invalid m3u8 URL. Working around by use of original channel name from stream # JSON and fallback to lowercase if it's not available. - channel_id = stream.get('channel', {}).get('name') or channel_id.lower() + channel_id = channel_name # XXX access_token = self._call_api( 'api/channels/%s/access_token' % channel_id, channel_id, From ca9691bc4607c37d6e46a4c87656080bc03c21a7 Mon Sep 17 00:00:00 2001 From: p1131 Date: Fri, 5 Jun 2020 14:59:02 +0300 Subject: [PATCH 2/3] flake8 fixes --- youtube_dl/extractor/twitch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/youtube_dl/extractor/twitch.py b/youtube_dl/extractor/twitch.py index 801b6406d..a2a77b84e 100644 --- a/youtube_dl/extractor/twitch.py +++ b/youtube_dl/extractor/twitch.py @@ -589,7 +589,7 @@ class TwitchStreamIE(TwitchBaseIE): assert stream is not None # IndexError is there because the first JSON contains a list - except (IndexError, AssertionError) as e: + except (IndexError, AssertionError): raise ExtractorError('%s is offline' % channel_name, expected=True) # Channel name may be typed if different case than the original channel name From cc37829e6850706831d30027417287c367de4d61 Mon Sep 17 00:00:00 2001 From: p1131 Date: Fri, 5 Jun 2020 17:12:31 +0300 Subject: [PATCH 3/3] minor improvements --- youtube_dl/extractor/twitch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/youtube_dl/extractor/twitch.py b/youtube_dl/extractor/twitch.py index a2a77b84e..6cecb7613 100644 --- a/youtube_dl/extractor/twitch.py +++ b/youtube_dl/extractor/twitch.py @@ -576,15 +576,15 @@ class TwitchStreamIE(TwitchBaseIE): def _real_extract(self, url): channel_name = self._match_id(url).lower() try: - # the kraken api (v5) accepts only channel IDS. + # the kraken api (v5) accepts only channel IDs. # kraken/users?login=CHANNELNAME returns a channel's ID from its name channel_id = self._call_api( 'kraken/users?login=%s' % channel_name, - channel_name, 'Getting channel ID').get('users')[0].get('_id') - assert channel_id is not None + channel_name, 'Downloading channel ID').get('users')[0].get('_id') + assert channel_id is not None and channel_id.isdigit() stream = self._call_api( - 'kraken/streams/%s?stream_type=all' % channel_id.lower(), + 'kraken/streams/%s?stream_type=all' % channel_id, channel_name, 'Downloading stream JSON').get('stream') assert stream is not None