scratchattach 2.1.8__py3-none-any.whl → 2.1.10a0__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.8.dist-info → scratchattach-2.1.10a0.dist-info}/LICENSE +21 -21
  55. {scratchattach-2.1.8.dist-info → scratchattach-2.1.10a0.dist-info}/METADATA +154 -146
  56. scratchattach-2.1.10a0.dist-info/RECORD +62 -0
  57. {scratchattach-2.1.8.dist-info → scratchattach-2.1.10a0.dist-info}/WHEEL +1 -1
  58. scratchattach-2.1.8.dist-info/RECORD +0 -40
  59. {scratchattach-2.1.8.dist-info → scratchattach-2.1.10a0.dist-info}/top_level.txt +0 -0
@@ -1,160 +1,161 @@
1
- """FilterBot class"""
2
-
3
- from .message_events import MessageEvents
4
- import time
5
-
6
- class HardFilter:
7
-
8
- def __init__(self, filter_name="UntitledFilter", *, equals=None, contains=None, author_name=None, project_id=None, profile=None, case_sensitive=False):
9
- self.equals=equals
10
- self.contains=contains
11
- self.author_name=author_name
12
- self.project_id=project_id
13
- self.profile=profile
14
- self.case_sensitive=case_sensitive
15
- self.filter_name = filter_name
16
-
17
- def apply(self, content, author_name, source_id):
18
- if not self.case_sensitive:
19
- content = content.lower()
20
- if self.equals is not None:
21
- if self.case_sensitive:
22
- if self.equals == content:
23
- return True
24
- else:
25
- if self.equals.lower() == content:
26
- return True
27
- if self.contains is not None:
28
- if self.case_sensitive:
29
- if self.contains.lower() in content:
30
- return True
31
- else:
32
- if self.contains in content:
33
- return True
34
- if self.author_name == author_name:
35
- return True
36
- if self.project_id == source_id or self.profile == source_id:
37
- return True
38
- return False
39
-
40
- class SoftFilter(HardFilter):
41
- def __init__(self, score:float, filter_name="UntitledFilter", *, equals=None, contains=None, author_name=None, project_id=None, profile=None, case_sensitive=False):
42
- self.score = score
43
- super().__init__(filter_name, equals=equals, contains=contains, author_name=author_name, project_id=project_id, profile=profile, case_sensitive=case_sensitive)
44
-
45
- class SpamFilter(HardFilter):
46
- def __init__(self, filter_name="UntitledFilter", *, equals=None, contains=None, author_name=None, project_id=None, profile=None, case_sensitive=False):
47
- self.memory = []
48
- super().__init__(filter_name, equals=equals, contains=contains, author_name=author_name, project_id=project_id, profile=profile, case_sensitive=case_sensitive)
49
-
50
- def apply(self, content, author_name, source_id):
51
- applies = super().apply(content, author_name, source_id)
52
- if not applies:
53
- return False
54
- self.memory.insert(0, {"content":content, "time":time.time()})
55
- print(content, self.memory)
56
- for comment in list(self.memory)[1:]:
57
- if comment["time"] < time.time() -300:
58
- self.memory.remove(comment)
59
- if comment["content"].lower() == content.lower():
60
- return True
61
- return False
62
-
63
- class Filterbot(MessageEvents):
64
-
65
- # The Filterbot class is built upon MessageEvents, similar to how CloudEvents is built upon CloudEvents
66
-
67
- def __init__(self, user, *, log_deletions=True):
68
- super().__init__(user)
69
- self.hard_filters = []
70
- self.soft_filters = []
71
- self.spam_filters = []
72
- self.log_deletions = log_deletions
73
- self.event(self.on_message, thread=False)
74
- self.update_interval = 2
75
-
76
- def add_filter(self, filter_obj):
77
- if isinstance(filter_obj, SoftFilter):
78
- self.soft_filters.append(filter_obj)
79
- elif isinstance(filter_obj, SpamFilter): # careful: SpamFilter is also HardFilter due to inheritence
80
- self.spam_filters.append(filter_obj)
81
- elif isinstance(filter_obj, HardFilter):
82
- self.hard_filters.append(filter_obj)
83
-
84
- def add_f4f_filter(self):
85
- self.add_filter(HardFilter("(f4f_filter) 'f4f'", contains="f4f"))
86
- self.add_filter(HardFilter("(f4f_filter) 'follow me'", contains="follow me"))
87
- self.add_filter(HardFilter("(f4f_filter) 'follow @'", contains="follow @"))
88
- self.add_filter(HardFilter("(f4f_filter) f 4 f'", contains="f 4 f"))
89
- self.add_filter(HardFilter("(f4f_filter) 'follow for'", contains="follow for"))
90
-
91
- def add_ads_filter(self):
92
- self.add_filter(SoftFilter(1, "(ads_filter) links", contains="scratch.mit.edu/projects/"))
93
- self.add_filter(SoftFilter(-1, "(ads_filter) feedback", contains="feedback"))
94
- self.add_filter(HardFilter("(ads_filter) 'check out my'", contains="check out my"))
95
- self.add_filter(HardFilter("(ads_filter) 'play my'", contains="play my"))
96
- self.add_filter(SoftFilter(1, "(ads_filter) 'advertis'", contains="advertis"))
97
-
98
- def add_spam_filter(self):
99
- self.add_filter(SpamFilter("(spam_filter)", contains=""))
100
-
101
- def add_genalpha_nonsense_filter(self):
102
- self.add_filter(HardFilter("(genalpha_nonsene_filter) 'skibidi'", contains="skibidi"))
103
- self.add_filter(HardFilter("[genalpha_nonsene_filter) 'rizzler'", contains="rizzler"))
104
- self.add_filter(HardFilter("(genalpha_nonsene_filter) 'fanum tax'", contains="fanum tax"))
105
-
106
- def on_message(self, message):
107
- if message.type == "addcomment":
108
- delete = False
109
- content = message.comment_fragment
110
-
111
- if message.comment_type == 0: # project comment
112
- source_id = message.comment_obj_id
113
- if self.user._session.connect_project(message.comment_obj_id).author_name != self.user.username:
114
- return # no permission to delete
115
- if message.comment_type == 1: # profile comment
116
- source_id = message.comment_obj_title
117
- if message.comment_obj_title != self.user.username:
118
- return # no permission to delete
119
- if message.comment_type == 2: # studio comment
120
- return # studio comments aren't handled
121
-
122
- # Apply hard filters
123
- for hard_filter in self.hard_filters:
124
- if hard_filter.apply(content, message.actor_username, source_id):
125
- delete=True
126
- if self.log_deletions:
127
- print(f"DETECTED: #{message.comment_id} violates hard filter: {hard_filter.filter_name}")
128
- break
129
-
130
- # Apply spam filters
131
- if delete is False:
132
- for spam_filter in self.spam_filters:
133
- if spam_filter.apply(content, message.actor_username, source_id):
134
- delete=True
135
- if self.log_deletions:
136
- print(f"DETECTED: #{message.comment_id} violates spam filter: {spam_filter.filter_name}")
137
- break
138
-
139
- # Apply soft filters
140
- if delete is False:
141
- score = 0
142
- violated_filers = []
143
- for soft_filter in self.soft_filters:
144
- if soft_filter.apply(content, message.actor_username, source_id):
145
- score += soft_filter.score
146
- violated_filers.append(soft_filter.name)
147
- if score >= 1:
148
- print(f"DETECTED: #{message.comment_id} violates too many soft filters: {violated_filers}")
149
- delete = True
150
-
151
- if delete is True:
152
- try:
153
- message.target().delete()
154
- if self.log_deletions:
155
- print(f"DELETED: #{message.comment_id} by f{message.actor_username}: '{content}'")
156
- except Exception as e:
157
- if self.log_deletions:
158
- print(f"DELETION FAILED: #{message.comment_id} by f{message.actor_username}: '{content}'")
159
-
1
+ """FilterBot class"""
2
+ from __future__ import annotations
3
+
4
+ from .message_events import MessageEvents
5
+ import time
6
+
7
+ class HardFilter:
8
+
9
+ def __init__(self, filter_name="UntitledFilter", *, equals=None, contains=None, author_name=None, project_id=None, profile=None, case_sensitive=False):
10
+ self.equals=equals
11
+ self.contains=contains
12
+ self.author_name=author_name
13
+ self.project_id=project_id
14
+ self.profile=profile
15
+ self.case_sensitive=case_sensitive
16
+ self.filter_name = filter_name
17
+
18
+ def apply(self, content, author_name, source_id):
19
+ if not self.case_sensitive:
20
+ content = content.lower()
21
+ if self.equals is not None:
22
+ if self.case_sensitive:
23
+ if self.equals == content:
24
+ return True
25
+ else:
26
+ if self.equals.lower() == content:
27
+ return True
28
+ if self.contains is not None:
29
+ if self.case_sensitive:
30
+ if self.contains.lower() in content:
31
+ return True
32
+ else:
33
+ if self.contains in content:
34
+ return True
35
+ if self.author_name == author_name:
36
+ return True
37
+ if self.project_id == source_id or self.profile == source_id:
38
+ return True
39
+ return False
40
+
41
+ class SoftFilter(HardFilter):
42
+ def __init__(self, score:float, filter_name="UntitledFilter", *, equals=None, contains=None, author_name=None, project_id=None, profile=None, case_sensitive=False):
43
+ self.score = score
44
+ super().__init__(filter_name, equals=equals, contains=contains, author_name=author_name, project_id=project_id, profile=profile, case_sensitive=case_sensitive)
45
+
46
+ class SpamFilter(HardFilter):
47
+ def __init__(self, filter_name="UntitledFilter", *, equals=None, contains=None, author_name=None, project_id=None, profile=None, case_sensitive=False):
48
+ self.memory = []
49
+ super().__init__(filter_name, equals=equals, contains=contains, author_name=author_name, project_id=project_id, profile=profile, case_sensitive=case_sensitive)
50
+
51
+ def apply(self, content, author_name, source_id):
52
+ applies = super().apply(content, author_name, source_id)
53
+ if not applies:
54
+ return False
55
+ self.memory.insert(0, {"content":content, "time":time.time()})
56
+ print(content, self.memory)
57
+ for comment in list(self.memory)[1:]:
58
+ if comment["time"] < time.time() -300:
59
+ self.memory.remove(comment)
60
+ if comment["content"].lower() == content.lower():
61
+ return True
62
+ return False
63
+
64
+ class Filterbot(MessageEvents):
65
+
66
+ # The Filterbot class is built upon MessageEvents, similar to how CloudEvents is built upon CloudEvents
67
+
68
+ def __init__(self, user, *, log_deletions=True):
69
+ super().__init__(user)
70
+ self.hard_filters = []
71
+ self.soft_filters = []
72
+ self.spam_filters = []
73
+ self.log_deletions = log_deletions
74
+ self.event(self.on_message, thread=False)
75
+ self.update_interval = 2
76
+
77
+ def add_filter(self, filter_obj):
78
+ if isinstance(filter_obj, SoftFilter):
79
+ self.soft_filters.append(filter_obj)
80
+ elif isinstance(filter_obj, SpamFilter): # careful: SpamFilter is also HardFilter due to inheritence
81
+ self.spam_filters.append(filter_obj)
82
+ elif isinstance(filter_obj, HardFilter):
83
+ self.hard_filters.append(filter_obj)
84
+
85
+ def add_f4f_filter(self):
86
+ self.add_filter(HardFilter("(f4f_filter) 'f4f'", contains="f4f"))
87
+ self.add_filter(HardFilter("(f4f_filter) 'follow me'", contains="follow me"))
88
+ self.add_filter(HardFilter("(f4f_filter) 'follow @'", contains="follow @"))
89
+ self.add_filter(HardFilter("(f4f_filter) f 4 f'", contains="f 4 f"))
90
+ self.add_filter(HardFilter("(f4f_filter) 'follow for'", contains="follow for"))
91
+
92
+ def add_ads_filter(self):
93
+ self.add_filter(SoftFilter(1, "(ads_filter) links", contains="scratch.mit.edu/projects/"))
94
+ self.add_filter(SoftFilter(-1, "(ads_filter) feedback", contains="feedback"))
95
+ self.add_filter(HardFilter("(ads_filter) 'check out my'", contains="check out my"))
96
+ self.add_filter(HardFilter("(ads_filter) 'play my'", contains="play my"))
97
+ self.add_filter(SoftFilter(1, "(ads_filter) 'advertis'", contains="advertis"))
98
+
99
+ def add_spam_filter(self):
100
+ self.add_filter(SpamFilter("(spam_filter)", contains=""))
101
+
102
+ def add_genalpha_nonsense_filter(self):
103
+ self.add_filter(HardFilter("(genalpha_nonsene_filter) 'skibidi'", contains="skibidi"))
104
+ self.add_filter(HardFilter("[genalpha_nonsene_filter) 'rizzler'", contains="rizzler"))
105
+ self.add_filter(HardFilter("(genalpha_nonsene_filter) 'fanum tax'", contains="fanum tax"))
106
+
107
+ def on_message(self, message):
108
+ if message.type == "addcomment":
109
+ delete = False
110
+ content = message.comment_fragment
111
+
112
+ if message.comment_type == 0: # project comment
113
+ source_id = message.comment_obj_id
114
+ if self.user._session.connect_project(message.comment_obj_id).author_name != self.user.username:
115
+ return # no permission to delete
116
+ if message.comment_type == 1: # profile comment
117
+ source_id = message.comment_obj_title
118
+ if message.comment_obj_title != self.user.username:
119
+ return # no permission to delete
120
+ if message.comment_type == 2: # studio comment
121
+ return # studio comments aren't handled
122
+
123
+ # Apply hard filters
124
+ for hard_filter in self.hard_filters:
125
+ if hard_filter.apply(content, message.actor_username, source_id):
126
+ delete=True
127
+ if self.log_deletions:
128
+ print(f"DETECTED: #{message.comment_id} violates hard filter: {hard_filter.filter_name}")
129
+ break
130
+
131
+ # Apply spam filters
132
+ if delete is False:
133
+ for spam_filter in self.spam_filters:
134
+ if spam_filter.apply(content, message.actor_username, source_id):
135
+ delete=True
136
+ if self.log_deletions:
137
+ print(f"DETECTED: #{message.comment_id} violates spam filter: {spam_filter.filter_name}")
138
+ break
139
+
140
+ # Apply soft filters
141
+ if delete is False:
142
+ score = 0
143
+ violated_filers = []
144
+ for soft_filter in self.soft_filters:
145
+ if soft_filter.apply(content, message.actor_username, source_id):
146
+ score += soft_filter.score
147
+ violated_filers.append(soft_filter.name)
148
+ if score >= 1:
149
+ print(f"DETECTED: #{message.comment_id} violates too many soft filters: {violated_filers}")
150
+ delete = True
151
+
152
+ if delete is True:
153
+ try:
154
+ message.target().delete()
155
+ if self.log_deletions:
156
+ print(f"DELETED: #{message.comment_id} by f{message.actor_username}: '{content}'")
157
+ except Exception as e:
158
+ if self.log_deletions:
159
+ print(f"DELETION FAILED: #{message.comment_id} by f{message.actor_username}: '{content}'")
160
+
160
161
 
@@ -1,41 +1,42 @@
1
- """MessageEvents class"""
2
-
3
- from ..site import user
4
- from ._base import BaseEventHandler
5
- import time
6
-
7
- class MessageEvents(BaseEventHandler):
8
- """
9
- Class that calls events when you receive messages on your Scratch account. Data fetched from Scratch's API.
10
- """
11
- def __init__(self, user, *, update_interval=2):
12
- super().__init__()
13
- self.user = user
14
- self.current_message_count = 0
15
- self.update_interval = update_interval
16
-
17
- def _updater(self):
18
- """
19
- A process that listens for cloud activity and executes events on cloud activity
20
- """
21
- self.current_message_count = int(self.user.message_count())
22
-
23
- self.call_event("on_ready")
24
-
25
- while True:
26
- if self.running is False:
27
- return
28
- message_count = int(self.user.message_count())
29
- if message_count != self.current_message_count:
30
- self.call_event("on_count_change", [int(self.current_message_count), int(message_count)])
31
- if message_count != 0:
32
- if message_count < self.current_message_count:
33
- self.current_message_count = 0
34
- if self.user._session is not None: # authentication check
35
- if self.user._session.username == self.user.username: # authorization check
36
- new_messages = self.user._session.messages(limit=message_count-self.current_message_count)
37
- for message in new_messages[::-1]:
38
- self.call_event("on_message", [message])
39
- self.current_message_count = int(message_count)
40
- time.sleep(self.update_interval)
1
+ """MessageEvents class"""
2
+ from __future__ import annotations
3
+
4
+ from ..site import user
5
+ from ._base import BaseEventHandler
6
+ import time
7
+
8
+ class MessageEvents(BaseEventHandler):
9
+ """
10
+ Class that calls events when you receive messages on your Scratch account. Data fetched from Scratch's API.
11
+ """
12
+ def __init__(self, user, *, update_interval=2):
13
+ super().__init__()
14
+ self.user = user
15
+ self.current_message_count = 0
16
+ self.update_interval = update_interval
17
+
18
+ def _updater(self):
19
+ """
20
+ A process that listens for cloud activity and executes events on cloud activity
21
+ """
22
+ self.current_message_count = int(self.user.message_count())
23
+
24
+ self.call_event("on_ready")
25
+
26
+ while True:
27
+ if self.running is False:
28
+ return
29
+ message_count = int(self.user.message_count())
30
+ if message_count != self.current_message_count:
31
+ self.call_event("on_count_change", [int(self.current_message_count), int(message_count)])
32
+ if message_count != 0:
33
+ if message_count < self.current_message_count:
34
+ self.current_message_count = 0
35
+ if self.user._session is not None: # authentication check
36
+ if self.user._session.username == self.user.username: # authorization check
37
+ new_messages = self.user._session.messages(limit=message_count-self.current_message_count)
38
+ for message in new_messages[::-1]:
39
+ self.call_event("on_message", [message])
40
+ self.current_message_count = int(message_count)
41
+ time.sleep(self.update_interval)
41
42