Member of The Internet Defense League Últimos cambios
Últimos Cambios
Blog personal: El hilo del laberinto Geocaching

El Argobot y sus Plug-Ins

Última Actualización: 26 de Febrero de 1.998 - Jueves

From: hostmaster@conecta.es
Message-Id: <199802181045.LAA17266@tyr.conecta.es>
To: ircops@esnet.org
Date: Wed, 18 Feb 1998 11:44:42 +0000
Subject: [IRCOPS-ESNET] Spec de interface contra Argobot

Ahi va ....
Adjunto doumento sobre posible interface para plugins de Argobot


Esto es una version pre-alpha (borrador caduca en 24 horas) de un inteface de comunicacion entre el argobot y un posible sistema de plugins para este

Se pretende establecer la manera en que un programa externo pide y recibe datos mientras el argobot esta en funcionamiento. Este interface esta sujeto a revison permanentemente mientras no se especifique lo contarrio.

En principio se define que Argobot leera su stdin para obteber las petciones y devolvera los resultados por stdout.

Para permitir el uso de varios plugins a la vez cada uno de ellos debera poder ser identifcado de manera unica

Para ello que el primer commando de cada instancia solicite al argobot un identificador que sera uncio para esa session ( cuya duracion no se define en este borrador)y que debera utilizarse en TODAS las parejas peticion/respuesta

El argobot por su parte debera mantener un lista de sesiones activas y utilizar el codigo de session adecuado para cada respuesta que envie y no permitir ningun peticion sin identificador de sesion (excepto el de peticion de session).

En principio se definen los siguientes comandos:

GETID : solicitud de identificador
El argobot debera devolver un identificador unico como unico elemento de la respuesta, con CR al final
VAR []: solicita/establece el valor de una variable interna
(por definir)
IRC : ejecuta un commando irc ( se aplican todas las limitaciones de modo, ircop,server.etc)
DUMP: vuelca a una tabla de valores a la salida
CFGVAR: igual que VAR pero para variables de configuracion
RESTART: re -ejcuta el argobot, releyendo el fichero de configuracion
QUIT: Fuerza al argobot a terminar

CODIGOS de ERROR
(por definir)

FORMATOS

  • El separador por defecto es el espacio.
  • Para incluir espacios en los parametros de un comando se deben incluir las doblew comillas

El primer parametro de cada comando es el id de session (valor devuelto por GETID)

El primer parametro de cada respuesta debe ser el id de session

El segundo parametro de cada respuesta debe ser 0(cero, OK) o el codigo de error

VARIOS

La comunicacion es sincrona y bloqueada: es decir si la solicitud es

<session id> IRC "/ping <lista de users>"

Argobot no debe responder hasta que tenga respuesta de todos o (si asi se estableciera) hasta que pase un determinado timeout:

<sesion id> pepe a!b@c.d.com 879
<sesion id> pepa r!b@c.d.com 467
.
.
.

SUGERENCIAS

Se requieren sugerencias sobre:

  • que variables se pueden pedir?
  • que variables se pueden modificar
  • que hacer con las respuestas de multiples lineas ?
  • como definir timeouts?
  • connotaciones de seguridad ?
  • Vale la pena implementar estructuras del tipo ?

    <id session> BEGIN
    <id session> commando
    <id session> commando
    <id session> commando
    <id session> commando
    <id session> END

  • Cualquier cosa que creais interesante


Message-ID: <34EAE78B.D2E19BFC@argo.es>
Date: Wed, 18 Feb 1998 12:52:11 -0100
From: "Jesús Cea Avión" <jcea@argo.es>
Organization: Argo Redes y Servicios Telematicos, S.A.
To: ircops@esnet.org
Subject: Re: [IRCOPS-ESNET] Spec de interface contra Argobot
References: <199802181045.LAA17266@tyr.conecta.es>

Tal y como Manolete sabe, Argobot permite usar plug-ins pasivos "de forma natural". Por plug-in pasivo entiendo aquel plug-in cuya única comunicación con el Argobot consiste en leer su salida. Se puede lograr cierto grado de interactividad (entre comillas) utilizando los comandos "timer" para ejecutar operaciones periódicas. Así es como se obtienen las estadísticas ESNET, en este momento.

En casos extremos el plug-in puede modificar el fichero de configuración del bot y forzar su relectura y ejecución mediante un KILL -HUP. Esta operación es muy costosa y su latencia es elevada, debido al control de flujo que imponen los servidores IRC (que se corregirá en un parche mío futuro :).

Es interesante, eso sí, definir una interfaz bidireccional para posibilitar plug-ins genéricos. De esta forma, por ejemplo, Fool puede hacer el tema del ASCII PUSH sin tener que preocuparse de desarrollar un bot completo. ¡¡Esto te interesa, Fool!! :).

> En principio se define que Argobot leera su stdin para obteber las
> petciones y devolvera los resultados por stdout.

El problema de tomar los resultados de la STDIN es que, normalmente, sólo puede haber un proceso escribiendo en la PIPE. Se pueden colgar más, pero corres el riesgo de que los diferentes plug-ins intermezclen sus comandos a trocitos lo que, obviamente, no funcionaría.

Mi idea es simple:

  1. El argobot se ejecuta y lee su fichero de configuración
  2. Por cada comando "plugin <programa> [parametros]" que encuentre, lanza un proceso independiente, encadenando sus stdin/out al argobot.
  3. Cada cosa que el argobot reciba del servidor, aparte de enviarse a la stdin (lo que se hace ahora), se manda también a todas las stdin de los plug-ins.
  4. El argobot lee los stdout de los plug-ins e interpreta lo que encuentre allí.
  5. Si un plug-in "muere" por la razón que sea, se graba acta en el log y nos olvidamos de él.
  6. Si el argobot recibe un KILL -HUP, mata todos los plug-ins, relee su fichero de config y vuelve al punto B.

Para los plug-ins se exige:

  1. Ser capaces de interpretar la parte del protocolo IRC que les corresponde (la parte que necesitan para funcionar).
  2. Morir si se les cierra la stdin o la stdout.
  3. Leer la stdin de forma rápida, ya que el argobot se bloquea si no es así.

> Para permitir el uso de varios plugins a la vez
> cada uno de ellos debera poder ser identifcado de manera unica

No es necesario, porque cada uno de ellos tiene una STDIN/OUT independiente.

> En principio se definen los siguientes comandos:

Vayamos por partes... primero veamos el mecanismo de intercomunicación y luego las funciones que se necesitan :).

Lo más sencillo es que el argobot se comunique con los plug-ins directamente usando el protocolo IRC, ampliándola un poco para acomodar cosas como el acceso a las variables internas.

> La comunicacion es sincrona y bloqueada: es decir si la solicitud es

El protocolo IRC es asíncrono. Menos mal, sino en situaciones de lag la cosa sería todavía peor de lo que es ahora :) para un cliente, y un servidor ni siquiera funcionaría. No hay más remedio que vivir este hecho.

Afortunadamente los diseñadores del protocolo IRC eran conscientes del problema y dotaron al protocolo de cierto "control de estado" mediante la numeración en las respuestas y la inclusión de respuestas especiales tipo "fin de lista", "fin de who", "fin de ban"...

Un escenario puede ser el siguiente:

Un plug-in manda comandos "/who" cada minuto, por ejemplo, y sólo interpreta las respuestas WHO, nada más. Ignora todo lo demás. Como es posible que otros plug-ins o, incluso, el propio argobot genere peticiones "/who", y no sabemos para quien es cada respuesta, y pueden ser distintas debido a diferentes parámetros, el bot debe:

  1. Contabilizar cada respuesta who.
  2. Cuando se recibe el "fin de who", hacer lo que haga falta con las respuestas recibidas (que pueden no ser para nosotros) y borrar la lista de respuestas para esperar al siguiente who.

Otra posibilidad es que el plug-in "espíe" los comandos que envíen el resto de plug-ins/Argobot, y lleve la cuenta. Ello es un poco problemático porque no se garantiza que las respuestas se reciban en el mismo orden en que se mandan los comandos, ya que pueden ir dirigidos a servidores diferentes. Sólo se garantiza el orden dentro de los comandos enviados a cada servidor en particular (recordemos que aunque estamos conectados con un nodo dado, podemos mandar comandos a otros).

Antes de definir qué comandos acepta el argobot, hay que acordar cosas como ésta.

Otro tema: aquí he hablado de plug-ins "totales", que leen todo lo que el argobot lee. Absolutamente todo. Eso incluye los comandos que se le mandan por privado, las conversaciones de la gente en el canal (a menos que el bot tenga el modo "d" silencioso), etc. Puede definirse un plug-in que "anclado" en un canal y que sólo vea los eventos relativos al mismo, como entradas y salidas del canal, conversaciones y cambios de modo. Otro punto a decidir. ¿Cómo lo veis?.

Personalmente me parece innecesario, ya que puede hacerse eso mismo sin m´s que filtrar en el plug-in todo lo que no tenga que ver con el canal. Lo único sería la carga de CPU, pero dudo mucho que eso sea un problema, por mucho tráfico que haya.

Otra cosa a decidir.

Lo que sigue está subordinado a cómo se defina lo que he escrito hasta ahora:

> .- que variables se pueden pedir?

En principio, las que se definen en el fichero de configuración. De todas formas hay que pensar si vale la pena. Si un plug-in requiere autentificación, por ejemplo, esos datos deben ir en el fichero de configuración del plug-in, no del bot.

> .- que variables se pueden modificar

Pues las mismas que se pueden leer :). El problema es que el argobot no puede, ahora, modificar variables aisladas. Todo lo que puede hacer es releerlas todas de nuevo. Eso se puede modificar, aunque es complejo (de ahí todo el tema de "recogida de basuras" de Olimpo, ya que Olimpo está modificando datos aislados todo el tiempo). Hay que pensar qué variables se necesitaría modificar. En este momento no veo esa necesidad, de todas formas.

> .- que hacer con las respuestas de multiples lineas ?

Eso se lo tendrá que currar cada plug-in :)

> .- como definir timeouts?

Comandos "signal" en los plug-ins. No tiene nada que ver con el argobot.

> .- connotaciones de seguridad ?

Si el argobot envía todo (incluídas las claves privadas que le manda la gente) lo que recibe a los plug-ins... imagina.

La muerte de un plug-in por "problemas" no nos causa ninguno porque el argobot ni se inmuta. Sólo le desaparecerá esa función.

El caso más problemático es lo que mandan los plug-ins a la red. No veo tampoco implicaciones de seguridad (salvo que nos pongan un plug-in malicioso, lo que ya es responsabilidad de cada administrador).

> .- Vale la pena implementar estructuras del tipo ?

Simplemente no se pueden hacer. El protocolo es asíncrono.

> .- Cualquier cosa que creais interesante

Hay que ver formas de intercomunicar los plug-ins a través del argobot. Como todos los plug-ins ven todo, lo más fácil es añadir un comando "echo" que haga que el argobot mande a todos los plug-ins (salvo al que manda el comando :) un texto. Para comunicaciones uno a uno basta con que el argobot asigne a cada plug-in un nombre al azar. Puede ser el primer texto. El comando puede ser:

ECHO * <- Manda a todos los plug-ins, menos al que manda el comando.
ECHO Nombre <- Manda a ese plug-in.

El remitente puede ir en el propio mensaje ECHO, si éste requiere respuesta.

Una vez acordados estos detalles, al menos de forma provisional, podemos ver los comandos que se pueden implementar y un protocolo IPC más depurado.

-- 
Jesus Cea Avion                         _/_/      _/_/_/        _/_/_/
jcea@argo.es http://www.argo.es/~jcea/ _/_/    _/_/  _/_/    _/_/  _/_/
                                      _/_/    _/_/          _/_/_/_/_/
PGP Key Available at KeyServ   _/_/  _/_/    _/_/          _/_/  _/_/
"Things are not so easy"      _/_/  _/_/    _/_/  _/_/    _/_/  _/_/
"My name is Dump, Core Dump"   _/_/_/        _/_/_/      _/_/  _/_/
"El amor es poner tu felicidad en la felicidad de otro" - Leibnitz


From: hostmaster@conecta.es
Message-ID: <199802181222.NAA19404@tyr.conecta.es>
To: ircops@esnet.org
Date: Wed, 18 Feb 1998 13:21:29 +0000
Subject: Re: [IRCOPS-ESNET] Spec de interface contra Argobot
In-reply-to: <34EAE78B.D2E19BFC@argo.es>

> > En principio se define que Argobot leera su stdin para obteber las
> > petciones y devolvera los resultados por stdout.
>
> El problema de tomar los resultados de la STDIN es que, normalmente,
> sólo puede haber un proceso escribiendo en la PIPE. Se pueden colgar
> más, pero corres el riesgo de que los diferentes plug-ins intermezclen
> sus comandos a trocitos lo que, obviamente, no funcionaría.

vale

> Mi idea es simple:
>
> a) El argobot se ejecuta y lee su fichero de configuración
> b) Por cada comando "plugin [parametros]" que encuentre,
> lanza un proceso independiente, encadenando sus stdin/out al Argobot.
> c) Cada cosa que el argobot reciba del servidor, aparte de enviarse
> a la stdin (lo que se hace ahora), se manda también a todas las
> stdin de los plug-ins.
> d) El argobot lee los stdout de los plug-ins e interpreta lo que
> encuentre allí.
> e) Si un plug-in "muere" por la razón que sea, se graba acta en el
> log y nos olvidamos de él.
> f) Si el argobot recibe un KILL -HUP, mata todos los plug-ins, relee
> su fichero de config y vuelve al punto B.
>
> Para los plug-ins se exige:
>
> a) Ser capaces de interpretar la parte del protocolo IRC que les
> corresponde (la parte que necesitan para funcionar).
>
> b) Morir si se les cierra la stdin o la stdout.
>
> c) Lee la stdin de forma rápida, ya que el argobot se bloquea si no es
> así.

Y a eso le llamas simple ????

A ver, detalles

  1. Creo que seria conveniente que los plugins fueran MUCHO mas sencillos vamos, que no haya que soñar con el protocolo o saberse de memoria el rfc para pedir la lista de usuarios

  2. Creo que es logico trasladar la complicacion de gestion de sesiones concurrentes de plugins al argobot puesto que una vez hecho, este es unico, mientras que los plugins pueden ser muy variados y SE HAN DE hacer cada vez

> > Para permitir el uso de varios plugins a la vez
> > cada uno de ellos debera poder ser identifcado de manera unica
> No es necesario, porque cada uno de ellos tiene una STDIN/OUT
> independiente.

Depende de la estrategia que utilizes

> > En principio se definen los siguientes comandos:
>
> Vayamos por partes... primero veamos el mecanismo de intercomunicación y
> luego las funciones que se necesitan :).

Correcto

> > La comunicacion es sincrona y bloqueada: es decir si la solicitud es
>
> El protocolo IRC es asíncrono. Menos mal, sino en situaciones de lag la
> cosa sería todavía peor de lo que es ahora :) para un cliente, y un
> servidor ni siquiera funcionaría. No hay m s remedio que vivir este
> hecho.

A ver. En escenario cliente servidor si Envio UNA peticion whois quiero UNA respuesta. TOTAL, COMPLETA Y UNICA, y no tener que analizar lo que me llegue.

Tu propuesta , segun entiendo se basa en trasladar la complicacion al cliente y que el argobot siga haciendo lo que hasta ahora pero redirigiendo a las stdin adecuadas

Pero eso limita la posibilidad de plugins que NO funcionen siempre.

Yo sugiero hacer algo parecido a un servidor pop3, sin validacion. a saber

  1. contacto inicial (como si fuera un socket, pero sin el)

    echo "hola" |argobot

  2. obtener id de session

    id_session=$(echo "getid" |argobot)

  3. commandos hasta que me canse

    result=$(echo $commando |argobot)

  4. fin de sesion

Esto NO es codigo, sólo es una idea

Podria ser necesario utilizar otros metodos en lugar de stdin/out (sockets af_unix, por ejemplo)

> tipo "fin de lista", "fin de who", "fin de ban"...

a eso me referia en en las sugerencias con el BEGIN...END

> Un escenario puede ser el siguiente:
>
> Un plug-in manda comandos "/who" cada minuto, por ejemplo, y sólo [...]

Si va asi, sólo tu y manolete sereis capaces de escribir plugins

> Antes de definir qué comandos acepta el Argobot hay que acordar cosas
> como ésta.
>
> Personalmente me parece innecesario, ya que puede hacerse eso mismo sin
> más que filtrar en el plug-in todo lo que no tenga que ver con el canal.
> Lo único sería la carga de CPU, pero dudo mucho que eso sea un problema,
> por mucho tráfico que haya.

Si, sólo falta eso.... (no todos somos genios)

> Hay que ver formas de intercomunicar los plugins a través del argobot.
> Como todos los plug-ins ven todo, lo más fácil es añadir un comando
> "echo" que haga que el argobot mande a todos los plug-ins (salvo al que
> manda el comando :) un texto. [...]

me parece perfecto

Marti Trasmonte          Conecta 2000 S.A
System Manager           hostmaster@conecta.es


Message-ID: <34EB02AF.24261EBD@argo.es>
Date: Wed, 18 Feb 1998 14:47:59 -0100
From: "Jesús Cea Avión" <jcea@argo.es>
Organization: Argo Redes y Servicios Telematicos, S.A.
Subject: Re: [IRCOPS-ESNET] Spec de interface contra Argobot
References: <199802181222.NAA19404@tyr.conecta.es>

Nota: Hay cosas que ya se han discutido por privado en el IRC, por lo que es posible que me salte algunas en este mensaje. Intentaré ser lo más explícito posible.

> Y a eso le llamas simple ????

Pues sí, bastante. Si lo miras con calma, no es sólo lo más simple, sino la única posibilidad si quieres poder instalar plug-ins genéricos. Y no digamos si quieres poder instalar varios plug-ins simultaneamente en el mismo bot.

Se me ocurre una posibilidad: para plug-ins más sencillitos basta con tener un plug-in estándar que a su vez lance otro, y que le pase a éste las cosas más o menos "cocinaditas" :). Como un proxy.

> .- Creo que seria conveniente que los plugins fueran MUCHO mas
> sencillos vamos, que no haya que soñar con el protocolo o saberse de
> memoria el rfc para pedir la lista de usuarios

Repito lo que he comentado por IRC hace unos minutos, para que conste:

[...]

[13:57] <Tyran> si, pero , estaras de acuero, en que el plugin es mucho
                 mas complejo
[13:58] <Tyran> pq, si entiendo bien
[13:58] <Tyran> argobot volcara TODO lo que reciba a TODOS los plugins
[13:58] <Tyran> si o no?
<jcea> si
[13:59] <Tyran> lo caul es innecsario en el caso de un plugin que sirva,
                p ej
[13:59] <Tyran> para administrar el argobot en remoto , via web 
<jcea> el argobot no sabe qué hace el plugin.
<jcea> ¿Cómo decide qué darle y qué no?
[14:00] <Tyran> (a lo mejor me estoy yendo por las ramas)
[14:00] <Tyran> pregunta inversa
[14:00] <Tyran> El plugin no sabe que le va a dar el argobot
[14:01] <Tyran> como decide que le toca a el y que no?
<jcea> justo.
<jcea> Como no lo sabe,
<jcea> simplemente recibe todo y va pillando lo que le interesa.
<jcea> El sí que sabe lo que le interesa, porque
<jcea> el conoce su función.
<jcea> Por ejemplo,
<jcea> un plugin que protege canales contra 
<jcea> insultos
<jcea> simplemente ignora todo lo que no sea un "privmsg" o un "notice".
<jcea> Si es un privmsg, comprueba que es un canal suyo.
<jcea> Si es un canal suyo, comprueba el texto.
<jcea> Si el texto no le gusta, manda un kick&ban,
<jcea> por ejemplo.
<jcea> Yo lo veo sencillísimo, la verdad.
[14:02] <Tyran> vale
[14:02] <Tyran> un plugin saca estadisticas de usuario por canal
[14:03] <Tyran> con whois
[14:03] <Tyran> y otro busca usuarios en la red
[14:03] <Tyran> con whois
[14:03] <Tyran> como se lo montan
<jcea> en caso de incompatibilidad, no tendrán más wevos que instalarse
       en bots separados.
<jcea> Pero es que tú tampoco me das soluciones a esto.
<jcea> No me explicas cómo llevar a cabo eso de "sesiones".
[...]

> .- Creo que es logico trasladar la complicacion de gestion de sesiones
> concurrentes de plugins al argobot puesto que una vez hecho, este es
> unico, mientras que los plugins pueden ser muy variados y SE HAN DE
> hacer cada vez

Pensé que había quedado bastante claro en mi mail anterior que el protocolo IRC es asíncrono y, por tanto, no es posible identificar sesiones };-). Dime cómo hacerlo y lo implemento. Te lo juro por Snoopy };-).

> > No es necesario, porque cada uno de ellos tiene una STDIN/OUT
> > independiente.
>
> Depende de la estrategia que utilizes

Por eso hay que decidir estas cosas antes de meternos con comandos y demás historias.

[Protocolo Asíncrono]
> A ver. En escenario cliente servidor si Envio UNA peticion whois
> quiero UNA respuesta. TOTAL, COMPLETA Y UNICA, y no tener que analizar
> lo que me llegue.

¿Qué entiendes por analizar?. Yo algo así como, tomando tu caso:

  1. ¿Lo que me llega es una respuesta a un WHOIS?
  2. No. La ignoro completamente.
  3. Si. La proceso.

¿Tan complicado resulta?. En total, dos "strchr()" para localizar el comando y un "strncmp()" para comprobar si el comando es un whois :).

> Tu propuesta , segun entiendo se basa en trasladar la complicacion al
> cliente y que el argobot siga haciendo lo que hasta ahora pero
> redirigiendo a las stdin adecuadas

El problema es que el argobot no sabe qué plug-in es el adecuado. Si se te ocurre cómo implementar el concepto de sesión que propones...

> Pero eso limita la posibilidad de plugins que NO funcionen siempre.

En todo caso, puedes tener plug-ins incompatibles entre sí. Eso no lo puedes solucionar. Tendrás que lanzar un bot aparte con el plug-in en cuestión. Naturalmente ello obliga a documentar muy clarito no sólo qué hace un plug-in, sino cómo lo hace.

> Yo sugiero hacer algo parecido a un servidor pop3, sin validacion.

Veamos...

[...]
> 2.- obtener id de session

Bueno, dime cómo se obtiene esa ID, y cómo se asocian respuestas del servidor a sesiones específicas };-).

> Podria ser necesario utilizar otros metodos en lugar de stdin/out
> (sockets af_unix, por ejemplo)

Si los plug-ins los lanza el argobot, que es lo lógico, stdin/out es lo más sencillo. Las pruebas de los plugins también se simplifican. Si quieres que un plug-in específico se ejecute a 1836 kilómetros de distancia, basta hacer que uno de los plug-ins estándar a desarrollar (y que se distribuye con el argobot "de serie") se conecte a un socket que tú le digas, haciendo algo tipo proxy.

Mira, ya tenemos dos plug-ins "proxy" estándares en los que trabajar };-).

> > tipo "fin de lista", "fin de who", "fin de ban"...
>
> a eso me referia en en las sugerencias con el BEGIN...END

La diferencias es que lo de "fin de..." te lo da el propio protocolo IRC. Tus "begin...end" tendría que hacerlos el Argobot, y éste no tiene información suficiente para ello. Volvemos al tema de las sesiones.

[...]
> Si va asi, sólo tu y manolete sereis capaces de escribir plugins

Dame alternativas y hablamos.

Ves las cosas mucho más complicadas de lo que son en realidad. Juguetea un poco con el protocolo IRC, vía telnet. Un plug-in no sería muy diferente a programar un script para un cliente IRC normal.

Salvo que aquí tenemos varios scripts funcionando de forma simultanea, cada uno haciendo lo que le da la gana sin coordinarse con los demás :).

> > Personalmente me parece innecesario, ya que puede hacerse eso mismo
> > sin más que filtrar en el plug-in todo lo que no tenga que ver con el
> > canal. Lo único sería la carga de CPU, pero dudo mucho que eso sea
> > un problema, por mucho tráfico que haya.
>
> Si, sólo falta eso.... (no todos somos genios)

Esto no lo he pillado :-?.

-- 
Jesus Cea Avion                         _/_/      _/_/_/        _/_/_/
jcea@argo.es http://www.argo.es/~jcea/ _/_/    _/_/  _/_/    _/_/  _/_/
                                      _/_/    _/_/          _/_/_/_/_/
PGP Key Available at KeyServ   _/_/  _/_/    _/_/          _/_/  _/_/
"Things are not so easy"      _/_/  _/_/    _/_/  _/_/    _/_/  _/_/
"My name is Dump, Core Dump"   _/_/_/        _/_/_/      _/_/  _/_/
"El amor es poner tu felicidad en la felicidad de otro" - Leibnitz



Python Zope ©1998 jcea@jcea.es

Más información sobre los OpenBadges

Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS