@@ -35,12 +35,13 @@ def irc_watcher(f_conn): | |||||
def run(): | def run(): | ||||
global f_conn | global f_conn | ||||
f_conn = frontend.get_connection() | f_conn = frontend.get_connection() | ||||
frontend.startup(f_conn) | |||||
t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,)) | t_watcher = threading.Thread(target=irc_watcher, args=(f_conn,)) | ||||
t_watcher.daemon = True | t_watcher.daemon = True | ||||
t_watcher.start() | t_watcher.start() | ||||
frontend.main(f_conn) | |||||
frontend.main() | |||||
w_conn.close() | w_conn.close() | ||||
f_conn.close() | f_conn.close() | ||||
@@ -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 |
@@ -5,8 +5,8 @@ | |||||
import socket | import socket | ||||
import threading | 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""" | """a class to interface with IRC""" | ||||
self.host = host | self.host = host | ||||
self.port = port | self.port = port | ||||
@@ -50,9 +50,9 @@ class Connection: | |||||
"""send a message""" | """send a message""" | ||||
self.send("PRIVMSG %s :%s" % (target, msg)) | self.send("PRIVMSG %s :%s" % (target, msg)) | ||||
def reply(self, target, nick, msg): | |||||
def reply(self, data, msg): | |||||
"""send a message as a reply""" | """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): | def action(self, target, msg): | ||||
"""send a message as an action""" | """send a message as an action""" | ||||
@@ -2,7 +2,7 @@ | |||||
# A class to store data from an individual line received on IRC. | # A class to store data from an individual line received on IRC. | ||||
class Data: | |||||
class Data(object): | |||||
def __init__(self): | def __init__(self): | ||||
"""store data from an individual line received on IRC""" | """store data from an individual line received on IRC""" | ||||
self.chan = str() | self.chan = str() | ||||
@@ -17,9 +17,17 @@ class Data: | |||||
while '' in args: # remove any empty arguments | while '' in args: # remove any empty arguments | ||||
args.remove('') | args.remove('') | ||||
self.args = args[1:] # the command arguments | |||||
self.is_command = False # whether this is a real command or not | |||||
try: | try: | ||||
self.command = args[0] # the command itself | self.command = args[0] # the command itself | ||||
except IndexError: | except IndexError: | ||||
self.command = None | 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 |
@@ -10,12 +10,19 @@ from irc import triggers | |||||
from irc.connection import Connection | from irc.connection import Connection | ||||
from irc.data import Data | from irc.data import Data | ||||
connection = None | |||||
def get_connection(): | def get_connection(): | ||||
connection = Connection(HOST, PORT, NICK, IDENT, REALNAME) | connection = Connection(HOST, PORT, NICK, IDENT, REALNAME) | ||||
return connection | return connection | ||||
def main(connection): | |||||
def startup(conn): | |||||
global connection | |||||
connection = conn | |||||
triggers.init_commands(connection) | |||||
connection.connect() | connection.connect() | ||||
def main(): | |||||
read_buffer = str() | read_buffer = str() | ||||
while 1: | while 1: | ||||
@@ -36,7 +43,7 @@ def main(connection): | |||||
data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0] | data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0] | ||||
data.chan = line[2][1:] | 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": | if line[1] == "PRIVMSG": | ||||
data.nick, data.ident, data.host = re.findall(":(.*?)!(.*?)@(.*?)\Z", line[0])[0] | 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 | if data.chan == NICK: # this is a privmsg to us, so set 'chan' as the nick of the sender | ||||
data.chan = data.nick | 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: | 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.msg.startswith("!restart"): # hardcode the !restart command (we can't restart from within an ordinary command) | ||||
if data.host in OWNERS: | if data.host in OWNERS: | ||||
@@ -1,36 +1,67 @@ | |||||
# -*- coding: utf-8 -*- | # -*- 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 |