prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  plugins.lrt: stream is reported Forbidden (though plays if opened manually)
  ### 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
  
  When trying to open https://www.lrt.lt/mediateka/tiesiogiai/lrt-televizija or https://www.lrt.lt/mediateka/tiesiogiai/lrt-plius, an error is reported (see the log below).
  
  However, if I try to manually pass the m3u8 URL mentioned in the error to `mpv`, like this (the URL taken from the log below, note the absence of the `%0A` at the end of it):
  
      mpv https://af5dcb595ac445ab94d7da3af2ebb360.dlvr1.net/lrt_hd/master.m3u8?RxKc3mPWTMxjM1SuDkHZeW1Fw3jEx0oqyryrSQODiHo-Bs31UZVEBEPkLtrdbPKVKrlorJgTLUnSwqks_5Y1QrSQRYfbtlWddOuLrpnY9-kuyM_3QE_yBbqwzhre
  
  ...then, after a few ffmpeg errors and warnings, it does open.
  
  The error started to appear a few days ago, worked perfectly before that (so, probably, they changed something at their side).
  
  Thanks.
  
  ### Debug log
  
  ```text
  [cli][debug] OS:         Linux-5.15.0-76-generic-x86_64-with-glibc2.35
  [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.18.0
  [cli][debug]  PySocks: 1.7.1
  [cli][debug]  requests: 2.31.0
  [cli][debug]  urllib3: 2.0.2
  [cli][debug]  websocket-client: 1.5.2
  [cli][debug] Arguments:
  [cli][debug]  url=https://www.lrt.lt/mediateka/tiesiogiai/lrt-televizija
  [cli][debug]  --loglevel=debug
  [cli][info] Found matching plugin lrt for URL https://www.lrt.lt/mediateka/tiesiogiai/lrt-televizija
  [utils.l10n][debug] Language code: en_US
  error: Unable to open URL: https://af5dcb595ac445ab94d7da3af2ebb360.dlvr1.net/lrt_hd/master.m3u8?RxKc3mPWTMxjM1SuDkHZeW1Fw3jEx0oqyryrSQODiHo-Bs31UZVEBEPkLtrdbPKVKrlorJgTLUnSwqks_5Y1QrSQRYfbtlWddOuLrpnY9-kuyM_3QE_yBbqwzhre
   (403 Client Error: Forbidden for url: https://af5dcb595ac445ab94d7da3af2ebb360.dlvr1.net/lrt_hd/master.m3u8?RxKc3mPWTMxjM1SuDkHZeW1Fw3jEx0oqyryrSQODiHo-Bs31UZVEBEPkLtrdbPKVKrlorJgTLUnSwqks_5Y1QrSQRYfbtlWddOuLrpnY9-kuyM_3QE_yBbqwzhre%0A)
  ```
  
  
  --- 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/lrt.py`
  Content:
  ```
  1 """
  2 $description Live TV channels from LRT, a Lithuanian public, state-owned broadcaster.
  3 $url lrt.lt
  4 $type live
  5 """
  6 
  7 import logging
  8 import re
  9 
  10 from streamlink.plugin import Plugin, pluginmatcher
  11 from streamlink.stream.hls import HLSStream
  12 
  13 
  14 log = logging.getLogger(__name__)
  15 
  16 
  17 @pluginmatcher(re.compile(
  18     r"https?://(?:www\.)?lrt\.lt/mediateka/tiesiogiai/",
  19 ))
  20 class LRT(Plugin):
  21     _video_id_re = re.compile(r"""var\svideo_id\s*=\s*["'](?P<video_id>\w+)["']""")
  22     API_URL = "https://www.lrt.lt/servisai/stream_url/live/get_live_url.php?channel={0}"
  23 
  24     def _get_streams(self):
  25         page = self.session.http.get(self.url)
  26         m = self._video_id_re.search(page.text)
  27         if m:
  28             video_id = m.group("video_id")
  29             data = self.session.http.get(self.API_URL.format(video_id)).json()
  30             hls_url = data["response"]["data"]["content"]
  31 
  32             yield from HLSStream.parse_variant_playlist(self.session, hls_url).items()
  33         else:
  34             log.debug("No match for video_id regex")
  35 
  36 
  37 __plugin__ = LRT
  38
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/src/streamlink/plugins/lrt.py b/src/streamlink/plugins/lrt.py
  --- a/src/streamlink/plugins/lrt.py
  +++ b/src/streamlink/plugins/lrt.py
  @@ -4,34 +4,42 @@
   $type live
   """
   
  -import logging
   import re
   
   from streamlink.plugin import Plugin, pluginmatcher
  +from streamlink.plugin.api import validate
   from streamlink.stream.hls import HLSStream
   
   
  -log = logging.getLogger(__name__)
  -
  -
   @pluginmatcher(re.compile(
       r"https?://(?:www\.)?lrt\.lt/mediateka/tiesiogiai/",
   ))
   class LRT(Plugin):
  -    _video_id_re = re.compile(r"""var\svideo_id\s*=\s*["'](?P<video_id>\w+)["']""")
  -    API_URL = "https://www.lrt.lt/servisai/stream_url/live/get_live_url.php?channel={0}"
  -
       def _get_streams(self):
  -        page = self.session.http.get(self.url)
  -        m = self._video_id_re.search(page.text)
  -        if m:
  -            video_id = m.group("video_id")
  -            data = self.session.http.get(self.API_URL.format(video_id)).json()
  -            hls_url = data["response"]["data"]["content"]
  -
  -            yield from HLSStream.parse_variant_playlist(self.session, hls_url).items()
  -        else:
  -            log.debug("No match for video_id regex")
  +        token_url = self.session.http.get(self.url, schema=validate.Schema(
  +            re.compile(r"""var\s+tokenURL\s*=\s*(?P<q>["'])(?P<url>https://\S+)(?P=q)"""),
  +            validate.none_or_all(validate.get("url")),
  +        ))
  +        if not token_url:
  +            return
  +
  +        hls_url = self.session.http.get(token_url, schema=validate.Schema(
  +            validate.parse_json(),
  +            {
  +                "response": {
  +                    "data": {
  +                        "content": validate.all(
  +                            str,
  +                            validate.transform(lambda url: url.strip()),
  +                            validate.url(path=validate.endswith(".m3u8")),
  +                        ),
  +                    },
  +                },
  +            },
  +            validate.get(("response", "data", "content")),
  +        ))
  +
  +        return HLSStream.parse_variant_playlist(self.session, hls_url)
   
   
   __plugin__ = LRT
  
verification_info: {"golden_diff": "diff --git a/src/streamlink/plugins/lrt.py b/src/streamlink/plugins/lrt.py\n--- a/src/streamlink/plugins/lrt.py\n+++ b/src/streamlink/plugins/lrt.py\n@@ -4,34 +4,42 @@\n $type live\n \"\"\"\n \n-import logging\n import re\n \n from streamlink.plugin import Plugin, pluginmatcher\n+from streamlink.plugin.api import validate\n from streamlink.stream.hls import HLSStream\n \n \n-log = logging.getLogger(__name__)\n-\n-\n @pluginmatcher(re.compile(\n     r\"https?://(?:www\\.)?lrt\\.lt/mediateka/tiesiogiai/\",\n ))\n class LRT(Plugin):\n-    _video_id_re = re.compile(r\"\"\"var\\svideo_id\\s*=\\s*[\"'](?P<video_id>\\w+)[\"']\"\"\")\n-    API_URL = \"https://www.lrt.lt/servisai/stream_url/live/get_live_url.php?channel={0}\"\n-\n     def _get_streams(self):\n-        page = self.session.http.get(self.url)\n-        m = self._video_id_re.search(page.text)\n-        if m:\n-            video_id = m.group(\"video_id\")\n-            data = self.session.http.get(self.API_URL.format(video_id)).json()\n-            hls_url = data[\"response\"][\"data\"][\"content\"]\n-\n-            yield from HLSStream.parse_variant_playlist(self.session, hls_url).items()\n-        else:\n-            log.debug(\"No match for video_id regex\")\n+        token_url = self.session.http.get(self.url, schema=validate.Schema(\n+            re.compile(r\"\"\"var\\s+tokenURL\\s*=\\s*(?P<q>[\"'])(?P<url>https://\\S+)(?P=q)\"\"\"),\n+            validate.none_or_all(validate.get(\"url\")),\n+        ))\n+        if not token_url:\n+            return\n+\n+        hls_url = self.session.http.get(token_url, schema=validate.Schema(\n+            validate.parse_json(),\n+            {\n+                \"response\": {\n+                    \"data\": {\n+                        \"content\": validate.all(\n+                            str,\n+                            validate.transform(lambda url: url.strip()),\n+                            validate.url(path=validate.endswith(\".m3u8\")),\n+                        ),\n+                    },\n+                },\n+            },\n+            validate.get((\"response\", \"data\", \"content\")),\n+        ))\n+\n+        return HLSStream.parse_variant_playlist(self.session, hls_url)\n \n \n __plugin__ = LRT\n", "issue": "plugins.lrt: stream is reported Forbidden (though plays if opened manually)\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\nWhen trying to open https://www.lrt.lt/mediateka/tiesiogiai/lrt-televizija or https://www.lrt.lt/mediateka/tiesiogiai/lrt-plius, an error is reported (see the log below).\r\n\r\nHowever, if I try to manually pass the m3u8 URL mentioned in the error to `mpv`, like this (the URL taken from the log below, note the absence of the `%0A` at the end of it):\r\n\r\n    mpv https://af5dcb595ac445ab94d7da3af2ebb360.dlvr1.net/lrt_hd/master.m3u8?RxKc3mPWTMxjM1SuDkHZeW1Fw3jEx0oqyryrSQODiHo-Bs31UZVEBEPkLtrdbPKVKrlorJgTLUnSwqks_5Y1QrSQRYfbtlWddOuLrpnY9-kuyM_3QE_yBbqwzhre\r\n\r\n...then, after a few ffmpeg errors and warnings, it does open.\r\n\r\nThe error started to appear a few days ago, worked perfectly before that (so, probably, they changed something at their side).\r\n\r\nThanks.\n\n### Debug log\n\n```text\n[cli][debug] OS:         Linux-5.15.0-76-generic-x86_64-with-glibc2.35\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.18.0\r\n[cli][debug]  PySocks: 1.7.1\r\n[cli][debug]  requests: 2.31.0\r\n[cli][debug]  urllib3: 2.0.2\r\n[cli][debug]  websocket-client: 1.5.2\r\n[cli][debug] Arguments:\r\n[cli][debug]  url=https://www.lrt.lt/mediateka/tiesiogiai/lrt-televizija\r\n[cli][debug]  --loglevel=debug\r\n[cli][info] Found matching plugin lrt for URL https://www.lrt.lt/mediateka/tiesiogiai/lrt-televizija\r\n[utils.l10n][debug] Language code: en_US\r\nerror: Unable to open URL: https://af5dcb595ac445ab94d7da3af2ebb360.dlvr1.net/lrt_hd/master.m3u8?RxKc3mPWTMxjM1SuDkHZeW1Fw3jEx0oqyryrSQODiHo-Bs31UZVEBEPkLtrdbPKVKrlorJgTLUnSwqks_5Y1QrSQRYfbtlWddOuLrpnY9-kuyM_3QE_yBbqwzhre\r\n (403 Client Error: Forbidden for url: https://af5dcb595ac445ab94d7da3af2ebb360.dlvr1.net/lrt_hd/master.m3u8?RxKc3mPWTMxjM1SuDkHZeW1Fw3jEx0oqyryrSQODiHo-Bs31UZVEBEPkLtrdbPKVKrlorJgTLUnSwqks_5Y1QrSQRYfbtlWddOuLrpnY9-kuyM_3QE_yBbqwzhre%0A)\n```\n\n", "before_files": [{"content": "\"\"\"\n$description Live TV channels from LRT, a Lithuanian public, state-owned broadcaster.\n$url lrt.lt\n$type live\n\"\"\"\n\nimport logging\nimport re\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.stream.hls import HLSStream\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"https?://(?:www\\.)?lrt\\.lt/mediateka/tiesiogiai/\",\n))\nclass LRT(Plugin):\n    _video_id_re = re.compile(r\"\"\"var\\svideo_id\\s*=\\s*[\"'](?P<video_id>\\w+)[\"']\"\"\")\n    API_URL = \"https://www.lrt.lt/servisai/stream_url/live/get_live_url.php?channel={0}\"\n\n    def _get_streams(self):\n        page = self.session.http.get(self.url)\n        m = self._video_id_re.search(page.text)\n        if m:\n            video_id = m.group(\"video_id\")\n            data = self.session.http.get(self.API_URL.format(video_id)).json()\n            hls_url = data[\"response\"][\"data\"][\"content\"]\n\n            yield from HLSStream.parse_variant_playlist(self.session, hls_url).items()\n        else:\n            log.debug(\"No match for video_id regex\")\n\n\n__plugin__ = LRT\n", "path": "src/streamlink/plugins/lrt.py"}], "after_files": [{"content": "\"\"\"\n$description Live TV channels from LRT, a Lithuanian public, state-owned broadcaster.\n$url lrt.lt\n$type live\n\"\"\"\n\nimport re\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.api import validate\nfrom streamlink.stream.hls import HLSStream\n\n\n@pluginmatcher(re.compile(\n    r\"https?://(?:www\\.)?lrt\\.lt/mediateka/tiesiogiai/\",\n))\nclass LRT(Plugin):\n    def _get_streams(self):\n        token_url = self.session.http.get(self.url, schema=validate.Schema(\n            re.compile(r\"\"\"var\\s+tokenURL\\s*=\\s*(?P<q>[\"'])(?P<url>https://\\S+)(?P=q)\"\"\"),\n            validate.none_or_all(validate.get(\"url\")),\n        ))\n        if not token_url:\n            return\n\n        hls_url = self.session.http.get(token_url, schema=validate.Schema(\n            validate.parse_json(),\n            {\n                \"response\": {\n                    \"data\": {\n                        \"content\": validate.all(\n                            str,\n                            validate.transform(lambda url: url.strip()),\n                            validate.url(path=validate.endswith(\".m3u8\")),\n                        ),\n                    },\n                },\n            },\n            validate.get((\"response\", \"data\", \"content\")),\n        ))\n\n        return HLSStream.parse_variant_playlist(self.session, hls_url)\n\n\n__plugin__ = LRT\n", "path": "src/streamlink/plugins/lrt.py"}]}