scratchattach 2.1.9__py3-none-any.whl → 2.1.10a1__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 (59) hide show
  1. scratchattach/__init__.py +28 -25
  2. scratchattach/cloud/__init__.py +2 -0
  3. scratchattach/cloud/_base.py +454 -282
  4. scratchattach/cloud/cloud.py +171 -168
  5. scratchattach/editor/__init__.py +21 -0
  6. scratchattach/editor/asset.py +199 -0
  7. scratchattach/editor/backpack_json.py +117 -0
  8. scratchattach/editor/base.py +142 -0
  9. scratchattach/editor/block.py +507 -0
  10. scratchattach/editor/blockshape.py +353 -0
  11. scratchattach/editor/build_defaulting.py +47 -0
  12. scratchattach/editor/comment.py +74 -0
  13. scratchattach/editor/commons.py +243 -0
  14. scratchattach/editor/extension.py +43 -0
  15. scratchattach/editor/field.py +90 -0
  16. scratchattach/editor/inputs.py +132 -0
  17. scratchattach/editor/meta.py +106 -0
  18. scratchattach/editor/monitor.py +175 -0
  19. scratchattach/editor/mutation.py +317 -0
  20. scratchattach/editor/pallete.py +91 -0
  21. scratchattach/editor/prim.py +170 -0
  22. scratchattach/editor/project.py +273 -0
  23. scratchattach/editor/sbuild.py +2837 -0
  24. scratchattach/editor/sprite.py +586 -0
  25. scratchattach/editor/twconfig.py +113 -0
  26. scratchattach/editor/vlb.py +134 -0
  27. scratchattach/eventhandlers/_base.py +99 -92
  28. scratchattach/eventhandlers/cloud_events.py +110 -103
  29. scratchattach/eventhandlers/cloud_recorder.py +26 -21
  30. scratchattach/eventhandlers/cloud_requests.py +460 -452
  31. scratchattach/eventhandlers/cloud_server.py +246 -244
  32. scratchattach/eventhandlers/cloud_storage.py +135 -134
  33. scratchattach/eventhandlers/combine.py +29 -27
  34. scratchattach/eventhandlers/filterbot.py +160 -159
  35. scratchattach/eventhandlers/message_events.py +41 -40
  36. scratchattach/other/other_apis.py +284 -212
  37. scratchattach/other/project_json_capabilities.py +475 -546
  38. scratchattach/site/_base.py +64 -46
  39. scratchattach/site/activity.py +414 -122
  40. scratchattach/site/backpack_asset.py +118 -84
  41. scratchattach/site/classroom.py +430 -142
  42. scratchattach/site/cloud_activity.py +107 -103
  43. scratchattach/site/comment.py +220 -190
  44. scratchattach/site/forum.py +400 -399
  45. scratchattach/site/project.py +806 -787
  46. scratchattach/site/session.py +1134 -867
  47. scratchattach/site/studio.py +611 -609
  48. scratchattach/site/user.py +835 -837
  49. scratchattach/utils/commons.py +243 -148
  50. scratchattach/utils/encoder.py +157 -156
  51. scratchattach/utils/enums.py +197 -190
  52. scratchattach/utils/exceptions.py +233 -206
  53. scratchattach/utils/requests.py +67 -59
  54. {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/METADATA +155 -146
  55. scratchattach-2.1.10a1.dist-info/RECORD +62 -0
  56. {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/WHEEL +1 -1
  57. {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info/licenses}/LICENSE +21 -21
  58. scratchattach-2.1.9.dist-info/RECORD +0 -40
  59. {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/top_level.txt +0 -0
@@ -1,168 +1,171 @@
1
- """v2 ready: ScratchCloud, TwCloud and CustomCloud classes"""
2
-
3
- from ._base import BaseCloud
4
- from typing import Type
5
- from ..utils.requests import Requests as requests
6
- from ..utils import exceptions, commons
7
- from ..site import cloud_activity
8
-
9
- class ScratchCloud(BaseCloud):
10
-
11
- def __init__(self, *, project_id, _session=None):
12
- super().__init__()
13
-
14
- self.project_id = project_id
15
-
16
- # Configure this object's attributes specifically for being used with Scratch's cloud:
17
- self.cloud_host = "wss://clouddata.scratch.mit.edu"
18
- self.length_limit = 256
19
- self._session = _session
20
- if self._session is not None:
21
- self.username = self._session.username
22
- self.cookie = "scratchsessionsid=" + self._session.id + ";"
23
- self.origin = "https://scratch.mit.edu"
24
-
25
- def connect(self):
26
- self._assert_auth() # Connecting to Scratch's cloud websocket requires a login to the Scratch website
27
- super().connect()
28
-
29
- def set_var(self, variable, value):
30
- self._assert_auth() # Setting a cloud var requires a login to the Scratch website
31
- super().set_var(variable, value)
32
-
33
- def set_vars(self, var_value_dict, *, intelligent_waits=True):
34
- self._assert_auth()
35
- super().set_vars(var_value_dict, intelligent_waits=intelligent_waits)
36
-
37
- def logs(self, *, filter_by_var_named=None, limit=100, offset=0):
38
- """
39
- Gets the data from Scratch's clouddata logs.
40
-
41
- Keyword Arguments:
42
- filter_by_var_named (str or None): If you only want to get data for one cloud variable, set this argument to its name.
43
- limit (int): Max. amount of returned activity.
44
- offset (int): Offset of the first activity in the returned list.
45
- 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.
46
- """
47
- try:
48
- data = requests.get(f"https://clouddata.scratch.mit.edu/logs?projectid={self.project_id}&limit={limit}&offset={offset}", timeout=10).json()
49
- if filter_by_var_named is not None:
50
- data = list(filter(lambda k: k["name"] == "☁ "+filter_by_var_named, data))
51
- for x in data:
52
- x["cloud"] = self
53
- x["name"] = x["name"][2:]
54
- return commons.parse_object_list(data, cloud_activity.CloudActivity, self._session, "name")
55
- except Exception as e:
56
- return exceptions.FetchError(str(e))
57
-
58
- def get_var(self, var, *, use_logs=False):
59
- if self._session is None or use_logs:
60
- logs = self.logs(limit=100)
61
- filtered = list(filter(lambda k: k.name == "☁ "+var, logs))
62
- if len(filtered) == 0:
63
- return None
64
- return filtered[0].value
65
- else:
66
- if self.recorder is None:
67
- initial_values = self.get_all_vars(use_logs=True)
68
- return super().get_var(var, recorder_initial_values=initial_values)
69
- else:
70
- return super().get_var(var)
71
-
72
- def get_all_vars(self, *, use_logs=False):
73
- if self._session is None or use_logs:
74
- logs = self.logs(limit=100)
75
- logs.reverse()
76
- clouddata = {}
77
- for activity in logs:
78
- clouddata[activity.name.replace("☁ ", "")] = activity.value
79
- return clouddata
80
- else:
81
- if self.recorder is None:
82
- initial_values = self.get_all_vars(use_logs=True)
83
- return super().get_all_vars(recorder_initial_values=initial_values)
84
- else:
85
- return super().get_all_vars()
86
-
87
- def events(self, *, use_logs=False):
88
- if self._session is None or use_logs:
89
- from ..eventhandlers.cloud_events import CloudLogEvents
90
- return CloudLogEvents(self)
91
- else:
92
- return super().events()
93
-
94
- class TwCloud(BaseCloud):
95
-
96
- def __init__(self, *, project_id, cloud_host="wss://clouddata.turbowarp.org", purpose="", contact=""):
97
- super().__init__()
98
-
99
- self.project_id = project_id
100
-
101
- # Configure this object's attributes specifically for being used with TurboWarp's cloud:
102
- self.cloud_host = cloud_host
103
- self.ws_shortterm_ratelimit = 0 # TurboWarp doesn't enforce a wait time between cloud variable sets
104
- self.ws_longterm_ratelimit = 0
105
- self.length_limit = 100000 # TurboWarp doesn't enforce a cloud variable length
106
- purpose_string = ""
107
- if purpose != "" or contact != "":
108
- purpose_string = f" (Purpose:{purpose}; Contact:{contact})"
109
- self.header = {"User-Agent":f"scratchattach/2.0.0{purpose_string}"}
110
-
111
- class CustomCloud(BaseCloud):
112
-
113
- def __init__(self, *, project_id, cloud_host, **kwargs):
114
- super().__init__()
115
-
116
- self.project_id = project_id
117
- self.cloud_host = cloud_host
118
-
119
- # Configure this object's attributes specifically for the cloud that the developer wants to connect to:
120
- # -> For this purpose, all additional keyword arguments (kwargs) will be set as attributes of the CustomCloud object
121
- # This allows the maximum amount of attribute customization
122
- # See the docstring for the cloud._base.BaseCloud class to find out what attributes can be set / specified as keyword args
123
- self.__dict__.update(kwargs)
124
-
125
- # If even more customization is needed, the developer can create a class inheriting from cloud._base.BaseCloud to override functions like .set_var etc.
126
-
127
-
128
- def get_cloud(project_id, *, CloudClass:Type[BaseCloud]=ScratchCloud) -> Type[BaseCloud]:
129
- """
130
- Connects to a cloud (by default Scratch's cloud) as logged out user.
131
-
132
- Warning:
133
- Since this method doesn't connect a login / session to the returned object, setting Scratch cloud variables won't be possible with it.
134
-
135
- To set Scratch cloud variables, use `scratchattach.Session.connect_scratch_cloud` instead.
136
-
137
- Args:
138
- project_id:
139
-
140
- Keyword arguments:
141
- CloudClass: The class that the returned object should be of. By default this class is scratchattach.cloud.ScratchCloud.
142
-
143
- Returns:
144
- Type[scratchattach._base.BaseCloud]: An object representing the cloud of a project. Can be of any class inheriting from BaseCloud.
145
- """
146
- print("Warning: To set Scratch cloud variables, use session.connect_cloud instead of get_cloud")
147
- return CloudClass(project_id=project_id)
148
-
149
- def get_scratch_cloud(project_id):
150
- """
151
- Warning:
152
- Since this method doesn't connect a login / session to the returned object, setting Scratch cloud variables won't be possible with it.
153
-
154
- To set Scratch cloud variables, use `scratchattach.Session.connect_scratch_cloud` instead.
155
-
156
-
157
- Returns:
158
- scratchattach.cloud.ScratchCloud: An object representing the Scratch cloud of a project.
159
- """
160
- print("Warning: To set Scratch cloud variables, use session.connect_scratch_cloud instead of get_scratch_cloud")
161
- return ScratchCloud(project_id=project_id)
162
-
163
- def get_tw_cloud(project_id, *, purpose="", contact="", cloud_host="wss://clouddata.turbowarp.org"):
164
- """
165
- Returns:
166
- scratchattach.cloud.TwCloud: An object representing the TurboWarp cloud of a project.
167
- """
168
- return TwCloud(project_id=project_id, purpose=purpose, contact=contact, cloud_host=cloud_host)
1
+ """v2 ready: ScratchCloud, TwCloud and CustomCloud classes"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._base import BaseCloud
6
+ from typing import Type
7
+ from ..utils.requests import Requests as requests
8
+ from ..utils import exceptions, commons
9
+ from ..site import cloud_activity
10
+
11
+
12
+ class ScratchCloud(BaseCloud):
13
+ def __init__(self, *, project_id, _session=None):
14
+ super().__init__()
15
+
16
+ self.project_id = project_id
17
+
18
+ # Configure this object's attributes specifically for being used with Scratch's cloud:
19
+ self.cloud_host = "wss://clouddata.scratch.mit.edu"
20
+ self.length_limit = 256
21
+ self._session = _session
22
+ if self._session is not None:
23
+ self.username = self._session.username
24
+ self.cookie = "scratchsessionsid=" + self._session.id + ";"
25
+ self.origin = "https://scratch.mit.edu"
26
+
27
+ def connect(self):
28
+ self._assert_auth() # Connecting to Scratch's cloud websocket requires a login to the Scratch website
29
+ super().connect()
30
+
31
+ def set_var(self, variable, value):
32
+ self._assert_auth() # Setting a cloud var requires a login to the Scratch website
33
+ super().set_var(variable, value)
34
+
35
+ def set_vars(self, var_value_dict, *, intelligent_waits=True):
36
+ self._assert_auth()
37
+ super().set_vars(var_value_dict, intelligent_waits=intelligent_waits)
38
+
39
+ def logs(self, *, filter_by_var_named=None, limit=100, offset=0) -> list[cloud_activity.CloudActivity]:
40
+ """
41
+ Gets the data from Scratch's clouddata logs.
42
+
43
+ Keyword Arguments:
44
+ filter_by_var_named (str or None): If you only want to get data for one cloud variable, set this argument to its name.
45
+ limit (int): Max. amount of returned activity.
46
+ offset (int): Offset of the first activity in the returned list.
47
+ 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.
48
+ """
49
+ try:
50
+ data = requests.get(f"https://clouddata.scratch.mit.edu/logs?projectid={self.project_id}&limit={limit}&offset={offset}", timeout=10).json()
51
+ if filter_by_var_named is not None:
52
+ filter_by_var_named = filter_by_var_named.removeprefix("☁ ")
53
+ data = list(filter(lambda k: k["name"] == ""+filter_by_var_named, data))
54
+ for x in data:
55
+ x["cloud"] = self
56
+ return commons.parse_object_list(data, cloud_activity.CloudActivity, self._session, "name")
57
+ except Exception as e:
58
+ raise exceptions.FetchError(str(e))
59
+
60
+ def get_var(self, var, *, use_logs=False):
61
+ var = var.removeprefix("☁ ")
62
+ if self._session is None or use_logs:
63
+ filtered = self.logs(limit=100, filter_by_var_named="☁ "+var)
64
+ if len(filtered) == 0:
65
+ return None
66
+ return filtered[0].value
67
+ else:
68
+ if self.recorder is None:
69
+ initial_values = self.get_all_vars(use_logs=True)
70
+ return super().get_var("☁ "+var, recorder_initial_values=initial_values)
71
+ else:
72
+ return super().get_var("☁ "+var)
73
+
74
+ def get_all_vars(self, *, use_logs=False):
75
+ if self._session is None or use_logs:
76
+ logs = self.logs(limit=100)
77
+ logs.reverse()
78
+ clouddata = {}
79
+ for activity in logs:
80
+ clouddata[activity.name] = activity.value
81
+ return clouddata
82
+ else:
83
+ if self.recorder is None:
84
+ initial_values = self.get_all_vars(use_logs=True)
85
+ return super().get_all_vars(recorder_initial_values=initial_values)
86
+ else:
87
+ return super().get_all_vars()
88
+
89
+ def events(self, *, use_logs=False):
90
+ if self._session is None or use_logs:
91
+ from ..eventhandlers.cloud_events import CloudLogEvents
92
+ return CloudLogEvents(self)
93
+ else:
94
+ return super().events()
95
+
96
+
97
+ class TwCloud(BaseCloud):
98
+ def __init__(self, *, project_id, cloud_host="wss://clouddata.turbowarp.org", purpose="", contact="",
99
+ _session=None):
100
+ super().__init__()
101
+
102
+ self.project_id = project_id
103
+
104
+ # Configure this object's attributes specifically for being used with TurboWarp's cloud:
105
+ self.cloud_host = cloud_host
106
+ self.ws_shortterm_ratelimit = 0 # TurboWarp doesn't enforce a wait time between cloud variable sets
107
+ self.ws_longterm_ratelimit = 0
108
+ self.length_limit = 100000 # TurboWarp doesn't enforce a cloud variable length
109
+ purpose_string = ""
110
+ if purpose != "" or contact != "":
111
+ purpose_string = f" (Purpose:{purpose}; Contact:{contact})"
112
+ self.header = {"User-Agent":f"scratchattach/2.0.0{purpose_string}"}
113
+
114
+ class CustomCloud(BaseCloud):
115
+
116
+ def __init__(self, *, project_id, cloud_host, **kwargs):
117
+ super().__init__()
118
+
119
+ self.project_id = project_id
120
+ self.cloud_host = cloud_host
121
+
122
+ # Configure this object's attributes specifically for the cloud that the developer wants to connect to:
123
+ # -> For this purpose, all additional keyword arguments (kwargs) will be set as attributes of the CustomCloud object
124
+ # This allows the maximum amount of attribute customization
125
+ # See the docstring for the cloud._base.BaseCloud class to find out what attributes can be set / specified as keyword args
126
+ self.__dict__.update(kwargs)
127
+
128
+ # If even more customization is needed, the developer can create a class inheriting from cloud._base.BaseCloud to override functions like .set_var etc.
129
+
130
+
131
+ def get_cloud(project_id, *, CloudClass:Type[BaseCloud]=ScratchCloud) -> BaseCloud:
132
+ """
133
+ Connects to a cloud (by default Scratch's cloud) as logged out user.
134
+
135
+ Warning:
136
+ Since this method doesn't connect a login / session to the returned object, setting Scratch cloud variables won't be possible with it.
137
+
138
+ To set Scratch cloud variables, use `scratchattach.site.session.Session.connect_scratch_cloud` instead.
139
+
140
+ Args:
141
+ project_id:
142
+
143
+ Keyword arguments:
144
+ CloudClass: The class that the returned object should be of. By default this class is scratchattach.cloud.ScratchCloud.
145
+
146
+ Returns:
147
+ Type[scratchattach.cloud._base.BaseCloud]: An object representing the cloud of a project. Can be of any class inheriting from BaseCloud.
148
+ """
149
+ print("Warning: To set Scratch cloud variables, use session.connect_cloud instead of get_cloud")
150
+ return CloudClass(project_id=project_id)
151
+
152
+ def get_scratch_cloud(project_id):
153
+ """
154
+ Warning:
155
+ Since this method doesn't connect a login / session to the returned object, setting Scratch cloud variables won't be possible with it.
156
+
157
+ To set Scratch cloud variables, use `scratchattach.Session.connect_scratch_cloud` instead.
158
+
159
+
160
+ Returns:
161
+ scratchattach.cloud.ScratchCloud: An object representing the Scratch cloud of a project.
162
+ """
163
+ print("Warning: To set Scratch cloud variables, use session.connect_scratch_cloud instead of get_scratch_cloud")
164
+ return ScratchCloud(project_id=project_id)
165
+
166
+ def get_tw_cloud(project_id, *, purpose="", contact="", cloud_host="wss://clouddata.turbowarp.org"):
167
+ """
168
+ Returns:
169
+ scratchattach.cloud.TwCloud: An object representing the TurboWarp cloud of a project.
170
+ """
171
+ return TwCloud(project_id=project_id, purpose=purpose, contact=contact, cloud_host=cloud_host)
@@ -0,0 +1,21 @@
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
@@ -0,0 +1,199 @@
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
+ filename: str
14
+ _data: bytes = field(repr=False, default=None)
15
+ _md5: str = field(repr=False, default=None)
16
+
17
+ @property
18
+ def data(self):
19
+ if self._data is None:
20
+ # Download and cache
21
+ rq = requests.get(f"https://assets.scratch.mit.edu/internalapi/asset/{self.filename}/get/")
22
+ if rq.status_code != 200:
23
+ raise ValueError(f"Can't download asset {self.filename}\nIs not uploaded to scratch! Response: {rq.text}")
24
+
25
+ self._data = rq.content
26
+
27
+ return self._data
28
+
29
+ @property
30
+ def md5(self):
31
+ if self._md5 is None:
32
+ self._md5 = md5(self.data).hexdigest()
33
+
34
+ return self._md5
35
+
36
+
37
+ class Asset(base.SpriteSubComponent):
38
+ def __init__(self,
39
+ name: str = "costume1",
40
+ file_name: str = "b7853f557e4426412e64bb3da6531a99.svg",
41
+ _sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT):
42
+ """
43
+ Represents a generic asset. Can be a sound or an image.
44
+ https://en.scratch-wiki.info/wiki/Scratch_File_Format#Assets
45
+ """
46
+ try:
47
+ asset_id, data_format = file_name.split('.')
48
+ except ValueError:
49
+ raise ValueError(f"Invalid file name: {file_name}, # of '.' in {file_name} ({file_name.count('.')}) != 2; "
50
+ f"(too many/few values to unpack)")
51
+ self.name = name
52
+
53
+ self.id = asset_id
54
+ self.data_format = data_format
55
+
56
+ super().__init__(_sprite)
57
+
58
+ def __repr__(self):
59
+ return f"Asset<{self.name!r}>"
60
+
61
+ @property
62
+ def folder(self):
63
+ return commons.get_folder_name(self.name)
64
+
65
+ @property
66
+ def name_nfldr(self):
67
+ return commons.get_name_nofldr(self.name)
68
+
69
+ @property
70
+ def file_name(self):
71
+ return f"{self.id}.{self.data_format}"
72
+
73
+ @property
74
+ def md5ext(self):
75
+ return self.file_name
76
+
77
+ @property
78
+ def parent(self):
79
+ if self.project is None:
80
+ return self.sprite
81
+ else:
82
+ return self.project
83
+
84
+ @property
85
+ def asset_file(self) -> AssetFile:
86
+ for asset_file in self.parent.asset_data:
87
+ if asset_file.filename == self.file_name:
88
+ return asset_file
89
+
90
+ # No pre-existing asset file object; create one and add it to the project
91
+ asset_file = AssetFile(self.file_name)
92
+ self.project.asset_data.append(asset_file)
93
+ return asset_file
94
+
95
+ @staticmethod
96
+ def from_json(data: dict):
97
+ _name = data.get("name")
98
+ _file_name = data.get("md5ext")
99
+ if _file_name is None:
100
+ if "dataFormat" in data and "assetId" in data:
101
+ _id = data["assetId"]
102
+ _data_format = data["dataFormat"]
103
+ _file_name = f"{_id}.{_data_format}"
104
+
105
+ return Asset(_name, _file_name)
106
+
107
+ def to_json(self) -> dict:
108
+ return {
109
+ "name": self.name,
110
+
111
+ "assetId": self.id,
112
+ "md5ext": self.file_name,
113
+ "dataFormat": self.data_format,
114
+ }
115
+
116
+ """
117
+ @staticmethod
118
+ def from_file(fp: str, name: str = None):
119
+ image_types = ("png", "jpg", "jpeg", "svg")
120
+ sound_types = ("wav", "mp3")
121
+
122
+ # Should save data as well so it can be uploaded to scratch if required (add to project asset data)
123
+ ...
124
+ """
125
+
126
+
127
+ class Costume(Asset):
128
+ def __init__(self,
129
+ name: str = "Cat",
130
+ file_name: str = "b7853f557e4426412e64bb3da6531a99.svg",
131
+
132
+ bitmap_resolution=None,
133
+ rotation_center_x: int | float = 48,
134
+ rotation_center_y: int | float = 50,
135
+ _sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT):
136
+ """
137
+ A costume. An asset with additional properties
138
+ https://en.scratch-wiki.info/wiki/Scratch_File_Format#Costumes
139
+ """
140
+ super().__init__(name, file_name, _sprite)
141
+
142
+ self.bitmap_resolution = bitmap_resolution
143
+ self.rotation_center_x = rotation_center_x
144
+ self.rotation_center_y = rotation_center_y
145
+
146
+ @staticmethod
147
+ def from_json(data):
148
+ _asset_load = Asset.from_json(data)
149
+
150
+ bitmap_resolution = data.get("bitmapResolution")
151
+
152
+ rotation_center_x = data["rotationCenterX"]
153
+ rotation_center_y = data["rotationCenterY"]
154
+ return Costume(_asset_load.name, _asset_load.file_name,
155
+
156
+ bitmap_resolution, rotation_center_x, rotation_center_y)
157
+
158
+ def to_json(self) -> dict:
159
+ _json = super().to_json()
160
+ _json.update({
161
+ "bitmapResolution": self.bitmap_resolution,
162
+ "rotationCenterX": self.rotation_center_x,
163
+ "rotationCenterY": self.rotation_center_y
164
+ })
165
+ return _json
166
+
167
+
168
+ class Sound(Asset):
169
+ def __init__(self,
170
+ name: str = "pop",
171
+ file_name: str = "83a9787d4cb6f3b7632b4ddfebf74367.wav",
172
+
173
+ rate: Optional[int] = None,
174
+ sample_count: Optional[int] = None,
175
+ _sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT):
176
+ """
177
+ A sound. An asset with additional properties
178
+ https://en.scratch-wiki.info/wiki/Scratch_File_Format#Sounds
179
+ """
180
+ super().__init__(name, file_name, _sprite)
181
+
182
+ self.rate = rate
183
+ self.sample_count = sample_count
184
+
185
+ @staticmethod
186
+ def from_json(data):
187
+ _asset_load = Asset.from_json(data)
188
+
189
+ rate = data.get("rate")
190
+ sample_count = data.get("sampleCount")
191
+ return Sound(_asset_load.name, _asset_load.file_name, rate, sample_count)
192
+
193
+ def to_json(self) -> dict:
194
+ _json = super().to_json()
195
+ commons.noneless_update(_json, {
196
+ "rate": self.rate,
197
+ "sampleCount": self.sample_count
198
+ })
199
+ return _json