Repository: streamlink/streamlink
Pull request #2080 (2018-09-27 14:40:39)

plugins.teleclubzoom: New plugin for teleclubzoom.ch
TV Channel from Switzerland.

closes https://github.com/streamlink/streamlink/issues/1993

@097115 



Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index db1e4227..79582bae 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -188,6 +188,7 @@ svtplay                 - svtplay.se         Yes   Yes   Streams may be geo-rest
 swisstxt                - srf.ch             Yes   No    Streams are geo-restricted to Switzerland.
                         - rsi.ch
 teamliquid              teamliquid.net       Yes   Yes
+teleclubzoom            teleclubzoom.ch      Yes   No    Streams are geo-restricted to Switzerland.
 telefe                  telefe.com           No    Yes   Streams are geo-restricted to Argentina.
 tf1                     - tf1.fr             Yes   No    Streams may be geo-restricted to France.
                         - lci.fr
diff --git a/src/streamlink/plugins/teleclubzoom.py b/src/streamlink/plugins/teleclubzoom.py
new file mode 100644
index 00000000..fe080a7b
--- /dev/null
+++ b/src/streamlink/plugins/teleclubzoom.py
@@ -0,0 +1,72 @@
+import logging
+import re
+
+from streamlink.compat import urlparse
+from streamlink.exceptions import PluginError
+from streamlink.plugin import Plugin
+from streamlink.plugin.api import useragents, validate
+from streamlink.plugin.api.utils import itertags
+from streamlink.stream import HLSStream
+from streamlink.utils import update_scheme
+
+log = logging.getLogger(__name__)
+
+
+class TeleclubZoom(Plugin):
+
+    _url_re = re.compile(r'https?://(?:www\.)?teleclubzoom\.ch')
+
+    API_URL = 'https://{netloc}/webservice/http/rest/client/live/play/{id}'
+    PLAYLIST_URL = 'https://{netloc}/{app}/ngrp:{name}_all/playlist.m3u8'
+
+    _api_schema = validate.Schema(
+        {
+            'playStreamName': validate.text,
+            'cdnHost': validate.text,
+            'streamProperties': {
+                validate.optional('server'): validate.text,
+                validate.optional('name'): validate.text,
+                'application': validate.text,
+            }
+        }
+    )
+
+    @classmethod
+    def can_handle_url(cls, url):
+        return cls._url_re.match(url) is not None
+
+    def _get_streams(self):
+        self.session.http.headers.update({'User-Agent': useragents.FIREFOX})
+
+        iframe_url = None
+        page = self.session.http.get(self.url)
+        for a in itertags(page.text, 'a'):
+            if a.attributes.get('class') == 'play-live':
+                iframe_url = update_scheme(self.url, a.attributes['data-url'])
+                break
+
+        if not iframe_url:
+            raise PluginError('Could not find iframe.')
+
+        parsed = urlparse(iframe_url)
+        path_list = parsed.path.split('/')
+        if len(path_list) != 6:
+            # only support a known iframe url style,
+            # the video id might be on a different spot if the url changes
+            raise PluginError('unsupported iframe URL: {0}'.format(iframe_url))
+
+        res = self.session.http.get(
+            self.API_URL.format(netloc=parsed.netloc, id=path_list[4]))
+
+        data = self.session.http.json(res, schema=self._api_schema)
+        log.trace('{0!r}'.format(data))
+
+        url = self.PLAYLIST_URL.format(
+            app=data['streamProperties']['application'],
+            name=data['playStreamName'],
+            netloc=data['cdnHost'],
+        )
+        return HLSStream.parse_variant_playlist(self.session, url)
+
+
+__plugin__ = TeleclubZoom


Test patch:
diff --git a/tests/plugins/test_teleclubzoom.py b/tests/plugins/test_teleclubzoom.py
new file mode 100644
index 00000000..d4d5ebb3
--- /dev/null
+++ b/tests/plugins/test_teleclubzoom.py
@@ -0,0 +1,20 @@
+import unittest
+
+from streamlink.plugins.teleclubzoom import TeleclubZoom
+
+
+class TestPluginTeleclubZoom(unittest.TestCase):
+    def test_can_handle_url(self):
+        should_match = [
+            'https://www.teleclubzoom.ch',
+            'https://www.teleclubzoom.ch/',
+        ]
+        for url in should_match:
+            self.assertTrue(TeleclubZoom.can_handle_url(url))
+
+    def test_can_handle_url_negative(self):
+        should_not_match = [
+            'https://example.com/index.html',
+        ]
+        for url in should_not_match:
+            self.assertFalse(TeleclubZoom.can_handle_url(url))