scratchattach 2.1.9__py3-none-any.whl → 2.1.10a1__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.9.dist-info → scratchattach-2.1.10a1.dist-info}/METADATA +155 -146
  55. scratchattach-2.1.10a1.dist-info/RECORD +62 -0
  56. {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/WHEEL +1 -1
  57. {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info/licenses}/LICENSE +21 -21
  58. scratchattach-2.1.9.dist-info/RECORD +0 -40
  59. {scratchattach-2.1.9.dist-info → scratchattach-2.1.10a1.dist-info}/top_level.txt +0 -0
@@ -1,244 +1,246 @@
1
- from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
2
- from threading import Thread
3
- from ..utils import exceptions
4
- import json
5
- import time
6
- from ..site import cloud_activity
7
- from ..site.user import User
8
- from ._base import BaseEventHandler
9
-
10
- class TwCloudSocket(WebSocket):
11
-
12
- def handleMessage(self):
13
- if not self.server.running:
14
- return
15
- try:
16
- if self.server.check_for_ip_ban(self):
17
- return
18
-
19
- data = json.loads(self.data)
20
-
21
- if data["method"] == "set":
22
- # cloud variable set received
23
- # check if project_id is in whitelisted projects (if there's a list of whitelisted projects)
24
- if self.server.whitelisted_projects is not None:
25
- if data["project_id"] not in self.server.whitelisted_projects:
26
- self.close(4002)
27
- if self.server.log_var_sets:
28
- 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"])
29
- return
30
- # check if value is valid
31
- if not self.server._check_value(data["value"]):
32
- if self.server.log_var_sets:
33
- print(self.address[0]+":"+str(self.address[1]), "sent an invalid var value")
34
- return
35
- # perform cloud var and forward to other players
36
- if self.server.log_var_sets:
37
- print(self.address[0]+":"+str(self.address[1]), f"set {data['name']} to {data['value']}, project:", str(data["project_id"]), "user:",data["user"])
38
- self.server.set_var(data["project_id"], data["name"], data["value"], user=data["user"], skip_forward=self)
39
- send_to_clients = {
40
- "method" : "set", "user" : data["user"], "project_id" : data["project_id"], "name" : data["name"],
41
- "value" : data["value"], "timestamp" : round(time.time() * 1000), "server" : "scratchattach/2.0.0",
42
- }
43
- # raise event
44
- _a = cloud_activity.CloudActivity(timestamp=time.time()*1000)
45
- data["name"] = data["name"].replace("☁ ", "")
46
- _a._update_from_dict(send_to_clients)
47
- self.server.call_event("on_set", [_a, self])
48
-
49
- elif data["method"] == "handshake":
50
- data = json.loads(self.data)
51
- # check if handshake is valid
52
- if not "user" in data:
53
- print(self.address[0]+":"+str(self.address[1]), "tried to handshake without providing a username")
54
- self.close(4002)
55
- return
56
- if not "project_id" in data:
57
- print(self.address[0]+":"+str(self.address[1]), "tried to handshake without providing a project_id")
58
- self.close(4002)
59
- return
60
- # check if project_id is in username is allowed
61
- if self.server.allow_nonscratch_names is False:
62
- if not User(username=data["user"]).does_exist():
63
- 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"])
64
- self.close(4002)
65
- return
66
- # check if project_id is in whitelisted projects (if there's a list of whitelisted projects)
67
- if self.server.whitelisted_projects is not None:
68
- if str(data["project_id"]) not in self.server.whitelisted_projects:
69
- self.close(4002)
70
- print(self.address[0]+":"+str(self.address[1]), "tried to handshake on a non-whitelisted project:", data["project_id"], "user:",data["user"])
71
- return
72
- # register handshake in users list (save username and project_id)
73
- print(self.address[0]+":"+str(self.address[1]), "handshaked, project:", data["project_id"], "user:",data["user"])
74
- self.server.tw_clients[self.address]["username"] = data["user"]
75
- self.server.tw_clients[self.address]["project_id"] = data["project_id"]
76
- # send current cloud variable values to the user who handshaked
77
- self.sendMessage("\n".join([
78
- json.dumps({
79
- "method" : "set", "project_id" : data["project_id"], "name" : "☁ "+varname,
80
- "value" : self.server.tw_variables[str(data["project_id"])][varname], "server" : "scratchattach/2.0.0",
81
- }) for varname in self.server.get_project_vars(str(data["project_id"]))])
82
- )
83
- self.sendMessage("This server uses @TimMcCool's scratchattach 2.0.0")
84
- # raise event
85
- self.server.call_event("on_handshake", [data["user"], data["project_id"], self])
86
-
87
- else:
88
- print("Error:", self.address[0]+":"+str(self.address[1]), "sent a message without providing a valid method (set, handshake)")
89
-
90
- except Exception as e:
91
- print("Internal error in handleMessage:", e)
92
-
93
- def handleConnected(self):
94
- if not self.server.running:
95
- return
96
- try:
97
- if self.server.check_for_ip_ban(self):
98
- return
99
-
100
- print(self.address[0]+":"+str(self.address[1]), "connected")
101
- self.server.tw_clients[self.address] = {"client":self, "username":None, "project_id":None}
102
- # raise event
103
- self.server.call_event("on_connect", [self])
104
- except Exception as e:
105
- print("Internal error in handleConntected:", e)
106
-
107
- def handleClose(self):
108
- if not self.server.running:
109
- return
110
- try:
111
- if self.address in self.server.tw_clients:
112
- # raise event
113
- self.server.call_event("on_disconnect", [self.server.tw_clients[self.address]["username"], self.server.tw_clients[self.address]["project_id"], self])
114
- print(self.address[0]+":"+str(self.address[1]), "disconnected")
115
- except Exception as e:
116
- print("Internal error in handleClose:", e)
117
-
118
- 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):
119
- """
120
- Inits a websocket server which can be used with TurboWarp's ?cloud_host URL parameter.
121
-
122
- Prints out the websocket address in the console.
123
- """
124
- class TwCloudServer(SimpleWebSocketServer, BaseEventHandler):
125
- def __init__(self, hostname, *, port, websocketclass):
126
- super().__init__(hostname, port=port, websocketclass=websocketclass)
127
- self.running = False
128
- self._events = {} # saves event functions called on cloud updates
129
-
130
- self.tw_clients = {} # saves connected clients
131
- self.tw_variables = {} # holds cloud variable states
132
-
133
- # server config
134
- self.allow_non_numeric = allow_non_numeric
135
- self.whitelisted_projects = whitelisted_projects
136
- self.length_limit = length_limit
137
- self.allow_nonscratch_names = allow_nonscratch_names
138
- self.blocked_ips = blocked_ips
139
- self.sync_players = sync_players
140
- self.log_var_sets = log_var_sets
141
-
142
- def check_for_ip_ban(self, client):
143
- 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:
144
- client.sendMessage("You have been banned from this server")
145
- client.close(4002)
146
- print(client.address[0]+":"+str(client.address[1]), "(IP-banned) was disconnected")
147
- return True
148
- return False
149
-
150
- def active_projects(self):
151
- only_active = {}
152
- for project_id in self.tw_variables:
153
- if self.active_user_ips(project_id) != []:
154
- only_active[project_id] = self.tw_variables[project_id]
155
- return only_active
156
-
157
- def active_user_names(self, project_id):
158
- return [self.tw_clients[user]["username"] for user in self.active_user_ips(project_id)]
159
-
160
- def active_user_ips(self, project_id):
161
- return list(filter(lambda user : str(self.tw_clients[user]["project_id"]) == str(project_id), self.tw_clients))
162
-
163
- def get_global_vars(self):
164
- return self.tw_variables
165
-
166
- def get_project_vars(self, project_id):
167
- project_id = str(project_id)
168
- if project_id in self.tw_variables:
169
- return self.tw_variables[project_id]
170
- else: return {}
171
-
172
- def get_var(self, project_id, var_name):
173
- project_id = str(project_id)
174
- var_name = var_name.replace("☁ ", "")
175
- if project_id in self.tw_variables:
176
- if var_name in self.tw_variables[project_id]:
177
- return self.tw_variables[project_id][var_name]
178
- else: return None
179
- else: return None
180
-
181
- def set_global_vars(self, data):
182
- for project_id in data:
183
- self.set_project_vars(project_id, data[project_id])
184
-
185
- def set_project_vars(self, project_id, data, *, user="@server"):
186
- project_id = str(project_id)
187
- self.tw_variables[project_id] = data
188
- for client in [self.tw_clients[ip]["client"] for ip in self.active_user_ips(project_id)]:
189
- client.sendMessage("\n".join([
190
- json.dumps({
191
- "method" : "set", "project_id" : project_id, "name" : "☁ "+varname,
192
- "value" : data[varname], "server" : "scratchattach/2.0.0", "timestamp" : time.time()*1000, "user" : user
193
- }) for varname in data])
194
- )
195
-
196
- def set_var(self, project_id, var_name, value, *, user="@server", skip_forward=None):
197
- var_name = var_name.replace("☁ ", "")
198
- project_id = str(project_id)
199
- if project_id not in self.tw_variables:
200
- self.tw_variables[project_id] = {}
201
- self.tw_variables[project_id][var_name] = value
202
-
203
- if self.sync_players is True:
204
- for client in [self.tw_clients[ip]["client"] for ip in self.active_user_ips(project_id)]:
205
- if client == skip_forward:
206
- continue
207
- client.sendMessage(
208
- json.dumps({
209
- "method" : "set", "project_id" : project_id, "name" : "☁ "+var_name,
210
- "value" : value, "timestamp" : time.time()*1000, "user" : user
211
- })
212
- )
213
-
214
- def _check_value(self, value):
215
- # Checks if a received cloud value satisfies the server's constraints
216
- if self.length_limit is not None:
217
- if len(str(value)) > self.length_limit:
218
- return False
219
- if self.allow_non_numeric is False:
220
- x = value.replace(".", "")
221
- x = x.replace("-", "")
222
- if not (x.isnumeric() or x == ""):
223
- return False
224
- return True
225
-
226
- def _updater(self):
227
- try:
228
- # Function called when .start() is executed (.start is inherited from BaseEventHandler)
229
- print(f"Serving websocket server: ws://{hostname}:{port}")
230
- self.serveforever()
231
- except Exception as e:
232
- raise exceptions.WebsocketServerError(str(e))
233
-
234
- def pause(self):
235
- self.running = False
236
-
237
- def resume(self):
238
- self.running = True
239
-
240
- def stop(self):
241
- self.running = False
242
- self.close()
243
-
244
- return TwCloudServer(hostname, port=port, websocketclass=TwCloudSocket)
1
+ from __future__ import annotations
2
+
3
+ from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
4
+ from threading import Thread
5
+ from ..utils import exceptions
6
+ import json
7
+ import time
8
+ from ..site import cloud_activity
9
+ from ..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)