Version en ligne

Tutoriel : Programmer un bot IRC simplement avec ircbot

Table des matières

Programmer un bot IRC simplement avec ircbot
Se procurer les fichiers
La programmation évènementielle et ircbot
Application à un bot simpliste
Allons un peu plus loin

Programmer un bot IRC simplement avec ircbot

Se procurer les fichiers

Ce tutoriel présente l'utilisation du module ircbot de Joel Rosdahl pour Python. Comme son nom l'indique, celui-ci permet de programmer son propre bot IRC. Il utilise le module irclib du même auteur, qui est plus général et qui ne sera que partiellement présenté ici.

Sont prérequises pour suivre ce tutoriel :

C'est bon ? Alors, suivez le guide. ;)

Se procurer les fichiers

La programmation évènementielle et ircbot

Les deux modules que nous allons étudier, irclib et ircbot, ont été développés par un programmeur tiers et ne sont donc pas livrés directement avec Python ; vous allez donc devoir vous les procurer avant de commencer !

Si vous êtes sous une distribution Linux gérant les installations par paquets, alors il existe peut-être un paquet python-irclib qui regroupe irclibetircbot. Par exemple, sous Ubuntu, taper sudo apt-get install python-irclib installera les modules. Sinon, vous pouvez télécharger la dernière version depuis la page du projet sur SourceForge.net.
Cependant, la version publique des modules gère les half operators de la même manière que les utilisateurs normaux ce qui peut produire quelques bugs (utilisateurs qui sont listés avec un % devant leur pseudonyme). Pour cette raison, je vous invite à utiliser une version modifiée par mes soins qui fait bien la distinction ici. Après cela, décompressez l'archive à l'aide de votre logiciel favori. La plupart des fichiers sont des programmes d'exemple, que je vous conseille de regarder plutôt après qu'avant d'avoir lu ce tutoriel. :p
Maintenant, deux choix s'offrent à vous :

La première solution est conseillée si vous utilisez vos bots pour vous-mêmes, puisqu'elle vous évitera de copier les fichiers à chaque fois que vous programmerez un nouveau bot. En revanche, si vous comptez distribuer vos programmes, il est fortement recommandé de livrer les fichiers des modules avec, puisque l'utilisateur n'aura pas nécessairement installé irclib et ircbot sur son ordinateur, d'autant plus si vous utilisez la version modifiée. Vous pouvez aussi installer les fichiers pour la période de développement et les distribuer une fois le bot terminé.


La programmation évènementielle et ircbot

La programmation évènementielle et ircbot

Se procurer les fichiers Application à un bot simpliste

Avant de commencer à coder, il va falloir faire un brin de théorie. Rien de bien compliqué, bien entendu. ;)

Si vous utilisez irclib ou ircbot, vous allez devoir faire ce que l'on appelle de la programmation évènementielle. Il s'agit d'une logique de programmation raisonnant sur des évènements : quand il se passe telle chose, alors on fait cela. Pour un exemple concret : quand quelqu'un rejoint le canal, alors je lui dis bonjour. Il s'avère que c'est particulièrement adapté à IRC, ce qui explique son utilisation ici.

Dans vos programmes, vous allez définir pour chaque évènement que vous souhaitez traiter une méthode qui sera appelée automatiquement par ircbot lorsque l'évènement en question se produira. Pour cela, vos bots devront hériter de la classe ircbot.SingleServerIRCBot.
Chaque méthode devra être de la forme suivante :

class MonBot(ircbot.SingleServerIRCBot):
    def on_evenement(self, serv, ev):
        # gestion de l'évènement

Nous allons maintenant étudier plus en détail ces deux arguments.

Communication avec le serveur

irclib nous permet de faire dialoguer notre bot avec le serveur comme le ferait un client IRC habituel à travers la classe irclib.ServerConnection, dont l'argument serv dont nous avons parlé plus haut est une instance. Elle dispose de nombreuses méthodes et seules les plus utilisées seront présentées ici.

irclib.ServerConnection.

Utilité

nick(self, pseudonyme)

Change le pseudonyme du bot.

get_nickname(self)

Renvoie votre pseudonyme actuel. Celui-ci n'est pas forcément celui que vous souhaitiez, en particulier si vous demandez un pseudonyme réservé.

privmsg(self, destinataire, message)

Envoie un message ; le destinataire peut être soit un canal tel que #kikoo, soit un nom d'utilisateur tel que dentuk.

action(self, destinataire, message)

Envoie une action ; sous la plupart des clients, cela correspond à la commande /me.

notice(self, destinataire, message)

Envoie une notice.

invite(self, pseudonyme, canal)

Invite un autre utilisateur à rejoindre le canal précisé.

join(self, canal, cle='')

Rejoint le canal précisé, en utilisant la clé donnée si le canal est protégé par mot de passe.

part(self, canal, message='')

Quitte le canal précisé, éventuellement avec un message d'au revoir.

disconnect(self, message='')

Quitte le serveur, éventuellement avec un message d'au revoir, et déconnecte le bot.

irclib.ServerConnection.

Utilité

kick(self, canal, pseudonyme, raison='')

Exclut un utilisateur du canal, éventuellement avec une raison.

mode(self, canal, commande)

Active ou retire un mode. Nous verrons comment utiliser les modes plus loin dans le tutoriel.

Voilà déjà de quoi bien vous amuser. Pour la liste complète des méthodes, lancez un shell Python et procédez ainsi :

>>> import irclib
>>> help(irclib.ServerConnection)

Toutefois, la documentation n'est pas très précise, le plus souvent on a droit à un « envoie telle commande au serveur », vous aurez donc peut-être besoin de la compléter par une recherche.

La variable d'évènement

Il y a dans irclib trois catégories d'évènements :

Un évènement est représenté par une instance de la classe irclib.Event, c'est le cas de l'argument ev de notre méthode modèle. Cette classe possède trois méthodes d'information qui nous seront utiles, et une autre qui, en général, ne sert pas quand on utilise ircbot.

irclib.Event.

Renseigne sur...

source(self)

... l'origine de l'évènement ; c'est-à-dire, selon l'évènement, soit un serveur, soit un utilisateur, soit un canal.

target(self)

... la cible de l'évènement ; c'est-à-dire, selon l'évènement, soit un utilisateur, soit un canal.

arguments(self)

... divers paramètres relatifs à l'évènement, toujours sous la forme d'une liste.

eventtype(self)

... le type de l'évènement ; par exemple, "welcome" pour l'évènement de bienvenue.

Nous allons maintenant voir quelques évènements que vous pouvez choisir de traiter. Bien entendu, vous n'avez pas besoin de tout savoir par coeur, vous pouvez toujours garder les tableaux de côté quand vous programmez. Je vous propose deux tableaux : dans le premier, il y a ce que représente la valeur de retour de chaque méthode de irclib.Event pour chaque évènement ; et dans l'autre, des exemples de ces valeurs de retour pour bien comprendre.

Émis quand...

ev.eventtype()

ev.source()

ev.target()

ev.arguments()

... vous êtes connectés au serveur.

welcome

Image utilisateurImage utilisateur

Serveur

Image utilisateurImage utilisateur

Votre pseudonyme

Image utilisateurImage utilisateur

Message d'accueil

... quelqu'un change de pseudonyme.

nick

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Nouveau pseudonyme

... quelqu'un vous envoie un message privé.

privmsg

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Votre pseudonyme

Image utilisateurImage utilisateur

Message envoyé

... quelqu'un envoie un message sur le canal.

pubmsg

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Canal

Image utilisateurImage utilisateur

Message envoyé

... quelqu'un utilise la commande /me sur le canal.

action

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Canal

Image utilisateurImage utilisateur

Message d'action

... quelqu'un vous envoie une notice.

privnotice

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Votre pseudonyme

Image utilisateurImage utilisateur

Message envoyé

... quelqu'un envoie une notice au canal.

pubnotice

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Canal

Image utilisateurImage utilisateur

Message envoyé

... quelqu'un invite une autre personne à rejoindre le canal.

invite

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Pseudonyme invité

Image utilisateurImage utilisateur

Canal

... quelqu'un rejoint le canal.

join

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Canal

... quelqu'un quitte le canal.

part

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Canal

Image utilisateurImage utilisateur

Message d'au revoir

... quelqu'un quitte le serveur.

quit

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Message d'au revoir

... un opérateur expulse un utilisateur.

kick

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Canal

  • Image utilisateurImage utilisateur

    Pseudonyme de la victime

  • Image utilisateurImage utilisateur

    Raison du kick

... un opérateur applique un ou des modes.

mode

Image utilisateurImage utilisateur

Utilisateur

Image utilisateurImage utilisateur

Canal

  • Mode(s) appliqué(s)

  • Argument éventuel

  • Argument éventuel

  • ...

ev.eventtype()

ev.source()

ev.target()

ev.arguments()

'welcome'

'marseille.fr.epiknet.org'

'bottuk'

['Welcome to the EpiKnet IRC Network bottuk!~bottuk@[...].net']

'nick'

'dentuk![email protected]'

'palatuk'

[]

'privmsg'

'dentuk![email protected]'

'bottuk'

['salut bottuk, comment va ?']

'pubmsg'

'dentuk![email protected]'

'#kikoo'

['bonjour tout le monde !']

'action'

'dentuk![email protected]'

'#kikoo'

['est heureux !']

'privnotice'

'dentuk![email protected]'

'bottuk'

['salut bottuk !']

'pubnotice'

'dentuk![email protected]'

'#kikoo'

['je notice tout le canal !']

'invite'

'dentuk![email protected]'

'TomicBomb'

['#kikoo']

'join'

'dentuk![email protected]'

'#kikoo'

[]

'part'

'dentuk![email protected]'

'#kikoo'

['je reviens']

'quit'

'dentuk![email protected]'

None

['Quit: Konversation terminated!']

'kick'

'dentuk![email protected]'

'#kikoo'

['bouletman', "pas d'insultes, merci !"]

'mode'

'dentuk![email protected]'

'#kikoo'

['+hh', 'bouh', 'A-dream']

Vous vous demandez peut-être comment exploiter les valeurs de retour de la méthode irclib.Event.source, que j'appellerai des masques pour la suite du tutoriel. Bien que l'on puisse extraire les différentes parties assez facilement avec Python seul, irclib fournit plusieurs fonctions pour procéder à l'extraction. Le tableau ci-dessous utilise un masque de la forme 'pseudonyme!utilisateur@domaine' .

irclib.

Origine du nom

Extrait...

nm_to_n(masque)

nickmask to nick

... le pseudonyme.

nm_to_u(masque)

nickmask to user

... l'utilisateur.

nm_to_h(masque)

nickmask to host

... le domaine.

nm_to_uh(masque)

nickmask to userhost

... l'utilisateur et le domaine.

Pour illustrer, je vous propose une petite session dans un shell Python. :)

>>> import irclib
>>> masque = 'dentuk![email protected]'
>>> irclib.nm_to_n(masque)
'dentuk'
>>> irclib.nm_to_u(masque)
'~dentuk'
>>> irclib.nm_to_h(masque)
'EpiK-BE9687C2.fbx.proxad.net'
>>> irclib.nm_to_uh(masque)
'[email protected]'

Parlons maintenant un peu de la classe ircbot.SingleServerIRCBot.

Étude de la classe mère

Nous allons maintenant voir quelques méthodes de la classe mère qui vous seront utiles pour la plupart de vos bots.

Méthode d'initialisation

En voici le prototype :

ircbot.SingleServerIRCBot.__init__(self, liste_serveurs, pseudonyme, nom_reel et intervalle_reconnexion=60)

Étudions les paramètres un à un.

Lancer le bot

On ne peut plus simple, le lancement se fait par l'appel de la méthode ircbot.SingleServerIRCBot.start qui ne prend aucun paramètre. :) Notez que cela interrompt l'exécution du script pour se consacrer uniquement au bot, comme une boucle infinie. On peut en sortir en levant une exception, par conséquent l'interruption Ctrl-C arrête le bot.

Arrêter le bot

La méthode ircbot.SingleServerIRCBot.die(self, message='') quitte le serveur, déconnecte le bot et met fin à l'exécution du script. C'est en général cette méthode que l'on utilise pour arrêter le bot. Si vous avez besoin de poursuivre l'exécution du script après la déconnexion, utilisez plutôt la méthode ircbot.SingleServerIRCBot.disconnect(self, message='') suivie de la levée d'une exception que vous intercepterez dans le code principal. Notez que si vous employez cette dernière seule, le bot se reconnectera au bout d'un certain temps précisé à l'initialisation.

Répondre aux commandes VERSION

Par défaut, la réponse envoyée à une commande VERSION est "ircbot.py by Joel Rosdahl <[email protected]>" . Si vous désirez changer cela, vous pouvez réimplémenter la méthode ircbot.SingleServerIRCBot.get_version(self) , et lui faire retourner le message de votre choix.


Se procurer les fichiers Application à un bot simpliste

Application à un bot simpliste

La programmation évènementielle et ircbot Allons un peu plus loin

Pour mieux comprendre tout ce que l'on a vu jusqu'ici, je propose de vous montrer un exemple de bot codé avec ircbot. Il s'agit d'un bot de modération simplifié au maximum, qui exclut les personnes écrivant des insultes dans leurs messages. Analysez bien le code et revoyez au besoin ce qui a déjà été vu, le but étant que vous soyez capables de faire un bot vous-mêmes après avoir vu cet exemple.
J'ai commenté les parties du code qui le nécessitaient, les autres parties sont simples et je ne m'étendrai donc pas dessus.

#!/usr/bin/env python2
# -*- coding: utf8 -*-

import irclib
import ircbot

class BotModeration(ircbot.SingleServerIRCBot):
    def __init__(self):
        """
        Constructeur qui pourrait prendre des paramètres dans un "vrai" programme.
        """
        ircbot.SingleServerIRCBot.__init__(self, [("irc.epiknet.org", 6667)],
                                           "moderator", "Bot de modération réalisé en Python avec ircbot")
        self.insultes = ["con", "pute"] # Liste à agrandir pour un "vrai" programme.

    def on_welcome(self, serv, ev):
        """
        Méthode appelée une fois connecté et identifié.
        Notez qu'on ne peut rejoindre les canaux auparavant.
        """
        serv.join("#test-ircbot")

    def on_pubmsg(self, serv, ev):
        """
        Méthode appelée à la réception d'un message, qui exclut son expéditeur s'il
        écrit une insulte.
        """
        # Il n'est pas indispensable de passer par des variables
        # Ici elles permettent de clarifier le tout.
        auteur = irclib.nm_to_n(ev.source())
        canal = ev.target()
        message = ev.arguments()[0].lower() # Les insultes sont écrites en minuscules.

        for insulte in self.insultes:
            if insulte in message:
                serv.kick(canal, auteur, "Les insultes ne sont pas autorisées ici !")
                break

if __name__ == "__main__":
    BotModeration().start()

La gestion des insultes pourrait bien sûr être grandement améliorée, mais ce n'est pas le but de cet exemple. Si vous avez tout compris jusqu'ici, vous devriez déjà être capables de programmer des bots intéressants. Si vous estimez ces connaissances suffisantes, vous pouvez arrêter ici le tutoriel. En revanche, si vous souhaitez aller un peu plus loin, passez à la partie suivante. :)


La programmation évènementielle et ircbot Allons un peu plus loin

Allons un peu plus loin

Application à un bot simpliste

Dans cette partie, nous allons voir diverses choses qui pourraient vous être utiles un jour, sans ordre précis. Si une sous-partie ne vous intéresse pas, passez à la suivante. ;)

Masques de bannissement

Si vous êtes opérateur d'un canal, vous avez probablement déjà vu des masques de bannissement. Par exemple, le masque *tuk!*@* englobe les personnes ayant un pseudo finissant par « tuk », quels que soient leur nom d'utilisateur et leur domaine. irclib fournit une fonction pour vérifier si un utilisateur vérifie un masque ainsi formé, dont voici le prototype :

irclib.mask_matches(masque_utilisateur, masque_a_verifier)

Cette fonction, à l'instar de la fonction re.match, retourne un objet de matches en cas de succès et None en cas d'échec. À noter que l'objet de matches est inexploitable. En général, on utilise la valeur de retour comme s'il s'agissait d'un booléen, parce que bool(None) vaut False et bool(un_objet_de_matches) vaut True .
Toujours pour illustrer, une autre session dans le shell Python :

>>> import irclib
>>> masque = 'dentuk![email protected]'
>>> irclib.mask_matches(masque, '*tuk!*@*')
<_sre.SRE_Match object at 0xb7a6d7c8>
>>> irclib.mask_matches(masque, 'Tomic*!*@*')
>>> bool(irclib.mask_matches(masque, '*tuk!*@*'))
True
>>> bool(irclib.mask_matches(masque, 'Tomic*!*@*'))
False

Cela peut, par exemple, servir à faire une commande !quit qui dit au bot de partir, mais réservée à son possesseur en identifiant ce dernier par un masque :

class Bot(ircbot.SingleServerIRCBot):
    def on_pubmsg(self, serv, ev):
        masque_auteur = ev.source()
        message = ev.arguments()[0].lower()
        if message == "!quit" and irclib.mask_matches(masque_auteur, "*!*@EpiK-BE9687C2.fbx.proxad.net"):
            self.die()

Exécution délimitée dans le temps

Vous pouvez avoir besoin d'attendre un certain temps avant d'exécuter une action. Par exemple, si vous faites un bot de quiz et que vous devez attendre une minute, avant de changer de question si personne n'a trouvé la question, pour éviter que le jeu ne soit bloqué. Mais si vous utilisiez time.sleep, vous bloqueriez l'exécution et irclib ne pourrait pas traiter les nouveaux évènements pendant ce temps. Pour régler ce problème, irclib propose deux méthodes.

Exécution au bout d'un certain temps

Voici la méthode à utiliser :

irclib.ServerConnection.execute_delayed(self, temps, fonction, arguments=())

Lorsque vous appelez cette méthode, irclib prend en compte qu'il faudra appeler la fonction fonction dans temps secondes avec les arguments arguments, et fera l'appel le moment voulu. temps peut être un nombre entier ou flottant, et arguments est un tuple contenant les arguments à passer à la fonction, qui est vide par défaut.

Par exemple, si vous étiez sadiques et que vous souhaitiez adapter le code du bot de modération, vu plus haut, en mettant un compte à rebours avant l'exclusion, vous pourriez implémenter la méthode on_pubmsg ainsi : :D

class BotModeration(ircbot.SingleServerIRCBot):
    def on_pubmsg(self, serv, ev):
        auteur = irclib.nm_to_n(ev.source())
        canal = ev.target()
        message = ev.arguments()[0].lower()

        for insulte in self.insultes:
            if insulte in message:
                serv.privmsg(canal, "3...")
                serv.execute_delayed(1, serv.privmsg, (canal, "2..."))
                serv.execute_delayed(2, serv.privmsg, (canal, "1..."))
                serv.execute_delayed(3, serv.kick, (canal, auteur, "0 !"))
                break

Exécution à un moment précis

irclib.ServerConnection.execute_at(self, timestamp, fonction, arguments=())

Très similaire, cette méthode fonctionne exactement de la même manière. Simplement, le premier paramètre est maintenant un timestamp tel que ceux retournés par time.time et time.mktime .

Gestion des canaux

Vous vous rappelez quand je vous ai parlé de la méthode irclib.ServerConnection.mode(self, canal, mode) ? Il est temps de voir quelques modes qui peuvent être activés et leur effet. :)

Mode

Signification

m

Le canal est modéré, seuls les privilégiés, les half operators et les opérateurs peuvent y parler.

s

Le canal est secret, il est caché lors d'une commande /whois et il n'est pas présent dans la liste des canaux du serveur.

p

Le canal est protégé, ce qui a le même effet qu'être secret.

i

Le canal est accessible uniquement sur invitation.

Si par exemple vous vouliez que le canal devienne modéré, vous devriez utiliser serv.mode(canal, "+m") . Pour qu'il ne le soit plus, ce serait serv.mode(canal, "-m") . Vous pouvez cumuler l'ajout et / ou le retrait de modes. Par exemple, si vous utilisez serv.mode(canal, "+is-m") , alors :

Viennent ensuite les modes avec paramètres.

Mode

Paramètre

Signification

v

Utilisateur

La personne est privilégiée.

h

Utilisateur

La personne est un half operators.

o

Utilisateur

La personne est un opérateur.

b

Masque de bannissement

Si une personne correspond au masque, elle ne peut pas rejoindre le canal.

e

Masque d'exception

Si une personne correspond au masque, les bannissements ne l'empêchent pas de rejoindre le canal.

k

Clé

Il faut connaître la clé pour rejoindre le canal.

Mettons que vous vouliez que bouh devienne half operator. La commande serait alors serv.mode(canal, "+h bouh") . Pour qu'il ne le soit plus, ce serait serv.mode(canal, "-h bouh") . Là aussi, vous pouvez combiner les modes, en séparant les paramètres par des espaces et en les mettant dans l'ordre où les modes correspondants apparaissent. Par exemple, voici ce que fait le code serv.mode(canal, "+ohhm-v TomicBomb bouh A-dream bouletman") en lisant de gauche à droite :

ircbot définit une classe pour savoir qui est présent sur un canal et quels modes y sont activés, la classe ircbot.Channel. Nous allons voir les méthodes correspondant aux modes étudiés plus haut, dans des tableaux pour changer. :lol:

En français

En anglais

Liste des...

pseudonyme en est-il un ?

Utilisateurs

Users

irclib.Channel.users(self)

irclib.Channel.has_user(self, pseudonyme)

Privilégiés

Voices

irclib.Channel.voiced(self)

irclib.Channel.is_voiced(self, pseudonyme)

« Demi-opérateurs »

Half operators

irclib.Channel.halfopers(self)

irclib.Channel.is_halfoper(self, pseudonyme)

Opérateurs

Operators

irclib.Channel.opers(self)

irclib.Channel.is_oper(self, pseudonyme)

Le canal est-il...

irclib.Channel.

... modéré ?

is_moderated(self)

... secret ?

is_secret(self)

... protégé ?

is_protected(self)

... accessible uniquement sur invitation ?

is_invite_only(self)

... protégé par une clé ?

has_key(self)

Vous pouvez récupérer la clé du canal avec la méthode irclib.Channel.key(self) , qui retourne None s'il n'y en a pas. Pour ceux qui préfèrent utiliser les noms des modes plutôt que de longs noms de méthodes, la classe prévoit une méthode irclib.Channel.has_mode(self, mode) .
Maintenant que l'on a vu ce que permettait la classe, il serait bon de l'utiliser. ^^ La classe mère ircbot.SingleServerIRCBot possède un attribut channels qui est un dictionnaire faisant correspondre un canal à un objet ircbot.Channel, lequel est mis à jour automatiquement suivant les évènements. :)
Retournons donc voir notre bot de modération pour lui interdire d'exclure les membres privilégiés :

class BotModeration(ircbot.SingleServerIRCBot):
    def on_pubmsg(self, serv, ev):
        auteur = irclib.nm_to_n(ev.source())
        canal = (ev.target(), self.channels[ev.target()]) # tuple (nom, ircbot.Channel)
        message = ev.arguments()[0].lower()

        if not canal[1].is_voiced(auteur) and not canal[1].is_halfoper(auteur) and not canal[1].is_oper(auteur):
            for insulte in self.insultes:
                if insulte in message:
                    serv.kick(canal[0], auteur, "Les insultes ne sont pas autorisées ici !")
                    break

Vous devriez maintenant pouvoir faire le bot IRC que vous souhaitez. Vous pouvez compléter ce tutoriel en lisant la documentation et / ou le code des modules étudiés, ou en vous renseignant sur le protocole IRC. Laissez libre cours à votre imagination, et amusez-vous bien ! ;)

Les images 16x16 associées aux serveurs, utilisateurs et canaux dans le tableau des évènements proviennent de http://www.famfamfam.com/.


Application à un bot simpliste