discord.pyのCogとExtensionを理解する

# はじめに
discord.pyでext.commandsフレームワークを使い始めたときに、Cogを使おうと思っても、うまくいかない人が多いです。そんな人のために、解説をしていきたいと思います。

追記: discord.py 2.0aの、extension/cogの読み込みが非同期になるというアップデートに対応しました。

# Cogとは？
[Cogの解説](https://discordpy.readthedocs.io/ja/latest/ext/commands/cogs.html)にも書いてありますが、Bot開発においてコマンドやリスナー、いくつかの状態を一つのクラスにまとめるために作られました。
例えば、コマンドのカテゴリーごとの分類や、リスナーを機能ごとに切り分けたようなものです。オブジェクト指向ですね。
Cogはcommands.Cogを継承したクラスを作れば作ることが可能です。
こんな感じ:

```py
from discord.ext import commands


class MyCog(commands.Cog):
    pass
```

## Cogの中でコマンドを作る
Cogの中でコマンドを作るためには、ちょっと違う方法を使います。
普通botでコマンドを作るときは、`@bot.command()`を使いますが、Cogの中で作るときは`@commands.command()`を使います。
こんな感じ:

```py
from discord.ext import commands


class MyCog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.command()
    async def cat(self, ctx):
        await ctx.send('にゃーん？')

    @commands.command()
    async def dog(self, ctx):
        await ctx.send('わん！')
```

## Cogの中でイベントリスナーを使う
Cogの中で`@bot.event`のようにイベントを取得したい場合は、`@commands.Cog.listener()`を使用します。
このlistenerにはname引数があり、これを使うことでどんな名前の関数でもリスナーにできます！
また、全てのリスナーが独立しているので、いくらでも作ることが可能です。
こんな感じ:

```py
from discord.ext import commands


class MyCog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.Cog.listener(name='on_message')
    async def good_reaction(self, message):
        if message.author.bot:
            return
        if 'いいね' in message.content:
            await message.add_reaction('\U0001f44d')
```

## \_\_init\_\_にbot引数つけてること多いけど何？
例えばbotにある関数、例えば`bot.reload_extension`などを使いたいとき、Botのインスタンスを参照する必要があります。なので、`__init__`するときに渡すことで、後から参照できるということです。

# Extensionとは？
[Extensionの解説](https://discordpy.readthedocs.io/ja/latest/ext/commands/extensions.html)にもありますが、ホットリロードを簡単に行えるようにするための機能です。これがあることで、Botを落とさないでコマンドを変更可能です。
Extensionはload/reload/unloadが可能です。
**Extensionのファイルにはsetup関数が必要です。**このsetup関数は、Extensionがloadされたときに必ず呼び出されます。
逆に、unloadされたときにはteardown関数が呼び出されます。

例えば:

```py
async def setup(bot):
    print('ロードされました')
```

とだけ書いたファイルを読み込めば、それだけでExtensionを使うことができます。

Extensionの例は[こちら](https://discordpy.readthedocs.io/ja/latest/ext/commands/extensions.html#id1)


# CogとExtensionの合わせ技
世の中にあるCogは、例えば:

```py
from cogfile import MyCog
from discord.ext import commands

bot = commands.Bot()

...
await bot.add_cog(MyCog(bot))
```

とすることでロードできますが、Cogの中身を変えたときにBotを落とさずリロードしたいことがあります。そんなとき、CogとExtensionの合わせ技を使うと、超絶簡単にかけるのです！:


```py:cogfile.py
from discord.ext import commands


class MyCog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.Cog.listener(name='on_message')
    async def good_reaction(self, message):
        if message.author.bot:
            return
        if 'いいね' in message.content:
            await message.add_reaction('\U0001f44d')


# これが重要
async def setup(bot):
    await bot.add_cog(MyCog(bot))

```

こうして

```py
# from cogfile import MyCog
from discord.ext import commands

bot = commands.Bot()

# bot.add_cog(MyCog(bot))
...
await bot.load_extension('cogfile')
```

とすることで、ホットリロードができる形でロードできます！

# 注意

世の中にあるCogは8割ほどがCogとExtensionを両方使用しているものですが、それを写したときにsetup関数を忘れエラーになる人が多いです。**setup関数は必須です**。

また、CogとExtensionは別物です！同じものと勘違いをされている方がいるので、是非教えてあげてください。

# さいごに
CogやExtensionでこういう説明/解説が欲しい！という方は、是非コメントにお書きください。