【discord.py 2.0】Cogについて理解する

:::note warn
**この記事は発展的な内容です。**
初心者の方でも簡単に理解できるよう心がけて説明をしたつもりではありますが、Pythonやdiscord.pyをあまり理解できていない方は先にそちらを理解することを**強く**推奨します。
:::

# はじめに
:::note info
2023年8月に記事をかなり書き換え、簡単な書き方からCogの仕組みまで理解できるようになりました。
:::

この記事を読もうとしているそこのあなた！！
ちょっと待ってください！！
すでにドキュメントは読んでいますか？？

読んでいない場合は下のリンクからすぐに読んできてください。読んでも理解ができなかった場合や、詳しい仕組みが知りたい場合はこの記事を読み進めてください。

https://discordpy.readthedocs.io/ja/latest/ext/commands/cogs.html

この記事では仕組みなど初心者には難しいと思われる場所を、下のように折りたたんであります。詳しい仕組みが知りたい！という方はぜひクリックして読んでみてください。

<details><summary>クリックしてネ！</summary>

( ᐛ👐)パァ
</details>


# そもそも Cog(コグ) って？
> Bot開発においてコマンドやリスナー、いくつかの状態を一つのクラスにまとめてしまいたい場合があるでしょう。コグはそれを実現したものです。
_https://discordpy.readthedocs.io/ja/latest/ext/commands/cogs.html_

公式ドキュメントにも書いてある通り、CogはBotのプログラムをいくつかに分割するために使用します。
大きなBotの場合、一つのファイルにまとめると3000行とか大変なことになりますからね()
また後述のExtensionを使うとBotの再起動なしにプログラムを書き換えることもできます。

# Cogを使う際の書き換え
## コマンド
Cogを使わないときはコマンドを定義する際に以下のように書きますが、

```py
@bot.command()
async def test(ctx):
    pass #処理内容
```

Cog内でコマンドを定義するときは以下のように書きます。

```py
@commands.command()
async def test(self, ctx):
    pass #処理内容
```

違いは`@bot.~`が`@commands.~`なったのと、引数に`self`が追加されたことぐらいですかね。

## イベント
Cogを使わないときはイベントを定義する際に以下のように書きますが、

```py
@bot.event
async def on_ready():
    pass #処理内容
```

Cog内でイベントを定義するときは以下のように書きます。

```py
@commands.Cog.listener()
async def on_ready(self):
    pass #処理内容
```

違いは`@bot.event`が`@commands.Cog.listener()`になったことと、さっきと同じように引数に`self`が追加されたことですね。

## 変数`bot`の使い方
Cogを使わないときはBotのデータにアクセスする際に以下のように書きますが、

```py
bot
```

Cog内でアクセスするときは以下のようになります。

```py
self.bot
```

違いは`self`を書かなければいけなくなったってことぐらいですね。

<details><summary>さっきから出てくるselfって何者？？</summary>

selfはdiscord.pyの機能ではなく、PythonのClassの機能になります。
selfを使うことで同一クラス内の関数でデータを共有することができます。
詳しくは下の記事を読んでみてください。

https://qiita.com/baby-0105/items/c7b2d9b22780a326e4b7

</details>

# Cogの追加
## 同一ファイルの時
```py
import discord
from discord.ext import commands

bot = commands.Bot(command_prefix="!")

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

    @commands.command()
    async def test(self, ctx):
        await ctx.send("test!")

@bot.event
async def setup_hook():
    await bot.add_cog(MyCog(bot))
```

こんな感じになります。

```py
@bot.event
async def setup_hook():
    await bot.add_cog(MyCog(bot))
```

の部分でCogを読み込んでいます。

Cog名(上でいうところの`MyCog`)に特に決まりはないため、好きに決めることができます。

<details><summary>__init__ってなに？？</summary>

これも、Pythonの機能になります。
Classの生成後に実行される関数です。
これを使うことでClassが生成されたとき(≒Cogが実行されたとき)の処理を書くことができます。
詳しくは下の記事を読んでみてください。

https://qiita.com/ishigen/items/2d8b6e6398743f2c8110

</details>

<details><summary>仕組み</summary>

1. Botの起動後[`setup_hook`](https://discordpy.readthedocs.io/ja/latest/ext/commands/api.html?highlight=setup_hook#discord.ext.commands.Bot.setup_hook)が呼び出される。

2. `bot.add_cog`でCogを追加。
</details>

## 別ファイルのとき
このままだと、Classに分けることはできてもファイルは一つのままです。
そこで、Classの部分を別のファイルに移してみましょう。

```txt:ファイル構成
.
┣━ main.py
┗━ sub.py
```

ファイル構成が上のようになっていて`sub.py`にある`MyCog`を読み込みたいときは以下のようにします。

```py:main.py
import discord
from discord.ext import commands
import sub

bot = commands.Bot(command_prefix="!")

@bot.event
async def setup_hook():
    await bot.add_cog(sub.MyCog(bot))
```
```py:sub.py
import discord
from discord.ext import commands

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

    @commands.command()
    async def test(self, ctx):
        await ctx.send("test!")
```

`main.py`を実行し、testコマンドを実行するとtest!と返ってきます。

<details><summary>仕組み</summary>

1. `main.py`の`import sub`で`sub.py`を読み込む。

2. `bot.add_cog`で`sub.py`にある`MyCog`を読み込む。
</details>


また、上記以外のディレクトリの場合も同じように`import`を使うことで読み込みできます。
詳しくは、下の記事をご覧ください。

https://qiita.com/karadaharu/items/37403e6e82ae4417d1b3

# Extension
Extensionとは上でも言いましたが、Botの再起動なしにプログラムを書き換えることをできるようにする機能のことです。

一般的にCogとExtensionを同時に使うことが多いので、Cog≒Extensionと誤解しやすいですが、別の機能です。

```txt:ファイル構成
.
┣━ main.py
┗━ sub.py
```
ファイル構成が上のようになっている場合、下のようにすることでExtensionを使えるようになります。

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

bot = commands.Bot(command_prefix="!")

@bot.event
async def setup_hook():
    await bot.load_extension("sub")
```

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

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

    @commands.command()
    async def test(self, ctx):
        pass

async def setup(bot):
    await bot.add_cog(MyCog(bot))
```

`main.py`を実行し、testコマンドを実行するとtest!と返ってきます。

<details><summary>仕組み</summary>

1. `main.py`の`bot.load_extension`で`sub.py`を読み込む。

2. `sub.py`の`setup`関数が読み込まれる。

3. **=== ここまでがExtensionの機能 ===**

4. `bot.add_cog`で`sub.py`にある`MyCog`を読み込む。
</details>

```txt:ファイル構成
.
┣━ main.py
┗━ cog
 ┗━ sub.py
```

ファイル構成が上のようになっている場合、`await bot.load_extension("cog.sub")`で読み込みできます。

# 最後に
わからないこと、追加してほしいこと、誤字脱字等があれば遠慮なくコメントしてください。
ただ、忙しいので見れない可能性もあります。予めご了承ください。