Discord Botを作ってみよう #02 - スラッシュコマンドを使ってみよう

# まえがき

[ひとりアドベントカレンダー](https://qiita.com/advent-calendar/2022/ukwhatn)という狂気の企画、3日目記事です。

# はじめに


この記事(群)は、Pythonと[Pycord](https://pycord.dev/)を利用してDiscord Botを開発しようとする方に向けたものです。

本記事群では、以下の流れでPycordの機能を紹介していきます。

>1. **[#01 - Hello, Pycord!](https://qiita.com/ukwhatn/items/c3abfbbd447f5802afa3)**
>2. **#02 - スラッシュコマンドを使ってみよう**(本記事)
>3. **[#03 - イベントリスナを使ってみよう](https://qiita.com/ukwhatn/items/187b468bd2c9c74fc9be)**
>4. **[#04 - インタラクティブなGUIを作ってみよう]()**
>5. **[#05 - Cogを導入しよう]()**
>6. **[#06 - （番外編)デプロイ環境のはなし]()**

:::note warn
この記事は、対象読者を**Pythonの記法に関する基礎知識はあるが、公式ドキュメントだけで実装できるほど慣れても居ない方**としています。

もしこの記事を読んでもコードの意味がわからない場合は、基礎知識が不足している可能性があります。Paizaなどの入門サイトや技術書を参照しながら、足りない知識を補完していくことを推奨します。
:::

:::note
* **動作確認日:** 2022/12/3
* **各種バージョン**
    * Python: 3.10.7
    * Pycord: 2.3.1
:::

# スラッシュコマンドを導入する

今回は、[前回](https://qiita.com/ukwhatn/items/c3abfbbd447f5802afa3)作ったBotにスラッシュコマンドを導入してみます。

## スラッシュコマンドとは？

普段Discordを使っている方は、`/`を使った時に画像のような選択画面が出るようになっていることにお気づきかもしれません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428968/f7410e3a-ad0b-59cb-2995-6898c46ecd62.png)

これは、「スラッシュコマンド」という機能です。

かつてDiscord Botでコマンドを利用するときは、メッセージをBotが確認して、コマンドの文字列が含まれているかを確認し、含まれていたら対応するメソッドを実行するという流れでした。
つまり、普通のメッセージとコマンドの区別は「prefix(コマンドの前につける文字 `/`とか`!`とかが多かった)があるかないか」などの曖昧なものだったのです。

長らく続いたその時代を終わらせたのが「スラッシュコマンド」の実装でした。
これによって、コマンドをコマンドとして送信できるようになって利便性が向上し、また開発も(ある程度)しやすくなったのです。

## 使ってみよう

[前回](https://qiita.com/ukwhatn/items/c3abfbbd447f5802afa3)のコードに、以下のようなコードを追加します。


```diff
# Botが見える場所でメッセージが投稿された時に動くメソッド
@bot.event
async def on_message(message: discord.Message):
    # メッセージ送信者がBot(自分を含む)だった場合は無視する
    if message.author.bot:
        return

    # メッセージが"hello"だった場合、"Hello!"と返信する
    if message.content == 'hello':
        await message.reply("Hello!")


+ # pingコマンドを実装
+ @bot.command(name="ping", description="pingを返します")
+ async def ping(ctx: discord.ApplicationContext):
+     await ctx.respond("pong")


# Botを起動
bot.run(TOKEN)
```

これを実装したあとのコードは以下の通りです。


<details><summary>コードを表示</summary>

```py
# Pycordを読み込む
import discord

# アクセストークンを設定
TOKEN = "YOUR_ACCESS_TOKEN"  # 自分のアクセストークンと置換してください

# Botの大元となるオブジェクトを生成する
bot = discord.Bot(
        intents=discord.Intents.all(),  # 全てのインテンツを利用できるようにする
        activity=discord.Game("Discord Bot入門"),  # "〇〇をプレイ中"の"〇〇"を設定,
)


# 起動時に自動的に動くメソッド
# #03で詳しく説明します
@bot.event
async def on_ready():
    # 起動すると、実行したターミナルに"Hello!"と表示される
    print("Hello!")


# Botが見える場所でメッセージが投稿された時に動くメソッド
@bot.event
async def on_message(message: discord.Message):
    # メッセージ送信者がBot(自分を含む)だった場合は無視する
    if message.author.bot:
        return

    # メッセージが"hello"だった場合、"Hello!"と返信する
    if message.content == 'hello':
        await message.reply("Hello!")


# pingコマンドを実装
@bot.command(name="ping", description="pingを返します")
async def ping(ctx: discord.ApplicationContext):
    await ctx.respond("pong")


# Botを起動
bot.run(TOKEN)

```
</details>

追加したら、Botを再起動してください。

再起動したあと、Botを参加させたサーバーのメッセージ入力欄に`/`と入力すると、画像のようにpingコマンドが増えているはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428968/f9132d2f-2a19-d6fb-39ad-095b43ede794.png)

これを選択して送信すると、画像のようにBotからpongと返ってきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428968/e2741348-0ce6-9c04-927d-d89e0f1478d4.png)

これでスラッシュコマンドの実装ができました！

## いろいろな使い方

### コマンドを実行したユーザの情報にアクセスする

先程のpingコマンドを少し変えてみましょう。

```py
# pingコマンドを実装
@bot.command(name="ping", description="pingを返します")
async def ping(ctx: discord.ApplicationContext):
    await ctx.respond(f"pong to {ctx.author.mention}")
```

このように変えて再起動したあと、もう一度pingコマンドを利用すると、画像のようにメンションがつきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428968/b4981d50-6185-1ceb-deda-dca65de4dff1.png)

pingメソッドの引数になっている`ctx`は`discord.ApplicationContext`クラスのインスタンスなのですが、ここにはコマンドの実行者`author`や実行された場所`channel`/`guild`など、様々な情報が含まれています。

もし他の情報を使ってみたい場合は、以下の公式リファレンスを参考にしてください。

* [ApplicationContext - Pycord Documentation](https://docs.pycord.dev/en/stable/api/application_commands.html#discord.ApplicationContext)


### 引数をとってみる

以下のように、`ctx`のあとに引数をとることもできます。

```py
@bot.command(name="greeting", description="挨拶を行います")
async def greeting(ctx: discord.ApplicationContext, name: str):
    await ctx.respond(f"Hi, {name}!")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428968/3d7a8ddc-d69a-1ef8-1aa6-8a3efbbc090c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428968/46d30737-f5b0-7d46-3d36-7ba453992f97.png)

また、先程のgreetingメソッドを以下のように変更すると、discord.Userクラスのインスタンスとして引数を受け取れます。
```py
@bot.command(name="greeting", description="挨拶を行います")
async def greeting(ctx: discord.ApplicationContext, user: discord.Option(discord.User, "対象のユーザー")):
    await ctx.respond(f"Hi, {user.mention}!")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428968/049639bf-482a-15a7-4441-c80eba28fc39.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428968/d412b3a3-a59f-2032-5e17-3ccf9c6c536e.png)






今日はここまで！

# このシリーズの他の記事

* **Discord Botを作ってみよう**
    1. **[#01 - Hello, Pycord!](https://qiita.com/ukwhatn/items/c3abfbbd447f5802afa3)**
    2. **#02 - スラッシュコマンドを使ってみよう**(本記事)
    3. **[#03 - イベントリスナを使ってみよう](https://qiita.com/ukwhatn/items/187b468bd2c9c74fc9be)**
    4. **[#04 - インタラクティブなGUIを作ってみよう]()**
    5. **[#05 - Cogを導入しよう]()**
    6. **[#06 - （番外編)デプロイ環境のはなし]()**