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