#!/usr/bin/python -u # -*- coding: utf-8 -1 -*- # Import some necessary libraries. import socket, sys, time, csv, Queue, random, re, pdb, select, os.path, datetime from threading import Thread import feedparser import xml.dom.minidom from time import mktime, localtime import iso8601 import ConfigParser # Help configuration. help_list = ["help", "faq", "aide"] hello_list = ["hello", "yo", "bonjour", "salut"] # Load configuration. configurationFilename="/etc/redminebot/redminebot.conf" if len(sys.argv) == 2: configurationFilename=sys.argv[1] if os.path.isfile(configurationFilename): print "Using configuration file: " + configurationFilename config = ConfigParser.RawConfigParser() config.read(configurationFilename) default_server=config.get("IRCSection", "irc.server") default_nickname=config.get("IRCSection", "irc.nickname") registered=config.get("IRCSection", "irc.registered") password=config.get("IRCSection", "irc.password") projectId1=config.get("IRCSection", "irc.projects.1.id") projectChannel1=config.get("IRCSection", "irc.projects.1.channel") projectId2=config.get("IRCSection", "irc.projects.2.id") projectChannel2=config.get("IRCSection", "irc.projects.2.channel") projectId3=config.get("IRCSection", "irc.projects.3.id") projectChannel3=config.get("IRCSection", "irc.projects.3.channel") else: print "Missing configuration file." sys.exit() ######################### ### Class Definitions ### ######################### class Project(object): def __init__(self, project, channel): self.name = project self.channel = channel self.redmine_next = datetime.datetime.utcnow() self.redmine_latest = datetime.datetime.utcnow() def set_ircsock ( self, ircsock ): self.ircsock = ircsock def redmine(self): t = datetime.datetime.utcnow() # print "Running: %s (%s) for %s" % ( t, self.redmine_next, self.channel ) if t >= self.redmine_next: latest_new = self.redmine_latest self.redmine_next = self.redmine_next + datetime.timedelta(seconds=10) redmine = feedparser.parse("http://agir.april.org/projects/%s/activity.atom?show_issues=1" % self.name) for i in reversed(range(len(redmine.entries))): e = redmine.entries[i] et = e.updated_parsed td = iso8601.parse_date(e.updated) if td.replace(tzinfo=None) > self.redmine_latest: msg = "Redmine: (%s): %s" % (e.link, e.title) self.ircsock.send("PRIVMSG {0} :{1}\n".format(self.channel, msg.encode('utf-8', 'ignore'))) # find the new latest time if td.replace(tzinfo=None) > latest_new: latest_new = td.replace(tzinfo=None) self.redmine_latest = latest_new # Defines a bot class Bot(object): def __init__(self, server, botnick): self.botnick = botnick self.hello_regex = re.compile(self.get_regex(hello_list), re.I) self.help_regex = re.compile(self.get_regex(help_list), re.I) self.server = server self.projects = [ ] def connect(self): self.ircsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ircsock.connect((self.server, 6667)) self.ircsock.send("USER {0} {0} {0} :Robot Agir April" ".\n".format(self.botnick)) # bot authentication self.ircsock.send("NICK {}\n".format(self.botnick)) # Assign the nick to the bot. if not password.strip() and registered == True: print("PRIVMSG {} {} {}".format("NickServ","IDENTIFY",password)) self.ircsock.send("PRIVMSG {} :{} {} {}".format("NickServ","IDENTIFY", self.botnick, password)) def add_project(self, project): project.set_ircsock ( self.ircsock ) self.ircsock.send("JOIN {} \n".format(project.channel)) # Joins channel self.projects.append(project) def get_project(self, name): for project in self.projects: if name[0] != '#' and project.name == name: return project elif name[0] == '#' and project.channel == name: return project # Main loop def loop(self): last_read = datetime.datetime.utcnow() while 1: # Loop forever ready_to_read, b, c = select.select([self.ircsock],[],[], 1) if ready_to_read: last_read = datetime.datetime.utcnow() ircmsg = self.msg_handler() ircmsg, actor, channel = self.parse_messages(ircmsg) if ircmsg is not None: self.message_response(ircmsg, actor, channel) if datetime.datetime.utcnow() - last_read > datetime.timedelta(minutes=10): raise Exception('timeout: nothing to read on socket since 10 minutes') # Responds to server Pings. def pong(self, ircmsg): response = "PONG :" + ircmsg.split("PING :")[1] + "\n" self.ircsock.send(response) self._redmine() def _redmine(self): for project in self.projects: project.redmine() # Parses messages and responds to them appropriately. def message_response(self, ircmsg, actor, channel): # If someone talks to (or refers to) the bot. if ( ':!' in ircmsg or self.botnick.lower() in ircmsg.lower() ) and \ actor != self.botnick and \ "PRIVMSG".lower() in ircmsg.lower(): if self.hello_regex.search(ircmsg): self.bot_hello(channel, 'Yo!') if self.help_regex.search(ircmsg): self.bot_help(channel) if 'refresh' in ircmsg.lower(): self.ircsock.send("PRIVMSG {} :Raffraîchissement en cours\n".format(channel)) project = self.get_project(channel) if project: project.redmine() self.ircsock.send("PRIVMSG {} :Fait !\n".format(channel)) # If the server pings us then we've got to respond! if ircmsg.find("PING :") != -1: self.pong(ircmsg) # Responds to a user that inputs "Hello Mybot". def bot_hello(self, channel, greeting): self.ircsock.send("PRIVMSG {0} :{1}\n".format(channel, greeting)) # Explains what the bot is when queried. def bot_help(self, channel): self.ircsock.send("PRIVMSG {} :Bonjour, je suis un bot qui reconnaît les options !help, !refresh et !bonjour\n".format(channel)) self.ircsock.send("PRIVMSG {} :Périodiquement, je vais afficher les actualités de http://agir.april.org/projects/{}.\n".format(channel,self.get_project(channel).name)) # Reads the messages from the server and adds them to the Queue and prints # them to the console. This function will be run in a thread, see below. def msg_handler(self): # pragma: no cover (this excludes this function from testing) new_msg = self.ircsock.recv(2048) # receive data from the server if not new_msg: print "Empty recv. It seems I’ve lost my mind. I stop to be reborn." sys.exit(7) else: new_msg = new_msg.strip('\n\r') # removing any unnecessary linebreaks if new_msg != '' and new_msg.find("PING :") == -1: print(datetime.datetime.now().isoformat() + " " + new_msg) return new_msg # Checks for messages. def parse_messages(self, ircmsg): try: actor = ircmsg.split(":")[1].split("!")[0] try: target = ircmsg.split(":")[1].split(" ")[2] except: target = None return " ".join(ircmsg.split()), actor, target except: # print "Wrong message:", ircmsg return None, None, None # Compile regex def get_regex(self, options): pattern = "(" for s in options: pattern += s pattern += '|' pattern = pattern[:-1] pattern += ")" return pattern ########################## ### The main function. ### ########################## def main(): print datetime.datetime.now().isoformat() + " redmine bot starting…" redmine_bot = Bot(default_server, default_nickname) redmine_bot.connect() if projectId1 and projectChannel1: redmine_bot.add_project(Project(projectId1, projectChannel1)) if projectId2 and projectChannel2: redmine_bot.add_project(Project(projectId2, projectChannel2)) if projectId3 and projectChannel3: redmine_bot.add_project(Project(projectId3, projectChannel3)) return redmine_bot.loop() if __name__ == "__main__": sys.exit(main())