全体のエラーハンドリング
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)

コメント