Repository: streamlink/streamlink

Issue:
kanald broken
<!--
Thanks for reporting a plugin issue!
USE THE TEMPLATE. Otherwise your plugin issue may be rejected.

First, see the contribution guidelines:
https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink

Also check the list of open and closed plugin issues:
https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22

Please see the text preview to avoid unnecessary formatting errors.
-->


## Plugin Issue

<!-- Replace the space character between the square brackets with an x in order to check the boxes -->
- [x] This is a plugin issue and I have read the contribution guidelines.
- [x] I am using the latest development version from the master branch.


### Description

<!-- Explain the plugin issue as thoroughly as you can. -->


### Reproduction steps / Explicit stream URLs to test

<!-- How can we reproduce this? Please note the exact steps below using the list format supplied. If you need more steps please add them. -->

Example link 1 (VOD): https://www.kanald.com.tr/sadakatsiz/fragmanlar/sadakatsiz-10-bolum-fragmani
Example link 2 (Live): https://www.kanald.com.tr/canli-yayin


### Log output

<!--
TEXT LOG OUTPUT IS REQUIRED for a plugin issue!
Use the `--loglevel debug` parameter and avoid using parameters which suppress log output.
https://streamlink.github.io/cli.html#cmdoption-l

Make sure to **remove usernames and passwords**
You can copy the output to https://gist.github.com/ or paste it below.

Don't post screenshots of the log output and instead copy the text from your terminal application.
-->

```
streamlink 'https://www.kanald.com.tr/sadakatsiz/fragmanlar/sadakatsiz-10-bolum-fragmani'
[cli][info] Found matching plugin dogan for URL https://www.kanald.com.tr/sadakatsiz/fragmanlar/sadakatsiz-10-bolum-fragmani
[plugin.dogan][error] Could not find the contentId for this stream
error: No playable streams found on this URL: https://www.kanald.com.tr/sadakatsiz/fragmanlar/sadakatsiz-10-bolum-fragmani
```


### Additional comments, etc.
Thanks in advance


[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)



Discussion:
I have work in progress on this.  I'll just need to validate the changes I've made across all the supported sites for the plugin.

> 
> 
> I have work in progress on this. I'll just need to validate the changes I've made across all the supported sites for the plugin.

Thank you, I can help with test if you want.

Resolving patch:
diff --git a/src/streamlink/plugins/dogan.py b/src/streamlink/plugins/dogan.py
--- 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 @@ def _get_content_id(self):
             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
--- a/tests/plugins/test_dogan.py
+++ b/tests/plugins/test_dogan.py
@@ -6,17 +6,39 @@
 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: