Repository: streamlink/streamlink

Issue:
NPO plugin unable to open stream URL
<!--
Thanks for reporting an issue!
USE THE TEMPLATE. Otherwise your issue may be rejected.

This template should only be used if your issue doesn't match the other more specific ones.

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 issues:
https://github.com/streamlink/streamlink/issues?q=is%3Aissue

Please see the text preview to avoid unnecessary formatting errors.
-->


## Issue

<!-- Replace [ ] with [x] in order to check the box -->
- [ ] This is not a bug report, feature request, or plugin issue/request.
- [x] I have read the contribution guidelines.


### Description

NPO plugin unable to open stream URL.

streamlink http://www.npo.nl/live/npo-1
[cli][info] Found matching plugin npo for URL http://www.npo.nl/live/npo-1
error: Unable to open URL: http://ida.omroep.nl/app.php/LI_NL1_4188102 (500 Server Error: Internal Server Error for url: http://ida.omroep.nl/app.php/LI_NL1_4188102?adaptive=yes&token=2tap7477ib3kpqusr9t6gqs6s4)

### Expected / Actual behavior

Play stream
500 Server Error

### 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. -->

1. streamlink http://www.npo.nl/live/npo-1

### Logs

<!--
TEXT LOG OUTPUT IS OPTIONAL for generic issues!
However, depending on the issue, log output can be useful for the developers to understand the problem. It also includes information about your platform. If you don't intend to include log output, please at least provide these platform details.
Thanks!

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.
-->

N/A


### Additional comments, screenshots, etc.

N/A

[Love Streamlink? Please consider supporting our collective. Thanks!](https://opencollective.com/streamlink/donate)



Discussion:
DRM
Are there other streams that work? If not we should mark it as broken and consider removing it. If there are other streams that work then a better error message is needed for the ones that don’t :)
It seems that they completely removed the "old"API:
error: Unable to open URL: http://ida.omroep.nl/app.php/auth (410 Client Error: Gone for url: https://ida.omroep.nl/app.php/auth)

I'm afraid that no content from NPO is accessible anymore.

Resolving patch:
diff --git a/src/streamlink/plugins/npo.py b/src/streamlink/plugins/npo.py
deleted file mode 100644
--- a/src/streamlink/plugins/npo.py
+++ /dev/null
@@ -1,132 +0,0 @@
-"""Plugin for NPO: Nederlandse Publieke Omroep
-
-Supports:
-   VODs:
-    - https://www.npo.nl/nos-journaal/07-07-2017/POW_03375651
-    - https://www.zapp.nl/topdoks/gemist/VPWON_1276930
-    - https://zappelin.nl/10-voor/gemist/VPWON_1271522
-    - https://www.npostart.nl/nos-journaal/07-07-2017/POW_03375651
-   Live:
-    - https://www.npo.nl/live/npo-1
-    - https://zappelin.nl/tv-kijken
-    - https://www.npostart.nl/live/npo-1
-
-"""
-
-import re
-
-from streamlink.plugin import Plugin, PluginArguments, PluginArgument
-from streamlink.plugin.api import useragents
-from streamlink.plugin.api import validate
-from streamlink.stream import HLSStream
-from streamlink.stream import HTTPStream
-from streamlink.utils import parse_json
-
-
-class NPO(Plugin):
-    api_url = "http://ida.omroep.nl/app.php/{endpoint}"
-    url_re = re.compile(r"https?://(\w+\.)?(npostart\.nl|npo\.nl|zapp\.nl|zappelin\.nl)/")
-    media_id_re = re.compile(r'''<npo-player\smedia-id=["'](?P<media_id>[^"']+)["']''')
-    prid_re = re.compile(r'''(?:data(-alt)?-)?prid\s*[=:]\s*(?P<q>["'])(\w+)(?P=q)''')
-    react_re = re.compile(r'''data-react-props\s*=\s*(?P<q>["'])(?P<data>.*?)(?P=q)''')
-
-    auth_schema = validate.Schema({"token": validate.text}, validate.get("token"))
-    streams_schema = validate.Schema({
-        "items": [
-            [{
-                "label": validate.text,
-                "contentType": validate.text,
-                "url": validate.url(),
-                "format": validate.text
-            }]
-        ]
-    }, validate.get("items"), validate.get(0))
-    stream_info_schema = validate.Schema(validate.any(
-        validate.url(),
-        validate.all({"errorcode": 0, "url": validate.url()},
-                     validate.get("url"))
-    ))
-    arguments = PluginArguments(
-        PluginArgument(
-            "subtitles",
-            action="store_true",
-            help="""
-        Include subtitles for the deaf or hard of hearing, if available.
-        """
-        )
-    )
-
-    @classmethod
-    def can_handle_url(cls, url):
-        return cls.url_re.match(url) is not None
-
-    def __init__(self, url):
-        super(NPO, self).__init__(url)
-        self._token = None
-        self.session.http.headers.update({"User-Agent": useragents.CHROME})
-
-    def api_call(self, endpoint, schema=None, params=None):
-        url = self.api_url.format(endpoint=endpoint)
-        res = self.session.http.get(url, params=params)
-        return self.session.http.json(res, schema=schema)
-
-    @property
-    def token(self):
-        if not self._token:
-            self._token = self.api_call("auth", schema=self.auth_schema)
-        return self._token
-
-    def _get_prid(self, subtitles=False):
-        res = self.session.http.get(self.url)
-        bprid = None
-
-        # Locate the asset id for the content on the page
-        for alt, _, prid in self.prid_re.findall(res.text):
-            if alt and subtitles:
-                bprid = prid
-            elif bprid is None:
-                bprid = prid
-
-        if bprid is None:
-            m = self.react_re.search(res.text)
-            if m:
-                data = parse_json(m.group("data").replace("&quot;", '"'))
-                bprid = data.get("mid")
-
-        if bprid is None:
-            m = self.media_id_re.search(res.text)
-            if m:
-                bprid = m.group('media_id')
-
-        return bprid
-
-    def _get_streams(self):
-        asset_id = self._get_prid(self.get_option("subtitles"))
-
-        if asset_id:
-            self.logger.debug("Found asset id: {0}", asset_id)
-            streams = self.api_call(asset_id,
-                                    params=dict(adaptive="yes",
-                                                token=self.token),
-                                    schema=self.streams_schema)
-
-            for stream in streams:
-                if stream["format"] in ("adaptive", "hls", "mp4"):
-                    if stream["contentType"] == "url":
-                        stream_url = stream["url"]
-                    else:
-                        # using type=json removes the javascript function wrapper
-                        info_url = stream["url"].replace("type=jsonp", "type=json")
-
-                        # find the actual stream URL
-                        stream_url = self.session.http.json(self.session.http.get(info_url),
-                                               schema=self.stream_info_schema)
-
-                    if stream["format"] in ("adaptive", "hls"):
-                        for s in HLSStream.parse_variant_playlist(self.session, stream_url).items():
-                            yield s
-                    elif stream["format"] in ("mp3", "mp4"):
-                        yield "vod", HTTPStream(self.session, stream_url)
-
-
-__plugin__ = NPO


Test patch:
diff --git a/tests/plugins/test_npo.py b/tests/plugins/test_npo.py
deleted file mode 100644
--- a/tests/plugins/test_npo.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import unittest
-
-from streamlink.plugins.npo import NPO
-
-
-class TestPluginNPO(unittest.TestCase):
-    def test_can_handle_url(self):
-        should_match = [
-            'https://www.npo.nl/live/npo-1',
-            'https://www.npo.nl/live/npo-2',
-            'https://www.npo.nl/live/npo-zapp',
-            'https://www.zapp.nl/tv-kijken',
-            'https://zappelin.nl/schatkist/videos/VPWON_1284504',
-        ]
-        for url in should_match:
-            self.assertTrue(NPO.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(NPO.can_handle_url(url))