maiden — это набор систем, которые помогут вам создавать приложения и библиотеки, взаимодействующие с чат-серверами. Он может помочь вам создать чат-бота или обычный чат-клиент. Он также предлагает множество частей, которые должны значительно упростить написание клиента для нового протокола чата.
Если вас интересует только использование maiden для настройки какого-либо бота, шаги для этого довольно просты. Сначала нам нужно загрузить maiden и все модули и компоненты, которые вы хотите использовать в своем боте.
(ql:quickload '( maiden maiden -irc maiden -commands maiden -silly))
А затем мы создадим ядро с добавленными к нему экземплярами потребителей такими, какими мы хотим их видеть.
(defvar *core* ( maiden :make-core
'(: maiden -irc :nickname " maiden Test" :host "irc.freenode.net" :channels ("##testing"))
: maiden -commands
: maiden -silly))
Команда make-core принимает для добавления либо имена пакетов (в виде строк или символов) потребителей, либо прямое имя класса потребителя. В первом случае он попытается найти подходящее имя потребительского класса самостоятельно.
И все. make-core
создаст ядро, создаст экземпляры всех потребителей, добавит их в него и запустит все. Многие модули, представленные в maiden будут использовать какую-либо конфигурацию или постоянное хранилище. Для управления им см. подсистему хранения.
Чтобы использовать maiden в качестве основы, вам сначала нужно определить свою собственную систему и пакет, как обычно для проекта. Сейчас мы просто будем использовать пакет maiden -user
для экспериментов. Далее нам нужно определить потребителя. Это можно сделать с помощью define-consumer
.
(in-package #: maiden -user)
(define-consumer ping-notifier (agent)
())
Обычно вам нужно определить агента. Агенты могут существовать на ядре только один раз. Позже мы рассмотрим пример для клиента. Теперь мы можем определять наши собственные методы и функции, которые специализируются на потребительском классе или действуют на него так, как вы привыкли в общем программировании CLOS. Далее мы определим наше собственное событие, которое будем использовать для отправки «пинг-запросов» в систему.
(define-event ping (passive-event)
())
Событие определяется как passive-event
поскольку оно не запрашивает непосредственно действие, которое необходимо предпринять, а скорее информирует систему о происходящем пинге. Однако теперь, чтобы заставить потребителя взаимодействовать с системой событий, нам также нужно определить обработчики. Это можно сделать с помощью define-handler
.
(define-handler (ping-notifier ping-receiver ping) (c ev)
(v:info :ping "Received a ping: ~a" ev))
Это определяет обработчик под названием ping-receiver
для нашего потребителя ping-notifier
. Он также указывает, что он будет прослушивать события типа ping
. Далее в списке аргументов говорится, что экземпляр потребителя привязан к c
, а экземпляр события — к ev
. Затем тело просто записывает информационное сообщение с помощью Verbose.
Давайте проверим это очень быстро.
(defvar *core* (make-core 'ping-notifier))
(do-issue *core* ping)
Это должно напечатать сообщение о состоянии в REPL, как и ожидалось. И это почти все, что нужно для использования этой системы. Обратите внимание, что для того, чтобы делать действительно полезные вещи, вам, вероятно, захочется использовать некоторые из уже существующих подсистем, которые предоставляет maiden проект помимо ядра. Они помогут вам с пользователями, каналами, учетными записями, командами, сетями, хранилищем и т. д. Также имейте в виду, что вы также можете использовать функции, которые Deeds предлагает сам по себе, например, выражения фильтрации для обработчиков.
Теперь давайте взглянем на примитивный тип клиента. Клиент просто сможет писать в файл через события.
(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.")))
Мы сделали write-event
client-event
поскольку оно должно быть специфичным для клиента, которому мы хотим писать, и мы сделали его active-event
поскольку оно запрашивает, чтобы что-то произошло. Теперь давайте определим наш обработчик, который будет фактически записывать последовательность в файл.
(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)))
Опция :match-consumer
изменяет фильтр обработчика таким образом, что фильтр будет передавать только события, client
слот которых содержит тот же экземпляр file-client
, которому принадлежит текущий экземпляр обработчика. Это важно, поскольку каждый экземпляр file-client
получит свои собственные экземпляры своих обработчиков в ядре. Без этой опции write-event
обрабатывалось бы каждым экземпляром file-client
независимо от того, для какого экземпляра это событие предназначалось. Также обратите внимание, что мы добавили аргумент sequence
в список аргументов обработчика. Этот аргумент будет заполнен соответствующим слотом из события. Если такой слот не найден, выдается сигнал об ошибке.
Пришло время проверить это. Мы просто повторно используем ядро сверху.
(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"
Как видите, события были направлены в соответствующие экземпляры обработчика в соответствии с нужным нам клиентом, и, таким образом, файлы содержат то, что мы от них ожидаем.
Наконец, стоит упомянуть, что также возможно динамически добавлять и удалять обработчики во время выполнения, и даже делать это для обработчиков, которые не связаны с конкретным потребителем. Это часто бывает полезно, когда вам нужно откуда-то дождаться ответного события. Чтобы справиться с логикой выполнения этого асинхронного действия и сохранить впечатление императивного потока, maiden предлагает — как и Deeds — макрос with-awaiting
. Его можно использовать следующим образом:
(with-awaiting (core event-type) (ev some-field)
(do-issue core initiating-event)
:timeout 20
some-field)
with-awaiting
очень похож на define-handler
, за исключением того, что он не принимает имя и вместо имени потребителя вначале ему нужен экземпляр ядра или потребителя. Также требуется одна дополнительная опция, которая в противном случае не используется: :timeout
. Еще одно необходимое дополнение — это «форма настройки» после списка аргументов. Чтобы правильно управлять всем и гарантировать отсутствие условий гонки в системе, вы должны инициировать процесс, который вызовет возможное событие ответа в этой форме настройки. Если вы инициируете его раньше, событие ответа может быть отправлено до того, как временный обработчик будет настроен в системе, и будет выглядеть так, как будто оно вообще не прибыло.
И это почти все основы. Как упоминалось выше, взгляните на подсистемы, которые включает в себя этот проект, поскольку они помогут вам решить всевозможные общие задачи и проблемы, связанные с системами чата и так далее.
Прежде чем понять maiden , стоит разобраться в Деяниях, хотя бы на поверхностном уровне. maiden довольно сильно опирается на это.
core
— это центральная часть maiden конфигурации. Он отвечает за управление и оркестровку других компонентов системы. Вы можете иметь несколько ядер, работающих одновременно в одном образе Lisp, и даже можете использовать общие компоненты между ними.
Точнее, ядро состоит из цикла событий и набора потребителей. Цикл событий отвечает за доставку событий обработчикам. Потребители несут ответственность за подключение обработчиков к циклу событий. Операции, которые вы, скорее всего, захотите выполнить над ядром, заключаются в следующем: выдача ему событий с помощью issue
, добавление к нему потребителей с помощью add-consumer
или удаление потребителя из него с помощью remove-consumer
.
Чтобы упростить создание полезного ядра с добавленными к нему потребителями, вы можете использовать функции make-core
и add-to-core
.
event
— это объект, который представляет собой изменение в системе. События могут использоваться либо для представления произошедшего изменения, либо для представления запроса на то, чтобы изменение произошло. Они называются passive-event
и active-event
соответственно.
Обычно вы будете использовать события следующими способами:
consumer
— это класс, который представляет компонент системы. К каждому потребителю может быть привязано множество обработчиков, которые будут реагировать на события в системе. Потребители делятся на два основных супертипа: agent
и client
. Агенты — это потребители, которые должны существовать в ядре только один раз, поскольку они реализуют функциональные возможности, мультиплексирование которых не имеет смысла каким-либо образом. С другой стороны, клиенты представляют собой своего рода мост к внешней системе, и, естественно, им должно быть разрешено иметь несколько экземпляров на одном ядре.
Таким образом, разработка набора команд или какого-либо интерфейса, вероятно, приведет к созданию агента, тогда как взаимодействие с такой службой, как XMPP, приведет к созданию клиента.
Определение потребителя должно происходить с помощью define-consumer
, который аналогичен стандартному defclass
, но гарантирует правильную настройку суперклассов и метаклассов.
handler
— это объекты, которые содержат функцию, которая выполняет определенные действия, когда определенное событие передается в ядро. Каждый обработчик привязан к конкретному потребителю и удаляется или добавляется в цикл событий ядра, когда потребитель удаляется или добавляется в ядро.
Определение обработчика происходит с помощью одного из define-handler
, define-function-handler
, define-instruction
или define-query
. Каждый из которых последовательно основывается на последнем, чтобы обеспечить более широкое сокращение общих требований. Обратите внимание, что способ, которым обработчик фактически получает свои события, может отличаться. Посмотрите документацию Deeds, чтобы узнать, какие классы обработчиков доступны.
В maiden проект включена пара подсистем, расширяющих базовую функциональность.
maiden проект также включает в себя несколько стандартных клиентов, которые можно использовать сразу.
Наконец, в проекте есть несколько модулей агентов, которые предоставляют функциональные возможности, полезные для создания чат-ботов и тому подобного. Их тоже можно сразу использовать.