pynintendoparental 1.1.3__py3-none-any.whl → 2.1.0__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 +11 -13
- pynintendoparental/_version.py +1 -1
- pynintendoparental/api.py +63 -101
- pynintendoparental/application.py +17 -11
- pynintendoparental/authenticator.py +18 -0
- pynintendoparental/const.py +21 -12
- pynintendoparental/device.py +219 -109
- pynintendoparental/enum.py +6 -0
- pynintendoparental/exceptions.py +5 -19
- pynintendoparental/player.py +20 -9
- pynintendoparental/utils.py +1 -0
- {pynintendoparental-1.1.3.dist-info → pynintendoparental-2.1.0.dist-info}/METADATA +8 -2
- pynintendoparental-2.1.0.dist-info/RECORD +17 -0
- pynintendoparental/authenticator/__init__.py +0 -226
- pynintendoparental/authenticator/const.py +0 -29
- pynintendoparental-1.1.3.dist-info/RECORD +0 -18
- {pynintendoparental-1.1.3.dist-info → pynintendoparental-2.1.0.dist-info}/WHEEL +0 -0
- {pynintendoparental-1.1.3.dist-info → pynintendoparental-2.1.0.dist-info}/licenses/LICENSE +0 -0
- {pynintendoparental-1.1.3.dist-info → pynintendoparental-2.1.0.dist-info}/top_level.txt +0 -0
pynintendoparental/device.py
CHANGED
|
@@ -6,14 +6,20 @@ import asyncio
|
|
|
6
6
|
from datetime import datetime, timedelta, time
|
|
7
7
|
from typing import Callable
|
|
8
8
|
|
|
9
|
+
from pynintendoauth.exceptions import HttpException
|
|
10
|
+
|
|
9
11
|
from .api import Api
|
|
10
12
|
from .const import _LOGGER, DAYS_OF_WEEK
|
|
11
|
-
from .exceptions import
|
|
13
|
+
from .exceptions import (
|
|
14
|
+
BedtimeOutOfRangeError,
|
|
15
|
+
DailyPlaytimeOutOfRangeError,
|
|
16
|
+
)
|
|
12
17
|
from .enum import AlarmSettingState, DeviceTimerMode, RestrictionMode
|
|
13
18
|
from .player import Player
|
|
14
19
|
from .utils import is_awaitable
|
|
15
20
|
from .application import Application
|
|
16
21
|
|
|
22
|
+
|
|
17
23
|
class Device:
|
|
18
24
|
"""A device"""
|
|
19
25
|
|
|
@@ -26,8 +32,9 @@ class Device:
|
|
|
26
32
|
self._api: Api = api
|
|
27
33
|
self.daily_summaries: dict = {}
|
|
28
34
|
self.parental_control_settings: dict = {}
|
|
29
|
-
self.players:
|
|
35
|
+
self.players: dict[str, Player] = {}
|
|
30
36
|
self.limit_time: int | float | None = 0
|
|
37
|
+
self.extra_playing_time: int | None = None
|
|
31
38
|
self.timer_mode: DeviceTimerMode | None = None
|
|
32
39
|
self.today_playing_time: int | float = 0
|
|
33
40
|
self.today_time_remaining: int | float = 0
|
|
@@ -52,10 +59,7 @@ class Device:
|
|
|
52
59
|
@property
|
|
53
60
|
def model(self) -> str:
|
|
54
61
|
"""Return the model."""
|
|
55
|
-
model_map = {
|
|
56
|
-
"P00": "Switch",
|
|
57
|
-
"P01": "Switch 2"
|
|
58
|
-
}
|
|
62
|
+
model_map = {"P00": "Switch", "P01": "Switch 2"}
|
|
59
63
|
return model_map.get(self.generation, "Unknown")
|
|
60
64
|
|
|
61
65
|
@property
|
|
@@ -63,21 +67,25 @@ class Device:
|
|
|
63
67
|
"""Return the generation."""
|
|
64
68
|
return self.extra.get("platformGeneration", None)
|
|
65
69
|
|
|
70
|
+
@property
|
|
71
|
+
def last_sync(self) -> float | None:
|
|
72
|
+
"""Return the last time this device was synced."""
|
|
73
|
+
return self.extra.get("synchronizedParentalControlSetting", {}).get(
|
|
74
|
+
"synchronizedAt", None
|
|
75
|
+
)
|
|
76
|
+
|
|
66
77
|
async def update(self):
|
|
67
78
|
"""Update data."""
|
|
68
79
|
_LOGGER.debug(">> Device.update()")
|
|
69
80
|
now = datetime.now()
|
|
70
81
|
await asyncio.gather(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
self._get_daily_summaries(now),
|
|
83
|
+
self._get_parental_control_setting(now),
|
|
84
|
+
self.get_monthly_summary(),
|
|
85
|
+
self._get_extras(),
|
|
75
86
|
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
else:
|
|
79
|
-
for player in self.players:
|
|
80
|
-
player.update_from_daily_summary(self.daily_summaries)
|
|
87
|
+
for player in self.players.values():
|
|
88
|
+
player.update_from_daily_summary(self.daily_summaries)
|
|
81
89
|
await self._execute_callbacks()
|
|
82
90
|
|
|
83
91
|
def add_device_callback(self, callback):
|
|
@@ -114,9 +122,7 @@ class Device:
|
|
|
114
122
|
"""Updates the pin for the device."""
|
|
115
123
|
_LOGGER.debug(">> Device.set_new_pin(pin=REDACTED)")
|
|
116
124
|
await self._send_api_update(
|
|
117
|
-
self._api.async_update_unlock_code,
|
|
118
|
-
new_code=pin,
|
|
119
|
-
device_id=self.device_id
|
|
125
|
+
self._api.async_update_unlock_code, new_code=pin, device_id=self.device_id
|
|
120
126
|
)
|
|
121
127
|
|
|
122
128
|
async def add_extra_time(self, minutes: int):
|
|
@@ -129,25 +135,30 @@ class Device:
|
|
|
129
135
|
async def set_restriction_mode(self, mode: RestrictionMode):
|
|
130
136
|
"""Updates the restriction mode of the device."""
|
|
131
137
|
_LOGGER.debug(">> Device.set_restriction_mode(mode=%s)", mode)
|
|
132
|
-
self.parental_control_settings["playTimerRegulations"]["restrictionMode"] = str(
|
|
138
|
+
self.parental_control_settings["playTimerRegulations"]["restrictionMode"] = str(
|
|
139
|
+
mode
|
|
140
|
+
)
|
|
133
141
|
response = await self._api.async_update_play_timer(
|
|
134
142
|
settings={
|
|
135
143
|
"deviceId": self.device_id,
|
|
136
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
144
|
+
"playTimerRegulations": self.parental_control_settings[
|
|
145
|
+
"playTimerRegulations"
|
|
146
|
+
],
|
|
137
147
|
}
|
|
138
148
|
)
|
|
139
149
|
now = datetime.now()
|
|
140
|
-
self._parse_parental_control_setting(
|
|
150
|
+
self._parse_parental_control_setting(
|
|
151
|
+
response["json"], now
|
|
152
|
+
) # Don't need to recalculate times
|
|
141
153
|
await self._execute_callbacks()
|
|
142
154
|
|
|
143
155
|
async def set_bedtime_alarm(self, value: time):
|
|
144
156
|
"""Update the bedtime alarm for the device."""
|
|
145
|
-
_LOGGER.debug(">> Device.set_bedtime_alarm(value=%s)",
|
|
146
|
-
value)
|
|
157
|
+
_LOGGER.debug(">> Device.set_bedtime_alarm(value=%s)", value)
|
|
147
158
|
if not (
|
|
148
|
-
(16 <= value.hour <= 22)
|
|
149
|
-
(value.hour == 23 and value.minute == 0)
|
|
150
|
-
(value.hour == 0 and value.minute == 0)
|
|
159
|
+
(16 <= value.hour <= 22)
|
|
160
|
+
or (value.hour == 23 and value.minute == 0)
|
|
161
|
+
or (value.hour == 0 and value.minute == 0)
|
|
151
162
|
):
|
|
152
163
|
raise BedtimeOutOfRangeError(value=value)
|
|
153
164
|
now = datetime.now()
|
|
@@ -157,24 +168,25 @@ class Device:
|
|
|
157
168
|
if bedtime["enabled"]:
|
|
158
169
|
bedtime = {
|
|
159
170
|
**bedtime,
|
|
160
|
-
"endingTime": {
|
|
161
|
-
"hour": value.hour,
|
|
162
|
-
"minute": value.minute
|
|
163
|
-
}
|
|
171
|
+
"endingTime": {"hour": value.hour, "minute": value.minute},
|
|
164
172
|
}
|
|
165
173
|
if self.timer_mode == DeviceTimerMode.DAILY:
|
|
166
|
-
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"][
|
|
174
|
+
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"][
|
|
175
|
+
"bedtime"
|
|
176
|
+
] = bedtime
|
|
167
177
|
else:
|
|
168
|
-
self.parental_control_settings["playTimerRegulations"][
|
|
169
|
-
|
|
170
|
-
]["bedtime"] = bedtime
|
|
178
|
+
self.parental_control_settings["playTimerRegulations"][
|
|
179
|
+
"eachDayOfTheWeekRegulations"
|
|
180
|
+
][DAYS_OF_WEEK[now.weekday()]]["bedtime"] = bedtime
|
|
171
181
|
await self._send_api_update(
|
|
172
182
|
self._api.async_update_play_timer,
|
|
173
183
|
settings={
|
|
174
184
|
"deviceId": self.device_id,
|
|
175
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
185
|
+
"playTimerRegulations": self.parental_control_settings[
|
|
186
|
+
"playTimerRegulations"
|
|
187
|
+
],
|
|
176
188
|
},
|
|
177
|
-
now=now
|
|
189
|
+
now=now,
|
|
178
190
|
)
|
|
179
191
|
|
|
180
192
|
async def set_timer_mode(self, mode: DeviceTimerMode):
|
|
@@ -186,14 +198,15 @@ class Device:
|
|
|
186
198
|
self._api.async_update_play_timer,
|
|
187
199
|
settings={
|
|
188
200
|
"deviceId": self.device_id,
|
|
189
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
190
|
-
|
|
201
|
+
"playTimerRegulations": self.parental_control_settings[
|
|
202
|
+
"playTimerRegulations"
|
|
203
|
+
],
|
|
204
|
+
},
|
|
191
205
|
)
|
|
192
206
|
|
|
193
207
|
async def update_max_daily_playtime(self, minutes: int | float = 0):
|
|
194
208
|
"""Updates the maximum daily playtime of a device."""
|
|
195
|
-
_LOGGER.debug(">> Device.update_max_daily_playtime(minutes=%s)",
|
|
196
|
-
minutes)
|
|
209
|
+
_LOGGER.debug(">> Device.update_max_daily_playtime(minutes=%s)", minutes)
|
|
197
210
|
if isinstance(minutes, float):
|
|
198
211
|
minutes = int(minutes)
|
|
199
212
|
if minutes > 360 or minutes < -1:
|
|
@@ -207,39 +220,63 @@ class Device:
|
|
|
207
220
|
_LOGGER.debug(
|
|
208
221
|
"Setting timeToPlayInOneDay.limitTime for device %s to value %s",
|
|
209
222
|
self.device_id,
|
|
210
|
-
minutes
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
223
|
+
minutes,
|
|
224
|
+
)
|
|
225
|
+
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"][
|
|
226
|
+
"timeToPlayInOneDay"
|
|
227
|
+
]["enabled"] = ttpiod
|
|
228
|
+
if (
|
|
229
|
+
"limitTime"
|
|
230
|
+
in self.parental_control_settings["playTimerRegulations"][
|
|
231
|
+
"dailyRegulations"
|
|
232
|
+
]["timeToPlayInOneDay"]
|
|
233
|
+
and minutes is None
|
|
234
|
+
):
|
|
235
|
+
self.parental_control_settings["playTimerRegulations"][
|
|
236
|
+
"dailyRegulations"
|
|
237
|
+
]["timeToPlayInOneDay"].pop("limitTime")
|
|
214
238
|
else:
|
|
215
|
-
self.parental_control_settings["playTimerRegulations"][
|
|
239
|
+
self.parental_control_settings["playTimerRegulations"][
|
|
240
|
+
"dailyRegulations"
|
|
241
|
+
]["timeToPlayInOneDay"]["limitTime"] = minutes
|
|
216
242
|
else:
|
|
217
243
|
_LOGGER.debug(
|
|
218
244
|
"Setting timeToPlayInOneDay.limitTime for device %s to value %s",
|
|
219
245
|
self.device_id,
|
|
220
|
-
minutes
|
|
246
|
+
minutes,
|
|
221
247
|
)
|
|
222
|
-
day_of_week_regs = self.parental_control_settings["playTimerRegulations"][
|
|
248
|
+
day_of_week_regs = self.parental_control_settings["playTimerRegulations"][
|
|
249
|
+
"eachDayOfTheWeekRegulations"
|
|
250
|
+
]
|
|
223
251
|
current_day = DAYS_OF_WEEK[now.weekday()]
|
|
224
252
|
day_of_week_regs[current_day]["timeToPlayInOneDay"]["enabled"] = ttpiod
|
|
225
|
-
if
|
|
253
|
+
if (
|
|
254
|
+
"limitTime" in day_of_week_regs[current_day]["timeToPlayInOneDay"]
|
|
255
|
+
and minutes is None
|
|
256
|
+
):
|
|
226
257
|
day_of_week_regs[current_day]["timeToPlayInOneDay"].pop("limitTime")
|
|
227
258
|
else:
|
|
228
|
-
day_of_week_regs[current_day]["timeToPlayInOneDay"]["limitTime"] =
|
|
259
|
+
day_of_week_regs[current_day]["timeToPlayInOneDay"]["limitTime"] = (
|
|
260
|
+
minutes
|
|
261
|
+
)
|
|
229
262
|
|
|
230
263
|
await self._send_api_update(
|
|
231
264
|
self._api.async_update_play_timer,
|
|
232
265
|
settings={
|
|
233
266
|
"deviceId": self.device_id,
|
|
234
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
267
|
+
"playTimerRegulations": self.parental_control_settings[
|
|
268
|
+
"playTimerRegulations"
|
|
269
|
+
],
|
|
235
270
|
},
|
|
236
|
-
now=now
|
|
271
|
+
now=now,
|
|
237
272
|
)
|
|
238
273
|
|
|
239
274
|
def _update_applications(self):
|
|
240
275
|
"""Updates applications from daily summary."""
|
|
241
276
|
_LOGGER.debug(">> Device._update_applications()")
|
|
242
|
-
parsed_apps = Application.from_whitelist(
|
|
277
|
+
parsed_apps = Application.from_whitelist(
|
|
278
|
+
self.parental_control_settings.get("whitelistedApplications", [])
|
|
279
|
+
)
|
|
243
280
|
for app in parsed_apps:
|
|
244
281
|
try:
|
|
245
282
|
self.get_application(app.application_id).update(app)
|
|
@@ -250,9 +287,13 @@ class Device:
|
|
|
250
287
|
def _get_today_regulation(self, now: datetime) -> dict:
|
|
251
288
|
"""Returns the regulation settings for the current day."""
|
|
252
289
|
if self.timer_mode == DeviceTimerMode.EACH_DAY_OF_THE_WEEK:
|
|
253
|
-
day_of_week_regs = self.parental_control_settings[
|
|
290
|
+
day_of_week_regs = self.parental_control_settings[
|
|
291
|
+
"playTimerRegulations"
|
|
292
|
+
].get("eachDayOfTheWeekRegulations", {})
|
|
254
293
|
return day_of_week_regs.get(DAYS_OF_WEEK[now.weekday()], {})
|
|
255
|
-
return self.parental_control_settings.get("playTimerRegulations", {}).get(
|
|
294
|
+
return self.parental_control_settings.get("playTimerRegulations", {}).get(
|
|
295
|
+
"dailyRegulations", {}
|
|
296
|
+
)
|
|
256
297
|
|
|
257
298
|
def _parse_parental_control_setting(self, pcs: dict, now: datetime):
|
|
258
299
|
"""Parse a parental control setting request response."""
|
|
@@ -260,13 +301,23 @@ class Device:
|
|
|
260
301
|
self.parental_control_settings = pcs["parentalControlSetting"]
|
|
261
302
|
|
|
262
303
|
# Clean up bedtimeStartingTime if it's empty
|
|
263
|
-
if
|
|
264
|
-
|
|
265
|
-
|
|
304
|
+
if (
|
|
305
|
+
"bedtimeStartingTime"
|
|
306
|
+
in self.parental_control_settings["playTimerRegulations"]
|
|
307
|
+
):
|
|
308
|
+
if (
|
|
309
|
+
self.parental_control_settings["playTimerRegulations"]
|
|
310
|
+
.get("bedtimeStartingTime", {})
|
|
311
|
+
.get("hour", 0)
|
|
312
|
+
== 0
|
|
313
|
+
):
|
|
314
|
+
self.parental_control_settings["playTimerRegulations"].pop(
|
|
315
|
+
"bedtimeStartingTime"
|
|
316
|
+
)
|
|
266
317
|
|
|
267
|
-
self.forced_termination_mode =
|
|
268
|
-
|
|
269
|
-
)
|
|
318
|
+
self.forced_termination_mode = self.parental_control_settings[
|
|
319
|
+
"playTimerRegulations"
|
|
320
|
+
]["restrictionMode"] == str(RestrictionMode.FORCED_TERMINATION)
|
|
270
321
|
|
|
271
322
|
# Update limit and bedtime from regulations
|
|
272
323
|
self.timer_mode = DeviceTimerMode(
|
|
@@ -275,12 +326,20 @@ class Device:
|
|
|
275
326
|
today_reg = self._get_today_regulation(now)
|
|
276
327
|
limit_time = today_reg.get("timeToPlayInOneDay", {}).get("limitTime")
|
|
277
328
|
self.limit_time = limit_time if limit_time is not None else -1
|
|
329
|
+
extra_playing_time_data = (
|
|
330
|
+
pcs.get("ownedDevice", {}).get("device", {}).get("extraPlayingTime")
|
|
331
|
+
)
|
|
332
|
+
self.extra_playing_time = None
|
|
333
|
+
if extra_playing_time_data:
|
|
334
|
+
self.extra_playing_time = extra_playing_time_data.get("inOneDay", {}).get(
|
|
335
|
+
"duration"
|
|
336
|
+
)
|
|
278
337
|
|
|
279
338
|
bedtime_setting = today_reg.get("bedtime", {})
|
|
280
339
|
if bedtime_setting.get("enabled"):
|
|
281
340
|
self.bedtime_alarm = time(
|
|
282
341
|
hour=bedtime_setting["endingTime"]["hour"],
|
|
283
|
-
minute=bedtime_setting["endingTime"]["minute"]
|
|
342
|
+
minute=bedtime_setting["endingTime"]["minute"],
|
|
284
343
|
)
|
|
285
344
|
else:
|
|
286
345
|
self.bedtime_alarm = time(hour=0, minute=0)
|
|
@@ -303,8 +362,10 @@ class Device:
|
|
|
303
362
|
self.today_playing_time = self.daily_summaries[0].get("playingTime") or 0
|
|
304
363
|
self.today_disabled_time = self.daily_summaries[0].get("disabledTime") or 0
|
|
305
364
|
self.today_exceeded_time = self.daily_summaries[0].get("exceededTime") or 0
|
|
306
|
-
_LOGGER.debug(
|
|
307
|
-
|
|
365
|
+
_LOGGER.debug(
|
|
366
|
+
"Cached playing, disabled and exceeded time for today for device %s",
|
|
367
|
+
self.device_id,
|
|
368
|
+
)
|
|
308
369
|
self._calculate_today_remaining_time(now)
|
|
309
370
|
|
|
310
371
|
month_playing_time: int = 0
|
|
@@ -319,56 +380,79 @@ class Device:
|
|
|
319
380
|
for app in parsed_apps:
|
|
320
381
|
try:
|
|
321
382
|
int_app = self.get_application(app.application_id)
|
|
322
|
-
_LOGGER.debug(
|
|
323
|
-
|
|
324
|
-
|
|
383
|
+
_LOGGER.debug(
|
|
384
|
+
"Updating cached app state %s for device %s",
|
|
385
|
+
int_app.application_id,
|
|
386
|
+
self.device_id,
|
|
387
|
+
)
|
|
325
388
|
int_app.update(app)
|
|
326
389
|
except ValueError:
|
|
327
|
-
_LOGGER.debug(
|
|
328
|
-
|
|
329
|
-
|
|
390
|
+
_LOGGER.debug(
|
|
391
|
+
"Creating new cached application entry %s for device %s",
|
|
392
|
+
app.application_id,
|
|
393
|
+
self.device_id,
|
|
394
|
+
)
|
|
330
395
|
self.applications.append(app)
|
|
331
396
|
|
|
332
397
|
# update application playtime
|
|
333
398
|
try:
|
|
334
399
|
for player in self.get_date_summary()[0].get("devicePlayers", []):
|
|
335
400
|
for app in player.get("playedApps", []):
|
|
336
|
-
self.get_application(app["applicationId"]).update_today_time_played(
|
|
401
|
+
self.get_application(app["applicationId"]).update_today_time_played(
|
|
402
|
+
app
|
|
403
|
+
)
|
|
337
404
|
self.application_update_failed = False
|
|
338
405
|
except ValueError as err:
|
|
339
|
-
_LOGGER.debug(
|
|
406
|
+
_LOGGER.debug(
|
|
407
|
+
"Unable to retrieve applications for device %s: %s", self.name, err
|
|
408
|
+
)
|
|
340
409
|
self.application_update_failed = True
|
|
341
410
|
|
|
342
411
|
def _calculate_today_remaining_time(self, now: datetime):
|
|
343
412
|
"""Calculates the remaining playing time for today."""
|
|
344
|
-
self.stats_update_failed = True
|
|
413
|
+
self.stats_update_failed = True # Assume failure until success
|
|
345
414
|
try:
|
|
346
|
-
minutes_in_day = 1440
|
|
415
|
+
minutes_in_day = 1440 # 24 * 60
|
|
347
416
|
current_minutes_past_midnight = now.hour * 60 + now.minute
|
|
348
417
|
|
|
349
418
|
if self.limit_time in (-1, None):
|
|
350
419
|
# No play limit, so remaining time is until end of day.
|
|
351
|
-
time_remaining_by_play_limit =
|
|
420
|
+
time_remaining_by_play_limit = (
|
|
421
|
+
minutes_in_day - current_minutes_past_midnight
|
|
422
|
+
)
|
|
352
423
|
else:
|
|
353
424
|
time_remaining_by_play_limit = self.limit_time - self.today_playing_time
|
|
354
425
|
|
|
355
426
|
# 2. Calculate remaining time until bedtime
|
|
356
|
-
if
|
|
427
|
+
if (
|
|
428
|
+
self.bedtime_alarm
|
|
429
|
+
and self.bedtime_alarm != time(hour=0, minute=0)
|
|
430
|
+
and self.alarms_enabled
|
|
431
|
+
):
|
|
357
432
|
bedtime_dt = datetime.combine(now.date(), self.bedtime_alarm)
|
|
358
|
-
if bedtime_dt > now:
|
|
433
|
+
if bedtime_dt > now: # Bedtime is in the future today
|
|
359
434
|
time_remaining_by_bedtime = (bedtime_dt - now).total_seconds() / 60
|
|
360
|
-
else:
|
|
435
|
+
else: # Bedtime has passed
|
|
361
436
|
time_remaining_by_bedtime = 0.0
|
|
362
437
|
else:
|
|
363
|
-
time_remaining_by_bedtime =
|
|
438
|
+
time_remaining_by_bedtime = (
|
|
439
|
+
minutes_in_day - current_minutes_past_midnight
|
|
440
|
+
)
|
|
364
441
|
|
|
365
442
|
# Effective remaining time is the minimum of the two constraints
|
|
366
|
-
effective_remaining_time = min(
|
|
443
|
+
effective_remaining_time = min(
|
|
444
|
+
time_remaining_by_play_limit, time_remaining_by_bedtime
|
|
445
|
+
)
|
|
367
446
|
self.today_time_remaining = int(max(0.0, effective_remaining_time))
|
|
368
|
-
_LOGGER.debug(
|
|
447
|
+
_LOGGER.debug(
|
|
448
|
+
"Calculated today's remaining time: %s minutes",
|
|
449
|
+
self.today_time_remaining,
|
|
450
|
+
)
|
|
369
451
|
self.stats_update_failed = False
|
|
370
452
|
except (ValueError, TypeError, AttributeError) as err:
|
|
371
|
-
_LOGGER.warning(
|
|
453
|
+
_LOGGER.warning(
|
|
454
|
+
"Unable to calculate remaining time for device %s: %s", self.name, err
|
|
455
|
+
)
|
|
372
456
|
|
|
373
457
|
async def _get_parental_control_setting(self, now: datetime):
|
|
374
458
|
"""Retreives parental control settings from the API."""
|
|
@@ -383,7 +467,7 @@ class Device:
|
|
|
383
467
|
"""Retrieve daily summaries."""
|
|
384
468
|
_LOGGER.debug(">> Device._get_daily_summaries()")
|
|
385
469
|
response = await self._api.async_get_device_daily_summaries(
|
|
386
|
-
device_id
|
|
470
|
+
device_id=self.device_id
|
|
387
471
|
)
|
|
388
472
|
self.daily_summaries = response["json"]["dailySummaries"]
|
|
389
473
|
_LOGGER.debug("New daily summary %s", self.daily_summaries)
|
|
@@ -395,14 +479,16 @@ class Device:
|
|
|
395
479
|
if self.alarms_enabled is not None:
|
|
396
480
|
# first refresh can come from self.extra without http request
|
|
397
481
|
response = await self._api.async_get_account_device(
|
|
398
|
-
device_id
|
|
482
|
+
device_id=self.device_id
|
|
399
483
|
)
|
|
400
484
|
self.extra = response["json"]["ownedDevice"]["device"]
|
|
401
485
|
status = self.extra["alarmSetting"]["visibility"]
|
|
402
486
|
self.alarms_enabled = status == str(AlarmSettingState.VISIBLE)
|
|
403
|
-
_LOGGER.debug(
|
|
404
|
-
|
|
405
|
-
|
|
487
|
+
_LOGGER.debug(
|
|
488
|
+
"Cached alarms enabled to state %s for device %s",
|
|
489
|
+
self.alarms_enabled,
|
|
490
|
+
self.device_id,
|
|
491
|
+
)
|
|
406
492
|
|
|
407
493
|
async def get_monthly_summary(self, search_date: datetime = None) -> dict | None:
|
|
408
494
|
"""Gets the monthly summary."""
|
|
@@ -420,69 +506,93 @@ class Device:
|
|
|
420
506
|
available_summaries = response["json"]["available"]
|
|
421
507
|
_LOGGER.debug("Available monthly summaries: %s", available_summaries)
|
|
422
508
|
if not available_summaries:
|
|
423
|
-
_LOGGER.debug(
|
|
509
|
+
_LOGGER.debug(
|
|
510
|
+
"No monthly summaries available for device %s", self.device_id
|
|
511
|
+
)
|
|
424
512
|
return None
|
|
425
513
|
# Use the most recent available summary
|
|
426
514
|
available_summary = available_summaries[0]
|
|
427
|
-
search_date = datetime.strptime(
|
|
428
|
-
|
|
515
|
+
search_date = datetime.strptime(
|
|
516
|
+
f"{available_summary['year']}-{available_summary['month']}-01",
|
|
517
|
+
"%Y-%m-%d",
|
|
518
|
+
)
|
|
519
|
+
_LOGGER.debug(
|
|
520
|
+
"Using search date %s for monthly summary request", search_date
|
|
521
|
+
)
|
|
429
522
|
latest = True
|
|
430
523
|
|
|
431
524
|
try:
|
|
432
525
|
response = await self._api.async_get_device_monthly_summary(
|
|
433
|
-
device_id=self.device_id,
|
|
434
|
-
year=search_date.year,
|
|
435
|
-
month=search_date.month
|
|
526
|
+
device_id=self.device_id, year=search_date.year, month=search_date.month
|
|
436
527
|
)
|
|
437
528
|
except HttpException as exc:
|
|
438
|
-
_LOGGER.warning(
|
|
439
|
-
|
|
440
|
-
|
|
529
|
+
_LOGGER.warning(
|
|
530
|
+
"HTTP Exception raised while getting monthly summary for device %s: %s",
|
|
531
|
+
self.device_id,
|
|
532
|
+
exc,
|
|
533
|
+
)
|
|
441
534
|
return None
|
|
442
535
|
else:
|
|
443
|
-
_LOGGER.debug(
|
|
444
|
-
|
|
445
|
-
|
|
536
|
+
_LOGGER.debug(
|
|
537
|
+
"Monthly summary query complete for device %s: %s",
|
|
538
|
+
self.device_id,
|
|
539
|
+
response["json"]["summary"],
|
|
540
|
+
)
|
|
446
541
|
if latest:
|
|
447
542
|
self.last_month_summary = summary = response["json"]["summary"]
|
|
543
|
+
# Generate player objects
|
|
544
|
+
for player in response.get("json", {}).get("summary", {}).get("players", []):
|
|
545
|
+
profile = player.get("profile")
|
|
546
|
+
if not profile or not profile.get("playerId"):
|
|
547
|
+
continue
|
|
548
|
+
player_id = profile["playerId"]
|
|
549
|
+
if player_id not in self.players:
|
|
550
|
+
self.players[player_id] = Player.from_profile(profile)
|
|
551
|
+
self.players[player_id].month_summary = player.get("summary", {})
|
|
448
552
|
return summary
|
|
449
553
|
return response["json"]["summary"]
|
|
450
554
|
|
|
451
|
-
|
|
452
555
|
def get_date_summary(self, input_date: datetime = datetime.now()) -> dict:
|
|
453
556
|
"""Returns usage for a given date."""
|
|
454
557
|
if not self.daily_summaries:
|
|
455
558
|
raise ValueError("No daily summaries available to search.")
|
|
456
559
|
summary = [
|
|
457
|
-
x
|
|
458
|
-
|
|
560
|
+
x
|
|
561
|
+
for x in self.daily_summaries
|
|
562
|
+
if x["date"] == input_date.strftime("%Y-%m-%d")
|
|
459
563
|
]
|
|
460
564
|
if len(summary) == 0:
|
|
461
565
|
input_date -= timedelta(days=1)
|
|
462
566
|
summary = [
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
567
|
+
x
|
|
568
|
+
for x in self.daily_summaries
|
|
569
|
+
if x["date"] == input_date.strftime("%Y-%m-%d")
|
|
570
|
+
]
|
|
466
571
|
if len(summary) == 0:
|
|
467
|
-
raise ValueError(
|
|
572
|
+
raise ValueError(
|
|
573
|
+
f"A summary for the given date {input_date} does not exist"
|
|
574
|
+
)
|
|
468
575
|
return summary
|
|
469
576
|
|
|
470
577
|
def get_application(self, application_id: str) -> Application:
|
|
471
578
|
"""Returns a single application."""
|
|
472
|
-
app = next(
|
|
579
|
+
app = next(
|
|
580
|
+
(app for app in self.applications if app.application_id == application_id),
|
|
581
|
+
None,
|
|
582
|
+
)
|
|
473
583
|
if app:
|
|
474
584
|
return app
|
|
475
585
|
raise ValueError(f"Application with id {application_id} not found.")
|
|
476
586
|
|
|
477
587
|
def get_player(self, player_id: str) -> Player:
|
|
478
588
|
"""Returns a player."""
|
|
479
|
-
player =
|
|
589
|
+
player = self.players.get(player_id)
|
|
480
590
|
if player:
|
|
481
591
|
return player
|
|
482
592
|
raise ValueError(f"Player with id {player_id} not found.")
|
|
483
593
|
|
|
484
594
|
@classmethod
|
|
485
|
-
async def from_devices_response(cls, raw: dict, api) -> list[
|
|
595
|
+
async def from_devices_response(cls, raw: dict, api) -> list["Device"]:
|
|
486
596
|
"""Parses a device request response body."""
|
|
487
597
|
_LOGGER.debug("Parsing device list response")
|
|
488
598
|
if "ownedDevices" not in raw.keys():
|
|
@@ -500,7 +610,7 @@ class Device:
|
|
|
500
610
|
return devices
|
|
501
611
|
|
|
502
612
|
@classmethod
|
|
503
|
-
def from_device_response(cls, raw: dict, api) ->
|
|
613
|
+
def from_device_response(cls, raw: dict, api) -> "Device":
|
|
504
614
|
"""Parses a single device request response body."""
|
|
505
615
|
_LOGGER.debug("Parsing device response")
|
|
506
616
|
if "deviceId" not in raw.keys():
|
pynintendoparental/enum.py
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from enum import Enum, StrEnum
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
class AlarmSettingState(Enum):
|
|
6
7
|
"""Alarm setting states."""
|
|
8
|
+
|
|
7
9
|
SUCCESS = 0
|
|
8
10
|
TO_VISIBLE = 1
|
|
9
11
|
TO_INVISIBLE = 2
|
|
@@ -13,16 +15,20 @@ class AlarmSettingState(Enum):
|
|
|
13
15
|
def __str__(self) -> str:
|
|
14
16
|
return self.name
|
|
15
17
|
|
|
18
|
+
|
|
16
19
|
class RestrictionMode(Enum):
|
|
17
20
|
"""Restriction modes."""
|
|
21
|
+
|
|
18
22
|
FORCED_TERMINATION = 0
|
|
19
23
|
ALARM = 1
|
|
20
24
|
|
|
21
25
|
def __str__(self) -> str:
|
|
22
26
|
return self.name
|
|
23
27
|
|
|
28
|
+
|
|
24
29
|
class DeviceTimerMode(StrEnum):
|
|
25
30
|
"""Device timer modes."""
|
|
31
|
+
|
|
26
32
|
DAILY = "DAILY"
|
|
27
33
|
EACH_DAY_OF_THE_WEEK = "EACH_DAY_OF_THE_WEEK"
|
|
28
34
|
|