discord.py V2(2.0)のスラッシュコマンドを使えるようにする

最近自分が作っているBotでは、discord.py V2のスラッシュコマンドは使い方がわかりにくかった(元々[interactions.py](https://readthedocs.org/projects/discord-interactions/)を使っていた)ため、discord.pyのフォークの[Disnake](https://disnake.dev)を使っていました。

ですが、discord.py V2が結構普及しているみたいだったので、今回はある程度スラッシュコマンドの書き方を理解して紹介していきます。

## 環境
```
Python 3.10
discord.py 2.1.0
```
## Botの起動
とりあえずBotの起動をしていきます。
今回の目的はdiscord.py V2でスラッシュコマンドを動かすことなため、app_commandsを使っていきます。
```python:bot.py
import discord
from discord import app_commands

TOKEN = ""

intents = discord.Intents.default()#適当に。
client = discord.Client(intents=intents)
tree = app_commands.CommandTree(client)

@client.event
async def on_ready():
    print("起動完了")
    await tree.sync()#スラッシュコマンドを同期

client.run(TOKEN)
```
Botのトークンを入れて起動完了と出力されれば成功です。

## スラッシュコマンドを作ってみる
とりあえず[APIリファレンス](https://discordpy.readthedocs.io/ja/latest/index.html)などを参考に、スラッシュコマンドのコードを書いてみました。
```python:slash_command.py
@tree.command(name="test",description="テストコマンドです。")
async def test_command(interaction: discord.Interaction):
    await interaction.response.send_message("てすと！",ephemeral=True)#ephemeral=True→「これらはあなただけに表示されています」
```
一応、ここまで書きましたが、普通のBotを作るにはまだ足りない部分があります。
例えばTwitterのシャドウバンチェックのBotを作ろうとした時は、optionにTwitterのIDを入力させるでしょう。
そのため、コマンドにオプションを作る必要があります。

## スラッシュコマンドにオプションをつける
Disnakeなどではデコレータにオプションをlist型で指定しますが、どうやらdiscord.py V2はそうではないようです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1933851/718e1055-c52f-1707-344b-6ad937d7b2e5.png" width="600">
一応APIリファレンスで調べたところ、OptionTypeで出てきますが、今回は別の方法でstringやattachmentなどのオプションタイプを指定していきます。
```python:slash_command_string.py
@tree.command(name="test",description="テストコマンドです。")
async def test_command(interaction: discord.Interaction,text:str):
    await interaction.response.send_message(text,ephemeral=True)
```
これはさっきのコードにオプションをつけ足したものです。String型でオプションを作りたかったため、2行目の引数textにstr型を指定しました。
このように、引数に型指定することでOptionTypeを指定することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1933851/03282a3e-a6af-865b-56b4-ef9b0af2aa1c.png)

型を指定するときに使う型は以下の通りです。
| OptionType | discord.pyでの型 |
|:------------:|:------------:|
|String(文字列)|str| 
|Integer(整数)|int|
|bool<br>(True,False)|bool|
|Channel|discord.TextChannel<br>discord.VoiceChannel|
|Member|discord.Member|
|Role|discord.Role| 
|Attachment(添付ファイル)|discord.Attachment| 

Disnakeなどでいう、required=False(required=Trueで、オプションを必須にする)は、discord.pyでは引数にデフォルト値を設定することでできます。
```python:required=False.py
@tree.command(name="test",description="テストコマンドです。")
async def test_command(interaction: discord.Interaction,text:str="てすと"):#デフォルト値を指定
    await interaction.response.send_message(text,ephemeral=True)
```

### Embedにスラッシュコマンドで送った画像を表示させる
添付ファイルはdiscord.Attachmentという型なため、引数.urlという感じで画像のURL(直リンク)を取得します。
画像を添付したら画像をEmbedに表示させるコードがこちらです。
```python:pictureEmbed.py
@tree.command(name="test",description="テストコマンドです。")
async def test_command(interaction: discord.Interaction,picture:discord.Attachment):
    embed=discord.Embed(title="画像",color=0xff0000)
    embed.set_image(url=picture.url)#URLでEmbedに画像を貼る
    await interaction.response.send_message(embed=embed)
```
![実行結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1933851/e985fb56-645b-a6dd-62ec-4c80b05299ac.png)

## おまけ
### コマンドのオプションの詳細を書く
`@app_commands.describe`を使います。
<details><summary>サンプルコード1</summary>

```python:describe.py
@tree.command(name="test",description="テストコマンドです。")
@app_commands.describe(picture="ここに画像をアップロード")#使われている引数の名前="詳細"
async def test_command(interaction: discord.Interaction,picture:discord.Attachment):
    embed=discord.Embed(title="画像",color=0xff0000)
    embed.set_image(url=picture.url)
    await interaction.response.send_message(embed=embed)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1933851/0195a9fc-1f55-3c81-a06f-35b5267d67e6.png)
</details>
<details><summary>サンプルコード2</summary>

```python:describe2.py
@tree.command(name="test",description="テストコマンドです。")
@app_commands.describe(picture="ここに画像をアップロード",picture2="2つ目の画像をアップロード")
async def test_command(interaction: discord.Interaction,picture:discord.Attachment,picture2:discord.Attachment):
    await interaction.response.send_message(f"{picture.url}\n{picture2.url}")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1933851/2ef4def9-85e8-78d3-82c3-b171da95de67.png)

</details>

### 管理者権限持ちのユーザーのみコマンドを実行できるようにする
`@app_commands.default_permissions(administrator=True)`を使います。
<details><summary>サンプルコード</summary>

```python:permission.py
@tree.command(name="test",description="Botを停止。管理者権限必須")
@app_commands.default_permissions(administrator=True)
async def test_command(interaction:discord.Interaction):
    await interaction.response.send_message("Botを停止します。",ephemeral=True)
    await client.close()
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1933851/2ccebdab-6de7-e03e-c8b2-bd7a1e03e76e.png" width="450">
</details>

## 最後に
今回はdiscord.pyでスラッシュコマンドの使い方を紹介しました。
AutoCompleteとかも使えそうだったのでdiscord.pyもいい感じに使えそうだと感じました。