add commands

This commit is contained in:
Zenaku 2026-05-30 08:25:00 -05:00
parent 8201bd3b0a
commit ffc6100660
11 changed files with 530 additions and 473 deletions

View file

@ -1,491 +1,48 @@
from core.Constants import Constants
from core.Errors import MissingSubscriptionError, InvalidSubscriptionError, UnknownConnectionTypeError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError
from core.controllers.ApplicationController import ApplicationController
from core.controllers.ApplicationVersionController import ApplicationVersionController
from core.controllers.ClientController import ClientController
from core.controllers.ClientVersionController import ClientVersionController
from core.controllers.ConfigurationController import ConfigurationController
from core.controllers.InvoiceController import InvoiceController
from core.controllers.LocationController import LocationController
from core.controllers.PolicyController import PolicyController
from core.controllers.ProfileController import ProfileController
from core.controllers.SubscriptionController import SubscriptionController
from core.controllers.SubscriptionPlanController import SubscriptionPlanController
from core.models.session.Application import Application
from core.models.session.SessionConnection import SessionConnection
from core.models.session.SessionProfile import SessionProfile
from core.models.system.SystemConnection import SystemConnection
from core.models.system.SystemProfile import SystemProfile
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
from core.observers.ClientObserver import ClientObserver
from core.observers.ConnectionObserver import ConnectionObserver
from core.observers.InvoiceObserver import InvoiceObserver
from core.observers.ProfileObserver import ProfileObserver
from importlib import metadata
from core.Errors import UnknownConnectionTypeError
from cli.helpers import get_distribution
from cli.commands import all_commands
from cli.commands import get_set, sync_update
from pathlib import Path
from typing import Optional, Union
import argparse
import pprint
import sys
def _handle_exception(identifier, message, traceback):
if issubclass(identifier, UnknownConnectionTypeError):
print('Please specify the desired connection method and try again.\n')
else:
sys.__excepthook__(identifier, message, traceback)
if __name__ == '__main__':
Path(Constants.HV_CONFIG_HOME).mkdir(parents=True, exist_ok=True)
Path(Constants.HV_DATA_HOME).mkdir(parents=True, exist_ok=True)
application_version_observer = ApplicationVersionObserver()
client_observer = ClientObserver()
connection_observer = ConnectionObserver()
invoice_observer = InvoiceObserver()
profile_observer = ProfileObserver()
sys.excepthook = _handle_exception
application_version_observer.subscribe('downloading', lambda event: print(f'Downloading {ApplicationController.get(event.subject.application_code).name}, version {event.subject.version_number}...'))
application_version_observer.subscribe('download_progressing', lambda event: print(f'Current progress: {event.meta.get('progress'):.2f}%', flush=True, end='\r'))
application_version_observer.subscribe('downloaded', lambda event: print('\n'))
distribution = get_distribution()
client_observer.subscribe('synchronizing', lambda event: print('Synchronizing...\n'))
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('updated', lambda event: print('\n'))
parser = argparse.ArgumentParser(prog=distribution.name)
parser.add_argument('--version', '-v', action='version', version=f'{distribution.name} v{distribution.version}')
subparsers = parser.add_subparsers(title='commands', dest='command')
connection_observer.subscribe('connecting', lambda event: print(f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...\n'))
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'))
connection_observer.subscribe('tor_bootstrapped', lambda event: print('\n'))
for command in all_commands:
command.register(subparsers)
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('settled', lambda event: print('The payment has been successfully verified.\n'))
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('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')))
def __get_distribution():
for candidate in metadata.distributions():
if 'cli' not in candidate.name:
continue
candidate_files = candidate.files
if candidate_files is None:
continue
for distribution_file in candidate_files:
if distribution_file.parts[0] == 'cli':
return candidate
return None
def __parse_composite_argument(argument: str, first_key: str, second_key: str, separator: str = ':'):
return dict(zip([first_key, second_key], argument.split(separator) + ['']))
def __parse_application_argument(application_argument: str):
return __parse_composite_argument(application_argument, 'application_code', 'version_number')
def __parse_location_argument(location_argument: str):
return __parse_composite_argument(location_argument, 'country_code', 'code')
def __sanitize_profile(candidate: Optional[Union[SessionProfile, SystemProfile]]):
if candidate is not None and candidate.has_subscription():
sanitized_billing_code = candidate.subscription.get_sanitized_billing_code()
candidate.subscription.billing_code = sanitized_billing_code
return candidate
def __handle_exception(identifier, message, traceback):
if issubclass(identifier, UnknownConnectionTypeError):
print('Please specify the desired connection method and try again.\n')
else:
sys.__excepthook__(identifier, message, traceback)
sys.excepthook = __handle_exception
distribution = __get_distribution()
pristine_parser = argparse.ArgumentParser(add_help=False)
pristine_parser.add_argument('--pristine', '-p', action='store_true')
connection_protection_parser = argparse.ArgumentParser(add_help=False)
connection_protection_parser.add_argument('--without-connection-protection', action='store_true')
endpoint_verification_parser = argparse.ArgumentParser(add_help=False)
endpoint_verification_parser.add_argument('--without-endpoint-verification', action='store_true')
profile_state_protection_parser = argparse.ArgumentParser(add_help=False)
profile_state_protection_parser.add_argument('--without-profile-state-protection', action='store_true')
main_parser = argparse.ArgumentParser(prog=distribution.name)
main_parser.add_argument('--version', '-v', action='version', version=f'{distribution.name} v{distribution.version}')
main_subparsers = main_parser.add_subparsers(title='commands', dest='command')
profile_parser = main_subparsers.add_parser('profile')
profile_subparsers = profile_parser.add_subparsers(title='subcommands', dest='subcommand')
profile_base_parser = argparse.ArgumentParser(add_help=False)
profile_base_parser.add_argument('--id', '-i', type=int, required=True)
profile_subparsers.add_parser('list')
profile_subparsers.add_parser('show', parents=[profile_base_parser])
profile_create_parser = profile_subparsers.add_parser('create')
profile_create_subparsers = profile_create_parser.add_subparsers(title='profile_types', dest='profile_type')
session_profile_create_parser = profile_create_subparsers.add_parser('session', parents=[profile_base_parser])
session_profile_create_parser.add_argument('--name', '-n', default='')
session_profile_create_parser.add_argument('--location', '-l', default='')
session_profile_create_parser.add_argument('--application', '-a', required=True)
session_profile_create_parser.add_argument('--connection', '-c', dest='connection_type', choices=['system', 'tor', 'wireguard'], default='system')
session_profile_create_parser.add_argument('--mask-connection', '-m', action='store_true')
session_profile_create_parser.add_argument('--resolution', '-r', default='1280x720')
system_profile_create_parser = profile_create_subparsers.add_parser('system', parents=[profile_base_parser])
system_profile_create_parser.add_argument('--name', '-n', default='')
system_profile_create_parser.add_argument('--location', '-l', default='')
system_profile_create_parser.add_argument('--connection', '-c', dest='connection_type', choices=['wireguard'], default='wireguard')
profile_subparsers.add_parser('destroy', parents=[profile_base_parser])
profile_subparsers.add_parser('enable', parents=[profile_base_parser, pristine_parser, connection_protection_parser, endpoint_verification_parser, profile_state_protection_parser])
profile_subparsers.add_parser('disable', parents=[profile_base_parser, connection_protection_parser])
application_parser = main_subparsers.add_parser('application')
application_subparsers = application_parser.add_subparsers(title='subcommands', dest='subcommand')
application_base_parser = argparse.ArgumentParser(add_help=False)
application_base_parser.add_argument('--application', '-a', required=True)
application_list_parser = application_subparsers.add_parser('list')
application_list_parser.add_argument('--code', '-c')
application_show_parser = application_subparsers.add_parser('show', parents=[application_base_parser])
application_install_parser = application_subparsers.add_parser('install', parents=[application_base_parser])
application_install_parser.add_argument('--reinstall', '-r', action='store_true')
application_uninstall_parser = application_subparsers.add_parser('uninstall', parents=[application_base_parser])
policy_parser = main_subparsers.add_parser('policy')
policy_subparsers = policy_parser.add_subparsers(title='subcommands', dest='subcommand')
policy_base_parser = argparse.ArgumentParser(add_help=False)
policy_base_parser.add_argument('--policy', '-p', choices=['capability', 'privilege'], required=True)
policy_preview_parser = policy_subparsers.add_parser('preview', parents=[policy_base_parser])
policy_instate_parser = policy_subparsers.add_parser('instate', parents=[policy_base_parser])
policy_inspect_parser = policy_subparsers.add_parser('inspect', parents=[policy_base_parser])
policy_revoke_parser = policy_subparsers.add_parser('revoke', parents=[policy_base_parser])
get_parser = main_subparsers.add_parser('get')
get_subparsers = get_parser.add_subparsers(title='subcommands', dest='subcommand')
get_connection_parser = get_subparsers.add_parser('connection')
get_endpoint_verification_parser = get_subparsers.add_parser('endpoint_verification')
set_parser = main_subparsers.add_parser('set')
set_subparsers = set_parser.add_subparsers(title='subcommands', dest='subcommand')
set_connection_parser = set_subparsers.add_parser('connection')
set_connection_parser.add_argument('connection_type', choices=['system', 'tor'])
set_endpoint_verification_parser = set_subparsers.add_parser('endpoint_verification')
set_endpoint_verification_parser.add_argument('endpoint_verification_state', choices=['enabled', 'disabled'])
sync_parser = main_subparsers.add_parser('sync')
update_parser = main_subparsers.add_parser('update')
arguments = main_parser.parse_args()
ignore = []
if getattr(arguments, 'without_connection_protection', False):
ignore.append(ConnectionUnprotectedError)
if getattr(arguments, 'without_endpoint_verification', False):
ignore.append(EndpointVerificationError)
if getattr(arguments, 'without_profile_state_protection', False):
ignore.append(ProfileStateConflictError)
ignore = tuple(ignore)
arguments = parser.parse_args()
if arguments.command is None:
main_parser.print_help()
parser.print_help()
elif arguments.command == 'profile':
elif arguments.command in (get_set.NAME, get_set.NAME_SET):
get_set.handle(arguments, parser)
if arguments.subcommand is None:
profile_parser.print_help()
elif arguments.subcommand == 'list':
profiles = ProfileController.get_all()
for key, value in profiles.items():
profiles[key] = __sanitize_profile(value)
pprint.pp(profiles)
elif arguments.subcommand == 'show':
pprint.pp(ProfileController.get(arguments.id))
elif arguments.subcommand == 'create':
location_details = __parse_location_argument(arguments.location)
location = LocationController.get(location_details.get('country_code'), location_details.get('code'))
if location is None:
main_parser.error('the following argument should be a valid reference: --location/-l')
if arguments.profile_type == 'session':
application_version_details = __parse_application_argument(arguments.application)
application_version = ApplicationVersionController.get(application_version_details.get('application_code'), application_version_details.get('version_number'))
if application_version is None:
main_parser.error('the following argument should be a valid reference: --application/-a')
connection = SessionConnection(arguments.connection_type, arguments.mask_connection)
profile = SessionProfile(arguments.id, arguments.name, None, location, arguments.resolution, application_version, connection)
ProfileController.create(profile, profile_observer=profile_observer)
elif arguments.command in (sync_update.NAME_SYNC, sync_update.NAME_UPDATE):
sync_update.handle(arguments, parser)
else:
connection = SystemConnection(arguments.connection_type)
profile = SystemProfile(arguments.id, arguments.name, None, location, connection)
ProfileController.create(profile, profile_observer=profile_observer)
elif arguments.subcommand == 'destroy':
profile = ProfileController.get(arguments.id)
if profile is not None:
ProfileController.destroy(profile, profile_observer=profile_observer)
else:
main_parser.error('the following argument should be a valid reference: --id/-i')
elif arguments.subcommand == 'enable':
profile = ProfileController.get(arguments.id)
if profile is not None:
try:
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer)
except (InvalidSubscriptionError, MissingSubscriptionError) as exception:
if type(exception).__name__ == 'InvalidSubscriptionError':
print('The profile\'s subscription appears to be invalid.\n')
elif type(exception).__name__ == 'MissingSubscriptionError':
print('The profile is not tied to a subscription.\n')
manage_subscription_input = None
while manage_subscription_input not in ('1', '2', '3', ''):
print('Please select from the following:\n')
print(' 1) Request new subscription')
print(' 2) Enter billing code')
print('\n 3) Exit')
manage_subscription_input = input('\nEnter your choice [1]: ')
if manage_subscription_input == '1' or manage_subscription_input == '':
print('\nCreating subscription...\n')
subscription_plan = SubscriptionPlanController.get(profile.connection, 720)
if subscription_plan is None:
raise RuntimeError('No compatible subscription plan was found. Please contact support.')
potential_subscription = SubscriptionController.create(subscription_plan, profile, connection_observer=connection_observer)
if potential_subscription is not None:
ProfileController.attach_subscription(profile, potential_subscription)
else:
raise RuntimeError('The subscription could not be created. Please try again later.')
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)
else:
raise RuntimeError('The subscription could not be activated. Please try again later.')
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer)
elif manage_subscription_input == '2':
billing_code = input('\nEnter your billing code: ')
print()
subscription = SubscriptionController.get(billing_code, connection_observer=connection_observer)
if subscription is not None:
ProfileController.attach_subscription(profile, subscription)
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer)
else:
print('\nThe billing code appears to be invalid.\n')
manage_subscription_input = None
elif manage_subscription_input == '3':
pass
else:
print('\nInput appears to be invalid. Please try again.\n')
else:
main_parser.error('the following argument should be a valid reference: --id/-i')
elif arguments.subcommand == 'disable':
profile = ProfileController.get(arguments.id)
if profile is not None:
ProfileController.disable(profile, ignore=ignore, profile_observer=profile_observer)
else:
main_parser.error('the following argument should be a valid reference: --id/-i')
elif arguments.command == 'application':
if arguments.subcommand is None:
application_parser.print_help()
elif arguments.subcommand == 'list':
if arguments.code:
application = Application.find(arguments.code)
if application is not None:
pprint.pp(ApplicationVersionController.get_all(application))
else:
main_parser.error('the following argument should be a valid reference: --code/-c')
else:
pprint.pp(ApplicationVersionController.get_all())
elif arguments.subcommand == 'show':
application_version_details = __parse_application_argument(arguments.application)
application_version = ApplicationVersionController.get(application_version_details.get('application_code'), application_version_details.get('version_number'))
if application_version is not None:
pprint.pp(application_version)
else:
main_parser.error('the following argument should be a valid reference: --application/-a')
elif arguments.subcommand == 'install':
application_version_details = __parse_application_argument(arguments.application)
application_version = ApplicationVersionController.get(application_version_details.get('application_code'), application_version_details.get('version_number'))
if application_version is not None:
ApplicationVersionController.install(application_version, arguments.reinstall, application_version_observer=application_version_observer, connection_observer=connection_observer)
else:
main_parser.error('the following argument should be a valid reference: --application/-a')
elif arguments.subcommand == 'uninstall':
application_version_details = __parse_application_argument(arguments.application)
application_version = ApplicationVersionController.get(application_version_details.get('application_code'), application_version_details.get('version_number'))
if application_version is not None:
ApplicationVersionController.uninstall(application_version)
else:
main_parser.error('the following argument should be a valid reference: --application/-a')
elif arguments.command == 'policy':
if arguments.subcommand is None:
policy_parser.print_help()
else:
policy = PolicyController.get(arguments.policy)
if policy is not None:
if arguments.subcommand == 'preview':
print(PolicyController.preview(policy))
elif arguments.subcommand == 'instate':
PolicyController.instate(policy)
elif arguments.subcommand == 'inspect':
if PolicyController.is_instated(policy):
pprint.pp({'status': 'Instated'})
elif PolicyController.is_suggestible(policy):
pprint.pp({'status': 'Suggested'})
else:
pprint.pp({'status': 'Uninstated'})
elif arguments.subcommand == 'revoke':
PolicyController.revoke(policy)
elif arguments.command == 'get':
if arguments.subcommand is None:
get_parser.print_help()
elif arguments.subcommand == 'connection':
print(ConfigurationController.get_connection())
elif arguments.subcommand == 'endpoint_verification':
if ConfigurationController.get_endpoint_verification_enabled():
print('enabled')
else:
print('disabled')
elif arguments.command == 'set':
if arguments.subcommand is None:
set_parser.print_help()
elif arguments.subcommand == 'connection':
ConfigurationController.set_connection(arguments.connection_type)
elif arguments.subcommand == 'endpoint_verification':
if arguments.endpoint_verification_state == 'enabled':
ConfigurationController.set_endpoint_verification_enabled(True)
elif arguments.endpoint_verification_state == 'disabled':
ConfigurationController.set_endpoint_verification_enabled(False)
elif arguments.command == 'sync':
ClientController.sync(client_observer=client_observer, connection_observer=connection_observer)
elif arguments.command == 'update':
client_version = ClientController.get_version()
if ClientVersionController.is_latest(client_version):
print('The client is already up to date.\n')
ClientController.update(client_observer=client_observer, connection_observer=connection_observer)
command = next((c for c in all_commands if getattr(c, 'NAME', None) == arguments.command), None)
if command:
command.handle(arguments, parser)

9
cli/commands/__init__.py Normal file
View file

@ -0,0 +1,9 @@
from cli.commands import profile
from cli.commands import application
from cli.commands import policy
from cli.commands import get_set
from cli.commands import sync_update
import importlib
operator_command = importlib.import_module('cli.commands.operator')
all_commands = [profile, application, operator_command, policy, get_set, sync_update]

View file

@ -0,0 +1,73 @@
from core.controllers.ApplicationVersionController import ApplicationVersionController
from core.models.session.Application import Application
from cli.helpers import parse_application_argument
from cli.observers import application_version_observer, connection_observer
import pprint
NAME = 'application'
def register(subparsers):
parser = subparsers.add_parser(NAME)
subs = parser.add_subparsers(title='subcommands', dest='subcommand')
base = _base_parser()
list_parser = subs.add_parser('list')
list_parser.add_argument('--code', '-c')
subs.add_parser('show', parents=[base])
install_parser = subs.add_parser('install', parents=[base])
install_parser.add_argument('--reinstall', '-r', action='store_true')
subs.add_parser('uninstall', parents=[base])
return parser
def handle(arguments, main_parser):
if arguments.subcommand is None:
main_parser.parse_args(['application', '--help'])
return
if arguments.subcommand == 'list':
if arguments.code:
application = Application.find(arguments.code)
if application is not None:
pprint.pp(ApplicationVersionController.get_all(application))
else:
main_parser.error('the following argument should be a valid reference: --code/-c')
else:
pprint.pp(ApplicationVersionController.get_all())
elif arguments.subcommand == 'show':
details = parse_application_argument(arguments.application)
app_version = ApplicationVersionController.get(details.get('application_code'), details.get('version_number'))
if app_version is not None:
pprint.pp(app_version)
else:
main_parser.error('the following argument should be a valid reference: --application/-a')
elif arguments.subcommand == 'install':
details = parse_application_argument(arguments.application)
app_version = ApplicationVersionController.get(details.get('application_code'), details.get('version_number'))
if app_version is not None:
ApplicationVersionController.install(app_version, arguments.reinstall, application_version_observer=application_version_observer, connection_observer=connection_observer)
else:
main_parser.error('the following argument should be a valid reference: --application/-a')
elif arguments.subcommand == 'uninstall':
details = parse_application_argument(arguments.application)
app_version = ApplicationVersionController.get(details.get('application_code'), details.get('version_number'))
if app_version is not None:
ApplicationVersionController.uninstall(app_version)
else:
main_parser.error('the following argument should be a valid reference: --application/-a')
def _base_parser():
import argparse
p = argparse.ArgumentParser(add_help=False)
p.add_argument('--application', '-a', required=True)
return p

40
cli/commands/get_set.py Normal file
View file

@ -0,0 +1,40 @@
from core.controllers.ConfigurationController import ConfigurationController
NAME = 'get'
NAME_SET = 'set'
def register(subparsers):
get_parser = subparsers.add_parser(NAME)
get_subs = get_parser.add_subparsers(title='subcommands', dest='subcommand')
get_subs.add_parser('connection')
get_subs.add_parser('endpoint_verification')
set_parser = subparsers.add_parser(NAME_SET)
set_subs = set_parser.add_subparsers(title='subcommands', dest='subcommand')
set_connection = set_subs.add_parser('connection')
set_connection.add_argument('connection_type', choices=['system', 'tor'])
set_endpoint = set_subs.add_parser('endpoint_verification')
set_endpoint.add_argument('endpoint_verification_state', choices=['enabled', 'disabled'])
return get_parser
def handle(arguments, main_parser):
if arguments.command == NAME:
if arguments.subcommand is None:
main_parser.parse_args(['get', '--help'])
elif arguments.subcommand == 'connection':
print(ConfigurationController.get_connection())
elif arguments.subcommand == 'endpoint_verification':
print('enabled' if ConfigurationController.get_endpoint_verification_enabled() else 'disabled')
elif arguments.command == NAME_SET:
if arguments.subcommand is None:
main_parser.parse_args(['set', '--help'])
elif arguments.subcommand == 'connection':
ConfigurationController.set_connection(arguments.connection_type)
elif arguments.subcommand == 'endpoint_verification':
ConfigurationController.set_endpoint_verification_enabled(arguments.endpoint_verification_state == 'enabled')

32
cli/commands/operator.py Normal file
View file

@ -0,0 +1,32 @@
from core.controllers.OperatorController import OperatorController
import pprint
NAME = 'operator'
def register(subparsers):
parser = subparsers.add_parser(NAME)
subs = parser.add_subparsers(title='subcommands', dest='subcommand')
subs.add_parser('list')
show_parser = subs.add_parser('show')
show_parser.add_argument('--id', '-i', type=int, required=True)
return parser
def handle(arguments, main_parser):
if arguments.subcommand is None:
main_parser.parse_args(['operator', '--help'])
return
if arguments.subcommand == 'list':
pprint.pp(OperatorController.get_all())
elif arguments.subcommand == 'show':
operator = OperatorController.get(arguments.id)
if operator is not None:
pprint.pp(operator)
else:
main_parser.error('the following argument should be a valid reference: --id/-i')

53
cli/commands/policy.py Normal file
View file

@ -0,0 +1,53 @@
from core.controllers.PolicyController import PolicyController
import pprint
NAME = 'policy'
def register(subparsers):
parser = subparsers.add_parser(NAME)
subs = parser.add_subparsers(title='subcommands', dest='subcommand')
base = _base_parser()
subs.add_parser('preview', parents=[base])
subs.add_parser('instate', parents=[base])
subs.add_parser('inspect', parents=[base])
subs.add_parser('revoke', parents=[base])
return parser
def handle(arguments, main_parser):
if arguments.subcommand is None:
main_parser.parse_args(['policy', '--help'])
return
policy = PolicyController.get(arguments.policy)
if policy is None:
main_parser.error('the following argument should be a valid reference: --policy/-p')
if arguments.subcommand == 'preview':
print(PolicyController.preview(policy))
elif arguments.subcommand == 'instate':
PolicyController.instate(policy)
elif arguments.subcommand == 'inspect':
if PolicyController.is_instated(policy):
pprint.pp({'status': 'Instated'})
elif PolicyController.is_suggestible(policy):
pprint.pp({'status': 'Suggested'})
else:
pprint.pp({'status': 'Uninstated'})
elif arguments.subcommand == 'revoke':
PolicyController.revoke(policy)
def _base_parser():
import argparse
p = argparse.ArgumentParser(add_help=False)
p.add_argument('--policy', '-p', choices=['capability', 'privilege'], required=True)
return p

201
cli/commands/profile.py Normal file
View file

@ -0,0 +1,201 @@
from core.Errors import MissingSubscriptionError, InvalidSubscriptionError, ConnectionUnprotectedError, EndpointVerificationError, ProfileStateConflictError
from core.controllers.ApplicationVersionController import ApplicationVersionController
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.system.SystemProfile import SystemProfile
from cli.helpers import sanitize_profile, parse_application_argument, parse_location_argument
from cli.observers import profile_observer, application_version_observer, connection_observer, invoice_observer
import pprint
NAME = 'profile'
def register(subparsers):
parser = subparsers.add_parser(NAME)
subs = parser.add_subparsers(title='subcommands', dest='subcommand')
base = _base_parser()
pristine = _pristine_parser()
connection_protection = _connection_protection_parser()
endpoint_verification = _endpoint_verification_parser()
profile_state_protection = _profile_state_protection_parser()
subs.add_parser('list')
subs.add_parser('show', parents=[base])
subs.add_parser('destroy', parents=[base])
subs.add_parser('enable', parents=[base, pristine, connection_protection, endpoint_verification, profile_state_protection])
subs.add_parser('disable', parents=[base, connection_protection])
create_parser = subs.add_parser('create')
create_subs = create_parser.add_subparsers(title='profile_types', dest='profile_type')
session_parser = create_subs.add_parser('session', parents=[base])
session_parser.add_argument('--name', '-n', default='')
session_parser.add_argument('--location', '-l', default='')
session_parser.add_argument('--application', '-a', required=True)
session_parser.add_argument('--connection', '-c', dest='connection_type', choices=['system', 'tor', 'wireguard'], default='system')
session_parser.add_argument('--mask-connection', '-m', action='store_true')
session_parser.add_argument('--resolution', '-r', default='1280x720')
system_parser = create_subs.add_parser('system', parents=[base])
system_parser.add_argument('--name', '-n', default='')
system_parser.add_argument('--location', '-l', default='')
system_parser.add_argument('--connection', '-c', dest='connection_type', choices=['wireguard'], default='wireguard')
return parser
def handle(arguments, main_parser):
if arguments.subcommand is None:
main_parser.parse_args(['profile', '--help'])
return
ignore = _build_ignore(arguments)
if arguments.subcommand == 'list':
profiles = ProfileController.get_all()
for key, value in profiles.items():
profiles[key] = sanitize_profile(value)
pprint.pp(profiles)
elif arguments.subcommand == 'show':
pprint.pp(ProfileController.get(arguments.id))
elif arguments.subcommand == 'create':
location_details = parse_location_argument(arguments.location)
location = LocationController.get(location_details.get('country_code'), location_details.get('code'))
if location is None:
main_parser.error('the following argument should be a valid reference: --location/-l')
if arguments.profile_type == 'session':
app_details = parse_application_argument(arguments.application)
app_version = ApplicationVersionController.get(app_details.get('application_code'), app_details.get('version_number'))
if app_version is None:
main_parser.error('the following argument should be a valid reference: --application/-a')
connection = SessionConnection(arguments.connection_type, arguments.mask_connection)
profile = SessionProfile(arguments.id, arguments.name, None, location, arguments.resolution, app_version, connection)
else:
connection = SystemConnection(arguments.connection_type)
profile = SystemProfile(arguments.id, arguments.name, None, location, connection)
ProfileController.create(profile, profile_observer=profile_observer)
elif arguments.subcommand == 'destroy':
profile = ProfileController.get(arguments.id)
if profile is not None:
ProfileController.destroy(profile, profile_observer=profile_observer)
else:
main_parser.error('the following argument should be a valid reference: --id/-i')
elif arguments.subcommand == 'enable':
profile = ProfileController.get(arguments.id)
if profile is None:
main_parser.error('the following argument should be a valid reference: --id/-i')
try:
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer)
except (InvalidSubscriptionError, MissingSubscriptionError) as exception:
_handle_subscription_error(exception, profile, ignore, arguments, main_parser)
elif arguments.subcommand == 'disable':
profile = ProfileController.get(arguments.id)
if profile is not None:
ProfileController.disable(profile, ignore=ignore, profile_observer=profile_observer)
else:
main_parser.error('the following argument should be a valid reference: --id/-i')
def _handle_subscription_error(exception, profile, ignore, arguments, main_parser):
if type(exception).__name__ == 'InvalidSubscriptionError':
print('The profile\'s subscription appears to be invalid.\n')
elif type(exception).__name__ == 'MissingSubscriptionError':
print('The profile is not tied to a subscription.\n')
manage_subscription_input = None
while manage_subscription_input not in ('1', '2', '3', ''):
print('Please select from the following:\n')
print(' 1) Request new subscription')
print(' 2) Enter billing code')
print('\n 3) Exit')
manage_subscription_input = input('\nEnter your choice [1]: ')
if manage_subscription_input in ('1', ''):
print('\nCreating subscription...\n')
subscription_plan = SubscriptionPlanController.get(profile.connection, 720)
if subscription_plan is None:
raise RuntimeError('No compatible subscription plan was found. Please contact support.')
potential_subscription = SubscriptionController.create(subscription_plan, profile, connection_observer=connection_observer)
if potential_subscription is None:
raise RuntimeError('The subscription could not be created. Please try again later.')
ProfileController.attach_subscription(profile, potential_subscription)
subscription = InvoiceController.handle_payment(potential_subscription.billing_code, invoice_observer=invoice_observer, connection_observer=connection_observer)
if subscription is None:
raise RuntimeError('The subscription could not be activated. Please try again later.')
ProfileController.attach_subscription(profile, subscription)
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer)
elif manage_subscription_input == '2':
billing_code = input('\nEnter your billing code: ')
print()
subscription = SubscriptionController.get(billing_code, connection_observer=connection_observer)
if subscription is not None:
ProfileController.attach_subscription(profile, subscription)
ProfileController.enable(profile, ignore=ignore, pristine=arguments.pristine, asynchronous=True, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer)
else:
print('\nThe billing code appears to be invalid.\n')
manage_subscription_input = None
elif manage_subscription_input == '3':
pass
else:
print('\nInput appears to be invalid. Please try again.\n')
def _build_ignore(arguments):
ignore = []
if getattr(arguments, 'without_connection_protection', False):
ignore.append(ConnectionUnprotectedError)
if getattr(arguments, 'without_endpoint_verification', False):
ignore.append(EndpointVerificationError)
if getattr(arguments, 'without_profile_state_protection', False):
ignore.append(ProfileStateConflictError)
return tuple(ignore)
def _base_parser():
import argparse
p = argparse.ArgumentParser(add_help=False)
p.add_argument('--id', '-i', type=int, required=True)
return p
def _pristine_parser():
import argparse
p = argparse.ArgumentParser(add_help=False)
p.add_argument('--pristine', '-p', action='store_true')
return p
def _connection_protection_parser():
import argparse
p = argparse.ArgumentParser(add_help=False)
p.add_argument('--without-connection-protection', action='store_true')
return p
def _endpoint_verification_parser():
import argparse
p = argparse.ArgumentParser(add_help=False)
p.add_argument('--without-endpoint-verification', action='store_true')
return p
def _profile_state_protection_parser():
import argparse
p = argparse.ArgumentParser(add_help=False)
p.add_argument('--without-profile-state-protection', action='store_true')
return p

View file

@ -0,0 +1,22 @@
from core.controllers.ClientController import ClientController
from core.controllers.ClientVersionController import ClientVersionController
from cli.observers import client_observer, connection_observer
NAME_SYNC = 'sync'
NAME_UPDATE = 'update'
def register(subparsers):
subparsers.add_parser(NAME_SYNC)
subparsers.add_parser(NAME_UPDATE)
def handle(arguments, main_parser):
if arguments.command == NAME_SYNC:
ClientController.sync(client_observer=client_observer, connection_observer=connection_observer)
elif arguments.command == NAME_UPDATE:
client_version = ClientController.get_version()
if ClientVersionController.is_latest(client_version):
print('The client is already up to date.\n')
ClientController.update(client_observer=client_observer, connection_observer=connection_observer)

33
cli/helpers.py Normal file
View file

@ -0,0 +1,33 @@
from importlib import metadata
def sanitize_profile(candidate):
if candidate is not None and candidate.has_subscription():
candidate.subscription.billing_code = candidate.subscription.get_sanitized_billing_code()
return candidate
def get_distribution():
for candidate in metadata.distributions():
if 'cli' not in candidate.name:
continue
candidate_files = candidate.files
if candidate_files is None:
continue
has_source_files = any(f.parts[0] == 'cli' for f in candidate_files)
is_editable = any('direct_url.json' in str(f) for f in candidate_files)
if has_source_files or is_editable:
return candidate
return None
def parse_composite_argument(argument: str, first_key: str, second_key: str, separator: str = ':'):
return dict(zip([first_key, second_key], argument.split(separator) + ['']))
def parse_application_argument(application_argument: str):
return parse_composite_argument(application_argument, 'application_code', 'version_number')
def parse_location_argument(location_argument: str):
return parse_composite_argument(location_argument, 'country_code', 'code')

37
cli/observers.py Normal file
View file

@ -0,0 +1,37 @@
from core.controllers.ApplicationController import ApplicationController
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
from core.observers.ClientObserver import ClientObserver
from core.observers.ConnectionObserver import ConnectionObserver
from core.observers.InvoiceObserver import InvoiceObserver
from core.observers.ProfileObserver import ProfileObserver
from cli.helpers import sanitize_profile
import pprint
application_version_observer = ApplicationVersionObserver()
client_observer = ClientObserver()
connection_observer = ConnectionObserver()
invoice_observer = InvoiceObserver()
profile_observer = ProfileObserver()
application_version_observer.subscribe('downloading', lambda event: print(f'Downloading {ApplicationController.get(event.subject.application_code).name}, version {event.subject.version_number}...'))
application_version_observer.subscribe('download_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r'))
application_version_observer.subscribe('downloaded', lambda event: print('\n'))
client_observer.subscribe('synchronizing', lambda event: print('Synchronizing...\n'))
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('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'))
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'))
connection_observer.subscribe('tor_bootstrapped', lambda event: print('\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('settled', lambda event: print('The payment has been successfully verified.\n'))
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('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')))

View file

@ -12,7 +12,7 @@ classifiers = [
"Operating System :: POSIX :: Linux",
]
dependencies = [
"sp-hydra-veil-core == 2.2.1",
"sp-hydra-veil-core == 2.3.0",
]
[project.urls]