2018-09-05 15:38:58 +02:00
#!/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 " ]
2018-09-29 22:32:15 +02:00
registered = True
2018-09-05 15:38:58 +02:00
#########################
### 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 ( ) )