agirbot/redminebot.py

256 lines
9.2 KiB
Python
Raw Normal View History

2021-12-05 18:30:01 +01:00
#!/usr/bin/env python3
2018-09-05 15:38:58 +02:00
# -*- coding: utf-8 -1 -*-
2021-12-05 18:30:01 +01:00
# Need: python3-iso8601 python3-feedparser
2018-09-05 15:38:58 +02:00
# Import some necessary libraries.
2021-12-05 18:30:01 +01:00
import socket, sys, time, csv, queue, random, re, pdb, select, os.path, datetime
2018-09-05 15:38:58 +02:00
from threading import Thread
import feedparser
import xml.dom.minidom
from time import mktime, localtime
import iso8601
2021-12-05 18:30:01 +01:00
import configparser
2018-09-05 15:38:58 +02:00
# Help configuration.
2018-09-05 15:38:58 +02:00
help_list = ["help", "faq", "aide"]
hello_list = ["hello", "yo", "bonjour", "salut"]
# Load configuration.
2021-12-05 18:30:01 +01:00
configurationFilename = "/etc/redminebot/redminebot.conf"
if len(sys.argv) == 2:
2021-12-05 18:30:01 +01:00
configurationFilename = sys.argv[1]
if os.path.isfile(configurationFilename):
2021-12-05 18:30:01 +01:00
print("Using configuration file: " + configurationFilename)
config = configparser.RawConfigParser()
config.read(configurationFilename)
2021-12-05 18:30:01 +01:00
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")
2019-08-25 19:42:54 +02:00
2021-12-05 18:30:01 +01:00
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:
2021-12-05 18:30:01 +01:00
print("Missing configuration file.")
sys.exit()
2018-09-05 15:38:58 +02:00
#########################
### Class Definitions ###
#########################
2021-12-05 18:30:01 +01:00
2018-09-05 15:38:58 +02:00
class Project(object):
def __init__(self, project, channel):
self.name = project
2021-12-05 18:30:01 +01:00
self.channel = channel
2018-09-05 15:38:58 +02:00
self.redmine_next = datetime.datetime.utcnow()
self.redmine_latest = datetime.datetime.utcnow()
2021-12-05 18:30:01 +01:00
def set_ircsock(self, ircsock):
2018-09-05 15:38:58 +02:00
self.ircsock = ircsock
def redmine(self):
t = datetime.datetime.utcnow()
2021-12-05 18:30:01 +01:00
# print ("Running: %s (%s) for %s" % ( t, self.redmine_next, self.channel ))
2018-09-05 15:38:58 +02:00
if t >= self.redmine_next:
latest_new = self.redmine_latest
2021-12-05 18:30:01 +01:00
2018-09-05 15:38:58 +02:00
self.redmine_next = self.redmine_next + datetime.timedelta(seconds=10)
2021-12-05 18:30:01 +01:00
redmine = feedparser.parse(
"http://agir.april.org/projects/%s/activity.atom?show_issues=1"
% self.name
)
2018-09-05 15:38:58 +02:00
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)
2021-12-05 18:30:01 +01:00
self.ircsock.send(
"PRIVMSG {0} :{1}\n".format(
self.channel, msg.encode("utf-8", "ignore")
).encode()
)
2018-09-05 15:38:58 +02:00
# find the new latest time
if td.replace(tzinfo=None) > latest_new:
latest_new = td.replace(tzinfo=None)
self.redmine_latest = latest_new
2021-12-05 18:30:01 +01:00
2018-09-05 15:38:58 +02:00
# Defines a bot
class Bot(object):
def __init__(self, server, botnick):
2021-12-05 18:30:01 +01:00
self.botnick = botnick
2018-09-05 15:38:58 +02:00
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
2021-12-05 18:30:01 +01:00
self.projects = []
2018-09-05 15:38:58 +02:00
def connect(self):
self.ircsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ircsock.connect((self.server, 6667))
2021-12-05 18:30:01 +01:00
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:
2021-12-05 18:30:01 +01:00
print("PRIVMSG {} {} {}".format("NickServ", "IDENTIFY", password))
self.ircsock.send(
"PRIVMSG {} :{} {} {}".format(
"NickServ", "IDENTIFY", self.botnick, password
).encode()
)
2018-09-05 15:38:58 +02:00
def add_project(self, project):
2021-12-05 18:30:01 +01:00
project.set_ircsock(self.ircsock)
self.ircsock.send("JOIN {} \n".format(project.channel).encode()) # Joins channel
2018-09-05 15:38:58 +02:00
self.projects.append(project)
def get_project(self, name):
for project in self.projects:
2021-12-05 18:30:01 +01:00
if name[0] != "#" and project.name == name:
2018-09-05 15:38:58 +02:00
return project
2021-12-05 18:30:01 +01:00
elif name[0] == "#" and project.channel == name:
2018-09-05 15:38:58 +02:00
return project
# Main loop
def loop(self):
last_read = datetime.datetime.utcnow()
while 1: # Loop forever
2021-12-05 18:30:01 +01:00
ready_to_read, b, c = select.select([self.ircsock], [], [], 1)
2018-09-05 15:38:58 +02:00
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):
2021-12-05 18:30:01 +01:00
raise Exception("timeout: nothing to read on socket since 10 minutes")
2018-09-05 15:38:58 +02:00
# Responds to server Pings.
def pong(self, ircmsg):
response = "PONG :" + ircmsg.split("PING :")[1] + "\n"
2021-12-05 18:30:01 +01:00
self.ircsock.send(response.encode())
2018-09-05 15:38:58 +02:00
self._redmine()
2021-12-05 18:30:01 +01:00
def _redmine(self):
2018-09-05 15:38:58 +02:00
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.
2021-12-05 18:30:01 +01:00
if (
(":!" in ircmsg or self.botnick.lower() in ircmsg.lower())
and actor != self.botnick
and "PRIVMSG".lower() in ircmsg.lower()
):
2018-09-05 15:38:58 +02:00
if self.hello_regex.search(ircmsg):
2021-12-05 18:30:01 +01:00
self.bot_hello(channel, "Yo!")
2018-09-05 15:38:58 +02:00
if self.help_regex.search(ircmsg):
self.bot_help(channel)
2021-12-05 18:30:01 +01:00
if "refresh" in ircmsg.lower():
self.ircsock.send(
"PRIVMSG {} :Raffraîchissement en cours\n".format(channel).encode()
)
2018-09-05 15:38:58 +02:00
project = self.get_project(channel)
if project:
project.redmine()
2021-12-05 18:30:01 +01:00
self.ircsock.send("PRIVMSG {} :Fait !\n".format(channel).encode())
2018-09-05 15:38:58 +02:00
# 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):
2021-12-05 18:30:01 +01:00
self.ircsock.send("PRIVMSG {0} :{1}\n".format(channel, greeting).encode())
2018-09-05 15:38:58 +02:00
# Explains what the bot is when queried.
def bot_help(self, channel):
2021-12-05 18:30:01 +01:00
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
2018-09-05 15:38:58 +02:00
# them to the console. This function will be run in a thread, see below.
2021-12-05 18:30:01 +01:00
def msg_handler(
self,
): # pragma: no cover (this excludes this function from testing)
2018-09-05 15:38:58 +02:00
new_msg = self.ircsock.recv(2048) # receive data from the server
2020-07-04 18:24:58 +02:00
if not new_msg:
2021-12-05 19:24:15 +01:00
print("Empty recv. It seems Ive lost my mind. I stop to be reborn.", new_msg)
#sys.exit(7)
time.sleep(1)
2020-07-04 18:24:58 +02:00
else:
2021-12-05 18:30:01 +01:00
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())
2018-09-05 15:38:58 +02:00
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:
2021-12-05 18:30:01 +01:00
# print ("Wrong message:", ircmsg)
2018-09-05 15:38:58 +02:00
return None, None, None
# Compile regex
def get_regex(self, options):
pattern = "("
for s in options:
pattern += s
2021-12-05 18:30:01 +01:00
pattern += "|"
2018-09-05 15:38:58 +02:00
pattern = pattern[:-1]
pattern += ")"
return pattern
##########################
### The main function. ###
##########################
2021-12-05 18:30:01 +01:00
2018-09-05 15:38:58 +02:00
def main():
2021-12-05 18:30:01 +01:00
print(datetime.datetime.now().isoformat() + " redmine bot starting…")
2018-09-05 15:38:58 +02:00
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))
2018-09-05 15:38:58 +02:00
return redmine_bot.loop()
2021-12-05 18:30:01 +01:00
2018-09-05 15:38:58 +02:00
if __name__ == "__main__":
2021-12-05 18:30:01 +01:00
sys.exit(main())