Bot IRC destiné à la gestion et la modération du salon par les utilisateurs.
Il permet à n'importe-qui de lancer un vote demandant une action (kick, ban, kickban, unban, topic), auquel les utilisateurs peuvent répondre.
S'il y a une majorité de 'oui', le bot effectue l'action.
Son point fort est qu'il permet donc une gestion du salon sans nécessiter d'opérateur ;
son point faible est qu'il ne possède pas d'anti-flood.
Il est donc conseillé de le coupler à un bot de modération qui lui, gérera le flood et les attaques de clones éventuelles.
Source / Exemple :
#!/usr/bin/python
# -*- coding: latin-1 -*-
import os
import socket
import ConfigParser
import string
import re
import time
import threading
class config:
def __init__(self):
"""Generate the bot config"""
conf = '''[Server]
# IRC Server
serv = irc.??
# port
port = 6667
[Bot]
nick = DBot
ident = dbot
realname = Democratic Bot
[Salon]
chan = #chan
[Owner]
# Nick(s) of the owner(s) (use comma to separate nicks)
owner = YaCoU, bot
[Misc]
# Voting time (seconds)
tmp = 10
# Trigger character (e.g. : !kick)
trigger = !
# Ban type (nick or ident or host)
bantype = host
[Colors]
# And for the lame, colors ! (^K : , ^B : )
color_1 =
color_2 =
'''
f = open('dbot.cfg','a')
f.write(conf)
raw_input('Error : Config file missing ! It have been automaticly generated, now, fill it !\nPress enter to continue...')
sys.exit()
class general:
def __init__(self):
"""initialize some required vars"""
self.conf()
self.readbuffer = ''
self.sock = socket.socket()
self.regOp = re.compile(':?[~@&]+' + self.NICK)
regStrip = re.compile("\x03(?:\d{1,2}(?:,\d{1,2})?)?", re.UNICODE) # For catching mIRC's colors code like ^B^K12,3[text]
self._strip = lambda msg: string.lower(regStrip.sub('', msg).strip(chr(15)).strip(chr(22)).strip(chr(31)))
def conf(self):
"""check if the config file is complete"""
if os.path.isfile('dbot.cfg'):
self.conf = ConfigParser.ConfigParser()
self.conf.read('dbot.cfg')
try:
self.SERV = self.conf.get('Server', 'serv')
if self.SERV == '' or self.SERV == 'irc.??':
raise ValueError('server')
self.PORT = int(self.conf.get('Server', 'port'))
if self.PORT == '':
raise ValueError('port')
self.NICK = self.conf.get('Bot', 'nick')
if self.NICK == '':
raise ValueError('pseudo')
self.IDENT = self.conf.get('Bot', 'ident')
if self.IDENT == '':
raise ValueError('ident')
self.REALNAME = self.conf.get('Bot', 'realname')
if self.REALNAME == '':
raise ValueError('real name')
self.CHAN = self.conf.get('Salon', 'chan')
if self.CHAN == '':
raise ValueError('salon')
self.owner = self.conf.get('Owner', 'owner')
if self.owner == '':
raise ValueError('owner')
self.BANTYPE = self.conf.get('Misc', 'bantype')
if self.BANTYPE == '':
raise ValueError('bantype')
self.tmpVote = self.conf.get('Misc', 'tmp')
if self.tmpVote == '':
raise ValueError('temps de vote')
self.TRIGGER = self.conf.get('Misc', 'trigger')
if self.TRIGGER == '':
raise ValueError('trigger')
self.COLORS = [self.conf.get('Colors', 'color_1'), self.conf.get('Colors','color_2')]
except ConfigParser.NoSectionError, err: # If a section miss
raw_input('Error : No section named ' + str(err).split()[2] + '.\nPress enter to continue...')
sys.exit()
except ValueError, err: # If a value miss
raw_input('Error : No data in section \'' + str(err) + '\'.\nPress enter to continue...')
sys.exit()
else:
config()
def connection(self):
""" connect bot to the server"""
try:
self.sock.connect((self.SERV, self.PORT))
self.sock.send('NICK %s\r\n' % self.NICK)
self.sock.send('USER %s %s dbot :%s\r\n' % (self.IDENT, self.SERV, self.REALNAME))
except:
raw_input('Error : can\'t connect to IRC server, check configuation and internet connection.\nPress enter to continue...')
sys.exit()
def tab2str(self, tab):
"""return a str from a list"""
result = ''
for word in tab:
result += word + chr(32)
return result[:-1]
class Timer(threading.Thread):
def __init__(self, parent, name=''):
threading.Thread.__init__(self)
self.irc = parent
self.gen = parent.gen
self.name = name
self.statut = 0
def run(self):
if self.name <> '': # If no name has be sent, it's the method instanciation
self.irc.msg(self.gen.CHAN, '{0}Vote now ! "{0}{1}vote yes{1}{0}" or "{0}{1}vote no{1}{0}" >{0}{1} {3}s{1}{0} <.'.format(self.gen.COLORS[0], self.gen.COLORS[1], self.gen.TRIGGER, self.gen.tmpVote))
self.statut = 1
if self.gen.tmpVote > 5:
time.sleep(int(self.gen.tmpVote) - 5)
self.irc.msg(self.gen.CHAN, '{1}{0} 5{1}{0} seconds left to vote !'.format(self.gen.COLORS[0], self.gen.COLORS[1]))
time.sleep(5)
else :
time.sleep(int(self.gen.tmpVote))
self.statut = 0
self.irc.vote()
class IRC:
def __init__(self):
self.gen = general()
print 'Configuration loaded'
self.gen.connection()
print 'Connection...'
self.threadCount = 0
self.avert = None
self.sock = self.gen.sock
self.cmd()
self.timer = Timer(self)
self.nickList = []
self.kickedNick = None
self.bannedNick = None
self.readbuffer = self.gen.readbuffer
self.join(self.gen.CHAN)
self.tVote = ['kick', 'ban', 'kickban', 'unban', 'topic']
self.VCount = 0
self.votedAction = None
self.paused = None
self.whoVote = []
self.waitingNicks = []
def cmd(self):
"""IRC commands"""
self.join = lambda chan: self.sock.send('JOIN %s\r\n' % chan)
self.msg = lambda target, msg: self.sock.send('PRIVMSG %s :%s\r\n' % (target, msg))
self.notice = lambda target, msg: self.sock.send('NOTICE %s :%s\r\n' % (target, msg))
self.names = lambda chan: self.sock.send('NAMES %s\r\n' % chan)
self.whois = lambda nick: self.sock.send('WHOIS %s %s\r\n' % (nick, nick))
self.kick = lambda chan, nick, reason='': self.sock.send('KICK %s %s :%s\r\n' % (chan, nick, reason))
self.ban = lambda chan, mask: self.sock.send('MODE %s +b %s\r\n' % (chan, mask))
self.unban = lambda chan, mask: self.sock.send('MODE %s -b %s\r\n' % (chan, mask))
self.topic = lambda chan, topic: self.sock.send('TOPIC %s :%s\r\n' % (chan, topic))
def vote(self):
if self.VCount > 0:
action = self.votedAction[0]
firstArg = self.votedAction[1][0]
strippedFirstArg = self.gen._strip(self.votedAction[1][0])
secondArg = self.gen.tab2str(self.votedAction[1][1:])
self.msg(self.gen.CHAN, '{0}{1}Yes{1}{0} wins !'.format(self.gen.COLORS[0], self.gen.COLORS[1]))
if action == 'kick':
if strippedFirstArg <> string.lower(self.gen.NICK):
self.kick(self.gen.CHAN, strippedFirstArg, secondArg)
else:
self.msg(self.gen.CHAN, 'Nice try !')
elif action == 'ban':
self.bannedNick = strippedFirstArg
self.whois(self.bannedNick)
elif action == 'kickban':
if strippedFirstArg <> string.lower(self.gen.NICK):
self.kick(self.gen.CHAN, strippedFirstArg, secondArg)
else:
self.msg(self.gen.CHAN, 'Nice try !')
self.bannedNick = strippedFirstArg
self.whois(self.bannedNick)
elif action == 'unban':
self.unban(self.gen.CHAN, strippedFirstArg)
elif action == 'topic':
self.topic(self.gen.CHAN, firstArg + ' ' + secondArg)
else:
self.msg(self.gen.CHAN, '{0}{1}No{1}{0} wins !'.format(self.gen.COLORS[0], self.gen.COLORS[1]))
self.votedAction = None
self.VCount = 0
self.whoVote = []
self.waitingNicks = []
def debug(self, text):
"""display all the lines except whois and names"""
rawsToIgnore = ['379', '378', '319', '312', '313', '310', '317', '311', '307', '318', '366', '353']
if text[1] not in rawsToIgnore:
print self.gen._strip(self.gen.tab2str(text[1:]))
else:
pass
def run(self):
"""main loop"""
while 1:
self.readbuffer = self.readbuffer + self.sock.recv(1024)
if not self.readbuffer:
break
else:
tmp = string.split(self.readbuffer,'\n')
self.readbuffer = tmp.pop()
for line in tmp:
line = string.rstrip(line)
line = string.split(line)
# Complete debug : print line
if line[0]=='PING':
self.sock.send('PONG %s\r\n' % line[1])
elif len(line) > 2:
self.debug(line)
if line[1] == '311': # Whois
if self.bannedNick:
if line[3] <> self.gen.NICK:
if self.gen.BANTYPE == 'nick':
mask = line[3] + '*!*@*'
elif self.gen.BANTYPE == 'ident':
mask = '*!' + line[4]
else:
mask = '*!*@' + line[5]
self.ban(self.gen.CHAN, mask)
self.bannedNick = None
elif self.timer.statut <> 0:
if line[5] not in self.whoVote:
if line[3] == self.waitingNicks[0][0]:
self.whoVote.append(line[5])
self.VCount += 1 if self.waitingNicks[0][1] == 'yes' else -1
self.notice(self.waitingNicks[0][0], 'Vote cast !')
self.waitingNicks.pop(0)
else :
self.notice(self.waitingNicks[0][0], 'You have already voted !')
elif len(line) >= 3:
print self.gen._strip(self.gen.tab2str(line[2:])) # D
if len(line) >= 4 and line[1] == 'PRIVMSG':
nick = line[0].split('!')[0].split(':')[1]
if line[2] == self.gen.CHAN:
if len(line[3]) >= 3:
if line[3][:2] == ':' + self.gen.TRIGGER:
command = self.gen._strip(line[3][2:])
# Commands to execute only is bot isn't paused
if command == 'vote' and len(line) >= 5 and self.paused is None:
vote = self.gen._strip(line[4])
if (self.timer.statut <> 0 and (vote == 'yes' or vote == 'no')):
self.waitingNicks.append([nick, vote])
self.whois(nick)
else :
if self.timer.statut == 0:
if self.gen._strip(line[4]) in self.tVote:
self.votedAction = (self.gen._strip(line[4]), line[5:])
self.timer = Timer(self, self.threadCount)
self.timer.start()
self.threadCount += 1
else :
self.notice(nick, 'Vote already in progress !')
# Only owners commands working even the bot is paused
if command == 'quit' and nick in self.gen.owner:
self.sock.send('QUIT :Ddisconnected\r\n')
sys.exit('success')
elif command == 'pause' and nick in self.gen.owner:
if self.paused is not None:
if self.timer.statut == 0:
self.paused = None
self.sock.send('AWAY\r\n')
self.msg(self.gen.CHAN, 'Bot reactivated.')
else:
self.notice(nick, 'Please wait while the vote is in progress to pause the bot.')
else:
self.paused = 1
self.sock.send('AWAY :Bot paused.\r\n')
self.msg(self.gen.CHAN, 'Bot paused.')
# Commands working event the bot is paused
elif command == 'help' and len(line) >= 4:
self.notice(nick, 'Bot commands : !vote <kick, ban, kickban, unban, topic> <params> (nick or reason or topic) to call a vote.')
self.notice(nick, '!vote [yes, no] pour to vote.')
go = IRC()
go.run()
Conclusion :
Je suis un éternel débutant, je sais que l'imbrication de conditions est bof,
soyez indulgents sur ce point, sinon, je suis ouvert à toute critique.
Je voulais mettre un zip avec le programme convertit en .exe par py2exe, mais ça rentre pas.
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.