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

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

MAATRIX GAMES

Игрок открывает Discord, чтобы узнать, жив ли сервер и сколько там сейчас людей — и вместо ответа видит статичный канал без единой подсказки. Приходится запускать игру, тыкаться в избранное, ждать таймаут, если сервер лёг. Чинится это одним небольшим ботом: он сам опрашивает сервер и держит статус там, где community и так сидит целыми днями — в имени голосового канала или в закреплённом embed-сообщении, которое обновляется само.

Два подхода: голосовой канал-индикатор vs embed-сообщение

Первый вариант — переименовывать голосовой (или текстовый) канал в что-то вроде 🟢 Онлайн: 23/60. Плюс в том, что статус виден сразу в списке каналов слева, без единого клика — люди привыкают смотреть туда машинально, как на индикатор в трее. Минус — у Discord жёсткий лимит на переименования канала (об этом ниже), и в имя канала много не запихнёшь: ни списка игроков, ни карты, ни аптайма.

Второй вариант — одно закреплённое сообщение с embed, которое бот периодически редактирует (не пересоздаёт — редактирует тот же message ID). Сюда помещается всё: карта, версия, список ников, время последнего рестарта, кнопка «скопировать IP». Правки embed не упираются в тот же лимит, что переименование канала, поэтому обновлять его можно чаще.

На практике лучшая связка — оба сразу: канал даёт статус одним взглядом, embed — детали по запросу. Ставить их конкурентами друг другу смысла нет, они решают разные задачи.

Gamedig: одна библиотека почти под все игры

Писать парсер бинарного ответа под каждый query-протокол вручную — занятие на вечер даже для одной игры, а если сервер хостит несколько тайтлов community, это вечер за вечером. Для этого есть Gamedig — open-source библиотека на Node.js, которая уже знает форматы запросов для нескольких сотен игр: Minecraft, Rust, Counter-Strike 2 и другие Source-игры, ARK: Survival, Valheim, Palworld и большинство популярных серверов из каталога аренды. Она сама выбирает нужный протокол по строковому идентификатору игры (type) и возвращает уже распарсенный JS-объект — не нужно руками лезть в байты и эндианность.

Если хочется понимать, что происходит под капотом Gamedig (или ваша игра в списке не значится и придётся писать опросник самому) — это отдельная тема, разобранная в статье про Server Query API для мониторинг-ботов: там про UDP-пакеты, таймауты и разницу протоколов по движкам.

Установка:

npm install gamedig discord.js dotenv

Базовый вызов — опросить Rust-сервер и получить онлайн:

const Gamedig = require('gamedig');

async function getStatus() {
  try {
    const state = await Gamedig.query({
      type: 'rust',
      host: process.env.GAME_HOST,
      port: Number(process.env.GAME_PORT),
    });
    return {
      online: true,
      players: state.players.length,
      maxplayers: state.maxplayers,
      map: state.map,
      name: state.name,
    };
  } catch (e) {
    // сервер не ответил за таймаут — считаем офлайном, это нормальная ситуация
    return { online: false };
  }
}

Важный нюанс: Gamedig.query бросает исключение не только когда сервер реально мёртв, но и если пакет банально потерялся по UDP или таймаут (по умолчанию около 2 секунд) оказался мал для медленного канала. Не спешите сразу писать в лог «сервер упал» — разумно требовать 2-3 подряд неудачных опроса, прежде чем помечать сервер офлайном, иначе бот будет дёргать канал туда-сюда на ровном месте.

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

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

Обновление имени голосового канала: минимальный бот и грабли рейт-лимита

Вот рабочий скелет на discord.js v14, который раз в интервал опрашивает сервер и переименовывает голосовой канал:

const { Client, GatewayIntentBits } = require('discord.js');
const Gamedig = require('gamedig');
require('dotenv').config();

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const CHANNEL_ID = process.env.STATUS_CHANNEL_ID;
const POLL_INTERVAL_MS = 5 * 60 * 1000; // см. раздел про рейт-лимиты ниже

let lastName = null;

async function updateChannel() {
  const channel = await client.channels.fetch(CHANNEL_ID);
  let newName;

  try {
    const state = await Gamedig.query({
      type: process.env.GAME_TYPE, // например 'minecraft', 'csgo' для CS2, 'rust'
      host: process.env.GAME_HOST,
      port: Number(process.env.GAME_PORT),
    });
    newName = `🟢 Онлайн: ${state.players.length}/${state.maxplayers}`;
  } catch {
    newName = '🔴 Сервер офлайн';
  }

  if (newName !== lastName) {
    await channel.setName(newName);
    lastName = newName;
  }
}

client.once('ready', () => {
  updateChannel();
  setInterval(updateChannel, POLL_INTERVAL_MS);
});

client.login(process.env.DISCORD_TOKEN);

Здесь два момента, на которых спотыкается почти каждый, кто делает такого бота впервые:

  • Проверка newName !== lastName не опциональна. Discord даёт на переименование канала (любого — голосового, текстового, категории) не более двух правок за 10 минут на канал, и это официальный, а не мягкий лимит: превысили — ловите 429 и бот замолкает по этому каналу на весь rate-limit window. Если писать имя без сравнения на каждый тик таймера, даже когда оно не изменилось, лимит выжрется моментально.
  • POLL_INTERVAL_MS в 5 минут — не рекомендация, а следствие того же лимита. Даже с проверкой на изменение имени, если онлайн на сервере скачет туда-сюда (заходят-выходят игроки), реальные переименования могут случаться часто. Опрос раз в 3-5 минут — разумный компромисс между «свежо» и «не словить бан по лимиту»; чаще осмысленно только если вы уверены, что онлайн стабилен и переименования будут редкими.

Отдельно проверьте права: у бота должно быть право Manage Channels именно в той гильдии, где висит статусный канал, иначе setName тихо упадёт с ошибкой доступа.

Embed-сообщение со списком игроков

Там, где нужны детали, а не только цифра в заголовке — редактируем embed в закреплённом сообщении. В отличие от переименования канала, здесь нет отдельного жёсткого лимита именно на редактирование — действуют общие лимиты Discord API на запросы к каналу, и при разумном интервале (раз в 30-60 секунд) вы туда не упрётесь.

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

async function updateStatusEmbed(channel, messageId) {
  const message = await channel.messages.fetch(messageId);

  let embed;
  try {
    const state = await Gamedig.query({
      type: process.env.GAME_TYPE,
      host: process.env.GAME_HOST,
      port: Number(process.env.GAME_PORT),
    });

    const playerList = state.players
      .map(p => p.name || '(без ника)')
      .slice(0, 20)
      .join(', ') || 'никого нет';

    embed = new EmbedBuilder()
      .setTitle(`🟢 ${state.name || 'Игровой сервер'}`)
      .addFields(
        { name: 'Игроки', value: `${state.players.length}/${state.maxplayers}`, inline: true },
        { name: 'Карта', value: state.map || '—', inline: true },
        { name: 'На сервере', value: playerList },
      )
      .setColor(0x57F287)
      .setTimestamp();
  } catch {
    embed = new EmbedBuilder()
      .setTitle('🔴 Сервер офлайн')
      .setColor(0xED4245)
      .setTimestamp();
  }

  await message.edit({ embeds: [embed] });
}

message.edit с одним и тем же embed, если данные не изменились, — не проблема с точки зрения лимитов так, как переименование канала, но всё равно нет смысла дёргать API впустую: оставьте ту же проверку на изменение содержимого, просто менее строгую. Список игроков стоит резать до разумного числа (в примере — 20), иначе на многолюдном сервере embed упрётся в лимит Discord на длину поля (1024 символа) и правка молча обрежется или упадёт с ошибкой.

Если помимо статуса вам нужно ещё и слать команды на сервер прямо из Discord — кикать, банить, объявлять вайп — это уже не query, а RCON, и это отдельная функциональность бота, разобранная в статье про Discord-бота для управления игровым сервером. Она хорошо комбинируется с мониторингом статуса в одном боте, но начинать стоит с чтения — с записи риски выше.

Готовые open-source боты vs самописный на discord.js

Не обязательно писать всё с нуля. На GitHub есть несколько зрелых ботов именно под задачу «статус сервера в Discord», которые можно развернуть за вечер:

ВариантЧто умеетКогда выбрать
Готовый open-source бот (например, специализированные game-status боты под Minecraft/Source)Канал-индикатор + embed из коробки, конфиг через .env или JSON, поддержка популярных игрСтандартный сценарий: одна-две игры, нет нужды в кастомной логике или интеграции с вашей БД
Самописный на discord.js + GamedigТо же самое плюс всё, что напишете сами: своя логика алертов, запись метрик в БД, интеграция с панелью хостинга, нестандартный формат embedНужна кастомизация, несколько серверов с разной логикой, или хотите встроить бота в существующую инфраструктуру
Готовый SaaS-сервис статуса (сторонние виджеты)Виджет на сайт, иногда — интеграция с Discord через вебхукНужен статус на сайте, а не в Discord, или не хочется держать свой процесс 24/7

Честно: для одного-двух серверов и стандартного набора функций (онлайн в канале + embed со списком) переизобретать велосипед смысла немного — берите готового бота, разбирайтесь в его конфиге, экономьте вечер. Самописный вариант окупается, когда вам действительно нужна логика, которой нет в готовых решениях — например, отдельный алерт-канал, который пингует роль «Админ», только если сервер лежит дольше 10 минут подряд, а не при каждой рассинхронизации опроса.

В обоих случаях бот должен где-то жить постоянно, а не на вашем ноутбуке — если ещё не разбирались, как поставить discord.js-бота на VPS с автозапуском через pm2 или systemd, это подробно описано в статье как поднять сервер Discord-бота: токен, хранение секретов, restart policy — всё то, что нужно независимо от того, пишете вы бота статуса сами или берёте готовый.

Нужен игровой сервер?

MAATRIX GAMES — 45+ игр, деплой за минуты, DDoS-защита, автобэкапы. Тарифы от 399 ₽/мес.

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

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

Gamedig не видит мой сервер, хотя он точно работает — в чём дело?

В 9 случаях из 10 — неверный type или порт. Убедитесь, что указываете именно query-порт, а не игровой (для некоторых игр они совпадают, для других — нет, например у Rust отдельный Steam query-порт через +server.queryport). Список поддерживаемых type для Gamedig — в документации проекта на GitHub, названия там не всегда совпадают с обиходным именем игры.

Можно ли обновлять статус чаще, чем раз в 5 минут?

Для embed — да, разумно и раз в 30-60 секунд. Для имени канала — нет, если только вы точно не уверены, что реальных переименований будет мало: лимит в 2 правки за 10 минут общий для канала, а не для вашего бота, и его легко словить случайно даже с редким опросом, если онлайн на сервере скачет.

Бот показывает офлайн, хотя сервер жив — что проверить в первую очередь?

Таймаут запроса и потерю UDP-пакета: одна неудача — это нормально для query-протокола, ложные срабатывания лечатся требованием нескольких подряд неудачных опросов перед сменой статуса на офлайн, а не мгновенной реакцией на первый же таймаут.

Нужен ли для этого RCON-доступ к серверу?

Нет — query-протокол read-only и не требует пароля, это принципиально другая история, чем RCON-команды. Бота мониторинга статуса безопасно давать в публичный канал сообщества именно потому, что он физически не может ничего сломать на сервере.

Как быть с несколькими серверами сразу (например, Minecraft и Counter-Strike 2 у одного community)?

Заводите отдельный канал (или отдельное embed-сообщение) на каждый сервер и опрашивайте их независимо — общая логика бота та же, просто конфиг с массивом { type, host, port, channelId } вместо одной записи.