初めに
- Python 3.10.6
- discord.py 2.1.0
やりたいこと
minecraft serverではサーバーの停止には権限が必要です.ですがdiscord bot上で操作する際にはユーザーが行うのではなく,サーバーに直接コマンドを入力して操作します.そのため,discord上にいる人が誰でもサーバーの停止ができる状態になってしまいます.これを解決するために,操作を行う人が必要な権限などを持っているかどうかを判定したいです.コマンドを実行するときの処理内で条件分岐をすればできますが,discord.pyでは権限などの判定ができるデコレータが存在するのでそちらを使います.こっちを使うことで再利用性が高まる,と思います.
基本のコードは以下のものを利用します.
基本
from discord import Client, Intents, Interaction, app_commands
from discord.app_commands import CommandTree
import os
class MyClient(Client):
def __init__(self, *, intents: Intents):
super().__init__(intents=intents)
self.tree = CommandTree(self)
async def setup_hook(self) -> None:
await super().setup_hook()
synced_commands = await self.tree.sync()
print(synced_commands)
async def on_ready(self):
print(f"Logged in as {client.user} (ID: {client.user.id})")
print("------")
intents = Intents.default()
client = MyClient(intents=intents)
client.run(os.getenv("TOKEN"))
というわけでそのデコレータの使い方を見ていきましょう.
check
discord.app_commands.check
デコレータを利用すると,下に記述するアプリケーションコマンドを実行できるかチェックする関数を登録できます.
内容
サンプルがあるのでそちらを参考に作っていきます.サーバー管理者かどうか,まあ要するに自分かどうかを判定させます.引数にIntaraction
を持つ判定をする関数を作り,その関数をデコレータに渡すようです.
from discord import Client, Intents, Interaction, app_commands
from discord.app_commands import CommandTree
import os
# 省略
client = MyClient(intents=intents)
def is_admin(interaction: Interaction):
return interaction.user.id == 851408507194572821
@client.tree.command()
@app_commands.check(is_admin)
async def check_is_admin(interaction: Interaction):
await interaction.response.send_message("you are admin", ephemeral=True)
client.run(os.getenv("TOKEN"))
これを実行してみると,
こんな感じで無事に判定できました.これがもし,以下のようなitohal_managerかどうか判定する場合,エラーが発生します.1
def is_bot(interaction: Interaction):
return interaction.user.id == 1030708397714198538
@client.tree.command()
@app_commands.check(is_bot)
async def check_is_bot(interaction: Interaction):
await interaction.response.send_message("you are bot", ephemeral=True)
discord.app_commands.errors.CheckFailure: The check functions for command 'check_is_bot' failed.
checks
checkデコレータでは判定したいことを関数で書いて登録しました.しかし,一部の処理はdiscord.pyでデコレータとして既に利用できるものがあります.
has_role / has_any_role
名前の通り,has_role もしくは has_any_roleでコマンドを実行した人が,判定したい対象のロールを持っているかどうかを確認できます.両者の違いは判定するロールが一つか複数かです.また,判定するロールはIDでもそのルールの名前でもどちらでも大丈夫です.
自分にkeeperロールとyesロールを付与しておきました.
これで以下のコマンドが実行できるか試します.ここで,has_any_role
デコレータに渡しているロールはyesロールとnoロールのIDです.
@client.tree.command()
@app_commands.checks.has_role("keeper")
async def is_keeper(interaction: Interaction):
await interaction.response.send_message("no goal!!!!", ephemeral=True)
@client.tree.command()
@app_commands.checks.has_any_role(1054345096620945408, 1054345230389882980)
async def has_yes_or_no(interaction: Interaction):
await interaction.response.send_message(
"you have yes or no role. right? \n> left \t right", ephemeral=True
)
こんな感じで両方とも実行してくれました.
has_permissions
has_permissions
デコレータは,コマンドを実行する人が指定した権限を,指定した真偽値であるか判定します.つまり,権限を持っているときに実行してほしければTrueを,持っていないときに実行してほしければFalseを指定します.また,判定したい権限はdiscord.Permissions
にあるプロパティの名前を指定します.discord.Permissions
をimportする必要はありませんでした.
以下ではコマンドを実行した人が,チャンネル削除などをできる権限を持っていればコマンドを実行するようにしています.
@client.tree.command()
@app_commands.checks.has_permissions(manage_channels=True)
async def can_manage_channel(interaction: Interaction):
await interaction.response.send_message("channel < 'do not remove......'")
権限を持っていれば実行できますね.
bot_has_permissions
bot_has_permissions
デコレータは,コマンドを実行した人ではなく,bot自体にその権限があるかどうかを確認します.
以下はbotがメッセージを送信できればコマンドを実行できます.
@app_commands.checks.bot_has_permissions(send_messages=True)
async def can_send_massage(interaction: Interaction):
await interaction.response.send_message("What were you watching?")
そして,以下のコマンドはbotがチャンネル削除などの権限を持っていなければ実行できます.
@client.tree.command()
@app_commands.checks.bot_has_permissions(manage_channels=False)
async def can_bot_manage_channel(interaction: Interaction):
await interaction.response.send_message("channel < ☜(゚ヮ゚☜) (☞゚ヮ゚)☞")
cooldown
cooldownでは,コマンドを連続実行する際の間隔を設定できます.何秒間(per)の間に何回(rate)実行できるかを引数にcooldown(rate, per)
などと指定します.また,key
という引数にクールダウンを発生させるインタラクションの対象を設定できます.つまり,ユーザーごとにクールダウンを発生させるかやチャンネルごとにクールダウンを発生させるということができます.具体的には,ユーザー単位でクールダウンを発生させる場合,あるユーザーがクールダウン中でも他のユーザーはコマンドを実行できる,といったことです.
dynamic_cooldown
dynamic_cooldownでは引数に,Cooldownクラスを返すような関数を指定します.返すCooldownクラスには,cooldownデコレータと同様にrate, per(, key)
を引数に渡します.
サンプルがあるのでそちらを見ると,
def cooldown_for_everyone_but_me(interaction: discord.Interaction) -> Optional[app_commands.Cooldown]:
if interaction.user.id == 80088516616269824:
return None
return app_commands.Cooldown(1, 10.0)
@tree.command()
@app_commands.checks.dynamic_cooldown(cooldown_for_everyone_but_me)
async def test(interaction: discord.Interaction):
await interaction.response.send_message('Hello')
@test.error
async def on_test_error(interaction: discord.Interaction, error: app_commands.AppCommandError):
if isinstance(error, app_commands.CommandOnCooldown):
await interaction.response.send_message(str(error), ephemeral=True)
とあります.cooldown_for_everyone_but_me
において,自身であればcooldownは発生させずに連続でコマンドを実行できるようにしているようです.
以上となります.discord botのコマンド実行を制限するためのcheckを紹介しました.ちなみにこれ自身でデコレータを作成して簡素化できるようです.
お疲れ様でした.
-
checkが失敗した場合に何か処理をするときは,Clientの
on_error
メソッドでCheckFailure
という例外を判定し,そこで処理を書くと思います. ↩