We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
plugins.ustvnow: pluginmatcher URL update needed
### 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 build from the master branch

### Description

You need to replace the search template in ustvnow.py plugin with the one below.

```
@pluginmatcher(re.compile(                                                                                                                                     
    r"https?://(?:www\.)?ustvnow\.com/channel/live/(?P<chname>\w+)",                                                                                           
))
```
What is in the code now - does not work due to changes that have occurred on the site www.ustvnow.com

### Debug log

```text
The log cannot be retrieved because changes must be made to the template of the link it "serves"
```


--- 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/ustvnow.py`
Content:
```
1 """
2 $description US live TV channels. OTT service from USTVnow.
3 $url ustvnow.com
4 $type live
5 $account Required, additional subscription required by some streams
6 """
7 
8 import base64
9 import json
10 import logging
11 import re
12 from urllib.parse import urljoin, urlparse
13 from uuid import uuid4
14 
15 from streamlink.plugin import Plugin, PluginError, pluginargument, pluginmatcher
16 from streamlink.stream.hls import HLSStream
17 from streamlink.utils.crypto import AES, SHA256, pad, unpad
18 
19 
20 log = logging.getLogger(__name__)
21 
22 
23 @pluginmatcher(re.compile(
24     r"https?://(?:www\.)?ustvnow\.com/live/(?P<scode>\w+)/-(?P<id>\d+)",
25 ))
26 @pluginargument(
27     "username",
28     required=True,
29     requires=["password"],
30     metavar="USERNAME",
31     help="Your USTV Now account username",
32 )
33 @pluginargument(
34     "password",
35     required=True,
36     sensitive=True,
37     metavar="PASSWORD",
38     help="Your USTV Now account password",
39 )
40 class USTVNow(Plugin):
41     _main_js_re = re.compile(r"""src=['"](main\..*\.js)['"]""")
42     _enc_key_re = re.compile(r'(?P<key>AES_(?:Key|IV))\s*:\s*"(?P<value>[^"]+)"')
43 
44     TENANT_CODE = "ustvnow"
45     _api_url = "https://teleupapi.revlet.net/service/api/v1/"
46     _token_url = _api_url + "get/token"
47     _signin_url = "https://www.ustvnow.com/signin"
48 
49     def __init__(self, *args, **kwargs):
50         super().__init__(*args, **kwargs)
51         self._encryption_config = {}
52         self._token = None
53 
54     @classmethod
55     def encrypt_data(cls, data, key, iv):
56         rkey = "".join(reversed(key)).encode("utf8")
57         riv = "".join(reversed(iv)).encode("utf8")
58 
59         fkey = SHA256.new(rkey).hexdigest()[:32].encode("utf8")
60 
61         cipher = AES.new(fkey, AES.MODE_CBC, riv)
62         encrypted = cipher.encrypt(pad(data, 16, "pkcs7"))
63         return base64.b64encode(encrypted)
64 
65     @classmethod
66     def decrypt_data(cls, data, key, iv):
67         rkey = "".join(reversed(key)).encode("utf8")
68         riv = "".join(reversed(iv)).encode("utf8")
69 
70         fkey = SHA256.new(rkey).hexdigest()[:32].encode("utf8")
71 
72         cipher = AES.new(fkey, AES.MODE_CBC, riv)
73         decrypted = cipher.decrypt(base64.b64decode(data))
74         if decrypted:
75             return unpad(decrypted, 16, "pkcs7")
76         else:
77             return decrypted
78 
79     def _get_encryption_config(self, url):
80         # find the path to the main.js
81         # load the main.js and extract the config
82         if not self._encryption_config:
83             res = self.session.http.get(url)
84             m = self._main_js_re.search(res.text)
85             main_js_path = m and m.group(1)
86             if main_js_path:
87                 res = self.session.http.get(urljoin(url, main_js_path))
88                 self._encryption_config = dict(self._enc_key_re.findall(res.text))
89 
90         return self._encryption_config.get("AES_Key"), self._encryption_config.get("AES_IV")
91 
92     @property
93     def box_id(self):
94         if not self.cache.get("box_id"):
95             self.cache.set("box_id", str(uuid4()))
96         return self.cache.get("box_id")
97 
98     def get_token(self):
99         """
100         Get the token for USTVNow
101         :return: a valid token
102         """
103 
104         if not self._token:
105             log.debug("Getting new session token")
106             res = self.session.http.get(self._token_url, params={
107                 "tenant_code": self.TENANT_CODE,
108                 "box_id": self.box_id,
109                 "product": self.TENANT_CODE,
110                 "device_id": 5,
111                 "display_lang_code": "ENG",
112                 "device_sub_type": "",
113                 "timezone": "UTC",
114             })
115 
116             data = res.json()
117             if data["status"]:
118                 self._token = data["response"]["sessionId"]
119                 log.debug("New token: {}".format(self._token))
120             else:
121                 log.error("Token acquisition failed: {details} ({detail})".format(**data["error"]))
122                 raise PluginError("could not obtain token")
123 
124         return self._token
125 
126     def api_request(self, path, data, metadata=None):
127         key, iv = self._get_encryption_config(self._signin_url)
128         post_data = {
129             "data": self.encrypt_data(json.dumps(data).encode("utf8"), key, iv).decode("utf8"),
130             "metadata": self.encrypt_data(json.dumps(metadata).encode("utf8"), key, iv).decode("utf8"),
131         }
132         headers = {"box-id": self.box_id,
133                    "session-id": self.get_token(),
134                    "tenant-code": self.TENANT_CODE,
135                    "content-type": "application/json"}
136         res = self.session.http.post(self._api_url + path, data=json.dumps(post_data), headers=headers).json()
137         data = {k: v and json.loads(self.decrypt_data(v, key, iv)) for k, v in res.items()}
138         return data
139 
140     def login(self, username, password):
141         log.debug("Trying to login...")
142         resp = self.api_request(
143             "send",
144             {
145                 "login_id": username,
146                 "login_key": password,
147                 "login_mode": "1",
148                 "manufacturer": "123",
149             },
150             {"request": "signin"},
151         )
152 
153         return resp["data"]["status"]
154 
155     def _get_streams(self):
156         """
157         Finds the streams from ustvnow.com.
158         """
159         if self.login(self.get_option("username"), self.get_option("password")):
160             path = urlparse(self.url).path.strip("/")
161             resp = self.api_request("send", {"path": path}, {"request": "page/stream"})
162             if resp["data"]["status"]:
163                 for stream in resp["data"]["response"]["streams"]:
164                     if stream["keys"]["licenseKey"]:
165                         log.warning("Stream possibly protected by DRM")
166                     yield from HLSStream.parse_variant_playlist(self.session, stream["url"]).items()
167             else:
168                 log.error("Could not find any streams: {code}: {message}".format(**resp["data"]["error"]))
169         else:
170             log.error("Failed to login, check username and password")
171 
172 
173 __plugin__ = USTVNow
174
```
--- 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/ustvnow.py b/src/streamlink/plugins/ustvnow.py
--- a/src/streamlink/plugins/ustvnow.py
+++ b/src/streamlink/plugins/ustvnow.py
@@ -21,7 +21,7 @@
 
 
 @pluginmatcher(re.compile(
-    r"https?://(?:www\.)?ustvnow\.com/live/(?P<scode>\w+)/-(?P<id>\d+)",
+    r"https?://(?:www\.)?ustvnow\.com/channel/live/(?P<chname>\w+)",
 ))
 @pluginargument(
     "username",
@@ -96,11 +96,6 @@
         return self.cache.get("box_id")
 
     def get_token(self):
-        """
-        Get the token for USTVNow
-        :return: a valid token
-        """
-
         if not self._token:
             log.debug("Getting new session token")
             res = self.session.http.get(self._token_url, params={
@@ -114,13 +109,13 @@
             })
 
             data = res.json()
-            if data["status"]:
-                self._token = data["response"]["sessionId"]
-                log.debug("New token: {}".format(self._token))
-            else:
+            if not data["status"]:
                 log.error("Token acquisition failed: {details} ({detail})".format(**data["error"]))
                 raise PluginError("could not obtain token")
 
+            self._token = data["response"]["sessionId"]
+            log.debug(f"New token: {self._token}")
+
         return self._token
 
     def api_request(self, path, data, metadata=None):
@@ -153,21 +148,20 @@
         return resp["data"]["status"]
 
     def _get_streams(self):
-        """
-        Finds the streams from ustvnow.com.
-        """
-        if self.login(self.get_option("username"), self.get_option("password")):
-            path = urlparse(self.url).path.strip("/")
-            resp = self.api_request("send", {"path": path}, {"request": "page/stream"})
-            if resp["data"]["status"]:
-                for stream in resp["data"]["response"]["streams"]:
-                    if stream["keys"]["licenseKey"]:
-                        log.warning("Stream possibly protected by DRM")
-                    yield from HLSStream.parse_variant_playlist(self.session, stream["url"]).items()
-            else:
-                log.error("Could not find any streams: {code}: {message}".format(**resp["data"]["error"]))
-        else:
+        if not self.login(self.get_option("username"), self.get_option("password")):
             log.error("Failed to login, check username and password")
+            return
+
+        path = urlparse(self.url).path.strip("/")
+        resp = self.api_request("send", {"path": path}, {"request": "page/stream"})
+        if not resp["data"]["status"]:
+            log.error("Could not find any streams: {code}: {message}".format(**resp["data"]["error"]))
+            return
+
+        for stream in resp["data"]["response"]["streams"]:
+            if stream["keys"]["licenseKey"]:
+                log.warning("Stream possibly protected by DRM")
+            yield from HLSStream.parse_variant_playlist(self.session, stream["url"]).items()
 
 
 __plugin__ = USTVNow

{"golden_diff": "diff --git a/src/streamlink/plugins/ustvnow.py b/src/streamlink/plugins/ustvnow.py\n--- a/src/streamlink/plugins/ustvnow.py\n+++ b/src/streamlink/plugins/ustvnow.py\n@@ -21,7 +21,7 @@\n \n \n @pluginmatcher(re.compile(\n-    r\"https?://(?:www\\.)?ustvnow\\.com/live/(?P<scode>\\w+)/-(?P<id>\\d+)\",\n+    r\"https?://(?:www\\.)?ustvnow\\.com/channel/live/(?P<chname>\\w+)\",\n ))\n @pluginargument(\n     \"username\",\n@@ -96,11 +96,6 @@\n         return self.cache.get(\"box_id\")\n \n     def get_token(self):\n-        \"\"\"\n-        Get the token for USTVNow\n-        :return: a valid token\n-        \"\"\"\n-\n         if not self._token:\n             log.debug(\"Getting new session token\")\n             res = self.session.http.get(self._token_url, params={\n@@ -114,13 +109,13 @@\n             })\n \n             data = res.json()\n-            if data[\"status\"]:\n-                self._token = data[\"response\"][\"sessionId\"]\n-                log.debug(\"New token: {}\".format(self._token))\n-            else:\n+            if not data[\"status\"]:\n                 log.error(\"Token acquisition failed: {details} ({detail})\".format(**data[\"error\"]))\n                 raise PluginError(\"could not obtain token\")\n \n+            self._token = data[\"response\"][\"sessionId\"]\n+            log.debug(f\"New token: {self._token}\")\n+\n         return self._token\n \n     def api_request(self, path, data, metadata=None):\n@@ -153,21 +148,20 @@\n         return resp[\"data\"][\"status\"]\n \n     def _get_streams(self):\n-        \"\"\"\n-        Finds the streams from ustvnow.com.\n-        \"\"\"\n-        if self.login(self.get_option(\"username\"), self.get_option(\"password\")):\n-            path = urlparse(self.url).path.strip(\"/\")\n-            resp = self.api_request(\"send\", {\"path\": path}, {\"request\": \"page/stream\"})\n-            if resp[\"data\"][\"status\"]:\n-                for stream in resp[\"data\"][\"response\"][\"streams\"]:\n-                    if stream[\"keys\"][\"licenseKey\"]:\n-                        log.warning(\"Stream possibly protected by DRM\")\n-                    yield from HLSStream.parse_variant_playlist(self.session, stream[\"url\"]).items()\n-            else:\n-                log.error(\"Could not find any streams: {code}: {message}\".format(**resp[\"data\"][\"error\"]))\n-        else:\n+        if not self.login(self.get_option(\"username\"), self.get_option(\"password\")):\n             log.error(\"Failed to login, check username and password\")\n+            return\n+\n+        path = urlparse(self.url).path.strip(\"/\")\n+        resp = self.api_request(\"send\", {\"path\": path}, {\"request\": \"page/stream\"})\n+        if not resp[\"data\"][\"status\"]:\n+            log.error(\"Could not find any streams: {code}: {message}\".format(**resp[\"data\"][\"error\"]))\n+            return\n+\n+        for stream in resp[\"data\"][\"response\"][\"streams\"]:\n+            if stream[\"keys\"][\"licenseKey\"]:\n+                log.warning(\"Stream possibly protected by DRM\")\n+            yield from HLSStream.parse_variant_playlist(self.session, stream[\"url\"]).items()\n \n \n __plugin__ = USTVNow\n", "issue": "plugins.ustvnow: pluginmatcher URL update needed\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 build from the master branch\n\n### Description\n\nYou need to replace the search template in ustvnow.py plugin with the one below.\r\n\r\n```\r\n@pluginmatcher(re.compile(                                                                                                                                     \r\n    r\"https?://(?:www\\.)?ustvnow\\.com/channel/live/(?P<chname>\\w+)\",                                                                                           \r\n))\r\n```\r\nWhat is in the code now - does not work due to changes that have occurred on the site www.ustvnow.com\n\n### Debug log\n\n```text\nThe log cannot be retrieved because changes must be made to the template of the link it \"serves\"\n```\n\n", "before_files": [{"content": "\"\"\"\n$description US live TV channels. OTT service from USTVnow.\n$url ustvnow.com\n$type live\n$account Required, additional subscription required by some streams\n\"\"\"\n\nimport base64\nimport json\nimport logging\nimport re\nfrom urllib.parse import urljoin, urlparse\nfrom uuid import uuid4\n\nfrom streamlink.plugin import Plugin, PluginError, pluginargument, pluginmatcher\nfrom streamlink.stream.hls import HLSStream\nfrom streamlink.utils.crypto import AES, SHA256, pad, unpad\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"https?://(?:www\\.)?ustvnow\\.com/live/(?P<scode>\\w+)/-(?P<id>\\d+)\",\n))\n@pluginargument(\n    \"username\",\n    required=True,\n    requires=[\"password\"],\n    metavar=\"USERNAME\",\n    help=\"Your USTV Now account username\",\n)\n@pluginargument(\n    \"password\",\n    required=True,\n    sensitive=True,\n    metavar=\"PASSWORD\",\n    help=\"Your USTV Now account password\",\n)\nclass USTVNow(Plugin):\n    _main_js_re = re.compile(r\"\"\"src=['\"](main\\..*\\.js)['\"]\"\"\")\n    _enc_key_re = re.compile(r'(?P<key>AES_(?:Key|IV))\\s*:\\s*\"(?P<value>[^\"]+)\"')\n\n    TENANT_CODE = \"ustvnow\"\n    _api_url = \"https://teleupapi.revlet.net/service/api/v1/\"\n    _token_url = _api_url + \"get/token\"\n    _signin_url = \"https://www.ustvnow.com/signin\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._encryption_config = {}\n        self._token = None\n\n    @classmethod\n    def encrypt_data(cls, data, key, iv):\n        rkey = \"\".join(reversed(key)).encode(\"utf8\")\n        riv = \"\".join(reversed(iv)).encode(\"utf8\")\n\n        fkey = SHA256.new(rkey).hexdigest()[:32].encode(\"utf8\")\n\n        cipher = AES.new(fkey, AES.MODE_CBC, riv)\n        encrypted = cipher.encrypt(pad(data, 16, \"pkcs7\"))\n        return base64.b64encode(encrypted)\n\n    @classmethod\n    def decrypt_data(cls, data, key, iv):\n        rkey = \"\".join(reversed(key)).encode(\"utf8\")\n        riv = \"\".join(reversed(iv)).encode(\"utf8\")\n\n        fkey = SHA256.new(rkey).hexdigest()[:32].encode(\"utf8\")\n\n        cipher = AES.new(fkey, AES.MODE_CBC, riv)\n        decrypted = cipher.decrypt(base64.b64decode(data))\n        if decrypted:\n            return unpad(decrypted, 16, \"pkcs7\")\n        else:\n            return decrypted\n\n    def _get_encryption_config(self, url):\n        # find the path to the main.js\n        # load the main.js and extract the config\n        if not self._encryption_config:\n            res = self.session.http.get(url)\n            m = self._main_js_re.search(res.text)\n            main_js_path = m and m.group(1)\n            if main_js_path:\n                res = self.session.http.get(urljoin(url, main_js_path))\n                self._encryption_config = dict(self._enc_key_re.findall(res.text))\n\n        return self._encryption_config.get(\"AES_Key\"), self._encryption_config.get(\"AES_IV\")\n\n    @property\n    def box_id(self):\n        if not self.cache.get(\"box_id\"):\n            self.cache.set(\"box_id\", str(uuid4()))\n        return self.cache.get(\"box_id\")\n\n    def get_token(self):\n        \"\"\"\n        Get the token for USTVNow\n        :return: a valid token\n        \"\"\"\n\n        if not self._token:\n            log.debug(\"Getting new session token\")\n            res = self.session.http.get(self._token_url, params={\n                \"tenant_code\": self.TENANT_CODE,\n                \"box_id\": self.box_id,\n                \"product\": self.TENANT_CODE,\n                \"device_id\": 5,\n                \"display_lang_code\": \"ENG\",\n                \"device_sub_type\": \"\",\n                \"timezone\": \"UTC\",\n            })\n\n            data = res.json()\n            if data[\"status\"]:\n                self._token = data[\"response\"][\"sessionId\"]\n                log.debug(\"New token: {}\".format(self._token))\n            else:\n                log.error(\"Token acquisition failed: {details} ({detail})\".format(**data[\"error\"]))\n                raise PluginError(\"could not obtain token\")\n\n        return self._token\n\n    def api_request(self, path, data, metadata=None):\n        key, iv = self._get_encryption_config(self._signin_url)\n        post_data = {\n            \"data\": self.encrypt_data(json.dumps(data).encode(\"utf8\"), key, iv).decode(\"utf8\"),\n            \"metadata\": self.encrypt_data(json.dumps(metadata).encode(\"utf8\"), key, iv).decode(\"utf8\"),\n        }\n        headers = {\"box-id\": self.box_id,\n                   \"session-id\": self.get_token(),\n                   \"tenant-code\": self.TENANT_CODE,\n                   \"content-type\": \"application/json\"}\n        res = self.session.http.post(self._api_url + path, data=json.dumps(post_data), headers=headers).json()\n        data = {k: v and json.loads(self.decrypt_data(v, key, iv)) for k, v in res.items()}\n        return data\n\n    def login(self, username, password):\n        log.debug(\"Trying to login...\")\n        resp = self.api_request(\n            \"send\",\n            {\n                \"login_id\": username,\n                \"login_key\": password,\n                \"login_mode\": \"1\",\n                \"manufacturer\": \"123\",\n            },\n            {\"request\": \"signin\"},\n        )\n\n        return resp[\"data\"][\"status\"]\n\n    def _get_streams(self):\n        \"\"\"\n        Finds the streams from ustvnow.com.\n        \"\"\"\n        if self.login(self.get_option(\"username\"), self.get_option(\"password\")):\n            path = urlparse(self.url).path.strip(\"/\")\n            resp = self.api_request(\"send\", {\"path\": path}, {\"request\": \"page/stream\"})\n            if resp[\"data\"][\"status\"]:\n                for stream in resp[\"data\"][\"response\"][\"streams\"]:\n                    if stream[\"keys\"][\"licenseKey\"]:\n                        log.warning(\"Stream possibly protected by DRM\")\n                    yield from HLSStream.parse_variant_playlist(self.session, stream[\"url\"]).items()\n            else:\n                log.error(\"Could not find any streams: {code}: {message}\".format(**resp[\"data\"][\"error\"]))\n        else:\n            log.error(\"Failed to login, check username and password\")\n\n\n__plugin__ = USTVNow\n", "path": "src/streamlink/plugins/ustvnow.py"}], "after_files": [{"content": "\"\"\"\n$description US live TV channels. OTT service from USTVnow.\n$url ustvnow.com\n$type live\n$account Required, additional subscription required by some streams\n\"\"\"\n\nimport base64\nimport json\nimport logging\nimport re\nfrom urllib.parse import urljoin, urlparse\nfrom uuid import uuid4\n\nfrom streamlink.plugin import Plugin, PluginError, pluginargument, pluginmatcher\nfrom streamlink.stream.hls import HLSStream\nfrom streamlink.utils.crypto import AES, SHA256, pad, unpad\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"https?://(?:www\\.)?ustvnow\\.com/channel/live/(?P<chname>\\w+)\",\n))\n@pluginargument(\n    \"username\",\n    required=True,\n    requires=[\"password\"],\n    metavar=\"USERNAME\",\n    help=\"Your USTV Now account username\",\n)\n@pluginargument(\n    \"password\",\n    required=True,\n    sensitive=True,\n    metavar=\"PASSWORD\",\n    help=\"Your USTV Now account password\",\n)\nclass USTVNow(Plugin):\n    _main_js_re = re.compile(r\"\"\"src=['\"](main\\..*\\.js)['\"]\"\"\")\n    _enc_key_re = re.compile(r'(?P<key>AES_(?:Key|IV))\\s*:\\s*\"(?P<value>[^\"]+)\"')\n\n    TENANT_CODE = \"ustvnow\"\n    _api_url = \"https://teleupapi.revlet.net/service/api/v1/\"\n    _token_url = _api_url + \"get/token\"\n    _signin_url = \"https://www.ustvnow.com/signin\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._encryption_config = {}\n        self._token = None\n\n    @classmethod\n    def encrypt_data(cls, data, key, iv):\n        rkey = \"\".join(reversed(key)).encode(\"utf8\")\n        riv = \"\".join(reversed(iv)).encode(\"utf8\")\n\n        fkey = SHA256.new(rkey).hexdigest()[:32].encode(\"utf8\")\n\n        cipher = AES.new(fkey, AES.MODE_CBC, riv)\n        encrypted = cipher.encrypt(pad(data, 16, \"pkcs7\"))\n        return base64.b64encode(encrypted)\n\n    @classmethod\n    def decrypt_data(cls, data, key, iv):\n        rkey = \"\".join(reversed(key)).encode(\"utf8\")\n        riv = \"\".join(reversed(iv)).encode(\"utf8\")\n\n        fkey = SHA256.new(rkey).hexdigest()[:32].encode(\"utf8\")\n\n        cipher = AES.new(fkey, AES.MODE_CBC, riv)\n        decrypted = cipher.decrypt(base64.b64decode(data))\n        if decrypted:\n            return unpad(decrypted, 16, \"pkcs7\")\n        else:\n            return decrypted\n\n    def _get_encryption_config(self, url):\n        # find the path to the main.js\n        # load the main.js and extract the config\n        if not self._encryption_config:\n            res = self.session.http.get(url)\n            m = self._main_js_re.search(res.text)\n            main_js_path = m and m.group(1)\n            if main_js_path:\n                res = self.session.http.get(urljoin(url, main_js_path))\n                self._encryption_config = dict(self._enc_key_re.findall(res.text))\n\n        return self._encryption_config.get(\"AES_Key\"), self._encryption_config.get(\"AES_IV\")\n\n    @property\n    def box_id(self):\n        if not self.cache.get(\"box_id\"):\n            self.cache.set(\"box_id\", str(uuid4()))\n        return self.cache.get(\"box_id\")\n\n    def get_token(self):\n        if not self._token:\n            log.debug(\"Getting new session token\")\n            res = self.session.http.get(self._token_url, params={\n                \"tenant_code\": self.TENANT_CODE,\n                \"box_id\": self.box_id,\n                \"product\": self.TENANT_CODE,\n                \"device_id\": 5,\n                \"display_lang_code\": \"ENG\",\n                \"device_sub_type\": \"\",\n                \"timezone\": \"UTC\",\n            })\n\n            data = res.json()\n            if not data[\"status\"]:\n                log.error(\"Token acquisition failed: {details} ({detail})\".format(**data[\"error\"]))\n                raise PluginError(\"could not obtain token\")\n\n            self._token = data[\"response\"][\"sessionId\"]\n            log.debug(f\"New token: {self._token}\")\n\n        return self._token\n\n    def api_request(self, path, data, metadata=None):\n        key, iv = self._get_encryption_config(self._signin_url)\n        post_data = {\n            \"data\": self.encrypt_data(json.dumps(data).encode(\"utf8\"), key, iv).decode(\"utf8\"),\n            \"metadata\": self.encrypt_data(json.dumps(metadata).encode(\"utf8\"), key, iv).decode(\"utf8\"),\n        }\n        headers = {\"box-id\": self.box_id,\n                   \"session-id\": self.get_token(),\n                   \"tenant-code\": self.TENANT_CODE,\n                   \"content-type\": \"application/json\"}\n        res = self.session.http.post(self._api_url + path, data=json.dumps(post_data), headers=headers).json()\n        data = {k: v and json.loads(self.decrypt_data(v, key, iv)) for k, v in res.items()}\n        return data\n\n    def login(self, username, password):\n        log.debug(\"Trying to login...\")\n        resp = self.api_request(\n            \"send\",\n            {\n                \"login_id\": username,\n                \"login_key\": password,\n                \"login_mode\": \"1\",\n                \"manufacturer\": \"123\",\n            },\n            {\"request\": \"signin\"},\n        )\n\n        return resp[\"data\"][\"status\"]\n\n    def _get_streams(self):\n        if not self.login(self.get_option(\"username\"), self.get_option(\"password\")):\n            log.error(\"Failed to login, check username and password\")\n            return\n\n        path = urlparse(self.url).path.strip(\"/\")\n        resp = self.api_request(\"send\", {\"path\": path}, {\"request\": \"page/stream\"})\n        if not resp[\"data\"][\"status\"]:\n            log.error(\"Could not find any streams: {code}: {message}\".format(**resp[\"data\"][\"error\"]))\n            return\n\n        for stream in resp[\"data\"][\"response\"][\"streams\"]:\n            if stream[\"keys\"][\"licenseKey\"]:\n                log.warning(\"Stream possibly protected by DRM\")\n            yield from HLSStream.parse_variant_playlist(self.session, stream[\"url\"]).items()\n\n\n__plugin__ = USTVNow\n", "path": "src/streamlink/plugins/ustvnow.py"}]}