________________________________________________________________ ____ __ __ / ___________________________________________________________ ____ __ _ / ` \ / \[18]/ + ,\_____ ___/ . ` |\/ , \__/ . \____ /:/=[ un affaire de bot php ] =\:\ _____/ ` x || , . " :X \_______________________________ ____ ____ / , ` ` || + , () . x , . ` ' ` \_ \ ,\\_ +\_ \ 'M ` | . || ` | ` , ` , + , ` ,--, + . \\_ \_ \ , \\_ K -+- / |+ -+- . , ` , ,',/ ,'. \_ \ . \\_ \_ \ - | ` / . | ; + + . ` | : . :`` ,_): ' ` \\_ \_ \ \\_` 1 , / ` . . x , .-=+=- `. ,. ,' ` , \_ \ + \\_` \_ \ 1 /\_____ x ' , . . X | ` `--' ` ` \\_ \_ \ , \\_ 1 + \\_ \___________________ , . . ` , + ' , , ; x ._\ \___\\___\ \ . \_ \ \__________________________________/ \ ` , \\_ /:/=[ Aka: Et wyzeman reinventa la roue...again ]=\:\ \ \__\__________________________________________ ______________________________ \` \__\ /__/ \\_ /:/=[ Wyzeman ]=\:\ _// \__\________________________/__/ Pour ceux qui sont tanner de scripter en TCL (the crappy language), dont surtout moi même, j'ai starter un dimanche matin où coder un truc useless etais une alternative tout à fait agréable plutot que découter le jour du seigneur à radiocan, un bot en php scriptable (hum hum) en php. Un bot programmer avec la philosophie que le bot en tant que telle ne devais etre qu'un frame interface entre irc et les scripts et que donc par default il ne saurait rien faire d'autre que de se connecter sur irc et dexecuter des scripts. Il existe sans doute beaucoup de facon leet de faire executer des scripts a un bot, mais personnellement étant un adepte du "mouais mais ca marche..." j'ai fait tout autrement et j'ai utilisé la méthode la plus lame et la plus simple possible, a chaque évent je fait executer lensemble des scripts scanner dans le repertoire de script par l'interpreteur php avec une convention au niveau des arguments, et store le buffer dans une variable qui est ensuite analyser par un filtre simple qui recherche quelque commande hardcoder dans le bot (cette parti pourrais meme devenir scriptables (word, des scripts analyser par des scripts!!!)). Je décrirais donc les différents modules et boutes de code significatif afin de permetre de suivre le raisonement du projet du début à la "fin". Le bot étant très réduits dans le nombre de ces fonctions officiel fonctionne avec très peu de modules (et utopiquement beaucoup de script). Les modules suivants suffise à la tache : socket, irc, script, file system. Un fichier de configuration est nescessaire au bot, les scripts tant qua eu pourrons referer a leur propre fichier de configuration, de plus qu'ils leur sera possible de faire appelle a des librairies de fonction ou a des classes d'object si désiré. Modules: 1) core 2) socket 3) irc 4) script 5) file system 0) la conf bien qu'il y ai peu de chose a dire sur la conf (une conf est une conf apres tout), mais juste la paster ici risque daider a suivre le reste du code et a meviter dla paster 5x dans l'article. bon on comprend vite le principe, non c pas multi network pour l'instant, mais c'est dans la todo liste de la v2.0. 1) core Le core du bot est relativement simple (tout à l'image de sont ensemble dailleur). il ne fait que loader les différents modules et du transfere du buffer de la connection irc au analyseur qui eux s'occuperont de lancer les scripts de facon conforme. Les seules lignes qui ne soit pas des appelles de constructeur dobjet ou des includes de fichier important, sont dans un premier temps le bout de code qui lit le repertoire des scripts afin d'en faire une liste garder dans un array, imédiatement suivie de l'inévitable loop de lecture et d'analyse du socket connecter a irc. con($conf['host'],$conf['port']); $irc = new irc($conf['ident'], $conf['realname'], $conf['nick'], $conf['chanlist']); $script = new script($conf, $sock, $link); //listing des scripts present dans /script et //garder sous un format array $wash = new wash(); $scriptlist = $wash->lsfile("scripts/"); for ($x=0;$xbuffering($link); //parsing du RFC IRC et initialisation des scripts $irc->analyse($conf['chanlist'], $buffer, $scriptlist, $script, $link, $sock); } ?> l'analyse des resultat du socket ou du status irc pourras creer un arret du loop, comme on pourras voir plus loin dans lanalyse des modules. 2) les sockets Il n'y a rien de tres compliquer a faire des sockets en php de même que puisque nous utilisons des classes, nous auront tout le loisir de les améliorer dans le future. Nous pouvons donc nous permetre (une fois de plus) d'être lazy et de créer une class socket php qui est en fait un interface d'utilisation de fopen() ce qui fera amplement l"affaire pour des connections irc ou proxy. Comme validation nous nous contenterons pour linstant de vérifié si le port est numerique et que le ip ne l'est pas. Les autres fonctions concerne l'initialisation du socket en question, sa lecture, sont ecriture et sa fermeture. Voila il n'en faut pas plus pour avoir une class socket utilisable en php pour un nombre illimiter de projet utilisant de simple socket TCP. validate($host, $port)) { return(TRUE); } else { die ("config error"); } } //verification (pour linstant tres basic des caracteres dentrer function validate($host, $port) { if (is_numeric($port) && !is_numeric($host)) { return(TRUE); } else { return(FALSE); } } //connecter un socket function con($host,$port) { $sock = fsockopen($host, $port); if (fsockopen) { return($sock); } else { return(FALSE); } } //disconnecter un socket function discon($sock) { if(fclose($sock)) { return(TRUE); } else { return(FALSE); } } //lire le socket function buffering($sock) { $buffer = fread($sock,32000); return($buffer); } //ecrire sur le socket function sending($sock, $word) { if(fwrite($sock, $word) === FALSE) { return(TRUE); } else { return(FALSE); } } } ?> ce a quoi nous pouvons quand meme faire quelque améliration, notament au niveau de la validation du ip et du port. de plus, un support de proxy pourrais etre rajouter afin de permetre a notre bot de les utilisers pour se connecter plus discretement sur irc, ou encore a nos scripts pour differentes tache (en utilisant une version modifier de cette classe socket ou directement celle ci, a moin que vous ayez votre propre classe socket). Plus concretement cest modifications pourrais etre les suivantes pour la validation, soit verifier si le port est entre 1 et 65535 (j'ai connu tres peu de serveur irc et/ou proxy avec un port negatif). De plus nous pourrions verifier si les quatres composant du ip sont numérique. Avec ca, on est a peu pret sur davoir affaire a un ip. On devrais aussi verifier si chaque portion du ip est situer entre 1 et 255. function validate($host, $port) { $ip=explode(".",$host); if ( $port >= 1 && $port <= 65535 && is_numeric($port) && !is_numeric($host) && count($ip) == 4 && is_numeric($ip[0]) && is_numeric($ip[1]) && is_numeric($ip[2]) && is_numeric($ip[3] && $ip[0] >=1 && ip[0] <=255 && $ip[1] >=1 && ip[1] <=255 && $ip[2] >=1 && ip[2] <=255 && $ip[3] >=1 && ip[3] <=255 &&)) { return(TRUE); } else { return(FALSE); } évidament vu comme ca ca peu etre platte à débuger dans 2 ans. Alors rendons ca un peu plus user friendly. function validate($host, $port) { $flag=FALSE; //transmutation du host en array $ip=explode(".",$host); //validation du port if (is_numeric($port)) { if ($port >= 1 && $port <= 65535) { $flag=TRUE; } else { $flag=FALSE; } } else { $flag=FALSE; } //validation du host //ok jsais le premier est pas mal bidon ;] if (!is_numeric($host)) { if (count($ip) == 4) { if (is_numeric($ip[0]) && is_numeric($ip[1]) && is_numeric($ip[2]) && is_numeric($ip[3])) { if($ip[0] >= 1 && $ip[0] <= 255 && $ip[1] >= 1 && $ip[1] <= 255 && $ip[2] >= 1 && $ip[2] <= 255 && $ip[3] >= 1 && $ip[3] <= 255 ) { $flag=TRUE; } else { $flag=FALSE; } } else { $flag=FALSE; } } else { $flag=FALSE; } } else { $flag=FALSE; } return($flag); } évidament ca prend bcoup plus de place :|. 3) irc il sagit ici de parser le protocole irc qui nous est pitcher par le socket ouvert sur le serveur. Pour les besoins de la demonstration seul les fonctions de base on ete parser, comme vous verrez larbre des possibles permet facilement dajouter de nouvelle fonction au parser sans entrez en conflit avec des scripts deja existant. le parser ne fait que catcher le protocole irc. Evidament une bonne connaissance du rfc irc (1459) est préférable pour bien comprendre cette partis. Pour ceux qui sont trop lazy pour taper rfc 1459 dans google, je vais ici expliquer le minimum pour etablir une connection avec un serveur irc. telnet je vous pris. telnet us.undernet.org 6667 Trying 207.172.156.252... Connected to us.undernet.org. Escape character is '^]'. NOTICE AUTH :*** Looking up your hostname NOTICE AUTH :*** Checking Ident NOTICE AUTH :*** Found your hostname ici le serveur attend quon sauthentifie user usernameici x x :realnameici nick usernickici ensuite le serveur nous envois un ping au quel nous devons repondre pour officialiser la connection. PING :109186035 PONG :109186035 ensuite vous poorrez vous debrouiller dans relativement toute les situations vivable sur irc à l'aide de 4 commande de base JOIN PART PRIVMSG MODE exemple join #hack.fr privmsg #hack.fr :ta soeur suce des choux part #hack.fr :sa suxx et pour mode ca peu ressembler a ceci si vous avez les droits necessaire mode #hack.fr +o wyzeman bon, je crois que c'est assez pour le rfc irc avant que les lecteurs qui se soit rendu jusqu'ici ne partent en courant. La technique que jai utiliser pour parser le protocole irc est tres simple, en gros je separe chaques parti du buffer recu en ligne (\n) et en mots (" ") (un espace signifie que un mots fini et que un autre commence (ca lair facile dit comme ca... pi ca l'est aussi..). Ensuite je fais analyser chaque premier mots de phrase afin de voir si il sagit d'un call irc, certaine commande irc demande une plus grande analyse. Ce besoin se retrouve surtout dans les commandes qui ne sont pas numeroté, la phrase doit donc etre étudier un peu plus dans sont ensemble pour la differencier d'une fausse commande. La position de chaque mots dans la phrase évitera de se faire arnaquer par une phrase crafter sur un chat de par exemple quelquun qui explique le protocole irc (le premier mots de chaque phrase dite sur un chan netant pas le premier mots que votre client recois (privmsg chan/nick :blablablaba) le premier mots d'une phrases dite sur irc se retrouvera donc a etre le 3eme mots envoyer via le protocole irc au client. Ainsi donc, aucun risque d'erreur d'interprétation. Un coup identifier chaque commande irc sera envoyer aux différents scripts qui soccuperont de faire le necessaire pour les traiters Exeption faite des commande necessaire a la connection qui elle sont hard coder dans le script. Chaque script etant executer et sont output sender sur le socket permetant ainsi d'interagir. Pour ce faire les scripts nont qua faire un echo ou un print des commandes a renvoyers au serveur qui seront catcher par lanalyser de script et envoyer sur le socket. Les commandes envoyer au socket par les scripts doivent respecter le protocole irc sinon elle ne feront pas grand chose ;]. ident = $this->make_ident($ident, $realname); $this->nick = $this->make_nick($nick); $this->trueserv = "dummy"; } //parsing du RFC IRC function analyse($chanlist, $buffer, $scriptlist, $script, $link, $sock) { echo $buffer; //division du buffer en string $string = explode ("\n",$buffer); for ($x=0;$xident; echo $this->nick; $sock->sending($link, $this->ident); $sock->sending($link, $this->nick); $script->spread("ident", $this->trueserv, $string[$x], $scriptlist, $link, $sock); // break; // } break; } break; } break; } break; //PING? PONG! aka still alive check case "ping": $pong = $this->make_pong($word[1]); echo $pong; $sock->sending($link, $pong); $script->spread("ping", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; //gestion des erreurs IRCD case "ERROR": switch (strtolower($word[1])) { //lien au serveur terminer case ":closing link:": $script->spread("closed link", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; } break; // motd case "001": //echo "MOTD*******************************"; $this->trueserver = ltrim($word[0],":"); $script->spread("motd", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; // end of motd case "376": //echo "EOMOTD******************************"; $this->expand($sock, $link, $chanlist); $script->spread("eomotd", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; case "PRIVMSG": $script->spread("prvmsg", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; default: break; } } } //tweaking de l'ident function make_ident($ident, $realname) { $ident = "USER ".$ident." X X :".$realname."\r\n"; return($ident); } //tweaking du nick function make_nick($nick) { $nick = "NICK ".$nick."\r\n"; return($nick); } //tweaking du pong function make_pong($ping) { $pong = "PONG ".ltrim(strtolower($ping), ":")."\r\n"; return($pong); } //mass join function expand($sock, $link, $chanlist) { for ($x=0;$xsending($link, $query); } } } ?> 4) script Il s'agit peu etre dune contradiction mais la parti la plus importante du bot, celle qui genre les scripts, est étrangement une des plus petite et simple. Sa seul fonction étant d'éxecuter l'ensemble des scripts à chaque event et d'envoyer les résultat de chaque script sur le socket. host = "dummy"; $this->chanlist = array(); $this->ident = $conf['ident']; $this->nick = $conf['nick']; } //execution des scripts et analyse des resultats function spread($event, $host, $string, $scriptlist, $link, $sock) { $host = $this->host; $ident = $this->ident; $nick = $this->nick; for ($x=0;$xsending($link,$output); } } } ?> 5) file système Le seul besoin actuel davoir une class file system dans ce bot est de lire lensemble des scripts a éxecuter. Les dits script pourrons reutiliser cette classe pour leur besoin ou en inventer une autre plus adapter. voila, donc comme vous lavez vus a travers cette article, faire un bot "complet" en php est une chose facile qui peu mener à un système éfficace et puissant. Pour ceux qui voudrais tester le bot sans se taper l'article, j'inclus ici à la fin l'ensemble des classes qui compose le bot. si quelqu'un à envie de continuer le devellopement de se bot, envoyer moi un message a wyzeman@mindkind.org. wyzebot,php begin #!/usr/bin/php con($conf['host'],$conf['port']); $irc = new irc($conf['ident'], $conf['realname'], $conf['nick'], $conf['chanlist']); $script = new script($conf, $sock, $link); $wash = new wash(); $scriptlist = $wash->lsfile("scripts/"); for ($x=0;$xbuffering($link); //parsing du RFC IRC et initialisation des scripts $irc->analyse($conf['chanlist'], $buffer, $scriptlist, $script, $link, $sock); } ?> wyzebot.php stop conf/general.conf.php begin conf/general.conf.php stop core/irc.php start ident = $this->make_ident($ident, $realname); $this->nick = $this->make_nick($nick); $this->trueserv = "dummy"; } //parsing du RFC IRC function analyse($chanlist, $buffer, $scriptlist, $script, $link, $sock) { echo $buffer; //division du buffer en string $string = explode ("\n",$buffer); for ($x=0;$xident; echo $this->nick; $sock->sending($link, $this->ident); $sock->sending($link, $this->nick); $script->spread("ident", $this->trueserv, $string[$x], $scriptlist, $link, $sock); // break; // } break; } break; } break; } break; //PING? PONG! aka still alive check case "ping": $pong = $this->make_pong($word[1]); echo $pong; $sock->sending($link, $pong); $script->spread("ping", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; //gestion des erreurs IRCD case "ERROR": switch (strtolower($word[1])) { //lien au serveur terminer case ":closing link:": $script->spread("closed link", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; } break; // motd case "001": //echo "MOTD*******************************"; $this->trueserver = ltrim($word[0],":"); $script->spread("motd", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; // end of motd case "376": //echo "EOMOTD******************************"; $this->expand($sock, $link, $chanlist); $script->spread("eomotd", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; case "PRIVMSG": $script->spread("prvmsg", $this->trueserv, $string[$x], $scriptlist, $link, $sock); break; default: break; } } } //tweaking de l'ident function make_ident($ident, $realname) { $ident = "USER ".$ident." X X :".$realname."\r\n"; return($ident); } //tweaking du nick function make_nick($nick) { $nick = "NICK ".$nick."\r\n"; return($nick); } //tweaking du pong function make_pong($ping) { $pong = "PONG ".ltrim(strtolower($ping), ":")."\r\n"; return($pong); } //mass join function expand($sock, $link, $chanlist) { for ($x=0;$xsending($link, $query); } } } ?> core/irc.php stop core/script.php start host = "dummy"; $this->chanlist = array(); $this->ident = $conf['ident']; $this->nick = $conf['nick']; } //execution des scripts et analyse des resultats function spread($event, $host, $string, $scriptlist, $link, $sock) { $host = $this->host; $ident = $this->ident; $nick = $this->nick; for ($x=0;$xsending($link, $output); } } } ?> core/script.php stop core/socket.php start validate($host, $port)) { return(TRUE); } else { die ("config error"); } } //verification (pour linstant tres basic des caracteres dentrer function validate($host, $port) { if (is_numeric($port) || !is_numeric($host)) { return(TRUE); } else { return(FALSE); } } //connecter un socket function con($host,$port) { $sock = fsockopen($host, $port); if (fsockopen) { return($sock); } else { return(FALSE); } } //disconnecter un socket function discon($sock) { if(fclose($sock)) { return(TRUE); } else { return(FALSE); } } //lire le socket function buffering($sock) { $buffer = fread($sock,32000); return($buffer); } //ecrire sur le socket function sending($sock, $word) { if(fwrite($sock, $word) === FALSE) { return(TRUE); } else { return(FALSE); } } } ?> core/socket.php stop core/wash.php start core/wash.php stop