lghorizon 0.7.2__py3-none-any.whl → 0.7.3__py3-none-any.whl
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/__init__.py +21 -3
- lghorizon/exceptions.py +10 -4
- lghorizon/lghorizon_api.py +60 -25
- lghorizon/models.py +21 -3
- {lghorizon-0.7.2.dist-info → lghorizon-0.7.3.dist-info}/METADATA +1 -1
- lghorizon-0.7.3.dist-info/RECORD +12 -0
- lghorizon-0.7.2.dist-info/RECORD +0 -12
- {lghorizon-0.7.2.dist-info → lghorizon-0.7.3.dist-info}/LICENSE +0 -0
- {lghorizon-0.7.2.dist-info → lghorizon-0.7.3.dist-info}/WHEEL +0 -0
- {lghorizon-0.7.2.dist-info → lghorizon-0.7.3.dist-info}/top_level.txt +0 -0
lghorizon/__init__.py
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
"""Python client for LG Horizon."""
|
|
2
|
+
|
|
2
3
|
from .lghorizon_api import LGHorizonApi
|
|
3
|
-
from .models import
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
from .models import (
|
|
5
|
+
LGHorizonBox,
|
|
6
|
+
LGHorizonRecordingListSeasonShow,
|
|
7
|
+
LGHorizonRecordingSingle,
|
|
8
|
+
LGHorizonRecordingShow,
|
|
9
|
+
LGHorizonRecordingEpisode,
|
|
10
|
+
LGHorizonCustomer,
|
|
11
|
+
)
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
LGHorizonApiUnauthorizedError,
|
|
14
|
+
LGHorizonApiConnectionError,
|
|
15
|
+
LGHorizonApiLockedError,
|
|
16
|
+
)
|
|
17
|
+
from .const import (
|
|
18
|
+
ONLINE_RUNNING,
|
|
19
|
+
ONLINE_STANDBY,
|
|
20
|
+
RECORDING_TYPE_SHOW,
|
|
21
|
+
RECORDING_TYPE_SEASON,
|
|
22
|
+
RECORDING_TYPE_SINGLE,
|
|
23
|
+
) # noqa
|
lghorizon/exceptions.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"""Exceptions for the LGHorizon API."""
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
class LGHorizonApiError(Exception):
|
|
4
|
-
"""Generic
|
|
5
|
+
"""Generic LGHorizon exception."""
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class LGHorizonApiConnectionError(LGHorizonApiError):
|
|
7
|
-
"""Generic
|
|
9
|
+
"""Generic LGHorizon exception."""
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
class LGHorizonApiUnauthorizedError(Exception):
|
|
10
|
-
"""Generic
|
|
11
|
-
|
|
13
|
+
"""Generic LGHorizon exception."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LGHorizonApiLockedError(LGHorizonApiUnauthorizedError):
|
|
17
|
+
"""Generic LGHorizon exception."""
|
lghorizon/lghorizon_api.py
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import json
|
|
5
5
|
import sys, traceback
|
|
6
|
-
from .exceptions import
|
|
6
|
+
from .exceptions import (
|
|
7
|
+
LGHorizonApiUnauthorizedError,
|
|
8
|
+
LGHorizonApiConnectionError,
|
|
9
|
+
LGHorizonApiLockedError,
|
|
10
|
+
)
|
|
7
11
|
import backoff
|
|
8
12
|
from requests import Session, exceptions as request_exceptions
|
|
9
13
|
from paho.mqtt.client import WebsocketConnectionError
|
|
@@ -47,7 +51,7 @@ class LGHorizonApi:
|
|
|
47
51
|
_auth: LGHorizonAuth = None
|
|
48
52
|
_session: Session = None
|
|
49
53
|
settop_boxes: Dict[str, LGHorizonBox] = None
|
|
50
|
-
|
|
54
|
+
customer: LGHorizonCustomer = None
|
|
51
55
|
_mqttClient: LGHorizonMqttClient = None
|
|
52
56
|
_channels: Dict[str, LGHorizonChannel] = None
|
|
53
57
|
_country_settings = None
|
|
@@ -57,6 +61,7 @@ class LGHorizonApi:
|
|
|
57
61
|
_identifier: str = None
|
|
58
62
|
_config: str = None
|
|
59
63
|
_refresh_callback: Callable = None
|
|
64
|
+
_profile_id: str = None
|
|
60
65
|
|
|
61
66
|
def __init__(
|
|
62
67
|
self,
|
|
@@ -65,6 +70,7 @@ class LGHorizonApi:
|
|
|
65
70
|
country_code: str = "nl",
|
|
66
71
|
identifier: str = None,
|
|
67
72
|
refresh_token=None,
|
|
73
|
+
profile_id=None,
|
|
68
74
|
) -> None:
|
|
69
75
|
"""Create LGHorizon API."""
|
|
70
76
|
self.username = username
|
|
@@ -78,10 +84,8 @@ class LGHorizonApi:
|
|
|
78
84
|
self._channels = {}
|
|
79
85
|
self._entitlements = []
|
|
80
86
|
self._identifier = identifier
|
|
87
|
+
self._profile_id = profile_id
|
|
81
88
|
|
|
82
|
-
@backoff.on_exception(
|
|
83
|
-
backoff.expo, LGHorizonApiConnectionError, max_tries=3, logger=_logger
|
|
84
|
-
)
|
|
85
89
|
def _authorize(self) -> None:
|
|
86
90
|
ctry_code = self._country_code[0:2]
|
|
87
91
|
if ctry_code == "be":
|
|
@@ -108,6 +112,8 @@ class LGHorizonApi:
|
|
|
108
112
|
error = error_json["error"]
|
|
109
113
|
if error and error["statusCode"] == 97401:
|
|
110
114
|
raise LGHorizonApiUnauthorizedError("Invalid credentials")
|
|
115
|
+
elif error and error["statusCode"] == 97117:
|
|
116
|
+
raise LGHorizonApiLockedError("Account locked")
|
|
111
117
|
elif error:
|
|
112
118
|
raise LGHorizonApiConnectionError(error["message"])
|
|
113
119
|
else:
|
|
@@ -149,7 +155,7 @@ class LGHorizonApi:
|
|
|
149
155
|
self._session.cookies["ACCESSTOKEN"] = self._auth.accessToken
|
|
150
156
|
|
|
151
157
|
if self._refresh_callback:
|
|
152
|
-
self._refresh_callback
|
|
158
|
+
self._refresh_callback()
|
|
153
159
|
|
|
154
160
|
_logger.debug("Authorization succeeded")
|
|
155
161
|
|
|
@@ -233,7 +239,14 @@ class LGHorizonApi:
|
|
|
233
239
|
_logger.debug(f"MQTT token: {self._auth.mqttToken}")
|
|
234
240
|
|
|
235
241
|
@backoff.on_exception(
|
|
236
|
-
backoff.expo,
|
|
242
|
+
backoff.expo,
|
|
243
|
+
BaseException,
|
|
244
|
+
jitter=None,
|
|
245
|
+
max_tries=3,
|
|
246
|
+
logger=_logger,
|
|
247
|
+
giveup=lambda e: isinstance(
|
|
248
|
+
e, (LGHorizonApiLockedError, LGHorizonApiUnauthorizedError)
|
|
249
|
+
),
|
|
237
250
|
)
|
|
238
251
|
def connect(self) -> None:
|
|
239
252
|
self._config = self._get_config(self._country_code)
|
|
@@ -246,13 +259,14 @@ class LGHorizonApi:
|
|
|
246
259
|
self._on_mqtt_connected,
|
|
247
260
|
self._on_mqtt_message,
|
|
248
261
|
)
|
|
262
|
+
|
|
249
263
|
self._register_customer_and_boxes()
|
|
250
264
|
self._mqttClient.connect()
|
|
251
265
|
|
|
252
266
|
def disconnect(self):
|
|
253
267
|
"""Disconnect."""
|
|
254
268
|
_logger.debug("Disconnect from API")
|
|
255
|
-
if not self._mqttClient.is_connected:
|
|
269
|
+
if not self._mqttClient or not self._mqttClient.is_connected:
|
|
256
270
|
return
|
|
257
271
|
self._mqttClient.disconnect()
|
|
258
272
|
|
|
@@ -263,9 +277,16 @@ class LGHorizonApi:
|
|
|
263
277
|
box.register_mqtt()
|
|
264
278
|
|
|
265
279
|
def _on_mqtt_message(self, message: str, topic: str) -> None:
|
|
266
|
-
if "
|
|
280
|
+
if "action" in message and message["action"] == "OPS.getProfilesUpdate":
|
|
281
|
+
self._update_customer()
|
|
282
|
+
self._channels.clear()
|
|
283
|
+
self._get_channels()
|
|
284
|
+
box: LGHorizonBox
|
|
285
|
+
for box in self.settop_boxes.values():
|
|
286
|
+
box.update_channels(self._channels)
|
|
287
|
+
elif "source" in message:
|
|
267
288
|
deviceId = message["source"]
|
|
268
|
-
if not isinstance(deviceId,str):
|
|
289
|
+
if not isinstance(deviceId, str):
|
|
269
290
|
_logger.debug("ignoring message - not a string")
|
|
270
291
|
return
|
|
271
292
|
if not deviceId in self.settop_boxes.keys():
|
|
@@ -275,6 +296,7 @@ class LGHorizonApi:
|
|
|
275
296
|
self.settop_boxes[deviceId].update_state(message)
|
|
276
297
|
if "status" in message:
|
|
277
298
|
self._handle_box_update(deviceId, message)
|
|
299
|
+
|
|
278
300
|
except Exception:
|
|
279
301
|
_logger.exception("Could not handle status message")
|
|
280
302
|
_logger.warning(f"Full message: {str(message)}")
|
|
@@ -346,7 +368,7 @@ class LGHorizonApi:
|
|
|
346
368
|
last_speed_change_time = playerState["lastSpeedChangeTime"]
|
|
347
369
|
relative_position = playerState["relativePosition"]
|
|
348
370
|
raw_vod = self._do_api_call(
|
|
349
|
-
f"{self._config['vodService']['URL']}/v2/detailscreen/{titleId}?language={self._country_settings['language']}&profileId=4504e28d-c1cb-4284-810b-f5eaab06f034&cityId={self.
|
|
371
|
+
f"{self._config['vodService']['URL']}/v2/detailscreen/{titleId}?language={self._country_settings['language']}&profileId=4504e28d-c1cb-4284-810b-f5eaab06f034&cityId={self.customer.cityId}"
|
|
350
372
|
)
|
|
351
373
|
vod = LGHorizonVod(raw_vod)
|
|
352
374
|
self.settop_boxes[deviceId].update_with_vod(
|
|
@@ -374,40 +396,50 @@ class LGHorizonApi:
|
|
|
374
396
|
return json_response
|
|
375
397
|
|
|
376
398
|
def _register_customer_and_boxes(self):
|
|
377
|
-
|
|
378
|
-
personalisation_result = self._do_api_call(
|
|
379
|
-
f"{self._config['personalizationService']['URL']}/v1/customer/{self._auth.householdId}?with=profiles%2Cdevices"
|
|
380
|
-
)
|
|
381
|
-
_logger.debug(f"Personalisation result: {personalisation_result}")
|
|
382
|
-
self._customer = LGHorizonCustomer(personalisation_result)
|
|
399
|
+
self._update_customer()
|
|
383
400
|
self._get_channels()
|
|
384
|
-
if
|
|
401
|
+
if len(self.customer.settop_boxes) == 0:
|
|
385
402
|
_logger.warning("No boxes found.")
|
|
386
403
|
return
|
|
387
404
|
_logger.info("Registering boxes")
|
|
388
|
-
for device in
|
|
405
|
+
for device in self.customer.settop_boxes:
|
|
389
406
|
platform_type = device["platformType"]
|
|
390
|
-
if not
|
|
407
|
+
if platform_type not in _supported_platforms:
|
|
391
408
|
continue
|
|
392
409
|
if (
|
|
393
410
|
"platform_types" in self._country_settings
|
|
394
411
|
and platform_type in self._country_settings["platform_types"]
|
|
395
412
|
):
|
|
396
|
-
|
|
413
|
+
platform_type = self._country_settings["platform_types"][platform_type]
|
|
397
414
|
else:
|
|
398
|
-
|
|
415
|
+
platform_type = None
|
|
399
416
|
box = LGHorizonBox(
|
|
400
|
-
device,
|
|
417
|
+
device, platform_type, self._mqttClient, self._auth, self._channels
|
|
401
418
|
)
|
|
402
419
|
self.settop_boxes[box.deviceId] = box
|
|
403
|
-
_logger.info(
|
|
420
|
+
_logger.info("Box %s registered...", box.deviceId)
|
|
421
|
+
|
|
422
|
+
def _update_customer(self):
|
|
423
|
+
_logger.info("Get customer data")
|
|
424
|
+
personalisation_result = self._do_api_call(
|
|
425
|
+
f"{self._config['personalizationService']['URL']}/v1/customer/{self._auth.householdId}?with=profiles%2Cdevices"
|
|
426
|
+
)
|
|
427
|
+
_logger.debug("Personalisation result: %s ", personalisation_result)
|
|
428
|
+
self.customer = LGHorizonCustomer(personalisation_result)
|
|
404
429
|
|
|
405
430
|
def _get_channels(self):
|
|
406
431
|
self._update_entitlements()
|
|
407
432
|
_logger.info("Retrieving channels...")
|
|
408
433
|
channels_result = self._do_api_call(
|
|
409
|
-
f"{self._config['linearService']['URL']}/v2/channels?cityId={self.
|
|
434
|
+
f"{self._config['linearService']['URL']}/v2/channels?cityId={self.customer.cityId}&language={self._country_settings['language']}&productClass=Orion-DASH"
|
|
410
435
|
)
|
|
436
|
+
profile_channels = []
|
|
437
|
+
if self._profile_id and self._profile_id in self.customer.profiles:
|
|
438
|
+
profile_channels = self.customer.profiles[
|
|
439
|
+
self._profile_id
|
|
440
|
+
].favorite_channels
|
|
441
|
+
|
|
442
|
+
has_profile_channels = len(profile_channels) > 0
|
|
411
443
|
for channel in channels_result:
|
|
412
444
|
if "isRadio" in channel and channel["isRadio"]:
|
|
413
445
|
continue
|
|
@@ -417,6 +449,9 @@ class LGHorizonApi:
|
|
|
417
449
|
if len(common_entitlements) == 0:
|
|
418
450
|
continue
|
|
419
451
|
channel_id = channel["id"]
|
|
452
|
+
if has_profile_channels and channel_id not in profile_channels:
|
|
453
|
+
continue
|
|
454
|
+
|
|
420
455
|
self._channels[channel_id] = LGHorizonChannel(channel)
|
|
421
456
|
_logger.info(f"{len(self._channels)} retrieved.")
|
|
422
457
|
|
lghorizon/models.py
CHANGED
|
@@ -438,6 +438,9 @@ class LGHorizonBox:
|
|
|
438
438
|
self.manufacturer = platform_type["manufacturer"]
|
|
439
439
|
self.model = platform_type["model"]
|
|
440
440
|
|
|
441
|
+
def update_channels(self, channels: Dict[str, LGHorizonChannel]):
|
|
442
|
+
self._channels = channels
|
|
443
|
+
|
|
441
444
|
def register_mqtt(self) -> None:
|
|
442
445
|
if not self._mqtt_client.is_connected:
|
|
443
446
|
raise Exception("MQTT client not connected.")
|
|
@@ -677,17 +680,32 @@ class LGHorizonBox:
|
|
|
677
680
|
self._mqtt_client.publish_message(topic, json.dumps(payload))
|
|
678
681
|
|
|
679
682
|
|
|
683
|
+
class LGHorizonProfile:
|
|
684
|
+
profile_id: str = None
|
|
685
|
+
name: str = None
|
|
686
|
+
favorite_channels: [] = None
|
|
687
|
+
|
|
688
|
+
def __init__(self, json_payload):
|
|
689
|
+
self.profile_id = json_payload["profileId"]
|
|
690
|
+
self.name = json_payload["name"]
|
|
691
|
+
self.favorite_channels = json_payload["favoriteChannels"]
|
|
692
|
+
|
|
693
|
+
|
|
680
694
|
class LGHorizonCustomer:
|
|
681
695
|
customerId: str = None
|
|
682
696
|
hashedCustomerId: str = None
|
|
683
697
|
countryId: str = None
|
|
684
698
|
cityId: int = 0
|
|
685
|
-
settop_boxes:
|
|
699
|
+
settop_boxes: [] = None
|
|
700
|
+
profiles: Dict[str, LGHorizonProfile] = {}
|
|
686
701
|
|
|
687
702
|
def __init__(self, json_payload):
|
|
688
703
|
self.customerId = json_payload["customerId"]
|
|
689
704
|
self.hashedCustomerId = json_payload["hashedCustomerId"]
|
|
690
705
|
self.countryId = json_payload["countryId"]
|
|
691
706
|
self.cityId = json_payload["cityId"]
|
|
692
|
-
if
|
|
693
|
-
|
|
707
|
+
if "assignedDevices" in json_payload:
|
|
708
|
+
self.settop_boxes = json_payload["assignedDevices"]
|
|
709
|
+
if "profiles" in json_payload:
|
|
710
|
+
for profile in json_payload["profiles"]:
|
|
711
|
+
self.profiles[profile["profileId"]] = LGHorizonProfile(profile)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
lghorizon/__init__.py,sha256=XqePG0hj8NnudOcfCtEWqPsWP5l2headzrQhkfuDlv8,544
|
|
2
|
+
lghorizon/const.py,sha256=S8UWatG7JPCqd1zk-iOoILTvFDEj7YhLcEXxLGhyJXs,4755
|
|
3
|
+
lghorizon/exceptions.py,sha256=-6v55KDTogBldGAg1wV9Mrxm5L5BsaVguhBgVMOeJHk,404
|
|
4
|
+
lghorizon/helpers.py,sha256=ZWpi7B3hBvwGV02KWQQHVyj7FLLUDtIvKc-Iqsj5VHA,263
|
|
5
|
+
lghorizon/lghorizon_api.py,sha256=PcC-zmy_VK5WgUQChnwySE9CE5LPP1E3HXA_Pp_ExMg,21956
|
|
6
|
+
lghorizon/models.py,sha256=S_Pq8z9PpaN10b17aoi2j-mCIdZf-TK411pBGRPDkvc,24542
|
|
7
|
+
lghorizon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
lghorizon-0.7.3.dist-info/LICENSE,sha256=6Dh2tur1gMX3r3rITjVwUONBEJxyyPZDY8p6DZXtimE,1059
|
|
9
|
+
lghorizon-0.7.3.dist-info/METADATA,sha256=VNpdXkA8YGvC7xQEXzI2y1IsyT03MsRVoWG2Q6L7078,1037
|
|
10
|
+
lghorizon-0.7.3.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
|
11
|
+
lghorizon-0.7.3.dist-info/top_level.txt,sha256=usii76_AxGfPI6gjrrh-NyZxcQQuF1B8_Q9kd7sID8Q,10
|
|
12
|
+
lghorizon-0.7.3.dist-info/RECORD,,
|
lghorizon-0.7.2.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
lghorizon/__init__.py,sha256=_VjVE44ErvJJMnF5QgXdlw_nQzbHZUhGWw5hF40PolQ,426
|
|
2
|
-
lghorizon/const.py,sha256=S8UWatG7JPCqd1zk-iOoILTvFDEj7YhLcEXxLGhyJXs,4755
|
|
3
|
-
lghorizon/exceptions.py,sha256=spEjRvbNdce2fauQiOFromAbV1QcfA0uMUt0nRVnnkM,318
|
|
4
|
-
lghorizon/helpers.py,sha256=ZWpi7B3hBvwGV02KWQQHVyj7FLLUDtIvKc-Iqsj5VHA,263
|
|
5
|
-
lghorizon/lghorizon_api.py,sha256=5jv06pO7vnLd7ddY0ag1KY6ZGqUNKQULlwYRqqJXjFE,20899
|
|
6
|
-
lghorizon/models.py,sha256=yrKlQMnHCy8FHwCel3lcTUEnd7_JQbETqf5yGW3S84o,23893
|
|
7
|
-
lghorizon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
lghorizon-0.7.2.dist-info/LICENSE,sha256=6Dh2tur1gMX3r3rITjVwUONBEJxyyPZDY8p6DZXtimE,1059
|
|
9
|
-
lghorizon-0.7.2.dist-info/METADATA,sha256=41HST2UBGXTFeSiUe79wb94FlFwpx5qobCFVS7SHC4w,1037
|
|
10
|
-
lghorizon-0.7.2.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
|
11
|
-
lghorizon-0.7.2.dist-info/top_level.txt,sha256=usii76_AxGfPI6gjrrh-NyZxcQQuF1B8_Q9kd7sID8Q,10
|
|
12
|
-
lghorizon-0.7.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|