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.
Files changed (83) hide show
  1. cli/__about__.py +1 -0
  2. cli/__init__.py +26 -0
  3. cli/cmd/__init__.py +4 -0
  4. cli/cmd/group.py +127 -0
  5. cli/cmd/login.py +60 -0
  6. cli/cmd/profile.py +7 -0
  7. cli/cmd/sessions.py +5 -0
  8. cli/context.py +142 -0
  9. cli/db.py +66 -0
  10. cli/namespace.py +14 -0
  11. cloud/__init__.py +2 -0
  12. cloud/_base.py +483 -0
  13. cloud/cloud.py +183 -0
  14. editor/__init__.py +22 -0
  15. editor/asset.py +265 -0
  16. editor/backpack_json.py +115 -0
  17. editor/base.py +191 -0
  18. editor/block.py +584 -0
  19. editor/blockshape.py +357 -0
  20. editor/build_defaulting.py +51 -0
  21. editor/code_translation/__init__.py +0 -0
  22. editor/code_translation/parse.py +177 -0
  23. editor/comment.py +80 -0
  24. editor/commons.py +145 -0
  25. editor/extension.py +50 -0
  26. editor/field.py +99 -0
  27. editor/inputs.py +138 -0
  28. editor/meta.py +117 -0
  29. editor/monitor.py +185 -0
  30. editor/mutation.py +381 -0
  31. editor/pallete.py +88 -0
  32. editor/prim.py +174 -0
  33. editor/project.py +381 -0
  34. editor/sprite.py +609 -0
  35. editor/twconfig.py +114 -0
  36. editor/vlb.py +134 -0
  37. eventhandlers/__init__.py +0 -0
  38. eventhandlers/_base.py +101 -0
  39. eventhandlers/cloud_events.py +130 -0
  40. eventhandlers/cloud_recorder.py +26 -0
  41. eventhandlers/cloud_requests.py +544 -0
  42. eventhandlers/cloud_server.py +249 -0
  43. eventhandlers/cloud_storage.py +135 -0
  44. eventhandlers/combine.py +30 -0
  45. eventhandlers/filterbot.py +163 -0
  46. eventhandlers/message_events.py +42 -0
  47. other/__init__.py +0 -0
  48. other/other_apis.py +598 -0
  49. other/project_json_capabilities.py +475 -0
  50. {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +1 -1
  51. scratchattach-3.0.0b1.dist-info/RECORD +79 -0
  52. scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
  53. site/__init__.py +0 -0
  54. site/_base.py +93 -0
  55. site/activity.py +426 -0
  56. site/alert.py +226 -0
  57. site/backpack_asset.py +119 -0
  58. site/browser_cookie3_stub.py +17 -0
  59. site/browser_cookies.py +61 -0
  60. site/classroom.py +454 -0
  61. site/cloud_activity.py +121 -0
  62. site/comment.py +228 -0
  63. site/forum.py +436 -0
  64. site/placeholder.py +132 -0
  65. site/project.py +932 -0
  66. site/session.py +1323 -0
  67. site/studio.py +704 -0
  68. site/typed_dicts.py +151 -0
  69. site/user.py +1252 -0
  70. utils/__init__.py +0 -0
  71. utils/commons.py +263 -0
  72. utils/encoder.py +161 -0
  73. utils/enums.py +237 -0
  74. utils/exceptions.py +277 -0
  75. utils/optional_async.py +154 -0
  76. utils/requests.py +306 -0
  77. scratchattach/__init__.py +0 -37
  78. scratchattach/__main__.py +0 -93
  79. scratchattach-3.0.0b0.dist-info/RECORD +0 -8
  80. scratchattach-3.0.0b0.dist-info/top_level.txt +0 -1
  81. {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +0 -0
  82. {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/entry_points.txt +0 -0
  83. {scratchattach-3.0.0b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
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,37 +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 (
15
- LoginDataWarning,
16
- GetAuthenticationWarning,
17
- StudioAuthenticationWarning,
18
- ClassroomAuthenticationWarning,
19
- ProjectAuthenticationWarning,
20
- UserAuthenticationWarning)
21
-
22
- from .site.activity import Activity, ActivityTypes
23
- from .site.backpack_asset import BackpackAsset
24
- from .site.comment import Comment, CommentSource
25
- from .site.cloud_activity import CloudActivity
26
- from .site.forum import ForumPost, ForumTopic, get_topic, get_topic_list, youtube_link_to_scratch
27
- from .site.project import Project, get_project, search_projects, explore_projects
28
- from .site.session import Session, login, login_by_id, login_by_session_string, login_by_io, login_by_file, \
29
- login_from_browser
30
- from .site.studio import Studio, get_studio, search_studios, explore_studios
31
- from .site.classroom import Classroom, get_classroom
32
- from .site.user import User, get_user, Rank
33
- from .site._base import BaseSiteComponent
34
- from .site.browser_cookies import Browser, ANY, FIREFOX, CHROME, CHROMIUM, VIVALDI, EDGE, EDGE_DEV, SAFARI
35
- from .site.placeholder import PlaceholderProject, get_placeholder_project, create_placeholder_project
36
-
37
- from . import editor
scratchattach/__main__.py DELETED
@@ -1,93 +0,0 @@
1
- """
2
- Scratchattach CLI. Most source code is in the `cli` directory
3
- """
4
-
5
- import argparse
6
-
7
- from scratchattach import cli
8
- from scratchattach.cli import db, cmd
9
- from scratchattach.cli.context import ctx, console
10
-
11
- import rich.traceback
12
-
13
- rich.traceback.install()
14
-
15
-
16
- # noinspection PyUnusedLocal
17
- def main():
18
- parser = argparse.ArgumentParser(
19
- prog="scratch",
20
- description="Scratchattach CLI",
21
- epilog=f"Running scratchattach CLI version {cli.VERSION}",
22
- )
23
-
24
- # Using walrus operator & ifs for artificial indentation
25
- if commands := parser.add_subparsers(dest="command"):
26
- commands.add_parser("profile", help="View your profile")
27
- commands.add_parser("sessions", help="View session list")
28
- if login := commands.add_parser("login", help="Login to Scratch"):
29
- login.add_argument("--sessid", dest="sessid", nargs="?", default=False, const=True,
30
- help="Login by session ID")
31
- if group := commands.add_parser("group", help="View current session group"):
32
- if group_commands := group.add_subparsers(dest="group_command"):
33
- group_commands.add_parser("list", help="List all session groups")
34
- group_commands.add_parser("add", help="Add sessions to group")
35
- group_commands.add_parser("remove", help="Remove sessions from a group")
36
- group_commands.add_parser("delete", help="Delete current group")
37
- if group_copy := group_commands.add_parser("copy", help="Copy current group with a new name"):
38
- group_copy.add_argument("group_name", help="New group name")
39
- if group_rename := group_commands.add_parser("rename", help="Rename current group"):
40
- group_rename.add_argument("group_name", help="New group name")
41
- if group_new := group_commands.add_parser("new", help="Create a new group"):
42
- group_new.add_argument("group_name")
43
- if group_switch := group_commands.add_parser("switch", help="Change the current group"):
44
- group_switch.add_argument("group_name")
45
-
46
- parser.add_argument("-U", "--username", dest="username", help="Name of user to look at")
47
- parser.add_argument("-P", "--project", dest="project_id", help="ID of project to look at")
48
- parser.add_argument("-S", "--studio", dest="studio_id", help="ID of studio to look at")
49
- parser.add_argument("-L", "--session_name", dest="session_name",
50
- help="Name of (registered) session/login to look at")
51
-
52
- args = parser.parse_args(namespace=cli.ArgSpace())
53
- cli.ctx.args = args
54
- cli.ctx.parser = parser
55
-
56
- match args.command:
57
- case "sessions":
58
- cmd.sessions()
59
- case "login":
60
- cmd.login()
61
- case "group":
62
- cmd.group()
63
- case "profile":
64
- cmd.profile()
65
- case None:
66
- if args.username:
67
- user = ctx.session.connect_user(args.username)
68
- console.print(cli.try_get_img(user.icon, (30, 30)))
69
- console.print(user)
70
- return
71
- if args.studio_id:
72
- studio = ctx.session.connect_studio(args.studio_id)
73
- console.print(cli.try_get_img(studio.thumbnail, (34, 20)))
74
- console.print(studio)
75
- return
76
- if args.project_id:
77
- project = ctx.session.connect_project(args.project_id)
78
- console.print(cli.try_get_img(project.thumbnail, (30, 23)))
79
- console.print(project)
80
- return
81
- if args.session_name:
82
- if sess := ctx.db_get_sess(args.session_name):
83
- console.print(sess)
84
- else:
85
- raise ValueError(f"No session logged in called {args.session_name!r} "
86
- f"- try using `scratch sessions` to see available sessions")
87
- return
88
-
89
- parser.print_help()
90
-
91
-
92
- if __name__ == '__main__':
93
- main()
@@ -1,8 +0,0 @@
1
- scratchattach/__init__.py,sha256=K26DsJHYlj7atN0WVQOmtN7d1rNAnmLcO3ljA1UD1lc,1849
2
- scratchattach/__main__.py,sha256=K520LnmXe5WAlr8UZQE1Owrm8BDncy19QQ5GJ75V9FM,4054
3
- scratchattach-3.0.0b0.dist-info/licenses/LICENSE,sha256=1PRKLhZU4wYt5M-C9f7q0W3go3u_ojnZMNOdR3g3J-E,1080
4
- scratchattach-3.0.0b0.dist-info/METADATA,sha256=qapruF1xW-anLx5HiFgZ8B1k6oH_a1YcV2WHGRsU5PI,5633
5
- scratchattach-3.0.0b0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
6
- scratchattach-3.0.0b0.dist-info/entry_points.txt,sha256=vNXuP05TQKEoIzmzmUzS7zbtSZx0p3JmeUW3QhNdYfg,56
7
- scratchattach-3.0.0b0.dist-info/top_level.txt,sha256=gIwCwW39ohXn0JlnvSzAjV7VtL3qPlRnHiRqBbxsEUE,14
8
- scratchattach-3.0.0b0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- scratchattach