scratchattach 2.1.14__py3-none-any.whl → 3.0.0b0__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 (69) hide show
  1. scratchattach/__init__.py +14 -6
  2. scratchattach/__main__.py +93 -0
  3. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/METADATA +7 -11
  4. scratchattach-3.0.0b0.dist-info/RECORD +8 -0
  5. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/WHEEL +1 -1
  6. scratchattach-3.0.0b0.dist-info/entry_points.txt +2 -0
  7. scratchattach/cloud/__init__.py +0 -2
  8. scratchattach/cloud/_base.py +0 -458
  9. scratchattach/cloud/cloud.py +0 -183
  10. scratchattach/editor/__init__.py +0 -21
  11. scratchattach/editor/asset.py +0 -253
  12. scratchattach/editor/backpack_json.py +0 -117
  13. scratchattach/editor/base.py +0 -193
  14. scratchattach/editor/block.py +0 -579
  15. scratchattach/editor/blockshape.py +0 -357
  16. scratchattach/editor/build_defaulting.py +0 -51
  17. scratchattach/editor/code_translation/__init__.py +0 -0
  18. scratchattach/editor/code_translation/parse.py +0 -177
  19. scratchattach/editor/comment.py +0 -80
  20. scratchattach/editor/commons.py +0 -306
  21. scratchattach/editor/extension.py +0 -50
  22. scratchattach/editor/field.py +0 -99
  23. scratchattach/editor/inputs.py +0 -135
  24. scratchattach/editor/meta.py +0 -114
  25. scratchattach/editor/monitor.py +0 -183
  26. scratchattach/editor/mutation.py +0 -324
  27. scratchattach/editor/pallete.py +0 -90
  28. scratchattach/editor/prim.py +0 -170
  29. scratchattach/editor/project.py +0 -279
  30. scratchattach/editor/sprite.py +0 -599
  31. scratchattach/editor/twconfig.py +0 -114
  32. scratchattach/editor/vlb.py +0 -134
  33. scratchattach/eventhandlers/__init__.py +0 -0
  34. scratchattach/eventhandlers/_base.py +0 -100
  35. scratchattach/eventhandlers/cloud_events.py +0 -110
  36. scratchattach/eventhandlers/cloud_recorder.py +0 -26
  37. scratchattach/eventhandlers/cloud_requests.py +0 -459
  38. scratchattach/eventhandlers/cloud_server.py +0 -246
  39. scratchattach/eventhandlers/cloud_storage.py +0 -136
  40. scratchattach/eventhandlers/combine.py +0 -30
  41. scratchattach/eventhandlers/filterbot.py +0 -161
  42. scratchattach/eventhandlers/message_events.py +0 -42
  43. scratchattach/other/__init__.py +0 -0
  44. scratchattach/other/other_apis.py +0 -284
  45. scratchattach/other/project_json_capabilities.py +0 -475
  46. scratchattach/site/__init__.py +0 -0
  47. scratchattach/site/_base.py +0 -66
  48. scratchattach/site/activity.py +0 -382
  49. scratchattach/site/alert.py +0 -227
  50. scratchattach/site/backpack_asset.py +0 -118
  51. scratchattach/site/browser_cookie3_stub.py +0 -17
  52. scratchattach/site/browser_cookies.py +0 -61
  53. scratchattach/site/classroom.py +0 -447
  54. scratchattach/site/cloud_activity.py +0 -107
  55. scratchattach/site/comment.py +0 -242
  56. scratchattach/site/forum.py +0 -432
  57. scratchattach/site/project.py +0 -825
  58. scratchattach/site/session.py +0 -1238
  59. scratchattach/site/studio.py +0 -611
  60. scratchattach/site/user.py +0 -956
  61. scratchattach/utils/__init__.py +0 -0
  62. scratchattach/utils/commons.py +0 -255
  63. scratchattach/utils/encoder.py +0 -158
  64. scratchattach/utils/enums.py +0 -236
  65. scratchattach/utils/exceptions.py +0 -243
  66. scratchattach/utils/requests.py +0 -93
  67. scratchattach-2.1.14.dist-info/RECORD +0 -66
  68. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/licenses/LICENSE +0 -0
  69. {scratchattach-2.1.14.dist-info → scratchattach-3.0.0b0.dist-info}/top_level.txt +0 -0
@@ -1,246 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
4
- from threading import Thread
5
- from scratchattach.utils import exceptions
6
- import json
7
- import time
8
- from scratchattach.site import cloud_activity
9
- from scratchattach.site.user import User
10
- from ._base import BaseEventHandler
11
-
12
- class TwCloudSocket(WebSocket):
13
-
14
- def handleMessage(self):
15
- if not self.server.running:
16
- return
17
- try:
18
- if self.server.check_for_ip_ban(self):
19
- return
20
-
21
- data = json.loads(self.data)
22
-
23
- if data["method"] == "set":
24
- # cloud variable set received
25
- # check if project_id is in whitelisted projects (if there's a list of whitelisted projects)
26
- if self.server.whitelisted_projects is not None:
27
- if data["project_id"] not in self.server.whitelisted_projects:
28
- self.close(4002)
29
- if self.server.log_var_sets:
30
- print(self.address[0]+":"+str(self.address[1]), "tried to set a var on non-whitelisted project and was disconnected, project:", data["project_id"], "user:",data["user"])
31
- return
32
- # check if value is valid
33
- if not self.server._check_value(data["value"]):
34
- if self.server.log_var_sets:
35
- print(self.address[0]+":"+str(self.address[1]), "sent an invalid var value")
36
- return
37
- # perform cloud var and forward to other players
38
- if self.server.log_var_sets:
39
- print(self.address[0]+":"+str(self.address[1]), f"set {data['name']} to {data['value']}, project:", str(data["project_id"]), "user:",data["user"])
40
- self.server.set_var(data["project_id"], data["name"], data["value"], user=data["user"], skip_forward=self)
41
- send_to_clients = {
42
- "method" : "set", "user" : data["user"], "project_id" : data["project_id"], "name" : data["name"],
43
- "value" : data["value"], "timestamp" : round(time.time() * 1000), "server" : "scratchattach/2.0.0",
44
- }
45
- # raise event
46
- _a = cloud_activity.CloudActivity(timestamp=time.time()*1000)
47
- data["name"] = data["name"].replace("☁ ", "")
48
- _a._update_from_dict(send_to_clients)
49
- self.server.call_event("on_set", [_a, self])
50
-
51
- elif data["method"] == "handshake":
52
- data = json.loads(self.data)
53
- # check if handshake is valid
54
- if not "user" in data:
55
- print(self.address[0]+":"+str(self.address[1]), "tried to handshake without providing a username")
56
- self.close(4002)
57
- return
58
- if not "project_id" in data:
59
- print(self.address[0]+":"+str(self.address[1]), "tried to handshake without providing a project_id")
60
- self.close(4002)
61
- return
62
- # check if project_id is in username is allowed
63
- if self.server.allow_nonscratch_names is False:
64
- if not User(username=data["user"]).does_exist():
65
- print(self.address[0]+":"+str(self.address[1]), "tried to handshake using a username not existing on Scratch, project:", data["project_id"], "user:",data["user"])
66
- self.close(4002)
67
- return
68
- # check if project_id is in whitelisted projects (if there's a list of whitelisted projects)
69
- if self.server.whitelisted_projects is not None:
70
- if str(data["project_id"]) not in self.server.whitelisted_projects:
71
- self.close(4002)
72
- print(self.address[0]+":"+str(self.address[1]), "tried to handshake on a non-whitelisted project:", data["project_id"], "user:",data["user"])
73
- return
74
- # register handshake in users list (save username and project_id)
75
- print(self.address[0]+":"+str(self.address[1]), "handshaked, project:", data["project_id"], "user:",data["user"])
76
- self.server.tw_clients[self.address]["username"] = data["user"]
77
- self.server.tw_clients[self.address]["project_id"] = data["project_id"]
78
- # send current cloud variable values to the user who handshaked
79
- self.sendMessage("\n".join([
80
- json.dumps({
81
- "method" : "set", "project_id" : data["project_id"], "name" : "☁ "+varname,
82
- "value" : self.server.tw_variables[str(data["project_id"])][varname], "server" : "scratchattach/2.0.0",
83
- }) for varname in self.server.get_project_vars(str(data["project_id"]))])
84
- )
85
- self.sendMessage("This server uses @TimMcCool's scratchattach 2.0.0")
86
- # raise event
87
- self.server.call_event("on_handshake", [data["user"], data["project_id"], self])
88
-
89
- else:
90
- print("Error:", self.address[0]+":"+str(self.address[1]), "sent a message without providing a valid method (set, handshake)")
91
-
92
- except Exception as e:
93
- print("Internal error in handleMessage:", e)
94
-
95
- def handleConnected(self):
96
- if not self.server.running:
97
- return
98
- try:
99
- if self.server.check_for_ip_ban(self):
100
- return
101
-
102
- print(self.address[0]+":"+str(self.address[1]), "connected")
103
- self.server.tw_clients[self.address] = {"client":self, "username":None, "project_id":None}
104
- # raise event
105
- self.server.call_event("on_connect", [self])
106
- except Exception as e:
107
- print("Internal error in handleConntected:", e)
108
-
109
- def handleClose(self):
110
- if not self.server.running:
111
- return
112
- try:
113
- if self.address in self.server.tw_clients:
114
- # raise event
115
- self.server.call_event("on_disconnect", [self.server.tw_clients[self.address]["username"], self.server.tw_clients[self.address]["project_id"], self])
116
- print(self.address[0]+":"+str(self.address[1]), "disconnected")
117
- except Exception as e:
118
- print("Internal error in handleClose:", e)
119
-
120
- def init_cloud_server(hostname='127.0.0.1', port=8080, *, thread=True, length_limit=None, allow_non_numeric=True, whitelisted_projects=None, allow_nonscratch_names=True, blocked_ips=[], sync_players=True, log_var_sets=True):
121
- """
122
- Inits a websocket server which can be used with TurboWarp's ?cloud_host URL parameter.
123
-
124
- Prints out the websocket address in the console.
125
- """
126
- class TwCloudServer(SimpleWebSocketServer, BaseEventHandler):
127
- def __init__(self, hostname, *, port, websocketclass):
128
- super().__init__(hostname, port=port, websocketclass=websocketclass)
129
- self.running = False
130
- self._events = {} # saves event functions called on cloud updates
131
-
132
- self.tw_clients = {} # saves connected clients
133
- self.tw_variables = {} # holds cloud variable states
134
-
135
- # server config
136
- self.allow_non_numeric = allow_non_numeric
137
- self.whitelisted_projects = whitelisted_projects
138
- self.length_limit = length_limit
139
- self.allow_nonscratch_names = allow_nonscratch_names
140
- self.blocked_ips = blocked_ips
141
- self.sync_players = sync_players
142
- self.log_var_sets = log_var_sets
143
-
144
- def check_for_ip_ban(self, client):
145
- if client.address[0] in self.blocked_ips or client.address[0]+":"+str(client.address[1]) in self.blocked_ips or client.address in self.blocked_ips:
146
- client.sendMessage("You have been banned from this server")
147
- client.close(4002)
148
- print(client.address[0]+":"+str(client.address[1]), "(IP-banned) was disconnected")
149
- return True
150
- return False
151
-
152
- def active_projects(self):
153
- only_active = {}
154
- for project_id in self.tw_variables:
155
- if self.active_user_ips(project_id) != []:
156
- only_active[project_id] = self.tw_variables[project_id]
157
- return only_active
158
-
159
- def active_user_names(self, project_id):
160
- return [self.tw_clients[user]["username"] for user in self.active_user_ips(project_id)]
161
-
162
- def active_user_ips(self, project_id):
163
- return list(filter(lambda user : str(self.tw_clients[user]["project_id"]) == str(project_id), self.tw_clients))
164
-
165
- def get_global_vars(self):
166
- return self.tw_variables
167
-
168
- def get_project_vars(self, project_id):
169
- project_id = str(project_id)
170
- if project_id in self.tw_variables:
171
- return self.tw_variables[project_id]
172
- else: return {}
173
-
174
- def get_var(self, project_id, var_name):
175
- project_id = str(project_id)
176
- var_name = var_name.replace("☁ ", "")
177
- if project_id in self.tw_variables:
178
- if var_name in self.tw_variables[project_id]:
179
- return self.tw_variables[project_id][var_name]
180
- else: return None
181
- else: return None
182
-
183
- def set_global_vars(self, data):
184
- for project_id in data:
185
- self.set_project_vars(project_id, data[project_id])
186
-
187
- def set_project_vars(self, project_id, data, *, user="@server"):
188
- project_id = str(project_id)
189
- self.tw_variables[project_id] = data
190
- for client in [self.tw_clients[ip]["client"] for ip in self.active_user_ips(project_id)]:
191
- client.sendMessage("\n".join([
192
- json.dumps({
193
- "method" : "set", "project_id" : project_id, "name" : "☁ "+varname,
194
- "value" : data[varname], "server" : "scratchattach/2.0.0", "timestamp" : time.time()*1000, "user" : user
195
- }) for varname in data])
196
- )
197
-
198
- def set_var(self, project_id, var_name, value, *, user="@server", skip_forward=None):
199
- var_name = var_name.replace("☁ ", "")
200
- project_id = str(project_id)
201
- if project_id not in self.tw_variables:
202
- self.tw_variables[project_id] = {}
203
- self.tw_variables[project_id][var_name] = value
204
-
205
- if self.sync_players is True:
206
- for client in [self.tw_clients[ip]["client"] for ip in self.active_user_ips(project_id)]:
207
- if client == skip_forward:
208
- continue
209
- client.sendMessage(
210
- json.dumps({
211
- "method" : "set", "project_id" : project_id, "name" : "☁ "+var_name,
212
- "value" : value, "timestamp" : time.time()*1000, "user" : user
213
- })
214
- )
215
-
216
- def _check_value(self, value):
217
- # Checks if a received cloud value satisfies the server's constraints
218
- if self.length_limit is not None:
219
- if len(str(value)) > self.length_limit:
220
- return False
221
- if self.allow_non_numeric is False:
222
- x = value.replace(".", "")
223
- x = x.replace("-", "")
224
- if not (x.isnumeric() or x == ""):
225
- return False
226
- return True
227
-
228
- def _updater(self):
229
- try:
230
- # Function called when .start() is executed (.start is inherited from BaseEventHandler)
231
- print(f"Serving websocket server: ws://{hostname}:{port}")
232
- self.serveforever()
233
- except Exception as e:
234
- raise exceptions.WebsocketServerError(str(e))
235
-
236
- def pause(self):
237
- self.running = False
238
-
239
- def resume(self):
240
- self.running = True
241
-
242
- def stop(self):
243
- self.running = False
244
- self.close()
245
-
246
- return TwCloudServer(hostname, port=port, websocketclass=TwCloudSocket)
@@ -1,136 +0,0 @@
1
- """CloudStorage class"""
2
- from __future__ import annotations
3
-
4
- from .cloud_requests import CloudRequests
5
- import json
6
- import time
7
- from threading import Thread
8
-
9
- class Database:
10
-
11
- """
12
- A Database is a simple key-value storage that stores data in a JSON file saved locally (other database services like MongoDB can be implemented)
13
- """
14
-
15
- def __init__(self, name, *, json_file_path, save_interval=30):
16
- self.save_event_function = None
17
- self.set_event_function = None
18
- self.name = name
19
-
20
- # Import from JSON file
21
- if not json_file_path.endswith(".json"):
22
- json_file_path = json_file_path+".json"
23
- self.json_file_path = json_file_path
24
-
25
- try:
26
- with open(json_file_path, 'r') as json_file:
27
- self.data = json.load(json_file)
28
- except FileNotFoundError:
29
- print(f"Creating file {json_file_path}. Your database {name} will be stored there.")
30
- self.data = {}
31
- self.save_to_json()
32
-
33
- if isinstance(self.data , list):
34
- raise ValueError(
35
- "Invalid JSON file content: Top-level object must be a dict, not a list"
36
- )
37
-
38
- # Start autosaving
39
- self.save_interval = save_interval
40
- if self.save_interval is not None:
41
- Thread(target=self._autosaver).start()
42
-
43
- def save_to_json(self):
44
- with open(self.json_file_path, 'w') as json_file:
45
- json.dump(self.data, json_file, indent=4)
46
-
47
- if self.save_event_function is not None:
48
- self.save_event_function()
49
-
50
- def keys(self) -> list:
51
- return list(self.data.keys())
52
-
53
- def get(self, key) -> str:
54
- if not key in self.data:
55
- return None
56
- return self.data[key]
57
-
58
- def set(self, key, value):
59
- self.data[key] = value
60
-
61
- if self.set_event_function is not None:
62
- self.set_event_function(key, value)
63
-
64
- def event(self, event_function):
65
- # Decorator function for adding the on_save event that is called when a save is performed
66
- if event_function.__name__ == "on_save":
67
- self.save_event_function = event_function
68
- if event_function.__name__ == "on_set":
69
- self.set_event_function = event_function
70
-
71
- def _autosaver(self):
72
- # Task autosaving the db. save interval specified in .save_interval attribute
73
- while True:
74
- time.sleep(self.save_interval)
75
- self.save_to_json()
76
-
77
- class CloudStorage(CloudRequests):
78
-
79
- """
80
- A CloudStorage object saves multiple databases and allows the connected Scratch project to access and modify the data of these databases through cloud requests
81
-
82
- The CloudStorage class is built upon CloudRequests
83
- """
84
-
85
- def __init__(self, cloud, used_cloud_vars=["1", "2", "3", "4", "5", "6", "7", "8", "9"], no_packet_loss=False):
86
- super().__init__(cloud, used_cloud_vars=used_cloud_vars, no_packet_loss=no_packet_loss)
87
- # Setup
88
- self._databases = {}
89
- self.request(self.get, thread=False)
90
- self.request(self.set, thread=False)
91
- self.request(self.keys, thread=False)
92
- self.request(self.database_names, thread=False)
93
- self.request(self.ping, thread=False)
94
-
95
- def ping(self):
96
- return "Database backend is running"
97
-
98
- def get(self, db_name, key) -> str:
99
- try:
100
- return self.get_database(db_name).get(key)
101
- except Exception:
102
- if self.get_database(db_name) is None:
103
- return f"Error: Database {db_name} doesn't exist"
104
- else:
105
- return f"Error: Key {key} doesn't exist in database {db_name}"
106
-
107
- def set(self, db_name, key, value):
108
- print(db_name, key, value, self._databases)
109
- return self.get_database(db_name).set(key, value)
110
-
111
- def keys(self, db_name) -> list:
112
- try:
113
- return self.get_database(db_name).keys()
114
- except Exception:
115
- return f"Error: Database {db_name} doesn't exist"
116
-
117
- def databases(self) -> list:
118
- return list(self._databases.values())
119
-
120
- def database_names(self) -> list:
121
- return list(self._databases.keys())
122
-
123
- def add_database(self, database:Database):
124
- self._databases[database.name] = database
125
-
126
- def get_database(self, name) -> Database:
127
- if name in self._databases:
128
- return self._databases[name]
129
- return None
130
-
131
- def save(self):
132
- """
133
- Saves the data in the JSON files for all databases in self._databases
134
- """
135
- for dbname in self._databases:
136
- self._databases[dbname].save_to_json()
@@ -1,30 +0,0 @@
1
- from __future__ import annotations
2
-
3
- class MultiEventHandler:
4
-
5
- def __init__(self, *handlers):
6
- self.handlers = handlers
7
-
8
- def request(self, function, *args, **kwargs):
9
- for handler in self.handlers:
10
- handler.request(function, *args, **kwargs)
11
-
12
- def event(self, function, *args, **kwargs):
13
- for handler in self.handlers:
14
- handler.event(function, *args, **kwargs)
15
-
16
- def start(self, *args, **kwargs):
17
- for handler in self.handlers:
18
- handler.start(*args, **kwargs)
19
-
20
- def stop(self, *args, **kwargs):
21
- for handler in self.handlers:
22
- handler.stop(*args, **kwargs)
23
-
24
- def pause(self, *args, **kwargs):
25
- for handler in self.handlers:
26
- handler.pause(*args, **kwargs)
27
-
28
- def resume(self, *args, **kwargs):
29
- for handler in self.handlers:
30
- handler.resume(*args, **kwargs)
@@ -1,161 +0,0 @@
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
-
161
-
@@ -1,42 +0,0 @@
1
- """MessageEvents class"""
2
- from __future__ import annotations
3
-
4
- from scratchattach.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)
42
-
File without changes