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
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import string
|
|
5
5
|
|
|
6
|
-
from typing import Optional, Final, Any, TypeVar, Callable, TYPE_CHECKING, Union
|
|
6
|
+
from typing import Optional, Final, Any, TypeVar, Callable, TYPE_CHECKING, Union, overload
|
|
7
7
|
from threading import Event as ManualResetEvent
|
|
8
8
|
from threading import Lock
|
|
9
9
|
|
|
@@ -162,19 +162,28 @@ def _get_object(identificator_name, identificator, __class: type[C], NotFoundExc
|
|
|
162
162
|
except Exception as e:
|
|
163
163
|
raise e
|
|
164
164
|
|
|
165
|
+
I = TypeVar("I")
|
|
166
|
+
@overload
|
|
167
|
+
def webscrape_count(raw: str, text_before: str, text_after: str, cls: type[I]) -> I:
|
|
168
|
+
pass
|
|
165
169
|
|
|
166
|
-
|
|
170
|
+
@overload
|
|
171
|
+
def webscrape_count(raw: str, text_before: str, text_after: str) -> int:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
def webscrape_count(raw, text_before, text_after, cls = int):
|
|
167
175
|
return cls(raw.split(text_before)[1].split(text_after)[0])
|
|
168
176
|
|
|
169
177
|
|
|
170
178
|
if TYPE_CHECKING:
|
|
171
179
|
C = TypeVar("C", bound=_base.BaseSiteComponent)
|
|
172
180
|
|
|
173
|
-
def parse_object_list(raw, __class: type[C], session=None, primary_key="id") -> list[C]:
|
|
181
|
+
def parse_object_list(raw, /, __class: type[C], session=None, primary_key="id") -> list[C]:
|
|
174
182
|
results = []
|
|
175
183
|
for raw_dict in raw:
|
|
176
184
|
try:
|
|
177
185
|
_obj = __class(**{primary_key: raw_dict[primary_key], "_session": session})
|
|
186
|
+
# noinspection PyProtectedMember
|
|
178
187
|
_obj._update_from_dict(raw_dict)
|
|
179
188
|
results.append(_obj)
|
|
180
189
|
except Exception as e:
|
|
@@ -252,4 +261,3 @@ def b62_decode(s: str):
|
|
|
252
261
|
ret = ret * 62 + chars.index(char)
|
|
253
262
|
|
|
254
263
|
return ret
|
|
255
|
-
|
|
@@ -122,11 +122,14 @@ class Encoding:
|
|
|
122
122
|
try:
|
|
123
123
|
inp = str(inp)
|
|
124
124
|
except Exception:
|
|
125
|
-
raise
|
|
125
|
+
raise exceptions.InvalidDecodeInput
|
|
126
|
+
|
|
126
127
|
outp = ""
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
# print(f"Encoding.decode({inp=})")
|
|
129
|
+
# This loops through a string like 'abCDefGHijKLmnOP' like so: l1+l2=ab, CD, ef, GH, etc.
|
|
130
|
+
for l1, l2 in zip(inp[::2], inp[1::2]):
|
|
131
|
+
outp += letters[int(l1 + l2)]
|
|
132
|
+
|
|
130
133
|
return outp
|
|
131
134
|
|
|
132
135
|
@staticmethod
|
|
@@ -232,9 +232,43 @@ class LoginDataWarning(UserWarning):
|
|
|
232
232
|
Warns you not to accidentally share your login data.
|
|
233
233
|
"""
|
|
234
234
|
|
|
235
|
-
class
|
|
235
|
+
class InvalidUpdateWarning(UserWarning):
|
|
236
236
|
"""
|
|
237
|
-
Warns
|
|
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
|
|
238
272
|
"""
|
|
239
273
|
|
|
240
274
|
class UnexpectedWebsocketEventWarning(RuntimeWarning):
|
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
|
+
)
|
utils/requests.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import MutableMapping, Iterator
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from types import TracebackType
|
|
6
|
+
from typing import Optional, Any, Self, Union
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from enum import Enum, auto
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
from aiohttp.cookiejar import DummyCookieJar
|
|
13
|
+
from typing_extensions import override
|
|
14
|
+
from requests import Session as HTTPSession
|
|
15
|
+
from requests import Response
|
|
16
|
+
import aiohttp
|
|
17
|
+
|
|
18
|
+
from . import exceptions
|
|
19
|
+
from . import optional_async
|
|
20
|
+
|
|
21
|
+
proxies: Optional[MutableMapping[str, str]] = None
|
|
22
|
+
|
|
23
|
+
class HTTPMethod(Enum):
|
|
24
|
+
GET = auto()
|
|
25
|
+
POST = auto()
|
|
26
|
+
PUT = auto()
|
|
27
|
+
DELETE = auto()
|
|
28
|
+
HEAD = auto()
|
|
29
|
+
OPTIONS = auto()
|
|
30
|
+
PATCH = auto()
|
|
31
|
+
TRACE = auto()
|
|
32
|
+
@classmethod
|
|
33
|
+
def of(cls, name: str) -> HTTPMethod:
|
|
34
|
+
member_map = {
|
|
35
|
+
"GET": cls.GET,
|
|
36
|
+
"POST": cls.POST,
|
|
37
|
+
"PUT": cls.PUT,
|
|
38
|
+
"DELETE": cls.DELETE,
|
|
39
|
+
"HEAD": cls.HEAD,
|
|
40
|
+
"OPTIONS": cls.OPTIONS,
|
|
41
|
+
"PATCH": cls.PATCH,
|
|
42
|
+
"TRACE": cls.TRACE
|
|
43
|
+
}
|
|
44
|
+
return member_map[name]
|
|
45
|
+
|
|
46
|
+
class AnyHTTPResponse(ABC):
|
|
47
|
+
request_method: HTTPMethod
|
|
48
|
+
status_code: int
|
|
49
|
+
content: bytes
|
|
50
|
+
text: str
|
|
51
|
+
headers: dict[str, str]
|
|
52
|
+
|
|
53
|
+
def json(self) -> Any:
|
|
54
|
+
return json.loads(self.text)
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class HTTPResponse(AnyHTTPResponse):
|
|
58
|
+
request_method: HTTPMethod = field(kw_only=True)
|
|
59
|
+
status_code: int = field(kw_only=True)
|
|
60
|
+
content: bytes = field(kw_only=True)
|
|
61
|
+
text: str = field(kw_only=True)
|
|
62
|
+
headers: dict[str, str] = field(kw_only=True)
|
|
63
|
+
|
|
64
|
+
class OAHTTPSession(ABC):
|
|
65
|
+
error_handling: bool = True
|
|
66
|
+
@abstractmethod
|
|
67
|
+
def sync_request(
|
|
68
|
+
self,
|
|
69
|
+
method: HTTPMethod,
|
|
70
|
+
url: str,
|
|
71
|
+
*,
|
|
72
|
+
cookies: Optional[dict[str, str]] = None,
|
|
73
|
+
headers: Optional[dict[str, str]] = None,
|
|
74
|
+
params: Optional[dict[str, str]] = None,
|
|
75
|
+
data: Optional[Union[dict[str, str], str]] = None,
|
|
76
|
+
json: Optional[Any] = None
|
|
77
|
+
) -> AnyHTTPResponse:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
async def async_request(
|
|
82
|
+
self,
|
|
83
|
+
method: HTTPMethod,
|
|
84
|
+
url: str,
|
|
85
|
+
*,
|
|
86
|
+
cookies: Optional[dict[str, str]] = None,
|
|
87
|
+
headers: Optional[dict[str, str]] = None,
|
|
88
|
+
params: Optional[dict[str, str]] = None,
|
|
89
|
+
data: Optional[Union[dict[str, str], str]] = None,
|
|
90
|
+
json: Optional[Any] = None
|
|
91
|
+
) -> AnyHTTPResponse:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
def check_response(self, r: AnyHTTPResponse):
|
|
95
|
+
if r.status_code == 403 or r.status_code == 401:
|
|
96
|
+
raise exceptions.Unauthorized(f"Request content: {r.content!r}")
|
|
97
|
+
if r.status_code == 500:
|
|
98
|
+
raise exceptions.APIError("Internal Scratch server error")
|
|
99
|
+
if r.status_code == 429:
|
|
100
|
+
raise exceptions.Response429("You are being rate-limited (or blocked) by Scratch")
|
|
101
|
+
if r.json() == {"code":"BadRequest","message":""}:
|
|
102
|
+
raise exceptions.BadRequest("Make sure all provided arguments are valid")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def request(
|
|
106
|
+
self,
|
|
107
|
+
method: Union[HTTPMethod, str],
|
|
108
|
+
url: str,
|
|
109
|
+
*,
|
|
110
|
+
cookies: Optional[dict[str, str]] = None,
|
|
111
|
+
headers: Optional[dict[str, str]] = None,
|
|
112
|
+
params: Optional[dict[str, str]] = None,
|
|
113
|
+
data: Optional[Union[dict[str, str], str]] = None,
|
|
114
|
+
json: Optional[Any] = None
|
|
115
|
+
) -> optional_async.CARequest:
|
|
116
|
+
if isinstance(method, str):
|
|
117
|
+
method = HTTPMethod.of(method.upper())
|
|
118
|
+
return optional_async.CARequest(
|
|
119
|
+
self,
|
|
120
|
+
method,
|
|
121
|
+
url,
|
|
122
|
+
cookies = cookies,
|
|
123
|
+
headers = headers,
|
|
124
|
+
params = params,
|
|
125
|
+
data = data,
|
|
126
|
+
json = json
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@contextmanager
|
|
130
|
+
def no_error_handling(self) -> Iterator[None]:
|
|
131
|
+
val_before = self.error_handling
|
|
132
|
+
self.error_handling = False
|
|
133
|
+
try:
|
|
134
|
+
yield
|
|
135
|
+
finally:
|
|
136
|
+
self.error_handling = val_before
|
|
137
|
+
|
|
138
|
+
@contextmanager
|
|
139
|
+
def yes_error_handling(self) -> Iterator[None]:
|
|
140
|
+
val_before = self.error_handling
|
|
141
|
+
self.error_handling = True
|
|
142
|
+
try:
|
|
143
|
+
yield
|
|
144
|
+
finally:
|
|
145
|
+
self.error_handling = val_before
|
|
146
|
+
|
|
147
|
+
class SyncRequests(OAHTTPSession):
|
|
148
|
+
@override
|
|
149
|
+
def sync_request(self, method, url, *, cookies = None, headers = None, params = None, data = None, json = None):
|
|
150
|
+
try:
|
|
151
|
+
r = requests.request(
|
|
152
|
+
method.name,
|
|
153
|
+
url,
|
|
154
|
+
cookies = cookies,
|
|
155
|
+
headers = headers,
|
|
156
|
+
params = params,
|
|
157
|
+
data = data,
|
|
158
|
+
json = json,
|
|
159
|
+
proxies = proxies
|
|
160
|
+
)
|
|
161
|
+
except Exception as e:
|
|
162
|
+
raise exceptions.FetchError(e)
|
|
163
|
+
response = HTTPResponse(
|
|
164
|
+
request_method=method,
|
|
165
|
+
status_code=r.status_code,
|
|
166
|
+
content=r.content,
|
|
167
|
+
text=r.text,
|
|
168
|
+
headers=r.headers
|
|
169
|
+
)
|
|
170
|
+
if self.error_handling:
|
|
171
|
+
self.check_response(response)
|
|
172
|
+
return response
|
|
173
|
+
|
|
174
|
+
async def async_request(self, method, url, *, cookies = None, headers = None, params = None, data = None, json = None):
|
|
175
|
+
raise NotImplementedError()
|
|
176
|
+
|
|
177
|
+
class AsyncRequests(OAHTTPSession):
|
|
178
|
+
client_session: aiohttp.ClientSession
|
|
179
|
+
async def __aenter__(self) -> Self:
|
|
180
|
+
self.client_session = await aiohttp.ClientSession(cookie_jar=DummyCookieJar()).__aenter__()
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
async def __aexit__(
|
|
184
|
+
self,
|
|
185
|
+
exc_type: Optional[type[BaseException]] = None,
|
|
186
|
+
exc_val: Optional[BaseException] = None,
|
|
187
|
+
exc_tb: Optional[TracebackType] = None
|
|
188
|
+
) -> None:
|
|
189
|
+
await self.client_session.__aexit__(exc_type, exc_val, exc_tb)
|
|
190
|
+
|
|
191
|
+
@override
|
|
192
|
+
def sync_request(self, method, url, *, cookies = None, headers = None, params = None, data = None, json = None):
|
|
193
|
+
raise NotImplementedError()
|
|
194
|
+
|
|
195
|
+
async def async_request(self, method, url, *, cookies = None, headers = None, params = None, data = None, json = None):
|
|
196
|
+
proxy = None
|
|
197
|
+
if url.startswith("http"):
|
|
198
|
+
proxy = proxies.get("http")
|
|
199
|
+
if url.startswith("https"):
|
|
200
|
+
proxy = proxies.get("https")
|
|
201
|
+
async with self.client_session.request(
|
|
202
|
+
method.name,
|
|
203
|
+
url,
|
|
204
|
+
cookies = cookies,
|
|
205
|
+
headers = headers,
|
|
206
|
+
params = params,
|
|
207
|
+
data = data,
|
|
208
|
+
json = json,
|
|
209
|
+
proxy = proxy
|
|
210
|
+
) as resp:
|
|
211
|
+
assert isinstance(resp, aiohttp.ClientResponse)
|
|
212
|
+
content = await resp.read()
|
|
213
|
+
try:
|
|
214
|
+
text = content.decode(resp.get_encoding())
|
|
215
|
+
except Exception:
|
|
216
|
+
text = ""
|
|
217
|
+
response = HTTPResponse(
|
|
218
|
+
request_method=method,
|
|
219
|
+
status_code=resp.status,
|
|
220
|
+
content=content,
|
|
221
|
+
text=text,
|
|
222
|
+
headers=resp.headers
|
|
223
|
+
)
|
|
224
|
+
if self.error_handling:
|
|
225
|
+
self.check_response(response)
|
|
226
|
+
return response
|
|
227
|
+
|
|
228
|
+
class Requests(HTTPSession):
|
|
229
|
+
"""
|
|
230
|
+
Centralized HTTP request handler (for better error handling and proxies)
|
|
231
|
+
"""
|
|
232
|
+
error_handling: bool = True
|
|
233
|
+
|
|
234
|
+
def check_response(self, r: Response):
|
|
235
|
+
if r.status_code == 403 or r.status_code == 401:
|
|
236
|
+
raise exceptions.Unauthorized(f"Request content: {r.content!r}")
|
|
237
|
+
if r.status_code == 500:
|
|
238
|
+
raise exceptions.APIError("Internal Scratch server error")
|
|
239
|
+
if r.status_code == 429:
|
|
240
|
+
raise exceptions.Response429("You are being rate-limited (or blocked) by Scratch")
|
|
241
|
+
if r.json() == {"code":"BadRequest","message":""}:
|
|
242
|
+
raise exceptions.BadRequest("Make sure all provided arguments are valid")
|
|
243
|
+
|
|
244
|
+
@override
|
|
245
|
+
def get(self, *args, **kwargs):
|
|
246
|
+
kwargs.setdefault("proxies", proxies)
|
|
247
|
+
try:
|
|
248
|
+
r = super().get(*args, **kwargs)
|
|
249
|
+
except Exception as e:
|
|
250
|
+
raise exceptions.FetchError(e)
|
|
251
|
+
if self.error_handling:
|
|
252
|
+
self.check_response(r)
|
|
253
|
+
return r
|
|
254
|
+
|
|
255
|
+
@override
|
|
256
|
+
def post(self, *args, **kwargs):
|
|
257
|
+
kwargs.setdefault("proxies", proxies)
|
|
258
|
+
try:
|
|
259
|
+
r = super().post(*args, **kwargs)
|
|
260
|
+
except Exception as e:
|
|
261
|
+
raise exceptions.FetchError(e)
|
|
262
|
+
if self.error_handling:
|
|
263
|
+
self.check_response(r)
|
|
264
|
+
return r
|
|
265
|
+
|
|
266
|
+
@override
|
|
267
|
+
def delete(self, *args, **kwargs):
|
|
268
|
+
kwargs.setdefault("proxies", proxies)
|
|
269
|
+
try:
|
|
270
|
+
r = super().delete(*args, **kwargs)
|
|
271
|
+
except Exception as e:
|
|
272
|
+
raise exceptions.FetchError(e)
|
|
273
|
+
if self.error_handling:
|
|
274
|
+
self.check_response(r)
|
|
275
|
+
return r
|
|
276
|
+
|
|
277
|
+
@override
|
|
278
|
+
def put(self, *args, **kwargs):
|
|
279
|
+
kwargs.setdefault("proxies", proxies)
|
|
280
|
+
try:
|
|
281
|
+
r = super().put(*args, **kwargs)
|
|
282
|
+
except Exception as e:
|
|
283
|
+
raise exceptions.FetchError(e)
|
|
284
|
+
if self.error_handling:
|
|
285
|
+
self.check_response(r)
|
|
286
|
+
return r
|
|
287
|
+
|
|
288
|
+
@contextmanager
|
|
289
|
+
def no_error_handling(self) -> Iterator[None]:
|
|
290
|
+
val_before = self.error_handling
|
|
291
|
+
self.error_handling = False
|
|
292
|
+
try:
|
|
293
|
+
yield
|
|
294
|
+
finally:
|
|
295
|
+
self.error_handling = val_before
|
|
296
|
+
|
|
297
|
+
@contextmanager
|
|
298
|
+
def yes_error_handling(self) -> Iterator[None]:
|
|
299
|
+
val_before = self.error_handling
|
|
300
|
+
self.error_handling = True
|
|
301
|
+
try:
|
|
302
|
+
yield
|
|
303
|
+
finally:
|
|
304
|
+
self.error_handling = val_before
|
|
305
|
+
|
|
306
|
+
requests = Requests()
|
scratchattach/__init__.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from .cloud.cloud import ScratchCloud, TwCloud, get_cloud, get_scratch_cloud, get_tw_cloud
|
|
2
|
-
from .cloud._base import BaseCloud, AnyCloud
|
|
3
|
-
|
|
4
|
-
from .eventhandlers.cloud_server import init_cloud_server
|
|
5
|
-
from .eventhandlers._base import BaseEventHandler
|
|
6
|
-
from .eventhandlers.filterbot import Filterbot, HardFilter, SoftFilter, SpamFilter
|
|
7
|
-
from .eventhandlers.cloud_storage import Database
|
|
8
|
-
from .eventhandlers.combine import MultiEventHandler
|
|
9
|
-
|
|
10
|
-
from .other.other_apis import *
|
|
11
|
-
from .other.project_json_capabilities import ProjectBody, get_empty_project_pb, get_pb_from_dict, read_sb3_file, download_asset
|
|
12
|
-
from .utils.encoder import Encoding
|
|
13
|
-
from .utils.enums import Languages, TTSVoices
|
|
14
|
-
from .utils.exceptions import LoginDataWarning
|
|
15
|
-
|
|
16
|
-
from .site.activity import Activity
|
|
17
|
-
from .site.backpack_asset import BackpackAsset
|
|
18
|
-
from .site.comment import Comment
|
|
19
|
-
from .site.cloud_activity import CloudActivity
|
|
20
|
-
from .site.forum import ForumPost, ForumTopic, get_topic, get_topic_list, youtube_link_to_scratch
|
|
21
|
-
from .site.project import Project, get_project, search_projects, explore_projects
|
|
22
|
-
from .site.session import Session, login, login_by_id, login_by_session_string, login_by_io, login_by_file, login_from_browser
|
|
23
|
-
from .site.studio import Studio, get_studio, search_studios, explore_studios
|
|
24
|
-
from .site.classroom import Classroom, get_classroom
|
|
25
|
-
from .site.user import User, get_user
|
|
26
|
-
from .site._base import BaseSiteComponent
|
|
27
|
-
from .site.browser_cookies import Browser, ANY, FIREFOX, CHROME, CHROMIUM, VIVALDI, EDGE, EDGE_DEV, SAFARI
|
|
28
|
-
|
|
29
|
-
from . import editor
|