lghorizon 0.9.0b0__tar.gz → 0.9.0.dev4__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.
Files changed (39) hide show
  1. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/.gitignore +0 -1
  2. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/PKG-INFO +1 -1
  3. lghorizon-0.9.0.dev4/lghorizon/__init__.py +41 -0
  4. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon/const.py +4 -11
  5. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon/helpers.py +1 -1
  6. {lghorizon-0.9.0b0/lghorizon/legacy → lghorizon-0.9.0.dev4/lghorizon}/lghorizon_api.py +70 -0
  7. {lghorizon-0.9.0b0/lghorizon/legacy → lghorizon-0.9.0.dev4/lghorizon}/models.py +8 -8
  8. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon.egg-info/PKG-INFO +1 -1
  9. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon.egg-info/SOURCES.txt +3 -11
  10. lghorizon-0.9.0.dev4/test.py +84 -0
  11. lghorizon-0.9.0b0/.vscode/launch.json +0 -15
  12. lghorizon-0.9.0b0/lghorizon/__init__.py +0 -6
  13. lghorizon-0.9.0b0/lghorizon/lghorizon_api.py +0 -270
  14. lghorizon-0.9.0b0/lghorizon/lghorizon_device.py +0 -336
  15. lghorizon-0.9.0b0/lghorizon/lghorizon_device_state_processor.py +0 -301
  16. lghorizon-0.9.0b0/lghorizon/lghorizon_message_factory.py +0 -39
  17. lghorizon-0.9.0b0/lghorizon/lghorizon_models.py +0 -1331
  18. lghorizon-0.9.0b0/lghorizon/lghorizon_mqtt_client.py +0 -123
  19. lghorizon-0.9.0b0/lghorizon/lghorizon_recording_factory.py +0 -41
  20. lghorizon-0.9.0b0/main.py +0 -94
  21. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/.coverage +0 -0
  22. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/.flake8 +0 -0
  23. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/.github/workflows/build-on-pr.yml +0 -0
  24. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/.github/workflows/publish-to-pypi.yml +0 -0
  25. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/LICENSE +0 -0
  26. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/README.md +0 -0
  27. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/instructions.txt +0 -0
  28. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon/exceptions.py +0 -0
  29. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon/py.typed +0 -0
  30. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon.egg-info/dependency_links.txt +0 -0
  31. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon.egg-info/not-zip-safe +0 -0
  32. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon.egg-info/requires.txt +0 -0
  33. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lghorizon.egg-info/top_level.txt +0 -0
  34. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/lib64 +0 -0
  35. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/pyvenv.cfg +0 -0
  36. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/renovate.json +0 -0
  37. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/secrets_stub.json +0 -0
  38. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/setup.cfg +0 -0
  39. {lghorizon-0.9.0b0 → lghorizon-0.9.0.dev4}/setup.py +0 -0
@@ -24,4 +24,3 @@ __pycache__/*
24
24
  test/nl.py
25
25
  secrets.json
26
26
  logfile.log
27
- lghorizon.log
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lghorizon
3
- Version: 0.9.0b0
3
+ Version: 0.9.0.dev4
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
@@ -0,0 +1,41 @@
1
+ """Python client for LG Horizon."""
2
+
3
+ from .lghorizon_api import LGHorizonApi
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
+ )
24
+
25
+ __all__ = [
26
+ "LGHorizonApi",
27
+ "LGHorizonBox",
28
+ "LGHorizonRecordingListSeasonShow",
29
+ "LGHorizonRecordingSingle",
30
+ "LGHorizonRecordingShow",
31
+ "LGHorizonRecordingEpisode",
32
+ "LGHorizonCustomer",
33
+ "LGHorizonApiUnauthorizedError",
34
+ "LGHorizonApiConnectionError",
35
+ "LGHorizonApiLockedError",
36
+ "ONLINE_RUNNING",
37
+ "ONLINE_STANDBY",
38
+ "RECORDING_TYPE_SHOW",
39
+ "RECORDING_TYPE_SEASON",
40
+ "RECORDING_TYPE_SINGLE",
41
+ ] # noqa
@@ -38,18 +38,11 @@ RECORDING_TYPE_SEASON = "season"
38
38
 
39
39
  BE_AUTH_URL = "https://login.prd.telenet.be/openid/login.do"
40
40
 
41
- PLATFORM_TYPES = {
42
- "EOS": {"manufacturer": "Arris", "model": "DCX960"},
43
- "EOS2": {"manufacturer": "HUMAX", "model": "2008C-STB-TN"},
44
- "HORIZON": {"manufacturer": "Arris", "model": "DCX960"},
45
- "APOLLO": {"manufacturer": "Arris", "model": "VIP5002W"},
46
- }
47
-
48
41
  COUNTRY_SETTINGS = {
49
42
  "nl": {
50
43
  "api_url": "https://spark-prod-nl.gnp.cloud.ziggogo.tv",
51
44
  "mqtt_url": "obomsg.prod.nl.horizon.tv",
52
- "use_refreshtoken": False,
45
+ "use_oauth": False,
53
46
  "channels": [
54
47
  {
55
48
  "channelId": "NL_000073_019506",
@@ -113,7 +106,7 @@ COUNTRY_SETTINGS = {
113
106
  },
114
107
  "be-nl-preprod": {
115
108
  "api_url": "https://spark-preprod-be.gnp.cloud.telenet.tv",
116
- "use_refreshtoken": True,
109
+ "use_oauth": True,
117
110
  "oauth_username_fieldname": "j_username",
118
111
  "oauth_password_fieldname": "j_password",
119
112
  "oauth_add_accept_header": False,
@@ -138,13 +131,13 @@ COUNTRY_SETTINGS = {
138
131
  },
139
132
  "ie": {
140
133
  "api_url": "https://spark-prod-ie.gnp.cloud.virginmediatv.ie",
141
- "use_refreshtoken": False,
134
+ "use_oauth": False,
142
135
  "channels": [],
143
136
  "language": "en",
144
137
  },
145
138
  "pl": {
146
139
  "api_url": "https://spark-prod-pl.gnp.cloud.upctv.pl",
147
- "use_refreshtoken": False,
140
+ "use_oauth": False,
148
141
  "channels": [],
149
142
  "language": "pl",
150
143
  "platform_types": {
@@ -3,7 +3,7 @@
3
3
  import random
4
4
 
5
5
 
6
- async def make_id(string_length=10):
6
+ def make_id(string_length=10):
7
7
  """Create an id with given length."""
8
8
  letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
9
9
  return "".join(random.choice(letters) for i in range(string_length))
@@ -167,6 +167,76 @@ class LGHorizonApi:
167
167
  """Set the refresh callback."""
168
168
  self._refresh_callback = refresh_callback
169
169
 
170
+ def _authorize_telenet(self):
171
+ """Authorize telenet users."""
172
+ try:
173
+ login_session = Session()
174
+ # Step 1 - Get Authorization data
175
+ _logger.debug("Step 1 - Get Authorization data")
176
+ auth_url = (
177
+ f"{self._country_settings['api_url']}/auth-service/v1/sso/authorization"
178
+ )
179
+ auth_response = login_session.get(auth_url)
180
+ if not auth_response.ok:
181
+ raise LGHorizonApiConnectionError("Can't connect to authorization URL")
182
+ auth_response_json = auth_response.json()
183
+ authorization_uri = auth_response_json["authorizationUri"]
184
+ authorization_validity_token = auth_response_json["validityToken"]
185
+
186
+ # Step 2 - Get Authorization cookie
187
+ _logger.debug("Step 2 - Get Authorization cookie")
188
+
189
+ auth_cookie_response = login_session.get(authorization_uri)
190
+ if not auth_cookie_response.ok:
191
+ raise LGHorizonApiConnectionError("Can't connect to authorization URL")
192
+
193
+ _logger.debug("Step 3 - Login")
194
+
195
+ username_fieldname = self._country_settings["oauth_username_fieldname"]
196
+ pasword_fieldname = self._country_settings["oauth_password_fieldname"]
197
+
198
+ payload = {
199
+ username_fieldname: self.username,
200
+ pasword_fieldname: self.password,
201
+ "rememberme": "true",
202
+ }
203
+
204
+ login_response = login_session.post(
205
+ self._country_settings["oauth_url"], payload, allow_redirects=False
206
+ )
207
+ if not login_response.ok:
208
+ raise LGHorizonApiConnectionError("Can't connect to authorization URL")
209
+ redirect_url = login_response.headers[
210
+ self._country_settings["oauth_redirect_header"]
211
+ ]
212
+
213
+ if self._identifier is not None:
214
+ redirect_url += f"&dtv_identifier={self._identifier}"
215
+ redirect_response = login_session.get(redirect_url, allow_redirects=False)
216
+ success_url = redirect_response.headers[
217
+ self._country_settings["oauth_redirect_header"]
218
+ ]
219
+ code_matches = re.findall(r"code=(.*)&", success_url)
220
+
221
+ authorization_code = code_matches[0]
222
+
223
+ new_payload = {
224
+ "authorizationGrant": {
225
+ "authorizationCode": authorization_code,
226
+ "validityToken": authorization_validity_token,
227
+ }
228
+ }
229
+ headers = {
230
+ "content-type": "application/json",
231
+ }
232
+ post_result = login_session.post(
233
+ auth_url, json.dumps(new_payload), headers=headers
234
+ )
235
+ self._auth.fill(post_result.json())
236
+ self._session.cookies["ACCESSTOKEN"] = self._auth.access_token
237
+ except Exception:
238
+ pass
239
+
170
240
  def _obtain_mqtt_token(self):
171
241
  _logger.debug("Obtain mqtt token...")
172
242
  mqtt_auth_url = self._config["authorizationService"]["URL"]
@@ -703,14 +703,14 @@ class LGHorizonBox:
703
703
  f"{self._auth.household_id}/{self.device_id}", payload
704
704
  )
705
705
 
706
- # def _set_unknown_channel_info(self) -> None:
707
- # """Set unknown channel info."""
708
- # _logger.warning("Couldn't set channel. Channel info set to unknown...")
709
- # self.playing_info.set_source_type(BOX_PLAY_STATE_CHANNEL)
710
- # self.playing_info.set_channel(None)
711
- # self.playing_info.set_title("No information available")
712
- # self.playing_info.set_image(None)
713
- # self.playing_info.set_paused(False)
706
+ def _set_unknown_channel_info(self) -> None:
707
+ """Set unknown channel info."""
708
+ _logger.warning("Couldn't set channel. Channel info set to unknown...")
709
+ self.playing_info.set_source_type(BOX_PLAY_STATE_CHANNEL)
710
+ self.playing_info.set_channel(None)
711
+ self.playing_info.set_title("No information available")
712
+ self.playing_info.set_image(None)
713
+ self.playing_info.set_paused(False)
714
714
 
715
715
  def _request_settop_box_state(self) -> None:
716
716
  """Send mqtt message to receive state from settop box."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lghorizon
3
- Version: 0.9.0b0
3
+ Version: 0.9.0.dev4
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
@@ -5,31 +5,23 @@ LICENSE
5
5
  README.md
6
6
  instructions.txt
7
7
  lib64
8
- main.py
9
8
  pyvenv.cfg
10
9
  renovate.json
11
10
  secrets_stub.json
12
11
  setup.py
12
+ test.py
13
13
  .github/workflows/build-on-pr.yml
14
14
  .github/workflows/publish-to-pypi.yml
15
- .vscode/launch.json
16
15
  lghorizon/__init__.py
17
16
  lghorizon/const.py
18
17
  lghorizon/exceptions.py
19
18
  lghorizon/helpers.py
20
19
  lghorizon/lghorizon_api.py
21
- lghorizon/lghorizon_device.py
22
- lghorizon/lghorizon_device_state_processor.py
23
- lghorizon/lghorizon_message_factory.py
24
- lghorizon/lghorizon_models.py
25
- lghorizon/lghorizon_mqtt_client.py
26
- lghorizon/lghorizon_recording_factory.py
20
+ lghorizon/models.py
27
21
  lghorizon/py.typed
28
22
  lghorizon.egg-info/PKG-INFO
29
23
  lghorizon.egg-info/SOURCES.txt
30
24
  lghorizon.egg-info/dependency_links.txt
31
25
  lghorizon.egg-info/not-zip-safe
32
26
  lghorizon.egg-info/requires.txt
33
- lghorizon.egg-info/top_level.txt
34
- lghorizon/legacy/lghorizon_api.py
35
- lghorizon/legacy/models.py
27
+ lghorizon.egg-info/top_level.txt
@@ -0,0 +1,84 @@
1
+ """ "Test the component."""
2
+
3
+ import json
4
+ import logging
5
+ import time
6
+ from lghorizon import LGHorizonApi
7
+
8
+ api: LGHorizonApi
9
+
10
+ logging.basicConfig(
11
+ level=logging.DEBUG,
12
+ format="%(asctime)s - %(levelname)s - %(message)s",
13
+ )
14
+
15
+ _Logger = logging.getLogger()
16
+
17
+ file_handler = logging.FileHandler("logfile.log", mode="w")
18
+ file_handler.setLevel(logging.DEBUG)
19
+ _Logger.addHandler(file_handler)
20
+
21
+ console_handler = logging.StreamHandler()
22
+ console_handler.setLevel(logging.DEBUG)
23
+ _Logger.addHandler(console_handler)
24
+
25
+ secrets: dict[str, str] = None
26
+
27
+
28
+ def read_secrets(file_path):
29
+ """Read secrets from file."""
30
+ try:
31
+ with open(file_path, "r", encoding="UTF-8") as file:
32
+ return json.load(file)
33
+ except FileNotFoundError:
34
+ print(f"Error: Secrets file not found at {file_path}")
35
+ return {}
36
+ except json.JSONDecodeError:
37
+ print(f"Error: Unable to decode JSON in {file_path}")
38
+ return {}
39
+
40
+
41
+ def event_loop():
42
+ """Default event loop."""
43
+ while True:
44
+ time.sleep(1) # Simulate some work
45
+
46
+ # Check for a breaking condition
47
+ if break_condition():
48
+ break
49
+
50
+
51
+ def break_condition():
52
+ """Break event loop on conditions."""
53
+ # Implement your breaking condition logic here
54
+ return False # Change this condition based on your requirements
55
+
56
+
57
+ if __name__ == "__main__":
58
+ try:
59
+ secrets = read_secrets("secrets.json")
60
+
61
+ refresh_token: str = None
62
+ if "refresh_token" in secrets:
63
+ refresh_token = secrets["refresh_token"]
64
+
65
+ profile_id: str = None
66
+ if "profile_id" in secrets:
67
+ profile_id = secrets["profile_id"]
68
+
69
+ api = LGHorizonApi(
70
+ secrets["username"],
71
+ secrets["password"],
72
+ secrets["country"],
73
+ # identifier="DTV3907048",
74
+ refresh_token=refresh_token,
75
+ profile_id=profile_id,
76
+ )
77
+ api.connect()
78
+ event_loop()
79
+ except KeyboardInterrupt:
80
+ print("\nScript interrupted by user.")
81
+ finally:
82
+ print("Script is exiting.")
83
+ if api:
84
+ api.disconnect()
@@ -1,15 +0,0 @@
1
- {
2
- // Use IntelliSense to learn about possible attributes.
3
- // Hover to view descriptions of existing attributes.
4
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
- "version": "0.2.0",
6
- "configurations": [
7
- {
8
- "name": "Python Debugger: Debug LGHorizon",
9
- "type": "debugpy",
10
- "request": "launch",
11
- "program": "main.py",
12
- "console": "integratedTerminal"
13
- }
14
- ]
15
- }
@@ -1,6 +0,0 @@
1
- """Python client for LG Horizon."""
2
-
3
- from .lghorizon_api import LGHorizonApi
4
- from .lghorizon_device import LGHorizonDevice
5
- from .lghorizon_models import *
6
- from .exceptions import *
@@ -1,270 +0,0 @@
1
- """LG Horizon API client."""
2
-
3
- import logging
4
- from typing import Any, Dict, cast
5
-
6
- from .lghorizon_device import LGHorizonDevice
7
- from .lghorizon_models import LGHorizonChannel
8
- from .lghorizon_models import LGHorizonAuth
9
- from .lghorizon_models import LGHorizonCustomer
10
- from .lghorizon_mqtt_client import LGHorizonMqttClient
11
- from .lghorizon_models import LGHorizonServicesConfig
12
- from .lghorizon_models import LGHorizonEntitlements
13
- from .lghorizon_models import LGHorizonProfile
14
- from .lghorizon_models import LGHorizonMessageType
15
- from .lghorizon_message_factory import LGHorizonMessageFactory
16
- from .lghorizon_models import LGHorizonStatusMessage, LGHorizonUIStatusMessage
17
- from .lghorizon_models import LGHorizonRunningState
18
- from .lghorizon_models import LGHorizonRecordingList, LGHorizonRecordingQuota
19
- from .lghorizon_recording_factory import LGHorizonRecordingFactory
20
- from .lghorizon_device_state_processor import LGHorizonDeviceStateProcessor
21
-
22
-
23
- _LOGGER = logging.getLogger(__name__)
24
-
25
-
26
- class LGHorizonApi:
27
- """LG Horizon API client."""
28
-
29
- _mqtt_client: LGHorizonMqttClient
30
- auth: LGHorizonAuth
31
- _service_config: LGHorizonServicesConfig
32
- _customer: LGHorizonCustomer
33
- _channels: Dict[str, LGHorizonChannel]
34
- _entitlements: LGHorizonEntitlements
35
- _profile_id: str
36
- _initialized: bool = False
37
- _devices: Dict[str, LGHorizonDevice] = {}
38
- _message_factory: LGHorizonMessageFactory = LGHorizonMessageFactory()
39
- _device_state_processor: LGHorizonDeviceStateProcessor | None
40
- _recording_factory: LGHorizonRecordingFactory = LGHorizonRecordingFactory()
41
-
42
- def __init__(self, auth: LGHorizonAuth, profile_id: str = "") -> None:
43
- """Initialize LG Horizon API client."""
44
- self.auth = auth
45
- self._profile_id = profile_id
46
- self._channels = {}
47
- self._device_state_processor = None
48
-
49
- async def initialize(self) -> None:
50
- """Initialize the API client."""
51
- self._service_config = await self.auth.get_service_config()
52
- self._customer = await self._get_customer_info()
53
- if self._profile_id == "":
54
- self._profile_id = list(self._customer.profiles.keys())[0]
55
- await self._refresh_entitlements()
56
- await self._refresh_channels()
57
- self._mqtt_client = await self._create_mqtt_client()
58
- await self._mqtt_client.connect()
59
- await self._register_devices()
60
- self._device_state_processor = LGHorizonDeviceStateProcessor(
61
- self.auth, self._channels, self._customer, self._profile_id
62
- )
63
- self._initialized = True
64
-
65
- async def get_devices(self) -> Dict[str, LGHorizonDevice]:
66
- """Get devices."""
67
- if not self._initialized:
68
- raise RuntimeError("LGHorizonApi not initialized")
69
-
70
- return self._devices
71
-
72
- async def get_profiles(self) -> Dict[str, LGHorizonProfile]:
73
- """Get profile IDs."""
74
- if not self._initialized:
75
- raise RuntimeError("LGHorizonApi not initialized")
76
-
77
- return self._customer.profiles
78
-
79
- async def get_profile_channels(
80
- self, profile_id: str
81
- ) -> Dict[str, LGHorizonChannel]:
82
- """Returns channels to display baed on profile."""
83
- # Attempt to retrieve the profile by the given profile_id
84
- profile = self._customer.profiles.get(profile_id)
85
-
86
- # If the specified profile is not found, and there are other profiles available,
87
- # default to the first profile in the customer's list.
88
- if not profile and self._customer.profiles:
89
- _LOGGER.debug(
90
- "Profile with ID '%s' not found. Defaulting to first available profile.",
91
- profile_id,
92
- )
93
- profile = list(self._customer.profiles.values())[0]
94
-
95
- # If a profile is found and it has favorite channels, filter the main channels list.
96
- if profile and profile.favorite_channels:
97
- _LOGGER.debug("Returning favorite channels for profile '%s'.", profile.name)
98
- # Use a set for faster lookup of favorite channel IDs
99
- profile_channel_ids = set(profile.favorite_channels)
100
- return {
101
- channel.id: channel
102
- for channel in self._channels.values()
103
- if channel.id in profile_channel_ids
104
- }
105
-
106
- # If no profile is found (even after defaulting) or the profile has no favorite channels,
107
- # return all available channels.
108
- _LOGGER.debug("No specific profile channels found, returning all channels.")
109
- return self._channels
110
-
111
- async def _register_devices(self) -> None:
112
- """Register devices."""
113
- _LOGGER.debug("Registering devices...")
114
- self._devices = {}
115
- channels = await self.get_profile_channels(self._profile_id)
116
- for raw_box in self._customer.assigned_devices:
117
- _LOGGER.debug("Creating box for device: %s", raw_box)
118
- if self._device_state_processor is None:
119
- self._device_state_processor = LGHorizonDeviceStateProcessor(
120
- self.auth, self._channels, self._customer, self._profile_id
121
- )
122
- device = LGHorizonDevice(
123
- raw_box,
124
- self._mqtt_client,
125
- self._device_state_processor,
126
- self.auth,
127
- channels,
128
- )
129
- self._devices[device.device_id] = device
130
-
131
- async def disconnect(self) -> None:
132
- """Disconnect the client."""
133
- if self._mqtt_client:
134
- await self._mqtt_client.disconnect()
135
- self._initialized = False
136
-
137
- async def _create_mqtt_client(self) -> LGHorizonMqttClient:
138
- mqtt_client = await LGHorizonMqttClient.create(
139
- self.auth,
140
- self._on_mqtt_connected,
141
- self._on_mqtt_message,
142
- )
143
- return mqtt_client
144
-
145
- async def _on_mqtt_connected(self):
146
- """MQTT connected callback."""
147
- await self._mqtt_client.subscribe(self.auth.household_id)
148
- # await self._mqtt_client.subscribe(self.auth.household_id + "/#")
149
- await self._mqtt_client.subscribe(
150
- self.auth.household_id + "/" + self._mqtt_client.client_id
151
- )
152
- await self._mqtt_client.subscribe(self.auth.household_id + "/+/status")
153
- await self._mqtt_client.subscribe(
154
- self.auth.household_id + "/+/networkRecordings"
155
- )
156
- await self._mqtt_client.subscribe(
157
- self.auth.household_id + "/+/networkRecordings/capacity"
158
- )
159
- await self._mqtt_client.subscribe(self.auth.household_id + "/+/localRecordings")
160
- await self._mqtt_client.subscribe(
161
- self.auth.household_id + "/+/localRecordings/capacity"
162
- )
163
- await self._mqtt_client.subscribe(self.auth.household_id + "/watchlistService")
164
- await self._mqtt_client.subscribe(self.auth.household_id + "/purchaseService")
165
- await self._mqtt_client.subscribe(
166
- self.auth.household_id + "/personalizationService"
167
- )
168
- await self._mqtt_client.subscribe(self.auth.household_id + "/recordingStatus")
169
- await self._mqtt_client.subscribe(
170
- self.auth.household_id + "/recordingStatus/lastUserAction"
171
- )
172
-
173
- async def _on_mqtt_message(self, mqtt_message: dict, mqtt_topic: str):
174
- """MQTT message callback."""
175
- message = await self._message_factory.create_message(mqtt_topic, mqtt_message)
176
- match message.message_type:
177
- case LGHorizonMessageType.STATUS:
178
- message.__class__ = LGHorizonStatusMessage
179
- status_message = cast(LGHorizonStatusMessage, message)
180
- device = self._devices[status_message.source]
181
- await device.handle_status_message(status_message)
182
- case LGHorizonMessageType.UI_STATUS:
183
- message.__class__ = LGHorizonUIStatusMessage
184
- ui_status_message = cast(LGHorizonUIStatusMessage, message)
185
- device = self._devices[ui_status_message.source]
186
- if (
187
- not device.device_state.state
188
- == LGHorizonRunningState.ONLINE_RUNNING
189
- ):
190
- return
191
- await device.handle_ui_status_message(ui_status_message)
192
-
193
- async def _get_customer_info(self) -> LGHorizonCustomer:
194
- service_url = await self._service_config.get_service_url(
195
- "personalizationService"
196
- )
197
- result = await self.auth.request(
198
- service_url,
199
- f"/v1/customer/{self.auth.household_id}?with=profiles%2Cdevices",
200
- )
201
- return LGHorizonCustomer(result)
202
-
203
- async def _refresh_entitlements(self) -> Any:
204
- """Retrieve entitlements."""
205
- _LOGGER.debug("Retrieving entitlements...")
206
- service_url = await self._service_config.get_service_url("purchaseService")
207
- result = await self.auth.request(
208
- service_url,
209
- f"/v2/customers/{self.auth.household_id}/entitlements?enableDaypass=true",
210
- )
211
- self._entitlements = LGHorizonEntitlements(result)
212
-
213
- async def _refresh_channels(self):
214
- """Retrieve channels."""
215
- _LOGGER.debug("Retrieving channels...")
216
- service_url = await self._service_config.get_service_url("linearService")
217
- lang = await self._customer.get_profile_lang(self._profile_id)
218
- channels_json = await self.auth.request(
219
- service_url,
220
- f"/v2/channels?cityId={self._customer.city_id}&language={lang}&productClass=Orion-DASH",
221
- )
222
- for channel_json in channels_json:
223
- channel = LGHorizonChannel(channel_json)
224
- common_entitlements = list(
225
- set(self._entitlements.entitlement_ids) & set(channel.linear_products)
226
- )
227
-
228
- if len(common_entitlements) == 0:
229
- continue
230
-
231
- self._channels[channel.id] = channel
232
-
233
- async def get_all_recordings(self) -> LGHorizonRecordingList:
234
- """Retrieve all recordings."""
235
- _LOGGER.debug("Retrieving recordings...")
236
- service_url = await self._service_config.get_service_url("recordingService")
237
- lang = await self._customer.get_profile_lang(self._profile_id)
238
- recordings_json = await self.auth.request(
239
- service_url,
240
- f"/customers/{self.auth.household_id}/recordings?isAdult=false&offset=0&limit=100&sort=time&sortOrder=desc&profileId={self._profile_id}&language={lang}",
241
- )
242
- recordings = await self._recording_factory.create_recordings(recordings_json)
243
- return recordings
244
-
245
- async def get_show_recordings(
246
- self, show_id: str, channel_id: str
247
- ) -> LGHorizonRecordingList:
248
- """Retrieve all recordings."""
249
- _LOGGER.debug("Retrieving recordings fro show...")
250
- service_url = await self._service_config.get_service_url("recordingService")
251
- lang = await self._customer.get_profile_lang(self._profile_id)
252
- episodes_json = await self.auth.request(
253
- service_url,
254
- f"/customers/8436830_nl/episodes/shows/{show_id}?source=recording&isAdult=false&offset=0&limit=100&profileId={self._profile_id}&language={lang}&channelId={channel_id}&sort=time&sortOrder=asc",
255
- )
256
- recordings = await self._recording_factory.create_episodes(episodes_json)
257
- return recordings
258
-
259
- async def get_recording_quota(self) -> LGHorizonRecordingQuota:
260
- """Refresh recording quota."""
261
- _LOGGER.debug("Refreshing recording quota...")
262
- service_url = await self._service_config.get_service_url("recordingService")
263
- quota_json = await self.auth.request(
264
- service_url,
265
- f"/customers/{self.auth.household_id}/quota",
266
- )
267
- return LGHorizonRecordingQuota(quota_json)
268
-
269
-
270
- __all__ = ["LGHorizonApi", "LGHorizonAuth"]