C++でdiscord botを作る

# C++でdiscord botを作る

この記事は[アドカレ](https://qiita.com/advent-calendar/2023/hachinohe_kosen)に参加しています。

## C++でdiscord bot
C++でdiscord botを作成するような日本語情報を見つけれなかったので、記事にすることにしました。

## D++
C++で一からdiscord botの処理を作るのは大変なので、[D++](https://dpp.dev/)というライブラリを使用します。基本的にdiscordjsなどと同じようにコードを書けますが、若干の違いもあるので注意が必要です。

D++のすべてのAPIの説明は[公式サイト](https://dpp.dev/)にあります。

## botの登録
[Discord Developer Portal](https://discord.com/developers/applications)でbotを登録します。トークンの取得とか、botに渡す権限やサーバーに招待する方法などについては、この記事では触れません。この辺はJavaScriptだろうがC++だろうが変わらないからです。

## 環境構築
C++でD++を使用するための環境構築について説明します。Visual Studio 2022を使用することを前提として説明しますが、他のコンパイラでもやることは殆ど変わらないはずです。

~~ライブラリをダウンロードしてパスを繋ぐだけなので、ご存じの方は読み飛ばしてください~~

### 1. ライブラリのダウンロード
まずは、[D++公式サイト](https://dpp.dev/)からコンパイル済みのライブラリをダウンロードします。

Visual Studio 2022を使用する場合には、以下の二つをダウンロードします。リリースビルドの際は`release build`の方にあるファイルを、デバックビルドの際は`debug build`の方にあるファイルを使用します。
**x64 Windows (64 bit vs2022 release build)**
**x64 Windows (64 bit vs2022 debug build)**

ダウンロードできたらzipを解凍しておきましょう。

### 2. プロジェクトにD++のパスを繋ぐ
Visual StudioのプロジェクトにD++のパスを繋ぎます。プロパティページを開いたら、以下の設定を行っていきます。

**・構成プロパティ→全般→C++ 言語標準**
C++ 17以上にします。(D++はC++ 17で開発されているため)

**・C/C++→全般→追加のインクルード ディレクトリ**
ダウンロードしたファイルの中にある`dpp.h`というファイルのあるフォルダのパスを追記します。

**・リンカー→全般→追加のライブラリ ディレクトリ**
ダウンロードしたファイルの中にある`dpp.lib`というファイルのあるフォルダのパスを追記します。

**・リンカー→入力→追加の依存ファイル**
`dpp.lib`を追記します。

### 3. dllをexeと同じフォルダに置く
これはコンパイル後の話になりますが、コンパイルしたexeを実行するには以下の6つのdllをexeと同じフォルダに置く必要があります。
・`dpp.dll`
・`libcrypto-1_1-x64.dll`
・`libsodium.dll`
・`libssl-1_1-x64.dll`
・`opus.dll`
・`zlib1.dll`

## プログラム
D++を使用できる環境が整ったら、コードを書いていきます。全体コード(プログラム例)の内容をつまんで解説していくので、最初に全体コードを見た方がいいかもしれないです。

### (0) ヘッダー
まずはヘッダーを`#include`します。
```c++
#include "dpp/dpp.h" //(0)
```

### (1) bot操作用のオブジェクト
基本的に`dpp::cluster`を使用してdiscord botを操作します。
コンストラクタではbotのトークンと、インテントを指定します。botのトークンは[Discord Developer Portal](https://discord.com/developers/applications)で入手することができます。インテントは、botが実行できる権限に関するフラグです。
```c++
//bot操作用のオブジェクト (1)
dpp::cluster bot(BOT_TOKEN, dpp::i_default_intents | dpp::i_message_content);
```

### (2) ログ表示
`bot.on_log()`に`dpp::utility::cout_logger()`を渡すことで、botのログ情報をD++がコンソールに表示してくれるようになります。デバッグなどをする際に役に立ちます。
```c++
//logをコンソールに (2)
bot.on_log(dpp::utility::cout_logger());
```

### (3) 最初に呼ばれるイベント
botを起動してから最初に呼ばれるイベントが`ready`です。`ready`で実行したいプログラムを`bot.on_ready()`に関数で渡します。可読性が上がるので、ラムダ式で書くのがおすすめです。
```c++
//最初に呼ばれるイベント (3)
bot.on_ready([&bot](const dpp::ready_t& event) {
    std::cout << "ログインしました" << std::endl;
});
```

### (4) メッセージが来た場合の操作
discordにいるユーザーによってメッセージが生成された際には`message_create`イベントが発生します。メッセージが生成されたときに何か処理をしたい場合には、`bot.on_message_create()`に実行したい関数を渡します。

以下のプログラム例では、`チャンネルid`に投稿されたメッセージが`!!ping`の場合には`Pong!`とbotがリプライします。`!!destroy`の場合には、`flag`変数に`false`を代入しています。
```c++
//メッセージが来た場合の操作 (4)
bot.on_message_create([&flag, &bot](const dpp::message_create_t& event) {
    if (event.msg.channel_id == チャンネルid) {
        if (event.msg.content == "!!ping") {
            event.reply("Pong!", true); //(5)
        }
        else if (event.msg.content == "!!destroy") {
            flag = false; //(6)
        }
    }
});
```

### (5) メッセージに返信
メッセージに返信するには、`dpp::message_create_t`の`reply`メソッドを使用します。
```c++
event.reply("Pong!", true); //(5)
```

### (6) `flag`の意味
(8)によるループ文から分かる通り、`flag`が`true`の間はbotが動き続けます。`flag`が`false`になると、botは停止します。
```c++
flag = false; //(6)
```

### (7) botをスタートさせる
各種イベントに実行したい関数を設定したら、`bot.start()`でbotの処理をスタートさせます。`bot.start()`に`true`を渡すと、すぐに処理が戻ってきますが、`false`を渡すと`bot.start()`の中で無限ループに入るので上手く使い分ける必要があります。
```c++
//botをスタートさせる (7)
bot.start(true);
```

### (8) 終了まで待機
スタートしたbotは、各種イベントが呼ばれるまでやることがありません。`bot.start()`に`true`を指定した場合には、すぐに処理が戻ってきてしまうため、時間つぶしをする必要があります。
```c++
//終了まで待機 (8)
while (flag) {
    Sleep(1000);
}
```

### (9) dpp::clusterのデストラクタが呼ばれる
`dpp::cluster`がスコープを抜けると、デストラクタが呼ばれます。デストラクタが呼ばれると、botの動作は停止します。
```c++
{
    dpp::cluster bot(BOT_TOKEN, dpp::i_default_intents | dpp::i_message_content);
} //dpp::clusterのデストラクタが呼ばれる //(9)
```

ここで注意したいのは、botを停止したいときに明示的に`shutdown()`を呼んではいけないことです。`shutdown()`を呼ぶと、内部のオブジェクトが`delete`されるのですが、その後にデストラクタでも`shutdown()`が呼ばれてしまいます。つまり、`shutdown()`を呼ぶと同じポインタを二回`delete`してしまうことになってしまいます。
https://github.com/brainboxdotcc/DPP/blob/master/src/dpp/cluster.cpp

こういうバグを引き起こさないためにも、皆さんはちゃんと`delete`したら`nullptr`を詰めるようにしましょうね。

## プログラム例
Visual Studio 2022 C++ 17 でコンパイルと実行を確認しました。
```main.cpp
/*

main.cpp

*/

#include <iostream>
#include <string>
#include "dpp/dpp.h" //(0)

const std::string BOT_TOKEN = "";

int main() {
    std::cout << "start" << std::endl;

    {
        bool flag = true;

        //bot操作用のオブジェクト (1)
        dpp::cluster bot(BOT_TOKEN, dpp::i_default_intents | dpp::i_message_content);

        //logをコンソールに (2)
        bot.on_log(dpp::utility::cout_logger());

        //最初に呼ばれるイベント (3)
        bot.on_ready([&bot](const dpp::ready_t& event) {
            std::cout << "ログインしました" << std::endl;
        });

        //メッセージが来た場合の操作 (4)
        bot.on_message_create([&flag, &bot](const dpp::message_create_t& event) {
            if (event.msg.channel_id == チャンネルid) {
                if (event.msg.content == "!!ping") {
                    event.reply("Pong!", true); //(5)
                }
                else if (event.msg.content == "!!destroy") {
                    flag = false; //(6)
                }
            }
        });

        //botをスタートさせる (7)
        bot.start(true);

        //終了まで待機 (8)
        while (flag) {
            Sleep(1000);
        }
    } //dpp::clusterのデストラクタが呼ばれる //(9)

    std::cout << "end" << std::endl;
    system("pause");
    return 0;
}
```

## 参考文献
・[D++: A C++ Discord API Library for Bots](https://dpp.dev/)

## むすび
C++でdiscord botを作る話でした。