forked from Support/sp-hydra-veil-gui
313 lines
14 KiB
Python
Executable file
313 lines
14 KiB
Python
Executable file
import shlex
|
|
import subprocess
|
|
|
|
from PyQt6.QtCore import QThread, pyqtSignal
|
|
|
|
from core.controllers.ApplicationVersionController import ApplicationVersionController
|
|
from core.controllers.ClientController import ClientController
|
|
from core.controllers.ConfigurationController import ConfigurationController
|
|
|
|
from core.controllers.SyncController import new_sync
|
|
from core.controllers.InvoiceController import InvoiceController
|
|
from core.controllers.LocationController import LocationController
|
|
from core.controllers.ProfileController import ProfileController
|
|
from core.controllers.SubscriptionController import SubscriptionController
|
|
from core.controllers.SubscriptionPlanController import SubscriptionPlanController
|
|
from core.models.session.SessionConnection import SessionConnection
|
|
from core.models.session.SessionProfile import SessionProfile
|
|
from core.models.system.SystemConnection import SystemConnection
|
|
from core.models.BaseProfile import ProfileType
|
|
from core.models.system.SystemProfile import SystemProfile
|
|
from core.models.Result import ResultError
|
|
from core.services.helpers.install_dependencies import setup_singbox_binary as install_singbox_binary
|
|
|
|
from gui.v2.actions.profile_operations import execute_menu_task, synchronize
|
|
from gui.v2.actions.menu_results import MenuResult
|
|
|
|
from gui.v2.infrastructure.setup_observers import (
|
|
application_version_observer,
|
|
client_observer,
|
|
connection_observer,
|
|
invoice_observer,
|
|
profile_observer,
|
|
ticket_observer,
|
|
)
|
|
|
|
|
|
class WorkerThread(QThread):
|
|
text_output = pyqtSignal(str)
|
|
sync_output = pyqtSignal(list, list, bool, bool, list, list)
|
|
sync_error = pyqtSignal(str)
|
|
invoice_output = pyqtSignal(object, str)
|
|
invoice_finished = pyqtSignal(bool)
|
|
profiles_output = pyqtSignal(dict)
|
|
special_output = pyqtSignal(str)
|
|
finished = pyqtSignal(bool)
|
|
update_finished = pyqtSignal(dict)
|
|
|
|
def __init__(self, action=None, profile_data=None, profile_type=None, package_name=None, package_command=None):
|
|
super().__init__()
|
|
self.action = action
|
|
self.profile_data = profile_data
|
|
self.profile_type = profile_type
|
|
self.package_name = package_name
|
|
self.package_command = package_command
|
|
self.is_running = True
|
|
self.is_disabling = False
|
|
|
|
def run(self):
|
|
action_handlers = {
|
|
'LIST_PROFILES': self.list_profiles,
|
|
'CREATE_SESSION_PROFILE': lambda: self.create_profile(self.profile_type),
|
|
'CREATE_SYSTEM_PROFILE': lambda: self.create_profile(self.profile_type),
|
|
'GET_SUBSCRIPTION': self.get_subscription,
|
|
'DISABLE_PROFILE': self.disable_profile,
|
|
'SYNC': self.sync,
|
|
'SYNC_TOR': self.sync,
|
|
'DESTROY_PROFILE': self.destroy_profile,
|
|
'DISABLE_ALL_PROFILES': self.disable_all_profiles,
|
|
'INSTALL_PACKAGE': self.install_package,
|
|
'SETUP_SINGBOX_BINARY': self.setup_singbox_binary,
|
|
'CHECK_FOR_UPDATE': self.check_for_update,
|
|
'DOWNLOAD_UPDATE': self.download_update,
|
|
'CHECK_INVOICE_STATUS': self.check_invoice_status,
|
|
}
|
|
handler = action_handlers.get(self.action)
|
|
if handler is not None:
|
|
handler()
|
|
|
|
def check_invoice_status(self):
|
|
try:
|
|
invoice = InvoiceController.get(self.profile_data['billing_code'])
|
|
if invoice:
|
|
status = invoice.status
|
|
if status == "expired":
|
|
self.invoice_finished.emit(False)
|
|
else:
|
|
self.invoice_finished.emit(True)
|
|
else:
|
|
self.invoice_finished.emit(False)
|
|
except Exception as e:
|
|
print(f"Error retrieving invoice: {str(e)}")
|
|
self.invoice_finished.emit(False)
|
|
|
|
def download_update(self):
|
|
self._menu_operation({"task": "update"})
|
|
|
|
def check_for_update(self):
|
|
self.text_output.emit("Checking for updates...")
|
|
new_sync(client_observer=client_observer, connection_observer=connection_observer)
|
|
update_available = ClientController.can_be_updated()
|
|
if update_available:
|
|
self.text_output.emit("An update is available. Downloading...")
|
|
self.finished.emit(True)
|
|
else:
|
|
self.text_output.emit("No updates available.")
|
|
self.finished.emit(False)
|
|
|
|
def install_package(self):
|
|
try:
|
|
self.text_output.emit(f"Installing {self.package_name}...")
|
|
subprocess.run(shlex.split(self.package_command), check=True)
|
|
self.text_output.emit(f"{self.package_name} installed!")
|
|
self.finished.emit(True)
|
|
except subprocess.CalledProcessError:
|
|
self.text_output.emit("Installation failed")
|
|
self.finished.emit(False)
|
|
except Exception as e:
|
|
self.text_output.emit(
|
|
f"An error occurred when installing {self.package_name}: {e}")
|
|
self.finished.emit(False)
|
|
|
|
def setup_singbox_binary(self):
|
|
connection_error = "Connection problems downloading Singbox or related data. Please disable Tor or try again with a better connection."
|
|
try:
|
|
setup_result = install_singbox_binary(application_version_observer, connection_observer)
|
|
except ConnectionError as e:
|
|
self.text_output.emit(f"{connection_error}: {str(e)}")
|
|
self.finished.emit(False)
|
|
return
|
|
except ValueError as e:
|
|
self.text_output.emit(f"Your configuration files may be corrupted, or a server-side error gave bad data: {str(e)}")
|
|
self.finished.emit(False)
|
|
return
|
|
except Exception as e:
|
|
self.text_output.emit(f"Unknown error: {str(e)}")
|
|
self.finished.emit(False)
|
|
return
|
|
|
|
if setup_result.valid:
|
|
self.text_output.emit("Setup done. You're all set to proceed with Singbox.")
|
|
self.finished.emit(True)
|
|
return
|
|
|
|
messages = {
|
|
ResultError.NEED_SYNC: "You must sync to find out which Singbox version is supported.",
|
|
ResultError.FILE_SYSTEM: "Please check the configuration file, disk space, permissions, and filesystem health.",
|
|
ResultError.CONNECTION: connection_error,
|
|
ResultError.INVALID_INPUT: "This is a rare bug. Check the error logs, then run the program again from the terminal with DEBUG=true.",
|
|
ResultError.PERMISSION: "Singbox requires sudo for setup. After that, the wrapper allows it to run without sudo on an ongoing basis.",
|
|
ResultError.MISSING_FILE: "Singbox download or file setup did not complete. Please try again.",
|
|
ResultError.UNKNOWN: f"Unknown error: {setup_result.message}",
|
|
}
|
|
self.text_output.emit(messages.get(setup_result.error_type, setup_result.message or "Unknown Singbox setup error"))
|
|
self.finished.emit(False)
|
|
|
|
def disable_all_profiles(self):
|
|
self._menu_operation({"task": "disable_all", "connected_ids": self.profile_data})
|
|
|
|
def destroy_profile(self):
|
|
self._menu_operation({"task": "delete", "id": self.profile_data["id"], "confirmed": True})
|
|
|
|
def list_profiles(self):
|
|
try:
|
|
profiles = ProfileController.get_all()
|
|
except Exception:
|
|
profiles = {}
|
|
self.text_output.emit("Could not load profiles. Sync or restart and try again.")
|
|
self.profiles_output.emit(profiles)
|
|
|
|
def create_profile(self, profile_type):
|
|
try:
|
|
location = LocationController.get(
|
|
self.profile_data['country_code'], self.profile_data['code'])
|
|
except Exception:
|
|
location = None
|
|
if location is None:
|
|
self.text_output.emit(
|
|
f"Invalid location code: {self.profile_data['location_code']}")
|
|
return
|
|
|
|
profile_id = int(
|
|
self.profile_data['id']) if self.profile_data['id'] else None
|
|
name = self.profile_data['name']
|
|
connection_type = self.profile_data['connection_type']
|
|
|
|
if profile_type == "session":
|
|
|
|
application_details = self.profile_data['application'].split(
|
|
':', 1)
|
|
try:
|
|
application_version = ApplicationVersionController.get(
|
|
application_details[0], application_details[1] if len(application_details) > 1 else None)
|
|
except Exception:
|
|
application_version = None
|
|
if application_version is None:
|
|
self.text_output.emit(
|
|
f"Invalid application: {self.profile_data['application']}")
|
|
return
|
|
|
|
mask_connection = True if connection_type == 'tor' or connection_type == 'system' else False
|
|
resolution = self.profile_data['resolution']
|
|
connection = SessionConnection(connection_type, mask_connection)
|
|
profile = SessionProfile(
|
|
id=profile_id,
|
|
name=name,
|
|
subscription=None,
|
|
type=ProfileType.SESSION,
|
|
location=location,
|
|
resolution=resolution,
|
|
application_version=application_version,
|
|
connection=connection)
|
|
elif profile_type == "system":
|
|
connection = SystemConnection(connection_type)
|
|
profile = SystemProfile(
|
|
id=profile_id,
|
|
name=name,
|
|
type=ProfileType.SYSTEM,
|
|
subscription=None,
|
|
location=location,
|
|
connection=connection)
|
|
else:
|
|
self.text_output.emit(f"Invalid profile type: {profile_type}")
|
|
return
|
|
try:
|
|
ProfileController.create(
|
|
profile, profile_observer=profile_observer)
|
|
except Exception as e:
|
|
self.text_output.emit(
|
|
f"An error occurred when creating profile {profile.id}")
|
|
|
|
self.text_output.emit(
|
|
f"{profile_type.capitalize()} Profile created with ID: {profile.id}")
|
|
|
|
def disable_profile(self):
|
|
self._menu_operation({"task": "disable", "id": self.profile_data["id"]})
|
|
|
|
def _menu_operation(self, request):
|
|
result = execute_menu_task(request, self._emit_menu_result, None)
|
|
self._emit_menu_result(result)
|
|
self.finished.emit(result["status"] == "success")
|
|
|
|
def _emit_menu_result(self, result: MenuResult):
|
|
self.text_output.emit(result.get("message", ""))
|
|
|
|
def sync(self):
|
|
connection = {"SYNC_TOR": "tor", "SYNC": "system"}[self.action]
|
|
result = synchronize(connection)
|
|
self.sync_output.emit(*result["sync_data"])
|
|
if result["status"] == "error":
|
|
self.sync_error.emit(result["message"])
|
|
|
|
def get_connection(self):
|
|
connection = ConfigurationController.get_connection()
|
|
self.text_output.emit(f"Current connection: {connection}")
|
|
|
|
def set_connection(self):
|
|
ConfigurationController.set_connection(
|
|
self.profile_data['connection_type'])
|
|
self.text_output.emit(
|
|
f"Connection set to '{self.profile_data['connection_type']}'")
|
|
|
|
def get_subscription(self):
|
|
try:
|
|
invoice_observer.subscribe('retrieved', lambda event: self.handle_event(
|
|
event, self.profile_data['currency']))
|
|
invoice_observer.subscribe('processing', lambda event: self.text_output.emit(
|
|
'A payment has been detected and is being verified...'))
|
|
invoice_observer.subscribe('settled', lambda event: self.text_output.emit(
|
|
'The payment has been successfully verified.'))
|
|
|
|
profile = ProfileController.get(int(self.profile_data['id']))
|
|
subscription_plan = SubscriptionPlanController.get(
|
|
profile.connection, self.profile_data['duration'])
|
|
if subscription_plan is None:
|
|
self.text_output.emit(
|
|
'No compatible subscription plan was found.')
|
|
return
|
|
potential_subscription = SubscriptionController.create(
|
|
subscription_plan, profile, connection_observer=connection_observer)
|
|
if potential_subscription is not None:
|
|
ProfileController.attach_subscription(
|
|
profile, potential_subscription)
|
|
else:
|
|
self.text_output.emit(
|
|
'The subscription could not be created. Try again later.')
|
|
return
|
|
|
|
subscription = InvoiceController.handle_payment(
|
|
potential_subscription.billing_code, invoice_observer=invoice_observer, connection_observer=connection_observer)
|
|
if subscription is not None:
|
|
ProfileController.attach_subscription(profile, subscription)
|
|
self.text_output.emit(
|
|
'Successfully activated the subscription')
|
|
self.invoice_finished.emit(True)
|
|
else:
|
|
self.text_output.emit(
|
|
'The subscription could not be activated. Try again later.')
|
|
self.invoice_finished.emit(False)
|
|
|
|
except Exception as e:
|
|
self.text_output.emit('An unknown error occurred')
|
|
self.invoice_finished.emit(False)
|
|
|
|
def handle_connection_events(self, event):
|
|
self.text_output.emit(f'Profile disabled')
|
|
|
|
def handle_event(self, event, currency=None):
|
|
invoice = event.subject
|
|
if isinstance(invoice, object):
|
|
self.invoice_output.emit(invoice, '')
|
|
self.text_output.emit("Invoice generated. Awaiting payment...")
|
|
else:
|
|
self.text_output.emit("Invalid invoice data received.")
|