scratchattach 2.1.14__py3-none-any.whl → 3.0.0b0__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 (69) hide show
  1. scratchattach/__init__.py +14 -6
  2. scratchattach/__main__.py +93 -0
  3. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/METADATA +7 -11
  4. scratchattach-3.0.0b0.dist-info/RECORD +8 -0
  5. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/WHEEL +1 -1
  6. scratchattach-3.0.0b0.dist-info/entry_points.txt +2 -0
  7. scratchattach/cloud/__init__.py +0 -2
  8. scratchattach/cloud/_base.py +0 -458
  9. scratchattach/cloud/cloud.py +0 -183
  10. scratchattach/editor/__init__.py +0 -21
  11. scratchattach/editor/asset.py +0 -253
  12. scratchattach/editor/backpack_json.py +0 -117
  13. scratchattach/editor/base.py +0 -193
  14. scratchattach/editor/block.py +0 -579
  15. scratchattach/editor/blockshape.py +0 -357
  16. scratchattach/editor/build_defaulting.py +0 -51
  17. scratchattach/editor/code_translation/__init__.py +0 -0
  18. scratchattach/editor/code_translation/parse.py +0 -177
  19. scratchattach/editor/comment.py +0 -80
  20. scratchattach/editor/commons.py +0 -306
  21. scratchattach/editor/extension.py +0 -50
  22. scratchattach/editor/field.py +0 -99
  23. scratchattach/editor/inputs.py +0 -135
  24. scratchattach/editor/meta.py +0 -114
  25. scratchattach/editor/monitor.py +0 -183
  26. scratchattach/editor/mutation.py +0 -324
  27. scratchattach/editor/pallete.py +0 -90
  28. scratchattach/editor/prim.py +0 -170
  29. scratchattach/editor/project.py +0 -279
  30. scratchattach/editor/sprite.py +0 -599
  31. scratchattach/editor/twconfig.py +0 -114
  32. scratchattach/editor/vlb.py +0 -134
  33. scratchattach/eventhandlers/__init__.py +0 -0
  34. scratchattach/eventhandlers/_base.py +0 -100
  35. scratchattach/eventhandlers/cloud_events.py +0 -110
  36. scratchattach/eventhandlers/cloud_recorder.py +0 -26
  37. scratchattach/eventhandlers/cloud_requests.py +0 -459
  38. scratchattach/eventhandlers/cloud_server.py +0 -246
  39. scratchattach/eventhandlers/cloud_storage.py +0 -136
  40. scratchattach/eventhandlers/combine.py +0 -30
  41. scratchattach/eventhandlers/filterbot.py +0 -161
  42. scratchattach/eventhandlers/message_events.py +0 -42
  43. scratchattach/other/__init__.py +0 -0
  44. scratchattach/other/other_apis.py +0 -284
  45. scratchattach/other/project_json_capabilities.py +0 -475
  46. scratchattach/site/__init__.py +0 -0
  47. scratchattach/site/_base.py +0 -66
  48. scratchattach/site/activity.py +0 -382
  49. scratchattach/site/alert.py +0 -227
  50. scratchattach/site/backpack_asset.py +0 -118
  51. scratchattach/site/browser_cookie3_stub.py +0 -17
  52. scratchattach/site/browser_cookies.py +0 -61
  53. scratchattach/site/classroom.py +0 -447
  54. scratchattach/site/cloud_activity.py +0 -107
  55. scratchattach/site/comment.py +0 -242
  56. scratchattach/site/forum.py +0 -432
  57. scratchattach/site/project.py +0 -825
  58. scratchattach/site/session.py +0 -1238
  59. scratchattach/site/studio.py +0 -611
  60. scratchattach/site/user.py +0 -956
  61. scratchattach/utils/__init__.py +0 -0
  62. scratchattach/utils/commons.py +0 -255
  63. scratchattach/utils/encoder.py +0 -158
  64. scratchattach/utils/enums.py +0 -236
  65. scratchattach/utils/exceptions.py +0 -243
  66. scratchattach/utils/requests.py +0 -93
  67. scratchattach-2.1.14.dist-info/RECORD +0 -66
  68. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/licenses/LICENSE +0 -0
  69. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/top_level.txt +0 -0
@@ -1,118 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import time
5
- import logging
6
-
7
- from ._base import BaseSiteComponent
8
- from scratchattach.utils import exceptions
9
- from scratchattach.utils.requests import requests
10
-
11
-
12
-
13
- class BackpackAsset(BaseSiteComponent):
14
- """
15
- Represents an asset from the backpack.
16
-
17
- Attributes:
18
-
19
- :.id:
20
-
21
- :.type: The asset type (costume, script etc.)
22
-
23
- :.mime: The format in which the content of the backpack asset is saved
24
-
25
- :.name: The name of the backpack asset
26
-
27
- :.filename: Filename of the file containing the content of the backpack asset
28
-
29
- :.thumbnail_url: Link that leads to the asset's thumbnail (the image shown in the backpack UI)
30
-
31
- :.download_url: Link that leads to a file containing the content of the backpack asset
32
- """
33
-
34
- def __init__(self, **entries):
35
- # Set attributes every BackpackAsset object needs to have:
36
- self._session = None
37
-
38
- # Update attributes from entries dict:
39
- self.__dict__.update(entries)
40
-
41
- def update(self):
42
- print("Warning: BackpackAsset objects can't be updated")
43
- return False # Objects of this type cannot be updated
44
-
45
-
46
- def _update_from_dict(self, data) -> bool:
47
- try:
48
- self.id = data["id"]
49
- except Exception:
50
- pass
51
- try:
52
- self.type = data["type"]
53
- except Exception:
54
- pass
55
- try:
56
- self.mime = data["mime"]
57
- except Exception:
58
- pass
59
- try:
60
- self.name = data["name"]
61
- except Exception:
62
- pass
63
- try:
64
- self.filename = data["body"]
65
- except Exception:
66
- pass
67
- try:
68
- self.thumbnail_url = "https://backpack.scratch.mit.edu/" + data["thumbnail"]
69
- except Exception:
70
- pass
71
- try:
72
- self.download_url = "https://backpack.scratch.mit.edu/" + data["body"]
73
- except Exception:
74
- pass
75
- return True
76
-
77
- @property
78
- def _data_bytes(self) -> bytes:
79
- try:
80
- return requests.get(self.download_url).content
81
- except Exception as e:
82
- raise exceptions.FetchError(f"Failed to download asset: {e}")
83
-
84
- @property
85
- def file_ext(self):
86
- return self.filename.split(".")[-1]
87
-
88
- @property
89
- def is_json(self):
90
- return self.file_ext == "json"
91
-
92
- @property
93
- def data(self) -> dict | list | int | None | str | bytes | float:
94
- if self.is_json:
95
- return json.loads(self._data_bytes)
96
- else:
97
- # It's either a zip
98
- return self._data_bytes
99
-
100
- def download(self, *, fp: str = ''):
101
- """
102
- Downloads the asset content to the given directory. The given filename is equal to the value saved in the .filename attribute.
103
-
104
- Args:
105
- fp (str): The path of the directory the file will be saved in.
106
- """
107
- if not (fp.endswith("/") or fp.endswith("\\")):
108
- fp = fp + "/"
109
- open(f"{fp}{self.filename}", "wb").write(self._data_bytes)
110
-
111
- def delete(self):
112
- self._assert_auth()
113
-
114
- return requests.delete(
115
- f"https://backpack.scratch.mit.edu/{self._session.username}/{self.id}",
116
- headers=self._session._headers,
117
- timeout=10,
118
- ).json()
@@ -1,17 +0,0 @@
1
- # browser_cookie3.pyi
2
-
3
- import http.cookiejar
4
- from typing import Optional
5
-
6
- def chrome(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
7
- def chromium(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
8
- def firefox(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
9
- def opera(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
10
- def edge(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
11
- def brave(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
12
- def vivaldi(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
13
- def safari(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
14
- def lynx(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
15
- def w3m(cookie_file: Optional[str] = None, key_file: Optional[str] = None) -> http.cookiejar.CookieJar: return NotImplemented
16
-
17
- def load() -> http.cookiejar.CookieJar: return NotImplemented
@@ -1,61 +0,0 @@
1
- from typing import Optional, TYPE_CHECKING
2
- from typing_extensions import assert_never
3
- from http.cookiejar import CookieJar
4
- from enum import Enum, auto
5
- browsercookie_err = None
6
- try:
7
- if TYPE_CHECKING:
8
- from . import browser_cookie3_stub as browser_cookie3
9
- else:
10
- import browser_cookie3
11
- except Exception as e:
12
- browsercookie = None
13
- browsercookie_err = e
14
-
15
- class Browser(Enum):
16
- ANY = auto()
17
- FIREFOX = auto()
18
- CHROME = auto()
19
- EDGE = auto()
20
- SAFARI = auto()
21
- CHROMIUM = auto()
22
- VIVALDI = auto()
23
- EDGE_DEV = auto()
24
-
25
-
26
- FIREFOX = Browser.FIREFOX
27
- CHROME = Browser.CHROME
28
- EDGE = Browser.EDGE
29
- SAFARI = Browser.SAFARI
30
- CHROMIUM = Browser.CHROMIUM
31
- VIVALDI = Browser.VIVALDI
32
- ANY = Browser.ANY
33
- EDGE_DEV = Browser.EDGE_DEV
34
-
35
- def cookies_from_browser(browser : Browser = ANY) -> dict[str, str]:
36
- """
37
- Import cookies from browser to login
38
- """
39
- if not browser_cookie3:
40
- raise browsercookie_err or ModuleNotFoundError()
41
- cookies : Optional[CookieJar] = None
42
- if browser is Browser.ANY:
43
- cookies = browser_cookie3.load()
44
- elif browser is Browser.FIREFOX:
45
- cookies = browser_cookie3.firefox()
46
- elif browser is Browser.CHROME:
47
- cookies = browser_cookie3.chrome()
48
- elif browser is Browser.EDGE:
49
- cookies = browser_cookie3.edge()
50
- elif browser is Browser.SAFARI:
51
- cookies = browser_cookie3.safari()
52
- elif browser is Browser.CHROMIUM:
53
- cookies = browser_cookie3.chromium()
54
- elif browser is Browser.VIVALDI:
55
- cookies = browser_cookie3.vivaldi()
56
- elif browser is Browser.EDGE_DEV:
57
- raise ValueError("EDGE_DEV is not supported anymore.")
58
- else:
59
- assert_never(browser)
60
- assert isinstance(cookies, CookieJar)
61
- return {cookie.name: cookie.value for cookie in cookies if "scratch.mit.edu" in cookie.domain and cookie.value}
@@ -1,447 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import datetime
4
- import json
5
- import warnings
6
- from dataclasses import dataclass, field
7
- from datetime import datetime
8
- from typing import Optional, TYPE_CHECKING, Any, Callable
9
-
10
- import bs4
11
- from bs4 import BeautifulSoup
12
-
13
- if TYPE_CHECKING:
14
- from scratchattach.site.session import Session
15
-
16
- from scratchattach.utils.commons import requests
17
- from . import user, activity
18
- from ._base import BaseSiteComponent
19
- from scratchattach.utils import exceptions, commons
20
- from scratchattach.utils.commons import headers
21
-
22
-
23
- @dataclass
24
- class Classroom(BaseSiteComponent):
25
- title: str = None
26
- id: int = None
27
- classtoken: str = None
28
-
29
- author: user.User = None
30
- about_class: str = None
31
- working_on: str = None
32
-
33
- is_closed: bool = False
34
- datetime: datetime = None
35
-
36
-
37
- update_function: Callable = field(repr=False, default=requests.get)
38
- _session: Optional[Session] = field(repr=False, default=None)
39
-
40
- def __post_init__(self):
41
- # Info on how the .update method has to fetch the data:
42
- # NOTE: THIS DOESN'T WORK WITH CLOSED CLASSES!
43
- if self.id:
44
- self.update_api = f"https://api.scratch.mit.edu/classrooms/{self.id}"
45
- elif self.classtoken:
46
- self.update_api = f"https://api.scratch.mit.edu/classtoken/{self.classtoken}"
47
- else:
48
- raise KeyError(f"No class id or token provided! {self.__dict__ = }")
49
-
50
- # Headers and cookies:
51
- if self._session is None:
52
- self._headers = commons.headers
53
- self._cookies = {}
54
- else:
55
- self._headers = self._session._headers
56
- self._cookies = self._session._cookies
57
-
58
- # Headers for operations that require accept and Content-Type fields:
59
- self._json_headers = {**self._headers,
60
- "accept": "application/json",
61
- "Content-Type": "application/json"}
62
-
63
- def __str__(self) -> str:
64
- return f"<Classroom {self.title!r}, id={self.id!r}>"
65
-
66
- def update(self):
67
- try:
68
- success = super().update()
69
- except exceptions.ClassroomNotFound:
70
- success = False
71
-
72
- if not success:
73
- response = requests.get(f"https://scratch.mit.edu/classes/{self.id}/")
74
- soup = BeautifulSoup(response.text, "html.parser")
75
-
76
- headings = soup.find_all("h1")
77
- for heading in headings:
78
- if heading.text == "Whoops! Our server is Scratch'ing its head":
79
- raise exceptions.ClassroomNotFound(f"Classroom id {self.id} is not closed and cannot be found.")
80
-
81
- # id, title, description, status, date_start (iso format), educator/username
82
-
83
- title = soup.find("title").contents[0][:-len(" on Scratch")]
84
-
85
- overviews = soup.find_all("p", {"class": "overview"})
86
- description, status = overviews[0].text, overviews[1].text
87
-
88
- educator_username = None
89
- pfx = "Scratch.INIT_DATA.PROFILE = {\n model: {\n id: '"
90
- sfx = "',\n userId: "
91
- for script in soup.find_all("script"):
92
- if pfx in script.text:
93
- educator_username = commons.webscrape_count(script.text, pfx, sfx, str)
94
-
95
- ret = {"id": self.id,
96
- "title": title,
97
- "description": description,
98
- "status": status,
99
- "educator": {"username": educator_username},
100
- "is_closed": True
101
- }
102
-
103
- return self._update_from_dict(ret)
104
- return success
105
-
106
- def _update_from_dict(self, classrooms):
107
- try:
108
- self.id = int(classrooms["id"])
109
- except Exception:
110
- pass
111
- try:
112
- self.title = classrooms["title"]
113
- except Exception:
114
- pass
115
- try:
116
- self.about_class = classrooms["description"]
117
- except Exception:
118
- pass
119
- try:
120
- self.working_on = classrooms["status"]
121
- except Exception:
122
- pass
123
- try:
124
- self.datetime = datetime.datetime.fromisoformat(classrooms["date_start"])
125
- except Exception:
126
- pass
127
- try:
128
- self.author = user.User(username=classrooms["educator"]["username"], _session=self._session)
129
- except Exception:
130
- pass
131
- try:
132
- self.author._update_from_dict(classrooms["educator"])
133
- except Exception:
134
- pass
135
- self.is_closed = classrooms.get("is_closed", False)
136
- return True
137
-
138
- def student_count(self) -> int:
139
- # student count
140
- text = requests.get(
141
- f"https://scratch.mit.edu/classes/{self.id}/",
142
- headers=self._headers
143
- ).text
144
- return commons.webscrape_count(text, "Students (", ")")
145
-
146
- def student_names(self, *, page=1) -> list[str]:
147
- """
148
- Returns the student on the class.
149
-
150
- Keyword Arguments:
151
- page: The page of the students that should be returned.
152
-
153
- Returns:
154
- list<str>: The usernames of the class students
155
- """
156
- if self.is_closed:
157
- ret = []
158
- response = requests.get(f"https://scratch.mit.edu/classes/{self.id}/")
159
- soup = BeautifulSoup(response.text, "html.parser")
160
-
161
- for scrollable in soup.find_all("ul", {"class": "scroll-content"}):
162
- for item in scrollable.contents:
163
- if not isinstance(item, bs4.NavigableString):
164
- if "user" in item.attrs["class"]:
165
- anchors = item.find_all("a")
166
- if len(anchors) == 2:
167
- ret.append(anchors[1].text.strip())
168
-
169
- return ret
170
-
171
- text = requests.get(
172
- f"https://scratch.mit.edu/classes/{self.id}/students/?page={page}",
173
- headers=self._headers
174
- ).text
175
- textlist = [i.split('/">')[0] for i in text.split(' <a href="/users/')[1:]]
176
- return textlist
177
-
178
- def class_studio_count(self) -> int:
179
- # studio count
180
- text = requests.get(
181
- f"https://scratch.mit.edu/classes/{self.id}/",
182
- headers=self._headers
183
- ).text
184
- return commons.webscrape_count(text, "Class Studios (", ")")
185
-
186
- def class_studio_ids(self, *, page: int = 1) -> list[int]:
187
- """
188
- Returns the class studio on the class.
189
-
190
- Keyword Arguments:
191
- page: The page of the students that should be returned.
192
-
193
- Returns:
194
- list<int>: The id of the class studios
195
- """
196
- if self.is_closed:
197
- ret = []
198
- response = requests.get(f"https://scratch.mit.edu/classes/{self.id}/")
199
- soup = BeautifulSoup(response.text, "html.parser")
200
-
201
- for scrollable in soup.find_all("ul", {"class": "scroll-content"}):
202
- for item in scrollable.contents:
203
- if not isinstance(item, bs4.NavigableString):
204
- if "gallery" in item.attrs["class"]:
205
- anchor = item.find("a")
206
- if "href" in anchor.attrs:
207
- ret.append(commons.webscrape_count(anchor.attrs["href"], "/studios/", "/"))
208
- return ret
209
-
210
- text = requests.get(
211
- f"https://scratch.mit.edu/classes/{self.id}/studios/?page={page}",
212
- headers=self._headers
213
- ).text
214
- textlist = [int(i.split('/">')[0]) for i in text.split('<span class="title">\n <a href="/studios/')[1:]]
215
- return textlist
216
-
217
- def _check_session(self) -> None:
218
- if self._session is None:
219
- raise exceptions.Unauthenticated(
220
- f"Classroom {self} has no associated session. Use session.connect_classroom() instead of sa.get_classroom()")
221
-
222
- def set_thumbnail(self, thumbnail: bytes) -> None:
223
- self._check_session()
224
- requests.post(f"https://scratch.mit.edu/site-api/classrooms/all/{self.id}/",
225
- headers=self._headers, cookies=self._cookies,
226
- files={"file": thumbnail})
227
-
228
- def set_description(self, desc: str) -> None:
229
- self._check_session()
230
- response = requests.put(f"https://scratch.mit.edu/site-api/classrooms/all/{self.id}/",
231
- headers=self._headers, cookies=self._cookies,
232
- json={"description": desc})
233
-
234
- try:
235
- data = response.json()
236
- if data["description"] == desc:
237
- # Success!
238
- return
239
- else:
240
- warnings.warn(f"{self._session} may not be authenticated to edit {self}")
241
-
242
- except Exception as e:
243
- warnings.warn(f"{self._session} may not be authenticated to edit {self}")
244
- raise e
245
-
246
- def set_working_on(self, status: str) -> None:
247
- self._check_session()
248
- response = requests.put(f"https://scratch.mit.edu/site-api/classrooms/all/{self.id}/",
249
- headers=self._headers, cookies=self._cookies,
250
- json={"status": status})
251
-
252
- try:
253
- data = response.json()
254
- if data["status"] == status:
255
- # Success!
256
- return
257
- else:
258
- warnings.warn(f"{self._session} may not be authenticated to edit {self}")
259
-
260
- except Exception as e:
261
- warnings.warn(f"{self._session} may not be authenticated to edit {self}")
262
- raise e
263
-
264
- def set_title(self, title: str) -> None:
265
- self._check_session()
266
- response = requests.put(f"https://scratch.mit.edu/site-api/classrooms/all/{self.id}/",
267
- headers=self._headers, cookies=self._cookies,
268
- json={"title": title})
269
-
270
- try:
271
- data = response.json()
272
- if data["title"] == title:
273
- # Success!
274
- return
275
- else:
276
- warnings.warn(f"{self._session} may not be authenticated to edit {self}")
277
-
278
- except Exception as e:
279
- warnings.warn(f"{self._session} may not be authenticated to edit {self}")
280
- raise e
281
-
282
- def add_studio(self, name: str, description: str = '') -> None:
283
- self._check_session()
284
- requests.post("https://scratch.mit.edu/classes/create_classroom_gallery/",
285
- json={
286
- "classroom_id": str(self.id),
287
- "classroom_token": self.classtoken,
288
- "title": name,
289
- "description": description},
290
- headers=self._headers, cookies=self._cookies)
291
-
292
- def reopen(self) -> None:
293
- self._check_session()
294
- response = requests.put(f"https://scratch.mit.edu/site-api/classrooms/all/{self.id}/",
295
- headers=self._headers, cookies=self._cookies,
296
- json={"visibility": "visible"})
297
-
298
- try:
299
- response.json()
300
-
301
- except Exception as e:
302
- warnings.warn(f"{self._session} may not be authenticated to edit {self}")
303
- raise e
304
-
305
- def close(self) -> None:
306
- self._check_session()
307
- response = requests.post(f"https://scratch.mit.edu/site-api/classrooms/close_classroom/{self.id}/",
308
- headers=self._headers, cookies=self._cookies)
309
-
310
- try:
311
- response.json()
312
-
313
- except Exception as e:
314
- warnings.warn(f"{self._session} may not be authenticated to edit {self}")
315
- raise e
316
-
317
- def register_student(self, username: str, password: str = '', birth_month: Optional[int] = None,
318
- birth_year: Optional[int] = None,
319
- gender: Optional[str] = None, country: Optional[str] = None, is_robot: bool = False) -> None:
320
- return register_by_token(self.id, self.classtoken, username, password, birth_month, birth_year, gender, country,
321
- is_robot)
322
-
323
- def generate_signup_link(self):
324
- if self.classtoken is not None:
325
- return f"https://scratch.mit.edu/signup/{self.classtoken}"
326
-
327
- self._check_session()
328
-
329
- response = requests.get(f"https://scratch.mit.edu/site-api/classrooms/generate_registration_link/{self.id}/",
330
- headers=self._headers, cookies=self._cookies)
331
- # Should really check for '404' page
332
- data = response.json()
333
- if "reg_link" in data:
334
- return data["reg_link"]
335
- else:
336
- raise exceptions.Unauthorized(f"{self._session} is not authorised to generate a signup link of {self}")
337
-
338
- def public_activity(self, *, limit=20):
339
- """
340
- Returns:
341
- list<scratchattach.Activity>: The user's activity data as parsed list of scratchattach.activity.Activity objects
342
- """
343
- if limit > 20:
344
- warnings.warn("The limit is set to more than 20. There may be an error")
345
- soup = BeautifulSoup(
346
- requests.get(f"https://scratch.mit.edu/site-api/classrooms/activity/public/{self.id}/?limit={limit}").text,
347
- 'html.parser')
348
-
349
- activities = []
350
- source = soup.find_all("li")
351
-
352
- for data in source:
353
- _activity = activity.Activity(_session=self._session, raw=data)
354
- _activity._update_from_html(data)
355
- activities.append(_activity)
356
-
357
- return activities
358
-
359
- def activity(self, student: str = "all", mode: str = "Last created", page: Optional[int] = None) -> list[
360
- dict[str, Any]]:
361
- """
362
- Get a list of private activity, only available to the class owner.
363
- Returns:
364
- list<activity.Activity> The private activity of users in the class
365
- """
366
-
367
- self._check_session()
368
-
369
- ascsort, descsort = commons.get_class_sort_mode(mode)
370
-
371
- with requests.no_error_handling():
372
- try:
373
- data = requests.get(
374
- f"https://scratch.mit.edu/site-api/classrooms/activity/{self.id}/{student}/",
375
- params={"page": page, "ascsort": ascsort, "descsort": descsort},
376
- headers=self._headers, cookies=self._cookies
377
- ).json()
378
- except json.JSONDecodeError:
379
- return []
380
-
381
- _activity = []
382
- for activity_json in data:
383
- _activity.append(activity.Activity(_session=self._session))
384
- _activity[-1]._update_from_json(activity_json)
385
-
386
- return _activity
387
-
388
-
389
- def get_classroom(class_id: str) -> Classroom:
390
- """
391
- Gets a class without logging in.
392
-
393
- Args:
394
- class_id (str): class id of the requested class
395
-
396
- Returns:
397
- scratchattach.classroom.Classroom: An object representing the requested classroom
398
-
399
- Warning:
400
- Any methods that require authentication will not work on the returned object.
401
-
402
- If you want to use these, get the user with :meth:`scratchattach.session.Session.connect_classroom` instead.
403
- """
404
- warnings.warn("For methods that require authentication, use session.connect_classroom instead of get_classroom")
405
- return commons._get_object("id", class_id, Classroom, exceptions.ClassroomNotFound)
406
-
407
-
408
- def get_classroom_from_token(class_token) -> Classroom:
409
- """
410
- Gets a class without logging in.
411
-
412
- Args:
413
- class_token (str): class token of the requested class
414
-
415
- Returns:
416
- scratchattach.classroom.Classroom: An object representing the requested classroom
417
-
418
- Warning:
419
- Any methods that require authentication will not work on the returned object.
420
-
421
- If you want to use these, get the user with :meth:`scratchattach.session.Session.connect_classroom` instead.
422
- """
423
- warnings.warn("For methods that require authentication, use session.connect_classroom instead of get_classroom")
424
- return commons._get_object("classtoken", class_token, Classroom, exceptions.ClassroomNotFound)
425
-
426
-
427
- def register_by_token(class_id: int, class_token: str, username: str, password: str, birth_month: int, birth_year: int,
428
- gender: str, country: str, is_robot: bool = False) -> None:
429
- data = {"classroom_id": class_id,
430
- "classroom_token": class_token,
431
-
432
- "username": username,
433
- "password": password,
434
- "birth_month": birth_month,
435
- "birth_year": birth_year,
436
- "gender": gender,
437
- "country": country,
438
- "is_robot": is_robot}
439
-
440
- response = requests.post("https://scratch.mit.edu/classes/register_new_student/",
441
- data=data, headers=commons.headers, cookies={"scratchcsrftoken": 'a'})
442
- ret = response.json()[0]
443
-
444
- if "username" in ret:
445
- return
446
- else:
447
- raise exceptions.Unauthorized(f"Can't create account: {response.text}")