pynintendoparental 2.3.2__py3-none-any.whl → 2.3.2.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.
- pynintendoparental/__init__.py +39 -4
- pynintendoparental/_version.py +1 -1
- pynintendoparental/api.py +18 -43
- pynintendoparental/application.py +66 -24
- pynintendoparental/authenticator.py +27 -1
- pynintendoparental/const.py +3 -1
- pynintendoparental/device.py +410 -178
- pynintendoparental/player.py +33 -4
- {pynintendoparental-2.3.2.dist-info → pynintendoparental-2.3.2.1.dist-info}/METADATA +2 -2
- pynintendoparental-2.3.2.1.dist-info/RECORD +17 -0
- {pynintendoparental-2.3.2.dist-info → pynintendoparental-2.3.2.1.dist-info}/WHEEL +1 -1
- pynintendoparental-2.3.2.dist-info/RECORD +0 -17
- {pynintendoparental-2.3.2.dist-info → pynintendoparental-2.3.2.1.dist-info}/licenses/LICENSE +0 -0
- {pynintendoparental-2.3.2.dist-info → pynintendoparental-2.3.2.1.dist-info}/top_level.txt +0 -0
pynintendoparental/device.py
CHANGED
|
@@ -2,32 +2,51 @@
|
|
|
2
2
|
"""Defines a single Nintendo Switch device."""
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
5
|
-
|
|
6
|
-
from datetime import datetime, timedelta, time
|
|
5
|
+
from datetime import datetime, time, timedelta
|
|
7
6
|
from typing import Callable
|
|
8
7
|
|
|
9
8
|
from pynintendoauth.exceptions import HttpException
|
|
10
9
|
|
|
11
10
|
from .api import Api
|
|
11
|
+
from .application import Application
|
|
12
12
|
from .const import _LOGGER, DAYS_OF_WEEK
|
|
13
|
-
from .exceptions import (
|
|
14
|
-
BedtimeOutOfRangeError,
|
|
15
|
-
DailyPlaytimeOutOfRangeError,
|
|
16
|
-
InvalidDeviceStateError,
|
|
17
|
-
)
|
|
18
13
|
from .enum import (
|
|
19
14
|
AlarmSettingState,
|
|
20
15
|
DeviceTimerMode,
|
|
21
16
|
FunctionalRestrictionLevel,
|
|
22
17
|
RestrictionMode,
|
|
23
18
|
)
|
|
19
|
+
from .exceptions import (
|
|
20
|
+
BedtimeOutOfRangeError,
|
|
21
|
+
DailyPlaytimeOutOfRangeError,
|
|
22
|
+
InvalidDeviceStateError,
|
|
23
|
+
)
|
|
24
24
|
from .player import Player
|
|
25
25
|
from .utils import is_awaitable
|
|
26
|
-
from .application import Application
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
class Device:
|
|
30
|
-
"""A device
|
|
29
|
+
"""A Nintendo Switch device.
|
|
30
|
+
|
|
31
|
+
Represents a single Nintendo Switch console with parental controls enabled.
|
|
32
|
+
This class provides methods to monitor and control various parental control settings.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
device_id: Unique identifier for the device.
|
|
36
|
+
name: User-friendly name/label for the device.
|
|
37
|
+
model: Device model (e.g., "Switch", "Switch 2").
|
|
38
|
+
limit_time: Daily playtime limit in minutes (-1 if no limit).
|
|
39
|
+
today_playing_time: Total playing time for the current day in minutes.
|
|
40
|
+
today_time_remaining: Remaining playtime for the current day in minutes.
|
|
41
|
+
players: Dictionary of Player objects keyed by player ID.
|
|
42
|
+
applications: Dictionary of Application objects keyed by application ID.
|
|
43
|
+
timer_mode: Current timer mode (DAILY or EACH_DAY_OF_THE_WEEK).
|
|
44
|
+
bedtime_alarm: Time when bedtime alarm sounds.
|
|
45
|
+
bedtime_end: Time when bedtime restrictions end.
|
|
46
|
+
forced_termination_mode: True if software suspension is enabled at playtime limit.
|
|
47
|
+
alarms_enabled: True if alarms are enabled.
|
|
48
|
+
last_sync: Timestamp of the last sync with Nintendo servers.
|
|
49
|
+
"""
|
|
31
50
|
|
|
32
51
|
def __init__(self, api):
|
|
33
52
|
"""INIT"""
|
|
@@ -65,26 +84,45 @@ class Device:
|
|
|
65
84
|
|
|
66
85
|
@property
|
|
67
86
|
def model(self) -> str:
|
|
68
|
-
"""Return the model.
|
|
87
|
+
"""Return the device model.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Device model name (e.g., "Switch", "Switch 2", or "Unknown").
|
|
91
|
+
"""
|
|
69
92
|
model_map = {"P00": "Switch", "P01": "Switch 2"}
|
|
70
93
|
return model_map.get(self.generation, "Unknown")
|
|
71
94
|
|
|
72
95
|
@property
|
|
73
96
|
def generation(self) -> str | None:
|
|
74
|
-
"""Return the generation.
|
|
97
|
+
"""Return the device generation code.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Platform generation code (e.g., "P00", "P01") or None if unknown.
|
|
101
|
+
"""
|
|
75
102
|
return self.extra.get("platformGeneration", None)
|
|
76
103
|
|
|
77
104
|
@property
|
|
78
105
|
def last_sync(self) -> float | None:
|
|
79
|
-
"""Return the last time this device was synced.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
106
|
+
"""Return the last time this device was synced with Nintendo servers.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Unix timestamp of the last synchronization, or None if never synced.
|
|
110
|
+
"""
|
|
111
|
+
return self.extra.get("synchronizedParentalControlSetting", {}).get("synchronizedAt", None)
|
|
112
|
+
|
|
113
|
+
async def update(self, now: datetime = None):
|
|
114
|
+
"""Update device data from Nintendo servers.
|
|
115
|
+
|
|
116
|
+
Fetches the latest information including daily summaries, parental control
|
|
117
|
+
settings, monthly summaries, and extra device information. Also updates
|
|
118
|
+
all associated players and applications.
|
|
83
119
|
|
|
84
|
-
|
|
85
|
-
|
|
120
|
+
Args:
|
|
121
|
+
now: Optional datetime for the update. Defaults to current time if not provided.
|
|
122
|
+
"""
|
|
86
123
|
_LOGGER.debug(">> Device.update()")
|
|
87
|
-
now
|
|
124
|
+
if now is None:
|
|
125
|
+
now = datetime.now()
|
|
88
126
|
await asyncio.gather(
|
|
89
127
|
self._get_daily_summaries(now),
|
|
90
128
|
self._get_parental_control_setting(now),
|
|
@@ -96,15 +134,40 @@ class Device:
|
|
|
96
134
|
self._update_applications()
|
|
97
135
|
await self._execute_callbacks()
|
|
98
136
|
|
|
99
|
-
def add_device_callback(self, callback):
|
|
100
|
-
"""Add a callback to
|
|
137
|
+
def add_device_callback(self, callback: Callable):
|
|
138
|
+
"""Add a callback function to be called when device state changes.
|
|
139
|
+
|
|
140
|
+
The callback will be invoked whenever the device data is updated.
|
|
141
|
+
Callbacks can be either synchronous or asynchronous functions.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
callback: A callable function. Can be sync or async.
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
ValueError: If the provided object is not callable.
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
```python
|
|
151
|
+
async def on_device_update():
|
|
152
|
+
print("Device updated!")
|
|
153
|
+
|
|
154
|
+
device.add_device_callback(on_device_update)
|
|
155
|
+
```
|
|
156
|
+
"""
|
|
101
157
|
if not callable(callback):
|
|
102
158
|
raise ValueError("Object must be callable.")
|
|
103
159
|
if callback not in self._callbacks:
|
|
104
160
|
self._callbacks.append(callback)
|
|
105
161
|
|
|
106
|
-
def remove_device_callback(self, callback):
|
|
107
|
-
"""Remove a
|
|
162
|
+
def remove_device_callback(self, callback: Callable):
|
|
163
|
+
"""Remove a previously registered device callback.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
callback: The callback function to remove.
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
ValueError: If the provided object is not callable or not found.
|
|
170
|
+
"""
|
|
108
171
|
if not callable(callback):
|
|
109
172
|
raise ValueError("Object must be callable.")
|
|
110
173
|
if callback in self._callbacks:
|
|
@@ -133,37 +196,83 @@ class Device:
|
|
|
133
196
|
await self._execute_callbacks()
|
|
134
197
|
|
|
135
198
|
async def set_new_pin(self, pin: str):
|
|
136
|
-
"""
|
|
199
|
+
"""Set a new PIN code for parental controls on this device.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
pin: The new PIN code to set. Must be a valid 4-digit string.
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
```python
|
|
206
|
+
await device.set_new_pin("1234")
|
|
207
|
+
```
|
|
208
|
+
"""
|
|
137
209
|
_LOGGER.debug(">> Device.set_new_pin(pin=REDACTED)")
|
|
138
|
-
await self._send_api_update(
|
|
139
|
-
self._api.async_update_unlock_code, new_code=pin, device_id=self.device_id
|
|
140
|
-
)
|
|
210
|
+
await self._send_api_update(self._api.async_update_unlock_code, new_code=pin, device_id=self.device_id)
|
|
141
211
|
|
|
142
212
|
async def add_extra_time(self, minutes: int):
|
|
143
|
-
"""Add extra time
|
|
213
|
+
"""Add extra playing time for the current day.
|
|
214
|
+
|
|
215
|
+
This grants additional playing time beyond the configured daily limit
|
|
216
|
+
for the current day only. The extra time does not carry over to other days.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
minutes: Number of additional minutes to add (must be positive).
|
|
220
|
+
|
|
221
|
+
Example:
|
|
222
|
+
```python
|
|
223
|
+
await device.add_extra_time(30) # Add 30 minutes
|
|
224
|
+
```
|
|
225
|
+
"""
|
|
144
226
|
_LOGGER.debug(">> Device.add_extra_time(minutes=%s)", minutes)
|
|
145
227
|
# This endpoint does not return parental control settings, so we call it directly.
|
|
146
228
|
await self._api.async_update_extra_playing_time(self.device_id, minutes)
|
|
147
229
|
await self._get_parental_control_setting(datetime.now())
|
|
148
230
|
|
|
149
231
|
async def set_restriction_mode(self, mode: RestrictionMode):
|
|
150
|
-
"""
|
|
232
|
+
"""Set the restriction mode for playtime limits.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
mode: The restriction mode to set. Options are:
|
|
236
|
+
- RestrictionMode.FORCED_TERMINATION: Software will be suspended when playtime limit is reached.
|
|
237
|
+
- RestrictionMode.ALARM: An alarm will be shown but software won't be suspended.
|
|
238
|
+
|
|
239
|
+
Example:
|
|
240
|
+
```python
|
|
241
|
+
from pynintendoparental.enum import RestrictionMode
|
|
242
|
+
|
|
243
|
+
await device.set_restriction_mode(RestrictionMode.FORCED_TERMINATION)
|
|
244
|
+
```
|
|
245
|
+
"""
|
|
151
246
|
_LOGGER.debug(">> Device.set_restriction_mode(mode=%s)", mode)
|
|
152
|
-
self.parental_control_settings["playTimerRegulations"]["restrictionMode"] = str(
|
|
153
|
-
mode
|
|
154
|
-
)
|
|
247
|
+
self.parental_control_settings["playTimerRegulations"]["restrictionMode"] = str(mode)
|
|
155
248
|
response = await self._api.async_update_play_timer(
|
|
156
249
|
self.device_id,
|
|
157
250
|
self.parental_control_settings["playTimerRegulations"],
|
|
158
251
|
)
|
|
159
252
|
now = datetime.now()
|
|
160
|
-
self._parse_parental_control_setting(
|
|
161
|
-
response["json"], now
|
|
162
|
-
) # Don't need to recalculate times
|
|
253
|
+
self._parse_parental_control_setting(response["json"], now) # Don't need to recalculate times
|
|
163
254
|
await self._execute_callbacks()
|
|
164
255
|
|
|
165
256
|
async def set_bedtime_alarm(self, value: time):
|
|
166
|
-
"""
|
|
257
|
+
"""Set the bedtime alarm time.
|
|
258
|
+
|
|
259
|
+
The bedtime alarm will sound at the specified time to notify that bedtime has arrived.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
value: Time when the bedtime alarm should sound. Must be between 16:00 (4 PM) and 23:00 (11 PM),
|
|
263
|
+
or time(0, 0) to disable the alarm.
|
|
264
|
+
|
|
265
|
+
Raises:
|
|
266
|
+
BedtimeOutOfRangeError: If the time is outside the valid range.
|
|
267
|
+
|
|
268
|
+
Example:
|
|
269
|
+
```python
|
|
270
|
+
from datetime import time
|
|
271
|
+
|
|
272
|
+
await device.set_bedtime_alarm(time(21, 0)) # Set alarm to 9:00 PM
|
|
273
|
+
await device.set_bedtime_alarm(time(0, 0)) # Disable alarm
|
|
274
|
+
```
|
|
275
|
+
"""
|
|
167
276
|
_LOGGER.debug(">> Device.set_bedtime_alarm(value=%s)", value)
|
|
168
277
|
if not ((16 <= value.hour <= 23) or (value.hour == 0 and value.minute == 0)):
|
|
169
278
|
raise BedtimeOutOfRangeError(value=value)
|
|
@@ -180,19 +289,13 @@ class Device:
|
|
|
180
289
|
else:
|
|
181
290
|
regulation = {**regulation, "endingTime": None}
|
|
182
291
|
if self.timer_mode == DeviceTimerMode.DAILY:
|
|
183
|
-
_LOGGER.debug(
|
|
184
|
-
|
|
185
|
-
)
|
|
186
|
-
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"][
|
|
187
|
-
"bedtime"
|
|
188
|
-
] = regulation
|
|
292
|
+
_LOGGER.debug(">> Device.set_bedtime_alarm(value=%s): Daily timer mode", value)
|
|
293
|
+
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"]["bedtime"] = regulation
|
|
189
294
|
else:
|
|
190
|
-
_LOGGER.debug(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
"eachDayOfTheWeekRegulations"
|
|
195
|
-
][DAYS_OF_WEEK[now.weekday()]]["bedtime"] = regulation
|
|
295
|
+
_LOGGER.debug(">> Device.set_bedtime_alarm(value=%s): Each day timer mode", value)
|
|
296
|
+
self.parental_control_settings["playTimerRegulations"]["eachDayOfTheWeekRegulations"][
|
|
297
|
+
DAYS_OF_WEEK[now.weekday()]
|
|
298
|
+
]["bedtime"] = regulation
|
|
196
299
|
_LOGGER.debug(
|
|
197
300
|
">> Device.set_bedtime_alarm(value=%s): Updating bedtime with object %s",
|
|
198
301
|
value,
|
|
@@ -206,28 +309,46 @@ class Device:
|
|
|
206
309
|
)
|
|
207
310
|
|
|
208
311
|
async def set_bedtime_end_time(self, value: time):
|
|
209
|
-
"""
|
|
312
|
+
"""Set the time when bedtime restrictions end.
|
|
313
|
+
|
|
314
|
+
This sets when the device can be used again after bedtime restrictions.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
value: Time when bedtime ends. Must be between 05:00 (5 AM) and 09:00 (9 AM),
|
|
318
|
+
or time(0, 0) to disable bedtime restrictions.
|
|
319
|
+
|
|
320
|
+
Raises:
|
|
321
|
+
BedtimeOutOfRangeError: If the time is outside the valid range.
|
|
322
|
+
|
|
323
|
+
Example:
|
|
324
|
+
```python
|
|
325
|
+
from datetime import time
|
|
326
|
+
|
|
327
|
+
await device.set_bedtime_end_time(time(7, 0)) # Bedtime ends at 7:00 AM
|
|
328
|
+
await device.set_bedtime_end_time(time(0, 0)) # Disable bedtime restrictions
|
|
329
|
+
```
|
|
330
|
+
"""
|
|
210
331
|
_LOGGER.debug(">> Device.set_bedtime_end_time(value=%s)", value)
|
|
211
332
|
if not time(5, 0) <= value <= time(9, 0) and value != time(0, 0):
|
|
212
333
|
raise BedtimeOutOfRangeError(value=value)
|
|
213
334
|
now = datetime.now()
|
|
214
335
|
if self.timer_mode == DeviceTimerMode.DAILY:
|
|
215
|
-
regulation = self.parental_control_settings["playTimerRegulations"][
|
|
216
|
-
"dailyRegulations"
|
|
217
|
-
]
|
|
336
|
+
regulation = self.parental_control_settings["playTimerRegulations"]["dailyRegulations"]
|
|
218
337
|
else:
|
|
219
|
-
regulation = self.parental_control_settings["playTimerRegulations"][
|
|
220
|
-
|
|
221
|
-
]
|
|
338
|
+
regulation = self.parental_control_settings["playTimerRegulations"]["eachDayOfTheWeekRegulations"][
|
|
339
|
+
DAYS_OF_WEEK[now.weekday()]
|
|
340
|
+
]
|
|
222
341
|
new_bedtime_settings = {
|
|
223
342
|
**regulation["bedtime"],
|
|
224
343
|
"enabled": regulation["bedtime"]["endingTime"] or value != time(0, 0),
|
|
225
|
-
"startingTime":
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
344
|
+
"startingTime": (
|
|
345
|
+
{
|
|
346
|
+
"hour": value.hour,
|
|
347
|
+
"minute": value.minute,
|
|
348
|
+
}
|
|
349
|
+
if value != time(0, 0)
|
|
350
|
+
else None
|
|
351
|
+
),
|
|
231
352
|
}
|
|
232
353
|
regulation["bedtime"] = new_bedtime_settings
|
|
233
354
|
await self._send_api_update(
|
|
@@ -238,7 +359,20 @@ class Device:
|
|
|
238
359
|
)
|
|
239
360
|
|
|
240
361
|
async def set_timer_mode(self, mode: DeviceTimerMode):
|
|
241
|
-
"""
|
|
362
|
+
"""Set the timer mode for playtime limits.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
mode: The timer mode to set. Options are:
|
|
366
|
+
- DeviceTimerMode.DAILY: Single playtime limit for all days.
|
|
367
|
+
- DeviceTimerMode.EACH_DAY_OF_THE_WEEK: Different limits for each day of the week.
|
|
368
|
+
|
|
369
|
+
Example:
|
|
370
|
+
```python
|
|
371
|
+
from pynintendoparental.enum import DeviceTimerMode
|
|
372
|
+
|
|
373
|
+
await device.set_timer_mode(DeviceTimerMode.DAILY)
|
|
374
|
+
```
|
|
375
|
+
"""
|
|
242
376
|
_LOGGER.debug(">> Device.set_timer_mode(mode=%s)", mode)
|
|
243
377
|
self.timer_mode = mode
|
|
244
378
|
self.parental_control_settings["playTimerRegulations"]["timerMode"] = str(mode)
|
|
@@ -257,9 +391,41 @@ class Device:
|
|
|
257
391
|
bedtime_end: time | None = None,
|
|
258
392
|
max_daily_playtime: int | float | None = None,
|
|
259
393
|
):
|
|
260
|
-
"""
|
|
394
|
+
"""Set restrictions for a specific day of the week.
|
|
395
|
+
|
|
396
|
+
This method only works when timer_mode is set to EACH_DAY_OF_THE_WEEK.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
enabled: Whether to enable playtime restrictions for this day.
|
|
400
|
+
bedtime_enabled: Whether to enable bedtime restrictions for this day.
|
|
401
|
+
day_of_week: Day of the week (e.g., "MONDAY", "TUESDAY", etc.).
|
|
402
|
+
bedtime_start: Time when bedtime restrictions start (required if bedtime_enabled=True).
|
|
403
|
+
bedtime_end: Time when bedtime restrictions end (required if bedtime_enabled=True).
|
|
404
|
+
max_daily_playtime: Maximum playtime in minutes for this day (required if enabled=True).
|
|
405
|
+
|
|
406
|
+
Raises:
|
|
407
|
+
InvalidDeviceStateError: If timer_mode is not EACH_DAY_OF_THE_WEEK.
|
|
408
|
+
ValueError: If day_of_week is invalid.
|
|
409
|
+
BedtimeOutOfRangeError: If bedtime values are outside valid ranges.
|
|
410
|
+
|
|
411
|
+
Example:
|
|
412
|
+
```python
|
|
413
|
+
from datetime import time
|
|
414
|
+
|
|
415
|
+
# Set Monday restrictions
|
|
416
|
+
await device.set_daily_restrictions(
|
|
417
|
+
enabled=True,
|
|
418
|
+
bedtime_enabled=True,
|
|
419
|
+
day_of_week="MONDAY",
|
|
420
|
+
bedtime_start=time(21, 0), # 9 PM
|
|
421
|
+
bedtime_end=time(7, 0), # 7 AM
|
|
422
|
+
max_daily_playtime=120 # 2 hours
|
|
423
|
+
)
|
|
424
|
+
```
|
|
425
|
+
"""
|
|
261
426
|
_LOGGER.debug(
|
|
262
|
-
">> Device.set_daily_restrictions(enabled=%s, bedtime_enabled=%s, day_of_week=%s,
|
|
427
|
+
">> Device.set_daily_restrictions(enabled=%s, bedtime_enabled=%s, day_of_week=%s, "
|
|
428
|
+
"bedtime_start=%s, bedtime_end=%s, max_daily_playtime=%s)",
|
|
263
429
|
enabled,
|
|
264
430
|
bedtime_enabled,
|
|
265
431
|
day_of_week,
|
|
@@ -268,14 +434,10 @@ class Device:
|
|
|
268
434
|
max_daily_playtime,
|
|
269
435
|
)
|
|
270
436
|
if self.timer_mode != DeviceTimerMode.EACH_DAY_OF_THE_WEEK:
|
|
271
|
-
raise InvalidDeviceStateError(
|
|
272
|
-
"Daily restrictions can only be set when timer_mode is EACH_DAY_OF_THE_WEEK."
|
|
273
|
-
)
|
|
437
|
+
raise InvalidDeviceStateError("Daily restrictions can only be set when timer_mode is EACH_DAY_OF_THE_WEEK.")
|
|
274
438
|
if day_of_week not in DAYS_OF_WEEK:
|
|
275
439
|
raise ValueError(f"Invalid day_of_week: {day_of_week}")
|
|
276
|
-
regulation = self.parental_control_settings["playTimerRegulations"][
|
|
277
|
-
"eachDayOfTheWeekRegulations"
|
|
278
|
-
][day_of_week]
|
|
440
|
+
regulation = self.parental_control_settings["playTimerRegulations"]["eachDayOfTheWeekRegulations"][day_of_week]
|
|
279
441
|
|
|
280
442
|
if bedtime_enabled and bedtime_start is not None and bedtime_end is not None:
|
|
281
443
|
if not time(5, 0) <= bedtime_start <= time(9, 0):
|
|
@@ -321,7 +483,24 @@ class Device:
|
|
|
321
483
|
)
|
|
322
484
|
|
|
323
485
|
async def set_functional_restriction_level(self, level: FunctionalRestrictionLevel):
|
|
324
|
-
"""
|
|
486
|
+
"""Set the content restriction level based on age ratings.
|
|
487
|
+
|
|
488
|
+
This controls which games and applications can be launched based on their age rating.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
level: The restriction level to set. Options are:
|
|
492
|
+
- FunctionalRestrictionLevel.CHILD: Suitable for young children.
|
|
493
|
+
- FunctionalRestrictionLevel.TEEN: Suitable for teenagers.
|
|
494
|
+
- FunctionalRestrictionLevel.YOUNG_ADULT: Suitable for young adults.
|
|
495
|
+
- FunctionalRestrictionLevel.CUSTOM: Custom restrictions.
|
|
496
|
+
|
|
497
|
+
Example:
|
|
498
|
+
```python
|
|
499
|
+
from pynintendoparental.enum import FunctionalRestrictionLevel
|
|
500
|
+
|
|
501
|
+
await device.set_functional_restriction_level(FunctionalRestrictionLevel.TEEN)
|
|
502
|
+
```
|
|
503
|
+
"""
|
|
325
504
|
_LOGGER.debug(">> Device.set_functional_restriction_level(level=%s)", level)
|
|
326
505
|
self.parental_control_settings["functionalRestrictionLevel"] = str(level)
|
|
327
506
|
await self._send_api_update(
|
|
@@ -331,7 +510,20 @@ class Device:
|
|
|
331
510
|
)
|
|
332
511
|
|
|
333
512
|
async def update_max_daily_playtime(self, minutes: int | float = 0):
|
|
334
|
-
"""
|
|
513
|
+
"""Set the maximum daily playtime limit.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
minutes: Maximum playtime in minutes (0-360). Use -1 to remove the limit.
|
|
517
|
+
|
|
518
|
+
Raises:
|
|
519
|
+
DailyPlaytimeOutOfRangeError: If minutes is outside the valid range.
|
|
520
|
+
|
|
521
|
+
Example:
|
|
522
|
+
```python
|
|
523
|
+
await device.update_max_daily_playtime(180) # 3 hours
|
|
524
|
+
await device.update_max_daily_playtime(-1) # Remove limit
|
|
525
|
+
```
|
|
526
|
+
"""
|
|
335
527
|
_LOGGER.debug(">> Device.update_max_daily_playtime(minutes=%s)", minutes)
|
|
336
528
|
if isinstance(minutes, float):
|
|
337
529
|
minutes = int(minutes)
|
|
@@ -348,43 +540,34 @@ class Device:
|
|
|
348
540
|
self.device_id,
|
|
349
541
|
minutes,
|
|
350
542
|
)
|
|
351
|
-
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"][
|
|
352
|
-
"
|
|
353
|
-
]
|
|
543
|
+
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"]["timeToPlayInOneDay"][
|
|
544
|
+
"enabled"
|
|
545
|
+
] = ttpiod
|
|
354
546
|
if (
|
|
355
547
|
"limitTime"
|
|
356
|
-
in self.parental_control_settings["playTimerRegulations"][
|
|
357
|
-
"dailyRegulations"
|
|
358
|
-
]["timeToPlayInOneDay"]
|
|
548
|
+
in self.parental_control_settings["playTimerRegulations"]["dailyRegulations"]["timeToPlayInOneDay"]
|
|
359
549
|
and minutes is None
|
|
360
550
|
):
|
|
361
|
-
self.parental_control_settings["playTimerRegulations"][
|
|
362
|
-
"
|
|
363
|
-
|
|
551
|
+
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"]["timeToPlayInOneDay"].pop(
|
|
552
|
+
"limitTime"
|
|
553
|
+
)
|
|
364
554
|
else:
|
|
365
|
-
self.parental_control_settings["playTimerRegulations"][
|
|
366
|
-
"
|
|
367
|
-
]
|
|
555
|
+
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"]["timeToPlayInOneDay"][
|
|
556
|
+
"limitTime"
|
|
557
|
+
] = minutes
|
|
368
558
|
else:
|
|
369
559
|
_LOGGER.debug(
|
|
370
560
|
"Setting timeToPlayInOneDay.limitTime for device %s to value %s",
|
|
371
561
|
self.device_id,
|
|
372
562
|
minutes,
|
|
373
563
|
)
|
|
374
|
-
day_of_week_regs = self.parental_control_settings["playTimerRegulations"][
|
|
375
|
-
"eachDayOfTheWeekRegulations"
|
|
376
|
-
]
|
|
564
|
+
day_of_week_regs = self.parental_control_settings["playTimerRegulations"]["eachDayOfTheWeekRegulations"]
|
|
377
565
|
current_day = DAYS_OF_WEEK[now.weekday()]
|
|
378
566
|
day_of_week_regs[current_day]["timeToPlayInOneDay"]["enabled"] = ttpiod
|
|
379
|
-
if
|
|
380
|
-
"limitTime" in day_of_week_regs[current_day]["timeToPlayInOneDay"]
|
|
381
|
-
and minutes is None
|
|
382
|
-
):
|
|
567
|
+
if "limitTime" in day_of_week_regs[current_day]["timeToPlayInOneDay"] and minutes is None:
|
|
383
568
|
day_of_week_regs[current_day]["timeToPlayInOneDay"].pop("limitTime")
|
|
384
569
|
else:
|
|
385
|
-
day_of_week_regs[current_day]["timeToPlayInOneDay"]["limitTime"] =
|
|
386
|
-
minutes
|
|
387
|
-
)
|
|
570
|
+
day_of_week_regs[current_day]["timeToPlayInOneDay"]["limitTime"] = minutes
|
|
388
571
|
|
|
389
572
|
await self._send_api_update(
|
|
390
573
|
self._api.async_update_play_timer,
|
|
@@ -411,52 +594,62 @@ class Device:
|
|
|
411
594
|
def _get_today_regulation(self, now: datetime) -> dict:
|
|
412
595
|
"""Returns the regulation settings for the current day."""
|
|
413
596
|
if self.timer_mode == DeviceTimerMode.EACH_DAY_OF_THE_WEEK:
|
|
414
|
-
day_of_week_regs = self.parental_control_settings[
|
|
415
|
-
"
|
|
416
|
-
|
|
597
|
+
day_of_week_regs = self.parental_control_settings["playTimerRegulations"].get(
|
|
598
|
+
"eachDayOfTheWeekRegulations", {}
|
|
599
|
+
)
|
|
417
600
|
return day_of_week_regs.get(DAYS_OF_WEEK[now.weekday()], {})
|
|
418
|
-
return self.parental_control_settings.get("playTimerRegulations", {}).get(
|
|
419
|
-
"dailyRegulations", {}
|
|
420
|
-
)
|
|
601
|
+
return self.parental_control_settings.get("playTimerRegulations", {}).get("dailyRegulations", {})
|
|
421
602
|
|
|
422
603
|
def _parse_parental_control_setting(self, pcs: dict, now: datetime):
|
|
423
604
|
"""Parse a parental control setting request response."""
|
|
424
605
|
_LOGGER.debug(">> Device._parse_parental_control_setting()")
|
|
425
606
|
self.parental_control_settings = pcs["parentalControlSetting"]
|
|
426
|
-
self.parental_control_settings["playTimerRegulations"].pop(
|
|
427
|
-
|
|
607
|
+
self.parental_control_settings["playTimerRegulations"].pop("bedtimeStartingTime", None)
|
|
608
|
+
self.parental_control_settings["playTimerRegulations"].pop("bedtimeEndingTime", None)
|
|
609
|
+
self.forced_termination_mode = self.parental_control_settings["playTimerRegulations"]["restrictionMode"] == str(
|
|
610
|
+
RestrictionMode.FORCED_TERMINATION
|
|
428
611
|
)
|
|
429
|
-
self.parental_control_settings["playTimerRegulations"].pop(
|
|
430
|
-
"bedtimeEndingTime", None
|
|
431
|
-
)
|
|
432
|
-
self.forced_termination_mode = self.parental_control_settings[
|
|
433
|
-
"playTimerRegulations"
|
|
434
|
-
]["restrictionMode"] == str(RestrictionMode.FORCED_TERMINATION)
|
|
435
612
|
|
|
436
613
|
# Update limit and bedtime from regulations
|
|
437
|
-
self.timer_mode = DeviceTimerMode(
|
|
438
|
-
self.parental_control_settings["playTimerRegulations"]["timerMode"]
|
|
439
|
-
)
|
|
614
|
+
self.timer_mode = DeviceTimerMode(self.parental_control_settings["playTimerRegulations"]["timerMode"])
|
|
440
615
|
today_reg = self._get_today_regulation(now)
|
|
441
616
|
limit_time = today_reg.get("timeToPlayInOneDay", {}).get("limitTime")
|
|
442
617
|
self.limit_time = limit_time if limit_time is not None else -1
|
|
443
|
-
extra_playing_time_data = (
|
|
444
|
-
pcs.get("ownedDevice", {}).get("device", {}).get("extraPlayingTime")
|
|
445
|
-
)
|
|
446
|
-
self.extra_playing_time = None
|
|
447
|
-
if extra_playing_time_data:
|
|
448
|
-
self.extra_playing_time = extra_playing_time_data.get("inOneDay", {}).get(
|
|
449
|
-
"duration"
|
|
450
|
-
)
|
|
451
|
-
|
|
452
618
|
bedtime_setting = today_reg.get("bedtime", {})
|
|
453
|
-
|
|
619
|
+
bedtime_enabled = bedtime_setting.get("enabled", False)
|
|
620
|
+
|
|
621
|
+
# Set bedtime_alarm first as we need it for extra_playing_time calculation
|
|
622
|
+
if bedtime_enabled and bedtime_setting.get("endingTime"):
|
|
454
623
|
self.bedtime_alarm = time(
|
|
455
624
|
hour=bedtime_setting["endingTime"]["hour"],
|
|
456
625
|
minute=bedtime_setting["endingTime"]["minute"],
|
|
457
626
|
)
|
|
458
627
|
else:
|
|
459
628
|
self.bedtime_alarm = time(hour=0, minute=0)
|
|
629
|
+
|
|
630
|
+
# Parse extra playing time based on whether bedtime is enabled
|
|
631
|
+
extra_playing_time_data = pcs.get("ownedDevice", {}).get("device", {}).get("extraPlayingTime")
|
|
632
|
+
self.extra_playing_time = None
|
|
633
|
+
if extra_playing_time_data is not None:
|
|
634
|
+
if bedtime_enabled and extra_playing_time_data.get("bedtime"):
|
|
635
|
+
# When bedtime is enabled, calculate the difference between new bedtime and original bedtime
|
|
636
|
+
extended_bedtime_data = extra_playing_time_data.get("bedtime", {}).get("endTime")
|
|
637
|
+
if extended_bedtime_data:
|
|
638
|
+
extended_bedtime = time(
|
|
639
|
+
hour=extended_bedtime_data["hour"],
|
|
640
|
+
minute=extended_bedtime_data["minute"],
|
|
641
|
+
)
|
|
642
|
+
# Calculate difference in minutes
|
|
643
|
+
original_minutes = self.bedtime_alarm.hour * 60 + self.bedtime_alarm.minute
|
|
644
|
+
extended_minutes = extended_bedtime.hour * 60 + extended_bedtime.minute
|
|
645
|
+
self.extra_playing_time = extended_minutes - original_minutes
|
|
646
|
+
# Update bedtime_alarm to the extended bedtime
|
|
647
|
+
self.bedtime_alarm = extended_bedtime
|
|
648
|
+
else:
|
|
649
|
+
# When bedtime is disabled, use inOneDay duration
|
|
650
|
+
in_one_day = extra_playing_time_data.get("inOneDay")
|
|
651
|
+
if in_one_day is not None:
|
|
652
|
+
self.extra_playing_time = in_one_day.get("duration")
|
|
460
653
|
if bedtime_setting.get("enabled") and bedtime_setting["startingTime"]:
|
|
461
654
|
self.bedtime_end = time(
|
|
462
655
|
hour=bedtime_setting["startingTime"]["hour"],
|
|
@@ -505,32 +698,26 @@ class Device:
|
|
|
505
698
|
|
|
506
699
|
if self.limit_time in (-1, None):
|
|
507
700
|
# No play limit, so remaining time is until end of day.
|
|
508
|
-
time_remaining_by_play_limit =
|
|
509
|
-
minutes_in_day - current_minutes_past_midnight
|
|
510
|
-
)
|
|
701
|
+
time_remaining_by_play_limit = minutes_in_day - current_minutes_past_midnight
|
|
511
702
|
else:
|
|
512
|
-
|
|
703
|
+
# Calculate remaining time from play limit, adding any extra playing time
|
|
704
|
+
effective_limit = self.limit_time
|
|
705
|
+
if self.extra_playing_time:
|
|
706
|
+
effective_limit += self.extra_playing_time
|
|
707
|
+
time_remaining_by_play_limit = effective_limit - self.today_playing_time
|
|
513
708
|
|
|
514
709
|
# 2. Calculate remaining time until bedtime
|
|
515
|
-
if (
|
|
516
|
-
self.bedtime_alarm
|
|
517
|
-
and self.bedtime_alarm != time(hour=0, minute=0)
|
|
518
|
-
and self.alarms_enabled
|
|
519
|
-
):
|
|
710
|
+
if self.bedtime_alarm and self.bedtime_alarm != time(hour=0, minute=0) and self.alarms_enabled:
|
|
520
711
|
bedtime_dt = datetime.combine(now.date(), self.bedtime_alarm)
|
|
521
712
|
if bedtime_dt > now: # Bedtime is in the future today
|
|
522
713
|
time_remaining_by_bedtime = (bedtime_dt - now).total_seconds() / 60
|
|
523
714
|
else: # Bedtime has passed
|
|
524
715
|
time_remaining_by_bedtime = 0.0
|
|
525
716
|
else:
|
|
526
|
-
time_remaining_by_bedtime =
|
|
527
|
-
minutes_in_day - current_minutes_past_midnight
|
|
528
|
-
)
|
|
717
|
+
time_remaining_by_bedtime = minutes_in_day - current_minutes_past_midnight
|
|
529
718
|
|
|
530
719
|
# Effective remaining time is the minimum of the two constraints
|
|
531
|
-
effective_remaining_time = min(
|
|
532
|
-
time_remaining_by_play_limit, time_remaining_by_bedtime
|
|
533
|
-
)
|
|
720
|
+
effective_remaining_time = min(time_remaining_by_play_limit, time_remaining_by_bedtime)
|
|
534
721
|
self.today_time_remaining = int(max(0.0, effective_remaining_time))
|
|
535
722
|
_LOGGER.debug(
|
|
536
723
|
"Calculated today's remaining time: %s minutes",
|
|
@@ -538,25 +725,19 @@ class Device:
|
|
|
538
725
|
)
|
|
539
726
|
self.stats_update_failed = False
|
|
540
727
|
except (ValueError, TypeError, AttributeError) as err:
|
|
541
|
-
_LOGGER.warning(
|
|
542
|
-
"Unable to calculate remaining time for device %s: %s", self.name, err
|
|
543
|
-
)
|
|
728
|
+
_LOGGER.warning("Unable to calculate remaining time for device %s: %s", self.name, err)
|
|
544
729
|
|
|
545
730
|
async def _get_parental_control_setting(self, now: datetime):
|
|
546
731
|
"""Retreives parental control settings from the API."""
|
|
547
732
|
_LOGGER.debug(">> Device._get_parental_control_setting()")
|
|
548
|
-
response = await self._api.async_get_device_parental_control_setting(
|
|
549
|
-
device_id=self.device_id
|
|
550
|
-
)
|
|
733
|
+
response = await self._api.async_get_device_parental_control_setting(device_id=self.device_id)
|
|
551
734
|
self._parse_parental_control_setting(response["json"], now)
|
|
552
735
|
self._calculate_times(now)
|
|
553
736
|
|
|
554
737
|
async def _get_daily_summaries(self, now: datetime):
|
|
555
738
|
"""Retrieve daily summaries."""
|
|
556
739
|
_LOGGER.debug(">> Device._get_daily_summaries()")
|
|
557
|
-
response = await self._api.async_get_device_daily_summaries(
|
|
558
|
-
device_id=self.device_id
|
|
559
|
-
)
|
|
740
|
+
response = await self._api.async_get_device_daily_summaries(device_id=self.device_id)
|
|
560
741
|
self.daily_summaries = response["json"]["dailySummaries"]
|
|
561
742
|
_LOGGER.debug("New daily summary %s", self.daily_summaries)
|
|
562
743
|
self._calculate_times(now)
|
|
@@ -566,9 +747,7 @@ class Device:
|
|
|
566
747
|
_LOGGER.debug(">> Device._get_extras()")
|
|
567
748
|
if self.alarms_enabled is not None:
|
|
568
749
|
# first refresh can come from self.extra without http request
|
|
569
|
-
response = await self._api.async_get_account_device(
|
|
570
|
-
device_id=self.device_id
|
|
571
|
-
)
|
|
750
|
+
response = await self._api.async_get_account_device(device_id=self.device_id)
|
|
572
751
|
self.extra = response["json"]["ownedDevice"]["device"]
|
|
573
752
|
status = self.extra["alarmSetting"]["visibility"]
|
|
574
753
|
self.alarms_enabled = status == str(AlarmSettingState.VISIBLE)
|
|
@@ -579,14 +758,30 @@ class Device:
|
|
|
579
758
|
)
|
|
580
759
|
|
|
581
760
|
async def get_monthly_summary(self, search_date: datetime = None) -> dict | None:
|
|
582
|
-
"""
|
|
761
|
+
"""Get the monthly usage summary for a specific month.
|
|
762
|
+
|
|
763
|
+
Args:
|
|
764
|
+
search_date: The month to get the summary for. If None, returns the most recent available summary.
|
|
765
|
+
|
|
766
|
+
Returns:
|
|
767
|
+
Dictionary containing monthly usage data, or None if no summary is available.
|
|
768
|
+
|
|
769
|
+
Example:
|
|
770
|
+
```python
|
|
771
|
+
from datetime import datetime
|
|
772
|
+
|
|
773
|
+
# Get summary for January 2024
|
|
774
|
+
summary = await device.get_monthly_summary(datetime(2024, 1, 1))
|
|
775
|
+
|
|
776
|
+
# Get most recent summary
|
|
777
|
+
summary = await device.get_monthly_summary()
|
|
778
|
+
```
|
|
779
|
+
"""
|
|
583
780
|
_LOGGER.debug(">> Device.get_monthly_summary(search_date=%s)", search_date)
|
|
584
781
|
latest = False
|
|
585
782
|
if search_date is None:
|
|
586
783
|
try:
|
|
587
|
-
response = await self._api.async_get_device_monthly_summaries(
|
|
588
|
-
device_id=self.device_id
|
|
589
|
-
)
|
|
784
|
+
response = await self._api.async_get_device_monthly_summaries(device_id=self.device_id)
|
|
590
785
|
except HttpException as exc:
|
|
591
786
|
_LOGGER.debug("Could not retrieve monthly summaries: %s", exc)
|
|
592
787
|
return
|
|
@@ -594,9 +789,7 @@ class Device:
|
|
|
594
789
|
available_summaries = response["json"]["available"]
|
|
595
790
|
_LOGGER.debug("Available monthly summaries: %s", available_summaries)
|
|
596
791
|
if not available_summaries:
|
|
597
|
-
_LOGGER.debug(
|
|
598
|
-
"No monthly summaries available for device %s", self.device_id
|
|
599
|
-
)
|
|
792
|
+
_LOGGER.debug("No monthly summaries available for device %s", self.device_id)
|
|
600
793
|
return None
|
|
601
794
|
# Use the most recent available summary
|
|
602
795
|
available_summary = available_summaries[0]
|
|
@@ -604,9 +797,7 @@ class Device:
|
|
|
604
797
|
f"{available_summary['year']}-{available_summary['month']}-01",
|
|
605
798
|
"%Y-%m-%d",
|
|
606
799
|
)
|
|
607
|
-
_LOGGER.debug(
|
|
608
|
-
"Using search date %s for monthly summary request", search_date
|
|
609
|
-
)
|
|
800
|
+
_LOGGER.debug("Using search date %s for monthly summary request", search_date)
|
|
610
801
|
latest = True
|
|
611
802
|
|
|
612
803
|
try:
|
|
@@ -629,9 +820,7 @@ class Device:
|
|
|
629
820
|
if latest:
|
|
630
821
|
self.last_month_summary = summary = response["json"]["summary"]
|
|
631
822
|
# Generate player objects
|
|
632
|
-
for player in (
|
|
633
|
-
response.get("json", {}).get("summary", {}).get("players", [])
|
|
634
|
-
):
|
|
823
|
+
for player in response.get("json", {}).get("summary", {}).get("players", []):
|
|
635
824
|
profile = player.get("profile")
|
|
636
825
|
if not profile or not profile.get("playerId"):
|
|
637
826
|
continue
|
|
@@ -643,42 +832,85 @@ class Device:
|
|
|
643
832
|
return response["json"]["summary"]
|
|
644
833
|
|
|
645
834
|
def get_date_summary(self, input_date: datetime = datetime.now()) -> dict:
|
|
646
|
-
"""
|
|
835
|
+
"""Get the usage summary for a specific date.
|
|
836
|
+
|
|
837
|
+
Args:
|
|
838
|
+
input_date: The date to get the summary for. Defaults to today.
|
|
839
|
+
|
|
840
|
+
Returns:
|
|
841
|
+
Dictionary containing usage data for the specified date.
|
|
842
|
+
|
|
843
|
+
Raises:
|
|
844
|
+
ValueError: If no summary exists for the given date or no summaries are available.
|
|
845
|
+
|
|
846
|
+
Example:
|
|
847
|
+
```python
|
|
848
|
+
from datetime import datetime, timedelta
|
|
849
|
+
|
|
850
|
+
# Get today's summary
|
|
851
|
+
today = device.get_date_summary()
|
|
852
|
+
|
|
853
|
+
# Get yesterday's summary
|
|
854
|
+
yesterday = device.get_date_summary(datetime.now() - timedelta(days=1))
|
|
855
|
+
```
|
|
856
|
+
"""
|
|
647
857
|
if not self.daily_summaries:
|
|
648
858
|
raise ValueError("No daily summaries available to search.")
|
|
649
|
-
summary = [
|
|
650
|
-
x
|
|
651
|
-
for x in self.daily_summaries
|
|
652
|
-
if x["date"] == input_date.strftime("%Y-%m-%d")
|
|
653
|
-
]
|
|
859
|
+
summary = [x for x in self.daily_summaries if x["date"] == input_date.strftime("%Y-%m-%d")]
|
|
654
860
|
if len(summary) == 0:
|
|
655
861
|
input_date -= timedelta(days=1)
|
|
656
|
-
summary = [
|
|
657
|
-
x
|
|
658
|
-
for x in self.daily_summaries
|
|
659
|
-
if x["date"] == input_date.strftime("%Y-%m-%d")
|
|
660
|
-
]
|
|
862
|
+
summary = [x for x in self.daily_summaries if x["date"] == input_date.strftime("%Y-%m-%d")]
|
|
661
863
|
if len(summary) == 0:
|
|
662
|
-
raise ValueError(
|
|
663
|
-
f"A summary for the given date {input_date} does not exist"
|
|
664
|
-
)
|
|
864
|
+
raise ValueError(f"A summary for the given date {input_date} does not exist")
|
|
665
865
|
return summary
|
|
666
866
|
|
|
667
867
|
def get_application(self, application_id: str) -> Application:
|
|
668
|
-
"""
|
|
868
|
+
"""Get an Application object by its application ID.
|
|
869
|
+
|
|
870
|
+
Args:
|
|
871
|
+
application_id: The unique identifier for the application.
|
|
872
|
+
|
|
873
|
+
Returns:
|
|
874
|
+
The Application object for the specified ID.
|
|
875
|
+
|
|
876
|
+
Raises:
|
|
877
|
+
ValueError: If the application is not found.
|
|
878
|
+
|
|
879
|
+
Example:
|
|
880
|
+
```python
|
|
881
|
+
app = device.get_application("0100ABC001234000")
|
|
882
|
+
print(f"Application: {app.name}")
|
|
883
|
+
```
|
|
884
|
+
"""
|
|
669
885
|
if application_id in self.applications:
|
|
670
886
|
return self.applications[application_id]
|
|
671
887
|
raise ValueError(f"Application with id {application_id} not found.")
|
|
672
888
|
|
|
673
889
|
def get_player(self, player_id: str) -> Player:
|
|
674
|
-
"""
|
|
890
|
+
"""Get a Player object by player ID.
|
|
891
|
+
|
|
892
|
+
Args:
|
|
893
|
+
player_id: The unique identifier for the player.
|
|
894
|
+
|
|
895
|
+
Returns:
|
|
896
|
+
The Player object for the specified ID.
|
|
897
|
+
|
|
898
|
+
Raises:
|
|
899
|
+
ValueError: If the player is not found.
|
|
900
|
+
|
|
901
|
+
Example:
|
|
902
|
+
```python
|
|
903
|
+
player = device.get_player("player123")
|
|
904
|
+
print(f"Player: {player.nickname}")
|
|
905
|
+
```
|
|
906
|
+
"""
|
|
675
907
|
player = self.players.get(player_id)
|
|
676
908
|
if player:
|
|
677
909
|
return player
|
|
678
910
|
raise ValueError(f"Player with id {player_id} not found.")
|
|
679
911
|
|
|
680
912
|
@classmethod
|
|
681
|
-
async def from_devices_response(cls, raw: dict, api) -> list["Device"]:
|
|
913
|
+
async def from_devices_response(cls, raw: dict, api, now: datetime = None) -> list["Device"]:
|
|
682
914
|
"""Parses a device request response body."""
|
|
683
915
|
_LOGGER.debug("Parsing device list response")
|
|
684
916
|
if "ownedDevices" not in raw.keys():
|
|
@@ -690,7 +922,7 @@ class Device:
|
|
|
690
922
|
parsed.name = device["label"]
|
|
691
923
|
parsed.sync_state = device["parentalControlSettingState"]["updatedAt"]
|
|
692
924
|
parsed.extra = device
|
|
693
|
-
await parsed.update()
|
|
925
|
+
await parsed.update(now=now)
|
|
694
926
|
devices.append(parsed)
|
|
695
927
|
|
|
696
928
|
return devices
|