maiden es una colección de sistemas que le ayudarán a crear aplicaciones y bibliotecas que interactúan con servidores de chat. Puede ayudarle a crear un bot de chat o un cliente de chat general. También ofrece una variedad de partes que deberían hacer que sea mucho más fácil escribir un cliente para un nuevo protocolo de chat.
Si sólo te interesa usar maiden para configurar un bot de algún tipo, los pasos para hacerlo son bastante sencillos. Primero, querremos cargar maiden y todos los módulos y componentes que le gustaría usar en su bot.
(ql:quickload '( maiden maiden -irc maiden -commands maiden -silly))
Y luego crearemos un núcleo con instancias de los consumidores agregadas como nos gustaría que fueran.
(defvar *core* ( maiden :make-core
'(: maiden -irc :nickname " maiden Test" :host "irc.freenode.net" :channels ("##testing"))
: maiden -commands
: maiden -silly))
El comando make-core toma los nombres de los paquetes (como cadenas o símbolos) de los consumidores para agregarlos o el nombre de clase directo de un consumidor. En el primer caso, intentará encontrar por sí solo el nombre de clase de consumidor apropiado.
Y eso es todo. make-core
creará un núcleo, creará una instancia de todos los consumidores, los agregará y pondrá en marcha todo. Muchos de los módulos proporcionados para maiden utilizarán algún tipo de configuración o almacenamiento persistente. Para su gestión consultar el subsistema de almacenamiento.
Para utilizar maiden como marco, primero deberá definir su propio sistema y paquete como es habitual para un proyecto. Por ahora solo usaremos el paquete maiden -user
para jugar. A continuación queremos definir un consumidor. Esto se puede hacer con define-consumer
.
(in-package #: maiden -user)
(define-consumer ping-notifier (agent)
())
Generalmente querrás definir un agente. Los agentes sólo pueden existir una vez en un núcleo. Más adelante veremos un ejemplo para un cliente. Ahora, de aquí en adelante podemos definir nuestros propios métodos y funciones que se especializan o actúan sobre la clase de consumidor como estaría acostumbrado en la programación CLOS general. A continuación, definiremos nuestro propio evento que usaremos para enviar "solicitudes de ping" al sistema.
(define-event ping (passive-event)
())
El evento se define como un passive-event
ya que no solicita directamente que se realice una acción, sino que informa al sistema de un ping que está sucediendo. Ahora, para que el consumidor interactúe con el sistema de eventos, también queremos definir controladores. Esto se puede hacer con define-handler
.
(define-handler (ping-notifier ping-receiver ping) (c ev)
(v:info :ping "Received a ping: ~a" ev))
Esto define un controlador llamado ping-receiver
en nuestro consumidor ping-notifier
. También especifica que escuchará eventos de tipo ping
. Luego, la lista de argumentos dice que la instancia del consumidor está vinculada a c
y la instancia del evento a ev
. Luego, el cuerpo simplemente registra un mensaje informativo utilizando Verbose.
Probemos esto muy rápido.
(defvar *core* (make-core 'ping-notifier))
(do-issue *core* ping)
Eso debería imprimir el mensaje de estado en REPL como se esperaba. Y eso es casi todo lo que implica utilizar este sistema. Tenga en cuenta que para hacer cosas realmente útiles, probablemente desee utilizar algunos de los subsistemas preexistentes que ofrece el proyecto maiden además del núcleo. Estos le ayudarán con los usuarios, canales, cuentas, comandos, redes, almacenamiento, etc. También tenga en cuenta que también puede utilizar las funciones que ofrece Deeds por sí solo, como el filtrado de expresiones para controladores.
Ahora echemos un vistazo a un tipo de cliente primitivo. El cliente simplemente podrá escribir en un archivo a través de eventos.
(define-consumer file-client (client)
((file :initarg :file :accessor file))
(:default-initargs :file (error "FILE required.")))
(define-event write-event (client-event active-event)
((sequence :initarg :sequence))
(:default-initargs :sequence (error "SEQUENCE required.")))
Hemos convertido el write-event
en un client-event
ya que debe ser específico de un cliente al que queremos escribir, y lo hemos convertido en un active-event
ya que solicita que suceda algo. Ahora definamos nuestro controlador que se encargará de escribir la secuencia en el archivo.
(define-handler (file-client writer write-event) (c ev sequence)
:match-consumer 'client
(with-open-file (stream (file c) :direction :output :if-exists :append :if-does-not-exist :create)
(write-sequence sequence stream)))
La opción :match-consumer
modifica el filtro del controlador de tal manera que el filtro solo pasará eventos cuya ranura client
contenga la misma instancia file-client
a la que pertenece la instancia del controlador actual. Esto es importante, ya que cada instancia de file-client
recibirá sus propias instancias de sus controladores en un núcleo. Sin esta opción, el write-event
sería manejado por cada instancia del file-client
independientemente de a qué instancia estaba destinado el evento. También tenga en cuenta que agregamos un argumento sequence
a la lista de argumentos del controlador. Este argumento se llenará con el espacio apropiado del evento. Si no se encuentra ninguna ranura, se indica un error.
Es hora de probarlo. Simplemente reutilizaremos el núcleo de arriba.
(add-to-core *core* '(file-client :file "~/foo" :name :foo)
'(file-client :file "~/bar" :name :bar))
(do-issue *core* write-event :sequence "foo" :client (consumer :foo *core*))
(do-issue *core* write-event :sequence "bar" :client (consumer :bar *core*))
(alexandria:read-file-into-string "~/foo") ; => "foo"
(alexandria:read-file-into-string "~/bar") ; => "bar"
Como puede ver, los eventos se dirigieron a las instancias de controlador adecuadas según el cliente que queríamos y, por lo tanto, los archivos contienen lo que esperábamos.
Finalmente, vale la pena mencionar que también es posible agregar y eliminar controladores dinámicamente en tiempo de ejecución, e incluso hacerlo para controladores que no están asociados con un consumidor en particular. Esto suele ser útil cuando necesita esperar un evento de respuesta desde algún lugar. Para manejar la lógica de hacer esto de forma asincrónica y retener la impresión de un flujo imperativo, maiden ofrece, tal como lo hace Deeds, una macro with-awaiting
. Se puede utilizar de la siguiente manera:
(with-awaiting (core event-type) (ev some-field)
(do-issue core initiating-event)
:timeout 20
some-field)
with-awaiting
es muy similar a define-handler
, con la excepción de que no toma un nombre y, en lugar de un nombre de consumidor al principio, necesita una instancia central o de consumidor. También requiere una opción adicional que de otro modo no se usaría :timeout
. Otro extra requerido es el "formulario de configuración" después de arglist. Para administrar todo adecuadamente y garantizar que no puedan ocurrir condiciones de carrera en el sistema, debe iniciar el proceso que generará el evento de respuesta eventual en este formulario de configuración. Si lo inicia antes de esa fecha, el evento de respuesta podría enviarse antes de que se configure el controlador temporal en el sistema y parecerá como si nunca hubiera llegado.
Y eso es prácticamente todo lo básico. Como se mencionó anteriormente, eche un vistazo a los subsistemas que incluye este proyecto, ya que lo ayudarán con todo tipo de tareas y problemas comunes relacionados con los sistemas de chat, etc.
Antes de comprender maiden , vale la pena comprender los hechos, aunque sólo sea a nivel superficial. maiden se basa en ello bastante.
Un core
es la parte central de una configuración maiden . Es responsable de gestionar y orquestar los demás componentes del sistema. Puede tener varios núcleos ejecutándose simultáneamente dentro de la misma imagen Lisp e incluso compartir componentes entre ellos.
Más específicamente, un Core se compone de un bucle de eventos y un conjunto de consumidores. El bucle de eventos es responsable de entregar eventos a los controladores. Los consumidores son responsables de adjuntar controladores al bucle de eventos. Las operaciones que probablemente desee realizar en un núcleo son las siguientes: emitirle eventos por issue
, agregarle consumidores mediante add-consumer
o eliminar un consumidor mediante remove-consumer
.
Para que le resulte más fácil crear un núcleo útil con consumidores agregados, puede utilizar las funciones make-core
y add-to-core
.
Un event
es un objeto que representa un cambio en el sistema. Los eventos se pueden utilizar para representar un cambio que se ha producido o para representar una solicitud para que se produzca un cambio. Estos se denominan passive-event
y active-event
respectivamente.
Generalmente utilizará eventos de las siguientes maneras:
Un consumer
es una clase que representa un componente del sistema. Cada consumidor puede tener una multitud de controladores vinculados a él, que reaccionarán a los eventos en el sistema. Los consumidores se dividen en dos supertipos básicos: agent
y client
. Los agentes son consumidores que solo deberían existir en un núcleo una vez, ya que implementan funciones que no tendría sentido multiplexar de alguna manera. Los clientes, por otro lado, representan una especie de puente hacia un sistema externo y, naturalmente, se les debe permitir tener múltiples instancias en el mismo núcleo.
Por lo tanto, desarrollar un conjunto de comandos o una interfaz de algún tipo probablemente conduciría a un agente, mientras que la interfaz con un servicio como XMPP conduciría a un cliente.
La definición de un consumidor debe realizarse con define-consumer
, que es similar al defclass
estándar, pero garantiza que las superclases y metaclases estén configuradas correctamente.
Los handler
son objetos que contienen una función que realiza ciertas acciones cuando se emite un evento particular en el núcleo. Cada controlador está vinculado a un consumidor particular y se elimina o se agrega al bucle de eventos del núcleo cuando el consumidor se elimina o se agrega al núcleo.
La definición del controlador ocurre a través de uno de define-handler
, define-function-handler
, define-instruction
o define-query
. Cada uno de los cuales se basa sucesivamente en el último para proporcionar una abreviatura más amplia de los requisitos comunes. Tenga en cuenta que la forma en que un controlador recibe realmente sus eventos puede diferir. Eche un vistazo a la documentación de Deeds para ver qué clases de controlador están disponibles.
En el proyecto maiden se incluyen un par de subsistemas que amplían la funcionalidad principal.
El proyecto maiden también incluye algunos clientes estándar que se pueden utilizar de inmediato.
Finalmente, el proyecto tiene un montón de módulos de agentes que brindan una funcionalidad que es útil para crear bots de chat y demás. También se pueden utilizar de inmediato.