scratchattach 2.1.8__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.
Files changed (59) hide show
  1. scratchattach/__init__.py +28 -25
  2. scratchattach/cloud/__init__.py +2 -0
  3. scratchattach/cloud/_base.py +454 -282
  4. scratchattach/cloud/cloud.py +171 -168
  5. scratchattach/editor/__init__.py +21 -0
  6. scratchattach/editor/asset.py +199 -0
  7. scratchattach/editor/backpack_json.py +117 -0
  8. scratchattach/editor/base.py +142 -0
  9. scratchattach/editor/block.py +507 -0
  10. scratchattach/editor/blockshape.py +353 -0
  11. scratchattach/editor/build_defaulting.py +47 -0
  12. scratchattach/editor/comment.py +74 -0
  13. scratchattach/editor/commons.py +243 -0
  14. scratchattach/editor/extension.py +43 -0
  15. scratchattach/editor/field.py +90 -0
  16. scratchattach/editor/inputs.py +132 -0
  17. scratchattach/editor/meta.py +106 -0
  18. scratchattach/editor/monitor.py +175 -0
  19. scratchattach/editor/mutation.py +317 -0
  20. scratchattach/editor/pallete.py +91 -0
  21. scratchattach/editor/prim.py +170 -0
  22. scratchattach/editor/project.py +273 -0
  23. scratchattach/editor/sbuild.py +2837 -0
  24. scratchattach/editor/sprite.py +586 -0
  25. scratchattach/editor/twconfig.py +113 -0
  26. scratchattach/editor/vlb.py +134 -0
  27. scratchattach/eventhandlers/_base.py +99 -92
  28. scratchattach/eventhandlers/cloud_events.py +110 -103
  29. scratchattach/eventhandlers/cloud_recorder.py +26 -21
  30. scratchattach/eventhandlers/cloud_requests.py +460 -452
  31. scratchattach/eventhandlers/cloud_server.py +246 -244
  32. scratchattach/eventhandlers/cloud_storage.py +135 -134
  33. scratchattach/eventhandlers/combine.py +29 -27
  34. scratchattach/eventhandlers/filterbot.py +160 -159
  35. scratchattach/eventhandlers/message_events.py +41 -40
  36. scratchattach/other/other_apis.py +284 -212
  37. scratchattach/other/project_json_capabilities.py +475 -546
  38. scratchattach/site/_base.py +64 -46
  39. scratchattach/site/activity.py +414 -122
  40. scratchattach/site/backpack_asset.py +118 -84
  41. scratchattach/site/classroom.py +430 -142
  42. scratchattach/site/cloud_activity.py +107 -103
  43. scratchattach/site/comment.py +220 -190
  44. scratchattach/site/forum.py +400 -399
  45. scratchattach/site/project.py +806 -787
  46. scratchattach/site/session.py +1134 -867
  47. scratchattach/site/studio.py +611 -609
  48. scratchattach/site/user.py +835 -837
  49. scratchattach/utils/commons.py +243 -148
  50. scratchattach/utils/encoder.py +157 -156
  51. scratchattach/utils/enums.py +197 -190
  52. scratchattach/utils/exceptions.py +233 -206
  53. scratchattach/utils/requests.py +67 -59
  54. {scratchattach-2.1.8.dist-info → scratchattach-2.1.10a0.dist-info}/LICENSE +21 -21
  55. {scratchattach-2.1.8.dist-info → scratchattach-2.1.10a0.dist-info}/METADATA +154 -146
  56. scratchattach-2.1.10a0.dist-info/RECORD +62 -0
  57. {scratchattach-2.1.8.dist-info → scratchattach-2.1.10a0.dist-info}/WHEEL +1 -1
  58. scratchattach-2.1.8.dist-info/RECORD +0 -40
  59. {scratchattach-2.1.8.dist-info → scratchattach-2.1.10a0.dist-info}/top_level.txt +0 -0
@@ -1,46 +1,64 @@
1
- from abc import ABC, abstractmethod
2
- import requests
3
- from threading import Thread
4
- from ..utils import exceptions, commons
5
-
6
- class BaseSiteComponent(ABC):
7
-
8
- def update(self):
9
- """
10
- Updates the attributes of the object by performing an API response. Returns True if the update was successful.
11
- """
12
- response = self.update_function(
13
- self.update_API,
14
- headers = self._headers,
15
- cookies = self._cookies, timeout=10
16
- )
17
- # Check for 429 error:
18
- if "429" in str(response):
19
- return "429"
20
- if response.text == '{\n "response": "Too many requests"\n}':
21
- return "429"
22
- # If no error: Parse JSON:
23
- response = response.json()
24
- if "code" in response:
25
- return False
26
- return self._update_from_dict(response)
27
-
28
- @abstractmethod
29
- def _update_from_dict(self, data) -> bool:
30
- """
31
- Parses the API response that is fetched in the update-method. Class specific, must be overridden in classes inheriting from this one.
32
- """
33
- pass
34
-
35
- def _assert_auth(self):
36
- if self._session is None:
37
- raise exceptions.Unauthenticated(
38
- "You need to use session.connect_xyz (NOT get_xyz) in order to perform this operation.")
39
-
40
- def _make_linked_object(self, identificator_id, identificator, Class, NotFoundException):
41
- """
42
- Internal function for making a linked object (authentication kept) based on an identificator (like a project id or username)
43
- Class must inherit from BaseSiteComponent
44
- """
45
- return commons._get_object(identificator_id, identificator, Class, NotFoundException, self._session)
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
+ """
@@ -1,122 +1,414 @@
1
- """Activity and CloudActivity class"""
2
-
3
- import json
4
- import re
5
- import time
6
-
7
- from . import user
8
- from . import session
9
- from . import project
10
- from . import studio
11
- from . import forum, comment
12
- from ..utils import exceptions
13
- from ._base import BaseSiteComponent
14
- from ..utils.commons import headers
15
- from bs4 import BeautifulSoup
16
-
17
- from ..utils.requests import Requests as requests
18
-
19
- class Activity(BaseSiteComponent):
20
-
21
- '''
22
- Represents a Scratch activity (message or other user page activity)
23
- '''
24
-
25
- def str(self):
26
- return str(self.raw)
27
-
28
- def __init__(self, **entries):
29
-
30
- # Set attributes every Activity object needs to have:
31
- self._session = None
32
- self.raw = None
33
-
34
- # Update attributes from entries dict:
35
- self.__dict__.update(entries)
36
-
37
- def update():
38
- print("Warning: Activity objects can't be updated")
39
- return False # Objects of this type cannot be updated
40
-
41
- def _update_from_dict(self, data):
42
- self.raw = data
43
- self.__dict__.update(data)
44
- return True
45
-
46
- def _update_from_html(self, data):
47
-
48
- self.raw = data
49
-
50
- time=data.find('div').find('span').findNext().findNext().text.strip()
51
-
52
- if '\xa0' in time:
53
- while '\xa0' in time: time=time.replace('\xa0', ' ')
54
-
55
- self.time = time
56
- self.actor_username=(data.find('div').find('span').text)
57
-
58
- self.target_name=(data.find('div').find('span').findNext().text)
59
- self.target_link=(data.find('div').find('span').findNext()["href"])
60
- self.target_id=(data.find('div').find('span').findNext()["href"].split("/")[-2])
61
-
62
- self.type=data.find('div').find_all('span')[0].next_sibling.strip()
63
- if self.type == "loved":
64
- self.type = "loveproject"
65
- if self.type == "favorited":
66
- self.type = "favoriteproject"
67
- if "curator" in self.type:
68
- self.type = "becomecurator"
69
- if "shared" in self.type:
70
- self.type = "shareproject"
71
- if "is now following" in self.type:
72
- if "users" in self.target_link:
73
- self.type = "followuser"
74
- else:
75
- self.type = "followstudio"
76
-
77
- return True
78
-
79
- def actor(self):
80
- """
81
- Returns the user that performed the activity as User object
82
- """
83
- return self._make_linked_object("username", self.actor_username, user.User, exceptions.UserNotFound)
84
-
85
- def target(self):
86
- """
87
- Returns the activity's target (depending on the activity, this is either a User, Project, Studio or Comment object).
88
- May also return None if the activity type is unknown.
89
- """
90
-
91
- if "project" in self.type: # target is a project
92
- if "target_id" in self.__dict__:
93
- return self._make_linked_object("id", self.target_id, project.Project, exceptions.ProjectNotFound)
94
- if "project_id" in self.__dict__:
95
- return self._make_linked_object("id", self.project_id, project.Project, exceptions.ProjectNotFound)
96
-
97
- if self.type == "becomecurator" or self.type == "followstudio": # target is a studio
98
- if "target_id" in self.__dict__:
99
- return self._make_linked_object("id", self.target_id, studio.Studio, exceptions.StudioNotFound)
100
- if "gallery_id" in self.__dict__:
101
- return self._make_linked_object("id", self.gallery_id, studio.Studio, exceptions.StudioNotFound)
102
- # NOTE: the "becomecurator" type is ambigous - if it is inside the studio activity tab, the target is the user who joined
103
- if "username" in self.__dict__:
104
- return self._make_linked_object("username", self.username, user.User, exceptions.UserNotFound)
105
-
106
- if self.type == "followuser" or "curator" in self.type: # target is a user
107
- if "target_name" in self.__dict__:
108
- return self._make_linked_object("username", self.target_name, user.User, exceptions.UserNotFound)
109
- if "followed_username" in self.__dict__:
110
- return self._make_linked_object("username", self.followed_username, user.User, exceptions.UserNotFound)
111
- if "recipient_username" in self.__dict__: # the recipient_username field always indicates the target is a user
112
- return self._make_linked_object("username", self.recipient_username, user.User, exceptions.UserNotFound)
113
-
114
- if self.type == "addcomment": # target is a comment
115
- if self.comment_type == 0:
116
- _c = project.Project(id=self.comment_obj_id, author_name=self._session.username, _session=self._session).comment_by_id(self.comment_id)
117
- if self.comment_type == 1:
118
- _c = user.User(username=self.comment_obj_title, _session=self._session).comment_by_id(self.comment_id)
119
- if self.comment_type == 2:
120
- _c = user.User(id=self.comment_obj_id, _session=self._session).comment_by_id(self.comment_id)
121
- return _c
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