lghorizon 0.8.7__py3-none-any.whl → 0.9.0b0__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.
@@ -1,539 +1,270 @@
1
- """Python client for LGHorizon."""
2
- # pylint: disable=broad-exception-caught
3
- # pylint: disable=line-too-long
1
+ """LG Horizon API client."""
4
2
 
5
3
  import logging
6
- import json
7
- import re
8
-
9
- from typing import Any, Callable, Dict, List
10
- import backoff
11
-
12
- from requests import Session, exceptions as request_exceptions
13
-
14
- from .exceptions import (
15
- LGHorizonApiUnauthorizedError,
16
- LGHorizonApiConnectionError,
17
- LGHorizonApiLockedError,
18
- )
19
-
20
- from .models import (
21
- LGHorizonAuth,
22
- LGHorizonBox,
23
- LGHorizonMqttClient,
24
- LGHorizonCustomer,
25
- LGHorizonChannel,
26
- LGHorizonReplayEvent,
27
- LGHorizonRecordingSingle,
28
- LGHorizonVod,
29
- LGHorizonApp,
30
- LGHorizonBaseRecording,
31
- LGHorizonRecordingListSeasonShow,
32
- LGHorizonRecordingEpisode,
33
- LGHorizonRecordingShow,
34
- )
35
-
36
- from .const import (
37
- COUNTRY_SETTINGS,
38
- BOX_PLAY_STATE_BUFFER,
39
- BOX_PLAY_STATE_CHANNEL,
40
- BOX_PLAY_STATE_DVR,
41
- BOX_PLAY_STATE_REPLAY,
42
- BOX_PLAY_STATE_VOD,
43
- RECORDING_TYPE_SINGLE,
44
- RECORDING_TYPE_SEASON,
45
- RECORDING_TYPE_SHOW,
46
- )
47
-
48
-
49
- _logger = logging.getLogger(__name__)
50
- _supported_platforms = ["EOS", "EOS2", "HORIZON", "APOLLO"]
4
+ from typing import Any, Dict, cast
51
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
52
21
 
53
- class LGHorizonApi:
54
- """Main class for handling connections with LGHorizon Settop boxes."""
55
-
56
- _auth: LGHorizonAuth = None
57
- _session: Session = None
58
- settop_boxes: Dict[str, LGHorizonBox] = None
59
- customer: LGHorizonCustomer = None
60
- _mqtt_client: LGHorizonMqttClient = None
61
- _channels: Dict[str, LGHorizonChannel] = None
62
- _country_settings = None
63
- _country_code: str = None
64
- recording_capacity: int = None
65
- _entitlements: List[str] = None
66
- _identifier: str = None
67
- _config: str = None
68
- _refresh_callback: Callable = None
69
- _profile_id: str = None
70
-
71
- def __init__(
72
- self,
73
- username: str,
74
- password: str,
75
- country_code: str = "nl",
76
- identifier: str = None,
77
- refresh_token=None,
78
- profile_id=None,
79
- ) -> None:
80
- """Create LGHorizon API."""
81
- self.username = username
82
- self.password = password
83
- self.refresh_token = refresh_token
84
- self._session = Session()
85
- self._country_settings = COUNTRY_SETTINGS[country_code]
86
- self._country_code = country_code
87
- self._auth = LGHorizonAuth()
88
- self.settop_boxes = {}
89
- self._channels = {}
90
- self._entitlements = []
91
- self._identifier = identifier
92
- self._profile_id = profile_id
93
22
 
94
- def _authorize(self) -> None:
95
- ctry_code = self._country_code[0:2]
96
- if ctry_code in ("gb", "ch", "be"):
97
- self._authorize_with_refresh_token()
98
- else:
99
- self._authorize_default()
100
-
101
- def _authorize_default(self) -> None:
102
- _logger.debug("Authorizing")
103
- auth_url = f"{self._country_settings['api_url']}/auth-service/v1/authorization"
104
- auth_headers = {"x-device-code": "web"}
105
- auth_payload = {"password": self.password, "username": self.username}
106
- try:
107
- auth_response = self._session.post(
108
- auth_url, headers=auth_headers, json=auth_payload
109
- )
110
- except Exception as ex:
111
- raise LGHorizonApiConnectionError("Unknown connection failure") from ex
112
-
113
- if not auth_response.ok:
114
- error_json = auth_response.json()
115
- error = error_json["error"]
116
- if error and error["statusCode"] == 97401:
117
- raise LGHorizonApiUnauthorizedError("Invalid credentials")
118
- elif error and error["statusCode"] == 97117:
119
- raise LGHorizonApiLockedError("Account locked")
120
- elif error:
121
- raise LGHorizonApiConnectionError(error["message"])
122
- else:
123
- raise LGHorizonApiConnectionError("Unknown connection error")
124
-
125
- self._auth.fill(auth_response.json())
126
- _logger.debug("Authorization succeeded")
127
-
128
- def _authorize_with_refresh_token(self) -> None:
129
- """Handle authorizzationg using request token."""
130
- _logger.debug("Authorizing via refresh")
131
- refresh_url = (
132
- f"{self._country_settings['api_url']}/auth-service/v1/authorization/refresh"
133
- )
134
- headers = {"content-type": "application/json", "charset": "utf-8"}
135
- payload = '{"refreshToken":"' + self.refresh_token + '"}'
23
+ _LOGGER = logging.getLogger(__name__)
136
24
 
137
- try:
138
- auth_response = self._session.post(
139
- refresh_url, headers=headers, data=payload
140
- )
141
- except Exception as ex:
142
- raise LGHorizonApiConnectionError("Unknown connection failure") from ex
143
-
144
- if not auth_response.ok:
145
- _logger.debug("response %s", auth_response)
146
- error_json = auth_response.json()
147
- error = None
148
- if "error" in error_json:
149
- error = error_json["error"]
150
- if error and error["statusCode"] == 97401:
151
- raise LGHorizonApiUnauthorizedError("Invalid credentials")
152
- elif error:
153
- raise LGHorizonApiConnectionError(error["message"])
154
- else:
155
- raise LGHorizonApiConnectionError("Unknown connection error")
156
-
157
- self._auth.fill(auth_response.json())
158
- self.refresh_token = self._auth.refresh_token
159
- self._session.cookies["ACCESSTOKEN"] = self._auth.access_token
160
-
161
- if self._refresh_callback:
162
- self._refresh_callback()
163
-
164
- _logger.debug("Authorization succeeded")
165
-
166
- def set_callback(self, refresh_callback: Callable) -> None:
167
- """Set the refresh callback."""
168
- self._refresh_callback = refresh_callback
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
25
 
186
- # Step 2 - Get Authorization cookie
187
- _logger.debug("Step 2 - Get Authorization cookie")
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
188
64
 
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")
65
+ async def get_devices(self) -> Dict[str, LGHorizonDevice]:
66
+ """Get devices."""
67
+ if not self._initialized:
68
+ raise RuntimeError("LGHorizonApi not initialized")
192
69
 
193
- _logger.debug("Step 3 - Login")
70
+ return self._devices
194
71
 
195
- username_fieldname = self._country_settings["oauth_username_fieldname"]
196
- pasword_fieldname = self._country_settings["oauth_password_fieldname"]
72
+ async def get_profiles(self) -> Dict[str, LGHorizonProfile]:
73
+ """Get profile IDs."""
74
+ if not self._initialized:
75
+ raise RuntimeError("LGHorizonApi not initialized")
197
76
 
198
- payload = {
199
- username_fieldname: self.username,
200
- pasword_fieldname: self.password,
201
- "rememberme": "true",
202
- }
77
+ return self._customer.profiles
203
78
 
204
- login_response = login_session.post(
205
- self._country_settings["oauth_url"], payload, allow_redirects=False
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,
206
92
  )
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",
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
231
104
  }
232
- post_result = login_session.post(
233
- auth_url, json.dumps(new_payload), headers=headers
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,
234
128
  )
235
- self._auth.fill(post_result.json())
236
- self._session.cookies["ACCESSTOKEN"] = self._auth.access_token
237
- except Exception:
238
- pass
239
-
240
- def _obtain_mqtt_token(self):
241
- _logger.debug("Obtain mqtt token...")
242
- mqtt_auth_url = self._config["authorizationService"]["URL"]
243
- mqtt_response = self._do_api_call(f"{mqtt_auth_url}/v1/mqtt/token")
244
- self._auth.mqttToken = mqtt_response["token"]
245
- _logger.debug("MQTT token: %s", self._auth.mqttToken)
246
-
247
- @backoff.on_exception(
248
- backoff.expo,
249
- BaseException,
250
- jitter=None,
251
- max_tries=3,
252
- logger=_logger,
253
- giveup=lambda e: isinstance(
254
- e, (LGHorizonApiLockedError, LGHorizonApiUnauthorizedError)
255
- ),
256
- )
257
- def connect(self) -> None:
258
- """Start connection process."""
259
- self._config = self._get_config(self._country_code)
260
- _logger.debug("Connect to API")
261
- self._authorize()
262
- self._obtain_mqtt_token()
263
- self._mqtt_client = LGHorizonMqttClient(
264
- self._auth,
265
- self._config["mqttBroker"]["URL"],
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,
266
140
  self._on_mqtt_connected,
267
141
  self._on_mqtt_message,
268
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
+ )
269
172
 
270
- self._register_customer_and_boxes()
271
- self._mqtt_client.connect()
272
-
273
- def disconnect(self):
274
- """Disconnect."""
275
- _logger.debug("Disconnect from API")
276
- if not self._mqtt_client or not self._mqtt_client.is_connected:
277
- return
278
- self._mqtt_client.disconnect()
279
-
280
- def _on_mqtt_connected(self) -> None:
281
- _logger.debug("Connected to MQTT server. Registering all boxes...")
282
- box: LGHorizonBox
283
- for box in self.settop_boxes.values():
284
- box.register_mqtt()
285
-
286
- def _on_mqtt_message(self, message: str, topic: str) -> None:
287
- if "action" in message and message["action"] == "OPS.getProfilesUpdate":
288
- self._update_customer()
289
- elif "source" in message:
290
- device_id = message["source"]
291
- if not isinstance(device_id, str):
292
- _logger.debug("ignoring message - not a string")
293
- return
294
- if device_id not in self.settop_boxes:
295
- return
296
- try:
297
- if "deviceType" in message and message["deviceType"] == "STB":
298
- self.settop_boxes[device_id].update_state(message)
299
- if "status" in message:
300
- self._handle_box_update(device_id, message)
301
-
302
- except Exception:
303
- _logger.exception("Could not handle status message")
304
- _logger.warning("Full message: %s", str(message))
305
- self.settop_boxes[device_id].playing_info.reset()
306
- self.settop_boxes[device_id].playing_info.set_paused(False)
307
- elif "CPE.capacity" in message:
308
- splitted_topic = topic.split("/")
309
- if len(splitted_topic) != 4:
310
- return
311
- device_id = splitted_topic[1]
312
- if device_id not in self.settop_boxes:
313
- return
314
- self.settop_boxes[device_id].update_recording_capacity(message)
315
-
316
- def _handle_box_update(self, device_id: str, raw_message: Any) -> None:
317
- status_payload = raw_message["status"]
318
- if "uiStatus" not in status_payload:
319
- return
320
- ui_status = status_payload["uiStatus"]
321
- if ui_status == "mainUI":
322
- player_state = status_payload["playerState"]
323
- if "sourceType" not in player_state or "source" not in player_state:
324
- return
325
- source_type = player_state["sourceType"]
326
- state_source = player_state["source"]
327
- self.settop_boxes[device_id].playing_info.set_paused(
328
- player_state["speed"] == 0
329
- )
330
- if (
331
- source_type
332
- in (
333
- BOX_PLAY_STATE_CHANNEL,
334
- BOX_PLAY_STATE_BUFFER,
335
- BOX_PLAY_STATE_REPLAY,
336
- )
337
- and "eventId" in state_source
338
- ):
339
- event_id = state_source["eventId"]
340
- raw_replay_event = self._do_api_call(
341
- f"{self._config['linearService']['URL']}/v2/replayEvent/{event_id}?returnLinearContent=true&language={self._country_settings['language']}"
342
- )
343
- replay_event = LGHorizonReplayEvent(raw_replay_event)
344
- channel = self._channels[replay_event.channel_id]
345
- self.settop_boxes[device_id].update_with_replay_event(
346
- source_type, replay_event, channel
347
- )
348
- elif source_type == BOX_PLAY_STATE_DVR:
349
- recording_id = state_source["recordingId"]
350
- session_start_time = state_source["sessionStartTime"]
351
- session_end_time = state_source["sessionEndTime"]
352
- last_speed_change_time = player_state["lastSpeedChangeTime"]
353
- relative_position = player_state["relativePosition"]
354
- raw_recording = self._do_api_call(
355
- f"{self._config['recordingService']['URL']}/customers/{self._auth.household_id}/details/single/{recording_id}?profileId=4504e28d-c1cb-4284-810b-f5eaab06f034&language={self._country_settings['language']}"
356
- )
357
- recording = LGHorizonRecordingSingle(raw_recording)
358
- channel = self._channels[recording.channel_id]
359
- self.settop_boxes[device_id].update_with_recording(
360
- source_type,
361
- recording,
362
- channel,
363
- session_start_time,
364
- session_end_time,
365
- last_speed_change_time,
366
- relative_position,
367
- )
368
- elif source_type == BOX_PLAY_STATE_VOD:
369
- title_id = state_source["titleId"]
370
- last_speed_change_time = player_state["lastSpeedChangeTime"]
371
- relative_position = player_state["relativePosition"]
372
- raw_vod = self._do_api_call(
373
- f"{self._config['vodService']['URL']}/v2/detailscreen/{title_id}?language={self._country_settings['language']}&profileId=4504e28d-c1cb-4284-810b-f5eaab06f034&cityId={self.customer.city_id}"
374
- )
375
- vod = LGHorizonVod(raw_vod)
376
- self.settop_boxes[device_id].update_with_vod(
377
- source_type, vod, last_speed_change_time, relative_position
378
- )
379
- elif ui_status == "apps":
380
- app = LGHorizonApp(status_payload["appsState"])
381
- self.settop_boxes[device_id].update_with_app("app", app)
382
-
383
- @backoff.on_exception(
384
- backoff.expo, LGHorizonApiConnectionError, max_tries=3, logger=_logger
385
- )
386
- def _do_api_call(self, url: str) -> str:
387
- _logger.info("Executing API call to %s", url)
388
- try:
389
- api_response = self._session.get(url)
390
- api_response.raise_for_status()
391
- json_response = api_response.json()
392
- except request_exceptions.HTTPError as http_ex:
393
- self._authorize()
394
- raise LGHorizonApiConnectionError(
395
- f"Unable to call {url}. Error:{str(http_ex)}"
396
- ) from http_ex
397
- _logger.debug("Result API call: %s", json_response)
398
- return json_response
399
-
400
- def _register_customer_and_boxes(self):
401
- self._update_customer()
402
- self._get_channels()
403
- if len(self.customer.settop_boxes) == 0:
404
- _logger.warning("No boxes found.")
405
- return
406
- _logger.info("Registering boxes")
407
- for device in self.customer.settop_boxes:
408
- platform_type = device["platformType"]
409
- if platform_type not in _supported_platforms:
410
- continue
411
- if (
412
- "platform_types" in self._country_settings
413
- and platform_type in self._country_settings["platform_types"]
414
- ):
415
- platform_type = self._country_settings["platform_types"][platform_type]
416
- else:
417
- platform_type = None
418
- box = LGHorizonBox(
419
- device, platform_type, self._mqtt_client, self._auth, self._channels
420
- )
421
- self.settop_boxes[box.device_id] = box
422
- _logger.info("Box %s registered...", box.device_id)
423
-
424
- def _update_customer(self):
425
- _logger.info("Get customer data")
426
- personalisation_result = self._do_api_call(
427
- f"{self._config['personalizationService']['URL']}/v1/customer/{self._auth.household_id}?with=profiles%2Cdevices"
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"
428
196
  )
429
- _logger.debug("Personalisation result: %s ", personalisation_result)
430
- self.customer = LGHorizonCustomer(personalisation_result)
431
-
432
- def _get_channels(self):
433
- self._update_entitlements()
434
- _logger.info("Retrieving channels...")
435
- channels_result = self._do_api_call(
436
- f"{self._config['linearService']['URL']}/v2/channels?cityId={self.customer.city_id}&language={self._country_settings['language']}&productClass=Orion-DASH"
197
+ result = await self.auth.request(
198
+ service_url,
199
+ f"/v1/customer/{self.auth.household_id}?with=profiles%2Cdevices",
437
200
  )
438
- for channel in channels_result:
439
- if "isRadio" in channel and channel["isRadio"]:
440
- continue
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)
441
224
  common_entitlements = list(
442
- set(self._entitlements) & set(channel["linearProducts"])
225
+ set(self._entitlements.entitlement_ids) & set(channel.linear_products)
443
226
  )
227
+
444
228
  if len(common_entitlements) == 0:
445
229
  continue
446
- channel_id = channel["id"]
447
- self._channels[channel_id] = LGHorizonChannel(channel)
448
- _logger.info("%s retrieved.", len(self._channels))
449
230
 
450
- def get_display_channels(self):
451
- """Returns channels to display baed on profile."""
452
- all_channels = self._channels.values()
453
- if not self._profile_id or self._profile_id not in self.customer.profiles:
454
- return all_channels
455
- profile_channel_ids = self.customer.profiles[self._profile_id].favorite_channels
456
- if len(profile_channel_ids) == 0:
457
- return all_channels
458
-
459
- return [
460
- channel for channel in all_channels if channel.id in profile_channel_ids
461
- ]
462
-
463
- def _get_replay_event(self, listing_id) -> Any:
464
- """Get listing."""
465
- _logger.info("Retrieving replay event details...")
466
- response = self._do_api_call(
467
- f"{self._config['linearService']['URL']}/v2/replayEvent/{listing_id}?returnLinearContent=true&language={self._country_settings['language']}"
468
- )
469
- _logger.info("Replay event details retrieved")
470
- return response
471
-
472
- def get_recording_capacity(self) -> int:
473
- """Returns remaining recording capacity"""
474
- ctry_code = self._country_code[0:2]
475
- if ctry_code == "gb":
476
- _logger.debug("GB: not supported")
477
- return None
478
- try:
479
- _logger.info("Retrieving recordingcapacity...")
480
- quota_content = self._do_api_call(
481
- f"{self._config['recordingService']['URL']}/customers/{self._auth.household_id}/quota"
482
- )
483
- if "quota" not in quota_content and "occupied" not in quota_content:
484
- _logger.error("Unable to fetch recording capacity...")
485
- return None
486
- capacity = (quota_content["occupied"] / quota_content["quota"]) * 100
487
- self.recording_capacity = round(capacity)
488
- _logger.debug("Remaining recordingcapacity %s %%", self.recording_capacity)
489
- return self.recording_capacity
490
- except Exception:
491
- _logger.error("Unable to fetch recording capacity...")
492
- return None
493
-
494
- def get_recordings(self) -> List[LGHorizonBaseRecording]:
495
- """Returns recordings."""
496
- _logger.info("Retrieving recordings...")
497
- recording_content = self._do_api_call(
498
- f"{self._config['recordingService']['URL']}/customers/{self._auth.household_id}/recordings?sort=time&sortOrder=desc&language={self._country_settings['language']}"
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}",
499
241
  )
500
- recordings = []
501
- for recording_data_item in recording_content["data"]:
502
- recording_type = recording_data_item["type"]
503
- if recording_type == RECORDING_TYPE_SINGLE:
504
- recordings.append(LGHorizonRecordingSingle(recording_data_item))
505
- elif recording_type in (RECORDING_TYPE_SEASON, RECORDING_TYPE_SHOW):
506
- recordings.append(LGHorizonRecordingListSeasonShow(recording_data_item))
507
- _logger.info("%s recordings retrieved...", len(recordings))
242
+ recordings = await self._recording_factory.create_recordings(recordings_json)
508
243
  return recordings
509
244
 
510
- def get_recording_show(self, show_id: str) -> list[LGHorizonRecordingSingle]:
511
- """Returns show recording"""
512
- _logger.info("Retrieving show recordings...")
513
- show_recording_content = self._do_api_call(
514
- f"{self._config['recordingService']['URL']}/customers/{self._auth.household_id}/episodes/shows/{show_id}?source=recording&language=nl&sort=time&sortOrder=asc"
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",
515
255
  )
516
- recordings = []
517
- for item in show_recording_content["data"]:
518
- if item["source"] == "show":
519
- recordings.append(LGHorizonRecordingShow(item))
520
- else:
521
- recordings.append(LGHorizonRecordingEpisode(item))
522
- _logger.info("%s showrecordings retrieved...", len(recordings))
256
+ recordings = await self._recording_factory.create_episodes(episodes_json)
523
257
  return recordings
524
258
 
525
- def _update_entitlements(self) -> None:
526
- _logger.info("Retrieving entitlements...")
527
- entitlements_json = self._do_api_call(
528
- f"{self._config['purchaseService']['URL']}/v2/customers/{self._auth.household_id}/entitlements?enableDaypass=true"
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",
529
266
  )
530
- self._entitlements.clear()
531
- for entitlement in entitlements_json["entitlements"]:
532
- self._entitlements.append(entitlement["id"])
533
-
534
- def _get_config(self, country_code: str):
535
- base_country_code = country_code[0:2]
536
- config_url = f"{self._country_settings['api_url']}/{base_country_code}/en/config-service/conf/web/backoffice.json"
537
- result = self._do_api_call(config_url)
538
- _logger.debug(result)
539
- return result
267
+ return LGHorizonRecordingQuota(quota_json)
268
+
269
+
270
+ __all__ = ["LGHorizonApi", "LGHorizonAuth"]