class CommandManager(object): def __init__(self, bot, connection): '''Determines if a message is in fact a command, stores a list of all valid commands.''' self._bot = bot self.con = connection self._bot_nick = connection._nick self.logger = logging.getLogger("GorillaBot") self.command_list = {} self.admin_only = [ 'addadmin', 'join', 'part', 'quit', 'shutdown', 'removeadmin' ] self.organize_commands() self._throttle_list = {} self.plugin_path = os.path.dirname( os.path.abspath(__file__)) + '/plugins' self.stalker = Stalker() def check_command(self, line): '''Messages of type PRIVMSG will be passed through this function to check if they are commands.''' # Separates the line into its four parts line_string = " ".join(line) line_string = line_string.replace("\"", "") line_string = line_string.replace("\'", "") parser = re.compile( "(?:\S+?:(\S+)!\S+\s)?([A-Z]+)\s(?:([^:]+)\s)?(?::(.+))?") r = re.search(parser, line_string) channel = r.group(3) irc_trailing = r.group(4) # Verify a message was sent if irc_trailing != None: # Check if the command was sent via private message to the bot if channel == self._bot_nick: private = True else: private = False command = "" command_type = "" command_regex = re.compile("(?:!(\S+))", re.IGNORECASE) if private: command_type = "private" # Change channel to sender's nick so that the message is sent as a reply to the # private message. channel = r.group(1) # First check if there's a exclamation-type command command_r = re.search(command_regex, irc_trailing) if command_r != None: # Exclamation type command was found command = command_r.group(1) else: # No exclamation-type command; assume first word of message command_r = re.search("(\S+)", irc_trailing) command = command_r.group(1) else: # Check if command was addressed to the bot (with or without exclamation) command_regex = "{}(?::|,|)\s(?:!?(\S+))".format( self._bot_nick) command_r = re.search(command_regex, irc_trailing) if command_r != None: # Directly-addressed command found command_type = "direct" command = command_r.group(1) else: # Check for exclamation command command_r = re.search("!(\S+)", irc_trailing) if command_r != None: # Exclamation command found if command_r.start(1) == 1: # Exclamation command at beginning of message command_type = "exclamation_first" else: # Command is elsewhere in message command_type = "exclamation" command = command_r.group(1) if command != "": if command in ["notify"]: exec_string = """self.stalker.{0}(self,"{1}","{2}","{3}")""".format( command, channel, command_type, line_string) exec(exec_string) elif command in self.admin_only: module_name = self.command_list[command] exec_string = """{0}(c, "{1}","{2}","{3}")""".format( command, channel, command_type, line_string) admin._is_admin(self, line_string, channel, exec_string) elif command in self.command_list: module_name = self.command_list[command] exec_string = """{0}(self,"{1}","{2}","{3}")""".format( module_name, channel, command_type, line_string) exec(exec_string) else: # There is no command in the line. self.check_regex(irc_trailing, channel, line_string) def check_regex(self, message, channel, line_string): '''Checks an IRC message to see if it matches a regex pattern.''' bat_regex = re.compile("batman", re.IGNORECASE) bat_r = re.search(bat_regex, message) if bat_r: exec_string = """batman.alfred(self, "{0}", "regex", "{1}")""".format( channel, line_string) exec(exec_string) def get_message(self, line): '''Isolates the trailing message from a full message string.''' parser = re.compile( "(?:\S+?:(\S+)!\S+\s)?([A-Z]+)\s(?:([^:]+)\s)?(?::(.+))?") r = re.search(parser, line) if r: return r.group(4) else: return None def get_sender(self, line): '''Isolates the nick of the sender of the message from a full message string.''' parser = re.compile( "(?:\S+?:(\S+)!\S+\s)?([A-Z]+)\s(?:([^:]+)\s)?(?::(.+))?") r = re.search(parser, line) if r: return r.group(1) else: return None def organize_commands(self): '''Collects commands from the various plugins, organizes them into a dict.''' for module in plugins.__all__: module_command_list = [] exec( "module_command_list += [name for name, data in getmembers({0})" "if isfunction(data)]".format(module)) for module_command in module_command_list: # Prevents private functions from being displayed or executed from IRC if module_command[0] != "_": exec("self.command_list['{0}'] = '{1}.{0}'".format( module_command, module)) self.con._commands = self.command_list def nick_change(self, line): self.stalker._nick_change(line) admin._nick_change(self, line) def nickserv_parse(self, line): '''Parses a message from NickServ and responds accordingly.''' if "identify" in line: self.logger.info("NickServ has requested identification.") self.con.nickserv_identify() elif "identified" in line: self.con._password = self.con._tentative_password self.logger.info("You have successfully identified as {}.".format( line[2])) self.con.join() elif ":Invalid" in line: self.logger.info( "You've entered an incorrect password. Please re-enter.") self.con.nickserv_identify() elif "ACC" in line and "0" in line: # Account is not registered; don't have to ident to join channels self.con.join() self.con.get_admin() def process_numcode(self, numcode, line): '''Parses a message with a reply code number and responds accordingly.''' if self.con._whois_dest: if numcode in ["301", "311", "401" ] and self.con._whois_dest[0] == 'notify': self.stalker.codes.append(numcode) elif numcode in ["311", "401" ] and self.con._whois_dest[0] == 'adminlist': self.con.set_admin(line) elif numcode in ["311", "353" ] and self.con._whois_dest[0] == 'isadmin': admin._is_admin_response(self, line, self.con._whois_dest[1]) elif numcode == "318" and self.con._whois_dest[0] == 'notify': # End of whois self.stalker._recv_numcode(self.con, line[3]) elif numcode == "396": # RPL_HOSTHIDDEN - Cloak set. self.logger.info("Cloak set as {}.".format(line[3])) elif numcode == "403": # ERR_NOSUCHCHANNEL self.logger.warning("No such channel exists.") elif numcode == "433": # ERR_NICKNAMEINUSE - Nickname is already in use. # TODO: Change response to something more productive than shutting down. self.logger.error( "Nickname is already in use. Closing connection.") self.con.quit() self.con.shut_down() elif numcode == "442": # ERR_NOTONCHANNEL - You're not in that channel self.logger.info( "You tried to part from {}, but you are not in that " "channel.".format(line[3])) elif numcode == "470": self.logger.error("Unable to join channel {}.".format(line[3])) self.logger.info( "You were forwarded to {}. Parting from this channel.".format( line[4])) self.con.part(line[4]) elif numcode == "473": self.logger.error("Unable to join invite-only channel {}.".format( line[3])) self.con.part(line[3], True) def throttle(self, command, delay=120): '''Keeps track of how often a command is executed, throttling it if it is executed too frequently. Default delay is two minutes, but this can be set for each function.''' now = time() if (command in self._throttle_list and now - self._throttle_list[command] < delay): # Command was executed less than [delay] ago. Throttling command. self.logger.info( "Command was executed {} seconds ago. Throttling command until" " {} seconds have passed.".format( now - self._throttle_list[command], delay)) return True else: self._throttle_list[command] = now return False
class CommandManager(object): def __init__(self, bot, connection): """Determines if a message is in fact a command, stores a list of all valid commands.""" self._bot = bot self.con = connection self._bot_nick = connection._nick self.logger = logging.getLogger("GorillaBot") self.command_list = {} self.admin_only = ["addadmin", "join", "part", "quit", "shutdown", "removeadmin"] self.organize_commands() self._throttle_list = {} self.plugin_path = os.path.dirname(os.path.abspath(__file__)) + "/plugins" self.stalker = Stalker() def check_command(self, line): """Messages of type PRIVMSG will be passed through this function to check if they are commands.""" # Separates the line into its four parts line_string = " ".join(line) line_string = line_string.replace('"', "") line_string = line_string.replace("'", "") parser = re.compile("(?:\S+?:(\S+)!\S+\s)?([A-Z]+)\s(?:([^:]+)\s)?(?::(.+))?") r = re.search(parser, line_string) channel = r.group(3) irc_trailing = r.group(4) # Verify a message was sent if irc_trailing != None: # Check if the command was sent via private message to the bot if channel == self._bot_nick: private = True else: private = False command = "" command_type = "" command_regex = re.compile("(?:!(\S+))", re.IGNORECASE) if private: command_type = "private" # Change channel to sender's nick so that the message is sent as a reply to the # private message. channel = r.group(1) # First check if there's a exclamation-type command command_r = re.search(command_regex, irc_trailing) if command_r != None: # Exclamation type command was found command = command_r.group(1) else: # No exclamation-type command; assume first word of message command_r = re.search("(\S+)", irc_trailing) command = command_r.group(1) else: # Check if command was addressed to the bot (with or without exclamation) command_regex = "{}(?::|,|)\s(?:!?(\S+))".format(self._bot_nick) command_r = re.search(command_regex, irc_trailing) if command_r != None: # Directly-addressed command found command_type = "direct" command = command_r.group(1) else: # Check for exclamation command command_r = re.search("!(\S+)", irc_trailing) if command_r != None: # Exclamation command found if command_r.start(1) == 1: # Exclamation command at beginning of message command_type = "exclamation_first" else: # Command is elsewhere in message command_type = "exclamation" command = command_r.group(1) if command != "": if command in ["notify"]: exec_string = """self.stalker.{0}(self,"{1}","{2}","{3}")""".format( command, channel, command_type, line_string ) exec(exec_string) elif command in self.admin_only: module_name = self.command_list[command] exec_string = """{0}(c, "{1}","{2}","{3}")""".format(command, channel, command_type, line_string) admin._is_admin(self, line_string, channel, exec_string) elif command in self.command_list: module_name = self.command_list[command] exec_string = """{0}(self,"{1}","{2}","{3}")""".format( module_name, channel, command_type, line_string ) exec(exec_string) else: # There is no command in the line. self.check_regex(irc_trailing, channel, line_string) def check_regex(self, message, channel, line_string): """Checks an IRC message to see if it matches a regex pattern.""" bat_regex = re.compile("batman", re.IGNORECASE) bat_r = re.search(bat_regex, message) if bat_r: exec_string = """batman.alfred(self, "{0}", "regex", "{1}")""".format(channel, line_string) exec(exec_string) def get_message(self, line): """Isolates the trailing message from a full message string.""" parser = re.compile("(?:\S+?:(\S+)!\S+\s)?([A-Z]+)\s(?:([^:]+)\s)?(?::(.+))?") r = re.search(parser, line) if r: return r.group(4) else: return None def get_sender(self, line): """Isolates the nick of the sender of the message from a full message string.""" parser = re.compile("(?:\S+?:(\S+)!\S+\s)?([A-Z]+)\s(?:([^:]+)\s)?(?::(.+))?") r = re.search(parser, line) if r: return r.group(1) else: return None def organize_commands(self): """Collects commands from the various plugins, organizes them into a dict.""" for module in plugins.__all__: module_command_list = [] exec("module_command_list += [name for name, data in getmembers({0})" "if isfunction(data)]".format(module)) for module_command in module_command_list: # Prevents private functions from being displayed or executed from IRC if module_command[0] != "_": exec("self.command_list['{0}'] = '{1}.{0}'".format(module_command, module)) self.con._commands = self.command_list def nick_change(self, line): self.stalker._nick_change(line) admin._nick_change(self, line) def nickserv_parse(self, line): """Parses a message from NickServ and responds accordingly.""" if "identify" in line: self.logger.info("NickServ has requested identification.") self.con.nickserv_identify() elif "identified" in line: self.con._password = self.con._tentative_password self.logger.info("You have successfully identified as {}.".format(line[2])) self.con.join() elif ":Invalid" in line: self.logger.info("You've entered an incorrect password. Please re-enter.") self.con.nickserv_identify() elif "ACC" in line and "0" in line: # Account is not registered; don't have to ident to join channels self.con.join() self.con.get_admin() def process_numcode(self, numcode, line): """Parses a message with a reply code number and responds accordingly.""" if self.con._whois_dest: if numcode in ["301", "311", "401"] and self.con._whois_dest[0] == "notify": self.stalker.codes.append(numcode) elif numcode in ["311", "401"] and self.con._whois_dest[0] == "adminlist": self.con.set_admin(line) elif numcode in ["311", "353"] and self.con._whois_dest[0] == "isadmin": admin._is_admin_response(self, line, self.con._whois_dest[1]) elif numcode == "318" and self.con._whois_dest[0] == "notify": # End of whois self.stalker._recv_numcode(self.con, line[3]) elif numcode == "396": # RPL_HOSTHIDDEN - Cloak set. self.logger.info("Cloak set as {}.".format(line[3])) elif numcode == "403": # ERR_NOSUCHCHANNEL self.logger.warning("No such channel exists.") elif numcode == "433": # ERR_NICKNAMEINUSE - Nickname is already in use. # TODO: Change response to something more productive than shutting down. self.logger.error("Nickname is already in use. Closing connection.") self.con.quit() self.con.shut_down() elif numcode == "442": # ERR_NOTONCHANNEL - You're not in that channel self.logger.info("You tried to part from {}, but you are not in that " "channel.".format(line[3])) elif numcode == "470": self.logger.error("Unable to join channel {}.".format(line[3])) self.logger.info("You were forwarded to {}. Parting from this channel.".format(line[4])) self.con.part(line[4]) elif numcode == "473": self.logger.error("Unable to join invite-only channel {}.".format(line[3])) self.con.part(line[3], True) def throttle(self, command, delay=120): """Keeps track of how often a command is executed, throttling it if it is executed too frequently. Default delay is two minutes, but this can be set for each function.""" now = time() if command in self._throttle_list and now - self._throttle_list[command] < delay: # Command was executed less than [delay] ago. Throttling command. self.logger.info( "Command was executed {} seconds ago. Throttling command until" " {} seconds have passed.".format(now - self._throttle_list[command], delay) ) return True else: self._throttle_list[command] = now return False