scratchattach 2.1.15b0__py3-none-any.whl → 3.0.0b0__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.
- scratchattach/__init__.py +14 -6
- scratchattach/__main__.py +93 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/METADATA +7 -11
- scratchattach-3.0.0b0.dist-info/RECORD +8 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/WHEEL +1 -1
- scratchattach-3.0.0b0.dist-info/entry_points.txt +2 -0
- scratchattach/cloud/__init__.py +0 -2
- scratchattach/cloud/_base.py +0 -458
- scratchattach/cloud/cloud.py +0 -183
- scratchattach/editor/__init__.py +0 -21
- scratchattach/editor/asset.py +0 -253
- scratchattach/editor/backpack_json.py +0 -117
- scratchattach/editor/base.py +0 -193
- scratchattach/editor/block.py +0 -579
- scratchattach/editor/blockshape.py +0 -357
- scratchattach/editor/build_defaulting.py +0 -51
- scratchattach/editor/code_translation/__init__.py +0 -0
- scratchattach/editor/code_translation/parse.py +0 -177
- scratchattach/editor/comment.py +0 -80
- scratchattach/editor/commons.py +0 -273
- scratchattach/editor/extension.py +0 -50
- scratchattach/editor/field.py +0 -99
- scratchattach/editor/inputs.py +0 -135
- scratchattach/editor/meta.py +0 -114
- scratchattach/editor/monitor.py +0 -183
- scratchattach/editor/mutation.py +0 -324
- scratchattach/editor/pallete.py +0 -90
- scratchattach/editor/prim.py +0 -170
- scratchattach/editor/project.py +0 -279
- scratchattach/editor/sprite.py +0 -599
- scratchattach/editor/twconfig.py +0 -114
- scratchattach/editor/vlb.py +0 -134
- scratchattach/eventhandlers/__init__.py +0 -0
- scratchattach/eventhandlers/_base.py +0 -100
- scratchattach/eventhandlers/cloud_events.py +0 -110
- scratchattach/eventhandlers/cloud_recorder.py +0 -26
- scratchattach/eventhandlers/cloud_requests.py +0 -459
- scratchattach/eventhandlers/cloud_server.py +0 -246
- scratchattach/eventhandlers/cloud_storage.py +0 -136
- scratchattach/eventhandlers/combine.py +0 -30
- scratchattach/eventhandlers/filterbot.py +0 -161
- scratchattach/eventhandlers/message_events.py +0 -42
- scratchattach/other/__init__.py +0 -0
- scratchattach/other/other_apis.py +0 -284
- scratchattach/other/project_json_capabilities.py +0 -475
- scratchattach/site/__init__.py +0 -0
- scratchattach/site/_base.py +0 -66
- scratchattach/site/activity.py +0 -382
- scratchattach/site/alert.py +0 -227
- scratchattach/site/backpack_asset.py +0 -118
- scratchattach/site/browser_cookie3_stub.py +0 -17
- scratchattach/site/browser_cookies.py +0 -61
- scratchattach/site/classroom.py +0 -447
- scratchattach/site/cloud_activity.py +0 -107
- scratchattach/site/comment.py +0 -242
- scratchattach/site/forum.py +0 -432
- scratchattach/site/project.py +0 -826
- scratchattach/site/session.py +0 -1238
- scratchattach/site/studio.py +0 -611
- scratchattach/site/user.py +0 -956
- scratchattach/utils/__init__.py +0 -0
- scratchattach/utils/commons.py +0 -255
- scratchattach/utils/encoder.py +0 -158
- scratchattach/utils/enums.py +0 -236
- scratchattach/utils/exceptions.py +0 -243
- scratchattach/utils/requests.py +0 -93
- scratchattach-2.1.15b0.dist-info/RECORD +0 -66
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/licenses/LICENSE +0 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/top_level.txt +0 -0
scratchattach/cloud/cloud.py
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
"""v2 ready: ScratchCloud, TwCloud and CustomCloud classes"""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
import warnings
|
|
5
|
-
|
|
6
|
-
from websocket import WebSocketBadStatusException
|
|
7
|
-
|
|
8
|
-
from ._base import BaseCloud
|
|
9
|
-
from typing import Type
|
|
10
|
-
from scratchattach.utils.requests import requests
|
|
11
|
-
from scratchattach.utils import exceptions, commons
|
|
12
|
-
from scratchattach.site import cloud_activity
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ScratchCloud(BaseCloud):
|
|
16
|
-
def __init__(self, *, project_id, _session=None):
|
|
17
|
-
super().__init__()
|
|
18
|
-
|
|
19
|
-
self.project_id = project_id
|
|
20
|
-
|
|
21
|
-
# Configure this object's attributes specifically for being used with Scratch's cloud:
|
|
22
|
-
self.cloud_host = "wss://clouddata.scratch.mit.edu"
|
|
23
|
-
self.length_limit = 256
|
|
24
|
-
self._session = _session
|
|
25
|
-
if self._session is not None:
|
|
26
|
-
self.username = self._session.username
|
|
27
|
-
self.cookie = "scratchsessionsid=" + self._session.id + ";"
|
|
28
|
-
self.origin = "https://scratch.mit.edu"
|
|
29
|
-
|
|
30
|
-
def connect(self):
|
|
31
|
-
self._assert_auth() # Connecting to Scratch's cloud websocket requires a login to the Scratch website
|
|
32
|
-
try:
|
|
33
|
-
super().connect()
|
|
34
|
-
except WebSocketBadStatusException as e:
|
|
35
|
-
raise exceptions.CloudConnectionError(f"Error: Scratch's Cloud system may be down. Please try again later.") from e
|
|
36
|
-
|
|
37
|
-
def set_var(self, variable, value):
|
|
38
|
-
self._assert_auth() # Setting a cloud var requires a login to the Scratch website
|
|
39
|
-
super().set_var(variable, value)
|
|
40
|
-
|
|
41
|
-
def set_vars(self, var_value_dict, *, intelligent_waits=True):
|
|
42
|
-
self._assert_auth()
|
|
43
|
-
super().set_vars(var_value_dict, intelligent_waits=intelligent_waits)
|
|
44
|
-
|
|
45
|
-
def logs(self, *, filter_by_var_named=None, limit=100, offset=0) -> list[cloud_activity.CloudActivity]:
|
|
46
|
-
"""
|
|
47
|
-
Gets the data from Scratch's clouddata logs.
|
|
48
|
-
|
|
49
|
-
Keyword Arguments:
|
|
50
|
-
filter_by_var_named (str or None): If you only want to get data for one cloud variable, set this argument to its name.
|
|
51
|
-
limit (int): Max. amount of returned activity.
|
|
52
|
-
offset (int): Offset of the first activity in the returned list.
|
|
53
|
-
log_url (str): If you want to get the clouddata from a cloud log API different to Scratch's normal cloud log API, set this argument to the URL of the API. Only set this argument if you know what you are doing. If you want to get the clouddata from the normal API, don't put this argument.
|
|
54
|
-
"""
|
|
55
|
-
try:
|
|
56
|
-
data = requests.get(f"https://clouddata.scratch.mit.edu/logs?projectid={self.project_id}&limit={limit}&offset={offset}", timeout=10).json()
|
|
57
|
-
if filter_by_var_named is not None:
|
|
58
|
-
filter_by_var_named = filter_by_var_named.removeprefix("☁ ")
|
|
59
|
-
data = list(filter(lambda k: k["name"] == "☁ "+filter_by_var_named, data))
|
|
60
|
-
for x in data:
|
|
61
|
-
x["cloud"] = self
|
|
62
|
-
return commons.parse_object_list(data, cloud_activity.CloudActivity, self._session, "name")
|
|
63
|
-
except Exception as e:
|
|
64
|
-
raise exceptions.FetchError(str(e))
|
|
65
|
-
|
|
66
|
-
def get_var(self, var, *, use_logs=False):
|
|
67
|
-
var = var.removeprefix("☁ ")
|
|
68
|
-
if self._session is None or use_logs:
|
|
69
|
-
filtered = self.logs(limit=100, filter_by_var_named="☁ "+var)
|
|
70
|
-
if len(filtered) == 0:
|
|
71
|
-
return None
|
|
72
|
-
return filtered[0].value
|
|
73
|
-
else:
|
|
74
|
-
if self.recorder is None:
|
|
75
|
-
initial_values = self.get_all_vars(use_logs=True)
|
|
76
|
-
return super().get_var("☁ "+var, recorder_initial_values=initial_values)
|
|
77
|
-
else:
|
|
78
|
-
return super().get_var("☁ "+var)
|
|
79
|
-
|
|
80
|
-
def get_all_vars(self, *, use_logs=False):
|
|
81
|
-
if self._session is None or use_logs:
|
|
82
|
-
logs = self.logs(limit=100)
|
|
83
|
-
logs.reverse()
|
|
84
|
-
clouddata = {}
|
|
85
|
-
for activity in logs:
|
|
86
|
-
clouddata[activity.name] = activity.value
|
|
87
|
-
return clouddata
|
|
88
|
-
else:
|
|
89
|
-
if self.recorder is None:
|
|
90
|
-
initial_values = self.get_all_vars(use_logs=True)
|
|
91
|
-
return super().get_all_vars(recorder_initial_values=initial_values)
|
|
92
|
-
else:
|
|
93
|
-
return super().get_all_vars()
|
|
94
|
-
|
|
95
|
-
def events(self, *, use_logs=False):
|
|
96
|
-
if self._session is None or use_logs:
|
|
97
|
-
from scratchattach.eventhandlers.cloud_events import CloudLogEvents
|
|
98
|
-
return CloudLogEvents(self)
|
|
99
|
-
else:
|
|
100
|
-
return super().events()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
class TwCloud(BaseCloud):
|
|
104
|
-
def __init__(self, *, project_id, cloud_host="wss://clouddata.turbowarp.org", purpose="", contact="",
|
|
105
|
-
_session=None):
|
|
106
|
-
super().__init__()
|
|
107
|
-
|
|
108
|
-
self.project_id = project_id
|
|
109
|
-
|
|
110
|
-
# Configure this object's attributes specifically for being used with TurboWarp's cloud:
|
|
111
|
-
self.cloud_host = cloud_host
|
|
112
|
-
self.ws_shortterm_ratelimit = 0 # TurboWarp doesn't enforce a wait time between cloud variable sets
|
|
113
|
-
self.ws_longterm_ratelimit = 0
|
|
114
|
-
self.length_limit = 100000 # TurboWarp doesn't enforce a cloud variable length
|
|
115
|
-
purpose_string = ""
|
|
116
|
-
if purpose != "" or contact != "":
|
|
117
|
-
purpose_string = f" (Purpose:{purpose}; Contact:{contact})"
|
|
118
|
-
self.header = {"User-Agent":f"scratchattach/2.0.0{purpose_string}"}
|
|
119
|
-
|
|
120
|
-
class CustomCloud(BaseCloud):
|
|
121
|
-
|
|
122
|
-
def __init__(self, *, project_id, cloud_host, **kwargs):
|
|
123
|
-
super().__init__()
|
|
124
|
-
|
|
125
|
-
self.project_id = project_id
|
|
126
|
-
self.cloud_host = cloud_host
|
|
127
|
-
|
|
128
|
-
# Configure this object's attributes specifically for the cloud that the developer wants to connect to:
|
|
129
|
-
# -> For this purpose, all additional keyword arguments (kwargs) will be set as attributes of the CustomCloud object
|
|
130
|
-
# This allows the maximum amount of attribute customization
|
|
131
|
-
# See the docstring for the cloud._base.BaseCloud class to find out what attributes can be set / specified as keyword args
|
|
132
|
-
self.__dict__.update(kwargs)
|
|
133
|
-
|
|
134
|
-
# If even more customization is needed, the developer can create a class inheriting from cloud._base.BaseCloud to override functions like .set_var etc.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def get_cloud(project_id, *, CloudClass:Type[BaseCloud]=ScratchCloud) -> BaseCloud:
|
|
138
|
-
"""
|
|
139
|
-
Connects to a cloud (by default Scratch's cloud) as logged out user.
|
|
140
|
-
|
|
141
|
-
Warning:
|
|
142
|
-
Since this method doesn't connect a login / session to the returned object, setting Scratch cloud variables won't be possible with it.
|
|
143
|
-
|
|
144
|
-
To set Scratch cloud variables, use `scratchattach.site.session.Session.connect_scratch_cloud` instead.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
project_id:
|
|
148
|
-
|
|
149
|
-
Keyword arguments:
|
|
150
|
-
CloudClass: The class that the returned object should be of. By default this class is scratchattach.cloud.ScratchCloud.
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
Type[scratchattach.cloud._base.BaseCloud]: An object representing the cloud of a project. Can be of any class inheriting from BaseCloud.
|
|
154
|
-
"""
|
|
155
|
-
warnings.warn(
|
|
156
|
-
"Warning: To set Scratch cloud variables, use session.connect_cloud instead of get_cloud",
|
|
157
|
-
exceptions.AnonymousSiteComponentWarning
|
|
158
|
-
)
|
|
159
|
-
return CloudClass(project_id=project_id)
|
|
160
|
-
|
|
161
|
-
def get_scratch_cloud(project_id):
|
|
162
|
-
"""
|
|
163
|
-
Warning:
|
|
164
|
-
Since this method doesn't connect a login / session to the returned object, setting Scratch cloud variables won't be possible with it.
|
|
165
|
-
|
|
166
|
-
To set Scratch cloud variables, use `scratchattach.Session.connect_scratch_cloud` instead.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Returns:
|
|
170
|
-
scratchattach.cloud.ScratchCloud: An object representing the Scratch cloud of a project.
|
|
171
|
-
"""
|
|
172
|
-
warnings.warn(
|
|
173
|
-
"To set Scratch cloud variables, use session.connect_scratch_cloud instead of get_scratch_cloud",
|
|
174
|
-
exceptions.AnonymousSiteComponentWarning
|
|
175
|
-
)
|
|
176
|
-
return ScratchCloud(project_id=project_id)
|
|
177
|
-
|
|
178
|
-
def get_tw_cloud(project_id, *, purpose="", contact="", cloud_host="wss://clouddata.turbowarp.org"):
|
|
179
|
-
"""
|
|
180
|
-
Returns:
|
|
181
|
-
scratchattach.cloud.TwCloud: An object representing the TurboWarp cloud of a project.
|
|
182
|
-
"""
|
|
183
|
-
return TwCloud(project_id=project_id, purpose=purpose, contact=contact, cloud_host=cloud_host)
|
scratchattach/editor/__init__.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
scratchattach.editor (sbeditor v2) - a library for all things sb3
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from .asset import Asset, Costume, Sound
|
|
6
|
-
from .project import Project
|
|
7
|
-
from .extension import Extensions, Extension
|
|
8
|
-
from .mutation import Mutation, Argument, parse_proc_code
|
|
9
|
-
from .meta import Meta, set_meta_platform
|
|
10
|
-
from .sprite import Sprite
|
|
11
|
-
from .block import Block
|
|
12
|
-
from .prim import Prim, PrimTypes
|
|
13
|
-
from .backpack_json import load_script as load_script_from_backpack
|
|
14
|
-
from .twconfig import TWConfig, is_valid_twconfig
|
|
15
|
-
from .inputs import Input, ShadowStatuses
|
|
16
|
-
from .field import Field
|
|
17
|
-
from .vlb import Variable, List, Broadcast
|
|
18
|
-
from .comment import Comment
|
|
19
|
-
from .monitor import Monitor
|
|
20
|
-
|
|
21
|
-
from .build_defaulting import add_chain, add_comment, add_block
|
scratchattach/editor/asset.py
DELETED
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from hashlib import md5
|
|
5
|
-
import requests
|
|
6
|
-
|
|
7
|
-
from . import base, commons, sprite, build_defaulting
|
|
8
|
-
from typing import Optional
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@dataclass(init=True, repr=True)
|
|
12
|
-
class AssetFile:
|
|
13
|
-
"""
|
|
14
|
-
Represents the file information for an asset
|
|
15
|
-
- stores the filename, data, and md5 hash
|
|
16
|
-
"""
|
|
17
|
-
filename: str
|
|
18
|
-
_data: bytes = field(repr=False, default_factory=bytes)
|
|
19
|
-
_md5: str = field(repr=False, default_factory=str)
|
|
20
|
-
|
|
21
|
-
@property
|
|
22
|
-
def data(self):
|
|
23
|
-
"""
|
|
24
|
-
Return the contents of the asset file, as bytes
|
|
25
|
-
"""
|
|
26
|
-
if self._data is None:
|
|
27
|
-
# Download and cache
|
|
28
|
-
rq = requests.get(f"https://assets.scratch.mit.edu/internalapi/asset/{self.filename}/get/")
|
|
29
|
-
if rq.status_code != 200:
|
|
30
|
-
raise ValueError(f"Can't download asset {self.filename}\nIs not uploaded to scratch! Response: {rq.text}")
|
|
31
|
-
|
|
32
|
-
self._data = rq.content
|
|
33
|
-
|
|
34
|
-
return self._data
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def md5(self):
|
|
38
|
-
"""
|
|
39
|
-
Compute/retrieve the md5 hash value of the asset file data
|
|
40
|
-
"""
|
|
41
|
-
if self._md5 is None:
|
|
42
|
-
self._md5 = md5(self.data).hexdigest()
|
|
43
|
-
|
|
44
|
-
return self._md5
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class Asset(base.SpriteSubComponent):
|
|
48
|
-
def __init__(self,
|
|
49
|
-
name: str = "costume1",
|
|
50
|
-
file_name: str = "b7853f557e4426412e64bb3da6531a99.svg",
|
|
51
|
-
_sprite: commons.SpriteInput = build_defaulting.SPRITE_DEFAULT):
|
|
52
|
-
"""
|
|
53
|
-
Represents a generic asset. Can be a sound or an image.
|
|
54
|
-
https://en.scratch-wiki.info/wiki/Scratch_File_Format#Assets
|
|
55
|
-
"""
|
|
56
|
-
try:
|
|
57
|
-
asset_id, data_format = file_name.split('.')
|
|
58
|
-
except ValueError:
|
|
59
|
-
raise ValueError(f"Invalid file name: {file_name}, # of '.' in {file_name} ({file_name.count('.')}) != 2; "
|
|
60
|
-
f"(too many/few values to unpack)")
|
|
61
|
-
self.name = name
|
|
62
|
-
|
|
63
|
-
self.id = asset_id
|
|
64
|
-
self.data_format = data_format
|
|
65
|
-
|
|
66
|
-
super().__init__(_sprite)
|
|
67
|
-
|
|
68
|
-
def __repr__(self):
|
|
69
|
-
return f"Asset<{self.name!r}>"
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def folder(self):
|
|
73
|
-
"""
|
|
74
|
-
Get the folder name of this asset, based on the asset name. Uses the turbowarp syntax
|
|
75
|
-
"""
|
|
76
|
-
return commons.get_folder_name(self.name)
|
|
77
|
-
|
|
78
|
-
@property
|
|
79
|
-
def name_nfldr(self):
|
|
80
|
-
"""
|
|
81
|
-
Get the asset name after removing the folder name
|
|
82
|
-
"""
|
|
83
|
-
return commons.get_name_nofldr(self.name)
|
|
84
|
-
|
|
85
|
-
@property
|
|
86
|
-
def file_name(self):
|
|
87
|
-
"""
|
|
88
|
-
Get the exact file name, as it would be within an sb3 file
|
|
89
|
-
equivalent to the md5ext value using in scratch project JSON
|
|
90
|
-
"""
|
|
91
|
-
return f"{self.id}.{self.data_format}"
|
|
92
|
-
|
|
93
|
-
@property
|
|
94
|
-
def md5ext(self):
|
|
95
|
-
"""
|
|
96
|
-
Get the exact file name, as it would be within an sb3 file
|
|
97
|
-
equivalent to the md5ext value using in scratch project JSON
|
|
98
|
-
"""
|
|
99
|
-
return self.file_name
|
|
100
|
-
|
|
101
|
-
@property
|
|
102
|
-
def parent(self):
|
|
103
|
-
"""
|
|
104
|
-
Return the project that this asset is attached to. If there is no attached project,
|
|
105
|
-
try returning the attached sprite
|
|
106
|
-
"""
|
|
107
|
-
if self.project is None:
|
|
108
|
-
return self.sprite
|
|
109
|
-
else:
|
|
110
|
-
return self.project
|
|
111
|
-
|
|
112
|
-
@property
|
|
113
|
-
def asset_file(self) -> AssetFile:
|
|
114
|
-
"""
|
|
115
|
-
Get the associated asset file object for this asset object
|
|
116
|
-
"""
|
|
117
|
-
for asset_file in self.parent.asset_data:
|
|
118
|
-
if asset_file.filename == self.file_name:
|
|
119
|
-
return asset_file
|
|
120
|
-
|
|
121
|
-
# No pre-existing asset file object; create one and add it to the project
|
|
122
|
-
asset_file = AssetFile(self.file_name)
|
|
123
|
-
self.project.asset_data.append(asset_file)
|
|
124
|
-
return asset_file
|
|
125
|
-
|
|
126
|
-
@staticmethod
|
|
127
|
-
def from_json(data: dict):
|
|
128
|
-
"""
|
|
129
|
-
Load asset data from project.json
|
|
130
|
-
"""
|
|
131
|
-
_name = data.get("name")
|
|
132
|
-
assert isinstance(_name, str)
|
|
133
|
-
_file_name = data.get("md5ext")
|
|
134
|
-
if _file_name is None:
|
|
135
|
-
if "dataFormat" in data and "assetId" in data:
|
|
136
|
-
_id = data["assetId"]
|
|
137
|
-
_data_format = data["dataFormat"]
|
|
138
|
-
_file_name = f"{_id}.{_data_format}"
|
|
139
|
-
else:
|
|
140
|
-
_file_name = ""
|
|
141
|
-
assert isinstance(_file_name, str)
|
|
142
|
-
|
|
143
|
-
return Asset(_name, _file_name)
|
|
144
|
-
|
|
145
|
-
def to_json(self) -> dict:
|
|
146
|
-
"""
|
|
147
|
-
Convert asset data to project.json format
|
|
148
|
-
"""
|
|
149
|
-
return {
|
|
150
|
-
"name": self.name,
|
|
151
|
-
|
|
152
|
-
"assetId": self.id,
|
|
153
|
-
"md5ext": self.file_name,
|
|
154
|
-
"dataFormat": self.data_format,
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
# todo: implement below:
|
|
158
|
-
"""
|
|
159
|
-
@staticmethod
|
|
160
|
-
def from_file(fp: str, name: str = None):
|
|
161
|
-
image_types = ("png", "jpg", "jpeg", "svg")
|
|
162
|
-
sound_types = ("wav", "mp3")
|
|
163
|
-
|
|
164
|
-
# Should save data as well so it can be uploaded to scratch if required (add to project asset data)
|
|
165
|
-
...
|
|
166
|
-
"""
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
class Costume(Asset):
|
|
170
|
-
def __init__(self,
|
|
171
|
-
name: str = "Cat",
|
|
172
|
-
file_name: str = "b7853f557e4426412e64bb3da6531a99.svg",
|
|
173
|
-
|
|
174
|
-
bitmap_resolution=None,
|
|
175
|
-
rotation_center_x: int | float = 48,
|
|
176
|
-
rotation_center_y: int | float = 50,
|
|
177
|
-
_sprite: commons.SpriteInput = build_defaulting.SPRITE_DEFAULT):
|
|
178
|
-
"""
|
|
179
|
-
A costume (image). An asset with additional properties
|
|
180
|
-
https://en.scratch-wiki.info/wiki/Scratch_File_Format#Costumes
|
|
181
|
-
"""
|
|
182
|
-
super().__init__(name, file_name, _sprite)
|
|
183
|
-
|
|
184
|
-
self.bitmap_resolution = bitmap_resolution
|
|
185
|
-
self.rotation_center_x = rotation_center_x
|
|
186
|
-
self.rotation_center_y = rotation_center_y
|
|
187
|
-
|
|
188
|
-
@staticmethod
|
|
189
|
-
def from_json(data):
|
|
190
|
-
"""
|
|
191
|
-
Load costume data from project.json
|
|
192
|
-
"""
|
|
193
|
-
_asset_load = Asset.from_json(data)
|
|
194
|
-
|
|
195
|
-
bitmap_resolution = data.get("bitmapResolution")
|
|
196
|
-
|
|
197
|
-
rotation_center_x = data["rotationCenterX"]
|
|
198
|
-
rotation_center_y = data["rotationCenterY"]
|
|
199
|
-
return Costume(_asset_load.name, _asset_load.file_name,
|
|
200
|
-
|
|
201
|
-
bitmap_resolution, rotation_center_x, rotation_center_y)
|
|
202
|
-
|
|
203
|
-
def to_json(self) -> dict:
|
|
204
|
-
"""
|
|
205
|
-
Convert costume to project.json format
|
|
206
|
-
"""
|
|
207
|
-
_json = super().to_json()
|
|
208
|
-
_json.update({
|
|
209
|
-
"bitmapResolution": self.bitmap_resolution,
|
|
210
|
-
"rotationCenterX": self.rotation_center_x,
|
|
211
|
-
"rotationCenterY": self.rotation_center_y
|
|
212
|
-
})
|
|
213
|
-
return _json
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
class Sound(Asset):
|
|
217
|
-
def __init__(self,
|
|
218
|
-
name: str = "pop",
|
|
219
|
-
file_name: str = "83a9787d4cb6f3b7632b4ddfebf74367.wav",
|
|
220
|
-
|
|
221
|
-
rate: Optional[int] = None,
|
|
222
|
-
sample_count: Optional[int] = None,
|
|
223
|
-
_sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT):
|
|
224
|
-
"""
|
|
225
|
-
A sound. An asset with additional properties
|
|
226
|
-
https://en.scratch-wiki.info/wiki/Scratch_File_Format#Sounds
|
|
227
|
-
"""
|
|
228
|
-
super().__init__(name, file_name, _sprite)
|
|
229
|
-
|
|
230
|
-
self.rate = rate
|
|
231
|
-
self.sample_count = sample_count
|
|
232
|
-
|
|
233
|
-
@staticmethod
|
|
234
|
-
def from_json(data):
|
|
235
|
-
"""
|
|
236
|
-
Load sound from project.json
|
|
237
|
-
"""
|
|
238
|
-
_asset_load = Asset.from_json(data)
|
|
239
|
-
|
|
240
|
-
rate = data.get("rate")
|
|
241
|
-
sample_count = data.get("sampleCount")
|
|
242
|
-
return Sound(_asset_load.name, _asset_load.file_name, rate, sample_count)
|
|
243
|
-
|
|
244
|
-
def to_json(self) -> dict:
|
|
245
|
-
"""
|
|
246
|
-
Convert Sound to project.json format
|
|
247
|
-
"""
|
|
248
|
-
_json = super().to_json()
|
|
249
|
-
commons.noneless_update(_json, {
|
|
250
|
-
"rate": self.rate,
|
|
251
|
-
"sampleCount": self.sample_count
|
|
252
|
-
})
|
|
253
|
-
return _json
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Module to deal with the backpack's weird JSON format, by overriding with new load methods
|
|
3
|
-
"""
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
from . import block, prim, field, inputs, mutation, sprite
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def parse_prim_fields(_fields: dict[str]) -> tuple[str | None, str | None, str | None]:
|
|
10
|
-
"""
|
|
11
|
-
Function for reading the fields in a backpack **primitive**
|
|
12
|
-
"""
|
|
13
|
-
for key, value in _fields.items():
|
|
14
|
-
key: str
|
|
15
|
-
value: dict[str, str]
|
|
16
|
-
prim_value, prim_name, prim_id = (None,) * 3
|
|
17
|
-
if key == "NUM":
|
|
18
|
-
prim_value = value.get("value")
|
|
19
|
-
else:
|
|
20
|
-
prim_name = value.get("value")
|
|
21
|
-
prim_id = value.get("id")
|
|
22
|
-
|
|
23
|
-
# There really should only be 1 item, and this function can only return for that item
|
|
24
|
-
return prim_value, prim_name, prim_id
|
|
25
|
-
return (None,) * 3
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class BpField(field.Field):
|
|
29
|
-
"""
|
|
30
|
-
A normal field but with a different load method
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
@staticmethod
|
|
34
|
-
def from_json(data: dict[str, str]) -> field.Field:
|
|
35
|
-
# We can very simply convert it to the regular format
|
|
36
|
-
data = [data.get("value"), data.get("id")]
|
|
37
|
-
return field.Field.from_json(data)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class BpInput(inputs.Input):
|
|
41
|
-
"""
|
|
42
|
-
A normal input but with a different load method
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
@staticmethod
|
|
46
|
-
def from_json(data: dict[str, str]) -> inputs.Input:
|
|
47
|
-
# The actual data is stored in a separate prim block
|
|
48
|
-
_id = data.get("shadow")
|
|
49
|
-
_obscurer_id = data.get("block")
|
|
50
|
-
|
|
51
|
-
if _obscurer_id == _id:
|
|
52
|
-
# If both the shadow and obscurer are the same, then there is no actual obscurer
|
|
53
|
-
_obscurer_id = None
|
|
54
|
-
# We cannot work out the shadow status yet since that is located in the primitive
|
|
55
|
-
return inputs.Input(None, _id=_id, _obscurer_id=_obscurer_id)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class BpBlock(block.Block):
|
|
59
|
-
"""
|
|
60
|
-
A normal block but with a different load method
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
@staticmethod
|
|
64
|
-
def from_json(data: dict) -> prim.Prim | block.Block:
|
|
65
|
-
"""
|
|
66
|
-
Load a block in the **backpack** JSON format
|
|
67
|
-
:param data: A dictionary (not list)
|
|
68
|
-
:return: A new block/prim object
|
|
69
|
-
"""
|
|
70
|
-
_opcode = data["opcode"]
|
|
71
|
-
|
|
72
|
-
_x, _y = data.get("x"), data.get("y")
|
|
73
|
-
if prim.is_prim_opcode(_opcode):
|
|
74
|
-
# This is actually a prim
|
|
75
|
-
prim_value, prim_name, prim_id = parse_prim_fields(data["fields"])
|
|
76
|
-
return prim.Prim(prim.PrimTypes.find(_opcode, "opcode"),
|
|
77
|
-
prim_value, prim_name, prim_id)
|
|
78
|
-
|
|
79
|
-
_next_id = data.get("next")
|
|
80
|
-
_parent_id = data.get("parent")
|
|
81
|
-
|
|
82
|
-
_shadow = data.get("shadow", False)
|
|
83
|
-
_top_level = data.get("topLevel", _parent_id is None)
|
|
84
|
-
|
|
85
|
-
_inputs = {}
|
|
86
|
-
for _input_code, _input_data in data.get("inputs", {}).items():
|
|
87
|
-
_inputs[_input_code] = BpInput.from_json(_input_data)
|
|
88
|
-
|
|
89
|
-
_fields = {}
|
|
90
|
-
for _field_code, _field_data in data.get("fields", {}).items():
|
|
91
|
-
_fields[_field_code] = BpField.from_json(_field_data)
|
|
92
|
-
|
|
93
|
-
if "mutation" in data:
|
|
94
|
-
_mutation = mutation.Mutation.from_json(data["mutation"])
|
|
95
|
-
else:
|
|
96
|
-
_mutation = None
|
|
97
|
-
|
|
98
|
-
return block.Block(_opcode, _shadow, _top_level, _mutation, _fields, _inputs, _x, _y, _next_id=_next_id,
|
|
99
|
-
_parent_id=_parent_id)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def load_script(_script_data: list[dict]) -> sprite.Sprite:
|
|
103
|
-
"""
|
|
104
|
-
Loads a script into a sprite from the backpack JSON format
|
|
105
|
-
:param _script_data: Backpack script JSON data
|
|
106
|
-
:return: a blockchain object containing the script
|
|
107
|
-
"""
|
|
108
|
-
# Using a sprite since it simplifies things, e.g. local global loading
|
|
109
|
-
_blockchain = sprite.Sprite()
|
|
110
|
-
|
|
111
|
-
for _block_data in _script_data:
|
|
112
|
-
_block = BpBlock.from_json(_block_data)
|
|
113
|
-
_block.sprite = _blockchain
|
|
114
|
-
_blockchain.blocks[_block_data["id"]] = _block
|
|
115
|
-
|
|
116
|
-
_blockchain.link_subcomponents()
|
|
117
|
-
return _blockchain
|