lghorizon 0.9.0.dev3__py3-none-any.whl → 0.9.1__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.
@@ -8,7 +8,7 @@ import time
8
8
  from abc import ABC, abstractmethod
9
9
  from datetime import datetime
10
10
  from enum import Enum
11
- from typing import Any, Dict, List, Optional
11
+ from typing import Any, Dict, List, Optional, Callable
12
12
 
13
13
  import backoff
14
14
  from aiohttp import ClientResponseError, ClientSession
@@ -52,7 +52,7 @@ class LGHorizonRecordingState(Enum):
52
52
  UNKNOWN = "unknown"
53
53
 
54
54
 
55
- class LGHorizonRecordingType(Enum):
55
+ class LGHorizonRecordingType(Enum): # type: ignore[no-redef]
56
56
  """Enumeration of LG Horizon recording states."""
57
57
 
58
58
  SINGLE = "single"
@@ -91,6 +91,12 @@ class LGHorizonMessage(ABC):
91
91
  def __init__(self, topic: str, payload: dict) -> None:
92
92
  """Abstract base class for LG Horizon messages."""
93
93
  self._topic = topic
94
+ """Initialize the abstract base class for LG Horizon messages.
95
+
96
+ Args:
97
+ topic: The MQTT topic of the message.
98
+ payload: The dictionary payload of the message.
99
+ """
94
100
  self._payload = payload
95
101
 
96
102
  def __repr__(self) -> str:
@@ -122,7 +128,7 @@ class LGHorizonStatusMessage(LGHorizonMessage):
122
128
 
123
129
 
124
130
  class LGHorizonSourceType(Enum):
125
- """Enumeration of LG Horizon message types."""
131
+ """Enumeration of LG Horizon source types."""
126
132
 
127
133
  LINEAR = "linear"
128
134
  REVIEWBUFFER = "reviewBuffer"
@@ -141,7 +147,7 @@ class LGHorizonSource(ABC):
141
147
 
142
148
  @property
143
149
  @abstractmethod
144
- def source_type(self) -> LGHorizonSourceType:
150
+ def source_type(self) -> LGHorizonSourceType: # type: ignore[no-redef]
145
151
  """Return the message type."""
146
152
 
147
153
 
@@ -182,7 +188,7 @@ class LGHorizonReviewBufferSource(LGHorizonSource):
182
188
 
183
189
 
184
190
  class LGHorizonNDVRSource(LGHorizonSource):
185
- """Represent the ReviewBuffer Source of an LG Horizon device."""
191
+ """Represent the Network Digital Video Recorder (NDVR) Source of an LG Horizon device."""
186
192
 
187
193
  @property
188
194
  def recording_id(self) -> str:
@@ -223,7 +229,7 @@ class LGHorizonVODSource(LGHorizonSource):
223
229
 
224
230
 
225
231
  class LGHorizonReplaySource(LGHorizonSource):
226
- """Represent the VOD Source of an LG Horizon device."""
232
+ """Represent the Replay Source of an LG Horizon device."""
227
233
 
228
234
  @property
229
235
  def event_id(self) -> str:
@@ -237,7 +243,7 @@ class LGHorizonReplaySource(LGHorizonSource):
237
243
 
238
244
 
239
245
  class LGHorizonUnknownSource(LGHorizonSource):
240
- """Represent the Linear Source of an LG Horizon device."""
246
+ """Represent an unknown source type of an LG Horizon device."""
241
247
 
242
248
  @property
243
249
  def source_type(self) -> LGHorizonSourceType:
@@ -268,6 +274,13 @@ class LGHorizonPlayerState:
268
274
  """Return the last speed change time."""
269
275
  return self._raw_json.get("lastSpeedChangeTime", 0.0)
270
276
 
277
+ @property
278
+ def relative_position(
279
+ self,
280
+ ) -> int:
281
+ """Return the last speed change time."""
282
+ return self._raw_json.get("relativePosition", 0.0)
283
+
271
284
  @property
272
285
  def source(self) -> LGHorizonSource | None: # Added None to the return type
273
286
  """Return the last speed change time."""
@@ -288,7 +301,7 @@ class LGHorizonPlayerState:
288
301
 
289
302
 
290
303
  class LGHorizonAppsState:
291
- """Represent the State of an LG Horizon device."""
304
+ """Represent the Apps State of an LG Horizon device."""
292
305
 
293
306
  def __init__(self, raw_json: dict) -> None:
294
307
  """Initialize the Apps state."""
@@ -311,7 +324,7 @@ class LGHorizonAppsState:
311
324
 
312
325
 
313
326
  class LGHorizonUIState:
314
- """Represent the State of an LG Horizon device."""
327
+ """Represent the UI State of an LG Horizon device."""
315
328
 
316
329
  _player_state: LGHorizonPlayerState | None = None
317
330
  _apps_state: LGHorizonAppsState | None = None
@@ -319,6 +332,11 @@ class LGHorizonUIState:
319
332
  def __init__(self, raw_json: dict) -> None:
320
333
  """Initialize the State."""
321
334
  self._raw_json = raw_json
335
+ """Initialize the UI State.
336
+
337
+ Args:
338
+ raw_json: The raw JSON dictionary containing UI state information.
339
+ """
322
340
 
323
341
  @property
324
342
  def ui_status(self) -> LGHorizonUIStateType:
@@ -453,6 +471,7 @@ class LGHorizonAuth:
453
471
  _country_code: str
454
472
  _host: str
455
473
  _use_refresh_token: bool
474
+ _token_refresh_callback: Callable[str, None] | None # pyright: ignore[reportInvalidTypeForm]
456
475
 
457
476
  def __init__(
458
477
  self,
@@ -474,6 +493,7 @@ class LGHorizonAuth:
474
493
  self._host = COUNTRY_SETTINGS[country_code]["api_url"]
475
494
  self._use_refresh_token = COUNTRY_SETTINGS[country_code]["use_refreshtoken"]
476
495
  self._service_config = None
496
+ self._token_refresh_callback = None
477
497
 
478
498
  @property
479
499
  def websession(self) -> ClientSession:
@@ -589,6 +609,8 @@ class LGHorizonAuth:
589
609
  self.household_id = auth_json["householdId"]
590
610
  self.access_token = auth_json["accessToken"]
591
611
  self.refresh_token = auth_json["refreshToken"]
612
+ if self._token_refresh_callback:
613
+ self._token_refresh_callback(self.refresh_token)
592
614
  self.username = auth_json["username"]
593
615
  self.token_expiry = auth_json["refreshTokenExpiry"]
594
616
 
@@ -669,6 +691,16 @@ class LGHorizonChannel:
669
691
  """Returns the channel number."""
670
692
  return self.channel_json["logicalChannelNumber"]
671
693
 
694
+ @property
695
+ def replay_pre_padding(self) -> int:
696
+ """Returns the channel number."""
697
+ return self.channel_json.get("replayPrePadding", 0)
698
+
699
+ @property
700
+ def replay_post_padding(self) -> int:
701
+ """Returns the channel number."""
702
+ return self.channel_json.get("replayPostPadding", 0)
703
+
672
704
  @property
673
705
  def is_radio(self) -> bool:
674
706
  """Returns if the channel is a radio channel."""
@@ -820,33 +852,44 @@ class LGHorizonCustomer:
820
852
  class LGHorizonDeviceState:
821
853
  """Represent current state of a box."""
822
854
 
855
+ _id: Optional[str]
823
856
  _channel_id: Optional[str]
824
857
  _channel_name: Optional[str]
825
- _title: Optional[str]
858
+ _show_title: Optional[str]
859
+ _episode_title: Optional[str]
860
+ _season_number: Optional[int]
861
+ _episode_number: Optional[int]
826
862
  _image: Optional[str]
827
863
  _source_type: LGHorizonSourceType
828
864
  _paused: bool
829
- _sub_title: Optional[str]
830
865
  _duration: Optional[float]
831
866
  _position: Optional[float]
832
867
  _last_position_update: Optional[datetime]
833
868
  _state: LGHorizonRunningState
834
869
  _speed: Optional[int]
870
+ _start_time: Optional[int]
871
+ _end_time: Optional[int]
835
872
 
836
873
  def __init__(self) -> None:
837
874
  """Initialize the playing info."""
838
875
  self._channel_id = None
839
- self._title = None
876
+ self._show_title = None
877
+ self._episode_title = None
878
+ self._season_number = None
879
+ self._episode_number = None
840
880
  self._image = None
841
881
  self._source_type = LGHorizonSourceType.UNKNOWN
882
+ self._ui_state_type = LGHorizonUIStateType.UNKNOWN
842
883
  self._paused = False
843
- self.sub_title = None
844
884
  self._duration = None
845
885
  self._position = None
846
886
  self._last_position_update = None
847
887
  self._state = LGHorizonRunningState.UNKNOWN
848
888
  self._speed = None
849
889
  self._channel_name = None
890
+ self._id = None
891
+ self._start_time = None
892
+ self._end_time = None
850
893
 
851
894
  @property
852
895
  def state(self) -> LGHorizonRunningState:
@@ -868,6 +911,16 @@ class LGHorizonDeviceState:
868
911
  """Set the channel ID."""
869
912
  self._channel_id = value
870
913
 
914
+ @property
915
+ def id(self) -> Optional[str]:
916
+ """Return the channel ID."""
917
+ return self._id
918
+
919
+ @id.setter
920
+ def id(self, value: Optional[str]) -> None:
921
+ """Set the channel ID."""
922
+ self._id = value
923
+
871
924
  @property
872
925
  def channel_name(self) -> Optional[str]:
873
926
  """Return the channel ID."""
@@ -879,14 +932,74 @@ class LGHorizonDeviceState:
879
932
  self._channel_name = value
880
933
 
881
934
  @property
882
- def title(self) -> Optional[str]:
935
+ def show_title(self) -> Optional[str]:
936
+ """Return the title."""
937
+ return self._show_title
938
+
939
+ @show_title.setter
940
+ def show_title(self, value: Optional[str]) -> None:
941
+ """Set the title."""
942
+ self._show_title = value
943
+
944
+ @property
945
+ def app_name(self) -> Optional[str]:
946
+ """Return the title."""
947
+ return self._app_name
948
+
949
+ @app_name.setter
950
+ def app_name(self, value: Optional[str]) -> None:
951
+ """Set the title."""
952
+ self._app_name = value
953
+
954
+ @property
955
+ def episode_title(self) -> Optional[str]:
956
+ """Return the title."""
957
+ return self._episode_title
958
+
959
+ @episode_title.setter
960
+ def episode_title(self, value: Optional[str]) -> None:
961
+ """Set the title."""
962
+ self._episode_title = value
963
+
964
+ @property
965
+ def episode_number(self) -> Optional[int]:
966
+ """Return the title."""
967
+ return self._episode_number
968
+
969
+ @episode_number.setter
970
+ def episode_number(self, value: Optional[int]) -> None:
971
+ """Set the title."""
972
+ self._episode_number = value
973
+
974
+ @property
975
+ def season_number(self) -> Optional[int]:
976
+ """Return the title."""
977
+ return self._season_number
978
+
979
+ @season_number.setter
980
+ def season_number(self, value: Optional[int]) -> None:
981
+ """Set the title."""
982
+ self._season_number = value
983
+
984
+ @property
985
+ def start_time(self) -> Optional[int]:
883
986
  """Return the title."""
884
- return self._title
987
+ return self._start_time
885
988
 
886
- @title.setter
887
- def title(self, value: Optional[str]) -> None:
989
+ @start_time.setter
990
+ def start_time(self, value: Optional[int]) -> None:
888
991
  """Set the title."""
889
- self._title = value
992
+ self._start_time = value
993
+
994
+ @property
995
+ def end_time(self) -> Optional[int]:
996
+ """Return the title."""
997
+ return self._end_time
998
+
999
+ @end_time.setter
1000
+ def end_time(self, value: Optional[int]) -> None:
1001
+ """Set the title."""
1002
+ self._end_time = value
890
1003
 
891
1004
  @property
892
1005
  def image(self) -> Optional[str]:
@@ -908,6 +1021,16 @@ class LGHorizonDeviceState:
908
1021
  """Set the source type."""
909
1022
  self._source_type = value
910
1023
 
1024
+ @property
1025
+ def ui_state_type(self) -> LGHorizonUIStateType:
1026
+ """Return the source type."""
1027
+ return self._ui_state_type
1028
+
1029
+ @ui_state_type.setter
1030
+ def ui_state_type(self, value: LGHorizonUIStateType) -> None:
1031
+ """Set the source type."""
1032
+ self._ui_state_type = value
1033
+
911
1034
  @property
912
1035
  def paused(self) -> bool:
913
1036
  """Return if the media is paused."""
@@ -915,16 +1038,6 @@ class LGHorizonDeviceState:
915
1038
  return False
916
1039
  return self.speed == 0
917
1040
 
918
- @property
919
- def sub_title(self) -> Optional[str]:
920
- """Return the channel title."""
921
- return self._sub_title
922
-
923
- @sub_title.setter
924
- def sub_title(self, value: Optional[str]) -> None:
925
- """Set the channel title."""
926
- self._sub_title = value
927
-
928
1041
  @property
929
1042
  def duration(self) -> Optional[float]:
930
1043
  """Return the duration of the media."""
@@ -946,12 +1059,12 @@ class LGHorizonDeviceState:
946
1059
  self._position = value
947
1060
 
948
1061
  @property
949
- def last_position_update(self) -> Optional[datetime]:
1062
+ def last_position_update(self) -> Optional[int]:
950
1063
  """Return the last time the position was updated."""
951
1064
  return self._last_position_update
952
1065
 
953
1066
  @last_position_update.setter
954
- def last_position_update(self, value: Optional[datetime]) -> None:
1067
+ def last_position_update(self, value: Optional[int]) -> None:
955
1068
  """Set the last position update time."""
956
1069
  self._last_position_update = value
957
1070
 
@@ -974,12 +1087,18 @@ class LGHorizonDeviceState:
974
1087
  async def reset(self) -> None:
975
1088
  """Reset all playing information."""
976
1089
  self.channel_id = None
977
- self.title = None
978
- self.sub_title = None
1090
+ self.episode_number = None
1091
+ self.season_number = None
1092
+ self.episode_title = None
1093
+ self.show_title = None
1094
+ self.app_name = None
979
1095
  self.image = None
980
1096
  self.source_type = LGHorizonSourceType.UNKNOWN
981
1097
  self.speed = None
982
1098
  self.channel_name = None
1099
+ self.id = None
1100
+ self.start_time = None
1101
+ self.end_time = None
983
1102
  await self.reset_progress()
984
1103
 
985
1104
 
@@ -1028,6 +1147,16 @@ class LGHorizonReplayEvent:
1028
1147
  """Return the season number."""
1029
1148
  return self._raw_json.get("seasonNumber")
1030
1149
 
1150
+ @property
1151
+ def start_time(self) -> Optional[int]:
1152
+ """Return the season number."""
1153
+ return self._raw_json.get("startTime", None)
1154
+
1155
+ @property
1156
+ def end_time(self) -> Optional[int]:
1157
+ """Return the season number."""
1158
+ return self._raw_json.get("endTime", None)
1159
+
1031
1160
  @property
1032
1161
  def title(self) -> str:
1033
1162
  """Return the title of the event."""
@@ -1049,10 +1178,6 @@ class LGHorizonReplayEvent:
1049
1178
  full_title += f": {self.episode_name}"
1050
1179
  return full_title
1051
1180
 
1052
- def __repr__(self) -> str:
1053
- """Return a string representation of the replay event."""
1054
- return f"LGHorizonReplayEvent(title='{self.title}', channel_id='{self.channel_id}', event_id='{self.event_id}')"
1055
-
1056
1181
 
1057
1182
  class LGHorizonVODType(Enum):
1058
1183
  """Enumeration of LG Horizon VOD types."""
@@ -1066,6 +1191,11 @@ class LGHorizonVOD:
1066
1191
  """LGHorizon video on demand."""
1067
1192
 
1068
1193
  def __init__(self, vod_json) -> None:
1194
+ """Initialize an LG Horizon VOD object.
1195
+
1196
+ Args:
1197
+ vod_json: The raw JSON dictionary containing VOD information.
1198
+ """
1069
1199
  self._vod_json = vod_json
1070
1200
 
1071
1201
  @property
@@ -1079,26 +1209,14 @@ class LGHorizonVOD:
1079
1209
  return self._vod_json["id"]
1080
1210
 
1081
1211
  @property
1082
- def season_number(self) -> Optional[int]:
1212
+ def season(self) -> Optional[int]:
1083
1213
  """Return the season number of the recording."""
1084
- return self._vod_json.get("seasonNumber", None)
1214
+ return self._vod_json.get("season", None)
1085
1215
 
1086
1216
  @property
1087
- def episode_number(self) -> Optional[int]:
1217
+ def episode(self) -> Optional[int]:
1088
1218
  """Return the episode number of the recording."""
1089
- return self._vod_json.get("episodeNumber", None)
1090
-
1091
- @property
1092
- def full_episode_title(self) -> Optional[str]:
1093
- """Return the ID of the VOD."""
1094
- if self.vod_type != LGHorizonVODType.EPISODE:
1095
- return None
1096
- if not self.season_number and not self.episode_number:
1097
- return None
1098
- full_title = f"""S{self.season_number:02d}E{self.episode_number:02d}"""
1099
- if self.title:
1100
- full_title += f": {self.title}"
1101
- return full_title
1219
+ return self._vod_json.get("episode", None)
1102
1220
 
1103
1221
  @property
1104
1222
  def title(self) -> str:
@@ -1117,7 +1235,7 @@ class LGHorizonVOD:
1117
1235
 
1118
1236
 
1119
1237
  class LGHOrizonRelevantEpisode:
1120
- """LGHorizon recording."""
1238
+ """Represents a relevant episode within a recording season or show."""
1121
1239
 
1122
1240
  def __init__(self, episode_json: dict) -> None:
1123
1241
  """Abstract base class for LG Horizon recordings."""
@@ -1178,7 +1296,7 @@ class LGHorizonRecording(ABC):
1178
1296
  @property
1179
1297
  def title(self) -> str:
1180
1298
  """Return the title of the recording."""
1181
- return self._recording_payload["title"]
1299
+ return self._recording_payload.get("title", "unknown")
1182
1300
 
1183
1301
  @property
1184
1302
  def channel_id(self) -> str:
@@ -1194,7 +1312,10 @@ class LGHorizonRecording(ABC):
1194
1312
  return None
1195
1313
 
1196
1314
  def __init__(self, recording_payload: dict) -> None:
1197
- """Abstract base class for LG Horizon recordings."""
1315
+ """Abstract base class for LG Horizon recordings.
1316
+ Args:
1317
+ recording_payload: The raw JSON dictionary containing recording information.
1318
+ """
1198
1319
  self._recording_payload = recording_payload
1199
1320
 
1200
1321
 
@@ -1206,6 +1327,11 @@ class LGHorizonRecordingSingle(LGHorizonRecording):
1206
1327
  """Return the episode title of the recording."""
1207
1328
  return self._recording_payload.get("episodeTitle", None)
1208
1329
 
1330
+ @property
1331
+ def episode_id(self) -> Optional[str]:
1332
+ """Return the episode title of the recording."""
1333
+ return self._recording_payload.get("episodeId", None)
1334
+
1209
1335
  @property
1210
1336
  def season_number(self) -> Optional[int]:
1211
1337
  """Return the season number of the recording."""
@@ -1221,29 +1347,39 @@ class LGHorizonRecordingSingle(LGHorizonRecording):
1221
1347
  """Return the show ID of the recording."""
1222
1348
  return self._recording_payload.get("showId", None)
1223
1349
 
1350
+ @property
1351
+ def show_title(self) -> Optional[str]:
1352
+ """Return the show ID of the recording."""
1353
+ return self._recording_payload.get("showTitle", None)
1354
+
1224
1355
  @property
1225
1356
  def season_id(self) -> Optional[str]:
1226
1357
  """Return the season ID of the recording."""
1227
1358
  return self._recording_payload.get("seasonId", None)
1228
1359
 
1229
- @property
1230
- def full_episode_title(self) -> Optional[str]:
1231
- """Return the full episode title of the recording."""
1232
- if not self.season_number and not self.episode_number:
1233
- return None
1234
- full_title = f"""S{self.season_number:02d}E{self.episode_number:02d}"""
1235
- if self.episode_title:
1236
- full_title += f": {self.episode_title}"
1237
- return full_title
1238
-
1239
1360
  @property
1240
1361
  def channel_id(self) -> Optional[str]:
1241
1362
  """Return the channel ID of the recording."""
1242
1363
  return self._recording_payload.get("channelId", None)
1243
1364
 
1365
+ @property
1366
+ def duration(self) -> Optional[int]:
1367
+ """Return the title."""
1368
+ return self.recording_payload.get("duration", None)
1369
+
1370
+ @property
1371
+ def start_time(self) -> Optional[int]:
1372
+ """Return the title."""
1373
+ return self.recording_payload.get("startTime", None)
1374
+
1375
+ @property
1376
+ def end_time(self) -> Optional[int]:
1377
+ """Return the title."""
1378
+ return self.recording_payload.get("endTime", None)
1379
+
1244
1380
 
1245
1381
  class LGHorizonRecordingSeason(LGHorizonRecording):
1246
- """LGHorizon recording."""
1382
+ """Represents an LG Horizon recording season."""
1247
1383
 
1248
1384
  _most_relevant_epsode: Optional[LGHOrizonRelevantEpisode]
1249
1385
 
@@ -1264,6 +1400,11 @@ class LGHorizonRecordingSeason(LGHorizonRecording):
1264
1400
  """Return the season title of the recording."""
1265
1401
  return self._recording_payload.get("seasonTitle", "")
1266
1402
 
1403
+ @property
1404
+ def show_id(self) -> str:
1405
+ """Return the season title of the recording."""
1406
+ return self._recording_payload.get("showId", "")
1407
+
1267
1408
  @property
1268
1409
  def most_relevant_episode(self) -> Optional[LGHOrizonRelevantEpisode]:
1269
1410
  """Return the most relevant episode of the season."""
@@ -1271,7 +1412,7 @@ class LGHorizonRecordingSeason(LGHorizonRecording):
1271
1412
 
1272
1413
 
1273
1414
  class LGHorizonRecordingShow(LGHorizonRecording):
1274
- """LGHorizon recording."""
1415
+ """Represents an LG Horizon recording show."""
1275
1416
 
1276
1417
  _most_relevant_epsode: Optional[LGHOrizonRelevantEpisode]
1277
1418
 
@@ -1294,7 +1435,7 @@ class LGHorizonRecordingShow(LGHorizonRecording):
1294
1435
 
1295
1436
 
1296
1437
  class LGHorizonRecordingList:
1297
- """LGHorizon recording."""
1438
+ """Represents a list of LG Horizon recordings."""
1298
1439
 
1299
1440
  @property
1300
1441
  def total(self) -> int:
@@ -1302,9 +1443,49 @@ class LGHorizonRecordingList:
1302
1443
  return len(self._recordings)
1303
1444
 
1304
1445
  def __init__(self, recordings: List[LGHorizonRecording]) -> None:
1305
- """Abstract base class for LG Horizon recordings."""
1446
+ """Initialize an LG Horizon recording list.
1447
+
1448
+ Args:
1449
+ recordings: A list of LGHorizonRecording objects.
1450
+ """
1306
1451
  self._recordings = recordings
1307
1452
 
1453
+ @property
1454
+ def recordings(self) -> List[LGHorizonRecording]:
1455
+ """Return the total number of recordings."""
1456
+ return self._recordings
1457
+
1458
+
1459
+ class LGHorizonShowRecordingList(LGHorizonRecordingList):
1460
+ """LGHorizon recording."""
1461
+
1462
+ def __init__(
1463
+ self,
1464
+ show_title: Optional[str],
1465
+ show_image,
1466
+ recordings: List[LGHorizonRecording],
1467
+ ) -> None:
1468
+ """Initialize an LG Horizon show recording list.
1469
+
1470
+ Args:
1471
+ show_title: The title of the show.
1472
+ show_image: The image URL for the show.
1473
+ recordings: A list of LGHorizonRecording objects belonging to the show.
1474
+ """
1475
+ super().__init__(recordings)
1476
+ self._show_title = show_title
1477
+ self._show_image = show_image
1478
+
1479
+ @property
1480
+ def show_title(self) -> str:
1481
+ """Title of the show."""
1482
+ return self._show_title
1483
+
1484
+ @property
1485
+ def show_image(self) -> Optional[str]:
1486
+ """Image of the show."""
1487
+ return self._show_image
1488
+
1308
1489
 
1309
1490
  class LGHorizonRecordingQuota:
1310
1491
  """LGHorizon recording quota."""