目的
Discordボットを使ってMinecraftサーバを操作できるようにする
準備
以下の導入が済んでいることを前提として話を進めていきます。
- Discordボット
- Minecraftサーバ
- tmux
- Java
- Python3
 ※この記事では上記すべてのやり方を解説しません。
実行環境
OS
Ubuntu Server 24.04.1 LTS
Minecraftサーバ
CurseForge for Minecraft 1.16.5 - 36.2.34
Java
OpenJDK 11.0.25
(2025/01/13 時点)
結論
最新版のコードはGithubに公開しています
https://github.com/MotchiyTuti/oss-discordbot
以下のコードを実行することで操作ができます。
import discord
import subprocess
import time
# Initialize intents
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
def execute(shell_script):
    subprocess.run(shell_script, shell=True)
@client.event
async def on_ready():
    print(f'We have logged in as {client.user}')
@client.event
async def on_message(message):
    # メッセージがBOTからのものでないことを確認
    if message.author.bot:
        return
    elif message.content.strip():
        try:
            await message.channel.send('wait...')
            command = message.content.split()
            await message.channel.send(f'now {command[0]}ing...')
            
            match command[0]:
                case 'open':
                    with open(f'server/{command[1]}/status.txt', encoding='utf-8') as s:
                        if s.read() == 'running':
                            await message.channel.send(f'{command[1]} is still running!')
                        else:
                            execute('tmux new -s '+command[1]+' -d')
                            execute(f'tmux send-keys -t {command[1]} "cd server/{command[1]}" ENTER')
                            execute(f'tmux send-keys -t {command[1]} "java -jar *.jar" ENTER')
                            with open(f'server/{command[1]}/status.txt', mode='w', encoding='utf-8') as s:
                                s.write('running')
                    await message.channel.send('finished!')
                case 'close':
                    with open(f'server/{command[1]}/status.txt', encoding='utf-8') as s:
                        if s.read() == 'waiting':
                            await message.channel.send(f'{command[1]} is still waiting!')
                        else:
                            if '-p' in command == True:
                                close_command = 'end'
                            else:
                                close_command = 'stop'
                            execute(f'tmux send-keys -t {command[1]} "'+close_command+'" ENTER')
                            execute(f'tmux send-keys -t {command[1]} "exit" ENTER')
                            with open(f'server/{command[1]}/status.txt', mode='w', encoding='utf-8') as s:
                                s.write('waiting')
                    await message.channel.send('finished!')
                case 'status':
                    with open(f'server/{command[1]}/status.txt', encoding='utf-8') as s:
                        await message.channel.send(f'{command[1]} is {s.read()}')
                    await message.channel.send('finished!')
        except Exception as e:
            # エラー発生時の処理
            await message.channel.send(f'error: {e}')
with open('token.txt', encoding='utf-8') as f:
    client.run(f.read())
使い方
1. ディレクトリ構成をそろえる
上記のdiscordbot.pyと同じディレクトリに token.txt と server ディレクトリを作成し、
1段階深いディレクトリにサーバのjarファイルがくるようにする。
┝ discordbot.py
┝ token.txt
└ server
    └ 250113_test
        ┝ config
        ┝ eula.txt
        ┝ ***.jar
2. token.txtにDiscordボットのトークンをペーストする
s3dfD3ydf...
※余計なことは書かない!
3. discordbot.pyを実行
シェルから以下のコマンドを実行する。
python3 discordbot.py
解説
import discord
ディスコードボットを動かすために必要なモジュール
import subprocess
Pythonからシェルコマンドを実行するために必要
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
ディスコードボットの設定
command = []
送られたメッセージリスト型にする先をつくる
def execute(shell_script):
    subprocess.run(shell_script, shell=True
シェルでのコマンドを使いやすくする
@client.event
async def on_ready():
    print(f'We have logged in as {client.user}')
適切にディスコードへとログインできたかの確認
@client.event
async def on_message(message):  # メッセージへの対応
    if message.author.bot:  # 送信者がBOTのとき
        return
ボット同士で会話しないようにする
elif message.content.strip():  # メッセージが空白でないとき
    try:
        await message.channel.send('wait...')
    except Exception as e:  # エラーが発生したとき
        await message.channel.send(f'error: {e}')
else:
    return
何かしらの読み取れるメッセージが送られたとき、wait... と返信する
command = message.content.split()
メッセージを区切ってリスト型に変換
match command[0]:
    case 'open':    # 先頭がopenのとき
    case 'close':   # 先頭closeのとき
メッセージの先頭がopen のときとclose のときの分岐
await message.channel.send('now '+command[0]+'ing...')
ディスコードにnow opening... やnow closeing... と送信
closeingのスペルがおかしいのは無視
execute('tmux new -s '+command[1]+' -d')
tmuxで新しいセッションを作る
(subprocessをそのまま使うと鯖にstopコマンドが使えなくなってしまうため)
execute(f'tmux send-keys -t {command[1]} "cd server/{command[1]}" ENTER')
サーバーディレクトリに移動
execute(f'tmux send-keys -t {command[1]} "java -jar *.jar" ENTER')
マイクラサーバーを起動
execute('tmux send-keys -t '+command[1]+' "stop" ENTER')
マイクラサーバーを停止
execute('tmux send-keys -t '+command[1]+' "exit" ENTER')
tmuxのセッションを停止
with open('token.txt', encoding='utf-8') as f:
    client.run(f.read())   # Discordにログイン
カレントディレクトリのtoken.txtからトークンを読み取り、Discordにログイン
おわりに
最後まで読んでいただき、ありがとうございます。
何か不具合や要望がありましたら、気軽に連絡してください。