0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

itohalAdvent Calendar 2022

Day 10

discord botのcooldownとinteraction_check

Posted at

前提

この記事はminecraft serverをdiscord botから操作しようというアドベントカレンダーの9日目です.
内容としては,

  • コマンドを打つことができるチャンネルの制限の設定
  • コマンド連打してくる悪い人への対策のためにcommandにcooldownを設定すること

といった細々としたことを説明します.
また,

  • WSL
    • Ubuntu 22.04.1
  • Python 3.10.6
  • discord.py

という環境です.
そして,discordにおけるサーバーとminecraftのサーバーが若干ややこしいかもしれないので,discordにおけるサーバーはギルドと呼びます

コマンドへの反応を制限

discord botにおいて,コマンドは基本誰でもどこでも利用できます.どこでもというのは,どのギルドでも,どのチャンネルでも,誰でも利用できるということです.そのため,あちこちに反応されると大事なログが流れるかもしれません.
その対策として,反応するチャンネルや反応するユーザーを制限しましょう.ギルドの制限に関しては,そもそもそのアプリを必要としているからbotを入れているわけですから,まあ今回は考えないことにしましょう.

さて,反応するチャンネルや反応するユーザーを確認するためinteraction_check()というものを利用します.

interaction_check()

interaction_check()ui.View()CommandTreeGroupなどで利用できるメソッドです.
これはinteraction,つまりコマンドを打ち込んだときに実行されるものです.引数にinteractionを取ります.返り値はコマンドを実行して良いかどうかのboolです.
また,返り値としてfalseとなった場合,on_error()メソッドで処理することもできます.実装しなくても大丈夫といえば大丈夫です.ログにエラー出力されますけどね.

これを利用して,前回記事に書いたコードを修正していきます.
コードは以下の通りです.

discord bot x minecraft serverのコード
MinecraftCommand.py
from discord import app_commands, Interaction
from MCServer import MCServer


class MinecraftCmd(app_commands.Group):
    def __init__(self, server):
        super().__init__(name="minecraft")
        self.server: MCServer = server

    @app_commands.command()
    async def start(self, interaction: Interaction):
        await interaction.response.send_message('minecraft server is starting...')
        started = self.server.start()
        if started:
            await interaction.followup.send(f"{interaction.user.mention}! server start successfully!")
        else:
            await interaction.followup.send(f"{interaction.user.mention}, sorry. server cannot start")

    @app_commands.command()
    async def stop(self, interaction: Interaction):
        await interaction.response.send_message('minecraft server is stopping...')
        stopped = self.server.stop()
        if stopped:
            await interaction.followup.send(f"{interaction.user.mention}! server stop successfully!")
        else:
            await interaction.followup.send(f"{interaction.user.mention}, sorry. server cannot stop")
discordbot.py
from MCServer import MCServer
from MinecraftCommand import MinecraftCmd
from discord import Intents, Client
from discord.app_commands import CommandTree


class MCClient(Client):
    def __init__(self, intents: Intents) -> None:
        super().__init__(intents=intents)
        self.tree = CommandTree(self)
        self.server = MCServer()

    async def setup_hook(self) -> None:
        self.tree.add_command(MinecraftCmd(self.server))
        await self.tree.sync()

    async def on_ready(self):
        print(f"login: {self.user.name} [{self.user.id}]")

チャンネルの制限

まずは現在の状態を確認しておきましょう.チャンネルの制限をしていない場合は以下の通りです.

  1. generalでminecraft serverを起動し
  2. minecraft_channelで停止ができている

という状態です.generalに大切なログがあった場合,探すのにこいつは邪魔です.そのため,minecraft_channelのときだけ反応するようにしましょう.

Groupにinteraction_check()を追加しましょう.このGroupはminecraft serverの起動と停止をするコマンドグループです.よって,ここに反応するチャンネルを制限しておきます.そのためにはチャンネルIDを取得する必要があります.

以下の歯車から設定画面を開いて,
image.png

アプリの設定 > 詳細設定から開発者モードをONにします.
image.png

これでチャンネルに対して右クリックをすればチャンネルIDがコピーできるようになります.
image.png

ではコードを改造していきましょう.

MinecraftCommand.py
- from discord import app_commands, Interaction
+ from discord import app_commands, Interaction, Client
+ from discord.app_commands import AppCommandError
from MCServer import MCServer


class MinecraftCmd(app_commands.Group):
-   def __init__(self, server):
+   def __init__(self, server, client: Client):
        super().__init__(name="minecraft")
        self.server: MCServer = server
+       self.allowed_channel_id = 1050795597852053565
+       self.client = client

    @app_commands.command()
    async def start(self, interaction: Interaction):
        await interaction.response.send_message('minecraft server is starting...')
        started = self.server.start()
        if started:
            await interaction.followup.send(f"{interaction.user.mention}! server start successfully!")
        else:
            await interaction.followup.send(f"{interaction.user.mention}, sorry. server cannot start")

    @app_commands.command()
    async def stop(self, interaction: Interaction):
        await interaction.response.send_message('minecraft server is stopping...')
        stopped = self.server.stop()
        if stopped:
            await interaction.followup.send(f"{interaction.user.mention}! server stop successfully!")
        else:
            await interaction.followup.send(f"{interaction.user.mention}, sorry. server cannot stop")

+   async def interaction_check(self, interaction: Interaction, /) -> bool:
+       return interaction.channel_id == self.allowed_channel_id
+
+   async def on_error(self, interaction: Interaction, error: AppCommandError, /) -> None:
+       allowed_channel = self.client.get_channel(1050795597852053565)
+       await allowed_channel.send(f"{interaction.user.mention}. plese this channel to run command")
+       print(error)

これを実行してみます.
generalの方では
image.png

となり,minecraft_channelの方で

image.png

と送られました.そしてminecraft_channelの方でコマンドを入力すると

image.png

無事に実行することができました.
人の制限についても,interactionuserを比較すればできると思います.

cooldown

コマンドは何回でも入力可能です.そのため,

画質がめっちゃ荒いですが,なんとなく想定している動作ではないことはわかるでしょう.この原因は短時間に何度もコマンドが動作してしまうからです.そのため,一度コマンドを打ったら,再度実行できるまで猶予をもらいましょう.
そのためにcooldownを設定します.またリファレンスにサンプルがあるのでそれを参考にします.

cooldownはappcommands.checksにあるデコレータです.引数は,クールダウン設定までの使用回数,クールダウンの長さ,制限の範囲です.

cooldownを適用させたコードが以下の通りです.

MinecraftCommand.py
from discord import app_commands, Interaction, Client
from discord.app_commands import AppCommandError
from MCServer import MCServer


class MinecraftCmd(app_commands.Group):
    def __init__(self, server, client: Client):
        super().__init__(name="minecraft")
        self.server: MCServer = server
        self.allowed_channel_id = 1050795597852053565
        self.client = client

    @app_commands.command()
+   @app_commands.checks.cooldown(1, 30)
    async def start(self, interaction: Interaction):
        await interaction.response.send_message('minecraft server is starting...')
        started = self.server.start()
        if started:
            await interaction.followup.send(f"{interaction.user.mention}! server start successfully!")
        else:
            await interaction.followup.send(f"{interaction.user.mention}, sorry. server cannot start")

    @app_commands.command()
+   @app_commands.checks.cooldown(1, 30)
    async def stop(self, interaction: Interaction):
        await interaction.response.send_message('minecraft server is stopping...')
        stopped = self.server.stop()
        if stopped:
            await interaction.followup.send(f"{interaction.user.mention}! server stop successfully!")
        else:
            await interaction.followup.send(f"{interaction.user.mention}, sorry. server cannot stop")

    async def interaction_check(self, interaction: Interaction, /) -> bool:
        return interaction.channel_id == self.allowed_channel_id

+   async def on_error(self, interaction: Interaction, error: AppCommandError, /) -> None:
+       if isinstance(error, app_commands.CommandOnCooldown):
+           await interaction.response.send_message(str(error), ephemeral=True)
+       elif isinstance(error, app_commands.CommandOnCooldown):
+           allowed_channel = self.client.get_channel(1050795597852053565)
+           await allowed_channel.send(f"{interaction.user.mention}. plese this channel to run command")
+           print(error)

実行すると以下のようになりました.

多少エラー出てますが,おおむねやりたいことができています.まあコマンドのたびにクールダウン中だよって表示されてしまいますが.

以上となります.

  • コマンドを打つことができるチャンネルの制限の設定
  • コマンド連打対策のためにcommandにcooldownを設定すること

を説明しました.
お疲れ様でした.

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?