@@ -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.""" | |||