We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
plugins.mitele: 403 Client Error: Missing hdnts for url
### Checklist

- [X] This is a plugin issue and not a different kind of issue
- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)
- [X] [I have checked the list of open and recently closed plugin issues](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22)
- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)

### Streamlink version

Latest stable release

### Description

The channel "Boing" on Mitele is not working with the plugin anymore...

### Debug log

```text
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.11.3
[cli][debug] Streamlink: 5.5.1
[cli][debug] Dependencies:
[cli][debug]  certifi: 2023.5.7
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 4.9.2
[cli][debug]  pycountry: 22.3.5
[cli][debug]  pycryptodome: 3.17
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.29.0
[cli][debug]  urllib3: 1.26.15
[cli][debug]  websocket-client: 1.5.1
[cli][debug] Arguments:
[cli][debug]  url=https://www.mitele.es/directo/boing/
[cli][debug]  stream=['best']
[cli][debug]  --loglevel=debug
[cli][debug]  --locale=es_ES
[cli][debug]  --player-external-http=True
[cli][debug]  --player-external-http-port=339
[cli][debug]  --hls-audio-select=['*']
[cli][debug]  --ffmpeg-ffmpeg=C:\Program Files\Streamlink\ffmpeg\ffmpeg.exe
[cli][debug]  --ffmpeg-copyts=True
[cli][debug]  --http-proxy=http://AsTJems3WD4V4HgrSTWWkJm9:ROBgagn2021@es-mad.pvdata.host:8080/
[cli][info] Found matching plugin mitele for URL https://www.mitele.es/directo/boing/
[utils.l10n][debug] Language code: es_ES
error: Unable to open URL: https://livek.mediaset.es/orilinear31/live/linear31/playlist/playlist.isml/ctv.m3u8 (403 Client Error: Missing hdnts for url: https://livek.mediaset.es/orilinear31/live/linear31/playlist/playlist.isml/ctv.m3u8)
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/streamlink/plugins/mitele.py`
Content:
```
1 """
2 $description Spanish live TV channels from Mediaset Group, including Boing, Cuatro, Divinity, Energy, FDF and Telecinco.
3 $url mitele.es
4 $type live
5 $region Spain
6 """
7 
8 import logging
9 import re
10 
11 from streamlink.plugin import Plugin, pluginmatcher
12 from streamlink.plugin.api import validate
13 from streamlink.stream.hls import HLSStream
14 from streamlink.utils.parse import parse_qsd
15 from streamlink.utils.url import update_qsd
16 
17 
18 log = logging.getLogger(__name__)
19 
20 
21 @pluginmatcher(re.compile(
22     r"https?://(?:www\.)?mitele\.es/directo/(?P<channel>[\w-]+)",
23 ))
24 class Mitele(Plugin):
25     URL_CARONTE = "https://caronte.mediaset.es/delivery/channel/mmc/{channel}/mtweb"
26     URL_GBX = "https://mab.mediaset.es/1.0.0/get"
27 
28     TOKEN_ERRORS = {
29         4038: "User has no privileges",
30     }
31 
32     def _get_streams(self):
33         channel = self.match.group("channel")
34 
35         pdata = self.session.http.get(
36             self.URL_CARONTE.format(channel=channel),
37             acceptable_status=(200, 403, 404),
38             schema=validate.Schema(
39                 validate.parse_json(),
40                 validate.any(
41                     {"code": int},
42                     {
43                         "cerbero": validate.url(),
44                         "bbx": str,
45                         "dls": validate.all(
46                             [{
47                                 "drm": bool,
48                                 "format": str,
49                                 "stream": validate.all(
50                                     validate.transform(str.strip),
51                                     validate.url(),
52                                 ),
53                                 "lid": validate.all(
54                                     int,
55                                     validate.transform(str),
56                                 ),
57                                 validate.optional("assetKey"): str,
58                             }],
59                             validate.filter(lambda obj: obj["format"] == "hls"),
60                         ),
61                     },
62                 ),
63             ),
64         )
65         if "code" in pdata:
66             log.error(f"Error getting pdata: {pdata['code']}")
67             return
68 
69         gbx = self.session.http.get(
70             self.URL_GBX,
71             params={
72                 "oid": "mtmw",
73                 "eid": f"/api/mtmw/v2/gbx/mtweb/live/mmc/{channel}",
74             },
75             schema=validate.Schema(
76                 validate.parse_json(),
77                 {"gbx": str},
78                 validate.get("gbx"),
79             ),
80         )
81 
82         tokens = self.session.http.post(
83             pdata["cerbero"],
84             acceptable_status=(200, 403, 404),
85             json={
86                 "bbx": pdata["bbx"],
87                 "gbx": gbx,
88             },
89             headers={"origin": "https://www.mitele.es"},
90             schema=validate.Schema(
91                 validate.parse_json(),
92                 validate.any(
93                     {"code": int},
94                     validate.all(
95                         {"tokens": {str: {"cdn": str}}},
96                         validate.get("tokens"),
97                     ),
98                 ),
99             ),
100         )
101         if "code" in tokens:
102             tokenerrors = self.TOKEN_ERRORS.get(tokens["code"], "unknown error")
103             log.error(f"Could not get stream tokens: {tokens['code']} ({tokenerrors})")
104             return
105 
106         urls = set()
107         for stream in pdata["dls"]:
108             if stream["drm"]:
109                 log.warning("Stream may be protected by DRM")
110                 continue
111             cdn_token = tokens.get(stream["lid"], {}).get("cdn", "")
112             qsd = parse_qsd(cdn_token)
113             urls.add(update_qsd(stream["stream"], qsd, quote_via=lambda string, *_, **__: string))
114 
115         for url in urls:
116             yield from HLSStream.parse_variant_playlist(
117                 self.session,
118                 url,
119                 headers={"Origin": "https://www.mitele.es"},
120                 name_fmt="{pixels}_{bitrate}",
121             ).items()
122 
123 
124 __plugin__ = Mitele
125
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/streamlink/plugins/mitele.py b/src/streamlink/plugins/mitele.py
--- a/src/streamlink/plugins/mitele.py
+++ b/src/streamlink/plugins/mitele.py
@@ -109,6 +109,8 @@
                 log.warning("Stream may be protected by DRM")
                 continue
             cdn_token = tokens.get(stream["lid"], {}).get("cdn", "")
+            if not cdn_token:
+                continue
             qsd = parse_qsd(cdn_token)
             urls.add(update_qsd(stream["stream"], qsd, quote_via=lambda string, *_, **__: string))

{"golden_diff": "diff --git a/src/streamlink/plugins/mitele.py b/src/streamlink/plugins/mitele.py\n--- a/src/streamlink/plugins/mitele.py\n+++ b/src/streamlink/plugins/mitele.py\n@@ -109,6 +109,8 @@\n                 log.warning(\"Stream may be protected by DRM\")\n                 continue\n             cdn_token = tokens.get(stream[\"lid\"], {}).get(\"cdn\", \"\")\n+            if not cdn_token:\n+                continue\n             qsd = parse_qsd(cdn_token)\n             urls.add(update_qsd(stream[\"stream\"], qsd, quote_via=lambda string, *_, **__: string))\n", "issue": "plugins.mitele: 403 Client Error: Missing hdnts for url\n### Checklist\n\n- [X] This is a plugin issue and not a different kind of issue\n- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)\n- [X] [I have checked the list of open and recently closed plugin issues](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22plugin+issue%22)\n- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)\n\n### Streamlink version\n\nLatest stable release\n\n### Description\n\nThe channel \"Boing\" on Mitele is not working with the plugin anymore...\n\n### Debug log\n\n```text\n[cli][debug] OS:         Windows 10\r\n[cli][debug] Python:     3.11.3\r\n[cli][debug] Streamlink: 5.5.1\r\n[cli][debug] Dependencies:\r\n[cli][debug]  certifi: 2023.5.7\r\n[cli][debug]  isodate: 0.6.1\r\n[cli][debug]  lxml: 4.9.2\r\n[cli][debug]  pycountry: 22.3.5\r\n[cli][debug]  pycryptodome: 3.17\r\n[cli][debug]  PySocks: 1.7.1\r\n[cli][debug]  requests: 2.29.0\r\n[cli][debug]  urllib3: 1.26.15\r\n[cli][debug]  websocket-client: 1.5.1\r\n[cli][debug] Arguments:\r\n[cli][debug]  url=https://www.mitele.es/directo/boing/\r\n[cli][debug]  stream=['best']\r\n[cli][debug]  --loglevel=debug\r\n[cli][debug]  --locale=es_ES\r\n[cli][debug]  --player-external-http=True\r\n[cli][debug]  --player-external-http-port=339\r\n[cli][debug]  --hls-audio-select=['*']\r\n[cli][debug]  --ffmpeg-ffmpeg=C:\\Program Files\\Streamlink\\ffmpeg\\ffmpeg.exe\r\n[cli][debug]  --ffmpeg-copyts=True\r\n[cli][debug]  --http-proxy=http://AsTJems3WD4V4HgrSTWWkJm9:ROBgagn2021@es-mad.pvdata.host:8080/\r\n[cli][info] Found matching plugin mitele for URL https://www.mitele.es/directo/boing/\r\n[utils.l10n][debug] Language code: es_ES\r\nerror: Unable to open URL: https://livek.mediaset.es/orilinear31/live/linear31/playlist/playlist.isml/ctv.m3u8 (403 Client Error: Missing hdnts for url: https://livek.mediaset.es/orilinear31/live/linear31/playlist/playlist.isml/ctv.m3u8)\n```\n\n", "before_files": [{"content": "\"\"\"\n$description Spanish live TV channels from Mediaset Group, including Boing, Cuatro, Divinity, Energy, FDF and Telecinco.\n$url mitele.es\n$type live\n$region Spain\n\"\"\"\n\nimport logging\nimport re\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.api import validate\nfrom streamlink.stream.hls import HLSStream\nfrom streamlink.utils.parse import parse_qsd\nfrom streamlink.utils.url import update_qsd\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"https?://(?:www\\.)?mitele\\.es/directo/(?P<channel>[\\w-]+)\",\n))\nclass Mitele(Plugin):\n    URL_CARONTE = \"https://caronte.mediaset.es/delivery/channel/mmc/{channel}/mtweb\"\n    URL_GBX = \"https://mab.mediaset.es/1.0.0/get\"\n\n    TOKEN_ERRORS = {\n        4038: \"User has no privileges\",\n    }\n\n    def _get_streams(self):\n        channel = self.match.group(\"channel\")\n\n        pdata = self.session.http.get(\n            self.URL_CARONTE.format(channel=channel),\n            acceptable_status=(200, 403, 404),\n            schema=validate.Schema(\n                validate.parse_json(),\n                validate.any(\n                    {\"code\": int},\n                    {\n                        \"cerbero\": validate.url(),\n                        \"bbx\": str,\n                        \"dls\": validate.all(\n                            [{\n                                \"drm\": bool,\n                                \"format\": str,\n                                \"stream\": validate.all(\n                                    validate.transform(str.strip),\n                                    validate.url(),\n                                ),\n                                \"lid\": validate.all(\n                                    int,\n                                    validate.transform(str),\n                                ),\n                                validate.optional(\"assetKey\"): str,\n                            }],\n                            validate.filter(lambda obj: obj[\"format\"] == \"hls\"),\n                        ),\n                    },\n                ),\n            ),\n        )\n        if \"code\" in pdata:\n            log.error(f\"Error getting pdata: {pdata['code']}\")\n            return\n\n        gbx = self.session.http.get(\n            self.URL_GBX,\n            params={\n                \"oid\": \"mtmw\",\n                \"eid\": f\"/api/mtmw/v2/gbx/mtweb/live/mmc/{channel}\",\n            },\n            schema=validate.Schema(\n                validate.parse_json(),\n                {\"gbx\": str},\n                validate.get(\"gbx\"),\n            ),\n        )\n\n        tokens = self.session.http.post(\n            pdata[\"cerbero\"],\n            acceptable_status=(200, 403, 404),\n            json={\n                \"bbx\": pdata[\"bbx\"],\n                \"gbx\": gbx,\n            },\n            headers={\"origin\": \"https://www.mitele.es\"},\n            schema=validate.Schema(\n                validate.parse_json(),\n                validate.any(\n                    {\"code\": int},\n                    validate.all(\n                        {\"tokens\": {str: {\"cdn\": str}}},\n                        validate.get(\"tokens\"),\n                    ),\n                ),\n            ),\n        )\n        if \"code\" in tokens:\n            tokenerrors = self.TOKEN_ERRORS.get(tokens[\"code\"], \"unknown error\")\n            log.error(f\"Could not get stream tokens: {tokens['code']} ({tokenerrors})\")\n            return\n\n        urls = set()\n        for stream in pdata[\"dls\"]:\n            if stream[\"drm\"]:\n                log.warning(\"Stream may be protected by DRM\")\n                continue\n            cdn_token = tokens.get(stream[\"lid\"], {}).get(\"cdn\", \"\")\n            qsd = parse_qsd(cdn_token)\n            urls.add(update_qsd(stream[\"stream\"], qsd, quote_via=lambda string, *_, **__: string))\n\n        for url in urls:\n            yield from HLSStream.parse_variant_playlist(\n                self.session,\n                url,\n                headers={\"Origin\": \"https://www.mitele.es\"},\n                name_fmt=\"{pixels}_{bitrate}\",\n            ).items()\n\n\n__plugin__ = Mitele\n", "path": "src/streamlink/plugins/mitele.py"}], "after_files": [{"content": "\"\"\"\n$description Spanish live TV channels from Mediaset Group, including Boing, Cuatro, Divinity, Energy, FDF and Telecinco.\n$url mitele.es\n$type live\n$region Spain\n\"\"\"\n\nimport logging\nimport re\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.api import validate\nfrom streamlink.stream.hls import HLSStream\nfrom streamlink.utils.parse import parse_qsd\nfrom streamlink.utils.url import update_qsd\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"https?://(?:www\\.)?mitele\\.es/directo/(?P<channel>[\\w-]+)\",\n))\nclass Mitele(Plugin):\n    URL_CARONTE = \"https://caronte.mediaset.es/delivery/channel/mmc/{channel}/mtweb\"\n    URL_GBX = \"https://mab.mediaset.es/1.0.0/get\"\n\n    TOKEN_ERRORS = {\n        4038: \"User has no privileges\",\n    }\n\n    def _get_streams(self):\n        channel = self.match.group(\"channel\")\n\n        pdata = self.session.http.get(\n            self.URL_CARONTE.format(channel=channel),\n            acceptable_status=(200, 403, 404),\n            schema=validate.Schema(\n                validate.parse_json(),\n                validate.any(\n                    {\"code\": int},\n                    {\n                        \"cerbero\": validate.url(),\n                        \"bbx\": str,\n                        \"dls\": validate.all(\n                            [{\n                                \"drm\": bool,\n                                \"format\": str,\n                                \"stream\": validate.all(\n                                    validate.transform(str.strip),\n                                    validate.url(),\n                                ),\n                                \"lid\": validate.all(\n                                    int,\n                                    validate.transform(str),\n                                ),\n                                validate.optional(\"assetKey\"): str,\n                            }],\n                            validate.filter(lambda obj: obj[\"format\"] == \"hls\"),\n                        ),\n                    },\n                ),\n            ),\n        )\n        if \"code\" in pdata:\n            log.error(f\"Error getting pdata: {pdata['code']}\")\n            return\n\n        gbx = self.session.http.get(\n            self.URL_GBX,\n            params={\n                \"oid\": \"mtmw\",\n                \"eid\": f\"/api/mtmw/v2/gbx/mtweb/live/mmc/{channel}\",\n            },\n            schema=validate.Schema(\n                validate.parse_json(),\n                {\"gbx\": str},\n                validate.get(\"gbx\"),\n            ),\n        )\n\n        tokens = self.session.http.post(\n            pdata[\"cerbero\"],\n            acceptable_status=(200, 403, 404),\n            json={\n                \"bbx\": pdata[\"bbx\"],\n                \"gbx\": gbx,\n            },\n            headers={\"origin\": \"https://www.mitele.es\"},\n            schema=validate.Schema(\n                validate.parse_json(),\n                validate.any(\n                    {\"code\": int},\n                    validate.all(\n                        {\"tokens\": {str: {\"cdn\": str}}},\n                        validate.get(\"tokens\"),\n                    ),\n                ),\n            ),\n        )\n        if \"code\" in tokens:\n            tokenerrors = self.TOKEN_ERRORS.get(tokens[\"code\"], \"unknown error\")\n            log.error(f\"Could not get stream tokens: {tokens['code']} ({tokenerrors})\")\n            return\n\n        urls = set()\n        for stream in pdata[\"dls\"]:\n            if stream[\"drm\"]:\n                log.warning(\"Stream may be protected by DRM\")\n                continue\n            cdn_token = tokens.get(stream[\"lid\"], {}).get(\"cdn\", \"\")\n            if not cdn_token:\n                continue\n            qsd = parse_qsd(cdn_token)\n            urls.add(update_qsd(stream[\"stream\"], qsd, quote_via=lambda string, *_, **__: string))\n\n        for url in urls:\n            yield from HLSStream.parse_variant_playlist(\n                self.session,\n                url,\n                headers={\"Origin\": \"https://www.mitele.es\"},\n                name_fmt=\"{pixels}_{bitrate}\",\n            ).items()\n\n\n__plugin__ = Mitele\n", "path": "src/streamlink/plugins/mitele.py"}]}