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)
())
通常、エージェントを定義する必要があります。エージェントはコア上に 1 回だけ存在できます。後でクライアントの例を見ていきます。ここからは、一般的な CLOS プログラミングで慣れているように、コンシューマ クラスに特化した、またはコンシューマ クラスに作用する独自のメソッドと関数を定義できるようになります。次に、システムに「ping リクエスト」を送信するために使用する独自のイベントを定義します。
(define-event ping (passive-event)
())
このイベントは、アクションの実行を直接要求するのではなく、発生している ping をシステムに通知するためpassive-event
として定義されます。ただし、実際にコンシューマーがイベント システムと対話できるようにするために、ハンドラーも定義する必要があります。これは、 define-handler
使用して実行できます。
(define-handler (ping-notifier ping-receiver ping) (c ev)
(v:info :ping "Received a ping: ~a" ev))
これにより、 ping-notifier
コンシューマー上でping-receiver
というハンドラーが定義されます。また、タイプping
のイベントをリッスンすることも指定します。その後、arglist は、コンシューマ インスタンスが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
オプションは、現在のハンドラー インスタンスが属しているものと同じfile-client
インスタンスがclient
スロットに含まれるイベントのみをフィルターが通過するようにハンドラーのフィルターを変更します。 file-client
の各インスタンスはコア上でそのハンドラーの独自のインスタンスを受け取るため、これは重要です。このオプションを使用しない場合、 write-event
イベントがどのインスタンスを対象としたものであるかに関係なく、 file-client
のすべてのインスタンスによって処理されます。また、ハンドラーの arglist に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
追加のオプションも必要です。もう 1 つの必要な追加要素は、arglist の後の「セットアップ フォーム」です。すべてを適切に管理し、システム内で競合状態が発生しないようにするには、このセットアップ フォームで最終的な応答イベントを促すプロセスを開始する必要があります。それより前に開始すると、一時ハンドラーがシステムに設定される前に応答イベントが送信される可能性があり、まったく到着しなかったかのように見えます。
そして、これがほぼすべての基本です。上で述べたように、このプロジェクトに含まれるサブシステムを見てください。これらのサブシステムは、チャット システムなどを中心としたあらゆる種類の一般的なタスクや問題の解決に役立ちます。
maidenを理解する前に、たとえ表面レベルであっても、行為を理解することは価値があります。 maidenそれに基づいてかなり重く構築されています。
core
、 maiden構成の中心部分です。システムの他のコンポーネントの管理と調整を担当します。同じ Lisp イメージ内で複数のコアを同時に実行でき、コア間でコンポーネントを共有することもできます。
より具体的には、コアはイベント ループと一連のコンシューマで構成されます。イベント ループは、イベントをハンドラーに配信する役割を果たします。コンシューマーは、イベント ループにハンドラーをアタッチする責任があります。したがって、コア上で実行する可能性が最も高い操作は、 issue
によるコアへのイベントの発行、 add-consumer
によるコアへのコンシューマの追加、またはremove-consumer
によるコアからのコンシューマの削除です。
コンシューマを追加した有用なコアの作成を容易にするために、 make-core
とadd-to-core
関数を利用できます。
event
システム内の変化を表すオブジェクトです。イベントは、発生した変更を表すか、変更の発生を求めるリクエストを表すために使用できます。これらはそれぞれpassive-event
およびactive-event
と呼ばれます。
通常、イベントは次の方法で使用します。
consumer
システム内のコンポーネントを表すクラスです。各コンシューマーには、システム内のイベントに反応する多数のハンドラーを関連付けることができます。コンシューマには、 agent
とclient
という 2 つの基本的なスーパータイプがあります。エージェントは、何らかの方法で多重化しても意味のない機能を実装するため、コア上に一度だけ存在すべきコンシューマです。一方、クライアントは外部システムへの何らかのブリッジを表しており、当然のことながら、同じコア上に複数のインスタンスを持つことが許可される必要があります。
したがって、一連のコマンドまたは何らかの種類のインターフェイスを開発すると、おそらくエージェントが作成されますが、XMPP などのサービスとインターフェイスすると、クライアントが作成されます。
コンシューマの定義は、 define-consumer
を使用して行う必要があります。これは標準のdefclass
に似ていますが、スーパークラスとメタクラスが適切に設定されていることを保証します。
handler
は、特定のイベントがコアに発行されたときに特定のアクションを実行する関数を保持するオブジェクトです。各ハンドラーは特定のコンシューマに関連付けられており、コンシューマがコアから削除または追加されると、コアのイベント ループから削除または追加されます。
ハンドラーの定義はdefine-handler
、 define-function-handler
、 define-instruction
、またはdefine-query
のいずれかによって行われます。これらは、共通の要件をより広範に省略して提供するために、最後に順次構築されます。ハンドラーが実際にイベントを受け取る方法は異なる場合があることに注意してください。どのようなハンドラー クラスが利用可能であるかを確認するには、Deeds のドキュメントを参照してください。
maidenプロジェクトには、コア機能を拡張するいくつかのサブシステムが含まれています。
このmaidenプロジェクトには、すぐに使用できる標準クライアントもいくつか含まれています。
最後に、このプロジェクトには、チャット ボットなどの作成に役立つ機能を提供するエージェント モジュールが多数含まれています。これらもすぐに使用できます。