Common Lisp で Discord Bot (Lispcord)

　Discord の Bot を Common Lisp で作れないかということで、 [Lispcord](https://github.com/lispcord/lispcord/) という Common Lisp で Discord Bot の作れるライブラリがあります。しかし、あまりまとまった情報がなかったのでまとめてみました。
　間違っているところがあれば教えていただければ幸いです。

# 導入
　Quick Lispを使って

```lisp
(ql:quickload :lispcord)
```
# Bot の作成等
　Bot 自体の作成方法はまとまった情報がすでにあるので、そちらを参照していただくことにして、 Lispcord での必要な作業についてまとめます。

```lisp
;とりあえずパッケージ

(defpackage :discord-bot
  (:use :cl :lispcord))
(in-package :discord-bot)

;Bot の定義 (TOKEN はダミーです、実際のものを入れてください)
(defbot *bot* "70X3И0§£']['ë|)4+73.05!3I2|<4")

;Bot の接続
(connect *bot*)

;Bot の切断
(disconnect *bot*)
```

# 返信等
　とりあえず、 `$link` って打つと [Quick Lisp](https://www.quicklisp.org) のリンクを返す Bot のコードです。 ()

```lisp
(ql:quickload :lispcord)

(defpackage :discord-bot
  (:use :cl :lispcord))
(in-package :discord-bot)

;Bot の定義 (TOKEN はダミーです、実際のものを入れてください)
(defbot *bot* "70X3И0§£']['ë|)4+73.05!3I2|<4")
(connect *bot*)


(add-event-handler :on-message-create
                   (lambda (msg)
                     (when (equal (lc:content msg) "$link")
                           (reply msg "https://www.quicklisp.org/")))
```

　`(add-event-handler :on-message-create 関数)`を実行すると、 Bot が入っているサーバーでメッセージが送信されるたびに`msg`を引数にその関数が呼び出されるようになります。`(lc:contents msg)`とすることでメッセージの本文が帰ってくるのでそれが`"$link"`だったら返信します。( `addevent-handler` で複数回関数を登録したら、一番最後に登録した関数だけ実行され、前に登録された関数は実行されないことに注意 )
　`(reply msg contents)` とすることでメッセージが送られたチャンネルに `contents` を送信できます。
　よく使う関数について簡単に見ていきます。

## メッセージの送信
　メッセージの送信に使う関数についてです。

### lc:make-message
　文字列だけでなく、 Embed を埋め込んだりしたメッセージを作成します。 `(lc:make-message <本文> :embed <embed> :tts <tts> :file <file>)` のように使います。( `embed`, `tts`, `file` は必要に応じてつける ( つまり `&key` ) です。) 

```lisp
(reply <msg> (lc:make-message <本文> :embed <embed>))
```

といった感じで使うことができます。

### create
　`(create <content> (from-id <id> :channel))` とすることで、`<content>` を `<id>` のチャンネルに送信できます。`(lc:channel-id <msg>)` とすることでメッセージが送信されたチャンネルを調べることができ、 `reply` はそれを使って実装されています。

### reply
　`(reply <msg> <content>)` メッセージが送信されたチャンネルに `<content>` を送信します。

## メッセージからの取得
　メッセージからチャンネルなどの取得について。

### lc:content
　`(lc:content msg)` とすることで、メッセージの本文を取得できます。

### lc:channel-id
　`(lc:channel-id)` とすることで、メッセージが送信されたチャンネルの id を取得できます

## Embed 関係
　便利な Embed の送信方法についてです。細かいところは説明しないので、下記の記事などを読んどくと良いかもしれません。

* [Discord.jsでembed (埋め込みメッセージ) を扱う](https://qiita.com/nedew/items/4e0c20c1a89e983a6992)
* [Discord.pyのEmbedにできることあれこれ(メモ)](https://qiita.com/Poteto143/items/bbc61d3adf9b6b72f75f)

### lc:make-embed

```lisp
(lc:make-embed :title <title> :type <type> :description <description> :url <url> :timestamp <timestamp> :color <color> :footer <footer> :image <image> :thumbnail <thumnail> :video <video> :provider <provider> :author <author> :fields <fields>)
```
 のようになっています。

### Embed の送信の例
　関数じゃないですが、まあ例です。`$quicklisp` を送ると [Quick Lisp](https://www.quicklisp.org/) のリンクが入った Embed を送信します。

```lisp
(ql:quickload :lispcord)

(defpackage :discord-bot
  (:use :cl :lispcord))
(in-package :discord-bot)

;Bot の定義 (TOKEN はダミーです、実際のものを入れてください)
(defbot *bot* "70X3И0§£']['ë|)4+73.05!3I2|<4")
(connect *bot*)


;Embed の作成
(defparameter *embed-quicklisp* (lc:make-message ""
                                                 :embed (lispcord.classes:make-embed :title "Quick Lisp"
                                                                                     :description "[Quick Lisp](https://www.quicklisp.org/)")))

;$quicklisp が送られたら Embed を送信するようにする。
(add-event-handler :on-message-create
                   (lambda (msg) 
                     (when (equal (lc:content msg) "$quicklisp")
                       (reply msg *embed-quicklisp*))))
```

　実際に使ってみると下のような感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599537/0148e1bc-1b66-06d3-f62e-4ea4b4b544fc.png)

# 本格的な Bot の作成
　`コマンド 引数等` を送信すると、コマンドを Hash Table に保存して実行できるようにしてみました。

```lisp
(ql:quickload :lispcord)
(ql:quickload :cl-ppcre)

(defpackage :discord-bot
  (:use :cl :lispcord))
(in-package :discord-bot)

;Bot の定義 (TOKEN はダミーです、実際のものを入れてください)
(defbot *bot* "70X3И0§£']['ë|)4+73.05!3I2|<4")
(connect *bot*)

;コマンドの Hash Table の定義
(defparameter *commands* (make-hash-table :test #'equal))

(add-event-handler :on-message-create
                   (lambda (msg)
                     ;cl-ppcre:split で分割する
                     (let ((str (cl-ppcre:split " " (lc:content msg))))
                       ;コマンドが Hash Table に登録されていれば実行する。
                       (when (gethash (first str) *commands*)
                         (funcall (gethash (first str) *commands*) msg str)))))

;Embed の定義 (先程のものと同じ)
(defparameter *embed-quicklisp* (lc:make-message ""
                                                 :embed (lispcord.classes:make-embed :title "Quick Lisp"
                                                                                     :description "[Quick Lisp](https://www.quicklisp.org/)")))

;コマンド "$quicklisp" を Hash Table に登録
(setf (gethash "$quicklisp" *commands*)
      #'(lambda (msg str)
          (reply msg *embed-quicklisp*))) 

;コマンド "$quicklisp" を Hash Table に登録
(setf (gethash "$link" *commands*) 
      #'(lambda (msg str) 
          (reply msg "https://www.quicklisp.org/")))
```

# おわりに
　まだ Lispcord の機能のほんの一部しかまとめてないので、少しずつ内容を増やしていく予定です。また、間違い等があったら、コメントでの指摘や編集リクエストをよろしくおねがいします。ここまで読んでいただきありがとうございます。