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.
- scratchattach/__init__.py +28 -25
- scratchattach/cloud/__init__.py +2 -0
- scratchattach/cloud/_base.py +454 -282
- scratchattach/cloud/cloud.py +171 -168
- scratchattach/editor/__init__.py +21 -0
- scratchattach/editor/asset.py +199 -0
- scratchattach/editor/backpack_json.py +117 -0
- scratchattach/editor/base.py +142 -0
- scratchattach/editor/block.py +507 -0
- scratchattach/editor/blockshape.py +353 -0
- scratchattach/editor/build_defaulting.py +47 -0
- scratchattach/editor/comment.py +74 -0
- scratchattach/editor/commons.py +243 -0
- scratchattach/editor/extension.py +43 -0
- scratchattach/editor/field.py +90 -0
- scratchattach/editor/inputs.py +132 -0
- scratchattach/editor/meta.py +106 -0
- scratchattach/editor/monitor.py +175 -0
- scratchattach/editor/mutation.py +317 -0
- scratchattach/editor/pallete.py +91 -0
- scratchattach/editor/prim.py +170 -0
- scratchattach/editor/project.py +273 -0
- scratchattach/editor/sbuild.py +2837 -0
- scratchattach/editor/sprite.py +586 -0
- scratchattach/editor/twconfig.py +113 -0
- scratchattach/editor/vlb.py +134 -0
- scratchattach/eventhandlers/_base.py +99 -92
- scratchattach/eventhandlers/cloud_events.py +110 -103
- scratchattach/eventhandlers/cloud_recorder.py +26 -21
- scratchattach/eventhandlers/cloud_requests.py +460 -452
- scratchattach/eventhandlers/cloud_server.py +246 -244
- scratchattach/eventhandlers/cloud_storage.py +135 -134
- scratchattach/eventhandlers/combine.py +29 -27
- scratchattach/eventhandlers/filterbot.py +160 -159
- scratchattach/eventhandlers/message_events.py +41 -40
- scratchattach/other/other_apis.py +284 -212
- scratchattach/other/project_json_capabilities.py +475 -546
- scratchattach/site/_base.py +64 -46
- scratchattach/site/activity.py +414 -122
- scratchattach/site/backpack_asset.py +118 -84
- scratchattach/site/classroom.py +430 -142
- scratchattach/site/cloud_activity.py +107 -103
- scratchattach/site/comment.py +220 -190
- scratchattach/site/forum.py +400 -399
- scratchattach/site/project.py +806 -787
- scratchattach/site/session.py +1134 -867
- scratchattach/site/studio.py +611 -609
- scratchattach/site/user.py +835 -837
- scratchattach/utils/commons.py +243 -148
- scratchattach/utils/encoder.py +157 -156
- scratchattach/utils/enums.py +197 -190
- scratchattach/utils/exceptions.py +233 -206
- scratchattach/utils/requests.py +67 -59
- {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/METADATA +155 -146
- scratchattach-2.1.10a1.dist-info/RECORD +62 -0
- {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/WHEEL +1 -1
- {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info/licenses}/LICENSE +21 -21
- scratchattach-2.1.9.dist-info/RECORD +0 -40
- {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
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
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Editor base classes
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
import json
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from io import TextIOWrapper
|
|
11
|
+
from typing import Optional, Any, TYPE_CHECKING, BinaryIO
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from . import project, sprite, block, mutation, asset
|
|
15
|
+
|
|
16
|
+
from . import build_defaulting
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Base(ABC):
|
|
20
|
+
def dcopy(self):
|
|
21
|
+
"""
|
|
22
|
+
:return: A **deep** copy of self
|
|
23
|
+
"""
|
|
24
|
+
return copy.deepcopy(self)
|
|
25
|
+
|
|
26
|
+
def copy(self):
|
|
27
|
+
"""
|
|
28
|
+
:return: A **shallow** copy of self
|
|
29
|
+
"""
|
|
30
|
+
return copy.copy(self)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class JSONSerializable(Base, ABC):
|
|
34
|
+
@staticmethod
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def from_json(data: dict | list | Any):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def to_json(self) -> dict | list | Any:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def save_json(self, name: str = ''):
|
|
44
|
+
data = self.to_json()
|
|
45
|
+
with open(f"{self.__class__.__name__.lower()}{name}.json", "w") as f:
|
|
46
|
+
json.dump(data, f)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class JSONExtractable(JSONSerializable, ABC):
|
|
50
|
+
@staticmethod
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def load_json(data: str | bytes | TextIOWrapper | BinaryIO, load_assets: bool = True, _name: Optional[str] = None) -> tuple[
|
|
53
|
+
str, list[asset.AssetFile], str]:
|
|
54
|
+
"""
|
|
55
|
+
Automatically extracts the JSON data as a string, as well as providing auto naming
|
|
56
|
+
:param data: Either a string of JSON, sb3 file as bytes or as a file object
|
|
57
|
+
:param load_assets: Whether to extract assets as well (if applicable)
|
|
58
|
+
:param _name: Any provided name (will automatically find one otherwise)
|
|
59
|
+
:return: tuple of the name, asset data & json as a string
|
|
60
|
+
"""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ProjectSubcomponent(JSONSerializable, ABC):
|
|
65
|
+
def __init__(self, _project: Optional[project.Project] = None):
|
|
66
|
+
self.project = _project
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SpriteSubComponent(JSONSerializable, ABC):
|
|
70
|
+
def __init__(self, _sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT):
|
|
71
|
+
if _sprite is build_defaulting.SPRITE_DEFAULT:
|
|
72
|
+
_sprite = build_defaulting.current_sprite()
|
|
73
|
+
|
|
74
|
+
self.sprite = _sprite
|
|
75
|
+
|
|
76
|
+
# @property
|
|
77
|
+
# def sprite(self):
|
|
78
|
+
# if self._sprite is None:
|
|
79
|
+
# print("ok, ", build_defaulting.current_sprite())
|
|
80
|
+
# return build_defaulting.current_sprite()
|
|
81
|
+
# else:
|
|
82
|
+
# return self._sprite
|
|
83
|
+
|
|
84
|
+
# @sprite.setter
|
|
85
|
+
# def sprite(self, value):
|
|
86
|
+
# self._sprite = value
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def project(self) -> project.Project:
|
|
90
|
+
return self.sprite.project
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class IDComponent(SpriteSubComponent, ABC):
|
|
94
|
+
def __init__(self, _id: str, _sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT):
|
|
95
|
+
self.id = _id
|
|
96
|
+
super().__init__(_sprite)
|
|
97
|
+
|
|
98
|
+
def __repr__(self):
|
|
99
|
+
return f"<{self.__class__.__name__}: {self.id}>"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class NamedIDComponent(IDComponent, ABC):
|
|
103
|
+
"""
|
|
104
|
+
Base class for Variables, Lists and Broadcasts (Name + ID + sprite)
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, _id: str, name: str, _sprite: sprite.Sprite = build_defaulting.SPRITE_DEFAULT):
|
|
108
|
+
self.name = name
|
|
109
|
+
super().__init__(_id, _sprite)
|
|
110
|
+
|
|
111
|
+
def __repr__(self):
|
|
112
|
+
return f"<{self.__class__.__name__} '{self.name}'>"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class BlockSubComponent(JSONSerializable, ABC):
|
|
116
|
+
def __init__(self, _block: Optional[block.Block] = None):
|
|
117
|
+
self.block = _block
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def sprite(self) -> sprite.Sprite:
|
|
121
|
+
return self.block.sprite
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def project(self) -> project.Project:
|
|
125
|
+
return self.sprite.project
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class MutationSubComponent(JSONSerializable, ABC):
|
|
129
|
+
def __init__(self, _mutation: Optional[mutation.Mutation] = None):
|
|
130
|
+
self.mutation = _mutation
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def block(self) -> block.Block:
|
|
134
|
+
return self.mutation.block
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def sprite(self) -> sprite.Sprite:
|
|
138
|
+
return self.block.sprite
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def project(self) -> project.Project:
|
|
142
|
+
return self.sprite.project
|