radical change

This commit is contained in:
Zenaku 2026-06-05 10:33:27 -05:00
parent cd4ee8aaaa
commit 445bd65b4a
3 changed files with 204 additions and 7 deletions

View file

@ -117,11 +117,10 @@ def handle(arguments, main_parser):
main_parser.error('sing-box is not installed. Please run the installation script first.') main_parser.error('sing-box is not installed. Please run the installation script first.')
except (InvalidSubscriptionError, MissingSubscriptionError) as exception: except (InvalidSubscriptionError, MissingSubscriptionError) as exception:
_handle_subscription_error(exception, profile, ignore, arguments, main_parser) _handle_subscription_error(exception, profile, ignore, arguments, main_parser)
elif arguments.subcommand == 'disable': elif arguments.subcommand == 'disable':
profile = ProfileController.get(arguments.id) profile = ProfileController.get(arguments.id)
if profile is not None: if profile is not None:
ProfileController.disable(profile, ignore=ignore, profile_observer=profile_observer) ProfileController.disable(profile, ignore=ignore, profile_observer=profile_observer, connection_observer=connection_observer)
else: else:
main_parser.error('the following argument should be a valid reference: --id/-i') main_parser.error('the following argument should be a valid reference: --id/-i')

View file

@ -4,6 +4,7 @@ from core.observers.ClientObserver import ClientObserver
from core.observers.ConnectionObserver import ConnectionObserver from core.observers.ConnectionObserver import ConnectionObserver
from core.observers.InvoiceObserver import InvoiceObserver from core.observers.InvoiceObserver import InvoiceObserver
from core.observers.ProfileObserver import ProfileObserver from core.observers.ProfileObserver import ProfileObserver
from cli.ui import Spinner, label_ok, label_error, label_warn, label_connected, label_disconnect, progress_bar
from cli.helpers import sanitize_profile from cli.helpers import sanitize_profile
import pprint import pprint
@ -22,10 +23,63 @@ client_observer.subscribe('updating', lambda event: print('Updating client...'))
client_observer.subscribe('update_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r')) client_observer.subscribe('update_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r'))
client_observer.subscribe('updated', lambda event: print('\n')) client_observer.subscribe('updated', lambda event: print('\n'))
connection_observer.subscribe('connecting', lambda event: print(f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...\n')) _spinner = None
connection_observer.subscribe('tor_bootstrapping', lambda event: print('Bootstrapping Tor...'))
connection_observer.subscribe('tor_bootstrap_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r')) def _on_connecting(event):
connection_observer.subscribe('tor_bootstrapped', lambda event: print('\n')) global _spinner
attempt = event.subject.get("attempt_count", "?")
total = event.subject.get("maximum_number_of_attempts", "?")
_spinner = Spinner(f"Connecting... [{attempt}/{total}]")
_spinner.start()
def _on_connected(event):
global _spinner
if _spinner:
_spinner.stop()
_spinner = None
d = event.subject or {}
label_connected(f"proxy={d.get('proxy_ip','?')} real={d.get('real_ip','?')} port={d.get('socks5_port','?')}")
def _on_disconnected(event):
global _spinner
if _spinner:
_spinner.stop()
_spinner = None
d = event.subject or {}
ip = d.get("ip", "?")
label_disconnect(f"real={ip}")
def _on_error(event):
global _spinner
if _spinner:
_spinner.stop()
_spinner = None
label_error(str(event.subject or "Unknown error"))
def _on_tor_bootstrapping(event):
global _spinner
_spinner = Spinner("Starting Tor...")
_spinner.start()
def _on_tor_bootstrap_progressing(event):
pct = int(event.meta.get("progress", 0))
progress_bar(pct, 100, "Tor bootstrap")
def _on_tor_bootstrapped(event):
global _spinner
if _spinner:
_spinner.stop()
_spinner = None
print()
label_ok("Tor ready")
connection_observer.subscribe('connecting', _on_connecting)
connection_observer.subscribe('connected', _on_connected)
connection_observer.subscribe('disconnected', _on_disconnected)
connection_observer.subscribe('error', _on_error)
connection_observer.subscribe('tor_bootstrapping', _on_tor_bootstrapping)
connection_observer.subscribe('tor_bootstrap_progressing', _on_tor_bootstrap_progressing)
connection_observer.subscribe('tor_bootstrapped', _on_tor_bootstrapped)
invoice_observer.subscribe('retrieved', lambda event: print(f'\n{pprint.pp(event.subject)}\n')) invoice_observer.subscribe('retrieved', lambda event: print(f'\n{pprint.pp(event.subject)}\n'))
invoice_observer.subscribe('processing', lambda event: print('A payment has been detected and is being verified...\n')) invoice_observer.subscribe('processing', lambda event: print('A payment has been detected and is being verified...\n'))
@ -34,4 +88,4 @@ invoice_observer.subscribe('settled', lambda event: print('The payment has been
profile_observer.subscribe('created', lambda event: pprint.pp((sanitize_profile(event.subject), 'Created'))) profile_observer.subscribe('created', lambda event: pprint.pp((sanitize_profile(event.subject), 'Created')))
profile_observer.subscribe('destroyed', lambda event: pprint.pp((sanitize_profile(event.subject), 'Destroyed'))) profile_observer.subscribe('destroyed', lambda event: pprint.pp((sanitize_profile(event.subject), 'Destroyed')))
profile_observer.subscribe('disabled', lambda event: pprint.pp((sanitize_profile(event.subject), 'Disabled')) if event.meta.get('explicitly') else None) profile_observer.subscribe('disabled', lambda event: pprint.pp((sanitize_profile(event.subject), 'Disabled')) if event.meta.get('explicitly') else None)
profile_observer.subscribe('enabled', lambda event: pprint.pp((sanitize_profile(event.subject), 'Enabled'))) profile_observer.subscribe('enabled', lambda event: pprint.pp((sanitize_profile(event.subject), 'Enabled')))

144
cli/ui.py Normal file
View file

@ -0,0 +1,144 @@
import sys
import threading
import time
RESET = "\033[0m"
BOLD = "\033[1m"
DIM = "\033[2m"
RED = "\033[0;31m"
GREEN = "\033[0;32m"
YELLOW = "\033[0;33m"
BLUE = "\033[0;34m"
CYAN = "\033[0;36m"
WHITE = "\033[0;37m"
BRED = "\033[1;31m"
BGREEN = "\033[1;32m"
BYELLOW = "\033[1;33m"
BBLUE = "\033[1;34m"
BCYAN = "\033[1;36m"
BWHITE = "\033[1;37m"
def label_ok(msg: str):
print(f" {BGREEN}[ OK ]{RESET} {msg}")
def label_warn(msg: str):
print(f" {BYELLOW}[ WARN ]{RESET} {msg}")
def label_error(msg: str):
print(f" {BRED}[ ERROR]{RESET} {msg}")
def label_info(msg: str):
print(f" {BCYAN}[ INFO ]{RESET} {msg}")
def label_skip(msg: str):
print(f" {DIM}[ SKIP ]{RESET} {msg}")
def label_connected(msg: str):
print(f" {BGREEN}[CONNECTED ]{RESET} {msg}")
def label_disconnect(msg: str):
print(f" {BRED}[DISCONNECTED]{RESET} {msg}")
def label_step(msg: str, current: int, total: int):
print(f" {BBLUE}[ {current:<2}/{total:<2} ]{RESET} {msg}")
def print_header(title: str, version: str = ""):
width = 60
line = "" * width
ver = f"{DIM}{version}{RESET}" if version else ""
print()
print(f"{BCYAN}{line}{RESET}")
print(f"{BCYAN} {'':<56}{RESET}")
print(f"{BCYAN} {BWHITE}{title} {ver}{'':<40}{BCYAN} {RESET}")
print(f"{BCYAN} {'':<56}{RESET}")
print(f"{BCYAN}{line}{RESET}")
print()
def print_section(label: str):
print()
print(f"{DIM}{CYAN}┌─{RESET} {BWHITE}{label}{RESET}")
def print_divider():
print(f"{DIM}{'' * 60}{RESET}")
def progress_bar(current: int, total: int, label: str = "", width: int = 40):
filled = int(current * width / total)
empty = width - filled
bar = "#" * filled + "·" * empty
pct = int(current * 100 / total)
sys.stdout.write(
f"\r {DIM}[{RESET}{BGREEN}{bar}{RESET}{DIM}]{RESET}"
f" {BWHITE}{pct:3d}%{RESET} {DIM}{label}{RESET} "
)
sys.stdout.flush()
def progress_simulate(steps: int = 20, label: str = "loading", delay: float = 0.04):
for i in range(1, steps + 1):
progress_bar(i, steps, label)
time.sleep(delay)
print()
class Spinner:
FRAMES = ("", "\\", "", "/")
def __init__(self, msg: str = "Processing..."):
self.msg = msg
self._stop_event = threading.Event()
self._thread: threading.Thread | None = None
def start(self):
self._stop_event.clear()
self._thread = threading.Thread(target=self._spin, daemon=True)
self._thread.start()
return self
def stop(self, result_msg: str = ""):
self._stop_event.set()
if self._thread:
self._thread.join()
sys.stdout.write(f"\r{' ' * 60}\r")
sys.stdout.flush()
if result_msg:
label_ok(result_msg)
def _spin(self):
i = 0
while not self._stop_event.is_set():
frame = self.FRAMES[i % len(self.FRAMES)]
sys.stdout.write(
f"\r {BCYAN}{frame}{RESET} {DIM}{self.msg}{RESET} "
)
sys.stdout.flush()
i += 1
time.sleep(0.1)
def __enter__(self):
self.start()
return self
def __exit__(self, *_):
self.stop()
def wait_with_spinner(msg: str, fn, *args, result_msg: str = "", **kwargs):
result_box = [None, None]
def _run():
try:
result_box[0] = fn(*args, **kwargs)
except Exception as e:
result_box[1] = e
s = Spinner(msg)
s.start()
t = threading.Thread(target=_run, daemon=True)
t.start()
t.join()
s.stop(result_msg)
if result_box[1] is not None:
raise result_box[1]
return result_box[0]