discord.py でCog・Extensionを使ってみよう！

# はじめに
こんにちは、[@nano_sudo](https://twitter.com/nano_sudo)です。
discord.pyでBotを作るとき、コードが長くなってしまうことがあります。
そんなときに便利なのが、Cog・Extensionです。
今回はdiscord.pyのCog・Extensionの使い方を説明します。

# 前提知識
- pythonの基礎的な文法(関数やクラスなど)

# Cogとは
Botを機能ごとに分けることができる機能です。
以下はdiscord.py公式ドキュメントの説明です。
> Bot開発においてコマンドやリスナー、いくつかの状態を一つのクラスにまとめてしまいたい場合があるでしょう。コグはそれを実現したものです。([discord.pyドキュメント](https://discordpy.readthedocs.io/ja/latest/ext/commands/cogs.html))
## Cog・Extensionを使うメリット
- 機能のグループ化
	Cogを使用することで、Botの機能をグループに分けることができる。
	(例:音楽コマンドや管理コマンドなど)
- 再利用性の向上
	Cogをコピーすれば、他のボットに同様の機能を簡単に実装することができる。
- 柔軟性
	Extensionを利用するとCogの有効化や無効化を簡単に行えるので、必要に応じてBotの動作を変更することができる。
- 開発の快適性
	Extensionにはホットリロード機能がついているので、開発がより快適にできるようになる。
	
# Cogの最小構成
コマンドは従来の`commands.command()`ではなく、`app_commands.command()`を使います。
`app_commands`では、discordクライアントにコマンドの候補を表示することができます。
## Cogを分ける基準
機能ごとに分けるのがおすすめです。
例:音楽コマンド、管理コマンドなど
## ファイル構成
```
.
├── main.py
└── cogs
    └── hello.py
```


```python:cogs/hello.py
from discord.ext import commands
from discord import app_commands

# commands.Cogを継承する
class MyCog(commands.Cog):
    def __init__(self, bot)
        self.bot = bot
    
    # イベントリスナー(ボットが起動したときやメッセージを受信したとき等)
    @commands.Cog.listener()
    async def on_ready(self):
        print("Cog ready!")
	
    # コマンドデコレーター(descriptionで説明が書ける)
    @app_commands.command(name="hello")
    async def hello(self,interaction:discord.Interaction)
        await interaction.response.send_message("hello!")
```
# コードの解説
## Cogの定義
```python
class Hello(commands.Cog):
```
`commands.Cog`を継承することで、Cogを定義することができます。
## コンストラクター
```python
def __init__(self,bot)
    self.bot = bot
```
:::note
コンストラクターには必ず`bot`引数を入れてください。
:::
## イベントリスナー
```python
@commands.Cog.listener()
async def on_ready(self):
    print("Cog ready!")
```
`@commands.Cog.listener()`デコレーターをつけることで、イベントリスナーを定義することができます。
ほかにも、`on_message`や`on_member_join`などのイベントリスナーがあります。
## コマンドデコレーター
```python
@app_commands.command(name="hello")
async def hello(self,interaction:discord.Interaction)
    await interaction.response.send_message("hello!")
```
`@app_commands.command()`デコレーターをつけることで、コマンドを定義することができます。
`description`引数でコマンドの説明を書くことができます。
`name`引数と関数名が一致していないと、エラーが発生するので注意です。
# Cogの有効化
```python:main.py
from discord.ext import commands
from cogs.hello import MyCog


# Botの定義
class MyBot(commands.Bot):
    def __init__(self):
        super().__init__(
            command_prefix="!",
            intents=discord.Intents.all()
        )

    async def setup_hook(self) -> None:
        guild_ids = [123456789012345678] # すぐに同期したいサーバーのIDを入れる
        await self.tree.sync(guild=None)
        for g in guild_ids:
            try:
                await self.tree.sync(guild=g)
            except discord.errors.Forbidden:
                # やりすぎるとForbiddenになるので、一応例外処理を入れておく
                print(f"サーバーID:{g}に登録できませんでした。")

bot = MyBot()

if __name__ == "__main__":
    # Cogを有効化
    bot.add_cog(MyCog(bot))
    # Botを起動
    bot.run("token")
```
コマンドを使うには、`Bot().tree.sync()`で一度同期する必要があります。
グローバルにコマンドを同期するには、`guild=None`にします。
私はいつも、`setup_hook`で同期していますが、ほかに良い方法があればぜひ教えてください!
`bot.add_cog()`でCogを使ってCogを有効化することができます。
# ExtensionからCogを有効化
Extension経由で読み込むと、ホットリロードや有効化・無効化が簡単にできるので、おすすめ

:::note alert
extensionを使用して読み込む場合は、Cogに`setup`関数を定義してください。
:::
```python:cogs/hello.py
# 以下をファイルの最後に追加
async def setup(bot):
    await bot.add_cog(MyCog(bot))
```
main.pyの`if __name__ == "__main__"`以下をこのように変更してください。
```python:main.py
# 以下をおきかえる
if __name__ == "__main__":
    # Extensionを有効化
    await bot.load_extension("cogs.hello")
    # Botを起動
    bot.run("token")
```

# まとめ
いかがでしたか？
今回はdiscord.pyのCog・Extensionの使い方を説明しました。
discord.pyを使ってBotを作るときは、ぜひCog・Extensionを使ってみてください！
質問やご意見・ご指摘などあれば、[Twitter](https://twitter.com/nano_sudo)かコメント欄にお願いします！

# Zennにも投稿しています！
https://zenn.dev/nano_sudo/articles/a00db1a55d6c4c