Create a discord bot for minecraft bedrock docker

Purpose

Explain how I create a discord bot for Minecraft bedrock on docker.
No explanation on docker while be done here but there is a lot of tutorial for installing docker on server.

Prerequisite

  • a Linux server (at home or on Internet)
  • python3
  • linux screen (sudo apt-get install screen)
  • create a dev account on discord https://discord.com/developers and create a bot

Minecraft docker

After you fully installed docker on the server start the Minecraft bedrock container from dockerhub:

1
2
3
4
5
6
7
8
9
10
11
docker run -d -it --name Minecraft \
-e EULA=TRUE \
-p 19132:19132/udp \
-e LEVEL_NAME='NightMare' \
-e LEVEL_SEED=789905374 \
-e SERVER_NAME='MyServer' \
-e MAX_PLAYERS=5 \
-e DEFAULT_PLAYER_PERMISSION_LEVEL=member \
-e GAMEMODE=survival \
-e DIFFICULTY=hard \
itzg/minecraft-bedrock-server

The full documentation of this container is here https://hub.docker.com/r/itzg/minecraft-bedrock-server

Know you will have something like :

1
2
3
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5246641c80da itzg/minecraft-bedrock-server "/usr/local/bin/entr…" 3 hours ago Up 39 minutes (healthy) 0.0.0.0:19132->19132/udp Minecraft

to interact with the server you need to do:

1
2
3
4
5
$ docker attach Minecraft
DEBU[2562] Forwarding signal signal="window changed"
DEBU[2562] Forwarding signal signal="window changed"
list
There are 0/5 players online:

Okay, now we have a Minecraft server and we can send command to it \o/.

To exit (docker) CTRL+P CTRL+Q. If you run CTRL+C you exit the docker and he stop :/

Now we are going to use “screen” to keep the “docker attach” open even we close our term/ssh.

1
2
3
$ screen -S Minecraft
$ docker attach Minecraft
CTRL+A CTRL+D
  • screen -S Minecraft: to create a screen with name “Minecraft”
  • docker attach : to open console mode of the container
  • CTRL+A CTRL+D : to exit the screen and keep running the code inside

Now we have a screen session open (screen -ls to list), but how can we send Minecraft command on it !!!

1
$ screen -S Minecraft -p 0 -X stuff "weather clear^M"
  • -S : session name
  • -p : preselect window
  • -X : send command
  • ^M : Enter

Here we send the command “weather clear” to the server.

Extract some log from docker

If we want to extract user connection and disconnection we can trace the log of the server.

For that we can create a job with bash to extract last log

1
$ docker logs --since=1m Minecraft > /var/log/dockerMinecraftLast.log

With this code we can extract last log from 1 min.

If you add it to crontab you can schedule the task every minute

1
2
$ crontab -e
* * * * * /PathToTheScript/lastDockerMinecraftLog.sh

The bot

code not fully optimized :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import discord
import asyncio

import os
from bdsPing import bdsPing
import re
import datetime

server='YourServerIP'
port=19132

class MyClient(discord.Client):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# create the background task and run it in the background
self.bg_task = self.loop.create_task(self.my_background_task())

async def on_ready(self):
print('Logged in as')
print(self.user.name)
print(self.user.id)
print('------')

async def on_message(self, message):

if message.content.startswith('!server'):
datas = bdsPing(server,port)
await message.channel.send("Server: {}:{}\nServer Name: {}\nPlayer: {}/{}\nGame Mode: {}".format(server,port,datas['ServerName'],datas['PlayerCount'],datas['PlayerLimit'],datas['GameMode']))

if message.content.startswith('!stats'):
stream = os.popen('docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" Minecraft')
out = stream.read()
await message.channel.send(out)

if message.content.startswith('!setDay'):
os.popen('screen -S Minecraft -p 0 -X stuff "time set day^M"')
await message.channel.send('set time to [day]')

if message.content.startswith('!setNight'):
os.popen('screen -S Minecraft -p 0 -X stuff "time set night^M"')
await message.channel.send('set time to [night]')

if message.content.startswith('!normal'):
os.popen('screen -S Minecraft -p 0 -X stuff "difficulty normal^M"')
await message.channel.send('set difficulty to [normal]')

if message.content.startswith('!hard'):
os.popen('screen -S Minecraft -p 0 -X stuff "difficulty hard^M"')
await message.channel.send('set difficulty to [hard]')

if message.content.startswith('!thunder'):
os.popen('screen -S Minecraft -p 0 -X stuff "weather thunder^M"')
await message.channel.send('set weather to [thunder]')

if message.content.startswith('!sun'):
os.popen('screen -S Minecraft -p 0 -X stuff "weather clear^M"')
await message.channel.send('set weather to [clear]')

if message.content.startswith('!help'):
msg = "Prefix cmd with <!> \nserver : show server info \nstats : show server stat \nsetDay : set day \nsetNight : set night \nnormal : set difficulty to normal \nhard : set difficulty to hard \nthunder : set weather to thunder \nsun : set weather to sun\nopAdmin : set User operator \ndeOpAdmin : remove User operator \ncreativeAdmin : set creative mode to User \nsurvieAdmin : set survie mode to User"
await message.channel.send(msg)

if message.content.startswith('!opAdmin'):
os.popen('screen -S Minecraft -p 0 -X stuff "op MicrosoftGameName^M"')
await message.channel.send('set operator [MicrosoftGameName]')

if message.content.startswith('!deOpAdmin'):
os.popen('screen -S Minecraft -p 0 -X stuff "deop MicrosoftGameName^M"')
await message.channel.send('set deoperator [MicrosoftGameName]')

if message.content.startswith('!creativeAdmin'):
os.popen('screen -S Minecraft -p 0 -X stuff "gamemode 1 MicrosoftGameName^M"')
await message.channel.send('set gamemode [creative MicrosoftGameName]')

if message.content.startswith('!survieAdmin'):
os.popen('screen -S Minecraft -p 0 -X stuff "gamemode 0 MicrosoftGameName^M"')
await message.channel.send('set gamemode [survie MicrosoftGameName]')


async def my_background_task(self):
await self.wait_until_ready()
oldLines = []
channel = self.get_channel(YourChannelID) # channel ID goes here
while not self.is_closed():
currentLines = []
f = open("/var/log/dockerMinecraftLast.log", "r")
lines = f.readlines()
for line in lines:
m = re.search(r"Player (connected|disconnected): ([a-zA-Z0-9]{1,}), xuid: ([0-9]{1,})",line)
if m is not None:
currentTime = datetime.datetime.now() + datetime.timedelta(hours=2)
prettyTime = "{}:{}".format(currentTime.hour,currentTime.minute)
log = "[{}] {} {}".format(prettyTime,m.group(2),m.group(1))
currentLines.append(log)

for current in currentLines:
if current not in oldLines:
await channel.send(current)

await asyncio.sleep(60) # task runs every 60 seconds
oldLines = currentLines

client = MyClient()
client.run('YOUR DISCORD KEY')

bdsPing.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import socket
import json

server='YourServerIp'
port=19132

def bdsPing(server,port):
#Ping
bytesToSend = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\xfe\xfe\xfe\xfe\xfd\xfd\xfd\xfd\x12\x34\x56\x78\x00\x00\x00\x00\x00\x00\x00\x00'

serverAddressPort = (server,port)
bufferSize = 1024

# Create a UDP socket at client side
UDPClientSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)

# Send to server using created UDP socket
UDPClientSocket.sendto(bytesToSend, serverAddressPort)

msgFromServer = UDPClientSocket.recvfrom(bufferSize)

#msg = "Message from Server {}".format(msgFromServer[0])
data = msgFromServer[0][35:].decode("utf-8")
indexes = ['Game','ServerName','ProtocolVersion','ServerVersion','PlayerCount','PlayerLimit','ServerId','WorldName','GameMode','NintendoLimited','Ipv4Port','Ipv6Port']
datas = data[:-1].split(';')
out = {}
for i in range(len(indexes)):
out[indexes[i]] = datas[i]

return out
#return json.dumps(out)


if __name__ == '__main__':

print(bdsPing(server,port))

To run the bot in background start it in a screen session.

1
2
3
screen -S BdsBot
python3 bot.py
CTRL+A CTRL+D

bds bot screen example

enjoy ;)