🐍

discord.py v1からv2に移行する

2022/10/10に公開

discord.py v2は2022年8月にリリースされました。
このリリースではスレッドやスラッシュコマンドの対応など、さまざまな変更がされています。

本記事では、discord.py v1.7.3 から v2.0.1 に移行するために行ったことをまとめました。

移行は、基本的に 公式ドキュメント を参照して行いました。
https://discordpy.readthedocs.io/ja/latest/migrating.html

インテントの必須化

メッセージコンテンツを取得するには Intents.message_contentTrue にする必要があるようです。
そしてAPIリファレンスの注釈にある通り、 Intents.message_content をTrueにする場合は、 Discord Developer Portal のBotの設定から、「MESSAGE CONTENT INTENT」をONにする必要があります。

Assetのリデザインと変更 & 属性の型の変更

v2以前は、MemberのAvatarのurlは Member.avatar_url で取得できました。
v2からは、 Member.display_avatar.url で取得できます。 Member.avatar はAvatarが未設定の場合 None が返るのでご注意ください。

参考:

ボイスチャンネル チャット

ボイスチャンネルのチャットにBotがアクセスするためには、「接続」の権限が必要なようです。

コマンド拡張の変更

Bot.load_extension()Bot.add_cog() にawaitが必要になりました。

非同期化により、 asyncio.gather() を使った並行なCogの追加ができそうです。

タスク拡張機能の変更

tasks.loop() にパラメータ time を渡せるようになりました。
v2までは、指定時刻まで実行したいタスクを待つコードを書いていたのですが、それが無くなり楽になりそうです。

HybridCommandへの移行

HybridCommand はテキストコマンドとスラッシュコマンド、両方として呼び出せるコマンドです。

参考:

移行前のBotはcommand_prefixに / 以外を使っていました。いきなりテキストコマンドが使えなくなるとユーザが違和感を持つと思い、CommandからHybridCommandに移行することにしました。

そもそもスラッシュコマンドとは

https://discord.com/developers/docs/interactions/application-commands
スラッシュコマンドはDiscordのアプリケーションコマンドの1つです。
アプリケーションコマンドはDiscordクライアントでアプリを操作するためのネイティブな方法……らしいです。

アプリケーションコマンドは他に、

  • ユーザコマンド:ユーザを右クリックすると出るコマンド
  • メッセージコマンド:メッセージを右クリックすると出るコマンド

があります。

アプリケーションコマンドはスコープ(公開範囲)を決められます。

  • グローバル:アプリケーションが追加されている全てのギルド、およびDMでコマンドが使えます。変更の反映には時間がかかります。
  • ギルド:特定のギルドでコマンドが使えます。変更は即反映されます。

HybridCommandの移行でハマったこと

移行でハマったことが3つありました。

  1. CommandTree.sync() とグローバル/ギルドコマンド
  2. Context.send() をしないと応答なしの扱いになる
  3. スラッシュコマンドでは送信元メッセージが存在しない
  4. HelpCommandがスラッシュコマンドに登録されない

1. CommandTree.sync() とグローバル/ギルドコマンド

HybridCommandはそのままでは使えず、 CommandTree.sync() を使ってコマンドの同期を取る必要があります。
CommandTreeは Bot.tree から取得できます。
CommandTree.sync() の引数によってグローバル・ギルドのスコープが変わるのでご注意ください。

Botのクラス内でコマンドの同期を取る場合、以下のように書けそうです。
ギルドコマンドを登録する場合は、CommandTree.copy_global_to() でグローバルコマンドをギルドコマンドにコピーする必要があります。

# グローバルコマンドの登録
async def sync_global_commands(self) -> None:
    await self.tree.sync()

# ギルドコマンドの登録
async def sync_guild_commands(self) -> None:
    await asyncio.gather(*[self.sync_guild_command(guild) for guild in self.guilds])

async def sync_guild_command(self, guild: discord.Guild) -> None:
    self.tree.copy_global_to(guild=guild)
    await self.tree.sync(guild=guild)

誤ってコマンドを登録して削除したい場合、 Bot.load_extension() でコマンドを追加する前に CommandTree.sync() すると削除できます。

2. Context.send() をしないと応答なしの扱いになる

それまでメッセージの送信に TextChannel.send() を使っていたのですが、タイムアウト扱いになりました。 Context.send() を代わりに使うようにしました。
ちなみに複数のメッセージを送信しても問題は無いようです(リプライ扱いになりました)。

3. スラッシュコマンドでは送信元メッセージが存在しない

送信元メッセージにreactionを追加できないため、reactionではなくメッセージを送るよう修正しました。

4. HelpCommandがスラッシュコマンドに登録されない

discord.pyでIssueが上がっていますが、まだcloseされていません。
https://github.com/Rapptz/discord.py/issues/8063

discord.pyのDiscordサーバで情報収集したところ「スラッシュコマンドはそれ自体がコマンドの説明を含んでいるから、ヘルプは不要では」との書き込みが。確かにそうかも……。
とはいえヘルプがないと困る場面があるのでスラッシュコマンドも使えるようにしました。

解決方法

BotのコマンドからEmbedを生成する関数(下のコード例では message.create_bot_help_embed がそれにあたります)を作り、それを HelpCommandapp_commands.Command で呼び出すようにしました。

HelpCommand側

async def send_bot_help(
    self, mapping: Mapping[Optional[commands.Cog], List[commands.Command]]
) -> None:
    """引数なしでhelpが呼び出された場合の処理"""
    command_list = []
    for cog in mapping:
        command_list += await self.filter_commands(mapping[cog])

    embed = message.create_bot_help_embed(command_list)
    await self.get_destination().send(embed=embed)

app_commands.Command側

@app_commands.command()
async def help(self, interaction: discord.Interaction) -> None:
    """ヘルプを表示します"""
    embed = message.create_bot_help_embed(list(self.bot.commands))
    await interaction.response.send_message(embed=embed)

Discussion