Minecraftのサーバーをdiscordから起動したい

# 経緯
MinecraftのサーバーをAWS lightsailで作ったのだが、常設はお金がかかる。しかし管理者しか起動できないなら、サーバーをレンタルした意味がない。じゃあbotに管理してもらおう。

# 内容
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447847/5799defc-aa47-d31a-03c3-c8c5381413f2.png)
このように、
・サーバーの残りキャパシティの表示。 (AWSの仕様でサーバーの体力みたいなもんです)
・サーバーの開始。
・サーバーの強制終了。
が可能です。
また、サーバーのステータスによってdiscord上のbotのステータスも変化します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447847/0c9c7170-0f52-9c26-7006-68e7f26936f6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447847/6b9cb407-6a44-5a1e-3ec8-09e455a8a677.png)

# ソース
以下です。

## bot本体
sys.path.appendがないと、sysytemctlで常時実行させた際に、discordがimoportできなかった気がします。
事前にMinecraftServerを起動できるrun.shを作成しておいてください。
/killについてですが、javaと名のつくプロセスを軒並み終了させてるだけなので、他のjavaプロセスと併用する際は気をつけてください。

```
#!/usr/bin/env python3
import sys

sys.path.append('/home/ubuntu/.local/lib/python3.8/site-packages')

import discord
from discord.ext import tasks
import psutil
import os
CHANNEL_ID = 0000 # your channel id
CLIENT_TOKEN = "YourDiscordClientToken"

svprocess = None
client = discord.Client()

@tasks.loop(seconds=10)
async def check_minecraft_status():
  is_run = False
  for proc in psutil.process_iter():
    try:
      if proc.name() == 'java':
        is_run = True
    except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
      pass
  if is_run:
    game = discord.Game("Minecraft")
    await client.change_presence(status=discord.Status.online, activity=game)
  else:
    await client.change_presence(status=discord.Status.idle, activity=None)

@client.event
async def on_ready():
  print('Logged in as')
  print(client.user.name)
  print(client.user.id)
  print('------')
  check_minecraft_status.start()

@client.event
async def on_message(message):
  ch = client.get_channel(CHANNEL_ID)
  if message.content == "/start":
    global svprocess 
    is_run = False
    print('Check existing server')
    for proc in psutil.process_iter():
      try:
        if proc.name() == 'java':
          is_run = True
      except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
        await ch.send('Error1')
    if is_run:
      await ch.send('Server has been running already. Login the server and type /stop in Minecraft game.\n You can also use /kill command in discord to kill the process (I recommend /stop).')
    else:
      os.system('/home/ubuntu/run.sh')
#      svprocess = subprocess.Popen('/home/ubuntu/run.sh', shell=True,  stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#      logmsg = ''
#      while True:
#        line = svprocess.stdout.readline().decode().strip()
#        if line:
#          logmsg = line  
#        if not line and svprocess.poll() is not None:
#          print("[SERVER] Line ended. Is the server stopped?")
#          break  
      await ch.send("Wait a minute.")

  if message.content == '/isRun':
    is_run = False
    for proc in psutil.process_iter():
      try:
        if proc.name() == 'java':
          is_run = True
      except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
        ch.send('No process. Server is Down.')
        pass
    if is_run:
      await ch.send('Server is Running.')
    else:
      await ch.send('Server is Down.')
  
  if message.content == '/kill':
    for proc in psutil.process_iter():
      try:
        if proc.name() == 'java':
          command = 'kill -KILL ' + str(proc.pid)
          os.system(command)
      except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
        pass
    await ch.send('Kill confirmed.')

  if message.content == '/psaux':
    msg = ''
    for proc in psutil.process_iter():
      try:
        msg = msg + 'NAME: ' + proc.name() + ' \tPID: ' + str(proc.pid) + '\n'
      except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
        pass
    print(msg)
    try:
      await ch.send(msg)
    except (Exception):
      await ch.send("Process list is too large. Extracted last 2000 char.\n" + msg[-1900:])

  if message.content == '/status':
    await ch.send(file=discord.File('/home/ubuntu/tmppictures/stat.png'))

    

    


client.run(CLIENT_TOKEN)

```

## サーバーのキャパシティのグラフを生成するやつ

AWSのboto3を使用します。AWS CLI, Boto3のインストールが必要です。Boto3はAWS CLIの認証情報に基づいて動作するので、AWS CLIのconfigureを済ませておいてください。

```
#!/usr/bin/env python3
import sys

sys.path.append('/home/ubuntu/.local/lib/python3.8/site-packages')

import boto3
from datetime import datetime
from datetime import timedelta
from matplotlib import pyplot
import pandas
import seaborn as sns

def make_statistics_file():
  client = boto3.client('lightsail')


  responce = client.get_instance_metric_data( instanceName='Minecraft1',
                                              metricName='BurstCapacityPercentage',
                                              period=120,
                                              startTime=datetime.now() + timedelta(days=-2),
                                              endTime=datetime.now(),
                                              unit='Percent',
                                              statistics=['Maximum'])

  df = pandas.DataFrame(columns=['Capacity','Time'])


  for h,i in enumerate(responce['metricData']):
    df.loc[h] = [(i['maximum']),i['timestamp']]
  df.set_index('Time',inplace=True)
  sns.set()
  sns.set_style(style='dark')
  df.plot(grid=True)
  pyplot.tick_params(labelsize=9)
  pyplot.ylim(-1, 101)
  pyplot.title(datetime.now().strftime('%m/%d %H:%M') + " created.")
  pyplot.savefig('/home/ubuntu/tmppictures/stat.png')
  pyplot.close('all')




if __name__=='__main__':
  make_statistics_file()

```

# その他
systemctl用の.serviceファイルなどはgithubにあげておきます。
User, Groupの指定をしないと動かなかった気がするので、気をつけてください。
https://github.com/ha191180/qiita_entry/tree/master/qiita%20minecraft