scratchattach 3.0.0b0__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
- cloud/__init__.py +2 -0
- cloud/_base.py +483 -0
- cloud/cloud.py +183 -0
- editor/__init__.py +22 -0
- editor/asset.py +265 -0
- editor/backpack_json.py +115 -0
- editor/base.py +191 -0
- editor/block.py +584 -0
- editor/blockshape.py +357 -0
- editor/build_defaulting.py +51 -0
- editor/code_translation/__init__.py +0 -0
- editor/code_translation/parse.py +177 -0
- editor/comment.py +80 -0
- editor/commons.py +145 -0
- editor/extension.py +50 -0
- editor/field.py +99 -0
- editor/inputs.py +138 -0
- editor/meta.py +117 -0
- editor/monitor.py +185 -0
- editor/mutation.py +381 -0
- editor/pallete.py +88 -0
- editor/prim.py +174 -0
- editor/project.py +381 -0
- editor/sprite.py +609 -0
- editor/twconfig.py +114 -0
- editor/vlb.py +134 -0
- eventhandlers/__init__.py +0 -0
- eventhandlers/_base.py +101 -0
- eventhandlers/cloud_events.py +130 -0
- eventhandlers/cloud_recorder.py +26 -0
- eventhandlers/cloud_requests.py +544 -0
- eventhandlers/cloud_server.py +249 -0
- eventhandlers/cloud_storage.py +135 -0
- eventhandlers/combine.py +30 -0
- eventhandlers/filterbot.py +163 -0
- eventhandlers/message_events.py +42 -0
- other/__init__.py +0 -0
- other/other_apis.py +598 -0
- other/project_json_capabilities.py +475 -0
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +1 -1
- scratchattach-3.0.0b1.dist-info/RECORD +79 -0
- scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
- site/__init__.py +0 -0
- site/_base.py +93 -0
- site/activity.py +426 -0
- site/alert.py +226 -0
- site/backpack_asset.py +119 -0
- site/browser_cookie3_stub.py +17 -0
- site/browser_cookies.py +61 -0
- site/classroom.py +454 -0
- site/cloud_activity.py +121 -0
- site/comment.py +228 -0
- site/forum.py +436 -0
- site/placeholder.py +132 -0
- site/project.py +932 -0
- site/session.py +1323 -0
- site/studio.py +704 -0
- site/typed_dicts.py +151 -0
- site/user.py +1252 -0
- utils/__init__.py +0 -0
- utils/commons.py +263 -0
- utils/encoder.py +161 -0
- utils/enums.py +237 -0
- utils/exceptions.py +277 -0
- utils/optional_async.py +154 -0
- utils/requests.py +306 -0
- scratchattach/__init__.py +0 -37
- scratchattach/__main__.py +0 -93
- scratchattach-3.0.0b0.dist-info/RECORD +0 -8
- scratchattach-3.0.0b0.dist-info/top_level.txt +0 -1
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +0 -0
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/entry_points.txt +0 -0
- {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
utils/exceptions.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Authentication / Authorization:
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
class Unauthenticated(Exception):
|
|
5
|
+
"""
|
|
6
|
+
Raised when a method that requires a login / session is called on an object that wasn't created with a session.
|
|
7
|
+
|
|
8
|
+
If you create Project, Studio, or User objects using :meth:`scratchattach.get_project`, :meth:`scratchattach.get_studio`, or :meth:`scratchattach.get_user`, they cannot be used for actions that require authentication. Instead, use the following methods to ensure the objects are connected to an authenticated session:
|
|
9
|
+
|
|
10
|
+
- :meth:`scratchattach.Session.connect_project`
|
|
11
|
+
|
|
12
|
+
- :meth:`scratchattach.Session.connect_user`
|
|
13
|
+
|
|
14
|
+
- :meth:`scratchattach.Session.connect_studio`
|
|
15
|
+
|
|
16
|
+
This also applies to cloud variables, forum topics, and forum posts.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, message=""):
|
|
20
|
+
self.message = "No login / session connected.\n\nThe object on which the method was called was created using scratchattach.get_xyz()\nUse session.connect_xyz() instead (xyz is a placeholder for user / project / cloud / ...).\n\nMore information: https://scratchattach.readthedocs.io/en/latest/scratchattach.html#scratchattach.utils.exceptions.Unauthenticated"
|
|
21
|
+
super().__init__(self.message)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Unauthorized(Exception):
|
|
25
|
+
"""
|
|
26
|
+
Raised when an action is performed that the user associated with the session that the object was created with is not allowed to do.
|
|
27
|
+
|
|
28
|
+
Example: Changing the "about me" of other users will raise this error.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, message=""):
|
|
32
|
+
self.message = (
|
|
33
|
+
f"The user corresponding to the connected login / session is not allowed to perform this action. "
|
|
34
|
+
f"{message}")
|
|
35
|
+
super().__init__(self.message)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class XTokenError(Exception):
|
|
39
|
+
"""
|
|
40
|
+
Raised when an action can't be performed because there is no XToken available.
|
|
41
|
+
|
|
42
|
+
This error can occur if the xtoken couldn't be fetched when the session was created. Some actions (like loving projects) require providing this token.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Not found errors:
|
|
47
|
+
|
|
48
|
+
class UserNotFound(Exception):
|
|
49
|
+
"""
|
|
50
|
+
Raised when a non-existent user is requested.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ProjectNotFound(Exception):
|
|
55
|
+
"""
|
|
56
|
+
Raised when a non-existent project is requested.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
class ClassroomNotFound(Exception):
|
|
60
|
+
"""
|
|
61
|
+
Raised when a non-existent Classroom is requested.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class StudioNotFound(Exception):
|
|
66
|
+
"""
|
|
67
|
+
Raised when a non-existent studio is requested.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class ForumContentNotFound(Exception):
|
|
72
|
+
"""
|
|
73
|
+
Raised when a non-existent forum topic / post is requested.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CommentNotFound(Exception):
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Invalid inputs
|
|
82
|
+
class InvalidLanguage(Exception):
|
|
83
|
+
"""
|
|
84
|
+
Raised when an invalid language/language code/language object is provided, for TTS or Translate
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class InvalidTTSGender(Exception):
|
|
89
|
+
"""
|
|
90
|
+
Raised when an invalid TTS gender is provided.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
# API errors:
|
|
94
|
+
|
|
95
|
+
class LoginFailure(Exception):
|
|
96
|
+
"""
|
|
97
|
+
Raised when the Scratch server doesn't respond with a session id.
|
|
98
|
+
|
|
99
|
+
This could be caused by an invalid username / password. Another cause could be that your IP address was banned from logging in to Scratch. If you're using an online IDE (like replit), try running the code on your computer.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class FetchError(Exception):
|
|
104
|
+
"""
|
|
105
|
+
Raised when getting information from the Scratch API fails. This can have various reasons. Make sure all provided arguments are valid.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class BadRequest(Exception):
|
|
110
|
+
"""
|
|
111
|
+
Raised when the Scratch API responds with a "Bad Request" error message. This can have various reasons. Make sure all provided arguments are valid.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
class RateLimitedError(Exception):
|
|
115
|
+
"""
|
|
116
|
+
Indicates a ratelimit enforced by scratchattach
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
class Response429(Exception):
|
|
120
|
+
"""
|
|
121
|
+
Raised when the Scratch API responds with a 429 error. This means that your network was ratelimited or blocked by Scratch. If you're using an online IDE (like replit.com), try running the code on your computer.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class CommentPostFailure(Exception):
|
|
126
|
+
"""
|
|
127
|
+
Raised when a comment fails to post. This can have various reasons.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class APIError(Exception):
|
|
132
|
+
"""
|
|
133
|
+
For API errors that can't be classified into one of the above errors
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ScrapeError(Exception):
|
|
138
|
+
"""
|
|
139
|
+
Raised when something goes wrong while web-scraping a page with bs4.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# Cloud / encoding errors:
|
|
145
|
+
|
|
146
|
+
class CloudConnectionError(Exception):
|
|
147
|
+
"""
|
|
148
|
+
Raised when connecting to Scratch's cloud server fails. This can have various reasons.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class InvalidCloudValue(Exception):
|
|
154
|
+
"""
|
|
155
|
+
Raised when a cloud variable is set to an invalid value.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class InvalidDecodeInput(Exception):
|
|
161
|
+
"""
|
|
162
|
+
Raised when the built-in decoder :meth:`scratchattach.encoder.Encoding.decode` receives an invalid input.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# Cloud Requests errors:
|
|
168
|
+
|
|
169
|
+
class RequestNotFound(Exception):
|
|
170
|
+
"""
|
|
171
|
+
Cloud Requests: Raised when a non-existent cloud request is edited using :meth:`scratchattach.cloud_requests.CloudRequests.edit_request`.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# Websocket server errors:
|
|
177
|
+
|
|
178
|
+
class WebsocketServerError(Exception):
|
|
179
|
+
"""
|
|
180
|
+
Raised when the self-hosted cloud websocket server fails to start.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Editor errors:
|
|
186
|
+
|
|
187
|
+
class UnclosedJSONError(Exception):
|
|
188
|
+
"""
|
|
189
|
+
Raised when a JSON string is never closed.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class BadVLBPrimitiveError(Exception):
|
|
194
|
+
"""
|
|
195
|
+
Raised when a Primitive claiming to be a variable/list/broadcast actually isn't
|
|
196
|
+
"""
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class UnlinkedVLB(Exception):
|
|
200
|
+
"""
|
|
201
|
+
Raised when a Primitive cannot be linked to variable/list/broadcast because the provided ID does not have an associated variable/list/broadcast
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class InvalidStageCount(Exception):
|
|
206
|
+
"""
|
|
207
|
+
Raised when a project has too many or too few Stage sprites
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class InvalidVLBName(Exception):
|
|
212
|
+
"""
|
|
213
|
+
Raised when an invalid VLB name is provided (not variable, list or broadcast)
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class BadBlockShape(Exception):
|
|
218
|
+
"""
|
|
219
|
+
Raised when the block shape cannot allow for the operation
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class BadScript(Exception):
|
|
224
|
+
"""
|
|
225
|
+
Raised when the block script cannot allow for the operation
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
# Warnings
|
|
229
|
+
|
|
230
|
+
class LoginDataWarning(UserWarning):
|
|
231
|
+
"""
|
|
232
|
+
Warns you not to accidentally share your login data.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
class InvalidUpdateWarning(UserWarning):
|
|
236
|
+
"""
|
|
237
|
+
Warns you that something cannot be updated.
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
class GetAuthenticationWarning(UserWarning):
|
|
241
|
+
"""
|
|
242
|
+
All authentication warnings.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
class UserAuthenticationWarning(GetAuthenticationWarning):
|
|
246
|
+
"""
|
|
247
|
+
Warns you to use session.connect_user instead of user.get_user
|
|
248
|
+
for actions that require authentication.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
class ProjectAuthenticationWarning(GetAuthenticationWarning):
|
|
252
|
+
"""
|
|
253
|
+
Warns you to use session.connect_project instead of project.get_project
|
|
254
|
+
for actions that require authentication.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
class StudioAuthenticationWarning(GetAuthenticationWarning):
|
|
258
|
+
"""
|
|
259
|
+
Warns you to use session.connect_studio instead of studio.get_studio
|
|
260
|
+
for actions that require authentication.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
class ClassroomAuthenticationWarning(GetAuthenticationWarning):
|
|
264
|
+
"""
|
|
265
|
+
Warns you to use session.connect_classroom or session.connect_classroom_from_token instead of classroom.get_classroom
|
|
266
|
+
for actions that require authentication.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
class CloudAuthenticationWarning(GetAuthenticationWarning):
|
|
270
|
+
"""
|
|
271
|
+
Warns you about usage of
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
class UnexpectedWebsocketEventWarning(RuntimeWarning):
|
|
275
|
+
"""
|
|
276
|
+
Warns about an unexpected occurrence with a websocket.
|
|
277
|
+
"""
|
utils/optional_async.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from collections.abc import Awaitable, Generator, Callable
|
|
5
|
+
from typing import Generic, TypeVar, ParamSpec, Optional, Union, Any
|
|
6
|
+
from functools import wraps
|
|
7
|
+
import asyncio
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from . import requests
|
|
11
|
+
|
|
12
|
+
P = ParamSpec("P")
|
|
13
|
+
R = TypeVar("R")
|
|
14
|
+
|
|
15
|
+
class CallableAwaitable(Generic[R], ABC, Awaitable[R]):
|
|
16
|
+
result: R
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def sync_impl(self) -> R:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def __pos__(self) -> R:
|
|
23
|
+
return self.sync_impl()
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
async def async_impl(self) -> R:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def __await__(self) -> Generator[None, None, R]:
|
|
30
|
+
return self.async_impl().__await__()
|
|
31
|
+
|
|
32
|
+
class OptionallyAsync(Generic[P, R], ABC):
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> CallableAwaitable[R]:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def optionally_async(func: Callable[P, Generator[CallableAwaitable, None, R]]) -> OptionallyAsync[P, R]:
|
|
38
|
+
class Wrapped(OptionallyAsync[P, R]):
|
|
39
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> CallableAwaitable[R]:
|
|
40
|
+
class Implementation(CallableAwaitable[R]):
|
|
41
|
+
def sync_impl(self) -> R:
|
|
42
|
+
i = func(*args, **kwargs)
|
|
43
|
+
try:
|
|
44
|
+
while True:
|
|
45
|
+
c = next(i)
|
|
46
|
+
c.result = +c
|
|
47
|
+
except StopIteration as excp:
|
|
48
|
+
return excp.value
|
|
49
|
+
|
|
50
|
+
async def async_impl(self) -> R:
|
|
51
|
+
i = func(*args, **kwargs)
|
|
52
|
+
try:
|
|
53
|
+
while True:
|
|
54
|
+
c = next(i)
|
|
55
|
+
c.result = await c
|
|
56
|
+
except StopIteration as excp:
|
|
57
|
+
return excp.value
|
|
58
|
+
|
|
59
|
+
return Implementation()
|
|
60
|
+
return Wrapped()
|
|
61
|
+
|
|
62
|
+
def make_async(func: Callable[P, Generator[CallableAwaitable, None, R]]) -> Callable[P, Awaitable[R]]:
|
|
63
|
+
@wraps(func)
|
|
64
|
+
async def async_impl(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
65
|
+
i = func(*args, **kwargs)
|
|
66
|
+
try:
|
|
67
|
+
while True:
|
|
68
|
+
c = next(i)
|
|
69
|
+
c.result = await c
|
|
70
|
+
except StopIteration as excp:
|
|
71
|
+
return excp.value
|
|
72
|
+
return async_impl
|
|
73
|
+
|
|
74
|
+
def make_sync(func: Callable[P, Generator[CallableAwaitable, None, R]]) -> Callable[P, R]:
|
|
75
|
+
@wraps(func)
|
|
76
|
+
def sync_impl(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
77
|
+
i = func(*args, **kwargs)
|
|
78
|
+
try:
|
|
79
|
+
while True:
|
|
80
|
+
c = next(i)
|
|
81
|
+
c.result = +c
|
|
82
|
+
except StopIteration as excp:
|
|
83
|
+
return excp.value
|
|
84
|
+
return sync_impl
|
|
85
|
+
|
|
86
|
+
class CASleep(CallableAwaitable[bool]):
|
|
87
|
+
amount: float
|
|
88
|
+
|
|
89
|
+
def __init__(self, amount: float) -> None:
|
|
90
|
+
self.amount = amount
|
|
91
|
+
|
|
92
|
+
def sync_impl(self):
|
|
93
|
+
time.sleep(self.amount)
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
async def async_impl(self):
|
|
97
|
+
await asyncio.sleep(self.amount)
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
def oa_sleep(amount: float):
|
|
101
|
+
return CASleep(amount)
|
|
102
|
+
|
|
103
|
+
class CARequest(CallableAwaitable["requests.AnyHTTPResponse"]):
|
|
104
|
+
requests_session: requests.OAHTTPSession
|
|
105
|
+
method: requests.HTTPMethod
|
|
106
|
+
url: str
|
|
107
|
+
cookies: Optional[dict[str, str]]
|
|
108
|
+
headers: Optional[dict[str, str]]
|
|
109
|
+
params: Optional[dict[str, str]]
|
|
110
|
+
data: Optional[Union[dict[str, str], str]]
|
|
111
|
+
json: Optional[dict[str, str]]
|
|
112
|
+
|
|
113
|
+
def __init__(
|
|
114
|
+
self,
|
|
115
|
+
requests_session: requests.OAHTTPSession,
|
|
116
|
+
method: requests.HTTPMethod,
|
|
117
|
+
url: str,
|
|
118
|
+
*,
|
|
119
|
+
cookies: Optional[dict[str, str]] = None,
|
|
120
|
+
headers: Optional[dict[str, str]] = None,
|
|
121
|
+
params: Optional[dict[str, str]] = None,
|
|
122
|
+
data: Optional[Union[dict[str, str], str]] = None,
|
|
123
|
+
json: Optional[Any] = None
|
|
124
|
+
) -> None:
|
|
125
|
+
self.requests_session = requests_session
|
|
126
|
+
self.method = method
|
|
127
|
+
self.url = url
|
|
128
|
+
self.cookies = cookies
|
|
129
|
+
self.headers = headers
|
|
130
|
+
self.params = params
|
|
131
|
+
self.data = data
|
|
132
|
+
self.json = json
|
|
133
|
+
|
|
134
|
+
def sync_impl(self):
|
|
135
|
+
return self.requests_session.sync_request(
|
|
136
|
+
method = self.method,
|
|
137
|
+
url = self.url,
|
|
138
|
+
cookies = self.cookies,
|
|
139
|
+
headers = self.headers,
|
|
140
|
+
params = self.params,
|
|
141
|
+
data = self.data,
|
|
142
|
+
json = self.json
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
async def async_impl(self):
|
|
146
|
+
return await self.requests_session.async_request(
|
|
147
|
+
method = self.method,
|
|
148
|
+
url = self.url,
|
|
149
|
+
cookies = self.cookies,
|
|
150
|
+
headers = self.headers,
|
|
151
|
+
params = self.params,
|
|
152
|
+
data = self.data,
|
|
153
|
+
json = self.json
|
|
154
|
+
)
|