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.
Files changed (87) hide show
  1. cli/__about__.py +1 -0
  2. cli/__init__.py +26 -0
  3. cli/cmd/__init__.py +4 -0
  4. cli/cmd/group.py +127 -0
  5. cli/cmd/login.py +60 -0
  6. cli/cmd/profile.py +7 -0
  7. cli/cmd/sessions.py +5 -0
  8. cli/context.py +142 -0
  9. cli/db.py +66 -0
  10. cli/namespace.py +14 -0
  11. {scratchattach/cloud → cloud}/_base.py +112 -87
  12. {scratchattach/cloud → cloud}/cloud.py +16 -16
  13. {scratchattach/editor → editor}/__init__.py +2 -1
  14. {scratchattach/editor → editor}/asset.py +26 -14
  15. {scratchattach/editor → editor}/backpack_json.py +3 -5
  16. {scratchattach/editor → editor}/base.py +2 -4
  17. {scratchattach/editor → editor}/block.py +27 -22
  18. {scratchattach/editor → editor}/blockshape.py +1 -1
  19. {scratchattach/editor → editor}/build_defaulting.py +2 -2
  20. editor/commons.py +145 -0
  21. {scratchattach/editor → editor}/field.py +1 -1
  22. {scratchattach/editor → editor}/inputs.py +6 -3
  23. {scratchattach/editor → editor}/meta.py +10 -7
  24. {scratchattach/editor → editor}/monitor.py +10 -8
  25. {scratchattach/editor → editor}/mutation.py +68 -11
  26. {scratchattach/editor → editor}/pallete.py +1 -3
  27. {scratchattach/editor → editor}/prim.py +4 -0
  28. {scratchattach/editor → editor}/project.py +118 -16
  29. {scratchattach/editor → editor}/sprite.py +25 -15
  30. {scratchattach/editor → editor}/vlb.py +2 -2
  31. {scratchattach/eventhandlers → eventhandlers}/_base.py +1 -0
  32. {scratchattach/eventhandlers → eventhandlers}/cloud_events.py +26 -6
  33. {scratchattach/eventhandlers → eventhandlers}/cloud_recorder.py +4 -4
  34. {scratchattach/eventhandlers → eventhandlers}/cloud_requests.py +139 -54
  35. {scratchattach/eventhandlers → eventhandlers}/cloud_server.py +6 -3
  36. {scratchattach/eventhandlers → eventhandlers}/cloud_storage.py +1 -2
  37. eventhandlers/filterbot.py +163 -0
  38. other/other_apis.py +598 -0
  39. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +7 -11
  40. scratchattach-3.0.0b1.dist-info/RECORD +79 -0
  41. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +1 -1
  42. scratchattach-3.0.0b1.dist-info/entry_points.txt +2 -0
  43. scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
  44. {scratchattach/site → site}/_base.py +32 -5
  45. site/activity.py +426 -0
  46. {scratchattach/site → site}/alert.py +4 -5
  47. {scratchattach/site → site}/backpack_asset.py +2 -1
  48. {scratchattach/site → site}/classroom.py +80 -73
  49. {scratchattach/site → site}/cloud_activity.py +43 -29
  50. {scratchattach/site → site}/comment.py +86 -100
  51. {scratchattach/site → site}/forum.py +8 -4
  52. site/placeholder.py +132 -0
  53. {scratchattach/site → site}/project.py +228 -122
  54. {scratchattach/site → site}/session.py +156 -71
  55. {scratchattach/site → site}/studio.py +139 -46
  56. site/typed_dicts.py +151 -0
  57. {scratchattach/site → site}/user.py +511 -215
  58. {scratchattach/utils → utils}/commons.py +12 -4
  59. {scratchattach/utils → utils}/encoder.py +7 -4
  60. {scratchattach/utils → utils}/enums.py +1 -0
  61. {scratchattach/utils → utils}/exceptions.py +36 -2
  62. utils/optional_async.py +154 -0
  63. utils/requests.py +306 -0
  64. scratchattach/__init__.py +0 -29
  65. scratchattach/editor/commons.py +0 -273
  66. scratchattach/eventhandlers/filterbot.py +0 -161
  67. scratchattach/other/other_apis.py +0 -284
  68. scratchattach/site/activity.py +0 -382
  69. scratchattach/utils/requests.py +0 -93
  70. scratchattach-2.1.15b0.dist-info/RECORD +0 -66
  71. scratchattach-2.1.15b0.dist-info/top_level.txt +0 -1
  72. {scratchattach/cloud → cloud}/__init__.py +0 -0
  73. {scratchattach/editor → editor}/code_translation/__init__.py +0 -0
  74. {scratchattach/editor → editor}/code_translation/parse.py +0 -0
  75. {scratchattach/editor → editor}/comment.py +0 -0
  76. {scratchattach/editor → editor}/extension.py +0 -0
  77. {scratchattach/editor → editor}/twconfig.py +0 -0
  78. {scratchattach/eventhandlers → eventhandlers}/__init__.py +0 -0
  79. {scratchattach/eventhandlers → eventhandlers}/combine.py +0 -0
  80. {scratchattach/eventhandlers → eventhandlers}/message_events.py +0 -0
  81. {scratchattach/other → other}/__init__.py +0 -0
  82. {scratchattach/other → other}/project_json_capabilities.py +0 -0
  83. {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
  84. {scratchattach/site → site}/__init__.py +0 -0
  85. {scratchattach/site → site}/browser_cookie3_stub.py +0 -0
  86. {scratchattach/site → site}/browser_cookies.py +0 -0
  87. {scratchattach/utils → utils}/__init__.py +0 -0
@@ -1,6 +1,7 @@
1
1
  """ForumTopic and ForumPost classes"""
2
2
  from __future__ import annotations
3
3
 
4
+ import warnings
4
5
  from dataclasses import dataclass, field
5
6
  from typing import Optional, Any
6
7
  from urllib.parse import urlparse, parse_qs
@@ -40,12 +41,15 @@ class ForumTopic(BaseSiteComponent):
40
41
  '''
41
42
  id: int
42
43
  title: str
43
- category_name: str
44
- last_updated: str
44
+ category_name: Optional[str] = None
45
+ last_updated: Optional[str] = None
45
46
  _session: Optional[module_session.Session] = field(default=None)
46
47
  reply_count: Optional[int] = field(default=None)
47
48
  view_count: Optional[int] = field(default=None)
48
49
 
50
+ def __str__(self):
51
+ return f"-F {self.title} ({self.id})"
52
+
49
53
  def __post_init__(self):
50
54
  # Info on how the .update method has to fetch the data:
51
55
  self.update_function = requests.get
@@ -114,7 +118,7 @@ class ForumTopic(BaseSiteComponent):
114
118
  list<scratchattach.forum.ForumPost>: A list containing the posts from the specified page of the forum topic
115
119
  """
116
120
  if order != "oldest":
117
- print("Warning: All post orders except for 'oldest' are deprecated and no longer work") # For backwards compatibility
121
+ warnings.warn("Warning: All post orders except for 'oldest' are deprecated and no longer work") # For backwards compatibility
118
122
 
119
123
  posts = []
120
124
 
@@ -143,7 +147,7 @@ class ForumTopic(BaseSiteComponent):
143
147
  link = breadcrumb_ul.find_all('a')[1] # Get the right anchor tag
144
148
  topic_category = link.text.strip() # Extract and strip text content
145
149
  except Exception as e:
146
- print(f"Warning: Couldn't scrape topic category for topic {self.id} - {e}")
150
+ warnings.warn(f"Warning: Couldn't scrape topic category for topic {self.id} - {e}")
147
151
  topic_category = ""
148
152
 
149
153
  # get corresponding posts:
site/placeholder.py ADDED
@@ -0,0 +1,132 @@
1
+ # Classes and methods for interacting with turbowarp placeholder (https://share.turbowarp.org/)
2
+ import re
3
+ import bs4
4
+ import json
5
+ import io
6
+
7
+ from dataclasses import dataclass
8
+ from typing_extensions import Optional
9
+ from bs4 import BeautifulSoup
10
+
11
+ from scratchattach.site import session
12
+ from scratchattach.site.typed_dicts import PlaceholderProjectDataDict
13
+ from scratchattach.utils.requests import requests
14
+ from scratchattach import editor
15
+ from scratchattach.utils import commons
16
+
17
+
18
+ @dataclass
19
+ class PlaceholderProject:
20
+ id: str
21
+
22
+ title: Optional[str] = None
23
+ description: Optional[str] = None
24
+ md5exts_to_sha256: Optional[dict[str, str]] = None
25
+ admin_ownership_token: Optional[str] = None # guessing it's a str
26
+
27
+ _session: Optional[session.Session] = None
28
+
29
+ def get_json(self):
30
+ with requests.no_error_handling():
31
+ return requests.get(f"https://share.turbowarp.org/api/projects/{self.id}").json()
32
+
33
+ def update_by_html(self) -> None:
34
+ """
35
+ Scrape JS to update the project. Requires hjson
36
+ """
37
+ try:
38
+ import hjson # type: ignore
39
+ except ImportError as e:
40
+ raise ImportError("Please use pip install hjson if you want to use placeholder projects!") from e
41
+
42
+ with requests.no_error_handling():
43
+ resp = requests.get(f"https://share.turbowarp.org/projects/{self.id}")
44
+ soup = BeautifulSoup(resp.text, "html.parser")
45
+
46
+ for script in soup.find_all("script"):
47
+ if not isinstance(script, bs4.element.Tag):
48
+ continue
49
+
50
+ if raw_data := re.search("const data = \\[.*\"data\":{metadata:{.*},md5extsToSha256:.*];",
51
+ str(script.contents[0])):
52
+ data = raw_data.group().removeprefix("const data = ").removesuffix(";")
53
+ # this data is NOT json. Therefore, we can't just JSON.parse it.
54
+ # it's actually native JavaScript, but we can extract the information in a relatively stable way using hjson
55
+ # maybe, instead, a request should be made to GarboMuffin.
56
+ data = hjson.loads(data)
57
+ # i am unsure if the other data here is of any use. It may be artifacts coming from svelte
58
+ parsed_data: PlaceholderProjectDataDict = data[1]["data"]
59
+
60
+ self.title = parsed_data["metadata"]["title"]
61
+ self.description = parsed_data["metadata"]["description"]
62
+ self.md5exts_to_sha256 = dict(parsed_data["md5extsToSha256"])
63
+ self.admin_ownership_token = parsed_data["adminOwnershipToken"]
64
+
65
+ break
66
+
67
+ def get_project_body(self):
68
+ self.update_by_html()
69
+
70
+ data = self.get_json()
71
+ body = editor.Project.from_json(data)
72
+ body.name = self.title
73
+
74
+ for asset in body.assets:
75
+ table = self.md5exts_to_sha256
76
+ assert table is not None # this should never happen
77
+ data = get_asset(table[asset.md5ext])
78
+ asset.asset_file.data = data
79
+
80
+ return body
81
+
82
+
83
+ def get_asset(sha256: str) -> bytes:
84
+ with requests.no_error_handling():
85
+ return requests.get(f"https://share.turbowarp.org/api/assets/{sha256}").content
86
+
87
+ def get_placeholder_project(_id: str):
88
+ return PlaceholderProject(_id)
89
+
90
+ def create_placeholder_project(title: str, data: bytes):
91
+ body = editor.Project.from_sb3(data)
92
+
93
+ asset_information: dict[str, dict[str, str | int]] = {}
94
+ for asset in body.assets:
95
+ print(asset)
96
+ print(asset.asset_file.sha256)
97
+ asset_information[asset.md5ext] = {
98
+ "sha256": asset.asset_file.sha256,
99
+ "size": len(asset.asset_file.data)
100
+ }
101
+
102
+ print(f"{asset_information = }")
103
+ print(f"{body.name = }")
104
+ with requests.no_error_handling():
105
+ resp = requests.post("https://share.turbowarp.org/api/projects/new", data={
106
+ "title": title,
107
+ "assetInformation": asset_information,
108
+ }, files={
109
+ "project": ("blob", data, 'application/octet-stream'),
110
+ }, headers={
111
+ 'accept': '*/*',
112
+ 'accept-language': 'en-GB,en-US;q=0.9,en;q=0.8',
113
+ # 'content-type': 'multipart/form-data; boundary=----WebKitFormBoundaryYzpNqB5A2GEr99Vd',
114
+ 'dnt': '1',
115
+ 'origin': 'https://share.turbowarp.org',
116
+ 'priority': 'u=1, i',
117
+ 'referer': 'https://share.turbowarp.org/',
118
+ 'sec-ch-ua': '"Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
119
+ 'sec-ch-ua-mobile': '?0',
120
+ 'sec-ch-ua-platform': '"Windows"',
121
+ 'sec-fetch-dest': 'empty',
122
+ 'sec-fetch-mode': 'cors',
123
+ 'sec-fetch-site': 'same-origin',
124
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
125
+ })
126
+
127
+ print(resp, resp.content)
128
+
129
+ if __name__ == '__main__':
130
+ p = get_placeholder_project("44c35afc-fe00-49d8-afe7-d71f4430c121")
131
+ pb = p.get_project_body()
132
+ pb.export("test plac.sb3")