pynintendoparental 1.1.3__py3-none-any.whl → 2.0.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.0.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,46 +69,45 @@ 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
@@ -113,109 +115,83 @@ class Api:
113
115
  async def async_get_account_details(self) -> dict:
114
116
  """Get account details."""
115
117
  return await self.send_request(
116
- endpoint="get_account_details",
117
- ACCOUNT_ID=self.account_id
118
+ endpoint="get_account_details", ACCOUNT_ID=self.account_id
118
119
  )
119
120
 
120
121
  async def async_get_account_devices(self) -> dict:
121
122
  """Get account devices."""
122
- return await self.send_request(
123
- endpoint="get_account_devices"
124
- )
123
+ return await self.send_request(endpoint="get_account_devices")
125
124
 
126
125
  async def async_get_account_device(self, device_id: str) -> dict:
127
126
  """Get account device."""
128
127
  return await self.send_request(
129
- endpoint="get_account_device",
130
- DEVICE_ID=device_id
128
+ endpoint="get_account_device", DEVICE_ID=device_id
131
129
  )
132
130
 
133
131
  async def async_get_device_daily_summaries(self, device_id: str) -> dict:
134
132
  """Get device daily summaries."""
135
133
  return await self.send_request(
136
- endpoint="get_device_daily_summaries",
137
- DEVICE_ID=device_id
134
+ endpoint="get_device_daily_summaries", DEVICE_ID=device_id
138
135
  )
139
136
 
140
137
  async def async_get_device_monthly_summaries(self, device_id: str) -> dict:
141
138
  """Get device monthly summaries."""
142
139
  return await self.send_request(
143
- endpoint="get_device_monthly_summaries",
144
- DEVICE_ID=device_id
140
+ endpoint="get_device_monthly_summaries", DEVICE_ID=device_id
145
141
  )
146
142
 
147
143
  async def async_get_device_parental_control_setting(self, device_id: str) -> dict:
148
144
  """Get device parental control setting."""
149
145
  return await self.send_request(
150
- endpoint="get_device_parental_control_setting",
151
- DEVICE_ID=device_id
146
+ endpoint="get_device_parental_control_setting", DEVICE_ID=device_id
152
147
  )
153
148
 
154
- async def async_get_device_parental_control_setting_state(self, device_id: str) -> dict:
149
+ async def async_get_device_parental_control_setting_state(
150
+ self, device_id: str
151
+ ) -> dict:
155
152
  """Get device parental control setting state."""
156
153
  return await self.send_request(
157
- endpoint="get_device_parental_control_setting_state",
158
- DEVICE_ID=device_id
154
+ endpoint="get_device_parental_control_setting_state", DEVICE_ID=device_id
159
155
  )
160
156
 
161
- async def async_get_device_monthly_summary(self, device_id: str, year: int, month: int) -> dict:
157
+ async def async_get_device_monthly_summary(
158
+ self, device_id: str, year: int, month: int
159
+ ) -> dict:
162
160
  """Get device monthly summary."""
163
161
  return await self.send_request(
164
162
  endpoint="get_device_monthly_summary",
165
163
  DEVICE_ID=device_id,
166
164
  YEAR=year,
167
- MONTH=f"{month:02d}"
165
+ MONTH=f"{month:02d}",
168
166
  )
169
167
 
170
- async def async_update_restriction_level(
171
- self,
172
- settings: dict
173
- ) -> dict:
168
+ async def async_update_restriction_level(self, settings: dict) -> dict:
174
169
  """Update device restriction level."""
175
170
  return await self.send_request(
176
- endpoint="update_restriction_level",
177
- body=settings
171
+ endpoint="update_restriction_level", body=settings
178
172
  )
179
173
 
180
- async def async_update_play_timer(
181
- self,
182
- settings: dict
183
- ) -> dict:
174
+ async def async_update_play_timer(self, settings: dict) -> dict:
184
175
  """Update device play timer settings."""
185
- return await self.send_request(
186
- endpoint="update_play_timer",
187
- body=settings
188
- )
176
+ return await self.send_request(endpoint="update_play_timer", body=settings)
189
177
 
190
- async def async_update_unlock_code(
191
- self,
192
- new_code: str,
193
- device_id: str
194
- ) -> dict:
178
+ async def async_update_unlock_code(self, new_code: str, device_id: str) -> dict:
195
179
  """Update device unlock code."""
196
180
  return await self.send_request(
197
181
  endpoint="update_unlock_code",
198
- body={
199
- "deviceId": device_id,
200
- "unlockCode": new_code
201
- }
182
+ body={"deviceId": device_id, "unlockCode": new_code},
202
183
  )
203
184
 
204
185
  async def async_update_extra_playing_time(
205
- self,
206
- device_id: str,
207
- additional_time: int
186
+ self, device_id: str, additional_time: int
208
187
  ) -> dict:
209
188
  """Update device extra playing time."""
210
189
  body = {
211
190
  "deviceId": device_id,
212
191
  "additionalTime": additional_time,
213
- "status": "TO_ADDED"
192
+ "status": "TO_ADDED",
214
193
  }
215
194
  if additional_time == -1:
216
195
  body["status"] = "TO_INFINITY"
217
196
  body.pop("additionalTime")
218
- return await self.send_request(
219
- endpoint="update_extra_playing_time",
220
- body=body
221
- )
197
+ 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
  }