pynintendoparental 2.1.3__tar.gz → 2.2.0__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-2.1.3 → pynintendoparental-2.2.0}/PKG-INFO +2 -2
- pynintendoparental-2.2.0/pynintendoparental/_version.py +1 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental/api.py +14 -2
- pynintendoparental-2.2.0/pynintendoparental/application.py +132 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental/const.py +13 -13
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental/device.py +180 -91
- pynintendoparental-2.2.0/pynintendoparental/enum.py +59 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental/exceptions.py +19 -2
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental.egg-info/PKG-INFO +2 -2
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental.egg-info/SOURCES.txt +2 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental.egg-info/requires.txt +1 -1
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/setup.py +1 -1
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/tests/test_api.py +9 -4
- pynintendoparental-2.2.0/tests/test_applications.py +211 -0
- pynintendoparental-2.2.0/tests/test_device.py +443 -0
- pynintendoparental-2.2.0/tests/test_enum.py +48 -0
- pynintendoparental-2.1.3/pynintendoparental/_version.py +0 -1
- pynintendoparental-2.1.3/pynintendoparental/application.py +0 -102
- pynintendoparental-2.1.3/pynintendoparental/enum.py +0 -36
- pynintendoparental-2.1.3/tests/test_device.py +0 -110
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/LICENSE +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/README.md +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental/__init__.py +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental/authenticator.py +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental/player.py +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental/py.typed +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental/utils.py +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental.egg-info/dependency_links.txt +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pynintendoparental.egg-info/top_level.txt +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/pyproject.toml +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/setup.cfg +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/tests/test_init.py +0 -0
- {pynintendoparental-2.1.3 → pynintendoparental-2.2.0}/tests/test_player.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pynintendoparental
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -23,7 +23,7 @@ Requires-Dist: isort<7,>=5; extra == "dev"
|
|
|
23
23
|
Requires-Dist: mypy<1.20,>=1.5; extra == "dev"
|
|
24
24
|
Requires-Dist: pytest<9,>=7; extra == "dev"
|
|
25
25
|
Requires-Dist: pytest-cov<8,>=4; extra == "dev"
|
|
26
|
-
Requires-Dist: pytest-asyncio<
|
|
26
|
+
Requires-Dist: pytest-asyncio<2.0,>=0.21; extra == "dev"
|
|
27
27
|
Requires-Dist: syrupy<6,>=5; extra == "dev"
|
|
28
28
|
Requires-Dist: twine<7,>=4; extra == "dev"
|
|
29
29
|
Dynamic: author
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.2.0"
|
|
@@ -147,14 +147,26 @@ class Api:
|
|
|
147
147
|
MONTH=f"{month:02d}",
|
|
148
148
|
)
|
|
149
149
|
|
|
150
|
-
async def async_update_restriction_level(
|
|
150
|
+
async def async_update_restriction_level(
|
|
151
|
+
self, device_id: str, parental_control_setting: dict
|
|
152
|
+
) -> dict:
|
|
151
153
|
"""Update device restriction level."""
|
|
154
|
+
settings = {
|
|
155
|
+
"deviceId": device_id,
|
|
156
|
+
**parental_control_setting,
|
|
157
|
+
}
|
|
152
158
|
return await self.send_request(
|
|
153
159
|
endpoint="update_restriction_level", body=settings
|
|
154
160
|
)
|
|
155
161
|
|
|
156
|
-
async def async_update_play_timer(
|
|
162
|
+
async def async_update_play_timer(
|
|
163
|
+
self, device_id: str, play_timer_regulations: dict
|
|
164
|
+
) -> dict:
|
|
157
165
|
"""Update device play timer settings."""
|
|
166
|
+
settings = {
|
|
167
|
+
"deviceId": device_id,
|
|
168
|
+
"playTimerRegulations": play_timer_regulations,
|
|
169
|
+
}
|
|
158
170
|
return await self.send_request(endpoint="update_play_timer", body=settings)
|
|
159
171
|
|
|
160
172
|
async def async_update_unlock_code(self, new_code: str, device_id: str) -> dict:
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""A Nintendo application."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Callable, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from .api import Api
|
|
8
|
+
from .const import _LOGGER
|
|
9
|
+
from .enum import SafeLaunchSetting
|
|
10
|
+
from .utils import is_awaitable
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .device import Device
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Application:
|
|
17
|
+
"""Model for an application"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
app_id: str,
|
|
22
|
+
name: str,
|
|
23
|
+
device_id: str,
|
|
24
|
+
api: Api,
|
|
25
|
+
send_api_update: Callable,
|
|
26
|
+
callbacks: list,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Initialise a application."""
|
|
29
|
+
self.application_id: str = app_id
|
|
30
|
+
self._device_id: str = device_id
|
|
31
|
+
self._api: Api = api
|
|
32
|
+
self._send_api_update: Callable = send_api_update
|
|
33
|
+
self.first_played_date: datetime = None
|
|
34
|
+
self.has_ugc: bool = None
|
|
35
|
+
self.image_url: str = None # uses small image from Nintendo
|
|
36
|
+
self.playing_days: int = None
|
|
37
|
+
self.shop_url: str = None
|
|
38
|
+
self.name: str = name
|
|
39
|
+
self.safe_launch_setting: SafeLaunchSetting = SafeLaunchSetting.NONE
|
|
40
|
+
self.today_time_played: int = 0
|
|
41
|
+
self._callbacks: list[Callable] = []
|
|
42
|
+
self._parental_control_settings: dict = {}
|
|
43
|
+
self._monthly_summary: dict = {}
|
|
44
|
+
self._daily_summary: dict = {}
|
|
45
|
+
self._device: "Device" | None = None
|
|
46
|
+
|
|
47
|
+
# Register internal callbacks
|
|
48
|
+
callbacks.append(self._internal_update_callback)
|
|
49
|
+
|
|
50
|
+
async def set_safe_launch_setting(self, safe_launch_setting: SafeLaunchSetting):
|
|
51
|
+
"""Set the safe launch setting for the application."""
|
|
52
|
+
if (
|
|
53
|
+
not self._device
|
|
54
|
+
or "whitelistedApplicationList" not in self._parental_control_settings
|
|
55
|
+
):
|
|
56
|
+
raise ValueError("Unable to set SafeLaunchSetting, callbacks not executed.")
|
|
57
|
+
# Update the application safe_launch_setting in the PCS
|
|
58
|
+
pcs = copy.deepcopy(self._parental_control_settings)
|
|
59
|
+
for app in pcs["whitelistedApplicationList"]:
|
|
60
|
+
if app["applicationId"].upper() == self.application_id.upper():
|
|
61
|
+
app["safeLaunch"] = str(safe_launch_setting)
|
|
62
|
+
break
|
|
63
|
+
else:
|
|
64
|
+
raise LookupError(
|
|
65
|
+
"Unable to set SafeLaunchSetting, application no longer in whitelist."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
await self._send_api_update(
|
|
69
|
+
self._api.async_update_restriction_level,
|
|
70
|
+
self._device_id,
|
|
71
|
+
pcs,
|
|
72
|
+
now=datetime.now(),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async def _internal_update_callback(self, device: "Device"):
|
|
76
|
+
"""Internal update callback method for the Device object to inform this Application has been updated."""
|
|
77
|
+
if not device:
|
|
78
|
+
return
|
|
79
|
+
_LOGGER.debug(
|
|
80
|
+
"Internal callback started for app %s - device %s",
|
|
81
|
+
self.application_id,
|
|
82
|
+
device.device_id,
|
|
83
|
+
)
|
|
84
|
+
self._device = device
|
|
85
|
+
self._device_id = device.device_id
|
|
86
|
+
self._parental_control_settings = device.parental_control_settings
|
|
87
|
+
self._monthly_summary = device.last_month_summary
|
|
88
|
+
self._daily_summary = device.daily_summaries
|
|
89
|
+
if "whitelistedApplicationList" not in self._parental_control_settings:
|
|
90
|
+
_LOGGER.warning(
|
|
91
|
+
">> Device %s is missing a application whitelist, unable to update safe launch settings for %s",
|
|
92
|
+
device.device_id,
|
|
93
|
+
self.application_id,
|
|
94
|
+
)
|
|
95
|
+
for app in self._parental_control_settings.get(
|
|
96
|
+
"whitelistedApplicationList", []
|
|
97
|
+
):
|
|
98
|
+
if app["applicationId"].upper() == self.application_id.upper():
|
|
99
|
+
self.safe_launch_setting = SafeLaunchSetting(
|
|
100
|
+
app.get("safeLaunch", "NONE")
|
|
101
|
+
)
|
|
102
|
+
self.image_url = app["imageUri"]
|
|
103
|
+
break
|
|
104
|
+
total_time_played: int = 0
|
|
105
|
+
if self._daily_summary:
|
|
106
|
+
for player_summary in self._daily_summary[0].get("players", []):
|
|
107
|
+
for player_app in player_summary.get("playedGames", []):
|
|
108
|
+
if (
|
|
109
|
+
player_app["meta"]["applicationId"].upper()
|
|
110
|
+
== self.application_id.upper()
|
|
111
|
+
):
|
|
112
|
+
total_time_played += player_app["playingTime"]
|
|
113
|
+
break
|
|
114
|
+
self.today_time_played = total_time_played
|
|
115
|
+
|
|
116
|
+
for cb in self._callbacks:
|
|
117
|
+
if is_awaitable(cb):
|
|
118
|
+
await cb(self)
|
|
119
|
+
else:
|
|
120
|
+
cb(self)
|
|
121
|
+
|
|
122
|
+
def add_application_callback(self, callback):
|
|
123
|
+
"""Add a callback to the application."""
|
|
124
|
+
if not callable(callback):
|
|
125
|
+
raise ValueError("Object must be callable.")
|
|
126
|
+
self._callbacks.append(callback)
|
|
127
|
+
|
|
128
|
+
def remove_application_callback(self, callback):
|
|
129
|
+
"""Remove a callback from the application."""
|
|
130
|
+
if callback not in self._callbacks:
|
|
131
|
+
raise ValueError("Callback not found.")
|
|
132
|
+
self._callbacks.remove(callback)
|
|
@@ -6,13 +6,13 @@ import logging
|
|
|
6
6
|
_LOGGER = logging.getLogger(__package__)
|
|
7
7
|
CLIENT_ID = "54789befb391a838"
|
|
8
8
|
MOBILE_APP_PKG = "com.nintendo.znma"
|
|
9
|
-
MOBILE_APP_VERSION = "2.3.
|
|
10
|
-
MOBILE_APP_BUILD = "
|
|
9
|
+
MOBILE_APP_VERSION = "2.3.1"
|
|
10
|
+
MOBILE_APP_BUILD = "620"
|
|
11
11
|
OS_NAME = "ANDROID"
|
|
12
12
|
OS_VERSION = "34"
|
|
13
13
|
OS_STR = f"{OS_NAME} {OS_VERSION}"
|
|
14
14
|
DEVICE_MODEL = "Pixel 4 XL"
|
|
15
|
-
BASE_URL = "https://app.lp1.znma.srv.nintendo.net
|
|
15
|
+
BASE_URL = "https://app.lp1.znma.srv.nintendo.net"
|
|
16
16
|
USER_AGENT = f"moon_ANDROID/{MOBILE_APP_VERSION} ({MOBILE_APP_PKG}; build:{MOBILE_APP_BUILD}; {OS_STR})"
|
|
17
17
|
|
|
18
18
|
DAYS_OF_WEEK = [
|
|
@@ -27,43 +27,43 @@ DAYS_OF_WEEK = [
|
|
|
27
27
|
|
|
28
28
|
ENDPOINTS = {
|
|
29
29
|
"get_account_devices": {
|
|
30
|
-
"url": "{BASE_URL}/actions/user/fetchOwnedDevices",
|
|
30
|
+
"url": "{BASE_URL}/v2/actions/user/fetchOwnedDevices",
|
|
31
31
|
"method": "GET",
|
|
32
32
|
},
|
|
33
33
|
"get_account_device": {
|
|
34
|
-
"url": "{BASE_URL}/actions/user/fetchOwnedDevice?deviceId={DEVICE_ID}",
|
|
34
|
+
"url": "{BASE_URL}/v2/actions/user/fetchOwnedDevice?deviceId={DEVICE_ID}",
|
|
35
35
|
"method": "GET",
|
|
36
36
|
},
|
|
37
37
|
"get_device_daily_summaries": {
|
|
38
|
-
"url": "{BASE_URL}/actions/playSummary/fetchDailySummaries?deviceId={DEVICE_ID}",
|
|
38
|
+
"url": "{BASE_URL}/v2/actions/playSummary/fetchDailySummaries?deviceId={DEVICE_ID}",
|
|
39
39
|
"method": "GET",
|
|
40
40
|
},
|
|
41
41
|
"get_device_monthly_summaries": {
|
|
42
|
-
"url": "{BASE_URL}/actions/playSummary/fetchLatestMonthlySummary?deviceId={DEVICE_ID}",
|
|
42
|
+
"url": "{BASE_URL}/v2/actions/playSummary/fetchLatestMonthlySummary?deviceId={DEVICE_ID}",
|
|
43
43
|
"method": "GET",
|
|
44
44
|
},
|
|
45
45
|
"get_device_parental_control_setting": {
|
|
46
|
-
"url": "{BASE_URL}/actions/parentalControlSetting/fetchParentalControlSetting?deviceId={DEVICE_ID}",
|
|
46
|
+
"url": "{BASE_URL}/v2/actions/parentalControlSetting/fetchParentalControlSetting?deviceId={DEVICE_ID}",
|
|
47
47
|
"method": "GET",
|
|
48
48
|
},
|
|
49
49
|
"update_restriction_level": {
|
|
50
|
-
"url": "{BASE_URL}/actions/parentalControlSetting/updateRestrictionLevel",
|
|
50
|
+
"url": "{BASE_URL}/v2/actions/parentalControlSetting/updateRestrictionLevel",
|
|
51
51
|
"method": "POST",
|
|
52
52
|
},
|
|
53
53
|
"update_play_timer": {
|
|
54
|
-
"url": "{BASE_URL}/actions/parentalControlSetting/updatePlayTimer",
|
|
54
|
+
"url": "{BASE_URL}/v3/actions/parentalControlSetting/updatePlayTimer",
|
|
55
55
|
"method": "POST",
|
|
56
56
|
},
|
|
57
57
|
"update_unlock_code": {
|
|
58
|
-
"url": "{BASE_URL}/actions/parentalControlSetting/updateUnlockCode",
|
|
58
|
+
"url": "{BASE_URL}/v2/actions/parentalControlSetting/updateUnlockCode",
|
|
59
59
|
"method": "POST",
|
|
60
60
|
},
|
|
61
61
|
"get_device_monthly_summary": {
|
|
62
|
-
"url": "{BASE_URL}/actions/playSummary/fetchMonthlySummary?deviceId={DEVICE_ID}&year={YEAR}&month={MONTH}&containLatest=false",
|
|
62
|
+
"url": "{BASE_URL}/v2/actions/playSummary/fetchMonthlySummary?deviceId={DEVICE_ID}&year={YEAR}&month={MONTH}&containLatest=false",
|
|
63
63
|
"method": "GET",
|
|
64
64
|
},
|
|
65
65
|
"update_extra_playing_time": {
|
|
66
|
-
"url": "{BASE_URL}/actions/device/updateExtraPlayingTime",
|
|
66
|
+
"url": "{BASE_URL}/v2/actions/device/updateExtraPlayingTime",
|
|
67
67
|
"method": "POST",
|
|
68
68
|
},
|
|
69
69
|
}
|
|
@@ -13,8 +13,14 @@ from .const import _LOGGER, DAYS_OF_WEEK
|
|
|
13
13
|
from .exceptions import (
|
|
14
14
|
BedtimeOutOfRangeError,
|
|
15
15
|
DailyPlaytimeOutOfRangeError,
|
|
16
|
+
InvalidDeviceStateError,
|
|
17
|
+
)
|
|
18
|
+
from .enum import (
|
|
19
|
+
AlarmSettingState,
|
|
20
|
+
DeviceTimerMode,
|
|
21
|
+
FunctionalRestrictionLevel,
|
|
22
|
+
RestrictionMode,
|
|
16
23
|
)
|
|
17
|
-
from .enum import AlarmSettingState, DeviceTimerMode, RestrictionMode
|
|
18
24
|
from .player import Player
|
|
19
25
|
from .utils import is_awaitable
|
|
20
26
|
from .application import Application
|
|
@@ -39,6 +45,7 @@ class Device:
|
|
|
39
45
|
self.today_playing_time: int | float = 0
|
|
40
46
|
self.today_time_remaining: int | float = 0
|
|
41
47
|
self.bedtime_alarm: time | None = None
|
|
48
|
+
self.bedtime_end: time | None = None
|
|
42
49
|
self.month_playing_time: int | float = 0
|
|
43
50
|
self.today_disabled_time: int | float = 0
|
|
44
51
|
self.today_exceeded_time: int | float = 0
|
|
@@ -46,14 +53,14 @@ class Device:
|
|
|
46
53
|
self.today_important_info: list = []
|
|
47
54
|
self.today_observations: list = []
|
|
48
55
|
self.last_month_summary: dict = {}
|
|
49
|
-
self.applications:
|
|
56
|
+
self.applications: dict[str, Application] = {}
|
|
50
57
|
self.whitelisted_applications: dict[str, bool] = {}
|
|
51
58
|
self.last_month_playing_time: int = 0
|
|
52
59
|
self.forced_termination_mode: bool = False
|
|
53
60
|
self.alarms_enabled: bool = False
|
|
54
61
|
self.stats_update_failed: bool = False
|
|
55
|
-
self.application_update_failed: bool = False
|
|
56
62
|
self._callbacks: list[Callable] = []
|
|
63
|
+
self._internal_callbacks: list[Callable] = []
|
|
57
64
|
_LOGGER.debug("Device init complete for %s", self.device_id)
|
|
58
65
|
|
|
59
66
|
@property
|
|
@@ -86,6 +93,7 @@ class Device:
|
|
|
86
93
|
)
|
|
87
94
|
for player in self.players.values():
|
|
88
95
|
player.update_from_daily_summary(self.daily_summaries)
|
|
96
|
+
self._update_applications()
|
|
89
97
|
await self._execute_callbacks()
|
|
90
98
|
|
|
91
99
|
def add_device_callback(self, callback):
|
|
@@ -104,6 +112,12 @@ class Device:
|
|
|
104
112
|
|
|
105
113
|
async def _execute_callbacks(self):
|
|
106
114
|
"""Execute all callbacks."""
|
|
115
|
+
for cb in self._internal_callbacks:
|
|
116
|
+
if is_awaitable(cb):
|
|
117
|
+
await cb(device=self)
|
|
118
|
+
else:
|
|
119
|
+
cb(device=self)
|
|
120
|
+
|
|
107
121
|
for cb in self._callbacks:
|
|
108
122
|
if is_awaitable(cb):
|
|
109
123
|
await cb()
|
|
@@ -139,12 +153,8 @@ class Device:
|
|
|
139
153
|
mode
|
|
140
154
|
)
|
|
141
155
|
response = await self._api.async_update_play_timer(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
145
|
-
"playTimerRegulations"
|
|
146
|
-
],
|
|
147
|
-
}
|
|
156
|
+
self.device_id,
|
|
157
|
+
self.parental_control_settings["playTimerRegulations"],
|
|
148
158
|
)
|
|
149
159
|
now = datetime.now()
|
|
150
160
|
self._parse_parental_control_setting(
|
|
@@ -155,37 +165,68 @@ class Device:
|
|
|
155
165
|
async def set_bedtime_alarm(self, value: time):
|
|
156
166
|
"""Update the bedtime alarm for the device."""
|
|
157
167
|
_LOGGER.debug(">> Device.set_bedtime_alarm(value=%s)", value)
|
|
158
|
-
if not (
|
|
159
|
-
(16 <= value.hour <= 22)
|
|
160
|
-
or (value.hour == 23 and value.minute == 0)
|
|
161
|
-
or (value.hour == 0 and value.minute == 0)
|
|
162
|
-
):
|
|
168
|
+
if not ((16 <= value.hour <= 23) or (value.hour == 0 and value.minute == 0)):
|
|
163
169
|
raise BedtimeOutOfRangeError(value=value)
|
|
164
170
|
now = datetime.now()
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
regulation = self._get_today_regulation(now).get("bedtime", {})
|
|
172
|
+
regulation["enabled"] = 16 <= value.hour <= 23
|
|
173
|
+
|
|
174
|
+
if regulation["enabled"]:
|
|
175
|
+
_LOGGER.debug(">> Device.set_bedtime_alarm(value=%s): Enabled", value)
|
|
176
|
+
regulation = {
|
|
177
|
+
**regulation,
|
|
171
178
|
"endingTime": {"hour": value.hour, "minute": value.minute},
|
|
172
179
|
}
|
|
180
|
+
else:
|
|
181
|
+
regulation = {**regulation, "endingTime": None}
|
|
173
182
|
if self.timer_mode == DeviceTimerMode.DAILY:
|
|
183
|
+
_LOGGER.debug(
|
|
184
|
+
">> Device.set_bedtime_alarm(value=%s): Daily timer mode", value
|
|
185
|
+
)
|
|
174
186
|
self.parental_control_settings["playTimerRegulations"]["dailyRegulations"][
|
|
175
187
|
"bedtime"
|
|
176
|
-
] =
|
|
188
|
+
] = regulation
|
|
177
189
|
else:
|
|
190
|
+
_LOGGER.debug(
|
|
191
|
+
">> Device.set_bedtime_alarm(value=%s): Each day timer mode", value
|
|
192
|
+
)
|
|
178
193
|
self.parental_control_settings["playTimerRegulations"][
|
|
179
194
|
"eachDayOfTheWeekRegulations"
|
|
180
|
-
][DAYS_OF_WEEK[now.weekday()]]["bedtime"] =
|
|
195
|
+
][DAYS_OF_WEEK[now.weekday()]]["bedtime"] = regulation
|
|
196
|
+
_LOGGER.debug(
|
|
197
|
+
">> Device.set_bedtime_alarm(value=%s): Updating bedtime with object %s",
|
|
198
|
+
value,
|
|
199
|
+
regulation,
|
|
200
|
+
)
|
|
181
201
|
await self._send_api_update(
|
|
182
202
|
self._api.async_update_play_timer,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
203
|
+
self.device_id,
|
|
204
|
+
self.parental_control_settings["playTimerRegulations"],
|
|
205
|
+
now=now,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
async def set_bedtime_end_time(self, value: time):
|
|
209
|
+
"""Update the bedtime end time for the device."""
|
|
210
|
+
_LOGGER.debug(">> Device.set_bedtime_end_time(value=%s)", value)
|
|
211
|
+
if not time(5, 0) <= value <= time(9, 0):
|
|
212
|
+
raise BedtimeOutOfRangeError(value=value)
|
|
213
|
+
now = datetime.now()
|
|
214
|
+
if self.timer_mode == DeviceTimerMode.DAILY:
|
|
215
|
+
regulation = self.parental_control_settings["playTimerRegulations"][
|
|
216
|
+
"dailyRegulations"
|
|
217
|
+
]
|
|
218
|
+
else:
|
|
219
|
+
regulation = self.parental_control_settings["playTimerRegulations"][
|
|
220
|
+
"eachDayOfTheWeekRegulations"
|
|
221
|
+
][DAYS_OF_WEEK[now.weekday()]]
|
|
222
|
+
regulation["bedtime"]["startingTime"] = {
|
|
223
|
+
"hour": value.hour,
|
|
224
|
+
"minute": value.minute,
|
|
225
|
+
}
|
|
226
|
+
await self._send_api_update(
|
|
227
|
+
self._api.async_update_play_timer,
|
|
228
|
+
self.device_id,
|
|
229
|
+
self.parental_control_settings["playTimerRegulations"],
|
|
189
230
|
now=now,
|
|
190
231
|
)
|
|
191
232
|
|
|
@@ -196,12 +237,90 @@ class Device:
|
|
|
196
237
|
self.parental_control_settings["playTimerRegulations"]["timerMode"] = str(mode)
|
|
197
238
|
await self._send_api_update(
|
|
198
239
|
self._api.async_update_play_timer,
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
240
|
+
self.device_id,
|
|
241
|
+
self.parental_control_settings["playTimerRegulations"],
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
async def set_daily_restrictions(
|
|
245
|
+
self,
|
|
246
|
+
enabled: bool,
|
|
247
|
+
bedtime_enabled: bool,
|
|
248
|
+
day_of_week: str,
|
|
249
|
+
bedtime_start: time | None = None,
|
|
250
|
+
bedtime_end: time | None = None,
|
|
251
|
+
max_daily_playtime: int | float | None = None,
|
|
252
|
+
):
|
|
253
|
+
"""Updates the daily restrictions of a device."""
|
|
254
|
+
_LOGGER.debug(
|
|
255
|
+
">> Device.set_daily_restrictions(enabled=%s, bedtime_enabled=%s, day_of_week=%s, bedtime_start=%s, bedtime_end=%s, max_daily_playtime=%s)",
|
|
256
|
+
enabled,
|
|
257
|
+
bedtime_enabled,
|
|
258
|
+
day_of_week,
|
|
259
|
+
bedtime_start,
|
|
260
|
+
bedtime_end,
|
|
261
|
+
max_daily_playtime,
|
|
262
|
+
)
|
|
263
|
+
if self.timer_mode != DeviceTimerMode.EACH_DAY_OF_THE_WEEK:
|
|
264
|
+
raise InvalidDeviceStateError(
|
|
265
|
+
"Daily restrictions can only be set when timer_mode is EACH_DAY_OF_THE_WEEK."
|
|
266
|
+
)
|
|
267
|
+
if day_of_week not in DAYS_OF_WEEK:
|
|
268
|
+
raise ValueError(f"Invalid day_of_week: {day_of_week}")
|
|
269
|
+
regulation = self.parental_control_settings["playTimerRegulations"][
|
|
270
|
+
"eachDayOfTheWeekRegulations"
|
|
271
|
+
][day_of_week]
|
|
272
|
+
|
|
273
|
+
if bedtime_enabled and bedtime_start is not None and bedtime_end is not None:
|
|
274
|
+
if not time(5, 0) <= bedtime_start <= time(9, 0):
|
|
275
|
+
raise BedtimeOutOfRangeError(value=bedtime_start)
|
|
276
|
+
if not (
|
|
277
|
+
(16 <= bedtime_end.hour <= 22)
|
|
278
|
+
or (bedtime_end.hour == 23 and bedtime_end.minute == 0)
|
|
279
|
+
or (bedtime_end.hour == 0 and bedtime_end.minute == 0)
|
|
280
|
+
):
|
|
281
|
+
raise BedtimeOutOfRangeError(value=bedtime_end)
|
|
282
|
+
regulation["bedtime"] = {
|
|
283
|
+
"enabled": True,
|
|
284
|
+
"startingTime": {
|
|
285
|
+
"hour": bedtime_start.hour,
|
|
286
|
+
"minute": bedtime_start.minute,
|
|
287
|
+
},
|
|
288
|
+
"endingTime": {"hour": bedtime_end.hour, "minute": bedtime_end.minute},
|
|
289
|
+
}
|
|
290
|
+
elif bedtime_enabled:
|
|
291
|
+
raise BedtimeOutOfRangeError(value=None)
|
|
292
|
+
else:
|
|
293
|
+
# Even when disabled, the API seems to expect a starting time.
|
|
294
|
+
regulation["bedtime"] = {
|
|
295
|
+
"enabled": False,
|
|
296
|
+
"startingTime": None,
|
|
297
|
+
"endingTime": None,
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
regulation["timeToPlayInOneDay"] = {"enabled": enabled}
|
|
301
|
+
if enabled and max_daily_playtime is not None:
|
|
302
|
+
if isinstance(max_daily_playtime, float):
|
|
303
|
+
max_daily_playtime = int(max_daily_playtime)
|
|
304
|
+
if not 0 <= max_daily_playtime <= 360:
|
|
305
|
+
raise DailyPlaytimeOutOfRangeError(max_daily_playtime)
|
|
306
|
+
regulation["timeToPlayInOneDay"]["limitTime"] = max_daily_playtime
|
|
307
|
+
else:
|
|
308
|
+
regulation["timeToPlayInOneDay"]["limitTime"] = None
|
|
309
|
+
|
|
310
|
+
await self._send_api_update(
|
|
311
|
+
self._api.async_update_play_timer,
|
|
312
|
+
self.device_id,
|
|
313
|
+
self.parental_control_settings["playTimerRegulations"],
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
async def set_functional_restriction_level(self, level: FunctionalRestrictionLevel):
|
|
317
|
+
"""Updates the functional restriction level of a device."""
|
|
318
|
+
_LOGGER.debug(">> Device.set_functional_restriction_level(level=%s)", level)
|
|
319
|
+
self.parental_control_settings["functionalRestrictionLevel"] = str(level)
|
|
320
|
+
await self._send_api_update(
|
|
321
|
+
self._api.async_update_restriction_level,
|
|
322
|
+
self.device_id,
|
|
323
|
+
self.parental_control_settings,
|
|
205
324
|
)
|
|
206
325
|
|
|
207
326
|
async def update_max_daily_playtime(self, minutes: int | float = 0):
|
|
@@ -262,27 +381,25 @@ class Device:
|
|
|
262
381
|
|
|
263
382
|
await self._send_api_update(
|
|
264
383
|
self._api.async_update_play_timer,
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
"playTimerRegulations": self.parental_control_settings[
|
|
268
|
-
"playTimerRegulations"
|
|
269
|
-
],
|
|
270
|
-
},
|
|
384
|
+
self.device_id,
|
|
385
|
+
self.parental_control_settings["playTimerRegulations"],
|
|
271
386
|
now=now,
|
|
272
387
|
)
|
|
273
388
|
|
|
274
389
|
def _update_applications(self):
|
|
275
|
-
"""Updates applications from
|
|
390
|
+
"""Updates applications from whitelisted applications list."""
|
|
276
391
|
_LOGGER.debug(">> Device._update_applications()")
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
self.
|
|
392
|
+
for app in self.parental_control_settings.get("whitelistedApplicationList", []):
|
|
393
|
+
if app["applicationId"] in self.applications:
|
|
394
|
+
continue
|
|
395
|
+
self.applications[app["applicationId"]] = Application(
|
|
396
|
+
app_id=app["applicationId"],
|
|
397
|
+
name=app["title"],
|
|
398
|
+
device_id=self.device_id,
|
|
399
|
+
api=self._api,
|
|
400
|
+
send_api_update=self._send_api_update,
|
|
401
|
+
callbacks=self._internal_callbacks,
|
|
402
|
+
)
|
|
286
403
|
|
|
287
404
|
def _get_today_regulation(self, now: datetime) -> dict:
|
|
288
405
|
"""Returns the regulation settings for the current day."""
|
|
@@ -336,15 +453,20 @@ class Device:
|
|
|
336
453
|
)
|
|
337
454
|
|
|
338
455
|
bedtime_setting = today_reg.get("bedtime", {})
|
|
339
|
-
if bedtime_setting.get("enabled"):
|
|
456
|
+
if bedtime_setting.get("enabled") and bedtime_setting["endingTime"]:
|
|
340
457
|
self.bedtime_alarm = time(
|
|
341
458
|
hour=bedtime_setting["endingTime"]["hour"],
|
|
342
459
|
minute=bedtime_setting["endingTime"]["minute"],
|
|
343
460
|
)
|
|
344
461
|
else:
|
|
345
462
|
self.bedtime_alarm = time(hour=0, minute=0)
|
|
346
|
-
|
|
347
|
-
|
|
463
|
+
if bedtime_setting.get("enabled") and bedtime_setting["startingTime"]:
|
|
464
|
+
self.bedtime_end = time(
|
|
465
|
+
hour=bedtime_setting["startingTime"]["hour"],
|
|
466
|
+
minute=bedtime_setting["startingTime"]["minute"],
|
|
467
|
+
)
|
|
468
|
+
else:
|
|
469
|
+
self.bedtime_end = time(hour=0, minute=0)
|
|
348
470
|
|
|
349
471
|
def _calculate_times(self, now: datetime):
|
|
350
472
|
"""Calculate times from parental control settings."""
|
|
@@ -376,37 +498,6 @@ class Device:
|
|
|
376
498
|
month_playing_time += summary["playingTime"]
|
|
377
499
|
self.month_playing_time = month_playing_time
|
|
378
500
|
_LOGGER.debug("Cached current month playing time for device %s", self.device_id)
|
|
379
|
-
parsed_apps = Application.from_daily_summary(self.daily_summaries)
|
|
380
|
-
for app in parsed_apps:
|
|
381
|
-
try:
|
|
382
|
-
int_app = self.get_application(app.application_id)
|
|
383
|
-
_LOGGER.debug(
|
|
384
|
-
"Updating cached app state %s for device %s",
|
|
385
|
-
int_app.application_id,
|
|
386
|
-
self.device_id,
|
|
387
|
-
)
|
|
388
|
-
int_app.update(app)
|
|
389
|
-
except ValueError:
|
|
390
|
-
_LOGGER.debug(
|
|
391
|
-
"Creating new cached application entry %s for device %s",
|
|
392
|
-
app.application_id,
|
|
393
|
-
self.device_id,
|
|
394
|
-
)
|
|
395
|
-
self.applications.append(app)
|
|
396
|
-
|
|
397
|
-
# update application playtime
|
|
398
|
-
try:
|
|
399
|
-
for player in self.get_date_summary()[0].get("devicePlayers", []):
|
|
400
|
-
for app in player.get("playedApps", []):
|
|
401
|
-
self.get_application(app["applicationId"]).update_today_time_played(
|
|
402
|
-
app
|
|
403
|
-
)
|
|
404
|
-
self.application_update_failed = False
|
|
405
|
-
except ValueError as err:
|
|
406
|
-
_LOGGER.debug(
|
|
407
|
-
"Unable to retrieve applications for device %s: %s", self.name, err
|
|
408
|
-
)
|
|
409
|
-
self.application_update_failed = True
|
|
410
501
|
|
|
411
502
|
def _calculate_today_remaining_time(self, now: datetime):
|
|
412
503
|
"""Calculates the remaining playing time for today."""
|
|
@@ -541,7 +632,9 @@ class Device:
|
|
|
541
632
|
if latest:
|
|
542
633
|
self.last_month_summary = summary = response["json"]["summary"]
|
|
543
634
|
# Generate player objects
|
|
544
|
-
for player in
|
|
635
|
+
for player in (
|
|
636
|
+
response.get("json", {}).get("summary", {}).get("players", [])
|
|
637
|
+
):
|
|
545
638
|
profile = player.get("profile")
|
|
546
639
|
if not profile or not profile.get("playerId"):
|
|
547
640
|
continue
|
|
@@ -576,12 +669,8 @@ class Device:
|
|
|
576
669
|
|
|
577
670
|
def get_application(self, application_id: str) -> Application:
|
|
578
671
|
"""Returns a single application."""
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
None,
|
|
582
|
-
)
|
|
583
|
-
if app:
|
|
584
|
-
return app
|
|
672
|
+
if application_id in self.applications:
|
|
673
|
+
return self.applications[application_id]
|
|
585
674
|
raise ValueError(f"Application with id {application_id} not found.")
|
|
586
675
|
|
|
587
676
|
def get_player(self, player_id: str) -> Player:
|