scratchattach 2.1.15b0__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.
- cli/__about__.py +1 -0
- cli/__init__.py +26 -0
- cli/cmd/__init__.py +4 -0
- cli/cmd/group.py +127 -0
- cli/cmd/login.py +60 -0
- cli/cmd/profile.py +7 -0
- cli/cmd/sessions.py +5 -0
- cli/context.py +142 -0
- cli/db.py +66 -0
- cli/namespace.py +14 -0
- {scratchattach/cloud → cloud}/_base.py +112 -87
- {scratchattach/cloud → cloud}/cloud.py +16 -16
- {scratchattach/editor → editor}/__init__.py +2 -1
- {scratchattach/editor → editor}/asset.py +26 -14
- {scratchattach/editor → editor}/backpack_json.py +3 -5
- {scratchattach/editor → editor}/base.py +2 -4
- {scratchattach/editor → editor}/block.py +27 -22
- {scratchattach/editor → editor}/blockshape.py +1 -1
- {scratchattach/editor → editor}/build_defaulting.py +2 -2
- editor/commons.py +145 -0
- {scratchattach/editor → editor}/field.py +1 -1
- {scratchattach/editor → editor}/inputs.py +6 -3
- {scratchattach/editor → editor}/meta.py +10 -7
- {scratchattach/editor → editor}/monitor.py +10 -8
- {scratchattach/editor → editor}/mutation.py +68 -11
- {scratchattach/editor → editor}/pallete.py +1 -3
- {scratchattach/editor → editor}/prim.py +4 -0
- {scratchattach/editor → editor}/project.py +118 -16
- {scratchattach/editor → editor}/sprite.py +25 -15
- {scratchattach/editor → editor}/vlb.py +2 -2
- {scratchattach/eventhandlers → eventhandlers}/_base.py +1 -0
- {scratchattach/eventhandlers → eventhandlers}/cloud_events.py +26 -6
- {scratchattach/eventhandlers → eventhandlers}/cloud_recorder.py +4 -4
- {scratchattach/eventhandlers → eventhandlers}/cloud_requests.py +139 -54
- {scratchattach/eventhandlers → eventhandlers}/cloud_server.py +6 -3
- {scratchattach/eventhandlers → eventhandlers}/cloud_storage.py +1 -2
- eventhandlers/filterbot.py +163 -0
- other/other_apis.py +598 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +7 -11
- scratchattach-3.0.0b1.dist-info/RECORD +79 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +1 -1
- scratchattach-3.0.0b1.dist-info/entry_points.txt +2 -0
- scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
- {scratchattach/site → site}/_base.py +32 -5
- site/activity.py +426 -0
- {scratchattach/site → site}/alert.py +4 -5
- {scratchattach/site → site}/backpack_asset.py +2 -1
- {scratchattach/site → site}/classroom.py +80 -73
- {scratchattach/site → site}/cloud_activity.py +43 -29
- {scratchattach/site → site}/comment.py +86 -100
- {scratchattach/site → site}/forum.py +8 -4
- site/placeholder.py +132 -0
- {scratchattach/site → site}/project.py +228 -122
- {scratchattach/site → site}/session.py +156 -71
- {scratchattach/site → site}/studio.py +139 -46
- site/typed_dicts.py +151 -0
- {scratchattach/site → site}/user.py +511 -215
- {scratchattach/utils → utils}/commons.py +12 -4
- {scratchattach/utils → utils}/encoder.py +7 -4
- {scratchattach/utils → utils}/enums.py +1 -0
- {scratchattach/utils → utils}/exceptions.py +36 -2
- utils/optional_async.py +154 -0
- utils/requests.py +306 -0
- scratchattach/__init__.py +0 -29
- scratchattach/editor/commons.py +0 -273
- scratchattach/eventhandlers/filterbot.py +0 -161
- scratchattach/other/other_apis.py +0 -284
- scratchattach/site/activity.py +0 -382
- scratchattach/utils/requests.py +0 -93
- scratchattach-2.1.15b0.dist-info/RECORD +0 -66
- scratchattach-2.1.15b0.dist-info/top_level.txt +0 -1
- {scratchattach/cloud → cloud}/__init__.py +0 -0
- {scratchattach/editor → editor}/code_translation/__init__.py +0 -0
- {scratchattach/editor → editor}/code_translation/parse.py +0 -0
- {scratchattach/editor → editor}/comment.py +0 -0
- {scratchattach/editor → editor}/extension.py +0 -0
- {scratchattach/editor → editor}/twconfig.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/__init__.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/combine.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/message_events.py +0 -0
- {scratchattach/other → other}/__init__.py +0 -0
- {scratchattach/other → other}/project_json_capabilities.py +0 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {scratchattach/site → site}/__init__.py +0 -0
- {scratchattach/site → site}/browser_cookie3_stub.py +0 -0
- {scratchattach/site → site}/browser_cookies.py +0 -0
- {scratchattach/utils → utils}/__init__.py +0 -0
|
@@ -2,20 +2,21 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import string
|
|
5
6
|
import warnings
|
|
6
7
|
from io import BytesIO, TextIOWrapper
|
|
7
8
|
from typing import Optional, Iterable, Generator, BinaryIO
|
|
9
|
+
from typing_extensions import deprecated
|
|
8
10
|
from zipfile import ZipFile
|
|
9
11
|
|
|
10
|
-
from . import base, meta, extension, monitor, sprite, asset, vlb, twconfig, comment, commons
|
|
12
|
+
from . import base, meta, extension, monitor, sprite, asset, vlb, twconfig, comment, commons, mutation
|
|
11
13
|
from scratchattach.site import session
|
|
12
|
-
from scratchattach.site.project import get_project
|
|
13
14
|
from scratchattach.utils import exceptions
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class Project(base.JSONExtractable):
|
|
17
18
|
"""
|
|
18
|
-
|
|
19
|
+
A Project (body). Represents the editor contents of a scratch project
|
|
19
20
|
"""
|
|
20
21
|
def __init__(self, _name: Optional[str] = None, _meta: Optional[meta.Meta] = None, _extensions: Iterable[extension.Extension] = (),
|
|
21
22
|
_monitors: Iterable[monitor.Monitor] = (), _sprites: Iterable[sprite.Sprite] = (), *,
|
|
@@ -31,7 +32,7 @@ class Project(base.JSONExtractable):
|
|
|
31
32
|
self.name = _name
|
|
32
33
|
|
|
33
34
|
self.meta = _meta
|
|
34
|
-
self.extensions = _extensions
|
|
35
|
+
self.extensions: list[extension.Extension] = list(_extensions)
|
|
35
36
|
self.monitors = list(_monitors)
|
|
36
37
|
self.sprites = list(_sprites)
|
|
37
38
|
|
|
@@ -69,11 +70,12 @@ class Project(base.JSONExtractable):
|
|
|
69
70
|
return _ret
|
|
70
71
|
|
|
71
72
|
@property
|
|
72
|
-
def stage(self) -> sprite.Sprite:
|
|
73
|
+
def stage(self) -> Optional[sprite.Sprite]:
|
|
73
74
|
for _sprite in self.sprites:
|
|
74
75
|
if _sprite.is_stage:
|
|
75
76
|
return _sprite
|
|
76
77
|
warnings.warn(f"Could not find stage for {self.name}")
|
|
78
|
+
return None
|
|
77
79
|
|
|
78
80
|
def to_json(self) -> dict:
|
|
79
81
|
_json = {
|
|
@@ -208,22 +210,25 @@ class Project(base.JSONExtractable):
|
|
|
208
210
|
return project
|
|
209
211
|
|
|
210
212
|
@staticmethod
|
|
213
|
+
@deprecated("Use get_project(id).body() instead")
|
|
211
214
|
def from_id(project_id: int, _name: Optional[str] = None):
|
|
212
|
-
|
|
213
|
-
|
|
215
|
+
raise Exception("This method is deprecated")
|
|
216
|
+
# _proj = get_project(project_id)
|
|
217
|
+
# data = json.loads(_proj.get_json())
|
|
214
218
|
|
|
215
|
-
if _name is None:
|
|
216
|
-
|
|
217
|
-
_name = str(_name)
|
|
219
|
+
# if _name is None:
|
|
220
|
+
# _name = _proj.title
|
|
221
|
+
# _name = str(_name)
|
|
218
222
|
|
|
219
|
-
_proj = Project.from_json(data)
|
|
220
|
-
_proj.name = _name
|
|
221
|
-
return _proj
|
|
223
|
+
# _proj = Project.from_json(data)
|
|
224
|
+
# _proj.name = _name
|
|
225
|
+
# return _proj
|
|
222
226
|
|
|
223
227
|
def find_vlb(self, value: str | None, by: str = "name",
|
|
224
|
-
multiple: bool = False) -> vlb.Variable | vlb.List | vlb.Broadcast | list[
|
|
225
|
-
vlb.Variable | vlb.List | vlb.Broadcast]:
|
|
226
|
-
|
|
228
|
+
multiple: bool = False) -> Optional[vlb.Variable | vlb.List | vlb.Broadcast | list[
|
|
229
|
+
vlb.Variable | vlb.List | vlb.Broadcast]]:
|
|
230
|
+
|
|
231
|
+
_ret: list[vlb.Variable | vlb.List | vlb.Broadcast] = []
|
|
227
232
|
for _sprite in self.sprites:
|
|
228
233
|
val = _sprite.find_vlb(value, by, multiple)
|
|
229
234
|
if multiple:
|
|
@@ -231,9 +236,12 @@ class Project(base.JSONExtractable):
|
|
|
231
236
|
else:
|
|
232
237
|
if val is not None:
|
|
233
238
|
return val
|
|
239
|
+
|
|
234
240
|
if multiple:
|
|
235
241
|
return _ret
|
|
236
242
|
|
|
243
|
+
return None
|
|
244
|
+
|
|
237
245
|
def find_sprite(self, value: str | None, by: str = "name",
|
|
238
246
|
multiple: bool = False) -> sprite.Sprite | list[sprite.Sprite]:
|
|
239
247
|
_ret = []
|
|
@@ -277,3 +285,97 @@ class Project(base.JSONExtractable):
|
|
|
277
285
|
_monitor.project = self
|
|
278
286
|
_monitor.reporter_id = self.new_id
|
|
279
287
|
self.monitors.append(_monitor)
|
|
288
|
+
return _monitor
|
|
289
|
+
|
|
290
|
+
def obfuscate(self, *, goto_origin: bool=True) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Randomly set all the variable names etc. Do not upload this project to the scratch website, as it is
|
|
293
|
+
against the community guidelines.
|
|
294
|
+
"""
|
|
295
|
+
# this code is an embarrassing mess. If certain features are added to sa.editor, then it could become a lot cleaner
|
|
296
|
+
chars = string.ascii_letters + string.digits + string.punctuation
|
|
297
|
+
|
|
298
|
+
def b10_to_cbase(b10: int | float):
|
|
299
|
+
ret = ''
|
|
300
|
+
new_base = len(chars)
|
|
301
|
+
while b10 >= 1:
|
|
302
|
+
ret = chars[int(b10 % new_base)] + ret
|
|
303
|
+
b10 /= new_base
|
|
304
|
+
|
|
305
|
+
return ret
|
|
306
|
+
|
|
307
|
+
used = 0
|
|
308
|
+
|
|
309
|
+
def rand():
|
|
310
|
+
nonlocal used
|
|
311
|
+
used += 1
|
|
312
|
+
|
|
313
|
+
return b10_to_cbase(used)
|
|
314
|
+
|
|
315
|
+
for _sprite in self.sprites:
|
|
316
|
+
procedure_mappings: dict[str, str] = {}
|
|
317
|
+
argument_mappings: dict[str, str] = {}
|
|
318
|
+
done_args: list[mutation.Argument] = []
|
|
319
|
+
|
|
320
|
+
for _variable in _sprite.variables:
|
|
321
|
+
_variable.name = rand()
|
|
322
|
+
for _list in _sprite.lists:
|
|
323
|
+
_list.name = rand()
|
|
324
|
+
# don't rename broadcasts as these can be dynamically called
|
|
325
|
+
|
|
326
|
+
def arg_get(name: str) -> str:
|
|
327
|
+
if name not in argument_mappings:
|
|
328
|
+
argument_mappings[name] = rand()
|
|
329
|
+
return argument_mappings[name]
|
|
330
|
+
|
|
331
|
+
for _block in _sprite.blocks.values():
|
|
332
|
+
if goto_origin:
|
|
333
|
+
_block.x, _block.y = 0, 0
|
|
334
|
+
|
|
335
|
+
# TODO: Add special handling for turbowarp debugger blocks
|
|
336
|
+
if _block.opcode in ("procedures_call", "procedures_prototype", "procedures_definition"):
|
|
337
|
+
if _block.mutation is None:
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
proccode = _block.mutation.proc_code
|
|
341
|
+
if proccode is None:
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
if proccode not in procedure_mappings:
|
|
345
|
+
parsed_ppc = _block.mutation.parsed_proc_code
|
|
346
|
+
|
|
347
|
+
if parsed_ppc is None:
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
new: list[str | mutation.ArgumentType] = []
|
|
351
|
+
for item in parsed_ppc:
|
|
352
|
+
if isinstance(item, str):
|
|
353
|
+
item = rand()
|
|
354
|
+
|
|
355
|
+
new.append(item)
|
|
356
|
+
|
|
357
|
+
new_proccode = mutation.construct_proccode(*new)
|
|
358
|
+
procedure_mappings[proccode] = new_proccode
|
|
359
|
+
|
|
360
|
+
_block.mutation.proc_code = procedure_mappings[proccode]
|
|
361
|
+
|
|
362
|
+
assert _block.mutation.arguments is not None
|
|
363
|
+
for arg in _block.mutation.arguments:
|
|
364
|
+
if arg in done_args:
|
|
365
|
+
continue
|
|
366
|
+
done_args.append(arg)
|
|
367
|
+
|
|
368
|
+
arg.name = arg_get(arg.name)
|
|
369
|
+
|
|
370
|
+
# print(_block, _block.mutation)
|
|
371
|
+
elif _block.opcode in ("argument_reporter_string_number", "argument_reporter_boolean"):
|
|
372
|
+
arg_name = _block.fields["VALUE"].value
|
|
373
|
+
assert isinstance(arg_name, str)
|
|
374
|
+
_block.fields["VALUE"].value = arg_get(arg_name)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# print(argument_mappings)
|
|
378
|
+
|
|
379
|
+
if goto_origin:
|
|
380
|
+
for _comment in _sprite.comments:
|
|
381
|
+
_comment.x, _comment.y = 0, 0
|
|
@@ -102,6 +102,11 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
102
102
|
self.link_prims()
|
|
103
103
|
self.link_blocks()
|
|
104
104
|
self.link_comments()
|
|
105
|
+
self.link_vlbs()
|
|
106
|
+
|
|
107
|
+
def link_vlbs(self):
|
|
108
|
+
for _vlb in self.vlbs:
|
|
109
|
+
_vlb.sprite = self
|
|
105
110
|
|
|
106
111
|
def link_prims(self):
|
|
107
112
|
"""
|
|
@@ -278,18 +283,18 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
278
283
|
_tempo, _video_state, _video_transparency, _text_to_speech_language = (None,) * 4
|
|
279
284
|
_visible, _x, _y, _size, _direction, _draggable, _rotation_style = (None,) * 7
|
|
280
285
|
if _is_stage:
|
|
281
|
-
_tempo = data
|
|
282
|
-
_video_state = data
|
|
283
|
-
_video_transparency = data
|
|
284
|
-
_text_to_speech_language = data
|
|
286
|
+
_tempo = data.get("tempo", 0)
|
|
287
|
+
_video_state = data.get("videoState", "off")
|
|
288
|
+
_video_transparency = data.get("videoTransparency", 0)
|
|
289
|
+
_text_to_speech_language = data.get("textToSpeechLanguage", "en")
|
|
285
290
|
else:
|
|
286
291
|
_visible = data["visible"]
|
|
287
|
-
_x = data
|
|
288
|
-
_y = data
|
|
289
|
-
_size = data
|
|
290
|
-
_direction = data
|
|
291
|
-
_draggable = data
|
|
292
|
-
_rotation_style = data
|
|
292
|
+
_x = data.get("x", 0)
|
|
293
|
+
_y = data.get("y", 0)
|
|
294
|
+
_size = data.get("size", 100)
|
|
295
|
+
_direction = data.get("direction", 90)
|
|
296
|
+
_draggable = data.get("draggable", False)
|
|
297
|
+
_rotation_style = data.get("rotationStyle", "all-around")
|
|
293
298
|
|
|
294
299
|
return Sprite(_is_stage, _name, _current_costume, _layer_order, _volume, _broadcasts, _variables, _lists,
|
|
295
300
|
_costumes,
|
|
@@ -340,7 +345,7 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
340
345
|
return _json
|
|
341
346
|
|
|
342
347
|
# Finding/getting from list/dict attributes
|
|
343
|
-
def find_asset(self, value: str, by: str = "name", multiple: bool = False, a_type: Optional[type]=None) -> asset.Asset | asset.Sound | asset.Costume | list[asset.Asset | asset.Sound | asset.Costume]:
|
|
348
|
+
def find_asset(self, value: str, by: str = "name", multiple: bool = False, a_type: Optional[type]=None) -> None | asset.Asset | asset.Sound | asset.Costume | list[asset.Asset | asset.Sound | asset.Costume]:
|
|
344
349
|
if a_type is None:
|
|
345
350
|
a_type = asset.Asset
|
|
346
351
|
|
|
@@ -361,8 +366,9 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
361
366
|
|
|
362
367
|
if multiple:
|
|
363
368
|
return _ret
|
|
369
|
+
return None
|
|
364
370
|
|
|
365
|
-
def find_variable(self, value: str, by: str = "name", multiple: bool = False) -> vlb.Variable | list[vlb.Variable]:
|
|
371
|
+
def find_variable(self, value: str, by: str = "name", multiple: bool = False) -> None | vlb.Variable | list[vlb.Variable]:
|
|
366
372
|
_ret = []
|
|
367
373
|
by = by.lower()
|
|
368
374
|
for _variable in self.variables + self._local_globals:
|
|
@@ -389,8 +395,9 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
389
395
|
|
|
390
396
|
if multiple:
|
|
391
397
|
return _ret
|
|
398
|
+
return None
|
|
392
399
|
|
|
393
|
-
def find_list(self, value: str, by: str = "name", multiple: bool = False) -> vlb.List | list[vlb.List]:
|
|
400
|
+
def find_list(self, value: str, by: str = "name", multiple: bool = False) -> None | vlb.List | list[vlb.List]:
|
|
394
401
|
_ret = []
|
|
395
402
|
by = by.lower()
|
|
396
403
|
for _list in self.lists + self._local_globals:
|
|
@@ -416,8 +423,9 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
416
423
|
|
|
417
424
|
if multiple:
|
|
418
425
|
return _ret
|
|
426
|
+
return None
|
|
419
427
|
|
|
420
|
-
def find_broadcast(self, value: str, by: str = "name", multiple: bool = False) -> vlb.Broadcast | list[
|
|
428
|
+
def find_broadcast(self, value: str, by: str = "name", multiple: bool = False) -> None | vlb.Broadcast | list[
|
|
421
429
|
vlb.Broadcast]:
|
|
422
430
|
_ret = []
|
|
423
431
|
by = by.lower()
|
|
@@ -444,6 +452,7 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
444
452
|
|
|
445
453
|
if multiple:
|
|
446
454
|
return _ret
|
|
455
|
+
return None
|
|
447
456
|
|
|
448
457
|
def find_vlb(self, value: str, by: str = "name",
|
|
449
458
|
multiple: bool = False) -> vlb.Variable | vlb.List | vlb.Broadcast | list[
|
|
@@ -461,7 +470,7 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
461
470
|
return _ret
|
|
462
471
|
return self.find_broadcast(value, by)
|
|
463
472
|
|
|
464
|
-
def find_block(self, value: str | Any, by: str, multiple: bool = False) -> block.Block | prim.Prim | list[
|
|
473
|
+
def find_block(self, value: str | Any, by: str, multiple: bool = False) -> None | block.Block | prim.Prim | list[
|
|
465
474
|
block.Block | prim.Prim]:
|
|
466
475
|
_ret = []
|
|
467
476
|
by = by.lower()
|
|
@@ -507,6 +516,7 @@ class Sprite(base.ProjectSubcomponent, base.JSONExtractable):
|
|
|
507
516
|
|
|
508
517
|
if multiple:
|
|
509
518
|
return _ret
|
|
519
|
+
return None
|
|
510
520
|
|
|
511
521
|
def export(self, fp: Optional[str] = None, *, export_as_zip: bool = True):
|
|
512
522
|
if fp is None:
|
|
@@ -73,7 +73,7 @@ class List(base.NamedIDComponent):
|
|
|
73
73
|
https://en.scratch-wiki.info/wiki/Scratch_File_Format#Targets:~:text=lists,as%20an%20array
|
|
74
74
|
"""
|
|
75
75
|
if _value is None:
|
|
76
|
-
_value = []
|
|
76
|
+
_value: list[str | int | float] = []
|
|
77
77
|
|
|
78
78
|
self.value = _value
|
|
79
79
|
super().__init__(_id, _name, _sprite)
|
|
@@ -91,7 +91,7 @@ class List(base.NamedIDComponent):
|
|
|
91
91
|
|
|
92
92
|
return List(_id, _name, _value)
|
|
93
93
|
|
|
94
|
-
def to_json(self) -> tuple[str,
|
|
94
|
+
def to_json(self) -> tuple[str, list[str | int | float]]:
|
|
95
95
|
"""
|
|
96
96
|
Returns List data as a tuple
|
|
97
97
|
"""
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"""CloudEvents class"""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import traceback
|
|
5
|
+
|
|
4
6
|
from scratchattach.cloud import _base
|
|
5
7
|
from ._base import BaseEventHandler
|
|
8
|
+
from scratchattach.utils import exceptions
|
|
6
9
|
from scratchattach.site import cloud_activity
|
|
7
10
|
import time
|
|
8
11
|
import json
|
|
@@ -18,6 +21,7 @@ class CloudEvents(BaseEventHandler):
|
|
|
18
21
|
self._session = cloud._session
|
|
19
22
|
self.source_stream = cloud.create_event_stream()
|
|
20
23
|
self.startup_time = time.time() * 1000
|
|
24
|
+
self.subsequent_reconnects = 0
|
|
21
25
|
|
|
22
26
|
def disconnect(self):
|
|
23
27
|
self.source_stream.close()
|
|
@@ -31,25 +35,34 @@ class CloudEvents(BaseEventHandler):
|
|
|
31
35
|
|
|
32
36
|
if self.running is False:
|
|
33
37
|
return
|
|
38
|
+
|
|
39
|
+
# TODO: refactor this method. It works, but is hard to read
|
|
34
40
|
while True:
|
|
35
41
|
try:
|
|
36
42
|
while True:
|
|
43
|
+
# print("Checking for more events")
|
|
37
44
|
for data in self.source_stream.read():
|
|
45
|
+
# print(f"Got event {data}")
|
|
46
|
+
self.subsequent_reconnects = 0
|
|
38
47
|
try:
|
|
39
48
|
_a = cloud_activity.CloudActivity(timestamp=time.time()*1000, _session=self._session, cloud=self.cloud)
|
|
40
49
|
if _a.timestamp < self.startup_time + 500: # catch the on_connect message sent by TurboWarp's (and sometimes Scratch's) cloud server
|
|
50
|
+
# print(f"Skipped as {_a.timestamp} < {self.startup_time + 500}")
|
|
41
51
|
continue
|
|
42
52
|
data["variable_name"] = data["name"]
|
|
43
53
|
data["name"] = data["variable_name"].replace("☁ ", "")
|
|
44
54
|
_a._update_from_dict(data)
|
|
55
|
+
# print(f"sending event {_a}")
|
|
45
56
|
self.call_event("on_"+_a.type, [_a])
|
|
46
57
|
except Exception as e:
|
|
58
|
+
print(f"Cloud events _updated ignored: {e} {traceback.format_exc()}")
|
|
47
59
|
pass
|
|
48
60
|
except Exception:
|
|
49
|
-
|
|
61
|
+
self.subsequent_reconnects += 1
|
|
50
62
|
time.sleep(0.1) # cooldown
|
|
51
63
|
|
|
52
|
-
|
|
64
|
+
if self.subsequent_reconnects >= 5:
|
|
65
|
+
print(f"Warning: {self.subsequent_reconnects} subsequent cloud disconnects. Cloud may be down, causing CloudEvents to not call events.")
|
|
53
66
|
self.call_event("on_reconnect", [])
|
|
54
67
|
|
|
55
68
|
class ManualCloudLogEvents:
|
|
@@ -63,22 +76,25 @@ class ManualCloudLogEvents:
|
|
|
63
76
|
self.source_cloud = cloud
|
|
64
77
|
self._session = cloud._session
|
|
65
78
|
self.last_timestamp = 0
|
|
66
|
-
|
|
79
|
+
self.subsequent_failed_log_fetches = 0
|
|
80
|
+
|
|
67
81
|
def update(self) -> Iterator[tuple[str, list[cloud_activity.CloudActivity]]]:
|
|
68
82
|
"""
|
|
69
83
|
Update once and yield all packets
|
|
70
84
|
"""
|
|
71
85
|
try:
|
|
72
86
|
data = self.source_cloud.logs(limit=25)
|
|
87
|
+
self.subsequent_failed_log_fetches = 0
|
|
73
88
|
for _a in data[::-1]:
|
|
74
89
|
if _a.timestamp <= self.last_timestamp:
|
|
75
90
|
continue
|
|
76
91
|
self.last_timestamp = _a.timestamp
|
|
77
92
|
yield ("on_"+_a.type, [_a])
|
|
78
93
|
except Exception:
|
|
79
|
-
|
|
94
|
+
self.subsequent_failed_log_fetches += 1
|
|
95
|
+
if self.subsequent_failed_log_fetches == 20:
|
|
96
|
+
print("Warning: 20 subsequent clouddata log fetches failed. Scrach's cloud logs may be down, causing CloudLogEvents to not call events.")
|
|
80
97
|
|
|
81
|
-
|
|
82
98
|
class CloudLogEvents(BaseEventHandler):
|
|
83
99
|
"""
|
|
84
100
|
Class that calls events on cloud updates that are received from a clouddata log.
|
|
@@ -95,7 +111,11 @@ class CloudLogEvents(BaseEventHandler):
|
|
|
95
111
|
self.manual_cloud_log_events = ManualCloudLogEvents(cloud)
|
|
96
112
|
|
|
97
113
|
def _updater(self):
|
|
98
|
-
|
|
114
|
+
try:
|
|
115
|
+
logs = self.source_cloud.logs(limit=25)
|
|
116
|
+
except exceptions.FetchError:
|
|
117
|
+
logs = []
|
|
118
|
+
|
|
99
119
|
self.last_timestamp = 0
|
|
100
120
|
if len(logs) != 0:
|
|
101
121
|
self.last_timestamp = logs[0].timestamp
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""CloudRecorder class (used by ScratchCloud, TwCloud and other classes inheriting from BaseCloud to deliver cloud var values)"""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
from typing import Optional, Any
|
|
5
|
+
|
|
4
6
|
from .cloud_events import CloudEvents
|
|
5
|
-
from typing import Optional
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class CloudRecorder(CloudEvents):
|
|
9
|
-
def __init__(self, cloud, *, initial_values: Optional[dict] = None):
|
|
10
|
-
|
|
11
|
-
initial_values = {}
|
|
10
|
+
def __init__(self, cloud, *, initial_values: Optional[dict[str, Any]] = None):
|
|
11
|
+
initial_values = initial_values or {}
|
|
12
12
|
|
|
13
13
|
super().__init__(cloud)
|
|
14
14
|
self.cloud_values = initial_values
|