MyBot, robot IRC écrit en C

MyBot - Téléchargements

Version 0.1

Téléchargement via CVS

Si vous désirez tester les dernières fonctionnalitées supra inutiles offertes par MyBot, vous avez la possibilitée d'utiliser la version CVS, qui est la version sur laquelle je développe.

cvs -d:pserver:anoncvs@rafi.zehome.com:/home/cvs/ed login
[Enter]
cvs -d:pserver:anoncvs@rafi.zehome.com:/home/cvs/ed co MyBot

Compilation

Comme je l'explique plus loin, j'ai fais un petit script shell pour aider à la compilation multiplateforme.

  ./compile.sh
  make all

Ces lignes devraient suffire à construire les modules .so chargeables par MyBot, et l'executable binaire mybot dans votre répertoire ".".

L'étape suivante consiste à copier le fichier de configuration mybot.conf.example en mybot.conf. Il faut ensuite éditer mybot.conf pour configurer le programme.

Pourquoi ?

Apprendre un protocole

J'ai développé MyBot dans le but d'arriver à prendre une RFC, celle d'IRC dans le cas présent, et d'être capable d'en créer une implémentation partielle.

Programmation: Language C

Je tiens à préciser que pour créer ce bot je me suis limité aux librairies nécessaires, autrement dit la libc, et c'est tout. Le but était justement de réinventer la roue, pour savoir si je pouvais refaire la roue, et comment.

L'objectif était aussi d'en apprendre un peu plus sur le language C que je m'évertue à apprendre pendant mon temps libre. J'ai ainsi pu coder un parseur de fichier de config relativement flexible pour mes besoins. Tres simple, mais efficace.

J'ai aussi apris quelques trucs sur la manipulation de pointeurs, notament qu'en passant un char * dans une fonction on ne passe que la valeur pointée par ce char * et pas l'adresse de ce pointeur. Cela peut paraitre logique pour quelqu'un qui fais du C depuis des années (cela me parait tout a fais logique désormais), mais pour un developpeur qui n'ayant travaillé qu'avec des language script auparavant, c'est pas si évident que ça. (je ne dit pas que c'est mon cas non plu \o/)

J'ai aussi voulu me mettre au défit en adaptant le chargeur de modules que j'ai pu créer pour mon serveur web edWeb, sur MyBot. C'est un succès, bien que l'adaptation ne fut pas facile et aussi simple que je le pensait au départ. Il m'a fallu par exemple refaire toute la gestion des handlers, et faire pas mal de cleanup dans la gestion des modules via dlopen dlclose dlsym, et autre fonction sympa.

J'ai par dessus ce chargeur de module créer une petite API pour créer ses modules MyBot le plus facilement possible. Les handlers sont par exemple gérés grace à des regexp POSIX. Cela permet une flexibilitée importante dans la création de handlers!

Beacoup de choses dans MyBot sont gérées via des liste chainées. Bon la je connaissait un peu, cela fais un petit moment que je m'intéresse aux données stoquées en liste chainées.

J'ai aussi manipulé un peu plus en détails les fonctions à liste d'arguments variable, ce n'est compliqué a utiliser, mais c'est intéressant :)

Problèmes rencontrés

Les autotools ! Aie aie aie que c'est compliqué, que c'est difficile, que la doc est complexe!

Bref l'horreur, pour la peine et vu ce que j'avais a modifier pour que mon code compile sur un unix ayant un compilateur C respectant le C ansi et le C99, c'étais dommage d'utiliser les autotools. J'ai donc créer un tres petit script shell modifiant un makefile inclu par le Makefile principal, qui lui compile les sources du programme. (Le problème était surtout la gestion des librairies partagées: Il faut compiler avec l'option -ldl sous Linux, mais pas sous *BSD)

Le protocole IRC! Aie, la RFC n'est pas respectée par tous les serveurs ! Chacun rajoute son petit morceau de code permettant telle ou telle feature, ... Bien évidement, cela explosait mon bot ;) (et ça doit toujours l'exploser, je ne l'ai testé qu'avec une petite minoritée de serveurs pour le moement...).

Programmer un module

Programmer un module pour MyBot est relativement simple. Un petit exemple:

#include <stdio.h>
#include <stdlib.h>

#include "common.h"
#include "pingModule.h"

/* Demande au serveur de s'enregistrer */
myModule *init() {
  return initModule(MODULE_VERSION,  /* module version     */
                   &createHandler,   /* handlers           */
                   "pingModule",     /* module Name        */
                   MODDESC);         /* module Description */
}

handlerList *createHandler(myModule *mod)
{
  handlerHelper_t handHelper = {
    .cmd     = "^PRIVMSG.*$",
    .value   = "^ping.*$",
    .nick    = NULL,
    .cmdArg  = NULL,
    .host    = NULL,
    .user    = NULL,
  };

  return addHandlerHelper(NULL, &handHelper, "ping_pong", mod);
}

char *ping_pong(void *args) {
  ircLine_t *ircLine;
  char *retour, *target, *dest;
  modInfo_t *modInfo;

  modInfo = (modInfo_t *) args;
  ircLine = modInfo->ircLine;

  dest = getDestination(ircLine);
  target = getTargetNum(ircLine, 2);

  retour = myprintf("PRIVMSG %s :%s: pong!", dest, target);
  free(target);
  free(dest);

  return retour;
}

Et le header pingModule.h:

#ifndef PINGMODULE_H
#define PINGMODULE_H

#define MODNAME "pingModule"
#define MODULE_VERSION "0.1"
#define MODDESC "Da ping module!"

myModule *init();
handlerList *createHandler(myModule *mod);
char *ping_pong(void *args);

#endif

Passons aux explications

#include <stdio.h>
#include <stdlib.h>

#include "common.h"
#include "pingModule.h" 

On inclue les fichiers de la librarie standard, pingModule.h qui est relativement explicite, et common.h.

C'est dans l'objet common que l'on va trouver les fonctions utiles pour enregistrer notre module dans MyBot, pour créer des handlers de façon plus simple, pour utiliser quelques fonctions utiles pour manipuler en douceur le protocole IRC.

/* Demande au serveur de s'enregistrer */
myModule *init() {
  return initModule(MODULE_VERSION,  /* module version     */
                   &createHandler,   /* handlers           */
                   MODNAME,          /* module Name        */
                   MODDESC,          /* module Description */
                   "pingModule.so"); /* unused             */
}

Cette fonction est appelée par le chargeur de modules des qu'il été chargé. Elle sert a initialiser le fonctionnement du module, notament en mettant à définir: le nom du module (vérification anti collision), sa description sommaire, la version de MyBot compatible, et enfin les handlers ratachés.

handlerList *createHandler(myModule *mod)
{
  handlerHelper_t handHelper = {
    .cmd     = "^PRIVMSG.*$",
    .value   = "^ping.*$",
    .nick    = NULL,
    .cmdArg  = NULL,
    .host    = NULL,
    .user    = NULL,
  };

  /* Handler ping */
  return addHandlerHelper(NULL, &handHelper, "ping_pong", mod);
}

Cette fonction est executée par la fonction init du module. (son adresse est passée en paramètre à init)

Ce module est tres simple, on défini une structure handHelper qui sera utilisée par addHandlerHelper défini dans l'objet common. Elle sert a informer quel handler on défini. Les valeurs dans cette structure doivent être des regexp. Si on ne veut pas tenir compte d'un paramètre, il suffit de le définir a NULL.

return addHandlerHelper(NULL, &handHelper, "ping_pong", mod);

Le premier argument de addHelperHelper est ici NULL car on n'a qu'un seul handler dans ce module, on n'a donc aucunement besoin d'une référence vers la liste de handlers (retournée par cette meme fonction addHandlerHelper).

Le second paramètre est l'adresse de la structure handlerHelper permettant de créer le handler.

Le 3° paramètre est le nom du symbole à executer lorsque le handler sera reconnu.

Et enfin, le 4° paramètre est un pointeur vers notre propre module.

char *ping_pong(void *args)
{
  ircLine_t *ircLine;
  char *retour, *target, *dest;
  modInfo_t *modInfo;

  modInfo = (modInfo_t *) args;
  ircLine = modInfo->ircLine;

  dest = getDestination(ircLine);
  target = getTargetNum(ircLine, 2);

  retour = myprintf("PRIVMSG %s :%s: pong!", dest, target);
  free(target);
  free(dest);

  return retour;
}

C'est cette fonction qui sera appelée par MyBot lorsque le handler correspondra aux critères.

dest = getDestination(ircLine);

getDestionation est défini dans common, et permet de savoir a qui répondre: à un canal, ou a la personne qui a posé une question en pm au robot ?

target = getTargetNum(ircLine, 2);

Qui est la cible ? Cette fonction récupère le 2° mot envoyé par un client, ou renvoie le pseudo de la personne ayant tapé "ping".

retour = myprintf("PRIVMSG %s :%s: pong!", dest, target);

Et enfin, on envoie le message de réponse :)

Voila et un module écrit, ce n'est pas si difficile que ça n'estce pas ?

Fichier de configuration

Voici a quoi ressemble un fichier de configuration MyBot

// This is MyBot config file.
// Lines starting with // are ignored.

hostname  = 192.168.1.11            // IRC server to connect to
port      = 6667                    // Port of the IRC server.

channel   = #test
channel   = #prout
channel   = #plop

nick      = edBot

master_nick     = ed              // Nick of the master!
master_host     = 192.168.1.2     // host is part of the mask: ed!ed@HOST
master_password = plopplop        // Password for master operations
                                  // If the user is logged in, it can
                                  // take the master control of the bot
                                  // *WITHOUT* master_nick && master_host

module_path = "./mod"             // Please consider using absolute PATH
                                  // And Please do not add the final '/'

module = "lydiaModule"
module =   pingModule

Comme vous pouvez le voir, certaine sections peuvent être utilisées plusieurs fois. Pour l'instant seuls channels et module peuvent être spécifiés plusieurs fois. Le robot se connectera aux n channels défini, et chargera les n modules définis.

Si toutefois vous spécifiez 2 fois la meme valeur, seule la première valeur trouvée dans la conf aura de l'importance.

Creditz

Merci à tous ceux qui ont bien voulu m'aider à résoudre certain bugs, merci d'avoir supporté les quelques tests real life de mon bot :)

Merci à vous de vous être intéressé à ce projet sans intéret!


XHTML 1.1 strict. Design & code par Laurent Coustet (générée en 0.000145s )