Bot irc multi-réseaux en python

Le bot irc du salon #z sur teepi (et autres).

Notez qu’il ne fait rien à part transmettre les messages de réseau en réseau (mais c’est son seul but aussi…)
par contre ça peut vous aider si vous êtes en manque d’inspiration de code python pour l’irc.

Je ne répond de rien si le bot plante chez vous, il a été programmé en 2 jours et ya pas de syncro sur les threads (plus particulièrement pas de section critique sur les opérations touchant à la liste des threads, seul point litigieux imho)

si vous n’aimez pas le frisson des threads hasardeux (hey, c’est un bot irc…) vous pouvez les rajouter.

plinklib.py

# -*- coding: UTF-8 -*-

import threading
import select
import socket
import sys
import time
import re

from time import gmtime, strftimeclass Logger(object):

def __init__(self, filename="default.txt"):

self.filename=filename

def start(self):

self.logfile = open(self.filename,'a')

self.logfile.write(strftime("Session Start: %a %b %d %H:%M:%S %Y", gmtime())+"\n")

def log(self, msg):

self.logfile.write(strftime("[%H:%M]“, gmtime()) + ” ” + msg + “\n”)

def flush(self):

self.logfile.flush()

class Auth(object):

accounts = {}

def __init__(self):

pass

def addaccount(self, nick, password, level):

self.accounts[nick] = {”password” : password,

“level” : level,

“mask” : “”}

def login(self, nick, password, mask):

for account in self.accounts:

if account == nick:

if self.accounts[account]["password"] == password:

self.accounts[account]["mask"] = mask

return True

return False

def hasright(self, mask, level):

for account in self.accounts:

if self.accounts[account]["mask"] == mask:

# we got our man !

if self.accounts[account]["level"] >= level:

return True

else:

print “l’utilisateur \”"+account+”\” a tenté d’effectuer une commande interdite”

return False

class Plink(object):

def __init__(self):

self.networks = {}

self.running = False

self.auth = Auth()

self.logger = Logger(”logs.txt”)

def addnetwork(self, config):

“”" creation d’un nouveau thread “”"

# thread de type ThreadClient (voir classe ci-bas)

self.networks[config[0]] = ThreadClient(self, self.networks, config)

def dynamicadd(self, config):

“”" creation d’un nouveau thread “”"

# thread de type ThreadClient (voir classe ci-bas)

self.networks[config[0]] = ThreadClient(self, self.networks, config)

self.networks[config[0]].start()

def getnetworks(self):

“”" retourne une liste de réseaux colorés “”"

netlist = “”

for net in self.networks:

if self.networks[net]:

# si il est pas supprimé (c’est ou cette cond, ou une section critique, alors…)

netlist += “%s%s ” % (self.networks[net].color, net)

return netlist

def start(self):

“”" Démarrage des threads “”"

self.running = True

self.logger.start()

self.logger.log(”*** Now talking in #z”) # fake évidemment ^^

for network in self.networks:

if self.networks[network]:

self.networks[network].start()

while self.running:

time.sleep(2)

sys.stdout.flush() # flush du log debug plink (console)

for network in self.networks:

if self.networks[network]:

self.networks[network].flush() # flush des logs des reseaux

self.logger.flush() # flush du log Eggdrop IRC

exit(0)

def quit(self, msg=”"):

print “fermeture…”

for network in self.networks:

self.networks[network].quit(msg)

self.networks[network] = None # pour que la taille de networks ne change pas

self.running = False

def networkclose(self, network):

self.networks[network].quit(”err”)

self.networks[network] = None

print “[PLINK] Réseau \”"+network+”\” supprimé.”

class ThreadClient(threading.Thread):

“”" Classe de thread “”"

networks = {}

username = “plink”

realname = “plink :)”

connected = False

running = False

onchan = False

def __init__(self, plink, networks, (network, color, server, port, chan, nick)):

threading.Thread.__init__(self)

self.plink = plink

self.networks = networks # dico des threads en cours

self.network = network

self.color = color

self.port = port

self.server = server

self.chan = chan

self.nick = nick

self.log = open(network+”.txt”, “wb”)

# credits for the regex belong to Python-IRC.py (see on google)

# minors corrections and ehancements by myself

# : optionnel (pour les notices ?)

self.svrmsg = re.compile(’^:([a-zA-Z0-9\.]+) ([0-9]+) ‘+ nick + ‘ :?(.*)$’)

self.chanmsg = re.compile(’^:(.+)![~]?(.+)@(.+) PRIVMSG (#?[azA-Z0-9_\\\-`\[\]{}()]+) :(.*)$’)

# notice, modes etc

self.genmsg = re.compile(’^:(.+)!~?(.+)@([a-zA-Z0-9\-\.]+) ([azA-Z0-9_\\\-`\[\]{}()]+) :?(.*)$’)

self.pingmsg = re.compile(’^PING :(.*)$’, re.IGNORECASE)

# regex des commandes “fun”

self.funkick = re.compile(’^’ + self.nick + ‘,? ?(?:tu peux)? ?(?:éjecter|ejecter|kick|kicker|quick|faire sortir)? ?(?:ce con de|ce con d\’) ?([a-z0-9_\-`]{1,32}) ?(?:sur ([a-z]{2,32}))? ?(?:stp|please|svp|s\’il te plait)’, re.IGNORECASE)

self.fungunther = re.compile(’gunther’,re.IGNORECASE)

def connect(self):

print “["+self.network+"] connection…”

self.sock = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )

self.sock.settimeout(60)

self.sock.connect((self.server, self.port))

def logging(self):

# logging

print “["+self.network+"] identification…”

time.sleep(0.5)

self.send(’NICK %s’ % self.nick)

time.sleep(0.5)

self.send(’USER %s idk %s :%s’ % (self.username, self.server, self.realname))

time.sleep(1)

def join(self):

print “["+self.network+"] join…”

self.send (’JOIN %s’ % self.chan)

# verifier si on est bien dessus

self.onchan = True

def send(self, msg):

“”" envoi un message RAW sur le socket “”"

if self.sock:

self.sock.send(msg + “\n”)

# bon, pas besoin de debugger la sortie enfait…

#self.debug(”[OUT] ” + msg)

else:

print “erreur, impossible d’envoyer le msg, le socket est mort”

#self.log.write(”[ERR] erreur, impossible d’envoyer le msg, le socket est mort\n”)

def sendchan(self, msg):

“”" envoi un message au salon géré par le thread “”"

if self.onchan:

self.send(’PRIVMSG %s :%s’ % (self.chan, msg))

else:

pass

# on ne garde pas en mémoire

def sendallchan(self, msg):

“”" envoi un message à tout les salons du bot (utile pour les commandes “fun”) “”"

self.sendchan(msg)

self.sendothers(msg)

def sendothers(self, msg):

“”" envoi un message sur tous les autres réseaux “”"

for network in self.networks:

if network != self.network:

if self.networks[network]:

self.networks[network].sendchan(msg)

def quit(self,msg):

print “["+self.network+"] déconnection…”

self.send(”QUIT :%s” % msg)

time.sleep(1)

#self.sock.close()

self.running = False

print “["+self.network+"] arrêt du thread…”

def debug(self, debugmsg):

if self.log:

self.log.write(debugmsg + “\n”)

else:

print “Erreur, impossible d’écrire le log !”

def flush(self):

if self.log:

self.log.flush()

def processing(self, raw_msg):

for line in raw_msg.splitlines():

# GESTION DES MESSAGES SERVEUR

if self.svrmsg.match(line):

parse = self.svrmsg.match(line)

# lors de la reception du MOTD (ou si il n’existe pas)

if parse.group(2) == “376″ or parse.group(2) == “422″:

self.debug(”Le serveur est prêt -> join()”)

self.join()

else:

self.debug(”[IN:SERVER_MSG:"+parse.group(2)+"] “+line)

# GESTION DES MESSAGES PRIVEES/SALON

elif self.chanmsg.match(line):

parse = self.chanmsg.match(line)

mots = parse.group(5).split(” “)

login = parse.group(1)

mask = parse.group(1)+”!”+parse.group(2)+”@”+parse.group(3)

## messages privés

if parse.group(4) == self.nick:

self.debug(”[IN:PRIV_MSG:FROM "+parse.group(1)+"] “+ parse.group(5))

if mots[0].upper() == “QUIT” and self.plink.auth.hasright(mask, 1000):

self.plink.quit(”bye bye”)

# authentification des utilisateurs

if (len(mots) > 1) and (mots[0].upper() == “IDENTIFY”):

self.debug(”[AUTH REQUEST] “+parse.group(1))

if self.plink.auth.login(login, mots[1], mask):

self.debug(”[AUTH OK] “+parse.group(1))

self.send(”PRIVMSG ” + parse.group(1) + ” :” + “AUTHENTIFICATION OK”)

else:

# CHANNEL MSG

#self.debug(”[IN:CHAN_MSG:"+parse.group(4)+"] “+ parse.group(5))

if mots[0] == “ACTION”:

self.plink.logger.log(”*** %s@%s %s” % (parse.group(1), self.network, ” “.join(mots[1:])))

self.sendothers(”ACTION * %s %s”

% (parse.group(1), ” “.join(mots[1:])))

else:

self.plink.logger.log(”<%s&%s> %s” % (parse.group(1), self.network, parse.group(5)))

self.sendothers(’<%s%s> %s’ % ( self.color, parse.group(1), parse.group(5)))

if mots[0] == “!networks”:

self.send(”PRIVMSG ” + parse.group(1) + ” :”+ self.plink.getnetworks())

if mots[0] == “!boulet” and self.plink.auth.hasright(mask, 10):

self.sendallchan(mots[1] + ” est un boulet !”)

if mots[0] == “!python” and self.plink.auth.hasright(mask,9000):

print “["+self.network+"] execution de \”" + ” “.join(mots[1:]) + “\” en cours…”

exec(” “.join(mots[1:]))

if mots[0] == “!eval” and self.plink.auth.hasright(mask,8000):

print “["+self.network+"] evaluation de \”" + ” “.join(mots[1:]) + “\” en cours…”

ret = eval(” “.join(mots[1:]))

self.sendallchan(ret)

if self.fungunther.match(parse.group(5)):

self.sendallchan(”ooh, you touch my tralala…”)

if self.funkick.match(parse.group(5)) and self.plink.auth.hasright(mask, 50):

cmd = self.funkick.match(parse.group(5))

msg = “I am the alpha and the omega”

if cmd.group(2):

print “kick inter reseau”

#self.send(”KICK ” + self.chan + ” “+ cmd.group(2) + “I am the operator”)

else:

print “kick normal.”

time.sleep(3)

self.sendchan(”" + cmd.group(1) + “, I’m soooo confused :(”)

time.sleep(3)

self.sendchan(”But you must left.”)

time.sleep(1)

self.send(”KICK ” + self.chan + ” “+ cmd.group(1) + ” ” + msg)

time.sleep(3)

self.sendchan(”Vous savez, “+cmd.group(1) + ” le méritait.”)

# GESTION DES MESSAGES D’INFO

elif self.genmsg.match(line):

parse = self.genmsg.match(line)

if parse.group(4).upper() == “QUIT”:

self.plink.logger.log(”*** %s&%s has left %s (%s)”

% (parse.group(1), self.network, self.chan, parse.group(5)))

self.sendothers(”ACTION 3* %s%s3 est parti (%s)”

% (self.color, parse.group(1), parse.group(5)))

elif parse.group(4).upper() == “JOIN”:

self.plink.logger.log(”*** %s&%s (%s@%s) has joined %s”

% (parse.group(1), self.network,parse.group(2),parse.group(3),self.chan))

self.sendothers(”ACTION 3* %s%s3 vient d’entrer sur %s%s”

% (self.color, parse.group(1), self.color, self.network))

elif parse.group(4).upper() == “NICK”:

self.plink.logger.log(”*** %s&%s is now known as %s&%s”

% (parse.group(1), self.network,parse.group(5),self.network))

self.sendothers(”ACTION 3* %s%s3 s’apelle maintenant <%s%s3>”

% (self.color, parse.group(1),self.color, parse.group(5)))

else:

self.debug(”[IN:ERR:UNKNOW_GENERAL_MSG] “+ line)

# GESTION DES PINGS

elif self.pingmsg.match(line):

parse = self.pingmsg.match(line)

self.sock.send(’PONG :’ + parse.group(1)+’\n’)

else:

self.debug(”[IN:ERR:NO_MATCH!] “+ line)

def run(self):

self.running = True

self.connect()

self.logging()

while self.running:

is_readable = [self.sock]

is_writable = []

is_error = [self.sock]

r, w, e = select.select(is_readable, is_writable, is_error, 1.0)

if e:

print “erreur, connection coupée !”

self.plink.networkclose(self.network) # on demande à plink de nous arrêter/supprimer

elif r:

data = self.sock.recv(8912)

if data:

self.processing(data)

else:

print “["+self.network+"] buffer vide ! running -> false”

self.plink.networkclose(self.network) # on demande à plink de nous arrêter/supprimer

self.sock.close() # erreur!
et voilà le fichier “plink”, qui permet de lancer la bestiole :

plink

#!/usr/bin/python# -*- coding: UTF-8 -*-

__author__ = "Julian `uxmal` Bilcke"

__date__ = "28 Aug 2007"

__version__ = "$Revision: 3.1.0 $"

__appname__ = "Plink 3.1.0"

__credits__ = "mwa"

import time

from plinklib import Plink

PORT = 6667

CHANNEL = "#monjardin"

BOTNAME = "courgette"

networks = (

("teepi", 7, "irc.teepi.net", PORT, CHANNEL, BOTNAME),

#("langochat", 12, "irc.langochat.net", PORT, CHANNEL, BOTNAME),

#("ircube", 9, "irc.ircube.org", PORT, CHANNEL, BOTNAME),

#("p0w0w", 15, "irc.p0w0w.org", PORT, CHANNEL, BOTNAME),

)plink = Plink()

for network in networks:

plink.addnetwork(network)

time.sleep(1) # pour pas flooder de messages "xxx a rejoint yyy"

plink.auth.addaccount("tomate","fraise",9999)

plink.auth.addaccount("celeri","banane",9999)

plink.start()

# désolé ça s'affiche pas très bien le code python là dessus..

Il reste encore des trucs à régler, notamment un timing pour forcer le /join du bot si le serveur est la réincarnation d’une carpe,
et heu.. d’autres trucs sur la syncro (corriger le ACTION aussi)

Bon, je me rapelle plus exactement (2 mois ça fait longtemps) mais je crois que le bot créer un fichier de log compatible eggdrop, gère l’identification des gens et permet de kicker à distance (si on est identifié).

Il peut aussi executer du code python à la volée (mais attention les bugs sont létaux)

0 Responses to “Bot irc multi-réseaux en python”


  1. No Comments

Leave a Reply