Discord-бот для мониторинга статуса игрового сервера
Игрок открывает 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 } вместо одной записи.