全体のエラーハンドリング
Discord.pyのcommandsを使うと、@bot.eventしたasync def on_command_error()でcommands経由の全エラーを対象に、またon_errorで非Commandsエラーを回収できます(これは標準機能)。
関数ごとのエラーハンドリング
あるいは
from discord.ext import commands import discord @bot.commands async def hoge(ctx): 処理 @hoge.error async def hoge_error(ctx, error): await ctx.send("エラーが出たよ!")
のように書けば、指定関数でのみエラーを処理し、エラー解説メッセージを出力する等の処理ができます。正規のコマンド例を個別に出すなんてことも出来ますね。
例えば引数不足ならこう。
@hoge.error async def hoge_error(ctx, error): if isinstance(error, commands.errors.MissingRequiredArgument ): await ctx.send("引数が足りないよ!")
MissingRequiredArgument等のエラー内容一覧は、commandsのリファレンスを参照してください。
InvokeErrorの注意点
作成した関数外でエラーが出た場合はCommandInvokeErrorとなり、error.orignalに本来のエラーが格納されるため、これを取り出して処理します。InvokeError以外ではoriginalが存在しないので、単純にif isinstance(error.original, エラー内容 ):と書くと、Invoke以外で動作しなくなる点に注意してください。
そのため、まずInvokeかどうかを判別し、Invokeであればoriginalを判定、とする必要があります。
if isinstance(error, commands.CommandInvokeError): if isinstance(error.original, エラー内容 ): await ctx.send("OOエラー(commands外)が発生しました。") else: await ctx.send("想定外のエラー(commands外)が発生しました。") elif isinstance(error, commands.errors.エラー内容 ): await ctx.send("XXエラー(commands内)が発生しました") elif isinstance(error, commands.errors.PrivateMessageOnly): return else: await ctx.send("想定外のエラー(commands内)が発生しました。")
関数外のエラー内容は、Discord.py本家のものと、Python組み込みのものを覚えておけば基本的には問題ないでしょう。例えばスプレッドシートを扱うのなら、スプレッドシートapiのエラーもここに入ります。
PrivateMessageOnly(@dm_only違反)のように、何も反応させたくない例外は個別に早期returnで処理します。
@hoge.error async def hoge_error(ctx, error): print(f"{type(error)=}") # デバッグ用 print(f"{error=}") print(f"{type(error.original)=}") print(f"{error.original=}")
としておくとコンソールにエラー種別と内容が出力されるため、開発が楽になります。
実用サンプルコード
ただコマンドごとのエラー作るのめんどくさいですし、基本的にはこれコピペ+送信先とかを自分なりに改変で行けると思います。普段私が動かしているBotから、一部を切り出して持ってきたものです。
error_logch = bot.get_channel(*****) # この行はon_readyに入れる perm_dic = {"add_reactions": "リアクションの追加", "administrator": "管理者", "attach_files": "ファイルを添付", "ban_members": "メンバーをBAN", "change_nickname": "ニックネームの変更", "connect": "接続(ボイスチャンネル)", "create_instant_invite": "招待を作成", "deafen_members": "メンバーのスピーカーをミュート", "embed_links": "埋め込みリンク", "external_emojis": "外部の絵文字を使用する", "external_stickers": "外部のスタンプを使用する(Use Ecternal Stickers)", "kick_members": "メンバーをキック", "manage_channels": "チャンネルの管理", "manage_emojis": "絵文字の管理", "manage_emojis_and_stickers": "絵文字・スタンプの管理", "manage_events": "", "manage_guild": "サーバー管理", "manage_messages": "メッセージの管理", "manage_nicknames": "ニックネームの管理", "manage_permissions": "ロールの管理", "manage_roles": "ロールの管理", "manage_threads": "スレッドの管理", "manage_webhooks": "ウェブフックの管理", "mention_everyone": "`@evryone`,`@here`,すべてのロールにメンション", "move_members": "メンバーを移動(ボイスチャンネル)", "mute_members": "メンバーをミュート", "priority_speaker": "優先スピーカー", "read_message_history": "メッセージ履歴を読む", "read_messages": "チャンネルを見る", "request_to_speak": "スピーカー参加権をリクエスト", "send_messages": "メッセージを送信", "send_tts_messages": "テキスト読み上げメッセージを送信する", "speak": "発言(ボイスチャンネル)", "stream": "WEBカメラ(映像を配信する)", "use_external_emojis": "外部の絵文字を使用する", "use_external_stickers": "外部のスタンプを使用する(Use Ecternal Stickers)", "use_private_threads": "非公開スレッドの使用(Private Thread)", "use_slash_commands": "スラッシュコマンドを使用", "use_threads": "公開スレッドの使用(Public Thread)", "use_voice_activation": "音声検出を使用", "value": "", "view_audit_log": "監査ログを表示", "view_channel": "チャンネルを見る", "view_guild_insights": "サーバーインサイトを見る", } def t_perm(perm): if perm in perm_dic: return perm_dic[perm] else: return perm @bot.event async def on_command_error(ctx, error): async def error_send(ctx, error): time = jst().strftime("%Y/%m/%d %H:%M") orig_error = getattr(error, "original", error) error_msg = "".join(traceback.TracebackException.from_exception(orig_error).format()) embed = discord.Embed(description=f'{ctx.author.display_name}({ctx.author.name}#{ctx.author.discriminator})\n{ctx.message.content}', color=0xf04747) embed.set_footer(text=f'{ctx.guild.name}(#{ctx.channel.name})\ntime:{time}\nbot.get_channel({ctx.channel.id}).fetch_message({ctx.message.id})') await error_logch.send("<@*****>", embed=embed, file=discord.File(io.StringIO(error_msg), "error.py")) msg = f'想定外のエラーが発生しました。コマンドが指定形式通りか確認して下さい。コマンドが正しいにも関わらずエラーが発生する場合、IDとともに公式サーバで質問して下さい。\nID: {ctx.message.id}' embed = discord.Embed(title='エラーが発生しました', description=msg) await ctx.send(embed=embed) await ctx.send("https://discord.gg/6VKu7XB7b5") print(error_msg) if isinstance(error, commands.CommandNotFound): return elif isinstance(error, commands.MissingPermissions): p = "\n".join([t_perm(perm) for perm in error.missing_permissions]) await ctx.send(f'{p}\nこのコマンドを実行するためには上記の権限が必要です。') elif isinstance(error, commands.BotMissingPermissions): p = "\n".join([t_perm(perm) for perm in error.missing_permissions]) await ctx.send(f'{p}上記の権限が不足しているためBotが動作できません。') elif isinstance(error, commands.NoPrivateMessage): await ctx.author.send("このコマンドはサーバーでのみ使用できます。", delete_after=15) elif isinstance(error, commands.PrivateMessageOnly): await ctx.author.send("このコマンドはDMでのみ使用できます。", delete_after=15) elif isinstance(error, commands.DisabledCommand): await ctx.send("このコマンドは製作者により無効化されています。") elif isinstance(error, discord.errors.NotFound): await ctx.send("もう一度試してみてください。") else: await error_send(ctx, error) @bot.event async def on_error(event, *args, **kwargs): time = jst().strftime("%Y/%m/%d %H:%M") error_msg = traceback.format_exc() if "in MakeThread" in error_msg and "Missing Access" in error_msg: await args[0].channel.send("権限不足により、新規スレッド自動作成が実行できません。Botの権限を見直して下さい。") return ar = "args" for arg in args: ar += f'\n\n{type(arg)}\n{repr(arg)}' ar += "kwargs" for kwarg in kwargs: ar += f'\n\n{type(kwarg)}\n{repr(kwarg)}' embed = discord.Embed(description=f'{event=}', color=0xf04747) embed.set_footer(text=f'{ar}\ntime:{time}') await error_logch.send("<@*****>", embed=embed, file=discord.File(io.StringIO(error_msg), "error.py")) print(error_msg)
コメント