Improved help system with category selector and main page

This commit is contained in:
loki5512344 2026-05-11 14:01:54 +02:00
parent 0f664e317e
commit f43db6a527
10 changed files with 275 additions and 52 deletions

View file

@ -100,4 +100,9 @@ public class ClearCommand implements Command {
);
});
}
@Override
public String getCategory() {
return "Модерация";
}
}

View file

@ -40,7 +40,7 @@ public class MinusRepCommand implements Command {
@Override
public String getCategory() {
return "Развлечения";
return "Статистика";
}
@Override

View file

@ -40,7 +40,7 @@ public class RepCommand implements Command {
@Override
public String getCategory() {
return "Развлечения";
return "Статистика";
}
@Override

View file

@ -37,7 +37,7 @@ public class RepTopCommand implements Command {
@Override
public String getCategory() {
return "Развлечения";
return "Статистика";
}
@Override

View file

@ -28,7 +28,7 @@ public class StatsCommand implements Command {
@Override
public String getCategory() {
return "Развлечения";
return "Статистика";
}
@Override

View file

@ -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<String, String> CATEGORY_EMOJIS = new HashMap<>();
private static final Map<String, Color> CATEGORY_COLORS = new HashMap<>();
private static final Map<String, String> 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<String, List<Command>> 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<String, List<Command>> 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 "Утилиты";
}
}

View file

@ -113,4 +113,9 @@ public class InfoCommand implements Command {
.build()
).queue();
}
@Override
public String getCategory() {
return "Утилиты";
}
}

View file

@ -36,4 +36,9 @@ public class PingCommand implements Command {
).queue();
});
}
@Override
public String getCategory() {
return "Утилиты";
}
}

View file

@ -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();

View file

@ -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<String, String> CATEGORY_EMOJIS = new HashMap<>();
private static final Map<String, Color> 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<String, List<Command>> commandsByCategory = commands.stream()
.collect(Collectors.groupingBy(Command::getCategory));
List<Command> 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<Command> 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;
}
}