feat: add social commands, MetricsCollector, moderation commands to feature packages

This commit is contained in:
loki5512344 2026-06-19 23:44:10 +02:00
parent 63f8763ff9
commit fcb5cd7207
14 changed files with 1328 additions and 0 deletions

View file

@ -0,0 +1,78 @@
package creator.loki.command.impl.entertainment.interaction.negative;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;
import java.util.List;
/**
* Тыкнуть пользователя с GIF
*/
public class PokeCommand implements Command {
private final CooldownManager cooldownManager;
public PokeCommand(CooldownManager cooldownManager) {
this.cooldownManager = cooldownManager;
}
@Override
public String getName() {
return "poke";
}
@Override
public String getDescription() {
return "Тыкнуть пользователя";
}
@Override
public String getUsage() {
return "poke <@user>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "poke")) {
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "poke");
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
return;
}
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
return;
}
Member target = mentioned.get(0);
cooldownManager.applyCooldown(context.getAuthor().getId(), "poke", 5);
String gifUrl = NekosLifeApi.fetchImage("poke");
if (gifUrl == null) {
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
return;
}
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setDescription("👉 " + context.getAuthor().getAsMention() + " тыкает в " + target.getAsMention() + "! 😏")
.setImage(gifUrl)
.setColor(new Color(0, 128, 128))
.setFooter("Powered by nekos.life")
.build()
).queue();
}
}

View file

@ -0,0 +1,68 @@
package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;
import java.util.List;
import java.util.Random;
public class RoastCommand implements Command {
private final Random random = new Random();
private static final String[] ROASTS = {
"Ты как Internet Explorer — медленный и никому не нужный",
"Твой код работает? Это баг, исправь его",
"Ты пишешь код как будто тебе за каждую строку платят",
"Твоя логика как у JavaScript: непредсказуемая",
"Ты как HTML без CSS — работаешь, но выглядишь ужасно",
"Твой IQ как температура в Антарктиде — ниже нуля",
"Ты как баг в продакшене — неожиданный и нежеланный",
"Твои шутки как deprecated код — устарели",
"Ты как NULL pointer — бесполезный и вызываешь ошибки",
"Твой уровень как индекс массива в Python — может быть отрицательным"
};
@Override
public String getName() {
return "roast";
}
@Override
public String getDescription() {
return "Дружеский рофл над пользователем";
}
@Override
public String getUsage() {
return "roast <@user>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("Укажи пользователя через @").queue();
return;
}
Member target = mentioned.get(0);
String roast = ROASTS[random.nextInt(ROASTS.length)];
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setTitle("🔥 Roast")
.setDescription(target.getAsMention() + ", " + roast)
.setColor(new Color(231, 76, 60))
.setFooter("Это шутка, не обижайся!")
.build()
).queue();
}
}

View file

@ -0,0 +1,78 @@
package creator.loki.command.impl.entertainment.interaction.negative;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;
import java.util.List;
/**
* Шлепнуть пользователя с GIF
*/
public class SlapCommand implements Command {
private final CooldownManager cooldownManager;
public SlapCommand(CooldownManager cooldownManager) {
this.cooldownManager = cooldownManager;
}
@Override
public String getName() {
return "slap";
}
@Override
public String getDescription() {
return "Шлепнуть пользователя";
}
@Override
public String getUsage() {
return "slap <@user>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "slap")) {
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "slap");
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
return;
}
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
return;
}
Member target = mentioned.get(0);
cooldownManager.applyCooldown(context.getAuthor().getId(), "slap", 5);
String gifUrl = NekosLifeApi.fetchImage("slap");
if (gifUrl == null) {
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
return;
}
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setDescription("👋 " + context.getAuthor().getAsMention() + " шлепает " + target.getAsMention() + "! 😠")
.setImage(gifUrl)
.setColor(new Color(255, 165, 0))
.setFooter("Powered by nekos.life")
.build()
).queue();
}
}

View file

@ -0,0 +1,78 @@
package creator.loki.command.impl.entertainment.interaction.negative;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;
import java.util.List;
/**
* Пощекотать пользователя с GIF
*/
public class TickleCommand implements Command {
private final CooldownManager cooldownManager;
public TickleCommand(CooldownManager cooldownManager) {
this.cooldownManager = cooldownManager;
}
@Override
public String getName() {
return "tickle";
}
@Override
public String getDescription() {
return "Пощекотать пользователя";
}
@Override
public String getUsage() {
return "tickle <@user>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "tickle")) {
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "tickle");
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
return;
}
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
return;
}
Member target = mentioned.get(0);
cooldownManager.applyCooldown(context.getAuthor().getId(), "tickle", 5);
String gifUrl = NekosLifeApi.fetchImage("tickle");
if (gifUrl == null) {
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
return;
}
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setDescription("🤗 " + context.getAuthor().getAsMention() + " щекочет " + target.getAsMention() + "!")
.setImage(gifUrl)
.setColor(new Color(0, 255, 0))
.setFooter("Powered by nekos.life")
.build()
).queue();
}
}

View file

@ -0,0 +1,67 @@
package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;
import java.util.List;
import java.util.Random;
public class ComplimentCommand implements Command {
private final Random random = new Random();
private static final String[] COMPLIMENTS = {
"Ты как идеальный код — красивый и без багов",
"Твоя улыбка ярче, чем RGB подсветка",
"Ты как хороший коммит — делаешь всё лучше",
"Твой вайб как у Python — простой и приятный",
"Ты как успешный деплой — всё работает отлично",
"Твоя энергия как у SSD — быстрая и эффективная",
"Ты как open source проект — полезный для всех",
"Твоя харизма как у TypeScript — добавляет уверенности",
"Ты как хороший API — понятный и надёжный",
"Твоё присутствие как кофе для программиста — необходимо"
};
@Override
public String getName() {
return "compliment";
}
@Override
public String getDescription() {
return "Сделать комплимент пользователю";
}
@Override
public String getUsage() {
return "compliment <@user>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("Укажи пользователя через @").queue();
return;
}
Member target = mentioned.get(0);
String compliment = COMPLIMENTS[random.nextInt(COMPLIMENTS.length)];
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setTitle("💖 Комплимент")
.setDescription(target.getAsMention() + ", " + compliment)
.setColor(new Color(255, 182, 193))
.build()
).queue();
}
}

View file

@ -0,0 +1,78 @@
package creator.loki.command.impl.entertainment.interaction.positive;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;
import java.util.List;
/**
* Обниматься с пользователем с GIF
*/
public class CuddleCommand implements Command {
private final CooldownManager cooldownManager;
public CuddleCommand(CooldownManager cooldownManager) {
this.cooldownManager = cooldownManager;
}
@Override
public String getName() {
return "cuddle";
}
@Override
public String getDescription() {
return "Обниматься с пользователем";
}
@Override
public String getUsage() {
return "cuddle <@user>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "cuddle")) {
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "cuddle");
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
return;
}
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
return;
}
Member target = mentioned.get(0);
cooldownManager.applyCooldown(context.getAuthor().getId(), "cuddle", 5);
String gifUrl = NekosLifeApi.fetchImage("cuddle");
if (gifUrl == null) {
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
return;
}
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setDescription("🥰 " + context.getAuthor().getAsMention() + " обнимается с " + target.getAsMention() + "! 💕")
.setImage(gifUrl)
.setColor(new Color(255, 0, 255))
.setFooter("Powered by nekos.life")
.build()
).queue();
}
}

View file

@ -0,0 +1,78 @@
package creator.loki.command.impl.entertainment.interaction.positive;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;
import java.util.List;
/**
* Покормить пользователя с GIF
*/
public class FeedCommand implements Command {
private final CooldownManager cooldownManager;
public FeedCommand(CooldownManager cooldownManager) {
this.cooldownManager = cooldownManager;
}
@Override
public String getName() {
return "feed";
}
@Override
public String getDescription() {
return "Покормить пользователя";
}
@Override
public String getUsage() {
return "feed <@user>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "feed")) {
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "feed");
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
return;
}
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
return;
}
Member target = mentioned.get(0);
cooldownManager.applyCooldown(context.getAuthor().getId(), "feed", 5);
String gifUrl = NekosLifeApi.fetchImage("feed");
if (gifUrl == null) {
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
return;
}
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setDescription("🍽️ " + context.getAuthor().getAsMention() + " кормит " + target.getAsMention() + "! 😋")
.setImage(gifUrl)
.setColor(new Color(255, 200, 100))
.setFooter("Powered by nekos.life")
.build()
).queue();
}
}

View file

@ -0,0 +1,61 @@
package creator.loki.command.impl.entertainment.interaction.positive;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;
import java.util.List;
public class HugCommand implements Command {
@Override
public String getName() {
return "hug";
}
@Override
public String getDescription() {
return "Обнять пользователя";
}
@Override
public String getUsage() {
return "hug <@user>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
return;
}
Member target = mentioned.get(0);
// Получаем GIF из nekos.life API
String gifUrl = NekosLifeApi.fetchImage("hug");
if (gifUrl == null) {
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
return;
}
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setDescription("🤗 " + context.getAuthor().getAsMention() + " обнял " + target.getAsMention() + " 💕")
.setImage(gifUrl)
.setColor(new Color(255, 182, 193))
.setFooter("Powered by nekos.life")
.build()
).queue();
}
}

View file

@ -0,0 +1,92 @@
package creator.loki.command.impl.entertainment.interaction.positive;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;
import java.util.List;
/**
* Поцелуй пользователя с GIF
*/
public class KissCommand implements Command {
private final CooldownManager cooldownManager;
public KissCommand(CooldownManager cooldownManager) {
this.cooldownManager = cooldownManager;
}
@Override
public String getName() {
return "kiss";
}
@Override
public String getDescription() {
return "Поцеловать пользователя";
}
@Override
public String getUsage() {
return "kiss <@user>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
// Проверка cooldown (5 секунд)
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "kiss")) {
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "kiss");
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
return;
}
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("❌ Укажи кого поцеловать! Использование: `!kiss @user`").queue();
return;
}
Member target = mentioned.get(0);
if (target.getId().equals(context.getAuthor().getId())) {
context.getChannel().sendMessage("❌ Нельзя целовать самого себя!").queue();
return;
}
if (target.getUser().isBot()) {
context.getChannel().sendMessage("❌ Боты не умеют целоваться!").queue();
return;
}
// Применяем cooldown
cooldownManager.applyCooldown(context.getAuthor().getId(), "kiss", 5);
// Получаем GIF из nekos.life API
String gifUrl = NekosLifeApi.fetchImage("kiss");
if (gifUrl == null) {
context.getChannel().sendMessage("❌ Не удалось получить изображение!").queue();
return;
}
context.getChannel().sendMessageEmbeds(
EmbedFactory.create()
.setDescription("💋 " + context.getAuthor().getAsMention() + " целует " + target.getAsMention() + " ❤️")
.setImage(gifUrl)
.setColor(new Color(255, 105, 180))
.setFooter("Powered by nekos.life")
.build()
).queue();
}
}

View file

@ -0,0 +1,91 @@
package creator.loki.command.impl.entertainment.interaction.positive;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.util.List;
public class PatCommand implements Command {
private static final int COOLDOWN_SECONDS = 5;
private final CooldownManager cooldownManager;
public PatCommand(CooldownManager cooldownManager) {
this.cooldownManager = cooldownManager;
}
@Override
public String getName() {
return "pat";
}
@Override
public String getDescription() {
return "Погладить пользователя";
}
@Override
public String getUsage() {
return "pat <@user> <количество 1-7>";
}
@Override
public String getCategory() {
return "Развлечения";
}
@Override
public void execute(CommandContext context) {
String userId = context.getAuthor().getId();
// Проверка cooldown
if (cooldownManager.isOnCooldown(userId, "pat")) {
long remaining = cooldownManager.getRemainingSeconds(userId, "pat");
context.getChannel().sendMessage(
"⏰ Подожди " + remaining + " сек."
).queue();
return;
}
// Проверка аргументов
if (context.getArgs().size() < 2) {
context.getChannel().sendMessage(
"❌ Используй: `!pat <@user> <количество 1-7>`"
).queue();
return;
}
// Получение упомянутого пользователя
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessage("❌ Укажи пользователя через @").queue();
return;
}
Member target = mentioned.get(0);
// Парсинг количества
int count;
try {
count = Integer.parseInt(context.getArgs().get(1));
} catch (NumberFormatException e) {
context.getChannel().sendMessage("❌ Укажи число от 1 до 7").queue();
return;
}
// Валидация количества
if (count < 1 || count > 7) {
context.getChannel().sendMessage("❌ Количество должно быть от 1 до 7").queue();
return;
}
// Применяем cooldown
cooldownManager.applyCooldown(userId, "pat", COOLDOWN_SECONDS);
// Отправляем каждое "pat-pat" отдельным сообщением
for (int i = 0; i < count; i++) {
context.getChannel().sendMessage("pat-pat " + target.getAsMention()).queue();
}
}
}

View file

@ -0,0 +1,203 @@
package creator.loki.feature.metrics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
/**
* Сборщик метрик для мониторинга бота
*/
public class MetricsCollector {
private static final Logger logger = LoggerFactory.getLogger(MetricsCollector.class);
// Счётчики команд
private final ConcurrentHashMap<String, LongAdder> commandCounts = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, LongAdder> commandErrors = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, AtomicLong> commandTotalTime = new ConcurrentHashMap<>();
// Общие метрики
private final LongAdder totalCommands = new LongAdder();
private final LongAdder totalErrors = new LongAdder();
private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
public MetricsCollector() {
logger.info("MetricsCollector инициализирован");
}
/**
* Записывает выполнение команды
*/
public void recordCommand(String commandName, long executionTimeMs) {
commandCounts.computeIfAbsent(commandName, k -> new LongAdder()).increment();
commandTotalTime.computeIfAbsent(commandName, k -> new AtomicLong()).addAndGet(executionTimeMs);
totalCommands.increment();
}
/**
* Записывает ошибку команды
*/
public void recordError(String commandName) {
commandErrors.computeIfAbsent(commandName, k -> new LongAdder()).increment();
totalErrors.increment();
}
/**
* Возвращает количество выполнений команды
*/
public long getCommandCount(String commandName) {
LongAdder counter = commandCounts.get(commandName);
return counter != null ? counter.sum() : 0;
}
/**
* Возвращает количество ошибок команды
*/
public long getCommandErrors(String commandName) {
LongAdder counter = commandErrors.get(commandName);
return counter != null ? counter.sum() : 0;
}
/**
* Возвращает среднее время выполнения команды
*/
public double getAverageExecutionTime(String commandName) {
long count = getCommandCount(commandName);
if (count == 0) {
return 0.0;
}
AtomicLong totalTime = commandTotalTime.get(commandName);
if (totalTime == null) {
return 0.0;
}
return (double) totalTime.get() / count;
}
/**
* Возвращает общее количество выполненных команд
*/
public long getTotalCommands() {
return totalCommands.sum();
}
/**
* Возвращает общее количество ошибок
*/
public long getTotalErrors() {
return totalErrors.sum();
}
/**
* Возвращает процент ошибок
*/
public double getErrorRate() {
long total = getTotalCommands();
if (total == 0) {
return 0.0;
}
return (double) getTotalErrors() / total * 100;
}
/**
* Возвращает время работы бота в секундах
*/
public long getUptimeSeconds() {
return (System.currentTimeMillis() - startTime.get()) / 1000;
}
/**
* JSON для {@code GET /health} (пробы оркестраторов).
*/
public String buildHealthJson() {
String version = Optional.ofNullable(MetricsCollector.class.getPackage().getImplementationVersion())
.filter(v -> !v.isBlank())
.orElse("dev");
return String.format(Locale.ROOT,
"{\"status\":\"ok\",\"version\":%s,\"uptimeSeconds\":%d,\"commandsRun\":%d,\"errors\":%d,\"errorRatePercent\":%.4f}",
jsonQuoted(version),
getUptimeSeconds(),
getTotalCommands(),
getTotalErrors(),
getErrorRate());
}
private static String jsonQuoted(String s) {
StringBuilder sb = new StringBuilder(s.length() + 2);
sb.append('"');
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '\\' -> sb.append("\\\\");
case '"' -> sb.append("\\\"");
case '\n' -> sb.append("\\n");
case '\r' -> sb.append("\\r");
case '\t' -> sb.append("\\t");
default -> sb.append(c < ' ' ? ' ' : c);
}
}
sb.append('"');
return sb.toString();
}
/**
* Возвращает топ команд по использованию
*/
public String getTopCommands(int limit) {
StringBuilder sb = new StringBuilder();
sb.append("📊 Топ команд:\n");
commandCounts.entrySet().stream()
.sorted((e1, e2) -> Long.compare(e2.getValue().sum(), e1.getValue().sum()))
.limit(limit)
.forEach(entry -> {
String cmd = entry.getKey();
long count = entry.getValue().sum();
long errors = getCommandErrors(cmd);
double avgTime = getAverageExecutionTime(cmd);
sb.append(String.format(" %s: %d выполнений, %.1f мс среднее, %d ошибок\n",
cmd, count, avgTime, errors));
});
return sb.toString();
}
/**
* Возвращает полную статистику
*/
public String getFullStats() {
long uptime = getUptimeSeconds();
long hours = uptime / 3600;
long minutes = (uptime % 3600) / 60;
long seconds = uptime % 60;
StringBuilder sb = new StringBuilder();
sb.append("📈 Статистика бота\n");
sb.append("━━━━━━━━━━━━━━━━━━━━\n");
sb.append(String.format("⏱️ Uptime: %dч %dм %dс\n", hours, minutes, seconds));
sb.append(String.format("📝 Всего команд: %d\n", getTotalCommands()));
sb.append(String.format("❌ Всего ошибок: %d (%.2f%%)\n", getTotalErrors(), getErrorRate()));
sb.append(String.format("📊 Уникальных команд: %d\n", commandCounts.size()));
sb.append("\n");
sb.append(getTopCommands(10));
return sb.toString();
}
/**
* Сбрасывает все метрики
*/
public void reset() {
commandCounts.clear();
commandErrors.clear();
commandTotalTime.clear();
totalCommands.reset();
totalErrors.reset();
startTime.set(System.currentTimeMillis());
logger.info("Метрики сброшены");
}
}

View file

@ -0,0 +1,129 @@
package creator.loki.feature.moderation.commands;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedTemplates;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import java.util.List;
public class BanCommand implements Command {
@Override
public String getName() {
return "ban";
}
@Override
public String getDescription() {
return "Банит пользователя на сервере";
}
@Override
public String getUsage() {
return "ban <@user> [причина]";
}
@Override
public String getCategory() {
return "Модерация";
}
@Override
public void execute(CommandContext context) {
if (context.getGuild() == null) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
).queue();
return;
}
// Проверка прав модератора
if (context.getAuthor() == null || !context.getAuthor().hasPermission(Permission.BAN_MEMBERS)) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Недостаточно прав", "У вас нет прав на бан пользователей")
).queue();
return;
}
// Проверка прав бота
if (!context.getGuild().getSelfMember().hasPermission(Permission.BAN_MEMBERS)) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Недостаточно прав", "У бота нет прав на бан пользователей")
).queue();
return;
}
// Получение упомянутого пользователя
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Укажите пользователя через @")
).queue();
return;
}
Member target = mentioned.get(0);
// Нельзя забанить самого себя
if (target.getId().equals(context.getAuthor().getId())) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Вы не можете забанить самого себя")
).queue();
return;
}
// Нельзя забанить бота
if (target.getUser().isBot() && target.getId().equals(context.getGuild().getSelfMember().getId())) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Я не могу забанить самого себя")
).queue();
return;
}
// Проверка иерархии ролей
if (!context.getAuthor().canInteract(target)) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Недостаточно прав", "Вы не можете забанить этого пользователя (роль выше вашей)")
).queue();
return;
}
if (!context.getGuild().getSelfMember().canInteract(target)) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Недостаточно прав", "Я не могу забанить этого пользователя (роль выше моей)")
).queue();
return;
}
// Причина бана
String reason = context.getArgs().size() > 1
? String.join(" ", context.getArgs().subList(1, context.getArgs().size()))
: "Причина не указана";
// Бан
context.getGuild().ban(target, 0, java.util.concurrent.TimeUnit.DAYS)
.reason(reason)
.queue(
success -> {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.success(
"Пользователь забанен",
"**Пользователь:** " + target.getUser().getGlobalName() + "\n" +
"**Причина:** " + reason + "\n" +
"**Модератор:** " + context.getAuthor().getAsMention()
)
).queue();
},
error -> {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error(
"Ошибка",
"Не удалось забанить. Проверь права бота, иерархию ролей и что участник на сервере."
)
).queue();
}
);
}
}

View file

@ -0,0 +1,106 @@
package creator.loki.feature.moderation.commands;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedTemplates;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import java.util.concurrent.TimeUnit;
public class ClearCommand implements Command {
@Override
public String getName() {
return "clear";
}
@Override
public String getDescription() {
return "Удаляет указанное количество сообщений";
}
@Override
public String getUsage() {
return "clear <количество>";
}
@Override
public void execute(CommandContext context) {
// Проверка прав
if (context.getAuthor() == null || !context.getAuthor().hasPermission(Permission.MESSAGE_MANAGE)) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error(
"Недостаточно прав",
"У вас нет прав на управление сообщениями"
)
).queue();
return;
}
// Проверка аргументов
if (context.getArgs().isEmpty()) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error(
"Неверное количество",
"Укажите число от 1 до 100"
)
).queue();
return;
}
int amount;
try {
amount = Integer.parseInt(context.getArgs().get(0));
} catch (NumberFormatException e) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error(
"Неверное количество",
"Укажите число от 1 до 100"
)
).queue();
return;
}
if (amount < 1 || amount > 100) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error(
"Неверное количество",
"Укажите число от 1 до 100"
)
).queue();
return;
}
// Удаление сообщений
if (!(context.getChannel() instanceof TextChannel channel)) {
return;
}
channel.getHistory().retrievePast(amount + 1).queue(messages -> {
channel.deleteMessages(messages).queue(
success -> {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.success(
"Сообщения удалены",
"Удалено сообщений: " + messages.size()
)
).queue(msg -> msg.delete().queueAfter(5, TimeUnit.SECONDS));
},
error -> {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error(
"Ошибка удаления",
"Не удалось удалить сообщения: " + error.getMessage()
)
).queue();
}
);
});
}
@Override
public String getCategory() {
return "Модерация";
}
}

View file

@ -0,0 +1,121 @@
package creator.loki.feature.moderation.commands;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedTemplates;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Member;
import java.util.List;
public class KickCommand implements Command {
@Override
public String getName() {
return "kick";
}
@Override
public String getDescription() {
return "Кикает пользователя с сервера";
}
@Override
public String getUsage() {
return "kick <@user> [причина]";
}
@Override
public String getCategory() {
return "Модерация";
}
@Override
public void execute(CommandContext context) {
if (context.getGuild() == null) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Команда доступна только на сервере")
).queue();
return;
}
if (context.getAuthor() == null || !context.getAuthor().hasPermission(Permission.KICK_MEMBERS)) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Недостаточно прав", "У вас нет прав на кик пользователей")
).queue();
return;
}
if (!context.getGuild().getSelfMember().hasPermission(Permission.KICK_MEMBERS)) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Недостаточно прав", "У бота нет прав на кик пользователей")
).queue();
return;
}
List<Member> mentioned = context.getMessage().getMentions().getMembers();
if (mentioned.isEmpty()) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Укажите пользователя через @")
).queue();
return;
}
Member target = mentioned.get(0);
if (target.getId().equals(context.getAuthor().getId())) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Вы не можете кикнуть самого себя")
).queue();
return;
}
if (target.getUser().isBot() && target.getId().equals(context.getGuild().getSelfMember().getId())) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Ошибка", "Я не могу кикнуть самого себя")
).queue();
return;
}
if (!context.getAuthor().canInteract(target)) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Недостаточно прав", "Вы не можете кикнуть этого пользователя")
).queue();
return;
}
if (!context.getGuild().getSelfMember().canInteract(target)) {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error("Недостаточно прав", "Я не могу кикнуть этого пользователя")
).queue();
return;
}
String reason = context.getArgs().size() > 1
? String.join(" ", context.getArgs().subList(1, context.getArgs().size()))
: "Причина не указана";
context.getGuild().kick(target)
.reason(reason)
.queue(
success -> {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.success(
"Пользователь кикнут",
"**Пользователь:** " + target.getUser().getGlobalName() + "\n" +
"**Причина:** " + reason + "\n" +
"**Модератор:** " + context.getAuthor().getAsMention()
)
).queue();
},
error -> {
context.getChannel().sendMessageEmbeds(
EmbedTemplates.error(
"Ошибка",
"Не удалось кикнуть. Проверь права бота (**Kick Members**), иерархию ролей и что участник не выше бота."
)
).queue();
}
);
}
}