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.
- 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.8.dist-info → scratchattach-2.1.10a0.dist-info}/LICENSE +21 -21
- {scratchattach-2.1.8.dist-info → scratchattach-2.1.10a0.dist-info}/METADATA +154 -146
- scratchattach-2.1.10a0.dist-info/RECORD +62 -0
- {scratchattach-2.1.8.dist-info → scratchattach-2.1.10a0.dist-info}/WHEEL +1 -1
- scratchattach-2.1.8.dist-info/RECORD +0 -40
- {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
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
self.
|
|
11
|
-
self.
|
|
12
|
-
self.
|
|
13
|
-
self.
|
|
14
|
-
self.
|
|
15
|
-
self.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
self.
|
|
71
|
-
self.
|
|
72
|
-
self.
|
|
73
|
-
self.
|
|
74
|
-
self.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
self.add_filter(HardFilter("(f4f_filter) '
|
|
87
|
-
self.add_filter(HardFilter("(f4f_filter) 'follow
|
|
88
|
-
self.add_filter(HardFilter("(f4f_filter)
|
|
89
|
-
self.add_filter(HardFilter("(f4f_filter)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
self.add_filter(SoftFilter(
|
|
94
|
-
self.add_filter(
|
|
95
|
-
self.add_filter(HardFilter("(ads_filter) '
|
|
96
|
-
self.add_filter(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
self.add_filter(HardFilter("
|
|
104
|
-
self.add_filter(HardFilter("
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
4
|
-
from
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
self.
|
|
15
|
-
self.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|