Commits

Benoit C committed e298ac7

modulable ircbot

Comments (0)

Files changed (8)

 from twisted.internet import reactor, protocol
 from twisted.python import log
 
+# autiste imports
 # system imports
 import time, sys, os
 import random
+import imp
 
 # yaml for yml configuration
 import yaml
 
-
-class ChannelLogger:
-    def __init__(self, conf, channel):
-        #just for live replies
-        self.lived = []
-        self.conf = conf
-        self.channel = channel
-        self.interval = 60 * 60 * 24 #one day
-        date = time.strftime("%Y_%m_%d", time.localtime(time.time()))
-        self.file = file("%s%s%s_%s.log" %
-            (conf['logdir'], os.sep, channel, date), 'a+'
-        )
-        current_time = int(time.time())
-        t = time.localtime(current_time)
-        r = (60 * 24 * 24) - ((t[3] * 60 + t[4] ) * 60 + t[5])
-        self.rollat = current_time + r
-
-    def roll(self):
-        self.file.close()
-        date = time.strftime("%Y_%m_%d", time.localtime(time.time()))
-        self.file = file("%s%s%s_%s.log" %
-            (self.conf['logdir'], os.sep, self.channel, date), 'a+'
-        )
-
-    def log(self, message):
-        current_time = time.time()
-        if current_time >= self.rollat:
-            self.roll()
-        t = time.localtime(int(current_time))
-        timestamp = time.strftime("[%H:%M]", time.localtime(time.time()))
-        self.file.write("%s %s\n" % (timestamp, message))
-        self.file.flush()
-
-    def live(self, message):
-        if len(self.lived) > 10000:
-            self.lived.remove(self.lived[0])
-        self.lived.append(message)
-
-    def close(self):
-        self.file.close()
-
-    def get_random(self):
-        """return random sentence from self.live"""
-        import random
-        nb = random.randint(0, len(self.lived) - 1)
-        return self.lived[nb]
-
-class MessageLogger:
-    """
-    An independent logger class (because separation of application
-    and protocol logic is a good thing).
-    """
-    def __init__(self, conf):
-        self.conf = conf
-        self.chan = {}
-        self.modules = {}
-
-    def __getitem__(self, key):
-        if key not in self.chan:
-            return None
-        return self.chan[key]
-
-    def live(self, chan, message):
-        self[chan].live(message)
-
-
-    def log(self, chan, message):
-        """Write a message to the file."""
-        if chan not in self.chan.keys():
-            self.chan[chan] = ChannelLogger(
-                self.conf, chan
-            )
-        self[chan].log(message)
-
-    def logall(self, message):
-        for chan in self.chan.keys():
-            self[chan].log(message)
-
-    def close(self):
-        for chan in self.chan.keys():
-            self[chan].close()
-
-    def random(self, channel):
-        return self.chan[channel].get_random()
-
-
-
+#autist
+from decorators import module_call
 
 class LogBot(irc.IRCClient):
     """A logging IRC bot."""
-    
-    def __init__(self, conf):
+
+    def __init__(self, nick, channels, conf):
         self.conf = conf
+        self.channels = channels
         self.modules = {}
-        self.nickname = conf.get('nick')
+        self.nickname = nick
+        self.load_modules()
 
     def load_modules(self):
-        print "loading modules"
+        for modname in self.conf.get('modules'):
+            try:
+                modconf = self.conf['modules'].get(modname)
+                ret = __import__("autiste.modules.%s" % modname, globals(), locals(), ['Module'])
+                mod = getattr(ret, "Module")
+                # ret = imp.find_module(modname+".py", "autiste/modules/")
+                self.modules[modname] = mod(modconf, self)
+            except ImportError, e:
+                print "Can't import module %s (%s)" % (modname, e)
+            except Exception, e:
+                print e
+        print self.modules
 
+    @module_call
     def connectionMade(self):
-        #self.logger = MessageLogger(self.factory.conf)
         irc.IRCClient.connectionMade(self)
 
+    @module_call
     def connectionLost(self, reason):
         irc.IRCClient.connectionLost(self, reason)
-#        self.logger.logall(
-#                "[disconnected at %s]" % 
-#                time.asctime(time.localtime(time.time())))
-#        self.logger.close()
+        for mod in self.modules.keys():
+            self.modules[mod].connectionLost(reason)
 
     # callbacks for events
     def userJoined(self, user, channel):
-#        self.logger.log(
-#                channel, 
-#                "-!- %s has joined %s" % (user, channel)
-#        )
-
         self.mode(channel, True, "o", None, user)
         pass
 
+    @module_call
     def signedOn(self):
         """Called when bot has succesfully signed on to server."""
-        for channel in self.conf["channels"]:
+        for channel in self.channels:
             print "joining %s" % channel
             self.join(channel)
 
+    @module_call
     def joined(self, channel):
         """This will get called when the bot joins the channel."""
-#        self.logger.log(channel, "[I have joined %s]" % channel)
+        pass
 
+    @module_call
     def privmsg(self, user, channel, msg):
         """This will get called when the bot receives a message."""
         user = user.split('!', 1)[0]
-#        self.logger.log(channel, "(%s) %s" % (user, msg))
-        if msg.startswith(self.nickname + ":"):
-            words = msg.split(' ')
-            if len(words) > 1:
-                self.random_sentence(channel, user)
-        else:
-#            self.logger.live(channel, msg)
-            pass
 
+    @module_call
     def action(self, user, channel, msg):
         """This will get called when the bot sees someone do an action."""
         user = user.split('!', 1)[0]
-#        self.logger.log(channel, "* %s %s" % (user, msg))
 
 
 
     A new protocol instance will be created each time we connect to the server.
     """
 
-    def __init__(self, conf):
+    def __init__(self, nick, channels, conf):
+        self.nickname = nick
         self.conf = conf
+        self.channels = channels
 
     def buildProtocol(self, addr):
         print "building protocol"
-        return LogBot(self.conf)
+        return LogBot(self.nickname, self.channels, self.conf)
 
     def startedConnecting(self, connector):
         print "started connecting"
         connector.connect()
 
 
-if __name__ == '__main__':
-    # initialize logging
-    #log.startLogging(sys.stdout)
-   
-    # configuration
-    if len(sys.argv) != 2:
-        print "Usage : %s <blah.yml>" % sys.argv[0]
-        sys.exit()
-    content = file(sys.argv[1]).read()
-    conf = yaml.load(content)
-    for server in conf['servers']:
-        server_conf = conf['servers'][server]
-
-        f = LogBotFactory(server_conf)
-        reactor.connectTCP(server_conf["host"], int(server_conf["port"]), f)
-        reactor.run()

autiste/modules/__init__.py

Empty file added.

autiste/modules/base.py

+import threading
+
+class IrcModule(object):
+    """this is the main pythagore base class"""
+
+    def __init__(self, bot_configuration, irc):
+        self.conf = bot_configuration
+        self.irc = irc
+
+    def name(self):
+        """say my name"""
+        return self.__class__.__name__
+
+
+
+class AsyncModule(threading.Thread, IrcModule):
+    def __init__(self, conf, irc):
+        IrcModule.__init__(self, conf, irc)
+        threading.Thread.__init__(self)
+

autiste/modules/link.py

+from base import AsyncModule
+import lxml.html
+class Module(AsyncModule):
+    """
+
+    """
+    def __init__(self, conf, irc):
+        AsyncModule.__init__(self, conf, irc)
+
+    def name(self):
+        return 'TwitterFeed'
+
+    def privmsg(self, user, channel, msg):
+        t = lxml.html.parse(url)
+        title = t.find(".//title").text
+        print user, channel, msg
+        self.irc.say(channel, title)
+
+def main():
+    """some quick mains"""
+    pass
+
+if __name__ == '__main__':
+    main()

autiste/modules/links.py

+import re
+import urllib
+import BeautifulSoup
+
+from base import AsyncModule
+
+urls = '(?: %s)' % '|'.join("""http ftp https""".split())
+ltrs = r'\w'
+gunk = r'/#~:.?+=&%@!\-'
+punc = r'.:?\-'
+any = "%(ltrs)s%(gunk)s%(punc)s" % { 'ltrs' : ltrs,
+                                     'gunk' : gunk,
+                                     'punc' : punc }
+
+url = r"""
+    \b                            # start at word boundary
+        %(urls)s    :             # need resource and a colon
+        [%(any)s]  +?             # followed by one or more
+                                  #  of any valid character, but
+                                  #  be conservative and take only
+                                  #  what you need to....
+    (?=                           # look-ahead non-consumptive assertion
+            [%(punc)s]*           # either 0 or more punctuation
+            (?:   [^%(any)s]      #  followed by a non-url char
+                |                 #   or end of the string
+                  $
+            )
+    )
+    """ % {'urls' : urls,
+           'any' : any,
+           'punc' : punc }
+
+url_re = re.compile(url, re.VERBOSE | re.MULTILINE)
+
+
+
+def grabUrls(text):
+    """Given a text string, returns all the urls we can find in it."""
+    return url_re.findall(text)
+
+class Module(AsyncModule):
+    """
+
+    """
+    def __init__(self, conf, irc):
+        AsyncModule.__init__(self, conf, irc)
+
+    def name(self):
+        return 'Title links grabber'
+
+    def privmsg(self, user, channel, msg):
+        urls = grabUrls(msg)
+        for url in urls:
+            try:
+                soup = BeautifulSoup.BeautifulSoup(urllib.urlopen(url))
+                title = soup.title.string
+                self.irc.say(channel, str(title))
+            except Exception, e:
+                print e
+                pass
+
+def main():
+    """some quick mains"""
+    pass
+
+if __name__ == '__main__':
+    main()

autiste/modules/logger.py

+import time
+import os
+
+from base import AsyncModule
+
+class ChannelLogger(object):
+    def __init__(self, conf, channel):
+        #just for live replies
+        self.lived = []
+        self.conf = conf
+        self.channel = channel
+        self.interval = 60 * 60 * 24 #one day
+        date = time.strftime("%Y_%m_%d", time.localtime(time.time()))
+        self.file = file("%s%s%s_%s.log" %
+            (conf['logdir'], os.sep, channel, date), 'a+'
+        )
+        current_time = int(time.time())
+        t = time.localtime(current_time)
+        r = (60 * 24 * 24) - ((t[3] * 60 + t[4] ) * 60 + t[5])
+        self.rollat = current_time + r
+
+    def roll(self):
+        self.file.close()
+        date = time.strftime("%Y_%m_%d", time.localtime(time.time()))
+        self.file = file("%s%s%s_%s.log" %
+            (self.conf['logdir'], os.sep, self.channel, date), 'a+'
+        )
+
+    def log(self, message):
+        current_time = time.time()
+        if current_time >= self.rollat:
+            self.roll()
+        t = time.localtime(int(current_time))
+        timestamp = time.strftime("[%H:%M]", time.localtime(time.time()))
+        self.file.write("%s %s\n" % (timestamp, message))
+        self.file.flush()
+
+    def live(self, message):
+        if len(self.lived) > 10000:
+            self.lived.remove(self.lived[0])
+        self.lived.append(message)
+
+    def close(self):
+        self.file.close()
+
+    def get_random(self):
+        """return random sentence from self.live"""
+        import random
+        nb = random.randint(0, len(self.lived) - 1)
+        return self.lived[nb]
+
+class MessageLogger(object):
+    """
+    An independent logger class (because separation of application
+    and protocol logic is a good thing).
+    """
+    def __init__(self, conf):
+        self.conf = conf
+        self.chan = {}
+        self.modules = {}
+
+    def __getitem__(self, key):
+        if key not in self.chan:
+            return None
+        return self.chan[key]
+
+    def live(self, chan, message):
+        self[chan].live(message)
+
+
+    def log(self, chan, message):
+        """Write a message to the file."""
+        if chan not in self.chan.keys():
+            self.chan[chan] = ChannelLogger(
+                self.conf, chan
+            )
+        self[chan].log(message)
+
+    def logall(self, message):
+        for chan in self.chan.keys():
+            self[chan].log(message)
+
+    def close(self):
+        for chan in self.chan.keys():
+            self[chan].close()
+
+    def random(self, channel):
+        return self.chan[channel].get_random()
+
+
+class Module(AsyncModule):
+    """
+
+    """
+    def __init__(self, conf, irc):
+        AsyncModule.__init__(self, conf, irc)
+        self.logger = None
+        print conf
+
+    def name(self):
+        return 'IrcLogger'
+
+    def connectionMade(self):
+        print self.conf
+        self.logger = MessageLogger(self.conf)
+
+    def connectionLost(self, reason):
+        self.logger.logall(
+                "[disconnected at %s]" % 
+                time.asctime(time.localtime(time.time())))
+        self.logger.close()
+
+    # callbacks for events
+    def userJoined(self, user, channel):
+        self.logger.log(channel,
+                "-!- %s has joined %s" % (user, channel))
+
+    def privmsg(self, user, channel, msg):
+        user = user.split('!', 1)[0]
+        self.logger.log(channel, "(%s) %s" % (user, msg))
+        self.logger.live(channel, msg)
+
+    def action(self, user, channel, msg):
+        """This will get called when the bot sees someone do an action."""
+        user = user.split('!', 1)[0]
+
+
+def main():
+    """some quick mains"""
+    pass
+
+if __name__ == '__main__':
+    main()

autiste/modules/twitter.py

+# -*- coding: latin-1 -*-
+"""
+
+
+
+"""
+import sys
+import pprint
+import twitter
+
+from base import AsyncModule
+
+class Module(AsyncModule):
+    """
+
+    """
+    def __init__(self, conf, irc):
+        AsyncModule.__init__(self, conf, irc)
+
+    def name(self):
+        return 'TwitterFeed'
+
+    def privmsg(self, user, channel, msg):
+        pass
+
+def main():
+    """some quick mains"""
+    pass
+
+if __name__ == '__main__':
+    main()
+# -*- coding: latin-1 -*-
+"""
+
+
+
+"""
+import traceback
+
+def module_call(func, **kwargs):
+    """tries to call the same function on all loaded module
+without try/except
+    """
+    def decorate(*args, **kwargs):
+        instance = args[0]
+        modlist = instance.modules
+        for module in modlist.keys():
+            strfunc = func.__name__
+            print strfunc
+            if strfunc in dir(instance.modules[module]):
+                funccall = getattr(instance.modules[module], strfunc)
+                if callable(funccall):
+                    newargs = args[1:]
+                    try:
+                        funccall(*newargs, **kwargs)
+                    except Exception, e:
+                        traceback.print_exc()
+
+        return func(*args, **kwargs)
+    return decorate