async def on_command_error(context, exception): """ Handle errors that are given from the bot. """ if settings.DEBUGGING: logger.log(exception) traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr) cmd = context.invoked_with channel = context.message.channel if isinstance(exception, commands.CommandNotFound): await channel.send(f'Command "{cmd}" unknown. Try "?help"', delete_after=settings.DEFAULT_DELETE_DELAY) elif isinstance(exception, discord.InvalidArgument): await channel.send(f'Invalid argument for command "{cmd}". Try "?help {cmd}"', delete_after=settings.DEFAULT_DELETE_DELAY) elif isinstance(exception, commands.MissingRequiredArgument): await channel.send(f'Missing argument for command "{cmd}". Try "?help {cmd}"', delete_after=settings.DEFAULT_DELETE_DELAY) elif isinstance(exception, commands.CommandInvokeError): await channel.send('Oops, looks like something on the back end broke. Please contact @mildmelon#5380.', delete_after=settings.DEFAULT_DELETE_DELAY) await asyncio.sleep(settings.DEFAULT_DELETE_DELAY) await context.message.delete()
def _cleanup_zombie_vpn(self, delay=1, log_lvl=logger.DEBUG): time.sleep(delay) logger.log(log_lvl, 'Cleanup the VPN zombie processes...') SystemHelper.kill_by_process(f'{self.vpn_dir}/vpnclient execsvc', silent=True, log_lvl=logger.down_lvl(log_lvl)) self.device.ip_resolver.cleanup_zombie(f'vpn_')
async def _help(self, context, *extra_commands): """ Get this message from the bot, and add a mailbox emoji to your help command. """ author = context.author channel = context.message.channel dm_channel = author.dm_channel message = context.message if dm_channel is None: dm_channel = await author.create_dm() # hacky way to force the help message into a dm instead of guild channel context.message.channel = dm_channel if len(extra_commands) != 0: await _default_help_command(context, *extra_commands) else: await _default_help_command(context) # undo the hacks context.message.channel = channel if context.guild: try: # try to add a :mailbox: reaction await message.add_reaction(Reaction.MAILBOX.value) except (discord.HTTPException, discord.Forbidden, discord.NotFound, discord.InvalidArgument) as e: # if the reaction failed, then send a basic message logger.log(f'Could not add :mailbox: reaction, {e}') await channel.send( f"Sent you a list of the commands, {author.name}")
def is_running(self, log_lvl=logger.DEBUG) -> bool: logger.log(log_lvl, 'Check if VPN is running...') pid = self._find_pid(logger.down_lvl(log_lvl)) if pid: logger.log(log_lvl, f'VPN PID [{pid}]') return True self.cleanup() return False
def _import_nonexistent(msg): """Import an object only if it does not exist in RE already.""" upa = ':'.join([str(p) for p in [msg['wsid'], msg['objid'], msg['ver']]]) log('INFO', f'_import_nonexistent on {upa}') # TODO _id = 'wsfull_object_version/' + upa exists = check_doc_existence(_id) if not exists: _import_obj(msg)
async def on_guild_update(before, after): """ When a guild updates, we want to update it's corresponding union. """ # check to see if there is a union within the database already if game.check_union_exists(before): game.get_union(before).set_name(after.name) logger.log(f'Updated the union named "{before.name}" to "{after.name}"') else: # theoretically there can't be a case where a union is updated before it's created... except in testing await manage.ask_guild_to_join_game(game, bot, after)
def get_vpn_ip(self, nic: str, lenient=True): try: logger.log(self.log_lvl, f'Query VPN IPv4 on {nic}...') return netifaces.ifaddresses(nic)[netifaces.AF_INET] except Exception as err: if lenient: logger.debug(f'Not found VPN IP {nic}. Error: {err}') return None raise err
def create_config(self, vpn_acc: str, replacements: dict): config_file = self._to_config_file(vpn_acc) logger.log(self.log_lvl, f'Create DHCP client VPN config[{config_file}]...') FileHelper.copy(self.resource_dir.joinpath(self.DHCLIENT_CONFIG_TMPL), config_file, force=True) FileHelper.replace_in_file(config_file, replacements, backup='') FileHelper.chmod(config_file, mode=0o0644)
def add_hook(self, service_name: str, replacements: dict): exit_hook_file = self._to_hook_file(service_name) logger.log(self.log_lvl, f'Create DHCP client VPN hook[{exit_hook_file}]...') FileHelper.copy(self.resource_dir.joinpath( self.DHCLIENT_EXIT_HOOK_TMPL), exit_hook_file, force=True) FileHelper.replace_in_file(exit_hook_file, replacements, backup='') FileHelper.chmod(exit_hook_file, mode=0o0744)
def _check_pid(pid_file: str, log_lvl=logger.TRACE) -> int: try: logger.log(log_lvl, f'Read PID file {pid_file}') pid = FileHelper.read_file_by_line(pid_file) pid = int(pid) if pid and pid > 0 and SystemHelper.is_pid_exists(pid): return pid except Exception as _: FileHelper.rm(pid_file) return 0
def setUpClass(cls): # Initialize specs log('INFO', 'Initializing specs for the RE API..') resp = requests.put( _CONFIG['re_api_url'] + '/api/v1/specs', headers={'Authorization': _CONFIG['ws_token']}, params={'init_collections': '1'} ) resp.raise_for_status() log('INFO', 'Done initializing RE specs.')
async def logout(self, context): """ Command only available to developers. Have IsleBot logout and close all connections. """ is_admin(context.author) logger.log('Logging out...') logger.write_logs(logout=True) await context.send('Logging out...') await self.bot.logout()
def do_disconnect_current(self, must_disable_service=False, log_lvl: int = logger.INFO, silent: bool = True): account = self.storage.get_current() if not account: logger.log(logger.down_lvl(log_lvl), 'Not found any VPN account') return self.do_disconnect([account], must_disable_service=must_disable_service, log_lvl=log_lvl, silent=silent)
def kill_by_pid(pid: list, _signal=signal.SIGTERM, silent=True, log_lvl=logger.DEBUG): for p in pid or []: try: logger.log(log_lvl, f'Kill PID [{p}::{_signal}]...') os.kill(int(p), _signal) except OSError as err: SystemHelper.handle_kill_error(err, silent) except ValueError as err: logger.decrease(log_lvl, f'Error PID [{p}]. Error: {err}')
def lease_ip(self, vpn_acc: str, vpn_nic: str, daemon=False, is_execute=True): logger.log(self.log_lvl, 'Lease a new VPN IP...') command = f'{self.ip_tool} {self._lease_ip_opt(vpn_acc, vpn_nic, daemon)}' if is_execute: SystemHelper.exec_command(command, silent=self.silent, log_lvl=logger.down_lvl(self.log_lvl)) return command
def pre_exec(self, silent=False, log_lvl=logger.DEBUG, **kwargs): logger.log(log_lvl, 'Start VPN Client if not yet running...') if not self.is_installed(silent, log_lvl): return if self.pid_handler.is_running(): self._prev_is_run = True return SystemHelper.exec_command(f'{self.opts.vpnclient} start', log_lvl=logger.down_lvl(log_lvl)) time.sleep(1) if not self.pid_handler.is_running(log_lvl=logger.down_lvl(log_lvl)): logger.error('Unable start VPN Client') sys.exit(ErrorCode.VPN_START_FAILED)
def create_symlink(source: Union[str, Path], link: Union[str, Path], force=False, log_lvl=logger.DEBUG): src = Path(source) lk = Path(link) logger.log(log_lvl, f'Create symlink from [{src}] to [{lk}]...') if not FileHelper.is_exists(src): raise RuntimeError(f'Given file[{src}] is not existed') if FileHelper.is_exists(lk): if FileHelper.is_dir(lk): raise RuntimeError(f'Given target link[{lk}] is directory') if not force: raise RuntimeError(f'Given target link[{lk}] is existed') os.remove(lk) os.symlink(src, lk, target_is_directory=FileHelper.is_dir(src))
def wait_for_services(): """Wait for dependency services such as the RE API.""" timeout = int(time.time()) + 60 while True: try: requests.get(_CONFIG['re_api_url'] + '/').raise_for_status() break except Exception as err: log('INFO', f'Service not yet online: {err}') if int(time.time()) >= timeout: raise RuntimeError("Timed out waiting for other services to come online.") time.sleep(3) log('INFO', 'Services started!')
def post_exec(self, silent=False, log_lvl=logger.DEBUG, **kwargs): logger.log(log_lvl, 'Stop VPN Client if applicable...') if not self.is_installed(True, log_lvl): return if (self._prev_is_run or not self.adhoc_task) and not kwargs.get('_force_stop', False): return lvl = logger.down_lvl(log_lvl) if self.pid_handler.is_running(log_lvl=lvl): SystemHelper.exec_command(f'{self.opts.vpnclient} stop', silent=silent, log_lvl=lvl) self._cleanup_zombie_vpn(1, log_lvl=lvl) self.pid_handler.cleanup()
def do_connect(self, account: str, log_lvl: int = logger.INFO): if not account: logger.error(f'VPN account is not correct') sys.exit(ErrorCode.INVALID_ARGUMENT) acc = self.storage.find(account) if not acc: logger.error(f'Not found VPN account') sys.exit(ErrorCode.VPN_ACCOUNT_NOT_FOUND) logger.log(log_lvl, f'Connect VPN account [{account}]...') self.storage.create_or_update(acc, _connect=True) self.exec_command(['AccountConnect'], params=account) self.lease_vpn_service(is_enable=acc.is_default, is_restart=acc.is_default, is_lease_ip=not acc.is_default, account=acc.account)
def do_uninstall(self, keep_vpn: bool = True, keep_dnsmasq: bool = True, service_opts: UnixServiceOpts = None, log_lvl: int = logger.INFO): vpn_service = self._standard_service_opt(service_opts).service_name logger.info(f'Uninstall VPN service [{vpn_service}]...') self.do_delete([a.account for a in self.storage.list()], force_stop=True, log_lvl=log_lvl) if not keep_vpn: logger.log(log_lvl, f'Remove VPN Client [{self.opts.vpn_dir}]...') self.device.ip_resolver.remove_hook(vpn_service) self.opts.remove_env() FileHelper.rm(self.opts.vpn_dir) self.device.dns_resolver.cleanup_config(vpn_service, keep_dnsmasq=keep_dnsmasq)
def do_delete(self, accounts: Sequence[str], log_lvl: int = logger.INFO, silent: bool = True, force_stop=False): cur_acc = self.storage.get_current() is_disable, is_stop = False, force_stop or cur_acc is None or cur_acc == '' for acc in accounts: logger.log(log_lvl, f'Delete VPN account [{acc}]...') is_default, is_current = self.storage.remove(acc) is_stop = is_current or is_stop is_disable = is_default or is_disable commands = ['AccountDisconnect', 'AccountDelete', 'NicDelete'] if is_default: commands.insert(1, 'AccountStartupRemove') self.storage.set_default('') self.exec_command(commands, acc, silent, log_lvl) self.shutdown_vpn_service(is_stop=is_stop, is_disable=is_disable, log_lvl=log_lvl)
async def ask_guild_to_join_game(bot, guild, channel=None): if channel is None: channel = find_open_channel(guild) # we don't to pollute our database with random guilds that are inactive, so ask first if they want to join confirm_msg = ConfirmMenu( bot, channel, [ 'Welcome, would you like this guild to be registered within the game?', 'This guild will not be registered.', # message when dismissed 'This guild is now registered.' ]) # message when confirmed await confirm_msg.send() logger.log( f'Asking guild "{guild.name}" [id:{guild.id}] to join the game.') confirmed = await confirm_msg.wait_for_user_reaction() if confirmed: # create a new guild for the guild union = Game.create_union(guild) logger.log(f'Created a new Union under the name "{union.name}"') else: await channel.send( 'I am leaving this guild, as I am no longer needed. ' 'If you change your mind and would like to register this guild, just add me back.' ) try: guild.leave() except HTTPException as exception: logger.log(exception) await channel.send( 'Sorry to bother you, this is kind of embarrassing. ' 'I am having some trouble leaving, would you mind kicking me please?' )
def do_disconnect(self, accounts: Sequence[str], must_disable_service=False, log_lvl: int = logger.INFO, silent: bool = True): cur_acc = self.storage.get_current() is_stop = cur_acc is None or cur_acc == '' for acc in accounts: logger.log(log_lvl, f'Disconnect VPN account [{acc}]...') self.exec_command('AccountDisconnect', params=acc, log_lvl=logger.down_lvl(log_lvl), silent=silent) self.device.ip_resolver.release_ip(acc, self.opts.account_to_nic(acc)) self.device.ip_resolver.cleanup_zombie( f'--no-pid.* {self.opts.account_to_nic(acc)}') is_stop = acc == cur_acc or is_stop if is_stop: self.shutdown_vpn_service(is_stop=True, is_disable=must_disable_service, log_lvl=log_lvl)
def _handle_msg(msg): """Receive a kafka message.""" event_type = msg.get('evtype') wsid = msg.get('wsid') if not wsid: raise RuntimeError(f'Invalid wsid in event: {wsid}') if not event_type: raise RuntimeError(f"Missing 'evtype' in event: {msg}") log('INFO', f'Received {msg["evtype"]} for {wsid}/{msg.get("objid", "?")}') if event_type in ['IMPORT', 'NEW_VERSION', 'COPY_OBJECT', 'RENAME_OBJECT']: _import_obj(msg) elif event_type == 'IMPORT_NONEXISTENT': _import_nonexistent(msg) elif event_type == 'OBJECT_DELETE_STATE_CHANGE': _delete_obj(msg) elif event_type == 'WORKSPACE_DELETE_STATE_CHANGE': _delete_ws(msg) elif event_type in ['CLONE_WORKSPACE', 'IMPORT_WORKSPACE']: _import_ws(msg) elif event_type == 'SET_GLOBAL_PERMISSION': _set_global_perms(msg) else: raise RuntimeError(f"Unrecognized event {event_type}.")
def backup(src: Union[str, Path], dest: Union[str, Path] = None, remove=True, force=True, log_lvl=logger.DEBUG) -> str: """ Backup :param src: given path :param dest: given destination or backup to same given source with suffix '.bak' :param remove: remove flag to decide removing source after backup :param force: force flag to decide removing dest if exists :param log_lvl: log level :return: the file destination """ p = Path(src) t = Path(dest) if dest else p.parent.joinpath(p.name + '.bak') logger.log(log_lvl, f'Backup [{p}] to [{t}]...') if FileHelper.is_symlink(p): FileHelper.create_symlink(FileHelper.get_target_link(p), t, force) to = t else: to = FileHelper.copy_advanced(p, t, force) if remove: logger.log(log_lvl, f'Remove [{p}] after backup...') os.remove(p) return to
def exec_command(command: str, shell=False, silent=False, log_lvl=logger.DEBUG) -> str: logger.decrease(log_lvl, "Execute command: %s", command) list_cmd = command.split(" | ") if not shell else [command] length = len(list_cmd) prev = None for idx, cmd in enumerate(list_cmd, 1): logger.trace("\tsub_command::%s::%s", cmd, prev) kwargs = {} if EnvHelper.is_py3_5() else {"encoding": "utf-8"} complete = subprocess.run(cmd.split() if not shell else cmd, input=prev, env=TWEAK_ENV, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) ret = complete.returncode lvl = (logger.TRACE if idx < length else logger.DEBUG) if ret == 0 or silent else logger.ERROR try: prev = SystemHelper.__handle_command_result( complete, silent, lvl) except RuntimeError as _: if not silent: logger.error('Failed when executing command %s', cmd) sys.exit(ret) finally: ret_val = ("0. Actual: %s" % ret) if silent and ret != 0 else ret logger.decrease(log_lvl, "%sReturn code: %s", "\t" if idx < length else "", ret_val) if prev: logger.log(log_lvl, "\t%s", prev) return prev
def lease_vpn_ip(self, account: str, log_lvl=logger.DEBUG): logger.log(log_lvl, 'Wait a VPN session is established...') loop_interval(lambda: self.get_vpn_status(account)['connected'], 'Unable connect VPN', max_retries=3, interval=5) nic = self.opts.account_to_nic(account) if self.device.dns_resolver.is_connman( ) and not self.device.dns_resolver.is_enable_connman_dhcp(): logger.log( logger.WARN, f'Please lease VPN IP manually by ' + f'[{self.device.ip_resolver.lease_ip(account, nic, daemon=True, is_execute=False)}]' ) return logger.log(log_lvl, 'Wait a VPN IP is leased...') self.device.ip_resolver.lease_ip(account, nic, daemon=True)
def _delete_obj(msg): """Handle an object deletion event (OBJECT_DELETE_STATE_CHANGE)""" log('INFO', '_delete_obj TODO') # TODO raise NotImplementedError()
def _import_obj(msg): log('INFO', 'Downloading obj') obj_info = download_info(msg['wsid'], msg['objid'], msg.get('ver')) import_object(obj_info)