Python IRC bot

The company I’m working at has an internal IRC channel, and an internal Wiki and Redmine system. I want to write a bot to say on IRC whenever there is an edit to the Wiki, whenever there is a new Redmine ticket, and whenever something is checked into the subversion repository.

To do this I will use the RSS feeds of the Wiki and of Redmine to get the changes, and write an IRC bot to write into the IRC channel. The subversion repository sends out a UDP packet on commits, so I will have to listen for that.

I eventually settled on using twsted python library. It is a bit strange at first, but once I got used to it writing the bot was easy. Here it is:

import sys # not needed?
import os
import random
import time
import feedparser # http://www.feedparser.org/
import xml.dom.minidom
import therapist
from twisted.internet import protocol
from twisted.internet import reactor
from twisted.internet.protocol import DatagramProtocol
from twisted.words.protocols import irc
import zlib
import json



class SubversionBroadcast(DatagramProtocol):
    def __init__(self):
        self.callback = None

    def datagramReceived(self, data, (host, port)):
        if self.callback != None:
            d = json.loads(zlib.decompress(data))
            try:
                #print d
                if len(d['message']) > 0 and d['message'].find('NOREVIEW') != -1 :
                    self.callback('Subversion (%s): (%s) %s' % (d['repository'], d['author'], d['message']))
                    print 'Subversion (%s): (%s) %s' % (d['repository'], d['author'], d['message'])
            except Exception as e:
                print 'Subversion failed'
                print e


class RwBot(irc.IRCClient):
    def __init__(self):
        # time.time() is a floating point number expressed in seconds since the epoch, in UTC.
        self.wiki_next = time.time() # Next time we will get the Wiki RSS.
        self.wiki_latest = time.time() # Date of last Wiki RSS item we printed.
        self.redmine_next = time.time() # Next time we will get the Redmine RSS.
        self.redmine_latest = time.time() # Date of last Redmine RSS item we printed.
    
    # Hacky: factory isn't available until after __init__
    # http://www.eflorenzano.com/blog/post/writing-markov-chain-irc-bot-twisted-and-python/
    def _get_nickname(self):
        return self.factory.nickname
    
    nickname = property(_get_nickname)
    
    def signedOn(self):
        self.join(self.factory.channel)
        self.factory.svn.callback = self.svn_commit
    
    def privmsg(self, user, channel, msg):
        if msg.find(self.nickname) != -1 :
            nick = user[0:user.find("!")]
            self.msg(channel,  nick + ': ' + therapist.respond(msg))
    
    def irc_PING(self, prefix, params):
        irc.IRCClient.irc_PING(self, prefix, params) # call base method (required to PONG).
        self._Wiki()
        self._Redmine()
        r = random.random()
        t = time.gmtime()
        if t.tm_hour > 10 and t.tm_hour < 17 and t.tm_wday < 6 and r > 0.99:
            silly = self._Silly()
            self.msg(self.factory.channel, silly)
    
    def _Wiki(self):
        t = time.time()
        if t > self.wiki_next:
            latest_new = self.wiki_latest
            # Then we run.
            self.wiki_next = self.wiki_next + 60*5
            wiki = feedparser.parse("http://wiki/Special:RecentChanges?title=Special:RecentChanges&feed=atom")
            for i in reversed(range(len(wiki.entries))):
                e = wiki.entries[i]
                et = time.mktime(e.updated_parsed)
                if et > self.wiki_latest:
                    # print it
                    msg = "Wiki (%s): %s" % (e.author_detail.name, e.link)
                    self.msg(self.factory.channel, msg.encode('ascii', 'ignore'))
                    
                    # find the new latest time
                    if et > latest_new:
                        latest_new = et
                #if
            #for
            self.wiki_latest = latest_new
    #_DoWiki
    
    def _Redmine(self):
        t = time.time()
        if t > self.redmine_next:
            latest_new = self.redmine_latest
            # Then we run.
            self.redmine_next = self.redmine_next + 60*5
            redmine = feedparser.parse("http://redmine/activity.atom?key=7c58101c32da49aba2e02f9c3354452efdcc0e7b")
            for i in reversed(range(len(redmine.entries))):
                e = redmine.entries[i]
                et = time.mktime(e.updated_parsed)
                if et > self.redmine_latest:
                    # print it
                    if e.title.find('(New)') != -1 or e.title.find('(Reopened)') != -1:
                        msg = "Redmine (%s): %s" % (e.link, e.title)
                        self.msg(self.factory.channel, msg.encode('ascii', 'ignore'))
                    
                    # find the new latest time
                    if et > latest_new:
                        latest_new = et
                #if
            #for
            self.redmine_latest = latest_new
    #_Redmine
   
    def _Silly(self):
        try:
            cmd = os.popen('fortune -n 80')
            fortune = cmd.read()
            cmd.close()
            lines = fortune.strip().split()
            return ' '.join(lines)
        except Exception as e:
            print e
            return ''
    #_Silly
    
    
    def svn_commit(self, msg):
        msg1 = msg.encode('ascii', 'ignore')
        words = msg1.split()
        #self.msg(self.factory.channel, ' '.join(words))
    #svn_commit
#RwBot



class RwBotFactory(protocol.ClientFactory):
    protocol = RwBot # http://twistedmatrix.com/documents/8.2.0/api/twisted.internet.protocol.Factory.html

    def __init__(self, channel, nickname, svn):
        self.channel = channel
        self.nickname = nickname
        self.realname = nickname
        self.username = nickname
        self.lineRate = 12
        self.svn = svn

    def clientConnectionLost(self, connector, reason):
        print "Lost connection (%s), reconnecting." % (reason,)
        connector.connect()

    def clientConnectionFailed(self, connector, reason):
        print "Could not connect: %s" % (reason,)
#RwBotFactory



svn = SubversionBroadcast()
rwBot = RwBotFactory('#development', 'rwBot', svn)


reactor.connectTCP('irc', 6667, rwBot)

reactor.listenUDP(45678, svn)

reactor.run()

Home | More stuff | Octad of the week