DockerとPycordでDiscordbotの作り方

# はじめに
[私が書いた記事](https://qiita.com/nikawamikan/items/1c253ce9b05d7edf1dd0)を参考にDiscordBOTを作ってくれる方にcogsの使い方がわからんなどの話を聞いたりして、現在ほとんどDockerで環境を作ってそこで実行しているのであてになる記事になってないな、ということでDockerを使ってBOTを作成する方法を紹介したいと思った次第。

そんなわけでDockerでの環境構築からBOTの疎通確認レベルのコードを書いてきます。（Docker環境の構築については書きません）
## 前提
OS
- Windos10 (ubuntuなどでもだいたい一緒)


インストールするもの
- DockerDesktop
- Python 3.10
    - py-cord

# BOTの作成
ディレクトリの構成として以下のような状態を想定しています
```
.
┣━ docker-compose.yaml
┣━ Dockerfile
┣━ requirements.txt
┣━ .env
┗━ bot
    ┣━ main.py
    ┗━ cogs
        ┗━ sub.py
```

## Dockerの構成
### Dockerfile
`Dockerfile`はアプリケーションを実行するための環境を定義するものです。
そのため、ここではpythonのイメージを取得して`requirements.txt`に書かれたライブラリを`pip install`するという事が書かれています。
```docker:Dockerfile
FROM python:3.10.5-alpine3.16
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
```
### requirements.txt
`requirements.txt`には必要なライブラリを記述します。
この時バージョンを指定することもできるので、現在ローカルの環境で使われているPycordのバージョンに合わせておくとトラブルが起きにくいです
```shell:requirements.txt
py-cord == 2.2.2
```
### docker-compose.yaml
`docker-compose.yaml`を作成します。
イメージとしては以下のような事をしています。
- Dockerfileのビルドを行いイメージとして利用する
- ディレクトリをボリュームとしてマウント
- 環境変数を渡す
- コマンドの実行
```yaml:docker-compose.yaml
version: '3.8'
services:
  bot:
    # . は現在docker-compose.yamlと同じディレクトリのDockerfileを指しています。
    # 複数のイメージを使う場合などは./botなどディレクトリを指定します。
    build: .
    volumes:
      # ./botのディレクトリを仮想環境の/usr/src/appにマウントする
      # rwと記述することで書き込みも可能なディレクトリとして認識される
      - ./bot:/usr/src/app:rw 
    environment:
      # ${}で囲んだものは.envファイルから環境変数を取得する
      - TOKEN=${TOKEN}
      - GUILDS=${GUILDS}
    command: ["python3", "main.py"]
    tty: true
```
### .env
`.env`ファイルは基本的にはBOTを動かすためのトークンの記述の他に実行するサーバーのIDなども一緒に定義しておきます
```shell:.env
# DiscordDevで発行したトークン
TOKEN="TOKEN" 
# DiscordサーバーのID（カンマ区切りで複数設定できるようにしてます）
GUILDS="000000000,000000000" 
```

## BOTの構成
### main.py
`main.py`ではbotをインスタンス化してcogsからコマンドなどを読み込む処理をします。
コマンドをこのファイルに書くと読みづらくなってしまうので基本書きません。
```py:main.py
import discord
from discord.ext import commands
import os

# docker-composeから渡された環境変数の設定
TOKEN = os.getenv("TOKEN") 
GUILDS = [int(v) for v in os.getenv("GUILDS").split(",")]
# intentsの設定（エラーが出るまで基本defaultで良いです） 
intents = discord.Intents.default()

# debug_guildsは公開BOTの場合は必要ないです
bot = commands.Bot(
    debug_guilds=GUILDS,
    intents=intents
)

# botが動いてるか確認するだけのヤツ
@bot.event
async def on_ready():
    print(f"Bot名:{bot.user} On ready!!")

# cogsディレクトリにあるsub.pyを読み込む処理です
bot.load_extensions(
    "cogs.sub",
    # "cogs.hoge", # 複数読み込む場合は並べてかけます
    store=False # cog読み込み中にエラーが起きた時に止まる様に設定しています。
)
# botの起動
bot.run(TOKEN)
```
コマンドを増やしたい場合などは`cogs`にpythonファイル生やして`load_extensions`で読み込ませるというような使い方になると思います。

### sub.py
`sub.py`では基本的にコマンドを定義していきます。
ここでは`com`というコマンドグループを定義し、その中に`hello`というコマンドを定義しています。
実際に使うときは`com hello name: 名前`のようなコマンドになります
```py:sub.py
import discord
from discord.ext import commands
from discord import SlashCommandGroup, Option, SlashCommandOptionType

# main.pyで読み込ませるためのCogを継承したクラスを作成します。
class SubCog(commands.Cog):
    def __init__(self, bot):
        print("start sub init")
        self.bot = bot

    # comというコマンドグループを定義します
    com = SlashCommandGroup("com", "comコマンドのグループ")

    # comというコマンドグループにhelloコマンドを定義します
    @com.command(name="hello", description="挨拶するだけのコマンド")
    async def hello(
        self,
        ctx: discord.ApplicationContext,
        # name引数を受け取る様に設定します。
        name: Option(
            str,
            description="引数の説明",
        )
    ):
        # 引数の段階だと型がstr型かわかりにくいので注釈をつけてます
        name: str = name 
        # 名前にさんづけしてこんにちはするだけの処理
        await ctx.response.send_message(f"{name}さん、こんにちは！")

# main.pyのload_extensionsのが実行する実際の関数を定義します
def setup(bot):
    bot.add_cog(SubCog(bot)) #add_cog関数にSubCogのインスタンスを渡します
```

# 実行方法など
仮想環境上で先程書いてもらった`main.py`を実行するわけだが、Dockerの場合ほとんどPythonを直接実行するのとイメージは変わらない。

VSCodeなどでターミナルを開き`docker-compose.yaml`があるディレクトリに移動して`docker-compose build`コマンドを打ち込むだけでimageなどが用意され実行環境が整います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1239781/49b76510-ae52-e110-9657-0d1d656e53fb.png)

実行環境が整ったらそのまま`docker-compose up`のコマンドを実行すれば実行がされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1239781/773e8220-aee8-ec4a-54e6-5724b0222046.png)

あとはコマンドを呼べるサーバーで`com hello name: 名前`のように打ち込めばスラッシュコマンドが使える事が確認できると思います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1239781/7b6e081d-9fe0-3fae-8b7a-0100eac23172.png)

# 終わりに
Pycordを使ったPythonのコードに関する部分などは疎通確認レベルのコードしか書いていません。
実際にbotを作成する場合はPycordなどのドキュメントを読んだりなんだりする必要が出てきます。

正直Discord.pyの方が公式で作られており、ドキュメントも日本語化されているのでオススメですが、DockerでBOTを動かす点でいえば一緒なので参考になれば、と思います。