マイクラ鯖をDiscordから起動する

## 目的
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 時点）

## 結論
以下のコードを実行することで操作ができます。
```python:discordbot.py
# This example requires the 'message_content' intent.

import discord
import subprocess

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...')
            command = message.content.split()
            match command[0]:
                case 'open':    # 先頭がopenのとき
                    await message.channel.send('now '+command[0]+'ing...')
                    execute('tmux new -s '+command[1]+' -d')
                    execute('tmux send-keys -t '+command[1]+' "cd server/'+command[1]+'"'+' ENTER')
                    execute('tmux send-keys -t '+command[1]+' "java -Xms2G -Xmx12G -jar forge-1.16.5-36.2.34.jar" ENTER')
                case 'close':   # 先頭closeのとき
                    execute('tmux send-keys -t '+command[1]+' "stop" ENTER')
                    execute('tmux send-keys -t '+command[1]+' "exit" ENTER')
        except Exception as e:  # エラーが発生したとき
            await message.channel.send(f'error: {e}')
    else:
        return

with open('token.txt', encoding='utf-8') as f:
    client.run(f.read())   # Discordにログイン
```

## 使い方

### 1. ディレクトリ構成をそろえる
上記のdiscordbot.pyと同じディレクトリに **token.txt** と **server** ディレクトリを作成する。
```text:ディレクトリ構成
┝ discordbot.py
┝ token.txt
└ server
```

### 2. Minecraftサーバを準備する
**server** ディレクトリから１段階深いディレクトリにサーバのjarファイルがくるようにする。
```text:ディレクトリ構成（一部省略）
┝ discordbot.py
┝ token.txt
└ server
    └ 250113_test
        ┝ config
        ┝ eula.txt
        ┝ forge-1.16.5-36.2.34.jar
        ├ minecraft_server.1.16.5.jar
```

### 3. token.txtにDiscordボットのトークンをペーストする
```text:token.txt
s3dfD3ydf...
```
※余計なことは書かない！

### 3. 実行するときのコマンドを書き換える
```python3:discordbot.py（前後省略）
@client.event
async def on_message(message):  # メッセージへの対応
    if message.author.bot:  # 送信者がBOTのとき
        return
    elif message.content.strip():  # メッセージが空白でないとき
        try:
            await message.channel.send('wait...')
            command = message.content.split()
            match command[0]:
                case 'open':    # 先頭がopenのとき
                    await message.channel.send('now '+command[0]+'ing...')
                    execute('tmux new -s '+command[1]+' -d')
                    execute('tmux send-keys -t '+command[1]+' "cd server/'+command[1]+'"'+' ENTER')
                    execute('tmux send-keys -t '+command[1]+' "java -Xms2G -Xmx12G -jar forge-1.16.5-36.2.34.jar" ENTER')
```
上記のうち一番下の **"java -Xms2G -Xmx12G -jar forge-1.16.5-36.2.34.jar"** 部分を書き換える。

#### ①サーバファイル
**forge-1.16.5-36.2.34.jar** をサーバファイル名（拡張子含む）に書き換え

#### ②最小メモリ容量
**-Xms2G** を割り当てる最小のメモリ容量に書き換え
例）-Xms1G -Xms4G

#### ③最大メモリ容量
②最小メモリ容量と同様、**-Xmx12G** を割り当てる最大のメモリ容量に書き換え

### 3. discordbot.pyを実行
シェルから以下のコマンドを実行する。
```bash:
python3 discordbot.py
```

## 解説
```python:
import discord
```
ディスコードボットを動かすために必要なモジュール
```python:
import subprocess
```
Pythonからシェルコマンドを実行するために必要
```python:
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
```
ディスコードボットの設定
```python:
command = []
```
送られたメッセージリスト型にする先をつくる
```python:
def execute(shell_script):
    subprocess.run(shell_script, shell=True
```
シェルでのコマンドを使いやすくする
```python:
@client.event
async def on_ready():
    print(f'We have logged in as {client.user}')
```
適切にディスコードへとログインできたかの確認
```python:
@client.event
async def on_message(message):  # メッセージへの対応
    if message.author.bot:  # 送信者がBOTのとき
        return
```
ボット同士で会話しないようにする
```python:
elif message.content.strip():  # メッセージが空白でないとき
    try:
        await message.channel.send('wait...')
    except Exception as e:  # エラーが発生したとき
        await message.channel.send(f'error: {e}')
else:
    return
```
何かしらの読み取れるメッセージが送られたとき、*wait...* と返信する
```python:
command = message.content.split()
```
メッセージを区切ってリスト型に変換
```python:
match command[0]:
    case 'open':    # 先頭がopenのとき
    case 'close':   # 先頭closeのとき
```
メッセージの先頭が*open* のときと*close* のときの分岐
```python:
await message.channel.send('now '+command[0]+'ing...')
```
ディスコードに*now opening...* や*now closeing...* と送信
closeingのスペルがおかしいのは無視
```python:
execute('tmux new -s '+command[1]+' -d')
```
tmuxで新しいセッションを作る
(subprocessをそのまま使うと鯖にstopコマンドが使えなくなってしまうため)
```python:
execute('tmux send-keys -t '+command[1]+' "cd server/'+command[1]+'"'+' ENTER')
```
サーバーディレクトリに移動
```python:
execute('tmux send-keys -t '+command[1]+' "java -Xms2G -Xmx12G -jar forge-1.16.5-36.2.34.jar" ENTER')
```
マイクラサーバーを起動
```python:
execute('tmux send-keys -t '+command[1]+' "stop" ENTER')
```
マイクラサーバーを停止
```python:
execute('tmux send-keys -t '+command[1]+' "exit" ENTER')
```
tmuxのセッションを停止
```python:
with open('token.txt', encoding='utf-8') as f:
    client.run(f.read())   # Discordにログイン
```

## おわりに
最後まで読んでいただき、ありがとうございます。
何か不具合や要望がありましたら、気軽に連絡してください。

## 参考記事
[プログラミング初心者のためのQiita記事投稿テンプレート](https://qiita.com/YutaManaka/items/611069745ff0f68af879)