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.
- consoleframework-2.0.0/ConsoleFramework/__init__.py +684 -0
- {consoleframework-1.3.1 → consoleframework-2.0.0}/ConsoleFramework.egg-info/PKG-INFO +6 -2
- {consoleframework-1.3.1 → consoleframework-2.0.0}/ConsoleFramework.egg-info/SOURCES.txt +1 -4
- {consoleframework-1.3.1 → consoleframework-2.0.0}/PKG-INFO +6 -2
- consoleframework-2.0.0/setup.py +20 -0
- consoleframework-1.3.1/ConsoleFramework/__init__.py +0 -27
- consoleframework-1.3.1/ConsoleFramework/core.cpp +0 -1355
- consoleframework-1.3.1/ConsoleFramework/core.cpython-313-aarch64-linux-android.so +0 -0
- consoleframework-1.3.1/setup.py +0 -26
- {consoleframework-1.3.1 → consoleframework-2.0.0}/ConsoleFramework.egg-info/dependency_links.txt +0 -0
- {consoleframework-1.3.1 → consoleframework-2.0.0}/ConsoleFramework.egg-info/not-zip-safe +0 -0
- {consoleframework-1.3.1 → consoleframework-2.0.0}/ConsoleFramework.egg-info/top_level.txt +0 -0
- {consoleframework-1.3.1 → consoleframework-2.0.0}/MANIFEST.in +0 -0
- {consoleframework-1.3.1 → consoleframework-2.0.0}/setup.cfg +0 -0
|
@@ -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:
|
|
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 -
|
|
22
|
+
ConsoleFramework - Pure Python library for console rendering, animations, processes and more.
|