feat: economy system with minigames, bank, rob, jobs, fishing, blackjack, slots
+ economy domain (EconomyAccount, Job, SlotCombination, EconomyTransaction) + EconomyRepository + SqlEconomyRepository + DB schema + EconomyService (transfer, daily, deposit/withdraw, stat upgrades) + FishingService (fish grid with difficulty levels, speed bonus) + BlackjackService (dealer stands on 16, ties lose) + SlotService (weighted combos up to x50) + JobService (6 jobs with level reqs and efficiency stat) + 11 commands: balance, baltop, pay, daily, bank, rob, fish, blackjack, slot, job, work + Tests for SqlEconomyRepository + EconomyModule + EconomyCommandRegistrar wired into BotModule
This commit is contained in:
parent
fcb5cd7207
commit
fe121c8546
270 changed files with 18632 additions and 11656 deletions
|
|
@ -1,50 +1,50 @@
|
|||
package creator.loki;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import creator.loki.core.BotInitializer;
|
||||
import creator.loki.di.BotModule;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class App {
|
||||
private static final Logger logger = LoggerFactory.getLogger(App.class);
|
||||
private static volatile boolean shutdownRequested = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Регистрируем shutdown hook ДО создания бота
|
||||
Thread shutdownHook = new Thread(() -> {
|
||||
if (!shutdownRequested) {
|
||||
shutdownRequested = true;
|
||||
logger.info("========================================");
|
||||
logger.info("Получен сигнал завершения");
|
||||
logger.info("Выполняется graceful shutdown...");
|
||||
logger.info("========================================");
|
||||
}
|
||||
}, "ShutdownHook");
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
|
||||
try {
|
||||
logger.info("Запуск Discord бота");
|
||||
|
||||
// Создаём Guice Injector с модулем конфигурации
|
||||
Injector injector = Guice.createInjector(new BotModule());
|
||||
|
||||
// Получаем BotInitializer с автоматически внедрёнными зависимостями
|
||||
BotInitializer initializer = injector.getInstance(BotInitializer.class);
|
||||
|
||||
// Запускаем бота
|
||||
JDA jda = initializer.start();
|
||||
|
||||
// Ждём полной загрузки
|
||||
jda.awaitReady();
|
||||
logger.info("Бот полностью готов к работе!");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Критическая ошибка при запуске бота", e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
package creator.loki.bootstrap;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import creator.loki.core.BotInitializer;
|
||||
import creator.loki.di.BotModule;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class App {
|
||||
private static final Logger logger = LoggerFactory.getLogger(App.class);
|
||||
private static volatile boolean shutdownRequested = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Регистрируем shutdown hook ДО создания бота
|
||||
Thread shutdownHook = new Thread(() -> {
|
||||
if (!shutdownRequested) {
|
||||
shutdownRequested = true;
|
||||
logger.info("========================================");
|
||||
logger.info("Получен сигнал завершения");
|
||||
logger.info("Выполняется graceful shutdown...");
|
||||
logger.info("========================================");
|
||||
}
|
||||
}, "ShutdownHook");
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
|
||||
try {
|
||||
logger.info("Запуск Discord бота");
|
||||
|
||||
// Создаём Guice Injector с модулем конфигурации
|
||||
Injector injector = Guice.createInjector(new BotModule());
|
||||
|
||||
// Получаем BotInitializer с автоматически внедрёнными зависимостями
|
||||
BotInitializer initializer = injector.getInstance(BotInitializer.class);
|
||||
|
||||
// Запускаем бота
|
||||
JDA jda = initializer.start();
|
||||
|
||||
// Ждём полной загрузки
|
||||
jda.awaitReady();
|
||||
logger.info("Бот полностью готов к работе!");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Критическая ошибка при запуске бота", e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
package creator.loki.listener;
|
||||
|
||||
import net.dv8tion.jda.api.events.session.ReadyEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ReadyListener extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ReadyListener.class);
|
||||
|
||||
@Override
|
||||
public void onReady(ReadyEvent event) {
|
||||
logger.info("Бот {} успешно запущен!", event.getJDA().getSelfUser().getName());
|
||||
logger.info("Подключен к {} серверам", event.getGuildTotalCount());
|
||||
}
|
||||
}
|
||||
package creator.loki.bootstrap;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
import net.dv8tion.jda.api.events.session.ReadyEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
public class ReadyListener extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ReadyListener.class);
|
||||
|
||||
@Override
|
||||
public void onReady(ReadyEvent event) {
|
||||
logger.info("Бот {} успешно запущен!", event.getJDA().getSelfUser().getName());
|
||||
logger.info("Подключен к {} серверам", event.getGuildTotalCount());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
package creator.loki.command;
|
||||
|
||||
public interface Command {
|
||||
String getName();
|
||||
String getDescription();
|
||||
|
||||
default String getUsage() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
default String getCategory() {
|
||||
return "Общие";
|
||||
}
|
||||
|
||||
void execute(CommandContext context);
|
||||
}
|
||||
package creator.loki.command;
|
||||
|
||||
public interface Command {
|
||||
String getName();
|
||||
String getDescription();
|
||||
|
||||
default String getUsage() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
default String getCategory() {
|
||||
return "Общие";
|
||||
}
|
||||
|
||||
void execute(CommandContext context);
|
||||
}
|
||||
|
|
|
|||
12
src/main/java/creator/loki/command/CommandCategories.java
Normal file
12
src/main/java/creator/loki/command/CommandCategories.java
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package creator.loki.command;
|
||||
|
||||
/**
|
||||
* Имена категорий для {@link Command#getCategory()} (справка, селектор help).
|
||||
*/
|
||||
public final class CommandCategories {
|
||||
|
||||
/** Команды с GIF (nekos.life): hug, slap, poke и т.п. */
|
||||
public static final String GIF_INTERACTIVE = "GIF-интерактив";
|
||||
|
||||
private CommandCategories() {}
|
||||
}
|
||||
|
|
@ -1,44 +1,44 @@
|
|||
package creator.loki.command;
|
||||
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CommandContext {
|
||||
private final Message message;
|
||||
private final List<String> args;
|
||||
private final MessageChannel channel;
|
||||
private final Member author;
|
||||
private final Guild guild;
|
||||
|
||||
public CommandContext(Message message, List<String> args, MessageChannel channel, Member author, Guild guild) {
|
||||
this.message = message;
|
||||
this.args = args;
|
||||
this.channel = channel;
|
||||
this.author = author;
|
||||
this.guild = guild;
|
||||
}
|
||||
|
||||
public Message getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public List<String> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public MessageChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public Member getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public Guild getGuild() {
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
package creator.loki.command;
|
||||
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Message;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CommandContext {
|
||||
private final Message message;
|
||||
private final List<String> args;
|
||||
private final MessageChannel channel;
|
||||
private final Member author;
|
||||
private final Guild guild;
|
||||
|
||||
public CommandContext(Message message, List<String> args, MessageChannel channel, Member author, Guild guild) {
|
||||
this.message = message;
|
||||
this.args = args;
|
||||
this.channel = channel;
|
||||
this.author = author;
|
||||
this.guild = guild;
|
||||
}
|
||||
|
||||
public Message getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public List<String> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public MessageChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public Member getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public Guild getGuild() {
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
package creator.loki.command;
|
||||
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
|
||||
/**
|
||||
* Интерфейс для slash команд
|
||||
*/
|
||||
public interface SlashCommand {
|
||||
/**
|
||||
* Выполняет slash команду
|
||||
*/
|
||||
void execute(SlashCommandInteractionEvent event);
|
||||
|
||||
/**
|
||||
* Возвращает данные команды для регистрации в Discord
|
||||
*/
|
||||
CommandData getCommandData();
|
||||
|
||||
/**
|
||||
* Название команды
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Описание команды
|
||||
*/
|
||||
String getDescription();
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
package creator.loki.command.impl.moderation;
|
||||
|
||||
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().getAsTag() + "\n" +
|
||||
"**Причина:** " + reason + "\n" +
|
||||
"**Модератор:** " + context.getAuthor().getAsMention()
|
||||
)
|
||||
).queue();
|
||||
},
|
||||
error -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось забанить: " + error.getMessage())
|
||||
).queue();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
package creator.loki.command.impl.moderation;
|
||||
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextChannel channel = (TextChannel) context.getChannel();
|
||||
|
||||
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 "Модерация";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
package creator.loki.command.impl.moderation;
|
||||
|
||||
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().getAsTag() + "\n" +
|
||||
"**Причина:** " + reason + "\n" +
|
||||
"**Модератор:** " + context.getAuthor().getAsMention()
|
||||
)
|
||||
).queue();
|
||||
},
|
||||
error -> {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось кикнуть: " + error.getMessage())
|
||||
).queue();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
package creator.loki.command.impl.setup;
|
||||
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Команда настройки логирования
|
||||
*/
|
||||
public class LogSetupCommand extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LogSetupCommand.class);
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
public LogSetupCommand(ConfigRepository configRepo) {
|
||||
this.configRepo = configRepo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
|
||||
if (!"log-setup".equals(event.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Member member = event.getMember();
|
||||
if (member == null || !member.hasPermission(Permission.ADMINISTRATOR)) {
|
||||
event.reply("❌ У вас нет прав для использования этой команды!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String subcommand = event.getSubcommandName();
|
||||
if (subcommand == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case "moderation" -> handleModeration(event);
|
||||
case "levelup" -> handleLevelUp(event);
|
||||
case "info" -> handleInfo(event);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleModeration(SlashCommandInteractionEvent event) {
|
||||
TextChannel channel = event.getOption("channel").getAsChannel().asTextChannel();
|
||||
|
||||
configRepo.set("moderation.log_channel_id", channel.getId());
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Канал логов модерации установлен")
|
||||
.setDescription("Логи модерации будут отправляться в " + channel.getAsMention())
|
||||
.addField("Что логируется",
|
||||
"• Удаление сообщений\n" +
|
||||
"• Баны/разбаны\n" +
|
||||
"• Кики\n" +
|
||||
"• Муты/размуты\n" +
|
||||
"• Отключение микрофона/звука", false)
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен канал логов модерации: {} ({})", channel.getName(), channel.getId());
|
||||
}
|
||||
|
||||
private void handleLevelUp(SlashCommandInteractionEvent event) {
|
||||
TextChannel channel = event.getOption("channel").getAsChannel().asTextChannel();
|
||||
|
||||
configRepo.set("levelup.notification_channel_id", channel.getId());
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Канал уведомлений о level up установлен")
|
||||
.setDescription("Уведомления о повышении уровня будут отправляться в " + channel.getAsMention())
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен канал уведомлений level up: {} ({})", channel.getName(), channel.getId());
|
||||
}
|
||||
|
||||
private void handleInfo(SlashCommandInteractionEvent event) {
|
||||
String moderationChannelId = configRepo.get("moderation.log_channel_id");
|
||||
String levelupChannelId = configRepo.get("levelup.notification_channel_id");
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("📋 Конфигурация логирования")
|
||||
.setColor(Color.BLUE);
|
||||
|
||||
if (moderationChannelId != null) {
|
||||
embed.addField("Логи модерации", "<#" + moderationChannelId + ">", false);
|
||||
} else {
|
||||
embed.addField("Логи модерации", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
if (levelupChannelId != null) {
|
||||
embed.addField("Уведомления level up", "<#" + levelupChannelId + ">", false);
|
||||
} else {
|
||||
embed.addField("Уведомления level up", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
event.replyEmbeds(embed.build()).setEphemeral(true).queue();
|
||||
}
|
||||
|
||||
public static void register(net.dv8tion.jda.api.JDA jda) {
|
||||
jda.updateCommands().addCommands(
|
||||
Commands.slash("log-setup", "Настройка логирования")
|
||||
.addSubcommands(
|
||||
new SubcommandData("moderation", "Установить канал для логов модерации")
|
||||
.addOption(OptionType.CHANNEL, "channel", "Текстовый канал", true),
|
||||
new SubcommandData("levelup", "Установить канал для уведомлений о level up")
|
||||
.addOption(OptionType.CHANNEL, "channel", "Текстовый канал", true),
|
||||
new SubcommandData("info", "Показать текущую конфигурацию")
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.feature.level.domain.LevelProgress;
|
||||
import creator.loki.feature.level.domain.UserStats;
|
||||
import creator.loki.level.LevelSystem;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Команда для просмотра уровня и статистики
|
||||
*/
|
||||
public class RankCommand implements Command {
|
||||
private final LevelSystem levelSystem;
|
||||
|
||||
public RankCommand(LevelSystem levelSystem) {
|
||||
this.levelSystem = levelSystem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rank";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Посмотреть свой уровень и статистику";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "rank [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Статистика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
Member target;
|
||||
|
||||
if (!context.getMessage().getMentions().getMembers().isEmpty()) {
|
||||
target = context.getMessage().getMentions().getMembers().get(0);
|
||||
} else {
|
||||
target = context.getAuthor();
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
context.getChannel().sendMessage("❌ Пользователь не найден!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String userId = target.getId();
|
||||
UserStats stats = levelSystem.getUserStats(userId);
|
||||
LevelProgress progress = levelSystem.getProgress(userId);
|
||||
|
||||
// Прогресс-бар
|
||||
int barLength = 20;
|
||||
int filled = (int) (progress.getPercentage() / 100.0 * barLength);
|
||||
StringBuilder progressBar = new StringBuilder();
|
||||
for (int i = 0; i < barLength; i++) {
|
||||
progressBar.append(i < filled ? "█" : "░");
|
||||
}
|
||||
|
||||
long hours = stats.getVoiceMinutes() / 60;
|
||||
long minutes = stats.getVoiceMinutes() % 60;
|
||||
|
||||
var embed = EmbedFactory.create()
|
||||
.setTitle("📊 Статистика " + target.getEffectiveName())
|
||||
.setThumbnail(target.getEffectiveAvatarUrl())
|
||||
.addField("🎯 Уровень", "**" + stats.getLevel() + "**", true)
|
||||
.addField("⭐ Всего XP", "**" + stats.getTotalXP() + "**", true)
|
||||
.addField("📈 Прогресс",
|
||||
progressBar.toString() + " **" + String.format("%.1f", progress.getPercentage()) + "%**\n" +
|
||||
progress.getCurrentLevelXP() + " / " + progress.getXpForNextLevel() + " XP до следующего уровня",
|
||||
false)
|
||||
.addField("💬 Сообщений", "**" + stats.getMessageCount() + "**", true)
|
||||
.addField("🎤 Время в войсе", "**" + hours + "ч " + minutes + "м**", true)
|
||||
.setColor(new Color(46, 204, 113))
|
||||
.build();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(embed).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
package creator.loki.command.impl.stats;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class StatsCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "stats";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Статистика сервера";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "stats";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Статистика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessage("Команда доступна только на сервере").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Guild guild = context.getGuild();
|
||||
|
||||
// Подсчёт статистики
|
||||
int totalMembers = guild.getMemberCount();
|
||||
long onlineMembers = guild.getMembers().stream()
|
||||
.filter(m -> m.getOnlineStatus() == OnlineStatus.ONLINE)
|
||||
.count();
|
||||
long inVoice = guild.getMembers().stream()
|
||||
.filter(m -> m.getVoiceState() != null && m.getVoiceState().inAudioChannel())
|
||||
.count();
|
||||
int textChannels = guild.getTextChannels().size();
|
||||
int voiceChannels = guild.getVoiceChannels().size();
|
||||
int roles = guild.getRoles().size();
|
||||
int bots = (int) guild.getMembers().stream()
|
||||
.filter(m -> m.getUser().isBot())
|
||||
.count();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("📊 Статистика сервера")
|
||||
.setThumbnail(guild.getIconUrl())
|
||||
.addField("👥 Участников", String.valueOf(totalMembers), true)
|
||||
.addField("🟢 Онлайн", String.valueOf(onlineMembers), true)
|
||||
.addField("🔊 В войсе", String.valueOf(inVoice), true)
|
||||
.addField("💬 Текстовых каналов", String.valueOf(textChannels), true)
|
||||
.addField("🎤 Голосовых каналов", String.valueOf(voiceChannels), true)
|
||||
.addField("🎭 Ролей", String.valueOf(roles), true)
|
||||
.addField("🤖 Ботов", String.valueOf(bots), true)
|
||||
.addField("👤 Людей", String.valueOf(totalMembers - bots), true)
|
||||
.setColor(new Color(52, 152, 219))
|
||||
.setFooter("Сервер: " + guild.getName())
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.core.registry.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;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HelpCommand implements Command {
|
||||
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<>();
|
||||
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) {
|
||||
this.registry = registry;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "help";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает список всех команд";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var commands = registry.getAllCommands();
|
||||
Map<String, List<Command>> commandsByCategory = commands.stream()
|
||||
.collect(Collectors.groupingBy(Command::getCategory));
|
||||
|
||||
// Главная страница
|
||||
EmbedFactory embed = createMainPage(context, commandsByCategory);
|
||||
|
||||
// Создаём селектор категорий
|
||||
StringSelectMenu.Builder menuBuilder = StringSelectMenu.create("help:category")
|
||||
.setPlaceholder("📂 Выберите категорию");
|
||||
|
||||
boolean isAdmin = context.getAuthor() != null &&
|
||||
context.getAuthor().hasPermission(Permission.ADMINISTRATOR);
|
||||
|
||||
commandsByCategory.keySet().stream()
|
||||
.sorted()
|
||||
.forEach(category -> {
|
||||
// Скрываем категорию "Настройка" для не-админов
|
||||
if (category.equals("Настройка") && !isAdmin) {
|
||||
return;
|
||||
}
|
||||
|
||||
String emoji = CATEGORY_EMOJIS.getOrDefault(category, "📌");
|
||||
String description = CATEGORY_DESCRIPTIONS.getOrDefault(category, "Команды категории");
|
||||
int count = commandsByCategory.get(category).size();
|
||||
|
||||
menuBuilder.addOption(
|
||||
category,
|
||||
"category:" + category,
|
||||
description + " (" + count + " команд)",
|
||||
net.dv8tion.jda.api.entities.emoji.Emoji.fromUnicode(emoji)
|
||||
);
|
||||
});
|
||||
|
||||
context.getChannel()
|
||||
.sendMessageEmbeds(embed.build())
|
||||
.addActionRow(menuBuilder.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 "Утилиты";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package creator.loki.command.impl.utility;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.profile.ProfileData;
|
||||
import creator.loki.profile.ProfileGenerator;
|
||||
import creator.loki.profile.ProfileService;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.utils.FileUpload;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Команда для отображения профиля пользователя
|
||||
*/
|
||||
public class ProfileCommand implements Command {
|
||||
private final ProfileService profileService;
|
||||
private final ProfileGenerator profileGenerator;
|
||||
|
||||
public ProfileCommand(JsonDataStore dataStore) {
|
||||
this.profileService = new ProfileService(dataStore);
|
||||
this.profileGenerator = new ProfileGenerator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "profile";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показать профиль пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "profile [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Общие";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessage("Команда доступна только на сервере").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Определяем целевого пользователя
|
||||
Member target;
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
target = context.getAuthor();
|
||||
} else {
|
||||
target = mentioned.get(0);
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
context.getChannel().sendMessage("Пользователь не найден").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Отправляем сообщение о генерации
|
||||
context.getChannel().sendMessage("🎨 Генерирую профиль...").queue(loadingMsg -> {
|
||||
try {
|
||||
// Получаем данные профиля
|
||||
ProfileData profileData = profileService.getProfileData(target, context.getGuild());
|
||||
|
||||
// Генерируем изображение
|
||||
byte[] imageBytes = profileGenerator.generateProfile(profileData);
|
||||
|
||||
// Отправляем изображение
|
||||
loadingMsg.delete().queue();
|
||||
context.getChannel().sendFiles(
|
||||
FileUpload.fromData(imageBytes, "profile.png")
|
||||
).queue();
|
||||
|
||||
} catch (Exception e) {
|
||||
loadingMsg.editMessage("❌ Ошибка генерации профиля: " + e.getMessage()).queue();
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
package creator.loki.command.impl.utility.system;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class MetricsCommand implements Command {
|
||||
private final MetricsCollector metrics;
|
||||
|
||||
public MetricsCommand(MetricsCollector metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext ctx) {
|
||||
// Только администраторы могут видеть метрики
|
||||
if (ctx.getAuthor() == null || !ctx.getAuthor().hasPermission(Permission.ADMINISTRATOR)) {
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("❌ Недостаточно прав")
|
||||
.setDescription("Эта команда доступна только администраторам.")
|
||||
.setColor(Color.RED)
|
||||
.build()
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String stats = metrics.getFullStats();
|
||||
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("📊 Метрики бота")
|
||||
.setDescription("```\n" + stats + "\n```")
|
||||
.setColor(new Color(88, 101, 242))
|
||||
.setFooter("Используй !metrics reset для сброса")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "metrics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает метрики и статистику бота (только для администраторов)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "metrics";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Система";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,26 @@
|
|||
package creator.loki.config;
|
||||
|
||||
public class BotConfig {
|
||||
private final String token;
|
||||
private final String prefix;
|
||||
private final String activity;
|
||||
|
||||
public BotConfig(String token, String prefix, String activity) {
|
||||
this.token = token;
|
||||
this.prefix = prefix;
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public String getActivity() {
|
||||
return activity;
|
||||
}
|
||||
}
|
||||
package creator.loki.config;
|
||||
|
||||
public class BotConfig {
|
||||
private final String token;
|
||||
private final String prefix;
|
||||
private final String activity;
|
||||
private final int healthHttpPort;
|
||||
private final String superAdminId;
|
||||
private final String dbPath;
|
||||
|
||||
public BotConfig(String token, String prefix, String activity, int healthHttpPort, String superAdminId, String dbPath) {
|
||||
this.token = token;
|
||||
this.prefix = prefix;
|
||||
this.activity = activity;
|
||||
this.healthHttpPort = healthHttpPort;
|
||||
this.superAdminId = superAdminId;
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
|
||||
public String getToken() { return token; }
|
||||
public String getPrefix() { return prefix; }
|
||||
public String getActivity() { return activity; }
|
||||
public int getHealthHttpPort() { return healthHttpPort; }
|
||||
public String getSuperAdminId() { return superAdminId; }
|
||||
public String getDbPath() { return dbPath; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,67 @@
|
|||
package creator.loki.config;
|
||||
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class ConfigLoader {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
|
||||
|
||||
private ConfigLoader() {
|
||||
// Запрет создания экземпляров
|
||||
}
|
||||
|
||||
public static BotConfig load() {
|
||||
Dotenv dotenv = Dotenv.configure()
|
||||
.ignoreIfMissing()
|
||||
.load();
|
||||
|
||||
String token = dotenv.get("DISCORD_BOT_TOKEN");
|
||||
if (token == null || token.isBlank()) {
|
||||
throw new IllegalStateException(
|
||||
"Токен бота не найден! Установите переменную окружения DISCORD_BOT_TOKEN или создайте .env файл"
|
||||
);
|
||||
}
|
||||
|
||||
String prefix = dotenv.get("BOT_PREFIX");
|
||||
if (prefix == null || prefix.isBlank()) {
|
||||
prefix = "!";
|
||||
}
|
||||
|
||||
String activity = dotenv.get("BOT_ACTIVITY");
|
||||
if (activity == null || activity.isBlank()) {
|
||||
activity = "Используй !help";
|
||||
}
|
||||
|
||||
logger.info("Конфигурация загружена успешно");
|
||||
return new BotConfig(token, prefix, activity);
|
||||
}
|
||||
}
|
||||
package creator.loki.config;
|
||||
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class ConfigLoader {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
|
||||
|
||||
private ConfigLoader() {
|
||||
}
|
||||
|
||||
private static int parsePort(String raw, String key) {
|
||||
if (raw == null || raw.isBlank()) return 0;
|
||||
try {
|
||||
int port = Integer.parseInt(raw.trim());
|
||||
if (port < 0 || port > 65535) {
|
||||
throw new IllegalArgumentException("порт вне диапазона 0..65535");
|
||||
}
|
||||
return port;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalStateException(
|
||||
"Некорректный " + key + ": \"" + raw + "\". Ожидается число 0..65535.",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static BotConfig load() {
|
||||
Dotenv dotenv = Dotenv.configure()
|
||||
.ignoreIfMissing()
|
||||
.load();
|
||||
|
||||
String token = dotenv.get("DISCORD_BOT_TOKEN");
|
||||
if (token == null || token.isBlank()) {
|
||||
throw new IllegalStateException(
|
||||
"Токен бота не найден! Установите переменную окружения DISCORD_BOT_TOKEN или создайте .env файл"
|
||||
);
|
||||
}
|
||||
|
||||
String prefix = dotenv.get("BOT_PREFIX");
|
||||
if (prefix == null || prefix.isBlank()) {
|
||||
prefix = "!";
|
||||
}
|
||||
|
||||
String activity = dotenv.get("BOT_ACTIVITY");
|
||||
if (activity == null || activity.isBlank()) {
|
||||
activity = "Используй !help";
|
||||
}
|
||||
|
||||
int healthHttpPort = parsePort(dotenv.get("HEALTH_HTTP_PORT"), "HEALTH_HTTP_PORT");
|
||||
|
||||
String superAdminId = dotenv.get("SUPER_ADMIN_ID");
|
||||
if (superAdminId == null || superAdminId.isBlank()) {
|
||||
superAdminId = "1414975197269987471";
|
||||
logger.warn("SUPER_ADMIN_ID не задан, используется значение по умолчанию");
|
||||
}
|
||||
|
||||
String dbPath = dotenv.get("DB_PATH");
|
||||
if (dbPath == null || dbPath.isBlank()) {
|
||||
dbPath = "data/bot.db";
|
||||
}
|
||||
|
||||
logger.info("Конфигурация загружена успешно");
|
||||
return new BotConfig(token, prefix, activity, healthHttpPort, superAdminId, dbPath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,11 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import creator.loki.command.impl.setup.LogSetupCommand;
|
||||
import creator.loki.command.impl.stats.leaderboard.LeaderboardButtonListener;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.core.EventDispatcher;
|
||||
import creator.loki.core.registry.CommandRegistry;
|
||||
import creator.loki.core.registry.PrefixCommandRegistrar;
|
||||
import creator.loki.core.registry.SlashCommandRegistrar;
|
||||
import creator.loki.core.registry.SlashCommandRegistry;
|
||||
import creator.loki.feature.counter.CounterManager;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.feature.counter.CounterSetupCommand;
|
||||
import creator.loki.feature.counter.MemberCounterListener;
|
||||
import creator.loki.feature.level.LevelUpNotifier;
|
||||
import creator.loki.feature.level.service.LevelService;
|
||||
import creator.loki.feature.voice.VoiceButtonHandler;
|
||||
import creator.loki.feature.voice.VoiceChannelListener;
|
||||
import creator.loki.feature.voice.VoiceChannelManager;
|
||||
import creator.loki.feature.voice.VoiceSetupCommand;
|
||||
import creator.loki.feature.level.VoiceTracker;
|
||||
import creator.loki.level.LevelSystem;
|
||||
import creator.loki.listener.*;
|
||||
import creator.loki.listener.interaction.SlashCommandListener;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import creator.loki.profile.ProfileService;
|
||||
import creator.loki.feature.role.CustomRoleManager;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.core.security.RateLimiter;
|
||||
import creator.loki.feature.voice.VoiceChannelManager;
|
||||
import creator.loki.feature.moderation.MuteScheduler;
|
||||
import creator.loki.feature.metrics.HealthHttpServer;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
|
|
@ -44,118 +18,52 @@ public class BotInitializer {
|
|||
private static final Logger logger = LoggerFactory.getLogger(BotInitializer.class);
|
||||
|
||||
private final BotConfig config;
|
||||
private final CommandRegistry registry;
|
||||
private final SlashCommandRegistry slashRegistry;
|
||||
private final EventDispatcher dispatcher;
|
||||
private final JsonDataStore dataStore;
|
||||
private final CustomRoleManager roleManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
private final RateLimiter rateLimiter;
|
||||
private final MetricsCollector metrics;
|
||||
private final LevelSystem levelSystem;
|
||||
private final LevelService levelService;
|
||||
private final VoiceTracker voiceTracker;
|
||||
private final ProfileService profileService;
|
||||
private final creator.loki.feature.profile.ProfileService newProfileService;
|
||||
private final VoiceChannelManager voiceChannelManager;
|
||||
private final CounterManager counterManager;
|
||||
private final VoiceChannelListener voiceChannelListener;
|
||||
private final PrefixCommandRegistrar registrar;
|
||||
private final ListenerAggregator listenerAggregator;
|
||||
private final MuteScheduler muteScheduler;
|
||||
private final MemberCounterListener memberCounterListener;
|
||||
private final Database database;
|
||||
private final ConfigRepository configRepo;
|
||||
private final VoiceSetupCommand voiceSetupCommand;
|
||||
private final CounterSetupCommand counterSetupCommand;
|
||||
private final LogSetupCommand logSetupCommand;
|
||||
private final VoiceButtonHandler voiceButtonHandler;
|
||||
private final ModerationLogger moderationLogger;
|
||||
private final LevelUpNotifier levelUpNotifier;
|
||||
private final HelpMenuHandler helpMenuHandler;
|
||||
private final HealthHttpServer healthHttpServer;
|
||||
private final ShutdownManager shutdownManager;
|
||||
|
||||
@Inject
|
||||
public BotInitializer(
|
||||
BotConfig config,
|
||||
CommandRegistry registry,
|
||||
SlashCommandRegistry slashRegistry,
|
||||
EventDispatcher dispatcher,
|
||||
JsonDataStore dataStore,
|
||||
CustomRoleManager roleManager,
|
||||
CooldownManager cooldownManager,
|
||||
RateLimiter rateLimiter,
|
||||
MetricsCollector metrics,
|
||||
LevelSystem levelSystem,
|
||||
LevelService levelService,
|
||||
VoiceTracker voiceTracker,
|
||||
ProfileService profileService,
|
||||
creator.loki.feature.profile.ProfileService newProfileService,
|
||||
VoiceChannelManager voiceChannelManager,
|
||||
CounterManager counterManager,
|
||||
Database database,
|
||||
ConfigRepository configRepo,
|
||||
LevelUpNotifier levelUpNotifier
|
||||
PrefixCommandRegistrar registrar,
|
||||
ListenerAggregator listenerAggregator,
|
||||
MuteScheduler muteScheduler,
|
||||
MemberCounterListener memberCounterListener,
|
||||
HealthHttpServer healthHttpServer,
|
||||
ShutdownManager shutdownManager
|
||||
) {
|
||||
this.config = config;
|
||||
this.registry = registry;
|
||||
this.slashRegistry = slashRegistry;
|
||||
this.dispatcher = dispatcher;
|
||||
this.dataStore = dataStore;
|
||||
this.roleManager = roleManager;
|
||||
this.cooldownManager = cooldownManager;
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.metrics = metrics;
|
||||
this.levelSystem = levelSystem;
|
||||
this.levelService = levelService;
|
||||
this.voiceTracker = voiceTracker;
|
||||
this.profileService = profileService;
|
||||
this.newProfileService = newProfileService;
|
||||
this.voiceChannelManager = voiceChannelManager;
|
||||
this.counterManager = counterManager;
|
||||
this.database = database;
|
||||
this.configRepo = configRepo;
|
||||
this.levelUpNotifier = levelUpNotifier;
|
||||
this.voiceChannelListener = new VoiceChannelListener(voiceChannelManager);
|
||||
this.memberCounterListener = new MemberCounterListener(counterManager);
|
||||
this.voiceSetupCommand = new VoiceSetupCommand(configRepo);
|
||||
this.counterSetupCommand = new CounterSetupCommand(configRepo);
|
||||
this.logSetupCommand = new LogSetupCommand(configRepo);
|
||||
this.voiceButtonHandler = new VoiceButtonHandler(voiceChannelManager);
|
||||
this.moderationLogger = new ModerationLogger(configRepo);
|
||||
this.helpMenuHandler = new HelpMenuHandler(registry, config);
|
||||
this.registrar = registrar;
|
||||
this.listenerAggregator = listenerAggregator;
|
||||
this.muteScheduler = muteScheduler;
|
||||
this.memberCounterListener = memberCounterListener;
|
||||
this.healthHttpServer = healthHttpServer;
|
||||
this.shutdownManager = shutdownManager;
|
||||
}
|
||||
|
||||
public JDA start() {
|
||||
logger.info("Инициализация бота...");
|
||||
|
||||
// Регистрируем команды через специализированные регистраторы
|
||||
registerCommands();
|
||||
registerSlashCommands();
|
||||
|
||||
// Создаём JDA
|
||||
registrar.registerAll();
|
||||
JDA jda = createJDA();
|
||||
|
||||
// Регистрируем slash команды в Discord
|
||||
registerSlashCommandsInDiscord(jda);
|
||||
|
||||
// Настраиваем graceful shutdown
|
||||
setupShutdownHook(jda);
|
||||
|
||||
try {
|
||||
jda.awaitReady();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Бот прерван во время ожидания готовности", e);
|
||||
}
|
||||
jda.updateCommands().queue();
|
||||
muteScheduler.restorePending(jda);
|
||||
for (var guild : jda.getGuilds()) {
|
||||
memberCounterListener.startScheduler(guild);
|
||||
}
|
||||
healthHttpServer.start();
|
||||
registerShutdownHook(jda);
|
||||
return jda;
|
||||
}
|
||||
|
||||
private void registerCommands() {
|
||||
PrefixCommandRegistrar registrar = new PrefixCommandRegistrar(
|
||||
registry, config, dataStore, roleManager, cooldownManager,
|
||||
levelSystem, levelService, newProfileService, metrics
|
||||
);
|
||||
registrar.registerAll();
|
||||
}
|
||||
|
||||
private void registerSlashCommands() {
|
||||
SlashCommandRegistrar registrar = new SlashCommandRegistrar(
|
||||
slashRegistry, dataStore, metrics
|
||||
);
|
||||
registrar.registerAll();
|
||||
}
|
||||
|
||||
private JDA createJDA() {
|
||||
return JDABuilder.createDefault(config.getToken())
|
||||
.setStatus(OnlineStatus.ONLINE)
|
||||
|
|
@ -166,85 +74,12 @@ public class BotInitializer {
|
|||
GatewayIntent.GUILD_MEMBERS,
|
||||
GatewayIntent.GUILD_VOICE_STATES
|
||||
)
|
||||
.addEventListeners(
|
||||
new ReadyListener(),
|
||||
new MessageListener(dispatcher, dataStore, levelSystem),
|
||||
new SlashCommandListener(slashRegistry, rateLimiter, metrics),
|
||||
voiceTracker,
|
||||
voiceChannelListener,
|
||||
memberCounterListener,
|
||||
voiceSetupCommand,
|
||||
counterSetupCommand,
|
||||
logSetupCommand,
|
||||
voiceButtonHandler,
|
||||
moderationLogger,
|
||||
helpMenuHandler,
|
||||
new LeaderboardButtonListener(levelSystem)
|
||||
)
|
||||
.addEventListeners(listenerAggregator.getAll())
|
||||
.build();
|
||||
}
|
||||
|
||||
private void registerSlashCommandsInDiscord(JDA jda) {
|
||||
try {
|
||||
jda.awaitReady();
|
||||
slashRegistry.registerCommandsInDiscord(jda);
|
||||
|
||||
// Регистрируем setup команды
|
||||
VoiceSetupCommand.register(jda);
|
||||
CounterSetupCommand.register(jda);
|
||||
LogSetupCommand.register(jda);
|
||||
|
||||
// Запускаем планировщик счетчика участников
|
||||
if (!jda.getGuilds().isEmpty()) {
|
||||
memberCounterListener.startScheduler(jda.getGuilds().get(0));
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Прервано ожидание готовности JDA", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupShutdownHook(JDA jda) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
logger.info("========================================");
|
||||
logger.info("Получен сигнал завершения работы бота...");
|
||||
logger.info("========================================");
|
||||
|
||||
shutdownComponents(jda);
|
||||
|
||||
logger.info("========================================");
|
||||
logger.info("Бот успешно завершён!");
|
||||
logger.info("========================================");
|
||||
}, "ShutdownHook"));
|
||||
}
|
||||
|
||||
private void shutdownComponents(JDA jda) {
|
||||
try {
|
||||
logger.info("Сохранение данных...");
|
||||
dataStore.flush();
|
||||
|
||||
logger.info("Остановка менеджеров...");
|
||||
cooldownManager.shutdown();
|
||||
rateLimiter.shutdown();
|
||||
voiceTracker.shutdown();
|
||||
memberCounterListener.shutdown();
|
||||
dispatcher.shutdown();
|
||||
|
||||
logger.info("Завершение JsonDataStore...");
|
||||
dataStore.shutdown();
|
||||
|
||||
logger.info("Закрытие базы данных...");
|
||||
database.close();
|
||||
|
||||
logger.info("Отключение JDA...");
|
||||
jda.shutdown();
|
||||
|
||||
if (!jda.awaitShutdown(java.time.Duration.ofSeconds(10))) {
|
||||
logger.warn("JDA не завершился за 10 секунд, принудительная остановка");
|
||||
jda.shutdownNow();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при завершении работы бота", e);
|
||||
}
|
||||
private void registerShutdownHook(JDA jda) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(
|
||||
() -> shutdownManager.shutdown(jda), "ShutdownHook"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,130 +1,130 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.core.registry.CommandRegistry;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.exception.CommandExceptionHandler;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import creator.loki.core.security.RateLimiter;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class EventDispatcher {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EventDispatcher.class);
|
||||
private final BotConfig config;
|
||||
private final CommandRegistry registry;
|
||||
private final ExecutorService executorService;
|
||||
private final RateLimiter rateLimiter;
|
||||
private final MetricsCollector metrics;
|
||||
|
||||
public EventDispatcher(BotConfig config, CommandRegistry registry, RateLimiter rateLimiter, MetricsCollector metrics) {
|
||||
this.config = config;
|
||||
this.registry = registry;
|
||||
this.executorService = Executors.newCachedThreadPool();
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public void handleMessage(MessageReceivedEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getAuthor().isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String content = event.getMessage().getContentRaw();
|
||||
if (!content.startsWith(config.getPrefix())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Парсим команду и аргументы
|
||||
String[] parts = content.substring(config.getPrefix().length()).trim().split("\\s+");
|
||||
if (parts.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String commandName = parts[0].toLowerCase();
|
||||
List<String> args = parts.length > 1
|
||||
? Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length))
|
||||
: List.of();
|
||||
|
||||
Command command = registry.getCommand(commandName);
|
||||
if (command == null) {
|
||||
event.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Команда не найдена",
|
||||
"Команда `" + commandName + "` не существует. Используй `" + config.getPrefix() + "help`"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем глобальный rate limit
|
||||
String userId = event.getAuthor().getId();
|
||||
String guildId = event.getGuild() != null ? event.getGuild().getId() : "DM";
|
||||
|
||||
if (!rateLimiter.allow(userId, guildId)) {
|
||||
long waitTime = rateLimiter.getUserWaitTime(userId);
|
||||
event.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"⏱️ Слишком быстро!",
|
||||
"Ты отправляешь команды слишком часто. Подожди " + waitTime + " секунд."
|
||||
)
|
||||
).queue();
|
||||
logger.warn("Rate limit для пользователя {} в гильдии {}", userId, guildId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Выполняем команду асинхронно
|
||||
executorService.submit(() -> {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
CommandContext context = new CommandContext(
|
||||
event.getMessage(),
|
||||
args,
|
||||
event.getChannel(),
|
||||
event.getMember(),
|
||||
event.getGuild()
|
||||
);
|
||||
command.execute(context);
|
||||
|
||||
// Записываем метрики
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
metrics.recordCommand(commandName, executionTime);
|
||||
|
||||
logger.info("Выполнена команда: {} от {} за {} мс",
|
||||
commandName, event.getAuthor().getName(), executionTime);
|
||||
} catch (Exception e) {
|
||||
// Записываем ошибку в метрики
|
||||
metrics.recordError(commandName);
|
||||
|
||||
// Используем централизованный обработчик ошибок
|
||||
CommandExceptionHandler.handle(e, event.getChannel(), commandName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
logger.info("Завершение работы EventDispatcher...");
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
logger.warn("ExecutorService не завершился за 10 секунд, принудительная остановка");
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Прервано ожидание завершения ExecutorService", e);
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
logger.info("EventDispatcher завершён");
|
||||
}
|
||||
}
|
||||
package creator.loki.core;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.core.registry.CommandRegistry;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.core.exception.CommandExceptionHandler;
|
||||
import creator.loki.feature.metrics.MetricsCollector;
|
||||
import creator.loki.core.security.RateLimiter;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class EventDispatcher {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EventDispatcher.class);
|
||||
private final BotConfig config;
|
||||
private final CommandRegistry registry;
|
||||
private final ExecutorService executorService;
|
||||
private final RateLimiter rateLimiter;
|
||||
private final MetricsCollector metrics;
|
||||
|
||||
public EventDispatcher(BotConfig config, CommandRegistry registry, RateLimiter rateLimiter, MetricsCollector metrics) {
|
||||
this.config = config;
|
||||
this.registry = registry;
|
||||
this.executorService = Executors.newVirtualThreadPerTaskExecutor();
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public void handleMessage(MessageReceivedEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getAuthor().isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String content = event.getMessage().getContentRaw();
|
||||
if (!content.startsWith(config.getPrefix())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Парсим команду и аргументы
|
||||
String[] parts = content.substring(config.getPrefix().length()).trim().split("\\s+");
|
||||
if (parts.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String commandName = parts[0].toLowerCase();
|
||||
List<String> args = parts.length > 1
|
||||
? Arrays.asList(Arrays.copyOfRange(parts, 1, parts.length))
|
||||
: List.of();
|
||||
|
||||
Command command = registry.getCommand(commandName);
|
||||
if (command == null) {
|
||||
event.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Команда не найдена",
|
||||
"Команда `" + commandName + "` не существует. Используй `" + config.getPrefix() + "help`"
|
||||
)
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем глобальный rate limit
|
||||
String userId = event.getAuthor().getId();
|
||||
String guildId = event.getGuild() != null ? event.getGuild().getId() : "DM";
|
||||
|
||||
if (!rateLimiter.allow(userId, guildId)) {
|
||||
long waitTime = rateLimiter.getUserWaitTime(userId);
|
||||
event.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"⏱️ Слишком быстро!",
|
||||
"Ты отправляешь команды слишком часто. Подожди " + waitTime + " секунд."
|
||||
)
|
||||
).queue();
|
||||
logger.warn("Rate limit для пользователя {} в гильдии {}", userId, guildId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Выполняем команду асинхронно
|
||||
executorService.submit(() -> {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
CommandContext context = new CommandContext(
|
||||
event.getMessage(),
|
||||
args,
|
||||
event.getChannel(),
|
||||
event.getMember(),
|
||||
event.getGuild()
|
||||
);
|
||||
command.execute(context);
|
||||
|
||||
// Записываем метрики
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
metrics.recordCommand(commandName, executionTime);
|
||||
|
||||
logger.info("Выполнена команда: {} от {} за {} мс",
|
||||
commandName, event.getAuthor().getName(), executionTime);
|
||||
} catch (Exception e) {
|
||||
// Записываем ошибку в метрики
|
||||
metrics.recordError(commandName);
|
||||
|
||||
// Используем централизованный обработчик ошибок
|
||||
CommandExceptionHandler.handle(e, event.getChannel(), commandName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
logger.info("Завершение работы EventDispatcher...");
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
logger.warn("ExecutorService не завершился за 10 секунд, принудительная остановка");
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
logger.error("Прервано ожидание завершения ExecutorService", e);
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
logger.info("EventDispatcher завершён");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
54
src/main/java/creator/loki/core/ListenerAggregator.java
Normal file
54
src/main/java/creator/loki/core/ListenerAggregator.java
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.feature.level.MessageListener;
|
||||
import creator.loki.feature.moderation.ModerationLogger;
|
||||
import creator.loki.bootstrap.ReadyListener;
|
||||
import creator.loki.feature.help.HelpMenuHandler;
|
||||
import creator.loki.feature.counter.MemberCounterListener;
|
||||
import creator.loki.feature.leaderboard.LeaderboardButtonListener;
|
||||
import creator.loki.feature.level.LevelUpNotifier;
|
||||
import creator.loki.feature.level.ResetLevelsButtonHandler;
|
||||
import creator.loki.feature.level.VoiceTracker;
|
||||
import creator.loki.feature.voice.VoiceButtonHandler;
|
||||
import creator.loki.feature.voice.VoiceChannelListener;
|
||||
import creator.loki.feature.voice.VoiceModalHandler;
|
||||
import creator.loki.feature.voice.VoiceSyncListener;
|
||||
import creator.loki.feature.welcome.WelcomeListener;
|
||||
|
||||
@Singleton
|
||||
public class ListenerAggregator {
|
||||
private final Object[] listeners;
|
||||
|
||||
@Inject
|
||||
public ListenerAggregator(
|
||||
ReadyListener readyListener,
|
||||
MessageListener messageListener,
|
||||
VoiceTracker voiceTracker,
|
||||
VoiceChannelListener voiceChannelListener,
|
||||
MemberCounterListener memberCounterListener,
|
||||
VoiceButtonHandler voiceButtonHandler,
|
||||
VoiceModalHandler voiceModalHandler,
|
||||
VoiceSyncListener voiceSyncListener,
|
||||
WelcomeListener welcomeListener,
|
||||
ModerationLogger moderationLogger,
|
||||
HelpMenuHandler helpMenuHandler,
|
||||
ResetLevelsButtonHandler resetLevelsButtonHandler,
|
||||
LeaderboardButtonListener leaderboardButtonListener,
|
||||
MessageCache messageCache
|
||||
) {
|
||||
this.listeners = new Object[]{
|
||||
readyListener, messageListener, voiceTracker,
|
||||
voiceChannelListener, memberCounterListener,
|
||||
voiceButtonHandler, voiceModalHandler, voiceSyncListener,
|
||||
welcomeListener, moderationLogger, helpMenuHandler,
|
||||
resetLevelsButtonHandler, leaderboardButtonListener,
|
||||
messageCache
|
||||
};
|
||||
}
|
||||
|
||||
public Object[] getAll() {
|
||||
return listeners;
|
||||
}
|
||||
}
|
||||
32
src/main/java/creator/loki/core/MessageCache.java
Normal file
32
src/main/java/creator/loki/core/MessageCache.java
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class MessageCache extends ListenerAdapter {
|
||||
private final Cache<String, String> cache;
|
||||
|
||||
public MessageCache() {
|
||||
this.cache = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(MessageReceivedEvent event) {
|
||||
if (event.getAuthor().isBot()) return;
|
||||
String content = event.getMessage().getContentRaw();
|
||||
if (!content.isBlank()) {
|
||||
cache.put(event.getMessageId(), content);
|
||||
}
|
||||
}
|
||||
|
||||
public String getContent(String messageId) {
|
||||
return cache.getIfPresent(messageId);
|
||||
}
|
||||
}
|
||||
85
src/main/java/creator/loki/core/ShutdownManager.java
Normal file
85
src/main/java/creator/loki/core/ShutdownManager.java
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.feature.counter.MemberCounterListener;
|
||||
import creator.loki.feature.level.VoiceTracker;
|
||||
import creator.loki.feature.moderation.MuteScheduler;
|
||||
import creator.loki.feature.metrics.HealthHttpServer;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.core.security.RateLimiter;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Singleton
|
||||
public class ShutdownManager {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ShutdownManager.class);
|
||||
|
||||
private final HealthHttpServer healthHttpServer;
|
||||
private final CooldownManager cooldownManager;
|
||||
private final RateLimiter rateLimiter;
|
||||
private final VoiceTracker voiceTracker;
|
||||
private final MemberCounterListener memberCounterListener;
|
||||
private final MuteScheduler muteScheduler;
|
||||
private final EventDispatcher dispatcher;
|
||||
private final Database database;
|
||||
|
||||
@Inject
|
||||
public ShutdownManager(
|
||||
HealthHttpServer healthHttpServer,
|
||||
CooldownManager cooldownManager,
|
||||
RateLimiter rateLimiter,
|
||||
VoiceTracker voiceTracker,
|
||||
MemberCounterListener memberCounterListener,
|
||||
MuteScheduler muteScheduler,
|
||||
EventDispatcher dispatcher,
|
||||
Database database
|
||||
) {
|
||||
this.healthHttpServer = healthHttpServer;
|
||||
this.cooldownManager = cooldownManager;
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.voiceTracker = voiceTracker;
|
||||
this.memberCounterListener = memberCounterListener;
|
||||
this.muteScheduler = muteScheduler;
|
||||
this.dispatcher = dispatcher;
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public void shutdown(JDA jda) {
|
||||
logger.info("========================================");
|
||||
logger.info("Получен сигнал завершения работы бота...");
|
||||
logger.info("========================================");
|
||||
|
||||
try {
|
||||
healthHttpServer.stop();
|
||||
logger.info("Остановка менеджеров...");
|
||||
cooldownManager.shutdown();
|
||||
rateLimiter.shutdown();
|
||||
voiceTracker.shutdown();
|
||||
memberCounterListener.shutdown();
|
||||
muteScheduler.shutdown();
|
||||
dispatcher.shutdown();
|
||||
|
||||
logger.info("Закрытие базы данных...");
|
||||
database.close();
|
||||
|
||||
logger.info("Отключение JDA...");
|
||||
jda.shutdown();
|
||||
|
||||
if (!jda.awaitShutdown(Duration.ofSeconds(10))) {
|
||||
logger.warn("JDA не завершился за 10 секунд, принудительная остановка");
|
||||
jda.shutdownNow();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при завершении работы бота", e);
|
||||
}
|
||||
|
||||
logger.info("========================================");
|
||||
logger.info("Бот успешно завершён!");
|
||||
logger.info("========================================");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,23 @@
|
|||
package creator.loki.exception;
|
||||
|
||||
/**
|
||||
* Исключение для ошибок выполнения команд
|
||||
*/
|
||||
public class CommandException extends Exception {
|
||||
private final String userMessage;
|
||||
|
||||
public CommandException(String userMessage) {
|
||||
super(userMessage);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
|
||||
public CommandException(String userMessage, Throwable cause) {
|
||||
super(userMessage, cause);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
|
||||
public String getUserMessage() {
|
||||
return userMessage;
|
||||
}
|
||||
}
|
||||
package creator.loki.core.exception;
|
||||
|
||||
/**
|
||||
* Исключение для ошибок выполнения команд
|
||||
*/
|
||||
public class CommandException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String userMessage;
|
||||
|
||||
public CommandException(String userMessage) {
|
||||
super(userMessage);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
|
||||
public CommandException(String userMessage, Throwable cause) {
|
||||
super(userMessage, cause);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
|
||||
public String getUserMessage() {
|
||||
return userMessage;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +1,63 @@
|
|||
package creator.loki.exception;
|
||||
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Обработчик исключений для команд
|
||||
*/
|
||||
public class CommandExceptionHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CommandExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* Обрабатывает исключение и отправляет сообщение пользователю
|
||||
*/
|
||||
public static void handle(Throwable throwable, MessageChannel channel, String commandName) {
|
||||
logger.error("Ошибка при выполнении команды: {}", commandName, throwable);
|
||||
|
||||
String userMessage;
|
||||
if (throwable instanceof CommandException) {
|
||||
userMessage = ((CommandException) throwable).getUserMessage();
|
||||
} else {
|
||||
userMessage = "Произошла непредвиденная ошибка при выполнении команды.";
|
||||
}
|
||||
|
||||
MessageEmbed errorEmbed = EmbedFactory.create()
|
||||
.setTitle("❌ Ошибка")
|
||||
.setDescription(userMessage)
|
||||
.setColor(Color.RED)
|
||||
.addField("Команда", commandName, true)
|
||||
.setFooter("Если ошибка повторяется, обратитесь к администратору")
|
||||
.build();
|
||||
|
||||
channel.sendMessageEmbeds(errorEmbed).queue(
|
||||
success -> {},
|
||||
error -> logger.error("Не удалось отправить сообщение об ошибке", error)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает исключение с кастомным сообщением
|
||||
*/
|
||||
public static void handle(Throwable throwable, MessageChannel channel, String commandName, String customMessage) {
|
||||
logger.error("Ошибка при выполнении команды: {}", commandName, throwable);
|
||||
|
||||
MessageEmbed errorEmbed = EmbedFactory.create()
|
||||
.setTitle("❌ Ошибка")
|
||||
.setDescription(customMessage)
|
||||
.setColor(Color.RED)
|
||||
.addField("Команда", commandName, true)
|
||||
.setFooter("Если ошибка повторяется, обратитесь к администратору")
|
||||
.build();
|
||||
|
||||
channel.sendMessageEmbeds(errorEmbed).queue(
|
||||
success -> {},
|
||||
error -> logger.error("Не удалось отправить сообщение об ошибке", error)
|
||||
);
|
||||
}
|
||||
}
|
||||
package creator.loki.core.exception;
|
||||
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Обработчик исключений для команд
|
||||
*/
|
||||
public class CommandExceptionHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CommandExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* Обрабатывает исключение и отправляет сообщение пользователю
|
||||
*/
|
||||
public static void handle(Throwable throwable, MessageChannel channel, String commandName) {
|
||||
logger.error("Ошибка при выполнении команды: {}", commandName, throwable);
|
||||
|
||||
String userMessage;
|
||||
if (throwable instanceof CommandException ce) {
|
||||
userMessage = ce.getUserMessage();
|
||||
} else {
|
||||
userMessage = "Не удалось выполнить команду. Попробуйте позже или обратитесь к администратору.";
|
||||
}
|
||||
|
||||
MessageEmbed errorEmbed = EmbedFactory.create()
|
||||
.withTitle("❌ Ошибка")
|
||||
.withDescription(userMessage)
|
||||
.withColor(Color.RED)
|
||||
.addField("Команда", commandName, true)
|
||||
.withFooter("Если ошибка повторяется, обратитесь к администратору")
|
||||
.build();
|
||||
|
||||
channel.sendMessageEmbeds(errorEmbed).queue(
|
||||
success -> {},
|
||||
error -> logger.error("Не удалось отправить сообщение об ошибке", error)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает исключение с кастомным сообщением
|
||||
*/
|
||||
public static void handle(Throwable throwable, MessageChannel channel, String commandName, String customMessage) {
|
||||
logger.error("Ошибка при выполнении команды: {}", commandName, throwable);
|
||||
|
||||
MessageEmbed errorEmbed = EmbedFactory.create()
|
||||
.withTitle("❌ Ошибка")
|
||||
.withDescription(customMessage)
|
||||
.withColor(Color.RED)
|
||||
.addField("Команда", commandName, true)
|
||||
.withFooter("Если ошибка повторяется, обратитесь к администратору")
|
||||
.build();
|
||||
|
||||
channel.sendMessageEmbeds(errorEmbed).queue(
|
||||
success -> {},
|
||||
error -> logger.error("Не удалось отправить сообщение об ошибке", error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package creator.loki.core.registry;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CommandRegistrar {
|
||||
void register(CommandRegistry registry);
|
||||
}
|
||||
|
|
@ -1,32 +1,38 @@
|
|||
package creator.loki.core.registry;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CommandRegistry {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CommandRegistry.class);
|
||||
private final Map<String, Command> commands = new HashMap<>();
|
||||
|
||||
public void register(Command command) {
|
||||
commands.put(command.getName().toLowerCase(), command);
|
||||
logger.debug("Зарегистрирована команда: {}", command.getName());
|
||||
}
|
||||
|
||||
public Command getCommand(String name) {
|
||||
return commands.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public Collection<Command> getAllCommands() {
|
||||
return Collections.unmodifiableCollection(commands.values());
|
||||
}
|
||||
|
||||
public int getCommandCount() {
|
||||
return commands.size();
|
||||
}
|
||||
}
|
||||
package creator.loki.core.registry;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CommandRegistry {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CommandRegistry.class);
|
||||
private final Map<String, Command> commands = new HashMap<>();
|
||||
|
||||
public void register(Command command) {
|
||||
String key = command.getName().toLowerCase();
|
||||
Command previous = commands.put(key, command);
|
||||
if (previous != null) {
|
||||
logger.warn("Имя команды «{}» уже занято: {} заменена на {}",
|
||||
key, previous.getClass().getSimpleName(), command.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.debug("Зарегистрирована команда: {}", command.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public Command getCommand(String name) {
|
||||
return commands.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public Collection<Command> getAllCommands() {
|
||||
return Collections.unmodifiableCollection(commands.values());
|
||||
}
|
||||
|
||||
public int getCommandCount() {
|
||||
return commands.size();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,156 +1,29 @@
|
|||
package creator.loki.core.registry;
|
||||
|
||||
import creator.loki.command.impl.entertainment.games.*;
|
||||
import creator.loki.command.impl.entertainment.games.random.CoinCommand;
|
||||
import creator.loki.command.impl.entertainment.games.random.DiceCommand;
|
||||
import creator.loki.command.impl.entertainment.games.random.GlazbobaCommand;
|
||||
import creator.loki.command.impl.entertainment.games.random.MemeCommand;
|
||||
import creator.loki.command.impl.entertainment.games.random.RateCommand;
|
||||
import creator.loki.command.impl.entertainment.games.rating.BoykisserCommand;
|
||||
import creator.loki.command.impl.entertainment.games.rating.FatCommand;
|
||||
import creator.loki.command.impl.entertainment.games.rating.GayCommand;
|
||||
import creator.loki.command.impl.entertainment.games.rating.IqCommand;
|
||||
import creator.loki.command.impl.entertainment.games.rating.MustacheCommand;
|
||||
import creator.loki.command.impl.entertainment.games.rating.PpCommand;
|
||||
import creator.loki.command.impl.entertainment.interaction.*;
|
||||
import creator.loki.command.impl.entertainment.interaction.negative.PokeCommand;
|
||||
import creator.loki.command.impl.entertainment.interaction.negative.SlapCommand;
|
||||
import creator.loki.command.impl.entertainment.interaction.negative.TickleCommand;
|
||||
import creator.loki.command.impl.entertainment.interaction.positive.CuddleCommand;
|
||||
import creator.loki.command.impl.entertainment.interaction.positive.FeedCommand;
|
||||
import creator.loki.command.impl.entertainment.interaction.positive.HugCommand;
|
||||
import creator.loki.command.impl.entertainment.interaction.positive.KissCommand;
|
||||
import creator.loki.command.impl.entertainment.interaction.positive.PatCommand;
|
||||
import creator.loki.command.impl.moderation.*;
|
||||
import creator.loki.feature.role.*;
|
||||
import creator.loki.command.impl.stats.*;
|
||||
import creator.loki.command.impl.utility.*;
|
||||
import creator.loki.command.impl.utility.info.*;
|
||||
import creator.loki.command.impl.utility.system.*;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.feature.level.service.LevelService;
|
||||
import creator.loki.feature.role.CreateRoleCommand;
|
||||
import creator.loki.feature.role.DeleteRoleCommand;
|
||||
import creator.loki.feature.role.EditRoleCommand;
|
||||
import creator.loki.feature.role.MyRolesCommand;
|
||||
import creator.loki.level.LevelSystem;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import creator.loki.profile.ProfileService;
|
||||
import creator.loki.feature.role.CustomRoleManager;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Регистратор префиксных команд (!) - Single Responsibility
|
||||
*/
|
||||
import java.util.Set;
|
||||
|
||||
@Singleton
|
||||
public class PrefixCommandRegistrar {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PrefixCommandRegistrar.class);
|
||||
|
||||
private final CommandRegistry registry;
|
||||
private final BotConfig config;
|
||||
private final JsonDataStore dataStore;
|
||||
private final CustomRoleManager roleManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
private final LevelSystem levelSystem;
|
||||
private final LevelService levelService;
|
||||
private final creator.loki.feature.profile.ProfileService newProfileService;
|
||||
private final MetricsCollector metrics;
|
||||
private final Set<CommandRegistrar> registrars;
|
||||
|
||||
public PrefixCommandRegistrar(
|
||||
CommandRegistry registry,
|
||||
BotConfig config,
|
||||
JsonDataStore dataStore,
|
||||
CustomRoleManager roleManager,
|
||||
CooldownManager cooldownManager,
|
||||
LevelSystem levelSystem,
|
||||
LevelService levelService,
|
||||
creator.loki.feature.profile.ProfileService newProfileService,
|
||||
MetricsCollector metrics
|
||||
) {
|
||||
@Inject
|
||||
public PrefixCommandRegistrar(CommandRegistry registry, Set<CommandRegistrar> registrars) {
|
||||
this.registry = registry;
|
||||
this.config = config;
|
||||
this.dataStore = dataStore;
|
||||
this.roleManager = roleManager;
|
||||
this.cooldownManager = cooldownManager;
|
||||
this.levelSystem = levelSystem;
|
||||
this.levelService = levelService;
|
||||
this.newProfileService = newProfileService;
|
||||
this.metrics = metrics;
|
||||
this.registrars = registrars;
|
||||
}
|
||||
|
||||
public void registerAll() {
|
||||
registerUtilityCommands();
|
||||
registerRoleCommands();
|
||||
registerInteractionCommands();
|
||||
registerGameCommands();
|
||||
registerStatsCommands();
|
||||
registerModerationCommands();
|
||||
|
||||
for (CommandRegistrar registrar : registrars) {
|
||||
registrar.register(registry);
|
||||
}
|
||||
logger.info("Зарегистрировано префиксных команд: {}", registry.getCommandCount());
|
||||
}
|
||||
|
||||
private void registerUtilityCommands() {
|
||||
registry.register(new PingCommand());
|
||||
registry.register(new HelpCommand(registry, config));
|
||||
registry.register(new InfoCommand());
|
||||
registry.register(new ClearCommand());
|
||||
registry.register(new ProfileCommand(dataStore));
|
||||
registry.register(new creator.loki.feature.profile.ProfileCommand(newProfileService));
|
||||
registry.register(new MetricsCommand(metrics));
|
||||
registry.register(new AvatarCommand());
|
||||
registry.register(new PollCommand());
|
||||
registry.register(new SwapCommand());
|
||||
}
|
||||
|
||||
private void registerRoleCommands() {
|
||||
registry.register(new CreateRoleCommand(roleManager));
|
||||
registry.register(new DeleteRoleCommand(roleManager));
|
||||
registry.register(new MyRolesCommand(roleManager));
|
||||
registry.register(new EditRoleCommand(roleManager));
|
||||
}
|
||||
|
||||
private void registerInteractionCommands() {
|
||||
registry.register(new PatCommand(cooldownManager));
|
||||
registry.register(new HugCommand());
|
||||
registry.register(new KissCommand(cooldownManager));
|
||||
registry.register(new SlapCommand(cooldownManager));
|
||||
registry.register(new TickleCommand(cooldownManager));
|
||||
registry.register(new PokeCommand(cooldownManager));
|
||||
registry.register(new CuddleCommand(cooldownManager));
|
||||
registry.register(new FeedCommand(cooldownManager));
|
||||
}
|
||||
|
||||
private void registerGameCommands() {
|
||||
registry.register(new EightBallCommand());
|
||||
registry.register(new CoinCommand());
|
||||
registry.register(new DiceCommand());
|
||||
registry.register(new MemeCommand());
|
||||
registry.register(new ShipCommand(dataStore));
|
||||
registry.register(new RoastCommand());
|
||||
registry.register(new ComplimentCommand());
|
||||
registry.register(new GlazbobaCommand());
|
||||
registry.register(new IqCommand(cooldownManager));
|
||||
registry.register(new GayCommand(cooldownManager));
|
||||
registry.register(new PpCommand(cooldownManager));
|
||||
registry.register(new RateCommand(cooldownManager));
|
||||
registry.register(new BoykisserCommand(cooldownManager));
|
||||
registry.register(new MustacheCommand(cooldownManager));
|
||||
registry.register(new FatCommand(cooldownManager));
|
||||
}
|
||||
|
||||
private void registerStatsCommands() {
|
||||
registry.register(new creator.loki.feature.level.RankCommand(levelService));
|
||||
registry.register(new creator.loki.command.impl.stats.leaderboard.NewLeaderboardCommand(levelSystem));
|
||||
registry.register(new RepCommand(dataStore, cooldownManager));
|
||||
registry.register(new MinusRepCommand(dataStore, cooldownManager));
|
||||
registry.register(new RepTopCommand(dataStore));
|
||||
registry.register(new creator.loki.feature.level.StatsCommand());
|
||||
}
|
||||
|
||||
private void registerModerationCommands() {
|
||||
registry.register(new BanCommand());
|
||||
registry.register(new KickCommand());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
package creator.loki.core.registry;
|
||||
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Регистратор slash команд (/) - Single Responsibility
|
||||
*/
|
||||
public class SlashCommandRegistrar {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SlashCommandRegistrar.class);
|
||||
|
||||
private final SlashCommandRegistry registry;
|
||||
private final JsonDataStore dataStore;
|
||||
private final MetricsCollector metrics;
|
||||
|
||||
public SlashCommandRegistrar(
|
||||
SlashCommandRegistry registry,
|
||||
JsonDataStore dataStore,
|
||||
MetricsCollector metrics
|
||||
) {
|
||||
this.registry = registry;
|
||||
this.dataStore = dataStore;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public void registerAll() {
|
||||
// Все slash команды удалены согласно рефакторингу
|
||||
// Используются только prefix команды (!)
|
||||
|
||||
logger.info("Зарегистрировано slash команд: {}", registry.getCommandCount());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
package creator.loki.core.registry;
|
||||
|
||||
import creator.loki.command.SlashCommand;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Реестр для slash команд
|
||||
*/
|
||||
public class SlashCommandRegistry {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SlashCommandRegistry.class);
|
||||
private final Map<String, SlashCommand> commands = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Регистрирует slash команду
|
||||
*/
|
||||
public void register(SlashCommand command) {
|
||||
commands.put(command.getName().toLowerCase(), command);
|
||||
logger.debug("Зарегистрирована slash команда: {}", command.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает slash команду по имени
|
||||
*/
|
||||
public SlashCommand getCommand(String name) {
|
||||
return commands.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает все slash команды
|
||||
*/
|
||||
public Collection<SlashCommand> getAllCommands() {
|
||||
return Collections.unmodifiableCollection(commands.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает количество slash команд
|
||||
*/
|
||||
public int getCommandCount() {
|
||||
return commands.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрирует все slash команды в Discord
|
||||
*/
|
||||
public void registerCommandsInDiscord(JDA jda) {
|
||||
List<CommandData> commandDataList = new ArrayList<>();
|
||||
|
||||
for (SlashCommand command : commands.values()) {
|
||||
commandDataList.add(command.getCommandData());
|
||||
}
|
||||
|
||||
jda.updateCommands().addCommands(commandDataList).queue(
|
||||
success -> logger.info("Зарегистрировано {} slash команд в Discord", commandDataList.size()),
|
||||
error -> logger.error("Ошибка при регистрации slash команд", error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +1,83 @@
|
|||
package creator.loki.core.security;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CooldownManager {
|
||||
private final ConcurrentHashMap<String, Instant> cooldowns = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService cleanupExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public CooldownManager() {
|
||||
// Очистка истёкших cooldown'ов каждые 5 минут
|
||||
cleanupExecutor.scheduleAtFixedRate(this::cleanup, 5, 5, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, находится ли пользователь на cooldown для команды
|
||||
*/
|
||||
public boolean isOnCooldown(String userId, String commandName) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = cooldowns.get(key);
|
||||
|
||||
if (cooldownEnd == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Instant.now().isAfter(cooldownEnd)) {
|
||||
cooldowns.remove(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает оставшееся время cooldown в секундах
|
||||
*/
|
||||
public long getRemainingSeconds(String userId, String commandName) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = cooldowns.get(key);
|
||||
|
||||
if (cooldownEnd == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long remaining = cooldownEnd.getEpochSecond() - Instant.now().getEpochSecond();
|
||||
return Math.max(0, remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Применяет cooldown для пользователя на команду
|
||||
*/
|
||||
public void applyCooldown(String userId, String commandName, int seconds) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = Instant.now().plusSeconds(seconds);
|
||||
cooldowns.put(key, cooldownEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает истёкшие cooldown'ы
|
||||
*/
|
||||
public void cleanup() {
|
||||
Instant now = Instant.now();
|
||||
cooldowns.entrySet().removeIf(entry -> now.isAfter(entry.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Завершает работу менеджера
|
||||
*/
|
||||
public void shutdown() {
|
||||
cleanupExecutor.shutdown();
|
||||
try {
|
||||
if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
package creator.loki.core.security;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CooldownManager {
|
||||
private final ConcurrentHashMap<String, Instant> cooldowns = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService cleanupExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
@SuppressWarnings("this-escape")
|
||||
public CooldownManager() {
|
||||
cleanupExecutor.scheduleAtFixedRate(() -> Thread.startVirtualThread(this::cleanup), 5, 5, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, находится ли пользователь на cooldown для команды
|
||||
*/
|
||||
public boolean isOnCooldown(String userId, String commandName) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = cooldowns.get(key);
|
||||
|
||||
if (cooldownEnd == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Instant.now().isAfter(cooldownEnd)) {
|
||||
cooldowns.remove(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает оставшееся время cooldown в секундах
|
||||
*/
|
||||
public long getRemainingSeconds(String userId, String commandName) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = cooldowns.get(key);
|
||||
|
||||
if (cooldownEnd == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long remaining = cooldownEnd.getEpochSecond() - Instant.now().getEpochSecond();
|
||||
return Math.max(0, remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Применяет cooldown для пользователя на команду
|
||||
*/
|
||||
public void applyCooldown(String userId, String commandName, int seconds) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = Instant.now().plusSeconds(seconds);
|
||||
cooldowns.put(key, cooldownEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает истёкшие cooldown'ы
|
||||
*/
|
||||
public void cleanup() {
|
||||
Instant now = Instant.now();
|
||||
cooldowns.entrySet().removeIf(entry -> now.isAfter(entry.getValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Завершает работу менеджера
|
||||
*/
|
||||
public void shutdown() {
|
||||
cleanupExecutor.shutdown();
|
||||
try {
|
||||
if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,157 +1,163 @@
|
|||
package creator.loki.core.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Rate limiter для защиты от спама
|
||||
* Использует алгоритм Token Bucket
|
||||
*/
|
||||
public class RateLimiter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RateLimiter.class);
|
||||
|
||||
// Лимиты по умолчанию
|
||||
private static final int DEFAULT_USER_CAPACITY = 10; // Максимум токенов на пользователя
|
||||
private static final int DEFAULT_USER_REFILL_RATE = 2; // Токенов в секунду
|
||||
private static final int DEFAULT_GUILD_CAPACITY = 50; // Максимум токенов на гильдию
|
||||
private static final int DEFAULT_GUILD_REFILL_RATE = 10; // Токенов в секунду
|
||||
|
||||
private final ConcurrentHashMap<String, TokenBucket> userBuckets = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TokenBucket> guildBuckets = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService cleanupExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public RateLimiter() {
|
||||
// Очистка старых bucket'ов каждые 5 минут
|
||||
cleanupExecutor.scheduleAtFixedRate(this::cleanup, 5, 5, TimeUnit.MINUTES);
|
||||
logger.info("RateLimiter инициализирован");
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, может ли пользователь выполнить действие
|
||||
*/
|
||||
public boolean allowUser(String userId) {
|
||||
TokenBucket bucket = userBuckets.computeIfAbsent(
|
||||
userId,
|
||||
k -> new TokenBucket(DEFAULT_USER_CAPACITY, DEFAULT_USER_REFILL_RATE)
|
||||
);
|
||||
return bucket.tryConsume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, может ли гильдия выполнить действие
|
||||
*/
|
||||
public boolean allowGuild(String guildId) {
|
||||
TokenBucket bucket = guildBuckets.computeIfAbsent(
|
||||
guildId,
|
||||
k -> new TokenBucket(DEFAULT_GUILD_CAPACITY, DEFAULT_GUILD_REFILL_RATE)
|
||||
);
|
||||
return bucket.tryConsume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет оба лимита: пользователя и гильдии
|
||||
*/
|
||||
public boolean allow(String userId, String guildId) {
|
||||
return allowUser(userId) && allowGuild(guildId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает оставшееся время до восстановления для пользователя (в секундах)
|
||||
*/
|
||||
public long getUserWaitTime(String userId) {
|
||||
TokenBucket bucket = userBuckets.get(userId);
|
||||
if (bucket == null) {
|
||||
return 0;
|
||||
}
|
||||
return bucket.getWaitTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает неактивные bucket'ы
|
||||
*/
|
||||
private void cleanup() {
|
||||
long now = System.currentTimeMillis();
|
||||
long threshold = now - TimeUnit.MINUTES.toMillis(10);
|
||||
|
||||
userBuckets.entrySet().removeIf(entry -> entry.getValue().getLastAccessTime() < threshold);
|
||||
guildBuckets.entrySet().removeIf(entry -> entry.getValue().getLastAccessTime() < threshold);
|
||||
|
||||
logger.debug("Очистка rate limiter: осталось {} user buckets, {} guild buckets",
|
||||
userBuckets.size(), guildBuckets.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Завершает работу rate limiter
|
||||
*/
|
||||
public void shutdown() {
|
||||
logger.info("Завершение работы RateLimiter...");
|
||||
cleanupExecutor.shutdown();
|
||||
try {
|
||||
if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
logger.info("RateLimiter завершён");
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Bucket для rate limiting
|
||||
*/
|
||||
private static class TokenBucket {
|
||||
private final int capacity;
|
||||
private final double refillRate; // токенов в секунду
|
||||
private double tokens;
|
||||
private long lastRefillTime;
|
||||
private long lastAccessTime;
|
||||
|
||||
public TokenBucket(int capacity, double refillRate) {
|
||||
this.capacity = capacity;
|
||||
this.refillRate = refillRate;
|
||||
this.tokens = capacity;
|
||||
this.lastRefillTime = System.currentTimeMillis();
|
||||
this.lastAccessTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public synchronized boolean tryConsume() {
|
||||
refill();
|
||||
lastAccessTime = System.currentTimeMillis();
|
||||
|
||||
if (tokens >= 1.0) {
|
||||
tokens -= 1.0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void refill() {
|
||||
long now = System.currentTimeMillis();
|
||||
long timePassed = now - lastRefillTime;
|
||||
double tokensToAdd = (timePassed / 1000.0) * refillRate;
|
||||
|
||||
tokens = Math.min(capacity, tokens + tokensToAdd);
|
||||
lastRefillTime = now;
|
||||
}
|
||||
|
||||
public synchronized long getWaitTime() {
|
||||
refill();
|
||||
if (tokens >= 1.0) {
|
||||
return 0;
|
||||
}
|
||||
double tokensNeeded = 1.0 - tokens;
|
||||
return (long) Math.ceil(tokensNeeded / refillRate);
|
||||
}
|
||||
|
||||
public long getLastAccessTime() {
|
||||
return lastAccessTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
package creator.loki.core.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Rate limiter для защиты от спама
|
||||
* Использует алгоритм Token Bucket
|
||||
*/
|
||||
public class RateLimiter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RateLimiter.class);
|
||||
|
||||
// Лимиты по умолчанию
|
||||
private static final int DEFAULT_USER_CAPACITY = 10; // Максимум токенов на пользователя
|
||||
private static final int DEFAULT_USER_REFILL_RATE = 2; // Токенов в секунду
|
||||
private static final int DEFAULT_GUILD_CAPACITY = 50; // Максимум токенов на гильдию
|
||||
private static final int DEFAULT_GUILD_REFILL_RATE = 10; // Токенов в секунду
|
||||
|
||||
private final ConcurrentHashMap<String, TokenBucket> userBuckets = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, TokenBucket> guildBuckets = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService cleanupExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public RateLimiter() {
|
||||
// Очистка старых bucket'ов каждые 5 минут
|
||||
cleanupExecutor.scheduleAtFixedRate(() -> Thread.startVirtualThread(this::cleanup), 5, 5, TimeUnit.MINUTES);
|
||||
logger.info("RateLimiter инициализирован");
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, может ли пользователь выполнить действие
|
||||
*/
|
||||
public boolean allowUser(String userId) {
|
||||
TokenBucket bucket = userBuckets.computeIfAbsent(
|
||||
userId,
|
||||
k -> new TokenBucket(DEFAULT_USER_CAPACITY, DEFAULT_USER_REFILL_RATE)
|
||||
);
|
||||
return bucket.tryConsume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, может ли гильдия выполнить действие
|
||||
*/
|
||||
public boolean allowGuild(String guildId) {
|
||||
TokenBucket bucket = guildBuckets.computeIfAbsent(
|
||||
guildId,
|
||||
k -> new TokenBucket(DEFAULT_GUILD_CAPACITY, DEFAULT_GUILD_REFILL_RATE)
|
||||
);
|
||||
return bucket.tryConsume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет оба лимита: пользователя и гильдии
|
||||
*/
|
||||
public boolean allow(String userId, String guildId) {
|
||||
return allowUser(userId) && allowGuild(guildId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает оставшееся время до восстановления для пользователя (в секундах)
|
||||
*/
|
||||
public long getUserWaitTime(String userId) {
|
||||
TokenBucket bucket = userBuckets.get(userId);
|
||||
if (bucket == null) {
|
||||
return 0;
|
||||
}
|
||||
return bucket.getWaitTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает неактивные bucket'ы
|
||||
*/
|
||||
private void cleanup() {
|
||||
long now = System.currentTimeMillis();
|
||||
long threshold = now - TimeUnit.MINUTES.toMillis(10);
|
||||
|
||||
int usersBefore = userBuckets.size();
|
||||
int guildsBefore = guildBuckets.size();
|
||||
userBuckets.entrySet().removeIf(entry -> entry.getValue().getLastAccessTime() < threshold);
|
||||
guildBuckets.entrySet().removeIf(entry -> entry.getValue().getLastAccessTime() < threshold);
|
||||
|
||||
int removedUsers = usersBefore - userBuckets.size();
|
||||
int removedGuilds = guildsBefore - guildBuckets.size();
|
||||
if (removedUsers > 0 || removedGuilds > 0) {
|
||||
logger.debug("Очистка rate limiter: удалено {} user / {} guild buckets, осталось {} / {}",
|
||||
removedUsers, removedGuilds, userBuckets.size(), guildBuckets.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Завершает работу rate limiter
|
||||
*/
|
||||
public void shutdown() {
|
||||
logger.info("Завершение работы RateLimiter...");
|
||||
cleanupExecutor.shutdown();
|
||||
try {
|
||||
if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
logger.info("RateLimiter завершён");
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Bucket для rate limiting
|
||||
*/
|
||||
private static class TokenBucket {
|
||||
private final int capacity;
|
||||
private final double refillRate; // токенов в секунду
|
||||
private double tokens;
|
||||
private long lastRefillTime;
|
||||
private long lastAccessTime;
|
||||
|
||||
public TokenBucket(int capacity, double refillRate) {
|
||||
this.capacity = capacity;
|
||||
this.refillRate = refillRate;
|
||||
this.tokens = capacity;
|
||||
this.lastRefillTime = System.currentTimeMillis();
|
||||
this.lastAccessTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public synchronized boolean tryConsume() {
|
||||
refill();
|
||||
lastAccessTime = System.currentTimeMillis();
|
||||
|
||||
if (tokens >= 1.0) {
|
||||
tokens -= 1.0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void refill() {
|
||||
long now = System.currentTimeMillis();
|
||||
long timePassed = now - lastRefillTime;
|
||||
double tokensToAdd = (timePassed / 1000.0) * refillRate;
|
||||
|
||||
tokens = Math.min(capacity, tokens + tokensToAdd);
|
||||
lastRefillTime = now;
|
||||
}
|
||||
|
||||
public synchronized long getWaitTime() {
|
||||
refill();
|
||||
if (tokens >= 1.0) {
|
||||
return 0;
|
||||
}
|
||||
double tokensNeeded = 1.0 - tokens;
|
||||
return (long) Math.ceil(tokensNeeded / refillRate);
|
||||
}
|
||||
|
||||
public long getLastAccessTime() {
|
||||
return lastAccessTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,55 @@
|
|||
package creator.loki.database;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.*;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* Менеджер SQLite базы данных
|
||||
*/
|
||||
public class Database {
|
||||
private static final Logger logger = LoggerFactory.getLogger(Database.class);
|
||||
private final String dbPath;
|
||||
private Connection connection;
|
||||
private HikariDataSource dataSource;
|
||||
|
||||
public Database(String dbPath) {
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подключиться к базе данных
|
||||
*/
|
||||
public void connect() {
|
||||
try {
|
||||
String url = "jdbc:sqlite:" + dbPath;
|
||||
connection = DriverManager.getConnection(url);
|
||||
logger.info("Подключение к базе данных: {}", dbPath);
|
||||
|
||||
// Включаем foreign keys
|
||||
try (Statement stmt = connection.createStatement()) {
|
||||
stmt.execute("PRAGMA foreign_keys = ON");
|
||||
}
|
||||
HikariConfig config = new HikariConfig();
|
||||
config.setJdbcUrl("jdbc:sqlite:" + dbPath);
|
||||
config.setMaximumPoolSize(5);
|
||||
config.setConnectionTimeout(10000);
|
||||
config.setIdleTimeout(300000);
|
||||
config.setMaxLifetime(600000);
|
||||
config.setPoolName("LokiDB");
|
||||
|
||||
dataSource = new HikariDataSource(config);
|
||||
logger.info("Подключение к базе данных: {} (pool size: {})", dbPath, config.getMaximumPoolSize());
|
||||
|
||||
try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement()) {
|
||||
stmt.execute("PRAGMA foreign_keys = ON");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка подключения к базе данных", e);
|
||||
logger.error("Ошибка включения foreign keys", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Connection getConnection() {
|
||||
try {
|
||||
return dataSource.getConnection();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения соединения из пула", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить соединение
|
||||
*/
|
||||
public Connection getConnection() {
|
||||
try {
|
||||
if (connection == null || connection.isClosed()) {
|
||||
connect();
|
||||
}
|
||||
return connection;
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка проверки соединения", e);
|
||||
connect();
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить SQL запрос
|
||||
*/
|
||||
public void execute(String sql) {
|
||||
try (Statement stmt = getConnection().createStatement()) {
|
||||
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
|
||||
stmt.execute(sql);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка выполнения SQL: {}", sql, e);
|
||||
|
|
@ -64,29 +57,34 @@ public class Database {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить SQL запрос с параметрами
|
||||
*/
|
||||
public PreparedStatement prepare(String sql) {
|
||||
try {
|
||||
return getConnection().prepareStatement(sql);
|
||||
Connection conn = getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql);
|
||||
return (PreparedStatement) Proxy.newProxyInstance(
|
||||
PreparedStatement.class.getClassLoader(),
|
||||
new Class<?>[]{PreparedStatement.class},
|
||||
(proxy, method, args) -> {
|
||||
if (method.getName().equals("close")) {
|
||||
try {
|
||||
return method.invoke(stmt, args);
|
||||
} finally {
|
||||
conn.close();
|
||||
}
|
||||
}
|
||||
return method.invoke(stmt, args);
|
||||
}
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка подготовки SQL: {}", sql, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрыть соединение
|
||||
*/
|
||||
public void close() {
|
||||
try {
|
||||
if (connection != null && !connection.isClosed()) {
|
||||
connection.close();
|
||||
logger.info("Соединение с базой данных закрыто");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка закрытия соединения", e);
|
||||
if (dataSource != null && !dataSource.isClosed()) {
|
||||
dataSource.close();
|
||||
logger.info("Пул соединений закрыт");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,191 +1,190 @@
|
|||
package creator.loki.database;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Миграция данных из JSON в SQLite
|
||||
*/
|
||||
public class DatabaseMigration {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseMigration.class);
|
||||
private final Database database;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
public DatabaseMigration(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Мигрировать данные из JSON
|
||||
*/
|
||||
public void migrateFromJson(String jsonPath) {
|
||||
logger.info("Начало миграции данных из JSON: {}", jsonPath);
|
||||
|
||||
try (FileReader reader = new FileReader(jsonPath)) {
|
||||
Type type = new TypeToken<Map<String, Object>>(){}.getType();
|
||||
Map<String, Object> data = gson.fromJson(reader, type);
|
||||
|
||||
if (data == null) {
|
||||
logger.warn("JSON файл пустой или не существует");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMigrated()) {
|
||||
logger.info("Данные уже мигрированы, пропускаем");
|
||||
return;
|
||||
}
|
||||
|
||||
migrateUserStats(data);
|
||||
migrateReputation(data);
|
||||
migrateShipPairs(data);
|
||||
migrateCustomRoles(data);
|
||||
markMigrated();
|
||||
|
||||
logger.info("Миграция завершена успешно");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка миграции данных", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMigrated() {
|
||||
try (PreparedStatement stmt = database.prepare("SELECT value FROM config WHERE key = 'migrated'")) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
return rs.next() && "true".equals(rs.getString("value"));
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void markMigrated() {
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO config (key, value) VALUES ('migrated', 'true')"
|
||||
)) {
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка сохранения статуса миграции", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateUserStats(Map<String, Object> data) {
|
||||
Map<String, Object> userStats = (Map<String, Object>) data.get("userStats");
|
||||
if (userStats == null) return;
|
||||
|
||||
logger.info("Миграция userStats: {} записей", userStats.size());
|
||||
|
||||
String sql = """
|
||||
INSERT OR REPLACE INTO user_stats
|
||||
(user_id, level, total_xp, message_count, voice_minutes, last_message_time, last_voice_join)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
for (Map.Entry<String, Object> entry : userStats.entrySet()) {
|
||||
Map<String, Object> stats = (Map<String, Object>) entry.getValue();
|
||||
|
||||
stmt.setString(1, (String) stats.get("userId"));
|
||||
stmt.setInt(2, ((Number) stats.get("level")).intValue());
|
||||
stmt.setInt(3, ((Number) stats.get("totalXP")).intValue());
|
||||
stmt.setInt(4, ((Number) stats.get("messageCount")).intValue());
|
||||
stmt.setLong(5, ((Number) stats.get("voiceMinutes")).longValue());
|
||||
stmt.setLong(6, ((Number) stats.get("lastMessageTime")).longValue());
|
||||
stmt.setLong(7, ((Number) stats.get("lastVoiceJoin")).longValue());
|
||||
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
logger.info("userStats мигрированы");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции userStats", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateReputation(Map<String, Object> data) {
|
||||
Map<String, Object> reputation = (Map<String, Object>) data.get("reputation");
|
||||
if (reputation == null) return;
|
||||
|
||||
logger.info("Миграция reputation: {} записей", reputation.size());
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO reputation (user_id, reputation) VALUES (?, ?)"
|
||||
)) {
|
||||
for (Map.Entry<String, Object> entry : reputation.entrySet()) {
|
||||
stmt.setString(1, entry.getKey());
|
||||
stmt.setDouble(2, ((Number) entry.getValue()).doubleValue());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
logger.info("reputation мигрирована");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции reputation", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateShipPairs(Map<String, Object> data) {
|
||||
Map<String, Object> shipPairs = (Map<String, Object>) data.get("shipPairs");
|
||||
if (shipPairs == null) return;
|
||||
|
||||
logger.info("Миграция shipPairs: {} записей", shipPairs.size());
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO ship_pairs (user1_id, user2_id, compatibility) VALUES (?, ?, ?)"
|
||||
)) {
|
||||
for (Map.Entry<String, Object> entry : shipPairs.entrySet()) {
|
||||
String[] users = entry.getKey().split("-");
|
||||
if (users.length == 2) {
|
||||
stmt.setString(1, users[0]);
|
||||
stmt.setString(2, users[1]);
|
||||
stmt.setDouble(3, ((Number) entry.getValue()).doubleValue());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
logger.info("shipPairs мигрированы");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции shipPairs", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateCustomRoles(Map<String, Object> data) {
|
||||
Map<String, Object> customRoles = (Map<String, Object>) data.get("custom_roles");
|
||||
if (customRoles == null) return;
|
||||
|
||||
logger.info("Миграция custom_roles");
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO custom_roles (guild_id, user_id, role_id) VALUES (?, ?, ?)"
|
||||
)) {
|
||||
for (Map.Entry<String, Object> guildEntry : customRoles.entrySet()) {
|
||||
String guildId = guildEntry.getKey();
|
||||
Map<String, Object> users = (Map<String, Object>) guildEntry.getValue();
|
||||
|
||||
for (Map.Entry<String, Object> userEntry : users.entrySet()) {
|
||||
String userId = userEntry.getKey();
|
||||
Object rolesObj = userEntry.getValue();
|
||||
|
||||
if (rolesObj instanceof java.util.List) {
|
||||
for (Object roleId : (java.util.List<?>) rolesObj) {
|
||||
stmt.setString(1, guildId);
|
||||
stmt.setString(2, userId);
|
||||
stmt.setString(3, roleId.toString());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("custom_roles мигрированы");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции custom_roles", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
package creator.loki.database;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.FileReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Миграция данных из JSON в SQLite
|
||||
*/
|
||||
public class DatabaseMigration {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseMigration.class);
|
||||
private final Database database;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
public DatabaseMigration(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Мигрировать данные из JSON
|
||||
*/
|
||||
public void migrateFromJson(String jsonPath) {
|
||||
logger.info("Начало миграции данных из JSON: {}", jsonPath);
|
||||
|
||||
try (FileReader reader = new FileReader(jsonPath)) {
|
||||
Type type = new TypeToken<Map<String, Object>>(){}.getType();
|
||||
Map<String, Object> data = gson.fromJson(reader, type);
|
||||
|
||||
if (data == null) {
|
||||
logger.warn("JSON файл пустой или не существует");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMigrated()) {
|
||||
logger.info("Данные уже мигрированы, пропускаем");
|
||||
return;
|
||||
}
|
||||
|
||||
migrateUserStats(data);
|
||||
migrateReputation(data);
|
||||
migrateShipPairs(data);
|
||||
migrateCustomRoles(data);
|
||||
markMigrated();
|
||||
|
||||
logger.info("Миграция завершена успешно");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка миграции данных", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMigrated() {
|
||||
try (PreparedStatement stmt = database.prepare("SELECT value FROM config WHERE key = 'migrated'")) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
return rs.next() && "true".equals(rs.getString("value"));
|
||||
} catch (SQLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void markMigrated() {
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO config (key, value) VALUES ('migrated', 'true')"
|
||||
)) {
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка сохранения статуса миграции", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateUserStats(Map<String, Object> data) {
|
||||
Map<String, Object> userStats = (Map<String, Object>) data.get("userStats");
|
||||
if (userStats == null) return;
|
||||
|
||||
logger.info("Миграция userStats: {} записей", userStats.size());
|
||||
|
||||
String sql = """
|
||||
INSERT OR REPLACE INTO user_stats
|
||||
(user_id, level, total_xp, message_count, voice_minutes, last_message_time, last_voice_join)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)""";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
for (Map.Entry<String, Object> entry : userStats.entrySet()) {
|
||||
Map<String, Object> stats = (Map<String, Object>) entry.getValue();
|
||||
|
||||
stmt.setString(1, (String) stats.get("userId"));
|
||||
stmt.setInt(2, ((Number) stats.get("level")).intValue());
|
||||
stmt.setInt(3, ((Number) stats.get("totalXP")).intValue());
|
||||
stmt.setInt(4, ((Number) stats.get("messageCount")).intValue());
|
||||
stmt.setLong(5, ((Number) stats.get("voiceMinutes")).longValue());
|
||||
stmt.setLong(6, ((Number) stats.get("lastMessageTime")).longValue());
|
||||
stmt.setLong(7, ((Number) stats.get("lastVoiceJoin")).longValue());
|
||||
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
logger.info("userStats мигрированы");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции userStats", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateReputation(Map<String, Object> data) {
|
||||
Map<String, Object> reputation = (Map<String, Object>) data.get("reputation");
|
||||
if (reputation == null) return;
|
||||
|
||||
logger.info("Миграция reputation: {} записей", reputation.size());
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO reputation (user_id, reputation) VALUES (?, ?)"
|
||||
)) {
|
||||
for (Map.Entry<String, Object> entry : reputation.entrySet()) {
|
||||
stmt.setString(1, entry.getKey());
|
||||
stmt.setDouble(2, ((Number) entry.getValue()).doubleValue());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
logger.info("reputation мигрирована");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции reputation", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateShipPairs(Map<String, Object> data) {
|
||||
Map<String, Object> shipPairs = (Map<String, Object>) data.get("shipPairs");
|
||||
if (shipPairs == null) return;
|
||||
|
||||
logger.info("Миграция shipPairs: {} записей", shipPairs.size());
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO ship_pairs (user1_id, user2_id, compatibility) VALUES (?, ?, ?)"
|
||||
)) {
|
||||
for (Map.Entry<String, Object> entry : shipPairs.entrySet()) {
|
||||
String[] users = entry.getKey().split("-");
|
||||
if (users.length == 2) {
|
||||
stmt.setString(1, users[0]);
|
||||
stmt.setString(2, users[1]);
|
||||
stmt.setDouble(3, ((Number) entry.getValue()).doubleValue());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
logger.info("shipPairs мигрированы");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции shipPairs", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateCustomRoles(Map<String, Object> data) {
|
||||
Map<String, Object> customRoles = (Map<String, Object>) data.get("custom_roles");
|
||||
if (customRoles == null) return;
|
||||
|
||||
logger.info("Миграция custom_roles");
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO custom_roles (guild_id, user_id, role_id) VALUES (?, ?, ?)"
|
||||
)) {
|
||||
for (Map.Entry<String, Object> guildEntry : customRoles.entrySet()) {
|
||||
String guildId = guildEntry.getKey();
|
||||
Map<String, Object> users = (Map<String, Object>) guildEntry.getValue();
|
||||
|
||||
for (Map.Entry<String, Object> userEntry : users.entrySet()) {
|
||||
String userId = userEntry.getKey();
|
||||
Object rolesObj = userEntry.getValue();
|
||||
|
||||
if (rolesObj instanceof java.util.List<?> rolesList) {
|
||||
for (Object roleId : rolesList) {
|
||||
stmt.setString(1, guildId);
|
||||
stmt.setString(2, userId);
|
||||
stmt.setString(3, roleId.toString());
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("custom_roles мигрированы");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка миграции custom_roles", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,112 +1,249 @@
|
|||
package creator.loki.database;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Схема базы данных - создание таблиц
|
||||
*/
|
||||
public class DatabaseSchema {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseSchema.class);
|
||||
private final Database database;
|
||||
|
||||
public DatabaseSchema(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать все таблицы
|
||||
*/
|
||||
public void createTables() {
|
||||
logger.info("Создание таблиц базы данных...");
|
||||
|
||||
createConfigTable();
|
||||
createUserStatsTable();
|
||||
createReputationTable();
|
||||
createShipPairsTable();
|
||||
createCustomRolesTable();
|
||||
createVoiceChannelsTable();
|
||||
createCounterStatsTable();
|
||||
|
||||
logger.info("Таблицы созданы успешно");
|
||||
}
|
||||
|
||||
private void createConfigTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createUserStatsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS user_stats (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
level INTEGER DEFAULT 0,
|
||||
total_xp INTEGER DEFAULT 0,
|
||||
message_count INTEGER DEFAULT 0,
|
||||
voice_minutes INTEGER DEFAULT 0,
|
||||
last_message_time INTEGER DEFAULT 0,
|
||||
last_voice_join INTEGER DEFAULT 0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createReputationTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS reputation (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
reputation REAL DEFAULT 0.0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createShipPairsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS ship_pairs (
|
||||
user1_id TEXT NOT NULL,
|
||||
user2_id TEXT NOT NULL,
|
||||
compatibility REAL NOT NULL,
|
||||
PRIMARY KEY (user1_id, user2_id)
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createCustomRolesTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS custom_roles (
|
||||
guild_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
role_id TEXT NOT NULL,
|
||||
PRIMARY KEY (guild_id, user_id, role_id)
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createVoiceChannelsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS voice_channels (
|
||||
channel_id TEXT PRIMARY KEY,
|
||||
owner_id TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
user_limit INTEGER DEFAULT 0,
|
||||
bitrate INTEGER DEFAULT 64000,
|
||||
locked INTEGER DEFAULT 0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createCounterStatsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS counter_stats (
|
||||
date TEXT PRIMARY KEY,
|
||||
joined INTEGER DEFAULT 0,
|
||||
left_count INTEGER DEFAULT 0,
|
||||
difference INTEGER DEFAULT 0
|
||||
)
|
||||
""");
|
||||
}
|
||||
}
|
||||
package creator.loki.database;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Схема базы данных - создание таблиц
|
||||
*/
|
||||
public class DatabaseSchema {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseSchema.class);
|
||||
private final Database database;
|
||||
|
||||
public DatabaseSchema(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать все таблицы
|
||||
*/
|
||||
public void createTables() {
|
||||
logger.info("Создание таблиц базы данных...");
|
||||
|
||||
createConfigTable();
|
||||
createUserStatsTable();
|
||||
createReputationTable();
|
||||
createShipPairsTable();
|
||||
createCustomRolesTable();
|
||||
createVoiceChannelsTable();
|
||||
createVoicePresetsTable();
|
||||
createCounterStatsTable();
|
||||
createRatingsTable();
|
||||
createWarningsTable();
|
||||
createModerationCasesTable();
|
||||
createActiveMutesTable();
|
||||
createEconomyTable();
|
||||
|
||||
createIndexes();
|
||||
|
||||
logger.info("Таблицы созданы успешно");
|
||||
}
|
||||
|
||||
private void createConfigTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createUserStatsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS user_stats (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
level INTEGER DEFAULT 0,
|
||||
total_xp INTEGER DEFAULT 0,
|
||||
message_count INTEGER DEFAULT 0,
|
||||
voice_minutes INTEGER DEFAULT 0,
|
||||
last_message_time INTEGER DEFAULT 0,
|
||||
last_voice_join INTEGER DEFAULT 0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createReputationTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS reputation (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
reputation REAL DEFAULT 0.0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createShipPairsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS ship_pairs (
|
||||
user1_id TEXT NOT NULL,
|
||||
user2_id TEXT NOT NULL,
|
||||
compatibility REAL NOT NULL,
|
||||
PRIMARY KEY (user1_id, user2_id)
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createCustomRolesTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS custom_roles (
|
||||
guild_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
role_id TEXT NOT NULL,
|
||||
PRIMARY KEY (guild_id, user_id, role_id)
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createVoiceChannelsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS voice_channels (
|
||||
channel_id TEXT PRIMARY KEY,
|
||||
owner_id TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
name TEXT,
|
||||
user_limit INTEGER DEFAULT 0,
|
||||
bitrate INTEGER DEFAULT 64000,
|
||||
locked INTEGER DEFAULT 0,
|
||||
text_channel_id TEXT
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createVoicePresetsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS voice_presets (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
user_limit INTEGER DEFAULT 0,
|
||||
bitrate INTEGER DEFAULT 64000,
|
||||
region TEXT DEFAULT 'auto',
|
||||
locked INTEGER DEFAULT 0,
|
||||
allowed_role_id TEXT,
|
||||
denied_role_id TEXT
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createCounterStatsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS counter_stats (
|
||||
date TEXT PRIMARY KEY,
|
||||
joined INTEGER DEFAULT 0,
|
||||
left_count INTEGER DEFAULT 0,
|
||||
difference INTEGER DEFAULT 0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createRatingsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS ratings (
|
||||
guild_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
rating_type TEXT NOT NULL,
|
||||
value INTEGER NOT NULL,
|
||||
PRIMARY KEY (guild_id, user_id, rating_type)
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createWarningsTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS warnings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
guild_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
moderator_id TEXT NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createModerationCasesTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS moderation_cases (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
guild_id TEXT NOT NULL,
|
||||
action TEXT NOT NULL,
|
||||
target_id TEXT NOT NULL,
|
||||
moderator_id TEXT NOT NULL,
|
||||
reason TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
duration_sec INTEGER,
|
||||
until_ms INTEGER
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createActiveMutesTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS active_mutes (
|
||||
guild_id TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
ends_at_ms INTEGER NOT NULL,
|
||||
case_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (guild_id, user_id)
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private void createEconomyTable() {
|
||||
database.execute("""
|
||||
CREATE TABLE IF NOT EXISTS economy (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
cash INTEGER NOT NULL DEFAULT 0,
|
||||
bank INTEGER NOT NULL DEFAULT 0,
|
||||
total_earned INTEGER NOT NULL DEFAULT 0,
|
||||
total_spent INTEGER NOT NULL DEFAULT 0,
|
||||
last_daily TEXT,
|
||||
daily_streak INTEGER DEFAULT 0,
|
||||
fishing_luck INTEGER DEFAULT 0,
|
||||
efficiency INTEGER DEFAULT 0,
|
||||
last_fish TEXT,
|
||||
job TEXT,
|
||||
last_work TEXT
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать индексы для ускорения запросов
|
||||
*/
|
||||
private void createIndexes() {
|
||||
logger.info("Создание индексов...");
|
||||
|
||||
// Индексы для user_stats
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_user_stats_level ON user_stats(level DESC)");
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_user_stats_total_xp ON user_stats(total_xp DESC)");
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_user_stats_message_count ON user_stats(message_count DESC)");
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_user_stats_voice_minutes ON user_stats(voice_minutes DESC)");
|
||||
|
||||
// Индексы для reputation
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_reputation_value ON reputation(reputation DESC)");
|
||||
|
||||
// Индексы для ratings
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_ratings_guild_type ON ratings(guild_id, rating_type)");
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_ratings_value ON ratings(value DESC)");
|
||||
|
||||
// Индексы для voice_channels
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_voice_channels_owner ON voice_channels(owner_id)");
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_voice_channels_created ON voice_channels(created_at)");
|
||||
|
||||
// Индексы для custom_roles
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_custom_roles_guild ON custom_roles(guild_id)");
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_custom_roles_user ON custom_roles(user_id)");
|
||||
|
||||
// Индексы для counter_stats
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_counter_stats_date ON counter_stats(date DESC)");
|
||||
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_warnings_guild_user ON warnings(guild_id, user_id)");
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_warnings_guild_time ON warnings(guild_id, created_at DESC)");
|
||||
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_mod_cases_guild_id ON moderation_cases(guild_id, id DESC)");
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_active_mutes_ends ON active_mutes(ends_at_ms)");
|
||||
|
||||
database.execute("CREATE INDEX IF NOT EXISTS idx_economy_balance ON economy((cash + bank) DESC)");
|
||||
|
||||
logger.info("Индексы созданы успешно");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,46 +3,78 @@ package creator.loki.di;
|
|||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.multibindings.Multibinder;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.config.ConfigLoader;
|
||||
import creator.loki.core.EventDispatcher;
|
||||
import creator.loki.core.ListenerAggregator;
|
||||
import creator.loki.core.MessageCache;
|
||||
import creator.loki.core.ShutdownManager;
|
||||
import creator.loki.core.registry.CommandRegistrar;
|
||||
import creator.loki.core.registry.CommandRegistry;
|
||||
import creator.loki.core.registry.SlashCommandRegistry;
|
||||
import creator.loki.core.registry.PrefixCommandRegistrar;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.core.security.RateLimiter;
|
||||
import creator.loki.feature.counter.CounterManager;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.database.DatabaseMigration;
|
||||
import creator.loki.database.DatabaseSchema;
|
||||
import creator.loki.feature.level.domain.XpFormula;
|
||||
import creator.loki.feature.level.LevelUpNotifier;
|
||||
import creator.loki.feature.level.service.LeaderboardService;
|
||||
import creator.loki.feature.level.service.LevelService;
|
||||
import creator.loki.feature.level.service.MessageCooldownService;
|
||||
import creator.loki.feature.level.VoiceTracker;
|
||||
import creator.loki.level.LevelSystem;
|
||||
import creator.loki.metrics.MetricsCollector;
|
||||
import creator.loki.profile.ProfileService;
|
||||
import creator.loki.repository.UserStatsRepository;
|
||||
import creator.loki.repository.impl.JsonUserStatsRepository;
|
||||
import creator.loki.feature.role.CustomRoleManager;
|
||||
import creator.loki.feature.voice.VoiceChannelManager;
|
||||
import creator.loki.feature.config.repository.ConfigRepository;
|
||||
import creator.loki.feature.config.repository.PrefixedGuildConfigRepository;
|
||||
import creator.loki.feature.rating.repository.RatingsRepository;
|
||||
import creator.loki.feature.reputation.repository.ReputationRepository;
|
||||
import creator.loki.feature.ship.repository.ShipPairRepository;
|
||||
import creator.loki.feature.counter.CounterModule;
|
||||
import creator.loki.feature.entertainment.EntertainmentCommandRegistrar;
|
||||
import creator.loki.feature.level.LevelCommandRegistrar;
|
||||
import creator.loki.feature.level.LevelModule;
|
||||
import creator.loki.feature.metrics.MetricsCollector;
|
||||
import creator.loki.feature.metrics.HealthHttpServer;
|
||||
import creator.loki.feature.metrics.MetricsModule;
|
||||
import creator.loki.feature.moderation.ModerationCommandRegistrar;
|
||||
import creator.loki.feature.moderation.ModerationModule;
|
||||
import creator.loki.feature.profile.ProfileModule;
|
||||
import creator.loki.feature.role.RoleModule;
|
||||
import creator.loki.feature.utility.UtilityCommandRegistrar;
|
||||
import creator.loki.feature.music.MusicModule;
|
||||
import creator.loki.feature.music.MusicCommandRegistrar;
|
||||
import creator.loki.feature.voice.VoiceModule;
|
||||
import creator.loki.feature.welcome.WelcomeModule;
|
||||
import creator.loki.feature.config.repository.GuildConfigRepository;
|
||||
import creator.loki.feature.economy.EconomyCommandRegistrar;
|
||||
import creator.loki.feature.economy.EconomyModule;
|
||||
|
||||
/**
|
||||
* Главный модуль Guice для конфигурации Dependency Injection
|
||||
*/
|
||||
public class BotModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
// Все основные компоненты - Singleton
|
||||
bind(CommandRegistry.class).in(Singleton.class);
|
||||
bind(SlashCommandRegistry.class).in(Singleton.class);
|
||||
bind(CooldownManager.class).in(Singleton.class);
|
||||
bind(RateLimiter.class).in(Singleton.class);
|
||||
bind(MetricsCollector.class).in(Singleton.class);
|
||||
bind(HealthHttpServer.class).in(Singleton.class);
|
||||
bind(ListenerAggregator.class).in(Singleton.class);
|
||||
bind(MessageCache.class).in(Singleton.class);
|
||||
bind(ShutdownManager.class).in(Singleton.class);
|
||||
bind(PrefixCommandRegistrar.class).in(Singleton.class);
|
||||
|
||||
install(new VoiceModule());
|
||||
install(new MusicModule());
|
||||
install(new LevelModule());
|
||||
install(new ModerationModule());
|
||||
install(new RoleModule());
|
||||
install(new CounterModule());
|
||||
install(new ProfileModule());
|
||||
install(new MetricsModule());
|
||||
install(new WelcomeModule());
|
||||
install(new EconomyModule());
|
||||
|
||||
Multibinder<CommandRegistrar> commandBinder = Multibinder.newSetBinder(binder(), CommandRegistrar.class);
|
||||
commandBinder.addBinding().to(LevelCommandRegistrar.class);
|
||||
commandBinder.addBinding().to(ModerationCommandRegistrar.class);
|
||||
commandBinder.addBinding().to(EntertainmentCommandRegistrar.class);
|
||||
commandBinder.addBinding().to(UtilityCommandRegistrar.class);
|
||||
commandBinder.addBinding().to(MusicCommandRegistrar.class);
|
||||
commandBinder.addBinding().to(EconomyCommandRegistrar.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -51,38 +83,6 @@ public class BotModule extends AbstractModule {
|
|||
return ConfigLoader.load();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
JsonDataStore provideJsonDataStore() {
|
||||
JsonDataStore dataStore = new JsonDataStore("data/bot_data.json");
|
||||
dataStore.load();
|
||||
return dataStore;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CustomRoleManager provideCustomRoleManager(JsonDataStore dataStore) {
|
||||
return new CustomRoleManager(dataStore);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LevelSystem provideLevelSystem(JsonDataStore dataStore) {
|
||||
return new LevelSystem(dataStore);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
VoiceTracker provideVoiceTracker(LevelService levelService) {
|
||||
return new VoiceTracker(levelService);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ProfileService provideProfileService(JsonDataStore dataStore) {
|
||||
return new ProfileService(dataStore);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
EventDispatcher provideEventDispatcher(
|
||||
|
|
@ -96,18 +96,13 @@ public class BotModule extends AbstractModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
Database provideDatabase() {
|
||||
Database database = new Database("data/bot.db");
|
||||
Database provideDatabase(BotConfig config) {
|
||||
Database database = new Database(config.getDbPath());
|
||||
database.connect();
|
||||
|
||||
// Создаем таблицы
|
||||
DatabaseSchema schema = new DatabaseSchema(database);
|
||||
schema.createTables();
|
||||
|
||||
// Мигрируем данные из JSON если нужно
|
||||
DatabaseMigration migration = new DatabaseMigration(database);
|
||||
migration.migrateFromJson("data/bot_data.json");
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
|
|
@ -119,69 +114,25 @@ public class BotModule extends AbstractModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
LevelUpNotifier provideLevelUpNotifier(ConfigRepository configRepo, LevelService levelService) {
|
||||
return new LevelUpNotifier(configRepo, levelService);
|
||||
GuildConfigRepository provideGuildConfigRepository(ConfigRepository configRepository) {
|
||||
return new PrefixedGuildConfigRepository(configRepository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
VoiceChannelManager provideVoiceChannelManager(JsonDataStore dataStore, ConfigRepository configRepo) {
|
||||
return new VoiceChannelManager(dataStore, configRepo);
|
||||
RatingsRepository provideRatingsRepository(Database database) {
|
||||
return new RatingsRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
UserStatsRepository provideUserStatsRepository(JsonDataStore dataStore) {
|
||||
return new JsonUserStatsRepository(dataStore);
|
||||
ReputationRepository provideReputationRepository(Database database) {
|
||||
return new ReputationRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CounterManager provideCounterManager(JsonDataStore dataStore, ConfigRepository configRepo) {
|
||||
return new CounterManager(dataStore, configRepo);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
XpFormula provideXpFormula() {
|
||||
return new XpFormula();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MessageCooldownService provideMessageCooldownService(UserStatsRepository repository) {
|
||||
return new MessageCooldownService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LeaderboardService provideLeaderboardService(UserStatsRepository repository) {
|
||||
return new LeaderboardService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LevelService provideLevelService(
|
||||
UserStatsRepository repository,
|
||||
XpFormula xpFormula,
|
||||
MessageCooldownService cooldownService
|
||||
) {
|
||||
return new LevelService(repository, xpFormula, cooldownService);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ProfileService provideOldProfileService(JsonDataStore dataStore) {
|
||||
return new ProfileService(dataStore);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
creator.loki.feature.profile.ProfileService provideNewProfileService(
|
||||
UserStatsRepository repository,
|
||||
LevelService levelService,
|
||||
LeaderboardService leaderboardService
|
||||
) {
|
||||
return new creator.loki.feature.profile.ProfileService(repository, levelService, leaderboardService);
|
||||
ShipPairRepository provideShipPairRepository(Database database) {
|
||||
return new ShipPairRepository(database);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +1,79 @@
|
|||
package creator.loki.embed;
|
||||
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.Instant;
|
||||
|
||||
public class EmbedFactory {
|
||||
private final EmbedBuilder builder;
|
||||
|
||||
private EmbedFactory() {
|
||||
this.builder = new EmbedBuilder();
|
||||
}
|
||||
|
||||
public static EmbedFactory create() {
|
||||
return new EmbedFactory();
|
||||
}
|
||||
|
||||
public EmbedFactory setTitle(String title) {
|
||||
builder.setTitle(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory setDescription(String description) {
|
||||
builder.setDescription(description);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory setColor(Color color) {
|
||||
builder.setColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory addField(String name, String value, boolean inline) {
|
||||
builder.addField(name, value, inline);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory setFooter(String text) {
|
||||
builder.setFooter(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory setFooter(String text, String iconUrl) {
|
||||
builder.setFooter(text, iconUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory setThumbnail(String url) {
|
||||
builder.setThumbnail(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory setImage(String url) {
|
||||
builder.setImage(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory setAuthor(String name) {
|
||||
builder.setAuthor(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory setAuthor(String name, String url) {
|
||||
builder.setAuthor(name, url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory setAuthor(String name, String url, String iconUrl) {
|
||||
builder.setAuthor(name, url, iconUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageEmbed build() {
|
||||
builder.setTimestamp(Instant.now());
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
package creator.loki.embed;
|
||||
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.Instant;
|
||||
|
||||
public class EmbedFactory {
|
||||
private final EmbedBuilder builder;
|
||||
|
||||
private EmbedFactory() {
|
||||
this.builder = new EmbedBuilder();
|
||||
}
|
||||
|
||||
public static EmbedFactory create() {
|
||||
return new EmbedFactory();
|
||||
}
|
||||
|
||||
public EmbedFactory withTitle(String title) {
|
||||
builder.setTitle(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withDescription(String description) {
|
||||
builder.setDescription(description);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withColor(Color color) {
|
||||
builder.setColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory addField(String name, String value, boolean inline) {
|
||||
builder.addField(name, value, inline);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withFooter(String text) {
|
||||
builder.setFooter(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withFooter(String text, String iconUrl) {
|
||||
builder.setFooter(text, iconUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withThumbnail(String url) {
|
||||
builder.setThumbnail(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withImage(String url) {
|
||||
builder.setImage(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withAuthor(String name) {
|
||||
builder.setAuthor(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withAuthor(String name, String url) {
|
||||
builder.setAuthor(name, url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withAuthor(String name, String url, String iconUrl) {
|
||||
builder.setAuthor(name, url, iconUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessageEmbed build() {
|
||||
builder.setTimestamp(Instant.now());
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,50 @@
|
|||
package creator.loki.embed;
|
||||
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.Instant;
|
||||
|
||||
public final class EmbedTemplates {
|
||||
|
||||
private EmbedTemplates() {
|
||||
// Запрет создания экземпляров
|
||||
}
|
||||
|
||||
public static MessageEmbed success(String title, String description) {
|
||||
return new EmbedBuilder()
|
||||
.setColor(Color.GREEN)
|
||||
.setTitle("✅ " + title)
|
||||
.setDescription(description)
|
||||
.setTimestamp(Instant.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static MessageEmbed error(String title, String description) {
|
||||
return new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("❌ " + title)
|
||||
.setDescription(description)
|
||||
.setTimestamp(Instant.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static MessageEmbed info(String title, String description) {
|
||||
return new EmbedBuilder()
|
||||
.setColor(Color.CYAN)
|
||||
.setTitle("ℹ️ " + title)
|
||||
.setDescription(description)
|
||||
.setTimestamp(Instant.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static MessageEmbed warning(String title, String description) {
|
||||
return new EmbedBuilder()
|
||||
.setColor(Color.ORANGE)
|
||||
.setTitle("⚠️ " + title)
|
||||
.setDescription(description)
|
||||
.setTimestamp(Instant.now())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
package creator.loki.embed;
|
||||
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.Instant;
|
||||
|
||||
public final class EmbedTemplates {
|
||||
|
||||
private EmbedTemplates() {
|
||||
// Запрет создания экземпляров
|
||||
}
|
||||
|
||||
public static MessageEmbed success(String title, String description) {
|
||||
return new EmbedBuilder()
|
||||
.setColor(Color.GREEN)
|
||||
.setTitle("✅ " + title)
|
||||
.setDescription(description)
|
||||
.setTimestamp(Instant.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static MessageEmbed error(String title, String description) {
|
||||
return new EmbedBuilder()
|
||||
.setColor(Color.RED)
|
||||
.setTitle("❌ " + title)
|
||||
.setDescription(description)
|
||||
.setTimestamp(Instant.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static MessageEmbed info(String title, String description) {
|
||||
return new EmbedBuilder()
|
||||
.setColor(Color.CYAN)
|
||||
.setTitle("ℹ️ " + title)
|
||||
.setDescription(description)
|
||||
.setTimestamp(Instant.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static MessageEmbed warning(String title, String description) {
|
||||
return new EmbedBuilder()
|
||||
.setColor(Color.ORANGE)
|
||||
.setTitle("⚠️ " + title)
|
||||
.setDescription(description)
|
||||
.setTimestamp(Instant.now())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
75
src/main/java/creator/loki/embed/HelpCategories.java
Normal file
75
src/main/java/creator/loki/embed/HelpCategories.java
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package creator.loki.embed;
|
||||
|
||||
import creator.loki.command.CommandCategories;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Map;
|
||||
|
||||
public final class HelpCategories {
|
||||
private static final Map<String, String> EMOJIS;
|
||||
private static final Map<String, Color> COLORS;
|
||||
private static final Map<String, String> DESCRIPTIONS;
|
||||
|
||||
static {
|
||||
EMOJIS = Map.ofEntries(
|
||||
Map.entry("Общие", "📋"),
|
||||
Map.entry("Роли", "🎭"),
|
||||
Map.entry("Развлечения", "🎮"),
|
||||
Map.entry(CommandCategories.GIF_INTERACTIVE, "💞"),
|
||||
Map.entry("Модерация", "🛡️"),
|
||||
Map.entry("Статистика", "📊"),
|
||||
Map.entry("Утилиты", "🔧"),
|
||||
Map.entry("Система", "💠"),
|
||||
Map.entry("Настройка", "⚙️"),
|
||||
Map.entry("Супер-Админ", "🔐")
|
||||
);
|
||||
|
||||
COLORS = Map.ofEntries(
|
||||
Map.entry("Общие", new Color(52, 152, 219)),
|
||||
Map.entry("Роли", new Color(155, 89, 182)),
|
||||
Map.entry("Развлечения", new Color(241, 196, 15)),
|
||||
Map.entry(CommandCategories.GIF_INTERACTIVE, new Color(233, 30, 99)),
|
||||
Map.entry("Модерация", new Color(231, 76, 60)),
|
||||
Map.entry("Статистика", new Color(46, 204, 113)),
|
||||
Map.entry("Утилиты", new Color(52, 152, 219)),
|
||||
Map.entry("Система", new Color(52, 73, 94)),
|
||||
Map.entry("Настройка", new Color(149, 165, 166)),
|
||||
Map.entry("Супер-Админ", new Color(113, 54, 138))
|
||||
);
|
||||
|
||||
DESCRIPTIONS = Map.ofEntries(
|
||||
Map.entry("Общие", "Основные команды бота"),
|
||||
Map.entry("Роли", "Управление кастомными ролями"),
|
||||
Map.entry("Развлечения", "Игры, мемы, рейтинги, текст"),
|
||||
Map.entry(CommandCategories.GIF_INTERACTIVE, "Действия с участником в GIF (nekos.life)"),
|
||||
Map.entry("Модерация", "Команды модерации сервера"),
|
||||
Map.entry("Статистика", "Уровни, репутация и статистика"),
|
||||
Map.entry("Утилиты", "Полезные инструменты"),
|
||||
Map.entry("Система", "Метрики и служебная информация для админов"),
|
||||
Map.entry("Настройка", "Настройка систем бота (только админы)"),
|
||||
Map.entry("Супер-Админ", "Сервисные команды владельца бота")
|
||||
);
|
||||
}
|
||||
|
||||
private HelpCategories() {
|
||||
}
|
||||
|
||||
public static String getEmoji(String category) {
|
||||
return EMOJIS.getOrDefault(category, "📌");
|
||||
}
|
||||
|
||||
public static Color getColor(String category) {
|
||||
return COLORS.getOrDefault(category, new Color(88, 101, 242));
|
||||
}
|
||||
|
||||
public static String getDescription(String category) {
|
||||
return DESCRIPTIONS.getOrDefault(category, "Команды категории");
|
||||
}
|
||||
|
||||
public static boolean isCategoryVisible(String category, boolean isAdmin, boolean isSuper) {
|
||||
if ("Настройка".equals(category) && !isAdmin) {
|
||||
return false;
|
||||
}
|
||||
return !"Супер-Админ".equals(category) || isSuper;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
package creator.loki.feature.admin.commands;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.feature.rating.repository.RatingsRepository;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Команда для генерации всех rating значений для всех участников сервера
|
||||
* Доступна только супер-админу
|
||||
*/
|
||||
public class GenerateRatingsCommand implements Command {
|
||||
private static final Logger logger = LoggerFactory.getLogger(GenerateRatingsCommand.class);
|
||||
private final RatingsRepository ratingsRepository;
|
||||
private final BotConfig config;
|
||||
private final Random random = new Random();
|
||||
|
||||
public GenerateRatingsCommand(RatingsRepository ratingsRepository, BotConfig config) {
|
||||
this.ratingsRepository = ratingsRepository;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "generateratings";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Генерирует все rating значения для всех участников сервера (только для супер-админа)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "generateratings";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Супер-Админ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (!config.getSuperAdminId().equals(context.getAuthor().getUser().getId())) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("❌ Недостаточно прав")
|
||||
.withDescription("Эта команда доступна только супер-администратору бота.")
|
||||
.withColor(Color.RED)
|
||||
.build()
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessage("❌ Команда работает только на сервере.").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
var initial = context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("⏳ Генерация рейтингов...")
|
||||
.withDescription("Начинаю генерацию всех rating значений для участников сервера...")
|
||||
.withColor(Color.YELLOW)
|
||||
.build()
|
||||
).complete();
|
||||
|
||||
try {
|
||||
String guildId = context.getGuild().getId();
|
||||
int memberCount = 0;
|
||||
int generatedCount = 0;
|
||||
|
||||
for (Member member : context.getGuild().getMembers()) {
|
||||
if (member.getUser().isBot()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
memberCount++;
|
||||
String userId = member.getUser().getId();
|
||||
|
||||
generateRatingForUser(guildId, userId, "ship");
|
||||
generateRatingForUser(guildId, userId, "gay");
|
||||
generateRatingForUser(guildId, userId, "iq");
|
||||
generateRatingForUser(guildId, userId, "pp");
|
||||
generateRatingForUser(guildId, userId, "mustache");
|
||||
generateRatingForUser(guildId, userId, "fat");
|
||||
generateRatingForUser(guildId, userId, "boykisser");
|
||||
|
||||
generatedCount++;
|
||||
}
|
||||
|
||||
initial.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("✅ Генерация завершена")
|
||||
.withDescription(
|
||||
"Успешно сгенерированы все rating значения!\n\n" +
|
||||
"**Статистика:**\n" +
|
||||
"• Обработано участников: **" + memberCount + "**\n" +
|
||||
"• Сгенерировано значений: **" + (generatedCount * 7) + "**\n" +
|
||||
"• Типы рейтингов: ship, gay, iq, pp, mustache, fat, boykisser"
|
||||
)
|
||||
.withColor(Color.GREEN)
|
||||
.withFooter("Инициатор: " + context.getAuthor().getUser().getName(), context.getAuthor().getAvatarUrl())
|
||||
.build()
|
||||
).queue();
|
||||
|
||||
logger.info("Супер-админ {} сгенерировал рейтинги для {} участников",
|
||||
context.getAuthor().getUser().getName(), memberCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при генерации рейтингов", e);
|
||||
initial.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("❌ Ошибка")
|
||||
.withDescription("Произошла ошибка при генерации рейтингов: " + e.getMessage())
|
||||
.withColor(Color.RED)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует rating значение для пользователя
|
||||
*/
|
||||
private void generateRatingForUser(String guildId, String userId, String ratingType) {
|
||||
// Проверяем существует ли уже значение
|
||||
if (ratingsRepository.hasRating(guildId, userId, ratingType)) {
|
||||
return; // Не перезаписываем существующие значения
|
||||
}
|
||||
|
||||
// Генерируем значение в зависимости от типа
|
||||
int value = switch (ratingType) {
|
||||
case "iq" -> random.nextInt(201); // 0-200
|
||||
case "pp" -> random.nextInt(31); // 0-30 см
|
||||
case "mustache" -> {
|
||||
// Редкий шанс на легендарные усы
|
||||
if (random.nextInt(100) < 1) {
|
||||
yield 1000 + random.nextInt(500); // 1000-1500
|
||||
}
|
||||
yield random.nextInt(101); // 0-100
|
||||
}
|
||||
case "fat" -> random.nextInt(101); // 0-100%
|
||||
case "boykisser" -> random.nextInt(101); // 0-100%
|
||||
default -> random.nextInt(101); // ship, gay: 0-100%
|
||||
};
|
||||
|
||||
// Сохраняем значение в БД
|
||||
ratingsRepository.setRating(guildId, userId, ratingType, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package creator.loki.database;
|
||||
package creator.loki.feature.config.repository;
|
||||
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.feature.config.repository.StringConfigStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
|
@ -10,7 +12,7 @@ import java.sql.SQLException;
|
|||
/**
|
||||
* Репозиторий для работы с конфигурацией
|
||||
*/
|
||||
public class ConfigRepository {
|
||||
public class ConfigRepository implements StringConfigStore {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigRepository.class);
|
||||
private final Database database;
|
||||
|
||||
|
|
@ -25,7 +27,7 @@ public class ConfigRepository {
|
|||
try (PreparedStatement stmt = database.prepare("SELECT value FROM config WHERE key = ?")) {
|
||||
stmt.setString(1, key);
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
|
||||
if (rs.next()) {
|
||||
return rs.getString("value");
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package creator.loki.feature.config.repository;
|
||||
|
||||
/**
|
||||
* Ключи конфигурации с привязкой к гильдии (хранение: {@code guildId + ":" + key} в общей таблице config).
|
||||
*/
|
||||
public interface GuildConfigRepository {
|
||||
|
||||
/**
|
||||
* @return значение или {@code null}, если нет записи
|
||||
*/
|
||||
String get(String guildId, String key);
|
||||
|
||||
void set(String guildId, String key, String value);
|
||||
|
||||
void delete(String guildId, String key);
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package creator.loki.feature.config.repository;
|
||||
|
||||
import creator.loki.feature.config.repository.GuildConfigRepository;
|
||||
import creator.loki.feature.config.repository.StringConfigStore;
|
||||
|
||||
/**
|
||||
* Реализация {@link GuildConfigRepository} поверх {@link StringConfigStore} с префиксом {@code guildId:}.
|
||||
*/
|
||||
public final class PrefixedGuildConfigRepository implements GuildConfigRepository {
|
||||
|
||||
private final StringConfigStore store;
|
||||
|
||||
public PrefixedGuildConfigRepository(StringConfigStore store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
private static String storageKey(String guildId, String key) {
|
||||
return guildId + ":" + key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(String guildId, String key) {
|
||||
return store.get(storageKey(guildId, key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(String guildId, String key, String value) {
|
||||
store.set(storageKey(guildId, key), value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String guildId, String key) {
|
||||
store.delete(storageKey(guildId, key));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package creator.loki.feature.config.repository;
|
||||
|
||||
/**
|
||||
* Плоское хранилище строковых ключей (глобальная таблица config и т.п.).
|
||||
*/
|
||||
public interface StringConfigStore {
|
||||
|
||||
/**
|
||||
* @return значение или {@code null}
|
||||
*/
|
||||
String get(String key);
|
||||
|
||||
void set(String key, String value);
|
||||
|
||||
void delete(String key);
|
||||
}
|
||||
|
|
@ -1,226 +1,110 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import creator.loki.data.JsonDataStore;
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Менеджер счетчика участников
|
||||
*/
|
||||
public class CounterManager {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CounterManager.class);
|
||||
private final JsonDataStore dataStore;
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
private String counterChannelId;
|
||||
private String format = "【👥】Участники: {count}";
|
||||
|
||||
public CounterManager(JsonDataStore dataStore, ConfigRepository configRepo) {
|
||||
this.dataStore = dataStore;
|
||||
this.configRepo = configRepo;
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить конфигурацию
|
||||
*/
|
||||
private void loadConfig() {
|
||||
this.counterChannelId = configRepo.get("counter.channel_id");
|
||||
String savedFormat = configRepo.get("counter.format");
|
||||
if (savedFormat != null) {
|
||||
this.format = savedFormat;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить данные счетчика
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> getCounterData() {
|
||||
Type type = new TypeToken<Map<String, Object>>(){}.getType();
|
||||
Map<String, Object> data = dataStore.getData("counter_data", type);
|
||||
return data != null ? data : new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить данные счетчика
|
||||
*/
|
||||
private void saveCounterData(Map<String, Object> data) {
|
||||
dataStore.setData("counter_data", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить канал счетчика
|
||||
*/
|
||||
public void setCounterChannel(String channelId) {
|
||||
configRepo.set("counter.channel_id", channelId);
|
||||
this.counterChannelId = channelId;
|
||||
logger.info("Установлен канал счетчика: {}", channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить формат отображения
|
||||
*/
|
||||
public void setFormat(String format) {
|
||||
configRepo.set("counter.format", format);
|
||||
this.format = format;
|
||||
logger.info("Установлен формат: {}", format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить текущую дневную статистику
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public DailyStats getDailyStats() {
|
||||
Map<String, Object> data = getCounterData();
|
||||
Map<String, Object> statsMap = (Map<String, Object>) data.get("daily_stats");
|
||||
|
||||
if (statsMap == null) {
|
||||
return new DailyStats();
|
||||
}
|
||||
|
||||
DailyStats stats = new DailyStats();
|
||||
stats.setDate((String) statsMap.getOrDefault("date", LocalDate.now().toString()));
|
||||
stats.setJoined(((Number) statsMap.getOrDefault("joined", 0)).intValue());
|
||||
stats.setLeft(((Number) statsMap.getOrDefault("left", 0)).intValue());
|
||||
|
||||
// Если статистика не сегодняшняя, создаем новую
|
||||
if (!stats.isToday()) {
|
||||
// Сохраняем старую в историю
|
||||
saveToHistory(stats);
|
||||
stats = new DailyStats();
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить статистику
|
||||
*/
|
||||
private void saveDailyStats(DailyStats stats) {
|
||||
Map<String, Object> data = getCounterData();
|
||||
|
||||
Map<String, Object> statsMap = new HashMap<>();
|
||||
statsMap.put("date", stats.getDate());
|
||||
statsMap.put("joined", stats.getJoined());
|
||||
statsMap.put("left", stats.getLeft());
|
||||
statsMap.put("difference", stats.getDifference());
|
||||
|
||||
data.put("daily_stats", statsMap);
|
||||
saveCounterData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить статистику в историю
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void saveToHistory(DailyStats stats) {
|
||||
Map<String, Object> data = getCounterData();
|
||||
List<Map<String, Object>> history = (List<Map<String, Object>>) data.get("history");
|
||||
|
||||
if (history == null) {
|
||||
history = new ArrayList<>();
|
||||
}
|
||||
|
||||
Map<String, Object> statsMap = new HashMap<>();
|
||||
statsMap.put("date", stats.getDate());
|
||||
statsMap.put("joined", stats.getJoined());
|
||||
statsMap.put("left", stats.getLeft());
|
||||
statsMap.put("difference", stats.getDifference());
|
||||
|
||||
history.add(statsMap);
|
||||
|
||||
// Храним только последние 30 дней
|
||||
if (history.size() > 30) {
|
||||
history.remove(0);
|
||||
}
|
||||
|
||||
data.put("history", history);
|
||||
saveCounterData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать присоединение участника
|
||||
*/
|
||||
public void recordJoin() {
|
||||
DailyStats stats = getDailyStats();
|
||||
stats.incrementJoined();
|
||||
saveDailyStats(stats);
|
||||
logger.debug("Зарегистрировано присоединение. Всего за день: {}", stats.getJoined());
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать выход участника
|
||||
*/
|
||||
public void recordLeave() {
|
||||
DailyStats stats = getDailyStats();
|
||||
stats.incrementLeft();
|
||||
saveDailyStats(stats);
|
||||
logger.debug("Зарегистрирован выход. Всего за день: {}", stats.getLeft());
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить счетчик участников
|
||||
*/
|
||||
public void updateCounter(Guild guild) {
|
||||
if (counterChannelId == null || counterChannelId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VoiceChannel channel = guild.getVoiceChannelById(counterChannelId);
|
||||
if (channel == null) {
|
||||
logger.warn("Канал счетчика не найден: {}", counterChannelId);
|
||||
return;
|
||||
}
|
||||
|
||||
int memberCount = guild.getMemberCount();
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
|
||||
// Обновляем название канала только если оно изменилось
|
||||
if (!channel.getName().equals(newName)) {
|
||||
channel.getManager().setName(newName).queue(
|
||||
success -> logger.debug("Обновлен счетчик участников: {}", memberCount),
|
||||
error -> logger.error("Ошибка при обновлении счетчика", error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить историю статистики
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<DailyStats> getHistory(int days) {
|
||||
Map<String, Object> data = getCounterData();
|
||||
List<Map<String, Object>> historyMaps = (List<Map<String, Object>>) data.get("history");
|
||||
|
||||
if (historyMaps == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<DailyStats> history = new ArrayList<>();
|
||||
int start = Math.max(0, historyMaps.size() - days);
|
||||
|
||||
for (int i = start; i < historyMaps.size(); i++) {
|
||||
Map<String, Object> statsMap = historyMaps.get(i);
|
||||
DailyStats stats = new DailyStats();
|
||||
stats.setDate((String) statsMap.get("date"));
|
||||
stats.setJoined(((Number) statsMap.get("joined")).intValue());
|
||||
stats.setLeft(((Number) statsMap.get("left")).intValue());
|
||||
history.add(stats);
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getCounterChannelId() { return counterChannelId; }
|
||||
public String getFormat() { return format; }
|
||||
}
|
||||
package creator.loki.feature.counter;
|
||||
|
||||
import creator.loki.feature.config.repository.ConfigRepository;
|
||||
import creator.loki.feature.counter.repository.CounterStatsRepository;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Менеджер счетчика участников
|
||||
*/
|
||||
public class CounterManager {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CounterManager.class);
|
||||
private final CounterStatsRepository statsRepo;
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
private String counterChannelId;
|
||||
private String format = "【👥】Участники: {count}";
|
||||
|
||||
public CounterManager(CounterStatsRepository statsRepo, ConfigRepository configRepo) {
|
||||
this.statsRepo = statsRepo;
|
||||
this.configRepo = configRepo;
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить конфигурацию
|
||||
*/
|
||||
private void loadConfig() {
|
||||
this.counterChannelId = configRepo.get("counter.channel_id");
|
||||
String savedFormat = configRepo.get("counter.format");
|
||||
if (savedFormat != null) {
|
||||
this.format = savedFormat;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить текущую дневную статистику
|
||||
*/
|
||||
public DailyStats getDailyStats() {
|
||||
return statsRepo.getTodayStats();
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить статистику
|
||||
*/
|
||||
private void saveDailyStats(DailyStats stats) {
|
||||
statsRepo.saveStats(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать присоединение участника
|
||||
*/
|
||||
public void recordJoin() {
|
||||
DailyStats stats = getDailyStats();
|
||||
stats.incrementJoined();
|
||||
saveDailyStats(stats);
|
||||
logger.debug("Зарегистрировано присоединение. Всего за день: {}", stats.getJoined());
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать выход участника
|
||||
*/
|
||||
public void recordLeave() {
|
||||
DailyStats stats = getDailyStats();
|
||||
stats.incrementLeft();
|
||||
saveDailyStats(stats);
|
||||
logger.debug("Зарегистрирован выход. Всего за день: {}", stats.getLeft());
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить счетчик участников
|
||||
*/
|
||||
public void updateCounter(Guild guild) {
|
||||
if (counterChannelId == null || counterChannelId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VoiceChannel channel = guild.getVoiceChannelById(counterChannelId);
|
||||
if (channel == null) {
|
||||
logger.warn("Канал счетчика не найден: {}", counterChannelId);
|
||||
return;
|
||||
}
|
||||
|
||||
int memberCount = guild.getMemberCount();
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
|
||||
// Обновляем название канала только если оно изменилось
|
||||
if (!channel.getName().equals(newName)) {
|
||||
channel.getManager().setName(newName).queue(
|
||||
success -> logger.debug("Обновлен счетчик участников: {}", memberCount),
|
||||
error -> logger.error("Ошибка при обновлении счетчика", error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить историю статистики
|
||||
*/
|
||||
public List<DailyStats> getHistory(int days) {
|
||||
return statsRepo.getHistory(days);
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getCounterChannelId() { return counterChannelId; }
|
||||
public String getFormat() { return format; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.feature.config.repository.ConfigRepository;
|
||||
import creator.loki.feature.counter.repository.CounterStatsRepository;
|
||||
|
||||
public class CounterModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CounterStatsRepository provideCounterStatsRepository(Database database) {
|
||||
return new CounterStatsRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CounterManager provideCounterManager(CounterStatsRepository statsRepo, ConfigRepository configRepo) {
|
||||
return new CounterManager(statsRepo, configRepo);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import creator.loki.database.ConfigRepository;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Команда настройки счетчика участников
|
||||
*/
|
||||
public class CounterSetupCommand extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CounterSetupCommand.class);
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
public CounterSetupCommand(ConfigRepository configRepo) {
|
||||
this.configRepo = configRepo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
|
||||
if (!"counter-setup".equals(event.getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка прав администратора
|
||||
Member member = event.getMember();
|
||||
if (member == null || !member.hasPermission(Permission.ADMINISTRATOR)) {
|
||||
event.reply("❌ У вас нет прав для использования этой команды!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String subcommand = event.getSubcommandName();
|
||||
if (subcommand == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case "channel" -> handleChannel(event);
|
||||
case "format" -> handleFormat(event);
|
||||
case "info" -> handleInfo(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить канал счетчика
|
||||
*/
|
||||
private void handleChannel(SlashCommandInteractionEvent event) {
|
||||
VoiceChannel channel = event.getOption("channel").getAsChannel().asVoiceChannel();
|
||||
|
||||
configRepo.set("counter.channel_id", channel.getId());
|
||||
|
||||
// Сразу обновляем название канала
|
||||
int memberCount = event.getGuild().getMemberCount();
|
||||
String format = configRepo.get("counter.format");
|
||||
if (format == null) {
|
||||
format = "【👥】Участники: {count}";
|
||||
configRepo.set("counter.format", format);
|
||||
}
|
||||
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
channel.getManager().setName(newName).queue();
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Канал счетчика установлен")
|
||||
.setDescription("Канал " + channel.getAsMention() + " теперь отображает количество участников")
|
||||
.addField("Текущее количество", String.valueOf(memberCount), false)
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен канал счетчика: {} ({})", channel.getName(), channel.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить формат отображения
|
||||
*/
|
||||
private void handleFormat(SlashCommandInteractionEvent event) {
|
||||
String format = event.getOption("format").getAsString();
|
||||
|
||||
if (!format.contains("{count}")) {
|
||||
event.reply("❌ Формат должен содержать `{count}`!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
configRepo.set("counter.format", format);
|
||||
|
||||
// Обновляем канал если он установлен
|
||||
String channelId = configRepo.get("counter.channel_id");
|
||||
if (channelId != null) {
|
||||
VoiceChannel channel = event.getGuild().getVoiceChannelById(channelId);
|
||||
if (channel != null) {
|
||||
int memberCount = event.getGuild().getMemberCount();
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
channel.getManager().setName(newName).queue();
|
||||
}
|
||||
}
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("✅ Формат обновлен")
|
||||
.setDescription("Новый формат: `" + format + "`")
|
||||
.addField("Пример", format.replace("{count}", "123"), false)
|
||||
.setColor(Color.GREEN);
|
||||
|
||||
event.replyEmbeds(embed.build()).queue();
|
||||
logger.info("Установлен формат счетчика: {}", format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Показать текущую конфигурацию
|
||||
*/
|
||||
private void handleInfo(SlashCommandInteractionEvent event) {
|
||||
String channelId = configRepo.get("counter.channel_id");
|
||||
String format = configRepo.get("counter.format");
|
||||
|
||||
EmbedBuilder embed = new EmbedBuilder()
|
||||
.setTitle("📊 Конфигурация счетчика участников")
|
||||
.setColor(Color.BLUE);
|
||||
|
||||
if (channelId != null) {
|
||||
embed.addField("Канал", "<#" + channelId + ">", false);
|
||||
} else {
|
||||
embed.addField("Канал", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
if (format != null) {
|
||||
embed.addField("Формат", "`" + format + "`", false);
|
||||
embed.addField("Пример", format.replace("{count}", String.valueOf(event.getGuild().getMemberCount())), false);
|
||||
} else {
|
||||
embed.addField("Формат", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
event.replyEmbeds(embed.build()).setEphemeral(true).queue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать команду в Discord
|
||||
*/
|
||||
public static void register(net.dv8tion.jda.api.JDA jda) {
|
||||
jda.updateCommands().addCommands(
|
||||
Commands.slash("counter-setup", "Настройка счетчика участников")
|
||||
.addSubcommands(
|
||||
new SubcommandData("channel", "Установить канал для счетчика")
|
||||
.addOption(OptionType.CHANNEL, "channel", "Голосовой канал", true),
|
||||
new SubcommandData("format", "Установить формат отображения")
|
||||
.addOption(OptionType.STRING, "format", "Формат (используйте {count})", true),
|
||||
new SubcommandData("info", "Показать текущую конфигурацию")
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.config.repository.ConfigRepository;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.GuildChannelResolve;
|
||||
import net.dv8tion.jda.api.Permission;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Prefix команда настройки счетчика участников
|
||||
*/
|
||||
public class CounterSetupPrefixCommand implements Command {
|
||||
private final CounterManager counterManager;
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
public CounterSetupPrefixCommand(CounterManager counterManager, ConfigRepository configRepo) {
|
||||
this.counterManager = counterManager;
|
||||
this.configRepo = configRepo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "counter-setup";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Настройка счетчика участников";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "counter-setup <channel|format|info> [значение]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Настройка";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка админ прав
|
||||
if (!context.getAuthor().hasPermission(Permission.ADMINISTRATOR)) {
|
||||
context.getChannel().sendMessage("❌ У вас нет прав администратора!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> args = context.getArgs();
|
||||
if (args.isEmpty()) {
|
||||
sendHelp(context);
|
||||
return;
|
||||
}
|
||||
|
||||
String action = args.get(0).toLowerCase();
|
||||
|
||||
switch (action) {
|
||||
case "channel" -> handleChannel(context, args);
|
||||
case "format" -> handleFormat(context, args);
|
||||
case "info" -> handleInfo(context);
|
||||
default -> sendHelp(context);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleChannel(CommandContext context, List<String> args) {
|
||||
if (args.size() < 2) {
|
||||
context.getChannel().sendMessage(
|
||||
"❌ Укажите голосовой канал: `#канал`, `<#id>` или числовой id. Пример: `!counter-setup channel 123456789012345678`"
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
VoiceChannel channel = GuildChannelResolve.voice(context.getGuild(), context.getMessage(), args, 1);
|
||||
if (channel == null) {
|
||||
context.getChannel().sendMessage(
|
||||
"❌ Не найден голосовой канал. Укажите `#войс`, `<#id>` или id из 17-20 цифр."
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
configRepo.set("counter.channel_id", channel.getId());
|
||||
|
||||
// Сразу обновляем название канала
|
||||
int memberCount = context.getGuild().getMemberCount();
|
||||
String format = configRepo.get("counter.format");
|
||||
if (format == null) {
|
||||
format = "【👥】Участники: {count}";
|
||||
configRepo.set("counter.format", format);
|
||||
}
|
||||
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
channel.getManager().setName(newName).queue();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("✅ Канал счетчика установлен")
|
||||
.withDescription("Канал " + channel.getAsMention() + " теперь отображает количество участников")
|
||||
.addField("Текущее количество", String.valueOf(memberCount), false)
|
||||
.withColor(Color.GREEN)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private void handleFormat(CommandContext context, List<String> args) {
|
||||
if (args.size() < 2) {
|
||||
context.getChannel().sendMessage("❌ Укажите формат! Пример: `!counter-setup format 【👥】Участники: {count}`").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Объединяем все аргументы в одну строку
|
||||
String format = String.join(" ", args.subList(1, args.size()));
|
||||
|
||||
if (!format.contains("{count}")) {
|
||||
context.getChannel().sendMessage("❌ Формат должен содержать `{count}`!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
configRepo.set("counter.format", format);
|
||||
|
||||
// Обновляем канал если он установлен
|
||||
String channelId = configRepo.get("counter.channel_id");
|
||||
if (channelId != null) {
|
||||
VoiceChannel channel = context.getGuild().getVoiceChannelById(channelId);
|
||||
if (channel != null) {
|
||||
int memberCount = context.getGuild().getMemberCount();
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
channel.getManager().setName(newName).queue();
|
||||
}
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("✅ Формат обновлен")
|
||||
.withDescription("Новый формат: `" + format + "`")
|
||||
.addField("Пример", format.replace("{count}", "123"), false)
|
||||
.withColor(Color.GREEN)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private void handleInfo(CommandContext context) {
|
||||
String channelId = configRepo.get("counter.channel_id");
|
||||
String format = configRepo.get("counter.format");
|
||||
|
||||
var embed = EmbedFactory.create()
|
||||
.withTitle("📊 Конфигурация счетчика участников")
|
||||
.withColor(Color.BLUE);
|
||||
|
||||
if (channelId != null) {
|
||||
embed.addField("Канал", "<#" + channelId + ">", false);
|
||||
} else {
|
||||
embed.addField("Канал", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
if (format != null) {
|
||||
embed.addField("Формат", "`" + format + "`", false);
|
||||
embed.addField("Пример", format.replace("{count}", String.valueOf(context.getGuild().getMemberCount())), false);
|
||||
} else {
|
||||
embed.addField("Формат", "❌ Не установлен", false);
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
private void sendHelp(CommandContext context) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("📊 Настройка счетчика участников")
|
||||
.withDescription("""
|
||||
**Доступные команды:**
|
||||
• `!counter-setup channel #войс` или `id` - канал счётчика
|
||||
• `!counter-setup format 【👥】Участники: {count}` - Установить формат
|
||||
• `!counter-setup info` - Показать текущую конфигурацию
|
||||
|
||||
В формате используйте `{count}` для отображения количества.
|
||||
Только для администраторов!
|
||||
""")
|
||||
.withColor(Color.BLUE)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +1,66 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Дневная статистика участников
|
||||
*/
|
||||
public class DailyStats {
|
||||
private String date;
|
||||
private int joined;
|
||||
private int left;
|
||||
private int difference;
|
||||
|
||||
public DailyStats() {
|
||||
this.date = LocalDate.now().toString();
|
||||
this.joined = 0;
|
||||
this.left = 0;
|
||||
this.difference = 0;
|
||||
}
|
||||
|
||||
public DailyStats(String date, int joined, int left) {
|
||||
this.date = date;
|
||||
this.joined = joined;
|
||||
this.left = left;
|
||||
this.difference = joined - left;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getDate() { return date; }
|
||||
public int getJoined() { return joined; }
|
||||
public int getLeft() { return left; }
|
||||
public int getDifference() { return difference; }
|
||||
|
||||
// Setters
|
||||
public void setDate(String date) { this.date = date; }
|
||||
public void setJoined(int joined) {
|
||||
this.joined = joined;
|
||||
updateDifference();
|
||||
}
|
||||
public void setLeft(int left) {
|
||||
this.left = left;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
// Методы
|
||||
public void incrementJoined() {
|
||||
this.joined++;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
public void incrementLeft() {
|
||||
this.left++;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
private void updateDifference() {
|
||||
this.difference = this.joined - this.left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить является ли статистика сегодняшней
|
||||
*/
|
||||
public boolean isToday() {
|
||||
return this.date.equals(LocalDate.now().toString());
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.counter;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Дневная статистика участников
|
||||
*/
|
||||
public class DailyStats {
|
||||
private String date;
|
||||
private int joined;
|
||||
private int left;
|
||||
private int difference;
|
||||
|
||||
public DailyStats() {
|
||||
this.date = LocalDate.now().toString();
|
||||
this.joined = 0;
|
||||
this.left = 0;
|
||||
this.difference = 0;
|
||||
}
|
||||
|
||||
public DailyStats(String date, int joined, int left) {
|
||||
this.date = date;
|
||||
this.joined = joined;
|
||||
this.left = left;
|
||||
this.difference = joined - left;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public String getDate() { return date; }
|
||||
public int getJoined() { return joined; }
|
||||
public int getLeft() { return left; }
|
||||
public int getDifference() { return difference; }
|
||||
|
||||
// Setters
|
||||
public void setDate(String date) { this.date = date; }
|
||||
public void setJoined(int joined) {
|
||||
this.joined = joined;
|
||||
updateDifference();
|
||||
}
|
||||
public void setLeft(int left) {
|
||||
this.left = left;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
// Методы
|
||||
public void incrementJoined() {
|
||||
this.joined++;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
public void incrementLeft() {
|
||||
this.left++;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
private void updateDifference() {
|
||||
this.difference = this.joined - this.left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить является ли статистика сегодняшней
|
||||
*/
|
||||
public boolean isToday() {
|
||||
return this.date.equals(LocalDate.now().toString());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,86 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import creator.loki.feature.counter.CounterManager;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Listener для счетчика участников
|
||||
*/
|
||||
public class MemberCounterListener extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MemberCounterListener.class);
|
||||
private final CounterManager counterManager;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private Guild guild;
|
||||
|
||||
public MemberCounterListener(CounterManager counterManager) {
|
||||
this.counterManager = counterManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить периодическое обновление счетчика
|
||||
*/
|
||||
public void startScheduler(Guild guild) {
|
||||
this.guild = guild;
|
||||
|
||||
// Обновляем счетчик каждые 5 минут
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
counterManager.updateCounter(guild);
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при обновлении счетчика", e);
|
||||
}
|
||||
}, 0, 5, TimeUnit.MINUTES);
|
||||
|
||||
logger.info("Запущен планировщик обновления счетчика участников");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberJoin(GuildMemberJoinEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
counterManager.recordJoin();
|
||||
counterManager.updateCounter(event.getGuild());
|
||||
|
||||
logger.info("Участник {} присоединился к серверу", event.getUser().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberRemove(GuildMemberRemoveEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
counterManager.recordLeave();
|
||||
counterManager.updateCounter(event.getGuild());
|
||||
|
||||
logger.info("Участник {} покинул сервер", event.getUser().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Остановить планировщик
|
||||
*/
|
||||
public void shutdown() {
|
||||
scheduler.shutdown();
|
||||
try {
|
||||
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
scheduler.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.counter;
|
||||
|
||||
import creator.loki.feature.counter.CounterManager;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent;
|
||||
import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Listener для счетчика участников
|
||||
*/
|
||||
public class MemberCounterListener extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MemberCounterListener.class);
|
||||
private final CounterManager counterManager;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private Guild guild;
|
||||
|
||||
public MemberCounterListener(CounterManager counterManager) {
|
||||
this.counterManager = counterManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить периодическое обновление счетчика
|
||||
*/
|
||||
public void startScheduler(Guild guild) {
|
||||
this.guild = guild;
|
||||
|
||||
// Обновляем счетчик каждые 5 минут
|
||||
scheduler.scheduleAtFixedRate(() -> Thread.startVirtualThread(() -> {
|
||||
try {
|
||||
counterManager.updateCounter(guild);
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при обновлении счетчика", e);
|
||||
}
|
||||
}), 0, 5, TimeUnit.MINUTES);
|
||||
|
||||
logger.info("Запущен планировщик обновления счетчика участников");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberJoin(GuildMemberJoinEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
counterManager.recordJoin();
|
||||
counterManager.updateCounter(event.getGuild());
|
||||
|
||||
logger.info("Участник {} присоединился к серверу", event.getUser().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberRemove(GuildMemberRemoveEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
counterManager.recordLeave();
|
||||
counterManager.updateCounter(event.getGuild());
|
||||
|
||||
logger.info("Участник {} покинул сервер", event.getUser().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Остановить планировщик
|
||||
*/
|
||||
public void shutdown() {
|
||||
scheduler.shutdown();
|
||||
try {
|
||||
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
scheduler.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
package creator.loki.feature.counter.repository;
|
||||
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.feature.counter.DailyStats;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Репозиторий для работы со статистикой счетчика участников
|
||||
*/
|
||||
public class CounterStatsRepository {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CounterStatsRepository.class);
|
||||
private final Database database;
|
||||
|
||||
public CounterStatsRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статистику за дату
|
||||
*/
|
||||
public DailyStats getStats(String date) {
|
||||
String sql = "SELECT * FROM counter_stats WHERE date = ?";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setString(1, date);
|
||||
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
DailyStats stats = new DailyStats();
|
||||
stats.setDate(rs.getString("date"));
|
||||
stats.setJoined(rs.getInt("joined"));
|
||||
stats.setLeft(rs.getInt("left_count"));
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения статистики за {}", date, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статистику за сегодня
|
||||
*/
|
||||
public DailyStats getTodayStats() {
|
||||
String today = LocalDate.now().toString();
|
||||
DailyStats stats = getStats(today);
|
||||
|
||||
if (stats == null) {
|
||||
stats = new DailyStats();
|
||||
stats.setDate(today);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить статистику
|
||||
*/
|
||||
public void saveStats(DailyStats stats) {
|
||||
String sql = """
|
||||
INSERT INTO counter_stats (date, joined, left_count, difference)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(date)
|
||||
DO UPDATE SET
|
||||
joined = excluded.joined,
|
||||
left_count = excluded.left_count,
|
||||
difference = excluded.difference
|
||||
""";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setString(1, stats.getDate());
|
||||
stmt.setInt(2, stats.getJoined());
|
||||
stmt.setInt(3, stats.getLeft());
|
||||
stmt.setInt(4, stats.getDifference());
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка сохранения статистики за {}", stats.getDate(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить историю за последние N дней
|
||||
*/
|
||||
public List<DailyStats> getHistory(int days) {
|
||||
List<DailyStats> history = new ArrayList<>();
|
||||
String sql = "SELECT * FROM counter_stats ORDER BY date DESC LIMIT ?";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setInt(1, days);
|
||||
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
DailyStats stats = new DailyStats();
|
||||
stats.setDate(rs.getString("date"));
|
||||
stats.setJoined(rs.getInt("joined"));
|
||||
stats.setLeft(rs.getInt("left_count"));
|
||||
history.add(stats);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения истории", e);
|
||||
}
|
||||
|
||||
java.util.Collections.reverse(history);
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить старую статистику (старше N дней)
|
||||
*/
|
||||
public void cleanOldStats(int daysToKeep) {
|
||||
String sql = """
|
||||
DELETE FROM counter_stats
|
||||
WHERE date < date('now', '-' || ? || ' days')
|
||||
""";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setInt(1, daysToKeep);
|
||||
int deleted = stmt.executeUpdate();
|
||||
if (deleted > 0) {
|
||||
logger.info("Удалено {} старых записей статистики", deleted);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка очистки старой статистики", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package creator.loki.feature.economy;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import creator.loki.core.registry.CommandRegistrar;
|
||||
import creator.loki.core.registry.CommandRegistry;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.feature.economy.command.bank.BankCommand;
|
||||
import creator.loki.feature.economy.command.core.BalanceCommand;
|
||||
import creator.loki.feature.economy.command.core.BaltopCommand;
|
||||
import creator.loki.feature.economy.command.core.DailyCommand;
|
||||
import creator.loki.feature.economy.command.core.PayCommand;
|
||||
import creator.loki.feature.economy.command.minigame.BlackjackCommand;
|
||||
import creator.loki.feature.economy.command.minigame.FishCommand;
|
||||
import creator.loki.feature.economy.command.minigame.JobCommand;
|
||||
import creator.loki.feature.economy.command.minigame.SlotCommand;
|
||||
import creator.loki.feature.economy.command.minigame.WorkCommand;
|
||||
import creator.loki.feature.economy.command.social.RobCommand;
|
||||
import creator.loki.feature.economy.service.BlackjackService;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.feature.economy.service.FishingService;
|
||||
import creator.loki.feature.economy.service.JobService;
|
||||
import creator.loki.feature.economy.service.SlotService;
|
||||
|
||||
public class EconomyCommandRegistrar implements CommandRegistrar {
|
||||
private final EconomyService economyService;
|
||||
private final FishingService fishingService;
|
||||
private final BlackjackService blackjackService;
|
||||
private final SlotService slotService;
|
||||
private final JobService jobService;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
@Inject
|
||||
public EconomyCommandRegistrar(
|
||||
EconomyService economyService,
|
||||
FishingService fishingService,
|
||||
BlackjackService blackjackService,
|
||||
SlotService slotService,
|
||||
JobService jobService,
|
||||
CooldownManager cooldownManager
|
||||
) {
|
||||
this.economyService = economyService;
|
||||
this.fishingService = fishingService;
|
||||
this.blackjackService = blackjackService;
|
||||
this.slotService = slotService;
|
||||
this.jobService = jobService;
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(CommandRegistry registry) {
|
||||
registry.register(new BalanceCommand(economyService));
|
||||
registry.register(new BaltopCommand(economyService));
|
||||
registry.register(new PayCommand(economyService));
|
||||
registry.register(new DailyCommand(economyService));
|
||||
registry.register(new BankCommand(economyService));
|
||||
registry.register(new RobCommand(economyService, cooldownManager));
|
||||
registry.register(new FishCommand(fishingService));
|
||||
registry.register(new BlackjackCommand(blackjackService, economyService));
|
||||
registry.register(new SlotCommand(slotService, economyService));
|
||||
registry.register(new JobCommand(jobService));
|
||||
registry.register(new WorkCommand(jobService));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package creator.loki.feature.economy;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Provides;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import creator.loki.feature.economy.repository.SqlEconomyRepository;
|
||||
import creator.loki.feature.economy.service.BlackjackService;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.feature.economy.service.FishingService;
|
||||
import creator.loki.feature.economy.service.JobService;
|
||||
import creator.loki.feature.economy.service.SlotService;
|
||||
|
||||
public class EconomyModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
EconomyRepository provideEconomyRepository(Database database) {
|
||||
return new SqlEconomyRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
EconomyService provideEconomyService(EconomyRepository repository, CooldownManager cooldownManager) {
|
||||
return new EconomyService(repository, cooldownManager);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
FishingService provideFishingService(EconomyRepository repository) {
|
||||
return new FishingService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
BlackjackService provideBlackjackService(EconomyRepository repository) {
|
||||
return new BlackjackService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SlotService provideSlotService(EconomyRepository repository) {
|
||||
return new SlotService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
JobService provideJobService(EconomyRepository repository) {
|
||||
return new JobService(repository);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package creator.loki.feature.economy.command.bank;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
public class BankCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
|
||||
public BankCommand(EconomyService economyService) {
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() { return "bank"; }
|
||||
|
||||
@Override
|
||||
public String getDescription() { return "Управление банковским счётом"; }
|
||||
|
||||
@Override
|
||||
public String getUsage() { return "bank [deposit|withdraw|balance] <amount>"; }
|
||||
|
||||
@Override
|
||||
public String getCategory() { return "Экономика"; }
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext ctx) {
|
||||
List<String> args = ctx.getArgs();
|
||||
String uid = ctx.getAuthor().getId();
|
||||
if (args.isEmpty() || args.get(0).equalsIgnoreCase("balance")) {
|
||||
EconomyAccount acc = economyService.getAccount(uid);
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🏦 Банк — " + ctx.getAuthor().getEffectiveName())
|
||||
.withColor(new Color(52, 152, 219))
|
||||
.addField("Наличные", "**" + acc.getCash() + "** LC", true)
|
||||
.addField("Банк", "**" + acc.getBank() + "** LC", true)
|
||||
.addField("Всего", "**" + acc.getTotalBalance() + "** LC", true)
|
||||
.build()
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
String sub = args.get(0).toLowerCase();
|
||||
if (!sub.equals("deposit") && !sub.equals("withdraw")) {
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Неизвестная подкоманда. Используй: " + getUsage())
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
if (args.size() < 2) {
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажите сумму: bank " + sub + " <amount>")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
long amount;
|
||||
try {
|
||||
amount = Long.parseLong(args.get(1));
|
||||
} catch (NumberFormatException e) {
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Сумма должна быть числом!")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
if (amount <= 0) {
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Сумма должна быть положительной!")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
EconomyAccount acc = economyService.getAccount(uid);
|
||||
boolean isDep = sub.equals("deposit");
|
||||
long balance = isDep ? acc.getCash() : acc.getBank();
|
||||
if (balance < amount) {
|
||||
ctx.getChannel().sendMessageEmbeds(EmbedTemplates.error("Ошибка",
|
||||
"Недостаточно " + (isDep ? "наличных" : "в банке") + "! У вас **" + balance + "** LC")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
if (isDep) economyService.deposit(uid, amount);
|
||||
else economyService.withdraw(uid, amount);
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success(isDep ? "Депозит" : "Снятие",
|
||||
"Ты " + (isDep ? "положил" : "снял") + " **" + amount + "** LC " + (isDep ? "в банк!" : "из банка!"))
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package creator.loki.feature.economy.command.core;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
public class BalanceCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
|
||||
public BalanceCommand(EconomyService economyService) {
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "balance";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показать баланс пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "balance [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
EconomyAccount account = economyService.getAccount(target.getId());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("💰 Баланс — " + target.getEffectiveName())
|
||||
.withColor(new Color(255, 215, 0))
|
||||
.addField("Наличные", "**" + account.getCash() + "** LC", true)
|
||||
.addField("Банк", "**" + account.getBank() + "** LC", true)
|
||||
.addField("Всего", "**" + account.getTotalBalance() + "** LC", true)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package creator.loki.feature.economy.command.core;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
public class BaltopCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
|
||||
public BaltopCommand(EconomyService economyService) {
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "baltop";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Топ 10 богатейших пользователей";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<EconomyAccount> top = economyService.getTopBalances(10);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < top.size(); i++) {
|
||||
EconomyAccount acc = top.get(i);
|
||||
String name = context.getGuild().getMemberById(acc.getUserId()) != null
|
||||
? context.getGuild().getMemberById(acc.getUserId()).getEffectiveName()
|
||||
: acc.getUserId();
|
||||
sb.append("**").append(i + 1).append(".** ")
|
||||
.append(name)
|
||||
.append(" — **").append(acc.getTotalBalance()).append("** LC\n");
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🏆 Топ богатейших")
|
||||
.withDescription(sb.toString())
|
||||
.withColor(new Color(255, 215, 0))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package creator.loki.feature.economy.command.core;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class DailyCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
|
||||
public DailyCommand(EconomyService economyService) {
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "daily";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Получить ежедневную награду";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
EconomyService.DailyResult result = economyService.claimDaily(context.getAuthor().getId());
|
||||
|
||||
if (result == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.warning("Ежедневная награда",
|
||||
"Ты уже получил награду сегодня! Возвращайся завтра.")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎁 Ежедневная награда")
|
||||
.withDescription("Ты получил **" + result.amount() + "** LC!")
|
||||
.withColor(new Color(46, 204, 113))
|
||||
.addField("Стрик", "**" + result.streak() + "** дней", true)
|
||||
.addField("Баланс", "**" + result.newCash() + "** LC", true)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package creator.loki.feature.economy.command.core;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PayCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
|
||||
public PayCommand(EconomyService economyService) {
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "pay";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Перевести деньги другому пользователю";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "pay <@user> <amount>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажите пользователя: " + getUsage())
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
Member target = mentioned.get(0);
|
||||
if (target.getId().equals(context.getAuthor().getId())) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Нельзя переводить деньги самому себе!")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> args = context.getArgs();
|
||||
if (args.size() < 2) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажите сумму: " + getUsage())
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
long amount;
|
||||
try {
|
||||
amount = Long.parseLong(args.get(1));
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Сумма должна быть числом!")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Сумма должна быть положительной!")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
var senderAccount = economyService.getAccount(context.getAuthor().getId());
|
||||
if (senderAccount.getCash() < amount) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Недостаточно средств! У вас **" + senderAccount.getCash() + "** LC")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
economyService.transfer(context.getAuthor().getId(), target.getId(), amount);
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success("Перевод выполнен",
|
||||
context.getAuthor().getAsMention() + " перевёл **" + amount + "** LC " + target.getAsMention())
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package creator.loki.feature.economy.command.minigame;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.service.BlackjackService;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class BlackjackCommand implements Command {
|
||||
|
||||
private final BlackjackService blackjackService;
|
||||
private final EconomyService economyService;
|
||||
private final Set<String> activeGames = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public BlackjackCommand(BlackjackService blackjackService, EconomyService economyService) {
|
||||
this.blackjackService = blackjackService;
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "blackjack";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Блэкджек — играй против дилера";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "bj <ставка> — начать игру\nbj hit — взять карту\nbj stand — остановиться";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var args = context.getArgs();
|
||||
var userId = context.getAuthor().getId();
|
||||
if (args.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.warning("Блэкджек", "Используй: `!bj <ставка>` чтобы начать")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
var action = args.get(0).toLowerCase();
|
||||
if (action.equals("hit")) {
|
||||
handleAction(context, userId, true);
|
||||
} else if (action.equals("stand")) {
|
||||
handleAction(context, userId, false);
|
||||
} else {
|
||||
handleStart(context, userId, action);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleStart(CommandContext context, String userId, String betStr) {
|
||||
if (activeGames.contains(userId)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "У тебя уже есть активная игра! Используй `!bj hit` или `!bj stand`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
long bet;
|
||||
try {
|
||||
bet = Long.parseLong(betStr);
|
||||
if (bet <= 0) throw new NumberFormatException();
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажи положительное число в качестве ставки")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
var account = economyService.getAccount(userId);
|
||||
if (account.getCash() < bet) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Недостаточно средств! Твой баланс: " + account.getCash() + " LC")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
BlackjackService.BlackjackState state = blackjackService.startGame(userId, bet);
|
||||
if (state == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Не удалось начать игру.")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
if (!state.isOver()) activeGames.add(userId);
|
||||
context.getChannel().sendMessageEmbeds(buildGameEmbed(state)).queue();
|
||||
}
|
||||
|
||||
private void handleAction(CommandContext context, String userId, boolean isHit) {
|
||||
if (!activeGames.contains(userId)) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Нет активной игры! Начни с `!bj <ставка>`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
BlackjackService.BlackjackState state = isHit
|
||||
? blackjackService.hit(userId)
|
||||
: blackjackService.stand(userId);
|
||||
if (state.isOver()) activeGames.remove(userId);
|
||||
context.getChannel().sendMessageEmbeds(buildGameEmbed(state)).queue();
|
||||
}
|
||||
|
||||
private MessageEmbed buildGameEmbed(BlackjackService.BlackjackState state) {
|
||||
var embed = EmbedFactory.create()
|
||||
.withTitle("🃏 Блэкджек")
|
||||
.withColor(new Color(34, 139, 34))
|
||||
.addField("Твои карты", formatCards(state.playerCards(), state.playerSum()), true);
|
||||
if (state.isOver()) {
|
||||
embed.addField("Карты дилера", formatCards(state.dealerCards(), state.dealerSum()), true)
|
||||
.addField("Результат", formatResult(state), false);
|
||||
} else {
|
||||
embed.addField("Карты дилера", formatHiddenDealerCards(state.dealerCards()), true)
|
||||
.withFooter("!bj hit — взять карту | !bj stand — остановиться");
|
||||
}
|
||||
return embed.addField("Ставка", state.bet() + " LC", false).build();
|
||||
}
|
||||
|
||||
private String formatCards(String[] cards, int sum) {
|
||||
return String.join(" + ", cards) + " = " + sum;
|
||||
}
|
||||
|
||||
private String formatHiddenDealerCards(String[] cards) {
|
||||
var sb = new StringBuilder("?");
|
||||
for (int i = 1; i < cards.length; i++) sb.append(" + ").append(cards[i]);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String formatResult(BlackjackService.BlackjackState state) {
|
||||
long payout = state.payout();
|
||||
if (payout > state.bet()) return "🎉 Блэкджек! +" + payout + " LC";
|
||||
if (payout > 0) return "✅ Ты выиграл +" + payout + " LC";
|
||||
if (payout == state.bet()) return "🤝 Ничья. Возврат " + payout + " LC";
|
||||
return "💔 Ты проиграл " + state.bet() + " LC";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package creator.loki.feature.economy.command.minigame;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.service.FishingService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class FishCommand implements Command {
|
||||
|
||||
private static record FishSession(String difficulty, long startTime) {}
|
||||
|
||||
private final FishingService fishingService;
|
||||
private final ConcurrentHashMap<String, FishSession> sessions = new ConcurrentHashMap<>();
|
||||
|
||||
public FishCommand(FishingService fishingService) {
|
||||
this.fishingService = fishingService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "fish";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Рыбалка — угадай количество рыбы в пруду";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "fish <easy/normal/hard> — начать рыбалку\nfish <число> — ответить";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var args = context.getArgs();
|
||||
var userId = context.getAuthor().getId();
|
||||
|
||||
if (args.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.warning("Рыбалка", "Используй: `!fish easy/normal/hard`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
var first = args.get(0).toLowerCase();
|
||||
|
||||
if (first.equals("easy") || first.equals("normal") || first.equals("hard")) {
|
||||
FishingService.FishResult result = fishingService.startFishing(userId, first);
|
||||
sessions.put(userId, new FishSession(first, System.currentTimeMillis()));
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎣 Рыбалка")
|
||||
.withDescription("```\n" + String.join("\n", result.grid()) + "\n```")
|
||||
.withColor(new Color(0, 150, 255))
|
||||
.addField("Вопрос", "Сколько **" + result.targetFish() + "** ты видишь?", false)
|
||||
.withFooter("Напиши !fish <число>, чтобы ответить")
|
||||
.build()
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int answer = Integer.parseInt(first);
|
||||
FishSession session = sessions.remove(userId);
|
||||
if (session == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Нет активной рыбалки! Начни с `!fish easy/normal/hard`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
FishingService.FishResult result = fishingService.checkAnswer(userId, answer, session.startTime(), session.difficulty());
|
||||
var embed = EmbedFactory.create()
|
||||
.withTitle("🎣 Рыбалка — Результат")
|
||||
.withDescription("```\n" + String.join("\n", result.grid()) + "\n```")
|
||||
.withColor(new Color(255, 215, 0));
|
||||
|
||||
if (result.reward() > 0) {
|
||||
embed.addField("Твой ответ", String.valueOf(answer), true)
|
||||
.addField("Правильный ответ", String.valueOf(result.count()), true)
|
||||
.addField("Награда", "+" + result.reward() + " LC", true)
|
||||
.addField("Новый баланс", result.cashAfter() + " LC", true);
|
||||
} else {
|
||||
embed.withDescription("❌ Неверно! Правильный ответ: **" + result.count() + "**\n\n```\n"
|
||||
+ String.join("\n", result.grid()) + "\n```");
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(embed.build()).queue();
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Неверный аргумент. Используй `!fish easy/normal/hard` или `!fish <число>`")
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
package creator.loki.feature.economy.command.minigame;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.service.JobService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class JobCommand implements Command {
|
||||
|
||||
private final JobService jobService;
|
||||
|
||||
public JobCommand(JobService jobService) {
|
||||
this.jobService = jobService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "job";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Управление работой — список, присоединиться";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "job — текущая работа\njob list — список работ\njob join <название> — устроиться";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var args = context.getArgs();
|
||||
var userId = context.getAuthor().getId();
|
||||
|
||||
if (args.isEmpty()) {
|
||||
handleCurrent(context, userId);
|
||||
} else if (args.get(0).equalsIgnoreCase("list")) {
|
||||
handleList(context);
|
||||
} else if (args.get(0).equalsIgnoreCase("join")) {
|
||||
if (args.size() < 2) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажи название работы: `!job join <название>`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
handleJoin(context, userId, args.get(1));
|
||||
} else {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.warning("Работа", "Используй: `!job`, `!job list`, `!job join <название>`")
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCurrent(CommandContext context, String userId) {
|
||||
String current = jobService.getCurrentJob(userId);
|
||||
if (current == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.info("Работа", "У тебя нет работы. Используй `!job list` чтобы посмотреть доступные.")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
JobService.JobInfo info = findJob(current);
|
||||
if (info == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.warning("Работа", "Текущая работа: **" + current + "**")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("💼 Твоя работа")
|
||||
.withColor(new Color(100, 200, 255))
|
||||
.addField("Должность", info.emoji() + " " + info.displayName(), false)
|
||||
.addField("Зарплата", info.minPayout() + " – " + info.maxPayout() + " LC", true)
|
||||
.addField("Требуемый уровень", String.valueOf(info.requiredLevel()), true)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private void handleList(CommandContext context) {
|
||||
var jobs = jobService.listJobs();
|
||||
var desc = new StringBuilder();
|
||||
|
||||
for (var job : jobs) {
|
||||
desc.append(job.emoji()).append(" **").append(job.displayName())
|
||||
.append("** — ").append(job.minPayout()).append("–").append(job.maxPayout())
|
||||
.append(" LC (ур. ").append(job.requiredLevel()).append(")\n");
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("📋 Доступные работы")
|
||||
.withDescription(desc.toString())
|
||||
.withColor(new Color(100, 200, 255))
|
||||
.withFooter("!job join <название> — устроиться на работу")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private void handleJoin(CommandContext context, String userId, String name) {
|
||||
var jobs = jobService.listJobs();
|
||||
JobService.JobInfo target = null;
|
||||
|
||||
for (var job : jobs) {
|
||||
if (job.name().equalsIgnoreCase(name) || job.displayName().equalsIgnoreCase(name)) {
|
||||
target = job;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Работа **" + name + "** не найдена. Используй `!job list`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
jobService.setJob(userId, target.name());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success("Устройство на работу",
|
||||
"Ты устроился на работу **" + target.emoji() + " " + target.displayName() + "**!\n"
|
||||
+ "Используй `!work` чтобы начать работать.")
|
||||
).queue();
|
||||
}
|
||||
|
||||
private JobService.JobInfo findJob(String name) {
|
||||
for (var job : jobService.listJobs()) {
|
||||
if (job.name().equalsIgnoreCase(name) || job.displayName().equalsIgnoreCase(name)) {
|
||||
return job;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package creator.loki.feature.economy.command.minigame;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.feature.economy.service.SlotService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class SlotCommand implements Command {
|
||||
|
||||
private final SlotService slotService;
|
||||
private final EconomyService economyService;
|
||||
|
||||
public SlotCommand(SlotService slotService, EconomyService economyService) {
|
||||
this.slotService = slotService;
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "slot";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Крути слот-машину и выигрывай";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "slot <ставка>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var args = context.getArgs();
|
||||
var userId = context.getAuthor().getId();
|
||||
|
||||
if (args.isEmpty()) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.warning("Слоты", "Используй: `!slot <ставка>`")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
long bet;
|
||||
try {
|
||||
bet = Long.parseLong(args.get(0));
|
||||
if (bet <= 0) throw new NumberFormatException();
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Ставка должна быть положительным числом!")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
var account = economyService.getAccount(userId);
|
||||
if (account.getCash() < bet) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Недостаточно средств! Твой баланс: " + account.getCash() + " LC")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
SlotService.SlotResult result = slotService.spin(userId, bet);
|
||||
|
||||
var comboDisplay = result.emoji1() + " | " + result.emoji2() + " | " + result.emoji3();
|
||||
var desc = new StringBuilder("**" + comboDisplay + "**\n");
|
||||
|
||||
if (result.multiplier() > 0) {
|
||||
desc.append("\n🎰 **").append(result.comboName()).append("!**")
|
||||
.append(" x").append(result.multiplier())
|
||||
.append("\n\n+**").append(result.winAmount()).append("** LC");
|
||||
} else {
|
||||
desc.append("\n\n😔 Повезёт в следующий раз!");
|
||||
}
|
||||
|
||||
desc.append("\n\nБаланс: **").append(result.cashAfter()).append("** LC");
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎰 Слот-машина")
|
||||
.withDescription(desc.toString())
|
||||
.withColor(new Color(255, 215, 0))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package creator.loki.feature.economy.command.minigame;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.economy.service.JobService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class WorkCommand implements Command {
|
||||
|
||||
private final JobService jobService;
|
||||
|
||||
public WorkCommand(JobService jobService) {
|
||||
this.jobService = jobService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "work";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Работать и зарабатывать деньги";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var userId = context.getAuthor().getId();
|
||||
|
||||
String currentJob = jobService.getCurrentJob(userId);
|
||||
if (currentJob == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.warning("Работа", "У тебя нет работы! Используй `!job list` чтобы выбрать.")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
JobService.JobInfo info = findJob(currentJob);
|
||||
JobService.WorkResult result = jobService.work(userId, 0);
|
||||
|
||||
if (result == null) {
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "⏳ Ты уже работал сегодня! Отдохни и возвращайся завтра.")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String emoji = info != null ? info.emoji() : "💼";
|
||||
String jobName = info != null ? info.displayName() : currentJob;
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("💼 Работа завершена")
|
||||
.withColor(new Color(100, 200, 255))
|
||||
.addField("Должность", emoji + " " + jobName, false)
|
||||
.addField("Заработано", "+**" + result.amount() + "** LC", true)
|
||||
.addField("Новый баланс", "**" + result.newCash() + "** LC", true)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private JobService.JobInfo findJob(String name) {
|
||||
for (var job : jobService.listJobs()) {
|
||||
if (job.name().equalsIgnoreCase(name) || job.displayName().equalsIgnoreCase(name)) {
|
||||
return job;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package creator.loki.feature.economy.command.social;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class RobCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
private final CooldownManager cooldownManager;
|
||||
private final Random random;
|
||||
|
||||
public RobCommand(EconomyService economyService, CooldownManager cooldownManager) {
|
||||
this.economyService = economyService;
|
||||
this.cooldownManager = cooldownManager;
|
||||
this.random = ThreadLocalRandom.current();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() { return "rob"; }
|
||||
|
||||
@Override
|
||||
public String getDescription() { return "Ограбить пользователя"; }
|
||||
|
||||
@Override
|
||||
public String getUsage() { return "rob <@user>"; }
|
||||
|
||||
@Override
|
||||
public String getCategory() { return "Экономика"; }
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext ctx) {
|
||||
List<Member> mentioned = ctx.getMessage().getMentions().getMembers();
|
||||
if (mentioned.isEmpty()) {
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажите пользователя: " + getUsage())
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
Member target = mentioned.get(0);
|
||||
String uid = ctx.getAuthor().getId();
|
||||
String tid = target.getId();
|
||||
if (tid.equals(uid)) {
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Нельзя ограбить самого себя!")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
if (cooldownManager.isOnCooldown(uid, "rob")) {
|
||||
long rem = cooldownManager.getRemainingSeconds(uid, "rob");
|
||||
ctx.getChannel().sendMessageEmbeds(EmbedTemplates.warning("Кдн",
|
||||
"Подожди ещё **" + (rem / 3600) + "ч " + ((rem % 3600) / 60) + "м**")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
cooldownManager.applyCooldown(uid, "rob", 86400);
|
||||
var sender = economyService.getAccount(uid);
|
||||
var victim = economyService.getAccount(tid);
|
||||
if (sender.getCash() <= 0 || victim.getCash() <= 0) {
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "У одного из вас нет наличных!")
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
if (random.nextDouble() < 0.4) {
|
||||
long steal = Math.min(Math.min((long) (victim.getCash() * 0.15), 10000L), victim.getCash());
|
||||
if (steal <= 0) {
|
||||
ctx.getChannel().sendMessageEmbeds(EmbedTemplates.info("Ограбление", "Слишком мелкая цель...")).queue();
|
||||
return;
|
||||
}
|
||||
economyService.transfer(tid, uid, steal);
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🔫 Ограбление удалось!")
|
||||
.withDescription("Ты украл **" + steal + "** LC у " + target.getAsMention())
|
||||
.withColor(new Color(46, 204, 113))
|
||||
.build()
|
||||
).queue();
|
||||
} else {
|
||||
long penalty = Math.min(Math.min((long) (sender.getCash() * 0.10), 5000L), sender.getCash());
|
||||
if (penalty > 0) economyService.transfer(uid, tid, penalty);
|
||||
ctx.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("👮 Ограбление провалено!")
|
||||
.withDescription("Ты заплатил **" + penalty + "** LC " + target.getAsMention())
|
||||
.withColor(new Color(231, 76, 60))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
package creator.loki.feature.economy.domain;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class EconomyAccount {
|
||||
private final String userId;
|
||||
private final long cash;
|
||||
private final long bank;
|
||||
private final long totalEarned;
|
||||
private final long totalSpent;
|
||||
private final String lastDaily;
|
||||
private final int dailyStreak;
|
||||
private final int fishingLuck;
|
||||
private final int efficiency;
|
||||
private final String lastFish;
|
||||
private final String job;
|
||||
private final String lastWork;
|
||||
|
||||
public EconomyAccount(String userId) {
|
||||
this.userId = userId;
|
||||
this.cash = 0;
|
||||
this.bank = 0;
|
||||
this.totalEarned = 0;
|
||||
this.totalSpent = 0;
|
||||
this.lastDaily = null;
|
||||
this.dailyStreak = 0;
|
||||
this.fishingLuck = 0;
|
||||
this.efficiency = 0;
|
||||
this.lastFish = null;
|
||||
this.job = null;
|
||||
this.lastWork = null;
|
||||
}
|
||||
|
||||
private EconomyAccount(Builder builder) {
|
||||
this.userId = builder.userId;
|
||||
this.cash = builder.cash;
|
||||
this.bank = builder.bank;
|
||||
this.totalEarned = builder.totalEarned;
|
||||
this.totalSpent = builder.totalSpent;
|
||||
this.lastDaily = builder.lastDaily;
|
||||
this.dailyStreak = builder.dailyStreak;
|
||||
this.fishingLuck = builder.fishingLuck;
|
||||
this.efficiency = builder.efficiency;
|
||||
this.lastFish = builder.lastFish;
|
||||
this.job = builder.job;
|
||||
this.lastWork = builder.lastWork;
|
||||
}
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public long getCash() { return cash; }
|
||||
public long getBank() { return bank; }
|
||||
public long getTotalEarned() { return totalEarned; }
|
||||
public long getTotalSpent() { return totalSpent; }
|
||||
public String getLastDaily() { return lastDaily; }
|
||||
public int getDailyStreak() { return dailyStreak; }
|
||||
public int getFishingLuck() { return fishingLuck; }
|
||||
public int getEfficiency() { return efficiency; }
|
||||
public String getLastFish() { return lastFish; }
|
||||
public String getJob() { return job; }
|
||||
public String getLastWork() { return lastWork; }
|
||||
|
||||
public long getTotalBalance() { return cash + bank; }
|
||||
|
||||
public EconomyAccount withCash(long cash) {
|
||||
return new Builder(this).cash(cash).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withBank(long bank) {
|
||||
return new Builder(this).bank(bank).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withAddedEarned(long amount) {
|
||||
return new Builder(this).totalEarned(this.totalEarned + amount).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withAddedSpent(long amount) {
|
||||
return new Builder(this).totalSpent(this.totalSpent + amount).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withFishingLuck(int level) {
|
||||
return new Builder(this).fishingLuck(level).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withEfficiency(int level) {
|
||||
return new Builder(this).efficiency(level).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
EconomyAccount that = (EconomyAccount) o;
|
||||
return Objects.equals(userId, that.userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() { return Objects.hash(userId); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("EconomyAccount[%s: Cash %d, Bank %d]",
|
||||
userId, cash, bank);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String userId;
|
||||
private long cash;
|
||||
private long bank;
|
||||
private long totalEarned;
|
||||
private long totalSpent;
|
||||
private String lastDaily;
|
||||
private int dailyStreak;
|
||||
private int fishingLuck;
|
||||
private int efficiency;
|
||||
private String lastFish;
|
||||
private String job;
|
||||
private String lastWork;
|
||||
|
||||
public Builder() {}
|
||||
|
||||
public Builder(EconomyAccount existing) {
|
||||
this.userId = existing.userId;
|
||||
this.cash = existing.cash;
|
||||
this.bank = existing.bank;
|
||||
this.totalEarned = existing.totalEarned;
|
||||
this.totalSpent = existing.totalSpent;
|
||||
this.lastDaily = existing.lastDaily;
|
||||
this.dailyStreak = existing.dailyStreak;
|
||||
this.fishingLuck = existing.fishingLuck;
|
||||
this.efficiency = existing.efficiency;
|
||||
this.lastFish = existing.lastFish;
|
||||
this.job = existing.job;
|
||||
this.lastWork = existing.lastWork;
|
||||
}
|
||||
|
||||
public Builder userId(String userId) { this.userId = userId; return this; }
|
||||
public Builder cash(long cash) { this.cash = cash; return this; }
|
||||
public Builder bank(long bank) { this.bank = bank; return this; }
|
||||
public Builder totalEarned(long totalEarned) { this.totalEarned = totalEarned; return this; }
|
||||
public Builder totalSpent(long totalSpent) { this.totalSpent = totalSpent; return this; }
|
||||
public Builder lastDaily(String lastDaily) { this.lastDaily = lastDaily; return this; }
|
||||
public Builder dailyStreak(int dailyStreak) { this.dailyStreak = dailyStreak; return this; }
|
||||
public Builder fishingLuck(int fishingLuck) { this.fishingLuck = fishingLuck; return this; }
|
||||
public Builder efficiency(int efficiency) { this.efficiency = efficiency; return this; }
|
||||
public Builder lastFish(String lastFish) { this.lastFish = lastFish; return this; }
|
||||
public Builder job(String job) { this.job = job; return this; }
|
||||
public Builder lastWork(String lastWork) { this.lastWork = lastWork; return this; }
|
||||
|
||||
public EconomyAccount build() { return new EconomyAccount(this); }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package creator.loki.feature.economy.domain;
|
||||
|
||||
public record EconomyTransaction(
|
||||
String fromId,
|
||||
String toId,
|
||||
long amount,
|
||||
String type,
|
||||
long timestamp
|
||||
) {}
|
||||
39
src/main/java/creator/loki/feature/economy/domain/Job.java
Normal file
39
src/main/java/creator/loki/feature/economy/domain/Job.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package creator.loki.feature.economy.domain;
|
||||
|
||||
public enum Job {
|
||||
WOODCUTTER("Лесоруб", 0, 15, 30, "\uD83E\uDE93"),
|
||||
MINER("Шахтёр", 0, 20, 40, "\u26CF\uFE0F"),
|
||||
FISHERMAN("Рыбак", 0, 25, 50, "\uD83C\uDFA3"),
|
||||
COOK("Повар", 15, 30, 60, "\uD83C\uDF73"),
|
||||
PROGRAMMER("Программист", 20, 40, 80, "\uD83D\uDCBB"),
|
||||
ASTRONAUT("Космонавт", 35, 50, 100, "\uD83E\uDEB8");
|
||||
|
||||
private final String displayName;
|
||||
private final int requiredLevel;
|
||||
private final int minPayout;
|
||||
private final int maxPayout;
|
||||
private final String emoji;
|
||||
|
||||
Job(String displayName, int requiredLevel, int minPayout, int maxPayout, String emoji) {
|
||||
this.displayName = displayName;
|
||||
this.requiredLevel = requiredLevel;
|
||||
this.minPayout = minPayout;
|
||||
this.maxPayout = maxPayout;
|
||||
this.emoji = emoji;
|
||||
}
|
||||
|
||||
public String getDisplayName() { return displayName; }
|
||||
public int getRequiredLevel() { return requiredLevel; }
|
||||
public int getMinPayout() { return minPayout; }
|
||||
public int getMaxPayout() { return maxPayout; }
|
||||
public String getEmoji() { return emoji; }
|
||||
|
||||
public static Job fromName(String name) {
|
||||
for (Job j : values()) {
|
||||
if (j.name().equalsIgnoreCase(name) || j.getDisplayName().equalsIgnoreCase(name)) {
|
||||
return j;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package creator.loki.feature.economy.domain;
|
||||
|
||||
public enum SlotCombination {
|
||||
CHERRY(new String[]{"\uD83C\uDF52", "\uD83C\uDF52", "\uD83C\uDF52"}, 3, 10.0),
|
||||
BELL(new String[]{"\uD83D\uDD14", "\uD83D\uDD14", "\uD83D\uDD14"}, 5, 5.0),
|
||||
DIAMOND(new String[]{"\uD83D\uDC8E", "\uD83D\uDC8E", "\uD83D\uDC8E"}, 10, 2.0),
|
||||
CROWN(new String[]{"\uD83D\uDC51", "\uD83D\uDC51", "\uD83D\uDC51"}, 15, 1.0),
|
||||
JACKPOT(new String[]{"\u26A1", "\u26A1", "\u26A1"}, 50, 0.1);
|
||||
|
||||
private final String[] emojis;
|
||||
private final int multiplier;
|
||||
private final double chance;
|
||||
|
||||
SlotCombination(String[] emojis, int multiplier, double chance) {
|
||||
this.emojis = emojis;
|
||||
this.multiplier = multiplier;
|
||||
this.chance = chance;
|
||||
}
|
||||
|
||||
public String[] getEmojis() { return emojis; }
|
||||
public int getMultiplier() { return multiplier; }
|
||||
public double getChance() { return chance; }
|
||||
|
||||
public static SlotCombination match(String e1, String e2, String e3) {
|
||||
if (!e1.equals(e2) || !e2.equals(e3)) return null;
|
||||
for (SlotCombination sc : values()) {
|
||||
if (sc.emojis[0].equals(e1)) return sc;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package creator.loki.feature.economy.repository;
|
||||
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface EconomyRepository {
|
||||
Optional<EconomyAccount> findById(String userId);
|
||||
void save(EconomyAccount account);
|
||||
boolean exists(String userId);
|
||||
List<EconomyAccount> findTopByBalance(int limit);
|
||||
|
||||
default EconomyAccount getAccount(String userId) {
|
||||
return findById(userId).orElse(new EconomyAccount(userId));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
package creator.loki.feature.economy.repository;
|
||||
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SqlEconomyRepository implements EconomyRepository {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SqlEconomyRepository.class);
|
||||
private static final int MAX_TOP = 100;
|
||||
private final Database database;
|
||||
|
||||
public SqlEconomyRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<EconomyAccount> findById(String userId) {
|
||||
String sql = """
|
||||
SELECT user_id, cash, bank, total_earned, total_spent,
|
||||
last_daily, daily_streak, fishing_luck, efficiency,
|
||||
last_fish, job, last_work
|
||||
FROM economy
|
||||
WHERE user_id = ?
|
||||
""";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setString(1, userId);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return Optional.of(mapResultSetToAccount(rs));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error fetching economy account for {}", userId, e);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(EconomyAccount account) {
|
||||
String sql = """
|
||||
INSERT INTO economy (user_id, cash, bank, total_earned, total_spent,
|
||||
last_daily, daily_streak, fishing_luck, efficiency,
|
||||
last_fish, job, last_work)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(user_id)
|
||||
DO UPDATE SET
|
||||
cash = excluded.cash,
|
||||
bank = excluded.bank,
|
||||
total_earned = excluded.total_earned,
|
||||
total_spent = excluded.total_spent,
|
||||
last_daily = excluded.last_daily,
|
||||
daily_streak = excluded.daily_streak,
|
||||
fishing_luck = excluded.fishing_luck,
|
||||
efficiency = excluded.efficiency,
|
||||
last_fish = excluded.last_fish,
|
||||
job = excluded.job,
|
||||
last_work = excluded.last_work
|
||||
""";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setString(1, account.getUserId());
|
||||
stmt.setLong(2, account.getCash());
|
||||
stmt.setLong(3, account.getBank());
|
||||
stmt.setLong(4, account.getTotalEarned());
|
||||
stmt.setLong(5, account.getTotalSpent());
|
||||
if (account.getLastDaily() != null) {
|
||||
stmt.setString(6, account.getLastDaily());
|
||||
} else {
|
||||
stmt.setNull(6, java.sql.Types.VARCHAR);
|
||||
}
|
||||
stmt.setInt(7, account.getDailyStreak());
|
||||
stmt.setInt(8, account.getFishingLuck());
|
||||
stmt.setInt(9, account.getEfficiency());
|
||||
if (account.getLastFish() != null) {
|
||||
stmt.setString(10, account.getLastFish());
|
||||
} else {
|
||||
stmt.setNull(10, java.sql.Types.VARCHAR);
|
||||
}
|
||||
if (account.getJob() != null) {
|
||||
stmt.setString(11, account.getJob());
|
||||
} else {
|
||||
stmt.setNull(11, java.sql.Types.VARCHAR);
|
||||
}
|
||||
if (account.getLastWork() != null) {
|
||||
stmt.setString(12, account.getLastWork());
|
||||
} else {
|
||||
stmt.setNull(12, java.sql.Types.VARCHAR);
|
||||
}
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error saving economy account for {}", account.getUserId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String userId) {
|
||||
String sql = "SELECT 1 FROM economy WHERE user_id = ?";
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setString(1, userId);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
return rs.next();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error checking economy existence for {}", userId, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EconomyAccount> findTopByBalance(int limit) {
|
||||
int lim = clampLimit(limit);
|
||||
String sql = """
|
||||
SELECT user_id, cash, bank, total_earned, total_spent,
|
||||
last_daily, daily_streak, fishing_luck, efficiency,
|
||||
last_fish, job, last_work
|
||||
FROM economy
|
||||
ORDER BY (cash + bank) DESC, cash DESC, user_id ASC
|
||||
LIMIT ?
|
||||
""";
|
||||
List<EconomyAccount> list = new ArrayList<>();
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setInt(1, lim);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
list.add(mapResultSetToAccount(rs));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error fetching top by balance", e);
|
||||
}
|
||||
return Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
private static int clampLimit(int limit) {
|
||||
if (limit <= 0) return 1;
|
||||
return Math.min(limit, MAX_TOP);
|
||||
}
|
||||
|
||||
private EconomyAccount mapResultSetToAccount(ResultSet rs) throws SQLException {
|
||||
return new EconomyAccount.Builder()
|
||||
.userId(rs.getString("user_id"))
|
||||
.cash(rs.getLong("cash"))
|
||||
.bank(rs.getLong("bank"))
|
||||
.totalEarned(rs.getLong("total_earned"))
|
||||
.totalSpent(rs.getLong("total_spent"))
|
||||
.lastDaily(rs.getString("last_daily"))
|
||||
.dailyStreak(rs.getInt("daily_streak"))
|
||||
.fishingLuck(rs.getInt("fishing_luck"))
|
||||
.efficiency(rs.getInt("efficiency"))
|
||||
.lastFish(rs.getString("last_fish"))
|
||||
.job(rs.getString("job"))
|
||||
.lastWork(rs.getString("last_work"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package creator.loki.feature.economy.service;
|
||||
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class BlackjackService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BlackjackService.class);
|
||||
|
||||
private static final String[] DECK = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};
|
||||
|
||||
private final EconomyRepository repository;
|
||||
private final ConcurrentHashMap<String, ActiveBlackjack> activeGames = new ConcurrentHashMap<>();
|
||||
|
||||
public BlackjackService(EconomyRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
private record ActiveBlackjack(List<String> playerCards, List<String> dealerCards, long bet, String userId) {}
|
||||
|
||||
public BlackjackState startGame(String userId, long bet) {
|
||||
if (bet <= 0 || activeGames.containsKey(userId)) return null;
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
if (account.getCash() < bet) return null;
|
||||
|
||||
repository.save(account.withCash(account.getCash() - bet));
|
||||
|
||||
List<String> playerCards = new ArrayList<>(List.of(drawCard(), drawCard()));
|
||||
List<String> dealerCards = new ArrayList<>(List.of(drawCard(), drawCard()));
|
||||
|
||||
int playerSum = calculateSum(playerCards);
|
||||
int dealerSum = calculateSum(dealerCards);
|
||||
boolean playerBj = playerSum == 21;
|
||||
boolean dealerBj = dealerSum == 21;
|
||||
|
||||
if (playerBj || dealerBj) {
|
||||
long payout;
|
||||
if (playerBj && !dealerBj) {
|
||||
payout = (long) (bet * 2.5);
|
||||
} else {
|
||||
payout = 0;
|
||||
}
|
||||
repository.save(repository.getAccount(userId).withCash(repository.getAccount(userId).getCash() + payout));
|
||||
return new BlackjackState(
|
||||
playerCards.toArray(String[]::new), dealerCards.toArray(String[]::new),
|
||||
playerSum, dealerSum, bet, true, payout
|
||||
);
|
||||
}
|
||||
|
||||
ActiveBlackjack game = new ActiveBlackjack(playerCards, dealerCards, bet, userId);
|
||||
activeGames.put(userId, game);
|
||||
return new BlackjackState(
|
||||
playerCards.toArray(String[]::new), dealerCards.toArray(String[]::new),
|
||||
playerSum, dealerSum, bet, false, 0
|
||||
);
|
||||
}
|
||||
|
||||
public BlackjackState hit(String userId) {
|
||||
ActiveBlackjack game = activeGames.get(userId);
|
||||
if (game == null) return null;
|
||||
|
||||
game.playerCards().add(drawCard());
|
||||
int sum = calculateSum(game.playerCards());
|
||||
|
||||
if (sum > 21) {
|
||||
activeGames.remove(userId);
|
||||
return new BlackjackState(
|
||||
game.playerCards().toArray(String[]::new), game.dealerCards().toArray(String[]::new),
|
||||
sum, calculateSum(game.dealerCards()), game.bet(), true, 0
|
||||
);
|
||||
}
|
||||
|
||||
return new BlackjackState(
|
||||
game.playerCards().toArray(String[]::new), game.dealerCards().toArray(String[]::new),
|
||||
sum, calculateSum(game.dealerCards()), game.bet(), false, 0
|
||||
);
|
||||
}
|
||||
|
||||
public BlackjackState stand(String userId) {
|
||||
ActiveBlackjack game = activeGames.remove(userId);
|
||||
if (game == null) return null;
|
||||
|
||||
List<String> dealerCards = new ArrayList<>(game.dealerCards());
|
||||
while (calculateSum(dealerCards) < 16) {
|
||||
dealerCards.add(drawCard());
|
||||
}
|
||||
|
||||
int playerSum = calculateSum(game.playerCards());
|
||||
int dealerSum = calculateSum(dealerCards);
|
||||
long payout;
|
||||
|
||||
if (dealerSum > 21 || playerSum > dealerSum) {
|
||||
payout = game.bet() * 2;
|
||||
} else {
|
||||
payout = 0;
|
||||
}
|
||||
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
repository.save(account.withCash(account.getCash() + payout));
|
||||
|
||||
return new BlackjackState(
|
||||
game.playerCards().toArray(String[]::new), dealerCards.toArray(String[]::new),
|
||||
playerSum, dealerSum, game.bet(), true, payout
|
||||
);
|
||||
}
|
||||
|
||||
private String drawCard() {
|
||||
return DECK[ThreadLocalRandom.current().nextInt(DECK.length)];
|
||||
}
|
||||
|
||||
private int calculateSum(List<String> cards) {
|
||||
int sum = 0;
|
||||
int aces = 0;
|
||||
for (String c : cards) {
|
||||
switch (c) {
|
||||
case "J", "Q", "K" -> sum += 10;
|
||||
case "A" -> { sum += 11; aces++; }
|
||||
default -> sum += Integer.parseInt(c);
|
||||
}
|
||||
}
|
||||
while (sum > 21 && aces > 0) {
|
||||
sum -= 10;
|
||||
aces--;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public record BlackjackState(String[] playerCards, String[] dealerCards, int playerSum, int dealerSum, long bet, boolean isOver, long payout) {}
|
||||
|
||||
public record BlackjackResult(boolean playerWon, long payout, String reason) {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package creator.loki.feature.economy.service;
|
||||
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
public class EconomyService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EconomyService.class);
|
||||
|
||||
private final EconomyRepository repository;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public EconomyService(EconomyRepository repository, CooldownManager cooldownManager) {
|
||||
this.repository = repository;
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
public EconomyAccount getAccount(String userId) {
|
||||
return repository.getAccount(userId);
|
||||
}
|
||||
|
||||
public boolean transfer(String fromId, String toId, long amount) {
|
||||
if (amount <= 0) return false;
|
||||
EconomyAccount from = repository.getAccount(fromId);
|
||||
if (from.getCash() < amount) return false;
|
||||
EconomyAccount to = repository.getAccount(toId);
|
||||
repository.save(from.withCash(from.getCash() - amount));
|
||||
repository.save(to.withCash(to.getCash() + amount));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean deposit(String userId, long amount) {
|
||||
if (amount <= 0) return false;
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
if (account.getCash() < amount) return false;
|
||||
repository.save(account.withCash(account.getCash() - amount).withBank(account.getBank() + amount));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean withdraw(String userId, long amount) {
|
||||
if (amount <= 0) return false;
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
if (account.getBank() < amount) return false;
|
||||
repository.save(account.withBank(account.getBank() - amount).withCash(account.getCash() + amount));
|
||||
return true;
|
||||
}
|
||||
|
||||
public long earnCash(String userId, long amount) {
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
EconomyAccount updated = account.withCash(account.getCash() + amount).withAddedEarned(amount);
|
||||
repository.save(updated);
|
||||
return updated.getCash();
|
||||
}
|
||||
|
||||
public boolean spendCash(String userId, long amount) {
|
||||
if (amount <= 0) return false;
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
if (account.getCash() < amount) return false;
|
||||
repository.save(account.withCash(account.getCash() - amount).withAddedSpent(amount));
|
||||
return true;
|
||||
}
|
||||
|
||||
public DailyResult claimDaily(String userId) {
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
String today = LocalDate.now().toString();
|
||||
if (today.equals(account.getLastDaily())) return null;
|
||||
String yesterday = LocalDate.now().minusDays(1).toString();
|
||||
int streak;
|
||||
if (yesterday.equals(account.getLastDaily())) {
|
||||
streak = account.getDailyStreak() + 1;
|
||||
} else {
|
||||
streak = 1;
|
||||
}
|
||||
long amount = streak >= 7 ? (long) (50 * 1.5) : 50;
|
||||
long newCash = account.getCash() + amount;
|
||||
EconomyAccount updated = new EconomyAccount.Builder(account)
|
||||
.cash(newCash)
|
||||
.totalEarned(account.getTotalEarned() + amount)
|
||||
.lastDaily(today)
|
||||
.dailyStreak(streak)
|
||||
.build();
|
||||
repository.save(updated);
|
||||
return new DailyResult(amount, streak, newCash);
|
||||
}
|
||||
|
||||
public int upgradeFishingLuck(String userId) {
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
if (account.getFishingLuck() >= 5) return -1;
|
||||
long cost = 800L * (account.getFishingLuck() + 1);
|
||||
if (account.getCash() < cost) return -1;
|
||||
int newLevel = account.getFishingLuck() + 1;
|
||||
repository.save(account.withCash(account.getCash() - cost).withFishingLuck(newLevel).withAddedSpent(cost));
|
||||
return newLevel;
|
||||
}
|
||||
|
||||
public int upgradeEfficiency(String userId) {
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
if (account.getEfficiency() >= 5) return -1;
|
||||
long cost = 800L * (account.getEfficiency() + 1);
|
||||
if (account.getCash() < cost) return -1;
|
||||
int newLevel = account.getEfficiency() + 1;
|
||||
repository.save(account.withCash(account.getCash() - cost).withEfficiency(newLevel).withAddedSpent(cost));
|
||||
return newLevel;
|
||||
}
|
||||
|
||||
public List<EconomyAccount> getTopBalances(int limit) {
|
||||
return repository.findTopByBalance(limit);
|
||||
}
|
||||
|
||||
public record DailyResult(long amount, int streak, long newCash) {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package creator.loki.feature.economy.service;
|
||||
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class FishingService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FishingService.class);
|
||||
|
||||
private static final String[] FISH = {"\uD83D\uDC1F", "\uD83D\uDC20", "\uD83D\uDC21", "\uD83D\uDC19"};
|
||||
private static final String WAVE = "\uD83C\uDF0A";
|
||||
private static final long COOLDOWN_MS = 7200000;
|
||||
|
||||
private final EconomyRepository repository;
|
||||
private final ConcurrentHashMap<String, ActiveFish> activeFishing = new ConcurrentHashMap<>();
|
||||
|
||||
public FishingService(EconomyRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
private record ActiveFish(String targetFish, int correctCount, long startTime, String difficulty, String[] grid) {}
|
||||
|
||||
public FishResult startFishing(String userId, String difficulty) {
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
if (account.getLastFish() != null) {
|
||||
long elapsed = System.currentTimeMillis() - Long.parseLong(account.getLastFish());
|
||||
if (elapsed < COOLDOWN_MS) return null;
|
||||
}
|
||||
|
||||
int size;
|
||||
int fishTypes;
|
||||
int baseReward;
|
||||
long timeLimit;
|
||||
switch (difficulty.toLowerCase()) {
|
||||
case "easy" -> { size = 5; fishTypes = 2; baseReward = 15; timeLimit = 30000; }
|
||||
case "normal" -> { size = 7; fishTypes = 3; baseReward = 30; timeLimit = 45000; }
|
||||
case "hard" -> { size = 9; fishTypes = 4; baseReward = 50; timeLimit = 60000; }
|
||||
default -> { return null; }
|
||||
}
|
||||
|
||||
String[] available = Arrays.copyOf(FISH, fishTypes);
|
||||
String[][] grid = new String[size][size];
|
||||
int[] counts = new int[fishTypes];
|
||||
var rng = ThreadLocalRandom.current();
|
||||
|
||||
for (int r = 0; r < size; r++) {
|
||||
for (int c = 0; c < size; c++) {
|
||||
if (rng.nextInt(100) < 40) {
|
||||
grid[r][c] = WAVE;
|
||||
} else {
|
||||
int fi = rng.nextInt(fishTypes);
|
||||
grid[r][c] = available[fi];
|
||||
counts[fi]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int targetIdx;
|
||||
do {
|
||||
targetIdx = rng.nextInt(fishTypes);
|
||||
} while (counts[targetIdx] == 0);
|
||||
|
||||
String targetFish = available[targetIdx];
|
||||
int correctCount = counts[targetIdx];
|
||||
|
||||
String[] rows = new String[size];
|
||||
for (int r = 0; r < size; r++) {
|
||||
rows[r] = String.join(" ", grid[r]);
|
||||
}
|
||||
|
||||
ActiveFish session = new ActiveFish(targetFish, correctCount, System.currentTimeMillis(), difficulty, rows);
|
||||
activeFishing.put(userId, session);
|
||||
|
||||
return new FishResult(rows, targetFish, correctCount, 0, account.getCash());
|
||||
}
|
||||
|
||||
public FishResult checkAnswer(String userId, int answer, long startTime, String difficulty) {
|
||||
ActiveFish session = activeFishing.remove(userId);
|
||||
if (session == null) return null;
|
||||
if (session.startTime() != startTime || !session.difficulty().equals(difficulty)) return null;
|
||||
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
long elapsed = System.currentTimeMillis() - session.startTime();
|
||||
|
||||
int baseReward;
|
||||
long timeLimit;
|
||||
switch (difficulty.toLowerCase()) {
|
||||
case "easy" -> { baseReward = 15; timeLimit = 30000; }
|
||||
case "normal" -> { baseReward = 30; timeLimit = 45000; }
|
||||
case "hard" -> { baseReward = 50; timeLimit = 60000; }
|
||||
default -> { return null; }
|
||||
}
|
||||
|
||||
long reward = 0;
|
||||
if (answer == session.correctCount() && elapsed <= timeLimit) {
|
||||
double ratio = (double) elapsed / timeLimit;
|
||||
double speedMult = ratio < 0.3 ? 2.0 : ratio < 0.5 ? 1.5 : 1.0;
|
||||
reward = (long) (baseReward * speedMult * (1 + account.getFishingLuck() * 0.01));
|
||||
}
|
||||
|
||||
long newCash = account.getCash() + reward;
|
||||
EconomyAccount updated = new EconomyAccount.Builder(account)
|
||||
.cash(newCash)
|
||||
.totalEarned(account.getTotalEarned() + reward)
|
||||
.lastFish(String.valueOf(System.currentTimeMillis()))
|
||||
.build();
|
||||
repository.save(updated);
|
||||
|
||||
return new FishResult(session.grid(), session.targetFish(), session.correctCount(), reward, newCash);
|
||||
}
|
||||
|
||||
public record FishResult(String[] grid, String targetFish, int count, long reward, long cashAfter) {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package creator.loki.feature.economy.service;
|
||||
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.domain.Job;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class JobService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JobService.class);
|
||||
|
||||
private static final long WORK_COOLDOWN = 21600000;
|
||||
|
||||
private final EconomyRepository repository;
|
||||
|
||||
public JobService(EconomyRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void setJob(String userId, String jobName) {
|
||||
Job job = Job.fromName(jobName);
|
||||
if (job == null) return;
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
EconomyAccount updated = new EconomyAccount.Builder(account)
|
||||
.job(job.name())
|
||||
.build();
|
||||
repository.save(updated);
|
||||
}
|
||||
|
||||
public WorkResult work(String userId, int userLevel) {
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
String jobName = account.getJob();
|
||||
if (jobName == null) return null;
|
||||
|
||||
Job job = Job.fromName(jobName);
|
||||
if (job == null) return null;
|
||||
if (userLevel < job.getRequiredLevel()) return null;
|
||||
|
||||
if (account.getLastWork() != null) {
|
||||
long elapsed = System.currentTimeMillis() - Long.parseLong(account.getLastWork());
|
||||
if (elapsed < WORK_COOLDOWN) return null;
|
||||
}
|
||||
|
||||
var rng = ThreadLocalRandom.current();
|
||||
long basePayout = rng.nextLong(job.getMinPayout(), job.getMaxPayout() + 1);
|
||||
long amount = (long) (basePayout * (1 + account.getEfficiency() * 0.01));
|
||||
|
||||
long newCash = account.getCash() + amount;
|
||||
EconomyAccount updated = new EconomyAccount.Builder(account)
|
||||
.cash(newCash)
|
||||
.totalEarned(account.getTotalEarned() + amount)
|
||||
.lastWork(String.valueOf(System.currentTimeMillis()))
|
||||
.build();
|
||||
repository.save(updated);
|
||||
|
||||
return new WorkResult(amount, newCash);
|
||||
}
|
||||
|
||||
public List<JobInfo> listJobs() {
|
||||
List<JobInfo> infos = new ArrayList<>();
|
||||
for (Job job : Job.values()) {
|
||||
infos.add(new JobInfo(
|
||||
job.name(),
|
||||
job.getDisplayName(),
|
||||
job.getRequiredLevel(),
|
||||
job.getMinPayout(),
|
||||
job.getMaxPayout(),
|
||||
job.getEmoji()
|
||||
));
|
||||
}
|
||||
return infos;
|
||||
}
|
||||
|
||||
public String getCurrentJob(String userId) {
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
String jobName = account.getJob();
|
||||
if (jobName == null) return null;
|
||||
Job job = Job.fromName(jobName);
|
||||
return job != null ? job.getDisplayName() : null;
|
||||
}
|
||||
|
||||
public record WorkResult(long amount, long newCash) {}
|
||||
|
||||
public record JobInfo(String name, String displayName, int requiredLevel, int minPayout, int maxPayout, String emoji) {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package creator.loki.feature.economy.service;
|
||||
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.domain.SlotCombination;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class SlotService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SlotService.class);
|
||||
|
||||
private static final String[] EMOJIS = {"\uD83C\uDF52", "\uD83D\uDD14", "\uD83D\uDC8E", "\uD83D\uDC51", "\u26A1"};
|
||||
private static final int[] WEIGHTS = {40, 25, 18, 12, 5};
|
||||
private static final int TOTAL_WEIGHT = 100;
|
||||
|
||||
private final EconomyRepository repository;
|
||||
|
||||
public SlotService(EconomyRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public SlotResult spin(String userId, long bet) {
|
||||
if (bet <= 0) return null;
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
if (account.getCash() < bet) return null;
|
||||
|
||||
repository.save(account.withCash(account.getCash() - bet));
|
||||
|
||||
String e1 = spinEmoji();
|
||||
String e2 = spinEmoji();
|
||||
String e3 = spinEmoji();
|
||||
|
||||
SlotCombination combo = SlotCombination.match(e1, e2, e3);
|
||||
long winAmount = combo != null ? bet * combo.getMultiplier() : 0;
|
||||
|
||||
long cashAfter = account.getCash() - bet + winAmount;
|
||||
repository.save(repository.getAccount(userId).withCash(cashAfter).withAddedEarned(winAmount));
|
||||
|
||||
return new SlotResult(e1, e2, e3,
|
||||
combo != null ? combo.name() : null,
|
||||
combo != null ? combo.getMultiplier() : 0,
|
||||
winAmount, cashAfter);
|
||||
}
|
||||
|
||||
private String spinEmoji() {
|
||||
int roll = ThreadLocalRandom.current().nextInt(TOTAL_WEIGHT);
|
||||
int cumulative = 0;
|
||||
for (int i = 0; i < EMOJIS.length; i++) {
|
||||
cumulative += WEIGHTS[i];
|
||||
if (roll < cumulative) return EMOJIS[i];
|
||||
}
|
||||
return EMOJIS[EMOJIS.length - 1];
|
||||
}
|
||||
|
||||
public record SlotResult(String emoji1, String emoji2, String emoji3, String comboName, int multiplier, long winAmount, long cashAfter) {}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package creator.loki.feature.entertainment;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import creator.loki.feature.social.commands.*;
|
||||
import creator.loki.feature.social.commands.*;
|
||||
import creator.loki.feature.entertainment.commands.*;
|
||||
import creator.loki.feature.entertainment.commands.*;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.core.registry.CommandRegistrar;
|
||||
import creator.loki.core.registry.CommandRegistry;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.feature.ship.repository.ShipPairRepository;
|
||||
import creator.loki.feature.ship.ShipCommand;
|
||||
|
||||
public class EntertainmentCommandRegistrar implements CommandRegistrar {
|
||||
private final CooldownManager cooldownManager;
|
||||
private final ShipPairRepository shipPairRepository;
|
||||
private final BotConfig config;
|
||||
|
||||
@Inject
|
||||
public EntertainmentCommandRegistrar(
|
||||
CooldownManager cooldownManager,
|
||||
ShipPairRepository shipPairRepository,
|
||||
BotConfig config
|
||||
) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
this.shipPairRepository = shipPairRepository;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(CommandRegistry registry) {
|
||||
registry.register(new EightBallCommand());
|
||||
registry.register(new CoinCommand());
|
||||
registry.register(new DiceCommand());
|
||||
registry.register(new MemeCommand());
|
||||
registry.register(new ShipCommand(shipPairRepository));
|
||||
registry.register(new RoastCommand());
|
||||
registry.register(new ComplimentCommand());
|
||||
registry.register(new GlazbobaCommand());
|
||||
|
||||
registry.register(new IqCommand(cooldownManager));
|
||||
registry.register(new GayCommand(cooldownManager));
|
||||
registry.register(new PpCommand(cooldownManager));
|
||||
registry.register(new RateCommand(cooldownManager));
|
||||
registry.register(new BoykisserCommand(cooldownManager));
|
||||
registry.register(new MustacheCommand(cooldownManager));
|
||||
registry.register(new FatCommand(cooldownManager));
|
||||
|
||||
registry.register(new PatCommand(cooldownManager));
|
||||
registry.register(new HugCommand());
|
||||
registry.register(new KissCommand(cooldownManager));
|
||||
registry.register(new SlapCommand(cooldownManager));
|
||||
registry.register(new TickleCommand(cooldownManager));
|
||||
registry.register(new PokeCommand(cooldownManager));
|
||||
registry.register(new CuddleCommand(cooldownManager));
|
||||
registry.register(new FeedCommand(cooldownManager));
|
||||
|
||||
registry.register(new RandomChoiceCommand(config, "choose", RandomChoiceCommand.Flavor.DEFAULT));
|
||||
registry.register(new RandomChoiceCommand(config, "pick", RandomChoiceCommand.Flavor.DEFAULT));
|
||||
registry.register(new RandomChoiceCommand(config, "decide", RandomChoiceCommand.Flavor.ORACLE));
|
||||
registry.register(new RandomChoiceCommand(config, "wheel", RandomChoiceCommand.Flavor.WHEEL));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,99 +1,101 @@
|
|||
package creator.loki.command.impl.entertainment.games.rating;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Boykisser метр с детерминированным результатом
|
||||
*/
|
||||
public class BoykisserCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public BoykisserCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "boykisser";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "На сколько ты boykisser (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "boykisser [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "boykisser")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "boykisser");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ Боты не целуют мальчиков!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "boykisser", 5);
|
||||
|
||||
// Детерминированный процент
|
||||
int boykisserLevel = getDeterministicValue(target.getId(), 777, 0, 100);
|
||||
|
||||
// Прогресс-бар с поцелуями
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = boykisserLevel / 10;
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append("💋");
|
||||
}
|
||||
for (int i = filled; i < 10; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(boykisserLevel, RatingConfig.getBoykisserLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("💋 Boykisser Метр")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Уровень", bar.toString() + "\n**" + boykisserLevel + "%** - " + level.getComment(), false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(level.getColor())
|
||||
.setFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.feature.entertainment.commands.RatingConfig;
|
||||
import creator.loki.feature.entertainment.commands.RatingLevel;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Boykisser метр с детерминированным результатом
|
||||
*/
|
||||
public class BoykisserCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public BoykisserCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "boykisser";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "На сколько ты boykisser (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "boykisser [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "boykisser")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "boykisser");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ Боты не целуют мальчиков!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "boykisser", 5);
|
||||
|
||||
// Детерминированный процент
|
||||
int boykisserLevel = getDeterministicValue(target.getId(), 777, 0, 100);
|
||||
|
||||
// Прогресс-бар с поцелуями
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = boykisserLevel / 10;
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append("💋");
|
||||
}
|
||||
for (int i = filled; i < 10; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(boykisserLevel, RatingConfig.getBoykisserLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("💋 Boykisser Метр")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Уровень", bar.toString() + "\n**" + boykisserLevel + "%** - " + level.getComment(), false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Разбор хвоста сообщения и строки вариантов для {@link RandomChoiceCommand}.
|
||||
*/
|
||||
final class ChoiceOptionParser {
|
||||
|
||||
private static final Pattern OR_SPLIT = Pattern.compile("(?i)\\s+или\\s+");
|
||||
|
||||
private record SplitRule(Predicate<String> when, Function<String, String[]> split) {
|
||||
}
|
||||
|
||||
/** Порядок важен: последнее правило - «слова через пробел». */
|
||||
private static final List<SplitRule> RULES = List.of(
|
||||
new SplitRule(s -> s.contains("|"), s -> s.split("\\|", -1)),
|
||||
new SplitRule(s -> s.contains(","), s -> s.split(",", -1)),
|
||||
new SplitRule(s -> OR_SPLIT.matcher(s).find(), OR_SPLIT::split),
|
||||
new SplitRule(s -> s.contains("/"), s -> s.split("/", -1)),
|
||||
new SplitRule(s -> true, s -> s.split("\\s+", -1))
|
||||
);
|
||||
|
||||
private ChoiceOptionParser() {
|
||||
}
|
||||
|
||||
static String tailAfterCommand(String raw, String prefix, String commandName) {
|
||||
String t = raw.trim();
|
||||
if (!t.startsWith(prefix)) {
|
||||
return "";
|
||||
}
|
||||
String rest = t.substring(prefix.length()).trim();
|
||||
int sp = rest.indexOf(' ');
|
||||
if (sp < 0) {
|
||||
return "";
|
||||
}
|
||||
String cmd = rest.substring(0, sp);
|
||||
if (!cmd.equalsIgnoreCase(commandName)) {
|
||||
return "";
|
||||
}
|
||||
return rest.substring(sp).trim();
|
||||
}
|
||||
|
||||
static List<String> parseOptions(String tail) {
|
||||
String t = tail.strip();
|
||||
for (SplitRule rule : RULES) {
|
||||
if (rule.when.test(t)) {
|
||||
return dedupeIgnoreCase(trimNonEmpty(rule.split.apply(t)));
|
||||
}
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private static List<String> trimNonEmpty(String[] arr) {
|
||||
return Arrays.stream(arr).map(String::trim).filter(s -> !s.isEmpty()).toList();
|
||||
}
|
||||
|
||||
private static List<String> dedupeIgnoreCase(List<String> parts) {
|
||||
Set<String> seen = new HashSet<>();
|
||||
List<String> out = new ArrayList<>();
|
||||
for (String p : parts) {
|
||||
String key = p.toLowerCase(Locale.ROOT);
|
||||
if (seen.add(key)) {
|
||||
out.add(p);
|
||||
}
|
||||
}
|
||||
return List.copyOf(out);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +1,47 @@
|
|||
package creator.loki.command.impl.entertainment.games.random;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class CoinCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "coin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Подбросить монетку";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "coin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
boolean isHeads = random.nextBoolean();
|
||||
String result = isHeads ? "Орёл" : "Решка";
|
||||
String emoji = isHeads ? "🦅" : "🪙";
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle(emoji + " Подбрасываю монетку...")
|
||||
.setDescription("**Результат:** " + result)
|
||||
.setColor(isHeads ? new Color(255, 215, 0) : new Color(192, 192, 192))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class CoinCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "coin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Подбросить монетку";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "coin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
boolean isHeads = random.nextBoolean();
|
||||
String result = isHeads ? "Орёл" : "Решка";
|
||||
String emoji = isHeads ? "🦅" : "🪙";
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle(emoji + " Подбрасываю монетку...")
|
||||
.withDescription("**Результат:** " + result)
|
||||
.withColor(isHeads ? new Color(255, 215, 0) : new Color(192, 192, 192))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +1,60 @@
|
|||
package creator.loki.command.impl.entertainment.games.random;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class DiceCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "dice";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Бросить кубик";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "dice [N]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
int maxValue = 6;
|
||||
|
||||
if (!context.getArgs().isEmpty()) {
|
||||
try {
|
||||
maxValue = Integer.parseInt(context.getArgs().get(0));
|
||||
if (maxValue < 2 || maxValue > 100) {
|
||||
context.getChannel().sendMessage("Число должно быть от 2 до 100").queue();
|
||||
return;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessage("Укажи корректное число").queue();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int result = random.nextInt(maxValue) + 1;
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🎲 Бросаю кубик...")
|
||||
.setDescription("**Результат:** " + result + " (из " + maxValue + ")")
|
||||
.setColor(new Color(46, 204, 113))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class DiceCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "dice";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Бросить кубик";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "dice [N]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
int maxValue = 6;
|
||||
|
||||
if (!context.getArgs().isEmpty()) {
|
||||
try {
|
||||
maxValue = Integer.parseInt(context.getArgs().get(0));
|
||||
if (maxValue < 2 || maxValue > 100) {
|
||||
context.getChannel().sendMessage("Число должно быть от 2 до 100").queue();
|
||||
return;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
context.getChannel().sendMessage("Укажи корректное число").queue();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int result = random.nextInt(maxValue) + 1;
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎲 Бросаю кубик...")
|
||||
.withDescription("**Результат:** " + result + " (из " + maxValue + ")")
|
||||
.withColor(new Color(46, 204, 113))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +1,74 @@
|
|||
package creator.loki.command.impl.entertainment.games;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class EightBallCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
private static final String[] ANSWERS = {
|
||||
"Да, определённо",
|
||||
"Бесспорно",
|
||||
"Без сомнений",
|
||||
"Можешь быть уверен",
|
||||
"Мне кажется — да",
|
||||
"Вероятнее всего",
|
||||
"Хорошие перспективы",
|
||||
"Знаки говорят — да",
|
||||
"Да",
|
||||
"Пока не ясно, попробуй снова",
|
||||
"Спроси позже",
|
||||
"Лучше не рассказывать",
|
||||
"Сейчас нельзя предсказать",
|
||||
"Сконцентрируйся и спроси опять",
|
||||
"Даже не думай",
|
||||
"Мой ответ — нет",
|
||||
"По моим данным — нет",
|
||||
"Перспективы не очень хорошие",
|
||||
"Весьма сомнительно"
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "8ball";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Магический шар предсказаний";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "8ball <вопрос>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getArgs().isEmpty()) {
|
||||
context.getChannel().sendMessage("Задай вопрос!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String question = String.join(" ", context.getArgs());
|
||||
String answer = ANSWERS[random.nextInt(ANSWERS.length)];
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🎱 Магический шар")
|
||||
.addField("Вопрос:", question, false)
|
||||
.addField("Ответ:", answer, false)
|
||||
.setColor(new Color(52, 73, 94))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Random;
|
||||
|
||||
public class EightBallCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
|
||||
private static final String[] ANSWERS = {
|
||||
"Да, определённо",
|
||||
"Бесспорно",
|
||||
"Без сомнений",
|
||||
"Можешь быть уверен",
|
||||
"Мне кажется - да",
|
||||
"Вероятнее всего",
|
||||
"Хорошие перспективы",
|
||||
"Знаки говорят - да",
|
||||
"Да",
|
||||
"Пока не ясно, попробуй снова",
|
||||
"Спроси позже",
|
||||
"Лучше не рассказывать",
|
||||
"Сейчас нельзя предсказать",
|
||||
"Сконцентрируйся и спроси опять",
|
||||
"Даже не думай",
|
||||
"Мой ответ - нет",
|
||||
"По моим данным - нет",
|
||||
"Перспективы не очень хорошие",
|
||||
"Весьма сомнительно"
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "8ball";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Магический шар предсказаний";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "8ball <вопрос>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
if (context.getArgs().isEmpty()) {
|
||||
context.getChannel().sendMessage("Задай вопрос!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
String question = String.join(" ", context.getArgs());
|
||||
String answer = ANSWERS[random.nextInt(ANSWERS.length)];
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎱 Магический шар")
|
||||
.addField("Вопрос:", question, false)
|
||||
.addField("Ответ:", answer, false)
|
||||
.withColor(new Color(52, 73, 94))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +1,96 @@
|
|||
package creator.loki.command.impl.entertainment.games.rating;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Fat метр - проверка на жирность
|
||||
*/
|
||||
public class FatCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public FatCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "fat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка на жирность (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "fat [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "fat")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "fat");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "fat", 5);
|
||||
|
||||
// Детерминированный процент от 0 до 100
|
||||
int fatLevel = getDeterministicValue(target.getId(), 999, 0, 100);
|
||||
|
||||
// Прогресс-бар
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = fatLevel / 10;
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append("🟩");
|
||||
}
|
||||
for (int i = filled; i < 10; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(fatLevel, RatingConfig.getFatLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🐷 Fat Метр")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Жирность", bar.toString() + "\n**" + fatLevel + "%** - " + level.getComment(), false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(level.getColor())
|
||||
.setFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.feature.entertainment.commands.RatingConfig;
|
||||
import creator.loki.feature.entertainment.commands.RatingLevel;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Fat метр - проверка на жирность
|
||||
*/
|
||||
public class FatCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public FatCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "fat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка на жирность (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "fat [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "fat")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "fat");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "fat", 5);
|
||||
|
||||
// Детерминированный процент от 0 до 100
|
||||
int fatLevel = getDeterministicValue(target.getId(), 999, 0, 100);
|
||||
|
||||
// Прогресс-бар
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = fatLevel / 10;
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append("🟩");
|
||||
}
|
||||
for (int i = filled; i < 10; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(fatLevel, RatingConfig.getFatLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🐷 Fat Метр")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Жирность", bar.toString() + "\n**" + fatLevel + "%** - " + level.getComment(), false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,97 +1,97 @@
|
|||
package creator.loki.command.impl.entertainment.games.rating;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Gay-метр с детерминированным результатом
|
||||
*/
|
||||
public class GayCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public GayCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "gay";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Гей-метр (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "gay [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "gay")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "gay");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ Боты не имеют ориентации!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "gay", 5);
|
||||
|
||||
// Детерминированный процент
|
||||
int gayLevel = getDeterministicValue(target.getId(), 456, 0, 100);
|
||||
|
||||
// Прогресс-бар с радугой
|
||||
String[] rainbow = {"🟥", "🟧", "🟨", "🟩", "🟦", "🟪"};
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = Math.min(gayLevel / 17 + 1, 6);
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append(rainbow[i]);
|
||||
}
|
||||
for (int i = filled; i < 6; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🏳️🌈 Гей-метр")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Уровень", bar.toString() + "\n**" + gayLevel + "%**", false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(new Color(255, 0, 255))
|
||||
.setFooter("💡 Это просто мем, не воспринимайте серьезно")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Gay-метр с детерминированным результатом
|
||||
*/
|
||||
public class GayCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public GayCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "gay";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Гей-метр (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "gay [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "gay")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "gay");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ Боты не имеют ориентации!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "gay", 5);
|
||||
|
||||
// Детерминированный процент
|
||||
int gayLevel = getDeterministicValue(target.getId(), 456, 0, 100);
|
||||
|
||||
// Прогресс-бар с радугой
|
||||
String[] rainbow = {"🟥", "🟧", "🟨", "🟩", "🟦", "🟪"};
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = Math.min(gayLevel / 17 + 1, 6);
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append(rainbow[i]);
|
||||
}
|
||||
for (int i = filled; i < 6; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🏳️🌈 Гей-метр")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Уровень", bar.toString() + "\n**" + gayLevel + "%**", false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(new Color(255, 0, 255))
|
||||
.withFooter("💡 Это просто мем, не воспринимайте серьезно")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,96 +1,96 @@
|
|||
package creator.loki.command.impl.entertainment.games.random;
|
||||
|
||||
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.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GlazbobaCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "glazboba";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Расширенная информация о пользователе";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "glazboba <@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);
|
||||
|
||||
// Анимация загрузки
|
||||
context.getChannel().sendMessage("🔍 Сканирую данные...").queue(loadingMsg -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
loadingMsg.editMessage("📊 Анализирую профиль...").queue();
|
||||
Thread.sleep(1000);
|
||||
loadingMsg.editMessage("🔐 Взламываю Discord...").queue();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Собираем информацию
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
|
||||
String createdAt = target.getUser().getTimeCreated().format(formatter);
|
||||
String joinedAt = target.getTimeJoined().format(formatter);
|
||||
String status = target.getOnlineStatus().name();
|
||||
String nickname = target.getNickname() != null ? target.getNickname() : "Нет";
|
||||
String roles = target.getRoles().stream()
|
||||
.map(role -> role.getName())
|
||||
.limit(10)
|
||||
.collect(Collectors.joining(", "));
|
||||
if (roles.isEmpty()) roles = "Нет ролей";
|
||||
|
||||
String activities = target.getActivities().isEmpty()
|
||||
? "Нет активности"
|
||||
: target.getActivities().get(0).getName();
|
||||
|
||||
boolean isBot = target.getUser().isBot();
|
||||
boolean isBoosting = target.isBoosting();
|
||||
|
||||
loadingMsg.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("👤 Информация о " + target.getEffectiveName())
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.addField("🆔 ID", target.getId(), true)
|
||||
.addField("📛 Никнейм", nickname, true)
|
||||
.addField("🤖 Бот", isBot ? "Да" : "Нет", true)
|
||||
.addField("📅 Аккаунт создан", createdAt, false)
|
||||
.addField("📥 Присоединился", joinedAt, false)
|
||||
.addField("🟢 Статус", status, true)
|
||||
.addField("🎮 Активность", activities, true)
|
||||
.addField("💎 Бустер", isBoosting ? "Да" : "Нет", true)
|
||||
.addField("🎭 Роли", roles, false)
|
||||
.setColor(target.getColor() != null ? target.getColor() : Color.BLUE)
|
||||
.setFooter("Запросил: " + context.getAuthor().getUser().getName())
|
||||
.build()
|
||||
).queue();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
loadingMsg.editMessage("❌ Ошибка загрузки").queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
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.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GlazbobaCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "glazboba";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Расширенная информация о пользователе";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "glazboba <@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);
|
||||
|
||||
// Анимация загрузки
|
||||
context.getChannel().sendMessage("🔍 Сканирую данные...").queue(loadingMsg -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
loadingMsg.editMessage("📊 Анализирую профиль...").queue();
|
||||
Thread.sleep(1000);
|
||||
loadingMsg.editMessage("🔐 Взламываю Discord...").queue();
|
||||
Thread.sleep(1000);
|
||||
|
||||
// Собираем информацию
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
|
||||
String createdAt = target.getUser().getTimeCreated().format(formatter);
|
||||
String joinedAt = target.getTimeJoined().format(formatter);
|
||||
String status = target.getOnlineStatus().name();
|
||||
String nickname = target.getNickname() != null ? target.getNickname() : "Нет";
|
||||
String roles = target.getRoles().stream()
|
||||
.map(role -> role.getName())
|
||||
.limit(10)
|
||||
.collect(Collectors.joining(", "));
|
||||
if (roles.isEmpty()) roles = "Нет ролей";
|
||||
|
||||
String activities = target.getActivities().isEmpty()
|
||||
? "Нет активности"
|
||||
: target.getActivities().get(0).getName();
|
||||
|
||||
boolean isBot = target.getUser().isBot();
|
||||
boolean isBoosting = target.isBoosting();
|
||||
|
||||
loadingMsg.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("👤 Информация о " + target.getEffectiveName())
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.addField("🆔 ID", target.getId(), true)
|
||||
.addField("📛 Никнейм", nickname, true)
|
||||
.addField("🤖 Бот", isBot ? "Да" : "Нет", true)
|
||||
.addField("📅 Аккаунт создан", createdAt, false)
|
||||
.addField("📥 Присоединился", joinedAt, false)
|
||||
.addField("🟢 Статус", status, true)
|
||||
.addField("🎮 Активность", activities, true)
|
||||
.addField("💎 Бустер", isBoosting ? "Да" : "Нет", true)
|
||||
.addField("🎭 Роли", roles, false)
|
||||
.withColor(target.getColor() != null ? target.getColor() : Color.BLUE)
|
||||
.withFooter("Запросил: " + context.getAuthor().getUser().getName())
|
||||
.build()
|
||||
).queue();
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
loadingMsg.editMessage("❌ Ошибка загрузки").queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +1,93 @@
|
|||
package creator.loki.command.impl.entertainment.games.rating;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* IQ тест с детерминированным результатом
|
||||
*/
|
||||
public class IqCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public IqCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "iq";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Тест IQ (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "iq [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "iq")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "iq");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "iq", 5);
|
||||
|
||||
int iq;
|
||||
if (target.getUser().isBot()) {
|
||||
iq = 9000; // Боты умные!
|
||||
} else if (target.getId().equals("1414975197269987471")) {
|
||||
iq = 1000; // Специальное значение
|
||||
} else {
|
||||
// Детерминированный IQ от 0 до 140
|
||||
iq = getDeterministicValue(target.getId(), 123, 0, 140);
|
||||
}
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(iq, RatingConfig.getIqLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🧠 Тест IQ")
|
||||
.setDescription("Результат для " + target.getAsMention())
|
||||
.addField("IQ Уровень", "**" + iq + "** - " + level.getComment(), false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(level.getColor())
|
||||
.setFooter("💡 Научно доказано вашим ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.feature.entertainment.commands.RatingConfig;
|
||||
import creator.loki.feature.entertainment.commands.RatingLevel;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* IQ тест с детерминированным результатом
|
||||
*/
|
||||
public class IqCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public IqCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "iq";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Тест IQ (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "iq [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "iq")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "iq");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "iq", 5);
|
||||
|
||||
int iq;
|
||||
if (target.getUser().isBot()) {
|
||||
iq = 9000; // Боты умные!
|
||||
} else if (target.getId().equals("1414975197269987471")) {
|
||||
iq = 1000; // Специальное значение
|
||||
} else {
|
||||
// Детерминированный IQ от 0 до 140
|
||||
iq = getDeterministicValue(target.getId(), 123, 0, 140);
|
||||
}
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(iq, RatingConfig.getIqLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🧠 Тест IQ")
|
||||
.withDescription("Результат для " + target.getAsMention())
|
||||
.addField("IQ Уровень", "**" + iq + "** - " + level.getComment(), false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Научно доказано вашим ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +1,73 @@
|
|||
package creator.loki.command.impl.entertainment.games.random;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class MemeCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "meme";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Случайный мем";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "meme";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
context.getChannel().sendMessage("🔍 Ищу мем...").queue(loadingMsg -> {
|
||||
try {
|
||||
URL url = new URL("https://meme-api.com/gimme");
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
in.close();
|
||||
|
||||
JsonObject json = JsonParser.parseString(response.toString()).getAsJsonObject();
|
||||
String memeUrl = json.get("url").getAsString();
|
||||
String title = json.get("title").getAsString();
|
||||
String subreddit = json.get("subreddit").getAsString();
|
||||
|
||||
loadingMsg.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle(title)
|
||||
.setImage(memeUrl)
|
||||
.setColor(new Color(255, 69, 0))
|
||||
.setFooter("r/" + subreddit)
|
||||
.build()
|
||||
).queue();
|
||||
|
||||
} catch (Exception e) {
|
||||
loadingMsg.editMessage("❌ Не удалось загрузить мем: " + e.getMessage()).queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
|
||||
public class MemeCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "meme";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Случайный мем";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "meme";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
context.getChannel().sendMessage("🔍 Ищу мем...").queue(loadingMsg -> {
|
||||
try {
|
||||
var url = new URI("https://meme-api.com/gimme").toURL();
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
in.close();
|
||||
|
||||
JsonObject json = JsonParser.parseString(response.toString()).getAsJsonObject();
|
||||
String memeUrl = json.get("url").getAsString();
|
||||
String title = json.get("title").getAsString();
|
||||
String subreddit = json.get("subreddit").getAsString();
|
||||
|
||||
loadingMsg.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle(title)
|
||||
.withImage(memeUrl)
|
||||
.withColor(new Color(255, 69, 0))
|
||||
.withFooter("r/" + subreddit)
|
||||
.build()
|
||||
).queue();
|
||||
|
||||
} catch (Exception e) {
|
||||
loadingMsg.editMessage("❌ Не удалось загрузить мем: " + e.getMessage()).queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,103 +1,105 @@
|
|||
package creator.loki.command.impl.entertainment.games.rating;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Mustache метр - проверка на усатость
|
||||
*/
|
||||
public class MustacheCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public MustacheCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "mustache";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка на усатость (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "mustache [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "mustache")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "mustache");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "mustache", 5);
|
||||
|
||||
int mustacheLevel;
|
||||
|
||||
// Специальные значения
|
||||
if (target.getUser().isBot() || target.getId().equals("1414975197269987471")) {
|
||||
mustacheLevel = 0; // Боты и специальный ID = 0%
|
||||
} else if (target.getId().equals("678283217094180915")) {
|
||||
mustacheLevel = 1000; // Специальный ID = 1000%
|
||||
} else {
|
||||
// Детерминированный процент от 0 до 100
|
||||
mustacheLevel = getDeterministicValue(target.getId(), 888, 0, 100);
|
||||
}
|
||||
|
||||
// Прогресс-бар с усами
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = Math.min(mustacheLevel / 10, 10);
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append("🧔");
|
||||
}
|
||||
for (int i = filled; i < 10; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(mustacheLevel, RatingConfig.getMustacheLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🧔 Mustache Метр")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Усатость", bar.toString() + "\n**" + mustacheLevel + "%** - " + level.getComment(), false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(level.getColor())
|
||||
.setFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.feature.entertainment.commands.RatingConfig;
|
||||
import creator.loki.feature.entertainment.commands.RatingLevel;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Mustache метр - проверка на усатость
|
||||
*/
|
||||
public class MustacheCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public MustacheCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "mustache";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка на усатость (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "mustache [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "mustache")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "mustache");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "mustache", 5);
|
||||
|
||||
int mustacheLevel;
|
||||
|
||||
// Специальные значения
|
||||
if (target.getUser().isBot() || target.getId().equals("1414975197269987471")) {
|
||||
mustacheLevel = 0; // Боты и специальный ID = 0%
|
||||
} else if (target.getId().equals("678283217094180915")) {
|
||||
mustacheLevel = 1000; // Специальный ID = 1000%
|
||||
} else {
|
||||
// Детерминированный процент от 0 до 100
|
||||
mustacheLevel = getDeterministicValue(target.getId(), 888, 0, 100);
|
||||
}
|
||||
|
||||
// Прогресс-бар с усами
|
||||
StringBuilder bar = new StringBuilder();
|
||||
int filled = Math.min(mustacheLevel / 10, 10);
|
||||
for (int i = 0; i < filled; i++) {
|
||||
bar.append("🧔");
|
||||
}
|
||||
for (int i = filled; i < 10; i++) {
|
||||
bar.append("⬜");
|
||||
}
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(mustacheLevel, RatingConfig.getMustacheLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🧔 Mustache Метр")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Усатость", bar.toString() + "\n**" + mustacheLevel + "%** - " + level.getComment(), false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +1,104 @@
|
|||
package creator.loki.command.impl.entertainment.games.rating;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* PP размер с детерминированным результатом
|
||||
*/
|
||||
public class PpCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public PpCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "pp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Измеритель pp (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "pp [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "pp")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "pp");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ У ботов нет pp!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "pp", 5);
|
||||
|
||||
// Специальное значение для конкретного пользователя
|
||||
int size;
|
||||
if (target.getId().equals("1414975197269987471")) {
|
||||
size = 100;
|
||||
} else {
|
||||
// Детерминированный размер от 0 до 30 см
|
||||
size = getDeterministicValue(target.getId(), 6969, 0, 30);
|
||||
}
|
||||
|
||||
// Визуализация
|
||||
StringBuilder ppVisual = new StringBuilder("8");
|
||||
for (int i = 0; i < size; i++) {
|
||||
ppVisual.append("=");
|
||||
}
|
||||
ppVisual.append("D");
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(size, RatingConfig.getPpLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("🍆 PP Размер")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Размер", "```" + ppVisual.toString() + "```\n**" + size + " см** - " + level.getComment(), false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(level.getColor())
|
||||
.setFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.feature.entertainment.commands.RatingConfig;
|
||||
import creator.loki.feature.entertainment.commands.RatingLevel;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* PP размер с детерминированным результатом
|
||||
*/
|
||||
public class PpCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public PpCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "pp";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Измеритель pp (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "pp [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "pp")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "pp");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
if (target.getUser().isBot()) {
|
||||
context.getChannel().sendMessage("❌ У ботов нет pp!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "pp", 5);
|
||||
|
||||
// Специальное значение для конкретного пользователя
|
||||
int size;
|
||||
if (target.getId().equals("1414975197269987471")) {
|
||||
size = 100;
|
||||
} else {
|
||||
// Детерминированный размер от 0 до 30 см
|
||||
size = getDeterministicValue(target.getId(), 6969, 0, 30);
|
||||
}
|
||||
|
||||
// Визуализация
|
||||
StringBuilder ppVisual = new StringBuilder("8");
|
||||
for (int i = 0; i < size; i++) {
|
||||
ppVisual.append("=");
|
||||
}
|
||||
ppVisual.append("D");
|
||||
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(size, RatingConfig.getPpLevels());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🍆 PP Размер")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Размер", "```" + ppVisual.toString() + "```\n**" + size + " см** - " + level.getComment(), false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* Случайный выбор из вариантов. Разбор строки - {@link ChoiceOptionParser}.
|
||||
*/
|
||||
public class RandomChoiceCommand implements Command {
|
||||
|
||||
public enum Flavor {
|
||||
DEFAULT(
|
||||
"Выбери один вариант - рандом решит за тебя.",
|
||||
new String[] {
|
||||
"🎲 Выпало: **%s**",
|
||||
"🎯 Окей, берём **%s**",
|
||||
"✨ Вселенная шепчет: **%s**",
|
||||
"🍀 Фортуна на твоей стороне - **%s**",
|
||||
"📌 Без лишних слов: **%s**"
|
||||
}
|
||||
),
|
||||
WHEEL(
|
||||
"Колесо фортуны (тот же рандом, но драматичнее).",
|
||||
new String[] {
|
||||
"🎡 Колесо остановилось на **%s**",
|
||||
"🎪 Барабанная дробь… **%s**!",
|
||||
"🎠 Карусель выбрала **%s**",
|
||||
"⚡ Судьба указывает на **%s**"
|
||||
}
|
||||
),
|
||||
ORACLE(
|
||||
"Оракул в шутливом тоне.",
|
||||
new String[] {
|
||||
"🔮 Оракул видит… **%s**",
|
||||
"🌙 Звёзды сложились: **%s**",
|
||||
"✴️ Судьба-печенька говорит: **%s**",
|
||||
"🧿 Сглаз обойдёт - бери **%s** и не оглядывайся"
|
||||
}
|
||||
);
|
||||
|
||||
private final String blurb;
|
||||
private final String[] templates;
|
||||
|
||||
Flavor(String blurb, String[] templates) {
|
||||
this.blurb = blurb;
|
||||
this.templates = templates;
|
||||
}
|
||||
|
||||
String randomLine(String pick) {
|
||||
var rnd = ThreadLocalRandom.current();
|
||||
return String.format(templates[rnd.nextInt(templates.length)], pick);
|
||||
}
|
||||
}
|
||||
|
||||
private final BotConfig config;
|
||||
private final String commandName;
|
||||
private final Flavor flavor;
|
||||
|
||||
public RandomChoiceCommand(BotConfig config, String commandName, Flavor flavor) {
|
||||
this.config = config;
|
||||
this.commandName = commandName;
|
||||
this.flavor = flavor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return commandName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return flavor.blurb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return commandName + " вариант1 | вариант2 | …";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
String p = config.getPrefix();
|
||||
String tail = ChoiceOptionParser.tailAfterCommand(context.getMessage().getContentRaw(), p, commandName);
|
||||
|
||||
if (tail.isBlank()) {
|
||||
reply(context, EmbedTemplates.error("Нужны варианты", usageExamples(p)));
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> options = ChoiceOptionParser.parseOptions(tail);
|
||||
if (options.size() < 2) {
|
||||
reply(context, EmbedTemplates.error(
|
||||
"Мало вариантов",
|
||||
"Минимум **2** разных варианта: `|`, запятая, `или`, `/` или слова через пробел."
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
var rnd = ThreadLocalRandom.current();
|
||||
String pick = options.get(rnd.nextInt(options.size()));
|
||||
String line = flavor.randomLine(pick);
|
||||
String listed = options.stream().map(s -> "`" + s + "`").reduce((a, b) -> a + " · " + b).orElse("");
|
||||
|
||||
var embed = new EmbedBuilder()
|
||||
.setTitle("🎲 " + commandName)
|
||||
.setDescription(line + "\n\n*Варианты:* " + listed)
|
||||
.setColor(new Color(88, 101, 242))
|
||||
.setFooter("choose · pick · decide · wheel");
|
||||
reply(context, embed.build());
|
||||
}
|
||||
|
||||
private static void reply(CommandContext ctx, net.dv8tion.jda.api.entities.MessageEmbed embed) {
|
||||
ctx.getChannel().sendMessageEmbeds(embed).queue();
|
||||
}
|
||||
|
||||
private String usageExamples(String p) {
|
||||
return "**Примеры:**\n"
|
||||
+ "`" + p + commandName + " пицца | суши | бургер`\n"
|
||||
+ "`" + p + commandName + " чай, кофе, сок`\n"
|
||||
+ "`" + p + commandName + " дома или в клуб`\n"
|
||||
+ "`" + p + commandName + " да / нет / может`";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,107 +1,101 @@
|
|||
package creator.loki.command.impl.entertainment.games.random;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Оценка пользователя из 10 с детерминированным результатом
|
||||
*/
|
||||
public class RateCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public RateCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rate";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Оценить пользователя из 10 (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "rate [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "rate")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "rate");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "rate", 5);
|
||||
|
||||
// Детерминированная оценка от 1 до 10
|
||||
int rating = getDeterministicValue(target.getId(), 420, 1, 10);
|
||||
|
||||
// Визуализация звездами
|
||||
StringBuilder stars = new StringBuilder();
|
||||
for (int i = 0; i < rating; i++) {
|
||||
stars.append("⭐");
|
||||
}
|
||||
for (int i = rating; i < 10; i++) {
|
||||
stars.append("☆");
|
||||
}
|
||||
|
||||
// Комментарий
|
||||
Map<Integer, String> comments = new HashMap<>();
|
||||
comments.put(10, "💯 ИДЕАЛЬНО!");
|
||||
comments.put(9, "🔥 Почти идеально!");
|
||||
comments.put(8, "😍 Отлично!");
|
||||
comments.put(7, "👍 Хорошо!");
|
||||
comments.put(6, "🙂 Неплохо");
|
||||
comments.put(5, "😐 Средне");
|
||||
comments.put(4, "😕 Ниже среднего");
|
||||
comments.put(3, "😬 Так себе");
|
||||
comments.put(2, "💀 Плохо");
|
||||
comments.put(1, "🗿 Ужасно");
|
||||
|
||||
String comment = comments.getOrDefault(rating, "🤔");
|
||||
Color color = rating >= 8 ? new Color(255, 215, 0) : new Color(52, 152, 219);
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.setTitle("⭐ Оценка")
|
||||
.setDescription(target.getAsMention())
|
||||
.addField("Рейтинг", stars.toString() + "\n**" + rating + "/10** - " + comment, false)
|
||||
.setThumbnail(target.getUser().getAvatarUrl())
|
||||
.setColor(color)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Оценка пользователя из 10 с детерминированным результатом
|
||||
*/
|
||||
public class RateCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public RateCommand(CooldownManager cooldownManager) {
|
||||
this.cooldownManager = cooldownManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rate";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Оценить пользователя из 10 (результат всегда одинаковый)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "rate [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка cooldown (5 секунд)
|
||||
if (cooldownManager.isOnCooldown(context.getAuthor().getId(), "rate")) {
|
||||
long remaining = cooldownManager.getRemainingSeconds(context.getAuthor().getId(), "rate");
|
||||
context.getChannel().sendMessage("⏰ Подожди ещё " + remaining + " сек").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
if (target == null) return;
|
||||
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "rate", 5);
|
||||
|
||||
// Детерминированная оценка от 1 до 10
|
||||
int rating = getDeterministicValue(target.getId(), 420, 1, 10);
|
||||
|
||||
// Визуализация звездами
|
||||
String stars = "⭐".repeat(rating) + "☆".repeat(10 - rating);
|
||||
|
||||
// Комментарий
|
||||
Map<Integer, String> comments = new HashMap<>();
|
||||
comments.put(10, "💯 ИДЕАЛЬНО!");
|
||||
comments.put(9, "🔥 Почти идеально!");
|
||||
comments.put(8, "😍 Отлично!");
|
||||
comments.put(7, "👍 Хорошо!");
|
||||
comments.put(6, "🙂 Неплохо");
|
||||
comments.put(5, "😐 Средне");
|
||||
comments.put(4, "😕 Ниже среднего");
|
||||
comments.put(3, "😬 Так себе");
|
||||
comments.put(2, "💀 Плохо");
|
||||
comments.put(1, "🗿 Ужасно");
|
||||
|
||||
String comment = comments.getOrDefault(rating, "🤔");
|
||||
Color color = rating >= 8 ? new Color(255, 215, 0) : new Color(52, 152, 219);
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("⭐ Оценка")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Рейтинг", stars + "\n**" + rating + "/10** - " + comment, false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(color)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
Random random = new Random(seed);
|
||||
return random.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package creator.loki.command.impl.entertainment.games.rating;
|
||||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Arrays;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue