El mayor portal de MU Online de Brasil — desde 2003
Tutorial Avanzado Website

Cómo crear un bot de Discord con el estado del servidor de MU

Monta un bot de Discord que muestra el estado online/offline, los jugadores conectados y los rankings de tu servidor de MU Online, con actualización automática y comandos slash.

BR Bruno · Actualizado el 10 jul 2025 · ⏱ 18 min de lectura
Respuesta rápida

Un bot de Discord que muestra el estado de tu servidor de MU Online en tiempo real es una de las herramientas de comunidad con mejor retorno sobre el esfuerzo que podés montar. Mientras el jugador está en Discord conversando, ve en un parpadeo si el servidor está en el aire, cuántas personas están c

Un bot de Discord que muestra el estado de tu servidor de MU Online en tiempo real es una de las herramientas de comunidad con mejor retorno sobre el esfuerzo que podés montar. Mientras el jugador está en Discord conversando, ve en un parpadeo si el servidor está en el aire, cuántas personas están conectadas y quién lidera el ranking de resets, sin necesidad de abrir el cliente ni el sitio. Esto reduce drásticamente las preguntas repetidas de "¿está on?" en los canales y proyecta una imagen de servidor cuidado y profesional. En este tutorial vas a montar, desde cero, un bot en Node.js con la biblioteca discord.js que consulta el estado del servidor y publica un panel que se actualiza solo, además de responder a comandos slash como /status y /online. Vamos a tratar tanto el camino de leer la base de datos directamente como el camino más seguro de consumir una API del propio sitio. Recordá desde ya: los nombres de tablas, columnas y puertos varían por versión y emulador (Season 6, Season 15, IGCN, MuEMU, etc.), así que los ejemplos aquí son un esqueleto a adaptar, no verdad universal.

Requisitos previos

Antes de escribir cualquier línea de código, asegurate de que el entorno base esté listo. Un bot es apenas un cliente más consumiendo datos que ya existen; si la fuente de datos no está accesible y estable, ningún código salva. Si todavía estás montando la infraestructura, conviene revisar antes la guía de cómo crear un servidor de MU Online, porque el bot presupone un servidor ya funcional.

  • Node.js 18 LTS o superior instalado en la máquina donde el bot va a correr (discord.js v14 exige como mínimo Node 16.11).
  • Acceso a la base de datos del servidor (SQL Server en la mayoría de las versiones) o una API de estado en el sitio.
  • Una cuenta en el Discord Developer Portal con permiso para crear aplicaciones.
  • Un servidor Discord donde tengas permiso de administrador para agregar el bot.
  • Conocimiento básico de línea de comandos y edición de archivos de configuración.
  • Firewall ajustado: si vas a leer la base desde otra máquina, el puerto del SQL Server (ej.: 1433) tiene que estar abierto solo para la IP del bot.

Arquitectura: base directa vs. API del sitio

Existen dos modelos para que el bot obtenga los datos, y la elección define toda la seguridad del proyecto.

EnfoqueVentajasDesventajasCuándo usar
Leer la base directamenteSimple, sin capa extra, datos crudosExpone el puerto del SQL en la red; riesgo si el token/credencial se filtraEl bot corre en el mismo VPS de la base, red cerrada
Consumir API del sitioControlás exactamente lo que sale; no abre la baseExige crear y mantener un endpoint en el sitioEl bot corre en máquina externa; entorno de producción

La recomendación para producción es la API del sitio: creás un endpoint como /api/status.php que devuelve un JSON escueto, y el bot solo ve eso. Así, aunque el token del bot se filtre, nadie alcanza la base. Vamos a mostrar los dos, pero tratá la API como el camino preferente.

Paso 1 — Crear la aplicación y el bot en Discord

  1. Accedé al Discord Developer Portal y hacé clic en New Application. Ponele un nombre como StatusMU.
  2. En el menú lateral, entrá en Bot y hacé clic en Add Bot.
  3. En Privileged Gateway Intents, para este bot de estado normalmente no necesitás intents privilegiados, lo que simplifica la aprobación. Dejalos desactivados si solo vas a publicar embeds.
  4. Hacé clic en Reset Token, copiá el token y guardalo con cuidado — se usará en el .env.
  5. Andá a OAuth2 → URL Generator, marcá los scopes bot y applications.commands, y en los permisos marcá Send Messages, Embed Links y Read Message History.
  6. Copiá la URL generada, abrila en el navegador y agregá el bot a tu servidor Discord.

Paso 2 — Estructura del proyecto

En la terminal de la máquina del bot, creá la carpeta e instalá las dependencias:

mkdir statusmu && cd statusmu
npm init -y
npm install discord.js dotenv mssql axios

Creá un archivo .env en la raíz del proyecto. Este archivo nunca debe ir a Git:

DISCORD_TOKEN=cole_seu_token_aqui
CLIENT_ID=id_da_aplicacao
GUILD_ID=id_do_seu_servidor_discord
STATUS_CHANNEL_ID=id_do_canal_de_status

# Opción A: base directa
DB_SERVER=127.0.0.1
DB_PORT=1433
DB_DATABASE=MuOnline
DB_USER=mubot_reader
DB_PASSWORD=SenhaForte!123

# Opción B: API del sitio
STATUS_API_URL=https://seusite.com/api/status.php

Paso 3 (opción A) — Endpoint de estado en el sitio

Si optás por la API — el camino seguro —, creá en el sitio un archivo PHP que devuelve el estado como JSON. Encapsula toda la lógica de consulta y nunca expone credenciales al bot:

<?php
// api/status.php
header('Content-Type: application/json');

$host = '127.0.0.1';
$db   = 'MuOnline';
$user = 'site_reader';
$pass = 'SenhaDoSite!123';

try {
    $conn = new PDO("sqlsrv:Server=$host;Database=$db", $user, $pass);
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // Jugadores conectados — el nombre de la tabla varía por versión
    $stmt = $conn->query("SELECT COUNT(*) AS online
                          FROM MEMB_STAT WHERE ConnectStat = 1");
    $online = (int) $stmt->fetch(PDO::FETCH_ASSOC)['online'];

    echo json_encode([
        'status'  => 'online',
        'online'  => $online,
        'updated' => date('c'),
    ]);
} catch (Exception $e) {
    echo json_encode(['status' => 'offline', 'online' => 0]);
}

Probá abriendo la URL en el navegador — deberías ver un JSON limpio. La gran ventaja: el bot solo conoce esa URL, y vos decidís exactamente qué campos aparecen.

Paso 4 — Probar de verdad si el servidor está en el aire

El error clásico es considerar el servidor "online" solo porque la base respondió. La base sigue en pie aunque el GameServer esté detenido. Para reflejar el estado real, hacé también una prueba de puerto TCP en el GameServer. En Node.js:

// checkPort.js
const net = require('net');

function checkPort(host, port, timeout = 3000) {
  return new Promise((resolve) => {
    const socket = new net.Socket();
    socket.setTimeout(timeout);
    socket.once('connect', () => { socket.destroy(); resolve(true); });
    socket.once('timeout', () => { socket.destroy(); resolve(false); });
    socket.once('error', () => { resolve(false); });
    socket.connect(port, host);
  });
}

module.exports = { checkPort };

Usá el puerto de tu GameServer (frecuentemente 55901 o 44405, pero varía por versión). Solo marcá como online cuando el puerto responda Y la consulta de datos funcione.

Paso 5 — El bot principal con panel automático

Creá el index.js. Consulta el estado cada 60 segundos y edita un único mensaje fijo en el canal, manteniendo el panel siempre actual sin ensuciar el chat:

require('dotenv').config();
const { Client, GatewayIntentBits, EmbedBuilder } = require('discord.js');
const axios = require('axios');
const { checkPort } = require('./checkPort');

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
let statusMessageId = null;

async function fetchStatus() {
  try {
    const gameOnline = await checkPort('127.0.0.1', 55901);
    const { data } = await axios.get(process.env.STATUS_API_URL, { timeout: 5000 });
    return {
      online: gameOnline && data.status === 'online',
      players: data.online ?? 0,
    };
  } catch (err) {
    return { online: false, players: 0 };
  }
}

function buildEmbed(status) {
  return new EmbedBuilder()
    .setTitle('Estado del Servidor')
    .setColor(status.online ? 0x2ecc71 : 0xe74c3c)
    .addFields(
      { name: 'Estado', value: status.online ? '🟢 Online' : '🔴 Offline', inline: true },
      { name: 'Jugadores', value: `${status.players}`, inline: true },
    )
    .setFooter({ text: 'Actualizado' })
    .setTimestamp();
}

async function updatePanel() {
  const channel = await client.channels.fetch(process.env.STATUS_CHANNEL_ID);
  const status = await fetchStatus();
  const embed = buildEmbed(status);

  if (statusMessageId) {
    try {
      const msg = await channel.messages.fetch(statusMessageId);
      await msg.edit({ embeds: [embed] });
      return;
    } catch { statusMessageId = null; }
  }
  const sent = await channel.send({ embeds: [embed] });
  statusMessageId = sent.id;
}

client.once('ready', () => {
  console.log(`Bot online como ${client.user.tag}`);
  updatePanel();
  setInterval(updatePanel, 60_000);
});

client.login(process.env.DISCORD_TOKEN);

Paso 6 — Comandos slash

Además del panel automático, dale al jugador el comando /status bajo demanda. Registrá el comando con un script de deploy y tratá la interacción en el bot:

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

const commands = [
  new SlashCommandBuilder().setName('status')
    .setDescription('Muestra el estado actual del servidor').toJSON(),
];

const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
rest.put(
  Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
  { body: commands }
).then(() => console.log('Comandos registrados')).catch(console.error);

En el index.js, agregá el listener de interacción, reaprovechando fetchStatus() y buildEmbed() para responder con el mismo embed. Los comandos de guild aparecen en segundos; los comandos globales pueden tardar hasta una hora en propagarse.

Paso 7 — Mantener el bot vivo con PM2

El proceso del bot necesita reiniciarse solo si se cae. Usá PM2:

npm install -g pm2
pm2 start index.js --name statusmu
pm2 save
pm2 startup

Con pm2 logs statusmu seguís la salida en tiempo real, y pm2 restart statusmu recarga después de cambios.

Errores comunes y soluciones

SíntomaCausa probableSolución
El bot muestra "online" con el servidor caídoSolo chequea la base, no el GameServerAgregá prueba de puerto TCP en el GameServer
ECONNREFUSED 1433TCP/IP deshabilitado o puerto bloqueadoHabilitá TCP/IP en el SQL Config Manager y abrí en el firewall
Los comandos slash no aparecenDeploy no ejecutado o scope equivocadoCorré deploy-commands.js y verificá CLIENT_ID/GUILD_ID
El panel crea un mensaje nuevo en cada cicloEl ID del mensaje no persisteGuardá el statusMessageId en archivo/DB para sobrevivir al restart
Invalid TokenToken equivocado o regeneradoCopiá el token actual en Bot → Reset Token y actualizá el .env
Rate limit al editarIntervalo demasiado cortoAumentá el intervalo a 60s o más

Buenas prácticas de seguridad

Nunca subas el .env al repositorio. Creá un usuario de base solo-lectura, con GRANT SELECT únicamente en las tablas necesarias, y jamás uses la cuenta sa. Si vas a exponer una API de estado en el sitio, devolvé solo los campos estrictamente públicos — nunca contraseñas, seriales ni e-mails. Preferí que el bot consuma la API en lugar de abrir el puerto de la base hacia internet. Y documentá el intervalo de actualización para que futuros mantenimientos no lo reduzcan al punto de generar rate limit.

Lista de verificación de lanzamiento

  • Node.js 18+ instalado en la máquina del bot
  • Aplicación y bot creados en el Developer Portal, token guardado en el .env
  • Bot agregado al servidor Discord con permisos de enviar mensajes y embeds
  • Fuente de datos definida (API del sitio o base solo-lectura)
  • Prueba de puerto TCP del GameServer implementada
  • Canal de estado creado e ID configurado
  • Panel automático actualizando cada 60s
  • Comando /status registrado y respondiendo
  • PM2 configurado con pm2 save y pm2 startup
  • .env fuera del control de versiones y con permisos restringidos

Preguntas frecuentes

¿El bot necesita correr en el mismo VPS del servidor de MU?

No. Solo necesita acceso a la base de datos o a un endpoint HTTP del sitio. Puede correr en otra máquina, siempre que el puerto del SQL Server (1433, por ejemplo) esté abierto o el sitio exponga una API de estado.

¿Cuál es la diferencia entre leer la base directamente y usar una API del sitio?

Leer la base es más directo pero expone el SQL en la red. Una API HTTP en el propio sitio es más segura porque controlás exactamente qué datos salen y no abrís el puerto de la base hacia afuera.

¿Por qué mi bot muestra 'online' incluso con el servidor caído?

Probablemente estés chequeando solo la conexión con la base, y la base sigue en pie aunque el GameServer esté detenido. Hacé también una prueba de puerto TCP en el GameServer para reflejar el estado real.

¿Con qué frecuencia debe actualizar el estado el bot?

Un intervalo de 60 segundos es seguro. Actualizaciones más frecuentes que 15 segundos pueden chocar con el rate limit de Discord al editar el mismo mensaje varias veces por minuto.

¿Es seguro dejar el token del bot en el código?

Nunca. El token da control total sobre el bot. Guardalo en un archivo .env fuera del control de versiones y restringí los permisos del archivo en el servidor.

BR
Editor de eventos, mapas e ítems

Bruno es especialista en eventos, mapas, bosses y economía de ítems de MU Online. Documenta cada detalle basándose en el juego real.

Sigue leyendo

Artículos relacionados