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.
Files changed (69) hide show
  1. scratchattach/__init__.py +14 -6
  2. scratchattach/__main__.py +93 -0
  3. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/METADATA +7 -11
  4. scratchattach-3.0.0b0.dist-info/RECORD +8 -0
  5. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/WHEEL +1 -1
  6. scratchattach-3.0.0b0.dist-info/entry_points.txt +2 -0
  7. scratchattach/cloud/__init__.py +0 -2
  8. scratchattach/cloud/_base.py +0 -458
  9. scratchattach/cloud/cloud.py +0 -183
  10. scratchattach/editor/__init__.py +0 -21
  11. scratchattach/editor/asset.py +0 -253
  12. scratchattach/editor/backpack_json.py +0 -117
  13. scratchattach/editor/base.py +0 -193
  14. scratchattach/editor/block.py +0 -579
  15. scratchattach/editor/blockshape.py +0 -357
  16. scratchattach/editor/build_defaulting.py +0 -51
  17. scratchattach/editor/code_translation/__init__.py +0 -0
  18. scratchattach/editor/code_translation/parse.py +0 -177
  19. scratchattach/editor/comment.py +0 -80
  20. scratchattach/editor/commons.py +0 -273
  21. scratchattach/editor/extension.py +0 -50
  22. scratchattach/editor/field.py +0 -99
  23. scratchattach/editor/inputs.py +0 -135
  24. scratchattach/editor/meta.py +0 -114
  25. scratchattach/editor/monitor.py +0 -183
  26. scratchattach/editor/mutation.py +0 -324
  27. scratchattach/editor/pallete.py +0 -90
  28. scratchattach/editor/prim.py +0 -170
  29. scratchattach/editor/project.py +0 -279
  30. scratchattach/editor/sprite.py +0 -599
  31. scratchattach/editor/twconfig.py +0 -114
  32. scratchattach/editor/vlb.py +0 -134
  33. scratchattach/eventhandlers/__init__.py +0 -0
  34. scratchattach/eventhandlers/_base.py +0 -100
  35. scratchattach/eventhandlers/cloud_events.py +0 -110
  36. scratchattach/eventhandlers/cloud_recorder.py +0 -26
  37. scratchattach/eventhandlers/cloud_requests.py +0 -459
  38. scratchattach/eventhandlers/cloud_server.py +0 -246
  39. scratchattach/eventhandlers/cloud_storage.py +0 -136
  40. scratchattach/eventhandlers/combine.py +0 -30
  41. scratchattach/eventhandlers/filterbot.py +0 -161
  42. scratchattach/eventhandlers/message_events.py +0 -42
  43. scratchattach/other/__init__.py +0 -0
  44. scratchattach/other/other_apis.py +0 -284
  45. scratchattach/other/project_json_capabilities.py +0 -475
  46. scratchattach/site/__init__.py +0 -0
  47. scratchattach/site/_base.py +0 -66
  48. scratchattach/site/activity.py +0 -382
  49. scratchattach/site/alert.py +0 -227
  50. scratchattach/site/backpack_asset.py +0 -118
  51. scratchattach/site/browser_cookie3_stub.py +0 -17
  52. scratchattach/site/browser_cookies.py +0 -61
  53. scratchattach/site/classroom.py +0 -447
  54. scratchattach/site/cloud_activity.py +0 -107
  55. scratchattach/site/comment.py +0 -242
  56. scratchattach/site/forum.py +0 -432
  57. scratchattach/site/project.py +0 -826
  58. scratchattach/site/session.py +0 -1238
  59. scratchattach/site/studio.py +0 -611
  60. scratchattach/site/user.py +0 -956
  61. scratchattach/utils/__init__.py +0 -0
  62. scratchattach/utils/commons.py +0 -255
  63. scratchattach/utils/encoder.py +0 -158
  64. scratchattach/utils/enums.py +0 -236
  65. scratchattach/utils/exceptions.py +0 -243
  66. scratchattach/utils/requests.py +0 -93
  67. scratchattach-2.1.15b0.dist-info/RECORD +0 -66
  68. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/licenses/LICENSE +0 -0
  69. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b0.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -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
@@ -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