pynintendoparental 0.6.9__tar.gz → 0.7.1__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-0.6.9 → pynintendoparental-0.7.1}/PKG-INFO +1 -1
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/__init__.py +1 -4
- pynintendoparental-0.7.1/pynintendoparental/_version.py +1 -0
- pynintendoparental-0.7.1/pynintendoparental/api.py +205 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/authenticator/__init__.py +35 -21
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/device.py +29 -31
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental.egg-info/PKG-INFO +1 -1
- pynintendoparental-0.6.9/pynintendoparental/_version.py +0 -1
- pynintendoparental-0.6.9/pynintendoparental/api.py +0 -96
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/LICENSE +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/README.md +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/application.py +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/authenticator/const.py +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/const.py +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/enum.py +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/exceptions.py +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/player.py +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/py.typed +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/utils.py +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental.egg-info/SOURCES.txt +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental.egg-info/dependency_links.txt +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental.egg-info/requires.txt +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental.egg-info/top_level.txt +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pyproject.toml +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/setup.cfg +0 -0
- {pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/setup.py +0 -0
|
@@ -30,10 +30,7 @@ class NintendoParental:
|
|
|
30
30
|
dev.device_id,
|
|
31
31
|
err)
|
|
32
32
|
|
|
33
|
-
response = await self._api.
|
|
34
|
-
endpoint="get_account_devices",
|
|
35
|
-
ACCOUNT_ID=self.account_id
|
|
36
|
-
)
|
|
33
|
+
response = await self._api.async_get_account_devices()
|
|
37
34
|
|
|
38
35
|
for dev_raw in response["json"]["items"]:
|
|
39
36
|
device: Device = Device.from_device_response(dev_raw, self._api)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.1"
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""API handler."""
|
|
2
|
+
|
|
3
|
+
import aiohttp
|
|
4
|
+
|
|
5
|
+
from .authenticator import Authenticator
|
|
6
|
+
from .const import (
|
|
7
|
+
ENDPOINTS,
|
|
8
|
+
BASE_URL,
|
|
9
|
+
USER_AGENT,
|
|
10
|
+
MOBILE_APP_PKG,
|
|
11
|
+
MOBILE_APP_BUILD,
|
|
12
|
+
MOBILE_APP_VERSION,
|
|
13
|
+
OS_VERSION,
|
|
14
|
+
OS_NAME,
|
|
15
|
+
DEVICE_MODEL,
|
|
16
|
+
_LOGGER
|
|
17
|
+
)
|
|
18
|
+
from .exceptions import HttpException
|
|
19
|
+
|
|
20
|
+
def _check_http_success(status: int) -> bool:
|
|
21
|
+
return status >= 200 and status < 300
|
|
22
|
+
|
|
23
|
+
class Api:
|
|
24
|
+
"""Nintendo Parental Controls API."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, auth, tz, lang):
|
|
27
|
+
"""INIT"""
|
|
28
|
+
self._auth: Authenticator = auth
|
|
29
|
+
self._tz = tz
|
|
30
|
+
self._language = lang
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def account_id(self):
|
|
34
|
+
"""Return the account id."""
|
|
35
|
+
return self._auth.account_id
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def _headers(self) -> dict:
|
|
39
|
+
"""Return web request headers."""
|
|
40
|
+
return {
|
|
41
|
+
"User-Agent": USER_AGENT,
|
|
42
|
+
"X-Moon-App-Id": MOBILE_APP_PKG,
|
|
43
|
+
"X-Moon-Os": OS_NAME,
|
|
44
|
+
"X-Moon-Os-Version": OS_VERSION,
|
|
45
|
+
"X-Moon-Model": DEVICE_MODEL,
|
|
46
|
+
"X-Moon-App-Display-Version": MOBILE_APP_VERSION,
|
|
47
|
+
"X-Moon-App-Internal-Version": MOBILE_APP_BUILD,
|
|
48
|
+
"X-Moon-TimeZone": self._tz,
|
|
49
|
+
"X-Moon-Os-Language": self._language,
|
|
50
|
+
"X-Moon-App-Language": self._language,
|
|
51
|
+
"Authorization": self._auth.access_token
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async def send_request(self, endpoint: str, body: object=None, **kwargs):
|
|
55
|
+
"""Sends a request to a given endpoint."""
|
|
56
|
+
_LOGGER.debug("Sending request to %s", endpoint)
|
|
57
|
+
# Get the endpoint from the endpoints map
|
|
58
|
+
e_point = ENDPOINTS.get(endpoint, None)
|
|
59
|
+
if e_point is None:
|
|
60
|
+
raise ValueError("Endpoint does not exist")
|
|
61
|
+
# refresh the token if it has expired.
|
|
62
|
+
if self._auth.access_token_expired:
|
|
63
|
+
_LOGGER.debug("Access token expired, requesting refresh.")
|
|
64
|
+
await self._auth.perform_refresh()
|
|
65
|
+
# format the URL using the kwargs
|
|
66
|
+
url = e_point.get("url").format(BASE_URL=BASE_URL, **kwargs)
|
|
67
|
+
_LOGGER.debug("Built URL %s", url)
|
|
68
|
+
# now send the HTTP request
|
|
69
|
+
resp: dict = {
|
|
70
|
+
"status": 0,
|
|
71
|
+
"text": "",
|
|
72
|
+
"json": "",
|
|
73
|
+
"headers": ""
|
|
74
|
+
}
|
|
75
|
+
async with self._auth.client_session.request(
|
|
76
|
+
method=e_point.get("method"),
|
|
77
|
+
url=url,
|
|
78
|
+
json=body,
|
|
79
|
+
headers=self._headers
|
|
80
|
+
) as response:
|
|
81
|
+
_LOGGER.debug("%s request to %s status code %s",
|
|
82
|
+
e_point.get("method"),
|
|
83
|
+
url,
|
|
84
|
+
response.status)
|
|
85
|
+
if _check_http_success(response.status):
|
|
86
|
+
resp["status"] = response.status
|
|
87
|
+
resp["text"] = await response.text()
|
|
88
|
+
try:
|
|
89
|
+
resp["json"] = await response.json()
|
|
90
|
+
except (aiohttp.ContentTypeError, ValueError) as e:
|
|
91
|
+
_LOGGER.warning(
|
|
92
|
+
"""Failed to decode JSON response from %s.
|
|
93
|
+
Status: %s, Error: %s.
|
|
94
|
+
Response text: %s...""",
|
|
95
|
+
url, response.status, e, resp['text'][:200]
|
|
96
|
+
)
|
|
97
|
+
resp["json"] = {}
|
|
98
|
+
resp["headers"] = response.headers
|
|
99
|
+
else:
|
|
100
|
+
raise HttpException("HTTP Error", response.status, await response.text())
|
|
101
|
+
|
|
102
|
+
# now return the resp dict
|
|
103
|
+
return resp
|
|
104
|
+
|
|
105
|
+
async def async_get_account_details(self) -> dict:
|
|
106
|
+
"""Get account details."""
|
|
107
|
+
return await self.send_request(
|
|
108
|
+
endpoint="get_account_details",
|
|
109
|
+
ACCOUNT_ID=self.account_id
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def async_get_account_devices(self) -> dict:
|
|
113
|
+
"""Get account devices."""
|
|
114
|
+
return await self.send_request(
|
|
115
|
+
endpoint="get_account_devices",
|
|
116
|
+
ACCOUNT_ID=self.account_id
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
async def async_get_account_device(self, device_id: str) -> dict:
|
|
120
|
+
"""Get account device."""
|
|
121
|
+
return await self.send_request(
|
|
122
|
+
endpoint="get_account_device",
|
|
123
|
+
ACCOUNT_ID=self.account_id,
|
|
124
|
+
DEVICE_ID=device_id
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
async def async_get_device_daily_summaries(self, device_id: str) -> dict:
|
|
128
|
+
"""Get device daily summaries."""
|
|
129
|
+
return await self.send_request(
|
|
130
|
+
endpoint="get_device_daily_summaries",
|
|
131
|
+
DEVICE_ID=device_id
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def async_get_device_monthly_summaries(self, device_id: str) -> dict:
|
|
135
|
+
"""Get device monthly summaries."""
|
|
136
|
+
return await self.send_request(
|
|
137
|
+
endpoint="get_device_monthly_summaries",
|
|
138
|
+
DEVICE_ID=device_id
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
async def async_get_device_parental_control_setting(self, device_id: str) -> dict:
|
|
142
|
+
"""Get device parental control setting."""
|
|
143
|
+
return await self.send_request(
|
|
144
|
+
endpoint="get_device_parental_control_setting",
|
|
145
|
+
DEVICE_ID=device_id
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
async def async_get_device_parental_control_setting_state(self, device_id: str) -> dict:
|
|
149
|
+
"""Get device parental control setting state."""
|
|
150
|
+
return await self.send_request(
|
|
151
|
+
endpoint="get_device_parental_control_setting_state",
|
|
152
|
+
DEVICE_ID=device_id
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
async def async_get_device_alarm_setting_state(self, device_id: str) -> dict:
|
|
156
|
+
"""Get device alarm setting state."""
|
|
157
|
+
return await self.send_request(
|
|
158
|
+
endpoint="get_device_alarm_setting_state",
|
|
159
|
+
DEVICE_ID=device_id
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
async def async_get_device_monthly_summary(self, device_id: str, year: int, month: int) -> dict:
|
|
163
|
+
"""Get device monthly summary."""
|
|
164
|
+
return await self.send_request(
|
|
165
|
+
endpoint="get_device_monthly_summary",
|
|
166
|
+
DEVICE_ID=device_id,
|
|
167
|
+
YEAR=year,
|
|
168
|
+
MONTH=f"{month:02d}"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
async def async_set_device_parental_control_setting(
|
|
172
|
+
self,
|
|
173
|
+
device_id: str,
|
|
174
|
+
settings: dict
|
|
175
|
+
) -> dict:
|
|
176
|
+
"""Update device parental control setting."""
|
|
177
|
+
return await self.send_request(
|
|
178
|
+
endpoint="update_device_parental_control_setting",
|
|
179
|
+
DEVICE_ID=device_id,
|
|
180
|
+
body=settings
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
async def async_set_device_whitelisted_applications(
|
|
184
|
+
self,
|
|
185
|
+
device_id: str,
|
|
186
|
+
applications: dict
|
|
187
|
+
) -> dict:
|
|
188
|
+
"""Update device whitelisted applications."""
|
|
189
|
+
return await self.send_request(
|
|
190
|
+
endpoint="update_device_whitelisted_applications",
|
|
191
|
+
DEVICE_ID=device_id,
|
|
192
|
+
body=applications
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
async def async_set_device_alarm_setting_state(
|
|
196
|
+
self,
|
|
197
|
+
device_id: str,
|
|
198
|
+
alarm_state: dict
|
|
199
|
+
) -> dict:
|
|
200
|
+
"""Update device alarm setting state."""
|
|
201
|
+
return await self.send_request(
|
|
202
|
+
endpoint="update_device_alarm_setting_state",
|
|
203
|
+
DEVICE_ID=device_id,
|
|
204
|
+
body=alarm_state
|
|
205
|
+
)
|
{pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/authenticator/__init__.py
RENAMED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
|
-
import re
|
|
6
5
|
import base64
|
|
7
6
|
import hashlib
|
|
8
7
|
import random
|
|
@@ -60,11 +59,16 @@ def _rand():
|
|
|
60
59
|
class Authenticator:
|
|
61
60
|
"""Authentication functions."""
|
|
62
61
|
|
|
63
|
-
def __init__(
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
session_token = None,
|
|
65
|
+
auth_code_verifier = None,
|
|
66
|
+
session: aiohttp.ClientSession = None
|
|
67
|
+
):
|
|
64
68
|
"""Basic init."""
|
|
65
69
|
_LOGGER.debug(">> Init authenticator.")
|
|
66
|
-
self.
|
|
67
|
-
self.
|
|
70
|
+
self._at_expiry: datetime = None
|
|
71
|
+
self._access_token: str = None
|
|
68
72
|
self.available_scopes: dict = None
|
|
69
73
|
self.account_id: str = None
|
|
70
74
|
self.account: dict = None
|
|
@@ -73,12 +77,25 @@ class Authenticator:
|
|
|
73
77
|
self._id_token: str = None
|
|
74
78
|
self._session_token: str = session_token
|
|
75
79
|
self.login_url: str = None
|
|
80
|
+
if session is None:
|
|
81
|
+
session = aiohttp.ClientSession()
|
|
82
|
+
self.client_session: aiohttp.ClientSession = session
|
|
76
83
|
|
|
77
84
|
@property
|
|
78
85
|
def get_session_token(self) -> str:
|
|
79
86
|
"""Return the session token."""
|
|
80
87
|
return self._session_token
|
|
81
88
|
|
|
89
|
+
@property
|
|
90
|
+
def access_token(self) -> str:
|
|
91
|
+
"""Return the formatted access token."""
|
|
92
|
+
return f"Bearer {self._access_token}"
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def access_token_expired(self) -> bool:
|
|
96
|
+
"""Check if the access token has expired."""
|
|
97
|
+
return self._at_expiry < (datetime.now()+timedelta(minutes=1))
|
|
98
|
+
|
|
82
99
|
async def _request_handler(self, method, url, json=None, data=None, headers: dict=None):
|
|
83
100
|
"""Send a HTTP request"""
|
|
84
101
|
if headers is None:
|
|
@@ -89,28 +106,25 @@ class Authenticator:
|
|
|
89
106
|
"json": "",
|
|
90
107
|
"headers": ""
|
|
91
108
|
}
|
|
92
|
-
async with
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
response["text"] = await resp.text()
|
|
104
|
-
response["json"] = await resp.json()
|
|
105
|
-
response["headers"] = resp.headers
|
|
109
|
+
async with self.client_session.request(
|
|
110
|
+
method=method,
|
|
111
|
+
url=url,
|
|
112
|
+
json=json,
|
|
113
|
+
data=data,
|
|
114
|
+
headers=headers
|
|
115
|
+
) as resp:
|
|
116
|
+
response["status"] = resp.status
|
|
117
|
+
response["text"] = await resp.text()
|
|
118
|
+
response["json"] = await resp.json()
|
|
119
|
+
response["headers"] = resp.headers
|
|
106
120
|
return response
|
|
107
121
|
|
|
108
122
|
def _read_tokens(self, tokens: dict):
|
|
109
123
|
"""Reads tokens into self."""
|
|
110
124
|
self.available_scopes = tokens.get("scope")
|
|
111
|
-
self.
|
|
125
|
+
self._at_expiry = datetime.now() + timedelta(seconds=tokens.get("expires_in"))
|
|
112
126
|
self._id_token = tokens.get("id_token")
|
|
113
|
-
self.
|
|
127
|
+
self._access_token = tokens.get("access_token")
|
|
114
128
|
|
|
115
129
|
async def perform_login(self, session_token_code):
|
|
116
130
|
"""Retrieves initial tokens."""
|
|
@@ -159,7 +173,7 @@ class Authenticator:
|
|
|
159
173
|
method="GET",
|
|
160
174
|
url=MY_ACCOUNT_ENDPOINT,
|
|
161
175
|
headers={
|
|
162
|
-
"Authorization":
|
|
176
|
+
"Authorization": self.access_token
|
|
163
177
|
}
|
|
164
178
|
)
|
|
165
179
|
if account["status"] != 200:
|
|
@@ -76,6 +76,13 @@ class Device:
|
|
|
76
76
|
if callback not in self._callbacks:
|
|
77
77
|
self._callbacks.append(callback)
|
|
78
78
|
|
|
79
|
+
def remove_device_callback(self, callback):
|
|
80
|
+
"""Remove a given device callback."""
|
|
81
|
+
if not callable(callback):
|
|
82
|
+
raise ValueError("Object must be callable.")
|
|
83
|
+
if callback in self._callbacks:
|
|
84
|
+
self._callbacks.remove(callback)
|
|
85
|
+
|
|
79
86
|
async def set_new_pin(self, pin: str):
|
|
80
87
|
"""Updates the pin for the device."""
|
|
81
88
|
_LOGGER.debug(">> Device.set_new_pin(pin=REDACTED)")
|
|
@@ -115,10 +122,9 @@ class Device:
|
|
|
115
122
|
async def _set_parental_control_setting(self):
|
|
116
123
|
"""Shortcut method to deduplicate code used to update parental control settings."""
|
|
117
124
|
_LOGGER.debug(">> Device._set_parental_control_setting()")
|
|
118
|
-
await self._api.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
DEVICE_ID=self.device_id
|
|
125
|
+
await self._api.async_set_device_parental_control_setting(
|
|
126
|
+
settings=self._get_update_parental_control_setting_body(),
|
|
127
|
+
device_id=self.device_id
|
|
122
128
|
)
|
|
123
129
|
await self._get_parental_control_setting()
|
|
124
130
|
|
|
@@ -209,9 +215,8 @@ class Device:
|
|
|
209
215
|
async def _get_parental_control_setting(self):
|
|
210
216
|
"""Retreives parental control settings from the API."""
|
|
211
217
|
_LOGGER.debug(">> Device._get_parental_control_setting()")
|
|
212
|
-
response = await self._api.
|
|
213
|
-
|
|
214
|
-
DEVICE_ID=self.device_id
|
|
218
|
+
response = await self._api.async_get_device_parental_control_setting(
|
|
219
|
+
device_id=self.device_id
|
|
215
220
|
)
|
|
216
221
|
self.parental_control_settings = response["json"]
|
|
217
222
|
if "bedtimeStartingTime" in self.parental_control_settings["playTimerRegulations"]:
|
|
@@ -235,9 +240,8 @@ class Device:
|
|
|
235
240
|
async def _get_daily_summaries(self):
|
|
236
241
|
"""Retrieve daily summaries."""
|
|
237
242
|
_LOGGER.debug(">> Device._get_daily_summaries()")
|
|
238
|
-
response = await self._api.
|
|
239
|
-
|
|
240
|
-
DEVICE_ID = self.device_id
|
|
243
|
+
response = await self._api.async_get_device_daily_summaries(
|
|
244
|
+
device_id = self.device_id
|
|
241
245
|
)
|
|
242
246
|
self.daily_summaries = response["json"]["items"]
|
|
243
247
|
_LOGGER.debug("New daily summary %s", self.daily_summaries)
|
|
@@ -276,7 +280,7 @@ class Device:
|
|
|
276
280
|
time_remaining_by_bedtime = 0.0
|
|
277
281
|
if bedtime_dt > now: # Bedtime is in the future today
|
|
278
282
|
time_remaining_by_bedtime = (bedtime_dt - now).total_seconds() / 60
|
|
279
|
-
time_remaining_by_bedtime = max(0.0, time_remaining_by_bedtime)
|
|
283
|
+
time_remaining_by_bedtime = max(0.0, time_remaining_by_bedtime)
|
|
280
284
|
# else: Bedtime has passed for today or is now, so time_remaining_by_bedtime remains 0.0
|
|
281
285
|
|
|
282
286
|
effective_remaining_time = min(effective_remaining_time, time_remaining_by_bedtime)
|
|
@@ -334,10 +338,8 @@ class Device:
|
|
|
334
338
|
_LOGGER.debug(">> Device._get_extras()")
|
|
335
339
|
if self.alarms_enabled is not None:
|
|
336
340
|
# first refresh can come from self.extra without http request
|
|
337
|
-
response = await self._api.
|
|
338
|
-
|
|
339
|
-
ACCOUNT_ID = self._api.account_id,
|
|
340
|
-
DEVICE_ID = self.device_id
|
|
341
|
+
response = await self._api.async_get_account_device(
|
|
342
|
+
device_id = self.device_id
|
|
341
343
|
)
|
|
342
344
|
self.extra = response["json"]
|
|
343
345
|
status = self.extra["device"]["alarmSetting"]["visibility"]
|
|
@@ -351,9 +353,8 @@ class Device:
|
|
|
351
353
|
_LOGGER.debug(">> Device.get_monthly_summary(search_date=%s)", search_date)
|
|
352
354
|
latest = False
|
|
353
355
|
if search_date is None:
|
|
354
|
-
response = await self._api.
|
|
355
|
-
|
|
356
|
-
DEVICE_ID=self.device_id
|
|
356
|
+
response = await self._api.async_get_device_monthly_summaries(
|
|
357
|
+
device_id=self.device_id
|
|
357
358
|
)
|
|
358
359
|
_LOGGER.debug("Available monthly summaries: %s", response)
|
|
359
360
|
response = response["json"]["indexes"][0]
|
|
@@ -362,11 +363,10 @@ class Device:
|
|
|
362
363
|
latest = True
|
|
363
364
|
|
|
364
365
|
try:
|
|
365
|
-
response = await self._api.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
MONTH=str(search_date.month).zfill(2)
|
|
366
|
+
response = await self._api.async_get_device_monthly_summary(
|
|
367
|
+
device_id = self.device_id,
|
|
368
|
+
year=search_date.year,
|
|
369
|
+
month=search_date.month
|
|
370
370
|
)
|
|
371
371
|
_LOGGER.debug("Monthly summary query complete for device %s: %s",
|
|
372
372
|
self.device_id,
|
|
@@ -385,12 +385,11 @@ class Device:
|
|
|
385
385
|
"""Updates the alarm state for the device."""
|
|
386
386
|
_LOGGER.debug(">> Device.set_alarm_state(state=%s)",
|
|
387
387
|
state)
|
|
388
|
-
await self._api.
|
|
389
|
-
|
|
390
|
-
body={
|
|
388
|
+
await self._api.async_set_device_alarm_setting_state(
|
|
389
|
+
alarm_state={
|
|
391
390
|
"status": str(state)
|
|
392
391
|
},
|
|
393
|
-
|
|
392
|
+
device_id = self.device_id
|
|
394
393
|
)
|
|
395
394
|
|
|
396
395
|
async def set_whitelisted_application(self, app_id: str, allowed: bool):
|
|
@@ -403,10 +402,9 @@ class Device:
|
|
|
403
402
|
# take a snapshot of the whitelisted apps state
|
|
404
403
|
current_state = self.parental_control_settings["whitelistedApplications"]
|
|
405
404
|
current_state[app_id]["safeLaunch"] = "ALLOW" if allowed else "NONE"
|
|
406
|
-
await self._api.
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
DEVICE_ID=self.device_id
|
|
405
|
+
await self._api.async_set_device_whitelisted_applications(
|
|
406
|
+
applications=current_state,
|
|
407
|
+
device_id=self.device_id
|
|
410
408
|
)
|
|
411
409
|
await self._get_parental_control_setting()
|
|
412
410
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.9"
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
"""API handler."""
|
|
2
|
-
|
|
3
|
-
from datetime import datetime, timedelta
|
|
4
|
-
|
|
5
|
-
import aiohttp
|
|
6
|
-
|
|
7
|
-
from .authenticator import Authenticator
|
|
8
|
-
from .const import (
|
|
9
|
-
ENDPOINTS,
|
|
10
|
-
BASE_URL,
|
|
11
|
-
USER_AGENT,
|
|
12
|
-
MOBILE_APP_PKG,
|
|
13
|
-
MOBILE_APP_BUILD,
|
|
14
|
-
MOBILE_APP_VERSION,
|
|
15
|
-
OS_VERSION,
|
|
16
|
-
OS_NAME,
|
|
17
|
-
DEVICE_MODEL,
|
|
18
|
-
_LOGGER
|
|
19
|
-
)
|
|
20
|
-
from .exceptions import HttpException
|
|
21
|
-
|
|
22
|
-
def _check_http_success(status: int) -> bool:
|
|
23
|
-
return status >= 200 and status < 300
|
|
24
|
-
|
|
25
|
-
class Api:
|
|
26
|
-
"""Nintendo Parental Controls API."""
|
|
27
|
-
|
|
28
|
-
def __init__(self, auth, tz, lang):
|
|
29
|
-
"""INIT"""
|
|
30
|
-
self._auth: Authenticator = auth
|
|
31
|
-
self._tz = tz
|
|
32
|
-
self._language = lang
|
|
33
|
-
|
|
34
|
-
@property
|
|
35
|
-
def _auth_token(self) -> str:
|
|
36
|
-
"""Returns the auth token."""
|
|
37
|
-
return f"Bearer {self._auth.access_token}"
|
|
38
|
-
|
|
39
|
-
@property
|
|
40
|
-
def account_id(self):
|
|
41
|
-
"""Return the account id."""
|
|
42
|
-
return self._auth.account_id
|
|
43
|
-
|
|
44
|
-
async def send_request(self, endpoint: str, body: object=None, **kwargs):
|
|
45
|
-
"""Sends a request to a given endpoint."""
|
|
46
|
-
_LOGGER.debug("Sending request to %s", endpoint)
|
|
47
|
-
# Get the endpoint from the endpoints map
|
|
48
|
-
e_point = ENDPOINTS.get(endpoint, None)
|
|
49
|
-
if e_point is None:
|
|
50
|
-
raise ValueError("Endpoint does not exist")
|
|
51
|
-
# refresh the token if it has expired.
|
|
52
|
-
if self._auth.expires < (datetime.now()+timedelta(seconds=30)):
|
|
53
|
-
_LOGGER.debug("Access token expired, requesting refresh.")
|
|
54
|
-
await self._auth.perform_refresh()
|
|
55
|
-
# format the URL using the kwargs
|
|
56
|
-
url = e_point.get("url").format(BASE_URL=BASE_URL, **kwargs)
|
|
57
|
-
_LOGGER.debug("Built URL %s", url)
|
|
58
|
-
# now send the HTTP request
|
|
59
|
-
resp: dict = {
|
|
60
|
-
"status": 0,
|
|
61
|
-
"text": "",
|
|
62
|
-
"json": "",
|
|
63
|
-
"headers": ""
|
|
64
|
-
}
|
|
65
|
-
async with aiohttp.ClientSession() as session:
|
|
66
|
-
# Add auth header
|
|
67
|
-
session.headers.add("Authorization", self._auth_token)
|
|
68
|
-
session.headers.add("User-Agent", USER_AGENT)
|
|
69
|
-
session.headers.add("X-Moon-App-Id", MOBILE_APP_PKG)
|
|
70
|
-
session.headers.add("X-Moon-Os", OS_NAME)
|
|
71
|
-
session.headers.add("X-Moon-Os-Version", OS_VERSION)
|
|
72
|
-
session.headers.add("X-Moon-Model", DEVICE_MODEL)
|
|
73
|
-
session.headers.add("X-Moon-TimeZone", self._tz)
|
|
74
|
-
session.headers.add("X-Moon-Os-Language", self._language)
|
|
75
|
-
session.headers.add("X-Moon-App-Language", self._language)
|
|
76
|
-
session.headers.add("X-Moon-App-Display-Version", MOBILE_APP_VERSION)
|
|
77
|
-
session.headers.add("X-Moon-App-Internal-Version", MOBILE_APP_BUILD)
|
|
78
|
-
async with session.request(
|
|
79
|
-
method=e_point.get("method"),
|
|
80
|
-
url=url,
|
|
81
|
-
json=body
|
|
82
|
-
) as response:
|
|
83
|
-
_LOGGER.debug("%s request to %s status code %s",
|
|
84
|
-
e_point.get("method"),
|
|
85
|
-
url,
|
|
86
|
-
response.status)
|
|
87
|
-
if _check_http_success(response.status):
|
|
88
|
-
resp["status"] = response.status
|
|
89
|
-
resp["text"] = await response.text()
|
|
90
|
-
resp["json"] = await response.json()
|
|
91
|
-
resp["headers"] = response.headers
|
|
92
|
-
else:
|
|
93
|
-
raise HttpException("HTTP Error", response.status, await response.text())
|
|
94
|
-
|
|
95
|
-
# now return the resp dict
|
|
96
|
-
return resp
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental/authenticator/const.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental.egg-info/requires.txt
RENAMED
|
File without changes
|
{pynintendoparental-0.6.9 → pynintendoparental-0.7.1}/pynintendoparental.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|