pynintendoparental 1.1.3__py3-none-any.whl → 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,34 +3,33 @@
3
3
 
4
4
  import asyncio
5
5
 
6
- from pynintendoparental.exceptions import HttpException, NoDevicesFoundException
6
+ from pynintendoauth.exceptions import HttpException
7
7
 
8
- from .authenticator import Authenticator
9
8
  from .api import Api
10
9
  from .const import _LOGGER
11
10
  from .device import Device
11
+ from .exceptions import NoDevicesFoundException
12
+ from .authenticator import Authenticator
13
+
12
14
 
13
15
  class NintendoParental:
14
16
  """Core Python API."""
15
17
 
16
- def __init__(self,
17
- auth: Authenticator,
18
- timezone,
19
- lang) -> None:
18
+ def __init__(self, auth: Authenticator, timezone, lang) -> None:
20
19
  self._api: Api = Api(auth=auth, tz=timezone, lang=lang)
21
20
  self.account_id = auth.account_id
22
21
  self.devices: dict[str, Device] = {}
23
22
 
24
23
  async def _get_devices(self):
25
24
  """Gets devices from the API and stores in self.devices"""
25
+
26
26
  async def update_device(dev: Device):
27
27
  """Update a device."""
28
28
  try:
29
29
  await dev.update()
30
30
  except Exception as err:
31
- _LOGGER.exception("Error updating device %s: %s",
32
- dev.device_id,
33
- err)
31
+ _LOGGER.exception("Error updating device %s: %s", dev.device_id, err)
32
+
34
33
  try:
35
34
  response = await self._api.async_get_account_devices()
36
35
  except HttpException as err:
@@ -55,10 +54,9 @@ class NintendoParental:
55
54
  _LOGGER.debug("Update complete.")
56
55
 
57
56
  @classmethod
58
- async def create(cls,
59
- auth: Authenticator,
60
- timezone: str = "Europe/London",
61
- lang: str = "en-GB") -> 'NintendoParental':
57
+ async def create(
58
+ cls, auth: Authenticator, timezone: str = "Europe/London", lang: str = "en-GB"
59
+ ) -> "NintendoParental":
62
60
  """Create an instance of NintendoParental."""
63
61
  self = cls(auth, timezone, lang)
64
62
  await self.update()
@@ -1 +1 @@
1
- __version__ = "1.1.3"
1
+ __version__ = "2.1.0"
pynintendoparental/api.py CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  import aiohttp
4
4
 
5
+ from pynintendoauth.exceptions import HttpException
6
+
5
7
  from .authenticator import Authenticator
6
8
  from .const import (
7
9
  ENDPOINTS,
@@ -13,13 +15,14 @@ from .const import (
13
15
  OS_VERSION,
14
16
  OS_NAME,
15
17
  DEVICE_MODEL,
16
- _LOGGER
18
+ _LOGGER,
17
19
  )
18
- from .exceptions import HttpException
20
+
19
21
 
20
22
  def _check_http_success(status: int) -> bool:
21
23
  return status >= 200 and status < 300
22
24
 
25
+
23
26
  class Api:
24
27
  """Nintendo Parental Controls API."""
25
28
 
@@ -48,10 +51,10 @@ class Api:
48
51
  "X-Moon-TimeZone": self._tz,
49
52
  "X-Moon-Os-Language": self._language,
50
53
  "X-Moon-App-Language": self._language,
51
- "Authorization": self._auth.access_token
54
+ "Authorization": self._auth.access_token,
52
55
  }
53
56
 
54
- async def send_request(self, endpoint: str, body: object=None, **kwargs):
57
+ async def send_request(self, endpoint: str, body: object = None, **kwargs):
55
58
  """Sends a request to a given endpoint."""
56
59
  _LOGGER.debug("Sending request to %s", endpoint)
57
60
  # Get the endpoint from the endpoints map
@@ -66,156 +69,115 @@ class Api:
66
69
  url = e_point.get("url").format(BASE_URL=BASE_URL, **kwargs)
67
70
  _LOGGER.debug("Built URL %s", url)
68
71
  # 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()
72
+ resp: dict = {"status": 0, "text": "", "json": "", "headers": ""}
73
+ response = await self._auth.async_authenticated_request(
74
+ method=e_point.get("method"), url=url, headers=self._headers, body=body
75
+ )
76
+ _LOGGER.debug(
77
+ "%s request to %s status code %s",
78
+ e_point.get("method"),
79
+ url,
80
+ response.status,
81
+ )
82
+ if not _check_http_success(response.status):
83
+ if response.content_type == "application/problem+json":
88
84
  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
- 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
108
- raise HttpException(response.status, await response.text())
85
+ error: dict = await response.json()
86
+ if "detail" in error:
87
+ raise HttpException(
88
+ response.status, error["detail"], error.get("errorCode")
89
+ )
90
+ except (aiohttp.ContentTypeError, ValueError):
91
+ # Fall through to the generic exception below on parsing failure.
92
+ pass
93
+ raise HttpException(response.status, await response.text())
94
+
95
+ resp["status"] = response.status
96
+ resp["text"] = await response.text()
97
+ try:
98
+ resp["json"] = await response.json()
99
+ except (aiohttp.ContentTypeError, ValueError) as e:
100
+ _LOGGER.warning(
101
+ """Failed to decode JSON response from %s.
102
+ Status: %s, Error: %s.
103
+ Response text: %s...""",
104
+ url,
105
+ response.status,
106
+ e,
107
+ resp["text"][:200],
108
+ )
109
+ resp["json"] = {}
110
+ resp["headers"] = response.headers
109
111
 
110
112
  # now return the resp dict
111
113
  return resp
112
114
 
113
- async def async_get_account_details(self) -> dict:
114
- """Get account details."""
115
- return await self.send_request(
116
- endpoint="get_account_details",
117
- ACCOUNT_ID=self.account_id
118
- )
119
-
120
115
  async def async_get_account_devices(self) -> dict:
121
116
  """Get account devices."""
122
- return await self.send_request(
123
- endpoint="get_account_devices"
124
- )
117
+ return await self.send_request(endpoint="get_account_devices")
125
118
 
126
119
  async def async_get_account_device(self, device_id: str) -> dict:
127
120
  """Get account device."""
128
121
  return await self.send_request(
129
- endpoint="get_account_device",
130
- DEVICE_ID=device_id
122
+ endpoint="get_account_device", DEVICE_ID=device_id
131
123
  )
132
124
 
133
125
  async def async_get_device_daily_summaries(self, device_id: str) -> dict:
134
126
  """Get device daily summaries."""
135
127
  return await self.send_request(
136
- endpoint="get_device_daily_summaries",
137
- DEVICE_ID=device_id
128
+ endpoint="get_device_daily_summaries", DEVICE_ID=device_id
138
129
  )
139
130
 
140
131
  async def async_get_device_monthly_summaries(self, device_id: str) -> dict:
141
132
  """Get device monthly summaries."""
142
133
  return await self.send_request(
143
- endpoint="get_device_monthly_summaries",
144
- DEVICE_ID=device_id
134
+ endpoint="get_device_monthly_summaries", DEVICE_ID=device_id
145
135
  )
146
136
 
147
137
  async def async_get_device_parental_control_setting(self, device_id: str) -> dict:
148
138
  """Get device parental control setting."""
149
139
  return await self.send_request(
150
- endpoint="get_device_parental_control_setting",
151
- DEVICE_ID=device_id
140
+ endpoint="get_device_parental_control_setting", DEVICE_ID=device_id
152
141
  )
153
142
 
154
- async def async_get_device_parental_control_setting_state(self, device_id: str) -> dict:
155
- """Get device parental control setting state."""
156
- return await self.send_request(
157
- endpoint="get_device_parental_control_setting_state",
158
- DEVICE_ID=device_id
159
- )
160
-
161
- async def async_get_device_monthly_summary(self, device_id: str, year: int, month: int) -> dict:
143
+ async def async_get_device_monthly_summary(
144
+ self, device_id: str, year: int, month: int
145
+ ) -> dict:
162
146
  """Get device monthly summary."""
163
147
  return await self.send_request(
164
148
  endpoint="get_device_monthly_summary",
165
149
  DEVICE_ID=device_id,
166
150
  YEAR=year,
167
- MONTH=f"{month:02d}"
151
+ MONTH=f"{month:02d}",
168
152
  )
169
153
 
170
- async def async_update_restriction_level(
171
- self,
172
- settings: dict
173
- ) -> dict:
154
+ async def async_update_restriction_level(self, settings: dict) -> dict:
174
155
  """Update device restriction level."""
175
156
  return await self.send_request(
176
- endpoint="update_restriction_level",
177
- body=settings
157
+ endpoint="update_restriction_level", body=settings
178
158
  )
179
159
 
180
- async def async_update_play_timer(
181
- self,
182
- settings: dict
183
- ) -> dict:
160
+ async def async_update_play_timer(self, settings: dict) -> dict:
184
161
  """Update device play timer settings."""
185
- return await self.send_request(
186
- endpoint="update_play_timer",
187
- body=settings
188
- )
162
+ return await self.send_request(endpoint="update_play_timer", body=settings)
189
163
 
190
- async def async_update_unlock_code(
191
- self,
192
- new_code: str,
193
- device_id: str
194
- ) -> dict:
164
+ async def async_update_unlock_code(self, new_code: str, device_id: str) -> dict:
195
165
  """Update device unlock code."""
196
166
  return await self.send_request(
197
167
  endpoint="update_unlock_code",
198
- body={
199
- "deviceId": device_id,
200
- "unlockCode": new_code
201
- }
168
+ body={"deviceId": device_id, "unlockCode": new_code},
202
169
  )
203
170
 
204
171
  async def async_update_extra_playing_time(
205
- self,
206
- device_id: str,
207
- additional_time: int
172
+ self, device_id: str, additional_time: int
208
173
  ) -> dict:
209
174
  """Update device extra playing time."""
210
175
  body = {
211
176
  "deviceId": device_id,
212
177
  "additionalTime": additional_time,
213
- "status": "TO_ADDED"
178
+ "status": "TO_ADDED",
214
179
  }
215
180
  if additional_time == -1:
216
181
  body["status"] = "TO_INFINITY"
217
182
  body.pop("additionalTime")
218
- return await self.send_request(
219
- endpoint="update_extra_playing_time",
220
- body=body
221
- )
183
+ return await self.send_request(endpoint="update_extra_playing_time", body=body)
@@ -4,6 +4,7 @@ from datetime import datetime
4
4
 
5
5
  from .const import _LOGGER
6
6
 
7
+
7
8
  class Application:
8
9
  """Model for an application"""
9
10
 
@@ -12,7 +13,7 @@ class Application:
12
13
  self.application_id: str = None
13
14
  self.first_played_date: datetime = None
14
15
  self.has_ugc: bool = None
15
- self.image_url: str = None # uses small image from Nintendo
16
+ self.image_url: str = None # uses small image from Nintendo
16
17
  self.playing_days: int = None
17
18
  self.shop_url: str = None
18
19
  self.name: str = None
@@ -20,11 +21,10 @@ class Application:
20
21
 
21
22
  def update_today_time_played(self, daily_summary: dict):
22
23
  """Updates the today time played for the given application."""
23
- _LOGGER.debug("Updating today time played for app %s",
24
- self.application_id)
24
+ _LOGGER.debug("Updating today time played for app %s", self.application_id)
25
25
  self.today_time_played = daily_summary.get("playingTime", 0)
26
26
 
27
- def update(self, updated: 'Application'):
27
+ def update(self, updated: "Application"):
28
28
  """Updates self with a given application."""
29
29
  _LOGGER.debug("Updating application %s", self.application_id)
30
30
  self.application_id = updated.application_id
@@ -37,7 +37,7 @@ class Application:
37
37
  self.today_time_played = updated.today_time_played
38
38
 
39
39
  @classmethod
40
- def from_daily_summary(cls, raw: list) -> list['Application']:
40
+ def from_daily_summary(cls, raw: list) -> list["Application"]:
41
41
  """Converts a raw daily summary response into a list of applications."""
42
42
  built = []
43
43
  if "playedApps" in raw:
@@ -49,7 +49,7 @@ class Application:
49
49
  return built
50
50
 
51
51
  @staticmethod
52
- def check_if_app_in_list(app_list: list['Application'], app: 'Application') -> bool:
52
+ def check_if_app_in_list(app_list: list["Application"], app: "Application") -> bool:
53
53
  """Checks if an app is in a list."""
54
54
  for app_li in app_list:
55
55
  if app_li.application_id == app.application_id:
@@ -57,7 +57,9 @@ class Application:
57
57
  return False
58
58
 
59
59
  @staticmethod
60
- def return_app_from_list(app_list: list['Application'], application_id: str) -> 'Application':
60
+ def return_app_from_list(
61
+ app_list: list["Application"], application_id: str
62
+ ) -> "Application":
61
63
  """Returns a single app from a given list."""
62
64
  for app in app_list:
63
65
  if app.application_id == application_id:
@@ -65,28 +67,32 @@ class Application:
65
67
  return None
66
68
 
67
69
  @classmethod
68
- def from_whitelist(cls, raw: dict) -> list['Application']:
70
+ def from_whitelist(cls, raw: dict) -> list["Application"]:
69
71
  """Converts a raw whitelist response into a list of applications."""
70
72
  parsed = []
71
73
  for app_id in raw:
72
74
  _LOGGER.debug("Parsing app %s", app_id)
73
75
  internal = cls()
74
76
  internal.application_id = raw[app_id]["applicationId"]
75
- internal.first_played_date = datetime.strptime(raw[app_id]["firstPlayDate"], "%Y-%m-%d")
77
+ internal.first_played_date = datetime.strptime(
78
+ raw[app_id]["firstPlayDate"], "%Y-%m-%d"
79
+ )
76
80
  internal.image_url = raw[app_id]["imageUri"]
77
81
  internal.name = raw[app_id]["title"]
78
82
  parsed.append(internal)
79
83
  return parsed
80
84
 
81
85
  @classmethod
82
- def from_monthly_summary(cls, raw: list) -> list['Application']:
86
+ def from_monthly_summary(cls, raw: list) -> list["Application"]:
83
87
  """Converts a raw monthly summary response into a list of applications."""
84
88
  parsed = []
85
89
  for app in raw:
86
90
  _LOGGER.debug("Parsing app %s", app)
87
91
  internal = cls()
88
92
  internal.application_id = app.get("applicationId").capitalize()
89
- internal.first_played_date = datetime.strptime(app.get("firstPlayDate"), "%Y-%m-%d")
93
+ internal.first_played_date = datetime.strptime(
94
+ app.get("firstPlayDate"), "%Y-%m-%d"
95
+ )
90
96
  internal.has_ugc = app.get("hasUgc", False)
91
97
  internal.image_url = app.get("imageUri").get("small")
92
98
  internal.playing_days = app.get("playingDays", None)
@@ -0,0 +1,18 @@
1
+ """Nintendo Authentication."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pynintendoauth import NintendoAuth
6
+
7
+ from .const import CLIENT_ID
8
+
9
+
10
+ class Authenticator(NintendoAuth):
11
+ """Authentication functions."""
12
+
13
+ def __init__(self, session_token=None, client_session=None):
14
+ super().__init__(
15
+ client_id=CLIENT_ID,
16
+ session_token=session_token,
17
+ client_session=client_session,
18
+ )
@@ -4,6 +4,7 @@
4
4
  import logging
5
5
 
6
6
  _LOGGER = logging.getLogger(__package__)
7
+ CLIENT_ID = "54789befb391a838"
7
8
  MOBILE_APP_PKG = "com.nintendo.znma"
8
9
  MOBILE_APP_VERSION = "2.2.0"
9
10
  MOBILE_APP_BUILD = "560"
@@ -14,47 +15,55 @@ DEVICE_MODEL = "Pixel 4 XL"
14
15
  BASE_URL = "https://app.lp1.znma.srv.nintendo.net/v2"
15
16
  USER_AGENT = f"moon_ANDROID/{MOBILE_APP_VERSION} ({MOBILE_APP_PKG}; build:{MOBILE_APP_BUILD}; {OS_STR})"
16
17
 
17
- DAYS_OF_WEEK = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"]
18
+ DAYS_OF_WEEK = [
19
+ "monday",
20
+ "tuesday",
21
+ "wednesday",
22
+ "thursday",
23
+ "friday",
24
+ "saturday",
25
+ "sunday",
26
+ ]
18
27
 
19
28
  ENDPOINTS = {
20
29
  "get_account_devices": {
21
30
  "url": "{BASE_URL}/actions/user/fetchOwnedDevices",
22
- "method": "GET"
31
+ "method": "GET",
23
32
  },
24
33
  "get_account_device": {
25
34
  "url": "{BASE_URL}/actions/user/fetchOwnedDevice?deviceId={DEVICE_ID}",
26
- "method": "GET"
35
+ "method": "GET",
27
36
  },
28
37
  "get_device_daily_summaries": {
29
38
  "url": "{BASE_URL}/actions/playSummary/fetchDailySummaries?deviceId={DEVICE_ID}",
30
- "method": "GET"
39
+ "method": "GET",
31
40
  },
32
41
  "get_device_monthly_summaries": {
33
42
  "url": "{BASE_URL}/actions/playSummary/fetchLatestMonthlySummary?deviceId={DEVICE_ID}",
34
- "method": "GET"
43
+ "method": "GET",
35
44
  },
36
45
  "get_device_parental_control_setting": {
37
46
  "url": "{BASE_URL}/actions/parentalControlSetting/fetchParentalControlSetting?deviceId={DEVICE_ID}",
38
- "method": "GET"
47
+ "method": "GET",
39
48
  },
40
49
  "update_restriction_level": {
41
50
  "url": "{BASE_URL}/actions/parentalControlSetting/updateRestrictionLevel",
42
- "method": "POST"
51
+ "method": "POST",
43
52
  },
44
53
  "update_play_timer": {
45
54
  "url": "{BASE_URL}/actions/parentalControlSetting/updatePlayTimer",
46
- "method": "POST"
55
+ "method": "POST",
47
56
  },
48
57
  "update_unlock_code": {
49
58
  "url": "{BASE_URL}/actions/parentalControlSetting/updateUnlockCode",
50
- "method": "POST"
59
+ "method": "POST",
51
60
  },
52
61
  "get_device_monthly_summary": {
53
62
  "url": "{BASE_URL}/actions/playSummary/fetchMonthlySummary?deviceId={DEVICE_ID}&year={YEAR}&month={MONTH}&containLatest=false",
54
- "method": "GET"
63
+ "method": "GET",
55
64
  },
56
65
  "update_extra_playing_time": {
57
66
  "url": "{BASE_URL}/actions/device/updateExtraPlayingTime",
58
- "method": "POST"
59
- }
67
+ "method": "POST",
68
+ },
60
69
  }