Repository: streamlink/streamlink
Pull request #3424 (2020-12-19 22:25:20)

plugins.dogan: fix/update
- update/add schemas for new/old API responses
- update for dreamturk.com.tr and dreamtv.com.tr API path
- remove dreamtv.com.tr/canli-yayin (no live and redirect loop on old URL)
- add VOD support for cnnturk.com
- add /dream-turk-ozel/.* and /programlar/.* VOD paths for dreamturk.com.tr
- add /dream-turk-ozel/.* VOD path for dreamtv.com.tr
- add /diziler/.* and /embed/.* VOD paths for teve2.com.tr
- update URLs in plugin test
- update plugin matrix

~~No accounts needed, but streams are geo-restricted to Turkey.~~

closes  #3416


mkbloke: Interesting.  I don't know what led me to believe that the streams were restricted before, but testing from a non-EU IP also works fine for me on the test URLs I've been using.  I've updated the plugin matrix accordingly.
bastimeyer: > I've updated the plugin matrix accordingly

You didn't push any new changes
mkbloke: No, I've not pushed anything yet.
mkbloke: It appears the website can show a message which translates to "you cannot watch this content in your region" for some content, but that content is still accessible via the underlying CDN.

mkbloke: I'm not sure adding all the distinct site paths in `url_re` is the best way to go.  It might be better to just use `site.tld/.*` for each and be done with it, but that's how it is for now...

Patch:
diff --git a/docs/plugin_matrix.rst b/docs/plugin_matrix.rst
index f5da6bf4..22c68221 100644
--- a/docs/plugin_matrix.rst
+++ b/docs/plugin_matrix.rst
@@ -59,11 +59,11 @@ delfi                   - delfi.lt           --    Yes
                         - delfi.lv
 deutschewelle           dw.com               Yes   Yes
 dlive                   dlive.tv             Yes   Yes
-dogan                   - teve2.com.tr       Yes   Yes   VOD is supported for teve2 and kanald
-                        - kanald.com.tr
-                        - dreamtv.com.tr
-                        - cnnturk.com
+dogan                   - cnnturk.com        Yes   Yes
                         - dreamturk.com.tr
+                        - dreamtv.com.tr
+                        - kanald.com.tr
+                        - teve2.com.tr
 dogus                   - ntvspor.net        Yes   No
                         - kralmuzik.com.tr
                         - ntv.com.tr
diff --git a/src/streamlink/plugins/dogan.py b/src/streamlink/plugins/dogan.py
index 4b635919..b6cf5dd7 100644
--- a/src/streamlink/plugins/dogan.py
+++ b/src/streamlink/plugins/dogan.py
@@ -14,29 +14,51 @@ class Dogan(Plugin):
     Support for the live streams from Doğan Media Group channels
     """
     url_re = re.compile(r"""
-        https?://(?:www.)?
-        (?:teve2.com.tr/(?:canli-yayin|filmler/.*|programlar/.*)|
-           kanald.com.tr/.*|
-           cnnturk.com/canli-yayin|
-           dreamtv.com.tr/canli-yayin|
-           dreamturk.com.tr/canli)
+        https?://(?:www\.)?
+        (?:cnnturk\.com/(?:action/embedvideo/.*|canli-yayin|tv-cnn-turk|video/.*)|
+           dreamturk\.com\.tr/(?:canli|canli-yayin-izle|dream-turk-ozel/.*|programlar/.*)|
+           dreamtv\.com\.tr/dream-ozel/.*|
+           kanald\.com\.tr/.*|
+           teve2\.com\.tr/(?:canli-yayin|diziler/.*|embed/.*|filmler/.*|programlar/.*))
     """, re.VERBOSE)
-    playerctrl_re = re.compile(r'''<div[^>]*?ng-controller=(?P<quote>["'])(?:Live)?PlayerCtrl(?P=quote).*?>''', re.DOTALL)
-    data_id_re = re.compile(r'''data-id=(?P<quote>["'])(?P<id>\w+)(?P=quote)''')
-    content_id_re = re.compile(r'"content(?:I|i)d", "(\w+)"')
+    playerctrl_re = re.compile(r'''<div\s+id="video-element".*?>''', re.DOTALL)
+    data_id_re = re.compile(r'''data-id=(?P<quote>["'])/?(?P<id>\w+)(?P=quote)''')
+    content_id_re = re.compile(r'"content[Ii]d",\s*"(\w+)"')
     item_id_re = re.compile(r"_itemId\s+=\s+'(\w+)';")
-    content_api = "/actions/content/media/{id}"
+    content_api = "/actions/media?id={id}"
+    dream_api = "/actions/content/media/{id}"
     new_content_api = "/action/media/{id}"
-    content_api_schema = validate.Schema({
-        "Id": validate.text,
-        "Media": {
-            "Link": {
-                "DefaultServiceUrl": validate.url(),
-                validate.optional("ServiceUrl"): validate.any(validate.url(), ""),
-                "SecurePath": validate.text,
-            }
-        }
-    })
+    content_api_schema = validate.Schema(
+        {
+            "data": {
+                "id": str,
+                "media": {
+                    "link": {
+                        validate.optional("defaultServiceUrl"): validate.any(validate.url(), ""),
+                        validate.optional("serviceUrl"): validate.any(validate.url(), ""),
+                        "securePath": str,
+                    },
+                },
+            },
+        },
+        validate.get("data"),
+        validate.get("media"),
+        validate.get("link"),
+    )
+    new_content_api_schema = validate.Schema(
+        {
+            "Media": {
+                "Link": {
+                    "ContentId": str,
+                    validate.optional("DefaultServiceUrl"): validate.any(validate.url(), ""),
+                    validate.optional("ServiceUrl"): validate.any(validate.url(), ""),
+                    "SecurePath": str,
+                },
+            },
+        },
+        validate.get("Media"),
+        validate.get("Link"),
+    )
 
     @classmethod
     def can_handle_url(cls, url):
@@ -66,19 +88,34 @@ class Dogan(Plugin):
             log.debug("Found contentId by itemId regex")
             return item_id_m.group(1)
 
-    def _get_hls_url(self, content_id):
-        # make the api url relative to the current domain
-        if "cnnturk" in self.url or "teve2.com.tr" in self.url:
-            log.debug("Using new content API url")
-            api_url = urljoin(self.url, self.new_content_api.format(id=content_id))
+    def _get_new_content_hls_url(self, content_id, api_url):
+        log.debug("Using new content API url")
+        d = self.session.http.get(urljoin(self.url, api_url.format(id=content_id)))
+        d = self.session.http.json(d, schema=self.new_content_api_schema)
+
+        if d["DefaultServiceUrl"] == "https://www.kanald.com.tr":
+            self.url = d["DefaultServiceUrl"]
+            return self._get_content_hls_url(content_id)
         else:
-            api_url = urljoin(self.url, self.content_api.format(id=content_id))
+            if d["SecurePath"].startswith("http"):
+                return d["SecurePath"]
+            else:
+                return urljoin((d["ServiceUrl"] or d["DefaultServiceUrl"]), d["SecurePath"])
+
+    def _get_content_hls_url(self, content_id):
+        d = self.session.http.get(urljoin(self.url, self.content_api.format(id=content_id)))
+        d = self.session.http.json(d, schema=self.content_api_schema)
 
-        apires = self.session.http.get(api_url)
+        return urljoin((d["serviceUrl"] or d["defaultServiceUrl"]), d["securePath"])
 
-        stream_data = self.session.http.json(apires, schema=self.content_api_schema)
-        d = stream_data["Media"]["Link"]
-        return urljoin((d["ServiceUrl"] or d["DefaultServiceUrl"]), d["SecurePath"])
+    def _get_hls_url(self, content_id):
+        # make the api url relative to the current domain
+        if "cnnturk.com" in self.url or "teve2.com.tr" in self.url:
+            return self._get_new_content_hls_url(content_id, self.new_content_api)
+        elif "dreamturk.com.tr" in self.url or "dreamtv.com.tr" in self.url:
+            return self._get_new_content_hls_url(content_id, self.dream_api)
+        else:
+            return self._get_content_hls_url(content_id)
 
     def _get_streams(self):
         content_id = self._get_content_id()


Test patch:
diff --git a/tests/plugins/test_dogan.py b/tests/plugins/test_dogan.py
index 657da72e..6fc31783 100644
--- a/tests/plugins/test_dogan.py
+++ b/tests/plugins/test_dogan.py
@@ -6,17 +6,39 @@ from streamlink.plugins.dogan import Dogan
 class TestPluginDogan(unittest.TestCase):
     def test_can_handle_url(self):
         should_match = [
+            'https://www.cnnturk.com/action/embedvideo/',
+            'https://www.cnnturk.com/action/embedvideo/5fa56d065cf3b018a8dd0bbc',
             'https://www.cnnturk.com/canli-yayin',
+            'https://www.cnnturk.com/tv-cnn-turk/',
+            'https://www.cnnturk.com/tv-cnn-turk/belgeseller/bir-zamanlar/bir-zamanlar-90lar-belgeseli',
+            'https://www.cnnturk.com/video/',
+            'https://www.cnnturk.com/video/turkiye/polis-otomobiliyle-tur-atan-sahisla-ilgili-islem-baslatildi-video',
             'https://www.dreamturk.com.tr/canli',
-            'https://www.dreamtv.com.tr/canli-yayin',
+            'https://www.dreamturk.com.tr/canli-yayin-izle',
+            'https://www.dreamturk.com.tr/dream-turk-ozel/',
+            'https://www.dreamturk.com.tr/dream-turk-ozel/radyo-d/ilyas-yalcintas-radyo-dnin-konugu-oldu',
+            'https://www.dreamturk.com.tr/programlar/',
+            'https://www.dreamturk.com.tr/programlar/t-rap/bolumler/t-rap-keisan-ozel',
+            'https://www.dreamtv.com.tr/dream-ozel/',
+            'https://www.dreamtv.com.tr/dream-ozel/konserler/acik-sahne-dream-ozel',
             'https://www.kanald.com.tr/canli-yayin',
+            'https://www.kanald.com.tr/sadakatsiz/fragmanlar/sadakatsiz-10-bolum-fragmani',
             'https://www.teve2.com.tr/canli-yayin',
+            'https://www.teve2.com.tr/diziler/',
+            'https://www.teve2.com.tr/diziler/guncel/oyle-bir-gecer-zaman-ki/bolumler/oyle-bir-gecer-zaman-ki-1-bolum',
+            'https://www.teve2.com.tr/embed/',
+            'https://www.teve2.com.tr/embed/55f6d5b8402011f264ec7f64',
+            'https://www.teve2.com.tr/filmler/',
+            'https://www.teve2.com.tr/filmler/guncel/yasamak-guzel-sey',
+            'https://www.teve2.com.tr/programlar/',
+            'https://www.teve2.com.tr/programlar/guncel/kelime-oyunu/bolumler/kelime-oyunu-800-bolum-19-12-2020',
         ]
         for url in should_match:
             self.assertTrue(Dogan.can_handle_url(url))
 
     def test_can_handle_url_negative(self):
         should_not_match = [
+            'https://www.dreamtv.com.tr/canli-yayin',
             'https://example.com/index.html',
         ]
         for url in should_not_match: