rexys-pyutils 0.1.0__tar.gz
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.
- rexys_pyutils-0.1.0/PKG-INFO +5 -0
- rexys_pyutils-0.1.0/README.md +1 -0
- rexys_pyutils-0.1.0/pyproject.toml +9 -0
- rexys_pyutils-0.1.0/pyutils/__init__.py +1 -0
- rexys_pyutils-0.1.0/pyutils/utils.py +356 -0
- rexys_pyutils-0.1.0/rexys_pyutils.egg-info/PKG-INFO +5 -0
- rexys_pyutils-0.1.0/rexys_pyutils.egg-info/SOURCES.txt +8 -0
- rexys_pyutils-0.1.0/rexys_pyutils.egg-info/dependency_links.txt +1 -0
- rexys_pyutils-0.1.0/rexys_pyutils.egg-info/top_level.txt +1 -0
- rexys_pyutils-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hello!
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .utils import Tuple, connection, Items
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import math, socket,base64,sys, hashlib, pickle, random, pygame as pyg
|
|
2
|
+
from typing_extensions import TypeAlias
|
|
3
|
+
from pygame.surface import SurfaceType
|
|
4
|
+
from socketio.exceptions import SocketIOError
|
|
5
|
+
from cryptography.fernet import Fernet
|
|
6
|
+
|
|
7
|
+
class Tuple:
|
|
8
|
+
"""
|
|
9
|
+
Usage: \n
|
|
10
|
+
var = Tuple(x, y, z=None, ...) \n
|
|
11
|
+
var.x = var.n1 = var[0] = x\n
|
|
12
|
+
var.y = var.n2 = var[1] = y\n
|
|
13
|
+
var.z = var.n3 = var[2] = z
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, *vArgs):
|
|
17
|
+
if not all(isinstance(i, (int, float)) for i in vArgs): raise TypeError("Tuples may only contain numbers for now")
|
|
18
|
+
self.argv = []
|
|
19
|
+
for i,obj in enumerate(vArgs):
|
|
20
|
+
self.argv.append(obj)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def __class__(self):
|
|
24
|
+
return tuple
|
|
25
|
+
|
|
26
|
+
def __getattr__(self, attr) -> int:
|
|
27
|
+
name = attr.lower()
|
|
28
|
+
if name.startswith('n') and name[1:].isdigit():
|
|
29
|
+
index = int(name[1:]) - 1
|
|
30
|
+
if 0 <= index < len(self.argv):
|
|
31
|
+
return self.argv[index]
|
|
32
|
+
raise IndexError(f"Tuple index out of range")
|
|
33
|
+
raise AttributeError(f"'Tuple' object has no attribute '{name}'")
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def x(self) -> int: return self.argv[0]
|
|
37
|
+
@property
|
|
38
|
+
def y(self) -> int: return self.argv[1]
|
|
39
|
+
@property
|
|
40
|
+
def z(self) -> int: return self.argv[2]
|
|
41
|
+
@property
|
|
42
|
+
def tuple(self) -> tuple: return tuple(self.argv)
|
|
43
|
+
|
|
44
|
+
def str(self):
|
|
45
|
+
"""Returns string representation of the Tuple"""
|
|
46
|
+
fullString = ""
|
|
47
|
+
for arg in self.argv:
|
|
48
|
+
fullString += str(arg)
|
|
49
|
+
fullString += ", "
|
|
50
|
+
return fullString
|
|
51
|
+
|
|
52
|
+
def __str__(self):
|
|
53
|
+
"""Returns string representation of the Tuple"""
|
|
54
|
+
fullString = ""
|
|
55
|
+
for arg in self.argv:
|
|
56
|
+
fullString += str(arg)
|
|
57
|
+
fullString += ", "
|
|
58
|
+
return fullString
|
|
59
|
+
|
|
60
|
+
def __repr__(self): # optional, for debugging
|
|
61
|
+
return self.__str__()
|
|
62
|
+
|
|
63
|
+
def __iter__(self):
|
|
64
|
+
for arg in self.argv:
|
|
65
|
+
yield arg
|
|
66
|
+
|
|
67
|
+
def __len__(self):
|
|
68
|
+
return len(self.argv)
|
|
69
|
+
|
|
70
|
+
def __getitem__(self, i):
|
|
71
|
+
return self.argv[i]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class connection(socket.socket):
|
|
75
|
+
def __init__(self, family: socket.AddressFamily | int = -1, type: socket.SocketKind | int = -1, proto: int = -1, fileno: int | None = None):
|
|
76
|
+
self.publicKey = None
|
|
77
|
+
self.privateKey = _generateKey()
|
|
78
|
+
self.is_host = None
|
|
79
|
+
self.mixedKey = None
|
|
80
|
+
self.key = None
|
|
81
|
+
super().__init__(family, type, proto, fileno)
|
|
82
|
+
def _fail(self, conn=None):
|
|
83
|
+
if conn is not None:
|
|
84
|
+
conn.close()
|
|
85
|
+
else:
|
|
86
|
+
super().close()
|
|
87
|
+
return None
|
|
88
|
+
def _recvDict(self, conn, bufsize=4096):
|
|
89
|
+
"""Receive and unpickle a dict from a socket. Returns None on failure."""
|
|
90
|
+
try:
|
|
91
|
+
data = conn.recv(bufsize)
|
|
92
|
+
except:
|
|
93
|
+
return None
|
|
94
|
+
if data is None: return None
|
|
95
|
+
try:
|
|
96
|
+
result = pickle.loads(data)
|
|
97
|
+
except:
|
|
98
|
+
return None
|
|
99
|
+
if not isinstance(result, dict): return None
|
|
100
|
+
return result
|
|
101
|
+
def _sendDict(self, conn, data: dict):
|
|
102
|
+
conn.send(pickle.dumps(data))
|
|
103
|
+
def bind(self, address, /):
|
|
104
|
+
self.publicKey = _generateKey()
|
|
105
|
+
self.is_host = True
|
|
106
|
+
return super().bind(address)
|
|
107
|
+
def accept(self):
|
|
108
|
+
conn, addr = super().accept()
|
|
109
|
+
client = connection(fileno=conn.fileno())
|
|
110
|
+
client.is_host = False
|
|
111
|
+
client.privateKey = self.privateKey
|
|
112
|
+
client.publicKey = self.publicKey
|
|
113
|
+
client.mixedKey = _combine(self.publicKey, self.privateKey)
|
|
114
|
+
# send public key and mixed key to client
|
|
115
|
+
conn.send(pickle.dumps({"publicKey": client.publicKey}))
|
|
116
|
+
conn.send(pickle.dumps({"mixedKeyServer": client.mixedKey}))
|
|
117
|
+
# receive client's mixed key
|
|
118
|
+
realData = self._recvDict(conn)
|
|
119
|
+
if realData is None: return self._fail(conn)
|
|
120
|
+
if realData.get("mixedKeyClient") is None: return self._fail(conn)
|
|
121
|
+
clientMixedKey = realData["mixedKeyClient"]
|
|
122
|
+
client.key = Fernet(_makeFernetKey(clientMixedKey, self.privateKey))
|
|
123
|
+
return client, addr
|
|
124
|
+
def connect(self, address, /):
|
|
125
|
+
self.is_host = False
|
|
126
|
+
data = super().connect(address)
|
|
127
|
+
# receive public key
|
|
128
|
+
realData = self._recvDict(self)
|
|
129
|
+
if realData is None: return self._fail()
|
|
130
|
+
if realData.get("publicKey") is None: return self._fail()
|
|
131
|
+
self.publicKey = realData["publicKey"]
|
|
132
|
+
# receive server's mixed key
|
|
133
|
+
realData = self._recvDict(self)
|
|
134
|
+
if realData is None: return self._fail()
|
|
135
|
+
if realData.get("mixedKeyServer") is None: return self._fail()
|
|
136
|
+
serverMixedKey = realData["mixedKeyServer"]
|
|
137
|
+
# send client's mixed key
|
|
138
|
+
self.mixedKey = _combine(self.publicKey, self.privateKey)
|
|
139
|
+
self._sendDict(self, {"mixedKeyClient": self.mixedKey})
|
|
140
|
+
self.key = Fernet(_makeFernetKey(serverMixedKey, self.mixedKey))
|
|
141
|
+
return None
|
|
142
|
+
def send(self, data: TypeAlias, flags: int = 0, /):
|
|
143
|
+
if isinstance(data, dict):
|
|
144
|
+
data = self.key.encrypt(pickle.dumps(data))
|
|
145
|
+
elif isinstance(data, str):
|
|
146
|
+
data = self.key.encrypt(data.encode())
|
|
147
|
+
elif isinstance(data, bytes):
|
|
148
|
+
data = self.key.encrypt(data)
|
|
149
|
+
else:
|
|
150
|
+
raise TypeError("data must be dict, str or bytes")
|
|
151
|
+
return super().send(data, flags)
|
|
152
|
+
def recv(self, bufsize: int, flags: int = 0, /):
|
|
153
|
+
data = super().recv(bufsize, flags)
|
|
154
|
+
return self.key.decrypt(data)
|
|
155
|
+
def _randomStr(length=15):
|
|
156
|
+
temp = ""
|
|
157
|
+
for i in range(length):
|
|
158
|
+
temp1 = round(random.random() * 62) + 48
|
|
159
|
+
temp2 = temp1
|
|
160
|
+
if 57 < temp1 < 65:
|
|
161
|
+
temp2 = temp1 + 7
|
|
162
|
+
elif 90 < temp1 < 97:
|
|
163
|
+
temp2 = temp1 + 6
|
|
164
|
+
temp += chr(temp2)
|
|
165
|
+
return temp
|
|
166
|
+
def _combine(a: str, b: str) -> str:
|
|
167
|
+
"""Combine two strings into an irreversible hash string."""
|
|
168
|
+
return hashlib.sha256((a + b).encode()).hexdigest()
|
|
169
|
+
def _makeFernetKey(a: str, b: str) -> bytes:
|
|
170
|
+
"""Combine two strings into a valid 32-byte Fernet key."""
|
|
171
|
+
raw = hashlib.sha256((a + b).encode()).digest() # 32 raw bytes
|
|
172
|
+
return base64.urlsafe_b64encode(raw) # Fernet needs base64
|
|
173
|
+
def _generateKey() -> str:
|
|
174
|
+
return _combine(_randomStr(), _randomStr())
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class _Outline:
|
|
178
|
+
def __init__(self, outline):
|
|
179
|
+
if isinstance(outline, dict):
|
|
180
|
+
enabled = outline.get("enabled", False)
|
|
181
|
+
thickness = outline.get("thickness", 5)
|
|
182
|
+
color = outline.get("color", "black")
|
|
183
|
+
rounding = outline.get("rounding", 0)
|
|
184
|
+
elif isinstance(outline, tuple):
|
|
185
|
+
enabled = outline[0] if len(outline) > 0 else False
|
|
186
|
+
thickness = outline[1] if len(outline) > 1 else 5
|
|
187
|
+
color = outline[2] if len(outline) > 2 else "black"
|
|
188
|
+
rounding = outline[3] if len(outline) > 3 else 0
|
|
189
|
+
else:
|
|
190
|
+
raise TypeError("outline must be a dict or tuple")
|
|
191
|
+
if not isinstance(enabled, bool):
|
|
192
|
+
raise TypeError('outline "enabled" must exist and be a bool')
|
|
193
|
+
if not isinstance(thickness, int):
|
|
194
|
+
raise TypeError('outline "thickness" must exist and be an int')
|
|
195
|
+
if not isinstance(color, str):
|
|
196
|
+
raise TypeError('outline "color" must exist and be a str')
|
|
197
|
+
if not isinstance(rounding, int):
|
|
198
|
+
raise TypeError('outline "rounding" must exist and be an int')
|
|
199
|
+
self.enabled = enabled
|
|
200
|
+
self.thickness = thickness
|
|
201
|
+
self.color = color
|
|
202
|
+
self.rounding = rounding
|
|
203
|
+
class _TextObject:
|
|
204
|
+
def __init__(self, name:str, text:str, x:int, y:int, size:int, color:str, autoPos:bool, priority:int, hit:bool=True, antiAlias:bool=True):
|
|
205
|
+
self.name = name
|
|
206
|
+
self.text = text
|
|
207
|
+
self.category = "text"
|
|
208
|
+
self.color = color
|
|
209
|
+
self.size = size
|
|
210
|
+
self.hit = hit
|
|
211
|
+
self.priority = priority
|
|
212
|
+
self.autoPos = autoPos
|
|
213
|
+
self.antiAlias = antiAlias
|
|
214
|
+
screen = pyg.display.get_surface()
|
|
215
|
+
if autoPos and screen:
|
|
216
|
+
self.x_ratio = x / screen.get_width()
|
|
217
|
+
self.y_ratio = y / screen.get_height()
|
|
218
|
+
else:
|
|
219
|
+
self.x_ratio = None
|
|
220
|
+
self.y_ratio = None
|
|
221
|
+
self.x = x
|
|
222
|
+
self.y = y
|
|
223
|
+
class _Object:
|
|
224
|
+
def __init__(self, name:str, x:int, y:int, size, category:str, outline:tuple, color:str, hit:bool=True, autoPos:bool=True, priority:int=10):
|
|
225
|
+
if not isinstance(outline, (dict, tuple)):
|
|
226
|
+
raise TypeError("outline must be a dict or tuple.")
|
|
227
|
+
if not 1 <= len(outline) <= 4:
|
|
228
|
+
raise TypeError("outline must follow {'enabled':bool,'thickness':int,'color':str,'rounding': int} or (bool,int,str,int)")
|
|
229
|
+
self.name = name
|
|
230
|
+
self.category = category
|
|
231
|
+
self.color = color
|
|
232
|
+
self.hit = hit
|
|
233
|
+
self.autoPos = autoPos
|
|
234
|
+
self.priority = priority
|
|
235
|
+
screen = pyg.display.get_surface()
|
|
236
|
+
if autoPos and screen:
|
|
237
|
+
self.x_ratio = x / screen.get_width()
|
|
238
|
+
self.y_ratio = y / screen.get_height()
|
|
239
|
+
else:
|
|
240
|
+
self.x_ratio = None
|
|
241
|
+
self.y_ratio = None
|
|
242
|
+
self.x = x
|
|
243
|
+
self.y = y
|
|
244
|
+
if category == "circle":
|
|
245
|
+
if isinstance(size, int):
|
|
246
|
+
self.size = size
|
|
247
|
+
elif isinstance(size, tuple):
|
|
248
|
+
self.size = size[0]
|
|
249
|
+
else:
|
|
250
|
+
raise TypeError("size must be an int or tuple")
|
|
251
|
+
self.radius = self.size
|
|
252
|
+
else:
|
|
253
|
+
if not isinstance(size, tuple): raise TypeError("size must be a tuple")
|
|
254
|
+
if len(size) < 2: raise TypeError("size must have 2 values")
|
|
255
|
+
self.width = size[0]
|
|
256
|
+
self.height = size[1]
|
|
257
|
+
try:
|
|
258
|
+
self.outline: _Outline = _Outline(outline)
|
|
259
|
+
except TypeError:
|
|
260
|
+
self.outline: _Outline = _Outline((False,))
|
|
261
|
+
|
|
262
|
+
class Items:
|
|
263
|
+
@staticmethod
|
|
264
|
+
def init(size: Tuple | tuple, *args, **kwargs) -> 'Items':
|
|
265
|
+
pyg.init()
|
|
266
|
+
surface = pyg.display.set_mode(size, vsync=1,*args, **kwargs)
|
|
267
|
+
return Items(surface)
|
|
268
|
+
def __init__(self, display: SurfaceType):
|
|
269
|
+
self.display = display
|
|
270
|
+
self.objects: list[_Object | _TextObject] = []
|
|
271
|
+
self.pixels: dict[tuple,list[_Object | _TextObject]] = {(0,0): []}
|
|
272
|
+
self.render = self.render(self)
|
|
273
|
+
def exist(self, name, x: int = None, y: int = None, literal: bool = False):
|
|
274
|
+
if x is None or y is None:
|
|
275
|
+
if isinstance(name, str):
|
|
276
|
+
for thing in self.objects:
|
|
277
|
+
if thing.name == name: return True
|
|
278
|
+
elif isinstance(name, _Object):
|
|
279
|
+
if literal:
|
|
280
|
+
if name in self.objects: return True
|
|
281
|
+
else:
|
|
282
|
+
for thing in self.objects:
|
|
283
|
+
if thing.name == name.name: return True
|
|
284
|
+
else:
|
|
285
|
+
raise TypeError("name must be a str or Object")
|
|
286
|
+
else:
|
|
287
|
+
if name is None:
|
|
288
|
+
return bool(self.pixels[(x, y)])
|
|
289
|
+
elif isinstance(name, str):
|
|
290
|
+
for pixel in self.pixels[(x, y)]:
|
|
291
|
+
if pixel.name == name: return True
|
|
292
|
+
elif isinstance(name, _Object):
|
|
293
|
+
if literal:
|
|
294
|
+
if name in self.pixels[(x, y)]: return True
|
|
295
|
+
else:
|
|
296
|
+
for pixel in self.pixels[(x, y)]:
|
|
297
|
+
if pixel.name == name.name and pixel.category == name.category: return True
|
|
298
|
+
else:
|
|
299
|
+
raise TypeError("name must be a str or Object")
|
|
300
|
+
return False
|
|
301
|
+
class render:
|
|
302
|
+
def __init__(self,selff):
|
|
303
|
+
self.parent = selff
|
|
304
|
+
def text(self,name,text,x,y,size,color="black",autoPos:bool=True,priority:int=10):
|
|
305
|
+
a = _TextObject(name, text, x, y, size, color, autoPos, priority)
|
|
306
|
+
self.parent.objects.append(a)
|
|
307
|
+
self.parent.objects = sorted(self.parent.objects, key=lambda x: x.priority)
|
|
308
|
+
return a
|
|
309
|
+
def box(self,name:str,x:int,y:int,width:int,height:int,color="white",outline=None):
|
|
310
|
+
a=_Object(name,x,y,(width,height),"rectangle",outline,color)
|
|
311
|
+
self.parent.objects.append(a)
|
|
312
|
+
self.parent.objects = sorted(self.parent.objects, key=lambda x: x.priority)
|
|
313
|
+
return a
|
|
314
|
+
def circle(self,name:str,x:int,y:int,radius:int,color="white",outline=(True,)):
|
|
315
|
+
a=_Object(name,x,y,radius,"circle",outline, color)
|
|
316
|
+
self.parent.objects.append(a)
|
|
317
|
+
self.parent.objects = sorted(self.parent.objects, key=lambda x: x.priority)
|
|
318
|
+
return a
|
|
319
|
+
|
|
320
|
+
def draw(self):
|
|
321
|
+
sw = self.display.get_width()
|
|
322
|
+
sh = self.display.get_height()
|
|
323
|
+
def getPos(item):
|
|
324
|
+
if item.x_ratio is not None:
|
|
325
|
+
return item.x_ratio * sw, item.y_ratio * sh
|
|
326
|
+
return item.x, item.y
|
|
327
|
+
for item in self.objects:
|
|
328
|
+
x, y = getPos(item)
|
|
329
|
+
if item.category == "text":
|
|
330
|
+
itemFont = pyg.font.Font(item.name, item.size)
|
|
331
|
+
itemSurface = itemFont.render(item.text, item.antiAlias, item.color)
|
|
332
|
+
self.display.blit(itemSurface, (x, y))
|
|
333
|
+
elif item.category == "rectangle":
|
|
334
|
+
pyg.draw.rect(self.display, item.color, (x, y, item.width, item.height))
|
|
335
|
+
if item.outline.enabled:
|
|
336
|
+
pyg.draw.rect(self.display, item.outline.color, (x, y, item.width, item.height),item.outline.thickness, item.outline.rounding)
|
|
337
|
+
elif item.category == "circle":
|
|
338
|
+
pyg.draw.circle(self.display, item.color, (x, y), item.radius)
|
|
339
|
+
if item.outline.enabled:
|
|
340
|
+
pyg.draw.circle(self.display, item.outline.color, (x, y), item.radius, item.outline.thickness)
|
|
341
|
+
def flip(self):
|
|
342
|
+
self.draw()
|
|
343
|
+
pyg.display.flip()
|
|
344
|
+
def update(self,items):
|
|
345
|
+
if isinstance(items, SurfaceType):
|
|
346
|
+
self.display = items
|
|
347
|
+
elif isinstance(items, tuple):
|
|
348
|
+
if all(isinstance(item,(_Object,_TextObject)) for item in items):
|
|
349
|
+
for item in items:
|
|
350
|
+
if item in self.objects: continue
|
|
351
|
+
self.objects.append(item)
|
|
352
|
+
self.objects = sorted(self.objects, key=lambda x: x.priority)
|
|
353
|
+
else:
|
|
354
|
+
raise TypeError("argument must be a tuple full of Objects or TextObjects")
|
|
355
|
+
else:
|
|
356
|
+
raise TypeError("argument must be a tuple or a surfaceType")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyutils
|