🤖

discord.pyでcogを使って開発しやすくする

に公開

はじめに

discord.pyでようせいさんというBotを作っています。ようせいさんの招待はこちら
はじめはサーバー内でメンバーを募集する機能を作っていましたが,徐々に機能が増えてコードが1000行を超え始めて,修正作業や新しい機能の追加が大変になりました。
ファイルを分ける方法を調べるとcogというものが出てきたので調べながら実装してみました。
Cogの有効化の方法がよくわからない部分も多いです😭

参考ページ

https://discordpy.readthedocs.io/ja/stable/ext/commands/cogs.html
https://zenn.dev/nano_sudo/articles/a00db1a55d6c4c
https://qiita.com/Takkun053/items/45b4a6acd7c74e651ec2

ファイルを分ける

まずはファイルを分けます。

.
┣ main.py
┣ config.py
┗ cogs
   ┣ event.py
   ┗ general.py

機能ごとにまとめてファイル分けしてます。

cogの定義

それぞれのファイルの中でcommand.cogを継承してCogを定義します。

cogs/general.py
import discord
from discord import app_commands
from discord.ext import commands


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

イベントリスナーを作成する

cogs/event.py
import discord
from discord.ext import commands


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

    @commands.Cog.listener()
    async def on_message(self, message: discord.Message) -> None:
        # pass

@commands.Cog.listenerをつけることでイベントリスナーを定義できる。

スラッシュコマンドを作成する

cogs/general.py
import discord
from discord import app_commands
from discord.ext import commands


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

    @app_commands.command(name='r', description='募集を行います')
    async def r(self, ctx: discord.Interaction, title: str, detail: str = None, max: int = None, role: str = None, channel: str = None):
        """募集を行う
        Args:
            title (str): 募集タイトル
            detail (str): 募集内容
            max (int): 募集人数
            role (str): 付与するロール
            channel (str): 新たなチャンネルを作成するかどうか(作成する場合"y"を入力)
        """
        pass

    @app_commands.command(name='dice', description='ダイスを振ります')
    @app_commands.describe(num='ダイスの個数')
    async def dice(self, ctx: discord.Interaction, num: int = 1):
        pass

@app_commands.commandを使用してスラッシュコマンドを定義できます。
name=でスラッシュコマンドの名前,description=でコマンドの説明が追加できます。
スラッシュコマンドとコマンドの説明はdiscordで/を打ち込んだ時に表示されるコマンドの候補に↓のように表示されます。

async def コマンドの名前(self, ctx, 引数...)のように記載します。
ctxには自動的にdiscord.Interactionが入れられる。アノテーションつけるとVScodeで補完機能が使いやすくなるのでつけてます。

引数のアノテーションをつけるとdiscordが型チェックして意図しない引数が入力されるのを防ぐことができます。↓

引数=初期値で初期値が設定できたり,引数=Noneで任意の引数にできたりします。どこかに書いてた気がするけどどこに書いてたかわすれた。見つけたら追記します。

@app_commands.describeまたは,Google、Sphinx、Numpy スタイルのドキュメント文字列でコマンド引数の説明を追加できます。

参考↓
https://discordpy.readthedocs.io/ja/stable/interactions/api.html?highlight=app_commands describe#discord.app_commands.describe

Cogを有効にする

main.pyでCogを有効にしてBotを起動します。ここが一番よくわからなかったです。
エクステンション?とやらを使って有効にしています。

詳しくは公式のドキュメントを参考に↓
https://discordpy.readthedocs.io/ja/stable/ext/commands/extensions.html

まずCogファイルの末尾にsetup関数を追加します。

cogs/general.py
async def setup(bot):
    await bot.add_cog(General(bot))

エクステンションは setup というエントリポイントを持つPythonファイルです。

らしいです。だからこれでCogが定義されているエクステンションが作成されたという理解をしています。間違えてたらごめんなさい。

そしてこのエクステンションをload_extension()を使用して読み込みます。

main.py
import discord
from discord.ext import commands
import config


INITIAL_EXTENSIONS = [
    'cogs.general',
    'cogs.event',
]

intents = discord.Intents.all()
intents.typing = False

class MyBot(commands.Bot):
    async def setup_hook(self):
        for cog in INITIAL_EXTENSIONS:
            await self.load_extension(cog)
        await self.tree.sync()

bot = MyBot(command_prefix='!', intents=intents)

bot.run(config.TOKEN)

INITIAL_EXTENSIONSにエクステンションを追加してforで読み込んでます。
await self.tree.sync()でアプリケーションコマンドをDiscordに同期します。

おわり

cogの有効化でめちゃくちゃつまづきました。エクステンションの理解が曖昧ですが動いているのでいいでしょう。ご指摘があればXかDiscordにご連絡ください。

Discussion