pynintendoparental 1.1.3__py3-none-any.whl → 2.0.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 +68 -92
- pynintendoparental/application.py +17 -11
- pynintendoparental/authenticator.py +18 -0
- pynintendoparental/const.py +21 -12
- pynintendoparental/device.py +190 -102
- pynintendoparental/enum.py +6 -0
- pynintendoparental/exceptions.py +4 -20
- pynintendoparental/player.py +3 -1
- pynintendoparental/utils.py +1 -0
- {pynintendoparental-1.1.3.dist-info → pynintendoparental-2.0.0.dist-info}/METADATA +3 -1
- pynintendoparental-2.0.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.0.0.dist-info}/WHEEL +0 -0
- {pynintendoparental-1.1.3.dist-info → pynintendoparental-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {pynintendoparental-1.1.3.dist-info → pynintendoparental-2.0.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
|
|
|
@@ -52,10 +58,7 @@ class Device:
|
|
|
52
58
|
@property
|
|
53
59
|
def model(self) -> str:
|
|
54
60
|
"""Return the model."""
|
|
55
|
-
model_map = {
|
|
56
|
-
"P00": "Switch",
|
|
57
|
-
"P01": "Switch 2"
|
|
58
|
-
}
|
|
61
|
+
model_map = {"P00": "Switch", "P01": "Switch 2"}
|
|
59
62
|
return model_map.get(self.generation, "Unknown")
|
|
60
63
|
|
|
61
64
|
@property
|
|
@@ -68,10 +71,10 @@ class Device:
|
|
|
68
71
|
_LOGGER.debug(">> Device.update()")
|
|
69
72
|
now = datetime.now()
|
|
70
73
|
await asyncio.gather(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
self._get_daily_summaries(now),
|
|
75
|
+
self._get_parental_control_setting(now),
|
|
76
|
+
self.get_monthly_summary(),
|
|
77
|
+
self._get_extras(),
|
|
75
78
|
)
|
|
76
79
|
if not self.players:
|
|
77
80
|
self.players = Player.from_device_daily_summary(self.daily_summaries)
|
|
@@ -114,9 +117,7 @@ class Device:
|
|
|
114
117
|
"""Updates the pin for the device."""
|
|
115
118
|
_LOGGER.debug(">> Device.set_new_pin(pin=REDACTED)")
|
|
116
119
|
await self._send_api_update(
|
|
117
|
-
self._api.async_update_unlock_code,
|
|
118
|
-
new_code=pin,
|
|
119
|
-
device_id=self.device_id
|
|
120
|
+
self._api.async_update_unlock_code, new_code=pin, device_id=self.device_id
|
|
120
121
|
)
|
|
121
122
|
|
|
122
123
|
async def add_extra_time(self, minutes: int):
|
|
@@ -129,25 +130,30 @@ class Device:
|
|
|
129
130
|
async def set_restriction_mode(self, mode: RestrictionMode):
|
|
130
131
|
"""Updates the restriction mode of the device."""
|
|
131
132
|
_LOGGER.debug(">> Device.set_restriction_mode(mode=%s)", mode)
|
|
132
|
-
self.parental_control_settings["playTimerRegulations"]["restrictionMode"] = str(
|
|
133
|
+
self.parental_control_settings["playTimerRegulations"]["restrictionMode"] = str(
|
|
134
|
+
mode
|
|
135
|
+
)
|
|
133
136
|
response = await self._api.async_update_play_timer(
|
|
134
137
|
settings={
|
|
135
138
|
"deviceId": self.device_id,
|
|
136
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
139
|
+
"playTimerRegulations": self.parental_control_settings[
|
|
140
|
+
"playTimerRegulations"
|
|
141
|
+
],
|
|
137
142
|
}
|
|
138
143
|
)
|
|
139
144
|
now = datetime.now()
|
|
140
|
-
self._parse_parental_control_setting(
|
|
145
|
+
self._parse_parental_control_setting(
|
|
146
|
+
response["json"], now
|
|
147
|
+
) # Don't need to recalculate times
|
|
141
148
|
await self._execute_callbacks()
|
|
142
149
|
|
|
143
150
|
async def set_bedtime_alarm(self, value: time):
|
|
144
151
|
"""Update the bedtime alarm for the device."""
|
|
145
|
-
_LOGGER.debug(">> Device.set_bedtime_alarm(value=%s)",
|
|
146
|
-
value)
|
|
152
|
+
_LOGGER.debug(">> Device.set_bedtime_alarm(value=%s)", value)
|
|
147
153
|
if not (
|
|
148
|
-
(16 <= value.hour <= 22)
|
|
149
|
-
(value.hour == 23 and value.minute == 0)
|
|
150
|
-
(value.hour == 0 and value.minute == 0)
|
|
154
|
+
(16 <= value.hour <= 22)
|
|
155
|
+
or (value.hour == 23 and value.minute == 0)
|
|
156
|
+
or (value.hour == 0 and value.minute == 0)
|
|
151
157
|
):
|
|
152
158
|
raise BedtimeOutOfRangeError(value=value)
|
|
153
159
|
now = datetime.now()
|
|
@@ -157,24 +163,25 @@ class Device:
|
|
|
157
163
|
if bedtime["enabled"]:
|
|
158
164
|
bedtime = {
|
|
159
165
|
**bedtime,
|
|
160
|
-
"endingTime": {
|
|
161
|
-
"hour": value.hour,
|
|
162
|
-
"minute": value.minute
|
|
163
|
-
}
|
|
166
|
+
"endingTime": {"hour": value.hour, "minute": value.minute},
|
|
164
167
|
}
|
|
165
168
|
if self.timer_mode == DeviceTimerMode.DAILY:
|
|
166
|
-
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"][
|
|
169
|
+
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"][
|
|
170
|
+
"bedtime"
|
|
171
|
+
] = bedtime
|
|
167
172
|
else:
|
|
168
|
-
self.parental_control_settings["playTimerRegulations"][
|
|
169
|
-
|
|
170
|
-
]["bedtime"] = bedtime
|
|
173
|
+
self.parental_control_settings["playTimerRegulations"][
|
|
174
|
+
"eachDayOfTheWeekRegulations"
|
|
175
|
+
][DAYS_OF_WEEK[now.weekday()]]["bedtime"] = bedtime
|
|
171
176
|
await self._send_api_update(
|
|
172
177
|
self._api.async_update_play_timer,
|
|
173
178
|
settings={
|
|
174
179
|
"deviceId": self.device_id,
|
|
175
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
180
|
+
"playTimerRegulations": self.parental_control_settings[
|
|
181
|
+
"playTimerRegulations"
|
|
182
|
+
],
|
|
176
183
|
},
|
|
177
|
-
now=now
|
|
184
|
+
now=now,
|
|
178
185
|
)
|
|
179
186
|
|
|
180
187
|
async def set_timer_mode(self, mode: DeviceTimerMode):
|
|
@@ -186,14 +193,15 @@ class Device:
|
|
|
186
193
|
self._api.async_update_play_timer,
|
|
187
194
|
settings={
|
|
188
195
|
"deviceId": self.device_id,
|
|
189
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
190
|
-
|
|
196
|
+
"playTimerRegulations": self.parental_control_settings[
|
|
197
|
+
"playTimerRegulations"
|
|
198
|
+
],
|
|
199
|
+
},
|
|
191
200
|
)
|
|
192
201
|
|
|
193
202
|
async def update_max_daily_playtime(self, minutes: int | float = 0):
|
|
194
203
|
"""Updates the maximum daily playtime of a device."""
|
|
195
|
-
_LOGGER.debug(">> Device.update_max_daily_playtime(minutes=%s)",
|
|
196
|
-
minutes)
|
|
204
|
+
_LOGGER.debug(">> Device.update_max_daily_playtime(minutes=%s)", minutes)
|
|
197
205
|
if isinstance(minutes, float):
|
|
198
206
|
minutes = int(minutes)
|
|
199
207
|
if minutes > 360 or minutes < -1:
|
|
@@ -207,39 +215,63 @@ class Device:
|
|
|
207
215
|
_LOGGER.debug(
|
|
208
216
|
"Setting timeToPlayInOneDay.limitTime for device %s to value %s",
|
|
209
217
|
self.device_id,
|
|
210
|
-
minutes
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
minutes,
|
|
219
|
+
)
|
|
220
|
+
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"][
|
|
221
|
+
"timeToPlayInOneDay"
|
|
222
|
+
]["enabled"] = ttpiod
|
|
223
|
+
if (
|
|
224
|
+
"limitTime"
|
|
225
|
+
in self.parental_control_settings["playTimerRegulations"][
|
|
226
|
+
"dailyRegulations"
|
|
227
|
+
]["timeToPlayInOneDay"]
|
|
228
|
+
and minutes is None
|
|
229
|
+
):
|
|
230
|
+
self.parental_control_settings["playTimerRegulations"][
|
|
231
|
+
"dailyRegulations"
|
|
232
|
+
]["timeToPlayInOneDay"].pop("limitTime")
|
|
214
233
|
else:
|
|
215
|
-
self.parental_control_settings["playTimerRegulations"][
|
|
234
|
+
self.parental_control_settings["playTimerRegulations"][
|
|
235
|
+
"dailyRegulations"
|
|
236
|
+
]["timeToPlayInOneDay"]["limitTime"] = minutes
|
|
216
237
|
else:
|
|
217
238
|
_LOGGER.debug(
|
|
218
239
|
"Setting timeToPlayInOneDay.limitTime for device %s to value %s",
|
|
219
240
|
self.device_id,
|
|
220
|
-
minutes
|
|
241
|
+
minutes,
|
|
221
242
|
)
|
|
222
|
-
day_of_week_regs = self.parental_control_settings["playTimerRegulations"][
|
|
243
|
+
day_of_week_regs = self.parental_control_settings["playTimerRegulations"][
|
|
244
|
+
"eachDayOfTheWeekRegulations"
|
|
245
|
+
]
|
|
223
246
|
current_day = DAYS_OF_WEEK[now.weekday()]
|
|
224
247
|
day_of_week_regs[current_day]["timeToPlayInOneDay"]["enabled"] = ttpiod
|
|
225
|
-
if
|
|
248
|
+
if (
|
|
249
|
+
"limitTime" in day_of_week_regs[current_day]["timeToPlayInOneDay"]
|
|
250
|
+
and minutes is None
|
|
251
|
+
):
|
|
226
252
|
day_of_week_regs[current_day]["timeToPlayInOneDay"].pop("limitTime")
|
|
227
253
|
else:
|
|
228
|
-
day_of_week_regs[current_day]["timeToPlayInOneDay"]["limitTime"] =
|
|
254
|
+
day_of_week_regs[current_day]["timeToPlayInOneDay"]["limitTime"] = (
|
|
255
|
+
minutes
|
|
256
|
+
)
|
|
229
257
|
|
|
230
258
|
await self._send_api_update(
|
|
231
259
|
self._api.async_update_play_timer,
|
|
232
260
|
settings={
|
|
233
261
|
"deviceId": self.device_id,
|
|
234
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
262
|
+
"playTimerRegulations": self.parental_control_settings[
|
|
263
|
+
"playTimerRegulations"
|
|
264
|
+
],
|
|
235
265
|
},
|
|
236
|
-
now=now
|
|
266
|
+
now=now,
|
|
237
267
|
)
|
|
238
268
|
|
|
239
269
|
def _update_applications(self):
|
|
240
270
|
"""Updates applications from daily summary."""
|
|
241
271
|
_LOGGER.debug(">> Device._update_applications()")
|
|
242
|
-
parsed_apps = Application.from_whitelist(
|
|
272
|
+
parsed_apps = Application.from_whitelist(
|
|
273
|
+
self.parental_control_settings.get("whitelistedApplications", [])
|
|
274
|
+
)
|
|
243
275
|
for app in parsed_apps:
|
|
244
276
|
try:
|
|
245
277
|
self.get_application(app.application_id).update(app)
|
|
@@ -250,9 +282,13 @@ class Device:
|
|
|
250
282
|
def _get_today_regulation(self, now: datetime) -> dict:
|
|
251
283
|
"""Returns the regulation settings for the current day."""
|
|
252
284
|
if self.timer_mode == DeviceTimerMode.EACH_DAY_OF_THE_WEEK:
|
|
253
|
-
day_of_week_regs = self.parental_control_settings[
|
|
285
|
+
day_of_week_regs = self.parental_control_settings[
|
|
286
|
+
"playTimerRegulations"
|
|
287
|
+
].get("eachDayOfTheWeekRegulations", {})
|
|
254
288
|
return day_of_week_regs.get(DAYS_OF_WEEK[now.weekday()], {})
|
|
255
|
-
return self.parental_control_settings.get("playTimerRegulations", {}).get(
|
|
289
|
+
return self.parental_control_settings.get("playTimerRegulations", {}).get(
|
|
290
|
+
"dailyRegulations", {}
|
|
291
|
+
)
|
|
256
292
|
|
|
257
293
|
def _parse_parental_control_setting(self, pcs: dict, now: datetime):
|
|
258
294
|
"""Parse a parental control setting request response."""
|
|
@@ -260,13 +296,23 @@ class Device:
|
|
|
260
296
|
self.parental_control_settings = pcs["parentalControlSetting"]
|
|
261
297
|
|
|
262
298
|
# Clean up bedtimeStartingTime if it's empty
|
|
263
|
-
if
|
|
264
|
-
|
|
265
|
-
|
|
299
|
+
if (
|
|
300
|
+
"bedtimeStartingTime"
|
|
301
|
+
in self.parental_control_settings["playTimerRegulations"]
|
|
302
|
+
):
|
|
303
|
+
if (
|
|
304
|
+
self.parental_control_settings["playTimerRegulations"]
|
|
305
|
+
.get("bedtimeStartingTime", {})
|
|
306
|
+
.get("hour", 0)
|
|
307
|
+
== 0
|
|
308
|
+
):
|
|
309
|
+
self.parental_control_settings["playTimerRegulations"].pop(
|
|
310
|
+
"bedtimeStartingTime"
|
|
311
|
+
)
|
|
266
312
|
|
|
267
|
-
self.forced_termination_mode =
|
|
268
|
-
|
|
269
|
-
)
|
|
313
|
+
self.forced_termination_mode = self.parental_control_settings[
|
|
314
|
+
"playTimerRegulations"
|
|
315
|
+
]["restrictionMode"] == str(RestrictionMode.FORCED_TERMINATION)
|
|
270
316
|
|
|
271
317
|
# Update limit and bedtime from regulations
|
|
272
318
|
self.timer_mode = DeviceTimerMode(
|
|
@@ -280,7 +326,7 @@ class Device:
|
|
|
280
326
|
if bedtime_setting.get("enabled"):
|
|
281
327
|
self.bedtime_alarm = time(
|
|
282
328
|
hour=bedtime_setting["endingTime"]["hour"],
|
|
283
|
-
minute=bedtime_setting["endingTime"]["minute"]
|
|
329
|
+
minute=bedtime_setting["endingTime"]["minute"],
|
|
284
330
|
)
|
|
285
331
|
else:
|
|
286
332
|
self.bedtime_alarm = time(hour=0, minute=0)
|
|
@@ -303,8 +349,10 @@ class Device:
|
|
|
303
349
|
self.today_playing_time = self.daily_summaries[0].get("playingTime") or 0
|
|
304
350
|
self.today_disabled_time = self.daily_summaries[0].get("disabledTime") or 0
|
|
305
351
|
self.today_exceeded_time = self.daily_summaries[0].get("exceededTime") or 0
|
|
306
|
-
_LOGGER.debug(
|
|
307
|
-
|
|
352
|
+
_LOGGER.debug(
|
|
353
|
+
"Cached playing, disabled and exceeded time for today for device %s",
|
|
354
|
+
self.device_id,
|
|
355
|
+
)
|
|
308
356
|
self._calculate_today_remaining_time(now)
|
|
309
357
|
|
|
310
358
|
month_playing_time: int = 0
|
|
@@ -319,56 +367,79 @@ class Device:
|
|
|
319
367
|
for app in parsed_apps:
|
|
320
368
|
try:
|
|
321
369
|
int_app = self.get_application(app.application_id)
|
|
322
|
-
_LOGGER.debug(
|
|
323
|
-
|
|
324
|
-
|
|
370
|
+
_LOGGER.debug(
|
|
371
|
+
"Updating cached app state %s for device %s",
|
|
372
|
+
int_app.application_id,
|
|
373
|
+
self.device_id,
|
|
374
|
+
)
|
|
325
375
|
int_app.update(app)
|
|
326
376
|
except ValueError:
|
|
327
|
-
_LOGGER.debug(
|
|
328
|
-
|
|
329
|
-
|
|
377
|
+
_LOGGER.debug(
|
|
378
|
+
"Creating new cached application entry %s for device %s",
|
|
379
|
+
app.application_id,
|
|
380
|
+
self.device_id,
|
|
381
|
+
)
|
|
330
382
|
self.applications.append(app)
|
|
331
383
|
|
|
332
384
|
# update application playtime
|
|
333
385
|
try:
|
|
334
386
|
for player in self.get_date_summary()[0].get("devicePlayers", []):
|
|
335
387
|
for app in player.get("playedApps", []):
|
|
336
|
-
self.get_application(app["applicationId"]).update_today_time_played(
|
|
388
|
+
self.get_application(app["applicationId"]).update_today_time_played(
|
|
389
|
+
app
|
|
390
|
+
)
|
|
337
391
|
self.application_update_failed = False
|
|
338
392
|
except ValueError as err:
|
|
339
|
-
_LOGGER.debug(
|
|
393
|
+
_LOGGER.debug(
|
|
394
|
+
"Unable to retrieve applications for device %s: %s", self.name, err
|
|
395
|
+
)
|
|
340
396
|
self.application_update_failed = True
|
|
341
397
|
|
|
342
398
|
def _calculate_today_remaining_time(self, now: datetime):
|
|
343
399
|
"""Calculates the remaining playing time for today."""
|
|
344
|
-
self.stats_update_failed = True
|
|
400
|
+
self.stats_update_failed = True # Assume failure until success
|
|
345
401
|
try:
|
|
346
|
-
minutes_in_day = 1440
|
|
402
|
+
minutes_in_day = 1440 # 24 * 60
|
|
347
403
|
current_minutes_past_midnight = now.hour * 60 + now.minute
|
|
348
404
|
|
|
349
405
|
if self.limit_time in (-1, None):
|
|
350
406
|
# No play limit, so remaining time is until end of day.
|
|
351
|
-
time_remaining_by_play_limit =
|
|
407
|
+
time_remaining_by_play_limit = (
|
|
408
|
+
minutes_in_day - current_minutes_past_midnight
|
|
409
|
+
)
|
|
352
410
|
else:
|
|
353
411
|
time_remaining_by_play_limit = self.limit_time - self.today_playing_time
|
|
354
412
|
|
|
355
413
|
# 2. Calculate remaining time until bedtime
|
|
356
|
-
if
|
|
414
|
+
if (
|
|
415
|
+
self.bedtime_alarm
|
|
416
|
+
and self.bedtime_alarm != time(hour=0, minute=0)
|
|
417
|
+
and self.alarms_enabled
|
|
418
|
+
):
|
|
357
419
|
bedtime_dt = datetime.combine(now.date(), self.bedtime_alarm)
|
|
358
|
-
if bedtime_dt > now:
|
|
420
|
+
if bedtime_dt > now: # Bedtime is in the future today
|
|
359
421
|
time_remaining_by_bedtime = (bedtime_dt - now).total_seconds() / 60
|
|
360
|
-
else:
|
|
422
|
+
else: # Bedtime has passed
|
|
361
423
|
time_remaining_by_bedtime = 0.0
|
|
362
424
|
else:
|
|
363
|
-
time_remaining_by_bedtime =
|
|
425
|
+
time_remaining_by_bedtime = (
|
|
426
|
+
minutes_in_day - current_minutes_past_midnight
|
|
427
|
+
)
|
|
364
428
|
|
|
365
429
|
# Effective remaining time is the minimum of the two constraints
|
|
366
|
-
effective_remaining_time = min(
|
|
430
|
+
effective_remaining_time = min(
|
|
431
|
+
time_remaining_by_play_limit, time_remaining_by_bedtime
|
|
432
|
+
)
|
|
367
433
|
self.today_time_remaining = int(max(0.0, effective_remaining_time))
|
|
368
|
-
_LOGGER.debug(
|
|
434
|
+
_LOGGER.debug(
|
|
435
|
+
"Calculated today's remaining time: %s minutes",
|
|
436
|
+
self.today_time_remaining,
|
|
437
|
+
)
|
|
369
438
|
self.stats_update_failed = False
|
|
370
439
|
except (ValueError, TypeError, AttributeError) as err:
|
|
371
|
-
_LOGGER.warning(
|
|
440
|
+
_LOGGER.warning(
|
|
441
|
+
"Unable to calculate remaining time for device %s: %s", self.name, err
|
|
442
|
+
)
|
|
372
443
|
|
|
373
444
|
async def _get_parental_control_setting(self, now: datetime):
|
|
374
445
|
"""Retreives parental control settings from the API."""
|
|
@@ -383,7 +454,7 @@ class Device:
|
|
|
383
454
|
"""Retrieve daily summaries."""
|
|
384
455
|
_LOGGER.debug(">> Device._get_daily_summaries()")
|
|
385
456
|
response = await self._api.async_get_device_daily_summaries(
|
|
386
|
-
device_id
|
|
457
|
+
device_id=self.device_id
|
|
387
458
|
)
|
|
388
459
|
self.daily_summaries = response["json"]["dailySummaries"]
|
|
389
460
|
_LOGGER.debug("New daily summary %s", self.daily_summaries)
|
|
@@ -395,14 +466,16 @@ class Device:
|
|
|
395
466
|
if self.alarms_enabled is not None:
|
|
396
467
|
# first refresh can come from self.extra without http request
|
|
397
468
|
response = await self._api.async_get_account_device(
|
|
398
|
-
device_id
|
|
469
|
+
device_id=self.device_id
|
|
399
470
|
)
|
|
400
471
|
self.extra = response["json"]["ownedDevice"]["device"]
|
|
401
472
|
status = self.extra["alarmSetting"]["visibility"]
|
|
402
473
|
self.alarms_enabled = status == str(AlarmSettingState.VISIBLE)
|
|
403
|
-
_LOGGER.debug(
|
|
404
|
-
|
|
405
|
-
|
|
474
|
+
_LOGGER.debug(
|
|
475
|
+
"Cached alarms enabled to state %s for device %s",
|
|
476
|
+
self.alarms_enabled,
|
|
477
|
+
self.device_id,
|
|
478
|
+
)
|
|
406
479
|
|
|
407
480
|
async def get_monthly_summary(self, search_date: datetime = None) -> dict | None:
|
|
408
481
|
"""Gets the monthly summary."""
|
|
@@ -420,56 +493,71 @@ class Device:
|
|
|
420
493
|
available_summaries = response["json"]["available"]
|
|
421
494
|
_LOGGER.debug("Available monthly summaries: %s", available_summaries)
|
|
422
495
|
if not available_summaries:
|
|
423
|
-
_LOGGER.debug(
|
|
496
|
+
_LOGGER.debug(
|
|
497
|
+
"No monthly summaries available for device %s", self.device_id
|
|
498
|
+
)
|
|
424
499
|
return None
|
|
425
500
|
# Use the most recent available summary
|
|
426
501
|
available_summary = available_summaries[0]
|
|
427
|
-
search_date = datetime.strptime(
|
|
428
|
-
|
|
502
|
+
search_date = datetime.strptime(
|
|
503
|
+
f"{available_summary['year']}-{available_summary['month']}-01",
|
|
504
|
+
"%Y-%m-%d",
|
|
505
|
+
)
|
|
506
|
+
_LOGGER.debug(
|
|
507
|
+
"Using search date %s for monthly summary request", search_date
|
|
508
|
+
)
|
|
429
509
|
latest = True
|
|
430
510
|
|
|
431
511
|
try:
|
|
432
512
|
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
|
|
513
|
+
device_id=self.device_id, year=search_date.year, month=search_date.month
|
|
436
514
|
)
|
|
437
515
|
except HttpException as exc:
|
|
438
|
-
_LOGGER.warning(
|
|
439
|
-
|
|
440
|
-
|
|
516
|
+
_LOGGER.warning(
|
|
517
|
+
"HTTP Exception raised while getting monthly summary for device %s: %s",
|
|
518
|
+
self.device_id,
|
|
519
|
+
exc,
|
|
520
|
+
)
|
|
441
521
|
return None
|
|
442
522
|
else:
|
|
443
|
-
_LOGGER.debug(
|
|
444
|
-
|
|
445
|
-
|
|
523
|
+
_LOGGER.debug(
|
|
524
|
+
"Monthly summary query complete for device %s: %s",
|
|
525
|
+
self.device_id,
|
|
526
|
+
response["json"]["summary"],
|
|
527
|
+
)
|
|
446
528
|
if latest:
|
|
447
529
|
self.last_month_summary = summary = response["json"]["summary"]
|
|
448
530
|
return summary
|
|
449
531
|
return response["json"]["summary"]
|
|
450
532
|
|
|
451
|
-
|
|
452
533
|
def get_date_summary(self, input_date: datetime = datetime.now()) -> dict:
|
|
453
534
|
"""Returns usage for a given date."""
|
|
454
535
|
if not self.daily_summaries:
|
|
455
536
|
raise ValueError("No daily summaries available to search.")
|
|
456
537
|
summary = [
|
|
457
|
-
x
|
|
458
|
-
|
|
538
|
+
x
|
|
539
|
+
for x in self.daily_summaries
|
|
540
|
+
if x["date"] == input_date.strftime("%Y-%m-%d")
|
|
459
541
|
]
|
|
460
542
|
if len(summary) == 0:
|
|
461
543
|
input_date -= timedelta(days=1)
|
|
462
544
|
summary = [
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
545
|
+
x
|
|
546
|
+
for x in self.daily_summaries
|
|
547
|
+
if x["date"] == input_date.strftime("%Y-%m-%d")
|
|
548
|
+
]
|
|
466
549
|
if len(summary) == 0:
|
|
467
|
-
raise ValueError(
|
|
550
|
+
raise ValueError(
|
|
551
|
+
f"A summary for the given date {input_date} does not exist"
|
|
552
|
+
)
|
|
468
553
|
return summary
|
|
469
554
|
|
|
470
555
|
def get_application(self, application_id: str) -> Application:
|
|
471
556
|
"""Returns a single application."""
|
|
472
|
-
app = next(
|
|
557
|
+
app = next(
|
|
558
|
+
(app for app in self.applications if app.application_id == application_id),
|
|
559
|
+
None,
|
|
560
|
+
)
|
|
473
561
|
if app:
|
|
474
562
|
return app
|
|
475
563
|
raise ValueError(f"Application with id {application_id} not found.")
|
|
@@ -482,7 +570,7 @@ class Device:
|
|
|
482
570
|
raise ValueError(f"Player with id {player_id} not found.")
|
|
483
571
|
|
|
484
572
|
@classmethod
|
|
485
|
-
async def from_devices_response(cls, raw: dict, api) -> list[
|
|
573
|
+
async def from_devices_response(cls, raw: dict, api) -> list["Device"]:
|
|
486
574
|
"""Parses a device request response body."""
|
|
487
575
|
_LOGGER.debug("Parsing device list response")
|
|
488
576
|
if "ownedDevices" not in raw.keys():
|
|
@@ -500,7 +588,7 @@ class Device:
|
|
|
500
588
|
return devices
|
|
501
589
|
|
|
502
590
|
@classmethod
|
|
503
|
-
def from_device_response(cls, raw: dict, api) ->
|
|
591
|
+
def from_device_response(cls, raw: dict, api) -> "Device":
|
|
504
592
|
"""Parses a single device request response body."""
|
|
505
593
|
_LOGGER.debug("Parsing device response")
|
|
506
594
|
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
|
|
pynintendoparental/exceptions.py
CHANGED
|
@@ -8,31 +8,13 @@ class RangeErrorKeys(StrEnum):
|
|
|
8
8
|
DAILY_PLAYTIME = "daily_playtime_out_of_range"
|
|
9
9
|
BEDTIME = "bedtime_alarm_out_of_range"
|
|
10
10
|
|
|
11
|
-
class HttpException(Exception):
|
|
12
|
-
"""A HTTP error occured"""
|
|
13
|
-
def __init__(self, status_code: int, message: str, error_code: str | None = None) -> None:
|
|
14
|
-
"""Initialize the exception."""
|
|
15
|
-
super().__init__(message)
|
|
16
|
-
self.status_code = status_code
|
|
17
|
-
self.message = message
|
|
18
|
-
self.error_code = error_code
|
|
19
|
-
|
|
20
|
-
def __str__(self) -> str:
|
|
21
|
-
if self.error_code:
|
|
22
|
-
return f"HTTP {self.status_code}: {self.message} ({self.error_code})"
|
|
23
|
-
return f"HTTP {self.status_code}: {self.message}"
|
|
24
|
-
|
|
25
|
-
class InvalidSessionTokenException(HttpException):
|
|
26
|
-
"""Provided session token was invalid (invalid_grant)."""
|
|
27
|
-
|
|
28
|
-
class InvalidOAuthConfigurationException(HttpException):
|
|
29
|
-
"""The OAuth scopes are invalid."""
|
|
30
|
-
|
|
31
11
|
class NoDevicesFoundException(Exception):
|
|
32
12
|
"""No devices were found for the account."""
|
|
33
13
|
|
|
14
|
+
|
|
34
15
|
class InputValidationError(Exception):
|
|
35
16
|
"""Input Validation Failed."""
|
|
17
|
+
|
|
36
18
|
value: object
|
|
37
19
|
error_key: str
|
|
38
20
|
|
|
@@ -40,11 +22,13 @@ class InputValidationError(Exception):
|
|
|
40
22
|
super().__init__(f"{self.__doc__} Received value: {value}")
|
|
41
23
|
self.value = value
|
|
42
24
|
|
|
25
|
+
|
|
43
26
|
class BedtimeOutOfRangeError(InputValidationError):
|
|
44
27
|
"""Bedtime is outside of the allowed range."""
|
|
45
28
|
|
|
46
29
|
error_key = RangeErrorKeys.BEDTIME
|
|
47
30
|
|
|
31
|
+
|
|
48
32
|
class DailyPlaytimeOutOfRangeError(InputValidationError):
|
|
49
33
|
"""Daily playtime is outside of the allowed range."""
|
|
50
34
|
|
pynintendoparental/player.py
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from .const import _LOGGER
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
class Player:
|
|
6
7
|
"""Defines a single player on a Nintendo device."""
|
|
8
|
+
|
|
7
9
|
def __init__(self):
|
|
8
10
|
"""Init a player."""
|
|
9
11
|
self.player_image: str = None
|
|
@@ -25,7 +27,7 @@ class Player:
|
|
|
25
27
|
break
|
|
26
28
|
|
|
27
29
|
@classmethod
|
|
28
|
-
def from_device_daily_summary(cls, raw: list[dict]) -> list[
|
|
30
|
+
def from_device_daily_summary(cls, raw: list[dict]) -> list["Player"]:
|
|
29
31
|
"""Converts a daily summary response into a list of players."""
|
|
30
32
|
players = []
|
|
31
33
|
_LOGGER.debug("Building players from device daily summary.")
|
pynintendoparental/utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pynintendoparental
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A Python module to interact with Nintendo Parental Controls
|
|
5
5
|
Home-page: http://github.com/pantherale0/pynintendoparental
|
|
6
6
|
Author: pantherale0
|
|
@@ -11,6 +11,7 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Requires-Python: >=3.8, <4
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pynintendoauth~=1.0.0
|
|
14
15
|
Provides-Extra: dev
|
|
15
16
|
Requires-Dist: bandit<1.9,>=1.7; extra == "dev"
|
|
16
17
|
Requires-Dist: black<26,>=23; extra == "dev"
|
|
@@ -29,6 +30,7 @@ Dynamic: home-page
|
|
|
29
30
|
Dynamic: license
|
|
30
31
|
Dynamic: license-file
|
|
31
32
|
Dynamic: provides-extra
|
|
33
|
+
Dynamic: requires-dist
|
|
32
34
|
Dynamic: requires-python
|
|
33
35
|
Dynamic: summary
|
|
34
36
|
|