lghorizon 0.9.0.dev2__py3-none-any.whl → 0.9.0.dev4__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lghorizon
3
- Version: 0.9.0.dev2
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,12 @@
1
+ lghorizon/__init__.py,sha256=GrctR8IIj0G7Sps_N_YjvKsrvMcbhp3O0KxE7yi-_R8,991
2
+ lghorizon/const.py,sha256=j_rugTybx8n2kKEKaRWhgAFzBSEKztWTBFP7ABuMEgg,5013
3
+ lghorizon/exceptions.py,sha256=-6v55KDTogBldGAg1wV9Mrxm5L5BsaVguhBgVMOeJHk,404
4
+ lghorizon/helpers.py,sha256=W7ppV9MJ1MyXH_49GliNtiu_kA7h1KRzMdmRdrC5kaw,266
5
+ lghorizon/lghorizon_api.py,sha256=PgQdW38yK30Lg45K4gvZEpYTK6NGGArhtu-gzuU_L-M,22279
6
+ lghorizon/models.py,sha256=W9Op8sq3NJHFWe2sk94o3TE9FIM1J6ZjDCCCg8VrpCM,26268
7
+ lghorizon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ lghorizon-0.9.0.dev4.dist-info/licenses/LICENSE,sha256=6Dh2tur1gMX3r3rITjVwUONBEJxyyPZDY8p6DZXtimE,1059
9
+ lghorizon-0.9.0.dev4.dist-info/METADATA,sha256=AwhqG5MYcrM7uyh-hM0AF7Xc_lD_7HFB-3SGLs8IuRc,1287
10
+ lghorizon-0.9.0.dev4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
+ lghorizon-0.9.0.dev4.dist-info/top_level.txt,sha256=usii76_AxGfPI6gjrrh-NyZxcQQuF1B8_Q9kd7sID8Q,10
12
+ lghorizon-0.9.0.dev4.dist-info/RECORD,,
@@ -1,336 +0,0 @@
1
- """LG Horizon Device."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import logging
7
- from typing import Any, Callable, Coroutine, Dict, Optional
8
- from .lghorizon_models import (
9
- LGHorizonRunningState,
10
- LGHorizonStatusMessage,
11
- LGHorizonUIStatusMessage,
12
- LGHorizonDeviceState,
13
- LGHorizonAuth,
14
- LGHorizonChannel,
15
- )
16
-
17
- from .exceptions import LGHorizonApiConnectionError
18
- from .helpers import make_id
19
- from .lghorizon_device_state_processor import LGHorizonDeviceStateProcessor
20
- from .lghorizon_mqtt_client import LGHorizonMqttClient
21
- from .const import (
22
- MEDIA_KEY_CHANNEL_DOWN,
23
- MEDIA_KEY_CHANNEL_UP,
24
- MEDIA_KEY_ENTER,
25
- MEDIA_KEY_FAST_FORWARD,
26
- MEDIA_KEY_PLAY_PAUSE,
27
- MEDIA_KEY_POWER,
28
- MEDIA_KEY_RECORD,
29
- MEDIA_KEY_REWIND,
30
- MEDIA_KEY_STOP,
31
- ONLINE_RUNNING,
32
- PLATFORM_TYPES,
33
- )
34
-
35
- _LOGGER = logging.getLogger(__name__)
36
-
37
-
38
- class LGHorizonDevice:
39
- """The LG Horizon device (set-top box)."""
40
-
41
- _device_id: str
42
- _hashed_cpe_id: str
43
- _device_friendly_name: str
44
- _platform_type: str
45
- _device_state: LGHorizonDeviceState
46
- _manufacturer: Optional[str]
47
- _model: Optional[str]
48
- _recording_capacity: Optional[int]
49
- _device_state_processor: LGHorizonDeviceStateProcessor
50
- _mqtt_client: LGHorizonMqttClient
51
- _change_callback: Callable[[str], Coroutine[Any, Any, Any]]
52
- _auth: LGHorizonAuth
53
- _channels: Dict[str, LGHorizonChannel]
54
- _last_ui_message_timestamp: int = 0
55
-
56
- def __init__(
57
- self,
58
- device_json,
59
- mqtt_client: LGHorizonMqttClient,
60
- device_state_processor: LGHorizonDeviceStateProcessor,
61
- auth: LGHorizonAuth,
62
- channels: Dict[str, LGHorizonChannel],
63
- ):
64
- """Initialize the LG Horizon device."""
65
- self._device_id = device_json["deviceId"]
66
- self._hashed_cpe_id = device_json["hashedCPEId"]
67
- self._device_friendly_name = device_json["settings"]["deviceFriendlyName"]
68
- self._platform_type = device_json.get("platformType")
69
- self._mqtt_client = mqtt_client
70
- self._auth = auth
71
- self._channels = channels
72
- self._device_state = LGHorizonDeviceState() # Initialize state
73
- self._manufacturer = None
74
- self._model = None
75
- self._recording_capacity = None
76
- self._device_state_processor = device_state_processor
77
-
78
- @property
79
- def device_id(self) -> str:
80
- """Return the device ID."""
81
- return self._device_id
82
-
83
- @property
84
- def platform_type(self) -> str:
85
- """Return the device ID."""
86
- return self._platform_type
87
-
88
- @property
89
- def manufacturer(self) -> str:
90
- """Return the manufacturer of the settop box."""
91
- platform_info = PLATFORM_TYPES.get(self._platform_type, dict())
92
- return platform_info.get("manufacturer", "unknown")
93
-
94
- @property
95
- def model(self) -> str:
96
- """Return the model of the settop box."""
97
- platform_info = PLATFORM_TYPES.get(self._platform_type, dict())
98
- return platform_info.get("model", "unknown")
99
-
100
- @property
101
- def is_available(self) -> bool:
102
- """Return the availability of the settop box."""
103
- return self._device_state.state in (
104
- LGHorizonRunningState.ONLINE_RUNNING,
105
- LGHorizonRunningState.ONLINE_STANDBY,
106
- )
107
-
108
- @property
109
- def hashed_cpe_id(self) -> str:
110
- """Return the hashed CPE ID."""
111
- return self._hashed_cpe_id
112
-
113
- @property
114
- def device_friendly_name(self) -> str:
115
- """Return the device friendly name."""
116
- return self._device_friendly_name
117
-
118
- @property
119
- def device_state(self) -> LGHorizonDeviceState:
120
- """Return the current playing information."""
121
- return self._device_state
122
-
123
- @property
124
- def recording_capacity(self) -> Optional[int]:
125
- """Return the recording capacity used."""
126
- return self._recording_capacity
127
-
128
- @recording_capacity.setter
129
- def recording_capacity(self, value: int) -> None:
130
- """Set the recording capacity used."""
131
- self._recording_capacity = value
132
-
133
- @property
134
- def last_ui_message_timestamp(self) -> int:
135
- """Return the last ui message timestamp."""
136
- return self._last_ui_message_timestamp
137
-
138
- @last_ui_message_timestamp.setter
139
- def last_ui_message_timestamp(self, value: int) -> None:
140
- """Set the last ui message timestamp."""
141
- self._last_ui_message_timestamp = value
142
-
143
- async def update_channels(self, channels: Dict[str, LGHorizonChannel]):
144
- """Update the channels list."""
145
- self._channels = channels
146
-
147
- async def register_mqtt(self) -> None:
148
- """Register the mqtt connection."""
149
- if not self._mqtt_client.is_connected:
150
- raise LGHorizonApiConnectionError("MQTT client not connected.")
151
- topic = f"{self._auth.household_id}/{self._mqtt_client.client_id}/status"
152
- payload = {
153
- "source": self._mqtt_client.client_id,
154
- "state": ONLINE_RUNNING,
155
- "deviceType": "HGO",
156
- }
157
- await self._mqtt_client.publish_message(topic, json.dumps(payload))
158
-
159
- async def set_callback(
160
- self, change_callback: Callable[[str], Coroutine[Any, Any, Any]]
161
- ) -> None:
162
- """Set a callback function."""
163
- self._change_callback = change_callback
164
- await self.register_mqtt() # type: ignore [assignment] # Callback can be None
165
-
166
- async def handle_status_message(
167
- self, status_message: LGHorizonStatusMessage
168
- ) -> None:
169
- """Register a new settop box."""
170
- old_running_state = self.device_state.state
171
- new_running_state = status_message.running_state
172
- if (
173
- old_running_state == new_running_state
174
- ): # Access backing field for comparison
175
- return
176
- await self._device_state_processor.process_state(
177
- self.device_state, status_message
178
- ) # Use the setter
179
- if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
180
- await self._request_settop_box_state()
181
-
182
- await self._trigger_callback()
183
- await self._request_settop_box_recording_capacity()
184
-
185
- async def handle_ui_status_message(
186
- self, status_message: LGHorizonUIStatusMessage
187
- ) -> None:
188
- """Handle UI status message."""
189
-
190
- await self._device_state_processor.process_ui_state(
191
- self.device_state, status_message
192
- )
193
- self.last_ui_message_timestamp = status_message.message_timestamp
194
- await self._trigger_callback()
195
-
196
- async def update_recording_capacity(self, payload) -> None:
197
- """Updates the recording capacity."""
198
- if "CPE.capacity" not in payload or "used" not in payload:
199
- return
200
- self.recording_capacity = payload["used"] # Use the setter
201
-
202
- async def _trigger_callback(self):
203
- if self._change_callback:
204
- _LOGGER.debug("Callback called from box %s", self.device_id)
205
- await self._change_callback(self.device_id)
206
-
207
- async def turn_on(self) -> None:
208
- """Turn the settop box on."""
209
-
210
- if self._device_state.state == LGHorizonRunningState.ONLINE_STANDBY:
211
- await self.send_key_to_box(MEDIA_KEY_POWER)
212
-
213
- async def turn_off(self) -> None:
214
- """Turn the settop box off."""
215
- if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
216
- await self.send_key_to_box(MEDIA_KEY_POWER)
217
- await self._device_state.reset()
218
-
219
- async def pause(self) -> None:
220
- """Pause the given settopbox."""
221
- if (
222
- self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING
223
- and not self._device_state.paused
224
- ):
225
- await self.send_key_to_box(MEDIA_KEY_PLAY_PAUSE)
226
-
227
- async def play(self) -> None:
228
- """Resume the settopbox."""
229
- if (
230
- self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING
231
- and self._device_state.paused
232
- ):
233
- await self.send_key_to_box(MEDIA_KEY_PLAY_PAUSE)
234
-
235
- async def stop(self) -> None:
236
- """Stop the settopbox."""
237
- if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
238
- await self.send_key_to_box(MEDIA_KEY_STOP)
239
-
240
- async def next_channel(self):
241
- """Select the next channel for given settop box."""
242
- if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
243
- await self.send_key_to_box(MEDIA_KEY_CHANNEL_UP)
244
-
245
- async def previous_channel(self) -> None:
246
- """Select the previous channel for given settop box."""
247
- if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
248
- await self.send_key_to_box(MEDIA_KEY_CHANNEL_DOWN)
249
-
250
- async def press_enter(self) -> None:
251
- """Press enter on the settop box."""
252
- if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
253
- await self.send_key_to_box(MEDIA_KEY_ENTER)
254
-
255
- async def rewind(self) -> None:
256
- """Rewind the settop box."""
257
- if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
258
- await self.send_key_to_box(MEDIA_KEY_REWIND)
259
-
260
- async def fast_forward(self) -> None:
261
- """Fast forward the settop box."""
262
- if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
263
- await self.send_key_to_box(MEDIA_KEY_FAST_FORWARD)
264
-
265
- async def record(self):
266
- """Record on the settop box."""
267
- if self._device_state.state == LGHorizonRunningState.ONLINE_RUNNING:
268
- await self.send_key_to_box(MEDIA_KEY_RECORD)
269
-
270
- async def set_channel(self, source: str) -> None:
271
- """Change te channel from the settopbox."""
272
- channel = [src for src in self._channels.values() if src.title == source][0]
273
- payload = (
274
- '{"id":"'
275
- + await make_id(8)
276
- + '","type":"CPE.pushToTV","source":{"clientId":"'
277
- + self._mqtt_client.client_id
278
- + '","friendlyDeviceName":"Home Assistant"},'
279
- + '"status":{"sourceType":"linear","source":{"channelId":"'
280
- + channel.id
281
- + '"},"relativePosition":0,"speed":1}}'
282
- )
283
-
284
- await self._mqtt_client.publish_message(
285
- f"{self._auth.household_id}/{self.device_id}", payload
286
- )
287
-
288
- async def play_recording(self, recording_id):
289
- """Play recording."""
290
- payload = (
291
- '{"id":"'
292
- + await make_id(8)
293
- + '","type":"CPE.pushToTV","source":{"clientId":"'
294
- + self._mqtt_client.client_id
295
- + '","friendlyDeviceName":"Home Assistant"},'
296
- + '"status":{"sourceType":"nDVR","source":{"recordingId":"'
297
- + recording_id
298
- + '"},"relativePosition":0}}'
299
- )
300
- await self._mqtt_client.publish_message(
301
- f"{self._auth.household_id}/{self.device_id}", payload
302
- )
303
-
304
- async def send_key_to_box(self, key: str) -> None:
305
- """Send emulated (remote) key press to settopbox."""
306
- payload_dict = {
307
- "type": "CPE.KeyEvent",
308
- "runtimeType": "key",
309
- "id": "ha",
310
- "source": self.device_id.lower(),
311
- "status": {"w3cKey": key, "eventType": "keyDownUp"},
312
- }
313
- payload = json.dumps(payload_dict)
314
- await self._mqtt_client.publish_message(
315
- f"{self._auth.household_id}/{self.device_id}", payload
316
- )
317
-
318
- async def _request_settop_box_state(self) -> None:
319
- """Send mqtt message to receive state from settop box."""
320
- topic = f"{self._auth.household_id}/{self.device_id}"
321
- payload = {
322
- "id": await make_id(8),
323
- "type": "CPE.getUiStatus",
324
- "source": self._mqtt_client.client_id,
325
- }
326
- await self._mqtt_client.publish_message(topic, json.dumps(payload))
327
-
328
- async def _request_settop_box_recording_capacity(self) -> None:
329
- """Send mqtt message to receive state from settop box."""
330
- topic = f"{self._auth.household_id}/{self.device_id}"
331
- payload = {
332
- "id": await make_id(8),
333
- "type": "CPE.capacity",
334
- "source": self._mqtt_client.client_id,
335
- }
336
- await self._mqtt_client.publish_message(topic, json.dumps(payload))
@@ -1,301 +0,0 @@
1
- """LG Horizon device (set-top box) model."""
2
-
3
- import random
4
- import json
5
- import urllib.parse
6
-
7
- from typing import cast, Dict, Optional
8
-
9
- from .lghorizon_models import LGHorizonDeviceState, LGHorizonRunningState
10
- from .lghorizon_models import LGHorizonStatusMessage, LGHorizonUIStatusMessage
11
- from .lghorizon_models import (
12
- LGHorizonSourceType,
13
- LGHorizonLinearSource,
14
- LGHorizonVODSource,
15
- LGHorizonReplaySource,
16
- LGHorizonNDVRSource,
17
- LGHorizonReviewBufferSource,
18
- )
19
- from .lghorizon_models import LGHorizonAuth
20
- from .lghorizon_models import (
21
- LGHorizonReplayEvent,
22
- LGHorizonVOD,
23
- )
24
-
25
- from .lghorizon_models import LGHorizonRecordingSingle
26
- from .lghorizon_models import LGHorizonChannel
27
- from .lghorizon_models import (
28
- LGHorizonUIStateType,
29
- LGHorizonAppsState,
30
- LGHorizonPlayerState,
31
- )
32
- from .lghorizon_models import LGHorizonCustomer
33
-
34
-
35
- class LGHorizonDeviceStateProcessor:
36
- """Process incoming device state messages"""
37
-
38
- def __init__(
39
- self,
40
- auth: LGHorizonAuth,
41
- channels: Dict[str, LGHorizonChannel],
42
- customer: LGHorizonCustomer,
43
- profile_id: str,
44
- ):
45
- self._auth = auth
46
- self._channels = channels
47
- self._customer = customer
48
- self._profile_id = profile_id
49
-
50
- async def process_state(
51
- self, device_state: LGHorizonDeviceState, status_message: LGHorizonStatusMessage
52
- ) -> None:
53
- """Process the device state based on the status message."""
54
- await device_state.reset()
55
- device_state.state = status_message.running_state
56
-
57
- async def process_ui_state(
58
- self,
59
- device_state: LGHorizonDeviceState,
60
- ui_status_message: LGHorizonUIStatusMessage,
61
- ) -> None:
62
- """Process the device state based on the UI status message."""
63
- await device_state.reset()
64
- if (
65
- ui_status_message.ui_state is None
66
- or device_state.state == LGHorizonRunningState.ONLINE_STANDBY
67
- ):
68
- await device_state.reset()
69
- return
70
-
71
- if ui_status_message.ui_state is None:
72
- return
73
- match ui_status_message.ui_state.ui_status:
74
- case LGHorizonUIStateType.MAINUI:
75
- if ui_status_message.ui_state.player_state is None:
76
- return
77
- await self._process_main_ui_state(
78
- device_state, ui_status_message.ui_state.player_state
79
- )
80
- case LGHorizonUIStateType.APPS:
81
- if ui_status_message.ui_state.apps_state is None:
82
- return
83
- await self._process_apps_state(
84
- device_state, ui_status_message.ui_state.apps_state
85
- )
86
-
87
- if ui_status_message.ui_state.ui_status == LGHorizonUIStateType.APPS:
88
- return
89
-
90
- if ui_status_message.ui_state.player_state is None:
91
- return
92
-
93
- async def _process_main_ui_state(
94
- self,
95
- device_state: LGHorizonDeviceState,
96
- player_state: LGHorizonPlayerState,
97
- ) -> None:
98
- if player_state is None:
99
- return
100
- await device_state.reset()
101
- device_state.source_type = player_state.source_type
102
- match player_state.source_type:
103
- case LGHorizonSourceType.LINEAR:
104
- await self._process_linear_state(device_state, player_state)
105
- case LGHorizonSourceType.REVIEWBUFFER:
106
- await self._process_reviewbuffer_state(device_state, player_state)
107
- case LGHorizonSourceType.REPLAY:
108
- await self._process_replay_state(device_state, player_state)
109
- case LGHorizonSourceType.VOD:
110
- await self._process_vod_state(device_state, player_state)
111
- case LGHorizonSourceType.NDVR:
112
- await self._process_ndvr_state(device_state, player_state)
113
-
114
- async def _process_apps_state(
115
- self,
116
- device_state: LGHorizonDeviceState,
117
- apps_state: LGHorizonAppsState,
118
- ) -> None:
119
- device_state.channel_id = apps_state.id
120
- device_state.title = apps_state.app_name
121
- device_state.image = apps_state.logo_path
122
-
123
- async def _process_linear_state(
124
- self,
125
- device_state: LGHorizonDeviceState,
126
- player_state: LGHorizonPlayerState,
127
- ) -> None:
128
- """Process the device state based on the UI status message."""
129
- if player_state.source is None:
130
- return
131
- player_state.source.__class__ = LGHorizonLinearSource
132
- source = cast(LGHorizonLinearSource, player_state.source)
133
- service_config = await self._auth.get_service_config()
134
- service_url = await service_config.get_service_url("linearService")
135
- lang = await self._customer.get_profile_lang(self._profile_id)
136
- service_path = f"/v2/replayEvent/{source.event_id}?returnLinearContent=true&language={lang}"
137
-
138
- event_json = await self._auth.request(
139
- service_url,
140
- service_path,
141
- )
142
- replay_event = LGHorizonReplayEvent(event_json)
143
- channel = self._channels[replay_event.channel_id]
144
- device_state.source_type = source.source_type
145
- device_state.channel_id = channel.channel_number
146
- device_state.channel_name = channel.title
147
- device_state.title = replay_event.title
148
- device_state.sub_title = replay_event.full_episode_title
149
-
150
- # Add random number to url to force refresh
151
- join_param = "?"
152
- if join_param in channel.stream_image:
153
- join_param = "&"
154
- image_url = (
155
- f"{channel.stream_image}{join_param}{str(random.randrange(1000000))}"
156
- )
157
- device_state.image = image_url
158
- await device_state.reset_progress()
159
-
160
- async def _process_reviewbuffer_state(
161
- self,
162
- device_state: LGHorizonDeviceState,
163
- player_state: LGHorizonPlayerState,
164
- ) -> None:
165
- """Process the device state based on the UI status message."""
166
- if player_state.source is None:
167
- return
168
- player_state.source.__class__ = LGHorizonReviewBufferSource
169
- source = cast(LGHorizonReviewBufferSource, player_state.source)
170
- service_config = await self._auth.get_service_config()
171
- service_url = await service_config.get_service_url("linearService")
172
- lang = await self._customer.get_profile_lang(self._profile_id)
173
- service_path = f"/v2/replayEvent/{source.event_id}?returnLinearContent=true&language={lang}"
174
-
175
- event_json = await self._auth.request(
176
- service_url,
177
- service_path,
178
- )
179
- replay_event = LGHorizonReplayEvent(event_json)
180
- channel = self._channels[replay_event.channel_id]
181
- device_state.source_type = source.source_type
182
- device_state.channel_id = channel.channel_number
183
- device_state.channel_name = channel.title
184
- device_state.title = replay_event.title
185
- device_state.sub_title = replay_event.full_episode_title
186
-
187
- # Add random number to url to force refresh
188
- join_param = "?"
189
- if join_param in channel.stream_image:
190
- join_param = "&"
191
- image_url = (
192
- f"{channel.stream_image}{join_param}{str(random.randrange(1000000))}"
193
- )
194
- device_state.image = image_url
195
- await device_state.reset_progress()
196
-
197
- async def _process_replay_state(
198
- self,
199
- device_state: LGHorizonDeviceState,
200
- player_state: LGHorizonPlayerState,
201
- ) -> None:
202
- """Process the device state based on the UI status message."""
203
- if player_state.source is None:
204
- return
205
- player_state.source.__class__ = LGHorizonReplaySource
206
- source = cast(LGHorizonReplaySource, player_state.source)
207
- service_config = await self._auth.get_service_config()
208
- service_url = await service_config.get_service_url("linearService")
209
- lang = await self._customer.get_profile_lang(self._profile_id)
210
- service_path = f"/v2/replayEvent/{source.event_id}?returnLinearContent=true&language={lang}"
211
-
212
- event_json = await self._auth.request(
213
- service_url,
214
- service_path,
215
- )
216
- replay_event = LGHorizonReplayEvent(event_json)
217
- device_state.source_type = source.source_type
218
- device_state.channel_id = None
219
- device_state.title = replay_event.title
220
- if replay_event.full_episode_title:
221
- device_state.sub_title = replay_event.full_episode_title
222
-
223
- # Add random number to url to force refresh
224
- device_state.image = await self._get_intent_image_url(replay_event.event_id)
225
- await device_state.reset_progress()
226
-
227
- async def _process_vod_state(
228
- self,
229
- device_state: LGHorizonDeviceState,
230
- player_state: LGHorizonPlayerState,
231
- ) -> None:
232
- """Process the device state based on the UI status message."""
233
- if player_state.source is None:
234
- return
235
- player_state.source.__class__ = LGHorizonVODSource
236
- source = cast(LGHorizonVODSource, player_state.source)
237
- service_config = await self._auth.get_service_config()
238
- service_url = await service_config.get_service_url("vodService")
239
- lang = await self._customer.get_profile_lang(self._profile_id)
240
- service_path = f"/v2/detailscreen/{source.title_id}?language={lang}&profileId={self._profile_id}&cityId={self._customer.city_id}"
241
-
242
- vod_json = await self._auth.request(
243
- service_url,
244
- service_path,
245
- )
246
- vod = LGHorizonVOD(vod_json)
247
- device_state.title = vod.title
248
- device_state.sub_title = vod.full_episode_title
249
- device_state.duration = vod.duration
250
- device_state.image = await self._get_intent_image_url(vod.id)
251
- await device_state.reset_progress()
252
-
253
- async def _process_ndvr_state(
254
- self, device_state: LGHorizonDeviceState, player_state: LGHorizonPlayerState
255
- ) -> None:
256
- """Process the device state based on the UI status message."""
257
- if player_state.source is None:
258
- return
259
- player_state.source.__class__ = LGHorizonNDVRSource
260
- source = cast(LGHorizonNDVRSource, player_state.source)
261
- service_config = await self._auth.get_service_config()
262
- service_url = await service_config.get_service_url("recordingService")
263
- lang = await self._customer.get_profile_lang(self._profile_id)
264
- service_path = f"/customers/{self._customer.customer_id}/details/single/{source.recording_id}?profileId={self._profile_id}&language={lang}"
265
- recording_json = await self._auth.request(
266
- service_url,
267
- service_path,
268
- )
269
- recording = LGHorizonRecordingSingle(recording_json)
270
- device_state.title = recording.title
271
- device_state.sub_title = recording.full_episode_title
272
- device_state.channel_id = recording.channel_id
273
- if recording.channel_id:
274
- channel = self._channels[recording.channel_id]
275
- device_state.channel_name = channel.title
276
-
277
- async def _get_intent_image_url(self, intent_id: str) -> Optional[str]:
278
- """Get intent image url."""
279
- service_config = await self._auth.get_service_config()
280
- intents_url = await service_config.get_service_url("imageService")
281
- intents_path = "/intent"
282
- body_json = [
283
- {
284
- "id": intent_id,
285
- "intents": ["detailedBackground", "posterTile"],
286
- }
287
- ]
288
- intents_body = urllib.parse.quote(
289
- json.dumps(body_json, separators=(",", ":"), indent=None), safe="~"
290
- )
291
-
292
- # Construct the full path with the URL-encoded JSON as a query parameter
293
- full_intents_path = f"{intents_path}?jsonBody={intents_body}"
294
- intents_result = await self._auth.request(intents_url, full_intents_path)
295
- if (
296
- "intents" in intents_result[0]
297
- and len(intents_result[0]["intents"]) > 0
298
- and intents_result[0]["intents"][0]["url"]
299
- ):
300
- return intents_result[0]["intents"][0]["url"]
301
- return None