* 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. | ||||