MAATRIX GAMES / Блог / Discord-бот для управления игровым сервером

Discord-бот для управления игровым сервером

MAATRIX GAMES

Заходить в отдельную RCON-консоль или панель хостинга каждый раз, когда нужно кикнуть гриферa, глянуть онлайн или объявить вайп — быстро надоедает, особенно если админите сервер не в одиночку. Игроки и так сидят в Discord — логично, чтобы там же жила панель управления. Разберём, как прикрутить к боту RCON, уведомления о статусе сервера, живую статистику онлайна и разграничение прав. Если бота ещё нет и вы не понимаете, с чего начать установку на VPS — сначала прочитайте статью про базовую установку Discord-бота, там про токен, discord.js/discord.py, автозапуск через pm2/systemd. Здесь исходим из того, что бот уже крутится 24/7, и говорим только про игровую начинку.

Что такое RCON и зачем его пускать через Discord

RCON (Remote Console) — это протокол, которым почти все современные игровые серверы принимают админ-команды по сети: логин по паролю, потом произвольная команда в виде текстовой строки, и сервер отвечает текстом же. У Source-игр (CS2, Rust, Garry's Mod) это классический Source RCON на UDP/TCP, у Minecraft — свой RCON-протокол (включается в server.properties через enable-rcon=true, rcon.password, rcon.port), у модовых серверов вроде ARK или 7 Days to Die — часто телнет-подобный интерфейс с похожей логикой.

Идея простая: бот держит открытое соединение (или открывает новое на каждую команду) к RCON-порту игрового сервера и просто проксирует то, что вы написали в Discord-канале, в этот протокол. Никакой магии — по сути обёртка вокруг тех же команд, которые вы вбивали бы в консоль хостинг-панели.

Для Source-игр в Node.js обычно берут пакет rcon или rcon-client, для Python — mcrcon. Для Minecraft подходят те же библиотеки — протокол один, что для ванильного сервера, что для Paper/Spigot.

Минимальный пример на Node.js (discord.js + rcon-client) — команда !rcon доступна только с ролью «Админ сервера»:

const { Rcon } = require('rcon-client');

client.on('messageCreate', async (msg) => {
  if (!msg.content.startsWith('!rcon ')) return;
  if (!msg.member.roles.cache.some(r => r.name === 'Админ сервера')) {
    return msg.reply('Недостаточно прав.');
  }

  const command = msg.content.slice(6).trim();
  try {
    const rcon = await Rcon.connect({
      host: process.env.GAME_HOST,
      port: Number(process.env.RCON_PORT),
      password: process.env.RCON_PASSWORD,
    });
    const response = await rcon.send(command);
    await rcon.end();
    msg.reply('```' + (response || 'OK (пустой ответ)') + '```');
  } catch (err) {
    msg.reply(`Ошибка RCON: ${err.message}`);
  }
});

Пароль и порт RCON храните в .env, никогда не пишите в коде бота напрямую — это отдельная точка входа в ваш сервер, и утечка токена бота (обсуждали в статье про установку) здесь так же критична, как утечка RCON-пароля.

Уведомления в канал: сервер упал, сервер поднялся

Второй по важности сценарий — не тянуть команды руками, а получать пуш в Discord, когда с сервером что-то произошло. Два рабочих подхода, и они не взаимоисключающие:

  1. Пассивный опрос (polling). Бот раз в 30-60 секунд стучится на игровой порт через gamedig (Node) или python-a2s/opengsq (Python) — это библиотеки для Server Query протокола, которым живут почти все игровые серверы (не путать с RCON, тут не нужен пароль, только статус: онлайн игроков, карта, версия). Если сервер не ответил N проверок подряд — шлём алерт в канал #server-status.
  2. Активный хук из скрипта запуска. Если сервер живёт на вашем же VPS под systemd или через скрипт-обвязку (как в статьях «Как поднять сервер...» для конкретных игр), проще всего дёрнуть Discord Webhook прямо из shell-скрипта при старте/падении процесса — без опроса, мгновенно и без лишней нагрузки на бота.

Пример systemd-юнита с ExecStartPre/ExecStopPost, который шлёт вебхук:

# /etc/systemd/system/rust-server.service
[Service]
ExecStartPre=/usr/bin/curl -s -X POST -H "Content-Type: application/json" \
  -d '{"content":"🟢 Rust-сервер запускается"}' \
  https://discord.com/api/webhooks/XXXXXXXX/YYYYYYYYYY
ExecStopPost=/usr/bin/curl -s -X POST -H "Content-Type: application/json" \
  -d '{"content":"🔴 Rust-сервер остановлен или упал"}' \
  https://discord.com/api/webhooks/XXXXXXXX/YYYYYYYYYY

Webhook — это отдельная сущность от самого бота (создаётся в настройках канала: Правка канала → Интеграции → Веб-хуки), не требует токена бота и прав на подключение к шлюзу Discord, поэтому для простых односторонних уведомлений он даже проще, чем городить логику в самом боте. Но если хотите красивые embed-сообщения со статусом (карта, онлайн, аптайм) — тот же вебхук принимает и JSON с полем embeds, не только content.

Нюанс: ExecStopPost срабатывает и при штатной остановке, и при краше. Если нужно различать «упал» и «остановили руками» — проверяйте код выхода через $EXIT_STATUS в скрипте-обёртке или полагайтесь на опрос Server Query, там разница видна по паттерну пропущенных проверок.

Поднять сервер Discord-бот за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Статус бота как живой онлайн-счётчик

Самый простой способ показать онлайн прямо в списке участников Discord-сервера, без единой команды от игроков — обновлять статус бота (Activity/Presence). Discord позволяет боту показывать «Играет в...» или, для ботов с включённым Server Members Intent, кастомный статус вида «Watching 14/60 online».

На discord.js это выглядит так — опрашиваем сервер раз в минуту и обновляем presence:

const Gamedig = require('gamedig');

async function updatePresence() {
  try {
    const state = await Gamedig.query({
      type: 'rust', // тип берётся из документации gamedig под вашу игру
      host: process.env.GAME_HOST,
      port: Number(process.env.QUERY_PORT),
    });
    client.user.setActivity(`${state.players.length}/${state.maxplayers} онлайн`, {
      type: ActivityType.Watching,
    });
  } catch {
    client.user.setActivity('сервер офлайн', { type: ActivityType.Watching });
  }
}

setInterval(updatePresence, 60_000);
updatePresence();

gamedig поддерживает большинство протоколов из коробки (Source-движок, Minecraft, ARK, 7 Days to Die и десятки других — список типов в README), так что для смены игры обычно достаточно поменять type и порт. Учтите: Discord ограничивает частоту смены presence — не дёргайте setActivity чаще раза в 15-20 секунд, иначе упрётесь в rate limit шлюза.

Для более наглядной картины многие вместо статуса переименовывают отдельный канал вида 📊-online-14-60 тем же таймером — виден даже тем, кто бота не открывал. Но у Discord лимит на переименование канала — не чаще нескольких раз за 10 минут, иначе поймаете 429.

Команды для игроков: онлайн, вайп, рестарт

Админский RCON — это одно, но часть команд бота полезно открыть вообще всем на сервере, без проверки ролей — это снижает поток вопросов «а когда вайп» в личку админам.

Базовый набор, который стоит сделать открытым:

  • /online — список игроков онлайн (тот же Server Query, что и для presence, просто списком, а не в статус).
  • /wipe или /nextwipe — время до следующего вайпа/рестарта. Если у вас крон-вайп по расписанию (частый случай для Rust — force wipe в первый четверг месяца плюс еженедельный map wipe), проще зашить расписание в код бота через node-cron, а не дёргать это из RCON.
  • /status — краткая карточка: карта, онлайн, версия сборки, аптайм.
  • /connect — присылает IP:порт и, если нужно, пароль сервера — банально, но экономит время новичкам.

Пример слэш-команды /nextwipe на discord.js с зашитым расписанием (еженедельный вайп по четвергам в 15:00 МСК):

const { SlashCommandBuilder } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('nextwipe')
    .setDescription('Когда следующий вайп сервера'),
  async execute(interaction) {
    const now = new Date();
    const nextThursday = new Date(now);
    nextThursday.setUTCDate(now.getUTCDate() + ((4 - now.getUTCDay() + 7) % 7 || 7));
    nextThursday.setUTCHours(12, 0, 0, 0); // 15:00 МСК = 12:00 UTC
    await interaction.reply(`Следующий вайп: <t:${Math.floor(nextThursday.getTime() / 1000)}:R>`);
  },
};

Формат <t:...:R> — это встроенный в Discord таймстемп, который сам отрисуется как «через 3 дня» и автоматически подстроится под часовой пояс каждого читающего — удобнее, чем текстом писать московское время и надеяться, что все поймут пересчёт.

Для игр, где вайп настраивается плагином (в Rust это часто Oxide/uMod-плагин или самописный крон-скрипт — ставится он так же, как остальные Oxide-плагины), лучше вместо жёстко зашитой даты в боте дёргать RCON-команду, которая читает время вайпа из самого плагина — надёжнее, чем дублировать расписание в двух местах.

Права доступа: роли, а не пароли

Худший сценарий — когда RCON-пароль знает половина сервера, потому что кто-то когда-то кинул его в общий чат «на всякий случай». Роли Discord решают это на уровне архитектуры бота, а не человеческой дисциплины.

Практическая схема ролей, которая работает для большинства серверов:

Роль DiscordЧто доступноПример команд
ИгрокТолько read-only команды/online, /nextwipe, /connect
Модератор+ кик/бан внутри игры, тепловые команды!rcon kick, !rcon ban, !rcon mute
Админ сервераПолный RCON, рестарт, смена карты!rcon (любая команда), /restart
Owner/Root+ управление самим ботом и хостингомсмена конфигов, деплой, доступ к панели

В коде это чаще всего проверка interaction.member.roles.cache.some(...) (как в примере выше) или, для более гибкой схемы, список разрешённых ID ролей в конфиге бота (.env или config.json), а не хардкод названий — названия ролей на сервере могут переименовать, а ID роли стабилен, пока роль не удалена.

Дополнительно стоит: принимать RCON-команды только в приватном канале #admin-console, видимом ролям Модератор+; логировать каждую команду в отдельный канал-аудит (кто, когда, что ввёл); не выдавать роль «Админ сервера» через реакции или автовыдачу — только вручную; ротировать RCON-пароль, если кто-то с доступом покинул команду.

Что делать, если бот и сервер на разных хостах

Частая ошибка новичков: бот крутится на одном VPS (например, дешёвом для бота), а игровой сервер — на другом (арендованном под игру, с нужным объёмом RAM). Тогда RCON-порт и Server Query порт игрового сервера должны быть доступны бота снаружи — то есть открыты в фаерволе именно на IP бота, а не «для всех» (0.0.0.0/0), если можно сузить.

Если у вас сервер на MAATRIX GAMES, RCON и Query-порты игры обычно уже проброшены и указаны в панели управления сервером — там же смотрите точный порт RCON (он не всегда совпадает с игровым портом, у Rust, например, отдельный +rcon.port). Если бот стоит отдельно, не забудьте:

  1. Узнать внешний IP бота (curl ifconfig.me на VPS с ботом).
  2. В фаерволе игрового сервера (ufw, iptables или правило в панели хостинга) разрешить входящие на RCON-порт именно с этого IP.
  3. Проверить связь до RCON вручную перед тем, как чинить код бота — например, mcrcon -H IP -P PORT -p PASSWORD "status". Если консоль не коннектится напрямую, бот тоже не сможет, и дело не в коде.

Держать бота и сервер на одном хосте проще с точки зрения сети (localhost вместо публичного IP в RCON-конфиге), но падение VPS убивает сразу и сервер, и панель управления им — для небольших проектов это приемлемый компромисс, для крупных community лучше разносить.

Поднять сервер Discord-бот за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Частые вопросы

RCON-порт вообще нужно открывать наружу, или можно ограничить только локальным доступом?

Если бот на том же хосте, что и игровой сервер — да, можно и нужно слушать RCON только на 127.0.0.1, это безопаснее любого файервол-правила. Открывайте наружу только если бот физически на другой машине.

Можно ли одним ботом управлять несколькими игровыми серверами сразу?

Да, обычная практика — держите словарь/конфиг вида «имя сервера → host/port/RCON-пароль» и добавляйте параметр сервера в команды (/rcon rust1 status), чтобы не путать, куда именно уходит команда.

Что если игра не поддерживает RCON вообще (например, некоторые модовые сборки)?

Тогда остаётся SSH-доступ к консоли сервера (screen/tmux-сессия, в которую бот пишет команды через screen -S rust -X stuff "команда\n") — грубее, чем RCON, но работает почти везде, где сервер запущен в screen или tmux.

Нужно ли боту право администратора на Discord-сервере?

Нет, и лучше не давать — боту достаточно точечных прав (Send Messages, Manage Channels если он переименовывает канал-счётчик, Use Slash Commands), права Administrator создают лишнюю поверхность атаки при утечке токена.

Как защититься, если токен бота или RCON-пароль всё же утекли?

Для токена бота — в Discord Developer Portal сразу Reset Token (старый токен инвалидируется мгновенно), для RCON — смените пароль в конфиге игры и перезапустите сервер; оба действия делайте сразу же, не дожидаясь подтверждения, что утечка была использована.