Minecraftサーバーを起動させるDiscord BotをPythonで

**追記： これは古い記事です。[こちらの記事](https://qiita.com/insane_catt/items/01c4e22f151db1e363d3)（ここにある内容のほとんどを移しています）をお読みください。こちらの記事では、直接ダウンロードして実行する人のために、新たなコマンドも追加し、大幅に改善したものをMITライセンスでGitHubで公開してあります。ご利用をご検討ください。** この記事はあくまでPythonの学習に役立てるようにお使いいただけると。
## 概要
Discord botを利用してMinecraftサーバーを起動できるようにする。
Discord botのプログラミングはPython。
MinecraftサーバーはJava版（プラグインを導入して、統合版も入れる仕様になっている）。
Minecraftサーバー、Discord bot、ともに同じコンピューターの上で実行する。
Minecraftサーバーの起動はscreen内で行う。
※このプログラムには終了コマンドを用意していないが、DiscordSRVのコンソールにstopと送れば終了できるためである。


なるべく初心者向けに書くつもりです。コードだけ見たい場合は目次から飛んでください。

### 筆者の動作環境
- 動作コンピューター：Raspberry Pi 4 Model B （RAM8GB、SDカード64GB）
- 利用しているMinecraftサーバーソフト：Paper 1.20.1（プラグインとして、**DiscordSRV**、GeyserMC（floodgate付き）を導入済み）

### 必要な環境構築
- **Python3**：Python製のbotなので、当たり前だが必要。
- **discord.py**：Discord botを動かすために必要なライブラリ。Pycordなどと混同しないで。ターミナルで以下のコマンドを入力し、インストールする。
```shell
python3 -m pip install -U discord.py
```

### 推奨する環境構築
もしこの後出すコードをそのまま（少しだけ変える必要はあるが）使いたい場合はこの環境構築は必須かな。
- **Paper**：軽量で、**プラグインを導入可能**な、SpigotベースのJava版Minecraftサーバーソフトウェアである。これの上に、次に説明するDiscordSRVや、これは任意だが、統合版とのクロスプレイを可能にするGeyserMCを導入することができる。
- **DiscordSRV**：Discordに、サーバーの参加ログなどや、Minecraftサーバーのコンソールを表示させることができるプラグイン。筆者はこれのコンソールを利用しているので、自作のDiscord botにはMinecraftサーバーを終了させるコマンドを持たせていない。なので、DiscordSRVを導入しない場合や、公式配布のMinecraftサーバーソフトウェア（重い、プラグイン入らない）を使用する場合には、SSHなどからscreenに接続し、stopコマンドを打つ手間があるので、めんどくさい。DiscordSRVの使い方は長くなるので割愛するが、便利だし有名なので導入するべき。
![SRV_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3530195/462ce4eb-3ff2-20f7-7e28-6db318c84b4c.png)
図１：参加ログ以外にも、進捗の達成や、死亡メッセージを表示することも可能
![SRV_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3530195/507aceb4-f63b-e836-8b02-fb24a64245cd.png)
図2：Minecraftサーバーに直接コマンドを打つことが可能なコンソール画面。
seedコマンドとかも普通に使える。
PaperLaunchBotというのが、今回自作したbot。

- **screen**：Minecraftサーバーをバックグラウンドで動作させ、またそれに自由に接続・切断することを可能にするソフト。以下のコマンドをターミナルで実行し、インストールできる。
```shell
sudo apt install screen -y
```

## 肝心のコード
上部にある設定欄を適宜書き換える必要がある。**絶対に必要**なのは、「TOKEN」を、次に書く「使い方」のところで取得したトークンで置き換えること。
```python
# 以下は簡単な設定欄です

TOKEN = "TOKEN"  # Discord botのトークンを、左欄の「TOKEN」と置き換えてください
SERVER_PATH = "minecraft/java/paper"  # Minecraftサーバー実行ファイルのあるフォルダのパスを入力してください
JAR_FILE = "paper.jar"  # 使用しているMinecraftサーバー実行ファイルの名前を入力してください
SCREEN_NAME = "papermc"  # 使用するscreenセッションの名前を入力してください
MAX_RAM = "5"  #使用する最大メモリを設定してください。デフォルトは5
MIN_RAM = "2"  #使用する最小メモリを設定してください。デフォルトは2


# 以下はコードです。必要に応じていじってください

from discord import Intents, Client, Interaction, Game
from discord.app_commands import CommandTree
import subprocess

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


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


    async def on_ready(self):
        await client.change_presence(activity=Game(name="/startでサーバーを起動"))  #「○○をプレイ中」と表示するところ
        print(f"login: {self.user.name} [{self.user.id}]")


intents = Intents.default()
client = MyClient(intents=intents)


@client.tree.command(name="hello", description="Hello, world!")  # コマンドの名前と、コマンドの説明です
async def hello(interaction: Interaction):
    await interaction.response.send_message(f'Hello, {interaction.user.mention}!')

@client.tree.command(name="start", description="サーバーを起動する")  # コマンドの名前と、コマンドの説明です
async def start(interaction: Interaction):
    if is_server_running():
        await interaction.response.send_message('サーバーは既に起動しています')
    else:
        start_server()
        await interaction.response.send_message('サーバーを起動します')


def is_server_running():  # サーバーが動作しているか確認する関数です
    process = subprocess.Popen(f"screen -ls {SCREEN_NAME}", stdout=subprocess.PIPE, shell=True)
    output, _ = process.communicate()
    return SCREEN_NAME in output.decode()

def start_server():  # screenを利用してサーバーを起動するコマンドは以下になります
    subprocess.Popen(f"screen -dmS {SCREEN_NAME} java -Xmx{MAX_RAM}G -Xms{MIN_RAM}G -jar {JAR_FILE} nogui", shell=True)

client.run(TOKEN)
```

## 使い方
1. さっき書いたPythonコードをよさげな名前のテキストファイルにコピペし、拡張子を.pyにし、Pythonファイルとして保存する。
1. https://discord.com/developers/applications にアクセスし、New Applicationボタンをクリック。名前を付け、チェックを入れ、Createする。
1. 画面左にBotと出るので、クリックし、Reset Tokenボタンを押し、Yes, do it!をクリック、その後Copyボタンを押して**トークンをさっきのPythonファイル内の「TOKEN」と置き換える。**
1. Privileged Gateway Intentsのところの切り替えスイッチを全部オンにし、下に出てきたSave changesボタンをクリック。
1. 画面左のOAuth2→URL Generatorとクリックし、大量のチェックボックスの中からbot、applications.commandsにチェックを入れる。その後、下にまた出てきた大量のチェックボックスの中から、もうめんどくさいのでAdministrator（管理者って意味）をクリック。（セキュリティー的に怖かったらいちいち設定する必要があるけど攻撃されるかもとか思わなければ管理者でいいと思う、自分のbotだしトークンを人に教えたり流出しなければいい。）
1. 一番下に出たGENERATED URLをコピーしてブラウザで開き、botをDiscordサーバーに招待する。
1. 以下のコマンドでさっきのPythonファイルを実行する。
```shell
python3 〇〇.py
```
Botがオンラインになったら、Discordに/startとコマンドを打つとしばらくしてマイクラサーバーが起動する。

## 上手くいかない場合の連絡先
Twitter（X笑）
https://twitter.com/insane_catt

DMまでどうぞ

## 追記：なぜこれを作ったか
PaperやGeyserMCのアップデートをする際、毎回サーバーを停めて新しいファイルにアップデートする必要があり、そこから起動するときに毎回Raspberry Piにキーボードをさしたり画面につなげたりするのがだるかったから。

おわり