Перейти к содержимому

Работа с событиями

Gateway шлёт боту всё, что происходит на всех серверах, где он состоит. Большинство событий вам не нужны. Эта страница — про то, какие события реально стоит обрабатывать и как не устроить себе проблемы.

Для полного списка с payload’ами — Gateway Events.

if (msg.op === 0) {
switch (msg.t) {
case 'READY': return onReady(msg.d);
case 'MESSAGE_CREATE': return onMessage(msg.d);
case 'GUILD_CREATE': return onGuildCreate(msg.d);
case 'GUILD_DELETE': return onGuildDelete(msg.d);
}
}

Этого хватит для бота, который отвечает на команды и следит в каких гильдиях он состоит.

СобытиеКогда использовать
MESSAGE_CREATEОбработка команд, реакции на ключевые слова, антиспам
MESSAGE_UPDATEАудит правок, анти-редактирование-после-ответа
MESSAGE_DELETEКеш «недавно удалённого» для модерации
MESSAGE_DELETE_BULKТо же, но массово
MESSAGE_REACTION_ADDReaction-roles, голосования, игры
MESSAGE_REACTION_REMOVEОбратная сторона reaction-roles
СобытиеКогда использовать
GUILD_MEMBER_ADDПриветствие, welcome-канал, выдача стартовой роли
GUILD_MEMBER_REMOVEЛоги выходов, анти-подставы (участник ушёл с долгом)
GUILD_MEMBER_UPDATEСлежение за изменением ролей
СобытиеКогда использовать
GUILD_CREATEПервичная загрузка гильдии; срабатывает при старте, после reconnect и при добавлении бота
GUILD_UPDATEЕсли кешируете имя/иконку гильдии
GUILD_DELETEБота выкинули с сервера — чистить стейт
CHANNEL_CREATE/UPDATE/DELETEЕсли кешируете список каналов
GUILD_ROLE_*Если связываете бот-логику с ролями
СобытиеКогда использовать
VOICE_STATE_UPDATEУзнать кто вошёл/вышел из голосового канала (для music-ботов или статистики активности)
СобытиеКогда использовать
TYPING_STARTПочти никогда — только если бот следит «а печатает ли он ещё»
PRESENCE_UPDATEРедко — статистика онлайна. Платит трафиком на больших серверах
PASSIVE_UPDATESВнутреннее — игнорируйте
SESSIONS_REPLACEВнутреннее — игнорируйте
if (msg.d.author?.bot) return;

Иначе бот попадёт в цикл «я написал → MESSAGE_CREATE → я обработал → я снова написал».

Если ваш бот не должен реагировать на ботов вообще (а не только на себя) — та же проверка author.bot. Реагировать только на живых людей — безопаснее.

В теории события начинают приходить только после READY, но на практике — если у вас race condition — можно получить MESSAGE_CREATE до того, как sessionReady = true. Флаг-гард:

let ready = false;
function onReady() { ready = true; }
function onMessage(data) {
if (!ready) return;
// ...
}

В IDENTIFY можно передать ignored_events — массив имён событий, которые боту не слать. На больших серверах это существенно снижает нагрузку:

{
"op": 2,
"d": {
"token": "Bot ...",
"properties": {"os": "linux", "browser": "bot", "device": "bot"},
"ignored_events": [
"TYPING_START",
"PRESENCE_UPDATE",
"VOICE_STATE_UPDATE",
"CHANNEL_PINS_UPDATE"
]
}
}

Подумайте об этом, если бот только читает команды в тексте — голос, набор, presence-обновления ему бесполезны.

Паттерн: отвечать на команду + следить за событиями

Заголовок раздела «Паттерн: отвечать на команду + следить за событиями»

Типичный бот — это машина состояний поверх Gateway. Структура кода обычно такая:

ws.on('message', (raw) => {
const msg = JSON.parse(raw);
// Сервисные опкоды (heartbeat, reconnect, invalid_session)
if (msg.op !== 0) return handleOpcode(msg);
// Dispatch — роутим по типу
const handler = dispatchHandlers[msg.t];
if (handler) void handler(msg.d);
});
const dispatchHandlers = {
READY: onReady,
MESSAGE_CREATE: onMessage,
GUILD_CREATE: onGuildCreate,
GUILD_DELETE: onGuildDelete,
GUILD_MEMBER_ADD: onMemberJoin,
MESSAGE_REACTION_ADD: onReactionAdd,
};

Сразу после READY приходят GUILD_CREATE события — по одному на каждую гильдию. Параллельно уже могут приходить MESSAGE_CREATE. Если бот обращается к кешу гильдии в обработчике сообщения, а кеш ещё не заполнен — будет ошибка.

Решение: дождаться первой волны GUILD_CREATE:

let pendingGuilds = new Set();
let hydrated = false;
function onReady(data) {
pendingGuilds = new Set(data.guilds.map((g) => g.id));
if (pendingGuilds.size === 0) hydrated = true;
}
function onGuildCreate(guild) {
pendingGuilds.delete(guild.id);
if (pendingGuilds.size === 0) {
hydrated = true;
console.log('все гильдии загружены, бот готов');
}
}
function onMessage(data) {
if (!hydrated) return; // пропускаем пока не готовы
// ...
}