ConsoleFramework 1.3.1__tar.gz → 2.0.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.
@@ -0,0 +1,684 @@
1
+ """
2
+ ConsoleFramework - The best Console Library of the Python.
3
+ By: Suleiman
4
+ Copyright © Suleiman 2026
5
+ All rights are reserved.
6
+ """
7
+ import os
8
+ import sys
9
+ import time
10
+ import math
11
+ import random
12
+ import socket
13
+ import requests
14
+ import json
15
+ import threading
16
+ import platform
17
+ import zipfile
18
+ import stat
19
+ import subprocess as CMD
20
+ import threading
21
+
22
+ class Colors:
23
+ RESET = "\033[0m"
24
+ RED = "\033[31m"
25
+ GREEN = "\033[32m"
26
+ BLUE = "\033[34m"
27
+ CYAN = "\033[36m"
28
+ MAGENTA = "\033[35m"
29
+ YELLOW = "\033[33m"
30
+ WHITE = "\033[37m"
31
+ BLACK = "\033[30m"
32
+ BG_RED = "\033[41m"
33
+ BG_GREEN = "\033[42m"
34
+ BG_BLUE = "\033[44m"
35
+ BG_CYAN = "\033[46m"
36
+ BG_MAGENTA = "\033[45m"
37
+ BG_YELLOW = "\033[43m"
38
+ BG_WHITE = "\033[47m"
39
+ BG_BLACK = "\033[40m"
40
+ BOLD = "\033[1m"
41
+ DIM = "\033[2m"
42
+ ITALIC = "\033[3m"
43
+ UNDERLINE = "\033[4m"
44
+ BLINK = "\033[5m"
45
+ REVERSE = "\033[7m"
46
+ HIDDEN = "\033[8m"
47
+ STRIKE = "\033[9m"
48
+
49
+ @staticmethod
50
+ def rgb(r, g, b):
51
+ return f"\033[38;2;{r};{g};{b}m"
52
+
53
+ @staticmethod
54
+ def bg_rgb(r, g, b):
55
+ return f"\033[48;2;{r};{g};{b}m"
56
+
57
+ @staticmethod
58
+ def colorize(text, color, reset=True):
59
+ reset_code = Colors.RESET if reset else ""
60
+ return f"{color}{text}{reset_code}"
61
+
62
+ class Render:
63
+ @staticmethod
64
+ def DrawPic(points=None, width=80, height=25, clear_before_draw=False, default_char=' '):
65
+ if clear_before_draw:
66
+ os.system("cls" if os.name == "nt" else "clear")
67
+ field = [[default_char for _ in range(width)] for _ in range(height)]
68
+ if points:
69
+ for item in points:
70
+ if len(item) == 3:
71
+ x, y, char = item
72
+ if 0 <= x < width and 0 <= y < height:
73
+ field[y][x] = char
74
+ for row in field:
75
+ print(''.join(row))
76
+
77
+ @staticmethod
78
+ def get_line_points(x1, y1, x2, y2, char='#'):
79
+ points = []
80
+ dx = abs(x2 - x1)
81
+ dy = abs(y2 - y1)
82
+ sx = 1 if x1 < x2 else -1
83
+ sy = 1 if y1 < y2 else -1
84
+ err = dx - dy
85
+ while True:
86
+ points.append((x1, y1, char))
87
+ if x1 == x2 and y1 == y2:
88
+ break
89
+ e2 = err * 2
90
+ if e2 > -dy:
91
+ err -= dy
92
+ x1 += sx
93
+ if e2 < dx:
94
+ err += dx
95
+ y1 += sy
96
+ return points
97
+
98
+ @staticmethod
99
+ def get_circle_points(cx, cy, r, char='#'):
100
+ points = []
101
+ for angle in range(0, 360, 5):
102
+ rad = angle * math.pi / 180
103
+ x = int(cx + r * math.cos(rad))
104
+ y = int(cy + r * math.sin(rad))
105
+ points.append((x, y, char))
106
+ return points
107
+
108
+ @staticmethod
109
+ def get_filled_circle_points(cx, cy, r, char='#'):
110
+ points = []
111
+ for y in range(cy - r, cy + r + 1):
112
+ for x in range(cx - r, cx + r + 1):
113
+ if (x - cx) ** 2 + (y - cy) ** 2 <= r ** 2:
114
+ points.append((x, y, char))
115
+ return points
116
+
117
+ @staticmethod
118
+ def get_rect_points(x1, y1, x2, y2, char='#'):
119
+ points = []
120
+ for x in range(x1, x2 + 1):
121
+ points.append((x, y1, char))
122
+ points.append((x, y2, char))
123
+ for y in range(y1, y2 + 1):
124
+ points.append((x1, y, char))
125
+ points.append((x2, y, char))
126
+ return points
127
+
128
+ @staticmethod
129
+ def get_fill_rect_points(x1, y1, x2, y2, char='#'):
130
+ points = []
131
+ for y in range(y1, y2 + 1):
132
+ for x in range(x1, x2 + 1):
133
+ points.append((x, y, char))
134
+ return points
135
+
136
+ @staticmethod
137
+ def get_triangle_points(x1, y1, x2, y2, x3, y3, char='#'):
138
+ points = []
139
+ points.extend(Render.get_line_points(x1, y1, x2, y2, char))
140
+ points.extend(Render.get_line_points(x2, y2, x3, y3, char))
141
+ points.extend(Render.get_line_points(x3, y3, x1, y1, char))
142
+ return points
143
+
144
+ @staticmethod
145
+ def get_filled_triangle_points(x1, y1, x2, y2, x3, y3, char='#'):
146
+ points = []
147
+ min_x = min(x1, x2, x3)
148
+ max_x = max(x1, x2, x3)
149
+ min_y = min(y1, y2, y3)
150
+ max_y = max(y1, y2, y3)
151
+ for y in range(min_y, max_y + 1):
152
+ for x in range(min_x, max_x + 1):
153
+ if Render._point_in_triangle(x, y, x1, y1, x2, y2, x3, y3):
154
+ points.append((x, y, char))
155
+ return points
156
+
157
+ @staticmethod
158
+ def get_heart_points(cx, cy, size=5, char='#'):
159
+ points = []
160
+ for y in range(-size, size + 1):
161
+ for x in range(-size, size + 1):
162
+ if ((x * x + y * y - size * size) ** 3 <= size * size * x * x * y * y * 0.5):
163
+ points.append((cx + x, cy - y, char))
164
+ return points
165
+
166
+ @staticmethod
167
+ def get_star_points(cx, cy, radius=5, points_count=5, char='*'):
168
+ points = []
169
+ angle = -math.pi / 2
170
+ step = math.pi / points_count
171
+ outer_points = []
172
+ inner_points = []
173
+ for i in range(points_count * 2):
174
+ r = radius if i % 2 == 0 else radius * 0.4
175
+ x = cx + r * math.cos(angle + i * step)
176
+ y = cy + r * math.sin(angle + i * step)
177
+ if i % 2 == 0:
178
+ outer_points.append((x, y))
179
+ else:
180
+ inner_points.append((x, y))
181
+ for i in range(points_count):
182
+ p1 = outer_points[i]
183
+ p2 = inner_points[i]
184
+ p3 = outer_points[(i + 1) % points_count]
185
+ points.extend(Render.get_line_points(int(p1[0]), int(p1[1]), int(p2[0]), int(p2[1]), char))
186
+ points.extend(Render.get_line_points(int(p2[0]), int(p2[1]), int(p3[0]), int(p3[1]), char))
187
+ return points
188
+
189
+ @staticmethod
190
+ def get_wave_points(cx, cy, amplitude=5, length=20, char='~'):
191
+ points = []
192
+ for x in range(length):
193
+ y = cy + int(amplitude * math.sin(x * 0.5))
194
+ points.append((cx + x, y, char))
195
+ return points
196
+
197
+ @staticmethod
198
+ def get_spiral_points(cx, cy, turns=3, radius=10, step=0.1, char='*'):
199
+ points = []
200
+ angle = 0
201
+ r = 0
202
+ while r < radius:
203
+ x = int(cx + r * math.cos(angle))
204
+ y = int(cy + r * math.sin(angle))
205
+ points.append((x, y, char))
206
+ angle += step
207
+ r += step * 2
208
+ return points
209
+
210
+ @staticmethod
211
+ def get_bezier_points(x1, y1, x2, y2, x3, y3, steps=20, char='#'):
212
+ points = []
213
+ for t in range(steps + 1):
214
+ t = t / steps
215
+ x = (1 - t) ** 2 * x1 + 2 * (1 - t) * t * x2 + t ** 2 * x3
216
+ y = (1 - t) ** 2 * y1 + 2 * (1 - t) * t * y2 + t ** 2 * y3
217
+ points.append((int(x), int(y), char))
218
+ return points
219
+
220
+ @staticmethod
221
+ def get_grid_points(x1, y1, x2, y2, step=2, char='+'):
222
+ points = []
223
+ for x in range(x1, x2 + 1, step):
224
+ for y in range(y1, y2 + 1, step):
225
+ points.append((x, y, char))
226
+ return points
227
+
228
+ @staticmethod
229
+ def get_noise_points(cx, cy, radius, count=50, char='.'):
230
+ points = []
231
+ for _ in range(count):
232
+ angle = random.uniform(0, 2 * math.pi)
233
+ r = random.uniform(0, radius)
234
+ x = int(cx + r * math.cos(angle))
235
+ y = int(cy + r * math.sin(angle))
236
+ points.append((x, y, char))
237
+ return points
238
+
239
+ @staticmethod
240
+ def _point_in_triangle(px, py, x1, y1, x2, y2, x3, y3):
241
+ def sign(a, b, c):
242
+ return (a[0] - c[0]) * (b[1] - c[1]) - (b[0] - c[0]) * (a[1] - c[1])
243
+ p = (px, py)
244
+ d1 = sign(p, (x2, y2), (x1, y1))
245
+ d2 = sign(p, (x3, y3), (x2, y2))
246
+ d3 = sign(p, (x1, y1), (x3, y3))
247
+ has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0)
248
+ has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0)
249
+ return not (has_neg and has_pos)
250
+
251
+ @staticmethod
252
+ def _rotx(x, y, z, angle):
253
+ rad = math.radians(angle)
254
+ cos_a = math.cos(rad)
255
+ sin_a = math.sin(rad)
256
+ y_new = y * cos_a - z * sin_a
257
+ z_new = y * sin_a + z * cos_a
258
+ return x, y_new, z_new
259
+
260
+ @staticmethod
261
+ def _roty(x, y, z, angle):
262
+ rad = math.radians(angle)
263
+ cos_a = math.cos(rad)
264
+ sin_a = math.sin(rad)
265
+ x_new = x * cos_a + z * sin_a
266
+ z_new = -x * sin_a + z * cos_a
267
+ return x_new, y, z_new
268
+
269
+ @staticmethod
270
+ def _rotz(x, y, z, angle):
271
+ rad = math.radians(angle)
272
+ cos_a = math.cos(rad)
273
+ sin_a = math.sin(rad)
274
+ x_new = x * cos_a - y * sin_a
275
+ y_new = x * sin_a + y * cos_a
276
+ return x_new, y_new, z
277
+
278
+ @staticmethod
279
+ def _project_3d(x, y, z, rotx, roty, rotz, scale, cx, cy):
280
+ x, y, z = Render._rotx(x, y, z, rotx)
281
+ x, y, z = Render._roty(x, y, z, roty)
282
+ x, y, z = Render._rotz(x, y, z, rotz)
283
+ x2d = int(x * scale + cx)
284
+ y2d = int(-y * scale + cy)
285
+ return x2d, y2d
286
+
287
+ @staticmethod
288
+ def get_cube_3d_points(size=5, rotx=0, roty=0, rotz=0, scale=10, cx=40, cy=12, char='#'):
289
+ s = size / 2
290
+ vertices = [
291
+ (-s, -s, -s), (s, -s, -s), (s, s, -s), (-s, s, -s),
292
+ (-s, -s, s), (s, -s, s), (s, s, s), (-s, s, s)
293
+ ]
294
+ edges = [
295
+ (0,1), (1,2), (2,3), (3,0),
296
+ (4,5), (5,6), (6,7), (7,4),
297
+ (0,4), (1,5), (2,6), (3,7)
298
+ ]
299
+ points = []
300
+ projected = []
301
+ for v in vertices:
302
+ x2d, y2d = Render._project_3d(v[0], v[1], v[2], rotx, roty, rotz, scale, cx, cy)
303
+ projected.append((x2d, y2d))
304
+ for edge in edges:
305
+ x1, y1 = projected[edge[0]]
306
+ x2, y2 = projected[edge[1]]
307
+ points.extend(Render.get_line_points(x1, y1, x2, y2, char))
308
+ return points
309
+
310
+ class Animate:
311
+ @staticmethod
312
+ def clear_screen():
313
+ os.system("cls" if os.name == "nt" else "clear")
314
+
315
+ @staticmethod
316
+ def animate_print(text, delay=0.05, end="\n", flush=True, backspace_effect=False):
317
+ if backspace_effect:
318
+ for ch in text:
319
+ sys.stdout.write(ch)
320
+ sys.stdout.flush()
321
+ time.sleep(delay)
322
+ time.sleep(0.2)
323
+ for _ in text:
324
+ sys.stdout.write("\b \b")
325
+ sys.stdout.flush()
326
+ time.sleep(delay / 2)
327
+ for ch in text:
328
+ sys.stdout.write(ch)
329
+ sys.stdout.flush()
330
+ time.sleep(delay)
331
+ else:
332
+ for ch in text:
333
+ sys.stdout.write(ch)
334
+ if flush:
335
+ sys.stdout.flush()
336
+ time.sleep(delay)
337
+ sys.stdout.write(end)
338
+ if flush:
339
+ sys.stdout.flush()
340
+
341
+ @staticmethod
342
+ def spinning_cursor(message="Loading", duration=3.0, delay=0.1, frames=None, clear_before=False):
343
+ if clear_before:
344
+ Animate.clear_screen()
345
+ if frames is None:
346
+ frames = ["|", "/", "-", "\\"]
347
+ end_time = time.time() + duration
348
+ idx = 0
349
+ while time.time() < end_time:
350
+ frame = frames[idx % len(frames)]
351
+ sys.stdout.write(f"\r{message} {frame} ")
352
+ sys.stdout.flush()
353
+ time.sleep(delay)
354
+ idx += 1
355
+ sys.stdout.write("\r" + " " * (len(message) + 3) + "\r")
356
+ sys.stdout.flush()
357
+
358
+ @staticmethod
359
+ def countdown(seconds, message="Countdown", clear_before=False):
360
+ if clear_before:
361
+ Animate.clear_screen()
362
+ for remaining in range(seconds, 0, -1):
363
+ sys.stdout.write(f"\r{message}: {remaining:2d} seconds remaining ")
364
+ sys.stdout.flush()
365
+ time.sleep(1)
366
+ sys.stdout.write(f"\r{message}: 0 seconds remaining! Done!\n")
367
+ sys.stdout.flush()
368
+
369
+ class Keyboard:
370
+ @staticmethod
371
+ def GetKey():
372
+ try:
373
+ import termios
374
+ import tty
375
+ fd = sys.stdin.fileno()
376
+ old = termios.tcgetattr(fd)
377
+ try:
378
+ tty.setraw(fd)
379
+ return sys.stdin.read(1)
380
+ finally:
381
+ termios.tcsetattr(fd, termios.TCSADRAIN, old)
382
+ except:
383
+ import msvcrt
384
+ return msvcrt.getch().decode()
385
+
386
+ class ConsoleInfo:
387
+ @staticmethod
388
+ def GetConsole():
389
+ return sys.stdout
390
+
391
+ @staticmethod
392
+ def GetPipVer():
393
+ try:
394
+ result = CMD.run(['python', '-m', 'pip', '--version'], capture_output=True, text=True, check=True)
395
+ return result.stdout.strip()
396
+ except:
397
+ return "Error: pip not found"
398
+ class Process:
399
+ @staticmethod
400
+ def StartPyCode(code, wait=False, args=None, python="python"):
401
+ try:
402
+ cmd = [python, "-c", code]
403
+ if args:
404
+ cmd.extend(args)
405
+ if wait:
406
+ proc = CMD.run(cmd, capture_output=True, text=True)
407
+ return proc
408
+ else:
409
+ proc = CMD.Popen(cmd)
410
+ return proc
411
+ except Exception as e:
412
+ print(f"Error executing code: {e}")
413
+ return None
414
+
415
+ @staticmethod
416
+ def KillPyScript(pid):
417
+ try:
418
+ os.kill(pid, 15)
419
+ time.sleep(0.2)
420
+ return True
421
+ except ProcessLookupError:
422
+ print(f"Process {pid} not found")
423
+ return False
424
+ except PermissionError:
425
+ print(f"Permission denied to kill process {pid}")
426
+ return False
427
+ except Exception as e:
428
+ print(f"Error killing process: {e}")
429
+ return False
430
+
431
+ @staticmethod
432
+ def KillPyScriptForce(pid):
433
+ try:
434
+ os.kill(pid, 9)
435
+ return True
436
+ except ProcessLookupError:
437
+ print(f"Process {pid} not found")
438
+ return False
439
+ except PermissionError:
440
+ print(f"Permission denied to kill process {pid}")
441
+ return False
442
+ except Exception as e:
443
+ print(f"Error killing process: {e}")
444
+ return False
445
+
446
+ @staticmethod
447
+ def IsRunning(pid):
448
+ try:
449
+ os.kill(pid, 0)
450
+ return True
451
+ except ProcessLookupError:
452
+ return False
453
+ except PermissionError:
454
+ return True
455
+
456
+ @staticmethod
457
+ def GetProcessList():
458
+ try:
459
+ result = CMD.run(['ps', '-e', '-o', 'pid,comm'], capture_output=True, text=True)
460
+ lines = result.stdout.strip().split('\n')[1:]
461
+ processes = []
462
+ for line in lines:
463
+ parts = line.strip().split(' ', 1)
464
+ if len(parts) == 2:
465
+ pid = int(parts[0])
466
+ name = parts[1]
467
+ processes.append({'pid': pid, 'name': name})
468
+ return processes
469
+ except Exception:
470
+ return []
471
+
472
+ @staticmethod
473
+ def GetProcessInfo(pid):
474
+ try:
475
+ result = CMD.run(['ps', '-p', str(pid), '-o', 'pid,comm,%cpu,%mem,etime'], capture_output=True, text=True)
476
+ lines = result.stdout.strip().split('\n')
477
+ if len(lines) < 2:
478
+ return None
479
+ parts = lines[1].strip().split()
480
+ if len(parts) >= 5:
481
+ return {
482
+ 'pid': int(parts[0]),
483
+ 'name': parts[1],
484
+ 'cpu': parts[2],
485
+ 'memory': parts[3],
486
+ 'time': parts[4]
487
+ }
488
+ return None
489
+ except Exception:
490
+ return None
491
+
492
+ @staticmethod
493
+ def WaitForProcess(pid, timeout=None):
494
+ start_time = time.time()
495
+ while Process.IsRunning(pid):
496
+ if timeout and (time.time() - start_time) > timeout:
497
+ return False
498
+ time.sleep(0.5)
499
+ return True
500
+
501
+ @staticmethod
502
+ def GetCurrentPID():
503
+ return os.getpid()
504
+ class Channel:
505
+ def __init__(self):
506
+ self.connection = None
507
+ self.host = None
508
+ self.port = None
509
+ self.is_server = False
510
+ self.ngrok_process = None
511
+ self.ngrok_url = None
512
+ self.renew_thread = None
513
+ self.renew_active = False
514
+
515
+ @staticmethod
516
+ def GetPublicIP():
517
+ try:
518
+ return requests.get('https://api.ipify.org', timeout=5).text
519
+ except Exception:
520
+ return None
521
+
522
+ @staticmethod
523
+ def DownloadNgrok():
524
+ system = platform.system().lower()
525
+ arch = platform.machine().lower()
526
+
527
+ if system == 'windows':
528
+ url = 'https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-windows-amd64.zip'
529
+ exe_name = 'ngrok.exe'
530
+ elif system == 'linux':
531
+ if 'aarch64' in arch or 'arm64' in arch:
532
+ url = 'https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-arm64.zip'
533
+ else:
534
+ url = 'https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip'
535
+ exe_name = 'ngrok'
536
+ elif system == 'darwin':
537
+ url = 'https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-darwin-amd64.zip'
538
+ exe_name = 'ngrok'
539
+ else:
540
+ print(f"[Error] Unsupported platform: {system}")
541
+ return False
542
+
543
+ if os.path.exists(exe_name):
544
+ return True
545
+
546
+ print(f"[Ngrok] Downloading from {url}...")
547
+ try:
548
+ response = requests.get(url, stream=True)
549
+ with open('ngrok.zip', 'wb') as f:
550
+ for chunk in response.iter_content(chunk_size=8192):
551
+ f.write(chunk)
552
+
553
+ with zipfile.ZipFile('ngrok.zip', 'r') as zip_ref:
554
+ zip_ref.extractall('.')
555
+
556
+ os.remove('ngrok.zip')
557
+
558
+ if system != 'windows':
559
+ st = os.stat(exe_name)
560
+ os.chmod(exe_name, st.st_mode | stat.S_IEXEC)
561
+
562
+ print(f"[Ngrok] Downloaded successfully!")
563
+ return True
564
+ except Exception as e:
565
+ print(f"[Error] Failed to download ngrok: {e}")
566
+ return False
567
+
568
+ def CreateNgrokTunnel(self, port=54321, region='us'):
569
+ if not os.path.exists('ngrok') and not os.path.exists('ngrok.exe'):
570
+ print("[Ngrok] ngrok not found. Downloading...")
571
+ if not self.DownloadNgrok():
572
+ return None
573
+
574
+ try:
575
+ exe_name = 'ngrok.exe' if platform.system().lower() == 'windows' else 'ngrok'
576
+
577
+ cmd = f"./{exe_name} tcp {port} --region={region} --log=stdout --log-level=info"
578
+ if platform.system().lower() == 'windows':
579
+ cmd = f"{exe_name} tcp {port} --region={region} --log=stdout --log-level=info"
580
+
581
+ self.ngrok_process = CMD.Popen(cmd, shell=True, stdout=CMD.PIPE, stderr=CMD.PIPE)
582
+ time.sleep(3)
583
+
584
+ response = requests.get('http://localhost:4040/api/tunnels')
585
+ data = response.json()
586
+ if data['tunnels']:
587
+ self.ngrok_url = data['tunnels'][0]['public_url']
588
+ print(f"[Ngrok] Tunnel created: {self.ngrok_url}")
589
+ return self.ngrok_url
590
+ else:
591
+ print("[Ngrok] No tunnels found")
592
+ return None
593
+ except Exception as e:
594
+ print(f"[Ngrok] Error: {e}")
595
+ return None
596
+
597
+ def StopNgrokTunnel(self):
598
+ if self.ngrok_process:
599
+ self.ngrok_process.terminate()
600
+ self.ngrok_process = None
601
+ self.ngrok_url = None
602
+ print("[Ngrok] Tunnel stopped")
603
+
604
+ def _renew_loop(self, port, region, interval):
605
+ while self.renew_active:
606
+ time.sleep(interval)
607
+ self.StopNgrokTunnel()
608
+ self.CreateNgrokTunnel(port, region)
609
+ print("[Ngrok] Tunnel renewed")
610
+
611
+ def StartAutoRenew(self, port=54321, region='us', interval=7000):
612
+ self.renew_active = True
613
+ self.renew_thread = threading.Thread(target=self._renew_loop, args=(port, region, interval))
614
+ self.renew_thread.daemon = True
615
+ self.renew_thread.start()
616
+
617
+ def StopAutoRenew(self):
618
+ self.renew_active = False
619
+ if self.renew_thread:
620
+ self.renew_thread.join(timeout=1)
621
+
622
+ def CreateServer(self, port=54321, ngrok=False, region='us', auto_renew=False, renew_interval=7000):
623
+ self.is_server = True
624
+ self.port = port
625
+
626
+ if ngrok:
627
+ self.CreateNgrokTunnel(port, region)
628
+ if auto_renew:
629
+ self.StartAutoRenew(port, region, renew_interval)
630
+
631
+ try:
632
+ server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
633
+ server.bind(('0.0.0.0', port))
634
+ server.listen(1)
635
+ self.connection, addr = server.accept()
636
+ return self.connection
637
+ except Exception as e:
638
+ print(f"[Error] Server failed: {e}")
639
+ return None
640
+
641
+ def Connect(self, host='127.0.0.1', port=54321):
642
+ self.host = host
643
+ self.port = port
644
+ try:
645
+ self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
646
+ self.connection.connect((host, port))
647
+ return self.connection
648
+ except Exception as e:
649
+ print(f"[Error] Connection failed: {e}")
650
+ return None
651
+
652
+ def ConnectPublic(self, port=54321):
653
+ public_ip = Channel.GetPublicIP()
654
+ if not public_ip:
655
+ print("[Error] Could not get public IP")
656
+ return None
657
+ return self.Connect(host=public_ip, port=port)
658
+
659
+ def Disconnect(self):
660
+ if self.connection:
661
+ self.connection.close()
662
+ self.connection = None
663
+ if self.ngrok_process:
664
+ self.StopNgrokTunnel()
665
+
666
+ def Send(self, message):
667
+ if not self.connection:
668
+ return False
669
+ try:
670
+ if isinstance(message, str):
671
+ message = message.encode()
672
+ self.connection.send(message + b'\n')
673
+ return True
674
+ except Exception:
675
+ return False
676
+
677
+ def Receive(self):
678
+ if not self.connection:
679
+ return None
680
+ try:
681
+ data = self.connection.recv(1024).decode().strip()
682
+ return data
683
+ except Exception:
684
+ return None
@@ -1,18 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ConsoleFramework
3
- Version: 1.3.1
3
+ Version: 2.0.0
4
4
  Summary: The best Console Library of the Python.
5
5
  Author: Suleiman
6
6
  Author-email: steal.apet@mail.ru
7
7
  License: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
8
11
  Requires-Python: >=3.6
9
12
  Description-Content-Type: text/plain
10
13
  Dynamic: author
11
14
  Dynamic: author-email
15
+ Dynamic: classifier
12
16
  Dynamic: description
13
17
  Dynamic: description-content-type
14
18
  Dynamic: license
15
19
  Dynamic: requires-python
16
20
  Dynamic: summary
17
21
 
18
- ConsoleFramework - C++ library for Python console rendering.
22
+ ConsoleFramework - Pure Python library for console rendering, animations, processes and more.