2012-08-14 03:51:55 +00:00
# -*- coding: utf-8 -*-
# Nemubot is a modulable IRC bot, built around XML configuration files.
# Copyright (C) 2012 Mercier Pierre-Olivier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2012-04-09 02:19:39 +00:00
from datetime import datetime
import re
2012-06-28 23:16:06 +00:00
import shlex
2012-04-09 02:19:39 +00:00
import time
2012-04-30 16:22:10 +00:00
2012-06-16 20:48:15 +00:00
import credits
2012-08-14 03:51:55 +00:00
from credits import Credits
import DCC
import xmlparser
2012-06-16 20:48:15 +00:00
2012-04-30 16:22:10 +00:00
CREDITS = { }
filename = " "
2012-06-16 20:48:15 +00:00
def load ( config_file ) :
global CREDITS , filename
2012-04-30 16:22:10 +00:00
CREDITS = dict ( )
2012-06-16 20:48:15 +00:00
filename = config_file
credits . BANLIST = xmlparser . parse_file ( filename )
2012-04-30 16:22:10 +00:00
2012-06-16 20:48:15 +00:00
def save ( ) :
global filename
credits . BANLIST . save ( filename )
2012-04-30 16:22:10 +00:00
2012-04-09 02:19:39 +00:00
class Message :
2012-07-23 01:11:24 +00:00
def __init__ ( self , srv , line , private = False ) :
2012-04-09 02:19:39 +00:00
self . srv = srv
self . time = datetime . now ( )
2012-07-23 01:11:24 +00:00
self . channel = None
2012-07-23 16:08:11 +00:00
self . content = b ' '
self . ctcp = False
2012-04-09 02:19:39 +00:00
line = line . rstrip ( ) #remove trailing 'rn'
2012-07-03 11:00:16 +00:00
words = line . split ( b ' ' )
if words [ 0 ] [ 0 ] == 58 : #58 is : in ASCII table
2012-07-23 00:19:43 +00:00
self . sender = words [ 0 ] [ 1 : ] . decode ( )
2012-07-23 16:08:11 +00:00
self . cmd = words [ 1 ] . decode ( )
2012-05-21 16:14:33 +00:00
else :
2012-07-23 16:08:11 +00:00
self . cmd = words [ 0 ] . decode ( )
2012-07-23 00:19:43 +00:00
self . sender = None
2012-05-21 16:14:33 +00:00
2012-07-23 16:08:11 +00:00
if self . cmd == ' PING ' :
self . content = words [ 1 ]
2012-07-23 00:19:43 +00:00
elif self . sender is not None :
self . nick = ( self . sender . split ( ' ! ' ) ) [ 0 ]
if self . nick != self . sender :
self . realname = ( self . sender . split ( ' ! ' ) ) [ 1 ]
2012-04-09 02:19:39 +00:00
else :
2012-07-23 00:19:43 +00:00
self . realname = self . nick
2012-07-23 02:19:53 +00:00
self . sender = self . nick + " ! " + self . realname
2012-04-09 02:19:39 +00:00
2012-05-21 17:08:41 +00:00
if len ( words ) > 2 :
2012-07-23 16:08:11 +00:00
self . channel = self . pickWords ( words [ 2 : ] ) . decode ( )
2012-05-21 17:08:41 +00:00
2012-07-23 16:08:11 +00:00
if self . cmd == ' PRIVMSG ' :
2012-07-03 11:00:16 +00:00
#Check for CTCP request
2012-07-16 22:56:27 +00:00
self . ctcp = len ( words [ 3 ] ) > 1 and ( words [ 3 ] [ 0 ] == 0x01 or words [ 3 ] [ 1 ] == 0x01 )
2012-07-24 00:34:01 +00:00
self . content = self . pickWords ( words [ 3 : ] )
2012-07-19 18:07:51 +00:00
elif self . cmd == ' 353 ' and len ( words ) > 3 :
for i in range ( 2 , len ( words ) ) :
2012-07-23 16:08:11 +00:00
if words [ i ] [ 0 ] == 58 :
2012-07-19 18:07:51 +00:00
self . content = words [ i : ]
#Remove the first :
self . content [ 0 ] = self . content [ 0 ] [ 1 : ]
2012-07-23 16:08:11 +00:00
self . channel = words [ i - 1 ] . decode ( )
2012-07-19 18:07:51 +00:00
break
2012-07-23 16:08:11 +00:00
elif self . cmd == ' NICK ' :
self . content = self . pickWords ( words [ 2 : ] )
2012-07-19 18:07:51 +00:00
elif self . cmd == ' MODE ' :
self . content = words [ 3 : ]
elif self . cmd == ' 332 ' :
self . channel = words [ 3 ]
2012-07-23 16:08:11 +00:00
self . content = self . pickWords ( words [ 4 : ] )
2012-07-20 17:15:01 +00:00
else :
2012-07-24 00:34:01 +00:00
#print (line)
2012-07-23 16:08:11 +00:00
self . content = self . pickWords ( words [ 3 : ] )
2012-04-09 02:19:39 +00:00
else :
2012-05-21 17:08:41 +00:00
print ( line )
2012-05-21 16:14:33 +00:00
if self . cmd == ' PRIVMSG ' :
2012-07-23 16:08:11 +00:00
self . channel = words [ 2 ] . decode ( )
self . content = b ' ' . join ( words [ 3 : ] )
self . decode ( )
2012-07-24 00:34:01 +00:00
self . private = private or ( self . channel is not None and self . srv is not None and self . channel == self . srv . nick )
2012-04-09 02:19:39 +00:00
2012-07-23 16:08:11 +00:00
def pickWords ( self , words ) :
""" Parse last argument of a line: can be a single word or a sentence starting with : """
if len ( words ) > 0 :
if words [ 0 ] [ 0 ] == 58 :
return b ' ' . join ( words [ 0 : ] ) [ 1 : ]
else :
return words [ 0 ]
else :
return " "
2012-07-16 22:56:27 +00:00
def decode ( self ) :
2012-07-23 16:08:11 +00:00
""" Decode the content string usign a specific encoding """
if isinstance ( self . content , bytes ) :
try :
self . content = self . content . decode ( )
except UnicodeDecodeError :
#TODO: use encoding from config file
self . content = self . content . decode ( ' utf-8 ' , ' replace ' )
2012-07-16 22:56:27 +00:00
2012-05-03 14:16:38 +00:00
@property
def is_owner ( self ) :
2012-07-23 00:19:43 +00:00
return self . nick == self . srv . owner
2012-05-03 14:16:38 +00:00
2012-04-09 02:19:39 +00:00
2012-07-23 00:19:43 +00:00
def send_msg ( self , channel , msg , cmd = " PRIVMSG " , endl = " \r \n " ) :
2012-04-30 16:22:10 +00:00
if CREDITS [ self . realname ] . speak ( ) :
2012-07-20 01:24:51 +00:00
self . srv . send_msg_verified ( self . sender , channel , msg , cmd , endl )
2012-04-09 02:19:39 +00:00
2012-07-23 00:19:43 +00:00
def send_global ( self , msg , cmd = " PRIVMSG " , endl = " \r \n " ) :
2012-04-30 16:22:10 +00:00
if CREDITS [ self . realname ] . speak ( ) :
self . srv . send_global ( msg , cmd , endl )
2012-04-09 02:19:39 +00:00
2012-07-23 00:19:43 +00:00
def send_chn ( self , msg ) :
2012-04-09 02:19:39 +00:00
""" Send msg on the same channel as receive message """
2012-07-23 01:11:24 +00:00
if ( self . srv . isDCC ( ) and self . channel == self . srv . nick ) or CREDITS [ self . realname ] . speak ( ) :
2012-04-30 16:22:10 +00:00
if self . channel == self . srv . nick :
self . send_snd ( msg )
else :
self . srv . send_msg ( self . channel , msg )
2012-04-09 02:19:39 +00:00
2012-07-23 00:19:43 +00:00
def send_snd ( self , msg ) :
""" Send msg to the person who send the original message """
if self . srv . isDCC ( self . sender ) or CREDITS [ self . realname ] . speak ( ) :
2012-04-30 16:22:10 +00:00
self . srv . send_msg_usr ( self . sender , msg )
2012-04-09 02:19:39 +00:00
2012-07-23 00:19:43 +00:00
def authorize ( self ) :
2012-07-23 17:20:46 +00:00
if self . srv . isDCC ( self . sender ) :
2012-07-03 11:00:16 +00:00
return True
elif self . realname not in CREDITS :
2012-04-30 16:22:10 +00:00
CREDITS [ self . realname ] = Credits ( self . realname )
elif self . content [ 0 ] == ' ` ' :
return True
elif not CREDITS [ self . realname ] . ask ( ) :
return False
return self . srv . accepted_channel ( self . channel )
2012-04-09 02:19:39 +00:00
2012-08-14 03:51:55 +00:00
def treat ( self , hooks ) :
2012-07-23 16:08:11 +00:00
if self . cmd == " PING " :
2012-04-09 02:19:39 +00:00
self . pong ( )
2012-07-23 16:08:11 +00:00
elif self . cmd == " PRIVMSG " and self . ctcp :
2012-05-21 16:14:33 +00:00
self . parsectcp ( )
2012-07-23 16:08:11 +00:00
elif self . cmd == " PRIVMSG " and self . authorize ( ) :
2012-08-14 03:51:55 +00:00
self . parsemsg ( hooks )
2012-07-19 18:07:51 +00:00
elif self . channel in self . srv . channels :
if self . cmd == " 353 " :
self . srv . channels [ self . channel ] . parse353 ( self )
elif self . cmd == " 332 " :
self . srv . channels [ self . channel ] . parse332 ( self )
elif self . cmd == " MODE " :
self . srv . channels [ self . channel ] . mode ( self )
elif self . cmd == " JOIN " :
2012-07-23 16:08:11 +00:00
self . srv . channels [ self . channel ] . join ( self . nick )
2012-07-19 18:07:51 +00:00
elif self . cmd == " PART " :
2012-07-23 16:08:11 +00:00
self . srv . channels [ self . channel ] . part ( self . nick )
2012-07-20 17:15:01 +00:00
elif self . cmd == " TOPIC " :
2012-07-23 17:20:46 +00:00
self . srv . channels [ self . channel ] . topic = self . content
2012-07-20 15:11:21 +00:00
elif self . cmd == " NICK " :
for chn in self . srv . channels . keys ( ) :
2012-07-23 16:08:11 +00:00
self . srv . channels [ chn ] . nick ( self . nick , self . content )
2012-07-20 15:11:21 +00:00
elif self . cmd == " QUIT " :
for chn in self . srv . channels . keys ( ) :
2012-07-23 16:08:11 +00:00
self . srv . channels [ chn ] . part ( self . nick )
2012-04-09 02:19:39 +00:00
def pong ( self ) :
self . srv . s . send ( ( " PONG %s \r \n " % self . content ) . encode ( ) )
2012-05-21 16:14:33 +00:00
def parsectcp ( self ) :
2012-07-03 11:00:16 +00:00
if self . content == ' \x01 CLIENTINFO \x01 ' :
self . srv . send_ctcp ( self . sender , " CLIENTINFO TIME USERINFO VERSION CLIENTINFO " )
elif self . content == ' \x01 TIME \x01 ' :
self . srv . send_ctcp ( self . sender , " TIME %s " % ( datetime . now ( ) ) )
elif self . content == ' \x01 USERINFO \x01 ' :
self . srv . send_ctcp ( self . sender , " USERINFO %s " % ( self . srv . realname ) )
elif self . content == ' \x01 VERSION \x01 ' :
2012-08-14 03:51:55 +00:00
self . srv . send_ctcp ( self . sender , " VERSION nemubot v %d " % VERSION )
2012-07-22 23:23:04 +00:00
elif self . content [ : 9 ] == ' \x01 DCC CHAT ' :
words = self . content [ 1 : len ( self . content ) - 1 ] . split ( ' ' )
ip = self . srv . toIP ( int ( words [ 3 ] ) )
2012-07-23 00:19:43 +00:00
fullname = " guest " + words [ 4 ] + words [ 3 ] + " !guest " + words [ 4 ] + words [ 3 ] + " @ " + ip
2012-07-22 23:23:04 +00:00
conn = dcc . DCC ( self . srv , fullname )
if conn . accept_user ( ip , int ( words [ 4 ] ) ) :
2012-07-23 00:19:43 +00:00
self . srv . dcc_clients [ conn . sender ] = conn
conn . send_dcc ( " Hi %s . To changes your name, say \" %s : my name is yournickname \" . " % ( conn . nick , self . srv . nick ) )
2012-07-22 23:23:04 +00:00
else :
print ( " DCC: unable to connect to %s : %s " % ( ip , words [ 4 ] ) )
2012-07-16 22:56:27 +00:00
elif self . content [ : 7 ] != ' \x01 ACTION ' :
2012-07-22 23:23:04 +00:00
print ( self . content )
2012-07-03 11:00:16 +00:00
self . srv . send_ctcp ( self . sender , " ERRMSG Unknown or unimplemented CTCP request " )
2012-05-21 16:14:33 +00:00
2012-04-30 16:22:10 +00:00
def reparsemsg ( self ) :
2012-08-14 03:51:55 +00:00
if self . hooks is not None :
self . parsemsg ( self . hooks )
2012-04-30 16:22:10 +00:00
else :
print ( " Can ' t reparse message " )
2012-08-14 03:51:55 +00:00
def parsemsg ( self , hooks ) :
2012-04-09 02:19:39 +00:00
#Treat all messages starting with 'nemubot:' as distinct commands
2012-04-11 15:33:57 +00:00
if self . content . find ( " %s : " % self . srv . nick ) == 0 :
2012-07-23 01:11:24 +00:00
#Remove the bot name
2012-06-16 20:48:15 +00:00
self . content = self . content [ len ( self . srv . nick ) + 1 : ] . strip ( )
2012-04-09 02:19:39 +00:00
messagel = self . content . lower ( )
2012-08-14 03:51:55 +00:00
# Treat ping
if re . match ( " .*(m[ ' ]?entends?[ -]+tu|h?ear me|do you copy|ping) " ,
messagel ) is not None :
self . send_chn ( " %s : pong " % ( self . nick ) )
# Ask hooks
2012-04-09 02:19:39 +00:00
else :
2012-08-14 03:51:55 +00:00
hooks . treat_ask ( self )
2012-04-09 02:19:39 +00:00
2012-04-18 21:35:58 +00:00
#Owner commands
2012-04-16 16:28:43 +00:00
elif self . content [ 0 ] == ' ` ' and self . sender == self . srv . owner :
self . cmd = self . content [ 1 : ] . split ( ' ' )
2012-06-30 16:41:38 +00:00
if self . cmd [ 0 ] == " ban " :
2012-04-30 16:22:10 +00:00
if len ( self . cmd ) > 1 :
2012-06-16 20:48:15 +00:00
credits . BANLIST . append ( self . cmd [ 1 ] )
2012-04-30 16:22:10 +00:00
else :
2012-06-16 20:48:15 +00:00
print ( credits . BANLIST )
2012-04-30 16:22:10 +00:00
elif self . cmd [ 0 ] == " banlist " :
2012-06-16 20:48:15 +00:00
print ( credits . BANLIST )
2012-04-30 16:22:10 +00:00
elif self . cmd [ 0 ] == " unban " :
if len ( self . cmd ) > 1 :
2012-06-16 20:48:15 +00:00
credits . BANLIST . remove ( self . cmd [ 1 ] )
2012-04-30 16:22:10 +00:00
elif self . cmd [ 0 ] == " credits " :
if len ( self . cmd ) > 1 and self . cmd [ 1 ] in CREDITS :
self . send_chn ( " %s a %d crédits. " % ( self . cmd [ 1 ] , CREDITS [ self . cmd [ 1 ] ] ) )
else :
for c in CREDITS . keys ( ) :
print ( CREDITS [ c ] . to_string ( ) )
2012-04-18 21:35:58 +00:00
#Messages stating with !
2012-07-23 16:08:11 +00:00
elif self . content [ 0 ] == ' ! ' and len ( self . content ) > 1 :
2012-08-14 03:51:55 +00:00
self . hooks = hooks
2012-06-30 16:41:38 +00:00
try :
self . cmd = shlex . split ( self . content [ 1 : ] )
except ValueError :
self . cmd = self . content [ 1 : ] . split ( ' ' )
2012-04-09 02:19:39 +00:00
if self . cmd [ 0 ] == " help " :
if len ( self . cmd ) > 1 :
if self . cmd [ 1 ] in mods :
2012-07-19 00:18:21 +00:00
try :
self . send_snd ( mods [ self . cmd [ 1 ] ] . help_full ( ) )
except AttributeError :
self . send_snd ( " No help for command %s " % self . cmd [ 1 ] )
2012-04-09 02:19:39 +00:00
else :
self . send_snd ( " No help for command %s " % self . cmd [ 1 ] )
else :
2012-04-18 21:35:58 +00:00
self . send_snd ( " Pour me demander quelque chose, commencez votre message par mon nom ; je réagis à certain messages commençant par !, consulter l ' aide de chaque module : " )
2012-05-21 10:21:14 +00:00
for im in mods :
2012-07-19 00:18:21 +00:00
try :
self . send_snd ( " - !help %s : %s " % ( im . name , im . help_tiny ( ) ) )
except AttributeError :
continue
2012-04-09 02:19:39 +00:00
2012-07-03 11:00:16 +00:00
elif self . cmd [ 0 ] == " dcctest " :
2012-07-23 09:50:08 +00:00
print ( " dcctest for " , self . sender )
self . srv . send_dcc ( " Test DCC " , self . sender )
2012-07-03 11:00:16 +00:00
elif self . cmd [ 0 ] == " pvdcctest " :
print ( " dcctest " )
self . send_snd ( " Test DCC " )
2012-07-23 02:09:48 +00:00
elif self . cmd [ 0 ] == " dccsendtest " :
print ( " dccsendtest " )
conn = dcc . DCC ( self . srv , self . sender )
conn . send_file ( " bot_sample.xml " )
2012-05-18 09:38:50 +00:00
else :
2012-08-14 03:51:55 +00:00
hooks . treat_cmd ( self )
2012-04-09 02:19:39 +00:00
else :
2012-08-14 03:51:55 +00:00
hooks . treat_answer ( self )
# Assume the message starts with nemubot:
if self . private :
hooks . treat_ask ( self )
2012-04-09 02:19:39 +00:00
2012-05-23 10:29:36 +00:00
# def parseOwnerCmd(self, cmd):
2012-04-18 21:35:58 +00:00
##############################
# #
# Extraction/Format text #
# #
##############################
def just_countdown ( self , delta , resolution = 5 ) :
sec = delta . seconds
hours , remainder = divmod ( sec , 3600 )
minutes , seconds = divmod ( remainder , 60 )
an = int ( delta . days / 365.25 )
days = delta . days % 365.25
sentence = " "
force = False
if resolution > 0 and ( force or an > 0 ) :
force = True
sentence + = " %i an " % ( an )
if an > 1 :
sentence + = " s "
if resolution > 2 :
sentence + = " , "
elif resolution > 1 :
sentence + = " et "
if resolution > 1 and ( force or days > 0 ) :
force = True
sentence + = " %i jour " % ( days )
if days > 1 :
sentence + = " s "
if resolution > 3 :
sentence + = " , "
elif resolution > 2 :
sentence + = " et "
if resolution > 2 and ( force or hours > 0 ) :
force = True
sentence + = " %i heure " % ( hours )
if hours > 1 :
sentence + = " s "
if resolution > 4 :
sentence + = " , "
elif resolution > 3 :
sentence + = " et "
if resolution > 3 and ( force or minutes > 0 ) :
force = True
sentence + = " %i minute " % ( minutes )
if minutes > 1 :
sentence + = " s "
if resolution > 4 :
sentence + = " et "
if resolution > 4 and ( force or seconds > 0 ) :
force = True
sentence + = " %i seconde " % ( seconds )
if seconds > 1 :
sentence + = " s "
return sentence [ 1 : ]
def countdown_format ( self , date , msg_before , msg_after , timezone = None ) :
""" Replace in a text %s by a sentence incidated the remaining time before/after an event """
if timezone != None :
os . environ [ ' TZ ' ] = timezone
time . tzset ( )
#Calculate time before the date
if datetime . now ( ) > date :
sentence_c = msg_after
delta = datetime . now ( ) - date
else :
sentence_c = msg_before
delta = date - datetime . now ( )
if timezone != None :
os . environ [ ' TZ ' ] = " Europe/Paris "
return sentence_c % self . just_countdown ( delta )
2012-04-09 02:19:39 +00:00
def extractDate ( self ) :
2012-04-18 21:35:58 +00:00
""" Parse a message to extract a time and date """
2012-04-09 02:19:39 +00:00
msgl = self . content . lower ( )
result = re . match ( " ^[^0-9]+(([0-9] { 1,4})[^0-9]+([0-9] { 1,2}|janvier|january|fevrier|février|february|mars|march|avril|april|mai|maï|may|juin|juni|juillet|july|jully|august|aout|août|septembre|september|october|octobre|oktober|novembre|november|decembre|décembre|december)([^0-9]+([0-9] { 1,4}))?)[^0-9]+(([0-9] { 1,2})[^0-9]*[h ' :]([^0-9]*([0-9] { 1,2})([^0-9]*[m \" :][^0-9]*([0-9] { 1,2}))?)?)?.*$ " , msgl + " TXT " )
if result is not None :
day = result . group ( 2 )
if len ( day ) == 4 :
year = day
day = 0
month = result . group ( 3 )
if month == " janvier " or month == " january " or month == " januar " :
month = 1
elif month == " fevrier " or month == " février " or month == " february " :
month = 2
elif month == " mars " or month == " march " :
month = 3
elif month == " avril " or month == " april " :
month = 4
elif month == " mai " or month == " may " or month == " maï " :
month = 5
elif month == " juin " or month == " juni " or month == " junni " :
month = 6
elif month == " juillet " or month == " jully " or month == " july " :
month = 7
elif month == " aout " or month == " août " or month == " august " :
month = 8
elif month == " september " or month == " septembre " :
month = 9
elif month == " october " or month == " october " or month == " oktober " :
month = 10
elif month == " november " or month == " novembre " :
month = 11
elif month == " december " or month == " decembre " or month == " décembre " :
month = 12
if day == 0 :
day = result . group ( 5 )
else :
year = result . group ( 5 )
hour = result . group ( 7 )
minute = result . group ( 9 )
second = result . group ( 11 )
print ( " Chaîne reconnue : %s / %s / %s %s : %s : %s " % ( day , month , year , hour , minute , second ) )
if year == None :
year = date . today ( ) . year
if hour == None :
hour = 0
if minute == None :
minute = 0
if second == None :
second = 1
else :
second = int ( second ) + 1
if second > 59 :
minute = int ( minute ) + 1
second = 0
return datetime ( int ( year ) , int ( month ) , int ( day ) , int ( hour ) , int ( minute ) , int ( second ) )
else :
return None