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