Passage éclair à python3

This commit is contained in:
François Poulain 2021-12-05 18:30:01 +01:00
parent 7969bf68f6
commit fb6ee3da6b

View File

@ -1,44 +1,46 @@
#!/usr/bin/python -u #!/usr/bin/env python3
# -*- coding: utf-8 -1 -*- # -*- coding: utf-8 -1 -*-
# Need: python3-iso8601 python3-feedparser
# Import some necessary libraries. # 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 from threading import Thread
import feedparser import feedparser
import xml.dom.minidom import xml.dom.minidom
from time import mktime, localtime from time import mktime, localtime
import iso8601 import iso8601
import ConfigParser import configparser
# Help configuration. # Help configuration.
help_list = ["help", "faq", "aide"] help_list = ["help", "faq", "aide"]
hello_list = ["hello", "yo", "bonjour", "salut"] hello_list = ["hello", "yo", "bonjour", "salut"]
# Load configuration. # Load configuration.
configurationFilename="/etc/redminebot/redminebot.conf" configurationFilename = "/etc/redminebot/redminebot.conf"
if len(sys.argv) == 2: if len(sys.argv) == 2:
configurationFilename=sys.argv[1] configurationFilename = sys.argv[1]
if os.path.isfile(configurationFilename): if os.path.isfile(configurationFilename):
print "Using configuration file: " + configurationFilename print("Using configuration file: " + configurationFilename)
config = ConfigParser.RawConfigParser() config = configparser.RawConfigParser()
config.read(configurationFilename) config.read(configurationFilename)
default_server=config.get("IRCSection", "irc.server") default_server = config.get("IRCSection", "irc.server")
default_nickname=config.get("IRCSection", "irc.nickname") default_nickname = config.get("IRCSection", "irc.nickname")
registered=config.get("IRCSection", "irc.registered") registered = config.get("IRCSection", "irc.registered")
password=config.get("IRCSection", "irc.password") password = config.get("IRCSection", "irc.password")
projectId1=config.get("IRCSection", "irc.projects.1.id") projectId1 = config.get("IRCSection", "irc.projects.1.id")
projectChannel1=config.get("IRCSection", "irc.projects.1.channel") projectChannel1 = config.get("IRCSection", "irc.projects.1.channel")
projectId2=config.get("IRCSection", "irc.projects.2.id") projectId2 = config.get("IRCSection", "irc.projects.2.id")
projectChannel2=config.get("IRCSection", "irc.projects.2.channel") projectChannel2 = config.get("IRCSection", "irc.projects.2.channel")
projectId3=config.get("IRCSection", "irc.projects.3.id") projectId3 = config.get("IRCSection", "irc.projects.3.id")
projectChannel3=config.get("IRCSection", "irc.projects.3.channel") projectChannel3 = config.get("IRCSection", "irc.projects.3.channel")
else: else:
print "Missing configuration file." print("Missing configuration file.")
sys.exit() sys.exit()
@ -46,77 +48,91 @@ else:
### Class Definitions ### ### Class Definitions ###
######################### #########################
class Project(object): class Project(object):
def __init__(self, project, channel): def __init__(self, project, channel):
self.name = project self.name = project
self.channel = channel self.channel = channel
self.redmine_next = datetime.datetime.utcnow() self.redmine_next = datetime.datetime.utcnow()
self.redmine_latest = datetime.datetime.utcnow() self.redmine_latest = datetime.datetime.utcnow()
def set_ircsock ( self, ircsock ): def set_ircsock(self, ircsock):
self.ircsock = ircsock self.ircsock = ircsock
def redmine(self): def redmine(self):
t = datetime.datetime.utcnow() 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: if t >= self.redmine_next:
latest_new = self.redmine_latest latest_new = self.redmine_latest
self.redmine_next = self.redmine_next + datetime.timedelta(seconds=10) 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))): for i in reversed(range(len(redmine.entries))):
e = redmine.entries[i] e = redmine.entries[i]
et = e.updated_parsed et = e.updated_parsed
td = iso8601.parse_date(e.updated) td = iso8601.parse_date(e.updated)
if td.replace(tzinfo=None) > self.redmine_latest: if td.replace(tzinfo=None) > self.redmine_latest:
msg = "Redmine: (%s): %s" % (e.link, e.title) 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 # find the new latest time
if td.replace(tzinfo=None) > latest_new: if td.replace(tzinfo=None) > latest_new:
latest_new = td.replace(tzinfo=None) latest_new = td.replace(tzinfo=None)
self.redmine_latest = latest_new self.redmine_latest = latest_new
# Defines a bot # Defines a bot
class Bot(object): class Bot(object):
def __init__(self, server, botnick): def __init__(self, server, botnick):
self.botnick = botnick self.botnick = botnick
self.hello_regex = re.compile(self.get_regex(hello_list), re.I) 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.help_regex = re.compile(self.get_regex(help_list), re.I)
self.server = server self.server = server
self.projects = [ ] self.projects = []
def connect(self): def connect(self):
self.ircsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.ircsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ircsock.connect((self.server, 6667)) self.ircsock.connect((self.server, 6667))
self.ircsock.send("USER {0} {0} {0} :Robot Agir April" self.ircsock.send(
".\n".format(self.botnick)) # bot authentication "USER {0} {0} {0} :Robot Agir April" ".\n".format(self.botnick).encode()
self.ircsock.send("NICK {}\n".format(self.botnick)) # Assign the nick to the bot. ) # bot authentication
self.ircsock.send(
"NICK {}\n".format(self.botnick).encode()
) # Assign the nick to the bot.
if not password.strip() and registered == True: if not password.strip() and registered == True:
print("PRIVMSG {} {} {}".format("NickServ","IDENTIFY",password)) print("PRIVMSG {} {} {}".format("NickServ", "IDENTIFY", password))
self.ircsock.send("PRIVMSG {} :{} {} {}".format("NickServ","IDENTIFY", self.botnick, password)) self.ircsock.send(
"PRIVMSG {} :{} {} {}".format(
"NickServ", "IDENTIFY", self.botnick, password
).encode()
)
def add_project(self, project): def add_project(self, project):
project.set_ircsock ( self.ircsock ) project.set_ircsock(self.ircsock)
self.ircsock.send("JOIN {} \n".format(project.channel)) # Joins channel self.ircsock.send("JOIN {} \n".format(project.channel).encode()) # Joins channel
self.projects.append(project) self.projects.append(project)
def get_project(self, name): def get_project(self, name):
for project in self.projects: for project in self.projects:
if name[0] != '#' and project.name == name: if name[0] != "#" and project.name == name:
return project return project
elif name[0] == '#' and project.channel == name: elif name[0] == "#" and project.channel == name:
return project return project
# Main loop # Main loop
def loop(self): def loop(self):
last_read = datetime.datetime.utcnow() last_read = datetime.datetime.utcnow()
while 1: # Loop forever 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: if ready_to_read:
last_read = datetime.datetime.utcnow() last_read = datetime.datetime.utcnow()
ircmsg = self.msg_handler() ircmsg = self.msg_handler()
@ -124,34 +140,38 @@ class Bot(object):
if ircmsg is not None: if ircmsg is not None:
self.message_response(ircmsg, actor, channel) self.message_response(ircmsg, actor, channel)
if datetime.datetime.utcnow() - last_read > datetime.timedelta(minutes=10): 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. # Responds to server Pings.
def pong(self, ircmsg): def pong(self, ircmsg):
response = "PONG :" + ircmsg.split("PING :")[1] + "\n" response = "PONG :" + ircmsg.split("PING :")[1] + "\n"
self.ircsock.send(response) self.ircsock.send(response.encode())
self._redmine() self._redmine()
def _redmine(self): def _redmine(self):
for project in self.projects: for project in self.projects:
project.redmine() project.redmine()
# Parses messages and responds to them appropriately. # Parses messages and responds to them appropriately.
def message_response(self, ircmsg, actor, channel): def message_response(self, ircmsg, actor, channel):
# If someone talks to (or refers to) the bot. # If someone talks to (or refers to) the bot.
if ( ':!' in ircmsg or self.botnick.lower() in ircmsg.lower() ) and \ if (
actor != self.botnick and \ (":!" in ircmsg or self.botnick.lower() in ircmsg.lower())
"PRIVMSG".lower() in ircmsg.lower(): and actor != self.botnick
and "PRIVMSG".lower() in ircmsg.lower()
):
if self.hello_regex.search(ircmsg): if self.hello_regex.search(ircmsg):
self.bot_hello(channel, 'Yo!') self.bot_hello(channel, "Yo!")
if self.help_regex.search(ircmsg): if self.help_regex.search(ircmsg):
self.bot_help(channel) self.bot_help(channel)
if 'refresh' in ircmsg.lower(): if "refresh" in ircmsg.lower():
self.ircsock.send("PRIVMSG {} :Raffraîchissement en cours\n".format(channel)) self.ircsock.send(
"PRIVMSG {} :Raffraîchissement en cours\n".format(channel).encode()
)
project = self.get_project(channel) project = self.get_project(channel)
if project: if project:
project.redmine() 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 the server pings us then we've got to respond!
if ircmsg.find("PING :") != -1: if ircmsg.find("PING :") != -1:
@ -159,24 +179,34 @@ class Bot(object):
# Responds to a user that inputs "Hello Mybot". # Responds to a user that inputs "Hello Mybot".
def bot_hello(self, channel, greeting): 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. # Explains what the bot is when queried.
def bot_help(self, channel): 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(
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)) "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. # 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 new_msg = self.ircsock.recv(2048) # receive data from the server
if not new_msg: if not new_msg:
print "Empty recv. It seems Ive lost my mind. I stop to be reborn." print("Empty recv. It seems Ive lost my mind. I stop to be reborn.")
sys.exit(7) sys.exit(7)
else: else:
new_msg = new_msg.strip('\n\r') # removing any unnecessary linebreaks new_msg = new_msg.strip("\n\r".encode()) # removing any unnecessary linebreaks
if new_msg != '' and new_msg.find("PING :") == -1: if new_msg != "" and new_msg.find("PING :".encode()) == -1:
print(datetime.datetime.now().isoformat() + " " + new_msg) print(datetime.datetime.now().isoformat() + " " + new_msg.decode())
return new_msg return new_msg
# Checks for messages. # Checks for messages.
@ -189,7 +219,7 @@ class Bot(object):
target = None target = None
return " ".join(ircmsg.split()), actor, target return " ".join(ircmsg.split()), actor, target
except: except:
# print "Wrong message:", ircmsg # print ("Wrong message:", ircmsg)
return None, None, None return None, None, None
# Compile regex # Compile regex
@ -197,7 +227,7 @@ class Bot(object):
pattern = "(" pattern = "("
for s in options: for s in options:
pattern += s pattern += s
pattern += '|' pattern += "|"
pattern = pattern[:-1] pattern = pattern[:-1]
pattern += ")" pattern += ")"
return pattern return pattern
@ -207,8 +237,9 @@ class Bot(object):
### The main function. ### ### The main function. ###
########################## ##########################
def main(): 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 = Bot(default_server, default_nickname)
redmine_bot.connect() redmine_bot.connect()
if projectId1 and projectChannel1: if projectId1 and projectChannel1:
@ -218,7 +249,7 @@ def main():
if projectId3 and projectChannel3: if projectId3 and projectChannel3:
redmine_bot.add_project(Project(projectId3, projectChannel3)) redmine_bot.add_project(Project(projectId3, projectChannel3))
return redmine_bot.loop() return redmine_bot.loop()
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())