from __future__ import annotations import logging import re from threading import Thread from typing import Any, List, TYPE_CHECKING, Tuple from bot import app_vars, errors from bot.TeamTalk.structs import Message, User, UserType from bot.commands import admin_commands, user_commands from bot.commands.task_processor import TaskProcessor re_command = re.compile("[a-z]+") re_arg_split = re.compile(r"(? None: command_thread = Thread(target=self._run, args=(message,)) command_thread.start() def _run(self, message: Message) -> None: try: command_name, arg = self.parse_command(message.text) if self.check_access(message.user, command_name): command_class = self.get_command(command_name, message.user) command = command_class(self) self.current_command_id = id(command) result = command(arg, message.user) if result: self.ttclient.send_message( result, message.user, ) # here was command.ttclient later except errors.InvalidArgumentError: self.ttclient.send_message( self.help(command_name, message.user), message.user, ) except errors.AccessDeniedError as e: self.ttclient.send_message(str(e), message.user) except (errors.ParseCommandError, errors.UnknownCommandError): self.ttclient.send_message( self.translator.translate('Unknown command. Send "start" for help.'), message.user, ) except Exception as e: logging.error("", exc_info=True) self.ttclient.send_message( self.translator.translate("Error: {}").format(str(e)), message.user, ) def check_access(self, user: User, command: str) -> bool: if ( not user.is_admin and user.type != UserType.Admin ) or app_vars.app_name in user.client_name: if app_vars.app_name in user.client_name: raise errors.AccessDeniedError("") elif user.is_banned: raise errors.AccessDeniedError( self.translator.translate("You are banned"), ) elif user.channel.id != self.ttclient.channel.id: raise errors.AccessDeniedError( self.translator.translate("You are not in bot's channel"), ) elif self.locked: raise errors.AccessDeniedError( self.translator.translate("Bot is locked"), ) elif command in self.config.general.blocked_commands: raise errors.AccessDeniedError( self.translator.translate("This command is blocked"), ) else: return True else: return True def get_command(self, command: str, user: Optional[User]) -> Any: if command in self.commands_dict: return self.commands_dict[command] elif ( (user is not None) and (user.is_admin or user.type == UserType.Admin) and command in self.admin_commands_dict ): return self.admin_commands_dict[command] else: raise errors.UnknownCommandError() def help(self, arg: str, user: User) -> str: if arg: if arg in self.commands_dict: return "{} {}".format(arg, self.commands_dict[arg](self).help) elif user.is_admin and arg in self.admin_commands_dict: return "{} {}".format(arg, self.admin_commands_dict[arg](self).help) else: return self.translator.translate("Unknown command") else: help_strings: List[str] = [] for i in list(self.commands_dict): help_strings.append(self.help(i, user)) if user.is_admin: for i in list(self.admin_commands_dict): help_strings.append(self.help(i, user)) return "\n".join(help_strings) def parse_command(self, text: str) -> Tuple[str, str]: text = text.strip() try: command = re.findall(re_command, text.split(" ")[0].lower())[0] except IndexError: raise errors.ParseCommandError() arg = " ".join(text.split(" ")[1::]) return command, arg def split_arg(self, arg: str) -> List[str]: args = re.split(re_arg_split, arg) for i, arg in enumerate(args): args[i] = args[i].strip().replace("\\|", "|") return args class ScheduledCommandProcessor(CommandProcessor): """Command processor, specifically tailored for scheduled tasks. Takes a CronEntry instead of a message.""" def __init__(self, bot: Bot): super().__init__(bot) def __call__(self, task: CronEntryModel) -> None: command_thread = Thread(target=self._run, args=(task,)) command_thread.start() def _run(self, task: CronEntryModel) -> None: """Takes a cron task and runs it. Doesn't provide user to the command, doesn't check access (as there is no user to check). Logs errors and reports them to the channel also if that is enabled. Cron tasks can run admin commands.""" try: command_name, arg = self.parse_command(task.command) if ( command_name in self.commands_dict or command_name in self.admin_commands_dict ): command_class = self.get_command(command_name, None) command = command_class(self) self.current_command_id = id(command) result = command(arg, None) if result: logger.info( f"Successfully ran cron command '{task.command}; result: {result}" ) self.ttclient.send_message( result, message.user, ) except errors.InvalidArgumentError: log.error( f"Invalid argument for scheduled command '{task.command}'; cron pattern: {task.pattern}" ) if self.config.general.send_channel_messages: self.ttclient.send_message( self.translator.translate( f"Scheduled command '{task.command}' failed: invalid argument" ), type=2, ) except errors.AccessDeniedError as e: log.error( f"Got access denied while running scheduled task '{task.command}'; cron pattern: {task.pattern}" ) except (errors.ParseCommandError, errors.UnknownCommandError): if self.config.general.send_channel_messages: self.ttclient.send_message( self.translator.translate( f"Unknown scheduled command: '{task.command}'." ), type=2, ) except Exception as e: logging.error( f"Error running scheduled command '{task.command}", exc_info=True ) if self.config.general.send_channel_messages: self.ttclient.send_message( self.translator.translate( f"Error running scheduled command '{task.command}: {e}" ), type=2, )