Whitelist через Discord-бот: форма заявки и авто-вайтлист для SMP

Whitelist через Discord-бот: форма заявки и авто-вайтлист для SMP

Если SMP пускает всех подряд, через неделю в чате будет хаос: гриферы, школьники без микрофона, и пара ботов на торговле. Whitelist через Discord-форму отсеивает случайных людей и собирает базу инфы по каждому игроку. Ниже разбор: какие готовые боты использовать, как сделать кастомный на discord.js, и как связать всё с RCON или DiscordSRV.

Зачем форма заявки в Discord, а не просто /whitelist

Открытый whitelist через /whitelist add nick работает только пока сервер маленький. Как только проект попадает в топы или Reddit, поток заявок становится неуправляемым. Discord-форма решает три задачи сразу.

Первое: фильтр случайных. Человек, который не готов написать пять предложений о себе, скорее всего не задержится дольше двух дней. Это естественный отсев.

Второе: сбор данных. Никнейм, возраст, прошлый опыт на SMP, как нашёл сервер, что любит делать в Minecraft. Эта инфа потом помогает админам понять аудиторию и куратору-роли решать конфликты.

Третье: единая точка входа. Все заявки в одном канале #applications, с кнопками accept/deny, с историей решений. Если игрок потом нарушит правила, можно посмотреть его исходную анкету и понять, врал ли он на старте.

В большинстве случаев Discord-бот ставится за пару часов и работает годами без правок.

Архитектура: как это устроено

Базовая схема:

  1. Игрок вступает в Discord-сервер проекта.
  2. Пишет slash-команду /apply в любом канале.
  3. Бот открывает Modal с полями: Minecraft username, age, experience, why join.
  4. После submit бот публикует заявку в приватный канал #applications с двумя кнопками Accept и Deny.
  5. Модератор жмёт Accept - бот через RCON или REST API отправляет на сервер whitelist add <username> и выдаёт игроку Discord-роль @Member.
  6. На Deny бот пишет игроку в DM причину отказа и логирует событие.

Связь с Minecraft-сервером строится через один из трёх каналов: RCON (TCP-протокол на порту 25575), плагин DiscordSRV с его API, или REST через простой плагин-обёртку. Самый надёжный вариант - DiscordSRV, потому что он переподключается сам и не падает при рестарте сервера.

Готовые боты: WhitelistBot, ApplicationBot, DraftBot

Если не хочется писать код, есть рабочие готовые решения.

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

ApplyBot (apply-bot.com) - специализированный бот для заявок. Поддерживает Modal-формы, разделение по категориям (whitelist, staff, builder), кастомные вопросы. Бесплатный тариф ограничен числом активных форм.

WhitelistBot - простой open-source бот на discord.py, найдёшь на GitHub под этим названием. Из коробки: одна форма, RCON-интеграция, кнопки accept/deny. Минус: давно не обновлялся, на новых версиях discord.py могут быть warnings.

Для большинства SMP до 200 игроков ApplyBot или DraftBot хватит без кастома. Если проект больше или нужна нестандартная логика (например, авто-проверка возраста аккаунта Discord), пишите свой.

Дешёвый путь: DiscordSRV-Apply

Если у вас уже стоит DiscordSRV для синхронизации чата, поставьте addon DiscordSRV-Apply. Форма открывается прямо в Discord через slash-команду, ответы летят в указанный канал, accept/deny кнопки тут же. Без отдельного бота, без хостинга, без VPS.

Конфиг addon выглядит так:

applications:
  default:
    questions:
      - "Твой Minecraft никнейм?"
      - "Сколько тебе лет?"
      - "Опиши свой опыт в SMP"
      - "Почему хочешь к нам?"
      - "Как нашёл сервер?"
    review-channel: "1234567890"
    on-accept:
      - "whitelist add %username%"
      - "lp user %username% parent set member"
      - "discord-role-add %discord_id% Member"
    on-deny:
      - "discord-dm %discord_id% Заявка отклонена. Причина: %reason%"

Это работает прямо из коробки, переменные %username% и %discord_id% подставляет сам DiscordSRV. На практике это самый быстрый способ запустить whitelist-форму на SMP до 500 игроков.

Кастомный бот на discord.js: каркас за 100 строк

Если нужна своя логика, вот скелет на discord.js v14. Он реализует /apply, открывает modal, постит в канал, обрабатывает кнопки.

const { Client, GatewayIntentBits, SlashCommandBuilder,
        ModalBuilder, TextInputBuilder, TextInputStyle,
        ActionRowBuilder, ButtonBuilder, ButtonStyle,
        EmbedBuilder } = require('discord.js');
const { Rcon } = require('rcon-client');

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const APPLICATIONS_CHANNEL = '1234567890';
const COOLDOWN = new Map();

client.on('interactionCreate', async (interaction) => {
  if (interaction.isChatInputCommand() && interaction.commandName === 'apply') {
    const last = COOLDOWN.get(interaction.user.id) || 0;
    if (Date.now() - last < 24 * 3600 * 1000) {
      return interaction.reply({ content: 'Ты уже подавал заявку за последние 24 часа.', ephemeral: true });
    }
    const accountAge = Date.now() - interaction.user.createdTimestamp;
    if (accountAge < 7 * 24 * 3600 * 1000) {
      return interaction.reply({ content: 'Аккаунт Discord слишком новый (нужно минимум 7 дней).', ephemeral: true });
    }

    const modal = new ModalBuilder().setCustomId('apply_modal').setTitle('Заявка на SMP');
    const fields = ['username', 'age', 'experience', 'why'];
    const labels = ['Minecraft никнейм', 'Возраст', 'Опыт в SMP', 'Почему к нам?'];
    fields.forEach((id, i) => {
      const input = new TextInputBuilder()
        .setCustomId(id).setLabel(labels[i])
        .setStyle(i >= 2 ? TextInputStyle.Paragraph : TextInputStyle.Short)
        .setRequired(true).setMaxLength(i >= 2 ? 500 : 50);
      modal.addComponents(new ActionRowBuilder().addComponents(input));
    });
    return interaction.showModal(modal);
  }

  if (interaction.isModalSubmit() && interaction.customId === 'apply_modal') {
    const data = {
      username: interaction.fields.getTextInputValue('username'),
      age: interaction.fields.getTextInputValue('age'),
      experience: interaction.fields.getTextInputValue('experience'),
      why: interaction.fields.getTextInputValue('why'),
    };
    COOLDOWN.set(interaction.user.id, Date.now());

    const embed = new EmbedBuilder()
      .setTitle(`Заявка от ${interaction.user.tag}`)
      .addFields(
        { name: 'Никнейм', value: data.username, inline: true },
        { name: 'Возраст', value: data.age, inline: true },
        { name: 'Опыт', value: data.experience },
        { name: 'Почему', value: data.why },
      ).setFooter({ text: `discord_id: ${interaction.user.id}` });

    const buttons = new ActionRowBuilder().addComponents(
      new ButtonBuilder().setCustomId(`accept_${interaction.user.id}_${data.username}`)
        .setLabel('Accept').setStyle(ButtonStyle.Success),
      new ButtonBuilder().setCustomId(`deny_${interaction.user.id}`)
        .setLabel('Deny').setStyle(ButtonStyle.Danger),
    );

    const channel = await client.channels.fetch(APPLICATIONS_CHANNEL);
    await channel.send({ embeds: [embed], components: [buttons] });
    return interaction.reply({ content: 'Заявка отправлена. Ждём решения модератора.', ephemeral: true });
  }

  if (interaction.isButton()) {
    const [action, userId, username] = interaction.customId.split('_');
    if (action === 'accept') {
      const rcon = await Rcon.connect({ host: 'mc.example.com', port: 25575, password: process.env.RCON });
      await rcon.send(`whitelist add ${username}`);
      await rcon.send(`lp user ${username} parent set member`);
      await rcon.end();
      const member = await interaction.guild.members.fetch(userId);
      await member.roles.add('ROLE_ID_MEMBER');
      await member.send(`Заявка принята. Подключайся: mc.example.com`).catch(() => {});
      await interaction.update({ content: `Принято модером ${interaction.user.tag}`, components: [] });
    } else if (action === 'deny') {
      const member = await interaction.guild.members.fetch(userId);
      await member.send('Заявка отклонена.').catch(() => {});
      await interaction.update({ content: `Отклонено модером ${interaction.user.tag}`, components: [] });
    }
  }
});

client.login(process.env.TOKEN);

Регистрация slash-команды делается отдельным скриптом register.js через REST API Discord, документация лежит на discord.js.guide. Дальше деплоится на любую VPS за 200 рублей в месяц или вообще на Raspberry Pi.

Связь с Minecraft: RCON, DiscordSRV или REST

RCON - стандартный способ. Включается в server.properties:

enable-rcon=true
rcon.port=25575
rcon.password=сложный_пароль_сюда

Из бота шлёшь команды через rcon-client (Node) или mcrcon (Python). Минус RCON: порт надо закрыть фаерволом ото всех IP, кроме адреса бота, иначе любой брутфорсер получит шелл на консоль сервера.

DiscordSRV даёт API через свой плагин-канал, можно слать команды через dsrv:execute. Безопаснее, потому что не открывает дополнительных портов. Минус: нужен сам DiscordSRV, который тащит за собой кучу зависимостей.

REST-плагин - пишешь свой Paper-плагин на 50 строк, который слушает HTTP на localhost и принимает токен в заголовке. Бот стучится через curl или fetch. Это самый чистый путь для production: нет открытых наружу портов, токен в заголовке, можно логировать всё что захочешь.

LinkAccount: привязка Discord к Minecraft

Если хочется связать Discord-аккаунт с Minecraft-ником намертво (для авто-ролей, шопа, статистики), используйте паттерн link-кодов.

Игрок заходит на сервер, пишет /link - плагин выдаёт ему код типа A7K2-9XQ8. В Discord он пишет боту /link A7K2-9XQ8 - бот отправляет код на сервер через RCON, плагин сверяет, привязывает discord_id к UUID и сохраняет в базе.

DiscordSRV делает это из коробки командой /discord link. Если своё - храните таблицу linked_accounts(uuid, discord_id, linked_at) в SQLite или MySQL.

Anti-spam и защита от фейков

Без защиты канал #applications за неделю забьётся мусором. Минимум что нужно:

  • Кулдаун на discord_id: одна заявка в 24 часа, или одна в неделю если первая отклонена.
  • Минимальный возраст аккаунта Discord: 7-30 дней. Свежесозданные аккаунты - почти всегда альты или боты.
  • Captcha-роль: перед /apply игрок должен пройти базовую verification через бота типа Wick или Captcha.bot.
  • Минимальная длина ответов: на поле "почему" требуйте 100+ символов, иначе будут ответы из одного слова.
  • Логирование IP-источника: если бот поднят за reverse proxy, можно отлавливать ботов по подозрительным паттернам.

На крупных проектах добавляют ещё проверку никнейма через Mojang API: существует ли такой Minecraft-аккаунт, не банен ли он на VPN-серверах.

Шаблон вопросов для SMP

Не делайте 20 вопросов, никто не пройдёт. Оптимум - 4-6 полей:

  1. Minecraft никнейм (точное написание).
  2. Возраст (число, без проверки честности, но фейков становится меньше).
  3. Опыт в SMP: на каких серверах играл, что строил.
  4. Что любишь делать в Minecraft: билдинг, редстоун, фарминг, PvP.
  5. Почему именно к нам, что зацепило.
  6. Как нашёл сервер: топы, Reddit, друг, YouTube.

Шестой вопрос полезен для аналитики маркетинга. Если 80% людей пишут "от друга" - значит органический рост, остальные каналы можно отключать.

Auto-roles после accept

После принятия заявки нужно одновременно:

  • Выдать роль @Member в Discord.
  • Привязать к LuckPerms-группе member на сервере.
  • Удалить роль @Applicant если она была.
  • Записать решение в лог-канал #mod-log с именем модератора и временем.

Через DiscordSRV это делается одной строкой в конфиге group sync. Через свой бот - три API-вызова подряд.

GroupRoleSynchronizationGroupsAndRolesToSync:
  member: "1234567890"
  vip: "9876543210"
  staff: "5555555555"

DiscordSRV сам отслеживает изменения ролей в Discord и группы в LuckPerms, синхронизация двусторонняя.

FAQ

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

Да, через DiscordSRV-Apply addon или платный ApplyBot. DiscordSRV-Apply ставится как обычный плагин Paper, конфигурируется через yaml, не требует ни строчки кода. Подходит для серверов до 500 активных игроков. ApplyBot настраивается прямо в Discord через slash-команды.

Как защититься от фейковых заявок и ботов?

Минимум: кулдаун 24 часа на discord_id, ограничение по возрасту аккаунта (7+ дней), captcha-верификация перед допуском к команде /apply. На крупных проектах добавляют проверку Minecraft-ника через Mojang API и ручную модерацию первых 24 часов после регистрации в Discord.

Бот должен быть в Discord или это плагин Minecraft?

Зависит от подхода. Discord-бот (discord.js, discord.py) хостится отдельно, общается с сервером через RCON. Плагин-вариант - это DiscordSRV + addon, всё крутится прямо на Minecraft-сервере. Плагин-путь проще для маленьких SMP, бот гибче для крупных.

Что делать с отклонёнными заявками?

Логируйте отказы в отдельный канал с причиной. Через 30 дней разрешайте повторную подачу автоматически. Не удаляйте сообщения с отказами сразу - модераторы должны видеть историю решений. На массовых отказах (более 50%) пересмотрите вопросы формы, скорее всего они слишком жёсткие.

Как сделать так, чтобы accept автоматически выдавал роли в Minecraft?

Через RCON команду lp user <username> parent set member после whitelist add. Если используете DiscordSRV, настройте group sync в конфиге - роль Discord будет автоматически мапиться на LuckPerms-группу при выдаче.

Стоит ли спрашивать реальный возраст?

Юридически проверить нельзя, но даже самозаявленный возраст отсекает часть троллей. Минимальный порог 13 лет (требование Discord ToS) ставьте обязательно. Для SMP с прицелом на взрослую аудиторию пишите 16+ или 18+ в описании, и в форме это явно указывайте.

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

Да, в конфиге бота указываете несколько RCON-эндпоинтов и в форме спрашиваете "на какой сервер заявка". Каждая заявка летит в свой канал #apps-survival, #apps-creative и так далее. Один процесс держит всех.

Что дальше

Если у вас уже есть DiscordSRV - попробуйте сначала addon DiscordSRV-Apply, он закрывает 90% потребностей за час работы. Если нужна кастомная логика (link-аккаунты, мульти-серверы, нестандартные проверки) - берите discord.js и делайте свой бот. Не открывайте RCON наружу, всегда фильтруйте по IP или поднимайте REST-плагин на localhost.

Whitelist через Discord-форму экономит часы модерации в неделю и поднимает качество комьюнити в разы. Главное: не делайте форму из 15 вопросов, мало кто её осилит.


Sunucunuzu DDoS Saldırılarından Koruyun

5 dakikada kurulumla ücretsiz koruma. 1 TB bant genişliği dahil.

Ücretsiz Deneyin


İlgili Makaleler