lghorizon 0.6.4__py3-none-any.whl → 0.6.6__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/const.py CHANGED
@@ -40,7 +40,6 @@ BE_AUTH_URL = "https://login.prd.telenet.be/openid/login.do"
40
40
  COUNTRY_SETTINGS = {
41
41
  "nl": {
42
42
  "api_url": "https://prod.spark.ziggogo.tv",
43
- "personalization_url_format": "https://prod.spark.ziggogo.tv/nld/web/personalization-service/v1/customer/{household_id}/devices",
44
43
  "mqtt_url": "obomsg.prod.nl.horizon.tv",
45
44
  "use_oauth": False,
46
45
  "channels": [
@@ -73,16 +72,12 @@ COUNTRY_SETTINGS = {
73
72
  },
74
73
  "ch": {
75
74
  "api_url": "https://prod.spark.sunrisetv.ch",
76
- "personalization_url_format": "https://prod.spark.sunrisetv.ch/eng/web/personalization-service/v1/customer/{householdId}/devices",
77
- "mqtt_url": "messagebroker-prod-ch.gnp.cloud.dmdsdp.com",
78
75
  "use_oauth": False,
79
76
  "channels": [],
80
77
  "language": "de",
81
78
  },
82
79
  "be-nl": {
83
- "api_url": "https://prod.spark.telenet.tv",
84
- "personalization_url_format": "https://prod.spark.telenettv.be/nld/web/personalization-service/v1/customer/{household_id}/devices",
85
- "mqtt_url": "obomsg.prod.be.horizon.tv",
80
+ "api_url": "https://spark-prod-be.gnp.cloud.telenet.tv",
86
81
  "use_oauth": True,
87
82
  "oauth_username_fieldname": "j_username",
88
83
  "oauth_password_fieldname": "j_password",
@@ -130,18 +125,16 @@ COUNTRY_SETTINGS = {
130
125
  # "oauth_redirect_header": "Location",
131
126
  # "channels": [],
132
127
  # },
133
- "at": {
134
- "api_url": "https://prod.spark.magentatv.at",
135
- "personalization_url_format": "https://prod.spark.magentatv.at/deu/web/personalization-service/v1/customer/{householdId}/devices",
136
- "mqtt_url": "obomsg.prod.at.horizon.tv",
137
- "use_oauth": False,
138
- "channels": [],
139
- "language": "de",
140
- },
128
+ # "at": {
129
+ # "api_url": "https://prod.spark.magentatv.at",
130
+ # "personalization_url_format": "https://prod.spark.magentatv.at/deu/web/personalization-service/v1/customer/{householdId}/devices",
131
+ # "mqtt_url": "obomsg.prod.at.horizon.tv",
132
+ # "use_oauth": False,
133
+ # "channels": [],
134
+ # "language": "de",
135
+ # },
141
136
  "gb": {
142
- "api_url": "https://prod.spark.virginmedia.com",
143
- "personalization_url_format": "https://prod.spark.virginmedia.com/eng/web/personalization-service/v1/customer/{household_id}/devices",
144
- "mqtt_url": "obomsg.prod.gb.horizon.tv",
137
+ "api_url": "https://spark-prod-gb.gnp.cloud.virgintvgo.virginmedia.com",
145
138
  "oauth_url": "https://id.virginmedia.com/rest/v40/session/start?protocol=oidc&rememberMe=true",
146
139
  "channels": [],
147
140
  "oesp_url": "https://prod.oesp.virginmedia.com/oesp/v4/GB/eng/web",
@@ -161,17 +154,13 @@ COUNTRY_SETTINGS = {
161
154
  # "channels": [],
162
155
  # },
163
156
  "ie": {
164
- "api_url": "https://prod.spark.virginmediatv.ie",
165
- "personalization_url_format": "https://prod.spark.virginmediatv.ie/eng/web/personalization-service/v1/customer/{householdId}/devices",
166
- "mqtt_url": "obomsg.prod.ie.horizon.tv",
157
+ "api_url": "https://spark-prod-ie.gnp.cloud.virginmediatv.ie",
167
158
  "use_oauth": False,
168
159
  "channels": [],
169
160
  "language": "en",
170
161
  },
171
162
  "pl": {
172
- "api_url": "https://prod.spark.upctv.pl/",
173
- "personalization_url_format": "https://prod.spark.upctv.pl/pol/web/personalization-service/v1/customer/{householdId}/devices",
174
- "mqtt_url": "obomsg.prod.pl.horizon.tv",
163
+ "api_url": "https://spark-prod-pl.gnp.cloud.upctv.pl",
175
164
  "use_oauth": False,
176
165
  "channels": [],
177
166
  "language": "pl",
@@ -8,7 +8,7 @@ from requests import Session, exceptions as request_exceptions
8
8
  from paho.mqtt.client import WebsocketConnectionError
9
9
  import re
10
10
  from .models import (
11
- LGHorizonAuth,
11
+ LGHorizonAuth,
12
12
  LGHorizonBox,
13
13
  LGHorizonMqttClient,
14
14
  LGHorizonCustomer,
@@ -20,8 +20,8 @@ from .models import (
20
20
  LGHorizonBaseRecording,
21
21
  LGHorizonRecordingListSeasonShow,
22
22
  LGHorizonRecordingEpisode,
23
- LGHorizonRecordingShow
24
- )
23
+ LGHorizonRecordingShow,
24
+ )
25
25
 
26
26
  from .const import (
27
27
  COUNTRY_SETTINGS,
@@ -32,12 +32,14 @@ from .const import (
32
32
  BOX_PLAY_STATE_VOD,
33
33
  RECORDING_TYPE_SINGLE,
34
34
  RECORDING_TYPE_SEASON,
35
- RECORDING_TYPE_SHOW)
35
+ RECORDING_TYPE_SHOW,
36
+ )
36
37
  from typing import Any, Dict, List
37
38
 
38
39
  _logger = logging.getLogger(__name__)
39
40
  _supported_platforms = ["EOS", "EOS2", "HORIZON", "APOLLO"]
40
41
 
42
+
41
43
  class LGHorizonApi:
42
44
  """Main class for handling connections with LGHorizon Settop boxes."""
43
45
 
@@ -48,12 +50,19 @@ class LGHorizonApi:
48
50
  _mqttClient: LGHorizonMqttClient = None
49
51
  _channels: Dict[str, LGHorizonChannel] = None
50
52
  _country_settings = None
51
- _country_code:str = None
52
- recording_capacity:int = None
53
- _entitlements:List[str] = None
54
- _identifier:str = None
55
-
56
- def __init__(self, username: str, password: str, country_code: str = "nl", identifier:str = None) -> None:
53
+ _country_code: str = None
54
+ recording_capacity: int = None
55
+ _entitlements: List[str] = None
56
+ _identifier: str = None
57
+ _config: str = None
58
+
59
+ def __init__(
60
+ self,
61
+ username: str,
62
+ password: str,
63
+ country_code: str = "nl",
64
+ identifier: str = None,
65
+ ) -> None:
57
66
  """Create LGHorizon API."""
58
67
  self.username = username
59
68
  self.password = password
@@ -66,11 +75,13 @@ class LGHorizonApi:
66
75
  self._entitlements = []
67
76
  self._identifier = identifier
68
77
 
69
- @backoff.on_exception(backoff.expo, LGHorizonApiConnectionError, max_tries=3, logger=_logger)
78
+ @backoff.on_exception(
79
+ backoff.expo, LGHorizonApiConnectionError, max_tries=3, logger=_logger
80
+ )
70
81
  def _authorize(self) -> None:
71
- if self._country_code == 'be-nl':
82
+ if self._country_code == "be-nl":
72
83
  self.authorize_telenet()
73
- elif self._country_code == 'gb':
84
+ elif self._country_code == "gb":
74
85
  self.authorize_gb()
75
86
  else:
76
87
  self._authorize_default()
@@ -78,18 +89,15 @@ class LGHorizonApi:
78
89
  def _authorize_default(self) -> None:
79
90
  _logger.debug("Authorizing")
80
91
  auth_url = f"{self._country_settings['api_url']}/auth-service/v1/authorization"
81
- auth_headers = {
82
- "x-device-code": "web"
83
- }
84
- auth_payload = {
85
- "password": self.password,
86
- "username": self.username
87
- }
92
+ auth_headers = {"x-device-code": "web"}
93
+ auth_payload = {"password": self.password, "username": self.username}
88
94
  try:
89
- auth_response = self._session.post(auth_url, headers=auth_headers, json=auth_payload)
95
+ auth_response = self._session.post(
96
+ auth_url, headers=auth_headers, json=auth_payload
97
+ )
90
98
  except Exception as ex:
91
99
  raise LGHorizonApiConnectionError("Unknown connection failure") from ex
92
-
100
+
93
101
  if not auth_response.ok:
94
102
  error_json = auth_response.json()
95
103
  error = error_json["error"]
@@ -104,10 +112,9 @@ class LGHorizonApi:
104
112
  _logger.debug("Authorization succeeded")
105
113
 
106
114
  def authorize_gb(self):
107
-
108
115
  try:
109
116
  login_session = Session()
110
- ####################################
117
+ ####################################
111
118
  _logger.debug("Step 1 - Get Authorization data")
112
119
  auth_url = f"{self._country_settings['oesp_url']}/authorization"
113
120
  auth_response = login_session.get(auth_url)
@@ -118,38 +125,36 @@ class LGHorizonApi:
118
125
  auth_state = auth_session["state"]
119
126
  authorizationUri = auth_session["authorizationUri"]
120
127
  authValidityToken = auth_session["validityToken"]
121
- ####################################
128
+ ####################################
122
129
  _logger.debug("Step 2 - Get Authorization cookie")
123
130
 
124
131
  auth_cookie_response = login_session.get(authorizationUri)
125
132
  if not auth_cookie_response.ok:
126
133
  raise LGHorizonApiConnectionError("Can't connect to authorization URL")
127
- ####################################
134
+ ####################################
128
135
  _logger.debug("Step 3 - Login")
129
- payload = {
130
- "username": self.username,
131
- "credential": self.password
132
- }
133
- headers = {
134
- "accept": "application/json; charset=UTF-8, */*"
135
- }
136
-
136
+ payload = {"username": self.username, "credential": self.password}
137
+ headers = {"accept": "application/json; charset=UTF-8, */*"}
138
+
137
139
  login_response = login_session.post(
138
- self._country_settings["oauth_url"], json.dumps(payload), headers=headers, allow_redirects=False
140
+ self._country_settings["oauth_url"],
141
+ json.dumps(payload),
142
+ headers=headers,
143
+ allow_redirects=False,
139
144
  )
140
145
  if not login_response.ok:
141
146
  raise LGHorizonApiConnectionError("Can't connect to authorization URL")
142
-
147
+
143
148
  if not "x-redirect-location" in login_response.headers:
144
149
  raise LGHorizonApiConnectionError("No redirect location in headers.")
145
-
150
+
146
151
  redirect_url = login_response.headers["x-redirect-location"]
147
- ####################################
152
+ ####################################
148
153
  _logger.debug("Step 4 - Follow redirect")
149
154
  redirect_response = login_session.get(redirect_url, allow_redirects=False)
150
155
  if not "Location" in redirect_response.headers:
151
156
  raise LGHorizonApiConnectionError("No success url in redirect.")
152
- ####################################
157
+ ####################################
153
158
  _logger.debug("Step 5 - Extract auth code")
154
159
  success_url = redirect_response.headers["Location"]
155
160
  codeMatches = re.findall(r"code=(.*)&", success_url)
@@ -160,36 +165,47 @@ class LGHorizonApi:
160
165
  if len(codeMatches) == 0:
161
166
  raise LGHorizonApiConnectionError("No state in redirect headers")
162
167
  authorizationState = stateMatches[0]
163
- _logger.debug(f"Auth code: {authorizationCode}, Auth state: {authorizationState}")
164
- ####################################
168
+ _logger.debug(
169
+ f"Auth code: {authorizationCode}, Auth state: {authorizationState}"
170
+ )
171
+ ####################################
165
172
  _logger.debug("Step 6 - Post auth data with valid code")
166
173
  authorization_payload = {
167
- "authorizationGrant":{
168
- "authorizationCode":authorizationCode,
169
- "validityToken":authValidityToken,
170
- "state": authorizationState
174
+ "authorizationGrant": {
175
+ "authorizationCode": authorizationCode,
176
+ "validityToken": authValidityToken,
177
+ "state": authorizationState,
171
178
  }
172
179
  }
173
180
  headers = {
174
- "content-type":"application/json",
181
+ "content-type": "application/json",
175
182
  }
176
- # VM requires the client to pass the response from /authorization verbatim to /session?token=true
177
- post_authorization_result = login_session.post(self._country_settings["oesp_url"]+"/authorization", json.dumps(authorization_payload), headers=headers)
178
- post_session_result = login_session.post(self._country_settings["oesp_url"]+"/session?token=true", json.dumps(post_authorization_result.json()), headers=headers)
183
+ # VM requires the client to pass the response from /authorization verbatim to /session?token=true
184
+ post_authorization_result = login_session.post(
185
+ self._country_settings["oesp_url"] + "/authorization",
186
+ json.dumps(authorization_payload),
187
+ headers=headers,
188
+ )
189
+ post_session_result = login_session.post(
190
+ self._country_settings["oesp_url"] + "/session?token=true",
191
+ json.dumps(post_authorization_result.json()),
192
+ headers=headers,
193
+ )
179
194
 
180
195
  self._auth.fill(post_session_result.json())
181
196
  self._session.cookies["ACCESSTOKEN"] = self._auth.accessToken
182
- ####################################
197
+ ####################################
183
198
  except Exception as ex:
184
199
  pass
185
200
 
186
201
  def authorize_telenet(self):
187
-
188
202
  try:
189
203
  login_session = Session()
190
- #Step 1 - Get Authorization data
204
+ # Step 1 - Get Authorization data
191
205
  _logger.debug("Step 1 - Get Authorization data")
192
- auth_url = f"{self._country_settings['api_url']}/auth-service/v1/sso/authorization"
206
+ auth_url = (
207
+ f"{self._country_settings['api_url']}/auth-service/v1/sso/authorization"
208
+ )
193
209
  auth_response = login_session.get(auth_url)
194
210
  if not auth_response.ok:
195
211
  raise LGHorizonApiConnectionError("Can't connect to authorization URL")
@@ -197,13 +213,13 @@ class LGHorizonApi:
197
213
  authorizationUri = auth_response_json["authorizationUri"]
198
214
  authValidtyToken = auth_response_json["validityToken"]
199
215
 
200
- #Step 2 - Get Authorization cookie
216
+ # Step 2 - Get Authorization cookie
201
217
  _logger.debug("Step 2 - Get Authorization cookie")
202
218
 
203
219
  auth_cookie_response = login_session.get(authorizationUri)
204
220
  if not auth_cookie_response.ok:
205
221
  raise LGHorizonApiConnectionError("Can't connect to authorization URL")
206
-
222
+
207
223
  _logger.debug("Step 3 - Login")
208
224
 
209
225
  username_fieldname = self._country_settings["oauth_username_fieldname"]
@@ -212,43 +228,50 @@ class LGHorizonApi:
212
228
  payload = {
213
229
  username_fieldname: self.username,
214
230
  pasword_fieldname: self.password,
215
- "rememberme": 'true'
231
+ "rememberme": "true",
216
232
  }
217
233
 
218
-
219
234
  login_response = login_session.post(
220
235
  self._country_settings["oauth_url"], payload, allow_redirects=False
221
236
  )
222
237
  if not login_response.ok:
223
238
  raise LGHorizonApiConnectionError("Can't connect to authorization URL")
224
- redirect_url = login_response.headers[self._country_settings["oauth_redirect_header"]]
225
-
239
+ redirect_url = login_response.headers[
240
+ self._country_settings["oauth_redirect_header"]
241
+ ]
242
+
226
243
  if not self._identifier is None:
227
244
  redirect_url += f"&dtv_identifier={self._identifier}"
228
245
  redirect_response = login_session.get(redirect_url, allow_redirects=False)
229
- success_url = redirect_response.headers[self._country_settings["oauth_redirect_header"]]
246
+ success_url = redirect_response.headers[
247
+ self._country_settings["oauth_redirect_header"]
248
+ ]
230
249
  codeMatches = re.findall(r"code=(.*)&", success_url)
231
-
250
+
232
251
  authorizationCode = codeMatches[0]
233
252
 
234
253
  new_payload = {
235
- "authorizationGrant":{
236
- "authorizationCode":authorizationCode,
237
- "validityToken":authValidtyToken
254
+ "authorizationGrant": {
255
+ "authorizationCode": authorizationCode,
256
+ "validityToken": authValidtyToken,
238
257
  }
239
258
  }
240
259
  headers = {
241
- "content-type":"application/json",
260
+ "content-type": "application/json",
242
261
  }
243
- post_result = login_session.post(auth_url, json.dumps(new_payload), headers = headers)
262
+ post_result = login_session.post(
263
+ auth_url, json.dumps(new_payload), headers=headers
264
+ )
244
265
  self._auth.fill(post_result.json())
245
266
  self._session.cookies["ACCESSTOKEN"] = self._auth.accessToken
246
267
  except Exception as ex:
247
- pass
268
+ pass
248
269
 
249
270
  def _obtain_mqtt_token(self):
250
271
  _logger.debug("Obtain mqtt token...")
251
- mqtt_response = self._do_api_call(f"{self._country_settings['api_url']}/auth-service/v1/mqtt/token")
272
+ mqtt_response = self._do_api_call(
273
+ f"{self._config['authorizationService']['URL']}/v1/mqtt/token"
274
+ )
252
275
  self._auth.mqttToken = mqtt_response["token"]
253
276
  _logger.debug(f"MQTT token: {self._auth.mqttToken}")
254
277
 
@@ -257,19 +280,29 @@ class LGHorizonApi:
257
280
  self._session.headers["x-oesp-token"] = self._auth.accessToken
258
281
  self._session.headers["x-oesp-username"] = self._auth.username
259
282
 
260
- mqtt_response = self._do_api_call(f"{self._country_settings['oesp_url']}/tokens/jwt")
283
+ mqtt_response = self._do_api_call(
284
+ f"{self._country_settings['oesp_url']}/tokens/jwt"
285
+ )
261
286
  self._auth.mqttToken = mqtt_response["token"]
262
287
  _logger.debug(f"MQTT token: {self._auth.mqttToken}")
263
288
 
264
- @backoff.on_exception(backoff.expo, BaseException, jitter=None, max_time=600, logger=_logger)
289
+ @backoff.on_exception(
290
+ backoff.expo, BaseException, jitter=None, max_time=600, logger=_logger
291
+ )
265
292
  def connect(self) -> None:
293
+ self._config = self._get_config(self._country_code)
266
294
  _logger.debug("Connect to API")
267
295
  self._authorize()
268
296
  if self._country_code == "gb":
269
297
  self._obtain_mqtt_token_gb()
270
298
  else:
271
299
  self._obtain_mqtt_token()
272
- self._mqttClient = LGHorizonMqttClient(self._auth, self._country_settings, self._on_mqtt_connected, self._on_mqtt_message)
300
+ self._mqttClient = LGHorizonMqttClient(
301
+ self._auth,
302
+ self._config["mqttBroker"]["URL"],
303
+ self._on_mqtt_connected,
304
+ self._on_mqtt_message,
305
+ )
273
306
  self._register_customer_and_boxes()
274
307
  self._mqttClient.connect()
275
308
 
@@ -282,11 +315,11 @@ class LGHorizonApi:
282
315
 
283
316
  def _on_mqtt_connected(self) -> None:
284
317
  _logger.debug("Connected to MQTT server. Registering all boxes...")
285
- box:LGHorizonBox
318
+ box: LGHorizonBox
286
319
  for box in self.settop_boxes.values():
287
320
  box.register_mqtt()
288
321
 
289
- def _on_mqtt_message(self, message:str, topic:str)-> None:
322
+ def _on_mqtt_message(self, message: str, topic: str) -> None:
290
323
  if "source" in message:
291
324
  deviceId = message["source"]
292
325
  if not deviceId in self.settop_boxes.keys():
@@ -296,13 +329,13 @@ class LGHorizonApi:
296
329
  self.settop_boxes[deviceId].update_state(message)
297
330
  if "status" in message:
298
331
  self._handle_box_update(deviceId, message)
299
- except Exception as ex:
332
+ except Exception:
300
333
  _logger.exception("Could not handle status message")
301
334
  _logger.warning(f"Full message: {str(message)}")
302
335
  self.settop_boxes[deviceId].playing_info.reset()
303
336
  self.settop_boxes[deviceId].playing_info.set_paused(False)
304
337
  elif "CPE.capacity" in message:
305
- splitted_topic = topic.split('/')
338
+ splitted_topic = topic.split("/")
306
339
  if len(splitted_topic) != 4:
307
340
  return
308
341
  deviceId = splitted_topic[1]
@@ -310,7 +343,7 @@ class LGHorizonApi:
310
343
  return
311
344
  self.settop_boxes[deviceId].update_recording_capacity(message)
312
345
 
313
- def _handle_box_update(self, deviceId:str, raw_message:Any) -> None:
346
+ def _handle_box_update(self, deviceId: str, raw_message: Any) -> None:
314
347
  statusPayload = raw_message["status"]
315
348
  if "uiStatus" not in statusPayload:
316
349
  return
@@ -321,40 +354,62 @@ class LGHorizonApi:
321
354
  return
322
355
  source_type = playerState["sourceType"]
323
356
  state_source = playerState["source"]
324
- self.settop_boxes[deviceId].playing_info.set_paused(playerState["speed"] == 0)
357
+ self.settop_boxes[deviceId].playing_info.set_paused(
358
+ playerState["speed"] == 0
359
+ )
325
360
  if source_type in (
326
361
  BOX_PLAY_STATE_CHANNEL,
327
362
  BOX_PLAY_STATE_BUFFER,
328
- BOX_PLAY_STATE_REPLAY
329
- ):
363
+ BOX_PLAY_STATE_REPLAY,
364
+ ):
330
365
  eventId = state_source["eventId"]
331
- raw_replay_event = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/linear-service/v2/replayEvent/{eventId}?returnLinearContent=true&language={self._country_settings['language']}")
366
+ raw_replay_event = self._do_api_call(
367
+ f"{self._config['linearService']['URL']}/v2/replayEvent/{eventId}?returnLinearContent=true&language={self._country_settings['language']}"
368
+ )
332
369
  replayEvent = LGHorizonReplayEvent(raw_replay_event)
333
370
  channel = self._channels[replayEvent.channelId]
334
- self.settop_boxes[deviceId].update_with_replay_event(source_type, replayEvent, channel)
371
+ self.settop_boxes[deviceId].update_with_replay_event(
372
+ source_type, replayEvent, channel
373
+ )
335
374
  elif source_type == BOX_PLAY_STATE_DVR:
336
375
  recordingId = state_source["recordingId"]
337
376
  session_start_time = state_source["sessionStartTime"]
338
377
  session_end_time = state_source["sessionEndTime"]
339
378
  last_speed_change_time = playerState["lastSpeedChangeTime"]
340
379
  relative_position = playerState["relativePosition"]
341
- raw_recording = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/recording-service/customers/{self._auth.householdId}/details/single/{recordingId}?profileId=4504e28d-c1cb-4284-810b-f5eaab06f034&language={self._country_settings['language']}")
380
+ raw_recording = self._do_api_call(
381
+ f"{self._config['recordingService']['URL']}/customers/{self._auth.householdId}/details/single/{recordingId}?profileId=4504e28d-c1cb-4284-810b-f5eaab06f034&language={self._country_settings['language']}"
382
+ )
342
383
  recording = LGHorizonRecordingSingle(raw_recording)
343
384
  channel = self._channels[recording.channelId]
344
- self.settop_boxes[deviceId].update_with_recording(source_type, recording, channel,session_start_time, session_end_time, last_speed_change_time, relative_position)
385
+ self.settop_boxes[deviceId].update_with_recording(
386
+ source_type,
387
+ recording,
388
+ channel,
389
+ session_start_time,
390
+ session_end_time,
391
+ last_speed_change_time,
392
+ relative_position,
393
+ )
345
394
  elif source_type == BOX_PLAY_STATE_VOD:
346
395
  titleId = state_source["titleId"]
347
396
  last_speed_change_time = playerState["lastSpeedChangeTime"]
348
397
  relative_position = playerState["relativePosition"]
349
- raw_vod = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/vod-service/v2/detailscreen/{titleId}?language={self._country_settings['language']}&profileId=4504e28d-c1cb-4284-810b-f5eaab06f034&cityId={self._customer.cityId}")
398
+ raw_vod = self._do_api_call(
399
+ f"{self._config['vodService']['URL']}/v2/detailscreen/{titleId}?language={self._country_settings['language']}&profileId=4504e28d-c1cb-4284-810b-f5eaab06f034&cityId={self._customer.cityId}"
400
+ )
350
401
  vod = LGHorizonVod(raw_vod)
351
- self.settop_boxes[deviceId].update_with_vod(source_type, vod, last_speed_change_time, relative_position)
402
+ self.settop_boxes[deviceId].update_with_vod(
403
+ source_type, vod, last_speed_change_time, relative_position
404
+ )
352
405
  elif uiStatus == "apps":
353
406
  app = LGHorizonApp(statusPayload["appsState"])
354
- self.settop_boxes[deviceId].update_with_app('app', app)
407
+ self.settop_boxes[deviceId].update_with_app("app", app)
355
408
 
356
- @backoff.on_exception(backoff.expo, LGHorizonApiConnectionError, max_tries=3, logger=_logger)
357
- def _do_api_call(self, url:str, tries:int = 0) -> str:
409
+ @backoff.on_exception(
410
+ backoff.expo, LGHorizonApiConnectionError, max_tries=3, logger=_logger
411
+ )
412
+ def _do_api_call(self, url: str, tries: int = 0) -> str:
358
413
  _logger.info(f"Executing API call to {url}")
359
414
  try:
360
415
  api_response = self._session.get(url)
@@ -362,13 +417,17 @@ class LGHorizonApi:
362
417
  json_response = api_response.json()
363
418
  except request_exceptions.HTTPError as httpEx:
364
419
  self._authorize()
365
- raise LGHorizonApiConnectionError(f"Unable to call {url}. Error:{str(httpEx)}")
420
+ raise LGHorizonApiConnectionError(
421
+ f"Unable to call {url}. Error:{str(httpEx)}"
422
+ )
366
423
  _logger.debug(f"Result API call: {json_response}")
367
424
  return json_response
368
-
425
+
369
426
  def _register_customer_and_boxes(self):
370
427
  _logger.info("Get personalisation info...")
371
- personalisation_result = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/personalization-service/v1/customer/{self._auth.householdId}?with=profiles%2Cdevices")
428
+ personalisation_result = self._do_api_call(
429
+ f"{self._config['personalizationService']['URL']}/v1/customer/{self._auth.householdId}?with=profiles%2Cdevices"
430
+ )
372
431
  _logger.debug(f"Personalisation result: {personalisation_result}")
373
432
  self._customer = LGHorizonCustomer(personalisation_result)
374
433
  self._get_channels()
@@ -380,79 +439,105 @@ class LGHorizonApi:
380
439
  platform_type = device["platformType"]
381
440
  if not platform_type in _supported_platforms:
382
441
  continue
383
- if "platform_types" in self._country_settings and platform_type in self._country_settings["platform_types"]:
442
+ if (
443
+ "platform_types" in self._country_settings
444
+ and platform_type in self._country_settings["platform_types"]
445
+ ):
384
446
  platformType = self._country_settings["platform_types"][platform_type]
385
447
  else:
386
448
  platformType = None
387
- box = LGHorizonBox(device, platformType, self._mqttClient,self._auth, self._channels)
449
+ box = LGHorizonBox(
450
+ device, platformType, self._mqttClient, self._auth, self._channels
451
+ )
388
452
  self.settop_boxes[box.deviceId] = box
389
453
  _logger.info(f"Box {box.deviceId} registered...")
390
-
454
+
391
455
  def _get_channels(self):
392
456
  self._update_entitlements()
393
457
  _logger.info("Retrieving channels...")
394
- channels_result = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/linear-service/v2/channels?cityId={self._customer.cityId}&language={self._country_settings['language']}&productClass=Orion-DASH")
458
+ channels_result = self._do_api_call(
459
+ f"{self._config['linearService']['URL']}/v2/channels?cityId={self._customer.cityId}&language={self._country_settings['language']}&productClass=Orion-DASH"
460
+ )
395
461
  for channel in channels_result:
396
462
  if "isRadio" in channel and channel["isRadio"]:
397
463
  continue
398
- common_entitlements = list(set(self._entitlements) & set(channel["linearProducts"]))
464
+ common_entitlements = list(
465
+ set(self._entitlements) & set(channel["linearProducts"])
466
+ )
399
467
  if len(common_entitlements) == 0:
400
468
  continue
401
469
  channel_id = channel["id"]
402
470
  self._channels[channel_id] = LGHorizonChannel(channel)
403
471
  _logger.info(f"{len(self._channels)} retrieved.")
404
472
 
405
- def _get_replay_event(self, listingId) -> Any:
473
+ def _get_replay_event(self, listingId) -> Any:
406
474
  """Get listing."""
407
475
  _logger.info("Retrieving replay event details...")
408
- response = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/linear-service/v2/replayEvent/{listingId}?returnLinearContent=true&language={self._country_settings['language']}")
476
+ response = self._do_api_call(
477
+ f"{self._config['linearService']['URL']}/v2/replayEvent/{listingId}?returnLinearContent=true&language={self._country_settings['language']}"
478
+ )
409
479
  _logger.info("Replay event details retrieved")
410
480
  return response
411
481
 
412
- def get_recording_capacity(self) -> int:
482
+ def get_recording_capacity(self) -> int:
413
483
  """Returns remaining recording capacity"""
414
484
  try:
415
485
  _logger.info("Retrieving recordingcapacity...")
416
- quota_content = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/recording-service/customers/{self._auth.householdId}/quota")
486
+ quota_content = self._do_api_call(
487
+ f"{self._config['recordingService']['URL']}/customers/{self._auth.householdId}/quota"
488
+ )
417
489
  if not "quota" in quota_content and not "occupied" in quota_content:
418
490
  _logger.error("Unable to fetch recording capacity...")
419
491
  return None
420
- capacity = (quota_content["occupied"] / quota_content["quota"]) * 100
492
+ capacity = (quota_content["occupied"] / quota_content["quota"]) * 100
421
493
  self.recording_capacity = round(capacity)
422
494
  _logger.debug(f"Remaining recordingcapacity {self.recording_capacity}%")
423
495
  return self.recording_capacity
424
496
  except:
425
497
  _logger.error("Unable to fetch recording capacity...")
426
498
  return None
427
-
499
+
428
500
  def get_recordings(self) -> List[LGHorizonBaseRecording]:
429
501
  _logger.info("Retrieving recordings...")
430
- recording_content = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/recording-service/customers/{self._auth.householdId}/recordings?sort=time&sortOrder=desc&language={self._country_settings['language']}")
431
- recordings= []
432
- for recording_data_item in recording_content['data']:
433
- type = recording_data_item['type']
502
+ recording_content = self._do_api_call(
503
+ f"{self._config['recordingService']['URL']}/customers/{self._auth.householdId}/recordings?sort=time&sortOrder=desc&language={self._country_settings['language']}"
504
+ )
505
+ recordings = []
506
+ for recording_data_item in recording_content["data"]:
507
+ type = recording_data_item["type"]
434
508
  if type == RECORDING_TYPE_SINGLE:
435
509
  recordings.append(LGHorizonRecordingSingle(recording_data_item))
436
510
  elif type in (RECORDING_TYPE_SEASON, RECORDING_TYPE_SHOW):
437
511
  recordings.append(LGHorizonRecordingListSeasonShow(recording_data_item))
438
- _logger.info(F"{len(recordings)} recordings retrieved...")
512
+ _logger.info(f"{len(recordings)} recordings retrieved...")
439
513
  return recordings
440
514
 
441
- def get_recording_show(self, showId:str) -> list[LGHorizonRecordingSingle]:
515
+ def get_recording_show(self, showId: str) -> list[LGHorizonRecordingSingle]:
442
516
  _logger.info("Retrieving show recordings...")
443
- show_recording_content = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/recording-service/customers/{self._auth.householdId}/episodes/shows/{showId}?source=recording&language=nl&sort=time&sortOrder=asc")
517
+ show_recording_content = self._do_api_call(
518
+ f"{self._config['recordingService']['URL']}/customers/{self._auth.householdId}/episodes/shows/{showId}?source=recording&language=nl&sort=time&sortOrder=asc"
519
+ )
444
520
  recordings = []
445
521
  for item in show_recording_content["data"]:
446
- if item['source'] == 'show':
522
+ if item["source"] == "show":
447
523
  recordings.append(LGHorizonRecordingShow(item))
448
524
  else:
449
525
  recordings.append(LGHorizonRecordingEpisode(item))
450
- _logger.info(F"{len(recordings)} showrecordings retrieved...")
526
+ _logger.info(f"{len(recordings)} showrecordings retrieved...")
451
527
  return recordings
452
-
528
+
453
529
  def _update_entitlements(self) -> None:
454
530
  _logger.info("Retrieving entitlements...")
455
- entitlements_json = self._do_api_call(f"{self._country_settings['api_url']}/eng/web/purchase-service/v2/customers/{self._auth.householdId}/entitlements?enableDaypass=true")
531
+ entitlements_json = self._do_api_call(
532
+ f"{self._config['purchaseService']['URL']}/v2/customers/{self._auth.householdId}/entitlements?enableDaypass=true"
533
+ )
456
534
  self._entitlements.clear()
457
535
  for entitlement in entitlements_json["entitlements"]:
458
536
  self._entitlements.append(entitlement["id"])
537
+
538
+ def _get_config(self, country_code: str):
539
+ ctryCode = country_code[0:2]
540
+ config_url = f"{self._country_settings['api_url']}/{ctryCode}/en/config-service/conf/web/backoffice.json"
541
+ result = self._do_api_call(config_url)
542
+ _logger.debug(result)
543
+ return result
lghorizon/models.py CHANGED
@@ -21,7 +21,7 @@ from .const import (
21
21
  MEDIA_KEY_FAST_FORWARD,
22
22
  MEDIA_KEY_RECORD,
23
23
  RECORDING_TYPE_SEASON,
24
- RECORDING_TYPE_SHOW
24
+ RECORDING_TYPE_SHOW,
25
25
  )
26
26
 
27
27
  import json
@@ -30,6 +30,7 @@ import logging
30
30
 
31
31
  _logger = logging.getLogger(__name__)
32
32
 
33
+
33
34
  class LGHorizonAuth:
34
35
  householdId: str
35
36
  accessToken: str
@@ -42,22 +43,26 @@ class LGHorizonAuth:
42
43
  def __init__(self):
43
44
  """Initialize a session."""
44
45
  pass
45
-
46
- def fill(self, auth_json)-> None:
46
+
47
+ def fill(self, auth_json) -> None:
47
48
  self.householdId = auth_json["householdId"]
48
49
  self.accessToken = auth_json["accessToken"]
49
50
  self.refreshToken = auth_json["refreshToken"]
50
51
  self.username = auth_json["username"]
51
52
  try:
52
- self.refreshTokenExpiry = datetime.fromtimestamp(auth_json["refreshTokenExpiry"])
53
+ self.refreshTokenExpiry = datetime.fromtimestamp(
54
+ auth_json["refreshTokenExpiry"]
55
+ )
53
56
  except ValueError:
54
57
  # VM uses milliseconds for the expiry time; if the year is too high to be valid, it assumes it's milliseconds and divides it
55
- self.refreshTokenExpiry = datetime.fromtimestamp(auth_json["refreshTokenExpiry"]//1000)
58
+ self.refreshTokenExpiry = datetime.fromtimestamp(
59
+ auth_json["refreshTokenExpiry"] // 1000
60
+ )
56
61
 
57
-
58
62
  def is_expired(self) -> bool:
59
63
  return self.refreshTokenExpiry
60
64
 
65
+
61
66
  class LGHorizonPlayingInfo:
62
67
  """Represent current state of a box."""
63
68
 
@@ -98,19 +103,19 @@ class LGHorizonPlayingInfo:
98
103
  def set_source_type(self, source_type):
99
104
  """Set source type."""
100
105
  self.source_type = source_type
101
-
106
+
102
107
  def set_duration(self, duration: float):
103
108
  """Set duration."""
104
109
  self.duration = duration
105
-
110
+
106
111
  def set_position(self, position: float):
107
112
  """Set position."""
108
113
  self.position = position
109
114
 
110
- def set_last_position_update(self, last_position_update:datetime):
115
+ def set_last_position_update(self, last_position_update: datetime):
111
116
  """Set last position update."""
112
117
  self.last_position_update = last_position_update
113
-
118
+
114
119
  def reset_progress(self):
115
120
  self.last_position_update = None
116
121
  self.duration = None
@@ -125,6 +130,7 @@ class LGHorizonPlayingInfo:
125
130
  self.channel_title = None
126
131
  self.reset_progress()
127
132
 
133
+
128
134
  class LGHorizonChannel:
129
135
  """Represent a channel."""
130
136
 
@@ -142,10 +148,10 @@ class LGHorizonChannel:
142
148
  if "logo" in channel_json and "focused" in channel_json["logo"]:
143
149
  self.logo_image = channel_json["logo"]["focused"]
144
150
  else:
145
- self.logo_image = ""
151
+ self.logo_image = ""
146
152
  self.channel_number = channel_json["logicalChannelNumber"]
147
-
148
- def get_stream_image(self, channel_json)->str:
153
+
154
+ def get_stream_image(self, channel_json) -> str:
149
155
  image_stream = channel_json["imageStream"]
150
156
  if "full" in image_stream:
151
157
  return image_stream["full"]
@@ -155,16 +161,16 @@ class LGHorizonChannel:
155
161
  return channel_json["logo"]["focused"]
156
162
  return ""
157
163
 
158
- class LGHorizonReplayEvent:
159
164
 
165
+ class LGHorizonReplayEvent:
160
166
  episodeNumber: int = None
161
167
  channelId: str = None
162
168
  eventId: str = None
163
- seasonNumber:int = None
164
- title:str = None
165
- episodeName:str = None
169
+ seasonNumber: int = None
170
+ title: str = None
171
+ episodeName: str = None
166
172
 
167
- def __init__(self, raw_json:str):
173
+ def __init__(self, raw_json: str):
168
174
  self.channelId = raw_json["channelId"]
169
175
  self.eventId = raw_json["eventId"]
170
176
  self.title = raw_json["title"]
@@ -175,22 +181,27 @@ class LGHorizonReplayEvent:
175
181
  if "seasonNumber" in raw_json:
176
182
  self.seasonNumber = raw_json["seasonNumber"]
177
183
 
184
+
178
185
  class LGHorizonBaseRecording:
179
-
180
- id:str = None
181
- title:str = None
182
- image:str = None
183
- type:str = None
186
+ id: str = None
187
+ title: str = None
188
+ image: str = None
189
+ type: str = None
184
190
  channelId: str = None
185
- def __init__(self, id:str, title:str, image:str, channelId: str, type:str) -> None:
191
+
192
+ def __init__(
193
+ self, id: str, title: str, image: str, channelId: str, type: str
194
+ ) -> None:
186
195
  self.id = id
187
196
  self.title = title
188
197
  self.image = image
189
198
  self.channelId = channelId
190
199
  self.type = type
191
200
 
201
+
192
202
  class LGHorizonRecordingSingle(LGHorizonBaseRecording):
193
203
  """Represents a single recording."""
204
+
194
205
  seasonNumber: int = None
195
206
  episodeNumber: int = None
196
207
 
@@ -202,23 +213,24 @@ class LGHorizonRecordingSingle(LGHorizonBaseRecording):
202
213
  recording_json["title"],
203
214
  recording_json["poster"]["url"],
204
215
  recording_json["channelId"],
205
- recording_json["type"]
206
- )
216
+ recording_json["type"],
217
+ )
207
218
  if "seasonNumber" in recording_json:
208
219
  self.seasonNumber = recording_json["seasonNumber"]
209
220
  if "episodeNumber" in recording_json:
210
221
  self.episodeNumber = recording_json["episodeNumber"]
211
222
 
223
+
212
224
  class LGHorizonRecordingEpisode:
213
225
  """Represents a single recording."""
214
-
226
+
215
227
  episodeId: str = None
216
- episodeTitle:str = None
228
+ episodeTitle: str = None
217
229
  seasonNumber: int = None
218
230
  episodeNumber: int = None
219
- showTitle:str = None
220
- recordingState:str = None
221
- image:str = None
231
+ showTitle: str = None
232
+ recordingState: str = None
233
+ image: str = None
222
234
 
223
235
  def __init__(self, recording_json):
224
236
  """Init the single recording."""
@@ -233,15 +245,16 @@ class LGHorizonRecordingEpisode:
233
245
  if "poster" in recording_json:
234
246
  self.image = recording_json["poster"]["url"]
235
247
 
248
+
236
249
  class LGHorizonRecordingShow:
237
250
  """Represents a single recording."""
238
-
251
+
239
252
  episodeId: str = None
240
- showTitle:str = None
253
+ showTitle: str = None
241
254
  seasonNumber: int = None
242
255
  episodeNumber: int = None
243
- recordingState:str = None
244
- image:str = None
256
+ recordingState: str = None
257
+ image: str = None
245
258
 
246
259
  def __init__(self, recording_json):
247
260
  """Init the single recording."""
@@ -257,55 +270,67 @@ class LGHorizonRecordingShow:
257
270
 
258
271
 
259
272
  class LGHorizonRecordingListSeasonShow(LGHorizonBaseRecording):
260
- showId:str = None
273
+ showId: str = None
274
+
261
275
  def __init__(self, recording_season_json):
262
276
  """Init the single recording."""
263
-
277
+
264
278
  LGHorizonBaseRecording.__init__(
265
279
  self,
266
280
  recording_season_json["id"],
267
281
  recording_season_json["title"],
268
282
  recording_season_json["poster"]["url"],
269
283
  recording_season_json["channelId"],
270
- recording_season_json["type"]
271
- )
284
+ recording_season_json["type"],
285
+ )
272
286
  if self.type == RECORDING_TYPE_SEASON:
273
287
  self.showId = recording_season_json["showId"]
274
288
  else:
275
289
  self.showId = recording_season_json["id"]
276
290
 
291
+
277
292
  class LGHorizonVod:
278
- title:str = None
293
+ title: str = None
279
294
  image: str = None
280
295
  duration: float = None
296
+
281
297
  def __init__(self, vod_json) -> None:
282
- self.title = vod_json['title']
283
- self.duration = vod_json['duration']
298
+ self.title = vod_json["title"]
299
+ self.duration = vod_json["duration"]
300
+
284
301
 
285
302
  class LGHorizonApp:
286
- title:str = None
287
- image:str = None
288
- def __init__(self, app_state_json:str)->None:
303
+ title: str = None
304
+ image: str = None
305
+
306
+ def __init__(self, app_state_json: str) -> None:
289
307
  self.title = app_state_json["appName"]
290
308
  self.image = app_state_json["logoPath"]
291
309
  if not self.image.startswith("http:"):
292
- self.image = "https:" + self.image
310
+ self.image = "https:" + self.image
311
+
293
312
 
294
313
  class LGHorizonMqttClient:
295
- _brokerUrl:str = None
296
- _mqtt_client :mqtt.Client
314
+ _brokerUrl: str = None
315
+ _mqtt_client: mqtt.Client
297
316
  _auth: LGHorizonAuth
298
317
  clientId: str = None
299
318
  _on_connected_callback: Callable = None
300
- _on_message_callback: Callable[[str, str],None] = None
319
+ _on_message_callback: Callable[[str, str], None] = None
301
320
 
302
321
  @property
303
322
  def is_connected(self):
304
323
  return self._mqtt_client.is_connected
305
324
 
306
- def __init__(self, auth:LGHorizonAuth, country_settings:Dict[str,Any], on_connected_callback:Callable = None, on_message_callback:Callable[[str],None] = None):
325
+ def __init__(
326
+ self,
327
+ auth: LGHorizonAuth,
328
+ mqtt_broker_url: str,
329
+ on_connected_callback: Callable = None,
330
+ on_message_callback: Callable[[str], None] = None,
331
+ ):
307
332
  self._auth = auth
308
- self._brokerUrl = country_settings["mqtt_url"]
333
+ self._brokerUrl = mqtt_broker_url.replace("wss://", "").replace(":443/mqtt", "")
309
334
  self.clientId = make_id()
310
335
  self._mqtt_client = mqtt.Client(self.clientId, transport="websockets")
311
336
  self._mqtt_client.username_pw_set(self._auth.householdId, self._auth.mqttToken)
@@ -314,7 +339,7 @@ class LGHorizonMqttClient:
314
339
  self._mqtt_client.on_connect = self._on_mqtt_connect
315
340
  self._on_connected_callback = on_connected_callback
316
341
  self._on_message_callback = on_message_callback
317
-
342
+
318
343
  def _on_mqtt_connect(self, client, userdata, flags, resultCode):
319
344
  if resultCode == 0:
320
345
  self._mqtt_client.on_message = self._on_client_message
@@ -323,26 +348,38 @@ class LGHorizonMqttClient:
323
348
  self._mqtt_client.subscribe(self._auth.householdId + "/" + self.clientId)
324
349
  self._mqtt_client.subscribe(self._auth.householdId + "/+/status")
325
350
  self._mqtt_client.subscribe(self._auth.householdId + "/+/networkRecordings")
326
- self._mqtt_client.subscribe(self._auth.householdId + "/+/networkRecordings/capacity")
351
+ self._mqtt_client.subscribe(
352
+ self._auth.householdId + "/+/networkRecordings/capacity"
353
+ )
327
354
  self._mqtt_client.subscribe(self._auth.householdId + "/+/localRecordings")
328
- self._mqtt_client.subscribe(self._auth.householdId + "/+/localRecordings/capacity")
355
+ self._mqtt_client.subscribe(
356
+ self._auth.householdId + "/+/localRecordings/capacity"
357
+ )
329
358
  self._mqtt_client.subscribe(self._auth.householdId + "/watchlistService")
330
359
  self._mqtt_client.subscribe(self._auth.householdId + "/purchaseService")
331
- self._mqtt_client.subscribe(self._auth.householdId + "/personalizationService")
360
+ self._mqtt_client.subscribe(
361
+ self._auth.householdId + "/personalizationService"
362
+ )
332
363
  self._mqtt_client.subscribe(self._auth.householdId + "/recordingStatus")
333
- self._mqtt_client.subscribe(self._auth.householdId + "/recordingStatus/lastUserAction")
364
+ self._mqtt_client.subscribe(
365
+ self._auth.householdId + "/recordingStatus/lastUserAction"
366
+ )
334
367
  if self._on_connected_callback:
335
368
  self._on_connected_callback()
336
369
  elif resultCode == 5:
337
- self._mqtt_client.username_pw_set(self._auth.householdId, self._auth.mqttToken)
370
+ self._mqtt_client.username_pw_set(
371
+ self._auth.householdId, self._auth.mqttToken
372
+ )
338
373
  self.connect()
339
374
  else:
340
- _logger.error(f"Cannot connect to MQTT server with resultCode: {resultCode}")
341
-
375
+ _logger.error(
376
+ f"Cannot connect to MQTT server with resultCode: {resultCode}"
377
+ )
378
+
342
379
  def connect(self) -> None:
343
380
  self._mqtt_client.connect(self._brokerUrl, 443)
344
381
  self._mqtt_client.loop_start()
345
-
382
+
346
383
  def _on_client_message(self, client, userdata, message):
347
384
  """Handle messages received by mqtt client."""
348
385
  _logger.debug(f"Received MQTT message. Topic: {message.topic}")
@@ -351,31 +388,38 @@ class LGHorizonMqttClient:
351
388
  if self._on_message_callback:
352
389
  self._on_message_callback(jsonPayload, message.topic)
353
390
 
354
- def publish_message(self, topic:str, json_payload:str) -> None:
355
- self._mqtt_client.publish(topic, json_payload, qos = 2)
356
-
391
+ def publish_message(self, topic: str, json_payload: str) -> None:
392
+ self._mqtt_client.publish(topic, json_payload, qos=2)
393
+
357
394
  def disconnect(self) -> None:
358
395
  if self._mqtt_client.is_connected:
359
396
  self._mqtt_client.disconnect()
360
397
 
361
- class LGHorizonBox:
362
398
 
363
- deviceId:str = None
364
- hashedCPEId:str = None
365
- deviceFriendlyName:str = None
399
+ class LGHorizonBox:
400
+ deviceId: str = None
401
+ hashedCPEId: str = None
402
+ deviceFriendlyName: str = None
366
403
  state: str = None
367
404
  playing_info: LGHorizonPlayingInfo = None
368
- manufacturer:str = None
405
+ manufacturer: str = None
369
406
  model: str = None
370
407
  recording_capacity: int = None
371
-
372
- _mqtt_client:LGHorizonMqttClient
408
+
409
+ _mqtt_client: LGHorizonMqttClient
373
410
  _change_callback: Callable = None
374
411
  _auth: LGHorizonAuth = None
375
- _channels:Dict[str, LGHorizonChannel] = None
412
+ _channels: Dict[str, LGHorizonChannel] = None
376
413
  _message_stamp = None
377
-
378
- def __init__(self, box_json:str, platform_type:Dict[str,str], mqtt_client:LGHorizonMqttClient, auth:LGHorizonAuth, channels:Dict[str, LGHorizonChannel]):
414
+
415
+ def __init__(
416
+ self,
417
+ box_json: str,
418
+ platform_type: Dict[str, str],
419
+ mqtt_client: LGHorizonMqttClient,
420
+ auth: LGHorizonAuth,
421
+ channels: Dict[str, LGHorizonChannel],
422
+ ):
379
423
  self.deviceId = box_json["deviceId"]
380
424
  self.hashedCPEId = box_json["hashedCPEId"]
381
425
  self.deviceFriendlyName = box_json["settings"]["deviceFriendlyName"]
@@ -386,8 +430,8 @@ class LGHorizonBox:
386
430
  if platform_type:
387
431
  self.manufacturer = platform_type["manufacturer"]
388
432
  self.model = platform_type["model"]
389
-
390
- def register_mqtt(self)->None:
433
+
434
+ def register_mqtt(self) -> None:
391
435
  if not self._mqtt_client.is_connected:
392
436
  raise Exception("MQTT client not connected.")
393
437
  topic = f"{self._auth.householdId}/{self._mqtt_client.clientId}/status"
@@ -395,10 +439,10 @@ class LGHorizonBox:
395
439
  "source": self._mqtt_client.clientId,
396
440
  "state": ONLINE_RUNNING,
397
441
  "deviceType": "HGO",
398
- }
442
+ }
399
443
  self._mqtt_client.publish_message(topic, json.dumps(payload))
400
-
401
- def set_callback(self, change_callback:Callable) -> None:
444
+
445
+ def set_callback(self, change_callback: Callable) -> None:
402
446
  self._change_callback = change_callback
403
447
 
404
448
  def update_state(self, payload):
@@ -419,8 +463,10 @@ class LGHorizonBox:
419
463
  if not "CPE.capacity" in payload or not "used" in payload:
420
464
  return
421
465
  self.recording_capacity = payload["used"]
422
-
423
- def update_with_replay_event(self, source_type: str, event:LGHorizonReplayEvent, channel: LGHorizonChannel) -> None:
466
+
467
+ def update_with_replay_event(
468
+ self, source_type: str, event: LGHorizonReplayEvent, channel: LGHorizonChannel
469
+ ) -> None:
424
470
  self.playing_info.set_source_type(source_type)
425
471
  self.playing_info.set_channel(channel.id)
426
472
  self.playing_info.set_channel_title(channel.title)
@@ -432,7 +478,16 @@ class LGHorizonBox:
432
478
  self.playing_info.reset_progress()
433
479
  self._trigger_callback()
434
480
 
435
- def update_with_recording(self, source_type: str, recording:LGHorizonRecordingSingle, channel: LGHorizonChannel, start:float, end: float, last_speed_change:float, relative_position: float) -> None:
481
+ def update_with_recording(
482
+ self,
483
+ source_type: str,
484
+ recording: LGHorizonRecordingSingle,
485
+ channel: LGHorizonChannel,
486
+ start: float,
487
+ end: float,
488
+ last_speed_change: float,
489
+ relative_position: float,
490
+ ) -> None:
436
491
  self.playing_info.set_source_type(source_type)
437
492
  self.playing_info.set_channel(channel.id)
438
493
  self.playing_info.set_channel_title(channel.title)
@@ -446,20 +501,26 @@ class LGHorizonBox:
446
501
  last_update_dt = datetime.fromtimestamp(last_speed_change / 1000.0)
447
502
  self.playing_info.set_last_position_update(last_update_dt)
448
503
  self._trigger_callback()
449
-
450
- def update_with_vod(self, source_type: str, vod:LGHorizonVod, last_speed_change:float, relative_position: float) -> None:
504
+
505
+ def update_with_vod(
506
+ self,
507
+ source_type: str,
508
+ vod: LGHorizonVod,
509
+ last_speed_change: float,
510
+ relative_position: float,
511
+ ) -> None:
451
512
  self.playing_info.set_source_type(source_type)
452
513
  self.playing_info.set_channel(None)
453
514
  self.playing_info.set_channel_title(None)
454
515
  self.playing_info.set_title(vod.title)
455
516
  self.playing_info.set_image(None)
456
517
  self.playing_info.set_duration(vod.duration)
457
- self.playing_info.set_position(relative_position/ 1000.0)
518
+ self.playing_info.set_position(relative_position / 1000.0)
458
519
  last_update_dt = datetime.fromtimestamp(last_speed_change / 1000.0)
459
520
  self.playing_info.set_last_position_update(last_update_dt)
460
521
  self._trigger_callback()
461
-
462
- def update_with_app(self, source_type: str, app:LGHorizonApp) -> None:
522
+
523
+ def update_with_app(self, source_type: str, app: LGHorizonApp) -> None:
463
524
  self.playing_info.set_source_type(source_type)
464
525
  self.playing_info.set_channel(None)
465
526
  self.playing_info.set_channel_title(app.title)
@@ -467,7 +528,7 @@ class LGHorizonBox:
467
528
  self.playing_info.set_image(app.image)
468
529
  self.playing_info.reset_progress()
469
530
  self._trigger_callback()
470
-
531
+
471
532
  def _trigger_callback(self):
472
533
  if self._change_callback:
473
534
  _logger.debug(f"Callback called from box {self.deviceId}")
@@ -475,7 +536,7 @@ class LGHorizonBox:
475
536
 
476
537
  def turn_on(self) -> None:
477
538
  """Turn the settop box on."""
478
-
539
+
479
540
  if self.state == ONLINE_STANDBY:
480
541
  self.send_key_to_box(MEDIA_KEY_POWER)
481
542
 
@@ -489,7 +550,7 @@ class LGHorizonBox:
489
550
  """Pause the given settopbox."""
490
551
  if self.state == ONLINE_RUNNING and not self.playing_info.paused:
491
552
  self.send_key_to_box(MEDIA_KEY_PLAY_PAUSE)
492
-
553
+
493
554
  def play(self) -> None:
494
555
  """Resume the settopbox."""
495
556
  if self.state == ONLINE_RUNNING and self.playing_info.paused:
@@ -534,7 +595,7 @@ class LGHorizonBox:
534
595
  """Return the availability of the settop box."""
535
596
  return self.state == ONLINE_RUNNING or self.state == ONLINE_STANDBY
536
597
 
537
- def set_channel(self, source:str) -> None:
598
+ def set_channel(self, source: str) -> None:
538
599
  """Change te channel from the settopbox."""
539
600
  channel = [src for src in self._channels.values() if src.title == source][0]
540
601
  payload = (
@@ -548,7 +609,9 @@ class LGHorizonBox:
548
609
  + '"},"relativePosition":0,"speed":1}}'
549
610
  )
550
611
 
551
- self._mqtt_client.publish_message(f"{self._auth.householdId}/{self.deviceId}", payload)
612
+ self._mqtt_client.publish_message(
613
+ f"{self._auth.householdId}/{self.deviceId}", payload
614
+ )
552
615
 
553
616
  def play_recording(self, recordingId):
554
617
  """Play recording."""
@@ -562,7 +625,9 @@ class LGHorizonBox:
562
625
  + recordingId
563
626
  + '"},"relativePosition":0}}'
564
627
  )
565
- self._mqtt_client.publish_message(f"{self._auth.householdId}/{self.deviceId}", payload)
628
+ self._mqtt_client.publish_message(
629
+ f"{self._auth.householdId}/{self.deviceId}", payload
630
+ )
566
631
 
567
632
  def send_key_to_box(self, key: str) -> None:
568
633
  """Send emulated (remote) key press to settopbox."""
@@ -571,7 +636,9 @@ class LGHorizonBox:
571
636
  + key
572
637
  + '","eventType":"keyDownUp"}}'
573
638
  )
574
- self._mqtt_client.publish_message(f"{self._auth.householdId}/{self.deviceId}", payload)
639
+ self._mqtt_client.publish_message(
640
+ f"{self._auth.householdId}/{self.deviceId}", payload
641
+ )
575
642
 
576
643
  def _set_unknown_channel_info(self) -> None:
577
644
  """Set unknown channel info."""
@@ -604,8 +671,8 @@ class LGHorizonBox:
604
671
 
605
672
 
606
673
  class LGHorizonCustomer:
607
- customerId:str = None
608
- hashedCustomerId:str = None
674
+ customerId: str = None
675
+ hashedCustomerId: str = None
609
676
  countryId: str = None
610
677
  cityId: int = 0
611
678
  settop_boxes: Dict[str, LGHorizonBox] = None
@@ -617,5 +684,3 @@ class LGHorizonCustomer:
617
684
  self.cityId = json_payload["cityId"]
618
685
  if not "assignedDevices" in json_payload:
619
686
  return
620
-
621
-
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lghorizon
3
- Version: 0.6.4
3
+ Version: 0.6.6
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=_VjVE44ErvJJMnF5QgXdlw_nQzbHZUhGWw5hF40PolQ,426
2
+ lghorizon/const.py,sha256=75Qqmf6XJcBYcOya18cdeRrWq2D_1Q45tz9MLlfQ8Ao,8407
3
+ lghorizon/exceptions.py,sha256=spEjRvbNdce2fauQiOFromAbV1QcfA0uMUt0nRVnnkM,318
4
+ lghorizon/helpers.py,sha256=ZWpi7B3hBvwGV02KWQQHVyj7FLLUDtIvKc-Iqsj5VHA,263
5
+ lghorizon/lghorizon_api.py,sha256=1lNnN6FaPo4oV4x_SLXBA5kLnIILiNz8w3CiRko26Ao,23597
6
+ lghorizon/models.py,sha256=SP12O8y3Bqp-rW47TZQxp25hyptPZAy41UzKnI9lw7E,23549
7
+ lghorizon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ lghorizon-0.6.6.dist-info/LICENSE,sha256=6Dh2tur1gMX3r3rITjVwUONBEJxyyPZDY8p6DZXtimE,1059
9
+ lghorizon-0.6.6.dist-info/METADATA,sha256=76jWhjcQk9sNb4nPcQu1PYAm4b2OiOkiCADqPAHZ6bo,1038
10
+ lghorizon-0.6.6.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
11
+ lghorizon-0.6.6.dist-info/top_level.txt,sha256=usii76_AxGfPI6gjrrh-NyZxcQQuF1B8_Q9kd7sID8Q,10
12
+ lghorizon-0.6.6.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- lghorizon/__init__.py,sha256=_VjVE44ErvJJMnF5QgXdlw_nQzbHZUhGWw5hF40PolQ,426
2
- lghorizon/const.py,sha256=nHmQqbRnKegPXCI2E3gs5nKvbPR3Mp-ptIZYcSMjmoQ,9429
3
- lghorizon/exceptions.py,sha256=spEjRvbNdce2fauQiOFromAbV1QcfA0uMUt0nRVnnkM,318
4
- lghorizon/helpers.py,sha256=ZWpi7B3hBvwGV02KWQQHVyj7FLLUDtIvKc-Iqsj5VHA,263
5
- lghorizon/lghorizon_api.py,sha256=o1EB22gYtnFrt5hTw3V5zIqOq7dyUHkg1MWjZO92Fcc,22402
6
- lghorizon/models.py,sha256=dvlMrrUHoLiT6zAydYnPGNMapFiZaOgDBrZyocrd4II,23028
7
- lghorizon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- lghorizon-0.6.4.dist-info/LICENSE,sha256=6Dh2tur1gMX3r3rITjVwUONBEJxyyPZDY8p6DZXtimE,1059
9
- lghorizon-0.6.4.dist-info/METADATA,sha256=KFX8-j7IgKZTTQp4as72EjDNabnh3filMrJNi5CnEZE,1038
10
- lghorizon-0.6.4.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
11
- lghorizon-0.6.4.dist-info/top_level.txt,sha256=usii76_AxGfPI6gjrrh-NyZxcQQuF1B8_Q9kd7sID8Q,10
12
- lghorizon-0.6.4.dist-info/RECORD,,