scratchattach 2.1.15b0__py3-none-any.whl → 3.0.0b1__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.
- cli/__about__.py +1 -0
- cli/__init__.py +26 -0
- cli/cmd/__init__.py +4 -0
- cli/cmd/group.py +127 -0
- cli/cmd/login.py +60 -0
- cli/cmd/profile.py +7 -0
- cli/cmd/sessions.py +5 -0
- cli/context.py +142 -0
- cli/db.py +66 -0
- cli/namespace.py +14 -0
- {scratchattach/cloud → cloud}/_base.py +112 -87
- {scratchattach/cloud → cloud}/cloud.py +16 -16
- {scratchattach/editor → editor}/__init__.py +2 -1
- {scratchattach/editor → editor}/asset.py +26 -14
- {scratchattach/editor → editor}/backpack_json.py +3 -5
- {scratchattach/editor → editor}/base.py +2 -4
- {scratchattach/editor → editor}/block.py +27 -22
- {scratchattach/editor → editor}/blockshape.py +1 -1
- {scratchattach/editor → editor}/build_defaulting.py +2 -2
- editor/commons.py +145 -0
- {scratchattach/editor → editor}/field.py +1 -1
- {scratchattach/editor → editor}/inputs.py +6 -3
- {scratchattach/editor → editor}/meta.py +10 -7
- {scratchattach/editor → editor}/monitor.py +10 -8
- {scratchattach/editor → editor}/mutation.py +68 -11
- {scratchattach/editor → editor}/pallete.py +1 -3
- {scratchattach/editor → editor}/prim.py +4 -0
- {scratchattach/editor → editor}/project.py +118 -16
- {scratchattach/editor → editor}/sprite.py +25 -15
- {scratchattach/editor → editor}/vlb.py +2 -2
- {scratchattach/eventhandlers → eventhandlers}/_base.py +1 -0
- {scratchattach/eventhandlers → eventhandlers}/cloud_events.py +26 -6
- {scratchattach/eventhandlers → eventhandlers}/cloud_recorder.py +4 -4
- {scratchattach/eventhandlers → eventhandlers}/cloud_requests.py +139 -54
- {scratchattach/eventhandlers → eventhandlers}/cloud_server.py +6 -3
- {scratchattach/eventhandlers → eventhandlers}/cloud_storage.py +1 -2
- eventhandlers/filterbot.py +163 -0
- other/other_apis.py +598 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +7 -11
- scratchattach-3.0.0b1.dist-info/RECORD +79 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +1 -1
- scratchattach-3.0.0b1.dist-info/entry_points.txt +2 -0
- scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
- {scratchattach/site → site}/_base.py +32 -5
- site/activity.py +426 -0
- {scratchattach/site → site}/alert.py +4 -5
- {scratchattach/site → site}/backpack_asset.py +2 -1
- {scratchattach/site → site}/classroom.py +80 -73
- {scratchattach/site → site}/cloud_activity.py +43 -29
- {scratchattach/site → site}/comment.py +86 -100
- {scratchattach/site → site}/forum.py +8 -4
- site/placeholder.py +132 -0
- {scratchattach/site → site}/project.py +228 -122
- {scratchattach/site → site}/session.py +156 -71
- {scratchattach/site → site}/studio.py +139 -46
- site/typed_dicts.py +151 -0
- {scratchattach/site → site}/user.py +511 -215
- {scratchattach/utils → utils}/commons.py +12 -4
- {scratchattach/utils → utils}/encoder.py +7 -4
- {scratchattach/utils → utils}/enums.py +1 -0
- {scratchattach/utils → utils}/exceptions.py +36 -2
- utils/optional_async.py +154 -0
- utils/requests.py +306 -0
- scratchattach/__init__.py +0 -29
- scratchattach/editor/commons.py +0 -273
- scratchattach/eventhandlers/filterbot.py +0 -161
- scratchattach/other/other_apis.py +0 -284
- scratchattach/site/activity.py +0 -382
- scratchattach/utils/requests.py +0 -93
- scratchattach-2.1.15b0.dist-info/RECORD +0 -66
- scratchattach-2.1.15b0.dist-info/top_level.txt +0 -1
- {scratchattach/cloud → cloud}/__init__.py +0 -0
- {scratchattach/editor → editor}/code_translation/__init__.py +0 -0
- {scratchattach/editor → editor}/code_translation/parse.py +0 -0
- {scratchattach/editor → editor}/comment.py +0 -0
- {scratchattach/editor → editor}/extension.py +0 -0
- {scratchattach/editor → editor}/twconfig.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/__init__.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/combine.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/message_events.py +0 -0
- {scratchattach/other → other}/__init__.py +0 -0
- {scratchattach/other → other}/project_json_capabilities.py +0 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {scratchattach/site → site}/__init__.py +0 -0
- {scratchattach/site → site}/browser_cookie3_stub.py +0 -0
- {scratchattach/site → site}/browser_cookies.py +0 -0
- {scratchattach/utils → utils}/__init__.py +0 -0
|
@@ -2,23 +2,29 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
|
+
import pprint
|
|
5
6
|
import random
|
|
6
7
|
import base64
|
|
7
8
|
import time
|
|
9
|
+
import warnings
|
|
8
10
|
import zipfile
|
|
9
11
|
from io import BytesIO
|
|
12
|
+
from typing import Callable
|
|
10
13
|
|
|
11
|
-
from typing import Any
|
|
14
|
+
from typing import Any, Optional
|
|
15
|
+
from typing_extensions import deprecated
|
|
12
16
|
from . import user, comment, studio
|
|
13
17
|
from scratchattach.utils import exceptions
|
|
14
18
|
from scratchattach.utils import commons
|
|
15
19
|
from scratchattach.utils.commons import empty_project_json, headers
|
|
16
20
|
from ._base import BaseSiteComponent
|
|
17
|
-
from scratchattach.other.project_json_capabilities import ProjectBody
|
|
21
|
+
# from scratchattach.other.project_json_capabilities import ProjectBody
|
|
22
|
+
from scratchattach import editor
|
|
18
23
|
from scratchattach.utils.requests import requests
|
|
19
24
|
|
|
20
25
|
CREATE_PROJECT_USES: list[float] = []
|
|
21
26
|
|
|
27
|
+
|
|
22
28
|
class PartialProject(BaseSiteComponent):
|
|
23
29
|
"""
|
|
24
30
|
Represents an unshared Scratch project that can't be accessed.
|
|
@@ -27,10 +33,10 @@ class PartialProject(BaseSiteComponent):
|
|
|
27
33
|
def __str__(self):
|
|
28
34
|
return f"Unshared project with id {self.id}"
|
|
29
35
|
|
|
30
|
-
def __init__(self, **entries):
|
|
36
|
+
def __init__(self, **entries) -> None:
|
|
31
37
|
|
|
32
38
|
# Info on how the .update method has to fetch the data:
|
|
33
|
-
self.update_function = requests.get
|
|
39
|
+
self.update_function: Callable = requests.get
|
|
34
40
|
self.update_api = f"https://api.scratch.mit.edu/projects/{entries['id']}"
|
|
35
41
|
|
|
36
42
|
# Set attributes every Project object needs to have:
|
|
@@ -39,10 +45,11 @@ class PartialProject(BaseSiteComponent):
|
|
|
39
45
|
self.id = 0
|
|
40
46
|
self.instructions = None
|
|
41
47
|
self.parent_title = None
|
|
48
|
+
self._moderation_status = None
|
|
42
49
|
|
|
43
50
|
# Update attributes from entries dict:
|
|
44
51
|
self.__dict__.update(entries)
|
|
45
|
-
|
|
52
|
+
|
|
46
53
|
# Headers and cookies:
|
|
47
54
|
if self._session is None:
|
|
48
55
|
self._headers = headers
|
|
@@ -61,50 +68,114 @@ class PartialProject(BaseSiteComponent):
|
|
|
61
68
|
self.id = int(data["id"])
|
|
62
69
|
except KeyError:
|
|
63
70
|
pass
|
|
64
|
-
try:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
try:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
try:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
try:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
try:
|
|
72
|
+
self.url = "https://scratch.mit.edu/projects/" + str(self.id)
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
try:
|
|
76
|
+
self.author_name = data["author"]["username"]
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
try:
|
|
80
|
+
self.author_name = data["username"]
|
|
81
|
+
except Exception:
|
|
82
|
+
pass
|
|
83
|
+
try:
|
|
84
|
+
self.comments_allowed = data["comments_allowed"]
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
try:
|
|
88
|
+
self.instructions = data["instructions"]
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
try:
|
|
92
|
+
self.notes = data["description"]
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
try:
|
|
96
|
+
self.created = data["history"]["created"]
|
|
97
|
+
except Exception:
|
|
98
|
+
pass
|
|
99
|
+
try:
|
|
100
|
+
self.last_modified = data["history"]["modified"]
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
try:
|
|
104
|
+
self.share_date = data["history"]["shared"]
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
try:
|
|
108
|
+
self.thumbnail_url = data["image"]
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
84
111
|
try:
|
|
85
112
|
self.remix_parent = data["remix"]["parent"]
|
|
86
113
|
self.remix_root = data["remix"]["root"]
|
|
87
114
|
except Exception:
|
|
88
115
|
self.remix_parent = None
|
|
89
116
|
self.remix_root = None
|
|
90
|
-
try:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
try:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
try:
|
|
99
|
-
|
|
117
|
+
try:
|
|
118
|
+
self.favorites = data["stats"]["favorites"]
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
try:
|
|
122
|
+
self.loves = data["stats"]["loves"]
|
|
123
|
+
except Exception:
|
|
124
|
+
pass
|
|
125
|
+
try:
|
|
126
|
+
self.remix_count = data["stats"]["remixes"]
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
try:
|
|
130
|
+
self.views = data["stats"]["views"]
|
|
131
|
+
except Exception:
|
|
132
|
+
pass
|
|
133
|
+
try:
|
|
134
|
+
self.title = data["title"]
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
100
137
|
try:
|
|
101
138
|
self.project_token = data["project_token"]
|
|
102
139
|
except Exception:
|
|
103
140
|
self.project_token = None
|
|
104
|
-
if "code" in data:
|
|
141
|
+
if "code" in data: # Project is unshared -> return false
|
|
105
142
|
return False
|
|
106
143
|
return True
|
|
107
144
|
|
|
145
|
+
def __rich__(self):
|
|
146
|
+
from rich.panel import Panel
|
|
147
|
+
from rich.table import Table
|
|
148
|
+
from rich import box
|
|
149
|
+
from rich.markup import escape
|
|
150
|
+
|
|
151
|
+
url = f"[link={self.url}]{self.title}[/]"
|
|
152
|
+
|
|
153
|
+
ret = Table.grid(expand=True)
|
|
154
|
+
ret.add_column(ratio=1)
|
|
155
|
+
ret.add_column(ratio=3)
|
|
156
|
+
|
|
157
|
+
info = Table(box=box.SIMPLE)
|
|
158
|
+
info.add_column(url, overflow="fold")
|
|
159
|
+
info.add_column(f"#{self.id}", overflow="fold")
|
|
160
|
+
|
|
161
|
+
info.add_row("By", self.author_name)
|
|
162
|
+
info.add_row("Created", escape(self.created))
|
|
163
|
+
info.add_row("Shared", escape(self.share_date))
|
|
164
|
+
info.add_row("Modified", escape(self.last_modified))
|
|
165
|
+
info.add_row("Comments allowed", escape(str(self.comments_allowed)))
|
|
166
|
+
info.add_row("Loves", str(self.loves))
|
|
167
|
+
info.add_row("Faves", str(self.favorites))
|
|
168
|
+
info.add_row("Remixes", str(self.remix_count))
|
|
169
|
+
info.add_row("Views", str(self.views))
|
|
170
|
+
|
|
171
|
+
desc = Table(box=box.SIMPLE)
|
|
172
|
+
desc.add_row("Instructions", escape(self.instructions))
|
|
173
|
+
desc.add_row("Notes & Credits", escape(self.notes))
|
|
174
|
+
|
|
175
|
+
ret.add_row(Panel(info, title=url), Panel(desc, title="Description"))
|
|
176
|
+
|
|
177
|
+
return ret
|
|
178
|
+
|
|
108
179
|
@property
|
|
109
180
|
def embed_url(self):
|
|
110
181
|
"""
|
|
@@ -113,7 +184,7 @@ class PartialProject(BaseSiteComponent):
|
|
|
113
184
|
"""
|
|
114
185
|
return f"{self.url}/embed"
|
|
115
186
|
|
|
116
|
-
def remixes(self, *, limit=40, offset=0):
|
|
187
|
+
def remixes(self, *, limit=40, offset=0) -> list[Project]:
|
|
117
188
|
"""
|
|
118
189
|
Returns:
|
|
119
190
|
list<scratchattach.project.Project>: A list containing the remixes of the project, each project is represented by a Project object.
|
|
@@ -129,11 +200,11 @@ class PartialProject(BaseSiteComponent):
|
|
|
129
200
|
"""
|
|
130
201
|
p = get_project(self.id)
|
|
131
202
|
return isinstance(p, Project)
|
|
132
|
-
|
|
203
|
+
|
|
133
204
|
def raw_json_or_empty(self) -> dict[str, Any]:
|
|
134
205
|
return empty_project_json
|
|
135
|
-
|
|
136
|
-
def create_remix(self, *, title=None, project_json=None):
|
|
206
|
+
|
|
207
|
+
def create_remix(self, *, title=None, project_json=None): # not working
|
|
137
208
|
"""
|
|
138
209
|
Creates a project on the Scratch website.
|
|
139
210
|
|
|
@@ -145,7 +216,7 @@ class PartialProject(BaseSiteComponent):
|
|
|
145
216
|
|
|
146
217
|
if title is None:
|
|
147
218
|
if "title" in self.__dict__:
|
|
148
|
-
title = self.title+" remix"
|
|
219
|
+
title = self.title + " remix"
|
|
149
220
|
else:
|
|
150
221
|
title = " remix"
|
|
151
222
|
if project_json is None:
|
|
@@ -157,7 +228,8 @@ class PartialProject(BaseSiteComponent):
|
|
|
157
228
|
if CREATE_PROJECT_USES[-1] < time.time() - 300:
|
|
158
229
|
CREATE_PROJECT_USES.pop()
|
|
159
230
|
else:
|
|
160
|
-
raise exceptions.BadRequest(
|
|
231
|
+
raise exceptions.BadRequest(
|
|
232
|
+
"Rate limit for remixing Scratch projects exceeded.\nThis rate limit is enforced by scratchattach, not by the Scratch API.\nFor security reasons, it cannot be turned off.\n\nDon't spam-create projects, it WILL get you banned.")
|
|
161
233
|
return
|
|
162
234
|
CREATE_PROJECT_USES.insert(0, time.time())
|
|
163
235
|
|
|
@@ -167,11 +239,12 @@ class PartialProject(BaseSiteComponent):
|
|
|
167
239
|
'title': title,
|
|
168
240
|
}
|
|
169
241
|
|
|
170
|
-
response = requests.post('https://projects.scratch.mit.edu/', params=params, cookies=self._cookies,
|
|
242
|
+
response = requests.post('https://projects.scratch.mit.edu/', params=params, cookies=self._cookies,
|
|
243
|
+
headers=self._headers, json=project_json).json()
|
|
171
244
|
_project = self._session.connect_project(response["content-name"])
|
|
172
245
|
_project.parent_title = base64.b64decode(response['content-title']).decode('utf-8').split(' remix')[0]
|
|
173
246
|
return _project
|
|
174
|
-
|
|
247
|
+
|
|
175
248
|
def load_description(self):
|
|
176
249
|
"""
|
|
177
250
|
Gets the instructions of the unshared project. Requires authentication.
|
|
@@ -212,7 +285,7 @@ class Project(PartialProject):
|
|
|
212
285
|
:.share_date:
|
|
213
286
|
|
|
214
287
|
:.thumbnail_url:
|
|
215
|
-
|
|
288
|
+
|
|
216
289
|
:.remix_parent:
|
|
217
290
|
|
|
218
291
|
:.remix_root:
|
|
@@ -220,7 +293,7 @@ class Project(PartialProject):
|
|
|
220
293
|
:.loves: The project's love count
|
|
221
294
|
|
|
222
295
|
:.favorites: The project's favorite count
|
|
223
|
-
|
|
296
|
+
|
|
224
297
|
:.remix_count: The number of remixes
|
|
225
298
|
|
|
226
299
|
:.views: The view count
|
|
@@ -230,8 +303,16 @@ class Project(PartialProject):
|
|
|
230
303
|
:.update(): Updates the attributes
|
|
231
304
|
"""
|
|
232
305
|
|
|
306
|
+
def __repr__(self):
|
|
307
|
+
return f"-P {self.id} ({self.title})"
|
|
308
|
+
|
|
233
309
|
def __str__(self):
|
|
234
|
-
return
|
|
310
|
+
return repr(self)
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def thumbnail(self) -> bytes:
|
|
314
|
+
with requests.no_error_handling():
|
|
315
|
+
return requests.get(self.thumbnail_url).content
|
|
235
316
|
|
|
236
317
|
def _assert_permission(self):
|
|
237
318
|
self._assert_auth()
|
|
@@ -242,7 +323,9 @@ class Project(PartialProject):
|
|
|
242
323
|
def load_description(self):
|
|
243
324
|
# Overrides the load_description method that exists for unshared projects
|
|
244
325
|
self.update()
|
|
245
|
-
|
|
326
|
+
|
|
327
|
+
# -- Project contents (body/json) -- #
|
|
328
|
+
|
|
246
329
|
def download(self, *, filename=None, dir="."):
|
|
247
330
|
"""
|
|
248
331
|
Downloads the project json to the given directory.
|
|
@@ -270,7 +353,8 @@ class Project(PartialProject):
|
|
|
270
353
|
"Method only works for projects created with Scratch 3"
|
|
271
354
|
)
|
|
272
355
|
)
|
|
273
|
-
|
|
356
|
+
|
|
357
|
+
@deprecated("Use raw_json instead")
|
|
274
358
|
def get_json(self) -> str:
|
|
275
359
|
"""
|
|
276
360
|
Downloads the project json and returns it as a string
|
|
@@ -290,17 +374,15 @@ class Project(PartialProject):
|
|
|
290
374
|
)
|
|
291
375
|
)
|
|
292
376
|
|
|
293
|
-
def body(self):
|
|
377
|
+
def body(self) -> editor.Project:
|
|
294
378
|
"""
|
|
295
379
|
Method only works for project created with Scratch 3.
|
|
296
380
|
|
|
297
381
|
Returns:
|
|
298
|
-
scratchattach.
|
|
382
|
+
scratchattach.editor.Project: The contents of the project as editor Project object
|
|
299
383
|
"""
|
|
300
384
|
raw_json = self.raw_json()
|
|
301
|
-
|
|
302
|
-
pb.from_json(raw_json)
|
|
303
|
-
return pb
|
|
385
|
+
return editor.Project.from_json(raw_json)
|
|
304
386
|
|
|
305
387
|
def raw_json(self):
|
|
306
388
|
"""
|
|
@@ -337,7 +419,7 @@ class Project(PartialProject):
|
|
|
337
419
|
|
|
338
420
|
def raw_json_or_empty(self):
|
|
339
421
|
return self.raw_json()
|
|
340
|
-
|
|
422
|
+
|
|
341
423
|
def creator_agent(self):
|
|
342
424
|
"""
|
|
343
425
|
Method only works for project created with Scratch 3.
|
|
@@ -347,7 +429,50 @@ class Project(PartialProject):
|
|
|
347
429
|
"""
|
|
348
430
|
return self.raw_json()["meta"]["agent"]
|
|
349
431
|
|
|
350
|
-
def
|
|
432
|
+
def set_body(self, project_body: editor.Project):
|
|
433
|
+
"""
|
|
434
|
+
Sets the project's contents You can use this to upload projects to the Scratch website.
|
|
435
|
+
Returns a dict with Scratch's raw JSON API response.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
project_body (scratchattach.ProjectBody): A ProjectBody object containing the contents of the project
|
|
439
|
+
"""
|
|
440
|
+
self._assert_permission()
|
|
441
|
+
|
|
442
|
+
return self.set_json(project_body.to_json())
|
|
443
|
+
|
|
444
|
+
def set_json(self, json_data):
|
|
445
|
+
"""
|
|
446
|
+
Sets the project json. You can use this to upload projects to the Scratch website.
|
|
447
|
+
Returns a dict with Scratch's raw JSON API response.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
json_data (dict or JSON): The new project JSON as encoded JSON object or as dict
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
self._assert_permission()
|
|
454
|
+
|
|
455
|
+
if not isinstance(json_data, dict):
|
|
456
|
+
json_data = json.loads(json_data)
|
|
457
|
+
|
|
458
|
+
return requests.put(
|
|
459
|
+
f"https://projects.scratch.mit.edu/{self.id}",
|
|
460
|
+
headers=self._headers,
|
|
461
|
+
cookies=self._cookies,
|
|
462
|
+
json=json_data,
|
|
463
|
+
).json()
|
|
464
|
+
|
|
465
|
+
def upload_json_from(self, project_id: int | str):
|
|
466
|
+
"""
|
|
467
|
+
Uploads the project json from the project with the given id to the project represented by this Project object
|
|
468
|
+
"""
|
|
469
|
+
self._assert_auth()
|
|
470
|
+
other_project = self._session.connect_project(project_id) # type: ignore
|
|
471
|
+
self.set_json(other_project.raw_json())
|
|
472
|
+
|
|
473
|
+
# -- other -- #
|
|
474
|
+
|
|
475
|
+
def author(self) -> user.User:
|
|
351
476
|
"""
|
|
352
477
|
Returns:
|
|
353
478
|
scratchattach.user.User: An object representing the Scratch user who created this project.
|
|
@@ -360,7 +485,8 @@ class Project(PartialProject):
|
|
|
360
485
|
list<scratchattach.studio.Studio>: A list containing the studios this project is in, each studio is represented by a Studio object.
|
|
361
486
|
"""
|
|
362
487
|
response = commons.api_iterative(
|
|
363
|
-
f"https://api.scratch.mit.edu/users/{self.author_name}/projects/{self.id}/studios", limit=limit,
|
|
488
|
+
f"https://api.scratch.mit.edu/users/{self.author_name}/projects/{self.id}/studios", limit=limit,
|
|
489
|
+
offset=offset, add_params=f"&cachebust={random.randint(0, 9999)}")
|
|
364
490
|
return commons.parse_object_list(response, studio.Studio, self._session)
|
|
365
491
|
|
|
366
492
|
def comments(self, *, limit=40, offset=0) -> list['comment.Comment']:
|
|
@@ -376,7 +502,8 @@ class Project(PartialProject):
|
|
|
376
502
|
"""
|
|
377
503
|
|
|
378
504
|
response = commons.api_iterative(
|
|
379
|
-
f"https://api.scratch.mit.edu/users/{self.author_name}/projects/{self.id}/comments/", limit=limit,
|
|
505
|
+
f"https://api.scratch.mit.edu/users/{self.author_name}/projects/{self.id}/comments/", limit=limit,
|
|
506
|
+
offset=offset, add_params=f"&cachebust={random.randint(0, 9999)}")
|
|
380
507
|
for i in response:
|
|
381
508
|
i["source"] = "project"
|
|
382
509
|
i["source_id"] = self.id
|
|
@@ -384,7 +511,8 @@ class Project(PartialProject):
|
|
|
384
511
|
|
|
385
512
|
def comment_replies(self, *, comment_id, limit=40, offset=0):
|
|
386
513
|
response = commons.api_iterative(
|
|
387
|
-
f"https://api.scratch.mit.edu/users/{self.author_name}/projects/{self.id}/comments/{comment_id}/replies/",
|
|
514
|
+
f"https://api.scratch.mit.edu/users/{self.author_name}/projects/{self.id}/comments/{comment_id}/replies/",
|
|
515
|
+
limit=limit, offset=offset, add_params=f"&cachebust={random.randint(0, 9999)}")
|
|
388
516
|
for x in response:
|
|
389
517
|
x["parent_id"] = comment_id
|
|
390
518
|
x["source"] = "project"
|
|
@@ -396,17 +524,22 @@ class Project(PartialProject):
|
|
|
396
524
|
Returns:
|
|
397
525
|
scratchattach.comments.Comment: A Comment object representing the requested comment.
|
|
398
526
|
"""
|
|
399
|
-
|
|
527
|
+
# https://api.scratch.mit.edu/users/TimMcCool/projects/404369790/comments/439984518
|
|
528
|
+
data = requests.get(
|
|
400
529
|
f"https://api.scratch.mit.edu/users/{self.author_name}/projects/{self.id}/comments/{comment_id}",
|
|
401
530
|
headers=self._headers,
|
|
402
531
|
cookies=self._cookies
|
|
403
532
|
).json()
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
533
|
+
|
|
534
|
+
if data is None or data.get("code") == "NotFound":
|
|
535
|
+
raise exceptions.CommentNotFound(
|
|
536
|
+
f"Cannot find comment #{comment_id} on -P {self.id} by -U {self.author_name}")
|
|
537
|
+
|
|
538
|
+
_comment = comment.Comment(id=data["id"], _session=self._session, source=comment.CommentSource.PROJECT,
|
|
539
|
+
source_id=self.id)
|
|
540
|
+
_comment._update_from_dict(data)
|
|
408
541
|
return _comment
|
|
409
|
-
|
|
542
|
+
|
|
410
543
|
def love(self):
|
|
411
544
|
"""
|
|
412
545
|
Posts a love on the project. You can only use this function if this object was created using :meth:`scratchattach.session.Session.connect_project`
|
|
@@ -488,8 +621,8 @@ class Project(PartialProject):
|
|
|
488
621
|
use_site_api (bool):
|
|
489
622
|
When enabled, the fields are set using the scratch.mit.edu/site-api API.
|
|
490
623
|
This function allows setting more fields than Project.set_fields.
|
|
491
|
-
For example you can also share / unshare the project by setting the "shared" field.
|
|
492
|
-
According to the Scratch team, this API is deprecated. As of 2024 it's still fully functional
|
|
624
|
+
For example, you can also share / unshare the project by setting the "shared" field.
|
|
625
|
+
According to the Scratch team, this API is deprecated. As of 2024 it's still fully functional though.
|
|
493
626
|
"""
|
|
494
627
|
self._assert_permission()
|
|
495
628
|
if use_site_api:
|
|
@@ -507,7 +640,7 @@ class Project(PartialProject):
|
|
|
507
640
|
json=fields_dict,
|
|
508
641
|
).json()
|
|
509
642
|
return self._update_from_dict(r)
|
|
510
|
-
|
|
643
|
+
|
|
511
644
|
def turn_off_commenting(self):
|
|
512
645
|
"""
|
|
513
646
|
Disables commenting on the project. You can only use this function if this object was created using :meth:`scratchattach.session.Session.connect_project`
|
|
@@ -584,7 +717,7 @@ class Project(PartialProject):
|
|
|
584
717
|
f"https://api.scratch.mit.edu/proxy/comments/project/{self.id}/comment/{comment_id}/",
|
|
585
718
|
headers=self._headers,
|
|
586
719
|
cookies=self._cookies,
|
|
587
|
-
)
|
|
720
|
+
)
|
|
588
721
|
|
|
589
722
|
def report_comment(self, *, comment_id):
|
|
590
723
|
"""
|
|
@@ -632,7 +765,8 @@ class Project(PartialProject):
|
|
|
632
765
|
)
|
|
633
766
|
if "id" not in r:
|
|
634
767
|
raise exceptions.CommentPostFailure(r)
|
|
635
|
-
_comment = comment.Comment(id=r["id"], _session=self._session, source=
|
|
768
|
+
_comment = comment.Comment(id=r["id"], _session=self._session, source=comment.CommentSource.PROJECT,
|
|
769
|
+
source_id=self.id)
|
|
636
770
|
_comment._update_from_dict(r)
|
|
637
771
|
return _comment
|
|
638
772
|
|
|
@@ -655,55 +789,12 @@ class Project(PartialProject):
|
|
|
655
789
|
return self.post_comment(
|
|
656
790
|
content, parent_id=parent_id, commentee_id=commentee_id
|
|
657
791
|
)
|
|
658
|
-
|
|
659
|
-
def set_body(self, project_body:ProjectBody):
|
|
660
|
-
"""
|
|
661
|
-
Sets the project's contents You can use this to upload projects to the Scratch website.
|
|
662
|
-
Returns a dict with Scratch's raw JSON API response.
|
|
663
|
-
|
|
664
|
-
Args:
|
|
665
|
-
project_body (scratchattach.ProjectBody): A ProjectBody object containing the contents of the project
|
|
666
|
-
"""
|
|
667
|
-
self._assert_permission()
|
|
668
|
-
|
|
669
|
-
return self.set_json(project_body.to_json())
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
def set_json(self, json_data):
|
|
673
|
-
"""
|
|
674
|
-
Sets the project json. You can use this to upload projects to the Scratch website.
|
|
675
|
-
Returns a dict with Scratch's raw JSON API response.
|
|
676
|
-
|
|
677
|
-
Args:
|
|
678
|
-
json_data (dict or JSON): The new project JSON as encoded JSON object or as dict
|
|
679
|
-
"""
|
|
680
|
-
|
|
681
|
-
self._assert_permission()
|
|
682
|
-
|
|
683
|
-
if not isinstance(json_data, dict):
|
|
684
|
-
json_data = json.loads(json_data)
|
|
685
|
-
|
|
686
|
-
return requests.put(
|
|
687
|
-
f"https://projects.scratch.mit.edu/{self.id}",
|
|
688
|
-
headers=self._headers,
|
|
689
|
-
cookies=self._cookies,
|
|
690
|
-
json=json_data,
|
|
691
|
-
).json()
|
|
692
|
-
|
|
693
|
-
def upload_json_from(self, project_id):
|
|
694
|
-
"""
|
|
695
|
-
Uploads the project json from the project with the given id to the project represented by this Project object
|
|
696
|
-
"""
|
|
697
|
-
self._assert_auth()
|
|
698
|
-
other_project = self._session.connect_project(project_id)
|
|
699
|
-
self.set_json(other_project.get_raw_json())
|
|
700
792
|
|
|
701
793
|
def set_title(self, text):
|
|
702
794
|
"""
|
|
703
795
|
Changes the projects title. You can only use this function if this object was created using :meth:`scratchattach.session.Session.connect_project`
|
|
704
796
|
"""
|
|
705
797
|
self.set_fields({"title": text})
|
|
706
|
-
|
|
707
798
|
|
|
708
799
|
def set_instructions(self, text):
|
|
709
800
|
"""
|
|
@@ -717,7 +808,7 @@ class Project(PartialProject):
|
|
|
717
808
|
"""
|
|
718
809
|
self.set_fields({"description": text})
|
|
719
810
|
|
|
720
|
-
|
|
811
|
+
@deprecated("Deprecated because ScratchDB is down indefinitely.")
|
|
721
812
|
def ranks(self):
|
|
722
813
|
"""
|
|
723
814
|
Gets information about the project's ranks. Fetched from ScratchDB.
|
|
@@ -728,12 +819,11 @@ class Project(PartialProject):
|
|
|
728
819
|
Returns:
|
|
729
820
|
dict: A dict containing the project's ranks. If the ranks aren't available, all values will be -1.
|
|
730
821
|
"""
|
|
731
|
-
print("Warning: Project.ranks method is deprecated because ScratchDB is down indefinitely.")
|
|
732
822
|
return requests.get(
|
|
733
823
|
f"https://scratchdb.lefty.one/v3/project/info/{self.id}"
|
|
734
824
|
).json()["statistics"]["ranks"]
|
|
735
825
|
|
|
736
|
-
def moderation_status(self):
|
|
826
|
+
def moderation_status(self, *, reload: bool = False):
|
|
737
827
|
"""
|
|
738
828
|
Gets information about the project's moderation status. Fetched from jeffalo's API.
|
|
739
829
|
|
|
@@ -750,19 +840,24 @@ class Project(PartialProject):
|
|
|
750
840
|
|
|
751
841
|
no_remixes: Unable to fetch the project's moderation status.
|
|
752
842
|
"""
|
|
843
|
+
if self._moderation_status and not reload:
|
|
844
|
+
return self._moderation_status
|
|
845
|
+
|
|
753
846
|
try:
|
|
754
847
|
return requests.get(
|
|
755
848
|
f"https://jeffalo.net/api/nfe/?project={self.id}"
|
|
756
849
|
).json()["status"]
|
|
757
850
|
except Exception:
|
|
758
|
-
raise
|
|
851
|
+
raise exceptions.FetchError
|
|
759
852
|
|
|
760
853
|
def visibility(self):
|
|
761
854
|
"""
|
|
762
855
|
Returns info about the project's visibility. Requires authentication.
|
|
763
856
|
"""
|
|
764
857
|
self._assert_auth()
|
|
765
|
-
return requests.get(f"https://api.scratch.mit.edu/users/{self._session.username}/projects/{self.id}/visibility",
|
|
858
|
+
return requests.get(f"https://api.scratch.mit.edu/users/{self._session.username}/projects/{self.id}/visibility",
|
|
859
|
+
headers=self._headers, cookies=self._cookies).json()
|
|
860
|
+
|
|
766
861
|
|
|
767
862
|
# ------ #
|
|
768
863
|
|
|
@@ -782,9 +877,17 @@ def get_project(project_id) -> Project:
|
|
|
782
877
|
|
|
783
878
|
If you want to use these methods, get the project with :meth:`scratchattach.session.Session.connect_project` instead.
|
|
784
879
|
"""
|
|
785
|
-
|
|
880
|
+
warnings.warn(
|
|
881
|
+
"For methods that require authentication, use session.connect_project instead of get_project.\n"
|
|
882
|
+
"If you want to remove this warning, "
|
|
883
|
+
"use `warnings.filterwarnings('ignore', category=scratchattach.ProjectAuthenticationWarning)`.\n"
|
|
884
|
+
"To ignore all warnings of the type GetAuthenticationWarning, which includes this warning, use "
|
|
885
|
+
"`warnings.filterwarnings('ignore', category=scratchattach.GetAuthenticationWarning)`.",
|
|
886
|
+
exceptions.ProjectAuthenticationWarning
|
|
887
|
+
)
|
|
786
888
|
return commons._get_object("id", project_id, Project, exceptions.ProjectNotFound)
|
|
787
889
|
|
|
890
|
+
|
|
788
891
|
def search_projects(*, query="", mode="trending", language="en", limit=40, offset=0):
|
|
789
892
|
'''
|
|
790
893
|
Uses the Scratch search to search projects.
|
|
@@ -802,9 +905,11 @@ def search_projects(*, query="", mode="trending", language="en", limit=40, offse
|
|
|
802
905
|
if not query:
|
|
803
906
|
raise ValueError("The query can't be empty for search")
|
|
804
907
|
response = commons.api_iterative(
|
|
805
|
-
f"https://api.scratch.mit.edu/search/projects", limit=limit, offset=offset,
|
|
908
|
+
f"https://api.scratch.mit.edu/search/projects", limit=limit, offset=offset,
|
|
909
|
+
add_params=f"&language={language}&mode={mode}&q={query}")
|
|
806
910
|
return commons.parse_object_list(response, Project)
|
|
807
911
|
|
|
912
|
+
|
|
808
913
|
def explore_projects(*, query="*", mode="trending", language="en", limit=40, offset=0):
|
|
809
914
|
'''
|
|
810
915
|
Gets projects from the explore page.
|
|
@@ -822,5 +927,6 @@ def explore_projects(*, query="*", mode="trending", language="en", limit=40, off
|
|
|
822
927
|
if not query:
|
|
823
928
|
raise ValueError("The query can't be empty for search")
|
|
824
929
|
response = commons.api_iterative(
|
|
825
|
-
f"https://api.scratch.mit.edu/explore/projects", limit=limit, offset=offset,
|
|
826
|
-
|
|
930
|
+
f"https://api.scratch.mit.edu/explore/projects", limit=limit, offset=offset,
|
|
931
|
+
add_params=f"&language={language}&mode={mode}&q={query}")
|
|
932
|
+
return commons.parse_object_list(response, Project)
|