Skip to content
Snippets Groups Projects
irc.py 12.2 KiB
Newer Older
FurryHead's avatar
FurryHead committed
import socket, os, threading
import plugins

class PluginManager(object):
    def __init__(self, server):
        self.server = server
        self.plugins = { }
        self.handlers = { 
            "join" : [ ],
            "part" : [ ],
            "quit" : [ ],
            "message" : [ ],
            "connect" : [ ],
            "disconnect": [ ],
            "action" : [ ],
            "nick" : [ ],
            "mode" : [ ],
            "ping" : [ ],
            "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__
                print("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__
                    print("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__()+")"
                print(err)
                return err
            
            try:
                self.plugins[pluginName.lower()] = pClass(self.server)
            except Exception, e:
                err = "Could not initialize "+pluginName+" ("+e.__class__.__name__+": "+e.__str__()+")"
                print(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":
                    for cmd,func in eList.items():
                        if func.__self__ == inst:
                            del eList[cmd]
                            destructor = getattr(inst, "__del__", None)
                            if destructor is not None:
                                destructor()
                else:
                    for func in eList[:]:
                        if func.__self__ == inst:
                            eList.remove(func)
                            destructor = getattr(inst, "__del__", None)
                            if destructor is not None:
                                destructor()
                            
            del self.plugins[pluginName.lower()]
        else:
            err = "Plugin "+pluginName+" is not loaded."
            return err
    
    def loadAllPlugins(self):
        errs = {}
        plugins = self.server.config["plugins"].split(",")
        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:
            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
        self.pluginManager.loadAllPlugins()
        self.running = False
        
    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)
        self.conn.connect((self.config["host"], int(self.config["port"])))
        ident = self.config["ident"] if self.config["ident"] != "" else self.config["nickname"]
        self.sendLine("USER "+ident+" * * *")
        self.sendLine("NICK "+self.config["nickname"])
        
        self.running = True
        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?")
          
            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
            
            # Takes the ':Nick!Ident@Host' chunk and assigns Nick to user
            user = words[0].split(":")[1].split("!")[0]
            
            #TODO: Add code for handling nick collisions
            
            if 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 self.config["channels"].split(","):
                    self.doJoin(chan)
            
            elif words[1] == "PRIVMSG":
                # We got a message
                channel = (words[2] if words[2] != self.config["nickname"] else user)
                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, args)
                else:
                    # Strings not found, it's a message
                    self.pluginManager.event("message", channel, user, message)
            
            elif words[1] == "JOIN":
                # Someone joined a channel that we're in
                if user != self.config["nickname"]:
                    self.pluginManager.event("join", words[2].strip(":"), user)
            
            elif words[1] == "PART":
                if user != self.config["nickname"]:
                    # Someone parted a channel we're in
                    self.pluginManager.event("part", words[2], user, " ".join(words[3:]))
            
            elif words[1] == "QUIT":
                # Someone quit the server
                self.pluginManager.event("quit", user, " ".join(words[2:])[1:]) 
            
            elif words[1] == "NICK":
                # Someone changed their nickname 
                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], "")
        
        #end of loop
        self.pluginManager.event("disconnect", self.config["host"])
        
    def prnt(self, line):
        print("["+self.config["host"]+"] "+line)
    
    def sendLine(self, line):
        self.conn.send(line+"\r\n")
    
    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=None):
        self.sendLine("QUIT " + (message or ""))
        self.pluginManager.event("quit", self.config["nickname"], (message or ""))
        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 doPart(self, channel, message=None):
        self.sendLine("PART "+channel)
        self.pluginManager.event("part", channel, self.config["nickname"], (message or ""))
    
    def doMode(self, channel, mode, user=None):
        self.sendLine("MODE "+channel+" "+mode+(user if user is not None else ""))
        self.pluginManager.event("mode", channel, self.config["nickname"], mode, (user if user is not None else ""))

cfg = {
    "host" : "irc.freenode.net",
    "port" : 6667,
    "nickname" : "SciPyBot",
    "ident" : "SciPyBot",
    "loginname" : None,
    "loginpass" : None,
    "start_channels" : [ "#guppy" ],
    "start_plugins" : ["Printer", "PluginLoader"],
    "cmdPrefix" : "+"
}