Skip to content

Commit 7241ea9

Browse files
committed
feat: placeholder project
note: the metadata is very flimsy to read. considering using a different strategy
1 parent 10ec49e commit 7241ea9

3 files changed

Lines changed: 93 additions & 12 deletions

File tree

scratchattach/editor/asset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ def from_json(data):
197197

198198
bitmap_resolution = data.get("bitmapResolution")
199199

200-
rotation_center_x = data["rotationCenterX"]
201-
rotation_center_y = data["rotationCenterY"]
200+
rotation_center_x = data.get("rotationCenterX", 0)
201+
rotation_center_y = data.get("rotationCenterY", 0)
202202
return Costume(_asset_load.name, _asset_load.file_name,
203203

204204
bitmap_resolution, rotation_center_x, rotation_center_y)

scratchattach/editor/sprite.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -283,18 +283,18 @@ def read_idcomponent(attr_name: str, cls: type[base.IDComponent]):
283283
_tempo, _video_state, _video_transparency, _text_to_speech_language = (None,) * 4
284284
_visible, _x, _y, _size, _direction, _draggable, _rotation_style = (None,) * 7
285285
if _is_stage:
286-
_tempo = data["tempo"]
287-
_video_state = data["videoState"]
288-
_video_transparency = data["videoTransparency"]
289-
_text_to_speech_language = data["textToSpeechLanguage"]
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")
290290
else:
291291
_visible = data["visible"]
292-
_x = data["x"]
293-
_y = data["y"]
294-
_size = data["size"]
295-
_direction = data["direction"]
296-
_draggable = data["draggable"]
297-
_rotation_style = data["rotationStyle"]
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")
298298

299299
return Sprite(_is_stage, _name, _current_costume, _layer_order, _volume, _broadcasts, _variables, _lists,
300300
_costumes,

scratchattach/site/placeholder.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Classes and methods for interacting with turbowarp placeholder (https://share.turbowarp.org/)
2+
import re
3+
import json
4+
import bs4
5+
6+
from dataclasses import dataclass
7+
from typing_extensions import Optional
8+
from bs4 import BeautifulSoup
9+
10+
from scratchattach.site import session
11+
from scratchattach.utils.requests import requests
12+
from scratchattach import editor
13+
14+
@dataclass
15+
class PlaceholderProject:
16+
id: str
17+
18+
title: Optional[str] = None
19+
description: Optional[str] = None
20+
md5exts_to_sha256: Optional[dict[str, str]] = None
21+
22+
_session: Optional[session.Session] = None
23+
24+
def get_json(self):
25+
with requests.no_error_handling():
26+
return requests.get(f"https://share.turbowarp.org/api/projects/{self.id}").json()
27+
28+
def update_by_html(self):
29+
"""
30+
Scrape JS to update the project. Hopefully this will not be necessary in the future, as it is very error-prone.
31+
"""
32+
33+
with requests.no_error_handling():
34+
resp = requests.get(f"https://share.turbowarp.org/projects/{self.id}")
35+
soup = BeautifulSoup(resp.text, "html.parser")
36+
37+
for script in soup.find_all("script"):
38+
if not isinstance(script, bs4.element.Tag):
39+
continue
40+
41+
if raw_data := re.search("const data = \\[.*\"data\":{metadata:{.*},md5extsToSha256:.*];", str(script.contents[0])):
42+
data = raw_data.group().removeprefix("const data = ").removesuffix(";")
43+
# this data is NOT json. Therefore, we can't just JSON.parse it.
44+
# it's actually native JavaScript, but we can extract the information in a really flimsy way using regex
45+
# This flimsy method would probably break with bad input, so maybe, instead, a request should be made to GarboMuffin.
46+
# If you want full JS support, use an automated browser.
47+
print(data)
48+
49+
# get title
50+
pf, sf = 'metadata:{title:"', '",description'
51+
if group := re.search(pf+'.*?'+sf, data).group():
52+
self.title = json.loads('"' + group.removeprefix(pf).removesuffix(sf) + '"')
53+
54+
# Get description. It is probably very easy to break this regex
55+
pf, sf = 'description:"', '"},'
56+
if group := re.search(pf+'.*?'+sf, data).group():
57+
self.description = json.loads('"' + group.removeprefix(pf).removesuffix(sf) + '"')
58+
59+
pf, sf = 'md5extsToSha256:{', '},'
60+
if group := re.search(pf+'.*?'+sf, data).group():
61+
self.md5exts_to_sha256 = json.loads('{' + group.removeprefix(pf).removesuffix(sf) + '}')
62+
63+
break
64+
65+
def get_project_body(self):
66+
data = self.get_json()
67+
body = editor.Project.from_json(data)
68+
69+
for asset in body.assets:
70+
print(asset.md5ext)
71+
72+
return body
73+
74+
75+
def get_placeholder_project(_id: str):
76+
return PlaceholderProject(_id)
77+
78+
if __name__ == '__main__':
79+
p = get_placeholder_project("44c35afc-fe00-49d8-afe7-d71f4430c121")
80+
p.update_by_html()
81+
print(p)

0 commit comments

Comments
 (0)