Brazil's biggest MU Online portal — since 2003
Tutorial Intermediate Web

How to build a Discord bot with custom commands for your MU Online server

Build a Discord bot in Node.js for your MU Online server, with ranking, server status, character lookup and account linking commands, querying the MuServer's MS SQL database in real time.

RO Rodrigo · Updated on Jun 29, 2022 · ⏱ 16 min read
Quick answer

A well-built Discord bot is nowadays one of the pillars of retention for any MU Online server: it's the player's first point of contact with the community, the channel where rankings, events and server status appear in real time, and the bridge between the game and support. Unlike generic "ready-mad

A well-built Discord bot is nowadays one of the pillars of retention for any MU Online server: it's the player's first point of contact with the community, the channel where rankings, events and server status appear in real time, and the bridge between the game and support. Unlike generic "ready-made" bots, a custom bot queries your MuServer's database directly, enabling commands like /ranking, /personagem and /status with real, up-to-the-second data. This tutorial builds a bot in Node.js with discord.js, from scratch through deployment, including a secure connection to MS SQL Server and the commands most requested by the Brazilian MU community.

Why build your own bot instead of using generic ones

General-purpose bots (moderation, music, memes) know nothing about your MU server. A bot of your own reads directly from the MuServer tables — Character, AccountCharacter, MEMB_STAT — and turns that data into useful commands inside Discord, so the player doesn't need to open the website or log into the game just to check their ranking position. This also opens the door to exclusive automations: automatic boss-spawn announcements, server-down alerts, and event notifications, all within the channel where the community already hangs out.

Prerequisites

  • Node.js 18 or higher installed on the machine that will run the bot.
  • An application registered in the Discord Developer Portal, with the bot token generated.
  • Network access to your MuServer's MS SQL Server (host, port, username and password with read permission).
  • Permission to add bots to the community's Discord server (administrator role or server owner).
  • A code editor (VS Code is the most common) and git installed — optional but recommended.

Project structure

Organize the bot into folders separated by responsibility, making it easier to maintain as commands grow:

mu-discord-bot/
├── index.js
├── deploy-commands.js
├── config.json
├── commands/
│   ├── ranking.js
│   ├── status.js
│   ├── personagem.js
│   └── vincular.js
├── db/
│   └── connection.js
└── package.json

Step 1 — Create the application and the bot token

In the Discord Developer Portal, create a new application, go to "Bot" and click "Add Bot". Copy the generated token — it should never be committed to git or shared. Under "OAuth2 > URL Generator", check the bot and applications.commands scopes, select the Send Messages, Use Slash Commands and Embed Links permissions, and use the generated URL to invite the bot to your Discord server.

Step 2 — Install dependencies and set up the base

mkdir mu-discord-bot && cd mu-discord-bot
npm init -y
npm install discord.js mssql dotenv

Create a .env file (never version this) with the credentials:

DISCORD_TOKEN=your_token_here
CLIENT_ID=application_id
GUILD_ID=discord_server_id
DB_HOST=127.0.0.1
DB_USER=mu_bot_readonly
DB_PASS=strong_password_here
DB_NAME=MuOnline

Step 3 — Connect to the MS SQL database with a restricted account

Create the mu_bot_readonly database account with SELECT-only permission on the Character, AccountCharacter and MEMB_STAT tables. This is the difference between a leaked token becoming a serious security incident or just an exposure of public ranking data.

// db/connection.js
const sql = require('mssql');

const config = {
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
  server: process.env.DB_HOST,
  database: process.env.DB_NAME,
  options: { encrypt: false, trustServerCertificate: true }
};

async function getPool() {
  return sql.connect(config);
}

module.exports = { getPool, sql };

Step 4 — Ranking command

// commands/ranking.js
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { getPool, sql } = require('../db/connection');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('ranking')
    .setDescription('Shows the server\'s top 10 resets'),
  async execute(interaction) {
    await interaction.deferReply();
    const pool = await getPool();
    const result = await pool.request().query(`
      SELECT TOP 10 Name, cLevel, Resets
      FROM Character
      ORDER BY Resets DESC, cLevel DESC
    `);
    const embed = new EmbedBuilder()
      .setTitle('🏆 Top 10 Resets')
      .setColor(0xffcc00)
      .setDescription(
        result.recordset
          .map((r, i) => `**${i + 1}.** ${r.Name} — Lvl ${r.cLevel} — ${r.Resets} resets`)
          .join('\n')
      );
    await interaction.editReply({ embeds: [embed] });
  }
};

Step 5 — Character lookup command

The /personagem <name> command should fetch only public data (level, class, resets, guild), never sensitive account information such as email or password. Validate the input parameter with sql.VarChar to prevent SQL injection — never concatenate strings directly into the query.

// commands/personagem.js (secure query snippet)
const request = pool.request();
request.input('nome', sql.VarChar, characterName);
const result = await request.query(
  'SELECT Name, cLevel, Class, Resets FROM Character WHERE Name = @nome'
);

Step 6 — Server status command

A /status command that checks whether the GameServer and ConnectServer are up is one of the most used by the community, since it prevents players from trying to log into a server that's down without knowing it. The simplest check is opening a TCP socket to the ConnectServer port (usually 44405) with a short timeout and reporting success or failure.

Step 7 — Linking a Discord account to a game account

StepWhere it happensDescription
Generate codeWeb panel or in-game commandSystem generates a 6-digit code valid for 10 minutes
Enter codeDiscord (DM to the bot)Player sends /vincular 123456
Validate and saveBot + bot's own databaseBot checks the code and saves the discord_id ↔ account_id link
Use the linkFuture commands/meupersonagem already knows which account to look up without the player typing the name

Never implement account linking by asking for the game account password over Discord — that normalizes a classic phishing vector within the community itself.

Step 8 — Register the slash commands and bring the bot online

// deploy-commands.js
const { REST, Routes } = require('discord.js');
const fs = require('fs');
require('dotenv').config();

const commands = fs.readdirSync('./commands')
  .map(file => require(`./commands/${file}`).data.toJSON());

const rest = new REST().setToken(process.env.DISCORD_TOKEN);

(async () => {
  await rest.put(
    Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
    { body: commands }
  );
  console.log('Commands registered successfully.');
})();

Run node deploy-commands.js once to register the commands, then start the bot with node index.js. For production, use a process manager like PM2 (pm2 start index.js --name mu-bot) to restart it automatically in case of a crash.

Step 9 — Hosting and continuous monitoring

A small VPS (1 vCPU, 1 GB RAM) can already run a medium-sized bot without issues, as long as the database isn't on the same overloaded instance as the GameServer. Set up file-based logging (not just console) and a simple alert — even a plain Discord webhook — so you know when the bot goes down, instead of finding out only when a player complains.

Common errors and fixes

SymptomLikely causeFix
Bot doesn't respond to commandsdeploy-commands.js not run after a changeRun the deploy again and wait for propagation (up to 1h for global commands)
Database connection errorFirewall blocking port 1433Open the port only for the bot machine's IP
Ranking command is slowQuery missing an index on the Resets columnCreate an index on the column used in ORDER BY
Token exposed on GitHub.env committed by mistakeRevoke the token immediately and generate a new one
Bot crashes on its own after hours runningProcess without a restart managerUse PM2 or systemd with automatic restart

Launch checklist

  • Application and token created in the Discord Developer Portal.
  • Database account restricted to SELECT created and tested.
  • .env out of version control (.gitignore configured).
  • Ranking, status and character lookup commands tested in production.
  • Account-linking flow validated with no password exposure.
  • Bot process running under PM2 or equivalent with automatic restart.
  • Logs and downtime alert configured.

With the bot online and responding to commands in real time, the natural next step is integrating it with your server's automatic events — for example, announcing boss spawns or the start of invasions directly on Discord, closing the loop between the MU Online server and the community playing on it.

Frequently asked questions

Do I need to know how to program to have a bot like this?

Basic JavaScript knowledge is recommended, but this tutorial provides the complete base code. By copying the files and adjusting the database connection string, an administrator with intermediate knowledge can get the bot up and running without writing logic from scratch.

Does the bot need to run on the same server as the game?

No. It's actually recommended to run it on a separate machine, as long as it has network access to the MuServer's MS SQL Server (port 1433 open) or to a read-only replica of the database, which is safer.

Is it safe to give the bot direct access to the game's database?

Only with a database account restricted to SELECT on the necessary tables (ranking, characters, accounts). Never use the SQL Server admin account on the bot — if the bot token leaks, a limited read-only access drastically reduces the possible damage.

How do I build a secure Discord-to-game account linking command?

Generate a temporary 6-digit code inside the web panel or through an in-game command, and ask the player to type that code to the bot via direct message. The bot records the link in its own table — never ask for the player's game account password over Discord.

Can this bot be hosted for free?

For testing, yes, on a small VPS or a free Node.js hosting plan. For production, with hundreds of simultaneous users, a paid VPS with at least 1 vCPU and 1 GB of RAM dedicated to the bot, separate from the GameServer, is recommended.

RO
Founder & editor-in-chief

Rodrigo has run ViciadosMU since the portal's early days. A specialist in MU Online server creation and administration, game history and the evolution of the seasons — he wrote much of the archive before 2024.

Keep reading

Related articles