import socket, os, threading, re import plugins class PluginManager(object): def __init__(self, server): self.server = server self.plugins = {} self.prnt = server.prnt self.handlers = { "join" : [ ], "part" : [ ], "quit" : [ ], "message" : [ ], "connect" : [ ], "disconnect": [ ], "action" : [ ], "nick" : [ ], "kick" : [ ], "mode" : [ ], "command" : { }, } def handle(self, event, handler_func, handler_commands=None): event = event.lower() if event == "command": if handler_commands is not None: for command in handler_commands: self.handlers[event][command.lower()] = handler_func else: self.handlers[event].append(handler_func) def unhandle(self, event, handler_func, handler_commands=None): event = event.lower() if event == "command": handlers = {} for command, handler in self.handlers[event]: if handler != handler_func and not command.lower() in handler_commands: handlers.insert(0, handler) self.handlers[event] = handlers else: handlers = [] for handler in self.handlers[event]: if handler != handler_func: handlers.insert(0, handler) self.handlers[event] = handlers def event(self, eventName, *args): eventName = eventName.lower() if eventName == "command": handler = None try: handler = self.handlers[eventName][args[2].lower()] except KeyError,ke: return #no such command try: handler(*args) except Exception, e: s = handler.__self__ self.prnt("Plugin "+s.__class__.__name__+" "+e.__class__.__name__+": "+e.__str__()) else: for handler in self.handlers[eventName]: try: handler(*args) except Exception, e: s = handler.__self__ self.prnt("Plugin "+s.__class__.__name__+" "+e.__class__.__name__+": "+e.__str__()) def loadedPlugin(self, pluginName): return pluginName.lower() in self.plugins def pluginExists(self, pluginName): return plugins.getPlugin(pluginName.lower()) is not None def loadPlugin(self, pluginName): plugins.refresh() if not self.loadedPlugin(pluginName.lower()): if not self.pluginExists(pluginName.lower()): err = "No such plugin." return err pClass = None try: pClass = plugins.getPlugin(pluginName.lower()) except Exception, e: err = "Cannot load plugin "+pluginName+" ("+e.__class__.__name__+": "+e.__str__()+")" self.prnt(err) return err try: self.plugins[pluginName.lower()] = pClass(self.server) except Exception, e: err = "Could not initialize "+pluginName+" ("+e.__class__.__name__+": "+e.__str__()+")" self.prnt(err) return err else: err = "Module has already been loaded." return err def getPlugin(self, pluginName): try: return self.plugins[pluginName.lower()] except KeyError: return None def reloadPlugin(self, pluginName): errs = {} err = self.unloadPlugin(pluginName.lower()) if err is not None: errs["unload"] = err err = self.loadPlugin(pluginName.lower()) if err is not None: errs["load"] = err return errs def unloadPlugin(self, pluginName): if self.loadedPlugin(pluginName.lower()): inst = self.plugins[pluginName.lower()] for event, eList in self.handlers.items(): if event == "command": newList = {} for cmd,func in eList.items(): if func.__self__ == inst: destructor = getattr(inst, "__del__", None) if destructor is not None: destructor() else: newList[cmd] = func self.handlers[event] = newList else: newList = [] for func in eList: if func.__self__ == inst: destructor = getattr(inst, "__del__", None) if destructor is not None: destructor() else: newList.append(func) self.handlers[event] = newList del self.plugins[pluginName.lower()] else: err = "Plugin "+pluginName+" is not loaded." return err def loadAllPlugins(self): errs = {} plugins = [k for k in self.server.config["plugins"].split(",") if k != ''] for plugin in plugins: err = self.loadPlugin(plugin.lower()) if err is not None: errs[plugin] = err return errs def unloadAllPlugins(self): errs = {} for plugin in self.plugins.copy().keys(): err = self.unloadPlugin(plugin.lower()) if err is not None: errs[plugin] = err return errs class IRC(threading.Thread): def __init__(self, config): super(IRC, self).__init__() self.pluginManager = PluginManager(self) self.config = config self.handle = self.pluginManager.handle self.unhandle = self.pluginManager.unhandle self.handlers = self.pluginManager.handlers self.plugins = self.pluginManager.plugins self.getPlugin = self.pluginManager.getPlugin errs = self.pluginManager.loadAllPlugins() if errs: for k,v in errs.items(): self.prnt("Plugin "+k+" error loading: "+v) self.running = False self.userlist = { } def _readline(self): ret = [] while True: c = self.conn.recv(1) if c == '\n': break else: if c != '\r': ret.append(c) return "".join(ret) def run(self): self.conn = socket.socket() self.conn.settimeout(60) try: self.conn.connect((self.config["host"], int(self.config["port"]))) self.goodstart = True except socket.error: self.prnt("Cannot connect to server.") self.goodstart = False if self.goodstart: ident = self.config["ident"] if self.config["ident"] != "" else self.config["nickname"] self.sendLine("USER "+ident+" * * *") self.sendLine("NICK "+self.config["nickname"]) self.running = self.goodstart while self.running: try: data = self._readline() except socket.timeout: self.sendLine("PING BACK") try: data = self._readline() continue except socket.timeout: raise IOError("Connection was broken.") if not self.running: continue if data == "": self.prnt("Connection lost to server.") self.running = False continue words = data.split(" ") if words[0] == "PING": # The server pinged us, we need to pong or we'll be disconnected # (make sure to send whatever follows the PING, in case they send a random hash) self.sendLine("PONG " + data[5:]) continue elif words[0] == "ERROR": #there was an error, we probably had a KeyboardInterrupt self.prnt("Connection lost to server.") self.running = False continue # Takes the ':Nick!Ident@Host' chunk and assigns (nick,ident,host) to user chunk = words[0].split(":")[1] user = (chunk.split("!")[0], chunk.split("!")[1].split("@")[0], chunk.split("!")[0].split("@")[1]) if words[1] == "433": #There was a nick collision self.config["nickname"] = self.config["nickname"] + "_" self.sendLine("NICK "+self.config["nickname"]) elif words[1] == "422" or words[1] == "376": # We successfully logged in, do post-login procedures if self.config["ns_name"] != "" and self.config["ns_pass"] != "": self.doMessage("NickServ", "IDENTIFY "+self.config["ns_name"]+" "+self.config["ns_pass"]) self.pluginManager.event("connect", self.config["host"]) for chan in [k for k in self.config["channels"].split(",") if k != '']: self.doJoin(chan) elif words[1] == "353": #user list, if it's large, we only got part of it. words = [k for k in data.replace(" =", "").split(" ") if k != ''] if self.userlist.get(words[3], None) is None: self.userlist[words[3]] = [] userlist = [u.strip(":") for u in words[3:]] self.userlist[words[3]] += userlist elif words[1] == "PRIVMSG": # We got a message channel = (words[2] if words[2] != self.config["nickname"] else user[0]) message = data[data.find(":", data.find(channel[(channel.find("-") == -1 and 1 or channel.find("-")):]))+1:] if message.find("\x01ACTION") == 0: # String was found, it's an action self.pluginManager.event("action", channel, user, message[8:-1]) elif message.find(self.config["comchar"]) == 0: # String was found, it's a command! args = message.split(" ") cmd = args[0][len(self.config["comchar"]):] args.pop(0) self.pluginManager.event("message", channel, user, message) self.pluginManager.event("command", channel, user, cmd.lower(), args) else: if re.match("^"+self.config["nickname"]+"[^A-Za-z0-9] .+$", message): #Someone addressed us, it's a command (probably) args = message.split(" ") cmd = args[1] args = args[2:] self.pluginManager.event("message", channel, user, message) self.pluginManager.event("command", channel, user, cmd.lower(), args) else: #Nothing was found, it has to be a message self.pluginManager.event("message", channel, user, message) elif words[1] == "JOIN": # Someone joined a channel that we're in if user[0] != self.config["nickname"]: self.pluginManager.event("join", words[2].strip(":"), user) self.userlist[words[2].strip(":")].append(user) elif words[1] == "PART": if user != self.config["nickname"]: # Someone parted a channel we're in if user in self.userlist[words[2].strip(":")]: self.userlist[words[2]].remove(user) self.pluginManager.event("part", words[2].strip(":"), user, " ".join(words[3:])) elif words[1] == "QUIT": # Someone quit the server for k in self.userlist.keys(): if user in self.userlist[k]: self.userlist[k].remove(user) self.pluginManager.event("quit", user, " ".join(words[2:])[1:]) elif words[1] == "NICK": # Someone changed their nickname for k in self.userlist.keys(): if user in self.userlist[k]: self.userlist[k].remove(user) self.userlist[k].append(words[2].strip(":")) self.pluginManager.event("nick", user, words[2].strip(":")) elif words[1] == "MODE": # Someone set a mode try: self.pluginManager.event("mode", words[2], user, words[3], words[4]) except IndexError: # words[4] is not valid, it wasn't set on a user self.pluginManager.event("mode", words[2], user, words[3], "") elif words[1] == "KICK": #someone kicked someone if self.userlist.get(words[2].lower(), None) is not None: self.userlist[words[2].lower()].remove(words[3]) self.pluginManager.event("kick", words[2], user, words[3], words[4][1:]) #end of loop if self.goodstart: self.pluginManager.event("disconnect", self.config["host"]) def prnt(self, line): print("["+self.config["host"]+"] "+line) def sendLine(self, line): try: self.conn.send(line+"\r\n") except socket.error: self.prnt("Connection lost to server.") exit() def doMessage(self, channel, message): self.sendLine("PRIVMSG "+channel+" :"+message) self.pluginManager.event("message", channel, self.config["nickname"], message) def doAction(self, channel, action): self.sendLine("PRIVMSG "+channel+" :\x01ACTION "+action+" \x01") self.pluginManager.event("action", channel, self.config["nickname"], action) def doQuit(self, message=""): self.sendLine("QUIT :" + message) self.pluginManager.event("quit", self.config["nickname"], message) self.running = False def doNotice(self, user, message): self.sendLine("NOTICE "+user+" :"+message) def doNick(self, newnick): self.sendLine("NICK " + newnick) self.pluginManager.event("nick", self.config["nickname"], newnick) self.config["nickname"] = newnick def doJoin(self, channel): self.sendLine("JOIN "+channel) self.pluginManager.event("join", channel, self.config["nickname"]) def doKick(self, channel, user, message=""): self.sendLine("KICK %s %s :%s" % (channel, user, message)) self.pluginManager.event("kick", channel, self.config["nickname"], user, message) def doPart(self, channel, message=""): self.sendLine("PART "+channel) if channel in self.userlist.keys(): del self.userlist[channel] self.pluginManager.event("part", channel, self.config["nickname"], message) def doMode(self, channel, mode, user=""): self.sendLine("MODE "+channel+" "+mode+user) self.pluginManager.event("mode", channel, self.config["nickname"], mode, user)