add redmine bot

This commit is contained in:
Quentin Gibeaux 2018-09-05 15:38:58 +02:00 committed by root
parent 543ce48cfe
commit 20c248c35d
5 changed files with 419 additions and 0 deletions

16
bots/redminebot/LICENSE Normal file
View File

@ -0,0 +1,16 @@
Copyright (C) 2017 Benjamin Drieu
This is heavily derived from IRC bot for Redmine, whose licence is:
IRC bot for Redmine
Copyright (C) 2011 Jasmin Rahimic
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

194
bots/redminebot/redminebot.py Executable file
View File

@ -0,0 +1,194 @@
#!/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
# IRC configuration
default_server = "irc.eu.freenode.net"
default_nickname = "agirbot"
help_list = ["help", "faq", "aide"]
hello_list = ["hello", "yo", "bonjour", "salut"]
#########################
### 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 os.path.isfile("password.txt"):
with open("password.txt", 'r') as f:
password = f.read()
if registered == True:
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
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()
redmine_bot.add_project(Project('gdtc','#gdtc'))
redmine_bot.add_project(Project('admins','#april-admin'))
return redmine_bot.loop()
if __name__ == "__main__":
sys.exit(main())

190
bots/redminebot/redminebot.py~ Executable file
View File

@ -0,0 +1,190 @@
#!/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
# IRC configuration
default_server = "irc.eu.freenode.net"
default_nickname = "agirbot"
help_list = ["help", "info", "faq", "aide"]
hello_list = ["hello", "yo", "bonjour", "salut"]
#########################
### 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 os.path.isfile("password.txt"):
with open("password.txt", 'r') as f:
password = f.read()
if registered == True:
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):
while 1: # Loop forever
ready_to_read, b, c = select.select([self.ircsock],[],[], 1)
if ready_to_read:
ircmsg = self.msg_handler()
ircmsg, actor, channel = self.parse_messages(ircmsg)
if ircmsg is not None:
self.message_response(ircmsg, actor, channel)
# 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
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()
redmine_bot.add_project(Project('gdtc','#gdtc'))
redmine_bot.add_project(Project('admins','#april-admin'))
return redmine_bot.loop()
if __name__ == "__main__":
sys.exit(main())

8
bots/redminebot/redminebot.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
BINDIR="`dirname "$0"`"
HOMEDIR="$BINDIR"
cd "$BINDIR"
nohup $BINDIR/redminebot.py >>"$HOMEDIR/redminebot.log" 2>&1 &

11
bots/redminebot/redminebot.sh~ Executable file
View File

@ -0,0 +1,11 @@
#!/bin/sh
BINDIR="`dirname "$0"`"
HOMEDIR="$BINDIR"
echo "BINDIR=$BINDIR"
echo "HOMEDIR=$HOMEDIR"
cd "$BINDIR"
nohup $BINDIR/redminebot.py >>"$HOMEDIR/redminebot.log" 2>&1 &