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/_base.py
CHANGED
|
@@ -1,46 +1,64 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
from ..utils import exceptions, commons
|
|
7
|
+
from types import FunctionType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseSiteComponent(ABC):
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self._session = None
|
|
14
|
+
self._cookies = None
|
|
15
|
+
self._headers = None
|
|
16
|
+
self.update_API = None
|
|
17
|
+
|
|
18
|
+
def update(self):
|
|
19
|
+
"""
|
|
20
|
+
Updates the attributes of the object by performing an API response. Returns True if the update was successful.
|
|
21
|
+
"""
|
|
22
|
+
response = self.update_function(
|
|
23
|
+
self.update_API,
|
|
24
|
+
headers=self._headers,
|
|
25
|
+
cookies=self._cookies, timeout=10
|
|
26
|
+
)
|
|
27
|
+
# Check for 429 error:
|
|
28
|
+
# Note, this is a bit naïve
|
|
29
|
+
if "429" in str(response):
|
|
30
|
+
return "429"
|
|
31
|
+
|
|
32
|
+
if response.text == '{\n "response": "Too many requests"\n}':
|
|
33
|
+
return "429"
|
|
34
|
+
|
|
35
|
+
# If no error: Parse JSON:
|
|
36
|
+
response = response.json()
|
|
37
|
+
if "code" in response:
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
return self._update_from_dict(response)
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def _update_from_dict(self, data) -> bool:
|
|
44
|
+
"""
|
|
45
|
+
Parses the API response that is fetched in the update-method. Class specific, must be overridden in classes inheriting from this one.
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def _assert_auth(self):
|
|
50
|
+
if self._session is None:
|
|
51
|
+
raise exceptions.Unauthenticated(
|
|
52
|
+
"You need to use session.connect_xyz (NOT get_xyz) in order to perform this operation.")
|
|
53
|
+
|
|
54
|
+
def _make_linked_object(self, identificator_id, identificator, Class, NotFoundException) -> BaseSiteComponent:
|
|
55
|
+
"""
|
|
56
|
+
Internal function for making a linked object (authentication kept) based on an identificator (like a project id or username)
|
|
57
|
+
Class must inherit from BaseSiteComponent
|
|
58
|
+
"""
|
|
59
|
+
return commons._get_object(identificator_id, identificator, Class, NotFoundException, self._session)
|
|
60
|
+
|
|
61
|
+
update_function: FunctionType = requests.get
|
|
62
|
+
"""
|
|
63
|
+
Internal function run on update. Function is a method of the 'requests' module/class
|
|
64
|
+
"""
|
scratchattach/site/activity.py
CHANGED
|
@@ -1,122 +1,414 @@
|
|
|
1
|
-
"""Activity and CloudActivity class"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from . import
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
self.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if
|
|
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
|
-
|
|
1
|
+
"""Activity and CloudActivity class"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from bs4 import PageElement
|
|
5
|
+
|
|
6
|
+
from . import user, project, studio
|
|
7
|
+
from ._base import BaseSiteComponent
|
|
8
|
+
from ..utils import exceptions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Activity(BaseSiteComponent):
|
|
12
|
+
"""
|
|
13
|
+
Represents a Scratch activity (message or other user page activity)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __repr__(self):
|
|
17
|
+
return repr(self.raw)
|
|
18
|
+
|
|
19
|
+
def str(self):
|
|
20
|
+
return str(self.raw)
|
|
21
|
+
|
|
22
|
+
def __init__(self, **entries):
|
|
23
|
+
|
|
24
|
+
# Set attributes every Activity object needs to have:
|
|
25
|
+
self._session = None
|
|
26
|
+
self.raw = None
|
|
27
|
+
|
|
28
|
+
# Possible attributes
|
|
29
|
+
self.project_id = None
|
|
30
|
+
self.gallery_id = None
|
|
31
|
+
|
|
32
|
+
self.username = None
|
|
33
|
+
self.followed_username = None
|
|
34
|
+
self.recipient_username = None
|
|
35
|
+
|
|
36
|
+
self.comment_type = None
|
|
37
|
+
self.comment_obj_id = None
|
|
38
|
+
self.comment_obj_title = None
|
|
39
|
+
self.comment_id = None
|
|
40
|
+
|
|
41
|
+
self.datetime_created = None
|
|
42
|
+
self.time = None
|
|
43
|
+
self.type = None
|
|
44
|
+
|
|
45
|
+
# Update attributes from entries dict:
|
|
46
|
+
self.__dict__.update(entries)
|
|
47
|
+
|
|
48
|
+
def update(self):
|
|
49
|
+
print("Warning: Activity objects can't be updated")
|
|
50
|
+
return False # Objects of this type cannot be updated
|
|
51
|
+
|
|
52
|
+
def _update_from_dict(self, data):
|
|
53
|
+
self.raw = data
|
|
54
|
+
self.__dict__.update(data)
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def _update_from_json(self, data: dict):
|
|
58
|
+
"""
|
|
59
|
+
Update using JSON, used in the classroom API.
|
|
60
|
+
"""
|
|
61
|
+
activity_type = data["type"]
|
|
62
|
+
|
|
63
|
+
_time = data["datetime_created"] if "datetime_created" in data else None
|
|
64
|
+
|
|
65
|
+
if "actor" in data:
|
|
66
|
+
username = data["actor"]["username"]
|
|
67
|
+
elif "actor_username" in data:
|
|
68
|
+
username = data["actor_username"]
|
|
69
|
+
else:
|
|
70
|
+
username = None
|
|
71
|
+
|
|
72
|
+
if data.get("recipient") is not None:
|
|
73
|
+
recipient_username = data["recipient"]["username"]
|
|
74
|
+
|
|
75
|
+
elif data.get("recipient_username") is not None:
|
|
76
|
+
recipient_username = data["recipient_username"]
|
|
77
|
+
|
|
78
|
+
elif data.get("project_creator") is not None:
|
|
79
|
+
recipient_username = data["project_creator"]["username"]
|
|
80
|
+
else:
|
|
81
|
+
recipient_username = None
|
|
82
|
+
|
|
83
|
+
default_case = False
|
|
84
|
+
"""Whether this is 'blank'; it will default to 'user performed an action'"""
|
|
85
|
+
if activity_type == 0:
|
|
86
|
+
# follow
|
|
87
|
+
followed_username = data["followed_username"]
|
|
88
|
+
|
|
89
|
+
self.raw = f"{username} followed user {followed_username}"
|
|
90
|
+
|
|
91
|
+
self.datetime_created = _time
|
|
92
|
+
self.type = "followuser"
|
|
93
|
+
self.username = username
|
|
94
|
+
self.followed_username = followed_username
|
|
95
|
+
|
|
96
|
+
elif activity_type == 1:
|
|
97
|
+
# follow studio
|
|
98
|
+
studio_id = data["gallery"]
|
|
99
|
+
|
|
100
|
+
raw = f"{username} followed studio https://scratch.mit.edu/studios/{studio_id}"
|
|
101
|
+
|
|
102
|
+
self.raw = raw
|
|
103
|
+
self.datetime_created = _time
|
|
104
|
+
self.type = "followstudio"
|
|
105
|
+
|
|
106
|
+
self.username = username
|
|
107
|
+
self.gallery_id = studio_id
|
|
108
|
+
|
|
109
|
+
elif activity_type == 2:
|
|
110
|
+
# love project
|
|
111
|
+
project_id = data["project"]
|
|
112
|
+
|
|
113
|
+
raw = f"{username} loved project https://scratch.mit.edu/projects/{project_id}"
|
|
114
|
+
|
|
115
|
+
self.raw = raw
|
|
116
|
+
self.datetime_created = _time,
|
|
117
|
+
self.type = "loveproject"
|
|
118
|
+
|
|
119
|
+
self.username = username
|
|
120
|
+
self.project_id = project_id
|
|
121
|
+
self.recipient_username = recipient_username
|
|
122
|
+
|
|
123
|
+
elif activity_type == 3:
|
|
124
|
+
# Favorite project
|
|
125
|
+
project_id = data["project"]
|
|
126
|
+
|
|
127
|
+
raw = f"{username} favorited project https://scratch.mit.edu/projects/{project_id}"
|
|
128
|
+
|
|
129
|
+
self.raw = raw
|
|
130
|
+
self.datetime_created = _time
|
|
131
|
+
self.type = "favoriteproject"
|
|
132
|
+
|
|
133
|
+
self.username = username
|
|
134
|
+
self.project_id = project_id
|
|
135
|
+
self.recipient_username = recipient_username
|
|
136
|
+
|
|
137
|
+
elif activity_type == 7:
|
|
138
|
+
# Add project to studio
|
|
139
|
+
|
|
140
|
+
project_id = data["project"]
|
|
141
|
+
studio_id = data["gallery"]
|
|
142
|
+
|
|
143
|
+
raw = f"{username} added the project https://scratch.mit.edu/projects/{project_id} to studio https://scratch.mit.edu/studios/{studio_id}"
|
|
144
|
+
|
|
145
|
+
self.raw = raw
|
|
146
|
+
self.datetime_created = _time
|
|
147
|
+
self.type = "addprojecttostudio"
|
|
148
|
+
|
|
149
|
+
self.username = username
|
|
150
|
+
self.project_id = project_id
|
|
151
|
+
self.recipient_username = recipient_username
|
|
152
|
+
|
|
153
|
+
elif activity_type == 8:
|
|
154
|
+
default_case = True
|
|
155
|
+
|
|
156
|
+
elif activity_type == 9:
|
|
157
|
+
default_case = True
|
|
158
|
+
|
|
159
|
+
elif activity_type == 10:
|
|
160
|
+
# Share/Reshare project
|
|
161
|
+
project_id = data["project"]
|
|
162
|
+
is_reshare = data["is_reshare"]
|
|
163
|
+
|
|
164
|
+
raw_reshare = "reshared" if is_reshare else "shared"
|
|
165
|
+
|
|
166
|
+
raw = f"{username} {raw_reshare} the project https://scratch.mit.edu/projects/{project_id}"
|
|
167
|
+
|
|
168
|
+
self.raw = raw
|
|
169
|
+
self.datetime_created = _time
|
|
170
|
+
self.type = "shareproject"
|
|
171
|
+
|
|
172
|
+
self.username = username
|
|
173
|
+
self.project_id = project_id
|
|
174
|
+
self.recipient_username = recipient_username
|
|
175
|
+
|
|
176
|
+
elif activity_type == 11:
|
|
177
|
+
# Remix
|
|
178
|
+
parent_id = data["parent"]
|
|
179
|
+
|
|
180
|
+
raw = f"{username} remixed the project https://scratch.mit.edu/projects/{parent_id}"
|
|
181
|
+
|
|
182
|
+
self.raw = raw
|
|
183
|
+
self.datetime_created = _time
|
|
184
|
+
self.type = "remixproject"
|
|
185
|
+
|
|
186
|
+
self.username = username
|
|
187
|
+
self.project_id = parent_id
|
|
188
|
+
self.recipient_username = recipient_username
|
|
189
|
+
|
|
190
|
+
elif activity_type == 12:
|
|
191
|
+
default_case = True
|
|
192
|
+
|
|
193
|
+
elif activity_type == 13:
|
|
194
|
+
# Create ('add') studio
|
|
195
|
+
studio_id = data["gallery"]
|
|
196
|
+
|
|
197
|
+
raw = f"{username} created the studio https://scratch.mit.edu/studios/{studio_id}"
|
|
198
|
+
|
|
199
|
+
self.raw = raw
|
|
200
|
+
self.datetime_created = _time
|
|
201
|
+
self.type = "createstudio"
|
|
202
|
+
|
|
203
|
+
self.username = username
|
|
204
|
+
self.gallery_id = studio_id
|
|
205
|
+
|
|
206
|
+
elif activity_type == 15:
|
|
207
|
+
# Update studio
|
|
208
|
+
studio_id = data["gallery"]
|
|
209
|
+
|
|
210
|
+
raw = f"{username} updated the studio https://scratch.mit.edu/studios/{studio_id}"
|
|
211
|
+
|
|
212
|
+
self.raw = raw
|
|
213
|
+
self.datetime_created = _time
|
|
214
|
+
self.type = "updatestudio"
|
|
215
|
+
|
|
216
|
+
self.username = username
|
|
217
|
+
self.gallery_id = studio_id
|
|
218
|
+
|
|
219
|
+
elif activity_type == 16:
|
|
220
|
+
default_case = True
|
|
221
|
+
|
|
222
|
+
elif activity_type == 17:
|
|
223
|
+
default_case = True
|
|
224
|
+
|
|
225
|
+
elif activity_type == 18:
|
|
226
|
+
default_case = True
|
|
227
|
+
|
|
228
|
+
elif activity_type == 19:
|
|
229
|
+
# Remove project from studio
|
|
230
|
+
|
|
231
|
+
project_id = data["project"]
|
|
232
|
+
studio_id = data["gallery"]
|
|
233
|
+
|
|
234
|
+
raw = f"{username} removed the project https://scratch.mit.edu/projects/{project_id} from studio https://scratch.mit.edu/studios/{studio_id}"
|
|
235
|
+
|
|
236
|
+
self.raw = raw
|
|
237
|
+
self.datetime_created = _time
|
|
238
|
+
self.type = "removeprojectfromstudio"
|
|
239
|
+
|
|
240
|
+
self.username = username
|
|
241
|
+
self.project_id = project_id
|
|
242
|
+
|
|
243
|
+
elif activity_type == 20:
|
|
244
|
+
default_case = True
|
|
245
|
+
|
|
246
|
+
elif activity_type == 21:
|
|
247
|
+
default_case = True
|
|
248
|
+
|
|
249
|
+
elif activity_type == 22:
|
|
250
|
+
# Was promoted to manager for studio
|
|
251
|
+
studio_id = data["gallery"]
|
|
252
|
+
|
|
253
|
+
raw = f"{recipient_username} was promoted to manager by {username} for studio https://scratch.mit.edu/studios/{studio_id}"
|
|
254
|
+
|
|
255
|
+
self.raw = raw
|
|
256
|
+
self.datetime_created = _time
|
|
257
|
+
self.type = "promotetomanager"
|
|
258
|
+
|
|
259
|
+
self.username = username
|
|
260
|
+
self.recipient_username = recipient_username
|
|
261
|
+
self.gallery_id = studio_id
|
|
262
|
+
|
|
263
|
+
elif activity_type == 23:
|
|
264
|
+
default_case = True
|
|
265
|
+
|
|
266
|
+
elif activity_type == 24:
|
|
267
|
+
default_case = True
|
|
268
|
+
|
|
269
|
+
elif activity_type == 25:
|
|
270
|
+
# Update profile
|
|
271
|
+
raw = f"{username} made a profile update"
|
|
272
|
+
|
|
273
|
+
self.raw = raw
|
|
274
|
+
self.datetime_created = _time
|
|
275
|
+
self.type = "updateprofile"
|
|
276
|
+
|
|
277
|
+
self.username = username
|
|
278
|
+
|
|
279
|
+
elif activity_type == 26:
|
|
280
|
+
default_case = True
|
|
281
|
+
|
|
282
|
+
elif activity_type == 27:
|
|
283
|
+
# Comment (quite complicated)
|
|
284
|
+
comment_type: int = data["comment_type"]
|
|
285
|
+
fragment = data["comment_fragment"]
|
|
286
|
+
comment_id = data["comment_id"]
|
|
287
|
+
comment_obj_id = data["comment_obj_id"]
|
|
288
|
+
comment_obj_title = data["comment_obj_title"]
|
|
289
|
+
|
|
290
|
+
if comment_type == 0:
|
|
291
|
+
# Project comment
|
|
292
|
+
raw = f"{username} commented on project https://scratch.mit.edu/projects/{comment_obj_id}/#comments-{comment_id} {fragment!r}"
|
|
293
|
+
|
|
294
|
+
elif comment_type == 1:
|
|
295
|
+
# Profile comment
|
|
296
|
+
raw = f"{username} commented on user https://scratch.mit.edu/users/{comment_obj_title}/#comments-{comment_id} {fragment!r}"
|
|
297
|
+
|
|
298
|
+
elif comment_type == 2:
|
|
299
|
+
# Studio comment
|
|
300
|
+
# Scratch actually provides an incorrect link, but it is fixed here:
|
|
301
|
+
raw = f"{username} commented on studio https://scratch.mit.edu/studios/{comment_obj_id}/comments/#comments-{comment_id} {fragment!r}"
|
|
302
|
+
|
|
303
|
+
else:
|
|
304
|
+
raw = f"{username} commented {fragment!r}" # This should never happen
|
|
305
|
+
|
|
306
|
+
self.raw = raw
|
|
307
|
+
self.datetime_created = _time
|
|
308
|
+
self.type = "addcomment"
|
|
309
|
+
|
|
310
|
+
self.username = username
|
|
311
|
+
|
|
312
|
+
self.comment_type = comment_type
|
|
313
|
+
self.comment_obj_id = comment_obj_id
|
|
314
|
+
self.comment_obj_title = comment_obj_title
|
|
315
|
+
self.comment_id = comment_id
|
|
316
|
+
|
|
317
|
+
else:
|
|
318
|
+
default_case = True
|
|
319
|
+
|
|
320
|
+
if default_case:
|
|
321
|
+
# This is coded in the scratch HTML, haven't found an example of it though
|
|
322
|
+
raw = f"{username} performed an action"
|
|
323
|
+
|
|
324
|
+
self.raw = raw
|
|
325
|
+
self.datetime_created = _time
|
|
326
|
+
self.type = "performaction"
|
|
327
|
+
|
|
328
|
+
self.username = username
|
|
329
|
+
|
|
330
|
+
def _update_from_html(self, data: PageElement):
|
|
331
|
+
|
|
332
|
+
self.raw = data
|
|
333
|
+
|
|
334
|
+
_time = data.find('div').find('span').findNext().findNext().text.strip()
|
|
335
|
+
|
|
336
|
+
if '\xa0' in _time:
|
|
337
|
+
while '\xa0' in _time:
|
|
338
|
+
_time = _time.replace('\xa0', ' ')
|
|
339
|
+
|
|
340
|
+
self.datetime_created = _time
|
|
341
|
+
self.actor_username = data.find('div').find('span').text
|
|
342
|
+
|
|
343
|
+
self.target_name = data.find('div').find('span').findNext().text
|
|
344
|
+
self.target_link = data.find('div').find('span').findNext()["href"]
|
|
345
|
+
self.target_id = data.find('div').find('span').findNext()["href"].split("/")[-2]
|
|
346
|
+
|
|
347
|
+
self.type = data.find('div').find_all('span')[0].next_sibling.strip()
|
|
348
|
+
if self.type == "loved":
|
|
349
|
+
self.type = "loveproject"
|
|
350
|
+
|
|
351
|
+
elif self.type == "favorited":
|
|
352
|
+
self.type = "favoriteproject"
|
|
353
|
+
|
|
354
|
+
elif "curator" in self.type:
|
|
355
|
+
self.type = "becomecurator"
|
|
356
|
+
|
|
357
|
+
elif "shared" in self.type:
|
|
358
|
+
self.type = "shareproject"
|
|
359
|
+
|
|
360
|
+
elif "is now following" in self.type:
|
|
361
|
+
if "users" in self.target_link:
|
|
362
|
+
self.type = "followuser"
|
|
363
|
+
else:
|
|
364
|
+
self.type = "followstudio"
|
|
365
|
+
|
|
366
|
+
return True
|
|
367
|
+
|
|
368
|
+
def actor(self):
|
|
369
|
+
"""
|
|
370
|
+
Returns the user that performed the activity as User object
|
|
371
|
+
"""
|
|
372
|
+
return self._make_linked_object("username", self.actor_username, user.User, exceptions.UserNotFound)
|
|
373
|
+
|
|
374
|
+
def target(self):
|
|
375
|
+
"""
|
|
376
|
+
Returns the activity's target (depending on the activity, this is either a User, Project, Studio or Comment object).
|
|
377
|
+
May also return None if the activity type is unknown.
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
if "project" in self.type: # target is a project
|
|
381
|
+
if "target_id" in self.__dict__:
|
|
382
|
+
return self._make_linked_object("id", self.target_id, project.Project, exceptions.ProjectNotFound)
|
|
383
|
+
if "project_id" in self.__dict__:
|
|
384
|
+
return self._make_linked_object("id", self.project_id, project.Project, exceptions.ProjectNotFound)
|
|
385
|
+
|
|
386
|
+
if self.type == "becomecurator" or self.type == "followstudio": # target is a studio
|
|
387
|
+
if "target_id" in self.__dict__:
|
|
388
|
+
return self._make_linked_object("id", self.target_id, studio.Studio, exceptions.StudioNotFound)
|
|
389
|
+
if "gallery_id" in self.__dict__:
|
|
390
|
+
return self._make_linked_object("id", self.gallery_id, studio.Studio, exceptions.StudioNotFound)
|
|
391
|
+
# NOTE: the "becomecurator" type is ambigous - if it is inside the studio activity tab, the target is the user who joined
|
|
392
|
+
if "username" in self.__dict__:
|
|
393
|
+
return self._make_linked_object("username", self.username, user.User, exceptions.UserNotFound)
|
|
394
|
+
|
|
395
|
+
if self.type == "followuser" or "curator" in self.type: # target is a user
|
|
396
|
+
if "target_name" in self.__dict__:
|
|
397
|
+
return self._make_linked_object("username", self.target_name, user.User, exceptions.UserNotFound)
|
|
398
|
+
if "followed_username" in self.__dict__:
|
|
399
|
+
return self._make_linked_object("username", self.followed_username, user.User, exceptions.UserNotFound)
|
|
400
|
+
if "recipient_username" in self.__dict__: # the recipient_username field always indicates the target is a user
|
|
401
|
+
return self._make_linked_object("username", self.recipient_username, user.User, exceptions.UserNotFound)
|
|
402
|
+
|
|
403
|
+
if self.type == "addcomment": # target is a comment
|
|
404
|
+
if self.comment_type == 0:
|
|
405
|
+
_c = project.Project(id=self.comment_obj_id, author_name=self._session.username,
|
|
406
|
+
_session=self._session).comment_by_id(self.comment_id)
|
|
407
|
+
if self.comment_type == 1:
|
|
408
|
+
_c = user.User(username=self.comment_obj_title, _session=self._session).comment_by_id(self.comment_id)
|
|
409
|
+
if self.comment_type == 2:
|
|
410
|
+
_c = user.User(id=self.comment_obj_id, _session=self._session).comment_by_id(self.comment_id)
|
|
411
|
+
else:
|
|
412
|
+
raise ValueError(f"{self.comment_type} is an invalid comment type")
|
|
413
|
+
|
|
414
|
+
return _c
|