* 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.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): | |||
| """Return whether this command should be called in response to 'data'. | |||
| @@ -29,11 +29,21 @@ class Command(BaseCommand): | |||
| """Displays help information.""" | |||
| 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): | |||
| 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) | |||
| else: | |||
| self.do_main_help(data) | |||
| def do_main_help(self, data): | |||
| """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) | |||
| 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) | |||
| 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.") | |||
| 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)) | |||
| else: | |||
| self.bot.stop("Stopped by {0}".format(data.nick)) | |||
| @@ -119,7 +119,7 @@ class Command(BaseCommand): | |||
| 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) | |||
| def do_start(self): | |||
| @@ -33,8 +33,9 @@ class KwargParseException(Exception): | |||
| class Data(object): | |||
| """Store data from an individual line received on IRC.""" | |||
| def __init__(self, line): | |||
| def __init__(self, bot, line): | |||
| self.line = line | |||
| self.my_nick = bot.config.irc["frontend"]["nick"].lower() | |||
| self.chan = self.nick = self.ident = self.host = self.msg = "" | |||
| def parse_args(self): | |||
| @@ -46,20 +47,28 @@ class Data(object): | |||
| # Isolate command arguments: | |||
| 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: | |||
| self.command = args[0] | |||
| self.command = args[0].lower() | |||
| except IndexError: | |||
| 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): | |||
| """Parse keyword arguments embedded in self.args. | |||
| @@ -51,7 +51,7 @@ class Frontend(IRCConnection): | |||
| def _process_message(self, line): | |||
| """Process a single message from IRC.""" | |||
| 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": | |||
| 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.__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): | |||
| """Given an IRC event, check if there's anything we can respond to.""" | |||
| self.lock.acquire() | |||
| 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() | |||
| command._wrap_process(data) | |||
| self._wrap_process(command, data) | |||
| return | |||
| self.lock.release() | |||
| @@ -165,11 +165,9 @@ class Task(BaseTask): | |||
| 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 += "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_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.""" | |||
| 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): | |||
| """Synchronize our local statistics database with the site. | |||