maiden ist eine Sammlung von Systemen, die Ihnen beim Erstellen von Anwendungen und Bibliotheken helfen, die mit Chat-Servern interagieren. Es kann Ihnen beim Erstellen eines Chat-Bots oder eines allgemeinen Chat-Clients helfen. Es bietet außerdem eine Vielzahl von Teilen, die es deutlich einfacher machen sollen, einen Client für ein neues Chat-Protokoll zu schreiben.
Wenn Sie maiden nur zum Einrichten eines Bots verwenden möchten, sind die Schritte dazu recht einfach. Zuerst möchten wir maiden und alle Module und Komponenten laden, die Sie in Ihrem Bot verwenden möchten.
(ql:quickload '( maiden maiden -irc maiden -commands maiden -silly))
Und dann erstellen wir einen Kern, dem Instanzen der Verbraucher hinzugefügt werden, wie wir es gerne hätten.
(defvar *core* ( maiden :make-core
'(: maiden -irc :nickname " maiden Test" :host "irc.freenode.net" :channels ("##testing"))
: maiden -commands
: maiden -silly))
Der Befehl make-core übernimmt entweder Paketnamen (als Zeichenfolgen oder Symbole) der hinzuzufügenden Verbraucher oder den direkten Klassennamen eines Verbrauchers. Im ersteren Fall wird versucht, selbst den passenden Namen der Verbraucherklasse zu finden.
Und das ist es. make-core
erstellt einen Kern, instanziiert alle Verbraucher, fügt sie hinzu und startet alles. Ein Teil der für maiden bereitgestellten Module wird eine Art Konfiguration oder dauerhaften Speicher nutzen. Informationen zur Verwaltung finden Sie im Speichersubsystem.
Um maiden als Framework zu verwenden, möchten Sie zunächst wie für ein Projekt üblich Ihr eigenes System und Paket definieren. Im Moment verwenden wir einfach das maiden -user
-Paket, um damit herumzuspielen. Als nächstes wollen wir einen Verbraucher definieren. Dies kann mit define-consumer
erfolgen.
(in-package #: maiden -user)
(define-consumer ping-notifier (agent)
())
Normalerweise möchten Sie einen Agenten definieren. Agenten können nur einmal auf einem Kern existieren. Wir werden später ein Beispiel für einen Kunden durchgehen. Von nun an können wir unsere eigenen Methoden und Funktionen definieren, die sich auf die Verbraucherklasse spezialisieren oder auf diese einwirken, wie Sie es von der allgemeinen CLOS-Programmierung gewohnt sind. Als Nächstes definieren wir unser eigenes Ereignis, mit dem wir „Ping-Anfragen“ an das System senden.
(define-event ping (passive-event)
())
Das Ereignis wird als passive-event
definiert, da es nicht direkt eine Aktion anfordert, sondern das System über einen stattfindenden Ping informiert. Damit der Verbraucher nun jedoch tatsächlich mit dem Ereignissystem interagieren kann, möchten wir auch Handler definieren. Dies kann mit define-handler
erfolgen.
(define-handler (ping-notifier ping-receiver ping) (c ev)
(v:info :ping "Received a ping: ~a" ev))
Dies definiert einen Handler namens ping-receiver
auf unserem ping-notifier
Consumer. Außerdem wird angegeben, dass auf Ereignisse vom Typ ping
gewartet wird. Die Arglist sagt danach, dass die Consumer-Instanz an c
und die Event-Instanz an ev
gebunden ist. Der Textkörper protokolliert dann einfach eine Informationsnachricht mit Verbose.
Lasst uns das ganz schnell testen.
(defvar *core* (make-core 'ping-notifier))
(do-issue *core* ping)
Dadurch sollte die Statusmeldung wie erwartet an die REPL ausgegeben werden. Und das ist das meiste, was die Verwendung dieses Systems ausmacht. Beachten Sie, dass Sie, um tatsächlich nützliche Dinge zu tun, wahrscheinlich einige der bereits vorhandenen Subsysteme nutzen möchten, die das maiden neben dem Kern bereitstellt. Diese helfen Ihnen bei Benutzern, Kanälen, Konten, Befehlen, Netzwerken, Speicher usw. Denken Sie auch daran, dass Sie die Funktionen, die Deeds bietet, auch eigenständig nutzen können, z. B. das Filtern von Ausdrücken für Handler.
Werfen wir nun einen Blick auf eine primitive Art von Client. Der Client kann einfach über Ereignisse in eine Datei schreiben.
(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.")))
Wir haben das write-event
einem client-event
gemacht, da es spezifisch für einen Client sein muss, an den wir schreiben möchten, und wir haben es zu einem active-event
gemacht, da es anfordert, dass etwas passiert. Definieren wir nun unseren Handler, der sich um das eigentliche Schreiben der Sequenz in die Datei kümmert.
(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)))
Die Option :match-consumer
ändert den Filter des Handlers so, dass der Filter nur Ereignisse durchlässt, deren client
-Slot dieselbe file-client
-Instanz enthält, zu der die aktuelle Handler-Instanz gehört. Dies ist wichtig, da jede Instanz des file-client
ihre eigenen Instanzen ihrer Handler auf einem Kern erhält. Ohne diese Option würde das write-event
von jeder Instanz des file-client
verarbeitet werden, unabhängig davon, für welche Instanz das Ereignis bestimmt war. Beachten Sie auch, dass wir der Argumentliste des Handlers ein sequence
hinzugefügt haben. Dieses Argument wird mit dem entsprechenden Slot aus dem Ereignis gefüllt. Konnte kein solcher Steckplatz gefunden werden, wird ein Fehler gemeldet.
Zeit, es auszuprobieren. Wir werden einfach den Kern von oben wiederverwenden.
(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"
Wie Sie sehen können, wurden die Ereignisse entsprechend dem von uns gewünschten Client an die entsprechenden Handler-Instanzen weitergeleitet, und die Dateien enthalten somit das, was wir von ihnen erwarten.
Abschließend ist noch zu erwähnen, dass es auch möglich ist, Handler zur Laufzeit dynamisch hinzuzufügen und zu entfernen, und zwar sogar für Handler, die keinem bestimmten Verbraucher zugeordnet sind. Dies ist oft nützlich, wenn Sie auf ein Antwortereignis von irgendwoher warten müssen. Um die Logik dieses asynchronen Vorgehens zu handhaben und den Eindruck eines imperativen Flusses zu bewahren, bietet maiden – genau wie Deeds – ein with-awaiting
“-Makro. Es kann wie folgt verwendet werden:
(with-awaiting (core event-type) (ev some-field)
(do-issue core initiating-event)
:timeout 20
some-field)
with-awaiting
ist define-handler
sehr ähnlich, mit der Ausnahme, dass es keinen Namen benötigt und anstelle eines Verbrauchernamens am Anfang eine Kern- oder Verbraucherinstanz benötigt. Außerdem ist eine zusätzliche Option erforderlich, die ansonsten nicht verwendet wird: :timeout
. Ein weiteres erforderliches Extra ist das „Setup-Formular“ nach der Arglist. Um alles ordnungsgemäß zu verwalten und sicherzustellen, dass im System keine Race Conditions auftreten, müssen Sie den Prozess initiieren, der das eventuelle Antwortereignis in diesem Setup-Formular auslöst. Wenn Sie es vorher initiieren, wird das Antwortereignis möglicherweise gesendet, bevor der temporäre Handler im System eingerichtet ist, und es sieht so aus, als ob es überhaupt nie angekommen wäre.
Und das sind so ziemlich alle Grundlagen. Werfen Sie, wie oben erwähnt, einen Blick auf die Subsysteme, die dieses Projekt umfasst, da sie Ihnen bei allen möglichen allgemeinen Aufgaben und Problemen im Zusammenhang mit Chat-Systemen usw. helfen werden.
Bevor man maiden versteht, lohnt es sich, die Taten zu verstehen, wenn auch nur oberflächlich. maiden baut ziemlich stark darauf auf.
Ein core
ist der zentrale Teil einer maiden . Es ist für die Verwaltung und Orchestrierung der anderen Komponenten des Systems verantwortlich. Sie können mehrere Kerne gleichzeitig im selben Lisp-Image ausführen und sogar Komponenten zwischen ihnen teilen.
Genauer gesagt besteht ein Kern aus einer Ereignisschleife und einer Reihe von Verbrauchern. Die Ereignisschleife ist für die Übermittlung von Ereignissen an Handler verantwortlich. Verbraucher sind dafür verantwortlich, Handler an die Ereignisschleife anzuhängen. Die Vorgänge, die Sie höchstwahrscheinlich auf einem Kern ausführen möchten, sind daher: Ereignisse an ihn ausgeben durch issue
, Verbraucher hinzufügen durch add-consumer
oder Entfernen eines Verbrauchers daraus durch remove-consumer
.
Um Ihnen die Erstellung eines nützlichen Kerns mit hinzugefügten Verbrauchern zu erleichtern, können Sie die Funktionen make-core
und add-to-core
nutzen.
Ein event
ist ein Objekt, das eine Änderung im System darstellt. Ereignisse können verwendet werden, um entweder eine aufgetretene Änderung darzustellen oder um eine Anforderung für die Durchführung einer Änderung darzustellen. Diese werden als passive-event
bzw. active-event
bezeichnet.
Im Allgemeinen verwenden Sie Ereignisse auf folgende Weise:
Ein consumer
ist eine Klasse, die eine Komponente im System darstellt. An jeden Verbraucher können eine Vielzahl von Handlern gebunden sein, die auf Ereignisse im System reagieren. Es gibt zwei grundlegende Supertypen von Verbrauchern: agent
und client
. Agenten sind Verbraucher, die nur einmal auf einem Kern vorhanden sein sollten, da sie Funktionen implementieren, deren Multiplexung auf irgendeine Weise keinen Sinn ergeben würde. Clients hingegen stellen eine Art Brücke zu einem externen System dar und sollten natürlich mehrere Instanzen auf demselben Kern haben dürfen.
Daher würde die Entwicklung eines Befehlssatzes oder einer Schnittstelle irgendeiner Art wahrscheinlich zu einem Agenten führen, während die Schnittstelle zu einem Dienst wie XMPP zu einem Client führen würde.
Das Definieren eines Verbrauchers sollte mit define-consumer
erfolgen, was dem Standard defclass
ähnelt, aber sicherstellt, dass die Superklassen und Metaklassen ordnungsgemäß eingerichtet sind.
handler
sind Objekte, die eine Funktion enthalten, die bestimmte Aktionen ausführt, wenn ein bestimmtes Ereignis an den Kern ausgegeben wird. Jeder Handler ist an einen bestimmten Verbraucher gebunden und wird entfernt oder zur Ereignisschleife des Kerns hinzugefügt, wenn der Verbraucher entfernt oder dem Kern hinzugefügt wird.
Die Handler-Definition erfolgt über „ define-handler
, define-function-handler
, define-instruction
“ oder define-query
. Sie bauen sukzessive auf den letzten auf, um eine umfassendere Abkürzung für gemeinsame Anforderungen bereitzustellen. Beachten Sie, dass die Art und Weise, wie ein Handler seine Ereignisse tatsächlich empfängt, unterschiedlich sein kann. Schauen Sie sich die Dokumentation der Deeds an, um zu sehen, welche Handlerklassen verfügbar sind.
Im maiden sind einige Subsysteme enthalten, die die Kernfunktionalität erweitern.
Das maiden beinhaltet auch einige Standard-Clients, die sofort genutzt werden können.
Schließlich verfügt das Projekt über eine Reihe von Agentenmodulen, die nützliche Funktionen für die Erstellung von Chatbots und dergleichen bereitstellen. Auch sie sind sofort einsetzbar.