Repository: streamlink/streamlink
Pull request #3767 (2021-06-02 12:06:12)

plugin.api.http_session: refactor HTTPSession
- call super() instead of referencing parent class by name
- fix incorrect signature of the constructor
- remove startswith check of the default "User-Agent" header
  it could be replaced with requests.utils.default_user_agent(), but it's totally unnecessary
- reformat some lines for better readability
- fix tests and add test for custom default attributes/values


Patch:
diff --git a/src/streamlink/plugin/api/http_session.py b/src/streamlink/plugin/api/http_session.py
index d56739be..5d3887e7 100644
--- a/src/streamlink/plugin/api/http_session.py
+++ b/src/streamlink/plugin/api/http_session.py
@@ -29,12 +29,10 @@ def _parse_keyvalue_list(val):
 
 
 class HTTPSession(Session):
-    def __init__(self, *args, **kwargs):
-        Session.__init__(self, *args, **kwargs)
-
-        if self.headers['User-Agent'].startswith('python-requests'):
-            self.headers['User-Agent'] = useragents.FIREFOX
+    def __init__(self):
+        super().__init__()
 
+        self.headers['User-Agent'] = useragents.FIREFOX
         self.timeout = 20.0
 
         self.mount('file://', FileAdapter())
@@ -123,12 +121,16 @@ class HTTPSession(Session):
 
         while True:
             try:
-                res = Session.request(self, method, url,
-                                      headers=headers,
-                                      params=params,
-                                      timeout=timeout,
-                                      proxies=proxies,
-                                      *args, **kwargs)
+                res = super().request(
+                    method,
+                    url,
+                    headers=headers,
+                    params=params,
+                    timeout=timeout,
+                    proxies=proxies,
+                    *args,
+                    **kwargs
+                )
                 if raise_for_status and res.status_code not in acceptable_status:
                     res.raise_for_status()
                 break
@@ -136,8 +138,7 @@ class HTTPSession(Session):
                 raise
             except Exception as rerr:
                 if retries >= total_retries:
-                    err = exception("Unable to open URL: {url} ({err})".format(url=url,
-                                                                               err=rerr))
+                    err = exception(f"Unable to open URL: {url} ({rerr})")
                     err.err = rerr
                     raise err
                 retries += 1
diff --git a/src/streamlink/plugins/twitch.py b/src/streamlink/plugins/twitch.py
index 4ef7f1d1..47a8ebe5 100644
--- a/src/streamlink/plugins/twitch.py
+++ b/src/streamlink/plugins/twitch.py
@@ -513,7 +513,7 @@ class Twitch(Plugin):
             (?:
                 /video/(?P<video_id>\d+)
                 |
-                /clip/(?P<clip_name>[\w]+)
+                /clip/(?P<clip_name>[\w-]+)
             )?
         )
     """, re.VERBOSE)
diff --git a/src/streamlink/plugins/zattoo.py b/src/streamlink/plugins/zattoo.py
index b0fbf4ac..bf527f7b 100644
--- a/src/streamlink/plugins/zattoo.py
+++ b/src/streamlink/plugins/zattoo.py
@@ -17,7 +17,6 @@ log = logging.getLogger(__name__)
 class Zattoo(Plugin):
     API_CHANNELS = '{0}/zapi/v2/cached/channels/{1}?details=False'
     API_HELLO = '{0}/zapi/session/hello'
-    API_HELLO_V2 = '{0}/zapi/v2/session/hello'
     API_HELLO_V3 = '{0}/zapi/v3/session/hello'
     API_LOGIN = '{0}/zapi/v2/account/login'
     API_LOGIN_V3 = '{0}/zapi/v3/account/login'
@@ -157,19 +156,16 @@ class Zattoo(Plugin):
         # a new session is required for the app_token
         self.session.http.cookies = cookiejar_from_dict({})
         if self.base_url == 'https://zattoo.com':
-            app_token_url = 'https://zattoo.com/client/token-2fb69f883fea03d06c68c6e5f21ddaea.json'
+            app_token_url = 'https://zattoo.com/token-46a1dfccbd4c3bdaf6182fea8f8aea3f.json'
         elif self.base_url == 'https://www.quantum-tv.com':
             app_token_url = 'https://www.quantum-tv.com/token-4d0d61d4ce0bf8d9982171f349d19f34.json'
         else:
             app_token_url = self.base_url
 
         res = self.session.http.get(app_token_url)
-        if self.base_url == 'https://www.quantum-tv.com':
+        if self.base_url == 'https://www.quantum-tv.com' or self.base_url == 'https://zattoo.com':
             app_token = self.session.http.json(res)["session_token"]
             hello_url = self.API_HELLO_V3.format(self.base_url)
-        elif self.base_url == 'https://zattoo.com':
-            app_token = self.session.http.json(res)['app_tid']
-            hello_url = self.API_HELLO_V2.format(self.base_url)
         else:
             match = self._app_token_re.search(res.text)
             app_token = match.group(1)
@@ -182,19 +178,12 @@ class Zattoo(Plugin):
             self._session_attributes.set(
                 'uuid', __uuid, expires=self.TIME_SESSION)
 
-        if self.base_url == 'https://zattoo.com':
-            params = {
-                'uuid': __uuid,
-                'app_tid': app_token,
-                'app_version': '1.0.0'
-            }
-        else:
-            params = {
-                'client_app_token': app_token,
-                'uuid': __uuid,
-            }
+        params = {
+            'client_app_token': app_token,
+            'uuid': __uuid,
+        }
 
-        if self.base_url == 'https://www.quantum-tv.com':
+        if self.base_url == 'https://www.quantum-tv.com' or self.base_url == 'https://zattoo.com':
             params['app_version'] = '3.2028.3'
         else:
             params['lang'] = 'en'


Test patch:
diff --git a/tests/plugins/test_twitch.py b/tests/plugins/test_twitch.py
index 50abd993..7d26639b 100644
--- a/tests/plugins/test_twitch.py
+++ b/tests/plugins/test_twitch.py
@@ -18,6 +18,7 @@ class TestPluginCanHandleUrlTwitch(PluginCanHandleUrl):
         'https://www.twitch.tv/twitch',
         'https://www.twitch.tv/videos/150942279',
         'https://clips.twitch.tv/ObservantBenevolentCarabeefPhilosoraptor',
+        'https://www.twitch.tv/weplaydota/clip/FurryIntelligentDonutAMPEnergyCherry-akPRxv7Y3w58WmFq'
         'https://www.twitch.tv/twitch/video/292713971',
         'https://www.twitch.tv/twitch/v/292713971',
     ]
diff --git a/tests/test_api_http_session.py b/tests/test_api_http_session.py
index 694e435f..3b692a1a 100644
--- a/tests/test_api_http_session.py
+++ b/tests/test_api_http_session.py
@@ -5,23 +5,29 @@ import requests
 
 from streamlink.exceptions import PluginError
 from streamlink.plugin.api.http_session import HTTPSession
+from streamlink.plugin.api.useragents import FIREFOX
 
 
 class TestPluginAPIHTTPSession(unittest.TestCase):
+    def test_session_init(self):
+        session = HTTPSession()
+        self.assertEqual(session.headers.get("User-Agent"), FIREFOX)
+        self.assertEqual(session.timeout, 20.0)
+        self.assertIn("file://", session.adapters.keys())
+
     @patch("streamlink.plugin.api.http_session.time.sleep")
-    @patch("streamlink.plugin.api.http_session.Session.request")
+    @patch("streamlink.plugin.api.http_session.Session.request", side_effect=requests.Timeout)
     def test_read_timeout(self, mock_request, mock_sleep):
-        mock_request.side_effect = requests.Timeout
         session = HTTPSession()
 
         with self.assertRaises(PluginError) as cm:
             session.get("http://localhost/", timeout=123, retries=3, retry_backoff=2, retry_max_backoff=5)
         self.assertTrue(str(cm.exception).startswith("Unable to open URL: http://localhost/"))
         self.assertEqual(mock_request.mock_calls, [
-            call(session, "GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
-            call(session, "GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
-            call(session, "GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
-            call(session, "GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
+            call("GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
+            call("GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
+            call("GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
+            call("GET", "http://localhost/", headers={}, params={}, timeout=123, proxies={}, allow_redirects=True),
         ])
         self.assertEqual(mock_sleep.mock_calls, [
             call(2),