* Support for starting commands using the bot's name * Moved _wrap_check and _wrap_process to the CommandManager * Removed extra sortkey in afc_statisticstags/v0.1^2
| @@ -67,22 +67,6 @@ class BaseCommand(object): | |||||
| self.mode = lambda t, level, msg: self.bot.frontend.mode(t, level, msg) | self.mode = lambda t, level, msg: self.bot.frontend.mode(t, level, msg) | ||||
| self.pong = lambda target: self.bot.frontend.pong(target) | self.pong = lambda target: self.bot.frontend.pong(target) | ||||
| def _wrap_check(self, data): | |||||
| """Check whether this command should be called, catching errors.""" | |||||
| try: | |||||
| return self.check(data) | |||||
| except Exception: | |||||
| e = "Error checking command '{0}' with data: {1}:" | |||||
| self.logger.exception(e.format(self.name, data)) | |||||
| def _wrap_process(self, data): | |||||
| """process() the message, catching and reporting any errors.""" | |||||
| try: | |||||
| self.process(data) | |||||
| except Exception: | |||||
| e = "Error executing command '{0}':" | |||||
| self.logger.exception(e.format(data.command)) | |||||
| def check(self, data): | def check(self, data): | ||||
| """Return whether this command should be called in response to 'data'. | """Return whether this command should be called in response to 'data'. | ||||
| @@ -29,11 +29,21 @@ class Command(BaseCommand): | |||||
| """Displays help information.""" | """Displays help information.""" | ||||
| name = "help" | name = "help" | ||||
| def check(self, data): | |||||
| if data.is_command: | |||||
| if data.command == "help": | |||||
| return True | |||||
| if not data.command and data.trigger == data.my_nick: | |||||
| return True | |||||
| return False | |||||
| def process(self, data): | def process(self, data): | ||||
| if not data.args: | |||||
| self.do_main_help(data) | |||||
| else: | |||||
| if not data.command: | |||||
| self.do_hello(data) | |||||
| if data.args: | |||||
| self.do_command_help(data) | self.do_command_help(data) | ||||
| else: | |||||
| self.do_main_help(data) | |||||
| def do_main_help(self, data): | def do_main_help(self, data): | ||||
| """Give the user a general help message with a list of all commands.""" | """Give the user a general help message with a list of all commands.""" | ||||
| @@ -66,3 +76,6 @@ class Command(BaseCommand): | |||||
| msg = "sorry, no help for \x0303{0}\x0301.".format(command) | msg = "sorry, no help for \x0303{0}\x0301.".format(command) | ||||
| self.reply(data, msg) | self.reply(data, msg) | ||||
| def do_hello(self, data): | |||||
| self.say(data.chan, "Yes, {0}?".format(data.nick)) | |||||
| @@ -43,12 +43,13 @@ class Command(BaseCommand): | |||||
| self.do_reload(data) | self.do_reload(data) | ||||
| def do_quit(self, data): | def do_quit(self, data): | ||||
| nick = self.config.irc.frontend["nick"] | |||||
| if not data.args or data.args[0].lower() != nick.lower(): | |||||
| args = data.args | |||||
| nick = self.config.irc.frontend["nick"].lower() | |||||
| if data.trigger != nick and (not args or args[0].lower() != nick): | |||||
| self.reply(data, "to confirm this action, the first argument must be my nickname.") | self.reply(data, "to confirm this action, the first argument must be my nickname.") | ||||
| return | return | ||||
| if data.args[1:]: | |||||
| msg = " ".join(data.args[1:]) | |||||
| if args[1:]: | |||||
| msg = " ".join(args[1:]) | |||||
| self.bot.stop("Stopped by {0}: {1}".format(data.nick, msg)) | self.bot.stop("Stopped by {0}: {1}".format(data.nick, msg)) | ||||
| else: | else: | ||||
| self.bot.stop("Stopped by {0}".format(data.nick)) | self.bot.stop("Stopped by {0}".format(data.nick)) | ||||
| @@ -119,7 +119,7 @@ class Command(BaseCommand): | |||||
| tasks = ", ".join(tasklist) | tasks = ", ".join(tasklist) | ||||
| msg = "{0} tasks loaded: {1}.".format(len(tasklist), tasks) | |||||
| msg = "\x02{0}\x0F tasks loaded: {1}.".format(len(tasklist), tasks) | |||||
| self.reply(self.data, msg) | self.reply(self.data, msg) | ||||
| def do_start(self): | def do_start(self): | ||||
| @@ -33,8 +33,9 @@ class KwargParseException(Exception): | |||||
| class Data(object): | class Data(object): | ||||
| """Store data from an individual line received on IRC.""" | """Store data from an individual line received on IRC.""" | ||||
| def __init__(self, line): | |||||
| def __init__(self, bot, line): | |||||
| self.line = line | self.line = line | ||||
| self.my_nick = bot.config.irc["frontend"]["nick"].lower() | |||||
| self.chan = self.nick = self.ident = self.host = self.msg = "" | self.chan = self.nick = self.ident = self.host = self.msg = "" | ||||
| def parse_args(self): | def parse_args(self): | ||||
| @@ -46,20 +47,28 @@ class Data(object): | |||||
| # Isolate command arguments: | # Isolate command arguments: | ||||
| self.args = args[1:] | self.args = args[1:] | ||||
| self.is_command = False # is this message a command? | |||||
| self.is_command = False # Is this message a command? | |||||
| self.trigger = None # What triggered this command? (!, ., or our nick) | |||||
| try: | try: | ||||
| self.command = args[0] | |||||
| self.command = args[0].lower() | |||||
| except IndexError: | except IndexError: | ||||
| self.command = None | self.command = None | ||||
| return | |||||
| try: | |||||
| if self.command.startswith('!') or self.command.startswith('.'): | |||||
| self.is_command = True | |||||
| self.command = self.command[1:] # Strip the '!' or '.' | |||||
| self.command = self.command.lower() | |||||
| except AttributeError: | |||||
| pass | |||||
| if self.command.startswith("!") or self.command.startswith("."): | |||||
| # e.g. "!command arg1 arg2" | |||||
| self.is_command = True | |||||
| self.trigger = self.command[0] | |||||
| self.command = self.command[1:] # Strip the "!" or "." | |||||
| elif self.command.startswith(self.my_nick): | |||||
| # e.g. "EarwigBot, command arg1 arg2" | |||||
| self.is_command = True | |||||
| self.trigger = self.my_nick | |||||
| try: | |||||
| self.command = self.args.pop(0).lower() | |||||
| except IndexError: | |||||
| self.command = "" | |||||
| def parse_kwargs(self): | def parse_kwargs(self): | ||||
| """Parse keyword arguments embedded in self.args. | """Parse keyword arguments embedded in self.args. | ||||
| @@ -51,7 +51,7 @@ class Frontend(IRCConnection): | |||||
| def _process_message(self, line): | def _process_message(self, line): | ||||
| """Process a single message from IRC.""" | """Process a single message from IRC.""" | ||||
| line = line.strip().split() | line = line.strip().split() | ||||
| data = Data(line) # New Data instance to store info about this line | |||||
| data = Data(self.bot, line) | |||||
| if line[1] == "JOIN": | if line[1] == "JOIN": | ||||
| data.nick, data.ident, data.host = self.sender_regex.findall(line[0])[0] | data.nick, data.ident, data.host = self.sender_regex.findall(line[0])[0] | ||||
| @@ -147,13 +147,29 @@ class CommandManager(_ResourceManager): | |||||
| base = super(CommandManager, self) | base = super(CommandManager, self) | ||||
| base.__init__(bot, "commands", "Command", BaseCommand) | base.__init__(bot, "commands", "Command", BaseCommand) | ||||
| def _wrap_check(self, command, data): | |||||
| """Check whether a command should be called, catching errors.""" | |||||
| try: | |||||
| return command.check(data) | |||||
| except Exception: | |||||
| e = "Error checking command '{0}' with data: {1}:" | |||||
| self.logger.exception(e.format(command.name, data)) | |||||
| def _wrap_process(self, command, data): | |||||
| """process() the message, catching and reporting any errors.""" | |||||
| try: | |||||
| command.process(data) | |||||
| except Exception: | |||||
| e = "Error executing command '{0}':" | |||||
| self.logger.exception(e.format(data.command)) | |||||
| def check(self, hook, data): | def check(self, hook, data): | ||||
| """Given an IRC event, check if there's anything we can respond to.""" | """Given an IRC event, check if there's anything we can respond to.""" | ||||
| self.lock.acquire() | self.lock.acquire() | ||||
| for command in self._resources.itervalues(): | for command in self._resources.itervalues(): | ||||
| if hook in command.hooks and command._wrap_check(data): | |||||
| if hook in command.hooks and self._wrap_check(command, data): | |||||
| self.lock.release() | self.lock.release() | ||||
| command._wrap_process(data) | |||||
| self._wrap_process(command, data) | |||||
| return | return | ||||
| self.lock.release() | self.lock.release() | ||||
| @@ -165,11 +165,9 @@ class Task(BaseTask): | |||||
| table, where keys are column names and values are their cell contents. | table, where keys are column names and values are their cell contents. | ||||
| """ | """ | ||||
| row = "{0}|s={page_status}|t={page_title}|h={page_short}|z={page_size}|" | row = "{0}|s={page_status}|t={page_title}|h={page_short}|z={page_size}|" | ||||
| row += "sr={page_special_user}|sh={page_special_hidden}|sd={page_special_time}|si={page_special_oldid}|" | |||||
| row += "mr={page_modify_user}|mh={page_modify_hidden}|md={page_modify_time}|mi={page_modify_oldid}" | |||||
| row += "sr={page_special_user}|sd={page_special_time}|si={page_special_oldid}|" | |||||
| row += "mr={page_modify_user}|md={page_modify_time}|mi={page_modify_oldid}" | |||||
| page["page_special_hidden"] = self.format_hidden(page["page_special_time"]) | |||||
| page["page_modify_hidden"] = self.format_hidden(page["page_modify_time"]) | |||||
| page["page_special_time"] = self.format_time(page["page_special_time"]) | page["page_special_time"] = self.format_time(page["page_special_time"]) | ||||
| page["page_modify_time"] = self.format_time(page["page_modify_time"]) | page["page_modify_time"] = self.format_time(page["page_modify_time"]) | ||||
| @@ -182,13 +180,6 @@ class Task(BaseTask): | |||||
| """Format a datetime into the standard MediaWiki timestamp format.""" | """Format a datetime into the standard MediaWiki timestamp format.""" | ||||
| return dt.strftime("%H:%M, %d %b %Y") | return dt.strftime("%H:%M, %d %b %Y") | ||||
| def format_hidden(self, dt): | |||||
| """Convert a datetime into seconds since the epoch. | |||||
| This is used by the template as a hidden sortkey. | |||||
| """ | |||||
| return int((dt - datetime(1970, 1, 1)).total_seconds()) | |||||
| def sync(self, **kwargs): | def sync(self, **kwargs): | ||||
| """Synchronize our local statistics database with the site. | """Synchronize our local statistics database with the site. | ||||