3
2

More than 1 year has passed since last update.

discord botのコマンド実行を制限するためのcheck

Posted at

初めに

  • Python 3.10.6
  • discord.py 2.1.0

やりたいこと

minecraft serverではサーバーの停止には権限が必要です.ですがdiscord bot上で操作する際にはユーザーが行うのではなく,サーバーに直接コマンドを入力して操作します.そのため,discord上にいる人が誰でもサーバーの停止ができる状態になってしまいます.これを解決するために,操作を行う人が必要な権限などを持っているかどうかを判定したいです.コマンドを実行するときの処理内で条件分岐をすればできますが,discord.pyでは権限などの判定ができるデコレータが存在するのでそちらを使います.こっちを使うことで再利用性が高まる,と思います.

基本のコードは以下のものを利用します.

基本
python
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を持つ判定をする関数を作り,その関数をデコレータに渡すようです.

python
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"))

これを実行してみると,

image.png

こんな感じで無事に判定できました.これがもし,以下のようなitohal_managerかどうか判定する場合,エラーが発生します.1

python
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ロールを付与しておきました.

image.png

これで以下のコマンドが実行できるか試します.ここで,has_any_roleデコレータに渡しているロールはyesロールとnoロールのIDです.

python
@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
    )

image.png

image.png

こんな感じで両方とも実行してくれました.

has_permissions

has_permissionsデコレータは,コマンドを実行する人が指定した権限を,指定した真偽値であるか判定します.つまり,権限を持っているときに実行してほしければTrueを,持っていないときに実行してほしければFalseを指定します.また,判定したい権限はdiscord.Permissionsにあるプロパティの名前を指定します.discord.Permissionsをimportする必要はありませんでした.

以下ではコマンドを実行した人が,チャンネル削除などをできる権限を持っていればコマンドを実行するようにしています.

python
@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......'")

image.png

権限を持っていれば実行できますね.

bot_has_permissions

bot_has_permissionsデコレータは,コマンドを実行した人ではなく,bot自体にその権限があるかどうかを確認します.

以下はbotがメッセージを送信できればコマンドを実行できます.

python
@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?")

image.png

そして,以下のコマンドはbotがチャンネル削除などの権限を持っていなければ実行できます.

python
@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 < ☜(゚ヮ゚☜) (☞゚ヮ゚)☞")

image.png

cooldown

cooldownでは,コマンドを連続実行する際の間隔を設定できます.何秒間(per)の間に何回(rate)実行できるかを引数にcooldown(rate, per)などと指定します.また,keyという引数にクールダウンを発生させるインタラクションの対象を設定できます.つまり,ユーザーごとにクールダウンを発生させるかやチャンネルごとにクールダウンを発生させるということができます.具体的には,ユーザー単位でクールダウンを発生させる場合,あるユーザーがクールダウン中でも他のユーザーはコマンドを実行できる,といったことです.

cooldown.gif

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を紹介しました.ちなみにこれ自身でデコレータを作成して簡素化できるようです.

お疲れ様でした.

  1. checkが失敗した場合に何か処理をするときは,Clientのon_errorメソッドCheckFailureという例外を判定し,そこで処理を書くと思います.

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2