pynintendoparental 0.6.9__tar.gz → 0.7.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.
Files changed (26) hide show
  1. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/PKG-INFO +1 -1
  2. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/__init__.py +1 -4
  3. pynintendoparental-0.7.0/pynintendoparental/_version.py +1 -0
  4. pynintendoparental-0.7.0/pynintendoparental/api.py +222 -0
  5. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/device.py +21 -30
  6. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental.egg-info/PKG-INFO +1 -1
  7. pynintendoparental-0.6.9/pynintendoparental/_version.py +0 -1
  8. pynintendoparental-0.6.9/pynintendoparental/api.py +0 -96
  9. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/LICENSE +0 -0
  10. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/README.md +0 -0
  11. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/application.py +0 -0
  12. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/authenticator/__init__.py +0 -0
  13. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/authenticator/const.py +0 -0
  14. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/const.py +0 -0
  15. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/enum.py +0 -0
  16. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/exceptions.py +0 -0
  17. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/player.py +0 -0
  18. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/py.typed +0 -0
  19. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental/utils.py +0 -0
  20. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental.egg-info/SOURCES.txt +0 -0
  21. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental.egg-info/dependency_links.txt +0 -0
  22. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental.egg-info/requires.txt +0 -0
  23. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pynintendoparental.egg-info/top_level.txt +0 -0
  24. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/pyproject.toml +0 -0
  25. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/setup.cfg +0 -0
  26. {pynintendoparental-0.6.9 → pynintendoparental-0.7.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pynintendoparental
3
- Version: 0.6.9
3
+ Version: 0.7.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
@@ -30,10 +30,7 @@ class NintendoParental:
30
30
  dev.device_id,
31
31
  err)
32
32
 
33
- response = await self._api.send_request(
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.0"
@@ -0,0 +1,222 @@
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, session: aiohttp.ClientSession=None):
29
+ """INIT"""
30
+ self._auth: Authenticator = auth
31
+ self._tz = tz
32
+ self._language = lang
33
+ if session is None:
34
+ session = aiohttp.ClientSession()
35
+ self._session_created_internally = True
36
+ self._session = session
37
+
38
+ @property
39
+ def _auth_token(self) -> str:
40
+ """Returns the auth token."""
41
+ return f"Bearer {self._auth.access_token}"
42
+
43
+ @property
44
+ def account_id(self):
45
+ """Return the account id."""
46
+ return self._auth.account_id
47
+
48
+ @property
49
+ def _headers(self) -> dict:
50
+ """Return web request headers."""
51
+ return {
52
+ "User-Agent": USER_AGENT,
53
+ "X-Moon-App-Id": MOBILE_APP_PKG,
54
+ "X-Moon-Os": OS_NAME,
55
+ "X-Moon-Os-Version": OS_VERSION,
56
+ "X-Moon-Model": DEVICE_MODEL,
57
+ "X-Moon-App-Display-Version": MOBILE_APP_VERSION,
58
+ "X-Moon-App-Internal-Version": MOBILE_APP_BUILD,
59
+ "X-Moon-TimeZone": self._tz,
60
+ "X-Moon-Os-Language": self._language,
61
+ "X-Moon-App-Language": self._language,
62
+ "Authorization": self._auth_token
63
+ }
64
+
65
+ async def async_close(self):
66
+ """Closes the underlying aiohttp.ClientSession if it was created by this instance."""
67
+ if hasattr(self, '_session_created_internally') and self._session_created_internally and self._session and not self._session.closed:
68
+ await self._session.close()
69
+ self._session = None # Optional: clear the session attribute
70
+
71
+ async def send_request(self, endpoint: str, body: object=None, **kwargs):
72
+ """Sends a request to a given endpoint."""
73
+ _LOGGER.debug("Sending request to %s", endpoint)
74
+ # Get the endpoint from the endpoints map
75
+ e_point = ENDPOINTS.get(endpoint, None)
76
+ if e_point is None:
77
+ raise ValueError("Endpoint does not exist")
78
+ # refresh the token if it has expired.
79
+ if self._auth.expires < (datetime.now()+timedelta(seconds=30)):
80
+ _LOGGER.debug("Access token expired, requesting refresh.")
81
+ await self._auth.perform_refresh()
82
+ # format the URL using the kwargs
83
+ url = e_point.get("url").format(BASE_URL=BASE_URL, **kwargs)
84
+ _LOGGER.debug("Built URL %s", url)
85
+ # now send the HTTP request
86
+ resp: dict = {
87
+ "status": 0,
88
+ "text": "",
89
+ "json": "",
90
+ "headers": ""
91
+ }
92
+ self._session.headers.update(self._headers)
93
+ async with self._session.request(
94
+ method=e_point.get("method"),
95
+ url=url,
96
+ json=body
97
+ ) as response:
98
+ _LOGGER.debug("%s request to %s status code %s",
99
+ e_point.get("method"),
100
+ url,
101
+ response.status)
102
+ if _check_http_success(response.status):
103
+ resp["status"] = response.status
104
+ resp["text"] = await response.text()
105
+ try:
106
+ resp["json"] = await response.json()
107
+ except (aiohttp.ContentTypeError, ValueError) as e:
108
+ _LOGGER.warning(
109
+ """Failed to decode JSON response from %s.
110
+ Status: %s, Error: %s.
111
+ Response text: %s...""",
112
+ url, response.status, e, resp['text'][:200]
113
+ )
114
+ resp["json"] = {}
115
+ resp["headers"] = response.headers
116
+ else:
117
+ raise HttpException("HTTP Error", response.status, await response.text())
118
+
119
+ # now return the resp dict
120
+ return resp
121
+
122
+ async def async_get_account_details(self) -> dict:
123
+ """Get account details."""
124
+ return await self.send_request(
125
+ endpoint="get_account_details",
126
+ ACCOUNT_ID=self.account_id
127
+ )
128
+
129
+ async def async_get_account_devices(self) -> dict:
130
+ """Get account devices."""
131
+ return await self.send_request(
132
+ endpoint="get_account_devices",
133
+ ACCOUNT_ID=self.account_id
134
+ )
135
+
136
+ async def async_get_account_device(self, device_id: str) -> dict:
137
+ """Get account device."""
138
+ return await self.send_request(
139
+ endpoint="get_account_device",
140
+ ACCOUNT_ID=self.account_id,
141
+ DEVICE_ID=device_id
142
+ )
143
+
144
+ async def async_get_device_daily_summaries(self, device_id: str) -> dict:
145
+ """Get device daily summaries."""
146
+ return await self.send_request(
147
+ endpoint="get_device_daily_summaries",
148
+ DEVICE_ID=device_id
149
+ )
150
+
151
+ async def async_get_device_monthly_summaries(self, device_id: str) -> dict:
152
+ """Get device monthly summaries."""
153
+ return await self.send_request(
154
+ endpoint="get_device_monthly_summaries",
155
+ DEVICE_ID=device_id
156
+ )
157
+
158
+ async def async_get_device_parental_control_setting(self, device_id: str) -> dict:
159
+ """Get device parental control setting."""
160
+ return await self.send_request(
161
+ endpoint="get_device_parental_control_setting",
162
+ DEVICE_ID=device_id
163
+ )
164
+
165
+ async def async_get_device_parental_control_setting_state(self, device_id: str) -> dict:
166
+ """Get device parental control setting state."""
167
+ return await self.send_request(
168
+ endpoint="get_device_parental_control_setting_state",
169
+ DEVICE_ID=device_id
170
+ )
171
+
172
+ async def async_get_device_alarm_setting_state(self, device_id: str) -> dict:
173
+ """Get device alarm setting state."""
174
+ return await self.send_request(
175
+ endpoint="get_device_alarm_setting_state",
176
+ DEVICE_ID=device_id
177
+ )
178
+
179
+ async def async_get_device_monthly_summary(self, device_id: str, year: int, month: int) -> dict:
180
+ """Get device monthly summary."""
181
+ return await self.send_request(
182
+ endpoint="get_device_monthly_summary",
183
+ DEVICE_ID=device_id,
184
+ YEAR=year,
185
+ MONTH=f"{month:02d}"
186
+ )
187
+
188
+ async def async_set_device_parental_control_setting(
189
+ self,
190
+ device_id: str,
191
+ settings: dict
192
+ ) -> dict:
193
+ """Update device parental control setting."""
194
+ return await self.send_request(
195
+ endpoint="update_device_parental_control_setting",
196
+ DEVICE_ID=device_id,
197
+ body=settings
198
+ )
199
+
200
+ async def async_set_device_whitelisted_applications(
201
+ self,
202
+ device_id: str,
203
+ applications: dict
204
+ ) -> dict:
205
+ """Update device whitelisted applications."""
206
+ return await self.send_request(
207
+ endpoint="update_device_whitelisted_applications",
208
+ DEVICE_ID=device_id,
209
+ body=applications
210
+ )
211
+
212
+ async def async_set_device_alarm_setting_state(
213
+ self,
214
+ device_id: str,
215
+ alarm_state: dict
216
+ ) -> dict:
217
+ """Update device alarm setting state."""
218
+ return await self.send_request(
219
+ endpoint="update_device_alarm_setting_state",
220
+ DEVICE_ID=device_id,
221
+ body=alarm_state
222
+ )
@@ -115,10 +115,9 @@ class Device:
115
115
  async def _set_parental_control_setting(self):
116
116
  """Shortcut method to deduplicate code used to update parental control settings."""
117
117
  _LOGGER.debug(">> Device._set_parental_control_setting()")
118
- await self._api.send_request(
119
- endpoint="update_device_parental_control_setting",
120
- body=self._get_update_parental_control_setting_body(),
121
- DEVICE_ID=self.device_id
118
+ await self._api.async_set_device_parental_control_setting(
119
+ settings=self._get_update_parental_control_setting_body(),
120
+ device_id=self.device_id
122
121
  )
123
122
  await self._get_parental_control_setting()
124
123
 
@@ -209,9 +208,8 @@ class Device:
209
208
  async def _get_parental_control_setting(self):
210
209
  """Retreives parental control settings from the API."""
211
210
  _LOGGER.debug(">> Device._get_parental_control_setting()")
212
- response = await self._api.send_request(
213
- endpoint="get_device_parental_control_setting",
214
- DEVICE_ID=self.device_id
211
+ response = await self._api.async_get_device_parental_control_setting(
212
+ device_id=self.device_id
215
213
  )
216
214
  self.parental_control_settings = response["json"]
217
215
  if "bedtimeStartingTime" in self.parental_control_settings["playTimerRegulations"]:
@@ -235,9 +233,8 @@ class Device:
235
233
  async def _get_daily_summaries(self):
236
234
  """Retrieve daily summaries."""
237
235
  _LOGGER.debug(">> Device._get_daily_summaries()")
238
- response = await self._api.send_request(
239
- endpoint="get_device_daily_summaries",
240
- DEVICE_ID = self.device_id
236
+ response = await self._api.async_get_device_daily_summaries(
237
+ device_id = self.device_id
241
238
  )
242
239
  self.daily_summaries = response["json"]["items"]
243
240
  _LOGGER.debug("New daily summary %s", self.daily_summaries)
@@ -334,10 +331,8 @@ class Device:
334
331
  _LOGGER.debug(">> Device._get_extras()")
335
332
  if self.alarms_enabled is not None:
336
333
  # first refresh can come from self.extra without http request
337
- response = await self._api.send_request(
338
- endpoint="get_account_device",
339
- ACCOUNT_ID = self._api.account_id,
340
- DEVICE_ID = self.device_id
334
+ response = await self._api.async_get_account_device(
335
+ device_id = self.device_id
341
336
  )
342
337
  self.extra = response["json"]
343
338
  status = self.extra["device"]["alarmSetting"]["visibility"]
@@ -351,9 +346,8 @@ class Device:
351
346
  _LOGGER.debug(">> Device.get_monthly_summary(search_date=%s)", search_date)
352
347
  latest = False
353
348
  if search_date is None:
354
- response = await self._api.send_request(
355
- endpoint="get_device_monthly_summaries",
356
- DEVICE_ID=self.device_id
349
+ response = await self._api.async_get_device_monthly_summaries(
350
+ device_id=self.device_id
357
351
  )
358
352
  _LOGGER.debug("Available monthly summaries: %s", response)
359
353
  response = response["json"]["indexes"][0]
@@ -362,11 +356,10 @@ class Device:
362
356
  latest = True
363
357
 
364
358
  try:
365
- response = await self._api.send_request(
366
- endpoint="get_device_monthly_summary",
367
- DEVICE_ID = self.device_id,
368
- YEAR=search_date.year,
369
- MONTH=str(search_date.month).zfill(2)
359
+ response = await self._api.async_get_device_monthly_summary(
360
+ device_id = self.device_id,
361
+ year=search_date.year,
362
+ month=search_date.month
370
363
  )
371
364
  _LOGGER.debug("Monthly summary query complete for device %s: %s",
372
365
  self.device_id,
@@ -385,12 +378,11 @@ class Device:
385
378
  """Updates the alarm state for the device."""
386
379
  _LOGGER.debug(">> Device.set_alarm_state(state=%s)",
387
380
  state)
388
- await self._api.send_request(
389
- endpoint="update_device_alarm_setting_state",
390
- body={
381
+ await self._api.async_set_device_alarm_setting_state(
382
+ alarm_state={
391
383
  "status": str(state)
392
384
  },
393
- DEVICE_ID = self.device_id
385
+ device_id = self.device_id
394
386
  )
395
387
 
396
388
  async def set_whitelisted_application(self, app_id: str, allowed: bool):
@@ -403,10 +395,9 @@ class Device:
403
395
  # take a snapshot of the whitelisted apps state
404
396
  current_state = self.parental_control_settings["whitelistedApplications"]
405
397
  current_state[app_id]["safeLaunch"] = "ALLOW" if allowed else "NONE"
406
- await self._api.send_request(
407
- endpoint="update_device_whitelisted_applications",
408
- body=current_state,
409
- DEVICE_ID=self.device_id
398
+ await self._api.async_set_device_whitelisted_applications(
399
+ applications=current_state,
400
+ device_id=self.device_id
410
401
  )
411
402
  await self._get_parental_control_setting()
412
403
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pynintendoparental
3
- Version: 0.6.9
3
+ Version: 0.7.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
@@ -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