pynintendoparental 1.1.2__tar.gz → 1.1.3__tar.gz
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-1.1.2 → pynintendoparental-1.1.3}/PKG-INFO +1 -1
- pynintendoparental-1.1.3/pynintendoparental/_version.py +1 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/api.py +8 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/device.py +134 -126
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/enum.py +9 -1
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/exceptions.py +4 -1
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental.egg-info/PKG-INFO +1 -1
- pynintendoparental-1.1.2/pynintendoparental/_version.py +0 -1
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/LICENSE +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/README.md +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/__init__.py +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/application.py +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/authenticator/__init__.py +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/authenticator/const.py +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/const.py +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/player.py +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/py.typed +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/utils.py +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental.egg-info/SOURCES.txt +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental.egg-info/dependency_links.txt +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental.egg-info/requires.txt +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental.egg-info/top_level.txt +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pyproject.toml +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/setup.cfg +0 -0
- {pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.1.3"
|
|
@@ -97,6 +97,14 @@ class Api:
|
|
|
97
97
|
resp["json"] = {}
|
|
98
98
|
resp["headers"] = response.headers
|
|
99
99
|
else:
|
|
100
|
+
if response.content_type == "application/problem+json":
|
|
101
|
+
try:
|
|
102
|
+
error = await response.json()
|
|
103
|
+
if "detail" in error:
|
|
104
|
+
raise HttpException(response.status, error["detail"], error.get("errorCode"))
|
|
105
|
+
except (aiohttp.ContentTypeError, ValueError):
|
|
106
|
+
# Fall through to the generic exception below on parsing failure.
|
|
107
|
+
pass
|
|
100
108
|
raise HttpException(response.status, await response.text())
|
|
101
109
|
|
|
102
110
|
# now return the resp dict
|
|
@@ -8,8 +8,8 @@ from typing import Callable
|
|
|
8
8
|
|
|
9
9
|
from .api import Api
|
|
10
10
|
from .const import _LOGGER, DAYS_OF_WEEK
|
|
11
|
-
from .exceptions import
|
|
12
|
-
from .enum import AlarmSettingState, RestrictionMode
|
|
11
|
+
from .exceptions import BedtimeOutOfRangeError, DailyPlaytimeOutOfRangeError, HttpException
|
|
12
|
+
from .enum import AlarmSettingState, DeviceTimerMode, RestrictionMode
|
|
13
13
|
from .player import Player
|
|
14
14
|
from .utils import is_awaitable
|
|
15
15
|
from .application import Application
|
|
@@ -28,7 +28,7 @@ class Device:
|
|
|
28
28
|
self.parental_control_settings: dict = {}
|
|
29
29
|
self.players: list[Player] = []
|
|
30
30
|
self.limit_time: int | float | None = 0
|
|
31
|
-
self.timer_mode:
|
|
31
|
+
self.timer_mode: DeviceTimerMode | None = None
|
|
32
32
|
self.today_playing_time: int | float = 0
|
|
33
33
|
self.today_time_remaining: int | float = 0
|
|
34
34
|
self.bedtime_alarm: time | None = None
|
|
@@ -66,13 +66,14 @@ class Device:
|
|
|
66
66
|
async def update(self):
|
|
67
67
|
"""Update data."""
|
|
68
68
|
_LOGGER.debug(">> Device.update()")
|
|
69
|
+
now = datetime.now()
|
|
69
70
|
await asyncio.gather(
|
|
70
|
-
self._get_daily_summaries(),
|
|
71
|
-
self._get_parental_control_setting(),
|
|
71
|
+
self._get_daily_summaries(now),
|
|
72
|
+
self._get_parental_control_setting(now),
|
|
72
73
|
self.get_monthly_summary(),
|
|
73
74
|
self._get_extras()
|
|
74
75
|
)
|
|
75
|
-
if self.players
|
|
76
|
+
if not self.players:
|
|
76
77
|
self.players = Player.from_device_daily_summary(self.daily_summaries)
|
|
77
78
|
else:
|
|
78
79
|
for player in self.players:
|
|
@@ -101,26 +102,29 @@ class Device:
|
|
|
101
102
|
else:
|
|
102
103
|
cb()
|
|
103
104
|
|
|
105
|
+
async def _send_api_update(self, api_call: Callable, *args, **kwargs):
|
|
106
|
+
"""Sends an update to the API and refreshes local state."""
|
|
107
|
+
now = kwargs.pop("now", datetime.now())
|
|
108
|
+
response = await api_call(*args, **kwargs)
|
|
109
|
+
self._parse_parental_control_setting(response["json"], now)
|
|
110
|
+
self._calculate_times(now)
|
|
111
|
+
await self._execute_callbacks()
|
|
112
|
+
|
|
104
113
|
async def set_new_pin(self, pin: str):
|
|
105
114
|
"""Updates the pin for the device."""
|
|
106
115
|
_LOGGER.debug(">> Device.set_new_pin(pin=REDACTED)")
|
|
107
|
-
self.
|
|
108
|
-
|
|
116
|
+
await self._send_api_update(
|
|
117
|
+
self._api.async_update_unlock_code,
|
|
109
118
|
new_code=pin,
|
|
110
119
|
device_id=self.device_id
|
|
111
120
|
)
|
|
112
|
-
self._parse_parental_control_setting(response["json"])
|
|
113
|
-
await self._execute_callbacks()
|
|
114
121
|
|
|
115
122
|
async def add_extra_time(self, minutes: int):
|
|
116
123
|
"""Add extra time to the device."""
|
|
117
124
|
_LOGGER.debug(">> Device.add_extra_time(minutes=%s)", minutes)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
await self._execute_callbacks()
|
|
123
|
-
|
|
125
|
+
# This endpoint does not return parental control settings, so we call it directly.
|
|
126
|
+
await self._api.async_update_extra_playing_time(self.device_id, minutes)
|
|
127
|
+
await self._get_parental_control_setting(datetime.now())
|
|
124
128
|
|
|
125
129
|
async def set_restriction_mode(self, mode: RestrictionMode):
|
|
126
130
|
"""Updates the restriction mode of the device."""
|
|
@@ -132,7 +136,8 @@ class Device:
|
|
|
132
136
|
"playTimerRegulations": self.parental_control_settings["playTimerRegulations"]
|
|
133
137
|
}
|
|
134
138
|
)
|
|
135
|
-
|
|
139
|
+
now = datetime.now()
|
|
140
|
+
self._parse_parental_control_setting(response["json"], now) # Don't need to recalculate times
|
|
136
141
|
await self._execute_callbacks()
|
|
137
142
|
|
|
138
143
|
async def set_bedtime_alarm(self, value: time):
|
|
@@ -145,6 +150,7 @@ class Device:
|
|
|
145
150
|
(value.hour == 0 and value.minute == 0)
|
|
146
151
|
):
|
|
147
152
|
raise BedtimeOutOfRangeError(value=value)
|
|
153
|
+
now = datetime.now()
|
|
148
154
|
bedtime = {
|
|
149
155
|
"enabled": value.hour != 0 and value.minute != 0,
|
|
150
156
|
}
|
|
@@ -156,21 +162,33 @@ class Device:
|
|
|
156
162
|
"minute": value.minute
|
|
157
163
|
}
|
|
158
164
|
}
|
|
159
|
-
if self.timer_mode ==
|
|
165
|
+
if self.timer_mode == DeviceTimerMode.DAILY:
|
|
160
166
|
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"]["bedtime"] = bedtime
|
|
161
167
|
else:
|
|
162
168
|
self.parental_control_settings["playTimerRegulations"]["eachDayOfTheWeekRegulations"][
|
|
163
|
-
DAYS_OF_WEEK[
|
|
169
|
+
DAYS_OF_WEEK[now.weekday()]
|
|
164
170
|
]["bedtime"] = bedtime
|
|
165
|
-
|
|
171
|
+
await self._send_api_update(
|
|
172
|
+
self._api.async_update_play_timer,
|
|
173
|
+
settings={
|
|
174
|
+
"deviceId": self.device_id,
|
|
175
|
+
"playTimerRegulations": self.parental_control_settings["playTimerRegulations"]
|
|
176
|
+
},
|
|
177
|
+
now=now
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
async def set_timer_mode(self, mode: DeviceTimerMode):
|
|
181
|
+
"""Updates the timer mode of the device."""
|
|
182
|
+
_LOGGER.debug(">> Device.set_timer_mode(mode=%s)", mode)
|
|
183
|
+
self.timer_mode = mode
|
|
184
|
+
self.parental_control_settings["playTimerRegulations"]["timerMode"] = str(mode)
|
|
185
|
+
await self._send_api_update(
|
|
186
|
+
self._api.async_update_play_timer,
|
|
166
187
|
settings={
|
|
167
188
|
"deviceId": self.device_id,
|
|
168
189
|
"playTimerRegulations": self.parental_control_settings["playTimerRegulations"]
|
|
169
190
|
}
|
|
170
191
|
)
|
|
171
|
-
self._parse_parental_control_setting(response["json"])
|
|
172
|
-
self._calculate_times()
|
|
173
|
-
await self._execute_callbacks()
|
|
174
192
|
|
|
175
193
|
async def update_max_daily_playtime(self, minutes: int | float = 0):
|
|
176
194
|
"""Updates the maximum daily playtime of a device."""
|
|
@@ -180,11 +198,12 @@ class Device:
|
|
|
180
198
|
minutes = int(minutes)
|
|
181
199
|
if minutes > 360 or minutes < -1:
|
|
182
200
|
raise DailyPlaytimeOutOfRangeError(minutes)
|
|
201
|
+
now = datetime.now()
|
|
183
202
|
ttpiod = True
|
|
184
203
|
if minutes == -1:
|
|
185
204
|
ttpiod = False
|
|
186
205
|
minutes = None
|
|
187
|
-
if self.timer_mode ==
|
|
206
|
+
if self.timer_mode == DeviceTimerMode.DAILY:
|
|
188
207
|
_LOGGER.debug(
|
|
189
208
|
"Setting timeToPlayInOneDay.limitTime for device %s to value %s",
|
|
190
209
|
self.device_id,
|
|
@@ -201,22 +220,21 @@ class Device:
|
|
|
201
220
|
minutes
|
|
202
221
|
)
|
|
203
222
|
day_of_week_regs = self.parental_control_settings["playTimerRegulations"]["eachDayOfTheWeekRegulations"]
|
|
204
|
-
current_day = DAYS_OF_WEEK[
|
|
223
|
+
current_day = DAYS_OF_WEEK[now.weekday()]
|
|
205
224
|
day_of_week_regs[current_day]["timeToPlayInOneDay"]["enabled"] = ttpiod
|
|
206
225
|
if "limitTime" in day_of_week_regs[current_day]["timeToPlayInOneDay"] and minutes is None:
|
|
207
226
|
day_of_week_regs[current_day]["timeToPlayInOneDay"].pop("limitTime")
|
|
208
227
|
else:
|
|
209
228
|
day_of_week_regs[current_day]["timeToPlayInOneDay"]["limitTime"] = minutes
|
|
210
229
|
|
|
211
|
-
|
|
230
|
+
await self._send_api_update(
|
|
231
|
+
self._api.async_update_play_timer,
|
|
212
232
|
settings={
|
|
213
233
|
"deviceId": self.device_id,
|
|
214
234
|
"playTimerRegulations": self.parental_control_settings["playTimerRegulations"]
|
|
215
|
-
}
|
|
235
|
+
},
|
|
236
|
+
now=now
|
|
216
237
|
)
|
|
217
|
-
self._parse_parental_control_setting(response["json"])
|
|
218
|
-
self._calculate_times()
|
|
219
|
-
await self._execute_callbacks()
|
|
220
238
|
|
|
221
239
|
def _update_applications(self):
|
|
222
240
|
"""Updates applications from daily summary."""
|
|
@@ -229,112 +247,71 @@ class Device:
|
|
|
229
247
|
except ValueError:
|
|
230
248
|
self.applications.append(app)
|
|
231
249
|
|
|
232
|
-
def
|
|
233
|
-
"""
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
regulations = current_day
|
|
239
|
-
else:
|
|
240
|
-
regulations = self.parental_control_settings.get("playTimerRegulations", {}).get("dailyRegulations", {})
|
|
241
|
-
|
|
242
|
-
limit_time = regulations.get("timeToPlayInOneDay", {}).get("limitTime")
|
|
243
|
-
self.limit_time = limit_time if limit_time is not None else -1
|
|
244
|
-
|
|
245
|
-
if self.timer_mode == "EACH_DAY_OF_THE_WEEK":
|
|
246
|
-
if current_day["bedtime"]["enabled"]:
|
|
247
|
-
self.bedtime_alarm = time(hour=
|
|
248
|
-
current_day["bedtime"]["endingTime"]["hour"],
|
|
249
|
-
minute=current_day["bedtime"]["endingTime"]["minute"])
|
|
250
|
-
else:
|
|
251
|
-
self.bedtime_alarm = time(hour=0, minute=0)
|
|
252
|
-
else:
|
|
253
|
-
bedtime_alarm = self.parental_control_settings["playTimerRegulations"]["dailyRegulations"]["bedtime"]
|
|
254
|
-
if bedtime_alarm["enabled"]:
|
|
255
|
-
self.bedtime_alarm = time(hour=
|
|
256
|
-
bedtime_alarm["endingTime"]["hour"],
|
|
257
|
-
minute=bedtime_alarm["endingTime"]["minute"])
|
|
258
|
-
else:
|
|
259
|
-
self.bedtime_alarm = time(hour=0, minute=0)
|
|
260
|
-
return True
|
|
250
|
+
def _get_today_regulation(self, now: datetime) -> dict:
|
|
251
|
+
"""Returns the regulation settings for the current day."""
|
|
252
|
+
if self.timer_mode == DeviceTimerMode.EACH_DAY_OF_THE_WEEK:
|
|
253
|
+
day_of_week_regs = self.parental_control_settings["playTimerRegulations"].get("eachDayOfTheWeekRegulations", {})
|
|
254
|
+
return day_of_week_regs.get(DAYS_OF_WEEK[now.weekday()], {})
|
|
255
|
+
return self.parental_control_settings.get("playTimerRegulations", {}).get("dailyRegulations", {})
|
|
261
256
|
|
|
262
|
-
def _parse_parental_control_setting(self, pcs: dict):
|
|
257
|
+
def _parse_parental_control_setting(self, pcs: dict, now: datetime):
|
|
263
258
|
"""Parse a parental control setting request response."""
|
|
264
259
|
_LOGGER.debug(">> Device._parse_parental_control_setting()")
|
|
265
260
|
self.parental_control_settings = pcs["parentalControlSetting"]
|
|
261
|
+
|
|
262
|
+
# Clean up bedtimeStartingTime if it's empty
|
|
266
263
|
if "bedtimeStartingTime" in self.parental_control_settings["playTimerRegulations"]:
|
|
267
264
|
if self.parental_control_settings["playTimerRegulations"].get("bedtimeStartingTime", {}).get("hour", 0) == 0:
|
|
268
265
|
self.parental_control_settings["playTimerRegulations"].pop("bedtimeStartingTime")
|
|
266
|
+
|
|
269
267
|
self.forced_termination_mode = (
|
|
270
268
|
self.parental_control_settings["playTimerRegulations"]["restrictionMode"] == str(RestrictionMode.FORCED_TERMINATION)
|
|
271
269
|
)
|
|
272
|
-
|
|
270
|
+
|
|
271
|
+
# Update limit and bedtime from regulations
|
|
272
|
+
self.timer_mode = DeviceTimerMode(
|
|
273
|
+
self.parental_control_settings["playTimerRegulations"]["timerMode"]
|
|
274
|
+
)
|
|
275
|
+
today_reg = self._get_today_regulation(now)
|
|
276
|
+
limit_time = today_reg.get("timeToPlayInOneDay", {}).get("limitTime")
|
|
277
|
+
self.limit_time = limit_time if limit_time is not None else -1
|
|
278
|
+
|
|
279
|
+
bedtime_setting = today_reg.get("bedtime", {})
|
|
280
|
+
if bedtime_setting.get("enabled"):
|
|
281
|
+
self.bedtime_alarm = time(
|
|
282
|
+
hour=bedtime_setting["endingTime"]["hour"],
|
|
283
|
+
minute=bedtime_setting["endingTime"]["minute"]
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
self.bedtime_alarm = time(hour=0, minute=0)
|
|
287
|
+
|
|
273
288
|
self._update_applications()
|
|
274
289
|
|
|
275
|
-
def _calculate_times(self):
|
|
290
|
+
def _calculate_times(self, now: datetime):
|
|
276
291
|
"""Calculate times from parental control settings."""
|
|
277
292
|
if not isinstance(self.daily_summaries, list) or not self.daily_summaries:
|
|
278
293
|
return
|
|
279
294
|
if len(self.daily_summaries) == 0:
|
|
280
295
|
return
|
|
281
296
|
_LOGGER.debug(">> Device._calculate_times()")
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
297
|
+
if self.daily_summaries[0]["date"] != now.strftime("%Y-%m-%d"):
|
|
298
|
+
_LOGGER.debug("No daily summary for today, assuming 0 playing time.")
|
|
299
|
+
self.today_playing_time = 0
|
|
300
|
+
self.today_disabled_time = 0
|
|
301
|
+
self.today_exceeded_time = 0
|
|
302
|
+
else:
|
|
303
|
+
self.today_playing_time = self.daily_summaries[0].get("playingTime") or 0
|
|
304
|
+
self.today_disabled_time = self.daily_summaries[0].get("disabledTime") or 0
|
|
305
|
+
self.today_exceeded_time = self.daily_summaries[0].get("exceededTime") or 0
|
|
288
306
|
_LOGGER.debug("Cached playing, disabled and exceeded time for today for device %s",
|
|
289
307
|
self.device_id)
|
|
290
|
-
|
|
291
|
-
now = datetime.now()
|
|
292
|
-
current_minutes_past_midnight = now.hour * 60 + now.minute
|
|
293
|
-
minutes_in_day = 1440 # 24 * 60
|
|
294
|
-
|
|
295
|
-
# 1. Calculate remaining time based on play limit
|
|
296
|
-
|
|
297
|
-
time_remaining_by_play_limit = 0.0
|
|
298
|
-
if self.limit_time in (-1, None):
|
|
299
|
-
# No specific play limit, effectively limited by end of day for this calculation step.
|
|
300
|
-
time_remaining_by_play_limit = float(minutes_in_day - current_minutes_past_midnight)
|
|
301
|
-
elif self.limit_time == 0:
|
|
302
|
-
time_remaining_by_play_limit = 0.0
|
|
303
|
-
else:
|
|
304
|
-
time_remaining_by_play_limit = float(self.limit_time - self.today_playing_time)
|
|
305
|
-
|
|
306
|
-
time_remaining_by_play_limit = max(0.0, time_remaining_by_play_limit)
|
|
307
|
-
|
|
308
|
-
# Initialize overall remaining time with play limit constraint
|
|
309
|
-
effective_remaining_time = time_remaining_by_play_limit
|
|
310
|
-
|
|
311
|
-
# 2. Factor in bedtime alarm, if any, to further constrain remaining time
|
|
312
|
-
if self.bedtime_alarm not in (None, time(hour=0, minute=0)):
|
|
313
|
-
bedtime_dt = datetime.combine(now.date(), self.bedtime_alarm)
|
|
314
|
-
time_remaining_by_bedtime = 0.0
|
|
315
|
-
if bedtime_dt > now: # Bedtime is in the future today
|
|
316
|
-
time_remaining_by_bedtime = (bedtime_dt - now).total_seconds() / 60
|
|
317
|
-
time_remaining_by_bedtime = max(0.0, time_remaining_by_bedtime)
|
|
318
|
-
# else: Bedtime has passed for today or is now, so time_remaining_by_bedtime remains 0.0
|
|
319
|
-
|
|
320
|
-
effective_remaining_time = min(effective_remaining_time, time_remaining_by_bedtime)
|
|
321
|
-
|
|
322
|
-
self.today_time_remaining = int(max(0.0, effective_remaining_time)) # Ensure non-negative and integer
|
|
323
|
-
_LOGGER.debug("Calculated and updated the amount of time remaining for today: %s", self.today_time_remaining)
|
|
324
|
-
self.stats_update_failed = False
|
|
325
|
-
except ValueError as err:
|
|
326
|
-
_LOGGER.debug("Unable to update daily summary for device %s: %s", self.name, err)
|
|
327
|
-
self.stats_update_failed = True
|
|
308
|
+
self._calculate_today_remaining_time(now)
|
|
328
309
|
|
|
329
|
-
current_month = datetime(
|
|
330
|
-
year=datetime.now().year,
|
|
331
|
-
month=datetime.now().month,
|
|
332
|
-
day=1)
|
|
333
310
|
month_playing_time: int = 0
|
|
334
311
|
|
|
335
312
|
for summary in self.daily_summaries:
|
|
336
313
|
date_parsed = datetime.strptime(summary["date"], "%Y-%m-%d")
|
|
337
|
-
if date_parsed
|
|
314
|
+
if date_parsed.year == now.year and date_parsed.month == now.month:
|
|
338
315
|
month_playing_time += summary["playingTime"]
|
|
339
316
|
self.month_playing_time = month_playing_time
|
|
340
317
|
_LOGGER.debug("Cached current month playing time for device %s", self.device_id)
|
|
@@ -362,16 +339,47 @@ class Device:
|
|
|
362
339
|
_LOGGER.debug("Unable to retrieve applications for device %s: %s", self.name, err)
|
|
363
340
|
self.application_update_failed = True
|
|
364
341
|
|
|
365
|
-
|
|
342
|
+
def _calculate_today_remaining_time(self, now: datetime):
|
|
343
|
+
"""Calculates the remaining playing time for today."""
|
|
344
|
+
self.stats_update_failed = True # Assume failure until success
|
|
345
|
+
try:
|
|
346
|
+
minutes_in_day = 1440 # 24 * 60
|
|
347
|
+
current_minutes_past_midnight = now.hour * 60 + now.minute
|
|
348
|
+
|
|
349
|
+
if self.limit_time in (-1, None):
|
|
350
|
+
# No play limit, so remaining time is until end of day.
|
|
351
|
+
time_remaining_by_play_limit = minutes_in_day - current_minutes_past_midnight
|
|
352
|
+
else:
|
|
353
|
+
time_remaining_by_play_limit = self.limit_time - self.today_playing_time
|
|
354
|
+
|
|
355
|
+
# 2. Calculate remaining time until bedtime
|
|
356
|
+
if self.bedtime_alarm and self.bedtime_alarm != time(hour=0, minute=0) and self.alarms_enabled:
|
|
357
|
+
bedtime_dt = datetime.combine(now.date(), self.bedtime_alarm)
|
|
358
|
+
if bedtime_dt > now: # Bedtime is in the future today
|
|
359
|
+
time_remaining_by_bedtime = (bedtime_dt - now).total_seconds() / 60
|
|
360
|
+
else: # Bedtime has passed
|
|
361
|
+
time_remaining_by_bedtime = 0.0
|
|
362
|
+
else:
|
|
363
|
+
time_remaining_by_bedtime = minutes_in_day - current_minutes_past_midnight
|
|
364
|
+
|
|
365
|
+
# Effective remaining time is the minimum of the two constraints
|
|
366
|
+
effective_remaining_time = min(time_remaining_by_play_limit, time_remaining_by_bedtime)
|
|
367
|
+
self.today_time_remaining = int(max(0.0, effective_remaining_time))
|
|
368
|
+
_LOGGER.debug("Calculated today's remaining time: %s minutes", self.today_time_remaining)
|
|
369
|
+
self.stats_update_failed = False
|
|
370
|
+
except (ValueError, TypeError, AttributeError) as err:
|
|
371
|
+
_LOGGER.warning("Unable to calculate remaining time for device %s: %s", self.name, err)
|
|
372
|
+
|
|
373
|
+
async def _get_parental_control_setting(self, now: datetime):
|
|
366
374
|
"""Retreives parental control settings from the API."""
|
|
367
375
|
_LOGGER.debug(">> Device._get_parental_control_setting()")
|
|
368
376
|
response = await self._api.async_get_device_parental_control_setting(
|
|
369
377
|
device_id=self.device_id
|
|
370
378
|
)
|
|
371
|
-
self._parse_parental_control_setting(response["json"])
|
|
372
|
-
self._calculate_times()
|
|
379
|
+
self._parse_parental_control_setting(response["json"], now)
|
|
380
|
+
self._calculate_times(now)
|
|
373
381
|
|
|
374
|
-
async def _get_daily_summaries(self):
|
|
382
|
+
async def _get_daily_summaries(self, now: datetime):
|
|
375
383
|
"""Retrieve daily summaries."""
|
|
376
384
|
_LOGGER.debug(">> Device._get_daily_summaries()")
|
|
377
385
|
response = await self._api.async_get_device_daily_summaries(
|
|
@@ -379,7 +387,7 @@ class Device:
|
|
|
379
387
|
)
|
|
380
388
|
self.daily_summaries = response["json"]["dailySummaries"]
|
|
381
389
|
_LOGGER.debug("New daily summary %s", self.daily_summaries)
|
|
382
|
-
self._calculate_times()
|
|
390
|
+
self._calculate_times(now)
|
|
383
391
|
|
|
384
392
|
async def _get_extras(self):
|
|
385
393
|
"""Retrieve extra properties."""
|
|
@@ -443,6 +451,8 @@ class Device:
|
|
|
443
451
|
|
|
444
452
|
def get_date_summary(self, input_date: datetime = datetime.now()) -> dict:
|
|
445
453
|
"""Returns usage for a given date."""
|
|
454
|
+
if not self.daily_summaries:
|
|
455
|
+
raise ValueError("No daily summaries available to search.")
|
|
446
456
|
summary = [
|
|
447
457
|
x for x in self.daily_summaries
|
|
448
458
|
if x["date"] == input_date.strftime('%Y-%m-%d')
|
|
@@ -459,19 +469,17 @@ class Device:
|
|
|
459
469
|
|
|
460
470
|
def get_application(self, application_id: str) -> Application:
|
|
461
471
|
"""Returns a single application."""
|
|
462
|
-
app =
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
raise ValueError("Application not found.")
|
|
472
|
+
app = next((app for app in self.applications if app.application_id == application_id), None)
|
|
473
|
+
if app:
|
|
474
|
+
return app
|
|
475
|
+
raise ValueError(f"Application with id {application_id} not found.")
|
|
467
476
|
|
|
468
477
|
def get_player(self, player_id: str) -> Player:
|
|
469
478
|
"""Returns a player."""
|
|
470
|
-
player =
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
raise ValueError("Player not found.")
|
|
479
|
+
player = next((p for p in self.players if p.player_id == player_id), None)
|
|
480
|
+
if player:
|
|
481
|
+
return player
|
|
482
|
+
raise ValueError(f"Player with id {player_id} not found.")
|
|
475
483
|
|
|
476
484
|
@classmethod
|
|
477
485
|
async def from_devices_response(cls, raw: dict, api) -> list['Device']:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Enums"""
|
|
2
2
|
|
|
3
|
-
from enum import Enum
|
|
3
|
+
from enum import Enum, StrEnum
|
|
4
4
|
|
|
5
5
|
class AlarmSettingState(Enum):
|
|
6
6
|
"""Alarm setting states."""
|
|
@@ -20,3 +20,11 @@ class RestrictionMode(Enum):
|
|
|
20
20
|
|
|
21
21
|
def __str__(self) -> str:
|
|
22
22
|
return self.name
|
|
23
|
+
|
|
24
|
+
class DeviceTimerMode(StrEnum):
|
|
25
|
+
"""Device timer modes."""
|
|
26
|
+
DAILY = "DAILY"
|
|
27
|
+
EACH_DAY_OF_THE_WEEK = "EACH_DAY_OF_THE_WEEK"
|
|
28
|
+
|
|
29
|
+
def __str__(self) -> str:
|
|
30
|
+
return self.name
|
|
@@ -10,13 +10,16 @@ class RangeErrorKeys(StrEnum):
|
|
|
10
10
|
|
|
11
11
|
class HttpException(Exception):
|
|
12
12
|
"""A HTTP error occured"""
|
|
13
|
-
def __init__(self, status_code: int, message: str) -> None:
|
|
13
|
+
def __init__(self, status_code: int, message: str, error_code: str | None = None) -> None:
|
|
14
14
|
"""Initialize the exception."""
|
|
15
15
|
super().__init__(message)
|
|
16
16
|
self.status_code = status_code
|
|
17
17
|
self.message = message
|
|
18
|
+
self.error_code = error_code
|
|
18
19
|
|
|
19
20
|
def __str__(self) -> str:
|
|
21
|
+
if self.error_code:
|
|
22
|
+
return f"HTTP {self.status_code}: {self.message} ({self.error_code})"
|
|
20
23
|
return f"HTTP {self.status_code}: {self.message}"
|
|
21
24
|
|
|
22
25
|
class InvalidSessionTokenException(HttpException):
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.1.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/authenticator/__init__.py
RENAMED
|
File without changes
|
{pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental/authenticator/const.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental.egg-info/requires.txt
RENAMED
|
File without changes
|
{pynintendoparental-1.1.2 → pynintendoparental-1.1.3}/pynintendoparental.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|