Linuxico BOT 1.0

por Javier Turégano Molina (Setas)

En este articulo se muestra cómo se creó Linuxico Bot 1.0. Se trata de un bot de IRC programado en C con acceso a base de datos MySQL.

 

 

- ARTICULOS -
Linuxico BOT 1.0
Autor: Javier Turégano Molina
(Setas)
01-07-2002

0. Indice  
 

0- Indice

1.- Introducción

2.- Esquema general de la aplicación

3.- La interfaz de los sockets

4.- Conexión a la base de datos MySQL

5.- Comandos de IRC utilizados

6.- Código fuente

7.- Conclusiones


1. Introducción  
 

Linuxico Bot v1.0 es un bot de irc programado en C con acceso a base de datos MySQL.

Pero, ¿qué es un bot de irc? Un bot de irc es un programa automatizado que se conecta a un servidor de charla y genera respuestas y mensajes automáticos a los usuarios.

Ahora que ya sabemos que es un bot de IRC vamos a ver que hace exactamente este en cuestión. Linuxico es un bot programado para introducirse en un canal y dar soporte a los usuarios a través de una serie de comandos predefinidos. Así los usuarios envian comandos al bot y este les responde con la información almacenada en su base de datos.

¿Qué servicios nos ofrece esta versión de Linuxico Bot?
- Saludo automático a la entrada del Bot.
- Saludo automático a los usuarios que entren en el canal.
- Sistema de noticias.

Gracias a la versatilidad que nos da el uso de la base de datos MySQL convinado con la potencia del lenguaje C en entornos Linux la ampliación de funcionalidades del BOT a otros servicios es bastante sencilla y ofrece miles de posibilidades (mensajes comerciales, charla automática, control de un servidor de irc, mensajes entre usuarios offline, servidor de ficheros de DCC, etc.).

Al iniciar el bot este se conecta a su servidor de IRC predeterminado y accede al canal #linuxico, allí dará soporte de noticias a todos los usuarios que entren o que le envíen un mensaje privado.

Pero pasemos a ver como está programado nuestro BOT


2.Esquema general de la aplicación  
 

- ESQUEMA -

El esquema que sigue el BOT es el siguiente:

INICIO

A - Se abre conexión con la base de datos de contenidos. (MYSQL)

B - Se establece la conexión con el servidor de IRC predeterminado (Sockets).

C -Se utiliza el protocolo de IRC para establecer la sesión.

BUCLE PRINCIPAL

D -Se espera recibir mensajes del servidor y de parte de los usuarios.

    • Caso del MOTD (Mensaje del día)

    • Caso de PING

    • Caso de mensaje de usuario.

      1. Mensaje no reconocido.

      2. AYUDA

      3. NOTICIAS

      4. NOTICIANUEVA

    • Caso de JOIN a nuestro canal.

FIN BUCLE PRINCIPAL

E - Cierre de conexiones y finalización de la aplicación.

FIN


- VISION DETALLADA -

Vamos a detallar cada uno de los pasos del esquema anterior.

A - Se abre conexión con la base de datos de contenidos. (MYSQL)

Inicializamos y realizamos la conexión con la base de datos de contenidos MYSQL. En este caso la base de datos se encuentra ubicada en el mismo computador, luego usaremos la dirección de bucle local 127.0.0.1 en el puerto de MySQL, el 3306.

Se ha creado la base de datos linuxico en el gestor MySQL y también un usuario con derechos para acceder a ella, linuxico cuya clave es GNU.

 

B - Se establece la conexión con el servidor de IRC predeterminado (Sockets).

Vamos a establecer una conexión con el servidor irc.debian.org (de la red openprojets). Para ello utilizaremos la interfaz de los sockets linux. Una vez configurada y realizada la conexión con el servidor (puerto de IRC: 6667) usaremos las funciones envia y receive creadas para enviar y recibir datos a través del socket conectado.

C Se utiliza el protocolo de IRC para establecer la sesión

Una vez que ya hemos realizado la conexión el bot usará el protocolo de IRC para negociar parámetros como el NICK, el HOST, etc e iniciar una sesión de IRC como linuxico en ese servidor.

-- BUCLE PRINCIPAL --

D -Se espera recibir mensajes del servidor y de parte de los usuarios.

En el bucle principal el bot se dedica a esperar mensajes por parte del servidor y de los usuarios y a procesarlos. Aquí es donde están implementados los servicios del BOT y donde podríamos añadir otros nuevos.

    • Caso del MOTD (Mensaje del día)

Al realizar la conexión al servidor de IRC recibiremos el mensaje del día definido para el mismo. Una vez recibido identificaremos nuestro HOST y entraremos al canal #linuxico. Una vez allí enviaremos el mensaje de entrada del bot y quedaremos a la espera de otros mensajes.

    • Caso de PING

Si el servidor de IRC nos envía un mensaje de PING el bot ha de contestar con PONG para indicarle que sigue a la escucha en casos de inactividad.

    • Caso de mensaje de usuario.

Si recibimos un mensaje privado dirigido hacia nosotros lo procesaremos sintácticamente y clasificaremos así:

      1. Comando no reconocido.

No existe ese comando así que le indicamos la AYUDA.

      1. AYUDA

Indica los comandos que podemos utilizar.

      1. NOTICIAS

Nos muestra las últimas noticias.

      1. NOTICIANUEVA blalbablalblalbla

Nos permite insertar nuevas noticias en la base de datos.


    • Caso de JOIN a nuestro canal.

Si algún usuario entra en nuestro canal lo saludaremos para indicarle que el bot está activo y que puede realizarle consultas.

--- FIN BUCLE PRINCIPAL ---


E - Cierre de conexiones y finalización de la aplicación.

Por último cerramos la conexión con la base de datos y con el servidor de IRC.

 

 

 


3. La interfaz de los sockets  
  Para establecer la conexión con el servidor de IRC hemos utilizado la interfaz de los sockets. También podriamos habernos ido a una implmentación de nivel superior como las librerias de IRC, etc.

Primero intentamos obtener la dirección IP del servidor de IRC irc.debian.org mediante gethostbyname.

if ((dir_servidor = gethostbyname(nombre_servidor)) == 0)
{
perror("Error al resolver la direccion del servidor.");
exit(1);
}

A continuación creamos la estructura necesaria para la creación y conexión del socket y las realizamos.

bzero(&pin,sizeof(pin));
bzero(buffer,sizeof(buffer));
bzero(buffer2,sizeof(buffer2));
bzero(recive_buff,sizeof(recive_buff));

pin.sin_family = AF_INET;
pin.sin_addr.s_addr = ((struct in_addr *) (dir_servidor->h_addr))->s_addr;
pin.sin_port = htons(port);

if ((s = socket(AF_INET, SOCK_STREAM,0)) == -1)
{
perror("Error al abrir el socket.");
exit(1);
}

if (connect(s, (void *)&pin, sizeof(pin)) == -1)
{
perror("Error de conexion con el socket.");
exit(1);
}

Para el envio y la recepción de mensajes a través del socket vamos autilizar estas dos funciones auxiliares:

Receive : Leerá del socket hasta que llegue al retorno de carro: \n

Envia : Envia un mensaje a través del socket.

void receive(int sock, char *recive_buff)
{
int i=0;
char rechar[2];
while (rechar[0]!= '\n')
{
if (read(sock, rechar, 1) == -1)
return;
strcpy (recive_buff + i, rechar);
recive_buff[i + 1] = 0;
i++;
}

}

void envia(int sock,char *envia_buff)
{
write(sock,envia_buff,strlen(envia_buff));
printf("-> %s\n",envia_buff);
}

En ambas funciones hemos utilizado la forma de acceder a los sockets como si fuera un fichero (read y write) de linux que nos dan gran versatilidad.

Al finalizar cerramos el socket s.

close(s);


4. Conexión a la base de datos MySQL  
  Para acceder a la base de datos MySQL vamos a utilizar la librería mysqlclient proporcionada dentro del paquete de MySQL.

Para ello la incluimos en la cabecera del código:

#include "/usr/local/mysql/include/mysql.h"


Y la lincamos al compilar con gcc junto con otras necesarias:

gcc -o linuxico linuxico.c -lmysqlclient -L/usr/local/mysql/lib -lnsl -lz

Aquí presentamos las variables auxiliares utilizadas y el proceso de conexión a la base de datos.


MYSQL bdd; // Identificador de la conexión a la base de datos.
MYSQL_RES *resultado; // Variable para almacenar resultados.
MYSQL_ROW fila; // Variable para recorrer tablas de resultado.

if (!mysql_init(&bdd))
{
perror("Error en la inicializacion de la base de datos");
exit(-1);
}

printf("2\n");

if (!mysql_real_connect(&bdd,"127.0.0.1","linuxico","GNU","linuxico",3306,NULL,0))
{
perror("ERROR en la conexion a la base de datos");
mysql_close(&bdd);
exit(-1);
}

mysql_init-> inicializa un descriptor de conexión de la base de datos.
mysql_real_connect -> crea una conexión identificada por el descriptor bdd al gestor ubicado en 127.0.0.1 pidiendo acceso como usuario linuxico y clave GNU a la base de datos linuxico en el puerto 3306.



Las consultas a la base de datos las realizaremos de la siguiente manera: realizamos una consulta SQL y almacenamos el resultado en la variable resultado, luego la vamos recorriendo mientras haya filas restantes y almacenando el valor en fila[0].


if (mysql_query(&bdd,"SELECT contenido FROM noticias"))
{
perror("ERROR en la consulta de noticias.");
mysql_close(&bdd);
exit(-1);
}

resultado = mysql_store_result(&bdd);

for (i=0;i < mysql_num_rows(resultado);i++)
{
bzero(aux,sizeof(aux));
fila = mysql_fetch_row(resultado);
sprintf(aux,"PRIVMSG %s : -> %s\n",nick,fila[0]);
envia(s,aux);
}

mysql_query -> Realiza una consulta SQL.

mysql_store_result -> Guardamos el resultado de la consulta.

mysql_num_rows -> Número de filas de la tabla resultante de la consulta.

mysql_fetch_row -> Obtiene una fila de la tabla.


5. Comandos de IRC utilizados  
  Para realizar las comunicaciones con el servidor mediante el protocolo de IRC se han utilizado los siguientes comandos.

NICK linuxico -> Indicamos cual es nuestro nick.

USER setas 62.42.216.243 irc.openprojects.net : me mola la gramola
-> Indicamos nuestro usuario, IP, red de IRC y mensaje de ID.

USERHOST linuxico -> Nombre del host desde el que nos conectamos.

JOIN #linuxcio -> Entrar en el canal linuxico.

PRIVMSG pepito : Bienvenido. -> Envia un mensaje a pepito de bienvenida.
PRIVMSG #linuxico : Bot activo -> Envía un mensaje general al canal linuxico.

PING -> Enviado por el servidor para controlar clientes inactivos.
PONG -> Contestado por el cliente para denotar que sigue activo.


6. Codigo fuente  
 

Este es el código fuente de la versión 1.0 del bot linuxico.

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include "/usr/local/mysql/include/mysql.h"

char recive_buff[700],envia_buff[700],nick[10],aux[50],msg[500],*aux2,consulta[150];
MYSQL bdd;
MYSQL_RES *resultado;
MYSQL_ROW fila;


void receive(int sock, char *recive_buff)
{
int i=0;
char rechar[2];
while (rechar[0]!= '\n')
{
if (read(sock, rechar, 1) == -1)
return;
strcpy (recive_buff + i, rechar);
recive_buff[i + 1] = 0;
i++;
}

}

void envia(int sock,char *envia_buff)
{
write(sock,envia_buff,strlen(envia_buff));
printf("-> %s\n",envia_buff);
}

int main(int argc, char *argv[])
{
char * nombre_servidor = "irc.debian.org";
//char * nombre_servidor = "nebula.irc-hispano.org";
int port = 6667;
struct hostent *dir_servidor;
struct sockaddr_in pin;
int s,espera,j,i;
char buffer[700],buffer2[700],buffer3[700];

// CONEXION A LA BASE DE DATOS DE CONTENIDOS

if (!mysql_init(&bdd))
{
perror("Error en la inicializacion de la base de datos");
exit(-1);
}

printf("2\n");

if (!mysql_real_connect(&bdd,"127.0.0.1","linuxico","GNU","linuxico",3306,NULL,0))
{
perror("ERROR en la conexion a la base de datos");
mysql_close(&bdd);
exit(-1);
}


// CONEXION CON EL SERVIDOR DE IRC

if ((dir_servidor = gethostbyname(nombre_servidor)) == 0)
{
perror("Error al resolver la direccion del servidor.");
exit(1);
}

bzero(&pin,sizeof(pin));
bzero(buffer,sizeof(buffer));
bzero(buffer2,sizeof(buffer2));
bzero(recive_buff,sizeof(recive_buff));


pin.sin_family = AF_INET;
pin.sin_addr.s_addr = ((struct in_addr *) (dir_servidor->h_addr))->s_addr;
pin.sin_port = htons(port);

if ((s = socket(AF_INET, SOCK_STREAM,0)) == -1)
{
perror("Error al abrir el socket.");
exit(1);
}

if (connect(s, (void *)&pin, sizeof(pin)) == -1)
{
perror("Error de conexion con el socket.");
exit(1);
}


// Enviamos el usuario

envia(s,"USER setas 62.42.216.243 irc.openprojects.net : me mola la gramola\n");


// Enviamos el nick
envia(s,"NICK linuxico\n");


// ------- BUCLE PRINCIPAL -------------
espera = 1;
while (espera)
{

receive(s,buffer3);
printf("%s",buffer3);

// Recibido el MOTD
if (strstr(buffer3,"MOTD"))
{

//Enviamos el HOST
envia(s,"USERHOST linuxico\n");

receive(s,buffer3);
printf("%s",buffer3);
bzero(buffer3,sizeof(buffer3));

// Entramos en nuestro canal
envia(s,"JOIN #linuxico\n");

receive(s,buffer3);
printf("%s",buffer3);
bzero(buffer3,sizeof(buffer3));

envia(s,"PRIVMSG #linuxico :Linuxico BOT activado.\n");

}

// Recibido PING
if (strstr(buffer3,"PING"))
{
bzero(msg,sizeof(nick));
bzero(aux,sizeof(aux));
j=0;

while(buffer3[j] != ':') j++;
memcpy(msg,&buffer3[j],strlen(buffer3)-j-1);
sprintf(aux,"PONG %s\n",msg);
printf("---------- %s",aux);
envia(s,aux);
}

// Recibido mensaje
if (strstr(buffer3,"PRIVMSG"))
{
aux2 = strstr(buffer3,"PRIVMSG");
if (aux2[8] != '#')
{
j=0;
bzero(nick,sizeof(nick));
bzero(msg,sizeof(nick));
bzero(aux,sizeof(aux));

while(buffer3[j] != '!') j++;
memcpy(nick,&buffer3[1],j-1);


j=1;
while(buffer3[j] != ':') j++;
memcpy(msg,&buffer3[j],strlen(buffer3)-j-1);

if (strstr(msg,"AYUDA")) //Recibido AYUDA
{
sprintf(aux,"PRIVMSG %s :<- Linuxico BOT v 1.0 ->\n",nick);
envia(s,aux);
sprintf(aux," \n",nick);
sprintf(aux,"PRIVMSG %s :Comandos disponibles:\n",nick);
envia(s,aux);
sprintf(aux,"PRIVMSG %s :AYUDA Muestra esta ayuda\n",nick);
envia(s,aux);
sprintf(aux,"PRIVMSG %s :NOTICIAS Listado de las últimas noticias.\n",nick);
envia(s,aux);
sprintf(aux,"PRIVMSG %s :NOTICIANUEVA Insertar nueva notiica.\n;",nick);
envia(s,aux);
}

else if (strstr(msg,"NOTICIAS")) // Noticias
{
bzero(aux,sizeof(aux));
sprintf(aux,"PRIVMSG %s :... Ultimas noticias ...\n",nick);
envia(s,aux);


if (mysql_query(&bdd,"SELECT contenido FROM noticias"))
{
perror("ERROR en la consulta de noticias.");
mysql_close(&bdd);
exit(-1);
}

resultado = mysql_store_result(&bdd);

for (i=0;i < mysql_num_rows(resultado);i++)
{
bzero(aux,sizeof(aux));
fila = mysql_fetch_row(resultado);
sprintf(aux,"PRIVMSG %s : -> %s\n",nick,fila[0]);
envia(s,aux);
}

}
else if (strstr(msg,"NOTICIANUEVA")) // Insertar Noticias
{
bzero(aux,sizeof(aux));

aux2 = strstr(msg,"NOTICIANUEVA");
aux2 = &aux2[13];

sprintf(consulta,"INSERT INTO noticias(contenido) VALUES('%s')",aux2);
printf("%s\n",consulta);
if (mysql_query(&bdd,consulta))
{
perror("ERROR en la insercion de noticias.");
mysql_close(&bdd);
exit(-1);
}
sprintf(aux,"PRIVMSG %s :Noticia archivada. Gracias.\n",nick);
envia(s,aux);
}

else //Comando no reconocido
{
bzero(aux,sizeof(aux));
sprintf(aux,"PRIVMSG %s :Comando no reconocido.\n",nick);
envia(s,aux);
sprintf(aux, "PRIVMSG %s :Para listar comandos usa AYUDA.\n",nick);
envia(s,aux);
}
}

}// Fin de comprobacion de mensaje privado y no al canal.

Join al canal
if (strstr(buffer3,"JOIN"))
{
j=0;
bzero(nick,sizeof(nick));
bzero(aux,sizeof(aux));

while(buffer3[j] != '!') j++;
memcpy(nick,&buffer3[1],j-1);
sprintf(aux,"PRIVMSG %s :Linuxico te da la bienvenida al canal, %s.\n",nick,nick);
envia(s,aux);
}


bzero(buffer3,sizeof(buffer3));

}



close(s);
mysql_close(&bdd);
exit(0);
}

7. Conclusiones  
  Con este articulo sobre la creacion de un BOT de IRC fundamentalmente con motivos educativos intento dar una pequeña vision de las posibilidades que se obtienen mezclando piezas sencillas como son la programacion en C, la potencia que nos da un sistemas de base de datos como MySQL y el protocolo de charla IRC. Espero que os sea de utilidad.

El artículo como en otras ocasiones se libera bajo licencia "copyleft". Se utiliza este tipo de licencia en alas de la libre circulación del mismo y el máximo aprovechamiento por parte de la comunidad de software libre, espero que os sea útil.

Para cualquier duda o comentario podeis contactar conmigo enviando un e-mail a:

javi (arroba) ideando.net