Discord Application Commands 非公式日本語版リファレンス その1

# はじめに

はじめまして。@nahiro_tusです。

この記事は、Discordのアプリケーションコマンドのドキュメントを読むのが大変だったので、自分なりにまとめてみた記事になります。

原文は英語なので、DeepLなどを活用しながら少しずつ読み進めていきました。もし間違っている個所や表現がおかしい部分などがありましたらコメントを頂ければ修正します。
今後Discordのアプリケーションコマンドを作ってみたい方や作る予定の方、現在開発をしている方の参考になれば幸いです。
この記事は以下のページを参考にして作っています。

https://discord.com/developers/docs/interactions/application-commands

# Application Commandsとは

Application Commandsを一言で表すなら、「ユーザーのアクションをトリガーとして処理を開始してくれる機能」です。これだけだと少々わかりにくいので、今までのDiscord botと比較してみましょう。

従来のDiscord botの構成は以下のようになっていました。

![従来のbotサーバ.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1586001/cc498fd0-6a23-cc9b-bb16-f4e39603d115.png)


botが各Discordサーバーにbotユーザとしてログインし、コマンド受け付け状態になります。この状態でbotに登録されたコマンドをユーザーが**手動で**入力すると処理が開始されます。

:::note info
**Discordサーバーの呼び方について**
開発者ドキュメントではDiscordサーバーのことを「**ギルド**」と呼んでいるので、以降はその呼び方で統一します。
:::


この構成のデメリットとして、

- botが常時接続状態である必要がある
- コマンドの構文やパラメータはユーザが覚えている必要がある

が挙げられます。
また、bot用サーバ側で構文解析を行っている構成上、送信したメッセージはすべてbot用サーバに送信されます。

Application Commandsでは、これらのデメリットが解消されています。

![appbotの構成.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1586001/282e17d3-3e0d-9ee0-9330-2c6d99af4c8a.png)


Application Commandsのコマンドが実行されると、Discord botにあらかじめ設定されたエンドポイントへリクエストを実行します。その後、ギルドへHTTPレスポンスを返したりバックエンドからDiscord APIへのリクエストを行ったりすることにより、メッセージを返信したりギルドの情報を更新したりできます。

従来のDiscord botと比較すると

- bot用サーバーが不要になった
    - botがDiscordに常時接続する必要がなくなったため、bot用のサーバを立てる必要がなくなった
    - Discord botに設定したAPIエンドポイントにリクエストするので、サーバーレス構成も容易
- コマンドを暗記が不要になった
    - スラッシュコマンドではコマンドが補完されるため、コマンドを覚える必要がなくなった
    - さらに、入力値を選択式にできるので、入力するパラメータを制限できる
- コマンドの入力内容のみが送信されるので、セキュリティ的にも安心

などのメリットがあります。

# Application Commandsの種類

Application Commandsは3種類あります。

- Slash Commands
- User Commands
- Message Commands

それぞれ簡単に解説します

## Slash Commands

おそらく最もオーソドックスなコマンド。別名`CHAT_INPUT`。

メッセージ入力欄に`/`を入力するとSlash Commandが入力できます。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1586001/89f8c064-05ba-9f28-0fff-13538099f5b9.png)

すでにビルドインで何種類か用意されており、テキストを強調するコマンドやちゃぶ台返しをするコマンドが使えるようになっています。

ちなみにちゃぶ台返しをするコマンドでは以下のようなメッセージを作れます。

```
テスト勉強なんてやってられんわ！ (╯°□°)╯︵ ┻━┻
```

テスト勉強はちゃんとしましょう。

## User Commands

別名`USER`。ユーザーを右クリック(もしくはタップ)すると表示されるUIベースのコマンドです。

![スクリーンショット 2023-07-11 224145 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1586001/b91b1a67-102f-ac3c-25b7-c600ec28997b.png)


ユーザーに関係する処理を実行するときに便利です。特定のユーザーにロールをまとめてつけたり、ユーザー情報を自分に送るときなどに使えそうです。

## Message Commands

別名`MESSAGE`。メッセージを右クリック(もしくはタップ)すると表示されるUIベースのコマンドです。

![Untitled (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1586001/25a5d907-fbb1-fb65-baff-46bdaae0a9ec.png)


メッセージに関係する処理を実行するときに便利です。メッセージをブックマークしたり、メッセージを翻訳するbotなどが作れそうです。

# コマンドの登録・更新・削除

コマンドは**HTTPエンドポイント経由でのみ登録することができます**。画面から登録することはできません。

## コマンドのスコープ
コマンドのスコープには**グローバル**と**ギルド**の2種類があります。
### グローバルスコープ
アプリケーションを追加したすべてのギルドで使用可能です。
また、自分と相手が同じギルドに属しており、そのギルドにグローバルスコープのコマンドを持つアプリがある場合、DM(ダイレクトメッセージ)でもそのコマンドを使用することができます。
### ギルドスコープ
そのコマンドを使用できるのは作成時に指定したギルドのみです。
また、DMで使用することもできません。

## 制約

コマンド名は、アプリケーションごと、タイプごと、各スコープごとに一意ある必要があります。以下は公式ドキュメントに書かれていた具体例です。

- 同じ名前の2つのグローバル`CHAT_INPUT`コマンドを含めることはできない

```
アプリケーション

スコープ：グローバル
タイプ：CHAT_INPUT
コマンド1:sample

スコープ：グローバル
タイプ：CHAT_INPUT
コマンド2:sample
```

- 同じギルドで同じ名前の2つのギルド`CHAT_INPUT`コマンドを含めることはできない

```
アプリケーション

スコープ：ギルドA
タイプ：CHAT_INPUT
コマンド名：sample

スコープ：ギルドA
タイプ：CHAT_INPUT
コマンド名：sample
```

- 同じ名前のグローバル`USER`コマンドを含めることができない

```
アプリケーション

スコープ：グローバル
タイプ：USER
コマンド名：sample

スコープ：グローバル
タイプ：USER
コマンド名：sample
```

- 同じ名前のグローバル`CHAT_INPUT`コマンドとギルド`CHAT_INPUT`コマンドを含めることができる

```
アプリケーション

スコープ：グローバル
コマンド名：sample

スコープ：ギルドA
コマンド名sample
```

- 同じ名前のグローバル`CHAT_INPUT`コマンドとグローバル`USER`コマンドを含めることができる

```
アプリケーション

スコープ：グローバル
タイプ：CHAT_INPUT
コマンド名：sample

スコープ：グローバル
タイプ：USER
コマンド名：sample
```

- 複数のアプリに同じ名前のコマンドを含めることができる

```
アプリケーションA
コマンド名：sample

アプリケーションB
コマンド名：sample
```

つまり、**アプリケーション名・スコープ・タイプ・コマンド名の4つのパラメータの組み合わせがユニーク**であればコマンドを登録することができます。

また、1つのアプリケーションあたりに登録できるコマンドの数はタイプごとに決まっています。

`CHAT_INPUT`

- 100個の(グローバルコマンド+ギルドコマンド)

`USER`

- 5個の(グローバルコマンド+ギルドコマンド)

`MESSAGE`

- 5個の(グローバルコマンド+ギルドコマンド)

:::note warn
**登録リクエスト上限**
1ギルドあたり、1日に登録できるコマンド数は200件まで
:::

## コマンドの登録方法

ここからはコマンド登録の具体的な方法について書いていきます。コマンドの登録は、特定のエンドポイントに対して`POST`メソッドでリクエストすると登録することができます。
パラメータの詳細については次回解説するので、ひとまず以下の内容を覚えておけば大丈夫です。
- `name`はコマンド名
- `type`はコマンドの種類
- `description`はコマンドの説明
- `option`は`CHAT_INPUT`固有のパラメータ
### グローバルコマンドの作成

グローバルコマンドはapplicationを招待したすべてのサーバーで使用可能なコマンドです。グローバルコマンドは登録時に読み取り修復機能処理が走ります。グローバルコマンドを更新する前に内部で整合性判定を行い、万が一チェックに失敗した場合はそのコマンドの更新が拒否されます。

登録するためには以下のようなHTTPリクエストを送信します。

```python:add_command.py
import requests

my_application_id = <作成したdiscord app のID>

# credentioals_token か app_tokenのどちらかを宣言
my_bot_token = <作成したdiscord app のトークン>
# my_credentials_token = <applications.commands.update の権限を付与したcredentials_token>

url = f"https://discord.com/api/v10/applications/{my_application_id}/commands"

# 以下はスラッシュコマンドを登録するときの例です
json = {
    "name": "blep",
    "type": 1,
    "description": "Send a random adorable animal photo",
    "options": [
        {
            "name": "animal",
            "description": "The type of animal",
            "type": 3,
            "required": True,
            "choices": [
                {
                    "name": "Dog",
                    "value": "animal_dog"
                },
                {
                    "name": "Cat",
                    "value": "animal_cat"
                },
                {
                    "name": "Penguin",
                    "value": "animal_penguin"
                }
            ]
        },
        {
            "name": "only_smol",
            "description": "Whether to show only baby animals",
            "type": 5,
            "required": False
        }
    ]
}

headers = {
    "Authorization": f"Bot {my_bot_token}"
}

# credentials tokenを使用する場合はこのコメントアウトを外して、bot tokenのヘッダーをコメントアウトする
# headers = {
#     "Authorization": f"Bearer {my_credentials_token}"
# }

r = requests.post(url, headers=headers, json=json)
```

コードを実行すると、以下のようなスラッシュコマンドが登録されます

![Untitled (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1586001/4c76dbe7-5a73-9b4d-b2ad-517c8154d481.png)


### ギルドコマンドの作成

ギルドコマンドは作成時に指定したギルドでのみ使用できるコマンドです。ギルドコマンドはグローバルコマンドとは違い、読み取り修復機能はありません。そのため、リクエストをすると**即時に設定が反映されます**。公式ドキュメントでは、開発やテストはギルドコマンドで実装し、一般公開時にグローバルコマンドへ変更することを推奨しています。

```python:add_command.py
import requests

# ここに変更が入っている
url = "https://discord.com/api/v10/applications/<my_application_id>/guilds/<guild_id>/commands"

# This is an example USER command, with a type of 2
json = {
    "name": "High Five",
    "type": 2
}

# For authorization, you can use either your bot token
headers = {
    "Authorization": "Bot <my_bot_token>"
}

# or a client credentials token for your app with the applications.commands.update scope
headers = {
    "Authorization": "Bearer <my_credentials_token>"
}

r = requests.post(url, headers=headers, json=json)
```

`type:2`を指定すると`USER`コマンドを登録することができます。

このコマンドを実行すると、ギルド`USER`コマンドが登録されます。

![スクリーンショット 2023-07-11 224145 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1586001/194c4c09-f0ca-2841-693c-b40dc4c543e6.png)


## コマンドの更新方法

コマンドの更新と削除には`PATCH`メソッドと`DELETE`メソッドを使います。

以下のエンドポントへ`PATCH`メソッドでリクエストすると更新、`DELETE`メソッドでリクエストすると削除が行えます。

- グローバルコマンドの場合

`https://discord.com/api/v10/applications/<my_application_id>/commands/<command_id>`

- ギルドコマンドの場合

`https://discord.com/api/v10/applications/<my_application_id>/guilds/<guild_id>/commands/<command_id>`

また、コマンドはアプリケーション名、スコープ、名前、タイプで識別されているため、`POST`メソッドは`upsert`(データが存在しなければ追加(insert)、存在すれば更新(update))として処理されます。
そのため、アプリケーション内ですでに存在するスコープ、名前、タイプの組み合わせに対して`POST`でリクエストをすると、`PATCH`でのリクエストと同じ処理になります。

# まとめ

今回はDiscord Application Commandsの基本情報と種類、具体的な登録方法について紹介しました。Application Commands のリファレンスはボリュームが大きいので、複数回に分けて紹介するつもりです。今のところ全3回を予定しているので、続きが気になる方は「いいね」を押してもらえるとうれしいです。(「いいね」が多いと執筆スピードが上がります。)

次回はApplication Commandsの各パラメータの解説と、コマンドの種類ごとの特徴について書きたいと思っています。

お楽しみに。