From e743734bac88c7845d82147a79a8208d3c1f9c12 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Tue, 19 Apr 2011 01:29:18 -0400 Subject: [PATCH] redoing irc command management: rewriting triggers and creating a BaseCommand class, also some changes to Data and Connection --- core/main.py | 3 +- irc/base_command.py | 24 ++++++++++++++++ irc/connection.py | 8 +++--- irc/data.py | 12 ++++++-- irc/frontend.py | 17 ++++++++---- irc/triggers.py | 79 +++++++++++++++++++++++++++++++++++++---------------- 6 files changed, 107 insertions(+), 36 deletions(-) create mode 100644 irc/base_command.py diff --git a/core/main.py b/core/main.py index bcf2889..8cc7273 100644 --- a/core/main.py +++ b/core/main.py @@ -35,12 +35,13 @@ def irc_watcher(f_conn): def run(): global f_conn f_conn = frontend.get_connection() + frontend.startup(f_conn) t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,)) t_watcher.daemon = True t_watcher.start() - frontend.main(f_conn) + frontend.main() w_conn.close() f_conn.close() diff --git a/irc/base_command.py b/irc/base_command.py new file mode 100644 index 0000000..2e1f123 --- /dev/null +++ b/irc/base_command.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# A base class for commands on IRC. + +class BaseCommand(object): + def __init__(self, connection): + """docstring""" + self.connection = connection + + def get_hook(self): + """Hooks are: 'msg', 'msg_private', 'msg_public', and 'join'.""" + return None + + def get_help(self, command): + """docstring""" + return None + + def check(self, data): + """docstring""" + return False + + def process(self, data): + """docstring""" + pass diff --git a/irc/connection.py b/irc/connection.py index a2c2ac0..1bcbefa 100644 --- a/irc/connection.py +++ b/irc/connection.py @@ -5,8 +5,8 @@ import socket import threading -class Connection: - def __init__(self, host, port, nick, ident, realname): +class Connection(object): + def __init__(self, host=None, port=None, nick=None, ident=None, realname=None): """a class to interface with IRC""" self.host = host self.port = port @@ -50,9 +50,9 @@ class Connection: """send a message""" self.send("PRIVMSG %s :%s" % (target, msg)) - def reply(self, target, nick, msg): + def reply(self, data, msg): """send a message as a reply""" - self.say(target, "%s%s%s: %s" % (chr(2), nick, chr(0x0f), msg)) + self.say(data.chan, "%s%s%s: %s" % (chr(2), data.nick, chr(0x0f), msg)) def action(self, target, msg): """send a message as an action""" diff --git a/irc/data.py b/irc/data.py index 092e709..0d0fc7b 100644 --- a/irc/data.py +++ b/irc/data.py @@ -2,7 +2,7 @@ # A class to store data from an individual line received on IRC. -class Data: +class Data(object): def __init__(self): """store data from an individual line received on IRC""" self.chan = str() @@ -17,9 +17,17 @@ class Data: while '' in args: # remove any empty arguments args.remove('') + self.args = args[1:] # the command arguments + self.is_command = False # whether this is a real command or not + try: self.command = args[0] # the command itself except IndexError: self.command = None - self.args = args[1:] # the command arguments + try: + if self.command.startswith('!') or self.command.startswith('.'): + self.is_command = True + self.command = self.command[1:] # strip '!' or '.' + except AttributeError: + pass diff --git a/irc/frontend.py b/irc/frontend.py index 1c6967a..6780096 100644 --- a/irc/frontend.py +++ b/irc/frontend.py @@ -10,12 +10,19 @@ from irc import triggers from irc.connection import Connection from irc.data import Data +connection = None + def get_connection(): connection = Connection(HOST, PORT, NICK, IDENT, REALNAME) return connection -def main(connection): +def startup(conn): + global connection + connection = conn + triggers.init_commands(connection) connection.connect() + +def main(): read_buffer = str() while 1: @@ -36,7 +43,7 @@ def main(connection): data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0] data.chan = line[2][1:] - triggers.check(connection, data, "join") # check if there's anything we can respond to, and if so, respond + triggers.check("join", data) # check if there's anything we can respond to, and if so, respond if line[1] == "PRIVMSG": data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0] @@ -45,11 +52,11 @@ def main(connection): if data.chan == NICK: # this is a privmsg to us, so set 'chan' as the nick of the sender data.chan = data.nick - triggers.check(connection, data, "msg_private") # only respond if it's a private message + triggers.check("msg_private", data) # only respond if it's a private message else: - triggers.check(connection, data, "msg_public") # only respond if it's a public (channel) message + triggers.check("msg_public", data) # only respond if it's a public (channel) message - triggers.check(connection, data, "msg") # check for general messages + triggers.check("msg", data) # check for general messages if data.msg.startswith("!restart"): # hardcode the !restart command (we can't restart from within an ordinary command) if data.host in OWNERS: diff --git a/irc/triggers.py b/irc/triggers.py index 0e6cda1..e6dfe38 100644 --- a/irc/triggers.py +++ b/irc/triggers.py @@ -1,36 +1,67 @@ # -*- coding: utf-8 -*- -# Check what events on IRC we can respond to. +# A module to manage IRC commands. -from irc.commands import test, help, git, link, chanops +import os +import traceback -def check(connection, data, hook): - data.parse_args() # parse command arguments into data.command and data.args +commands = [] + +def init_commands(connection, silent=False): + """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/ + + for f in files: + if f.startswith("_") or not f.endswith(".py"): # ignore non-python files or files beginning with "_" + continue + + module = f[:-3] # strip .py from end - if hook == "join": - pass + try: + exec "from irc.commands import %s" % module + except: # importing the file failed for some reason... + if not silent: + print "Couldn't load file %s:" % f + traceback.print_exc() + continue - if hook == "msg_private": - pass + m = eval(module) # 'module' is a string, so get the actual object for processing + process_module(connection, m) - if hook == "msg_public": - pass + if not silent: + pretty_cmnds = map(lambda c: c.__class__.__name__, commands) + print "Found %s command classes: %s." % (len(commands), ', '.join(pretty_cmnds)) - if hook == "msg": - if data.command == "!test": - test.call(connection, data) +def process_module(connection, module): + """go through all objects in a module and add valid command classes to the commands variable""" + global commands + objects = dir(module) - elif data.command == "!help": - help.call(connection, data) + for this_obj in objects: # go through everything in the file + obj = eval("module.%s" % this_obj) # this_obj is a string, so get the actual object corresponding to that string - elif data.command == "!git": - git.call(connection, data) + try: + bases = obj.__bases__ + except AttributeError: # object isn't a valid class, so ignore it + continue - elif (data.command == "!link" or - ("[[" in data.msg and "]]" in data.msg) or - ("{{" in data.msg and "}}" in data.msg)): - link.call(connection, data) + for base in bases: + if base.__name__ == "BaseCommand": # this inherits BaseCommand, so it must be a command class + command = obj(connection) # initialize a new command object + commands.append(command) + print "Added command class %s from %s..." % (this_obj, module.__name__) + continue + +def get_commands(): + """get our 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""" + data.parse_args() # parse command arguments into data.command and data.args - elif (data.command == "!voice" or data.command == "!devoice" or - data.command == "!op" or data.command == "!deop"): - chanops.call(connection, data) + for command in commands: + if command.get_hook() == hook: + if command.check(data): + command.process(data) + break