From f43db6a527c9ff7356befae23b1ca2abc79a3605 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Mon, 11 May 2026 14:01:54 +0200 Subject: [PATCH] Improved help system with category selector and main page --- .../command/impl/moderation/ClearCommand.java | 5 + .../command/impl/stats/MinusRepCommand.java | 2 +- .../loki/command/impl/stats/RepCommand.java | 2 +- .../command/impl/stats/RepTopCommand.java | 2 +- .../loki/command/impl/stats/StatsCommand.java | 2 +- .../command/impl/utility/HelpCommand.java | 142 ++++++++++------ .../command/impl/utility/InfoCommand.java | 5 + .../command/impl/utility/PingCommand.java | 5 + .../creator/loki/core/BotInitializer.java | 3 + .../loki/listener/HelpMenuHandler.java | 159 ++++++++++++++++++ 10 files changed, 275 insertions(+), 52 deletions(-) create mode 100644 src/main/java/creator/loki/listener/HelpMenuHandler.java diff --git a/src/main/java/creator/loki/command/impl/moderation/ClearCommand.java b/src/main/java/creator/loki/command/impl/moderation/ClearCommand.java index 71cd2df..45ac930 100644 --- a/src/main/java/creator/loki/command/impl/moderation/ClearCommand.java +++ b/src/main/java/creator/loki/command/impl/moderation/ClearCommand.java @@ -100,4 +100,9 @@ public class ClearCommand implements Command { ); }); } + + @Override + public String getCategory() { + return "Модерация"; + } } diff --git a/src/main/java/creator/loki/command/impl/stats/MinusRepCommand.java b/src/main/java/creator/loki/command/impl/stats/MinusRepCommand.java index 6c47df6..d5e187e 100644 --- a/src/main/java/creator/loki/command/impl/stats/MinusRepCommand.java +++ b/src/main/java/creator/loki/command/impl/stats/MinusRepCommand.java @@ -40,7 +40,7 @@ public class MinusRepCommand implements Command { @Override public String getCategory() { - return "Развлечения"; + return "Статистика"; } @Override diff --git a/src/main/java/creator/loki/command/impl/stats/RepCommand.java b/src/main/java/creator/loki/command/impl/stats/RepCommand.java index a7022a6..99c6c69 100644 --- a/src/main/java/creator/loki/command/impl/stats/RepCommand.java +++ b/src/main/java/creator/loki/command/impl/stats/RepCommand.java @@ -40,7 +40,7 @@ public class RepCommand implements Command { @Override public String getCategory() { - return "Развлечения"; + return "Статистика"; } @Override diff --git a/src/main/java/creator/loki/command/impl/stats/RepTopCommand.java b/src/main/java/creator/loki/command/impl/stats/RepTopCommand.java index 5bd9dc8..8d88aad 100644 --- a/src/main/java/creator/loki/command/impl/stats/RepTopCommand.java +++ b/src/main/java/creator/loki/command/impl/stats/RepTopCommand.java @@ -37,7 +37,7 @@ public class RepTopCommand implements Command { @Override public String getCategory() { - return "Развлечения"; + return "Статистика"; } @Override diff --git a/src/main/java/creator/loki/command/impl/stats/StatsCommand.java b/src/main/java/creator/loki/command/impl/stats/StatsCommand.java index c65ecaa..a72ef83 100644 --- a/src/main/java/creator/loki/command/impl/stats/StatsCommand.java +++ b/src/main/java/creator/loki/command/impl/stats/StatsCommand.java @@ -28,7 +28,7 @@ public class StatsCommand implements Command { @Override public String getCategory() { - return "Развлечения"; + return "Статистика"; } @Override diff --git a/src/main/java/creator/loki/command/impl/utility/HelpCommand.java b/src/main/java/creator/loki/command/impl/utility/HelpCommand.java index d599b7c..5439a5b 100644 --- a/src/main/java/creator/loki/command/impl/utility/HelpCommand.java +++ b/src/main/java/creator/loki/command/impl/utility/HelpCommand.java @@ -5,6 +5,8 @@ import creator.loki.command.CommandContext; import creator.loki.config.BotConfig; import creator.loki.core.CommandRegistry; import creator.loki.embed.EmbedFactory; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu; import java.awt.Color; import java.util.HashMap; @@ -18,17 +20,32 @@ public class HelpCommand implements Command { private static final Map CATEGORY_EMOJIS = new HashMap<>(); private static final Map CATEGORY_COLORS = new HashMap<>(); + private static final Map CATEGORY_DESCRIPTIONS = new HashMap<>(); static { CATEGORY_EMOJIS.put("Общие", "📋"); CATEGORY_EMOJIS.put("Роли", "🎭"); CATEGORY_EMOJIS.put("Развлечения", "🎮"); CATEGORY_EMOJIS.put("Модерация", "🛡️"); + CATEGORY_EMOJIS.put("Статистика", "📊"); + CATEGORY_EMOJIS.put("Утилиты", "🔧"); + CATEGORY_EMOJIS.put("Настройка", "⚙️"); CATEGORY_COLORS.put("Общие", new Color(52, 152, 219)); CATEGORY_COLORS.put("Роли", new Color(155, 89, 182)); CATEGORY_COLORS.put("Развлечения", new Color(241, 196, 15)); CATEGORY_COLORS.put("Модерация", new Color(231, 76, 60)); + CATEGORY_COLORS.put("Статистика", new Color(46, 204, 113)); + CATEGORY_COLORS.put("Утилиты", new Color(52, 152, 219)); + CATEGORY_COLORS.put("Настройка", new Color(149, 165, 166)); + + CATEGORY_DESCRIPTIONS.put("Общие", "Основные команды бота"); + CATEGORY_DESCRIPTIONS.put("Роли", "Управление кастомными ролями"); + CATEGORY_DESCRIPTIONS.put("Развлечения", "Игры и взаимодействия"); + CATEGORY_DESCRIPTIONS.put("Модерация", "Команды модерации сервера"); + CATEGORY_DESCRIPTIONS.put("Статистика", "Уровни, репутация и статистика"); + CATEGORY_DESCRIPTIONS.put("Утилиты", "Полезные инструменты"); + CATEGORY_DESCRIPTIONS.put("Настройка", "Настройка систем бота (только админы)"); } public HelpCommand(CommandRegistry registry, BotConfig config) { @@ -49,65 +66,94 @@ public class HelpCommand implements Command { @Override public void execute(CommandContext context) { var commands = registry.getAllCommands(); - - // Группируем команды по категориям Map> commandsByCategory = commands.stream() .collect(Collectors.groupingBy(Command::getCategory)); - // Создаём красивый embed - EmbedFactory embed = EmbedFactory.create() - .setTitle("📚 Справка по командам") - .setDescription( - "**Префикс:** `" + config.getPrefix() + "`\n" + - "**Всего команд:** " + commands.size() + "\n\n" + - "Используй `" + config.getPrefix() + "<команда>` для выполнения" - ) - .setColor(new Color(88, 101, 242)) - .setThumbnail(context.getGuild() != null ? - context.getGuild().getIconUrl() : null); + // Главная страница + EmbedFactory embed = createMainPage(context, commandsByCategory); - // Добавляем команды по категориям - commandsByCategory.forEach((category, cmds) -> { - String emoji = CATEGORY_EMOJIS.getOrDefault(category, "📌"); - StringBuilder commandList = new StringBuilder(); + // Создаём селектор категорий + StringSelectMenu.Builder menuBuilder = StringSelectMenu.create("help:category") + .setPlaceholder("📂 Выберите категорию"); - int count = 0; - int part = 1; + boolean isAdmin = context.getAuthor() != null && + context.getAuthor().hasPermission(Permission.ADMINISTRATOR); - for (Command cmd : cmds) { - String cmdText = "`" + config.getPrefix() + cmd.getUsage() + "`\n" + - cmd.getDescription() + "\n\n"; - - // Если добавление команды превысит лимит, создаём новое поле - if (commandList.length() + cmdText.length() > 1000) { - embed.addField( - emoji + " " + category + (part > 1 ? " (" + part + ")" : ""), - commandList.toString().trim(), - false - ); - commandList = new StringBuilder(); - part++; + commandsByCategory.keySet().stream() + .sorted() + .forEach(category -> { + // Скрываем категорию "Настройка" для не-админов + if (category.equals("Настройка") && !isAdmin) { + return; } - commandList.append(cmdText); - count++; - } + String emoji = CATEGORY_EMOJIS.getOrDefault(category, "📌"); + String description = CATEGORY_DESCRIPTIONS.getOrDefault(category, "Команды категории"); + int count = commandsByCategory.get(category).size(); - // Добавляем оставшиеся команды - if (commandList.length() > 0) { - embed.addField( - emoji + " " + category + (part > 1 ? " (" + part + ")" : ""), - commandList.toString().trim(), - false + menuBuilder.addOption( + category, + "category:" + category, + description + " (" + count + " команд)", + net.dv8tion.jda.api.entities.emoji.Emoji.fromUnicode(emoji) ); - } - }); + }); - embed.setFooter( - "Запросил: " + context.getAuthor().getUser().getName(), - context.getAuthor().getUser().getAvatarUrl() - ); + context.getChannel() + .sendMessageEmbeds(embed.build()) + .addActionRow(menuBuilder.build()) + .queue(); + } - context.getChannel().sendMessageEmbeds(embed.build()).queue(); + private EmbedFactory createMainPage(CommandContext context, Map> commandsByCategory) { + int totalCommands = commandsByCategory.values().stream() + .mapToInt(List::size) + .sum(); + + boolean isAdmin = context.getAuthor() != null && + context.getAuthor().hasPermission(Permission.ADMINISTRATOR); + + StringBuilder categoriesText = new StringBuilder(); + commandsByCategory.keySet().stream() + .sorted() + .forEach(category -> { + // Скрываем категорию "Настройка" для не-админов + if (category.equals("Настройка") && !isAdmin) { + return; + } + + String emoji = CATEGORY_EMOJIS.getOrDefault(category, "📌"); + int count = commandsByCategory.get(category).size(); + categoriesText.append(emoji).append(" **").append(category) + .append("** — ").append(count).append(" команд\n"); + }); + + return EmbedFactory.create() + .setTitle("📚 Справка по командам Loki Bot") + .setDescription( + "Привет! Я многофункциональный Discord бот с системой уровней, " + + "личными голосовыми каналами и множеством развлекательных команд.\n\n" + + "**Префикс команд:** `" + config.getPrefix() + "`\n" + + "**Всего команд:** " + totalCommands + "\n\n" + + "**📂 Категории команд:**\n" + categoriesText.toString() + "\n" + + "Используй селектор ниже для просмотра команд категории." + ) + .setColor(new Color(88, 101, 242)) + .setThumbnail(context.getGuild() != null ? context.getGuild().getIconUrl() : null) + .addField("💡 Как использовать", + "Большинство команд используют префикс `!`\n" + + "Пример: `!rank`, `!ship @user1 @user2`\n\n" + + "Некоторые важные команды используют `/`\n" + + "Пример: `/voice-setup`, `/counter-setup`", + false) + .setFooter( + "Запросил: " + context.getAuthor().getUser().getName(), + context.getAuthor().getUser().getAvatarUrl() + ); + } + + @Override + public String getCategory() { + return "Утилиты"; } } diff --git a/src/main/java/creator/loki/command/impl/utility/InfoCommand.java b/src/main/java/creator/loki/command/impl/utility/InfoCommand.java index 37c97ff..b04f987 100644 --- a/src/main/java/creator/loki/command/impl/utility/InfoCommand.java +++ b/src/main/java/creator/loki/command/impl/utility/InfoCommand.java @@ -113,4 +113,9 @@ public class InfoCommand implements Command { .build() ).queue(); } + + @Override + public String getCategory() { + return "Утилиты"; + } } diff --git a/src/main/java/creator/loki/command/impl/utility/PingCommand.java b/src/main/java/creator/loki/command/impl/utility/PingCommand.java index 29cc0d8..035885b 100644 --- a/src/main/java/creator/loki/command/impl/utility/PingCommand.java +++ b/src/main/java/creator/loki/command/impl/utility/PingCommand.java @@ -36,4 +36,9 @@ public class PingCommand implements Command { ).queue(); }); } + + @Override + public String getCategory() { + return "Утилиты"; + } } diff --git a/src/main/java/creator/loki/core/BotInitializer.java b/src/main/java/creator/loki/core/BotInitializer.java index 85740be..52cd397 100644 --- a/src/main/java/creator/loki/core/BotInitializer.java +++ b/src/main/java/creator/loki/core/BotInitializer.java @@ -55,6 +55,7 @@ public class BotInitializer { private final VoiceButtonHandler voiceButtonHandler; private final ModerationLogger moderationLogger; private final LevelUpNotifier levelUpNotifier; + private final HelpMenuHandler helpMenuHandler; @Inject public BotInitializer( @@ -102,6 +103,7 @@ public class BotInitializer { this.logSetupCommand = new LogSetupCommand(configRepo); this.voiceButtonHandler = new VoiceButtonHandler(voiceChannelManager); this.moderationLogger = new ModerationLogger(configRepo); + this.helpMenuHandler = new HelpMenuHandler(registry, config); } public JDA start() { @@ -160,6 +162,7 @@ public class BotInitializer { logSetupCommand, voiceButtonHandler, moderationLogger, + helpMenuHandler, new LeaderboardButtonListener(levelSystem) ) .build(); diff --git a/src/main/java/creator/loki/listener/HelpMenuHandler.java b/src/main/java/creator/loki/listener/HelpMenuHandler.java new file mode 100644 index 0000000..d099254 --- /dev/null +++ b/src/main/java/creator/loki/listener/HelpMenuHandler.java @@ -0,0 +1,159 @@ +package creator.loki.listener; + +import creator.loki.command.Command; +import creator.loki.config.BotConfig; +import creator.loki.core.CommandRegistry; +import creator.loki.embed.EmbedFactory; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu; + +import java.awt.Color; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class HelpMenuHandler extends ListenerAdapter { + private final CommandRegistry registry; + private final BotConfig config; + + private static final Map CATEGORY_EMOJIS = new HashMap<>(); + private static final Map CATEGORY_COLORS = new HashMap<>(); + + static { + CATEGORY_EMOJIS.put("Общие", "📋"); + CATEGORY_EMOJIS.put("Роли", "🎭"); + CATEGORY_EMOJIS.put("Развлечения", "🎮"); + CATEGORY_EMOJIS.put("Модерация", "🛡️"); + CATEGORY_EMOJIS.put("Статистика", "📊"); + CATEGORY_EMOJIS.put("Утилиты", "🔧"); + CATEGORY_EMOJIS.put("Настройка", "⚙️"); + + CATEGORY_COLORS.put("Общие", new Color(52, 152, 219)); + CATEGORY_COLORS.put("Роли", new Color(155, 89, 182)); + CATEGORY_COLORS.put("Развлечения", new Color(241, 196, 15)); + CATEGORY_COLORS.put("Модерация", new Color(231, 76, 60)); + CATEGORY_COLORS.put("Статистика", new Color(46, 204, 113)); + CATEGORY_COLORS.put("Утилиты", new Color(52, 152, 219)); + CATEGORY_COLORS.put("Настройка", new Color(149, 165, 166)); + } + + public HelpMenuHandler(CommandRegistry registry, BotConfig config) { + this.registry = registry; + this.config = config; + } + + @Override + public void onStringSelectInteraction(StringSelectInteractionEvent event) { + if (!event.getComponentId().equals("help:category")) { + return; + } + + String selectedValue = event.getValues().get(0); + if (!selectedValue.startsWith("category:")) { + return; + } + + String category = selectedValue.substring("category:".length()); + + // Проверка прав для категории "Настройка" + if (category.equals("Настройка") && event.getMember() != null && + !event.getMember().hasPermission(Permission.ADMINISTRATOR)) { + event.reply("❌ Эта категория доступна только администраторам!") + .setEphemeral(true) + .queue(); + return; + } + + var commands = registry.getAllCommands(); + Map> commandsByCategory = commands.stream() + .collect(Collectors.groupingBy(Command::getCategory)); + + List categoryCommands = commandsByCategory.get(category); + if (categoryCommands == null || categoryCommands.isEmpty()) { + event.reply("❌ В этой категории нет команд!") + .setEphemeral(true) + .queue(); + return; + } + + // Создаём embed для категории + EmbedFactory embed = createCategoryEmbed(category, categoryCommands, event); + + // Пересоздаём селектор + StringSelectMenu.Builder menuBuilder = StringSelectMenu.create("help:category") + .setPlaceholder("📂 Выберите категорию"); + + boolean isAdmin = event.getMember() != null && + event.getMember().hasPermission(Permission.ADMINISTRATOR); + + commandsByCategory.keySet().stream() + .sorted() + .forEach(cat -> { + if (cat.equals("Настройка") && !isAdmin) { + return; + } + + String emoji = CATEGORY_EMOJIS.getOrDefault(cat, "📌"); + int count = commandsByCategory.get(cat).size(); + + menuBuilder.addOption( + cat, + "category:" + cat, + count + " команд", + net.dv8tion.jda.api.entities.emoji.Emoji.fromUnicode(emoji) + ); + }); + + event.editMessageEmbeds(embed.build()) + .setActionRow(menuBuilder.build()) + .queue(); + } + + private EmbedFactory createCategoryEmbed(String category, List commands, + StringSelectInteractionEvent event) { + String emoji = CATEGORY_EMOJIS.getOrDefault(category, "📌"); + Color color = CATEGORY_COLORS.getOrDefault(category, new Color(88, 101, 242)); + + EmbedFactory embed = EmbedFactory.create() + .setTitle(emoji + " " + category) + .setDescription("Список команд категории **" + category + "**\n" + + "Префикс: `" + config.getPrefix() + "`") + .setColor(color); + + // Группируем команды по 8 в поле + StringBuilder commandList = new StringBuilder(); + int count = 0; + + for (Command cmd : commands) { + String prefix = cmd.getName().startsWith("/") ? "" : config.getPrefix(); + commandList.append("`").append(prefix).append(cmd.getUsage()).append("`\n") + .append(cmd.getDescription()).append("\n\n"); + count++; + + // Если достигли 8 команд или лимита символов, создаём новое поле + if (count % 8 == 0 || commandList.length() > 900) { + embed.addField("", commandList.toString().trim(), false); + commandList = new StringBuilder(); + } + } + + // Добавляем оставшиеся команды + if (commandList.length() > 0) { + embed.addField("", commandList.toString().trim(), false); + } + + embed.addField("💡 Подсказка", + "Используй селектор ниже для переключения между категориями", + false); + + embed.setFooter( + "Запросил: " + event.getUser().getName() + " • Всего команд: " + commands.size(), + event.getUser().getAvatarUrl() + ); + + return embed; + } +}