Repository: streamlink/streamlink

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)
```



Discussion:

Resolving patch:
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


Test patch: