scratchattach 3.0.0b0__py3-none-any.whl → 3.0.0b2__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.
Files changed (80) hide show
  1. scratchattach/cli/__about__.py +1 -0
  2. scratchattach/cli/__init__.py +26 -0
  3. scratchattach/cli/cmd/__init__.py +4 -0
  4. scratchattach/cli/cmd/group.py +127 -0
  5. scratchattach/cli/cmd/login.py +60 -0
  6. scratchattach/cli/cmd/profile.py +7 -0
  7. scratchattach/cli/cmd/sessions.py +5 -0
  8. scratchattach/cli/context.py +142 -0
  9. scratchattach/cli/db.py +66 -0
  10. scratchattach/cli/namespace.py +14 -0
  11. scratchattach/cloud/__init__.py +2 -0
  12. scratchattach/cloud/_base.py +483 -0
  13. scratchattach/cloud/cloud.py +183 -0
  14. scratchattach/editor/__init__.py +22 -0
  15. scratchattach/editor/asset.py +265 -0
  16. scratchattach/editor/backpack_json.py +115 -0
  17. scratchattach/editor/base.py +191 -0
  18. scratchattach/editor/block.py +584 -0
  19. scratchattach/editor/blockshape.py +357 -0
  20. scratchattach/editor/build_defaulting.py +51 -0
  21. scratchattach/editor/code_translation/__init__.py +0 -0
  22. scratchattach/editor/code_translation/parse.py +177 -0
  23. scratchattach/editor/comment.py +80 -0
  24. scratchattach/editor/commons.py +145 -0
  25. scratchattach/editor/extension.py +50 -0
  26. scratchattach/editor/field.py +99 -0
  27. scratchattach/editor/inputs.py +138 -0
  28. scratchattach/editor/meta.py +117 -0
  29. scratchattach/editor/monitor.py +185 -0
  30. scratchattach/editor/mutation.py +381 -0
  31. scratchattach/editor/pallete.py +88 -0
  32. scratchattach/editor/prim.py +174 -0
  33. scratchattach/editor/project.py +381 -0
  34. scratchattach/editor/sprite.py +609 -0
  35. scratchattach/editor/twconfig.py +114 -0
  36. scratchattach/editor/vlb.py +134 -0
  37. scratchattach/eventhandlers/__init__.py +0 -0
  38. scratchattach/eventhandlers/_base.py +101 -0
  39. scratchattach/eventhandlers/cloud_events.py +130 -0
  40. scratchattach/eventhandlers/cloud_recorder.py +26 -0
  41. scratchattach/eventhandlers/cloud_requests.py +544 -0
  42. scratchattach/eventhandlers/cloud_server.py +249 -0
  43. scratchattach/eventhandlers/cloud_storage.py +135 -0
  44. scratchattach/eventhandlers/combine.py +30 -0
  45. scratchattach/eventhandlers/filterbot.py +163 -0
  46. scratchattach/eventhandlers/message_events.py +42 -0
  47. scratchattach/other/__init__.py +0 -0
  48. scratchattach/other/other_apis.py +598 -0
  49. scratchattach/other/project_json_capabilities.py +475 -0
  50. scratchattach/site/__init__.py +0 -0
  51. scratchattach/site/_base.py +93 -0
  52. scratchattach/site/activity.py +426 -0
  53. scratchattach/site/alert.py +226 -0
  54. scratchattach/site/backpack_asset.py +119 -0
  55. scratchattach/site/browser_cookie3_stub.py +17 -0
  56. scratchattach/site/browser_cookies.py +61 -0
  57. scratchattach/site/classroom.py +454 -0
  58. scratchattach/site/cloud_activity.py +121 -0
  59. scratchattach/site/comment.py +228 -0
  60. scratchattach/site/forum.py +436 -0
  61. scratchattach/site/placeholder.py +132 -0
  62. scratchattach/site/project.py +932 -0
  63. scratchattach/site/session.py +1323 -0
  64. scratchattach/site/studio.py +704 -0
  65. scratchattach/site/typed_dicts.py +151 -0
  66. scratchattach/site/user.py +1252 -0
  67. scratchattach/utils/__init__.py +0 -0
  68. scratchattach/utils/commons.py +263 -0
  69. scratchattach/utils/encoder.py +161 -0
  70. scratchattach/utils/enums.py +237 -0
  71. scratchattach/utils/exceptions.py +277 -0
  72. scratchattach/utils/optional_async.py +154 -0
  73. scratchattach/utils/requests.py +306 -0
  74. {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b2.dist-info}/METADATA +1 -1
  75. scratchattach-3.0.0b2.dist-info/RECORD +81 -0
  76. scratchattach-3.0.0b0.dist-info/RECORD +0 -8
  77. {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b2.dist-info}/WHEEL +0 -0
  78. {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b2.dist-info}/entry_points.txt +0 -0
  79. {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b2.dist-info}/licenses/LICENSE +0 -0
  80. {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,598 @@
1
+ """Other Scratch API-related functions"""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass, field
5
+ from typing import Any
6
+ import json
7
+ import dataclasses
8
+
9
+ from scratchattach.site import project, studio, session
10
+ from scratchattach.utils import commons
11
+ from scratchattach.utils import exceptions
12
+ from scratchattach.utils.commons import parse_object_list
13
+ from scratchattach.utils.enums import Languages, Language, TTSVoices, TTSVoice
14
+ from scratchattach.utils.exceptions import BadRequest, InvalidLanguage, InvalidTTSGender
15
+ from scratchattach.utils.requests import requests
16
+ from typing import Optional, TypedDict
17
+
18
+
19
+ # --- Front page ---
20
+
21
+ class FeaturedDataRaw(TypedDict):
22
+ community_newest_projects: list[dict[str, str | int]]
23
+ community_most_remixed_projects: list[dict[str, str | int]]
24
+ scratch_design_studio: list[dict[str, str | int]]
25
+ curator_top_projects: list[dict[str, str | int]]
26
+ community_featured_studios: list[dict[str, str | int]]
27
+ community_most_loved_projects: list[dict[str, str | int]]
28
+ community_featured_projects: list[dict[str, str | int]]
29
+
30
+ class FeaturedData(TypedDict):
31
+ community_newest_projects: list[project.Project]
32
+ community_most_remixed_projects: list[project.Project]
33
+ scratch_design_studio: list[studio.Studio]
34
+ curator_top_projects: list[project.Project]
35
+ community_featured_studios: list[studio.Studio]
36
+ community_most_loved_projects: list[project.Project]
37
+ community_featured_projects: list[project.Project]
38
+
39
+ def get_news(*, limit=10, offset=0):
40
+ return commons.api_iterative("https://api.scratch.mit.edu/news", limit=limit, offset=offset)
41
+
42
+ def get_featured_data(sess: Optional[session.Session] = None) -> FeaturedData:
43
+ data: FeaturedDataRaw = requests.get("https://api.scratch.mit.edu/proxy/featured").json()
44
+
45
+ return {
46
+ "community_newest_projects": parse_object_list(data["community_newest_projects"], project.Project, sess),
47
+ "community_most_remixed_projects": parse_object_list(data["community_most_remixed_projects"], project.Project, sess),
48
+ "scratch_design_studio": parse_object_list(data["scratch_design_studio"], studio.Studio, sess),
49
+ "curator_top_projects": parse_object_list(data["curator_top_projects"], project.Project, sess),
50
+ "community_featured_studios": parse_object_list(data["community_featured_studios"], studio.Studio, sess),
51
+ "community_most_loved_projects": parse_object_list(data["community_most_loved_projects"], project.Project, sess),
52
+ "community_featured_projects": parse_object_list(data["community_featured_projects"], project.Project, sess)
53
+ }
54
+
55
+
56
+ def featured_projects():
57
+ return get_featured_data()["community_featured_projects"]
58
+
59
+
60
+ def featured_studios():
61
+ return get_featured_data()["community_featured_studios"]
62
+
63
+
64
+ def top_loved():
65
+ return get_featured_data()["community_most_loved_projects"]
66
+
67
+
68
+ def top_remixed():
69
+ return get_featured_data()["community_most_remixed_projects"]
70
+
71
+
72
+ def newest_projects():
73
+ return get_featured_data()["community_newest_projects"]
74
+
75
+
76
+ def curated_projects():
77
+ return get_featured_data()["curator_top_projects"]
78
+
79
+
80
+ def design_studio_projects():
81
+ return get_featured_data()["scratch_design_studio"]
82
+
83
+
84
+ # --- Statistics ---
85
+
86
+ class TotalSiteStats(TypedDict):
87
+ PROJECT_COUNT: int
88
+ USER_COUNT: int
89
+ STUDIO_COMMENT_COUNT: int
90
+ PROFILE_COMMENT_COUNT: int
91
+ STUDIO_COUNT: int
92
+ COMMENT_COUNT: int
93
+ PROJECT_COMMENT_COUNT: int
94
+
95
+
96
+ def total_site_stats() -> TotalSiteStats:
97
+ data = requests.get("https://scratch.mit.edu/statistics/data/daily/").json()
98
+ data.pop("_TS")
99
+ return data
100
+
101
+
102
+ class MonthlySiteTraffic(TypedDict):
103
+ pageviews: str
104
+ users: str
105
+ sessions: str
106
+
107
+ class CloudStatusRedis(TypedDict):
108
+ connected: bool
109
+ ready: bool
110
+
111
+ @dataclass
112
+ class CloudStatus:
113
+ is_online: bool
114
+ _raw: Any = field(repr=False, default=None)
115
+
116
+ _: dataclasses.KW_ONLY
117
+ uptime: Optional[float] = None
118
+ load: Optional[list[float]] = None
119
+ redis: Optional[CloudStatusRedis] = None
120
+
121
+ def monthly_site_traffic() -> MonthlySiteTraffic:
122
+ data = requests.get("https://scratch.mit.edu/statistics/data/monthly-ga/").json()
123
+ data.pop("_TS")
124
+ return data
125
+
126
+ def get_cloud_status() -> CloudStatus:
127
+ with requests.no_error_handling():
128
+ try:
129
+ resp = requests.get("https://clouddata.scratch.mit.edu/health", timeout=5)
130
+ if resp.status_code != 200:
131
+ return CloudStatus(False, resp.content)
132
+ try:
133
+ data = resp.json()
134
+ return CloudStatus(True, resp.content,
135
+ uptime=data["uptime"],
136
+ load=data["load"],
137
+ redis=data["redis"])
138
+
139
+ except json.JSONDecodeError:
140
+ return CloudStatus(True, resp.content)
141
+ except exceptions.FetchError:
142
+ return CloudStatus(False)
143
+
144
+ type CountryCounts = TypedDict("CountryCounts", {
145
+ '0': int, # not sure what 0 is. maybe it's the 'other' category
146
+ 'AT': int,
147
+ 'Afghanistan': int,
148
+ 'Aland Islands': int,
149
+ 'Albania': int,
150
+ 'Algeria': int,
151
+ 'American Samoa': int,
152
+ 'Andorra': int,
153
+ 'Angola': int,
154
+ 'Anguilla': int,
155
+ 'Antigua and Barbuda': int,
156
+ 'Argentina': int,
157
+ 'Armenia': int,
158
+ 'Aruba': int,
159
+ 'Australia': int,
160
+ 'Austria': int,
161
+ 'Azerbaijan': int,
162
+ 'Bahamas': int,
163
+ 'Bahrain': int,
164
+ 'Bangladesh': int,
165
+ 'Barbados': int,
166
+ 'Belarus': int,
167
+ 'Belgium': int,
168
+ 'Belize': int,
169
+ 'Benin': int,
170
+ 'Bermuda': int,
171
+ 'Bhutan': int,
172
+ 'Bolivia': int,
173
+ 'Bonaire, Sint Eustatius and Saba': int,
174
+ 'Bosnia and Herzegovina': int,
175
+ 'Botswana': int,
176
+ 'Bouvet Island': int,
177
+ 'Brazil': int,
178
+ 'British Indian Ocean Territory': int,
179
+ 'Brunei': int,
180
+ 'Brunei Darussalam': int,
181
+ 'Bulgaria': int,
182
+ 'Burkina Faso': int,
183
+ 'Burundi': int,
184
+ 'CA': int,
185
+ 'Cambodia': int,
186
+ 'Cameroon': int,
187
+ 'Canada': int,
188
+ 'Cape Verde': int,
189
+ 'Cayman Islands': int,
190
+ 'Central African Republic': int,
191
+ 'Chad': int,
192
+ 'Chile': int,
193
+ 'China': int,
194
+ 'Christmas Island': int,
195
+ 'Cocos (Keeling) Islands': int,
196
+ 'Colombia': int,
197
+ 'Comoros': int,
198
+ 'Congo': int,
199
+ 'Congo, Dem. Rep. of The': int,
200
+ 'Congo, The Democratic Republic of The': int,
201
+ 'Cook Islands': int,
202
+ 'Costa Rica': int,
203
+ "Cote D'ivoire": int,
204
+ 'Croatia': int,
205
+ 'Cuba': int,
206
+ 'Curacao': int,
207
+ 'Cyprus': int,
208
+ 'Czech Republic': int,
209
+ 'Denmark': int,
210
+ 'Djibouti': int,
211
+ 'Dominica': int,
212
+ 'Dominican Republic': int,
213
+ 'Ecuador': int,
214
+ 'Egypt': int,
215
+ 'El Salvador': int,
216
+ 'England': int,
217
+ 'Equatorial Guinea': int,
218
+ 'Eritrea': int,
219
+ 'Estonia': int,
220
+ 'Ethiopia': int,
221
+ 'Falkland Islands (Malvinas)': int,
222
+ 'Faroe Islands': int,
223
+ 'Fiji': int,
224
+ 'Finland': int,
225
+ 'France': int,
226
+ 'French Guiana': int,
227
+ 'French Polynesia': int,
228
+ 'French Southern Territories': int,
229
+ 'GB': int,
230
+ 'GG': int,
231
+ 'Gabon': int,
232
+ 'Gambia': int,
233
+ 'Georgia': int,
234
+ 'Germany': int,
235
+ 'Ghana': int,
236
+ 'Gibraltar': int,
237
+ 'Greece': int,
238
+ 'Greenland': int,
239
+ 'Grenada': int,
240
+ 'Guadeloupe': int,
241
+ 'Guam': int,
242
+ 'Guatemala': int,
243
+ 'Guernsey': int,
244
+ 'Guinea': int,
245
+ 'Guinea-Bissau': int,
246
+ 'Guyana': int,
247
+ 'Haiti': int,
248
+ 'Heard Island and Mcdonald Islands': int,
249
+ 'Holy See (Vatican City State)': int,
250
+ 'Honduras': int,
251
+ 'Hong Kong': int,
252
+ 'Hungary': int,
253
+ 'IT': int,
254
+ 'Iceland': int,
255
+ 'India': int,
256
+ 'Indonesia': int,
257
+ 'Iran': int,
258
+ 'Iran, Islamic Republic of': int,
259
+ 'Iraq': int,
260
+ 'Ireland': int,
261
+ 'Isle of Man': int,
262
+ 'Israel': int,
263
+ 'Italy': int,
264
+ 'Jamaica': int,
265
+ 'Japan': int,
266
+ 'Jersey': int,
267
+ 'Jordan': int,
268
+ 'Kazakhstan': int,
269
+ 'Kenya': int,
270
+ 'Kiribati': int,
271
+ "Korea, Dem. People's Rep.": int,
272
+ "Korea, Democratic People's Republic of": int,
273
+ 'Korea, Republic of': int,
274
+ 'Kosovo': int,
275
+ 'Kuwait': int,
276
+ 'Kyrgyzstan': int,
277
+ 'Laos': int,
278
+ 'Latvia': int,
279
+ 'Lebanon': int,
280
+ 'Lesotho': int,
281
+ 'Liberia': int,
282
+ 'Libya': int,
283
+ 'Libyan Arab Jamahiriya': int,
284
+ 'Liechtenstein': int,
285
+ 'Lithuania': int,
286
+ 'Location not given': int,
287
+ 'Luxembourg': int,
288
+ 'Macao': int,
289
+ 'Macedonia': int,
290
+ 'Macedonia, The Former Yugoslav Republic of': int,
291
+ 'Madagascar': int,
292
+ 'Malawi': int,
293
+ 'Malaysia': int,
294
+ 'Maldives': int,
295
+ 'Mali': int,
296
+ 'Malta': int,
297
+ 'Marshall Islands': int,
298
+ 'Martinique': int,
299
+ 'Mauritania': int,
300
+ 'Mauritius': int,
301
+ 'Mayotte': int,
302
+ 'Mexico': int,
303
+ 'Micronesia, Federated States of': int,
304
+ 'Moldova': int,
305
+ 'Moldova, Republic of': int,
306
+ 'Monaco': int,
307
+ 'Mongolia': int,
308
+ 'Montenegro': int,
309
+ 'Montserrat': int,
310
+ 'Morocco': int,
311
+ 'Mozambique': int,
312
+ 'Myanmar': int,
313
+ 'NO': int,
314
+ 'Namibia': int,
315
+ 'Nauru': int,
316
+ 'Nepal': int,
317
+ 'Netherlands': int,
318
+ 'Netherlands Antilles': int,
319
+ 'New Caledonia': int,
320
+ 'New Zealand': int,
321
+ 'Nicaragua': int,
322
+ 'Niger': int,
323
+ 'Nigeria': int,
324
+ 'Niue': int,
325
+ 'Norfolk Island': int,
326
+ 'North Korea': int,
327
+ 'Northern Mariana Islands': int,
328
+ 'Norway': int,
329
+ 'Oman': int,
330
+ 'Pakistan': int,
331
+ 'Palau': int,
332
+ 'Palestine': int,
333
+ 'Palestine, State of': int,
334
+ 'Palestinian Territory, Occupied': int,
335
+ 'Panama': int,
336
+ 'Papua New Guinea': int,
337
+ 'Paraguay': int,
338
+ 'Peru': int,
339
+ 'Philippines': int,
340
+ 'Pitcairn': int,
341
+ 'Poland': int,
342
+ 'Portugal': int,
343
+ 'Puerto Rico': int,
344
+ 'Qatar': int,
345
+ 'Reunion': int,
346
+ 'Romania': int,
347
+ 'Russia': int,
348
+ 'Russian Federation': int,
349
+ 'Rwanda': int,
350
+ 'ST': int,
351
+ 'Saint Barthelemy': int,
352
+ 'Saint Helena': int,
353
+ 'Saint Kitts and Nevis': int,
354
+ 'Saint Lucia': int,
355
+ 'Saint Martin': int,
356
+ 'Saint Pierre and Miquelon': int,
357
+ 'Saint Vincent and The Grenadines': int,
358
+ 'Samoa': int,
359
+ 'San Marino': int,
360
+ 'Sao Tome and Principe': int,
361
+ 'Saudi Arabia': int,
362
+ 'Senegal': int,
363
+ 'Serbia': int,
364
+ 'Serbia and Montenegro': int,
365
+ 'Seychelles': int,
366
+ 'Sierra Leone': int,
367
+ 'Singapore': int,
368
+ 'Sint Maarten': int,
369
+ 'Slovakia': int,
370
+ 'Slovenia': int,
371
+ 'Solomon Islands': int,
372
+ 'Somalia': int,
373
+ 'Somewhere': int,
374
+ 'South Africa': int,
375
+ 'South Georgia and the South Sandwich Islands': int,
376
+ 'South Korea': int,
377
+ 'South Sudan': int,
378
+ 'Spain': int,
379
+ 'Sri Lanka': int,
380
+ 'St. Vincent': int,
381
+ 'Sudan': int,
382
+ 'Suriname': int,
383
+ 'Svalbard and Jan Mayen': int,
384
+ 'Swaziland': int,
385
+ 'Sweden': int,
386
+ 'Switzerland': int,
387
+ 'Syria': int,
388
+ 'Syrian Arab Republic': int,
389
+ 'TV': int,
390
+ 'Taiwan': int,
391
+ 'Taiwan, Province of China': int,
392
+ 'Tajikistan': int,
393
+ 'Tanzania': int,
394
+ 'Tanzania, United Republic of': int,
395
+ 'Thailand': int,
396
+ 'Timor-leste': int,
397
+ 'Togo': int,
398
+ 'Tokelau': int,
399
+ 'Tonga': int,
400
+ 'Trinidad and Tobago': int,
401
+ 'Tunisia': int,
402
+ 'Turkey': int,
403
+ 'Turkmenistan': int,
404
+ 'Turks and Caicos Islands': int,
405
+ 'Tuvalu': int,
406
+ 'US': int,
407
+ 'US Minor': int,
408
+ 'Uganda': int,
409
+ 'Ukraine': int,
410
+ 'United Arab Emirates': int,
411
+ 'United Kingdom': int,
412
+ 'United States': int,
413
+ 'United States Minor Outlying Islands': int,
414
+ 'Uruguay': int,
415
+ 'Uzbekistan': int,
416
+ 'Vanuatu': int,
417
+ 'Vatican City': int,
418
+ 'Venezuela': int,
419
+ 'Viet Nam': int,
420
+ 'Vietnam': int,
421
+ 'Virgin Islands, British': int,
422
+ 'Virgin Islands, U.S.': int,
423
+ 'Wallis and Futuna': int,
424
+ 'Western Sahara': int,
425
+ 'Yemen': int,
426
+ 'Zambia': int,
427
+ 'Zimbabwe': int
428
+ })
429
+
430
+
431
+ def country_counts() -> CountryCounts:
432
+ return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["country_distribution"]
433
+
434
+
435
+ def age_distribution():
436
+ data = requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["age_distribution_data"][0]["values"]
437
+ return_data = {}
438
+ for value in data:
439
+ return_data[value["x"]] = value["y"]
440
+ return return_data
441
+
442
+
443
+ def monthly_comment_activity():
444
+ return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["comment_data"]
445
+
446
+
447
+ def monthly_project_shares():
448
+ return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["project_data"]
449
+
450
+
451
+ def monthly_active_users():
452
+ return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["active_user_data"]
453
+
454
+
455
+ def monthly_activity_trends():
456
+ return requests.get("https://scratch.mit.edu/statistics/data/monthly/").json()["activity_data"]
457
+
458
+
459
+ # --- CSRF Token Generation API ---
460
+
461
+ def get_csrf_token():
462
+ """
463
+ Generates a scratchcsrftoken using Scratch's API.
464
+
465
+ Returns:
466
+ str: The generated scratchcsrftoken
467
+ """
468
+ return requests.get(
469
+ "https://scratch.mit.edu/csrf_token/"
470
+ ).headers["set-cookie"].split(";")[3][len(" Path=/, scratchcsrftoken="):]
471
+
472
+ # --- Accounts --- #
473
+
474
+ def check_email(email: str) -> bool:
475
+ """
476
+ Returns whether an email is considered valid for registering a scratch account or not
477
+ """
478
+ data = requests.get(f"https://scratch.mit.edu/accounts/check_email/", params={
479
+ "email": email,
480
+ }).json()[0]
481
+ return data["msg"] == "valid email" # either "valid email" or "Enter a valid email address." or "This field is required."
482
+
483
+
484
+ # --- Various other api.scratch.mit.edu API endpoints ---
485
+
486
+ def get_health():
487
+ return requests.get("https://api.scratch.mit.edu/health").json()
488
+
489
+
490
+ def get_total_project_count() -> int:
491
+ return requests.get("https://api.scratch.mit.edu/projects/count/all").json()["count"]
492
+
493
+
494
+ def check_username(username):
495
+ return requests.get(f"https://api.scratch.mit.edu/accounts/checkusername/{username}").json()["msg"]
496
+
497
+
498
+ def check_password(password):
499
+ return requests.post("https://api.scratch.mit.edu/accounts/checkpassword/", json={"password": password}).json()[
500
+ "msg"]
501
+
502
+
503
+ # --- April fools endpoints ---
504
+
505
+ def aprilfools_get_counter() -> int:
506
+ return requests.get("https://api.scratch.mit.edu/surprise").json()["surprise"]
507
+
508
+
509
+ def aprilfools_increment_counter() -> int:
510
+ return requests.post("https://api.scratch.mit.edu/surprise").json()["surprise"]
511
+
512
+
513
+ # --- Resources ---
514
+ def get_resource_urls():
515
+ return requests.get("https://resources.scratch.mit.edu/localized-urls.json").json()
516
+
517
+
518
+ # --- Misc ---
519
+ # I'm not sure what to label this as
520
+ def scratch_team_members() -> dict:
521
+ # Unfortunately, the only place to find this is a js file, not a json file, which is annoying
522
+ text = requests.get("https://scratch.mit.edu/js/credits.bundle.js").text
523
+ text = "[{\"userName\"" + text.split("JSON.parse('[{\"userName\"")[1]
524
+ text = text.split("\"}]')")[0] + "\"}]"
525
+
526
+ return json.loads(text)
527
+
528
+
529
+ def send_password_reset_email(username: Optional[str] = None, email: Optional[str] = None):
530
+ requests.post("https://scratch.mit.edu/accounts/password_reset/", data={
531
+ "username": username,
532
+ "email": email,
533
+ }, headers=commons.headers, cookies={"scratchcsrftoken": 'a'})
534
+
535
+
536
+ def translate(language: str | Languages, text: str = "hello"):
537
+ if isinstance(language, str):
538
+ lang = Languages.find_by_attrs(language.lower(), ["code", "tts_locale", "name"], str.lower)
539
+ elif isinstance(language, Languages):
540
+ lang = language.value
541
+ else:
542
+ lang = language
543
+
544
+ if not isinstance(lang, Language):
545
+ raise InvalidLanguage(f"{language} is not a language")
546
+
547
+ if lang.code is None:
548
+ raise InvalidLanguage(f"{lang} is not a valid translate language")
549
+
550
+ response_json = requests.get(
551
+ f"https://translate-service.scratch.mit.edu/translate?language={lang.code}&text={text}").json()
552
+
553
+ if "result" in response_json:
554
+ return response_json["result"]
555
+ else:
556
+ raise BadRequest(f"Language '{language}' does not seem to be valid.\nResponse: {response_json}")
557
+
558
+
559
+ def text2speech(text: str = "hello", voice_name: str = "female", language: str = "en-US"):
560
+ """
561
+ Sends a request to Scratch's TTS synthesis service.
562
+ Returns:
563
+ - The TTS audio (mp3) as bytes
564
+ - The playback rate (e.g. for giant it would be 0.84)
565
+ """
566
+ if isinstance(voice_name, str):
567
+ voice = TTSVoices.find_by_attrs(voice_name.lower(), ["name", "gender"], str.lower)
568
+ elif isinstance(voice_name, TTSVoices):
569
+ voice = voice_name.value
570
+ else:
571
+ voice = voice_name
572
+
573
+ if not isinstance(voice, TTSVoice):
574
+ raise InvalidTTSGender(f"TTS Gender {voice_name} is not supported.")
575
+
576
+ # If it's kitten, make sure to change everything to just meows
577
+ if voice.name == "kitten":
578
+ text = ''
579
+ for word in text.split(' '):
580
+ if word.strip() != '':
581
+ text += "meow "
582
+
583
+ if isinstance(language, str):
584
+ lang = Languages.find_by_attrs(language.lower(), ["code", "tts_locale", "name"], str.lower)
585
+ elif isinstance(language, Languages):
586
+ lang = language.value
587
+ else:
588
+ lang = language
589
+
590
+ if not isinstance(lang, Language):
591
+ raise InvalidLanguage(f"Language '{language}' is not a language")
592
+
593
+ if lang.tts_locale is None:
594
+ raise InvalidLanguage(f"Language '{language}' is not a valid TTS language")
595
+
596
+ response = requests.get(f"https://synthesis-service.scratch.mit.edu/synth"
597
+ f"?locale={lang.tts_locale}&gender={voice.gender}&text={text}")
598
+ return response.content, voice.playback_rate