Brazil's biggest MU Online portal — since 2003
Tutorial Advanced Tutoriais

How to Integrate a Discord Bot with Your MU Online Server

Learn how to build and configure a Discord bot that connects to your MU Online SQL Server database to display rankings, server status, and live notifications.

VI ViciadosMU Team · Updated on 3 jul 2026 · ⏱ 12 min read

Overview

Integrating a Discord bot with your MU Online server lets you automate tasks such as displaying live rankings, announcing Castle Siege events, broadcasting top resets, and monitoring server status — all directly inside your community's Discord channels.

This tutorial covers building a bot with Node.js + discord.js v14, connecting to the SQL Server database of a MuServer installation (Season 6 Episode 3, compatible with S4–S13), and deploying it on the same Windows Server VPS where your MU server runs.


Prerequisites

  • VPS running Windows Server 2012/2016/2019 with MuServer installed
  • SQL Server 2008/2012/2014/2017 with the MuOnline database accessible
  • Node.js 18 LTS installed on the VPS (or on another machine with access to port 1433)
  • A Discord developer account with permission to create applications
  • Access to the Discord Developer Portal

Part 1 — Create the Discord Application and Bot

1. Go to discord.com/developers/applications and click New Application.

2. Give the application a name (e.g., MuBot ViciadosMU) and confirm.

3. In the left sidebar, go to Bot → click Add Bot → confirm.

4. Under Privileged Gateway Intents, enable:

  • SERVER MEMBERS INTENT
  • MESSAGE CONTENT INTENT

5. Copy the bot Token — you will use it in the .env file. Never share this token publicly.

6. Go to OAuth2 → URL Generator, check the bot and applications.commands scopes, then check these permissions:

  • Send Messages
  • Embed Links
  • Read Message History
  • Use Slash Commands

7. Copy the generated URL, open it in a browser, and add the bot to your community's Discord server.


Part 2 — Configure SQL Server for Connections

1. Open SQL Server Configuration Manager on the VPS.

2. Under SQL Server Network Configuration → Protocols for MSSQLSERVER, enable TCP/IP.

3. Double-click TCP/IP → go to the IP Addresses tab → scroll down to IPAll → set TCP Port to 1433.

4. Restart the SQL Server service:

net stop MSSQLSERVER
net start MSSQLSERVER

5. Create a dedicated read-only SQL login for the bot (do not use sa):

USE [master]
GO

CREATE LOGIN [mubot_reader] WITH PASSWORD = 'YourStrongPassword!123',
    DEFAULT_DATABASE = [MuOnline],
    CHECK_EXPIRATION = OFF,
    CHECK_POLICY = OFF;
GO

USE [MuOnline]
GO

CREATE USER [mubot_reader] FOR LOGIN [mubot_reader];
GO

-- Grant read-only access to the required tables only
GRANT SELECT ON [dbo].[Character] TO [mubot_reader];
GRANT SELECT ON [dbo].[MEMB_INFO] TO [mubot_reader];
GRANT SELECT ON [dbo].[CastleSiege] TO [mubot_reader];
GO
Atenção: Never grant INSERT, UPDATE, or DELETE permissions to the bot user. Write access to the MU Online database can compromise server integrity.

6. If the bot runs on an external machine, open port 1433 in the Windows Firewall:

netsh advfirewall firewall add rule name="SQL Server MuBot" ^
    protocol=TCP dir=in localport=1433 action=allow

Part 3 — Install and Configure the Bot Project

1. On the VPS (or the machine hosting the bot), open PowerShell and create the project structure:

mkdir C:\MuBot
cd C:\MuBot
npm init -y
npm install discord.js@14 mssql dotenv

2. Create C:\MuBot\.env:

DISCORD_TOKEN=YOUR_BOT_TOKEN_HERE
CLIENT_ID=YOUR_APPLICATION_ID
GUILD_ID=YOUR_DISCORD_SERVER_ID
DB_SERVER=127.0.0.1
DB_PORT=1433
DB_DATABASE=MuOnline
DB_USER=mubot_reader
DB_PASSWORD=YourStrongPassword!123

3. Create C:\MuBot\database.js:

const sql = require('mssql');
require('dotenv').config();

const config = {
    server: process.env.DB_SERVER,
    port: parseInt(process.env.DB_PORT),
    database: process.env.DB_DATABASE,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    options: {
        encrypt: false,
        trustServerCertificate: true,
        enableArithAbort: true,
    },
    pool: {
        max: 10,
        min: 0,
        idleTimeoutMillis: 30000,
    },
    connectionTimeout: 15000,
    requestTimeout: 15000,
};

const pool = new sql.ConnectionPool(config);
const poolConnect = pool.connect();

pool.on('error', err => {
    console.error('[DB] Connection pool error:', err);
});

module.exports = { pool, poolConnect, sql };

Part 4 — Implement the Slash Commands

1. Create C:\MuBot\commands\ranking.js:

const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { pool, poolConnect, sql } = require('../database');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('ranking')
        .setDescription('Shows the Top 10 resets on the server'),

    async execute(interaction) {
        await interaction.deferReply();

        try {
            await poolConnect;
            const request = pool.request();

            const result = await request.query(`
                SELECT TOP 10
                    ROW_NUMBER() OVER (ORDER BY c.Resets DESC, c.cLevel DESC) AS Position,
                    c.Name        AS Character,
                    c.Resets      AS Resets,
                    c.cLevel      AS Level,
                    CASE c.Class
                        WHEN 0  THEN 'Dark Wizard'
                        WHEN 1  THEN 'Soul Master'
                        WHEN 2  THEN 'Grand Master'
                        WHEN 16 THEN 'Dark Knight'
                        WHEN 17 THEN 'Blade Knight'
                        WHEN 18 THEN 'Blade Master'
                        WHEN 32 THEN 'Fairy Elf'
                        WHEN 33 THEN 'Muse Elf'
                        WHEN 34 THEN 'High Elf'
                        WHEN 48 THEN 'Magic Gladiator'
                        WHEN 64 THEN 'Dark Lord'
                        WHEN 80 THEN 'Summoner'
                        ELSE 'Unknown'
                    END AS Class
                FROM MuOnline.dbo.Character c
                WHERE c.Name NOT LIKE 'GM_%'
                ORDER BY c.Resets DESC, c.cLevel DESC
            `);

            const embed = new EmbedBuilder()
                .setTitle('🏆 Top 10 Resets')
                .setColor(0xFFD700)
                .setTimestamp()
                .setFooter({ text: 'ViciadosMU' });

            const lines = result.recordset.map(row =>
                `**${row.Position}.** ${row.Character} — ` +
                `${row.Resets} Resets | Lv ${row.Level} | ${row.Class}`
            ).join('\n');

            embed.setDescription(lines || 'No data found.');

            await interaction.editReply({ embeds: [embed] });

        } catch (err) {
            console.error('[ranking] Error:', err);
            await interaction.editReply('Database query failed. Please try again.');
        }
    },
};

2. Create C:\MuBot\commands\status.js:

const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
const { pool, poolConnect, sql } = require('../database');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('status')
        .setDescription('Shows the current MU Online server status'),

    async execute(interaction) {
        await interaction.deferReply();

        try {
            await poolConnect;
            const request = pool.request();

            const result = await request.query(`
                SELECT COUNT(*) AS Online
                FROM MuOnline.dbo.MEMB_STAT
                WHERE ConnectStat = 1
            `);

            const online = result.recordset[0].Online;

            const embed = new EmbedBuilder()
                .setTitle('🟢 Server Status')
                .setColor(0x00FF00)
                .addFields(
                    { name: 'Players Online', value: `${online}`, inline: true },
                    { name: 'Server', value: 'Online', inline: true }
                )
                .setTimestamp()
                .setFooter({ text: 'ViciadosMU' });

            await interaction.editReply({ embeds: [embed] });

        } catch (err) {
            console.error('[status] Error:', err);
            await interaction.editReply('Server unavailable or connection error.');
        }
    },
};

Part 5 — Main Entry Point and Command Registration

1. Create C:\MuBot\index.js:

const { Client, GatewayIntentBits, Collection } = require('discord.js');
const fs = require('fs');
const path = require('path');
require('dotenv').config();

const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
    ],
});

client.commands = new Collection();

const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(f => f.endsWith('.js'));

for (const file of commandFiles) {
    const command = require(path.join(commandsPath, file));
    client.commands.set(command.data.name, command);
}

client.once('ready', () => {
    console.log(`[Bot] Online as ${client.user.tag}`);
});

client.on('interactionCreate', async interaction => {
    if (!interaction.isChatInputCommand()) return;

    const command = client.commands.get(interaction.commandName);
    if (!command) return;

    try {
        await command.execute(interaction);
    } catch (err) {
        console.error(err);
        if (interaction.replied || interaction.deferred) {
            await interaction.followUp({ content: 'Internal error.', ephemeral: true });
        } else {
            await interaction.reply({ content: 'Internal error.', ephemeral: true });
        }
    }
});

client.login(process.env.DISCORD_TOKEN);

2. Create C:\MuBot\deploy-commands.js to register slash commands with Discord:

const { REST, Routes } = require('discord.js');
const fs = require('fs');
const path = require('path');
require('dotenv').config();

const commands = [];
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(f => f.endsWith('.js'));

for (const file of commandFiles) {
    const command = require(path.join(commandsPath, file));
    commands.push(command.data.toJSON());
}

const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);

(async () => {
    try {
        console.log('Registering slash commands...');
        await rest.put(
            Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
            { body: commands }
        );
        console.log('Slash commands registered successfully!');
    } catch (err) {
        console.error(err);
    }
})();

3. Register the commands:

cd C:\MuBot
node deploy-commands.js

4. Start the bot:

node index.js

Part 6 — Keep the Bot Running with PM2

To ensure the bot starts automatically and restarts after failures, use PM2:

npm install -g pm2
cd C:\MuBot
pm2 start index.js --name "mubot"
pm2 save
pm2 startup
Dica: Use pm2 logs mubot to monitor logs in real time and pm2 restart mubot to restart the bot after updates.

Troubleshooting

Error ECONNREFUSED 127.0.0.1:1433 → SQL Server is not listening on port 1433. Verify that TCP/IP is enabled in SQL Server Configuration Manager and that the service has been restarted.

Error Login failed for user 'mubot_reader' → Confirm that SQL Server is set to mixed authentication mode (SQL Server and Windows Authentication). Check this under Properties → Security in SQL Server Management Studio.

Slash commands do not appear in Discord → Run node deploy-commands.js again. Guild commands appear within 1 minute; global commands can take up to 1 hour to propagate.

Query returns 0 online players → Verify that the MEMB_STAT table exists in your database. Some MuServer builds use ConnectServer.dbo.MEMB_STAT in a separate database.

Nota: The Character table name varies by MuServer version: it may be T_Character, Characters, or dbo.Character. Confirm the correct name by running SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' against your database.

Perguntas frequentes

Which Node.js version should I use?

Use Node.js 18 LTS or higher. Older versions may have compatibility issues with discord.js v14, which requires at least Node.js 16.9.0.

Does the bot need to run on the same server (VPS) as MU Online?

No. The bot can run on any machine with access to the SQL Server. Just open port 1433 in the VPS firewall and configure SQL Server to accept remote connections.

How do I prevent the bot from exposing sensitive player data?

Never return fields such as password, serial, or md5pwd in your queries. Use SELECT only on the columns you need (Name, Level, Class, Reset) and create a dedicated read-only SQL user for the bot.

The bot stops responding after a few hours — why?

This usually means the SQL Server connection was dropped due to inactivity. Implement automatic reconnection with a connection pool (mssql uses a pool by default) and configure connectionTimeout and requestTimeout in your configuration object.

VI

ViciadosMU Team

Equipe editorial do ViciadosMU — portal de MU Online no ar desde 2003.

Keep reading

Related articles