| @@ -1,32 +1,41 @@ | |||
| # -*- coding: utf-8 -*- | |||
| class BaseCommand(object): | |||
| """A base class for commands on IRC.""" | |||
| """A base class for commands on IRC. | |||
| This docstring is reported to the user when they use !help <command>. | |||
| """ | |||
| # This is the command's name, as reported to the user when they use !help: | |||
| name = "base_command" | |||
| # Hooks are "msg", "msg_private", "msg_public", and "join". "msg" is the | |||
| # default behavior; if you wish to override that, change the value in your | |||
| # command subclass: | |||
| hooks = ["msg"] | |||
| def __init__(self, connection): | |||
| self.connection = connection | |||
| def get_hooks(self): | |||
| """Hooks are: 'msg', 'msg_private', 'msg_public', and 'join'. Return | |||
| the hooks you want this command to be called on.""" | |||
| return [] | |||
| def check(self, data): | |||
| """Returns whether this command should be called in response to 'data'. | |||
| def get_help(self, command): | |||
| """Return help information for the command, used by !help. return None | |||
| for no help. If a given class handles multiple commands, the command | |||
| variable can be used to return different help for each one.""" | |||
| return None | |||
| Given a Data() instance, return True if we should respond to this | |||
| activity, or False if we should ignore it or it doesn't apply to us. | |||
| def check(self, data): | |||
| """Given a Data() object, return True if we should respond to this | |||
| activity, or False if we should ignore it/it doesn't apply to us. Most | |||
| commands return True if data.command == 'command_name', otherwise | |||
| they return False.""" | |||
| Most commands return True if data.command == self.name, otherwise they | |||
| return False. This is the default behavior of check(); you need only | |||
| override it if you wish to change that. | |||
| """ | |||
| if data.is_command and data.command == self.name: | |||
| return True | |||
| return False | |||
| def process(self, data): | |||
| """Handle an activity (usually a message) on IRC. At this point, thanks | |||
| """Main entry point for doing a command. | |||
| Handle an activity (usually a message) on IRC. At this point, thanks | |||
| to self.check() which is called automatically by command_handler, we | |||
| know this is something we should respond to, so (usually) a | |||
| 'if data.command != "command_name": return' is unnecessary.""" | |||
| 'if data.command != "command_name": return' is unnecessary. | |||
| """ | |||
| pass | |||
| @@ -5,15 +5,23 @@ class BaseTask(object): | |||
| task_name = None | |||
| def __init__(self): | |||
| """This is called once immediately after the task class is loaded by | |||
| the task manager (in wiki.task_manager.load_class_from_file()).""" | |||
| """Constructor for new tasks. | |||
| This is called once immediately after the task class is loaded by | |||
| the task manager (in tasks._load_class_from_file()). | |||
| """ | |||
| pass | |||
| def run(self, **kwargs): | |||
| """This is called directly by task_manager.start_task() and is the main | |||
| way to make a task do stuff. kwargs will be any keyword arguments | |||
| passed to start_task(), which are (of course) optional. The same task | |||
| instance is preserved between runs, so you can theoretically store data | |||
| in self (e.g. start_task('mytask', action='store', data='foo')) and | |||
| then use it later (e.g. start_task('mytask', action='save')).""" | |||
| """Main entry point to run a given task. | |||
| This is called directly by tasks.start() and is the main way to make a | |||
| task do stuff. kwargs will be any keyword arguments passed to start() | |||
| which are entirely optional. | |||
| The same task instance is preserved between runs, so you can | |||
| theoretically store data in self (e.g. | |||
| start('mytask', action='store', data='foo')) and then use it later | |||
| (e.g. start('mytask', action='save')). | |||
| """ | |||
| pass | |||
| @@ -19,7 +19,7 @@ class Connection(object): | |||
| self.ident = ident | |||
| self.realname = realname | |||
| # A lock to prevent us from sending two messages at once. | |||
| # A lock to prevent us from sending two messages at once: | |||
| self.lock = threading.Lock() | |||
| def connect(self): | |||
| @@ -58,8 +58,7 @@ class Connection(object): | |||
| self.send(message) | |||
| def reply(self, data, msg): | |||
| """Send a private message as a reply to a user on the server. `data` is | |||
| a Data object (or anything with chan and nick attributes).""" | |||
| """Send a private message as a reply to a user on the server.""" | |||
| message = "".join((chr(2), data.nick, chr(0x0f), ": ", msg)) | |||
| self.say(data.chan, message) | |||
| @@ -13,11 +13,7 @@ class Data(object): | |||
| def __init__(self, line): | |||
| self.line = line | |||
| self.chan = str() | |||
| self.nick = str() | |||
| self.ident = str() | |||
| self.host = str() | |||
| self.msg = str() | |||
| self.chan = self.nick = self.ident = self.host = self.msg = "" | |||
| def parse_args(self): | |||
| """Parse command args from self.msg into self.command and self.args.""" | |||
| @@ -1,16 +1,23 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # A module to manage IRC commands. | |||
| """ | |||
| EarwigBot's IRC Command Manager | |||
| This package provides the IRC "commands" used by the bot's front-end component. | |||
| In __init__, you can find some functions used to load and run these commands. | |||
| """ | |||
| import os | |||
| import traceback | |||
| __all__ = ["load", "get_all", "check"] | |||
| _commands = [] | |||
| # Store commands in a dict, where the key is the command's name and the value | |||
| # is an instance of the command's class: | |||
| _commands = {} | |||
| def _process_module(connection, module): | |||
| """go through all objects in a module and add valid command classes to the commands variable""" | |||
| def _load_class_from_file(connection, module): | |||
| """Add.""" | |||
| global commands | |||
| objects = dir(module) | |||
| @@ -30,9 +37,9 @@ def _process_module(connection, module): | |||
| continue | |||
| def load(connection): | |||
| """load all valid command classes from irc/commmands/ into the commands variable""" | |||
| files = os.listdir(os.path.join("irc", "commands")) # get all files in irc/commands/ | |||
| files.sort() # alphabetically sort list of files | |||
| """Load all valid commands into the _commands global variable.""" | |||
| files = os.listdir(os.path.join("bot", "commands")) | |||
| files.sort() | |||
| for f in files: | |||
| if f.startswith("_") or not f.endswith(".py"): # ignore non-python files or files beginning with "_" | |||
| @@ -50,12 +57,11 @@ def load(connection): | |||
| print "Found %s command classes: %s." % (len(commands), ', '.join(pretty_cmnds)) | |||
| def get_all(): | |||
| """Return our list of all commands.""" | |||
| """Return our dict of all loaded commands.""" | |||
| return _commands | |||
| def check(hook, data): | |||
| """Given an event on IRC, check if there's anything we can respond to by | |||
| calling each command class""" | |||
| """Given an event on IRC, check if there's anything we can respond to.""" | |||
| # parse command arguments into data.command and data.args | |||
| data.parse_args() | |||
| @@ -1,54 +1,44 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # Generates help information. | |||
| from classes import BaseCommand, Data | |||
| import commands | |||
| from irc.classes import BaseCommand, Data | |||
| from irc import command_handler | |||
| class Help(BaseCommand): | |||
| def get_hooks(self): | |||
| return ["msg"] | |||
| def get_help(self, command): | |||
| return "Generates help information." | |||
| def check(self, data): | |||
| if data.is_command and data.command == "help": | |||
| return True | |||
| return False | |||
| class Command(BaseCommand): | |||
| """Generates help information.""" | |||
| name = "help" | |||
| def process(self, data): | |||
| self.cmnds = commands.get_all().keys() | |||
| if not data.args: | |||
| self.do_general_help(data) | |||
| self.do_main_help(data) | |||
| else: | |||
| if data.args[0] == "list": | |||
| self.do_list_help(data) | |||
| else: | |||
| self.do_command_help(data) | |||
| def do_general_help(self, data): | |||
| self.connection.reply(data, "I am a bot! You can get help for any command with '!help <command>', or a list of all loaded modules with '!help list'.") | |||
| self.do_command_help(data) | |||
| def do_list_help(self, data): | |||
| commands = command_handler.get_commands() | |||
| cmnds = map(lambda c: c.__class__.__name__, commands) | |||
| pretty_cmnds = ', '.join(cmnds) | |||
| self.connection.reply(data, "%s command classes loaded: %s." % (len(cmnds), pretty_cmnds)) | |||
| def do_main_help(self, data): | |||
| """Give the user a general help message with a list of all commands.""" | |||
| msg = "I am a bot! I have {0} commands loaded: {1}. You can get help for any command with '!help <command>'." | |||
| msg.format(len(self.cmnds), ', '.join(self.cmnds)) | |||
| self.connection.reply(data, msg) | |||
| def do_command_help(self, data): | |||
| """Give the user help for a specific command.""" | |||
| command = data.args[0] | |||
| commands = command_handler.get_commands() | |||
| dummy = Data() # dummy message to test which command classes pick up this command | |||
| dummy.command = command.lower() # lowercase command name | |||
| # Create a dummy message to test which commands pick up the user's | |||
| # input: | |||
| dummy = Data() | |||
| dummy.command = command.lower() | |||
| dummy.is_command = True | |||
| for cmnd in commands: | |||
| for cmnd in self.cmnds: | |||
| if cmnd.check(dummy): | |||
| help = cmnd.get_help(command) | |||
| doc = cmnd.__doc__ | |||
| if doc: | |||
| msg = "info for command \x0303{0}\x0301: \"{1}\"" | |||
| msg.format(command, doc) | |||
| self.connection.reply(data, msg) | |||
| return | |||
| break | |||
| try: | |||
| self.connection.reply(data, "info for command \x0303%s\x0301: \"%s\"" % (command, help)) | |||
| except UnboundLocalError: | |||
| self.connection.reply(data, "sorry, no help for \x0303%s\x0301." % command) | |||
| msg = "sorry, no help for \x0303{0}\x0301.".format(command) | |||
| self.connection.reply(data, msg) | |||
| @@ -1,22 +1,12 @@ | |||
| # -*- coding: utf-8 -*- | |||
| # A very simple command to test the bot. | |||
| import random | |||
| from irc.classes import BaseCommand | |||
| class Test(BaseCommand): | |||
| def get_hooks(self): | |||
| return ["msg"] | |||
| def get_help(self, command): | |||
| return "Test the bot!" | |||
| from classes import BaseCommand | |||
| def check(self, data): | |||
| if data.is_command and data.command == "test": | |||
| return True | |||
| return False | |||
| class Command(BaseCommand): | |||
| """Test the bot!""" | |||
| name = "test" | |||
| def process(self, data): | |||
| hey = random.randint(0, 1) | |||
| @@ -16,9 +16,9 @@ import config | |||
| __all__ = ["load", "schedule", "start"] | |||
| # store loaded tasks as a dict where the key is the task name and the value is | |||
| # an instance of the task class (wiki.tasks.task_file.Task()) | |||
| _tasks = dict() | |||
| # Store loaded tasks as a dict where the key is the task name and the value is | |||
| # an instance of the task class: | |||
| _tasks = {} | |||
| def _load_class_from_file(f): | |||
| """Look in a given file for the task class.""" | |||