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

Команды

У Флудилки нет встроенной системы slash-команд — команды это просто обычные сообщения, которые бот анализирует сам. Этот гайд — про паттерны такого парсинга.

Есть три распространённых способа звать бота:

Чаще всего — !, ?, . или /:

!ping
!roll 2d6
!rank @вася

Плюсы: быстро печатать, знакомо пользователям Discord. Минусы: конфликты между ботами, если их много.

@мой_бот помоги
@мой_бot кто я

Плюсы: однозначно — если упомянули именно вашего бота, значит обращаются к нему. Минусы: длиннее печатать.

Префикс для коротких команд + упоминание для разговоров с ботом. Часто делают так.

const PREFIX = '!';
function handleMessage(msg) {
if (msg.author.bot) return;
if (!msg.content.startsWith(PREFIX)) return;
const [command, ...args] = msg.content.slice(PREFIX.length).trim().split(/\s+/);
switch (command.toLowerCase()) {
case 'ping':
return sendMessage(msg.channel_id, 'pong 🏓');
case 'roll':
return handleRoll(msg, args);
case 'rank':
return handleRank(msg, args);
}
}

Без кулдауна любая команда — DoS-вектор: пользователь спамит !roll 100d6 и бот упирается в rate-limit. Минимальный анти-спам:

const cooldowns = new Map(); // key = `${userId}:${command}`, value = resetAt ms
function onCooldown(userId, command, windowMs = 3000) {
const key = `${userId}:${command}`;
const now = Date.now();
const reset = cooldowns.get(key) ?? 0;
if (reset > now) return reset - now; // осталось мс
cooldowns.set(key, now + windowMs);
return 0;
}
async function handleRoll(msg, args) {
const wait = onCooldown(msg.author.id, 'roll');
if (wait) {
await sendMessage(msg.channel_id, `Подожди ещё ${Math.ceil(wait / 1000)}с 🕒`);
return;
}
// ... обычная обработка
}

Для стойкости к рестартам — храните cooldowns в Redis или SQLite. См. Хранение состояния.

Обязательно проверяйте msg.author.bot === true и игнорируйте. Иначе первый же бот, который ответит на ваше сообщение, запустит бесконечный цикл «бот отвечает боту».

// !kick @вася троллит
const [target, ...reasonWords] = args;
const userId = target?.match(/^<@!?(\d+)>$/)?.[1];
const reason = reasonWords.join(' ');
// !mute @вася --minutes 30 --reason "спам в канале"
const parsed = {};
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--')) {
parsed[args[i].slice(2)] = args[i + 1];
i++;
}
}

Упоминания в сообщении выглядят как <@userId> (пользователь), <@&roleId> (роль), <#channelId> (канал). Массив msg.mentions уже содержит объекты пользователей — можно брать оттуда, не парся текст руками.

const targetUser = msg.mentions[0];
if (!targetUser) return sendMessage(msg.channel_id, 'Укажи пользователя: `!kick @ник`');

Не роняйте бота на ошибке в одной команде — загибайте только эту команду:

async function dispatch(command, msg, args) {
try {
await commandHandlers[command](msg, args);
} catch (e) {
console.error(`command ${command} failed:`, e);
await sendMessage(msg.channel_id, '❌ Что-то пошло не так. Я записал в логи.');
}
}