lghorizon 0.6.12__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lghorizon-0.6.12 → lghorizon-0.7.0}/.github/workflows/build-on-pr.yml +1 -1
- {lghorizon-0.6.12 → lghorizon-0.7.0}/.github/workflows/publish-to-pypi.yml +1 -1
- {lghorizon-0.6.12 → lghorizon-0.7.0}/PKG-INFO +2 -2
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon/const.py +1 -2
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon/lghorizon_api.py +30 -97
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon.egg-info/PKG-INFO +2 -2
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon.egg-info/requires.txt +1 -1
- {lghorizon-0.6.12 → lghorizon-0.7.0}/setup.py +4 -4
- {lghorizon-0.6.12 → lghorizon-0.7.0}/test.py +6 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/.coverage +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/.flake8 +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/.gitignore +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/LICENSE +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/README.md +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/instructions.txt +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon/__init__.py +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon/exceptions.py +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon/helpers.py +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon/models.py +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon/py.typed +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon.egg-info/SOURCES.txt +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon.egg-info/dependency_links.txt +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon.egg-info/not-zip-safe +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lghorizon.egg-info/top_level.txt +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/lib64 +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/pyvenv.cfg +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/renovate.json +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/secrets_stub.json +0 -0
- {lghorizon-0.6.12 → lghorizon-0.7.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lghorizon
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Python client for Liberty Global Horizon settop boxes
|
|
5
5
|
Home-page: https://github.com/sholofly/LGHorizon-python
|
|
6
6
|
Author: Rudolf Offereins
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
20
20
|
Requires-Python: >=3.9
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: paho-mqtt
|
|
23
|
+
Requires-Dist: paho-mqtt<2.0.0
|
|
24
24
|
Requires-Dist: requests>=2.22.0
|
|
25
25
|
Requires-Dist: backoff>=1.9.0
|
|
26
26
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Python client for LGHorizon."""
|
|
2
|
+
|
|
2
3
|
# flake8: noqa
|
|
3
4
|
# Box states
|
|
4
5
|
ONLINE_RUNNING = "ONLINE_RUNNING"
|
|
@@ -118,9 +119,7 @@ COUNTRY_SETTINGS = {
|
|
|
118
119
|
},
|
|
119
120
|
"gb": {
|
|
120
121
|
"api_url": "https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com",
|
|
121
|
-
"oauth_url": "https://id.virginmedia.com/rest/v40/session/start?protocol=oidc&rememberMe=true",
|
|
122
122
|
"channels": [],
|
|
123
|
-
"oesp_url": "https://prod.oesp.virginmedia.com/oesp/v4/GB/eng/web",
|
|
124
123
|
"language": "en",
|
|
125
124
|
},
|
|
126
125
|
"ie": {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Python client for LGHorizon."""
|
|
2
|
+
|
|
2
3
|
import logging
|
|
3
4
|
import json
|
|
4
5
|
import sys, traceback
|
|
@@ -62,10 +63,12 @@ class LGHorizonApi:
|
|
|
62
63
|
password: str,
|
|
63
64
|
country_code: str = "nl",
|
|
64
65
|
identifier: str = None,
|
|
66
|
+
refresh_token = None,
|
|
65
67
|
) -> None:
|
|
66
68
|
"""Create LGHorizon API."""
|
|
67
69
|
self.username = username
|
|
68
70
|
self.password = password
|
|
71
|
+
self.refresh_token = refresh_token
|
|
69
72
|
self._session = Session()
|
|
70
73
|
self._country_settings = COUNTRY_SETTINGS[country_code]
|
|
71
74
|
self._country_code = country_code
|
|
@@ -112,92 +115,36 @@ class LGHorizonApi:
|
|
|
112
115
|
self._auth.fill(auth_response.json())
|
|
113
116
|
_logger.debug("Authorization succeeded")
|
|
114
117
|
|
|
115
|
-
def authorize_gb(self):
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
auth_url = f"{self._country_settings['oesp_url']}/authorization"
|
|
121
|
-
auth_response = login_session.get(auth_url)
|
|
122
|
-
if not auth_response.ok:
|
|
123
|
-
raise LGHorizonApiConnectionError("Can't connect to authorization URL")
|
|
124
|
-
auth_response_json = auth_response.json()
|
|
125
|
-
auth_session = auth_response_json["session"]
|
|
126
|
-
auth_state = auth_session["state"]
|
|
127
|
-
authorizationUri = auth_session["authorizationUri"]
|
|
128
|
-
authValidityToken = auth_session["validityToken"]
|
|
129
|
-
####################################
|
|
130
|
-
_logger.debug("Step 2 - Get Authorization cookie")
|
|
131
|
-
|
|
132
|
-
auth_cookie_response = login_session.get(authorizationUri)
|
|
133
|
-
if not auth_cookie_response.ok:
|
|
134
|
-
raise LGHorizonApiConnectionError("Can't connect to authorization URL")
|
|
135
|
-
####################################
|
|
136
|
-
_logger.debug("Step 3 - Login")
|
|
137
|
-
payload = {"username": self.username, "credential": self.password}
|
|
138
|
-
headers = {"accept": "application/json; charset=UTF-8, */*"}
|
|
118
|
+
def authorize_gb(self) -> None:
|
|
119
|
+
_logger.debug("Authorizing via refresh")
|
|
120
|
+
refresh_url = (f"{self._country_settings['api_url']}/auth-service/v1/authorization/refresh")
|
|
121
|
+
headers = {"content-type": "application/json", "charset": "utf-8"}
|
|
122
|
+
payload = '{"refreshToken":"' + self.refresh_token + '"}'
|
|
139
123
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
headers=headers,
|
|
144
|
-
allow_redirects=False,
|
|
124
|
+
try:
|
|
125
|
+
auth_response = self._session.post(
|
|
126
|
+
refresh_url, headers=headers, data=payload
|
|
145
127
|
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if not "x-redirect-location" in login_response.headers:
|
|
150
|
-
raise LGHorizonApiConnectionError("No redirect location in headers.")
|
|
128
|
+
except Exception as ex:
|
|
129
|
+
raise LGHorizonApiConnectionError("Unknown connection failure") from ex
|
|
151
130
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
raise LGHorizonApiConnectionError("
|
|
164
|
-
authorizationCode = codeMatches[0]
|
|
165
|
-
stateMatches = re.findall(r"state=(.*)", success_url)
|
|
166
|
-
if len(codeMatches) == 0:
|
|
167
|
-
raise LGHorizonApiConnectionError("No state in redirect headers")
|
|
168
|
-
authorizationState = stateMatches[0]
|
|
169
|
-
_logger.debug(
|
|
170
|
-
f"Auth code: {authorizationCode}, Auth state: {authorizationState}"
|
|
171
|
-
)
|
|
172
|
-
####################################
|
|
173
|
-
_logger.debug("Step 6 - Post auth data with valid code")
|
|
174
|
-
authorization_payload = {
|
|
175
|
-
"authorizationGrant": {
|
|
176
|
-
"authorizationCode": authorizationCode,
|
|
177
|
-
"validityToken": authValidityToken,
|
|
178
|
-
"state": authorizationState,
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
headers = {
|
|
182
|
-
"content-type": "application/json",
|
|
183
|
-
}
|
|
184
|
-
# VM requires the client to pass the response from /authorization verbatim to /session?token=true
|
|
185
|
-
post_authorization_result = login_session.post(
|
|
186
|
-
self._country_settings["oesp_url"] + "/authorization",
|
|
187
|
-
json.dumps(authorization_payload),
|
|
188
|
-
headers=headers,
|
|
189
|
-
)
|
|
190
|
-
post_session_result = login_session.post(
|
|
191
|
-
self._country_settings["oesp_url"] + "/session?token=true",
|
|
192
|
-
json.dumps(post_authorization_result.json()),
|
|
193
|
-
headers=headers,
|
|
194
|
-
)
|
|
131
|
+
if not auth_response.ok:
|
|
132
|
+
_logger.debug("response %s", auth_response)
|
|
133
|
+
error_json = auth_response.json()
|
|
134
|
+
error = None
|
|
135
|
+
if "error" in error_json:
|
|
136
|
+
error = error_json["error"]
|
|
137
|
+
if error and error["statusCode"] == 97401:
|
|
138
|
+
raise LGHorizonApiUnauthorizedError("Invalid credentials")
|
|
139
|
+
elif error:
|
|
140
|
+
raise LGHorizonApiConnectionError(error["message"])
|
|
141
|
+
else:
|
|
142
|
+
raise LGHorizonApiConnectionError("Unknown connection error")
|
|
195
143
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
pass
|
|
144
|
+
self._auth.fill(auth_response.json())
|
|
145
|
+
self.refresh_token = self._auth.refreshToken
|
|
146
|
+
self._session.cookies["ACCESSTOKEN"] = self._auth.accessToken
|
|
147
|
+
_logger.debug("Authorization succeeded")
|
|
201
148
|
|
|
202
149
|
def authorize_telenet(self):
|
|
203
150
|
try:
|
|
@@ -275,17 +222,6 @@ class LGHorizonApi:
|
|
|
275
222
|
self._auth.mqttToken = mqtt_response["token"]
|
|
276
223
|
_logger.debug(f"MQTT token: {self._auth.mqttToken}")
|
|
277
224
|
|
|
278
|
-
def _obtain_mqtt_token_gb(self):
|
|
279
|
-
_logger.debug("Obtain Virgin GB mqtt token...")
|
|
280
|
-
self._session.headers["x-oesp-token"] = self._auth.accessToken
|
|
281
|
-
self._session.headers["x-oesp-username"] = self._auth.username
|
|
282
|
-
|
|
283
|
-
mqtt_response = self._do_api_call(
|
|
284
|
-
f"{self._country_settings['oesp_url']}/tokens/jwt"
|
|
285
|
-
)
|
|
286
|
-
self._auth.mqttToken = mqtt_response["token"]
|
|
287
|
-
_logger.debug(f"MQTT token: {self._auth.mqttToken}")
|
|
288
|
-
|
|
289
225
|
@backoff.on_exception(
|
|
290
226
|
backoff.expo, BaseException, jitter=None, max_time=600, logger=_logger
|
|
291
227
|
)
|
|
@@ -293,10 +229,7 @@ class LGHorizonApi:
|
|
|
293
229
|
self._config = self._get_config(self._country_code)
|
|
294
230
|
_logger.debug("Connect to API")
|
|
295
231
|
self._authorize()
|
|
296
|
-
|
|
297
|
-
self._obtain_mqtt_token_gb()
|
|
298
|
-
else:
|
|
299
|
-
self._obtain_mqtt_token()
|
|
232
|
+
self._obtain_mqtt_token()
|
|
300
233
|
self._mqttClient = LGHorizonMqttClient(
|
|
301
234
|
self._auth,
|
|
302
235
|
self._config["mqttBroker"]["URL"],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: lghorizon
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Python client for Liberty Global Horizon settop boxes
|
|
5
5
|
Home-page: https://github.com/sholofly/LGHorizon-python
|
|
6
6
|
Author: Rudolf Offereins
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
20
20
|
Requires-Python: >=3.9
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
-
Requires-Dist: paho-mqtt
|
|
23
|
+
Requires-Dist: paho-mqtt<2.0.0
|
|
24
24
|
Requires-Dist: requests>=2.22.0
|
|
25
25
|
Requires-Dist: backoff>=1.9.0
|
|
26
26
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""Set up LG Horizon."""
|
|
2
|
+
|
|
2
3
|
import setuptools
|
|
3
4
|
import os
|
|
4
5
|
import re
|
|
@@ -8,7 +9,6 @@ with open("README.md", "r") as fh:
|
|
|
8
9
|
long_description = fh.read()
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
|
|
12
12
|
def read(*parts):
|
|
13
13
|
"""Read file."""
|
|
14
14
|
filename = os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts)
|
|
@@ -27,8 +27,8 @@ setuptools.setup(
|
|
|
27
27
|
url="https://github.com/sholofly/LGHorizon-python",
|
|
28
28
|
packages=setuptools.find_packages(include=["lghorizon"]),
|
|
29
29
|
license="MIT license",
|
|
30
|
-
install_requires=["paho-mqtt
|
|
31
|
-
keywords=["LG","Horizon", "API", "Settop box"],
|
|
30
|
+
install_requires=["paho-mqtt<2.0.0", "requests>=2.22.0", "backoff>=1.9.0"],
|
|
31
|
+
keywords=["LG", "Horizon", "API", "Settop box"],
|
|
32
32
|
classifiers=[
|
|
33
33
|
"Development Status :: 3 - Alpha",
|
|
34
34
|
"Programming Language :: Python :: 3",
|
|
@@ -44,5 +44,5 @@ setuptools.setup(
|
|
|
44
44
|
python_requires=">=3.9",
|
|
45
45
|
zip_safe=False,
|
|
46
46
|
use_scm_version=True,
|
|
47
|
-
setup_requires=[
|
|
47
|
+
setup_requires=["setuptools_scm"],
|
|
48
48
|
)
|
|
@@ -52,11 +52,17 @@ if __name__ == "__main__":
|
|
|
52
52
|
try:
|
|
53
53
|
secrets_file_path = "secrets.json"
|
|
54
54
|
secrets = read_secrets(secrets_file_path)
|
|
55
|
+
|
|
56
|
+
refresh_token = None
|
|
57
|
+
if "refresh_token" in secrets:
|
|
58
|
+
refresh_token = secrets["refresh_token"]
|
|
59
|
+
|
|
55
60
|
api = LGHorizonApi(
|
|
56
61
|
secrets["username"],
|
|
57
62
|
secrets["password"],
|
|
58
63
|
secrets["country"],
|
|
59
64
|
# identifier="DTV3907048",
|
|
65
|
+
refresh_token = refresh_token,
|
|
60
66
|
)
|
|
61
67
|
api.connect()
|
|
62
68
|
event_loop()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|