Skip to main content
Robert "Nayan" Sawyer
Home Portfolio Photography Blog Contact Chat

MineDisbot

A Discord bot for managing Minecraft servers

Published: 3/13/2026 | Updated: 3/13/2026

Tags: code portfolio, programming, discord, minecraft, bot, showcase

MC
# minecraftSimulated Discord channel for MineDisBot
No server active

Last summer I had a problem. I had three Minecraft servers for me and my friends that I wanted to run on the same machine, but I could only run one at a time, and I didn’t want to give my friends SSH access to my server. I needed a simple way for them to start and stop servers without letting them start multiple servers simultaneously. So, as usual, I decided to write a custom tool to solve the problem.

Setting up a discord bot in Python is pretty simple with the discord.py package. You just initialize the bot, and then you can handle events with functions that have the @bot.event decorator.

import discord
from discord.ext import commands

async def on_ready():
    print(f'Logged in as {bot.user})')
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)

@bot.event
async def on_ready():
    print(f'Logged in as {bot.user})')

@bot.event
async def on_message(message):
    print(f'Message from {message.author}: {message.content}')

Then it’s just a matter of verifying the command prefix and channel and you can pass the commands off to command handlers. For example, here is the !listplayers command (you do need another function to trigger these commands, but that’s not shown here):

@bot.command()
async def listplayers(ctx):
    ''' Lists the players in the server '''
    global active_server
    if active_server is None:
        await ctx.send("No server is currently active.")
        return
    subprocess.Popen(f"screen -S {active_server} -p 0 -X stuff 'list\n'", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
    await ctx.send("Please wait, listing players may take a second...")

Notice that the action taken is to open a subprocess and execute a bash command that runs the list command in the screen session the Minecraft server is running in.

IPC (Inter-Process Communication)

One of the features I really wanted to support with the bot was being able to see and respond to server chat via Discord. I could have achieved that by writing an actual Minecraft mod or plugin, but when managing multiple servers at once that starts to get far more complicated than I wanted to deal with.

So I set up the hackiest, most ridiculous, and most insecure solution imaginable. I gave the bot direct access to the command line.

Now that’s not to say I was totally stupid about it. The only bot command that could allow arbitrary execution is !command, and that will fail if there isn’t an appropriately named screen instance. The only way to create one of those instances is to start a server, which will then capture any stdin input, with the screen session terminating when the server closes. An attacker would also have to be an admin in Discord to even send !command, and since it’s only a few of my friends I am not overly worried about the risk. I definitely would not use this bot for a public server though.

With that disclaimer out of the way, the way I achieved this communication is rather interesting. See, I need the servers to be accessible to me when I log in via SSH, hence the screen sessions to manage them. Sending a command to a screen session is pretty straightforward, however, if stdout is going to a screen session, how do I monitor it?

I could tail the logs, but that is unreliable and very slow. The actual file system writes for Minecraft logs can lag behind the server by many seconds. I could, theoretically, have the discord bot hooked into the screen session at all times, but that’s laggy too, not to mention unstable. Fortunately, there is a utility perfectly suited to this situation. I can use tee to split stdout to the screen session AND a file at the same time. And since basically everything in linux is a file pointer, I can create a named pipe that is monitored by the Discord bot and have my output visible from the command line and the bot simultaneously.

Of course it’s a little bit more complicated than that. Capturing the output is what I wanted to achieve, but when the server writes a lot of messages in a short period of time it could cause the bot to stop listening for commands while it reads all those messages. That’s easily fixable by making the bot read the pipe on a separate thread, which with asyncio is significantly simpler in Python than in some of my C programs.

A live demo?!?

I wanted something interactive for this portfolio page, but with work, all the writing that I’m doing, and the general chaos of life, I didn’t feel like rewriting the whole thing in Typescript… so I threw AI at it. In my opinion one of the coolest use cases for AI-assisted coding is situations exactly like this. “Here’s a (pretty simple) tool. I need it to work in another environment, go make a version in Typescript.”

It almost never works on the first try, even with something as simple as this, but with some tweaking and bug fixes it doesn’t take long. I started with just asking for a simulator of the bot written in Typescript. That mostly worked, but it included an index.html file, and was not something I could just drop into my site, so I copied the files to my site and asked the agent to make a react component version.

That spit out a component that rendered (aside from easily fixed import bugs), but with a bunch of sidebar stuff and extra things I didn’t need, so I cut that code out and started testing it. The UI was still a little messed up, and it was not integrated with Astro correctly (client: load), so I did two final passes, one with Opus 4.6 to fix all the UI issues that were cause by me cutting out the unnecessary parts, and one with GPT 5.4 to fix the logic, and that’s the version you see above.

Comments

Loading comments…

I use cookies to prevent spam. To comment, please enable cookies.