From fb6ee3da6bf6df8a2753d7c5d7736fe76f691b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Poulain?= Date: Sun, 5 Dec 2021 18:30:01 +0100 Subject: [PATCH] =?UTF-8?q?Passage=20=C3=A9clair=20=C3=A0=20python3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- redminebot.py | 159 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 95 insertions(+), 64 deletions(-) diff --git a/redminebot.py b/redminebot.py index 3a7c46f..83e8000 100755 --- a/redminebot.py +++ b/redminebot.py @@ -1,44 +1,46 @@ -#!/usr/bin/python -u +#!/usr/bin/env python3 # -*- coding: utf-8 -1 -*- +# Need: python3-iso8601 python3-feedparser + # Import some necessary libraries. -import socket, sys, time, csv, Queue, random, re, pdb, select, os.path, datetime +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 +import configparser # Help configuration. help_list = ["help", "faq", "aide"] hello_list = ["hello", "yo", "bonjour", "salut"] # Load configuration. -configurationFilename="/etc/redminebot/redminebot.conf" +configurationFilename = "/etc/redminebot/redminebot.conf" if len(sys.argv) == 2: - configurationFilename=sys.argv[1] + configurationFilename = sys.argv[1] if os.path.isfile(configurationFilename): - print "Using configuration file: " + configurationFilename - config = ConfigParser.RawConfigParser() + 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") + 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") + 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." + print("Missing configuration file.") sys.exit() @@ -46,77 +48,91 @@ else: ### Class Definitions ### ######################### + class Project(object): def __init__(self, project, channel): self.name = project - self.channel = channel + self.channel = channel self.redmine_next = datetime.datetime.utcnow() self.redmine_latest = datetime.datetime.utcnow() - def set_ircsock ( self, ircsock ): + 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 ) + # 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) - + 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'))) - + self.ircsock.send( + "PRIVMSG {0} :{1}\n".format( + self.channel, msg.encode("utf-8", "ignore") + ).encode() + ) + # 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.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 = [ ] + 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. + self.ircsock.send( + "USER {0} {0} {0} :Robot Agir April" ".\n".format(self.botnick).encode() + ) # bot authentication + self.ircsock.send( + "NICK {}\n".format(self.botnick).encode() + ) # 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)) + print("PRIVMSG {} {} {}".format("NickServ", "IDENTIFY", password)) + self.ircsock.send( + "PRIVMSG {} :{} {} {}".format( + "NickServ", "IDENTIFY", self.botnick, password + ).encode() + ) def add_project(self, project): - project.set_ircsock ( self.ircsock ) - self.ircsock.send("JOIN {} \n".format(project.channel)) # Joins channel + project.set_ircsock(self.ircsock) + self.ircsock.send("JOIN {} \n".format(project.channel).encode()) # Joins channel self.projects.append(project) def get_project(self, name): for project in self.projects: - if name[0] != '#' and project.name == name: + if name[0] != "#" and project.name == name: return project - elif name[0] == '#' and project.channel == name: + 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) + ready_to_read, b, c = select.select([self.ircsock], [], [], 1) if ready_to_read: last_read = datetime.datetime.utcnow() ircmsg = self.msg_handler() @@ -124,34 +140,38 @@ class Bot(object): 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') + 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.ircsock.send(response.encode()) self._redmine() - def _redmine(self): + 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 ( + (":!" 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!') + 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)) + if "refresh" in ircmsg.lower(): + self.ircsock.send( + "PRIVMSG {} :Raffraîchissement en cours\n".format(channel).encode() + ) project = self.get_project(channel) if project: project.redmine() - self.ircsock.send("PRIVMSG {} :Fait !\n".format(channel)) + self.ircsock.send("PRIVMSG {} :Fait !\n".format(channel).encode()) # If the server pings us then we've got to respond! if ircmsg.find("PING :") != -1: @@ -159,24 +179,34 @@ class Bot(object): # Responds to a user that inputs "Hello Mybot". def bot_hello(self, channel, greeting): - self.ircsock.send("PRIVMSG {0} :{1}\n".format(channel, greeting)) + self.ircsock.send("PRIVMSG {0} :{1}\n".format(channel, greeting).encode()) # 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)) + self.ircsock.send( + "PRIVMSG {} :Bonjour, je suis un bot qui reconnaît les options !help, !refresh et !bonjour\n".format( + channel + ).encode() + ) + 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 + ).encode() + ) - # Reads the messages from the server and adds them to the Queue and prints + # 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) + 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." + 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) + new_msg = new_msg.strip("\n\r".encode()) # removing any unnecessary linebreaks + if new_msg != "" and new_msg.find("PING :".encode()) == -1: + print(datetime.datetime.now().isoformat() + " " + new_msg.decode()) return new_msg # Checks for messages. @@ -189,7 +219,7 @@ class Bot(object): target = None return " ".join(ircmsg.split()), actor, target except: -# print "Wrong message:", ircmsg + # print ("Wrong message:", ircmsg) return None, None, None # Compile regex @@ -197,7 +227,7 @@ class Bot(object): pattern = "(" for s in options: pattern += s - pattern += '|' + pattern += "|" pattern = pattern[:-1] pattern += ")" return pattern @@ -207,8 +237,9 @@ class Bot(object): ### The main function. ### ########################## + def main(): - print datetime.datetime.now().isoformat() + " redmine bot starting…" + print(datetime.datetime.now().isoformat() + " redmine bot starting…") redmine_bot = Bot(default_server, default_nickname) redmine_bot.connect() if projectId1 and projectChannel1: @@ -218,7 +249,7 @@ def main(): if projectId3 and projectChannel3: redmine_bot.add_project(Project(projectId3, projectChannel3)) return redmine_bot.loop() - + if __name__ == "__main__": - sys.exit(main()) + sys.exit(main()) \ No newline at end of file