v2.3: Major refactoring, Lavalink music system, Spotify support, CI/CD
- Full refactoring: max 200 lines/file, max 3 files/dir, KISS/DRY/SOLID - Lavalink client 3.4.0 + LavaSrc plugin (Spotify, Apple Music, Deezer) - 3-bot architecture: main (!), music (q!), radio (r!) - Filters: bassboost, nightcore, vaporwave, equalizer - Slash commands for music (/play, /skip, etc.) - Persistent queue in SQLite, auto-leave timer - Radio manager with auto-reconnect - Custom welcome GIF greetings via nekos.life - JDA 5.6.1, gson 2.14, hikaricp 6.3, all deps updated - Fixed YouTube playback via ytmsearch (LavaSrc) - CI/CD: Docker publish + release workflows - SpotBugs: all real bugs fixed, ignoreFailures=false - Checkstyle: Google Style (adapted), 0 violations
This commit is contained in:
parent
f5398dc779
commit
29a21e4178
301 changed files with 21751 additions and 18099 deletions
|
|
@ -2,49 +2,53 @@ package creator.loki.bootstrap;
|
|||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import creator.loki.core.BotCluster;
|
||||
import creator.loki.core.BotInitializer;
|
||||
import creator.loki.di.BotModule;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
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;
|
||||
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) {
|
||||
public static void main(String[] args) {
|
||||
Thread shutdownHook =
|
||||
new Thread(
|
||||
() -> {
|
||||
if (!shutdownRequested) {
|
||||
shutdownRequested = true;
|
||||
logger.info("========================================");
|
||||
logger.info("Получен сигнал завершения");
|
||||
logger.info("Выполняется graceful shutdown...");
|
||||
logger.info("========================================");
|
||||
}
|
||||
}, "ShutdownHook");
|
||||
LoggerDecorator.warn(logger, "Получен сигнал завершения");
|
||||
LoggerDecorator.warn(logger, "Выполняется graceful shutdown...");
|
||||
}
|
||||
},
|
||||
"ShutdownHook");
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
|
||||
try {
|
||||
logger.info("Запуск Discord бота");
|
||||
try {
|
||||
LoggerDecorator.startup(logger, "Запуск Discord ботов");
|
||||
|
||||
// Создаём Guice Injector с модулем конфигурации
|
||||
Injector injector = Guice.createInjector(new BotModule());
|
||||
Injector injector = Guice.createInjector(new BotModule());
|
||||
|
||||
// Получаем BotInitializer с автоматически внедрёнными зависимостями
|
||||
BotInitializer initializer = injector.getInstance(BotInitializer.class);
|
||||
BotInitializer initializer = injector.getInstance(BotInitializer.class);
|
||||
|
||||
// Запускаем бота
|
||||
JDA jda = initializer.start();
|
||||
BotCluster cluster = initializer.start();
|
||||
|
||||
// Ждём полной загрузки
|
||||
jda.awaitReady();
|
||||
logger.info("Бот полностью готов к работе!");
|
||||
cluster.getMain().awaitReady();
|
||||
LoggerDecorator.success(logger, "Main бот полностью готов к работе!");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Критическая ошибка при запуске бота", e);
|
||||
System.exit(1);
|
||||
}
|
||||
if (cluster.hasMusicBot()) {
|
||||
LoggerDecorator.success(logger, "Music бот готов к работе!");
|
||||
}
|
||||
if (cluster.hasRadioBot()) {
|
||||
LoggerDecorator.success(logger, "Radio бот готов к работе!");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
LoggerDecorator.error(logger, "Критическая ошибка при запуске ботов", e);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package creator.loki.bootstrap;
|
||||
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import net.dv8tion.jda.api.events.session.ReadyEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.slf4j.Logger;
|
||||
|
|
@ -8,11 +9,12 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
@Singleton
|
||||
public class ReadyListener extends ListenerAdapter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ReadyListener.class);
|
||||
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());
|
||||
}
|
||||
@Override
|
||||
public void onReady(ReadyEvent event) {
|
||||
LoggerDecorator.success(
|
||||
logger, "Бот " + event.getJDA().getSelfUser().getName() + " успешно запущен!");
|
||||
LoggerDecorator.bot(logger, "Подключен к " + event.getGuildTotalCount() + " серверам");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
package creator.loki.command;
|
||||
|
||||
public interface Command {
|
||||
String getName();
|
||||
String getDescription();
|
||||
String getName();
|
||||
|
||||
default String getUsage() {
|
||||
return getName();
|
||||
}
|
||||
String getDescription();
|
||||
|
||||
default String getCategory() {
|
||||
return "Общие";
|
||||
}
|
||||
default String getUsage() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
void execute(CommandContext context);
|
||||
default String getCategory() {
|
||||
return "Общие";
|
||||
}
|
||||
|
||||
void execute(CommandContext context);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
package creator.loki.command;
|
||||
|
||||
/**
|
||||
* Имена категорий для {@link Command#getCategory()} (справка, селектор help).
|
||||
*/
|
||||
/** Имена категорий для {@link Command#getCategory()} (справка, селектор help). */
|
||||
public final class CommandCategories {
|
||||
|
||||
/** Команды с GIF (nekos.life): hug, slap, poke и т.п. */
|
||||
public static final String GIF_INTERACTIVE = "GIF-интерактив";
|
||||
/** Команды с GIF (nekos.life): hug, slap, poke и т.п. */
|
||||
public static final String GIF_INTERACTIVE = "GIF-интерактив";
|
||||
|
||||
private CommandCategories() {}
|
||||
private CommandCategories() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,45 @@
|
|||
package creator.loki.command;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
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 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 Message getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public List<String> getArgs() {
|
||||
return args;
|
||||
}
|
||||
public List<String> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public MessageChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
public MessageChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public Member getAuthor() {
|
||||
return author;
|
||||
}
|
||||
public Member getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public Guild getGuild() {
|
||||
return guild;
|
||||
}
|
||||
public Guild getGuild() {
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,74 @@
|
|||
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;
|
||||
private final String mainToken;
|
||||
private final String musicToken;
|
||||
private final String radioToken;
|
||||
private final String prefix;
|
||||
private final String activity;
|
||||
private final int healthHttpPort;
|
||||
private final String superAdminId;
|
||||
private final String dbPath;
|
||||
private final LavalinkConfig lavalinkConfig;
|
||||
|
||||
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 BotConfig(
|
||||
String mainToken,
|
||||
String musicToken,
|
||||
String radioToken,
|
||||
String prefix,
|
||||
String activity,
|
||||
int healthHttpPort,
|
||||
String superAdminId,
|
||||
String dbPath,
|
||||
LavalinkConfig lavalinkConfig) {
|
||||
this.mainToken = mainToken;
|
||||
this.musicToken = musicToken;
|
||||
this.radioToken = radioToken;
|
||||
this.prefix = prefix;
|
||||
this.activity = activity;
|
||||
this.healthHttpPort = healthHttpPort;
|
||||
this.superAdminId = superAdminId;
|
||||
this.dbPath = dbPath;
|
||||
this.lavalinkConfig = lavalinkConfig;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
public String getMainToken() {
|
||||
return mainToken;
|
||||
}
|
||||
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
public String getToken() {
|
||||
return mainToken;
|
||||
}
|
||||
|
||||
public String getActivity() {
|
||||
return activity;
|
||||
}
|
||||
public String getMusicToken() {
|
||||
return musicToken;
|
||||
}
|
||||
|
||||
public int getHealthHttpPort() {
|
||||
return healthHttpPort;
|
||||
}
|
||||
public String getRadioToken() {
|
||||
return radioToken;
|
||||
}
|
||||
|
||||
public String getSuperAdminId() {
|
||||
return superAdminId;
|
||||
}
|
||||
public String getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public String getDbPath() {
|
||||
return dbPath;
|
||||
}
|
||||
public String getActivity() {
|
||||
return activity;
|
||||
}
|
||||
|
||||
public int getHealthHttpPort() {
|
||||
return healthHttpPort;
|
||||
}
|
||||
|
||||
public String getSuperAdminId() {
|
||||
return superAdminId;
|
||||
}
|
||||
|
||||
public String getDbPath() {
|
||||
return dbPath;
|
||||
}
|
||||
|
||||
public LavalinkConfig getLavalinkConfig() {
|
||||
return lavalinkConfig;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +1,89 @@
|
|||
package creator.loki.config;
|
||||
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
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 static final Logger logger = LoggerFactory.getLogger(ConfigLoader.class);
|
||||
|
||||
private ConfigLoader() {
|
||||
private ConfigLoader() {}
|
||||
|
||||
public static LavalinkConfig getLavalinkConfig() {
|
||||
Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
|
||||
return new LavalinkConfig(
|
||||
getEnv(dotenv, "LAVALINK_HOST", "127.0.0.1"),
|
||||
Integer.parseInt(getEnv(dotenv, "LAVALINK_PORT", "2333")),
|
||||
getEnv(dotenv, "LAVALINK_PASSWORD", "youshallnotpass"),
|
||||
Boolean.parseBoolean(getEnv(dotenv, "LAVALINK_ENABLED", "true")));
|
||||
}
|
||||
|
||||
private static String getEnv(Dotenv dotenv, String key, String defaultValue) {
|
||||
String value = dotenv.get(key);
|
||||
return (value != null && !value.isBlank()) ? value : defaultValue;
|
||||
}
|
||||
|
||||
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 mainToken = dotenv.get("DISCORD_BOT_TOKEN");
|
||||
if (mainToken == null || mainToken.isBlank()) {
|
||||
throw new IllegalStateException(
|
||||
"Токен бота не найден! Установите переменную окружения DISCORD_BOT_TOKEN или создайте .env файл");
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
String musicToken = dotenv.get("DISCORD_BOT_TOKEN_MUSIC");
|
||||
String radioToken = dotenv.get("DISCORD_BOT_TOKEN_RADIO");
|
||||
|
||||
String prefix = dotenv.get("BOT_PREFIX");
|
||||
if (prefix == null || prefix.isBlank()) {
|
||||
prefix = "!";
|
||||
}
|
||||
|
||||
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);
|
||||
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";
|
||||
LoggerDecorator.warn(logger, "SUPER_ADMIN_ID не задан, используется значение по умолчанию");
|
||||
}
|
||||
|
||||
String dbPath = dotenv.get("DB_PATH");
|
||||
if (dbPath == null || dbPath.isBlank()) {
|
||||
dbPath = "data/bot.db";
|
||||
}
|
||||
|
||||
LavalinkConfig lavalinkConfig = getLavalinkConfig();
|
||||
LoggerDecorator.success(logger, "Конфигурация загружена успешно");
|
||||
return new BotConfig(
|
||||
mainToken,
|
||||
musicToken,
|
||||
radioToken,
|
||||
prefix,
|
||||
activity,
|
||||
healthHttpPort,
|
||||
superAdminId,
|
||||
dbPath,
|
||||
lavalinkConfig);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
src/main/java/creator/loki/config/LavalinkConfig.java
Normal file
39
src/main/java/creator/loki/config/LavalinkConfig.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package creator.loki.config;
|
||||
|
||||
public class LavalinkConfig {
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final String password;
|
||||
private final boolean enabled;
|
||||
|
||||
public LavalinkConfig(String host, int port, String password, boolean enabled) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.password = password;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return "http://" + host + ":" + port;
|
||||
}
|
||||
|
||||
public String getWsUri() {
|
||||
return "ws://" + host + ":" + port;
|
||||
}
|
||||
}
|
||||
35
src/main/java/creator/loki/core/BotCluster.java
Normal file
35
src/main/java/creator/loki/core/BotCluster.java
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
|
||||
public class BotCluster {
|
||||
private final JDA main;
|
||||
private final JDA music;
|
||||
private final JDA radio;
|
||||
|
||||
public BotCluster(JDA main, JDA music, JDA radio) {
|
||||
this.main = main;
|
||||
this.music = music;
|
||||
this.radio = radio;
|
||||
}
|
||||
|
||||
public JDA getMain() {
|
||||
return main;
|
||||
}
|
||||
|
||||
public JDA getMusic() {
|
||||
return music;
|
||||
}
|
||||
|
||||
public JDA getRadio() {
|
||||
return radio;
|
||||
}
|
||||
|
||||
public boolean hasMusicBot() {
|
||||
return music != null;
|
||||
}
|
||||
|
||||
public boolean hasRadioBot() {
|
||||
return radio != null;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,82 +4,174 @@ import com.google.inject.Inject;
|
|||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.core.registry.PrefixCommandRegistrar;
|
||||
import creator.loki.feature.counter.MemberCounterListener;
|
||||
import creator.loki.feature.moderation.MuteScheduler;
|
||||
import creator.loki.feature.metrics.HealthHttpServer;
|
||||
import creator.loki.feature.moderation.MuteScheduler;
|
||||
import creator.loki.feature.music.core.LavalinkManager;
|
||||
import creator.loki.feature.music.listener.MusicBotListener;
|
||||
import creator.loki.feature.music.repository.QueueRepository;
|
||||
import creator.loki.feature.radio.RadioBotListener;
|
||||
import creator.loki.feature.radio.RadioManager;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import dev.arbjerg.lavalink.libraries.jda.JDAVoiceUpdateListener;
|
||||
import net.dv8tion.jda.api.JDA;
|
||||
import net.dv8tion.jda.api.JDABuilder;
|
||||
import net.dv8tion.jda.api.OnlineStatus;
|
||||
import net.dv8tion.jda.api.entities.Activity;
|
||||
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||
import net.dv8tion.jda.api.utils.cache.CacheFlag;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BotInitializer {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BotInitializer.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(BotInitializer.class);
|
||||
|
||||
private final BotConfig config;
|
||||
private final PrefixCommandRegistrar registrar;
|
||||
private final ListenerAggregator listenerAggregator;
|
||||
private final MuteScheduler muteScheduler;
|
||||
private final MemberCounterListener memberCounterListener;
|
||||
private final HealthHttpServer healthHttpServer;
|
||||
private final ShutdownManager shutdownManager;
|
||||
private final BotConfig config;
|
||||
private final PrefixCommandRegistrar registrar;
|
||||
private final ListenerAggregator listenerAggregator;
|
||||
private final MuteScheduler muteScheduler;
|
||||
private final MemberCounterListener memberCounterListener;
|
||||
private final HealthHttpServer healthHttpServer;
|
||||
private final ShutdownManager shutdownManager;
|
||||
private final LavalinkManager lavalinkManager;
|
||||
private final RadioManager radioManager;
|
||||
private final QueueRepository queueRepository;
|
||||
|
||||
@Inject
|
||||
public BotInitializer(
|
||||
BotConfig config,
|
||||
PrefixCommandRegistrar registrar,
|
||||
ListenerAggregator listenerAggregator,
|
||||
MuteScheduler muteScheduler,
|
||||
MemberCounterListener memberCounterListener,
|
||||
HealthHttpServer healthHttpServer,
|
||||
ShutdownManager shutdownManager
|
||||
) {
|
||||
this.config = config;
|
||||
this.registrar = registrar;
|
||||
this.listenerAggregator = listenerAggregator;
|
||||
this.muteScheduler = muteScheduler;
|
||||
this.memberCounterListener = memberCounterListener;
|
||||
this.healthHttpServer = healthHttpServer;
|
||||
this.shutdownManager = shutdownManager;
|
||||
@Inject
|
||||
public BotInitializer(
|
||||
BotConfig config,
|
||||
PrefixCommandRegistrar registrar,
|
||||
ListenerAggregator listenerAggregator,
|
||||
MuteScheduler muteScheduler,
|
||||
MemberCounterListener memberCounterListener,
|
||||
HealthHttpServer healthHttpServer,
|
||||
ShutdownManager shutdownManager,
|
||||
LavalinkManager lavalinkManager,
|
||||
RadioManager radioManager,
|
||||
QueueRepository queueRepository) {
|
||||
this.config = config;
|
||||
this.registrar = registrar;
|
||||
this.listenerAggregator = listenerAggregator;
|
||||
this.muteScheduler = muteScheduler;
|
||||
this.memberCounterListener = memberCounterListener;
|
||||
this.healthHttpServer = healthHttpServer;
|
||||
this.shutdownManager = shutdownManager;
|
||||
this.lavalinkManager = lavalinkManager;
|
||||
this.radioManager = radioManager;
|
||||
this.queueRepository = queueRepository;
|
||||
}
|
||||
|
||||
public BotCluster start() {
|
||||
LoggerDecorator.startup(logger, "Инициализация main бота...");
|
||||
registrar.registerAll();
|
||||
JDA mainJda = createMainJDA();
|
||||
try {
|
||||
mainJda.awaitReady();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Main бот прерван во время ожидания готовности", e);
|
||||
}
|
||||
mainJda.updateCommands().queue();
|
||||
muteScheduler.restorePending(mainJda);
|
||||
for (var guild : mainJda.getGuilds()) {
|
||||
memberCounterListener.startScheduler(guild);
|
||||
}
|
||||
healthHttpServer.start();
|
||||
|
||||
JDA musicJda = null;
|
||||
if (config.getMusicToken() != null && !config.getMusicToken().isBlank()) {
|
||||
LoggerDecorator.startup(logger, "Инициализация music бота...");
|
||||
musicJda = createMusicJDA();
|
||||
try {
|
||||
musicJda.awaitReady();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Music бот прерван во время ожидания готовности", e);
|
||||
}
|
||||
}
|
||||
|
||||
public JDA start() {
|
||||
logger.info("Инициализация бота...");
|
||||
registrar.registerAll();
|
||||
JDA jda = createJDA();
|
||||
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;
|
||||
JDA radioJda = null;
|
||||
if (config.getRadioToken() != null && !config.getRadioToken().isBlank()) {
|
||||
LoggerDecorator.startup(logger, "Инициализация radio бота...");
|
||||
radioJda = createRadioJDA();
|
||||
try {
|
||||
radioJda.awaitReady();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Radio бот прерван во время ожидания готовности", e);
|
||||
}
|
||||
radioManager.setRadioJda(radioJda);
|
||||
}
|
||||
|
||||
private JDA createJDA() {
|
||||
return JDABuilder.createDefault(config.getToken())
|
||||
.setStatus(OnlineStatus.ONLINE)
|
||||
.setActivity(Activity.playing(config.getActivity()))
|
||||
.enableIntents(
|
||||
GatewayIntent.GUILD_MESSAGES,
|
||||
GatewayIntent.MESSAGE_CONTENT,
|
||||
GatewayIntent.GUILD_MEMBERS,
|
||||
GatewayIntent.GUILD_VOICE_STATES
|
||||
)
|
||||
.addEventListeners(listenerAggregator.getAll())
|
||||
.build();
|
||||
BotCluster cluster = new BotCluster(mainJda, musicJda, radioJda);
|
||||
registerShutdownHook(cluster);
|
||||
return cluster;
|
||||
}
|
||||
|
||||
private JDA createMainJDA() {
|
||||
var builder =
|
||||
JDABuilder.createDefault(config.getMainToken())
|
||||
.setStatus(OnlineStatus.ONLINE)
|
||||
.setActivity(Activity.playing(config.getActivity()))
|
||||
.enableIntents(
|
||||
GatewayIntent.GUILD_MESSAGES,
|
||||
GatewayIntent.MESSAGE_CONTENT,
|
||||
GatewayIntent.GUILD_MEMBERS,
|
||||
GatewayIntent.GUILD_VOICE_STATES)
|
||||
.enableCache(CacheFlag.VOICE_STATE)
|
||||
.addEventListeners(listenerAggregator.getAll());
|
||||
|
||||
if (lavalinkManager.getClient() != null) {
|
||||
builder.setVoiceDispatchInterceptor(new JDAVoiceUpdateListener(lavalinkManager.getClient()));
|
||||
}
|
||||
|
||||
private void registerShutdownHook(JDA jda) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(
|
||||
() -> shutdownManager.shutdown(jda), "ShutdownHook"));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private JDA createMusicJDA() {
|
||||
LavalinkManager musicLavalink =
|
||||
new LavalinkManager(config, queueRepository, config.getMusicToken());
|
||||
|
||||
var builder =
|
||||
JDABuilder.createDefault(config.getMusicToken())
|
||||
.setStatus(OnlineStatus.ONLINE)
|
||||
.setActivity(Activity.playing("🎵 q!play"))
|
||||
.enableIntents(
|
||||
GatewayIntent.GUILD_MESSAGES,
|
||||
GatewayIntent.MESSAGE_CONTENT,
|
||||
GatewayIntent.GUILD_VOICE_STATES)
|
||||
.enableCache(CacheFlag.VOICE_STATE);
|
||||
|
||||
if (musicLavalink.getClient() != null) {
|
||||
builder.setVoiceDispatchInterceptor(new JDAVoiceUpdateListener(musicLavalink.getClient()));
|
||||
}
|
||||
|
||||
builder.addEventListeners(
|
||||
new MusicBotListener(
|
||||
musicLavalink,
|
||||
queueRepository,
|
||||
new creator.loki.feature.music.core.PlayerManager(musicLavalink)));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private JDA createRadioJDA() {
|
||||
var builder =
|
||||
JDABuilder.createDefault(config.getRadioToken())
|
||||
.setStatus(OnlineStatus.ONLINE)
|
||||
.setActivity(Activity.playing("📻 r!play"))
|
||||
.enableIntents(
|
||||
GatewayIntent.GUILD_MESSAGES,
|
||||
GatewayIntent.MESSAGE_CONTENT,
|
||||
GatewayIntent.GUILD_VOICE_STATES)
|
||||
.enableCache(CacheFlag.VOICE_STATE);
|
||||
|
||||
builder.addEventListeners(new RadioBotListener(radioManager));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void registerShutdownHook(BotCluster cluster) {
|
||||
Runtime.getRuntime()
|
||||
.addShutdownHook(new Thread(() -> shutdownManager.shutdown(cluster), "ShutdownHook"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,130 +1,146 @@
|
|||
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;
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.core.exception.CommandExceptionHandler;
|
||||
import creator.loki.core.registry.CommandRegistry;
|
||||
import creator.loki.core.security.RateLimiter;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.metrics.MetricsCollector;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
public void handleMessage(MessageReceivedEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getAuthor().isBot()) {
|
||||
return;
|
||||
}
|
||||
String content = event.getMessage().getContentRaw();
|
||||
if (!content.startsWith(config.getPrefix())) {
|
||||
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[] 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();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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().getId();
|
||||
|
||||
// Проверяем глобальный 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();
|
||||
LoggerDecorator.warn(
|
||||
logger, "Rate limit для пользователя " + userId + " в гильдии " + guildId);
|
||||
return;
|
||||
}
|
||||
|
||||
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(
|
||||
// Выполняем команду асинхронно
|
||||
executorService.submit(
|
||||
() -> {
|
||||
long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
CommandContext context =
|
||||
new CommandContext(
|
||||
event.getMessage(),
|
||||
args,
|
||||
event.getChannel(),
|
||||
event.getMember(),
|
||||
event.getGuild()
|
||||
);
|
||||
command.execute(context);
|
||||
event.getGuild());
|
||||
command.execute(context);
|
||||
|
||||
// Записываем метрики
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
metrics.recordCommand(commandName, executionTime);
|
||||
// Записываем метрики
|
||||
long executionTime = System.currentTimeMillis() - startTime;
|
||||
metrics.recordCommand(commandName, executionTime);
|
||||
|
||||
logger.info("Выполнена команда: {} от {} за {} мс",
|
||||
commandName, event.getAuthor().getName(), executionTime);
|
||||
} catch (Exception e) {
|
||||
// Записываем ошибку в метрики
|
||||
metrics.recordError(commandName);
|
||||
LoggerDecorator.command(
|
||||
logger,
|
||||
commandName,
|
||||
event.getAuthor().getName(),
|
||||
"Выполнена за " + executionTime + " мс");
|
||||
} catch (Exception e) {
|
||||
// Записываем ошибку в метрики
|
||||
metrics.recordError(commandName);
|
||||
|
||||
// Используем централизованный обработчик ошибок
|
||||
CommandExceptionHandler.handle(e, event.getChannel(), 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 завершён");
|
||||
public void shutdown() {
|
||||
LoggerDecorator.bot(logger, "Завершение работы EventDispatcher...");
|
||||
executorService.shutdown();
|
||||
try {
|
||||
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
||||
LoggerDecorator.warn(
|
||||
logger, "ExecutorService не завершился за 10 секунд, принудительная остановка");
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LoggerDecorator.error(logger, "Прервано ожидание завершения ExecutorService", e);
|
||||
executorService.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
LoggerDecorator.success(logger, "EventDispatcher завершён");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,16 @@ 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.economy.FishMessageListener;
|
||||
import creator.loki.feature.help.HelpMenuHandler;
|
||||
import creator.loki.feature.leaderboard.LeaderboardButtonListener;
|
||||
import creator.loki.feature.level.LevelUpNotifier;
|
||||
import creator.loki.feature.level.MessageListener;
|
||||
import creator.loki.feature.level.ResetLevelsButtonHandler;
|
||||
import creator.loki.feature.level.VoiceTracker;
|
||||
import creator.loki.feature.moderation.log.ModerationLogger;
|
||||
import creator.loki.feature.music.MusicSlashCommands;
|
||||
import creator.loki.feature.voice.VoiceButtonHandler;
|
||||
import creator.loki.feature.voice.VoiceChannelListener;
|
||||
import creator.loki.feature.voice.VoiceModalHandler;
|
||||
|
|
@ -19,36 +20,48 @@ import creator.loki.feature.welcome.WelcomeListener;
|
|||
|
||||
@Singleton
|
||||
public class ListenerAggregator {
|
||||
private final Object[] listeners;
|
||||
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
|
||||
@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,
|
||||
MusicSlashCommands musicSlashCommands,
|
||||
FishMessageListener fishMessageListener) {
|
||||
this.listeners =
|
||||
new Object[] {
|
||||
readyListener,
|
||||
messageListener,
|
||||
voiceTracker,
|
||||
voiceChannelListener,
|
||||
memberCounterListener,
|
||||
voiceButtonHandler,
|
||||
voiceModalHandler,
|
||||
voiceSyncListener,
|
||||
welcomeListener,
|
||||
moderationLogger,
|
||||
helpMenuHandler,
|
||||
resetLevelsButtonHandler,
|
||||
leaderboardButtonListener,
|
||||
messageCache,
|
||||
musicSlashCommands,
|
||||
fishMessageListener
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Object[] getAll() {
|
||||
return listeners;
|
||||
}
|
||||
public Object[] getAll() {
|
||||
return listeners;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,30 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
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;
|
||||
private final Cache<String, String> cache;
|
||||
|
||||
public MessageCache() {
|
||||
this.cache = Caffeine.newBuilder()
|
||||
.maximumSize(10_000)
|
||||
.expireAfterWrite(1, TimeUnit.HOURS)
|
||||
.build();
|
||||
}
|
||||
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);
|
||||
}
|
||||
@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);
|
||||
}
|
||||
public String getContent(String messageId) {
|
||||
return cache.getIfPresent(messageId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,85 +1,118 @@
|
|||
package creator.loki.core;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.core.security.RateLimiter;
|
||||
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 creator.loki.feature.moderation.MuteScheduler;
|
||||
import creator.loki.feature.radio.RadioManager;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
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 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;
|
||||
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;
|
||||
private final RadioManager radioManager;
|
||||
|
||||
@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;
|
||||
@Inject
|
||||
public ShutdownManager(
|
||||
HealthHttpServer healthHttpServer,
|
||||
CooldownManager cooldownManager,
|
||||
RateLimiter rateLimiter,
|
||||
VoiceTracker voiceTracker,
|
||||
MemberCounterListener memberCounterListener,
|
||||
MuteScheduler muteScheduler,
|
||||
EventDispatcher dispatcher,
|
||||
Database database,
|
||||
RadioManager radioManager) {
|
||||
this.healthHttpServer = healthHttpServer;
|
||||
this.cooldownManager = cooldownManager;
|
||||
this.rateLimiter = rateLimiter;
|
||||
this.voiceTracker = voiceTracker;
|
||||
this.memberCounterListener = memberCounterListener;
|
||||
this.muteScheduler = muteScheduler;
|
||||
this.dispatcher = dispatcher;
|
||||
this.database = database;
|
||||
this.radioManager = radioManager;
|
||||
}
|
||||
|
||||
public void shutdown(JDA jda) {
|
||||
shutdown(new BotCluster(jda, null, null));
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
shutdown(new BotCluster(null, null, null));
|
||||
}
|
||||
|
||||
public void shutdown(BotCluster cluster) {
|
||||
logger.info("========================================");
|
||||
logger.info("Получен сигнал завершения работы ботов...");
|
||||
logger.info("========================================");
|
||||
|
||||
try {
|
||||
// Stop all radio playback
|
||||
if (cluster.hasRadioBot()) {
|
||||
radioManager.getRadioJda().getGuilds().forEach(g -> radioManager.stopRadio(g.getIdLong()));
|
||||
}
|
||||
|
||||
healthHttpServer.stop();
|
||||
LoggerDecorator.bot(logger, "Остановка менеджеров...");
|
||||
cooldownManager.shutdown();
|
||||
rateLimiter.shutdown();
|
||||
voiceTracker.shutdown();
|
||||
memberCounterListener.shutdown();
|
||||
muteScheduler.shutdown();
|
||||
dispatcher.shutdown();
|
||||
|
||||
LoggerDecorator.database(logger, "Закрытие базы данных...");
|
||||
database.close();
|
||||
|
||||
shutdownJDA(cluster.getMain(), "Main");
|
||||
|
||||
if (cluster.hasMusicBot()) {
|
||||
shutdownJDA(cluster.getMusic(), "Music");
|
||||
}
|
||||
|
||||
if (cluster.hasRadioBot()) {
|
||||
shutdownJDA(cluster.getRadio(), "Radio");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LoggerDecorator.error(logger, "Ошибка при завершении работы ботов", e);
|
||||
}
|
||||
|
||||
public void shutdown(JDA jda) {
|
||||
logger.info("========================================");
|
||||
logger.info("Получен сигнал завершения работы бота...");
|
||||
logger.info("========================================");
|
||||
LoggerDecorator.success(logger, "Боты успешно завершены!");
|
||||
}
|
||||
|
||||
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("========================================");
|
||||
private void shutdownJDA(JDA jda, String name) {
|
||||
LoggerDecorator.bot(logger, "Отключение " + name + " JDA...");
|
||||
jda.shutdown();
|
||||
try {
|
||||
if (!jda.awaitShutdown(Duration.ofSeconds(10))) {
|
||||
LoggerDecorator.warn(
|
||||
logger, name + " JDA не завершился за 10 секунд, принудительная остановка");
|
||||
jda.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LoggerDecorator.warn(logger, name + " JDA shutdown прерван");
|
||||
jda.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,21 @@
|
|||
package creator.loki.core.exception;
|
||||
|
||||
/**
|
||||
* Исключение для ошибок выполнения команд
|
||||
*/
|
||||
/** Исключение для ошибок выполнения команд */
|
||||
public class CommandException extends Exception {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String userMessage;
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final String userMessage;
|
||||
|
||||
public CommandException(String userMessage) {
|
||||
super(userMessage);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
public CommandException(String userMessage) {
|
||||
super(userMessage);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
|
||||
public CommandException(String userMessage, Throwable cause) {
|
||||
super(userMessage, cause);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
public CommandException(String userMessage, Throwable cause) {
|
||||
super(userMessage, cause);
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
|
||||
public String getUserMessage() {
|
||||
return userMessage;
|
||||
}
|
||||
public String getUserMessage() {
|
||||
return userMessage;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,32 @@
|
|||
package creator.loki.core.exception;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
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);
|
||||
private static final Logger logger = LoggerFactory.getLogger(CommandExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* Обрабатывает исключение и отправляет сообщение пользователю
|
||||
*/
|
||||
public static void handle(Throwable throwable, MessageChannel channel, String commandName) {
|
||||
logger.error("Ошибка при выполнении команды: {}", commandName, throwable);
|
||||
/** Обрабатывает исключение и отправляет сообщение пользователю */
|
||||
public static void handle(Throwable throwable, MessageChannel channel, String commandName) {
|
||||
LoggerDecorator.error(logger, "Ошибка при выполнении команды: " + commandName, throwable);
|
||||
|
||||
String userMessage;
|
||||
if (throwable instanceof CommandException ce) {
|
||||
userMessage = ce.getUserMessage();
|
||||
} else {
|
||||
userMessage = "Не удалось выполнить команду. Попробуйте позже или обратитесь к администратору.";
|
||||
}
|
||||
String userMessage;
|
||||
if (throwable instanceof CommandException ce) {
|
||||
userMessage = ce.getUserMessage();
|
||||
} else {
|
||||
userMessage =
|
||||
"Не удалось выполнить команду. Попробуйте позже или обратитесь к администратору.";
|
||||
}
|
||||
|
||||
MessageEmbed errorEmbed = EmbedFactory.create()
|
||||
MessageEmbed errorEmbed =
|
||||
EmbedFactory.create()
|
||||
.withTitle("❌ Ошибка")
|
||||
.withDescription(userMessage)
|
||||
.withColor(Color.RED)
|
||||
|
|
@ -35,19 +34,21 @@ public class CommandExceptionHandler {
|
|||
.withFooter("Если ошибка повторяется, обратитесь к администратору")
|
||||
.build();
|
||||
|
||||
channel.sendMessageEmbeds(errorEmbed).queue(
|
||||
success -> { },
|
||||
error -> logger.error("Не удалось отправить сообщение об ошибке", error)
|
||||
);
|
||||
}
|
||||
channel
|
||||
.sendMessageEmbeds(errorEmbed)
|
||||
.queue(
|
||||
success -> {},
|
||||
error ->
|
||||
LoggerDecorator.error(logger, "Не удалось отправить сообщение об ошибке", error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает исключение с кастомным сообщением
|
||||
*/
|
||||
public static void handle(Throwable throwable, MessageChannel channel, String commandName, String customMessage) {
|
||||
logger.error("Ошибка при выполнении команды: {}", commandName, throwable);
|
||||
/** Обрабатывает исключение с кастомным сообщением */
|
||||
public static void handle(
|
||||
Throwable throwable, MessageChannel channel, String commandName, String customMessage) {
|
||||
LoggerDecorator.error(logger, "Ошибка при выполнении команды: " + commandName, throwable);
|
||||
|
||||
MessageEmbed errorEmbed = EmbedFactory.create()
|
||||
MessageEmbed errorEmbed =
|
||||
EmbedFactory.create()
|
||||
.withTitle("❌ Ошибка")
|
||||
.withDescription(customMessage)
|
||||
.withColor(Color.RED)
|
||||
|
|
@ -55,9 +56,11 @@ public class CommandExceptionHandler {
|
|||
.withFooter("Если ошибка повторяется, обратитесь к администратору")
|
||||
.build();
|
||||
|
||||
channel.sendMessageEmbeds(errorEmbed).queue(
|
||||
success -> { },
|
||||
error -> logger.error("Не удалось отправить сообщение об ошибке", error)
|
||||
);
|
||||
}
|
||||
channel
|
||||
.sendMessageEmbeds(errorEmbed)
|
||||
.queue(
|
||||
success -> {},
|
||||
error ->
|
||||
LoggerDecorator.error(logger, "Не удалось отправить сообщение об ошибке", error));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ package creator.loki.core.registry;
|
|||
|
||||
@FunctionalInterface
|
||||
public interface CommandRegistrar {
|
||||
void register(CommandRegistry registry);
|
||||
void register(CommandRegistry registry);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,45 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CommandRegistry {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CommandRegistry.class);
|
||||
private final Map<String, Command> commands = new HashMap<>();
|
||||
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 void register(Command command) {
|
||||
String key = command.getName().toLowerCase();
|
||||
Command previous = commands.put(key, command);
|
||||
if (previous != null) {
|
||||
LoggerDecorator.warn(
|
||||
logger,
|
||||
"Имя команды «"
|
||||
+ key
|
||||
+ "» уже занято: "
|
||||
+ previous.getClass().getSimpleName()
|
||||
+ " заменена на "
|
||||
+ command.getClass().getSimpleName());
|
||||
} else {
|
||||
logger.debug("Зарегистрирована команда: {}", command.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public Command getCommand(String name) {
|
||||
return commands.get(name.toLowerCase());
|
||||
}
|
||||
public Command getCommand(String name) {
|
||||
return commands.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public Collection<Command> getAllCommands() {
|
||||
return Collections.unmodifiableCollection(commands.values());
|
||||
}
|
||||
public Collection<Command> getAllCommands() {
|
||||
return Collections.unmodifiableCollection(commands.values());
|
||||
}
|
||||
|
||||
public int getCommandCount() {
|
||||
return commands.size();
|
||||
}
|
||||
public int getCommandCount() {
|
||||
return commands.size();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,31 @@
|
|||
package creator.loki.core.registry;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
public class PrefixCommandRegistrar {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PrefixCommandRegistrar.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(PrefixCommandRegistrar.class);
|
||||
|
||||
private final CommandRegistry registry;
|
||||
private final Set<CommandRegistrar> registrars;
|
||||
private final CommandRegistry registry;
|
||||
private final Set<CommandRegistrar> registrars;
|
||||
|
||||
@Inject
|
||||
public PrefixCommandRegistrar(CommandRegistry registry, Set<CommandRegistrar> registrars) {
|
||||
this.registry = registry;
|
||||
this.registrars = registrars;
|
||||
}
|
||||
|
||||
public void registerAll() {
|
||||
for (CommandRegistrar registrar : registrars) {
|
||||
registrar.register(registry);
|
||||
}
|
||||
logger.info("Зарегистрировано префиксных команд: {}", registry.getCommandCount());
|
||||
@Inject
|
||||
public PrefixCommandRegistrar(CommandRegistry registry, Set<CommandRegistrar> registrars) {
|
||||
this.registry = registry;
|
||||
this.registrars = registrars;
|
||||
}
|
||||
|
||||
public void registerAll() {
|
||||
for (CommandRegistrar registrar : registrars) {
|
||||
registrar.register(registry);
|
||||
}
|
||||
LoggerDecorator.success(
|
||||
logger, "Зарегистрировано префиксных команд: " + registry.getCommandCount());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,77 +7,69 @@ 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();
|
||||
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);
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, находится ли пользователь на 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;
|
||||
if (Instant.now().isAfter(cooldownEnd)) {
|
||||
cooldowns.remove(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает оставшееся время cooldown в секундах
|
||||
*/
|
||||
public long getRemainingSeconds(String userId, String commandName) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = cooldowns.get(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cooldownEnd == null) {
|
||||
return 0;
|
||||
}
|
||||
/** Возвращает оставшееся время cooldown в секундах */
|
||||
public long getRemainingSeconds(String userId, String commandName) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = cooldowns.get(key);
|
||||
|
||||
long remaining = cooldownEnd.getEpochSecond() - Instant.now().getEpochSecond();
|
||||
return Math.max(0, remaining);
|
||||
if (cooldownEnd == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Применяет cooldown для пользователя на команду
|
||||
*/
|
||||
public void applyCooldown(String userId, String commandName, int seconds) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = Instant.now().plusSeconds(seconds);
|
||||
cooldowns.put(key, cooldownEnd);
|
||||
}
|
||||
long remaining = cooldownEnd.getEpochSecond() - Instant.now().getEpochSecond();
|
||||
return Math.max(0, remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Очищает истёкшие cooldown'ы
|
||||
*/
|
||||
public void cleanup() {
|
||||
Instant now = Instant.now();
|
||||
cooldowns.entrySet().removeIf(entry -> now.isAfter(entry.getValue()));
|
||||
}
|
||||
/** Применяет cooldown для пользователя на команду */
|
||||
public void applyCooldown(String userId, String commandName, int seconds) {
|
||||
String key = userId + ":" + commandName;
|
||||
Instant cooldownEnd = Instant.now().plusSeconds(seconds);
|
||||
cooldowns.put(key, cooldownEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Завершает работу менеджера
|
||||
*/
|
||||
public void shutdown() {
|
||||
cleanupExecutor.shutdown();
|
||||
try {
|
||||
if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
/** Очищает истёкшие 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,163 +1,150 @@
|
|||
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
|
||||
*/
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Rate limiter для защиты от спама Использует алгоритм Token Bucket */
|
||||
public class RateLimiter {
|
||||
private static final Logger logger = LoggerFactory.getLogger(RateLimiter.class);
|
||||
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 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();
|
||||
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 RateLimiter() {
|
||||
// Очистка старых bucket'ов каждые 5 минут
|
||||
cleanupExecutor.scheduleAtFixedRate(
|
||||
() -> Thread.startVirtualThread(this::cleanup), 5, 5, TimeUnit.MINUTES);
|
||||
LoggerDecorator.bot(logger, "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() {
|
||||
LoggerDecorator.bot(logger, "Завершение работы RateLimiter...");
|
||||
cleanupExecutor.shutdown();
|
||||
try {
|
||||
if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
cleanupExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
LoggerDecorator.success(logger, "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 boolean allowUser(String userId) {
|
||||
TokenBucket bucket = userBuckets.computeIfAbsent(
|
||||
userId,
|
||||
k -> new TokenBucket(DEFAULT_USER_CAPACITY, DEFAULT_USER_REFILL_RATE)
|
||||
);
|
||||
return bucket.tryConsume();
|
||||
public synchronized boolean tryConsume() {
|
||||
refill();
|
||||
lastAccessTime = System.currentTimeMillis();
|
||||
|
||||
if (tokens >= 1.0) {
|
||||
tokens -= 1.0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, может ли гильдия выполнить действие
|
||||
*/
|
||||
public boolean allowGuild(String guildId) {
|
||||
TokenBucket bucket = guildBuckets.computeIfAbsent(
|
||||
guildId,
|
||||
k -> new TokenBucket(DEFAULT_GUILD_CAPACITY, DEFAULT_GUILD_REFILL_RATE)
|
||||
);
|
||||
return bucket.tryConsume();
|
||||
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 boolean allow(String userId, String guildId) {
|
||||
return allowUser(userId) && allowGuild(guildId);
|
||||
public synchronized long getWaitTime() {
|
||||
refill();
|
||||
if (tokens >= 1.0) {
|
||||
return 0;
|
||||
}
|
||||
double tokensNeeded = 1.0 - tokens;
|
||||
return (long) Math.ceil(tokensNeeded / refillRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает оставшееся время до восстановления для пользователя (в секундах)
|
||||
*/
|
||||
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;
|
||||
}
|
||||
public long getLastAccessTime() {
|
||||
return lastAccessTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,90 +1,108 @@
|
|||
package creator.loki.database;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Database {
|
||||
private static final Logger logger = LoggerFactory.getLogger(Database.class);
|
||||
private final String dbPath;
|
||||
private HikariDataSource dataSource;
|
||||
private static final Logger logger = LoggerFactory.getLogger(Database.class);
|
||||
private final String dbPath;
|
||||
private HikariDataSource dataSource;
|
||||
|
||||
public Database(String dbPath) {
|
||||
this.dbPath = dbPath;
|
||||
public Database(String dbPath) {
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
HikariConfig config = new HikariConfig();
|
||||
config.setJdbcUrl("jdbc:sqlite:" + dbPath);
|
||||
config.setMaximumPoolSize(5);
|
||||
config.setConnectionTimeout(10000);
|
||||
config.setIdleTimeout(0);
|
||||
config.setMaxLifetime(600000);
|
||||
config.setPoolName("LokiDB");
|
||||
|
||||
dataSource = new HikariDataSource(config);
|
||||
LoggerDecorator.database(
|
||||
logger,
|
||||
"Подключение к базе данных: "
|
||||
+ dbPath
|
||||
+ " (pool size: "
|
||||
+ config.getMaximumPoolSize()
|
||||
+ ")");
|
||||
|
||||
try (Connection conn = dataSource.getConnection();
|
||||
Statement stmt = conn.createStatement()) {
|
||||
stmt.execute("PRAGMA foreign_keys = ON");
|
||||
} catch (SQLException e) {
|
||||
LoggerDecorator.error(logger, "Ошибка включения foreign keys", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void connect() {
|
||||
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("Ошибка включения foreign keys", e);
|
||||
}
|
||||
public Connection getConnection() {
|
||||
try {
|
||||
return dataSource.getConnection();
|
||||
} catch (SQLException e) {
|
||||
LoggerDecorator.error(logger, "Ошибка получения соединения из пула", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Connection getConnection() {
|
||||
try {
|
||||
return dataSource.getConnection();
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения соединения из пула", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
public void execute(String sql) {
|
||||
try (Connection conn = getConnection();
|
||||
Statement stmt = conn.createStatement()) {
|
||||
stmt.execute(sql);
|
||||
} catch (SQLException e) {
|
||||
LoggerDecorator.error(logger, "Ошибка выполнения SQL: " + sql, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void execute(String sql) {
|
||||
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
|
||||
stmt.execute(sql);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка выполнения SQL: {}", sql, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public PreparedStatement prepare(String sql) {
|
||||
try {
|
||||
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();
|
||||
}
|
||||
}
|
||||
public PreparedStatement prepare(String sql) {
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = getConnection();
|
||||
PreparedStatement stmt = conn.prepareStatement(sql);
|
||||
Connection finalConn = conn;
|
||||
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 {
|
||||
finalConn.close();
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка подготовки SQL: {}", sql, e);
|
||||
throw new RuntimeException(e);
|
||||
return method.invoke(stmt, args);
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
if (conn != null) {
|
||||
try {
|
||||
conn.close();
|
||||
} catch (SQLException ignored) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
LoggerDecorator.error(logger, "Ошибка подготовки SQL: " + sql, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (dataSource != null && !dataSource.isClosed()) {
|
||||
dataSource.close();
|
||||
logger.info("Пул соединений закрыт");
|
||||
}
|
||||
public void close() {
|
||||
if (dataSource != null && !dataSource.isClosed()) {
|
||||
dataSource.close();
|
||||
LoggerDecorator.database(logger, "Пул соединений закрыт");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,190 +1,196 @@
|
|||
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.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Миграция данных из JSON в SQLite
|
||||
*/
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Миграция данных из JSON в SQLite */
|
||||
public class DatabaseMigration {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseMigration.class);
|
||||
private final Database database;
|
||||
private final Gson gson = new Gson();
|
||||
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;
|
||||
public DatabaseMigration(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/** Мигрировать данные из JSON */
|
||||
public void migrateFromJson(String jsonPath) {
|
||||
LoggerDecorator.database(logger, "Начало миграции данных из JSON: " + jsonPath);
|
||||
|
||||
if (!new java.io.File(jsonPath).exists()) {
|
||||
logger.info("No legacy data file found at {}, skipping migration", jsonPath);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Мигрировать данные из JSON
|
||||
*/
|
||||
public void migrateFromJson(String jsonPath) {
|
||||
logger.info("Начало миграции данных из JSON: {}", jsonPath);
|
||||
try (InputStreamReader reader =
|
||||
new InputStreamReader(new FileInputStream(jsonPath), StandardCharsets.UTF_8)) {
|
||||
Type type = new TypeToken<Map<String, Object>>() {}.getType();
|
||||
Map<String, Object> data = gson.fromJson(reader, type);
|
||||
|
||||
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) {
|
||||
LoggerDecorator.warn(logger, "JSON файл пустой или не существует");
|
||||
return;
|
||||
}
|
||||
|
||||
if (data == null) {
|
||||
logger.warn("JSON файл пустой или не существует");
|
||||
return;
|
||||
}
|
||||
if (isMigrated()) {
|
||||
LoggerDecorator.database(logger, "Данные уже мигрированы, пропускаем");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMigrated()) {
|
||||
logger.info("Данные уже мигрированы, пропускаем");
|
||||
return;
|
||||
}
|
||||
migrateUserStats(data);
|
||||
migrateReputation(data);
|
||||
migrateShipPairs(data);
|
||||
migrateCustomRoles(data);
|
||||
markMigrated();
|
||||
|
||||
migrateUserStats(data);
|
||||
migrateReputation(data);
|
||||
migrateShipPairs(data);
|
||||
migrateCustomRoles(data);
|
||||
markMigrated();
|
||||
LoggerDecorator.success(logger, "Миграция завершена успешно");
|
||||
|
||||
logger.info("Миграция завершена успешно");
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка миграции данных", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LoggerDecorator.error(logger, "Ошибка миграции данных", 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 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);
|
||||
}
|
||||
private void markMigrated() {
|
||||
try (PreparedStatement stmt =
|
||||
database.prepare(
|
||||
"INSERT OR REPLACE INTO config (key, value) VALUES ('migrated', 'true')")) {
|
||||
stmt.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LoggerDecorator.error(logger, "Ошибка сохранения статуса миграции", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void migrateUserStats(Map<String, Object> data) {
|
||||
Map<String, Object> userStats = (Map<String, Object>) data.get("userStats");
|
||||
if (userStats == null) return;
|
||||
@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());
|
||||
LoggerDecorator.database(logger, "Миграция userStats: " + userStats.size() + " записей");
|
||||
|
||||
String sql = """
|
||||
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();
|
||||
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.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);
|
||||
}
|
||||
stmt.executeUpdate();
|
||||
}
|
||||
LoggerDecorator.success(logger, "userStats мигрированы");
|
||||
} catch (SQLException e) {
|
||||
LoggerDecorator.error(logger, "Ошибка миграции 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;
|
||||
@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());
|
||||
LoggerDecorator.database(logger, "Миграция 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);
|
||||
}
|
||||
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();
|
||||
}
|
||||
LoggerDecorator.success(logger, "reputation мигрирована");
|
||||
} catch (SQLException e) {
|
||||
LoggerDecorator.error(logger, "Ошибка миграции 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;
|
||||
@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());
|
||||
LoggerDecorator.database(logger, "Миграция 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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
LoggerDecorator.success(logger, "shipPairs мигрированы");
|
||||
} catch (SQLException e) {
|
||||
LoggerDecorator.error(logger, "Ошибка миграции 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;
|
||||
@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");
|
||||
LoggerDecorator.database(logger, "Миграция 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();
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
LoggerDecorator.success(logger, "custom_roles мигрированы");
|
||||
} catch (SQLException e) {
|
||||
LoggerDecorator.error(logger, "Ошибка миграции custom_roles", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,249 +1,23 @@
|
|||
package creator.loki.database;
|
||||
|
||||
import creator.loki.database.schema.CoreTables;
|
||||
import creator.loki.database.schema.FeatureTables;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Схема базы данных - создание таблиц
|
||||
*/
|
||||
public class DatabaseSchema {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseSchema.class);
|
||||
private final Database database;
|
||||
private static final Logger logger = LoggerFactory.getLogger(DatabaseSchema.class);
|
||||
private final Database database;
|
||||
|
||||
public DatabaseSchema(Database database) {
|
||||
this.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("Индексы созданы успешно");
|
||||
}
|
||||
public void createTables() {
|
||||
LoggerDecorator.database(logger, "Создание таблиц базы данных...");
|
||||
CoreTables.create(database);
|
||||
FeatureTables.create(database);
|
||||
LoggerDecorator.success(logger, "Таблицы созданы успешно");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
73
src/main/java/creator/loki/database/schema/CoreTables.java
Normal file
73
src/main/java/creator/loki/database/schema/CoreTables.java
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package creator.loki.database.schema;
|
||||
|
||||
import creator.loki.database.Database;
|
||||
|
||||
public final class CoreTables {
|
||||
|
||||
public static void create(Database database) {
|
||||
createConfig(database);
|
||||
createUserStats(database);
|
||||
createReputation(database);
|
||||
createShipPairs(database);
|
||||
createIndexes(database);
|
||||
}
|
||||
|
||||
private static void createConfig(Database database) {
|
||||
database.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS config (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private static void createUserStats(Database database) {
|
||||
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 static void createReputation(Database database) {
|
||||
database.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS reputation (
|
||||
user_id TEXT PRIMARY KEY,
|
||||
reputation REAL DEFAULT 0.0
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private static void createShipPairs(Database database) {
|
||||
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 static void createIndexes(Database database) {
|
||||
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)");
|
||||
database.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_reputation_value ON reputation(reputation DESC)");
|
||||
}
|
||||
}
|
||||
193
src/main/java/creator/loki/database/schema/FeatureTables.java
Normal file
193
src/main/java/creator/loki/database/schema/FeatureTables.java
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package creator.loki.database.schema;
|
||||
|
||||
import creator.loki.database.Database;
|
||||
|
||||
public final class FeatureTables {
|
||||
|
||||
public static void create(Database database) {
|
||||
createEconomy(database);
|
||||
createMusicQueue(database);
|
||||
createWarnings(database);
|
||||
createModerationCases(database);
|
||||
createActiveMutes(database);
|
||||
createCustomRoles(database);
|
||||
createVoiceChannels(database);
|
||||
createVoicePresets(database);
|
||||
createCounterStats(database);
|
||||
createRatings(database);
|
||||
createIndexes(database);
|
||||
}
|
||||
|
||||
private static void createEconomy(Database database) {
|
||||
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 static void createMusicQueue(Database database) {
|
||||
database.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS music_queue (
|
||||
guild_id TEXT NOT NULL,
|
||||
position INTEGER NOT NULL,
|
||||
track_encoded TEXT NOT NULL,
|
||||
track_title TEXT,
|
||||
track_author TEXT,
|
||||
track_uri TEXT,
|
||||
track_duration BIGINT,
|
||||
PRIMARY KEY (guild_id, position)
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private static void createWarnings(Database database) {
|
||||
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 static void createModerationCases(Database database) {
|
||||
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 static void createActiveMutes(Database database) {
|
||||
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 static void createCustomRoles(Database database) {
|
||||
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 static void createVoiceChannels(Database database) {
|
||||
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 static void createVoicePresets(Database database) {
|
||||
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 static void createCounterStats(Database database) {
|
||||
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 static void createRatings(Database database) {
|
||||
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 static void createIndexes(Database database) {
|
||||
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)");
|
||||
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)");
|
||||
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)");
|
||||
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)");
|
||||
}
|
||||
}
|
||||
|
|
@ -19,120 +19,131 @@ import creator.loki.database.Database;
|
|||
import creator.loki.database.DatabaseMigration;
|
||||
import creator.loki.database.DatabaseSchema;
|
||||
import creator.loki.feature.config.repository.ConfigRepository;
|
||||
import creator.loki.feature.config.repository.GuildConfigRepository;
|
||||
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.economy.EconomyCommandRegistrar;
|
||||
import creator.loki.feature.economy.EconomyModule;
|
||||
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.MetricsCollector;
|
||||
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.music.MusicModule;
|
||||
import creator.loki.feature.music.repository.QueueRepository;
|
||||
import creator.loki.feature.profile.ProfileModule;
|
||||
import creator.loki.feature.radio.RadioCommandRegistrar;
|
||||
import creator.loki.feature.radio.RadioModule;
|
||||
import creator.loki.feature.rating.repository.RatingsRepository;
|
||||
import creator.loki.feature.reputation.repository.ReputationRepository;
|
||||
import creator.loki.feature.role.RoleModule;
|
||||
import creator.loki.feature.ship.repository.ShipPairRepository;
|
||||
import creator.loki.feature.utility.UtilityCommandRegistrar;
|
||||
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;
|
||||
|
||||
public class BotModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(CommandRegistry.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);
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(CommandRegistry.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());
|
||||
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());
|
||||
install(new RadioModule());
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
commandBinder.addBinding().to(RadioCommandRegistrar.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
BotConfig provideBotConfig() {
|
||||
return ConfigLoader.load();
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
BotConfig provideBotConfig() {
|
||||
return ConfigLoader.load();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
EventDispatcher provideEventDispatcher(
|
||||
BotConfig config,
|
||||
CommandRegistry registry,
|
||||
RateLimiter rateLimiter,
|
||||
MetricsCollector metrics
|
||||
) {
|
||||
return new EventDispatcher(config, registry, rateLimiter, metrics);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
EventDispatcher provideEventDispatcher(
|
||||
BotConfig config,
|
||||
CommandRegistry registry,
|
||||
RateLimiter rateLimiter,
|
||||
MetricsCollector metrics) {
|
||||
return new EventDispatcher(config, registry, rateLimiter, metrics);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Database provideDatabase(BotConfig config) {
|
||||
Database database = new Database(config.getDbPath());
|
||||
database.connect();
|
||||
DatabaseSchema schema = new DatabaseSchema(database);
|
||||
schema.createTables();
|
||||
DatabaseMigration migration = new DatabaseMigration(database);
|
||||
migration.migrateFromJson("data/bot_data.json");
|
||||
return database;
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
Database provideDatabase(BotConfig config) {
|
||||
Database database = new Database(config.getDbPath());
|
||||
database.connect();
|
||||
DatabaseSchema schema = new DatabaseSchema(database);
|
||||
schema.createTables();
|
||||
DatabaseMigration migration = new DatabaseMigration(database);
|
||||
migration.migrateFromJson("data/bot_data.json");
|
||||
return database;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ConfigRepository provideConfigRepository(Database database) {
|
||||
return new ConfigRepository(database);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
ConfigRepository provideConfigRepository(Database database) {
|
||||
return new ConfigRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
GuildConfigRepository provideGuildConfigRepository(ConfigRepository configRepository) {
|
||||
return new PrefixedGuildConfigRepository(configRepository);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
GuildConfigRepository provideGuildConfigRepository(ConfigRepository configRepository) {
|
||||
return new PrefixedGuildConfigRepository(configRepository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
RatingsRepository provideRatingsRepository(Database database) {
|
||||
return new RatingsRepository(database);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
RatingsRepository provideRatingsRepository(Database database) {
|
||||
return new RatingsRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ReputationRepository provideReputationRepository(Database database) {
|
||||
return new ReputationRepository(database);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
ReputationRepository provideReputationRepository(Database database) {
|
||||
return new ReputationRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ShipPairRepository provideShipPairRepository(Database database) {
|
||||
return new ShipPairRepository(database);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
ShipPairRepository provideShipPairRepository(Database database) {
|
||||
return new ShipPairRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
QueueRepository provideQueueRepository(Database database) {
|
||||
return new QueueRepository(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;
|
||||
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
public class EmbedFactory {
|
||||
private final EmbedBuilder builder;
|
||||
private final EmbedBuilder builder;
|
||||
|
||||
private EmbedFactory() {
|
||||
this.builder = new EmbedBuilder();
|
||||
}
|
||||
private EmbedFactory() {
|
||||
this.builder = new EmbedBuilder();
|
||||
}
|
||||
|
||||
public static EmbedFactory create() {
|
||||
return new EmbedFactory();
|
||||
}
|
||||
public static EmbedFactory create() {
|
||||
return new EmbedFactory();
|
||||
}
|
||||
|
||||
public EmbedFactory withTitle(String title) {
|
||||
builder.setTitle(title);
|
||||
return this;
|
||||
}
|
||||
public EmbedFactory withTitle(String title) {
|
||||
builder.setTitle(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withDescription(String description) {
|
||||
builder.setDescription(description);
|
||||
return this;
|
||||
}
|
||||
public EmbedFactory withDescription(String description) {
|
||||
builder.setDescription(description);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withColor(Color color) {
|
||||
builder.setColor(color);
|
||||
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 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) {
|
||||
builder.setFooter(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withFooter(String text, String iconUrl) {
|
||||
builder.setFooter(text, iconUrl);
|
||||
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 withThumbnail(String url) {
|
||||
builder.setThumbnail(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withImage(String url) {
|
||||
builder.setImage(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) {
|
||||
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) {
|
||||
builder.setAuthor(name, url);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EmbedFactory withAuthor(String name, String url, String iconUrl) {
|
||||
builder.setAuthor(name, url, iconUrl);
|
||||
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();
|
||||
}
|
||||
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;
|
||||
|
||||
import net.dv8tion.jda.api.EmbedBuilder;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
public final class EmbedTemplates {
|
||||
|
||||
private 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 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 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 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();
|
||||
}
|
||||
public static MessageEmbed warning(String title, String description) {
|
||||
return new EmbedBuilder()
|
||||
.setColor(Color.ORANGE)
|
||||
.setTitle("⚠️ " + title)
|
||||
.setDescription(description)
|
||||
.setTimestamp(Instant.now())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
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;
|
||||
import creator.loki.command.CommandCategories;
|
||||
|
||||
static {
|
||||
EMOJIS = Map.ofEntries(
|
||||
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("Развлечения", "🎮"),
|
||||
|
|
@ -21,10 +22,10 @@ public final class HelpCategories {
|
|||
Map.entry("Утилиты", "🔧"),
|
||||
Map.entry("Система", "💠"),
|
||||
Map.entry("Настройка", "⚙️"),
|
||||
Map.entry("Супер-Админ", "🔐")
|
||||
);
|
||||
Map.entry("Супер-Админ", "🔐"));
|
||||
|
||||
COLORS = Map.ofEntries(
|
||||
COLORS =
|
||||
Map.ofEntries(
|
||||
Map.entry("Общие", new Color(52, 152, 219)),
|
||||
Map.entry("Роли", new Color(155, 89, 182)),
|
||||
Map.entry("Развлечения", new Color(241, 196, 15)),
|
||||
|
|
@ -34,42 +35,41 @@ public final class HelpCategories {
|
|||
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))
|
||||
);
|
||||
Map.entry("Супер-Админ", new Color(113, 54, 138)));
|
||||
|
||||
DESCRIPTIONS = Map.ofEntries(
|
||||
DESCRIPTIONS =
|
||||
Map.ofEntries(
|
||||
Map.entry("Общие", "Основные команды бота"),
|
||||
Map.entry("Роли", "Управление кастомными ролями"),
|
||||
Map.entry("Развлечения", "Игры, мемы, рейтинги, текст"),
|
||||
Map.entry(CommandCategories.GIF_INTERACTIVE, "Действия с участником в GIF (nekos.life)"),
|
||||
Map.entry(
|
||||
CommandCategories.GIF_INTERACTIVE, "Действия с участником в GIF (nekos.life)"),
|
||||
Map.entry("Модерация", "Команды модерации сервера"),
|
||||
Map.entry("Статистика", "Уровни, репутация и статистика"),
|
||||
Map.entry("Утилиты", "Полезные инструменты"),
|
||||
Map.entry("Система", "Метрики и служебная информация для админов"),
|
||||
Map.entry("Настройка", "Настройка систем бота (только админы)"),
|
||||
Map.entry("Супер-Админ", "Сервисные команды владельца бота")
|
||||
);
|
||||
}
|
||||
Map.entry("Супер-Админ", "Сервисные команды владельца бота"));
|
||||
}
|
||||
|
||||
private HelpCategories() {
|
||||
}
|
||||
private HelpCategories() {}
|
||||
|
||||
public static String getEmoji(String category) {
|
||||
return EMOJIS.getOrDefault(category, "📌");
|
||||
}
|
||||
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 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 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;
|
||||
public static boolean isCategoryVisible(String category, boolean isAdmin, boolean isSuper) {
|
||||
if ("Настройка".equals(category) && !isAdmin) {
|
||||
return false;
|
||||
}
|
||||
return !"Супер-Админ".equals(category) || isSuper;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,158 +1,172 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.feature.rating.repository.RatingsRepository;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Команда для генерации всех rating значений для всех участников сервера
|
||||
* Доступна только супер-админу
|
||||
* Команда для генерации всех 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();
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "generateratings";
|
||||
if (context.getGuild() == null) {
|
||||
context.getChannel().sendMessage("❌ Команда работает только на сервере.").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@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(
|
||||
var initial =
|
||||
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("✅ Генерация завершена")
|
||||
.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();
|
||||
"Начинаю генерацию всех rating значений для участников сервера...")
|
||||
.withColor(Color.YELLOW)
|
||||
.build())
|
||||
.complete();
|
||||
|
||||
logger.info("Супер-админ {} сгенерировал рейтинги для {} участников",
|
||||
context.getAuthor().getUser().getName(), memberCount);
|
||||
try {
|
||||
String guildId = context.getGuild().getId();
|
||||
int memberCount = 0;
|
||||
int generatedCount = 0;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при генерации рейтингов", e);
|
||||
initial.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("❌ Ошибка")
|
||||
.withDescription("Произошла ошибка при генерации рейтингов: " + e.getMessage())
|
||||
.withColor(Color.RED)
|
||||
.build()
|
||||
).queue();
|
||||
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; // Не перезаписываем существующие значения
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует 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
|
||||
// Генерируем значение в зависимости от типа
|
||||
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
|
||||
}
|
||||
case "fat" -> random.nextInt(101); // 0-100%
|
||||
case "boykisser" -> random.nextInt(101); // 0-100%
|
||||
default -> random.nextInt(101); // ship, gay: 0-100%
|
||||
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);
|
||||
}
|
||||
// Сохраняем значение в БД
|
||||
ratingsRepository.setRating(guildId, userId, ratingType, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,84 +1,74 @@
|
|||
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;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Репозиторий для работы с конфигурацией
|
||||
*/
|
||||
import creator.loki.database.Database;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Репозиторий для работы с конфигурацией */
|
||||
public class ConfigRepository implements StringConfigStore {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigRepository.class);
|
||||
private final Database database;
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConfigRepository.class);
|
||||
private final Database database;
|
||||
|
||||
public ConfigRepository(Database database) {
|
||||
this.database = database;
|
||||
public ConfigRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/** Получить значение конфигурации */
|
||||
public String get(String key) {
|
||||
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");
|
||||
}
|
||||
return null;
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения конфигурации: {}", key, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить значение конфигурации
|
||||
*/
|
||||
public String get(String key) {
|
||||
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");
|
||||
}
|
||||
return null;
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения конфигурации: {}", key, e);
|
||||
return null;
|
||||
}
|
||||
/** Установить значение конфигурации */
|
||||
public void set(String key, String value) {
|
||||
try (PreparedStatement stmt =
|
||||
database.prepare("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)")) {
|
||||
stmt.setString(1, key);
|
||||
stmt.setString(2, value);
|
||||
stmt.executeUpdate();
|
||||
logger.info("Конфигурация обновлена: {} = {}", key, value);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка сохранения конфигурации: {}", key, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить значение конфигурации
|
||||
*/
|
||||
public void set(String key, String value) {
|
||||
try (PreparedStatement stmt = database.prepare(
|
||||
"INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)"
|
||||
)) {
|
||||
stmt.setString(1, key);
|
||||
stmt.setString(2, value);
|
||||
stmt.executeUpdate();
|
||||
logger.info("Конфигурация обновлена: {} = {}", key, value);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка сохранения конфигурации: {}", key, e);
|
||||
}
|
||||
public Map<String, String> getAll() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
try (PreparedStatement stmt = database.prepare("SELECT key, value FROM config")) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
result.put(rs.getString("key"), rs.getString("value"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения всех конфигураций", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, String> getAll() {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
try (PreparedStatement stmt = database.prepare("SELECT key, value FROM config")) {
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
while (rs.next()) {
|
||||
result.put(rs.getString("key"), rs.getString("value"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения всех конфигураций", e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить значение конфигурации
|
||||
*/
|
||||
public void delete(String key) {
|
||||
try (PreparedStatement stmt = database.prepare("DELETE FROM config WHERE key = ?")) {
|
||||
stmt.setString(1, key);
|
||||
stmt.executeUpdate();
|
||||
logger.info("Конфигурация удалена: {}", key);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка удаления конфигурации: {}", key, e);
|
||||
}
|
||||
/** Удалить значение конфигурации */
|
||||
public void delete(String key) {
|
||||
try (PreparedStatement stmt = database.prepare("DELETE FROM config WHERE key = ?")) {
|
||||
stmt.setString(1, key);
|
||||
stmt.executeUpdate();
|
||||
logger.info("Конфигурация удалена: {}", key);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка удаления конфигурации: {}", key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
package creator.loki.feature.config.repository;
|
||||
|
||||
/**
|
||||
* Ключи конфигурации с привязкой к гильдии (хранение: {@code guildId + ":" + key} в общей таблице config).
|
||||
* Ключи конфигурации с привязкой к гильдии (хранение: {@code guildId + ":" + key} в общей таблице
|
||||
* config).
|
||||
*/
|
||||
public interface GuildConfigRepository {
|
||||
|
||||
/**
|
||||
* @return значение или {@code null}, если нет записи
|
||||
*/
|
||||
String get(String guildId, String key);
|
||||
/**
|
||||
* @return значение или {@code null}, если нет записи
|
||||
*/
|
||||
String get(String guildId, String key);
|
||||
|
||||
void set(String guildId, String key, String value);
|
||||
void set(String guildId, String key, String value);
|
||||
|
||||
void delete(String guildId, String key);
|
||||
void delete(String guildId, String key);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,33 @@
|
|||
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:}.
|
||||
* Реализация {@link GuildConfigRepository} поверх {@link StringConfigStore} с префиксом {@code
|
||||
* guildId:}.
|
||||
*/
|
||||
public final class PrefixedGuildConfigRepository implements GuildConfigRepository {
|
||||
|
||||
private final StringConfigStore store;
|
||||
private final StringConfigStore store;
|
||||
|
||||
public PrefixedGuildConfigRepository(StringConfigStore store) {
|
||||
this.store = store;
|
||||
}
|
||||
public PrefixedGuildConfigRepository(StringConfigStore store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
private static String storageKey(String guildId, String key) {
|
||||
return guildId + ":" + key;
|
||||
}
|
||||
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 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 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));
|
||||
}
|
||||
@Override
|
||||
public void delete(String guildId, String key) {
|
||||
store.delete(storageKey(guildId, key));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
package creator.loki.feature.config.repository;
|
||||
|
||||
/**
|
||||
* Плоское хранилище строковых ключей (глобальная таблица config и т.п.).
|
||||
*/
|
||||
/** Плоское хранилище строковых ключей (глобальная таблица config и т.п.). */
|
||||
public interface StringConfigStore {
|
||||
|
||||
/**
|
||||
* @return значение или {@code null}
|
||||
*/
|
||||
String get(String key);
|
||||
/**
|
||||
* @return значение или {@code null}
|
||||
*/
|
||||
String get(String key);
|
||||
|
||||
void set(String key, String value);
|
||||
void set(String key, String value);
|
||||
|
||||
void delete(String key);
|
||||
void delete(String key);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
public class CounterConfigFormatter {
|
||||
|
||||
public static void sendChannelSetConfirmation(
|
||||
net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel channel,
|
||||
int memberCount,
|
||||
net.dv8tion.jda.api.entities.channel.middleman.MessageChannel textChannel) {
|
||||
textChannel
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("✅ Канал счетчика установлен")
|
||||
.withDescription(
|
||||
"Канал " + channel.getAsMention() + " теперь отображает количество участников")
|
||||
.addField("Текущее количество", String.valueOf(memberCount), false)
|
||||
.withColor(Color.GREEN)
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
|
||||
public static void sendFormatUpdated(
|
||||
String format, net.dv8tion.jda.api.entities.channel.middleman.MessageChannel channel) {
|
||||
channel
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("✅ Формат обновлен")
|
||||
.withDescription("Новый формат: `" + format + "`")
|
||||
.addField("Пример", format.replace("{count}", "123"), false)
|
||||
.withColor(Color.GREEN)
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
|
||||
public static void sendInfo(
|
||||
net.dv8tion.jda.api.entities.Guild guild,
|
||||
String channelId,
|
||||
String format,
|
||||
net.dv8tion.jda.api.entities.channel.middleman.MessageChannel channel) {
|
||||
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(guild.getMemberCount())), false);
|
||||
} else {
|
||||
embed.addField("Формат", "❌ Не установлен", false);
|
||||
}
|
||||
channel.sendMessageEmbeds(embed.build()).queue();
|
||||
}
|
||||
|
||||
public static void sendHelp(
|
||||
net.dv8tion.jda.api.entities.channel.middleman.MessageChannel channel) {
|
||||
channel
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("📊 Настройка счетчика участников")
|
||||
.withDescription(
|
||||
"""
|
||||
**Доступные команды:**
|
||||
• `!counter-setup channel #войс` или `id` - канал счётчика
|
||||
• `!counter-setup format 【👥】Участники: {count}` - Установить формат
|
||||
• `!counter-setup info` - Показать текущую конфигурацию
|
||||
|
||||
В формате используйте `{count}` для отображения количества.
|
||||
Только для администраторов!
|
||||
""")
|
||||
.withColor(Color.BLUE)
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +1,102 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import creator.loki.feature.config.repository.ConfigRepository;
|
||||
import creator.loki.feature.counter.repository.CounterStatsRepository;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
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 static final Logger logger = LoggerFactory.getLogger(CounterManager.class);
|
||||
private final CounterStatsRepository statsRepo;
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
private String counterChannelId;
|
||||
private String format = "【👥】Участники: {count}";
|
||||
private String counterChannelId;
|
||||
private String format = "【👥】Участники: {count}";
|
||||
|
||||
public CounterManager(CounterStatsRepository statsRepo, ConfigRepository configRepo) {
|
||||
this.statsRepo = statsRepo;
|
||||
this.configRepo = configRepo;
|
||||
loadConfig();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить конфигурацию
|
||||
*/
|
||||
private void loadConfig() {
|
||||
this.counterChannelId = configRepo.get("counter.channel_id");
|
||||
String savedFormat = configRepo.get("counter.format");
|
||||
if (savedFormat != null) {
|
||||
this.format = savedFormat;
|
||||
}
|
||||
VoiceChannel channel = guild.getVoiceChannelById(counterChannelId);
|
||||
if (channel == null) {
|
||||
LoggerDecorator.warn(logger, "Канал счетчика не найден: " + counterChannelId);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить текущую дневную статистику
|
||||
*/
|
||||
public DailyStats getDailyStats() {
|
||||
return statsRepo.getTodayStats();
|
||||
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 -> LoggerDecorator.error(logger, "Ошибка при обновлении счетчика", error));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить статистику
|
||||
*/
|
||||
private void saveDailyStats(DailyStats stats) {
|
||||
statsRepo.saveStats(stats);
|
||||
}
|
||||
/** Получить историю статистики */
|
||||
public List<DailyStats> getHistory(int days) {
|
||||
return statsRepo.getHistory(days);
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать присоединение участника
|
||||
*/
|
||||
public void recordJoin() {
|
||||
DailyStats stats = getDailyStats();
|
||||
stats.incrementJoined();
|
||||
saveDailyStats(stats);
|
||||
logger.debug("Зарегистрировано присоединение. Всего за день: {}", stats.getJoined());
|
||||
}
|
||||
// Getters
|
||||
public String getCounterChannelId() {
|
||||
return counterChannelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Зарегистрировать выход участника
|
||||
*/
|
||||
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;
|
||||
}
|
||||
public String getFormat() {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,19 +9,19 @@ import creator.loki.feature.counter.repository.CounterStatsRepository;
|
|||
|
||||
public class CounterModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
}
|
||||
@Override
|
||||
protected void configure() {}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CounterStatsRepository provideCounterStatsRepository(Database database) {
|
||||
return new CounterStatsRepository(database);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
CounterStatsRepository provideCounterStatsRepository(Database database) {
|
||||
return new CounterStatsRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
CounterManager provideCounterManager(CounterStatsRepository statsRepo, ConfigRepository configRepo) {
|
||||
return new CounterManager(statsRepo, configRepo);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
CounterManager provideCounterManager(
|
||||
CounterStatsRepository statsRepo, ConfigRepository configRepo) {
|
||||
return new CounterManager(statsRepo, configRepo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,186 +1,136 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
private final CounterManager counterManager;
|
||||
private final ConfigRepository configRepo;
|
||||
|
||||
public CounterSetupPrefixCommand(CounterManager counterManager, ConfigRepository configRepo) {
|
||||
this.counterManager = counterManager;
|
||||
this.configRepo = 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "counter-setup";
|
||||
List<String> args = context.getArgs();
|
||||
if (args.isEmpty()) {
|
||||
CounterConfigFormatter.sendHelp(context.getChannel());
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Настройка счетчика участников";
|
||||
switch (args.get(0).toLowerCase()) {
|
||||
case "channel" -> handleChannel(context, args);
|
||||
case "format" -> handleFormat(context, args);
|
||||
case "info" -> handleInfo(context);
|
||||
default -> CounterConfigFormatter.sendHelp(context.getChannel());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleChannel(CommandContext context, List<String> args) {
|
||||
if (args.size() < 2) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessage(
|
||||
"❌ Укажите голосовой канал: `#канал`, `<#id>` или числовой id."
|
||||
+ "\nПример: `!counter-setup channel 123456789012345678`")
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "counter-setup <channel|format|info> [значение]";
|
||||
VoiceChannel channel =
|
||||
GuildChannelResolve.voice(context.getGuild(), context.getMessage(), args, 1);
|
||||
if (channel == null) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessage(
|
||||
"❌ Не найден голосовой канал. Укажите `#войс`, `<#id>` или id из 17-20 цифр.")
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
// Проверка админ прав
|
||||
if (!context.getAuthor().hasPermission(Permission.ADMINISTRATOR)) {
|
||||
context.getChannel().sendMessage("❌ У вас нет прав администратора!").queue();
|
||||
return;
|
||||
}
|
||||
String newName = format.replace("{count}", String.valueOf(memberCount));
|
||||
channel.getManager().setName(newName).queue();
|
||||
|
||||
List<String> args = context.getArgs();
|
||||
if (args.isEmpty()) {
|
||||
sendHelp(context);
|
||||
return;
|
||||
}
|
||||
CounterConfigFormatter.sendChannelSetConfirmation(channel, memberCount, context.getChannel());
|
||||
}
|
||||
|
||||
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 handleFormat(CommandContext context, List<String> args) {
|
||||
if (args.size() < 2) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessage("❌ Укажите формат! Пример: `!counter-setup format 【👥】Участники: {count}`")
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
private void handleChannel(CommandContext context, List<String> args) {
|
||||
if (args.size() < 2) {
|
||||
context.getChannel().sendMessage(
|
||||
"❌ Укажите голосовой канал: `#канал`, `<#id>` или числовой id. Пример: `!counter-setup channel 123456789012345678`"
|
||||
).queue();
|
||||
return;
|
||||
}
|
||||
String format = String.join(" ", args.subList(1, args.size()));
|
||||
if (!format.contains("{count}")) {
|
||||
context.getChannel().sendMessage("❌ Формат должен содержать `{count}`!").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());
|
||||
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 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();
|
||||
channel
|
||||
.getManager()
|
||||
.setName(format.replace("{count}", String.valueOf(memberCount)))
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFormat(CommandContext context, List<String> args) {
|
||||
if (args.size() < 2) {
|
||||
context.getChannel().sendMessage("❌ Укажите формат! Пример: `!counter-setup format 【👥】Участники: {count}`").queue();
|
||||
return;
|
||||
}
|
||||
CounterConfigFormatter.sendFormatUpdated(format, context.getChannel());
|
||||
}
|
||||
|
||||
// Объединяем все аргументы в одну строку
|
||||
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();
|
||||
}
|
||||
}
|
||||
private void handleInfo(CommandContext context) {
|
||||
String channelId = configRepo.get("counter.channel_id");
|
||||
String format = configRepo.get("counter.format");
|
||||
CounterConfigFormatter.sendInfo(context.getGuild(), channelId, format, context.getChannel());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,78 +2,76 @@ package creator.loki.feature.counter;
|
|||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* Дневная статистика участников
|
||||
*/
|
||||
/** Дневная статистика участников */
|
||||
public class DailyStats {
|
||||
private String date;
|
||||
private int joined;
|
||||
private int left;
|
||||
private int difference;
|
||||
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() {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// Getters
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
public int getJoined() {
|
||||
return joined;
|
||||
}
|
||||
public int getJoined() {
|
||||
return joined;
|
||||
}
|
||||
|
||||
public int getLeft() {
|
||||
return left;
|
||||
}
|
||||
public int getLeft() {
|
||||
return left;
|
||||
}
|
||||
|
||||
public int getDifference() {
|
||||
return difference;
|
||||
}
|
||||
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();
|
||||
}
|
||||
// Setters
|
||||
public void setDate(String date) {
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
// Методы
|
||||
public void incrementJoined() {
|
||||
this.joined++;
|
||||
updateDifference();
|
||||
}
|
||||
public void setJoined(int joined) {
|
||||
this.joined = joined;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
public void incrementLeft() {
|
||||
this.left++;
|
||||
updateDifference();
|
||||
}
|
||||
public void setLeft(int left) {
|
||||
this.left = left;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
private void updateDifference() {
|
||||
this.difference = this.joined - this.left;
|
||||
}
|
||||
// Методы
|
||||
public void incrementJoined() {
|
||||
this.joined++;
|
||||
updateDifference();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить является ли статистика сегодняшней
|
||||
*/
|
||||
public boolean isToday() {
|
||||
return this.date.equals(LocalDate.now().toString());
|
||||
}
|
||||
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,6 +1,11 @@
|
|||
package creator.loki.feature.counter;
|
||||
|
||||
import creator.loki.feature.counter.CounterManager;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
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;
|
||||
|
|
@ -8,81 +13,77 @@ import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Listener для счетчика участников
|
||||
*/
|
||||
/** 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;
|
||||
private static final Logger logger = LoggerFactory.getLogger(MemberCounterListener.class);
|
||||
private final CounterManager counterManager;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private Guild guild;
|
||||
|
||||
@Inject
|
||||
public MemberCounterListener(CounterManager counterManager) {
|
||||
this.counterManager = counterManager;
|
||||
@Inject
|
||||
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) {
|
||||
LoggerDecorator.error(logger, "Ошибка при обновлении счетчика", e);
|
||||
}
|
||||
}),
|
||||
0,
|
||||
5,
|
||||
TimeUnit.MINUTES);
|
||||
|
||||
LoggerDecorator.bot(logger, "Запущен планировщик обновления счетчика участников");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberJoin(GuildMemberJoinEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить периодическое обновление счетчика
|
||||
*/
|
||||
public void startScheduler(Guild guild) {
|
||||
this.guild = guild;
|
||||
counterManager.recordJoin();
|
||||
counterManager.updateCounter(event.getGuild());
|
||||
|
||||
// Обновляем счетчик каждые 5 минут
|
||||
scheduler.scheduleAtFixedRate(() -> Thread.startVirtualThread(() -> {
|
||||
try {
|
||||
counterManager.updateCounter(guild);
|
||||
} catch (Exception e) {
|
||||
logger.error("Ошибка при обновлении счетчика", e);
|
||||
}
|
||||
}), 0, 5, TimeUnit.MINUTES);
|
||||
LoggerDecorator.bot(
|
||||
logger, "Участник " + event.getUser().getName() + " присоединился к серверу");
|
||||
}
|
||||
|
||||
logger.info("Запущен планировщик обновления счетчика участников");
|
||||
@Override
|
||||
public void onGuildMemberRemove(GuildMemberRemoveEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuildMemberJoin(GuildMemberJoinEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (event.getUser().isBot()) {
|
||||
return;
|
||||
}
|
||||
counterManager.recordLeave();
|
||||
counterManager.updateCounter(event.getGuild());
|
||||
|
||||
counterManager.recordJoin();
|
||||
counterManager.updateCounter(event.getGuild());
|
||||
LoggerDecorator.bot(logger, "Участник " + event.getUser().getName() + " покинул сервер");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
/** Остановить планировщик */
|
||||
public void shutdown() {
|
||||
scheduler.shutdown();
|
||||
try {
|
||||
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
scheduler.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
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;
|
||||
|
|
@ -12,62 +7,60 @@ import java.time.LocalDate;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Репозиторий для работы со статистикой счетчика участников
|
||||
*/
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.feature.counter.DailyStats;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Репозиторий для работы со статистикой счетчика участников */
|
||||
public class CounterStatsRepository {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CounterStatsRepository.class);
|
||||
private final Database database;
|
||||
private static final Logger logger = LoggerFactory.getLogger(CounterStatsRepository.class);
|
||||
private final Database database;
|
||||
|
||||
public CounterStatsRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
public CounterStatsRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статистику за дату
|
||||
*/
|
||||
public DailyStats getStats(String date) {
|
||||
String sql = "SELECT * FROM counter_stats WHERE date = ?";
|
||||
/** Получить статистику за дату */
|
||||
public DailyStats getStats(String date) {
|
||||
String sql = "SELECT * FROM counter_stats WHERE date = ?";
|
||||
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setString(1, 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);
|
||||
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;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Ошибка получения статистики за {}", date, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статистику за сегодня
|
||||
*/
|
||||
public DailyStats getTodayStats() {
|
||||
String today = LocalDate.now().toString();
|
||||
DailyStats stats = getStats(today);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (stats == null) {
|
||||
stats = new DailyStats();
|
||||
stats.setDate(today);
|
||||
}
|
||||
/** Получить статистику за сегодня */
|
||||
public DailyStats getTodayStats() {
|
||||
String today = LocalDate.now().toString();
|
||||
DailyStats stats = getStats(today);
|
||||
|
||||
return stats;
|
||||
if (stats == null) {
|
||||
stats = new DailyStats();
|
||||
stats.setDate(today);
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить статистику
|
||||
*/
|
||||
public void saveStats(DailyStats stats) {
|
||||
String sql = """
|
||||
return stats;
|
||||
}
|
||||
|
||||
/** Сохранить статистику */
|
||||
public void saveStats(DailyStats stats) {
|
||||
String sql =
|
||||
"""
|
||||
INSERT INTO counter_stats (date, joined, left_count, difference)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(date)
|
||||
|
|
@ -77,61 +70,58 @@ public class CounterStatsRepository {
|
|||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить историю за последние N дней
|
||||
*/
|
||||
public List<DailyStats> getHistory(int days) {
|
||||
List<DailyStats> history = new ArrayList<>();
|
||||
String sql = "SELECT * FROM counter_stats ORDER BY date DESC LIMIT ?";
|
||||
java.util.Collections.reverse(history);
|
||||
return history;
|
||||
}
|
||||
|
||||
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 = """
|
||||
/** Удалить старую статистику (старше 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,42 +22,41 @@ 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;
|
||||
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;
|
||||
}
|
||||
@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));
|
||||
}
|
||||
@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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,43 +15,49 @@ import creator.loki.feature.economy.service.SlotService;
|
|||
|
||||
public class EconomyModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
}
|
||||
@Override
|
||||
protected void configure() {}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
EconomyRepository provideEconomyRepository(Database database) {
|
||||
return new SqlEconomyRepository(database);
|
||||
}
|
||||
@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
|
||||
EconomyService provideEconomyService(
|
||||
EconomyRepository repository, CooldownManager cooldownManager) {
|
||||
return new EconomyService(repository, cooldownManager);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
FishingService provideFishingService(EconomyRepository repository) {
|
||||
return new FishingService(repository);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
FishingService provideFishingService(EconomyRepository repository) {
|
||||
return new FishingService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
BlackjackService provideBlackjackService(EconomyRepository repository) {
|
||||
return new BlackjackService(repository);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
BlackjackService provideBlackjackService(EconomyRepository repository) {
|
||||
return new BlackjackService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SlotService provideSlotService(EconomyRepository repository) {
|
||||
return new SlotService(repository);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
SlotService provideSlotService(EconomyRepository repository) {
|
||||
return new SlotService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
JobService provideJobService(EconomyRepository repository) {
|
||||
return new JobService(repository);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
JobService provideJobService(EconomyRepository repository) {
|
||||
return new JobService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
FishMessageListener provideFishMessageListener(FishingService fishingService) {
|
||||
return new FishMessageListener(fishingService);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
package creator.loki.feature.economy;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.feature.economy.service.FishingService;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Singleton
|
||||
public class FishMessageListener extends ListenerAdapter {
|
||||
|
||||
private final FishingService fishingService;
|
||||
|
||||
@Inject
|
||||
public FishMessageListener(FishingService fishingService) {
|
||||
this.fishingService = fishingService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
|
||||
if (event.getAuthor().isBot()) return;
|
||||
|
||||
String content = event.getMessage().getContentRaw().trim();
|
||||
|
||||
int answer;
|
||||
try {
|
||||
answer = Integer.parseInt(content);
|
||||
} catch (NumberFormatException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
String userId = event.getAuthor().getId();
|
||||
FishingService.FishResult result = fishingService.checkAnswer(userId, answer);
|
||||
if (result == null) return;
|
||||
|
||||
if (result.reward() > 0) {
|
||||
event
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("\uD83C\uDFA3 Улов!")
|
||||
.withDescription(
|
||||
"**"
|
||||
+ event.getAuthor().getAsMention()
|
||||
+ "**, ты поймал "
|
||||
+ result.count()
|
||||
+ " "
|
||||
+ result.targetFish()
|
||||
+ "!\n"
|
||||
+ "Твой баланс: **"
|
||||
+ result.cashAfter()
|
||||
+ "** LC")
|
||||
.withColor(new Color(0, 200, 0))
|
||||
.build())
|
||||
.queue();
|
||||
} else {
|
||||
event
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("\uD83D\uDC1F Рыбалка — мимо!")
|
||||
.withDescription(
|
||||
"**"
|
||||
+ event.getAuthor().getAsMention()
|
||||
+ "**, правильный ответ: **"
|
||||
+ result.count()
|
||||
+ "** "
|
||||
+ result.targetFish())
|
||||
.withColor(new Color(200, 0, 0))
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +1,118 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
|
||||
public class BankCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
private final EconomyService economyService;
|
||||
|
||||
public BankCommand(EconomyService economyService) {
|
||||
this.economyService = economyService;
|
||||
}
|
||||
public BankCommand(EconomyService economyService) {
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "bank";
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return "bank";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Управление банковским счётом";
|
||||
}
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Управление банковским счётом";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "bank [deposit|withdraw|balance] <amount>";
|
||||
}
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "bank [deposit|withdraw|balance] <amount>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
@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();
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +1,59 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
public class BalanceCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
private final EconomyService economyService;
|
||||
|
||||
public BalanceCommand(EconomyService economyService) {
|
||||
this.economyService = economyService;
|
||||
}
|
||||
public BalanceCommand(EconomyService economyService) {
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "balance";
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return "balance";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показать баланс пользователя";
|
||||
}
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показать баланс пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "balance [@user]";
|
||||
}
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "balance [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
@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);
|
||||
@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());
|
||||
EconomyAccount account = economyService.getAccount(target.getId());
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
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();
|
||||
}
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +1,64 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
|
||||
public class BaltopCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
private final EconomyService economyService;
|
||||
|
||||
public BaltopCommand(EconomyService economyService) {
|
||||
this.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");
|
||||
}
|
||||
|
||||
@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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🏆 Топ богатейших")
|
||||
.withDescription(sb.toString())
|
||||
.withColor(new Color(255, 215, 0))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +1,59 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
|
||||
public class DailyCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
private final EconomyService economyService;
|
||||
|
||||
public DailyCommand(EconomyService economyService) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@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(
|
||||
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();
|
||||
}
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +1,112 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
public class PayCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
private final EconomyService economyService;
|
||||
|
||||
public PayCommand(EconomyService economyService) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "pay";
|
||||
Member target = mentioned.get(0);
|
||||
if (target.getId().equals(context.getAuthor().getId())) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Нельзя переводить деньги самому себе!"))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Перевести деньги другому пользователю";
|
||||
List<String> args = context.getArgs();
|
||||
if (args.size() < 2) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(EmbedTemplates.error("Ошибка", "Укажите сумму: " + getUsage()))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "pay <@user> <amount>";
|
||||
long amount;
|
||||
try {
|
||||
amount = Long.parseLong(args.get(1));
|
||||
} catch (NumberFormatException e) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(EmbedTemplates.error("Ошибка", "Сумма должна быть числом!"))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
if (amount <= 0) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(EmbedTemplates.error("Ошибка", "Сумма должна быть положительной!"))
|
||||
.queue();
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,148 +1,162 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.economy.service.BlackjackService;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
public class BlackjackCommand implements Command {
|
||||
|
||||
private final BlackjackService blackjackService;
|
||||
private final EconomyService economyService;
|
||||
private final Set<String> activeGames = ConcurrentHashMap.newKeySet();
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "blackjack";
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Блэкджек — играй против дилера";
|
||||
private void handleStart(CommandContext context, String userId, String betStr) {
|
||||
if (activeGames.contains(userId)) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Ошибка", "У тебя уже есть активная игра! Используй `!bj hit` или `!bj stand`"))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "bj <ставка> — начать игру\nbj hit — взять карту\nbj stand — остановиться";
|
||||
long bet;
|
||||
try {
|
||||
bet = Long.parseLong(betStr);
|
||||
if (bet <= 0) throw new NumberFormatException();
|
||||
} catch (NumberFormatException e) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.error("Ошибка", "Укажи положительное число в качестве ставки"))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
var account = economyService.getAccount(userId);
|
||||
if (account.getCash() < bet) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Ошибка", "Недостаточно средств! Твой баланс: " + account.getCash() + " LC"))
|
||||
.queue();
|
||||
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);
|
||||
}
|
||||
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 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 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()
|
||||
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();
|
||||
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 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 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";
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,106 +1,73 @@
|
|||
package creator.loki.feature.economy.command.minigame;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
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;
|
||||
import creator.loki.feature.economy.service.FishingService;
|
||||
|
||||
public class FishCommand implements Command {
|
||||
|
||||
private static record FishSession(String difficulty, long startTime) {}
|
||||
private final FishingService fishingService;
|
||||
|
||||
private final FishingService fishingService;
|
||||
private final ConcurrentHashMap<String, FishSession> sessions = new ConcurrentHashMap<>();
|
||||
public FishCommand(FishingService fishingService) {
|
||||
this.fishingService = fishingService;
|
||||
}
|
||||
|
||||
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] — начать рыбалку\nПотом просто напиши число в чат";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var args = context.getArgs();
|
||||
var userId = context.getAuthor().getId();
|
||||
|
||||
String difficulty = "easy";
|
||||
if (!args.isEmpty()) {
|
||||
String first = args.get(0).toLowerCase();
|
||||
if (first.equals("easy") || first.equals("normal") || first.equals("hard")) {
|
||||
difficulty = first;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "fish";
|
||||
FishingService.FishResult result = fishingService.startFishing(userId, difficulty);
|
||||
if (result == null) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(EmbedTemplates.warning("Рыбалка", "Подожди 2 часа между рыбалками!"))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@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("🎣 Рыбалка — Результат")
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎣 Рыбалка — " + difficulty)
|
||||
.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();
|
||||
}
|
||||
}
|
||||
.withColor(new Color(0, 150, 255))
|
||||
.addField("Вопрос", "Сколько **" + result.targetFish() + "** ты видишь?", false)
|
||||
.withFooter("Просто напиши число в чат")
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,146 +1,177 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.economy.service.JobService;
|
||||
|
||||
public class JobCommand implements Command {
|
||||
|
||||
private final JobService jobService;
|
||||
private final JobService jobService;
|
||||
|
||||
public JobCommand(JobService jobService) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "job";
|
||||
JobService.JobInfo info = findJob(current);
|
||||
if (info == null) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.warning("Работа", "Текущая работа: **" + current + "**"))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@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(
|
||||
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();
|
||||
.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");
|
||||
}
|
||||
|
||||
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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("📋 Доступные работы")
|
||||
.withDescription(desc.toString())
|
||||
.withColor(new Color(100, 200, 255))
|
||||
.withFooter("!job join <название> — устроиться на работу")
|
||||
.build()
|
||||
).queue();
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if (target == null) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Ошибка", "Работа **" + name + "** не найдена. Используй `!job list`"))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
private JobService.JobInfo findJob(String name) {
|
||||
for (var job : jobService.listJobs()) {
|
||||
if (job.name().equalsIgnoreCase(name) || job.displayName().equalsIgnoreCase(name)) {
|
||||
return job;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +1,109 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.feature.economy.service.SlotService;
|
||||
|
||||
public class SlotCommand implements Command {
|
||||
|
||||
private final SlotService slotService;
|
||||
private final EconomyService economyService;
|
||||
private final SlotService slotService;
|
||||
private final EconomyService economyService;
|
||||
|
||||
public SlotCommand(SlotService slotService, EconomyService economyService) {
|
||||
this.slotService = slotService;
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "slot";
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Крути слот-машину и выигрывай";
|
||||
var account = economyService.getAccount(userId);
|
||||
if (account.getCash() < bet) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Ошибка", "Недостаточно средств! Твой баланс: " + account.getCash() + " LC"))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "slot <ставка>";
|
||||
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😔 Повезёт в следующий раз!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
desc.append("\n\nБаланс: **").append(result.cashAfter()).append("** LC");
|
||||
|
||||
@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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎰 Слот-машина")
|
||||
.withDescription(desc.toString())
|
||||
.withColor(new Color(255, 215, 0))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,78 +1,86 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.economy.service.JobService;
|
||||
|
||||
public class WorkCommand implements Command {
|
||||
|
||||
private final JobService jobService;
|
||||
private final JobService jobService;
|
||||
|
||||
public WorkCommand(JobService jobService) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "work";
|
||||
JobService.JobInfo info = findJob(currentJob);
|
||||
JobService.WorkResult result = jobService.work(userId, 0);
|
||||
|
||||
if (result == null) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Ошибка", "⏳ Ты уже работал сегодня! Отдохни и возвращайся завтра."))
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Работать и зарабатывать деньги";
|
||||
}
|
||||
String emoji = info != null ? info.emoji() : "💼";
|
||||
String jobName = info != null ? info.displayName() : currentJob;
|
||||
|
||||
@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(
|
||||
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();
|
||||
}
|
||||
.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;
|
||||
private JobService.JobInfo findJob(String name) {
|
||||
for (var job : jobService.listJobs()) {
|
||||
if (job.name().equalsIgnoreCase(name) || job.displayName().equalsIgnoreCase(name)) {
|
||||
return job;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,107 +1,113 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
public class RobCommand implements Command {
|
||||
private final EconomyService economyService;
|
||||
private final CooldownManager cooldownManager;
|
||||
private final Random random;
|
||||
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();
|
||||
}
|
||||
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 getName() {
|
||||
return "rob";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Ограбить пользователя";
|
||||
}
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Ограбить пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "rob <@user>";
|
||||
}
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "rob <@user>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Экономика";
|
||||
}
|
||||
@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();
|
||||
}
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,237 +3,140 @@ 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;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
EconomyAccount(EconomyAccountBuilder 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 String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public long getCash() {
|
||||
return cash;
|
||||
}
|
||||
public long getCash() {
|
||||
return cash;
|
||||
}
|
||||
|
||||
public long getBank() {
|
||||
return bank;
|
||||
}
|
||||
public long getBank() {
|
||||
return bank;
|
||||
}
|
||||
|
||||
public long getTotalEarned() {
|
||||
return totalEarned;
|
||||
}
|
||||
public long getTotalEarned() {
|
||||
return totalEarned;
|
||||
}
|
||||
|
||||
public long getTotalSpent() {
|
||||
return totalSpent;
|
||||
}
|
||||
public long getTotalSpent() {
|
||||
return totalSpent;
|
||||
}
|
||||
|
||||
public String getLastDaily() {
|
||||
return lastDaily;
|
||||
}
|
||||
public String getLastDaily() {
|
||||
return lastDaily;
|
||||
}
|
||||
|
||||
public int getDailyStreak() {
|
||||
return dailyStreak;
|
||||
}
|
||||
public int getDailyStreak() {
|
||||
return dailyStreak;
|
||||
}
|
||||
|
||||
public int getFishingLuck() {
|
||||
return fishingLuck;
|
||||
}
|
||||
public int getFishingLuck() {
|
||||
return fishingLuck;
|
||||
}
|
||||
|
||||
public int getEfficiency() {
|
||||
return efficiency;
|
||||
}
|
||||
public int getEfficiency() {
|
||||
return efficiency;
|
||||
}
|
||||
|
||||
public String getLastFish() {
|
||||
return lastFish;
|
||||
}
|
||||
public String getLastFish() {
|
||||
return lastFish;
|
||||
}
|
||||
|
||||
public String getJob() {
|
||||
return job;
|
||||
}
|
||||
public String getJob() {
|
||||
return job;
|
||||
}
|
||||
|
||||
public String getLastWork() {
|
||||
return lastWork;
|
||||
}
|
||||
public String getLastWork() {
|
||||
return lastWork;
|
||||
}
|
||||
|
||||
public long getTotalBalance() {
|
||||
return cash + bank;
|
||||
}
|
||||
public long getTotalBalance() {
|
||||
return cash + bank;
|
||||
}
|
||||
|
||||
public EconomyAccount withCash(long cash) {
|
||||
return new Builder(this).cash(cash).build();
|
||||
}
|
||||
public EconomyAccount withCash(long cash) {
|
||||
return new EconomyAccountBuilder(this).cash(cash).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withBank(long bank) {
|
||||
return new Builder(this).bank(bank).build();
|
||||
}
|
||||
public EconomyAccount withBank(long bank) {
|
||||
return new EconomyAccountBuilder(this).bank(bank).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withAddedEarned(long amount) {
|
||||
return new Builder(this).totalEarned(this.totalEarned + amount).build();
|
||||
}
|
||||
public EconomyAccount withAddedEarned(long amount) {
|
||||
return new EconomyAccountBuilder(this).totalEarned(this.totalEarned + amount).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withAddedSpent(long amount) {
|
||||
return new Builder(this).totalSpent(this.totalSpent + amount).build();
|
||||
}
|
||||
public EconomyAccount withAddedSpent(long amount) {
|
||||
return new EconomyAccountBuilder(this).totalSpent(this.totalSpent + amount).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withFishingLuck(int level) {
|
||||
return new Builder(this).fishingLuck(level).build();
|
||||
}
|
||||
public EconomyAccount withFishingLuck(int level) {
|
||||
return new EconomyAccountBuilder(this).fishingLuck(level).build();
|
||||
}
|
||||
|
||||
public EconomyAccount withEfficiency(int level) {
|
||||
return new Builder(this).efficiency(level).build();
|
||||
}
|
||||
public EconomyAccount withEfficiency(int level) {
|
||||
return new EconomyAccountBuilder(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 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 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);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("EconomyAccount[%s: Cash %d, Bank %d]", userId, cash, bank);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
package creator.loki.feature.economy.domain;
|
||||
|
||||
public class EconomyAccountBuilder {
|
||||
String userId;
|
||||
long cash;
|
||||
long bank;
|
||||
long totalEarned;
|
||||
long totalSpent;
|
||||
String lastDaily;
|
||||
int dailyStreak;
|
||||
int fishingLuck;
|
||||
int efficiency;
|
||||
String lastFish;
|
||||
String job;
|
||||
String lastWork;
|
||||
|
||||
public EconomyAccountBuilder() {}
|
||||
|
||||
public EconomyAccountBuilder(EconomyAccount existing) {
|
||||
this.userId = existing.getUserId();
|
||||
this.cash = existing.getCash();
|
||||
this.bank = existing.getBank();
|
||||
this.totalEarned = existing.getTotalEarned();
|
||||
this.totalSpent = existing.getTotalSpent();
|
||||
this.lastDaily = existing.getLastDaily();
|
||||
this.dailyStreak = existing.getDailyStreak();
|
||||
this.fishingLuck = existing.getFishingLuck();
|
||||
this.efficiency = existing.getEfficiency();
|
||||
this.lastFish = existing.getLastFish();
|
||||
this.job = existing.getJob();
|
||||
this.lastWork = existing.getLastWork();
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder userId(String userId) {
|
||||
this.userId = userId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder cash(long cash) {
|
||||
this.cash = cash;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder bank(long bank) {
|
||||
this.bank = bank;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder totalEarned(long totalEarned) {
|
||||
this.totalEarned = totalEarned;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder totalSpent(long totalSpent) {
|
||||
this.totalSpent = totalSpent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder lastDaily(String lastDaily) {
|
||||
this.lastDaily = lastDaily;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder dailyStreak(int dailyStreak) {
|
||||
this.dailyStreak = dailyStreak;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder fishingLuck(int fishingLuck) {
|
||||
this.fishingLuck = fishingLuck;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder efficiency(int efficiency) {
|
||||
this.efficiency = efficiency;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder lastFish(String lastFish) {
|
||||
this.lastFish = lastFish;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder job(String job) {
|
||||
this.job = job;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccountBuilder lastWork(String lastWork) {
|
||||
this.lastWork = lastWork;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EconomyAccount build() {
|
||||
return new EconomyAccount(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,4 @@
|
|||
package creator.loki.feature.economy.domain;
|
||||
|
||||
public record EconomyTransaction(
|
||||
String fromId,
|
||||
String toId,
|
||||
long amount,
|
||||
String type,
|
||||
long timestamp
|
||||
) {}
|
||||
String fromId, String toId, long amount, String type, long timestamp) {}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,53 @@
|
|||
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");
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,39 @@
|
|||
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);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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 String[] getEmojis() {
|
||||
return emojis;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
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);
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
|
||||
default EconomyAccount getAccount(String userId) {
|
||||
return findById(userId).orElse(new EconomyAccount(userId));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
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;
|
||||
|
|
@ -13,18 +8,25 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import creator.loki.database.Database;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.domain.EconomyAccountBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
public SqlEconomyRepository(Database database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<EconomyAccount> findById(String userId) {
|
||||
String sql = """
|
||||
@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
|
||||
|
|
@ -32,22 +34,23 @@ public class SqlEconomyRepository implements EconomyRepository {
|
|||
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);
|
||||
try (PreparedStatement stmt = database.prepare(sql)) {
|
||||
stmt.setString(1, userId);
|
||||
try (ResultSet rs = stmt.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
return Optional.of(mapResultSetToAccount(rs));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error fetching economy account for {}", userId, e);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(EconomyAccount account) {
|
||||
String sql = """
|
||||
@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)
|
||||
|
|
@ -67,59 +70,60 @@ public class SqlEconomyRepository implements EconomyRepository {
|
|||
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);
|
||||
}
|
||||
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 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 = """
|
||||
@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
|
||||
|
|
@ -127,39 +131,39 @@ public class SqlEconomyRepository implements EconomyRepository {
|
|||
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);
|
||||
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));
|
||||
}
|
||||
return Collections.unmodifiableList(list);
|
||||
}
|
||||
} 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 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();
|
||||
}
|
||||
private EconomyAccount mapResultSetToAccount(ResultSet rs) throws SQLException {
|
||||
return new EconomyAccountBuilder()
|
||||
.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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,141 +1,172 @@
|
|||
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;
|
||||
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class BlackjackService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BlackjackService.class);
|
||||
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 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<>();
|
||||
private final EconomyRepository repository;
|
||||
private final ConcurrentHashMap<String, ActiveBlackjack> activeGames = new ConcurrentHashMap<>();
|
||||
|
||||
public BlackjackService(EconomyRepository repository) {
|
||||
this.repository = repository;
|
||||
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);
|
||||
}
|
||||
|
||||
private record ActiveBlackjack(List<String> playerCards, List<String> dealerCards, long bet, String userId) {}
|
||||
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 startGame(String userId, long bet) {
|
||||
if (bet <= 0 || activeGames.containsKey(userId)) return null;
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
if (account.getCash() < bet) return null;
|
||||
public BlackjackState hit(String userId) {
|
||||
ActiveBlackjack game = activeGames.get(userId);
|
||||
if (game == null) return null;
|
||||
|
||||
repository.save(account.withCash(account.getCash() - bet));
|
||||
game.playerCards().add(drawCard());
|
||||
int sum = calculateSum(game.playerCards());
|
||||
|
||||
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
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
public BlackjackState hit(String userId) {
|
||||
ActiveBlackjack game = activeGames.get(userId);
|
||||
if (game == null) return null;
|
||||
return new BlackjackState(
|
||||
game.playerCards().toArray(String[]::new),
|
||||
game.dealerCards().toArray(String[]::new),
|
||||
sum,
|
||||
calculateSum(game.dealerCards()),
|
||||
game.bet(),
|
||||
false,
|
||||
0);
|
||||
}
|
||||
|
||||
game.playerCards().add(drawCard());
|
||||
int sum = calculateSum(game.playerCards());
|
||||
public BlackjackState stand(String userId) {
|
||||
ActiveBlackjack game = activeGames.remove(userId);
|
||||
if (game == null) return null;
|
||||
|
||||
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
|
||||
);
|
||||
List<String> dealerCards = new ArrayList<>(game.dealerCards());
|
||||
while (calculateSum(dealerCards) < 16) {
|
||||
dealerCards.add(drawCard());
|
||||
}
|
||||
|
||||
public BlackjackState stand(String userId) {
|
||||
ActiveBlackjack game = activeGames.remove(userId);
|
||||
if (game == null) return null;
|
||||
int playerSum = calculateSum(game.playerCards());
|
||||
int dealerSum = calculateSum(dealerCards);
|
||||
long payout;
|
||||
|
||||
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
|
||||
);
|
||||
if (dealerSum > 21 || playerSum > dealerSum) {
|
||||
payout = game.bet() * 2;
|
||||
} else {
|
||||
payout = 0;
|
||||
}
|
||||
|
||||
private String drawCard() {
|
||||
return DECK[ThreadLocalRandom.current().nextInt(DECK.length)];
|
||||
}
|
||||
EconomyAccount account = repository.getAccount(userId);
|
||||
repository.save(account.withCash(account.getCash() + payout));
|
||||
|
||||
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);
|
||||
}
|
||||
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++;
|
||||
}
|
||||
while (sum > 21 && aces > 0) {
|
||||
sum -= 10;
|
||||
aces--;
|
||||
}
|
||||
return sum;
|
||||
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) {}
|
||||
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) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,118 +1,123 @@
|
|||
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;
|
||||
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.domain.EconomyAccountBuilder;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class EconomyService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EconomyService.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(EconomyService.class);
|
||||
|
||||
private final EconomyRepository repository;
|
||||
private final CooldownManager cooldownManager;
|
||||
private final EconomyRepository repository;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public EconomyService(EconomyRepository repository, CooldownManager cooldownManager) {
|
||||
this.repository = repository;
|
||||
this.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;
|
||||
}
|
||||
|
||||
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)
|
||||
long amount = streak >= 7 ? (long) (50 * 1.5) : 50;
|
||||
long newCash = account.getCash() + amount;
|
||||
EconomyAccount updated =
|
||||
new EconomyAccountBuilder(account)
|
||||
.cash(newCash)
|
||||
.totalEarned(account.getTotalEarned() + amount)
|
||||
.lastDaily(today)
|
||||
.dailyStreak(streak)
|
||||
.build();
|
||||
repository.save(updated);
|
||||
return new DailyResult(amount, streak, newCash);
|
||||
}
|
||||
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 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 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) {}
|
||||
public List<EconomyAccount> getTopBalances(int limit) {
|
||||
return repository.findTopByBalance(limit);
|
||||
}
|
||||
|
||||
public record DailyResult(long amount, int streak, long newCash) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,136 +1,157 @@
|
|||
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;
|
||||
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.domain.EconomyAccountBuilder;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class FishingService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FishingService.class);
|
||||
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 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<>();
|
||||
private final EconomyRepository repository;
|
||||
private final ConcurrentHashMap<String, ActiveFish> activeFishing = new ConcurrentHashMap<>();
|
||||
|
||||
public FishingService(EconomyRepository repository) {
|
||||
this.repository = repository;
|
||||
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;
|
||||
}
|
||||
|
||||
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());
|
||||
int size;
|
||||
int fishTypes;
|
||||
int baseReward;
|
||||
switch (difficulty.toLowerCase()) {
|
||||
case "easy" -> {
|
||||
size = 5;
|
||||
fishTypes = 2;
|
||||
baseReward = 15;
|
||||
}
|
||||
case "normal" -> {
|
||||
size = 7;
|
||||
fishTypes = 3;
|
||||
baseReward = 30;
|
||||
}
|
||||
case "hard" -> {
|
||||
size = 9;
|
||||
fishTypes = 4;
|
||||
baseReward = 50;
|
||||
}
|
||||
default -> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
String[] available = Arrays.copyOf(FISH, fishTypes);
|
||||
String[][] grid = new String[size][size];
|
||||
int[] counts = new int[fishTypes];
|
||||
var rng = ThreadLocalRandom.current();
|
||||
|
||||
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;
|
||||
}
|
||||
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]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
int targetIdx;
|
||||
do {
|
||||
targetIdx = rng.nextInt(fishTypes);
|
||||
} while (counts[targetIdx] == 0);
|
||||
|
||||
long newCash = account.getCash() + reward;
|
||||
EconomyAccount updated = new EconomyAccount.Builder(account)
|
||||
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) {
|
||||
ActiveFish session = activeFishing.get(userId);
|
||||
if (session == null) return null;
|
||||
return checkAnswer(userId, answer, session.startTime(), session.difficulty());
|
||||
}
|
||||
|
||||
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 EconomyAccountBuilder(account)
|
||||
.cash(newCash)
|
||||
.totalEarned(account.getTotalEarned() + reward)
|
||||
.lastFish(String.valueOf(System.currentTimeMillis()))
|
||||
.build();
|
||||
repository.save(updated);
|
||||
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) {}
|
||||
return new FishResult(
|
||||
session.grid(), session.targetFish(), session.correctCount(), reward, newCash);
|
||||
}
|
||||
|
||||
public record FishResult(
|
||||
String[] grid, String targetFish, int count, long reward, long cashAfter) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,91 +1,96 @@
|
|||
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;
|
||||
|
||||
import creator.loki.feature.economy.domain.EconomyAccount;
|
||||
import creator.loki.feature.economy.domain.EconomyAccountBuilder;
|
||||
import creator.loki.feature.economy.domain.Job;
|
||||
import creator.loki.feature.economy.repository.EconomyRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class JobService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(JobService.class);
|
||||
private static final Logger logger = LoggerFactory.getLogger(JobService.class);
|
||||
|
||||
private static final long WORK_COOLDOWN = 21600000;
|
||||
private static final long WORK_COOLDOWN = 21600000;
|
||||
|
||||
private final EconomyRepository repository;
|
||||
private final EconomyRepository repository;
|
||||
|
||||
public JobService(EconomyRepository repository) {
|
||||
this.repository = 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 EconomyAccountBuilder(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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
var rng = ThreadLocalRandom.current();
|
||||
long basePayout = rng.nextLong(job.getMinPayout(), job.getMaxPayout() + 1);
|
||||
long amount = (long) (basePayout * (1 + account.getEfficiency() * 0.01));
|
||||
|
||||
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)
|
||||
long newCash = account.getCash() + amount;
|
||||
EconomyAccount updated =
|
||||
new EconomyAccountBuilder(account)
|
||||
.cash(newCash)
|
||||
.totalEarned(account.getTotalEarned() + amount)
|
||||
.lastWork(String.valueOf(System.currentTimeMillis()))
|
||||
.build();
|
||||
repository.save(updated);
|
||||
repository.save(updated);
|
||||
|
||||
return new WorkResult(amount, newCash);
|
||||
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 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 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) {}
|
||||
public record WorkResult(long amount, long newCash) {}
|
||||
|
||||
public record JobInfo(
|
||||
String name,
|
||||
String displayName,
|
||||
int requiredLevel,
|
||||
int minPayout,
|
||||
int maxPayout,
|
||||
String emoji) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,72 @@
|
|||
package creator.loki.feature.economy.service;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
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 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 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;
|
||||
private final EconomyRepository repository;
|
||||
|
||||
public SlotService(EconomyRepository repository) {
|
||||
this.repository = 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 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) {}
|
||||
|
||||
public record SlotResult(
|
||||
String emoji1,
|
||||
String emoji2,
|
||||
String emoji3,
|
||||
String comboName,
|
||||
int multiplier,
|
||||
long winAmount,
|
||||
long cashAfter) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,64 +1,60 @@
|
|||
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.entertainment.commands.*;
|
||||
import creator.loki.feature.ship.ShipCommand;
|
||||
import creator.loki.feature.ship.repository.ShipPairRepository;
|
||||
import creator.loki.feature.social.commands.*;
|
||||
|
||||
public class EntertainmentCommandRegistrar implements CommandRegistrar {
|
||||
private final CooldownManager cooldownManager;
|
||||
private final ShipPairRepository shipPairRepository;
|
||||
private final BotConfig config;
|
||||
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;
|
||||
}
|
||||
@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());
|
||||
@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 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 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));
|
||||
}
|
||||
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,101 +1,104 @@
|
|||
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 метр с детерминированным результатом
|
||||
*/
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
/** Boykisser метр с детерминированным результатом */
|
||||
public class BoykisserCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public BoykisserCommand(CooldownManager cooldownManager) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "boykisser";
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "На сколько ты boykisser (результат всегда одинаковый)";
|
||||
// Применяем 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("⬜");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "boykisser [@user]";
|
||||
}
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(boykisserLevel, RatingConfig.getBoykisserLevels());
|
||||
|
||||
@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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("💋 Boykisser Метр")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Уровень", bar.toString() + "\n**" + boykisserLevel + "%** - " + level.getComment(), false)
|
||||
.addField(
|
||||
"Уровень",
|
||||
bar.toString() + "\n**" + boykisserLevel + "%** - " + level.getComment(),
|
||||
false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.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;
|
||||
}
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
RANDOM.setSeed(seed);
|
||||
return RANDOM.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,69 +10,64 @@ import java.util.function.Function;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
* Разбор хвоста сообщения и строки вариантов для {@link RandomChoiceCommand}.
|
||||
*/
|
||||
/** Разбор хвоста сообщения и строки вариантов для {@link RandomChoiceCommand}. */
|
||||
final class ChoiceOptionParser {
|
||||
|
||||
private static final Pattern OR_SPLIT = Pattern.compile("(?i)\\s+или\\s+");
|
||||
private static final Pattern OR_SPLIT = Pattern.compile("(?i)\\s+или\\s+");
|
||||
|
||||
private record SplitRule(Predicate<String> when, Function<String, String[]> split) {
|
||||
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 "";
|
||||
}
|
||||
|
||||
/** Порядок важен: последнее правило - «слова через пробел». */
|
||||
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() {
|
||||
String rest = t.substring(prefix.length()).trim();
|
||||
int sp = rest.indexOf(' ');
|
||||
if (sp < 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
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> 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);
|
||||
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,49 @@
|
|||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.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();
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "coin";
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return "coin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Подбросить монетку";
|
||||
}
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Подбросить монетку";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "coin";
|
||||
}
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "coin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
boolean isHeads = random.nextBoolean();
|
||||
String result = isHeads ? "Орёл" : "Решка";
|
||||
String emoji = isHeads ? "🦅" : "🪙";
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
boolean isHeads = random.nextBoolean();
|
||||
String result = isHeads ? "Орёл" : "Решка";
|
||||
String emoji = isHeads ? "🦅" : "🪙";
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle(emoji + " Подбрасываю монетку...")
|
||||
.withDescription("**Результат:** " + result)
|
||||
.withColor(isHeads ? new Color(255, 215, 0) : new Color(192, 192, 192))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,62 @@
|
|||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.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();
|
||||
private final Random random = new Random();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "dice";
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return "dice";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Бросить кубик";
|
||||
}
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Бросить кубик";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "dice [N]";
|
||||
}
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "dice [N]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
int maxValue = 6;
|
||||
@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;
|
||||
}
|
||||
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;
|
||||
int result = random.nextInt(maxValue) + 1;
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎲 Бросаю кубик...")
|
||||
.withDescription("**Результат:** " + result + " (из " + maxValue + ")")
|
||||
.withColor(new Color(46, 204, 113))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,74 +1,76 @@
|
|||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.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 EightBallCommand implements Command {
|
||||
private final Random random = new Random();
|
||||
private final Random random = new Random();
|
||||
|
||||
private static final String[] ANSWERS = {
|
||||
"Да, определённо",
|
||||
"Бесспорно",
|
||||
"Без сомнений",
|
||||
"Можешь быть уверен",
|
||||
"Мне кажется - да",
|
||||
"Вероятнее всего",
|
||||
"Хорошие перспективы",
|
||||
"Знаки говорят - да",
|
||||
"Да",
|
||||
"Пока не ясно, попробуй снова",
|
||||
"Спроси позже",
|
||||
"Лучше не рассказывать",
|
||||
"Сейчас нельзя предсказать",
|
||||
"Сконцентрируйся и спроси опять",
|
||||
"Даже не думай",
|
||||
"Мой ответ - нет",
|
||||
"По моим данным - нет",
|
||||
"Перспективы не очень хорошие",
|
||||
"Весьма сомнительно"
|
||||
};
|
||||
private static final String[] ANSWERS = {
|
||||
"Да, определённо",
|
||||
"Бесспорно",
|
||||
"Без сомнений",
|
||||
"Можешь быть уверен",
|
||||
"Мне кажется - да",
|
||||
"Вероятнее всего",
|
||||
"Хорошие перспективы",
|
||||
"Знаки говорят - да",
|
||||
"Да",
|
||||
"Пока не ясно, попробуй снова",
|
||||
"Спроси позже",
|
||||
"Лучше не рассказывать",
|
||||
"Сейчас нельзя предсказать",
|
||||
"Сконцентрируйся и спроси опять",
|
||||
"Даже не думай",
|
||||
"Мой ответ - нет",
|
||||
"По моим данным - нет",
|
||||
"Перспективы не очень хорошие",
|
||||
"Весьма сомнительно"
|
||||
};
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "8ball";
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Магический шар предсказаний";
|
||||
}
|
||||
String question = String.join(" ", context.getArgs());
|
||||
String answer = ANSWERS[random.nextInt(ANSWERS.length)];
|
||||
|
||||
@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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎱 Магический шар")
|
||||
.addField("Вопрос:", question, false)
|
||||
.addField("Ответ:", answer, false)
|
||||
.withColor(new Color(52, 73, 94))
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +1,98 @@
|
|||
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 метр - проверка на жирность
|
||||
*/
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
/** Fat метр - проверка на жирность */
|
||||
public class FatCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public FatCommand(CooldownManager cooldownManager) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "fat";
|
||||
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("⬜");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка на жирность (результат всегда одинаковый)";
|
||||
}
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(fatLevel, RatingConfig.getFatLevels());
|
||||
|
||||
@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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🐷 Fat Метр")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Жирность", bar.toString() + "\n**" + fatLevel + "%** - " + level.getComment(), false)
|
||||
.addField(
|
||||
"Жирность",
|
||||
bar.toString() + "\n**" + fatLevel + "%** - " + level.getComment(),
|
||||
false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.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;
|
||||
}
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
RANDOM.setSeed(seed);
|
||||
return RANDOM.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +1,83 @@
|
|||
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-метр с детерминированным результатом
|
||||
*/
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
/** Gay-метр с детерминированным результатом */
|
||||
public class GayCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public GayCommand(CooldownManager cooldownManager) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "gay";
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
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("⬜");
|
||||
}
|
||||
|
||||
@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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🏳️🌈 Гей-метр")
|
||||
.withDescription(target.getAsMention())
|
||||
|
|
@ -85,13 +85,15 @@ public class GayCommand implements Command {
|
|||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(new Color(255, 0, 255))
|
||||
.withFooter("💡 Это просто мем, не воспринимайте серьезно")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.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;
|
||||
}
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
RANDOM.setSeed(seed);
|
||||
return RANDOM.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,54 @@
|
|||
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;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
public class GlazbobaCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "glazboba";
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Расширенная информация о пользователе";
|
||||
}
|
||||
Member target = mentioned.get(0);
|
||||
|
||||
@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 {
|
||||
// Анимация загрузки
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessage("🔍 Сканирую данные...")
|
||||
.queue(
|
||||
loadingMsg -> {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
loadingMsg.editMessage("📊 Анализирую профиль...").queue();
|
||||
Thread.sleep(1000);
|
||||
|
|
@ -57,40 +61,43 @@ public class GlazbobaCommand implements Command {
|
|||
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(", "));
|
||||
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();
|
||||
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();
|
||||
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) {
|
||||
} catch (InterruptedException e) {
|
||||
loadingMsg.editMessage("❌ Ошибка загрузки").queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,79 +1,76 @@
|
|||
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 тест с детерминированным результатом
|
||||
*/
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
/** IQ тест с детерминированным результатом */
|
||||
public class IqCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public IqCommand(CooldownManager cooldownManager) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "iq";
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Тест IQ (результат всегда одинаковый)";
|
||||
}
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(iq, RatingConfig.getIqLevels());
|
||||
|
||||
@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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🧠 Тест IQ")
|
||||
.withDescription("Результат для " + target.getAsMention())
|
||||
|
|
@ -81,13 +78,15 @@ public class IqCommand implements Command {
|
|||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Научно доказано вашим ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.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;
|
||||
}
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
RANDOM.setSeed(seed);
|
||||
return RANDOM.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,60 @@
|
|||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
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 getName() {
|
||||
return "meme";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Случайный мем";
|
||||
}
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Случайный мем";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "meme";
|
||||
}
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "meme";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
context.getChannel().sendMessage("🔍 Ищу мем...").queue(loadingMsg -> {
|
||||
try {
|
||||
@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()));
|
||||
BufferedReader in =
|
||||
new BufferedReader(
|
||||
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
response.append(line);
|
||||
response.append(line);
|
||||
}
|
||||
in.close();
|
||||
|
||||
|
|
@ -56,18 +63,19 @@ public class MemeCommand implements Command {
|
|||
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();
|
||||
loadingMsg
|
||||
.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle(title)
|
||||
.withImage(memeUrl)
|
||||
.withColor(new Color(255, 69, 0))
|
||||
.withFooter("r/" + subreddit)
|
||||
.build())
|
||||
.queue();
|
||||
|
||||
} catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
loadingMsg.editMessage("❌ Не удалось загрузить мем: " + e.getMessage()).queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,105 +1,107 @@
|
|||
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 метр - проверка на усатость
|
||||
*/
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
/** Mustache метр - проверка на усатость */
|
||||
public class MustacheCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public MustacheCommand(CooldownManager cooldownManager) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "mustache";
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка на усатость (результат всегда одинаковый)";
|
||||
// Прогресс-бар с усами
|
||||
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("⬜");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "mustache [@user]";
|
||||
}
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(mustacheLevel, RatingConfig.getMustacheLevels());
|
||||
|
||||
@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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🧔 Mustache Метр")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Усатость", bar.toString() + "\n**" + mustacheLevel + "%** - " + level.getComment(), false)
|
||||
.addField(
|
||||
"Усатость",
|
||||
bar.toString() + "\n**" + mustacheLevel + "%** - " + level.getComment(),
|
||||
false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.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;
|
||||
}
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
RANDOM.setSeed(seed);
|
||||
return RANDOM.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,104 +1,111 @@
|
|||
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 размер с детерминированным результатом
|
||||
*/
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
/** PP размер с детерминированным результатом */
|
||||
public class PpCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public PpCommand(CooldownManager cooldownManager) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "pp";
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Измеритель pp (результат всегда одинаковый)";
|
||||
// Применяем 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "pp [@user]";
|
||||
// Визуализация
|
||||
StringBuilder ppVisual = new StringBuilder("8");
|
||||
for (int i = 0; i < size; i++) {
|
||||
ppVisual.append("=");
|
||||
}
|
||||
ppVisual.append("D");
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
// Определяем уровень
|
||||
RatingLevel level = RatingLevel.findLevel(size, RatingConfig.getPpLevels());
|
||||
|
||||
@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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🍆 PP Размер")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Размер", "```" + ppVisual.toString() + "```\n**" + size + " см** - " + level.getComment(), false)
|
||||
.addField(
|
||||
"Размер",
|
||||
"```"
|
||||
+ ppVisual.toString()
|
||||
+ "```\n**"
|
||||
+ size
|
||||
+ " см** - "
|
||||
+ level.getComment(),
|
||||
false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(level.getColor())
|
||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.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;
|
||||
}
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
RANDOM.setSeed(seed);
|
||||
return RANDOM.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,135 +1,146 @@
|
|||
package creator.loki.feature.entertainment.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
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}.
|
||||
*/
|
||||
/** Случайный выбор из вариантов. Разбор строки - {@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** и не оглядывайся"
|
||||
}
|
||||
);
|
||||
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;
|
||||
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);
|
||||
}
|
||||
Flavor(String blurb, String[] templates) {
|
||||
this.blurb = blurb;
|
||||
this.templates = templates;
|
||||
}
|
||||
|
||||
private final BotConfig config;
|
||||
private final String commandName;
|
||||
private final Flavor flavor;
|
||||
String randomLine(String pick) {
|
||||
var rnd = ThreadLocalRandom.current();
|
||||
return String.format(templates[rnd.nextInt(templates.length)], pick);
|
||||
}
|
||||
}
|
||||
|
||||
public RandomChoiceCommand(BotConfig config, String commandName, Flavor flavor) {
|
||||
this.config = config;
|
||||
this.commandName = commandName;
|
||||
this.flavor = flavor;
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return commandName;
|
||||
List<String> options = ChoiceOptionParser.parseOptions(tail);
|
||||
if (options.size() < 2) {
|
||||
reply(
|
||||
context,
|
||||
EmbedTemplates.error(
|
||||
"Мало вариантов",
|
||||
"Минимум **2** разных варианта: `|`, запятая, `или`, `/` или слова через пробел."));
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return flavor.blurb;
|
||||
}
|
||||
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("");
|
||||
|
||||
@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()
|
||||
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());
|
||||
}
|
||||
reply(context, embed.build());
|
||||
}
|
||||
|
||||
private static void reply(CommandContext ctx, net.dv8tion.jda.api.entities.MessageEmbed embed) {
|
||||
ctx.getChannel().sendMessageEmbeds(embed).queue();
|
||||
}
|
||||
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 + " да / нет / может`";
|
||||
}
|
||||
private String usageExamples(String p) {
|
||||
return "**Примеры:**\n"
|
||||
+ "`"
|
||||
+ p
|
||||
+ commandName
|
||||
+ " пицца | суши | бургер`\n"
|
||||
+ "`"
|
||||
+ p
|
||||
+ commandName
|
||||
+ " чай, кофе, сок`\n"
|
||||
+ "`"
|
||||
+ p
|
||||
+ commandName
|
||||
+ " дома или в клуб`\n"
|
||||
+ "`"
|
||||
+ p
|
||||
+ commandName
|
||||
+ " да / нет / может`";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,101 +1,103 @@
|
|||
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 с детерминированным результатом
|
||||
*/
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.core.security.CooldownManager;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
|
||||
/** Оценка пользователя из 10 с детерминированным результатом */
|
||||
public class RateCommand implements Command {
|
||||
|
||||
private final CooldownManager cooldownManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
|
||||
public RateCommand(CooldownManager cooldownManager) {
|
||||
this.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rate";
|
||||
}
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Оценить пользователя из 10 (результат всегда одинаковый)";
|
||||
}
|
||||
if (target == null) return;
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "rate [@user]";
|
||||
}
|
||||
// Применяем cooldown
|
||||
cooldownManager.applyCooldown(context.getAuthor().getId(), "rate", 5);
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
// Детерминированная оценка от 1 до 10
|
||||
int rating = getDeterministicValue(target.getId(), 420, 1, 10);
|
||||
|
||||
@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;
|
||||
}
|
||||
// Визуализация звездами
|
||||
String stars = "⭐".repeat(rating) + "☆".repeat(10 - rating);
|
||||
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
Member target = mentioned.isEmpty() ? context.getAuthor() : mentioned.get(0);
|
||||
// Комментарий
|
||||
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, "🗿 Ужасно");
|
||||
|
||||
if (target == null) return;
|
||||
String comment = comments.getOrDefault(rating, "🤔");
|
||||
Color color = rating >= 8 ? new Color(255, 215, 0) : new Color(52, 152, 219);
|
||||
|
||||
// Применяем 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(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("⭐ Оценка")
|
||||
.withDescription(target.getAsMention())
|
||||
.addField("Рейтинг", stars + "\n**" + rating + "/10** - " + comment, false)
|
||||
.withThumbnail(target.getUser().getAvatarUrl())
|
||||
.withColor(color)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.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;
|
||||
}
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
private int getDeterministicValue(String userId, int salt, int min, int max) {
|
||||
long seed = Long.parseLong(userId) + salt;
|
||||
RANDOM.setSeed(seed);
|
||||
return RANDOM.nextInt(max - min + 1) + min;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,78 +4,61 @@ import java.awt.Color;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Конфигурации уровней для различных rating команд
|
||||
*/
|
||||
/** Конфигурации уровней для различных rating команд */
|
||||
public class RatingConfig {
|
||||
|
||||
/**
|
||||
* Уровни для Mustache команды
|
||||
*/
|
||||
public static List<RatingLevel> getMustacheLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(1000, "🎩 ЛЕГЕНДАРНЫЕ УСИЩИ!", new Color(255, 215, 0)),
|
||||
new RatingLevel(80, "🧔 Роскошные усы!", new Color(139, 69, 19)),
|
||||
new RatingLevel(60, "👨 Хорошие усы", new Color(160, 82, 45)),
|
||||
new RatingLevel(40, "😊 Усики растут", new Color(205, 133, 63)),
|
||||
new RatingLevel(20, "🙂 Лёгкая щетина", new Color(210, 180, 140)),
|
||||
new RatingLevel(1, "😐 Почти нет усов", new Color(128, 128, 128)),
|
||||
new RatingLevel(0, "🪒 Гладко выбрит", new Color(200, 200, 200))
|
||||
);
|
||||
}
|
||||
/** Уровни для Mustache команды */
|
||||
public static List<RatingLevel> getMustacheLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(1000, "🎩 ЛЕГЕНДАРНЫЕ УСИЩИ!", new Color(255, 215, 0)),
|
||||
new RatingLevel(80, "🧔 Роскошные усы!", new Color(139, 69, 19)),
|
||||
new RatingLevel(60, "👨 Хорошие усы", new Color(160, 82, 45)),
|
||||
new RatingLevel(40, "😊 Усики растут", new Color(205, 133, 63)),
|
||||
new RatingLevel(20, "🙂 Лёгкая щетина", new Color(210, 180, 140)),
|
||||
new RatingLevel(1, "😐 Почти нет усов", new Color(128, 128, 128)),
|
||||
new RatingLevel(0, "🪒 Гладко выбрит", new Color(200, 200, 200)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Уровни для IQ команды
|
||||
*/
|
||||
public static List<RatingLevel> getIqLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(140, "🧠 Гений!", new Color(255, 215, 0)),
|
||||
new RatingLevel(120, "🎓 Очень умный", new Color(100, 255, 150)),
|
||||
new RatingLevel(100, "📚 Средний", new Color(52, 152, 219)),
|
||||
new RatingLevel(80, "📖 Ниже среднего", new Color(255, 165, 0)),
|
||||
new RatingLevel(0, "🤔 Низкий", new Color(231, 76, 60))
|
||||
);
|
||||
}
|
||||
/** Уровни для IQ команды */
|
||||
public static List<RatingLevel> getIqLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(140, "🧠 Гений!", new Color(255, 215, 0)),
|
||||
new RatingLevel(120, "🎓 Очень умный", new Color(100, 255, 150)),
|
||||
new RatingLevel(100, "📚 Средний", new Color(52, 152, 219)),
|
||||
new RatingLevel(80, "📖 Ниже среднего", new Color(255, 165, 0)),
|
||||
new RatingLevel(0, "🤔 Низкий", new Color(231, 76, 60)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Уровни для Fat команды
|
||||
*/
|
||||
public static List<RatingLevel> getFatLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(90, "🐷 СВИНОТА!", new Color(255, 0, 0)),
|
||||
new RatingLevel(70, "🍔 Очень жирный", new Color(255, 100, 0)),
|
||||
new RatingLevel(50, "🍕 Жирноват", new Color(255, 165, 0)),
|
||||
new RatingLevel(30, "👍 Нормальный вес", new Color(0, 200, 0)),
|
||||
new RatingLevel(10, "🥗 Худой", new Color(100, 200, 255)),
|
||||
new RatingLevel(0, "💀 Очень худой", new Color(150, 150, 150))
|
||||
);
|
||||
}
|
||||
/** Уровни для Fat команды */
|
||||
public static List<RatingLevel> getFatLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(90, "🐷 СВИНОТА!", new Color(255, 0, 0)),
|
||||
new RatingLevel(70, "🍔 Очень жирный", new Color(255, 100, 0)),
|
||||
new RatingLevel(50, "🍕 Жирноват", new Color(255, 165, 0)),
|
||||
new RatingLevel(30, "👍 Нормальный вес", new Color(0, 200, 0)),
|
||||
new RatingLevel(10, "🥗 Худой", new Color(100, 200, 255)),
|
||||
new RatingLevel(0, "💀 Очень худой", new Color(150, 150, 150)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Уровни для PP команды (размер в см)
|
||||
*/
|
||||
public static List<RatingLevel> getPpLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(25, "😳 НЕВЕРОЯТНО!", new Color(255, 215, 0)),
|
||||
new RatingLevel(20, "😮 Впечатляет", new Color(100, 255, 150)),
|
||||
new RatingLevel(15, "👍 Выше среднего", new Color(52, 152, 219)),
|
||||
new RatingLevel(10, "🙂 Средний", new Color(128, 128, 128)),
|
||||
new RatingLevel(5, "😐 Ниже среднего", new Color(255, 165, 0)),
|
||||
new RatingLevel(0, "💀 Микро", new Color(231, 76, 60))
|
||||
);
|
||||
}
|
||||
/** Уровни для PP команды (размер в см) */
|
||||
public static List<RatingLevel> getPpLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(25, "😳 НЕВЕРОЯТНО!", new Color(255, 215, 0)),
|
||||
new RatingLevel(20, "😮 Впечатляет", new Color(100, 255, 150)),
|
||||
new RatingLevel(15, "👍 Выше среднего", new Color(52, 152, 219)),
|
||||
new RatingLevel(10, "🙂 Средний", new Color(128, 128, 128)),
|
||||
new RatingLevel(5, "😐 Ниже среднего", new Color(255, 165, 0)),
|
||||
new RatingLevel(0, "💀 Микро", new Color(231, 76, 60)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Уровни для Boykisser команды
|
||||
*/
|
||||
public static List<RatingLevel> getBoykisserLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(90, "💋 ЛЕГЕНДАРНЫЙ BOYKISSER!", new Color(255, 105, 180)),
|
||||
new RatingLevel(70, "😘 Активный boykisser", new Color(255, 182, 193)),
|
||||
new RatingLevel(50, "😊 Любит целоваться", new Color(255, 192, 203)),
|
||||
new RatingLevel(30, "🙂 Иногда целуется", new Color(255, 218, 185)),
|
||||
new RatingLevel(10, "😐 Редко целуется", new Color(176, 196, 222)),
|
||||
new RatingLevel(0, "😶 Не целуется", new Color(128, 128, 128))
|
||||
);
|
||||
}
|
||||
/** Уровни для Boykisser команды */
|
||||
public static List<RatingLevel> getBoykisserLevels() {
|
||||
return Arrays.asList(
|
||||
new RatingLevel(90, "💋 ЛЕГЕНДАРНЫЙ BOYKISSER!", new Color(255, 105, 180)),
|
||||
new RatingLevel(70, "😘 Активный boykisser", new Color(255, 182, 193)),
|
||||
new RatingLevel(50, "😊 Любит целоваться", new Color(255, 192, 203)),
|
||||
new RatingLevel(30, "🙂 Иногда целуется", new Color(255, 218, 185)),
|
||||
new RatingLevel(10, "😐 Редко целуется", new Color(176, 196, 222)),
|
||||
new RatingLevel(0, "😶 Не целуется", new Color(128, 128, 128)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,42 +3,41 @@ package creator.loki.feature.entertainment.commands;
|
|||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Уровень рейтинга с порогом, комментарием и цветом
|
||||
*/
|
||||
/** Уровень рейтинга с порогом, комментарием и цветом */
|
||||
public class RatingLevel {
|
||||
private final int threshold;
|
||||
private final String comment;
|
||||
private final Color color;
|
||||
private final int threshold;
|
||||
private final String comment;
|
||||
private final Color color;
|
||||
|
||||
public RatingLevel(int threshold, String comment, Color color) {
|
||||
this.threshold = threshold;
|
||||
this.comment = comment;
|
||||
this.color = color;
|
||||
}
|
||||
public RatingLevel(int threshold, String comment, Color color) {
|
||||
this.threshold = threshold;
|
||||
this.comment = comment;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public int getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
public int getThreshold() {
|
||||
return threshold;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Находит подходящий уровень для значения
|
||||
* @param value значение для проверки
|
||||
* @param levels список уровней (должен быть отсортирован по убыванию threshold)
|
||||
* @return подходящий уровень
|
||||
*/
|
||||
public static RatingLevel findLevel(int value, List<RatingLevel> levels) {
|
||||
return levels.stream()
|
||||
.filter(level -> value >= level.threshold)
|
||||
.findFirst()
|
||||
.orElse(levels.get(levels.size() - 1));
|
||||
}
|
||||
/**
|
||||
* Находит подходящий уровень для значения
|
||||
*
|
||||
* @param value значение для проверки
|
||||
* @param levels список уровней (должен быть отсортирован по убыванию threshold)
|
||||
* @return подходящий уровень
|
||||
*/
|
||||
public static RatingLevel findLevel(int value, List<RatingLevel> levels) {
|
||||
return levels.stream()
|
||||
.filter(level -> value >= level.threshold)
|
||||
.findFirst()
|
||||
.orElse(levels.get(levels.size() - 1));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
package creator.loki.feature.help;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.command.Command;
|
||||
|
|
@ -12,138 +17,136 @@ import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionE
|
|||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class HelpMenuHandler extends ListenerAdapter {
|
||||
private final CommandRegistry registry;
|
||||
private final BotConfig config;
|
||||
private final CommandRegistry registry;
|
||||
private final BotConfig config;
|
||||
|
||||
@Inject
|
||||
public HelpMenuHandler(CommandRegistry registry, BotConfig config) {
|
||||
this.registry = registry;
|
||||
this.config = config;
|
||||
@Inject
|
||||
public HelpMenuHandler(CommandRegistry registry, BotConfig config) {
|
||||
this.registry = registry;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStringSelectInteraction(StringSelectInteractionEvent event) {
|
||||
if (!event.getComponentId().equals("help:category")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStringSelectInteraction(StringSelectInteractionEvent event) {
|
||||
if (!event.getComponentId().equals("help:category")) {
|
||||
return;
|
||||
}
|
||||
String selectedValue = event.getValues().get(0);
|
||||
if (!selectedValue.startsWith("category:")) {
|
||||
return;
|
||||
}
|
||||
|
||||
String selectedValue = event.getValues().get(0);
|
||||
if (!selectedValue.startsWith("category:")) {
|
||||
return;
|
||||
}
|
||||
String category = selectedValue.substring("category:".length());
|
||||
|
||||
String category = selectedValue.substring("category:".length());
|
||||
// Проверка прав для категории "Настройка"
|
||||
if ("Настройка".equals(category)
|
||||
&& event.getMember() != null
|
||||
&& !event.getMember().hasPermission(Permission.ADMINISTRATOR)) {
|
||||
event.reply("❌ Эта категория доступна только администраторам!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверка прав для категории "Настройка"
|
||||
if ("Настройка".equals(category) && event.getMember() != null
|
||||
&& !event.getMember().hasPermission(Permission.ADMINISTRATOR)) {
|
||||
event.reply("❌ Эта категория доступна только администраторам!")
|
||||
.setEphemeral(true)
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
if ("Супер-Админ".equals(category)
|
||||
&& !config.getSuperAdminId().equals(event.getUser().getId())) {
|
||||
event.reply("❌ Эта категория доступна только владельцу бота.").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
if ("Супер-Админ".equals(category) && !config.getSuperAdminId().equals(event.getUser().getId())) {
|
||||
event.reply("❌ Эта категория доступна только владельцу бота.")
|
||||
.setEphemeral(true)
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
var commands = registry.getAllCommands();
|
||||
Map<String, List<Command>> commandsByCategory =
|
||||
commands.stream().collect(Collectors.groupingBy(Command::getCategory));
|
||||
|
||||
var commands = registry.getAllCommands();
|
||||
Map<String, List<Command>> commandsByCategory = commands.stream()
|
||||
.collect(Collectors.groupingBy(Command::getCategory));
|
||||
List<Command> categoryCommands = commandsByCategory.get(category);
|
||||
if (categoryCommands == null || categoryCommands.isEmpty()) {
|
||||
event.reply("❌ В этой категории нет команд!").setEphemeral(true).queue();
|
||||
return;
|
||||
}
|
||||
|
||||
List<Command> categoryCommands = commandsByCategory.get(category);
|
||||
if (categoryCommands == null || categoryCommands.isEmpty()) {
|
||||
event.reply("❌ В этой категории нет команд!")
|
||||
.setEphemeral(true)
|
||||
.queue();
|
||||
return;
|
||||
}
|
||||
// Создаём embed для категории
|
||||
EmbedFactory embed = createCategoryEmbed(category, categoryCommands, event);
|
||||
|
||||
// Создаём embed для категории
|
||||
EmbedFactory embed = createCategoryEmbed(category, categoryCommands, event);
|
||||
// Пересоздаём селектор
|
||||
StringSelectMenu.Builder menuBuilder =
|
||||
StringSelectMenu.create("help:category").setPlaceholder("📂 Выберите категорию");
|
||||
|
||||
// Пересоздаём селектор
|
||||
StringSelectMenu.Builder menuBuilder = StringSelectMenu.create("help:category")
|
||||
.setPlaceholder("📂 Выберите категорию");
|
||||
boolean isAdmin =
|
||||
event.getMember() != null && event.getMember().hasPermission(Permission.ADMINISTRATOR);
|
||||
boolean isSuper = config.getSuperAdminId().equals(event.getUser().getId());
|
||||
|
||||
boolean isAdmin = event.getMember() != null
|
||||
&& event.getMember().hasPermission(Permission.ADMINISTRATOR);
|
||||
boolean isSuper = config.getSuperAdminId().equals(event.getUser().getId());
|
||||
commandsByCategory.keySet().stream()
|
||||
.sorted()
|
||||
.forEach(
|
||||
cat -> {
|
||||
if (!HelpCategories.isCategoryVisible(cat, isAdmin, isSuper)) {
|
||||
return;
|
||||
}
|
||||
|
||||
commandsByCategory.keySet().stream()
|
||||
.sorted()
|
||||
.forEach(cat -> {
|
||||
if (!HelpCategories.isCategoryVisible(cat, isAdmin, isSuper)) {
|
||||
return;
|
||||
}
|
||||
String emoji = HelpCategories.getEmoji(cat);
|
||||
int count = commandsByCategory.get(cat).size();
|
||||
|
||||
String emoji = HelpCategories.getEmoji(cat);
|
||||
int count = commandsByCategory.get(cat).size();
|
||||
|
||||
menuBuilder.addOption(
|
||||
cat,
|
||||
"category:" + cat,
|
||||
count + " команд",
|
||||
net.dv8tion.jda.api.entities.emoji.Emoji.fromUnicode(emoji)
|
||||
);
|
||||
menuBuilder.addOption(
|
||||
cat,
|
||||
"category:" + cat,
|
||||
count + " команд",
|
||||
net.dv8tion.jda.api.entities.emoji.Emoji.fromUnicode(emoji));
|
||||
});
|
||||
|
||||
event.editMessageEmbeds(embed.build())
|
||||
.setActionRow(menuBuilder.build())
|
||||
.queue();
|
||||
}
|
||||
event.editMessageEmbeds(embed.build()).setActionRow(menuBuilder.build()).queue();
|
||||
}
|
||||
|
||||
private EmbedFactory createCategoryEmbed(String category, List<Command> commands,
|
||||
StringSelectInteractionEvent event) {
|
||||
String emoji = HelpCategories.getEmoji(category);
|
||||
Color color = HelpCategories.getColor(category);
|
||||
private EmbedFactory createCategoryEmbed(
|
||||
String category, List<Command> commands, StringSelectInteractionEvent event) {
|
||||
String emoji = HelpCategories.getEmoji(category);
|
||||
Color color = HelpCategories.getColor(category);
|
||||
|
||||
EmbedFactory embed = EmbedFactory.create()
|
||||
EmbedFactory embed =
|
||||
EmbedFactory.create()
|
||||
.withTitle(emoji + " " + category)
|
||||
.withDescription("Список команд категории **" + category + "**\n" +
|
||||
"Префикс: `" + config.getPrefix() + "`")
|
||||
.withDescription(
|
||||
"Список команд категории **"
|
||||
+ category
|
||||
+ "**\n"
|
||||
+ "Префикс: `"
|
||||
+ config.getPrefix()
|
||||
+ "`")
|
||||
.withColor(color);
|
||||
|
||||
// Группируем команды по 8 в поле
|
||||
StringBuilder commandList = new StringBuilder();
|
||||
int count = 0;
|
||||
// Группируем команды по 8 в поле
|
||||
StringBuilder commandList = new StringBuilder();
|
||||
int count = 0;
|
||||
|
||||
for (Command cmd : commands) {
|
||||
commandList.append("`").append(config.getPrefix()).append(cmd.getUsage()).append("`\n")
|
||||
.append(cmd.getDescription()).append("\n\n");
|
||||
count++;
|
||||
for (Command cmd : commands) {
|
||||
commandList
|
||||
.append("`")
|
||||
.append(config.getPrefix())
|
||||
.append(cmd.getUsage())
|
||||
.append("`\n")
|
||||
.append(cmd.getDescription())
|
||||
.append("\n\n");
|
||||
count++;
|
||||
|
||||
// Если достигли 8 команд или лимита символов, создаём новое поле
|
||||
if (count % 8 == 0 || commandList.length() > 900) {
|
||||
embed.addField("", commandList.toString().trim(), false);
|
||||
commandList = new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем оставшиеся команды
|
||||
if (commandList.length() > 0) {
|
||||
embed.addField("", commandList.toString().trim(), false);
|
||||
}
|
||||
|
||||
embed.addField("💡 Подсказка",
|
||||
"Используй селектор ниже для переключения между категориями",
|
||||
false);
|
||||
|
||||
embed.withFooter(
|
||||
"Запросил: " + event.getUser().getName() + " • Всего команд: " + commands.size(),
|
||||
event.getUser().getAvatarUrl()
|
||||
);
|
||||
|
||||
return embed;
|
||||
// Если достигли 8 команд или лимита символов, создаём новое поле
|
||||
if (count % 8 == 0 || commandList.length() > 900) {
|
||||
embed.addField("", commandList.toString().trim(), false);
|
||||
commandList = new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем оставшиеся команды
|
||||
if (commandList.length() > 0) {
|
||||
embed.addField("", commandList.toString().trim(), false);
|
||||
}
|
||||
|
||||
embed.addField(
|
||||
"💡 Подсказка", "Используй селектор ниже для переключения между категориями", false);
|
||||
|
||||
embed.withFooter(
|
||||
"Запросил: " + event.getUser().getName() + " • Всего команд: " + commands.size(),
|
||||
event.getUser().getAvatarUrl());
|
||||
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
package creator.loki.feature.help.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.config.BotConfig;
|
||||
|
|
@ -10,119 +15,136 @@ import net.dv8tion.jda.api.Permission;
|
|||
import net.dv8tion.jda.api.entities.User;
|
||||
import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu;
|
||||
|
||||
import java.awt.Color;
|
||||
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 final CommandRegistry registry;
|
||||
private final BotConfig config;
|
||||
|
||||
public HelpCommand(CommandRegistry registry, BotConfig config) {
|
||||
this.registry = registry;
|
||||
this.config = config;
|
||||
}
|
||||
public HelpCommand(CommandRegistry registry, BotConfig config) {
|
||||
this.registry = registry;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "help";
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return "help";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает список всех команд";
|
||||
}
|
||||
@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));
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var commands = registry.getAllCommands();
|
||||
Map<String, List<Command>> commandsByCategory =
|
||||
commands.stream().collect(Collectors.groupingBy(Command::getCategory));
|
||||
|
||||
User viewer = context.getAuthor() != null ? context.getAuthor().getUser() : context.getMessage().getAuthor();
|
||||
boolean isAdmin = context.getAuthor() != null
|
||||
&& context.getAuthor().hasPermission(Permission.ADMINISTRATOR);
|
||||
boolean isSuper = config.getSuperAdminId().equals(viewer.getId());
|
||||
User viewer =
|
||||
context.getAuthor() != null
|
||||
? context.getAuthor().getUser()
|
||||
: context.getMessage().getAuthor();
|
||||
boolean isAdmin =
|
||||
context.getAuthor() != null && context.getAuthor().hasPermission(Permission.ADMINISTRATOR);
|
||||
boolean isSuper = config.getSuperAdminId().equals(viewer.getId());
|
||||
|
||||
// Главная страница
|
||||
EmbedFactory embed = createMainPage(context, commandsByCategory, isAdmin, isSuper);
|
||||
// Главная страница
|
||||
EmbedFactory embed = createMainPage(context, commandsByCategory, isAdmin, isSuper);
|
||||
|
||||
// Создаём селектор категорий
|
||||
StringSelectMenu.Builder menuBuilder = StringSelectMenu.create("help:category")
|
||||
.setPlaceholder("📂 Выберите категорию");
|
||||
// Создаём селектор категорий
|
||||
StringSelectMenu.Builder menuBuilder =
|
||||
StringSelectMenu.create("help:category").setPlaceholder("📂 Выберите категорию");
|
||||
|
||||
commandsByCategory.keySet().stream()
|
||||
.sorted()
|
||||
.forEach(category -> {
|
||||
if (!HelpCategories.isCategoryVisible(category, isAdmin, isSuper)) {
|
||||
return;
|
||||
}
|
||||
commandsByCategory.keySet().stream()
|
||||
.sorted()
|
||||
.forEach(
|
||||
category -> {
|
||||
if (!HelpCategories.isCategoryVisible(category, isAdmin, isSuper)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String emoji = HelpCategories.getEmoji(category);
|
||||
String description = HelpCategories.getDescription(category);
|
||||
int count = commandsByCategory.get(category).size();
|
||||
String emoji = HelpCategories.getEmoji(category);
|
||||
String description = HelpCategories.getDescription(category);
|
||||
int count = commandsByCategory.get(category).size();
|
||||
|
||||
menuBuilder.addOption(
|
||||
category,
|
||||
"category:" + category,
|
||||
description + " (" + count + " команд)",
|
||||
net.dv8tion.jda.api.entities.emoji.Emoji.fromUnicode(emoji)
|
||||
);
|
||||
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();
|
||||
}
|
||||
context.getChannel().sendMessageEmbeds(embed.build()).addActionRow(menuBuilder.build()).queue();
|
||||
}
|
||||
|
||||
private EmbedFactory createMainPage(CommandContext context, Map<String, List<Command>> commandsByCategory,
|
||||
boolean isAdmin, boolean isSuper) {
|
||||
int totalCommands = commandsByCategory.entrySet().stream()
|
||||
private EmbedFactory createMainPage(
|
||||
CommandContext context,
|
||||
Map<String, List<Command>> commandsByCategory,
|
||||
boolean isAdmin,
|
||||
boolean isSuper) {
|
||||
int totalCommands =
|
||||
commandsByCategory.entrySet().stream()
|
||||
.filter(e -> HelpCategories.isCategoryVisible(e.getKey(), isAdmin, isSuper))
|
||||
.mapToInt(e -> e.getValue().size())
|
||||
.sum();
|
||||
|
||||
StringBuilder categoriesText = new StringBuilder();
|
||||
commandsByCategory.keySet().stream()
|
||||
.sorted()
|
||||
.forEach(category -> {
|
||||
if (!HelpCategories.isCategoryVisible(category, isAdmin, isSuper)) {
|
||||
return;
|
||||
}
|
||||
StringBuilder categoriesText = new StringBuilder();
|
||||
commandsByCategory.keySet().stream()
|
||||
.sorted()
|
||||
.forEach(
|
||||
category -> {
|
||||
if (!HelpCategories.isCategoryVisible(category, isAdmin, isSuper)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String emoji = HelpCategories.getEmoji(category);
|
||||
int count = commandsByCategory.get(category).size();
|
||||
categoriesText.append(emoji).append(" **").append(category)
|
||||
.append("** - ").append(count).append(" команд\n");
|
||||
String emoji = HelpCategories.getEmoji(category);
|
||||
int count = commandsByCategory.get(category).size();
|
||||
categoriesText
|
||||
.append(emoji)
|
||||
.append(" **")
|
||||
.append(category)
|
||||
.append("** - ")
|
||||
.append(count)
|
||||
.append(" команд\n");
|
||||
});
|
||||
|
||||
return EmbedFactory.create()
|
||||
.withTitle("📚 Справка по командам Loki Bot")
|
||||
.withDescription(
|
||||
"Привет! Я многофункциональный Discord бот с системой уровней, " +
|
||||
"личными голосовыми каналами и множеством развлекательных команд.\n\n" +
|
||||
"**Префикс команд:** `" + config.getPrefix() + "`\n" +
|
||||
"**Всего команд:** " + totalCommands + "\n\n" +
|
||||
"**📂 Категории команд:**\n" + categoriesText.toString() + "\n" +
|
||||
"Используй селектор ниже для просмотра команд категории."
|
||||
)
|
||||
.withColor(new Color(88, 101, 242))
|
||||
.withThumbnail(context.getGuild() != null ? context.getGuild().getIconUrl() : null)
|
||||
.addField("💡 Как использовать",
|
||||
"Все команды - с префиксом `" + config.getPrefix() + "`\n" +
|
||||
"Примеры: `" + config.getPrefix() + "rank`, `" + config.getPrefix() + "ship @user1 @user2`, `" +
|
||||
config.getPrefix() + "voice-setup info`",
|
||||
false)
|
||||
.withFooter(
|
||||
"Запросил: " + context.getAuthor().getUser().getName(),
|
||||
context.getAuthor().getUser().getAvatarUrl()
|
||||
);
|
||||
}
|
||||
return EmbedFactory.create()
|
||||
.withTitle("📚 Справка по командам Loki Bot")
|
||||
.withDescription(
|
||||
"Привет! Я многофункциональный Discord бот с системой уровней, "
|
||||
+ "личными голосовыми каналами и множеством развлекательных команд.\n\n"
|
||||
+ "**Префикс команд:** `"
|
||||
+ config.getPrefix()
|
||||
+ "`\n"
|
||||
+ "**Всего команд:** "
|
||||
+ totalCommands
|
||||
+ "\n\n"
|
||||
+ "**📂 Категории команд:**\n"
|
||||
+ categoriesText.toString()
|
||||
+ "\n"
|
||||
+ "Используй селектор ниже для просмотра команд категории.")
|
||||
.withColor(new Color(88, 101, 242))
|
||||
.withThumbnail(context.getGuild() != null ? context.getGuild().getIconUrl() : null)
|
||||
.addField(
|
||||
"💡 Как использовать",
|
||||
"Все команды - с префиксом `"
|
||||
+ config.getPrefix()
|
||||
+ "`\n"
|
||||
+ "Примеры: `"
|
||||
+ config.getPrefix()
|
||||
+ "rank`, `"
|
||||
+ config.getPrefix()
|
||||
+ "ship @user1 @user2`, `"
|
||||
+ config.getPrefix()
|
||||
+ "voice-setup info`",
|
||||
false)
|
||||
.withFooter(
|
||||
"Запросил: " + context.getAuthor().getUser().getName(),
|
||||
context.getAuthor().getUser().getAvatarUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Утилиты";
|
||||
}
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Утилиты";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,60 @@
|
|||
package creator.loki.feature.info.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
|
||||
public class AvatarCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "avatar";
|
||||
@Override
|
||||
public String getName() {
|
||||
return "avatar";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает аватар пользователя";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "avatar [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
User targetUser;
|
||||
|
||||
// Если упомянут пользователь - показываем его аватар
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (!mentioned.isEmpty()) {
|
||||
targetUser = mentioned.get(0).getUser();
|
||||
} else {
|
||||
// Иначе показываем аватар автора команды
|
||||
targetUser = context.getAuthor().getUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Показывает аватар пользователя";
|
||||
}
|
||||
String avatarUrl = targetUser.getEffectiveAvatarUrl() + "?size=512";
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "avatar [@user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Развлечения";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
User targetUser;
|
||||
|
||||
// Если упомянут пользователь - показываем его аватар
|
||||
List<Member> mentioned = context.getMessage().getMentions().getMembers();
|
||||
if (!mentioned.isEmpty()) {
|
||||
targetUser = mentioned.get(0).getUser();
|
||||
} else {
|
||||
// Иначе показываем аватар автора команды
|
||||
targetUser = context.getAuthor().getUser();
|
||||
}
|
||||
|
||||
String avatarUrl = targetUser.getEffectiveAvatarUrl() + "?size=512";
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🖼️ Аватар " + targetUser.getName())
|
||||
.withImage(avatarUrl)
|
||||
.withColor(Color.BLUE)
|
||||
.withFooter("ID: " + targetUser.getId())
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package creator.loki.feature.info.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
|
@ -9,108 +13,107 @@ import net.dv8tion.jda.api.entities.Member;
|
|||
import net.dv8tion.jda.api.entities.Role;
|
||||
import net.dv8tion.jda.api.entities.User;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InfoCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "info";
|
||||
@Override
|
||||
public String getName() {
|
||||
return "info";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Информация о сервере или пользователе";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "info [server|user]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
String type = context.getArgs().isEmpty() ? "server" : context.getArgs().get(0).toLowerCase();
|
||||
|
||||
switch (type) {
|
||||
case "server" -> showServerInfo(context);
|
||||
case "user" -> showUserInfo(context);
|
||||
default ->
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.error("Неверный аргумент", "Используй: `!info [server|user]`"))
|
||||
.queue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Информация о сервере или пользователе";
|
||||
}
|
||||
private void showServerInfo(CommandContext context) {
|
||||
Guild guild = context.getGuild();
|
||||
if (guild == null) return;
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "info [server|user]";
|
||||
}
|
||||
String iconUrl = guild.getIconUrl();
|
||||
if (iconUrl == null) iconUrl = "";
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
String type = context.getArgs().isEmpty() ? "server" : context.getArgs().get(0).toLowerCase();
|
||||
|
||||
switch (type) {
|
||||
case "server" -> showServerInfo(context);
|
||||
case "user" -> showUserInfo(context);
|
||||
default -> context.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.error(
|
||||
"Неверный аргумент",
|
||||
"Используй: `!info [server|user]`"
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
}
|
||||
|
||||
private void showServerInfo(CommandContext context) {
|
||||
Guild guild = context.getGuild();
|
||||
if (guild == null) return;
|
||||
|
||||
String iconUrl = guild.getIconUrl();
|
||||
if (iconUrl == null) iconUrl = "";
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🏰 Информация о сервере")
|
||||
.withColor(Color.BLUE)
|
||||
.withThumbnail(iconUrl)
|
||||
.addField("Название", guild.getName(), true)
|
||||
.addField("ID", guild.getId(), true)
|
||||
.addField("Владелец", guild.getOwner() != null ? guild.getOwner().getAsMention() : "Неизвестно", true)
|
||||
.addField(
|
||||
"Владелец",
|
||||
guild.getOwner() != null ? guild.getOwner().getAsMention() : "Неизвестно",
|
||||
true)
|
||||
.addField("Участников", String.valueOf(guild.getMemberCount()), true)
|
||||
.addField("Каналов", String.valueOf(guild.getChannels().size()), true)
|
||||
.addField("Ролей", String.valueOf(guild.getRoles().size()), true)
|
||||
.addField(
|
||||
"Создан",
|
||||
guild.getTimeCreated().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")),
|
||||
false
|
||||
)
|
||||
.build()
|
||||
).queue();
|
||||
false)
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
|
||||
private void showUserInfo(CommandContext context) {
|
||||
Member member = context.getAuthor();
|
||||
if (member == null) return;
|
||||
|
||||
User user = member.getUser();
|
||||
|
||||
String roles =
|
||||
member.getRoles().stream().map(Role::getAsMention).collect(Collectors.joining(", "));
|
||||
if (roles.isEmpty()) {
|
||||
roles = "Нет ролей";
|
||||
}
|
||||
|
||||
private void showUserInfo(CommandContext context) {
|
||||
Member member = context.getAuthor();
|
||||
if (member == null) return;
|
||||
|
||||
User user = member.getUser();
|
||||
|
||||
String roles = member.getRoles().stream()
|
||||
.map(Role::getAsMention)
|
||||
.collect(Collectors.joining(", "));
|
||||
if (roles.isEmpty()) {
|
||||
roles = "Нет ролей";
|
||||
}
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("👤 Информация о пользователе")
|
||||
.withColor(Color.CYAN)
|
||||
.withThumbnail(user.getEffectiveAvatarUrl())
|
||||
.addField("Имя", user.getName(), true)
|
||||
.addField("ID", user.getId(), true)
|
||||
.addField("Никнейм", member.getNickname() != null ? member.getNickname() : "Не установлен", true)
|
||||
.addField(
|
||||
"Никнейм",
|
||||
member.getNickname() != null ? member.getNickname() : "Не установлен",
|
||||
true)
|
||||
.addField("Бот?", user.isBot() ? "Да" : "Нет", true)
|
||||
.addField(
|
||||
"Присоединился",
|
||||
member.getTimeJoined().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")),
|
||||
false
|
||||
)
|
||||
.addField(
|
||||
"Роли (" + member.getRoles().size() + ")",
|
||||
roles,
|
||||
false
|
||||
)
|
||||
.build()
|
||||
).queue();
|
||||
}
|
||||
false)
|
||||
.addField("Роли (" + member.getRoles().size() + ")", roles, false)
|
||||
.build())
|
||||
.queue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Утилиты";
|
||||
}
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Утилиты";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,49 @@
|
|||
package creator.loki.feature.info.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
public class PingCommand implements Command {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ping";
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ping";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка задержки бота";
|
||||
}
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Проверка задержки бота";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
context.getChannel().sendMessage("Измеряю задержку...").queue(message -> {
|
||||
long ping = System.currentTimeMillis() - startTime;
|
||||
long gatewayPing = context.getMessage().getJDA().getGatewayPing();
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessage("Измеряю задержку...")
|
||||
.queue(
|
||||
message -> {
|
||||
long ping = System.currentTimeMillis() - startTime;
|
||||
long gatewayPing = context.getMessage().getJDA().getGatewayPing();
|
||||
|
||||
message.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🏓 Pong!")
|
||||
.withColor(Color.GREEN)
|
||||
.addField("Задержка API", ping + "ms", true)
|
||||
.addField("Задержка Gateway", gatewayPing + "ms", true)
|
||||
.build()
|
||||
).queue();
|
||||
});
|
||||
}
|
||||
message
|
||||
.editMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("🏓 Pong!")
|
||||
.withColor(Color.GREEN)
|
||||
.addField("Задержка API", ping + "ms", true)
|
||||
.addField("Задержка Gateway", gatewayPing + "ms", true)
|
||||
.build())
|
||||
.queue();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Утилиты";
|
||||
}
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Утилиты";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,128 +1,187 @@
|
|||
package creator.loki.feature.info.commands;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.feature.serverstats.ServerStatsService;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.channel.ChannelType;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.Category;
|
||||
import net.dv8tion.jda.api.utils.FileUpload;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class ServerStatsCommand implements Command {
|
||||
private final ServerStatsService statsService;
|
||||
private final ServerStatsService statsService;
|
||||
|
||||
public ServerStatsCommand(ServerStatsService statsService) {
|
||||
this.statsService = statsService;
|
||||
public ServerStatsCommand(ServerStatsService statsService) {
|
||||
this.statsService = statsService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "serverstats";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "📊 Статистика сервера (участники, каналы, роли)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "serverstats [csv|chart]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Информация";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
Guild guild = context.getGuild();
|
||||
if (guild == null) {
|
||||
context.getChannel().sendMessage("Только на сервере.").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "serverstats";
|
||||
String arg = context.getArgs().isEmpty() ? "" : context.getArgs().get(0).toLowerCase();
|
||||
|
||||
switch (arg) {
|
||||
case "csv" -> handleCsv(context, guild);
|
||||
case "chart" -> handleChart(context, guild);
|
||||
default -> handleEmbed(context, guild);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "📊 Статистика сервера (участники, каналы, роли)";
|
||||
}
|
||||
private void handleEmbed(CommandContext context, Guild guild) {
|
||||
guild
|
||||
.loadMembers()
|
||||
.onSuccess(
|
||||
members -> {
|
||||
long total = members.size();
|
||||
long bots = members.stream().filter(m -> m.getUser().isBot()).count();
|
||||
long humans = total - bots;
|
||||
long online =
|
||||
members.stream()
|
||||
.filter(
|
||||
m ->
|
||||
m.getOnlineStatus() != null
|
||||
&& m.getOnlineStatus() != net.dv8tion.jda.api.OnlineStatus.OFFLINE
|
||||
&& m.getOnlineStatus()
|
||||
!= net.dv8tion.jda.api.OnlineStatus.UNKNOWN)
|
||||
.count();
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "serverstats [csv|chart]";
|
||||
}
|
||||
long textChannels = guild.getTextChannels().size();
|
||||
long voiceChannels = guild.getVoiceChannels().size();
|
||||
long categories = guild.getCategories().size();
|
||||
long totalChannels = textChannels + voiceChannels + categories;
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Информация";
|
||||
}
|
||||
int roles = guild.getRoles().size();
|
||||
int emojis = guild.getEmojis().size();
|
||||
int boostTier = guild.getBoostTier().getKey();
|
||||
int boostCount = guild.getBoostCount();
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
Guild guild = context.getGuild();
|
||||
if (guild == null) {
|
||||
context.getChannel().sendMessage("Только на сервере.").queue();
|
||||
return;
|
||||
}
|
||||
String created =
|
||||
guild.getTimeCreated().format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
|
||||
|
||||
String arg = context.getArgs().isEmpty() ? "" : context.getArgs().get(0).toLowerCase();
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("📊 Статистика сервера")
|
||||
.withThumbnail(guild.getIconUrl())
|
||||
.withColor(new Color(88, 101, 242))
|
||||
.addField(
|
||||
"👤 Участники",
|
||||
"Всего: **"
|
||||
+ total
|
||||
+ "**\n👥 Людей: **"
|
||||
+ humans
|
||||
+ "**\n🤖 Ботов: **"
|
||||
+ bots
|
||||
+ "**\n🟢 Онлайн: **"
|
||||
+ online
|
||||
+ "**",
|
||||
true)
|
||||
.addField(
|
||||
"💬 Каналы",
|
||||
"Текстовых: **"
|
||||
+ textChannels
|
||||
+ "**\n"
|
||||
+ "🔊 Голосовых: **"
|
||||
+ voiceChannels
|
||||
+ "**\n"
|
||||
+ "📁 Категорий: **"
|
||||
+ categories
|
||||
+ "**\n"
|
||||
+ "Всего: **"
|
||||
+ totalChannels
|
||||
+ "**",
|
||||
true)
|
||||
.addField(
|
||||
"⚙️ Прочее",
|
||||
"Ролей: **"
|
||||
+ roles
|
||||
+ "**\n😀 Эмодзи: **"
|
||||
+ emojis
|
||||
+ "**\n🚀 Бустов: **"
|
||||
+ boostCount
|
||||
+ "** (ур. "
|
||||
+ boostTier
|
||||
+ ")",
|
||||
true)
|
||||
.withFooter(
|
||||
"Создан: "
|
||||
+ created
|
||||
+ " | Владелец: "
|
||||
+ (guild.getOwner() != null
|
||||
? guild.getOwner().getUser().getGlobalName()
|
||||
: "неизвестен"))
|
||||
.build())
|
||||
.queue();
|
||||
});
|
||||
}
|
||||
|
||||
switch (arg) {
|
||||
case "csv" -> handleCsv(context, guild);
|
||||
case "chart" -> handleChart(context, guild);
|
||||
default -> handleEmbed(context, guild);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEmbed(CommandContext context, Guild guild) {
|
||||
guild.loadMembers().onSuccess(members -> {
|
||||
long total = members.size();
|
||||
long bots = members.stream().filter(m -> m.getUser().isBot()).count();
|
||||
long humans = total - bots;
|
||||
long online = members.stream().filter(m -> m.getOnlineStatus() != null
|
||||
&& m.getOnlineStatus() != net.dv8tion.jda.api.OnlineStatus.OFFLINE
|
||||
&& m.getOnlineStatus() != net.dv8tion.jda.api.OnlineStatus.UNKNOWN).count();
|
||||
|
||||
long textChannels = guild.getTextChannels().size();
|
||||
long voiceChannels = guild.getVoiceChannels().size();
|
||||
long categories = guild.getCategories().size();
|
||||
long totalChannels = textChannels + voiceChannels + categories;
|
||||
|
||||
int roles = guild.getRoles().size();
|
||||
int emojis = guild.getEmojis().size();
|
||||
int boostTier = guild.getBoostTier().getKey();
|
||||
int boostCount = guild.getBoostCount();
|
||||
|
||||
String created = guild.getTimeCreated().format(DateTimeFormatter.ofPattern("dd.MM.yyyy"));
|
||||
|
||||
context.getChannel().sendMessageEmbeds(
|
||||
EmbedFactory.create()
|
||||
.withTitle("📊 Статистика сервера")
|
||||
.withThumbnail(guild.getIconUrl())
|
||||
.withColor(new Color(88, 101, 242))
|
||||
.addField("👤 Участники",
|
||||
"Всего: **" + total + "**\n👥 Людей: **" + humans + "**\n🤖 Ботов: **" + bots + "**\n🟢 Онлайн: **" + online + "**", true)
|
||||
.addField("💬 Каналы",
|
||||
"Текстовых: **" + textChannels + "**\n"
|
||||
+ "🔊 Голосовых: **" + voiceChannels + "**\n"
|
||||
+ "📁 Категорий: **" + categories + "**\n"
|
||||
+ "Всего: **" + totalChannels + "**", true)
|
||||
.addField("⚙️ Прочее",
|
||||
"Ролей: **" + roles + "**\n😀 Эмодзи: **" + emojis + "**\n🚀 Бустов: **" + boostCount + "** (ур. " + boostTier + ")", true)
|
||||
.withFooter("Создан: " + created + " | Владелец: "
|
||||
+ (guild.getOwner() != null ? guild.getOwner().getUser().getGlobalName() : "неизвестен"))
|
||||
.build()
|
||||
).queue();
|
||||
});
|
||||
}
|
||||
|
||||
private void handleCsv(CommandContext context, Guild guild) {
|
||||
context.getChannel().sendMessage("⏳ Генерирую CSV...").queue(msg -> {
|
||||
try {
|
||||
private void handleCsv(CommandContext context, Guild guild) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessage("⏳ Генерирую CSV...")
|
||||
.queue(
|
||||
msg -> {
|
||||
try {
|
||||
var history = statsService.getHistory(30);
|
||||
byte[] csv = statsService.generateCsv(guild, history);
|
||||
context.getChannel().sendFiles(
|
||||
FileUpload.fromData(csv, "serverstats.csv")
|
||||
).queue(s -> msg.delete().queue(), e -> msg.editMessage("❌ Ошибка генерации CSV").queue());
|
||||
} catch (Exception e) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendFiles(FileUpload.fromData(csv, "serverstats.csv"))
|
||||
.queue(
|
||||
s -> msg.delete().queue(),
|
||||
e -> msg.editMessage("❌ Ошибка генерации CSV").queue());
|
||||
} catch (Exception e) {
|
||||
msg.editMessage("❌ Ошибка: " + e.getMessage()).queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleChart(CommandContext context, Guild guild) {
|
||||
context.getChannel().sendMessage("⏳ Генерирую график...").queue(msg -> {
|
||||
try {
|
||||
private void handleChart(CommandContext context, Guild guild) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessage("⏳ Генерирую график...")
|
||||
.queue(
|
||||
msg -> {
|
||||
try {
|
||||
var history = statsService.getHistory(30);
|
||||
byte[] chart = statsService.generateChart(history);
|
||||
context.getChannel().sendFiles(
|
||||
FileUpload.fromData(chart, "chart.png")
|
||||
).queue(s -> msg.delete().queue(), e -> msg.editMessage("❌ Ошибка отправки графика").queue());
|
||||
} catch (Exception e) {
|
||||
context
|
||||
.getChannel()
|
||||
.sendFiles(FileUpload.fromData(chart, "chart.png"))
|
||||
.queue(
|
||||
s -> msg.delete().queue(),
|
||||
e -> msg.editMessage("❌ Ошибка отправки графика").queue());
|
||||
} catch (Exception e) {
|
||||
msg.editMessage("❌ Ошибка: " + e.getMessage()).queue();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,50 +6,51 @@ import creator.loki.feature.level.service.LeaderboardService;
|
|||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
|
||||
/**
|
||||
* Обработчик кнопок таблицы лидеров (Single Responsibility)
|
||||
*/
|
||||
/** Обработчик кнопок таблицы лидеров (Single Responsibility) */
|
||||
@Singleton
|
||||
public class LeaderboardButtonListener extends ListenerAdapter {
|
||||
private final LeaderboardService leaderboardService;
|
||||
private final LeaderboardService leaderboardService;
|
||||
|
||||
@Inject
|
||||
public LeaderboardButtonListener(LeaderboardService leaderboardService) {
|
||||
this.leaderboardService = leaderboardService;
|
||||
@Inject
|
||||
public LeaderboardButtonListener(LeaderboardService leaderboardService) {
|
||||
this.leaderboardService = leaderboardService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonInteraction(ButtonInteractionEvent event) {
|
||||
String buttonId = event.getComponentId();
|
||||
|
||||
if (!buttonId.startsWith("lb_")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onButtonInteraction(ButtonInteractionEvent event) {
|
||||
String buttonId = event.getComponentId();
|
||||
event.deferEdit().queue();
|
||||
|
||||
if (!buttonId.startsWith("lb_")) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.deferEdit().queue();
|
||||
|
||||
switch (buttonId) {
|
||||
case "lb_level" -> showLevelLeaderboard(event);
|
||||
case "lb_messages" -> showMessageLeaderboard(event);
|
||||
case "lb_voice" -> showVoiceLeaderboard(event);
|
||||
}
|
||||
switch (buttonId) {
|
||||
case "lb_level" -> showLevelLeaderboard(event);
|
||||
case "lb_messages" -> showMessageLeaderboard(event);
|
||||
case "lb_voice" -> showVoiceLeaderboard(event);
|
||||
default -> {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showLevelLeaderboard(ButtonInteractionEvent event) {
|
||||
var top = leaderboardService.getTopByLevel(10);
|
||||
var embed = LeaderboardFormatter.formatLevelLeaderboard(top);
|
||||
event.getHook().editOriginalEmbeds(embed).queue();
|
||||
}
|
||||
private void showLevelLeaderboard(ButtonInteractionEvent event) {
|
||||
var top = leaderboardService.getTopByLevel(10);
|
||||
var embed = LeaderboardFormatter.formatLevelLeaderboard(top);
|
||||
event.getHook().editOriginalEmbeds(embed).queue();
|
||||
}
|
||||
|
||||
private void showMessageLeaderboard(ButtonInteractionEvent event) {
|
||||
var top = leaderboardService.getTopByMessages(10);
|
||||
var embed = LeaderboardFormatter.formatMessageLeaderboard(top);
|
||||
event.getHook().editOriginalEmbeds(embed).queue();
|
||||
}
|
||||
private void showMessageLeaderboard(ButtonInteractionEvent event) {
|
||||
var top = leaderboardService.getTopByMessages(10);
|
||||
var embed = LeaderboardFormatter.formatMessageLeaderboard(top);
|
||||
event.getHook().editOriginalEmbeds(embed).queue();
|
||||
}
|
||||
|
||||
private void showVoiceLeaderboard(ButtonInteractionEvent event) {
|
||||
var top = leaderboardService.getTopByVoice(10);
|
||||
var embed = LeaderboardFormatter.formatVoiceLeaderboard(top);
|
||||
event.getHook().editOriginalEmbeds(embed).queue();
|
||||
}
|
||||
private void showVoiceLeaderboard(ButtonInteractionEvent event) {
|
||||
var top = leaderboardService.getTopByVoice(10);
|
||||
var embed = LeaderboardFormatter.formatVoiceLeaderboard(top);
|
||||
event.getHook().editOriginalEmbeds(embed).queue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
package creator.loki.feature.leaderboard;
|
||||
|
||||
import net.dv8tion.jda.api.interactions.components.buttons.Button;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Кнопки для таблицы лидеров
|
||||
*/
|
||||
import net.dv8tion.jda.api.interactions.components.buttons.Button;
|
||||
|
||||
/** Кнопки для таблицы лидеров */
|
||||
public class LeaderboardButtons {
|
||||
|
||||
public static List<Button> getButtons() {
|
||||
return List.of(
|
||||
Button.primary("lb_level", "📊 Уровни"),
|
||||
Button.success("lb_messages", "💬 Сообщения"),
|
||||
Button.secondary("lb_voice", "🎤 Войс")
|
||||
);
|
||||
}
|
||||
public static List<Button> getButtons() {
|
||||
return List.of(
|
||||
Button.primary("lb_level", "📊 Уровни"),
|
||||
Button.success("lb_messages", "💬 Сообщения"),
|
||||
Button.secondary("lb_voice", "🎤 Войс"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,59 @@
|
|||
package creator.loki.feature.leaderboard;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import creator.loki.command.Command;
|
||||
import creator.loki.command.CommandContext;
|
||||
import creator.loki.feature.level.service.LeaderboardService;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Команда таблицы лидеров (рефакторинг: 241 → 50 строк)
|
||||
*/
|
||||
/** Команда таблицы лидеров (рефакторинг: 241 → 50 строк) */
|
||||
public class LeaderboardCommand implements Command {
|
||||
private final LeaderboardService leaderboardService;
|
||||
private final LeaderboardService leaderboardService;
|
||||
|
||||
public LeaderboardCommand(LeaderboardService leaderboardService) {
|
||||
this.leaderboardService = leaderboardService;
|
||||
public LeaderboardCommand(LeaderboardService leaderboardService) {
|
||||
this.leaderboardService = leaderboardService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "leaderboard";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Таблица лидеров (уровни, сообщения, войс)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "leaderboard";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Статистика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var top = leaderboardService.getTopByLevel(10);
|
||||
|
||||
if (top.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Пока нет статистики!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "leaderboard";
|
||||
}
|
||||
var embed = LeaderboardFormatter.formatLevelLeaderboard(top);
|
||||
var buttons = LeaderboardButtons.getButtons();
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Таблица лидеров (уровни, сообщения, войс)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsage() {
|
||||
return "leaderboard";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return "Статистика";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandContext context) {
|
||||
var top = leaderboardService.getTopByLevel(10);
|
||||
|
||||
if (top.isEmpty()) {
|
||||
context.getChannel().sendMessage("❌ Пока нет статистики!").queue();
|
||||
return;
|
||||
}
|
||||
|
||||
var embed = LeaderboardFormatter.formatLevelLeaderboard(top);
|
||||
var buttons = LeaderboardButtons.getButtons();
|
||||
|
||||
context.getChannel().sendMessageEmbeds(embed)
|
||||
.setActionRow(buttons)
|
||||
.queue(message -> {
|
||||
// Удаляем кнопки через 5 минут
|
||||
message.editMessageComponents().queueAfter(5, TimeUnit.MINUTES);
|
||||
context
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(embed)
|
||||
.setActionRow(buttons)
|
||||
.queue(
|
||||
message -> {
|
||||
// Удаляем кнопки через 5 минут
|
||||
message.editMessageComponents().queueAfter(5, TimeUnit.MINUTES);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,89 +1,90 @@
|
|||
package creator.loki.feature.leaderboard;
|
||||
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.feature.level.domain.UserStats;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Форматирование таблиц лидеров (DRY принцип)
|
||||
*/
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.feature.level.domain.UserStats;
|
||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||
|
||||
/** Форматирование таблиц лидеров (DRY принцип) */
|
||||
public class LeaderboardFormatter {
|
||||
|
||||
public static MessageEmbed formatLevelLeaderboard(List<Map.Entry<String, UserStats>> top) {
|
||||
return formatLeaderboard(
|
||||
top,
|
||||
"🏆 Топ-10 по уровням",
|
||||
new Color(255, 215, 0),
|
||||
entry -> {
|
||||
UserStats stats = entry.getValue();
|
||||
return "Уровень **" + stats.getLevel() + "** (" + stats.getTotalXP() + " XP)";
|
||||
}
|
||||
);
|
||||
public static MessageEmbed formatLevelLeaderboard(List<Map.Entry<String, UserStats>> top) {
|
||||
return formatLeaderboard(
|
||||
top,
|
||||
"🏆 Топ-10 по уровням",
|
||||
new Color(255, 215, 0),
|
||||
entry -> {
|
||||
UserStats stats = entry.getValue();
|
||||
return "Уровень **" + stats.getLevel() + "** (" + stats.getTotalXP() + " XP)";
|
||||
});
|
||||
}
|
||||
|
||||
public static MessageEmbed formatMessageLeaderboard(List<Map.Entry<String, UserStats>> top) {
|
||||
return formatLeaderboard(
|
||||
top,
|
||||
"💬 Топ-10 по сообщениям",
|
||||
new Color(52, 152, 219),
|
||||
entry -> "**" + entry.getValue().getMessageCount() + "** сообщений");
|
||||
}
|
||||
|
||||
public static MessageEmbed formatVoiceLeaderboard(List<Map.Entry<String, UserStats>> top) {
|
||||
return formatLeaderboard(
|
||||
top,
|
||||
"🎤 Топ-10 по времени в войсе",
|
||||
new Color(155, 89, 182),
|
||||
entry -> {
|
||||
long minutes = entry.getValue().getVoiceMinutes();
|
||||
long hours = minutes / 60;
|
||||
long mins = minutes % 60;
|
||||
return "**" + hours + "ч " + mins + "м** в войсе";
|
||||
});
|
||||
}
|
||||
|
||||
private static MessageEmbed formatLeaderboard(
|
||||
List<Map.Entry<String, UserStats>> top,
|
||||
String title,
|
||||
Color color,
|
||||
Function<Map.Entry<String, UserStats>, String> statFormatter) {
|
||||
StringBuilder description = new StringBuilder();
|
||||
int position = 1;
|
||||
|
||||
for (var entry : top) {
|
||||
String emoji = getPositionEmoji(position);
|
||||
String userId = entry.getKey();
|
||||
String stats = statFormatter.apply(entry);
|
||||
|
||||
description
|
||||
.append(emoji)
|
||||
.append(" **#")
|
||||
.append(position)
|
||||
.append("** <@")
|
||||
.append(userId)
|
||||
.append(">")
|
||||
.append(" - ")
|
||||
.append(stats)
|
||||
.append("\n");
|
||||
|
||||
position++;
|
||||
}
|
||||
|
||||
public static MessageEmbed formatMessageLeaderboard(List<Map.Entry<String, UserStats>> top) {
|
||||
return formatLeaderboard(
|
||||
top,
|
||||
"💬 Топ-10 по сообщениям",
|
||||
new Color(52, 152, 219),
|
||||
entry -> "**" + entry.getValue().getMessageCount() + "** сообщений"
|
||||
);
|
||||
}
|
||||
return EmbedFactory.create()
|
||||
.withTitle(title)
|
||||
.withDescription(description.toString())
|
||||
.withColor(color)
|
||||
.withFooter("Выбери категорию ниже")
|
||||
.build();
|
||||
}
|
||||
|
||||
public static MessageEmbed formatVoiceLeaderboard(List<Map.Entry<String, UserStats>> top) {
|
||||
return formatLeaderboard(
|
||||
top,
|
||||
"🎤 Топ-10 по времени в войсе",
|
||||
new Color(155, 89, 182),
|
||||
entry -> {
|
||||
long minutes = entry.getValue().getVoiceMinutes();
|
||||
long hours = minutes / 60;
|
||||
long mins = minutes % 60;
|
||||
return "**" + hours + "ч " + mins + "м** в войсе";
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static MessageEmbed formatLeaderboard(
|
||||
List<Map.Entry<String, UserStats>> top,
|
||||
String title,
|
||||
Color color,
|
||||
Function<Map.Entry<String, UserStats>, String> statFormatter
|
||||
) {
|
||||
StringBuilder description = new StringBuilder();
|
||||
int position = 1;
|
||||
|
||||
for (var entry : top) {
|
||||
String emoji = getPositionEmoji(position);
|
||||
String userId = entry.getKey();
|
||||
String stats = statFormatter.apply(entry);
|
||||
|
||||
description.append(emoji)
|
||||
.append(" **#").append(position).append("** <@").append(userId).append(">")
|
||||
.append(" - ").append(stats).append("\n");
|
||||
|
||||
position++;
|
||||
}
|
||||
|
||||
return EmbedFactory.create()
|
||||
.withTitle(title)
|
||||
.withDescription(description.toString())
|
||||
.withColor(color)
|
||||
.withFooter("Выбери категорию ниже")
|
||||
.build();
|
||||
}
|
||||
|
||||
private static String getPositionEmoji(int position) {
|
||||
return switch (position) {
|
||||
case 1 -> "🥇";
|
||||
case 2 -> "🥈";
|
||||
case 3 -> "🥉";
|
||||
default -> "⭐";
|
||||
};
|
||||
}
|
||||
private static String getPositionEmoji(int position) {
|
||||
return switch (position) {
|
||||
case 1 -> "🥇";
|
||||
case 2 -> "🥈";
|
||||
case 3 -> "🥉";
|
||||
default -> "⭐";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,38 +7,36 @@ import creator.loki.feature.config.repository.ConfigRepository;
|
|||
import creator.loki.feature.leaderboard.LeaderboardCommand;
|
||||
import creator.loki.feature.level.commands.LevelRoleCommand;
|
||||
import creator.loki.feature.level.service.LeaderboardService;
|
||||
import creator.loki.feature.level.service.LevelService;
|
||||
import creator.loki.feature.profile.ProfileCommand;
|
||||
import creator.loki.feature.profile.ProfileService;
|
||||
|
||||
public class LevelCommandRegistrar implements CommandRegistrar {
|
||||
private final LeaderboardService leaderboardService;
|
||||
private final ResetLevelsCommand resetLevelsCommand;
|
||||
private final ProfileService profileService;
|
||||
private final LevelRoleService levelRoleService;
|
||||
private final ConfigRepository configRepository;
|
||||
private final LeaderboardService leaderboardService;
|
||||
private final ResetLevelsCommand resetLevelsCommand;
|
||||
private final ProfileService profileService;
|
||||
private final LevelRoleService levelRoleService;
|
||||
private final ConfigRepository configRepository;
|
||||
|
||||
@Inject
|
||||
public LevelCommandRegistrar(
|
||||
LeaderboardService leaderboardService,
|
||||
ResetLevelsCommand resetLevelsCommand,
|
||||
ProfileService profileService,
|
||||
LevelRoleService levelRoleService,
|
||||
ConfigRepository configRepository
|
||||
) {
|
||||
this.leaderboardService = leaderboardService;
|
||||
this.resetLevelsCommand = resetLevelsCommand;
|
||||
this.profileService = profileService;
|
||||
this.levelRoleService = levelRoleService;
|
||||
this.configRepository = configRepository;
|
||||
}
|
||||
@Inject
|
||||
public LevelCommandRegistrar(
|
||||
LeaderboardService leaderboardService,
|
||||
ResetLevelsCommand resetLevelsCommand,
|
||||
ProfileService profileService,
|
||||
LevelRoleService levelRoleService,
|
||||
ConfigRepository configRepository) {
|
||||
this.leaderboardService = leaderboardService;
|
||||
this.resetLevelsCommand = resetLevelsCommand;
|
||||
this.profileService = profileService;
|
||||
this.levelRoleService = levelRoleService;
|
||||
this.configRepository = configRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(CommandRegistry registry) {
|
||||
registry.register(new LeaderboardCommand(leaderboardService));
|
||||
registry.register(new StatsCommand());
|
||||
registry.register(resetLevelsCommand);
|
||||
registry.register(new ProfileCommand(profileService));
|
||||
registry.register(new LevelRoleCommand(levelRoleService, configRepository));
|
||||
}
|
||||
@Override
|
||||
public void register(CommandRegistry registry) {
|
||||
registry.register(new LeaderboardCommand(leaderboardService));
|
||||
registry.register(new StatsCommand());
|
||||
registry.register(resetLevelsCommand);
|
||||
registry.register(new ProfileCommand(profileService));
|
||||
registry.register(new LevelRoleCommand(levelRoleService, configRepository));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,74 +5,70 @@ 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.level.repository.SqlUserStatsRepository;
|
||||
import creator.loki.feature.config.repository.GuildConfigRepository;
|
||||
import creator.loki.feature.level.domain.XpFormula;
|
||||
import creator.loki.feature.level.repository.SqlUserStatsRepository;
|
||||
import creator.loki.feature.level.repository.UserStatsRepository;
|
||||
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.config.repository.GuildConfigRepository;
|
||||
import creator.loki.feature.level.repository.UserStatsRepository;
|
||||
|
||||
public class LevelModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(VoiceTracker.class).in(Singleton.class);
|
||||
bind(ResetLevelsButtonHandler.class).in(Singleton.class);
|
||||
}
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(VoiceTracker.class).in(Singleton.class);
|
||||
bind(ResetLevelsButtonHandler.class).in(Singleton.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
UserStatsRepository provideUserStatsRepository(Database database) {
|
||||
return new SqlUserStatsRepository(database);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
UserStatsRepository provideUserStatsRepository(Database database) {
|
||||
return new SqlUserStatsRepository(database);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
XpFormula provideXpFormula() {
|
||||
return new XpFormula();
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
XpFormula provideXpFormula() {
|
||||
return new XpFormula();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
MessageCooldownService provideMessageCooldownService(UserStatsRepository repository) {
|
||||
return new MessageCooldownService(repository);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
MessageCooldownService provideMessageCooldownService(UserStatsRepository repository) {
|
||||
return new MessageCooldownService(repository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LeaderboardService provideLeaderboardService(UserStatsRepository repository) {
|
||||
return new LeaderboardService(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
|
||||
LevelService provideLevelService(
|
||||
UserStatsRepository repository, XpFormula xpFormula, MessageCooldownService cooldownService) {
|
||||
return new LevelService(repository, xpFormula, cooldownService);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LevelRoleService provideLevelRoleService(ConfigRepository configRepository) {
|
||||
return new LevelRoleService(configRepository);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
LevelRoleService provideLevelRoleService(ConfigRepository configRepository) {
|
||||
return new LevelRoleService(configRepository);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
LevelUpNotifier provideLevelUpNotifier(ConfigRepository configRepo, LevelService levelService, LevelRoleService levelRoleService) {
|
||||
return new LevelUpNotifier(configRepo, levelService, levelRoleService);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
LevelUpNotifier provideLevelUpNotifier(
|
||||
ConfigRepository configRepo, LevelService levelService, LevelRoleService levelRoleService) {
|
||||
return new LevelUpNotifier(configRepo, levelService, levelRoleService);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ResetLevelsCommand provideResetLevelsCommand(
|
||||
UserStatsRepository userStatsRepository,
|
||||
GuildConfigRepository guildConfigRepository
|
||||
) {
|
||||
return new ResetLevelsCommand(userStatsRepository, guildConfigRepository);
|
||||
}
|
||||
@Provides
|
||||
@Singleton
|
||||
ResetLevelsCommand provideResetLevelsCommand(
|
||||
UserStatsRepository userStatsRepository, GuildConfigRepository guildConfigRepository) {
|
||||
return new ResetLevelsCommand(userStatsRepository, guildConfigRepository);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +1,92 @@
|
|||
package creator.loki.feature.level;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import creator.loki.feature.config.repository.ConfigRepository;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class LevelRoleService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LevelRoleService.class);
|
||||
private static final Map<Integer, String> DEFAULT_ROLES = Map.of(
|
||||
5, "Активный",
|
||||
10, "Постоянный",
|
||||
20, "Ветеран",
|
||||
30, "Легенда",
|
||||
50, "Мастер"
|
||||
);
|
||||
private static final Logger logger = LoggerFactory.getLogger(LevelRoleService.class);
|
||||
private static final Map<Integer, String> DEFAULT_ROLES =
|
||||
Map.of(
|
||||
5, "Активный",
|
||||
10, "Постоянный",
|
||||
20, "Ветеран",
|
||||
30, "Легенда",
|
||||
50, "Мастер");
|
||||
|
||||
private final ConfigRepository configRepository;
|
||||
private final ConfigRepository configRepository;
|
||||
|
||||
public LevelRoleService(ConfigRepository configRepository) {
|
||||
this.configRepository = configRepository;
|
||||
public LevelRoleService(ConfigRepository configRepository) {
|
||||
this.configRepository = configRepository;
|
||||
}
|
||||
|
||||
public String getRoleForLevel(int level) {
|
||||
String configRole = configRepository.get("level_role_" + level);
|
||||
if (configRole != null) return configRole;
|
||||
return DEFAULT_ROLES.get(level);
|
||||
}
|
||||
|
||||
public void setRoleForLevel(int level, String roleName) {
|
||||
configRepository.set("level_role_" + level, roleName);
|
||||
}
|
||||
|
||||
public void removeRoleForLevel(int level) {
|
||||
configRepository.delete("level_role_" + level);
|
||||
}
|
||||
|
||||
public String assignRewardRole(Member member, int newLevel) {
|
||||
String roleName = getRoleForLevel(newLevel);
|
||||
if (roleName == null) return null;
|
||||
|
||||
List<Role> roles = member.getGuild().getRolesByName(roleName, true);
|
||||
if (roles.isEmpty()) {
|
||||
LoggerDecorator.warn(
|
||||
logger,
|
||||
"Role '"
|
||||
+ roleName
|
||||
+ "' not found in guild "
|
||||
+ member.getGuild().getId()
|
||||
+ " for level "
|
||||
+ newLevel
|
||||
+ " reward");
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getRoleForLevel(int level) {
|
||||
String configRole = configRepository.get("level_role_" + level);
|
||||
if (configRole != null) return configRole;
|
||||
return DEFAULT_ROLES.get(level);
|
||||
Role role = roles.get(0);
|
||||
if (member.getRoles().contains(role)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setRoleForLevel(int level, String roleName) {
|
||||
configRepository.set("level_role_" + level, roleName);
|
||||
}
|
||||
member
|
||||
.getGuild()
|
||||
.addRoleToMember(member, role)
|
||||
.queue(
|
||||
success ->
|
||||
LoggerDecorator.success(
|
||||
logger,
|
||||
"Assigned role '"
|
||||
+ roleName
|
||||
+ "' to "
|
||||
+ member.getEffectiveName()
|
||||
+ " for level "
|
||||
+ newLevel),
|
||||
error ->
|
||||
LoggerDecorator.error(
|
||||
logger,
|
||||
"Failed to assign role '"
|
||||
+ roleName
|
||||
+ "' to "
|
||||
+ member.getEffectiveName()
|
||||
+ ": "
|
||||
+ error.getMessage(),
|
||||
new Exception(error.getMessage())));
|
||||
|
||||
public void removeRoleForLevel(int level) {
|
||||
configRepository.delete("level_role_" + level);
|
||||
}
|
||||
|
||||
public String assignRewardRole(Member member, int newLevel) {
|
||||
String roleName = getRoleForLevel(newLevel);
|
||||
if (roleName == null) return null;
|
||||
|
||||
List<Role> roles = member.getGuild().getRolesByName(roleName, true);
|
||||
if (roles.isEmpty()) {
|
||||
logger.warn("Role '{}' not found in guild {} for level {} reward",
|
||||
roleName, member.getGuild().getId(), newLevel);
|
||||
return null;
|
||||
}
|
||||
|
||||
Role role = roles.get(0);
|
||||
if (member.getRoles().contains(role)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
member.getGuild().addRoleToMember(member, role).queue(
|
||||
success -> logger.info("Assigned role '{}' to {} for level {}",
|
||||
roleName, member.getEffectiveName(), newLevel),
|
||||
error -> logger.error("Failed to assign role '{}' to {}: {}",
|
||||
roleName, member.getEffectiveName(), error.getMessage())
|
||||
);
|
||||
|
||||
return roleName;
|
||||
}
|
||||
return roleName;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,93 +1,101 @@
|
|||
package creator.loki.feature.level;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import creator.loki.embed.EmbedFactory;
|
||||
import creator.loki.feature.config.repository.ConfigRepository;
|
||||
import creator.loki.feature.level.domain.LevelProgress;
|
||||
import creator.loki.feature.level.service.LevelService;
|
||||
import creator.loki.util.LoggerDecorator;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Уведомления о повышении уровня
|
||||
*/
|
||||
/** Уведомления о повышении уровня */
|
||||
public class LevelUpNotifier {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LevelUpNotifier.class);
|
||||
private final ConfigRepository configRepo;
|
||||
private final LevelService levelService;
|
||||
private final LevelRoleService levelRoleService;
|
||||
private static final Logger logger = LoggerFactory.getLogger(LevelUpNotifier.class);
|
||||
private final ConfigRepository configRepo;
|
||||
private final LevelService levelService;
|
||||
private final LevelRoleService levelRoleService;
|
||||
|
||||
public LevelUpNotifier(ConfigRepository configRepo, LevelService levelService, LevelRoleService levelRoleService) {
|
||||
this.configRepo = configRepo;
|
||||
this.levelService = levelService;
|
||||
this.levelRoleService = levelRoleService;
|
||||
}
|
||||
public LevelUpNotifier(
|
||||
ConfigRepository configRepo, LevelService levelService, LevelRoleService levelRoleService) {
|
||||
this.configRepo = configRepo;
|
||||
this.levelService = levelService;
|
||||
this.levelRoleService = levelRoleService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправить уведомление о повышении уровня
|
||||
*/
|
||||
public void sendLevelUpNotification(Member member, int oldLevel, int newLevel, MessageChannel channel) {
|
||||
LevelProgress progress = levelService.getProgress(member.getId());
|
||||
/** Отправить уведомление о повышении уровня */
|
||||
public void sendLevelUpNotification(
|
||||
Member member, int oldLevel, int newLevel, MessageChannel channel) {
|
||||
LevelProgress progress = levelService.getProgress(member.getId());
|
||||
|
||||
String progressBar = buildProgressBar(progress.getPercentage());
|
||||
String rewardRole = levelRoleService.assignRewardRole(member, newLevel);
|
||||
String progressBar = buildProgressBar(progress.getPercentage());
|
||||
String rewardRole = levelRoleService.assignRewardRole(member, newLevel);
|
||||
|
||||
EmbedFactory embed = EmbedFactory.create()
|
||||
EmbedFactory embed =
|
||||
EmbedFactory.create()
|
||||
.withTitle("🎉 Повышение уровня!")
|
||||
.withDescription(member.getAsMention() + " достиг **" + newLevel + " уровня**!")
|
||||
.addField("Прогресс", progressBar, false)
|
||||
.addField("Всего XP", String.valueOf(progress.getTotalXP()), true)
|
||||
.addField("До следующего", progress.getCurrentLevelXP() + " / " + progress.getXpForNextLevel() + " XP", true)
|
||||
.addField(
|
||||
"До следующего",
|
||||
progress.getCurrentLevelXP() + " / " + progress.getXpForNextLevel() + " XP",
|
||||
true)
|
||||
.withColor(getColorForLevel(newLevel))
|
||||
.withThumbnail(member.getEffectiveAvatarUrl())
|
||||
.withFooter("Продолжай в том же духе!");
|
||||
|
||||
if (rewardRole != null) {
|
||||
embed.addField("🎁 Награда", rewardRole, false);
|
||||
}
|
||||
|
||||
channel.sendMessageEmbeds(embed.build()).queue(
|
||||
success -> logger.info("Отправлено уведомление о level up для {}: {} -> {}",
|
||||
member.getEffectiveName(), oldLevel, newLevel),
|
||||
error -> logger.error("Ошибка отправки уведомления о level up", error)
|
||||
);
|
||||
if (rewardRole != null) {
|
||||
embed.addField("🎁 Награда", rewardRole, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправить уведомление о level up в войсе
|
||||
*/
|
||||
public void sendVoiceLevelUpNotification(Member member, int oldLevel, int newLevel) {
|
||||
String channelId = configRepo.get("levelup.notification_channel_id");
|
||||
if (channelId == null) {
|
||||
// Если канал не настроен, пропускаем
|
||||
return;
|
||||
}
|
||||
channel
|
||||
.sendMessageEmbeds(embed.build())
|
||||
.queue(
|
||||
success ->
|
||||
LoggerDecorator.bot(
|
||||
logger,
|
||||
"Отправлено уведомление о level up для "
|
||||
+ member.getEffectiveName()
|
||||
+ ": "
|
||||
+ oldLevel
|
||||
+ " -> "
|
||||
+ newLevel),
|
||||
error ->
|
||||
LoggerDecorator.error(logger, "Ошибка отправки уведомления о level up", error));
|
||||
}
|
||||
|
||||
TextChannel channel = member.getGuild().getTextChannelById(channelId);
|
||||
if (channel != null) {
|
||||
sendLevelUpNotification(member, oldLevel, newLevel, channel);
|
||||
}
|
||||
/** Отправить уведомление о level up в войсе */
|
||||
public void sendVoiceLevelUpNotification(Member member, int oldLevel, int newLevel) {
|
||||
String channelId = configRepo.get("levelup.notification_channel_id");
|
||||
if (channelId == null) {
|
||||
// Если канал не настроен, пропускаем
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить цвет для уровня
|
||||
*/
|
||||
private Color getColorForLevel(int level) {
|
||||
if (level >= 50) return Color.decode("#FFD700"); // Золотой
|
||||
if (level >= 30) return Color.decode("#E74C3C"); // Красный
|
||||
if (level >= 20) return Color.decode("#9B59B6"); // Фиолетовый
|
||||
if (level >= 10) return Color.decode("#3498DB"); // Синий
|
||||
return Color.decode("#2ECC71"); // Зеленый
|
||||
TextChannel channel = member.getGuild().getTextChannelById(channelId);
|
||||
if (channel != null) {
|
||||
sendLevelUpNotification(member, oldLevel, newLevel, channel);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildProgressBar(double percentage) {
|
||||
int filled = (int) (percentage / 10);
|
||||
filled = Math.min(filled, 10);
|
||||
int empty = 10 - filled;
|
||||
return "▓".repeat(filled) + "░".repeat(empty) + " " + String.format("%.0f%%", percentage);
|
||||
}
|
||||
/** Получить цвет для уровня */
|
||||
private Color getColorForLevel(int level) {
|
||||
if (level >= 50) return Color.decode("#FFD700"); // Золотой
|
||||
if (level >= 30) return Color.decode("#E74C3C"); // Красный
|
||||
if (level >= 20) return Color.decode("#9B59B6"); // Фиолетовый
|
||||
if (level >= 10) return Color.decode("#3498DB"); // Синий
|
||||
return Color.decode("#2ECC71"); // Зеленый
|
||||
}
|
||||
|
||||
private String buildProgressBar(double percentage) {
|
||||
int filled = (int) (percentage / 10);
|
||||
filled = Math.min(filled, 10);
|
||||
int empty = 10 - filled;
|
||||
return "▓".repeat(filled) + "░".repeat(empty) + " " + String.format("%.0f%%", percentage);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,61 @@
|
|||
package creator.loki.feature.level;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import creator.loki.core.EventDispatcher;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.feature.level.service.LevelService;
|
||||
import creator.loki.feature.level.domain.LevelUpResult;
|
||||
import creator.loki.embed.EmbedTemplates;
|
||||
import creator.loki.feature.economy.service.EconomyService;
|
||||
import creator.loki.feature.level.domain.LevelUpResult;
|
||||
import creator.loki.feature.level.service.LevelService;
|
||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
public class MessageListener extends ListenerAdapter {
|
||||
private final EventDispatcher dispatcher;
|
||||
private final LevelService levelService;
|
||||
private final EconomyService economyService;
|
||||
private static final Logger log = LoggerFactory.getLogger(MessageListener.class);
|
||||
private final EventDispatcher dispatcher;
|
||||
private final LevelService levelService;
|
||||
private final EconomyService economyService;
|
||||
|
||||
@Inject
|
||||
public MessageListener(EventDispatcher dispatcher, LevelService levelService, EconomyService economyService) {
|
||||
this.dispatcher = dispatcher;
|
||||
this.levelService = levelService;
|
||||
this.economyService = economyService;
|
||||
}
|
||||
@Inject
|
||||
public MessageListener(
|
||||
EventDispatcher dispatcher, LevelService levelService, EconomyService economyService) {
|
||||
this.dispatcher = dispatcher;
|
||||
this.levelService = levelService;
|
||||
this.economyService = economyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(MessageReceivedEvent event) {
|
||||
// Игнорируем ботов
|
||||
if (!event.getAuthor().isBot()) {
|
||||
String userId = event.getAuthor().getId();
|
||||
|
||||
// Начисляем XP и проверяем повышение уровня
|
||||
LevelUpResult levelUp = levelService.addMessageXP(userId);
|
||||
if (levelUp != null) {
|
||||
// Отправляем сообщение о повышении уровня
|
||||
event.getChannel().sendMessageEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"🎉 Повышение уровня!",
|
||||
event.getAuthor().getAsMention() + " достиг **" + levelUp.getNewLevel() + " уровня**!\n" +
|
||||
"Всего XP: **" + levelUp.getTotalXP() + "**"
|
||||
)
|
||||
).queue();
|
||||
}
|
||||
|
||||
int lcAmount = ThreadLocalRandom.current().nextInt(1, 4);
|
||||
economyService.earnCash(userId, lcAmount);
|
||||
@Override
|
||||
public void onMessageReceived(MessageReceivedEvent event) {
|
||||
try {
|
||||
if (!event.getAuthor().isBot()) {
|
||||
String userId = event.getAuthor().getId();
|
||||
LevelUpResult levelUp = levelService.addMessageXP(userId);
|
||||
if (levelUp != null) {
|
||||
event
|
||||
.getChannel()
|
||||
.sendMessageEmbeds(
|
||||
EmbedTemplates.success(
|
||||
"🎉 Повышение уровня!",
|
||||
event.getAuthor().getAsMention()
|
||||
+ " достиг **"
|
||||
+ levelUp.getNewLevel()
|
||||
+ " уровня**! Всего XP: **"
|
||||
+ levelUp.getTotalXP()
|
||||
+ "**"))
|
||||
.queue();
|
||||
}
|
||||
|
||||
dispatcher.handleMessage(event);
|
||||
int lcAmount = ThreadLocalRandom.current().nextInt(1, 4);
|
||||
economyService.earnCash(userId, lcAmount);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("XP/economy error (non-fatal): {}", e.getMessage());
|
||||
}
|
||||
|
||||
dispatcher.handleMessage(event);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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