amazon-parent-dashboard 1.0.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.
@@ -0,0 +1,318 @@
1
+ Metadata-Version: 2.1
2
+ Name: amazon_parent_dashboard
3
+ Version: 1.0.0
4
+ Summary: A simple interface to add bonus time to your Amazon Parent Dashboard devices
5
+ Home-page: https://github.com/GrantGMiller/amazon_parent_dashboard
6
+ Author: Grant miller
7
+ Author-email: grant@grant-miller.com
8
+ License: PSF
9
+ Project-URL: Source Code, https://github.com/GrantGMiller/amazon_parent_dashboard
10
+ Keywords: grant miller familylink bonus time
11
+ Description-Content-Type: text/x-rst
12
+ Requires-Dist: requests
13
+ Requires-Dist: pytz
14
+
15
+ Amazon Kids Parent Dashboard Client
16
+ ====================================
17
+
18
+ Overview
19
+ --------
20
+
21
+ This module provides an unofficial Python client for the Amazon Kids Parent
22
+ Dashboard at https://parents.amazon.com.
23
+
24
+ It was developed from browser network captures and uses Parent Dashboard
25
+ endpoints that are not documented as a public API. Amazon may change these
26
+ endpoints, request formats, authentication requirements, or response formats
27
+ at any time.
28
+
29
+ The client currently supports:
30
+
31
+ * Retrieve the children names.
32
+ * Reading a child's time-limit configuration.
33
+ * Getting the current day's maximum screen-time limit.
34
+ * Adding bonus time for a child.
35
+ * Pausing and resuming a child's devices.
36
+
37
+ Authentication requires you to copy cookies from an already authenticated Amazon session. This
38
+ module does not implement an OAuth flow or guarantee automatic renewal of an
39
+ expired session.
40
+
41
+ Requirements/Dependencies
42
+ -------------------------
43
+
44
+ * Python 3.8 or newer.
45
+ * ``requests``
46
+ * ``pytz``
47
+
48
+
49
+ Authentication
50
+ --------------
51
+
52
+ The constructor accepts cookies in one of three formats:
53
+
54
+ #. A Python dictionary mapping cookie names to cookie values.
55
+ #. A JSON string representing that dictionary.
56
+ #. A cookie-header string containing semicolon-separated ``name=value`` pairs.
57
+
58
+ You can pass cookies directly::
59
+
60
+ from parent_dashboard import ParentDashboard
61
+
62
+ cookies = {
63
+ "session-id": "YOUR_SESSION_ID",
64
+ "session-token": "YOUR_SESSION_TOKEN",
65
+ # Add the other cookies required by your current session.
66
+ }
67
+
68
+ dashboard = ParentDashboard(
69
+ cookies=cookies,
70
+ children={str(child_name): str(child_id)}
71
+ )
72
+
73
+ The example cookie names above are illustrative. Copy the required cookie
74
+ pairs from your own authenticated browser session; the exact set may vary.
75
+
76
+ **Keep cookies secret.** Treat them like passwords: do not
77
+ commit them to source control, include them in bug reports, or print them in
78
+ logs. Use environment variables or a secrets manager and restrict access to
79
+ the server account running the client.
80
+
81
+ Configuring children
82
+ --------------------
83
+
84
+ The client does not automatically discover children names. Pass a mapping from each
85
+ child's display name to their Amazon ``childDirectedId`` / directed ID::
86
+
87
+ children = {
88
+ "Forrest": "amzn1.account.REPLACE_WITH_DIRECTED_ID",
89
+ "Jenny": "amzn1.account.REPLACE_WITH_DIRECTED_ID",
90
+ }
91
+
92
+ dashboard = ParentDashboard(
93
+ cookies=cookies,
94
+ children=children,
95
+ )
96
+
97
+ Replace each placeholder with the actual directed ID for that child. The
98
+ client matches names case-insensitively, but the supplied name must otherwise
99
+ match one of the configured names. If ``children`` is omitted, calls that
100
+ require a child will raise ``ParentDashboardError``.
101
+
102
+ Basic usage
103
+ -----------
104
+
105
+ Get configured child names::
106
+
107
+ print(dashboard.get_children())
108
+
109
+ Read the raw time-limit response for a child::
110
+
111
+ limits = dashboard.get_time_limits("Lillianne")
112
+ print(limits)
113
+
114
+ Get the current day's screen-time limit in minutes::
115
+
116
+ max_minutes = dashboard.get_max_screentime("Lillianne")
117
+ if max_minutes is None:
118
+ print("No enabled screen-time limit was found for today.")
119
+ else:
120
+ print(f"Today's limit: {max_minutes} minutes")
121
+
122
+ ``get_max_screentime`` uses the configured time zone to choose the current
123
+ weekday. It returns ``None`` when the day's schedule or content-time limits
124
+ are disabled. Otherwise it expects the response to contain an integer value
125
+ under ``timeLimits.contentTimeLimits.ALL`` for the matching day.
126
+
127
+ Set bonus time::
128
+
129
+ dashboard.add_bonus_time("Lillianne", 15)
130
+
131
+ The current implementation sends a ``PUT`` request to
132
+ ``/ajax/set-bonus-time`` with a duration of 15 minutes. **Despite the method
133
+ name, it is not yet confirmed whether the endpoint adds 15 minutes to an
134
+ existing bonus or sets the bonus duration to 15 minutes.** Verify this behavior
135
+ carefully in the Parent Dashboard before relying on repeated calls. Do not
136
+ assume calling it twice safely adds twice as much time.
137
+
138
+ Pause devices for a child for 60 minutes::
139
+
140
+ dashboard.pause_devices("Rosalie", 60)
141
+
142
+ Resume devices for a child::
143
+
144
+ dashboard.resume_devices("Rosalie")
145
+
146
+
147
+
148
+ Time zone
149
+ ---------
150
+
151
+ The constructor defaults to ``America/New_York``::
152
+
153
+ dashboard = ParentDashboard(
154
+ cookies=cookies,
155
+ children=children,
156
+ time_zone="America/New_York",
157
+ )
158
+
159
+ The time zone is sent with bonus-time requests and is also used to determine
160
+ the weekday in ``get_max_screentime``. Use an appropriate IANA time-zone name
161
+ for your household.
162
+
163
+ Constructor reference
164
+ ---------------------
165
+
166
+ ``ParentDashboard(cookies=None, children=None, time_zone="America/New_York")``
167
+
168
+ ``cookies``
169
+ Cookie dictionary, JSON string, or semicolon-separated cookie-header
170
+ string. If omitted, the client reads ``AMAZON_PARENTS_COOKIES``. If neither
171
+ is set, construction raises ``AuthError``.
172
+
173
+ ``children``
174
+ Dictionary mapping child display names to directed IDs.
175
+
176
+ ``time_zone``
177
+ IANA time-zone string used for bonus-time requests and weekday selection.
178
+
179
+ Public methods
180
+ --------------
181
+
182
+ ``get_children()``
183
+ Return the configured child names as a list. This does not discover
184
+ children from Amazon; it returns the names supplied in ``children``.
185
+
186
+ ``get_time_limits(child_name)``
187
+ Return the raw JSON response from the adjusted-time-limits endpoint.
188
+
189
+ ``get_max_screentime(child_name)``
190
+ Return today's enabled ``ALL`` content-time limit in minutes, or ``None``
191
+ when the relevant limits are disabled. Raises ``ParentDashboardError`` if
192
+ the expected response structure is missing.
193
+
194
+ ``add_bonus_time(child_name, minutes)``
195
+ Send a bonus-time configuration request. ``minutes`` must be positive.
196
+ Whether the endpoint adds to or replaces the current bonus has not been
197
+ confirmed.
198
+
199
+ ``pause_devices(child_name, minutes)``
200
+ Request that the child's devices be paused for the requested number of
201
+ minutes. ``minutes`` must be positive.
202
+
203
+ ``resume_devices(child_name)``
204
+ Request that the child's devices be resumed.
205
+
206
+ Exceptions
207
+ ----------
208
+
209
+ ``ParentDashboardError``
210
+ Base exception for client errors, unexpected responses, and HTTP failures.
211
+
212
+ ``AuthError``
213
+ Raised when cookies are missing/invalid, the request is redirected to
214
+ sign-in, or CSRF-token retrieval fails.
215
+
216
+ ``KeyError``
217
+ Raised by child lookup when the requested name is not in the configured
218
+ mapping.
219
+
220
+ ``ValueError``
221
+ Raised when a pause or bonus duration is not positive.
222
+
223
+ Running the module directly
224
+ ---------------------------
225
+
226
+ The module's ``__main__`` example expects a local ``config`` module containing
227
+ at least these attributes::
228
+
229
+ cookie_string
230
+ child_name_id_map
231
+ child_name
232
+
233
+ It also expects a ``helper`` module with a ``get_cookie_dict`` function.
234
+ The example reads the selected child's maximum screen-time limit. Adapt these
235
+ names to your own configuration, and do not commit real authentication
236
+ credentials to a repository.
237
+
238
+ Automation and server deployment
239
+ --------------------------------
240
+
241
+ For a scheduled job, keep your rule logic separate from the client. For
242
+ example::
243
+
244
+ from parent_dashboard import ParentDashboard
245
+
246
+ dashboard = ParentDashboard(
247
+ children=children,
248
+ time_zone="America/New_York",
249
+ )
250
+
251
+ child_name = "Lillianne"
252
+ max_minutes = dashboard.get_max_screentime(child_name)
253
+
254
+ # Insert your own decision logic here.
255
+ # Only call add_bonus_time after verifying the endpoint's semantics.
256
+
257
+ Run the script using a scheduler such as ``systemd`` or cron. In production:
258
+
259
+ * Store credentials in environment variables or a secrets manager.
260
+ * Run the job as a dedicated, unprivileged Linux user.
261
+ * Log decisions and outcomes, but never log cookies, CSRF tokens, or full
262
+ authentication headers.
263
+ * Handle ``AuthError`` and notify the administrator when manual sign-in is
264
+ required.
265
+ * Add persistent state or an idempotency record so a retried job does not
266
+ accidentally award the same bonus more than once.
267
+ * Test with a small, controlled bonus and verify the result in the Parent
268
+ Dashboard before enabling automatic awards.
269
+ * Do not assume that a browser session will remain valid indefinitely. This
270
+ client does not implement supported OAuth refresh-token handling.
271
+
272
+ Example error handling::
273
+
274
+ from parent_dashboard import AuthError, ParentDashboardError
275
+
276
+ try:
277
+ dashboard.add_bonus_time("Lillianne", 10)
278
+ except AuthError as exc:
279
+ # Notify yourself that the session needs attention.
280
+ print(f"Authentication needs attention: {exc}")
281
+ except ParentDashboardError as exc:
282
+ print(f"Parent Dashboard request failed: {exc}")
283
+
284
+ Troubleshooting
285
+ ---------------
286
+
287
+ **``AuthError: No cookies``**
288
+
289
+ Pass ``cookies=...`` or set ``AMAZON_PARENTS_COOKIES``.
290
+
291
+ **Redirected to sign-in / invalid cookies**
292
+
293
+ The session may have expired or been revoked. Obtain a fresh authenticated
294
+ session and update the configured cookies. The client cannot guarantee
295
+ automatic reauthentication.
296
+
297
+ **HTTP 401 or 403**
298
+
299
+ The session may be invalid or the endpoint may
300
+ require a different request format. For eligible read-only requests, the
301
+ client attempts one CSRF refresh and retry. It does not automatically retry
302
+ state-changing requests.
303
+
304
+ **Unexpected time-limit response**
305
+
306
+ Inspect the raw result of ``get_time_limits(child_name)``. The parser in
307
+ ``get_max_screentime`` expects ``periodConfigurations`` with a matching
308
+ ``DayOfWeek`` entry and an integer ``ALL`` value.
309
+
310
+ Limitations and disclaimer
311
+ --------------------------
312
+
313
+ This is an unofficial client built from browser network captures. It is not
314
+ an Amazon-supported SDK, and the endpoints are not guaranteed to remain
315
+ available or compatible. The client does not currently discover child IDs,
316
+ guarantee session renewal, or verify whether setting bonus time is additive.
317
+ Use it only with an account and devices you are authorized to manage, and
318
+ verify changes in the Parent Dashboard.
@@ -0,0 +1,304 @@
1
+ Amazon Kids Parent Dashboard Client
2
+ ====================================
3
+
4
+ Overview
5
+ --------
6
+
7
+ This module provides an unofficial Python client for the Amazon Kids Parent
8
+ Dashboard at https://parents.amazon.com.
9
+
10
+ It was developed from browser network captures and uses Parent Dashboard
11
+ endpoints that are not documented as a public API. Amazon may change these
12
+ endpoints, request formats, authentication requirements, or response formats
13
+ at any time.
14
+
15
+ The client currently supports:
16
+
17
+ * Retrieve the children names.
18
+ * Reading a child's time-limit configuration.
19
+ * Getting the current day's maximum screen-time limit.
20
+ * Adding bonus time for a child.
21
+ * Pausing and resuming a child's devices.
22
+
23
+ Authentication requires you to copy cookies from an already authenticated Amazon session. This
24
+ module does not implement an OAuth flow or guarantee automatic renewal of an
25
+ expired session.
26
+
27
+ Requirements/Dependencies
28
+ -------------------------
29
+
30
+ * Python 3.8 or newer.
31
+ * ``requests``
32
+ * ``pytz``
33
+
34
+
35
+ Authentication
36
+ --------------
37
+
38
+ The constructor accepts cookies in one of three formats:
39
+
40
+ #. A Python dictionary mapping cookie names to cookie values.
41
+ #. A JSON string representing that dictionary.
42
+ #. A cookie-header string containing semicolon-separated ``name=value`` pairs.
43
+
44
+ You can pass cookies directly::
45
+
46
+ from parent_dashboard import ParentDashboard
47
+
48
+ cookies = {
49
+ "session-id": "YOUR_SESSION_ID",
50
+ "session-token": "YOUR_SESSION_TOKEN",
51
+ # Add the other cookies required by your current session.
52
+ }
53
+
54
+ dashboard = ParentDashboard(
55
+ cookies=cookies,
56
+ children={str(child_name): str(child_id)}
57
+ )
58
+
59
+ The example cookie names above are illustrative. Copy the required cookie
60
+ pairs from your own authenticated browser session; the exact set may vary.
61
+
62
+ **Keep cookies secret.** Treat them like passwords: do not
63
+ commit them to source control, include them in bug reports, or print them in
64
+ logs. Use environment variables or a secrets manager and restrict access to
65
+ the server account running the client.
66
+
67
+ Configuring children
68
+ --------------------
69
+
70
+ The client does not automatically discover children names. Pass a mapping from each
71
+ child's display name to their Amazon ``childDirectedId`` / directed ID::
72
+
73
+ children = {
74
+ "Forrest": "amzn1.account.REPLACE_WITH_DIRECTED_ID",
75
+ "Jenny": "amzn1.account.REPLACE_WITH_DIRECTED_ID",
76
+ }
77
+
78
+ dashboard = ParentDashboard(
79
+ cookies=cookies,
80
+ children=children,
81
+ )
82
+
83
+ Replace each placeholder with the actual directed ID for that child. The
84
+ client matches names case-insensitively, but the supplied name must otherwise
85
+ match one of the configured names. If ``children`` is omitted, calls that
86
+ require a child will raise ``ParentDashboardError``.
87
+
88
+ Basic usage
89
+ -----------
90
+
91
+ Get configured child names::
92
+
93
+ print(dashboard.get_children())
94
+
95
+ Read the raw time-limit response for a child::
96
+
97
+ limits = dashboard.get_time_limits("Lillianne")
98
+ print(limits)
99
+
100
+ Get the current day's screen-time limit in minutes::
101
+
102
+ max_minutes = dashboard.get_max_screentime("Lillianne")
103
+ if max_minutes is None:
104
+ print("No enabled screen-time limit was found for today.")
105
+ else:
106
+ print(f"Today's limit: {max_minutes} minutes")
107
+
108
+ ``get_max_screentime`` uses the configured time zone to choose the current
109
+ weekday. It returns ``None`` when the day's schedule or content-time limits
110
+ are disabled. Otherwise it expects the response to contain an integer value
111
+ under ``timeLimits.contentTimeLimits.ALL`` for the matching day.
112
+
113
+ Set bonus time::
114
+
115
+ dashboard.add_bonus_time("Lillianne", 15)
116
+
117
+ The current implementation sends a ``PUT`` request to
118
+ ``/ajax/set-bonus-time`` with a duration of 15 minutes. **Despite the method
119
+ name, it is not yet confirmed whether the endpoint adds 15 minutes to an
120
+ existing bonus or sets the bonus duration to 15 minutes.** Verify this behavior
121
+ carefully in the Parent Dashboard before relying on repeated calls. Do not
122
+ assume calling it twice safely adds twice as much time.
123
+
124
+ Pause devices for a child for 60 minutes::
125
+
126
+ dashboard.pause_devices("Rosalie", 60)
127
+
128
+ Resume devices for a child::
129
+
130
+ dashboard.resume_devices("Rosalie")
131
+
132
+
133
+
134
+ Time zone
135
+ ---------
136
+
137
+ The constructor defaults to ``America/New_York``::
138
+
139
+ dashboard = ParentDashboard(
140
+ cookies=cookies,
141
+ children=children,
142
+ time_zone="America/New_York",
143
+ )
144
+
145
+ The time zone is sent with bonus-time requests and is also used to determine
146
+ the weekday in ``get_max_screentime``. Use an appropriate IANA time-zone name
147
+ for your household.
148
+
149
+ Constructor reference
150
+ ---------------------
151
+
152
+ ``ParentDashboard(cookies=None, children=None, time_zone="America/New_York")``
153
+
154
+ ``cookies``
155
+ Cookie dictionary, JSON string, or semicolon-separated cookie-header
156
+ string. If omitted, the client reads ``AMAZON_PARENTS_COOKIES``. If neither
157
+ is set, construction raises ``AuthError``.
158
+
159
+ ``children``
160
+ Dictionary mapping child display names to directed IDs.
161
+
162
+ ``time_zone``
163
+ IANA time-zone string used for bonus-time requests and weekday selection.
164
+
165
+ Public methods
166
+ --------------
167
+
168
+ ``get_children()``
169
+ Return the configured child names as a list. This does not discover
170
+ children from Amazon; it returns the names supplied in ``children``.
171
+
172
+ ``get_time_limits(child_name)``
173
+ Return the raw JSON response from the adjusted-time-limits endpoint.
174
+
175
+ ``get_max_screentime(child_name)``
176
+ Return today's enabled ``ALL`` content-time limit in minutes, or ``None``
177
+ when the relevant limits are disabled. Raises ``ParentDashboardError`` if
178
+ the expected response structure is missing.
179
+
180
+ ``add_bonus_time(child_name, minutes)``
181
+ Send a bonus-time configuration request. ``minutes`` must be positive.
182
+ Whether the endpoint adds to or replaces the current bonus has not been
183
+ confirmed.
184
+
185
+ ``pause_devices(child_name, minutes)``
186
+ Request that the child's devices be paused for the requested number of
187
+ minutes. ``minutes`` must be positive.
188
+
189
+ ``resume_devices(child_name)``
190
+ Request that the child's devices be resumed.
191
+
192
+ Exceptions
193
+ ----------
194
+
195
+ ``ParentDashboardError``
196
+ Base exception for client errors, unexpected responses, and HTTP failures.
197
+
198
+ ``AuthError``
199
+ Raised when cookies are missing/invalid, the request is redirected to
200
+ sign-in, or CSRF-token retrieval fails.
201
+
202
+ ``KeyError``
203
+ Raised by child lookup when the requested name is not in the configured
204
+ mapping.
205
+
206
+ ``ValueError``
207
+ Raised when a pause or bonus duration is not positive.
208
+
209
+ Running the module directly
210
+ ---------------------------
211
+
212
+ The module's ``__main__`` example expects a local ``config`` module containing
213
+ at least these attributes::
214
+
215
+ cookie_string
216
+ child_name_id_map
217
+ child_name
218
+
219
+ It also expects a ``helper`` module with a ``get_cookie_dict`` function.
220
+ The example reads the selected child's maximum screen-time limit. Adapt these
221
+ names to your own configuration, and do not commit real authentication
222
+ credentials to a repository.
223
+
224
+ Automation and server deployment
225
+ --------------------------------
226
+
227
+ For a scheduled job, keep your rule logic separate from the client. For
228
+ example::
229
+
230
+ from parent_dashboard import ParentDashboard
231
+
232
+ dashboard = ParentDashboard(
233
+ children=children,
234
+ time_zone="America/New_York",
235
+ )
236
+
237
+ child_name = "Lillianne"
238
+ max_minutes = dashboard.get_max_screentime(child_name)
239
+
240
+ # Insert your own decision logic here.
241
+ # Only call add_bonus_time after verifying the endpoint's semantics.
242
+
243
+ Run the script using a scheduler such as ``systemd`` or cron. In production:
244
+
245
+ * Store credentials in environment variables or a secrets manager.
246
+ * Run the job as a dedicated, unprivileged Linux user.
247
+ * Log decisions and outcomes, but never log cookies, CSRF tokens, or full
248
+ authentication headers.
249
+ * Handle ``AuthError`` and notify the administrator when manual sign-in is
250
+ required.
251
+ * Add persistent state or an idempotency record so a retried job does not
252
+ accidentally award the same bonus more than once.
253
+ * Test with a small, controlled bonus and verify the result in the Parent
254
+ Dashboard before enabling automatic awards.
255
+ * Do not assume that a browser session will remain valid indefinitely. This
256
+ client does not implement supported OAuth refresh-token handling.
257
+
258
+ Example error handling::
259
+
260
+ from parent_dashboard import AuthError, ParentDashboardError
261
+
262
+ try:
263
+ dashboard.add_bonus_time("Lillianne", 10)
264
+ except AuthError as exc:
265
+ # Notify yourself that the session needs attention.
266
+ print(f"Authentication needs attention: {exc}")
267
+ except ParentDashboardError as exc:
268
+ print(f"Parent Dashboard request failed: {exc}")
269
+
270
+ Troubleshooting
271
+ ---------------
272
+
273
+ **``AuthError: No cookies``**
274
+
275
+ Pass ``cookies=...`` or set ``AMAZON_PARENTS_COOKIES``.
276
+
277
+ **Redirected to sign-in / invalid cookies**
278
+
279
+ The session may have expired or been revoked. Obtain a fresh authenticated
280
+ session and update the configured cookies. The client cannot guarantee
281
+ automatic reauthentication.
282
+
283
+ **HTTP 401 or 403**
284
+
285
+ The session may be invalid or the endpoint may
286
+ require a different request format. For eligible read-only requests, the
287
+ client attempts one CSRF refresh and retry. It does not automatically retry
288
+ state-changing requests.
289
+
290
+ **Unexpected time-limit response**
291
+
292
+ Inspect the raw result of ``get_time_limits(child_name)``. The parser in
293
+ ``get_max_screentime`` expects ``periodConfigurations`` with a matching
294
+ ``DayOfWeek`` entry and an integer ``ALL`` value.
295
+
296
+ Limitations and disclaimer
297
+ --------------------------
298
+
299
+ This is an unofficial client built from browser network captures. It is not
300
+ an Amazon-supported SDK, and the endpoints are not guaranteed to remain
301
+ available or compatible. The client does not currently discover child IDs,
302
+ guarantee session renewal, or verify whether setting bonus time is additive.
303
+ Use it only with an account and devices you are authorized to manage, and
304
+ verify changes in the Parent Dashboard.
@@ -0,0 +1,357 @@
1
+ """Unofficial client for https://parents.amazon.com (Amazon Kids Parent Dashboard).
2
+
3
+ Built from a browser HAR capture. Endpoints confirmed by the HAR:
4
+
5
+ POST /ajax/set-offscreen-time pause / resume devices
6
+ PUT /ajax/set-bonus-time bonus time
7
+ GET /ajax/get-adjusted-time-limits?childDirectedId=...
8
+
9
+ NOT covered by the HAR (see TODOs below):
10
+ * how to list children / map names -> directed IDs
11
+ * the response shape of get-adjusted-time-limits
12
+ * where the x-amzn-csrf token comes from (best-effort scrape of the home page)
13
+
14
+ Auth: the HAR had its cookies stripped, so this takes your logged-in
15
+ amazon.com cookies from the constructor or the AMAZON_PARENTS_COOKIES env var
16
+ (either a JSON object or a "name=value; name=value" header string).
17
+ """
18
+
19
+ import json
20
+ import os
21
+ import re
22
+ from datetime import datetime, timezone
23
+ from typing import Dict, List, Optional, Union
24
+
25
+ import pytz
26
+ import requests
27
+
28
+ import config
29
+
30
+ BASE_URL = "https://parents.amazon.com"
31
+ USER_AGENT = (
32
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
33
+ "(KHTML, like Gecko) Chrome/155.0.0.0 Safari/537.36"
34
+ )
35
+
36
+ # Best-effort guesses for where the page embeds the CSRF token. Unverified.
37
+ _CSRF_PATTERNS = [
38
+ re.compile(r'"csrfToken"\s*:\s*"([^"]+)"'),
39
+ re.compile(r'"x-amzn-csrf"\s*:\s*"([^"]+)"', re.I),
40
+ re.compile(r'<meta[^>]+name=["\']x-amzn-csrf["\'][^>]+content=["\']([^"\']+)', re.I),
41
+ re.compile(r'<meta[^>]+name=["\']csrf-token["\'][^>]+content=["\']([^"\']+)', re.I),
42
+ re.compile(r'ft-panda-csrf-token=([^;\s,]+)', ) # this is the one we want
43
+ ]
44
+
45
+
46
+ class ParentDashboardError(Exception):
47
+ pass
48
+
49
+
50
+ class AuthError(ParentDashboardError):
51
+ pass
52
+
53
+
54
+ def _parse_cookies(raw: Union[str, Dict[str, str]]) -> Dict[str, str]:
55
+ if isinstance(raw, dict):
56
+ return raw
57
+ raw = raw.strip()
58
+ if raw.startswith("{"):
59
+ return json.loads(raw)
60
+ cookies = {}
61
+ for part in raw.split(";"):
62
+ if "=" in part:
63
+ k, v = part.split("=", 1)
64
+ cookies[k.strip()] = v.strip()
65
+ return cookies
66
+
67
+
68
+ class ParentDashboard:
69
+ def __init__(
70
+ self,
71
+ cookies: Optional[Union[str, Dict[str, str]]] = None,
72
+ csrf_token: Optional[str] = None, # this will be populated automatically on a failed request.
73
+ children: Optional[Dict[str, str]] = None,
74
+ time_zone: str = "America/New_York",
75
+ ):
76
+ """
77
+ :param cookies: logged-in amazon.com cookies (dict, JSON string, or
78
+ "k=v; k=v" string). Falls back to $AMAZON_PARENTS_COOKIES.
79
+ :param csrf_token: optional x-amzn-csrf value. If omitted, uses
80
+ $AMAZON_PARENTS_CSRF_TOKEN or scrapes it from the home page.
81
+ :param children: mapping of child name -> directed ID
82
+ (e.g. {"Alex": "amzn1.account.AEZ5..."}). The IDs appear in the
83
+ request bodies in the browser network tab.
84
+ :param time_zone: IANA zone sent with bonus-time requests.
85
+ """
86
+ raw = cookies or os.environ.get("AMAZON_PARENTS_COOKIES")
87
+ if not raw:
88
+ raise AuthError(
89
+ "No cookies. Pass cookies=... or set AMAZON_PARENTS_COOKIES."
90
+ )
91
+ self._session = requests.Session()
92
+ self._session.headers.update(
93
+ {
94
+ "user-agent": USER_AGENT,
95
+ "accept": "application/json, text/plain, */*",
96
+ "origin": BASE_URL,
97
+ "referer": BASE_URL + "/",
98
+ }
99
+ )
100
+ self._session.cookies.update(_parse_cookies(raw))
101
+ self._csrf = csrf_token or os.environ.get("AMAZON_PARENTS_CSRF_TOKEN")
102
+ if self._csrf:
103
+ self._session.cookies.set("ft-panda-csrf-token", self._csrf)
104
+ self._children: Optional[Dict[str, str]] = dict(children) if children else None
105
+ self._time_zone = time_zone
106
+
107
+ # ------------------------------------------------------------------ http
108
+
109
+ def discover_child_directed_ids(self) -> Dict[str, str]:
110
+ """Discover child names and directed IDs from the dashboard HTML."""
111
+
112
+ response = self._request(
113
+ method='get',
114
+ path=f"/",
115
+ )
116
+ response.raise_for_status()
117
+
118
+ if "signin" in response.url.lower():
119
+ raise AuthError("Cookies are expired or invalid.")
120
+
121
+ # Extract the JSON string passed to JSON.parse(...).
122
+ match = re.search(
123
+ r"window\.__PREFETCHED_HOUSEHOLD\s*=\s*"
124
+ r"JSON\.parse\('((?:\\.|[^'\\])*)'\)\s*;",
125
+ response.text,
126
+ )
127
+
128
+ if not match:
129
+ raise ParentDashboardError(
130
+ "Could not find __PREFETCHED_HOUSEHOLD in the dashboard HTML."
131
+ )
132
+
133
+ # The JavaScript string contains escaped JSON. Decode the string
134
+ # before parsing the resulting JSON object.
135
+ household_json = json.loads(f'"{match.group(1)}"')
136
+ household = json.loads(household_json)
137
+
138
+ children = {
139
+ member["firstName"]: member["directedId"]
140
+ for member in household.get("members", [])
141
+ if member.get("role") == "CHILD"
142
+ and member.get("firstName")
143
+ and member.get("directedId")
144
+ }
145
+
146
+ if not children:
147
+ raise ParentDashboardError(
148
+ "No children were found in the household data."
149
+ )
150
+
151
+
152
+ self._children = children
153
+ return children
154
+
155
+ def _refresh_csrf(self) -> None:
156
+ resp = self._session.get(BASE_URL + "/")
157
+ if "signin" in resp.url:
158
+ raise AuthError("Cookies are expired or invalid (redirected to sign-in).")
159
+ for pattern in _CSRF_PATTERNS:
160
+ m = pattern.search(str(resp.headers))
161
+ if m:
162
+ self._csrf = m.group(1)
163
+ self._session.cookies.set("ft-panda-csrf-token", self._csrf)
164
+ return
165
+ raise AuthError(
166
+ "Couldn't find the CSRF token in the home page. Copy the "
167
+ "'x-amzn-csrf' request header from your browser and pass "
168
+ "csrf_token=... instead."
169
+ )
170
+
171
+ def _request(self, method: str, path: str, *, params=None, body=None,
172
+ _retry: bool = True) -> requests.Response:
173
+ print('_request=', method, path, params, body)
174
+ if not self._csrf:
175
+ self._refresh_csrf()
176
+
177
+ resp = self._session.request(
178
+ method,
179
+ BASE_URL + path,
180
+ params=params,
181
+ json=body,
182
+ headers={"x-amzn-csrf": self._csrf},
183
+ )
184
+ print('139 resp.text=', resp.text)
185
+ if "signin" in resp.url:
186
+ raise AuthError("Cookies are expired or invalid (redirected to sign-in).")
187
+ if resp.status_code in (401, 403) and _retry and method.upper() in {
188
+ "GET", "HEAD", "OPTIONS"
189
+ }:
190
+ previous_csrf = self._csrf
191
+ try:
192
+ print('_request response=', resp.status_code, resp.text)
193
+ print('_request response.headers=', resp.headers)
194
+ self._refresh_csrf()
195
+ except AuthError as exc:
196
+ raise AuthError(
197
+ f"{method} {path} -> {resp.status_code}; "
198
+ f"CSRF refresh failed: {exc}; response: {resp.text[:200]}"
199
+ ) from exc
200
+ if self._csrf != previous_csrf:
201
+ return self._request(
202
+ method, path, params=params, body=body, _retry=False
203
+ )
204
+ if not resp.ok:
205
+ print('160 _request response=', resp.status_code, resp.text, resp.url)
206
+ print('161 _request response.headers=', resp.headers)
207
+ raise ParentDashboardError(
208
+ f"{method} {path} -> {resp.status_code}: {resp.text[:200]}"
209
+ )
210
+ return resp
211
+
212
+ # -------------------------------------------------------------- children
213
+
214
+ def _load_children(self) -> Dict[str, str]:
215
+ """Return child names mapped to their Amazon directed IDs."""
216
+ if self._children is None:
217
+ self.discover_child_directed_ids()
218
+ return self._children
219
+
220
+ def _child_id(self, child_name: str) -> str:
221
+ children = self._load_children()
222
+ for name, directed_id in children.items():
223
+ if name.lower() == child_name.lower():
224
+ return directed_id
225
+ raise KeyError(f"Unknown child {child_name!r}; known: {list(children)}")
226
+
227
+ # ------------------------------------------------------------ public API
228
+
229
+ def get_children(self) -> List[str]:
230
+ '''
231
+ Return the names of the children
232
+ :return:
233
+ '''
234
+ return list(self._load_children())
235
+
236
+ def add_bonus_time(self, child_name: str, minutes: int) -> None:
237
+ '''
238
+ Add bonus time to the child
239
+
240
+ Note: the endpoint is "set-bonus-time" and the UI row is labelled
241
+ "update bonus time", so this may SET the bonus to `minutes` rather
242
+ than add to an existing bonus. Verify by calling it twice.
243
+ :param child_name:
244
+ :param minutes:
245
+ :return: bool response.ok
246
+ '''
247
+ if minutes <= 0:
248
+ raise ValueError("minutes must be positive")
249
+ resp = self._request(
250
+ "PUT",
251
+ "/ajax/set-bonus-time",
252
+ body={
253
+ "childDirectedId": self._child_id(child_name),
254
+ "currentTime": datetime.now(timezone.utc).strftime(
255
+ "%Y-%m-%dT%H:%M:%S.%f"
256
+ )[:-3] + "Z",
257
+ "currentTimeZone": self._time_zone,
258
+ "bonusTimeConfigurationType": "Duration",
259
+ "bonusTimeDurationInMinutes": int(minutes),
260
+ },
261
+ )
262
+ return resp.ok
263
+
264
+ def get_time_limits(self, child_name: str) -> dict:
265
+ """Raw JSON from /ajax/get-adjusted-time-limits (shape not in the HAR)."""
266
+ resp = self._request(
267
+ "GET",
268
+ "/ajax/get-adjusted-time-limits",
269
+ params={"childDirectedId": self._child_id(child_name)},
270
+ )
271
+ return resp.json()
272
+
273
+ def get_max_screentime(self, child_name: str) -> Optional[int]:
274
+ '''
275
+ Get today's screentime limit in minutes, or None if the child has no
276
+ time limit enabled for today.
277
+
278
+ Response shape (confirmed from a capture): {"periodConfigurations":
279
+ [{"type": "DayOfWeek", "name": "Monday", "enabled": bool,
280
+ "timeLimits": {"contentTimeLimitsEnabled": bool,
281
+ "contentTimeLimits": {...}}, ...}, ...]}
282
+ :param child_name:
283
+ :return:
284
+ '''
285
+ data = self.get_time_limits(child_name)
286
+ weekday = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
287
+ "Saturday", "Sunday"][datetime.now(pytz.timezone(self._time_zone)).weekday()]
288
+ day = next(
289
+ (p for p in data.get("periodConfigurations", [])
290
+ if p.get("type") == "DayOfWeek" and p.get("name") == weekday),
291
+ None,
292
+ )
293
+ if day is None:
294
+ raise ParentDashboardError(f"No {weekday} entry in time-limit response")
295
+ limits = day.get("timeLimits", {})
296
+ if not day.get("enabled") or not limits.get("contentTimeLimitsEnabled"):
297
+ return None
298
+ content_time_limits = limits.get("contentTimeLimits", {})
299
+ total_minutes = content_time_limits.get("ALL")
300
+ if not isinstance(total_minutes, int) or isinstance(total_minutes, bool):
301
+ raise ParentDashboardError(
302
+ "Enabled content time limits are missing an integer 'ALL' value: "
303
+ f"{content_time_limits!r}"
304
+ )
305
+ return total_minutes
306
+
307
+ def pause_devices(self, child_name: str, minutes: int) -> None:
308
+ '''
309
+ Pause/lock the devices for the child
310
+
311
+ The HAR only shows 60 minutes (the UI picks from a dropdown); other
312
+ durations are untested.
313
+ :param child_name:
314
+ :param minutes:
315
+ :return:
316
+ '''
317
+ if minutes <= 0:
318
+ raise ValueError("minutes must be positive")
319
+ self._request(
320
+ "POST",
321
+ "/ajax/set-offscreen-time",
322
+ body={
323
+ "directedIds": [self._child_id(child_name)],
324
+ "expirationTimeInSeconds": int(minutes) * 60,
325
+ },
326
+ )
327
+
328
+ def resume_devices(self, child_name: str):
329
+ '''
330
+ Resume/unlock the devices for the child
331
+ :param child_name:
332
+ :return:
333
+ '''
334
+ self._request(
335
+ "POST",
336
+ "/ajax/set-offscreen-time",
337
+ body={
338
+ "directedIds": [self._child_id(child_name)],
339
+ "expirationTimeInSeconds": -1,
340
+ },
341
+ )
342
+
343
+
344
+ if __name__ == "__main__":
345
+ pd = ParentDashboard(
346
+ cookies=config.cookie_string,
347
+ )
348
+
349
+ child_name = config.child_name
350
+ max_time = pd.get_max_screentime(child_name)
351
+ print(f'Max time for {child_name} is {max_time}')
352
+
353
+ is_success = pd.add_bonus_time(child_name, 15)
354
+ print(f'{"successfully" if is_success else "failed"} added bonus time to {child_name}')
355
+
356
+ max_time = pd.get_max_screentime(child_name)
357
+ print(f'Max time for {child_name} is {max_time}')
@@ -0,0 +1,318 @@
1
+ Metadata-Version: 2.1
2
+ Name: amazon_parent_dashboard
3
+ Version: 1.0.0
4
+ Summary: A simple interface to add bonus time to your Amazon Parent Dashboard devices
5
+ Home-page: https://github.com/GrantGMiller/amazon_parent_dashboard
6
+ Author: Grant miller
7
+ Author-email: grant@grant-miller.com
8
+ License: PSF
9
+ Project-URL: Source Code, https://github.com/GrantGMiller/amazon_parent_dashboard
10
+ Keywords: grant miller familylink bonus time
11
+ Description-Content-Type: text/x-rst
12
+ Requires-Dist: requests
13
+ Requires-Dist: pytz
14
+
15
+ Amazon Kids Parent Dashboard Client
16
+ ====================================
17
+
18
+ Overview
19
+ --------
20
+
21
+ This module provides an unofficial Python client for the Amazon Kids Parent
22
+ Dashboard at https://parents.amazon.com.
23
+
24
+ It was developed from browser network captures and uses Parent Dashboard
25
+ endpoints that are not documented as a public API. Amazon may change these
26
+ endpoints, request formats, authentication requirements, or response formats
27
+ at any time.
28
+
29
+ The client currently supports:
30
+
31
+ * Retrieve the children names.
32
+ * Reading a child's time-limit configuration.
33
+ * Getting the current day's maximum screen-time limit.
34
+ * Adding bonus time for a child.
35
+ * Pausing and resuming a child's devices.
36
+
37
+ Authentication requires you to copy cookies from an already authenticated Amazon session. This
38
+ module does not implement an OAuth flow or guarantee automatic renewal of an
39
+ expired session.
40
+
41
+ Requirements/Dependencies
42
+ -------------------------
43
+
44
+ * Python 3.8 or newer.
45
+ * ``requests``
46
+ * ``pytz``
47
+
48
+
49
+ Authentication
50
+ --------------
51
+
52
+ The constructor accepts cookies in one of three formats:
53
+
54
+ #. A Python dictionary mapping cookie names to cookie values.
55
+ #. A JSON string representing that dictionary.
56
+ #. A cookie-header string containing semicolon-separated ``name=value`` pairs.
57
+
58
+ You can pass cookies directly::
59
+
60
+ from parent_dashboard import ParentDashboard
61
+
62
+ cookies = {
63
+ "session-id": "YOUR_SESSION_ID",
64
+ "session-token": "YOUR_SESSION_TOKEN",
65
+ # Add the other cookies required by your current session.
66
+ }
67
+
68
+ dashboard = ParentDashboard(
69
+ cookies=cookies,
70
+ children={str(child_name): str(child_id)}
71
+ )
72
+
73
+ The example cookie names above are illustrative. Copy the required cookie
74
+ pairs from your own authenticated browser session; the exact set may vary.
75
+
76
+ **Keep cookies secret.** Treat them like passwords: do not
77
+ commit them to source control, include them in bug reports, or print them in
78
+ logs. Use environment variables or a secrets manager and restrict access to
79
+ the server account running the client.
80
+
81
+ Configuring children
82
+ --------------------
83
+
84
+ The client does not automatically discover children names. Pass a mapping from each
85
+ child's display name to their Amazon ``childDirectedId`` / directed ID::
86
+
87
+ children = {
88
+ "Forrest": "amzn1.account.REPLACE_WITH_DIRECTED_ID",
89
+ "Jenny": "amzn1.account.REPLACE_WITH_DIRECTED_ID",
90
+ }
91
+
92
+ dashboard = ParentDashboard(
93
+ cookies=cookies,
94
+ children=children,
95
+ )
96
+
97
+ Replace each placeholder with the actual directed ID for that child. The
98
+ client matches names case-insensitively, but the supplied name must otherwise
99
+ match one of the configured names. If ``children`` is omitted, calls that
100
+ require a child will raise ``ParentDashboardError``.
101
+
102
+ Basic usage
103
+ -----------
104
+
105
+ Get configured child names::
106
+
107
+ print(dashboard.get_children())
108
+
109
+ Read the raw time-limit response for a child::
110
+
111
+ limits = dashboard.get_time_limits("Lillianne")
112
+ print(limits)
113
+
114
+ Get the current day's screen-time limit in minutes::
115
+
116
+ max_minutes = dashboard.get_max_screentime("Lillianne")
117
+ if max_minutes is None:
118
+ print("No enabled screen-time limit was found for today.")
119
+ else:
120
+ print(f"Today's limit: {max_minutes} minutes")
121
+
122
+ ``get_max_screentime`` uses the configured time zone to choose the current
123
+ weekday. It returns ``None`` when the day's schedule or content-time limits
124
+ are disabled. Otherwise it expects the response to contain an integer value
125
+ under ``timeLimits.contentTimeLimits.ALL`` for the matching day.
126
+
127
+ Set bonus time::
128
+
129
+ dashboard.add_bonus_time("Lillianne", 15)
130
+
131
+ The current implementation sends a ``PUT`` request to
132
+ ``/ajax/set-bonus-time`` with a duration of 15 minutes. **Despite the method
133
+ name, it is not yet confirmed whether the endpoint adds 15 minutes to an
134
+ existing bonus or sets the bonus duration to 15 minutes.** Verify this behavior
135
+ carefully in the Parent Dashboard before relying on repeated calls. Do not
136
+ assume calling it twice safely adds twice as much time.
137
+
138
+ Pause devices for a child for 60 minutes::
139
+
140
+ dashboard.pause_devices("Rosalie", 60)
141
+
142
+ Resume devices for a child::
143
+
144
+ dashboard.resume_devices("Rosalie")
145
+
146
+
147
+
148
+ Time zone
149
+ ---------
150
+
151
+ The constructor defaults to ``America/New_York``::
152
+
153
+ dashboard = ParentDashboard(
154
+ cookies=cookies,
155
+ children=children,
156
+ time_zone="America/New_York",
157
+ )
158
+
159
+ The time zone is sent with bonus-time requests and is also used to determine
160
+ the weekday in ``get_max_screentime``. Use an appropriate IANA time-zone name
161
+ for your household.
162
+
163
+ Constructor reference
164
+ ---------------------
165
+
166
+ ``ParentDashboard(cookies=None, children=None, time_zone="America/New_York")``
167
+
168
+ ``cookies``
169
+ Cookie dictionary, JSON string, or semicolon-separated cookie-header
170
+ string. If omitted, the client reads ``AMAZON_PARENTS_COOKIES``. If neither
171
+ is set, construction raises ``AuthError``.
172
+
173
+ ``children``
174
+ Dictionary mapping child display names to directed IDs.
175
+
176
+ ``time_zone``
177
+ IANA time-zone string used for bonus-time requests and weekday selection.
178
+
179
+ Public methods
180
+ --------------
181
+
182
+ ``get_children()``
183
+ Return the configured child names as a list. This does not discover
184
+ children from Amazon; it returns the names supplied in ``children``.
185
+
186
+ ``get_time_limits(child_name)``
187
+ Return the raw JSON response from the adjusted-time-limits endpoint.
188
+
189
+ ``get_max_screentime(child_name)``
190
+ Return today's enabled ``ALL`` content-time limit in minutes, or ``None``
191
+ when the relevant limits are disabled. Raises ``ParentDashboardError`` if
192
+ the expected response structure is missing.
193
+
194
+ ``add_bonus_time(child_name, minutes)``
195
+ Send a bonus-time configuration request. ``minutes`` must be positive.
196
+ Whether the endpoint adds to or replaces the current bonus has not been
197
+ confirmed.
198
+
199
+ ``pause_devices(child_name, minutes)``
200
+ Request that the child's devices be paused for the requested number of
201
+ minutes. ``minutes`` must be positive.
202
+
203
+ ``resume_devices(child_name)``
204
+ Request that the child's devices be resumed.
205
+
206
+ Exceptions
207
+ ----------
208
+
209
+ ``ParentDashboardError``
210
+ Base exception for client errors, unexpected responses, and HTTP failures.
211
+
212
+ ``AuthError``
213
+ Raised when cookies are missing/invalid, the request is redirected to
214
+ sign-in, or CSRF-token retrieval fails.
215
+
216
+ ``KeyError``
217
+ Raised by child lookup when the requested name is not in the configured
218
+ mapping.
219
+
220
+ ``ValueError``
221
+ Raised when a pause or bonus duration is not positive.
222
+
223
+ Running the module directly
224
+ ---------------------------
225
+
226
+ The module's ``__main__`` example expects a local ``config`` module containing
227
+ at least these attributes::
228
+
229
+ cookie_string
230
+ child_name_id_map
231
+ child_name
232
+
233
+ It also expects a ``helper`` module with a ``get_cookie_dict`` function.
234
+ The example reads the selected child's maximum screen-time limit. Adapt these
235
+ names to your own configuration, and do not commit real authentication
236
+ credentials to a repository.
237
+
238
+ Automation and server deployment
239
+ --------------------------------
240
+
241
+ For a scheduled job, keep your rule logic separate from the client. For
242
+ example::
243
+
244
+ from parent_dashboard import ParentDashboard
245
+
246
+ dashboard = ParentDashboard(
247
+ children=children,
248
+ time_zone="America/New_York",
249
+ )
250
+
251
+ child_name = "Lillianne"
252
+ max_minutes = dashboard.get_max_screentime(child_name)
253
+
254
+ # Insert your own decision logic here.
255
+ # Only call add_bonus_time after verifying the endpoint's semantics.
256
+
257
+ Run the script using a scheduler such as ``systemd`` or cron. In production:
258
+
259
+ * Store credentials in environment variables or a secrets manager.
260
+ * Run the job as a dedicated, unprivileged Linux user.
261
+ * Log decisions and outcomes, but never log cookies, CSRF tokens, or full
262
+ authentication headers.
263
+ * Handle ``AuthError`` and notify the administrator when manual sign-in is
264
+ required.
265
+ * Add persistent state or an idempotency record so a retried job does not
266
+ accidentally award the same bonus more than once.
267
+ * Test with a small, controlled bonus and verify the result in the Parent
268
+ Dashboard before enabling automatic awards.
269
+ * Do not assume that a browser session will remain valid indefinitely. This
270
+ client does not implement supported OAuth refresh-token handling.
271
+
272
+ Example error handling::
273
+
274
+ from parent_dashboard import AuthError, ParentDashboardError
275
+
276
+ try:
277
+ dashboard.add_bonus_time("Lillianne", 10)
278
+ except AuthError as exc:
279
+ # Notify yourself that the session needs attention.
280
+ print(f"Authentication needs attention: {exc}")
281
+ except ParentDashboardError as exc:
282
+ print(f"Parent Dashboard request failed: {exc}")
283
+
284
+ Troubleshooting
285
+ ---------------
286
+
287
+ **``AuthError: No cookies``**
288
+
289
+ Pass ``cookies=...`` or set ``AMAZON_PARENTS_COOKIES``.
290
+
291
+ **Redirected to sign-in / invalid cookies**
292
+
293
+ The session may have expired or been revoked. Obtain a fresh authenticated
294
+ session and update the configured cookies. The client cannot guarantee
295
+ automatic reauthentication.
296
+
297
+ **HTTP 401 or 403**
298
+
299
+ The session may be invalid or the endpoint may
300
+ require a different request format. For eligible read-only requests, the
301
+ client attempts one CSRF refresh and retry. It does not automatically retry
302
+ state-changing requests.
303
+
304
+ **Unexpected time-limit response**
305
+
306
+ Inspect the raw result of ``get_time_limits(child_name)``. The parser in
307
+ ``get_max_screentime`` expects ``periodConfigurations`` with a matching
308
+ ``DayOfWeek`` entry and an integer ``ALL`` value.
309
+
310
+ Limitations and disclaimer
311
+ --------------------------
312
+
313
+ This is an unofficial client built from browser network captures. It is not
314
+ an Amazon-supported SDK, and the endpoints are not guaranteed to remain
315
+ available or compatible. The client does not currently discover child IDs,
316
+ guarantee session renewal, or verify whether setting bonus time is additive.
317
+ Use it only with an account and devices you are authorized to manage, and
318
+ verify changes in the Parent Dashboard.
@@ -0,0 +1,8 @@
1
+ README.rst
2
+ setup.py
3
+ amazon_parent_dashboard/__init__.py
4
+ amazon_parent_dashboard.egg-info/PKG-INFO
5
+ amazon_parent_dashboard.egg-info/SOURCES.txt
6
+ amazon_parent_dashboard.egg-info/dependency_links.txt
7
+ amazon_parent_dashboard.egg-info/requires.txt
8
+ amazon_parent_dashboard.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ amazon_parent_dashboard
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,30 @@
1
+ from os import path
2
+
3
+ from setuptools import setup
4
+
5
+ this_directory = path.abspath(path.dirname(__file__))
6
+ with open(path.join(this_directory, 'README.rst'), encoding='utf-8') as f:
7
+ long_description = f.read()
8
+
9
+ packages = ['amazon_parent_dashboard']
10
+
11
+ setup(
12
+ name="amazon_parent_dashboard",
13
+ version="1.0.0", # works on my machine
14
+ packages=packages,
15
+ install_requires=[
16
+ "requests",
17
+ 'pytz'
18
+ ],
19
+ author="Grant miller",
20
+ author_email="grant@grant-miller.com",
21
+ description="A simple interface to add bonus time to your Amazon Parent Dashboard devices",
22
+ long_description=long_description,
23
+ long_description_content_type="text/x-rst",
24
+ license="PSF",
25
+ keywords="grant miller familylink bonus time",
26
+ url="https://github.com/GrantGMiller/amazon_parent_dashboard", # project home page, if any
27
+ project_urls={
28
+ "Source Code": "https://github.com/GrantGMiller/amazon_parent_dashboard",
29
+ },
30
+ )