前提
この記事は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()やCommandTree,Groupなどで利用できるメソッドです.
これはinteraction,つまりコマンドを打ち込んだときに実行されるものです.引数にinteractionを取ります.返り値はコマンドを実行して良いかどうかのboolです.
また,返り値としてfalseとなった場合,on_error()
メソッドで処理することもできます.実装しなくても大丈夫といえば大丈夫です.ログにエラー出力されますけどね.
これを利用して,前回記事に書いたコードを修正していきます.
コードは以下の通りです.
discord bot x minecraft serverのコード
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")
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}]")
チャンネルの制限
まずは現在の状態を確認しておきましょう.チャンネルの制限をしていない場合は以下の通りです.
- generalでminecraft serverを起動し
- minecraft_channelで停止ができている
という状態です.generalに大切なログがあった場合,探すのにこいつは邪魔です.そのため,minecraft_channelのときだけ反応するようにしましょう.
Group
にinteraction_check()を追加しましょう.このGroup
はminecraft serverの起動と停止をするコマンドグループです.よって,ここに反応するチャンネルを制限しておきます.そのためにはチャンネルIDを取得する必要があります.
これでチャンネルに対して右クリックをすればチャンネルIDがコピーできるようになります.
ではコードを改造していきましょう.
- 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)
となり,minecraft_channelの方で
と送られました.そしてminecraft_channelの方でコマンドを入力すると
無事に実行することができました.
人の制限についても,interaction
のuser
を比較すればできると思います.
cooldown
コマンドは何回でも入力可能です.そのため,
画質がめっちゃ荒いですが,なんとなく想定している動作ではないことはわかるでしょう.この原因は短時間に何度もコマンドが動作してしまうからです.そのため,一度コマンドを打ったら,再度実行できるまで猶予をもらいましょう.
そのためにcooldownを設定します.またリファレンスにサンプルがあるのでそれを参考にします.
cooldownはappcommands.checks
にあるデコレータです.引数は,クールダウン設定までの使用回数,クールダウンの長さ,制限の範囲です.
cooldownを適用させたコードが以下の通りです.
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を設定すること
を説明しました.
お疲れ様でした.