feat: ai page parser, message scanner, lang filter, /clear, prompt rewrite
This commit is contained in:
parent
a111bcd674
commit
cc046d7ee6
65 changed files with 1631 additions and 745 deletions
|
|
@ -10,4 +10,5 @@ out
|
||||||
.vscode
|
.vscode
|
||||||
*.md
|
*.md
|
||||||
logs
|
logs
|
||||||
data
|
data/dossiers
|
||||||
|
data/*.db
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ DB_PATH=data/bot.db
|
||||||
# DeepSeek AI API (для roast)
|
# DeepSeek AI API (для roast)
|
||||||
DEEPSEEK_API_KEY=your_deepseek_key
|
DEEPSEEK_API_KEY=your_deepseek_key
|
||||||
|
|
||||||
|
|
||||||
# Lavalink
|
# Lavalink
|
||||||
LAVALINK_HOST=127.0.0.1
|
LAVALINK_HOST=127.0.0.1
|
||||||
LAVALINK_PORT=2333
|
LAVALINK_PORT=2333
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,13 @@ WORKDIR /build
|
||||||
COPY gradle gradle
|
COPY gradle gradle
|
||||||
COPY gradlew settings.gradle build.gradle ./
|
COPY gradlew settings.gradle build.gradle ./
|
||||||
COPY src src
|
COPY src src
|
||||||
RUN ./gradlew jar --no-daemon --stacktrace
|
RUN ./gradlew jar --stacktrace
|
||||||
|
|
||||||
FROM eclipse-temurin:21-jre-alpine
|
FROM eclipse-temurin:21-jre-alpine
|
||||||
RUN addgroup -S bot && adduser -S bot -G bot
|
RUN addgroup -S bot && adduser -S bot -G bot
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /build/build/libs/discord-bot-1.0.0.jar app.jar
|
COPY --from=builder /build/build/libs/discord-bot-1.0.0.jar app.jar
|
||||||
|
COPY data/prompts /app/prompts
|
||||||
RUN mkdir -p /app/data /app/logs && chown -R bot:bot /app
|
RUN mkdir -p /app/data /app/logs && chown -R bot:bot /app
|
||||||
USER bot
|
USER bot
|
||||||
VOLUME /app/data
|
VOLUME /app/data
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ dependencies {
|
||||||
implementation libs.hikaricp
|
implementation libs.hikaricp
|
||||||
implementation libs.okhttp
|
implementation libs.okhttp
|
||||||
implementation libs.logging.interceptor
|
implementation libs.logging.interceptor
|
||||||
|
implementation libs.jsoup
|
||||||
|
|
||||||
testImplementation libs.junit.jupiter
|
testImplementation libs.junit.jupiter
|
||||||
testImplementation libs.mockito.core
|
testImplementation libs.mockito.core
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ services:
|
||||||
SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET}
|
SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET}
|
||||||
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY}
|
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY}
|
||||||
AI_CHANNEL_ID: ${AI_CHANNEL_ID}
|
AI_CHANNEL_ID: ${AI_CHANNEL_ID}
|
||||||
GIPHY_API_KEY: ${GIPHY_API_KEY}
|
|
||||||
AI_CONTEXT_MSGS: ${AI_CONTEXT_MSGS:-10}
|
AI_CONTEXT_MSGS: ${AI_CONTEXT_MSGS:-10}
|
||||||
AI_MSG_LENGTH: ${AI_MSG_LENGTH:-300}
|
AI_MSG_LENGTH: ${AI_MSG_LENGTH:-300}
|
||||||
AI_CONTEXT_CHARS: ${AI_CONTEXT_CHARS:-3000}
|
AI_CONTEXT_CHARS: ${AI_CONTEXT_CHARS:-3000}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,12 @@
|
||||||
jdaVersion=5.0.0-beta.20
|
jdaVersion=5.0.0-beta.20
|
||||||
dotenvVersion=3.0.0
|
dotenvVersion=3.0.0
|
||||||
slf4jVersion=2.0.12
|
slf4jVersion=2.0.12
|
||||||
logbackVersion=1.5.3
|
logbackVersion=5.0.3
|
||||||
|
|
||||||
# Gradle
|
# Gradle JVM
|
||||||
org.gradle.jvmargs=-Xmx2048m
|
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8
|
||||||
org.gradle.parallel=true
|
|
||||||
org.gradle.caching=true
|
|
||||||
|
|
||||||
# Speed optimizations
|
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.configureondemand=true
|
org.gradle.configureondemand=true
|
||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8
|
org.gradle.workers.max=8
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ junitPlatformLauncher = "1.12.1"
|
||||||
checkstyle = "10.23.0"
|
checkstyle = "10.23.0"
|
||||||
spotbugs = "4.9.3"
|
spotbugs = "4.9.3"
|
||||||
spotbugsPlugin = "6.1.7"
|
spotbugsPlugin = "6.1.7"
|
||||||
|
jsoup = "1.19.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
jda = { module = "net.dv8tion:JDA", version.ref = "jda" }
|
jda = { module = "net.dv8tion:JDA", version.ref = "jda" }
|
||||||
|
|
@ -39,6 +40,7 @@ junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher
|
||||||
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
|
checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" }
|
||||||
spotbugs = { module = "com.github.spotbugs:spotbugs", version.ref = "spotbugs" }
|
spotbugs = { module = "com.github.spotbugs:spotbugs", version.ref = "spotbugs" }
|
||||||
spotbugs-annotations = { module = "com.github.spotbugs:spotbugs-annotations", version.ref = "spotbugs" }
|
spotbugs-annotations = { module = "com.github.spotbugs:spotbugs-annotations", version.ref = "spotbugs" }
|
||||||
|
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
checkstyle = { id = "checkstyle", version = "" }
|
checkstyle = { id = "checkstyle", version = "" }
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ public class BotConfig {
|
||||||
private final String superAdminId;
|
private final String superAdminId;
|
||||||
private final String dbPath;
|
private final String dbPath;
|
||||||
private final String deepseekApiKey;
|
private final String deepseekApiKey;
|
||||||
private final String giphyApiKey;
|
|
||||||
private final String aiChannelId;
|
private final String aiChannelId;
|
||||||
private final int aiContextMsgs;
|
private final int aiContextMsgs;
|
||||||
private final int aiMsgLength;
|
private final int aiMsgLength;
|
||||||
|
|
@ -29,7 +28,6 @@ public class BotConfig {
|
||||||
String superAdminId,
|
String superAdminId,
|
||||||
String dbPath,
|
String dbPath,
|
||||||
String deepseekApiKey,
|
String deepseekApiKey,
|
||||||
String giphyApiKey,
|
|
||||||
String aiChannelId,
|
String aiChannelId,
|
||||||
int aiContextMsgs,
|
int aiContextMsgs,
|
||||||
int aiMsgLength,
|
int aiMsgLength,
|
||||||
|
|
@ -46,7 +44,6 @@ public class BotConfig {
|
||||||
this.superAdminId = superAdminId;
|
this.superAdminId = superAdminId;
|
||||||
this.dbPath = dbPath;
|
this.dbPath = dbPath;
|
||||||
this.deepseekApiKey = deepseekApiKey;
|
this.deepseekApiKey = deepseekApiKey;
|
||||||
this.giphyApiKey = giphyApiKey;
|
|
||||||
this.aiChannelId = aiChannelId;
|
this.aiChannelId = aiChannelId;
|
||||||
this.aiContextMsgs = aiContextMsgs;
|
this.aiContextMsgs = aiContextMsgs;
|
||||||
this.aiMsgLength = aiMsgLength;
|
this.aiMsgLength = aiMsgLength;
|
||||||
|
|
@ -96,10 +93,6 @@ public class BotConfig {
|
||||||
return deepseekApiKey;
|
return deepseekApiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGiphyApiKey() {
|
|
||||||
return giphyApiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAiChannelId() {
|
public String getAiChannelId() {
|
||||||
return aiChannelId;
|
return aiChannelId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,6 @@ public final class ConfigLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
String deepseekApiKey = dotenv.get("DEEPSEEK_API_KEY");
|
String deepseekApiKey = dotenv.get("DEEPSEEK_API_KEY");
|
||||||
String giphyApiKey = dotenv.get("GIPHY_API_KEY");
|
|
||||||
String aiChannelId = dotenv.get("AI_CHANNEL_ID");
|
String aiChannelId = dotenv.get("AI_CHANNEL_ID");
|
||||||
|
|
||||||
int aiContextMsgs = Integer.parseInt(getEnv(dotenv, "AI_CONTEXT_MSGS", "10"));
|
int aiContextMsgs = Integer.parseInt(getEnv(dotenv, "AI_CONTEXT_MSGS", "10"));
|
||||||
|
|
@ -95,7 +94,6 @@ public final class ConfigLoader {
|
||||||
superAdminId,
|
superAdminId,
|
||||||
dbPath,
|
dbPath,
|
||||||
deepseekApiKey,
|
deepseekApiKey,
|
||||||
giphyApiKey,
|
|
||||||
aiChannelId,
|
aiChannelId,
|
||||||
aiContextMsgs,
|
aiContextMsgs,
|
||||||
aiMsgLength,
|
aiMsgLength,
|
||||||
|
|
|
||||||
|
|
@ -68,10 +68,18 @@ public class BotInitializer {
|
||||||
LoggerDecorator.startup(logger, "Инициализация main бота...");
|
LoggerDecorator.startup(logger, "Инициализация main бота...");
|
||||||
registrar.registerAll();
|
registrar.registerAll();
|
||||||
JDA mainJda = buildMainJDA();
|
JDA mainJda = buildMainJDA();
|
||||||
mainJda.upsertCommand(
|
mainJda
|
||||||
Commands.slash("agr", "Наслать AI на пользователя (только для админов)")
|
.upsertCommand(
|
||||||
.addOption(OptionType.USER, "user", "Цель (по умолчанию Andrert_)", false)
|
Commands.slash("agr", "Наслать AI на пользователя (только для админов)")
|
||||||
).queue();
|
.addOption(OptionType.USER, "user", "Цель (по умолчанию Andrert_)", false))
|
||||||
|
.queue();
|
||||||
|
mainJda
|
||||||
|
.upsertCommand(
|
||||||
|
Commands.slash("clear", "Удалить сообщения")
|
||||||
|
.addOption(OptionType.INTEGER, "amount", "Количество (1-100)", true)
|
||||||
|
.addOption(
|
||||||
|
OptionType.USER, "user", "Оставить только сообщения пользователя", false))
|
||||||
|
.queue();
|
||||||
muteScheduler.restorePending(mainJda);
|
muteScheduler.restorePending(mainJda);
|
||||||
mainJda.getGuilds().forEach(g -> memberCounterListener.startScheduler(g));
|
mainJda.getGuilds().forEach(g -> memberCounterListener.startScheduler(g));
|
||||||
healthHttpServer.start();
|
healthHttpServer.start();
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,17 @@ import creator.loki.bootstrap.ReadyListener;
|
||||||
import creator.loki.feature.counter.service.MemberCounterListener;
|
import creator.loki.feature.counter.service.MemberCounterListener;
|
||||||
import creator.loki.feature.economy.FishMessageListener;
|
import creator.loki.feature.economy.FishMessageListener;
|
||||||
import creator.loki.feature.help.HelpMenuHandler;
|
import creator.loki.feature.help.HelpMenuHandler;
|
||||||
import creator.loki.feature.roast.AgrSlashCommands;
|
|
||||||
import creator.loki.feature.roast.AndrertListener;
|
|
||||||
import creator.loki.feature.leaderboard.LeaderboardButtonListener;
|
import creator.loki.feature.leaderboard.LeaderboardButtonListener;
|
||||||
import creator.loki.feature.level.listener.MessageListener;
|
import creator.loki.feature.level.listener.MessageListener;
|
||||||
import creator.loki.feature.level.listener.ResetLevelsButtonHandler;
|
import creator.loki.feature.level.listener.ResetLevelsButtonHandler;
|
||||||
import creator.loki.feature.level.service.VoiceTracker;
|
import creator.loki.feature.level.service.VoiceTracker;
|
||||||
|
import creator.loki.feature.moderation.commands.utils.ClearSlashCommands;
|
||||||
import creator.loki.feature.moderation.log.ModerationLogger;
|
import creator.loki.feature.moderation.log.ModerationLogger;
|
||||||
import creator.loki.feature.music.MusicSlashCommands;
|
import creator.loki.feature.music.MusicSlashCommands;
|
||||||
|
import creator.loki.feature.roast.AndrertListener;
|
||||||
import creator.loki.feature.voice.handler.VoiceButtonHandler;
|
import creator.loki.feature.voice.handler.VoiceButtonHandler;
|
||||||
import creator.loki.feature.voice.listener.VoiceChannelListener;
|
|
||||||
import creator.loki.feature.voice.handler.VoiceModalHandler;
|
import creator.loki.feature.voice.handler.VoiceModalHandler;
|
||||||
|
import creator.loki.feature.voice.listener.VoiceChannelListener;
|
||||||
import creator.loki.feature.voice.listener.VoiceSyncListener;
|
import creator.loki.feature.voice.listener.VoiceSyncListener;
|
||||||
import creator.loki.feature.welcome.WelcomeListener;
|
import creator.loki.feature.welcome.WelcomeListener;
|
||||||
|
|
||||||
|
|
@ -42,7 +42,8 @@ public class ListenerAggregator {
|
||||||
MessageCache messageCache,
|
MessageCache messageCache,
|
||||||
MusicSlashCommands musicSlashCommands,
|
MusicSlashCommands musicSlashCommands,
|
||||||
FishMessageListener fishMessageListener,
|
FishMessageListener fishMessageListener,
|
||||||
AndrertListener andrertListener) {
|
AndrertListener andrertListener,
|
||||||
|
ClearSlashCommands clearSlashCommands) {
|
||||||
this.listeners =
|
this.listeners =
|
||||||
new Object[] {
|
new Object[] {
|
||||||
readyListener,
|
readyListener,
|
||||||
|
|
@ -61,7 +62,8 @@ public class ListenerAggregator {
|
||||||
messageCache,
|
messageCache,
|
||||||
musicSlashCommands,
|
musicSlashCommands,
|
||||||
fishMessageListener,
|
fishMessageListener,
|
||||||
andrertListener
|
andrertListener,
|
||||||
|
clearSlashCommands
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ public class DatabaseMigration {
|
||||||
LoggerDecorator.success(logger, "Миграция завершена успешно");
|
LoggerDecorator.success(logger, "Миграция завершена успешно");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
private Map<String, Object> parseJson(String path) {
|
private Map<String, Object> parseJson(String path) {
|
||||||
try (var reader = new InputStreamReader(new FileInputStream(path), UTF_8)) {
|
try (var reader = new InputStreamReader(new FileInputStream(path), UTF_8)) {
|
||||||
var type = new TypeToken<Map<String, Object>>() {}.getType();
|
var type = new TypeToken<Map<String, Object>>() {}.getType();
|
||||||
|
|
@ -58,6 +59,7 @@ public class DatabaseMigration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
private void migrateUserStats(Map<String, Object> data) {
|
private void migrateUserStats(Map<String, Object> data) {
|
||||||
var userStats = (Map<String, Object>) data.get("userStats");
|
var userStats = (Map<String, Object>) data.get("userStats");
|
||||||
if (userStats == null) return;
|
if (userStats == null) return;
|
||||||
|
|
@ -82,6 +84,7 @@ public class DatabaseMigration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
private void migrateReputation(Map<String, Object> data) {
|
private void migrateReputation(Map<String, Object> data) {
|
||||||
var reputation = (Map<String, Object>) data.get("reputation");
|
var reputation = (Map<String, Object>) data.get("reputation");
|
||||||
if (reputation == null) return;
|
if (reputation == null) return;
|
||||||
|
|
@ -99,6 +102,7 @@ public class DatabaseMigration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
private void migrateShipPairs(Map<String, Object> data) {
|
private void migrateShipPairs(Map<String, Object> data) {
|
||||||
var shipPairs = (Map<String, Object>) data.get("shipPairs");
|
var shipPairs = (Map<String, Object>) data.get("shipPairs");
|
||||||
if (shipPairs == null) return;
|
if (shipPairs == null) return;
|
||||||
|
|
@ -121,6 +125,7 @@ public class DatabaseMigration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
private void migrateCustomRoles(Map<String, Object> data) {
|
private void migrateCustomRoles(Map<String, Object> data) {
|
||||||
var customRoles = (Map<String, Object>) data.get("custom_roles");
|
var customRoles = (Map<String, Object>) data.get("custom_roles");
|
||||||
if (customRoles == null) return;
|
if (customRoles == null) return;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ import creator.loki.feature.reputation.repository.ReputationRepository;
|
||||||
import creator.loki.feature.ship.repository.ShipPairRepository;
|
import creator.loki.feature.ship.repository.ShipPairRepository;
|
||||||
|
|
||||||
public class DatabaseModule extends AbstractModule {
|
public class DatabaseModule extends AbstractModule {
|
||||||
@Provides @Singleton
|
@Provides
|
||||||
|
@Singleton
|
||||||
Database provideDatabase(BotConfig config) {
|
Database provideDatabase(BotConfig config) {
|
||||||
Database database = new Database(config.getDbPath());
|
Database database = new Database(config.getDbPath());
|
||||||
database.connect();
|
database.connect();
|
||||||
|
|
@ -22,32 +23,38 @@ public class DatabaseModule extends AbstractModule {
|
||||||
return database;
|
return database;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides @Singleton
|
@Provides
|
||||||
|
@Singleton
|
||||||
ConfigRepository provideConfigRepository(Database database) {
|
ConfigRepository provideConfigRepository(Database database) {
|
||||||
return new ConfigRepository(database);
|
return new ConfigRepository(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides @Singleton
|
@Provides
|
||||||
|
@Singleton
|
||||||
GuildConfigRepository provideGuildConfigRepository(ConfigRepository cr) {
|
GuildConfigRepository provideGuildConfigRepository(ConfigRepository cr) {
|
||||||
return new PrefixedGuildConfigRepository(cr);
|
return new PrefixedGuildConfigRepository(cr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides @Singleton
|
@Provides
|
||||||
|
@Singleton
|
||||||
RatingsRepository provideRatingsRepository(Database database) {
|
RatingsRepository provideRatingsRepository(Database database) {
|
||||||
return new RatingsRepository(database);
|
return new RatingsRepository(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides @Singleton
|
@Provides
|
||||||
|
@Singleton
|
||||||
ReputationRepository provideReputationRepository(Database database) {
|
ReputationRepository provideReputationRepository(Database database) {
|
||||||
return new ReputationRepository(database);
|
return new ReputationRepository(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides @Singleton
|
@Provides
|
||||||
|
@Singleton
|
||||||
ShipPairRepository provideShipPairRepository(Database database) {
|
ShipPairRepository provideShipPairRepository(Database database) {
|
||||||
return new ShipPairRepository(database);
|
return new ShipPairRepository(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides @Singleton
|
@Provides
|
||||||
|
@Singleton
|
||||||
QueueRepository provideQueueRepository(Database database) {
|
QueueRepository provideQueueRepository(Database database) {
|
||||||
return new QueueRepository(database);
|
return new QueueRepository(database);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import creator.loki.feature.counter.CounterModule;
|
||||||
import creator.loki.feature.economy.EconomyCommandRegistrar;
|
import creator.loki.feature.economy.EconomyCommandRegistrar;
|
||||||
import creator.loki.feature.economy.EconomyModule;
|
import creator.loki.feature.economy.EconomyModule;
|
||||||
import creator.loki.feature.entertainment.EntertainmentCommandRegistrar;
|
import creator.loki.feature.entertainment.EntertainmentCommandRegistrar;
|
||||||
import creator.loki.feature.level.commands.LevelCommandRegistrar;
|
|
||||||
import creator.loki.feature.level.LevelModule;
|
import creator.loki.feature.level.LevelModule;
|
||||||
|
import creator.loki.feature.level.commands.LevelCommandRegistrar;
|
||||||
import creator.loki.feature.metrics.HealthHttpServer;
|
import creator.loki.feature.metrics.HealthHttpServer;
|
||||||
import creator.loki.feature.metrics.MetricsCollector;
|
import creator.loki.feature.metrics.MetricsCollector;
|
||||||
import creator.loki.feature.metrics.MetricsModule;
|
import creator.loki.feature.metrics.MetricsModule;
|
||||||
|
|
@ -76,12 +76,19 @@ public class BotModule extends AbstractModule {
|
||||||
commandBinder.addBinding().to(RadioCommandRegistrar.class);
|
commandBinder.addBinding().to(RadioCommandRegistrar.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides @Singleton
|
@Provides
|
||||||
BotConfig provideBotConfig() { return ConfigLoader.load(); }
|
@Singleton
|
||||||
|
BotConfig provideBotConfig() {
|
||||||
|
return ConfigLoader.load();
|
||||||
|
}
|
||||||
|
|
||||||
@Provides @Singleton
|
@Provides
|
||||||
|
@Singleton
|
||||||
EventDispatcher provideEventDispatcher(
|
EventDispatcher provideEventDispatcher(
|
||||||
BotConfig config, CommandRegistry registry, RateLimiter rateLimiter, MetricsCollector metrics) {
|
BotConfig config,
|
||||||
|
CommandRegistry registry,
|
||||||
|
RateLimiter rateLimiter,
|
||||||
|
MetricsCollector metrics) {
|
||||||
return new EventDispatcher(config, registry, rateLimiter, metrics);
|
return new EventDispatcher(config, registry, rateLimiter, metrics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@ import creator.loki.config.BotConfig;
|
||||||
import creator.loki.core.registry.CommandRegistrar;
|
import creator.loki.core.registry.CommandRegistrar;
|
||||||
import creator.loki.core.registry.CommandRegistry;
|
import creator.loki.core.registry.CommandRegistry;
|
||||||
import creator.loki.core.security.CooldownManager;
|
import creator.loki.core.security.CooldownManager;
|
||||||
|
import creator.loki.feature.entertainment.commands.GlazbobaCommand;
|
||||||
|
import creator.loki.feature.entertainment.commands.MemeCommand;
|
||||||
import creator.loki.feature.entertainment.commands.random.CoinCommand;
|
import creator.loki.feature.entertainment.commands.random.CoinCommand;
|
||||||
import creator.loki.feature.entertainment.commands.random.DiceCommand;
|
import creator.loki.feature.entertainment.commands.random.DiceCommand;
|
||||||
import creator.loki.feature.entertainment.commands.random.EightBallCommand;
|
import creator.loki.feature.entertainment.commands.random.EightBallCommand;
|
||||||
import creator.loki.feature.entertainment.commands.GlazbobaCommand;
|
|
||||||
import creator.loki.feature.entertainment.commands.MemeCommand;
|
|
||||||
import creator.loki.feature.entertainment.commands.random.RandomChoiceCommand;
|
import creator.loki.feature.entertainment.commands.random.RandomChoiceCommand;
|
||||||
|
import creator.loki.feature.entertainment.service.RatingService;
|
||||||
import creator.loki.feature.rating.commands.AuraCommand;
|
import creator.loki.feature.rating.commands.AuraCommand;
|
||||||
import creator.loki.feature.rating.commands.BoykisserCommand;
|
import creator.loki.feature.rating.commands.BoykisserCommand;
|
||||||
import creator.loki.feature.rating.commands.FatCommand;
|
import creator.loki.feature.rating.commands.FatCommand;
|
||||||
|
|
@ -25,7 +26,6 @@ import creator.loki.feature.rating.commands.RateCommand;
|
||||||
import creator.loki.feature.rating.commands.RizzCommand;
|
import creator.loki.feature.rating.commands.RizzCommand;
|
||||||
import creator.loki.feature.rating.commands.SimpCommand;
|
import creator.loki.feature.rating.commands.SimpCommand;
|
||||||
import creator.loki.feature.rating.commands.VibeCommand;
|
import creator.loki.feature.rating.commands.VibeCommand;
|
||||||
import creator.loki.feature.entertainment.service.RatingService;
|
|
||||||
import creator.loki.feature.ship.ShipCommand;
|
import creator.loki.feature.ship.ShipCommand;
|
||||||
import creator.loki.feature.ship.repository.ShipPairRepository;
|
import creator.loki.feature.ship.repository.ShipPairRepository;
|
||||||
import creator.loki.feature.social.commands.ComplimentCommand;
|
import creator.loki.feature.social.commands.ComplimentCommand;
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,10 @@ public class GlazbobaCommand implements Command {
|
||||||
.addField("🎮 Активность", activities, true)
|
.addField("🎮 Активность", activities, true)
|
||||||
.addField("💎 Бустер", isBoosting ? "Да" : "Нет", true)
|
.addField("💎 Бустер", isBoosting ? "Да" : "Нет", true)
|
||||||
.addField("🎭 Роли", roles, false)
|
.addField("🎭 Роли", roles, false)
|
||||||
.withColor(target.getColor() != null ? target.getColor() : Color.BLUE)
|
.withColor(
|
||||||
|
target.getColors().getPrimary() != null
|
||||||
|
? target.getColors().getPrimary()
|
||||||
|
: Color.BLUE)
|
||||||
.withFooter("Запросил: " + context.getAuthor().getUser().getName())
|
.withFooter("Запросил: " + context.getAuthor().getUser().getName())
|
||||||
.build())
|
.build())
|
||||||
.queue();
|
.queue();
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import java.util.function.Predicate;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/** Разбор хвоста сообщения и строки вариантов для {@link RandomChoiceCommand}. */
|
/** Разбор хвоста сообщения и строки вариантов для {@link RandomChoiceCommand}. */
|
||||||
final public class ChoiceOptionParser {
|
public 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+");
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ final public class ChoiceOptionParser {
|
||||||
return rest.substring(sp).trim();
|
return rest.substring(sp).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> parseOptions(String tail) {
|
public static List<String> parseOptions(String tail) {
|
||||||
String t = tail.strip();
|
String t = tail.strip();
|
||||||
for (SplitRule rule : RULES) {
|
for (SplitRule rule : RULES) {
|
||||||
if (rule.when.test(t)) {
|
if (rule.when.test(t)) {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import com.google.inject.Singleton;
|
||||||
import creator.loki.database.Database;
|
import creator.loki.database.Database;
|
||||||
import creator.loki.feature.config.repository.ConfigRepository;
|
import creator.loki.feature.config.repository.ConfigRepository;
|
||||||
import creator.loki.feature.config.repository.GuildConfigRepository;
|
import creator.loki.feature.config.repository.GuildConfigRepository;
|
||||||
|
import creator.loki.feature.level.commands.ResetLevelsCommand;
|
||||||
import creator.loki.feature.level.domain.XpFormula;
|
import creator.loki.feature.level.domain.XpFormula;
|
||||||
|
import creator.loki.feature.level.listener.ResetLevelsButtonHandler;
|
||||||
import creator.loki.feature.level.repository.SqlUserStatsRepository;
|
import creator.loki.feature.level.repository.SqlUserStatsRepository;
|
||||||
import creator.loki.feature.level.repository.UserStatsRepository;
|
import creator.loki.feature.level.repository.UserStatsRepository;
|
||||||
import creator.loki.feature.level.commands.ResetLevelsCommand;
|
|
||||||
import creator.loki.feature.level.listener.ResetLevelsButtonHandler;
|
|
||||||
import creator.loki.feature.level.service.LeaderboardService;
|
import creator.loki.feature.level.service.LeaderboardService;
|
||||||
import creator.loki.feature.level.service.LevelRoleService;
|
import creator.loki.feature.level.service.LevelRoleService;
|
||||||
import creator.loki.feature.level.service.LevelService;
|
import creator.loki.feature.level.service.LevelService;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import creator.loki.core.registry.CommandRegistrar;
|
||||||
import creator.loki.core.registry.CommandRegistry;
|
import creator.loki.core.registry.CommandRegistry;
|
||||||
import creator.loki.feature.config.repository.ConfigRepository;
|
import creator.loki.feature.config.repository.ConfigRepository;
|
||||||
import creator.loki.feature.leaderboard.LeaderboardCommand;
|
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.LeaderboardService;
|
||||||
import creator.loki.feature.level.service.LevelRoleService;
|
import creator.loki.feature.level.service.LevelRoleService;
|
||||||
import creator.loki.feature.profile.command.ProfileCommand;
|
import creator.loki.feature.profile.command.ProfileCommand;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import java.awt.Color;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import creator.loki.embed.EmbedFactory;
|
import creator.loki.embed.EmbedFactory;
|
||||||
import creator.loki.util.LoggerDecorator;
|
|
||||||
import creator.loki.feature.level.commands.ResetLevelsCommand;
|
import creator.loki.feature.level.commands.ResetLevelsCommand;
|
||||||
|
import creator.loki.util.LoggerDecorator;
|
||||||
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import java.awt.Color;
|
||||||
import creator.loki.embed.EmbedFactory;
|
import creator.loki.embed.EmbedFactory;
|
||||||
import creator.loki.feature.config.repository.ConfigRepository;
|
import creator.loki.feature.config.repository.ConfigRepository;
|
||||||
import creator.loki.feature.level.domain.LevelProgress;
|
import creator.loki.feature.level.domain.LevelProgress;
|
||||||
import creator.loki.feature.level.service.LevelService;
|
|
||||||
import creator.loki.util.LoggerDecorator;
|
import creator.loki.util.LoggerDecorator;
|
||||||
import net.dv8tion.jda.api.entities.Member;
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import creator.loki.feature.economy.service.EconomyService;
|
import creator.loki.feature.economy.service.EconomyService;
|
||||||
import creator.loki.feature.level.domain.LevelUpResult;
|
import creator.loki.feature.level.domain.LevelUpResult;
|
||||||
import creator.loki.feature.level.service.LevelService;
|
|
||||||
import net.dv8tion.jda.api.entities.Member;
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
|
import net.dv8tion.jda.api.events.guild.voice.GuildVoiceUpdateEvent;
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@ import creator.loki.feature.moderation.repository.ActiveMuteRepository;
|
||||||
import creator.loki.feature.moderation.repository.ModerationCaseRepository;
|
import creator.loki.feature.moderation.repository.ModerationCaseRepository;
|
||||||
import creator.loki.feature.moderation.repository.WarningRepository;
|
import creator.loki.feature.moderation.repository.WarningRepository;
|
||||||
import creator.loki.feature.role.CustomRoleManager;
|
import creator.loki.feature.role.CustomRoleManager;
|
||||||
|
import creator.loki.feature.role.command.CreateRoleCommand;
|
||||||
import creator.loki.feature.role.command.DeleteRoleCommand;
|
import creator.loki.feature.role.command.DeleteRoleCommand;
|
||||||
import creator.loki.feature.role.command.EditRoleCommand;
|
import creator.loki.feature.role.command.EditRoleCommand;
|
||||||
import creator.loki.feature.role.command.MyRolesCommand;
|
import creator.loki.feature.role.command.MyRolesCommand;
|
||||||
import creator.loki.feature.role.command.CreateRoleCommand;
|
|
||||||
|
|
||||||
public class ModerationCommandRegistrar implements CommandRegistrar {
|
public class ModerationCommandRegistrar implements CommandRegistrar {
|
||||||
private final CustomRoleManager roleManager;
|
private final CustomRoleManager roleManager;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
package creator.loki.feature.moderation.commands.utils;
|
package creator.loki.feature.moderation.commands.utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import creator.loki.command.Command;
|
import creator.loki.command.Command;
|
||||||
import creator.loki.command.CommandContext;
|
import creator.loki.command.CommandContext;
|
||||||
import creator.loki.embed.EmbedTemplates;
|
import creator.loki.embed.EmbedTemplates;
|
||||||
import net.dv8tion.jda.api.Permission;
|
import net.dv8tion.jda.api.Permission;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
|
||||||
public class ClearCommand implements Command {
|
public class ClearCommand implements Command {
|
||||||
|
|
@ -16,12 +20,12 @@ public class ClearCommand implements Command {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return "Удаляет указанное количество сообщений";
|
return "Удаляет сообщения. Можно фильтровать по пользователю";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUsage() {
|
public String getUsage() {
|
||||||
return "clear <количество>";
|
return "clear <количество> [@user]";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -48,20 +52,44 @@ public class ClearCommand implements Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int clamped = Math.clamp(amount, 1, 100);
|
int clamped = Math.clamp(amount, 1, 100);
|
||||||
|
|
||||||
|
Member target = null;
|
||||||
|
if (ctx.getArgs().size() > 1) {
|
||||||
|
String arg = ctx.getArgs().get(1);
|
||||||
|
String targetId = arg.replaceAll("<@!?(\\d+)>", "$1");
|
||||||
|
if (targetId.matches("\\d+")) {
|
||||||
|
target = ctx.getGuild().getMemberById(targetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!(ctx.getChannel() instanceof TextChannel channel)) return;
|
if (!(ctx.getChannel() instanceof TextChannel channel)) return;
|
||||||
|
final Member finalTarget = target;
|
||||||
channel
|
channel
|
||||||
.getHistory()
|
.getHistory()
|
||||||
.retrievePast(clamped + 1)
|
.retrievePast(clamped + 1)
|
||||||
.queue(
|
.queue(
|
||||||
messages -> {
|
messages -> {
|
||||||
|
List<Message> toDelete;
|
||||||
|
if (finalTarget != null) {
|
||||||
|
toDelete =
|
||||||
|
messages.stream()
|
||||||
|
.filter(m -> m.getAuthor().getId().equals(finalTarget.getId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
toDelete = messages;
|
||||||
|
}
|
||||||
|
if (toDelete.isEmpty()) {
|
||||||
|
err(ctx, "Нет сообщений от этого пользователя для удаления");
|
||||||
|
return;
|
||||||
|
}
|
||||||
channel
|
channel
|
||||||
.deleteMessages(messages)
|
.deleteMessages(toDelete)
|
||||||
.queue(
|
.queue(
|
||||||
success ->
|
success ->
|
||||||
ctx.getChannel()
|
ctx.getChannel()
|
||||||
.sendMessageEmbeds(
|
.sendMessageEmbeds(
|
||||||
EmbedTemplates.success(
|
EmbedTemplates.success(
|
||||||
"Сообщения удалены", "Удалено сообщений: " + messages.size()))
|
"Сообщения удалены", "Удалено сообщений: " + toDelete.size()))
|
||||||
.queue(msg -> msg.delete().queueAfter(5, TimeUnit.SECONDS)),
|
.queue(msg -> msg.delete().queueAfter(5, TimeUnit.SECONDS)),
|
||||||
error -> err(ctx, "Не удалось удалить сообщения: " + error.getMessage()));
|
error -> err(ctx, "Не удалось удалить сообщения: " + error.getMessage()));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package creator.loki.feature.moderation.commands.utils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import creator.loki.embed.EmbedTemplates;
|
||||||
|
import net.dv8tion.jda.api.Permission;
|
||||||
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
import net.dv8tion.jda.api.events.guild.GuildReadyEvent;
|
||||||
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||||
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.OptionType;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ClearSlashCommands extends ListenerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGuildReady(@NotNull GuildReadyEvent event) {
|
||||||
|
event
|
||||||
|
.getJDA()
|
||||||
|
.updateCommands()
|
||||||
|
.addCommands(
|
||||||
|
Commands.slash("clear", "Удалить сообщения")
|
||||||
|
.addOption(OptionType.INTEGER, "amount", "Количество сообщений (1-100)", true)
|
||||||
|
.addOption(
|
||||||
|
OptionType.USER, "user", "Оставить только сообщения пользователя", false))
|
||||||
|
.queue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent event) {
|
||||||
|
if (!event.getName().equals("clear")) return;
|
||||||
|
|
||||||
|
var member = event.getMember();
|
||||||
|
if (member == null || !member.hasPermission(Permission.MESSAGE_MANAGE)) {
|
||||||
|
event
|
||||||
|
.replyEmbeds(EmbedTemplates.error("Ошибка", "Нет прав на управление сообщениями"))
|
||||||
|
.setEphemeral(true)
|
||||||
|
.queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.deferReply(true).queue();
|
||||||
|
|
||||||
|
int amount = event.getOption("amount").getAsInt();
|
||||||
|
int clamped = Math.clamp(amount, 1, 100);
|
||||||
|
|
||||||
|
String targetId = null;
|
||||||
|
var userOption = event.getOption("user");
|
||||||
|
if (userOption != null) {
|
||||||
|
targetId = userOption.getAsUser().getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event.getChannel() instanceof TextChannel channel)) {
|
||||||
|
event
|
||||||
|
.getHook()
|
||||||
|
.editOriginalEmbeds(
|
||||||
|
EmbedTemplates.error("Ошибка", "Команда работает только в текстовых каналах"))
|
||||||
|
.queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String finalTargetId = targetId;
|
||||||
|
channel
|
||||||
|
.getHistory()
|
||||||
|
.retrievePast(clamped + 1)
|
||||||
|
.queue(
|
||||||
|
messages -> {
|
||||||
|
List<Message> toDelete;
|
||||||
|
if (finalTargetId != null) {
|
||||||
|
toDelete =
|
||||||
|
messages.stream()
|
||||||
|
.filter(m -> m.getAuthor().getId().equals(finalTargetId))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
toDelete = messages;
|
||||||
|
}
|
||||||
|
if (toDelete.isEmpty()) {
|
||||||
|
event
|
||||||
|
.getHook()
|
||||||
|
.editOriginalEmbeds(
|
||||||
|
EmbedTemplates.error("Ошибка", "Нет сообщений для удаления"))
|
||||||
|
.queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
channel
|
||||||
|
.deleteMessages(toDelete)
|
||||||
|
.queue(
|
||||||
|
success ->
|
||||||
|
event
|
||||||
|
.getHook()
|
||||||
|
.editOriginalEmbeds(
|
||||||
|
EmbedTemplates.success(
|
||||||
|
"Сообщения удалены", "Удалено сообщений: " + toDelete.size()))
|
||||||
|
.queue(),
|
||||||
|
error ->
|
||||||
|
event
|
||||||
|
.getHook()
|
||||||
|
.editOriginalEmbeds(
|
||||||
|
EmbedTemplates.error(
|
||||||
|
"Ошибка", "Не удалось удалить: " + error.getMessage()))
|
||||||
|
.queue());
|
||||||
|
},
|
||||||
|
error ->
|
||||||
|
event
|
||||||
|
.getHook()
|
||||||
|
.editOriginalEmbeds(
|
||||||
|
EmbedTemplates.error("Ошибка", "Не удалось получить историю"))
|
||||||
|
.queue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ public class GuildMusicManager {
|
||||||
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
|
||||||
private ScheduledFuture<?> autoLeaveFuture;
|
private ScheduledFuture<?> autoLeaveFuture;
|
||||||
|
|
||||||
|
@SuppressWarnings("this-escape")
|
||||||
public GuildMusicManager(
|
public GuildMusicManager(
|
||||||
long guildId, LavalinkManager lavalinkManager, QueueRepository queueRepository) {
|
long guildId, LavalinkManager lavalinkManager, QueueRepository queueRepository) {
|
||||||
this.guildId = guildId;
|
this.guildId = guildId;
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,10 @@ import java.awt.geom.RoundRectangle2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
import creator.loki.feature.profile.model.ProfileData;
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import creator.loki.feature.profile.model.ProfileData;
|
||||||
|
|
||||||
public class RankCardGenerator {
|
public class RankCardGenerator {
|
||||||
private final RankCardRenderer renderer = new RankCardRenderer();
|
private final RankCardRenderer renderer = new RankCardRenderer();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,24 @@ public class AuraCommand implements Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() { return "aura"; }
|
public String getName() {
|
||||||
|
return "aura";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() { return "Проверить свою ауру (результат всегда одинаковый)"; }
|
public String getDescription() {
|
||||||
|
return "Проверить свою ауру (результат всегда одинаковый)";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUsage() { return "aura [@user]"; }
|
public String getUsage() {
|
||||||
|
return "aura [@user]";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCategory() { return "Развлечения"; }
|
public String getCategory() {
|
||||||
|
return "Развлечения";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext context) {
|
public void execute(CommandContext context) {
|
||||||
|
|
@ -39,26 +47,42 @@ public class AuraCommand implements Command {
|
||||||
int aura = ratingService.getDeterministicValue(target.getId(), 999, -50, 100);
|
int aura = ratingService.getDeterministicValue(target.getId(), 999, -50, 100);
|
||||||
String emoji;
|
String emoji;
|
||||||
Color color;
|
Color color;
|
||||||
if (aura >= 80) { emoji = "🌟"; color = new Color(255, 215, 0); }
|
if (aura >= 80) {
|
||||||
else if (aura >= 50) { emoji = "✨"; color = new Color(100, 255, 150); }
|
emoji = "🌟";
|
||||||
else if (aura >= 20) { emoji = "😐"; color = new Color(52, 152, 219); }
|
color = new Color(255, 215, 0);
|
||||||
else if (aura >= 0) { emoji = "💫"; color = new Color(128, 128, 128); }
|
} else if (aura >= 50) {
|
||||||
else if (aura >= -25) { emoji = "🌑"; color = new Color(231, 76, 60); }
|
emoji = "✨";
|
||||||
else { emoji = "💀"; color = new Color(100, 0, 0); }
|
color = new Color(100, 255, 150);
|
||||||
|
} else if (aura >= 20) {
|
||||||
|
emoji = "😐";
|
||||||
|
color = new Color(52, 152, 219);
|
||||||
|
} else if (aura >= 0) {
|
||||||
|
emoji = "💫";
|
||||||
|
color = new Color(128, 128, 128);
|
||||||
|
} else if (aura >= -25) {
|
||||||
|
emoji = "🌑";
|
||||||
|
color = new Color(231, 76, 60);
|
||||||
|
} else {
|
||||||
|
emoji = "💀";
|
||||||
|
color = new Color(100, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
StringBuilder bar = new StringBuilder();
|
StringBuilder bar = new StringBuilder();
|
||||||
int filled = Math.max(0, Math.min(10, (aura + 50) / 15));
|
int filled = Math.max(0, Math.min(10, (aura + 50) / 15));
|
||||||
for (int i = 0; i < filled; i++) bar.append("✧");
|
for (int i = 0; i < filled; i++) bar.append("✧");
|
||||||
for (int i = filled; i < 10; i++) bar.append("◇");
|
for (int i = filled; i < 10; i++) bar.append("◇");
|
||||||
|
|
||||||
context.getChannel().sendMessageEmbeds(
|
context
|
||||||
EmbedFactory.create()
|
.getChannel()
|
||||||
.withTitle("✨ Аура")
|
.sendMessageEmbeds(
|
||||||
.withDescription(target.getAsMention())
|
EmbedFactory.create()
|
||||||
.addField("Сила", emoji + " **" + aura + "**\n" + bar, false)
|
.withTitle("✨ Аура")
|
||||||
.withThumbnail(target.getUser().getAvatarUrl())
|
.withDescription(target.getAsMention())
|
||||||
.withColor(color)
|
.addField("Сила", emoji + " **" + aura + "**\n" + bar, false)
|
||||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
.withThumbnail(target.getUser().getAvatarUrl())
|
||||||
.build()).queue();
|
.withColor(color)
|
||||||
|
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||||
|
.build())
|
||||||
|
.queue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,29 @@ public class GlazeCommand implements Command {
|
||||||
private final RatingService ratingService;
|
private final RatingService ratingService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public GlazeCommand(RatingService ratingService) { this.ratingService = ratingService; }
|
public GlazeCommand(RatingService ratingService) {
|
||||||
|
this.ratingService = ratingService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public String getName() { return "glaze"; }
|
@Override
|
||||||
@Override public String getDescription() { return "На сколько ты глазированный (результат всегда одинаковый)"; }
|
public String getName() {
|
||||||
@Override public String getUsage() { return "glaze [@user]"; }
|
return "glaze";
|
||||||
@Override public String getCategory() { return "Развлечения"; }
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "На сколько ты глазированный (результат всегда одинаковый)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsage() {
|
||||||
|
return "glaze [@user]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCategory() {
|
||||||
|
return "Развлечения";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext context) {
|
public void execute(CommandContext context) {
|
||||||
|
|
@ -31,10 +48,17 @@ public class GlazeCommand implements Command {
|
||||||
for (int i = 0; i < filled; i++) bar.append("🍩");
|
for (int i = 0; i < filled; i++) bar.append("🍩");
|
||||||
for (int i = filled; i < 10; i++) bar.append("⬜");
|
for (int i = filled; i < 10; i++) bar.append("⬜");
|
||||||
RatingLevel rl = RatingLevel.findLevel(level, RatingConfig.getGlazeLevels());
|
RatingLevel rl = RatingLevel.findLevel(level, RatingConfig.getGlazeLevels());
|
||||||
context.getChannel().sendMessageEmbeds(
|
context
|
||||||
EmbedFactory.create().withTitle("🍩 Глазурь-метр").withDescription(target.getAsMention())
|
.getChannel()
|
||||||
.addField("Глазурь", bar + "\n**" + level + "%** - " + rl.getComment(), false)
|
.sendMessageEmbeds(
|
||||||
.withThumbnail(target.getUser().getAvatarUrl()).withColor(rl.getColor())
|
EmbedFactory.create()
|
||||||
.withFooter("💡 Детерминировано вашим Discord ID").build()).queue();
|
.withTitle("🍩 Глазурь-метр")
|
||||||
|
.withDescription(target.getAsMention())
|
||||||
|
.addField("Глазурь", bar + "\n**" + level + "%** - " + rl.getComment(), false)
|
||||||
|
.withThumbnail(target.getUser().getAvatarUrl())
|
||||||
|
.withColor(rl.getColor())
|
||||||
|
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||||
|
.build())
|
||||||
|
.queue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,29 @@ public class GoonCommand implements Command {
|
||||||
private final RatingService ratingService;
|
private final RatingService ratingService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public GoonCommand(RatingService ratingService) { this.ratingService = ratingService; }
|
public GoonCommand(RatingService ratingService) {
|
||||||
|
this.ratingService = ratingService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public String getName() { return "goon"; }
|
@Override
|
||||||
@Override public String getDescription() { return "Гуон-детектор (результат всегда одинаковый)"; }
|
public String getName() {
|
||||||
@Override public String getUsage() { return "goon [@user]"; }
|
return "goon";
|
||||||
@Override public String getCategory() { return "Развлечения"; }
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Гуон-детектор (результат всегда одинаковый)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsage() {
|
||||||
|
return "goon [@user]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCategory() {
|
||||||
|
return "Развлечения";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext context) {
|
public void execute(CommandContext context) {
|
||||||
|
|
@ -30,11 +47,21 @@ public class GoonCommand implements Command {
|
||||||
int filled = level / 10;
|
int filled = level / 10;
|
||||||
for (int i = 0; i < filled; i++) bar.append("💀");
|
for (int i = 0; i < filled; i++) bar.append("💀");
|
||||||
for (int i = filled; i < 10; i++) bar.append("⬜");
|
for (int i = filled; i < 10; i++) bar.append("⬜");
|
||||||
Color color = level >= 70 ? new Color(180, 0, 0) : level >= 40 ? new Color(128, 0, 128) : new Color(100, 100, 100);
|
Color color =
|
||||||
context.getChannel().sendMessageEmbeds(
|
level >= 70
|
||||||
EmbedFactory.create().withTitle("💀 Goon-детектор").withDescription(target.getAsMention())
|
? new Color(180, 0, 0)
|
||||||
.addField("Уровень", bar + "\n**" + level + "%**", false)
|
: level >= 40 ? new Color(128, 0, 128) : new Color(100, 100, 100);
|
||||||
.withThumbnail(target.getUser().getAvatarUrl()).withColor(color)
|
context
|
||||||
.withFooter("💡 Детерминировано вашим Discord ID").build()).queue();
|
.getChannel()
|
||||||
|
.sendMessageEmbeds(
|
||||||
|
EmbedFactory.create()
|
||||||
|
.withTitle("💀 Goon-детектор")
|
||||||
|
.withDescription(target.getAsMention())
|
||||||
|
.addField("Уровень", bar + "\n**" + level + "%**", false)
|
||||||
|
.withThumbnail(target.getUser().getAvatarUrl())
|
||||||
|
.withColor(color)
|
||||||
|
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||||
|
.build())
|
||||||
|
.queue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,24 @@ public class LuckCommand implements Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() { return "luck"; }
|
public String getName() {
|
||||||
|
return "luck";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() { return "Проверить свою удачу (результат всегда одинаковый)"; }
|
public String getDescription() {
|
||||||
|
return "Проверить свою удачу (результат всегда одинаковый)";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUsage() { return "luck [@user]"; }
|
public String getUsage() {
|
||||||
|
return "luck [@user]";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCategory() { return "Развлечения"; }
|
public String getCategory() {
|
||||||
|
return "Развлечения";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext context) {
|
public void execute(CommandContext context) {
|
||||||
|
|
@ -43,14 +51,17 @@ public class LuckCommand implements Command {
|
||||||
for (int i = filled; i < 10; i++) bar.append("⬜");
|
for (int i = filled; i < 10; i++) bar.append("⬜");
|
||||||
|
|
||||||
RatingLevel level = RatingLevel.findLevel(luck, RatingConfig.getLuckLevels());
|
RatingLevel level = RatingLevel.findLevel(luck, RatingConfig.getLuckLevels());
|
||||||
context.getChannel().sendMessageEmbeds(
|
context
|
||||||
EmbedFactory.create()
|
.getChannel()
|
||||||
.withTitle("🍀 Удача")
|
.sendMessageEmbeds(
|
||||||
.withDescription(target.getAsMention())
|
EmbedFactory.create()
|
||||||
.addField("Везение", bar + "\n**" + luck + "%** - " + level.getComment(), false)
|
.withTitle("🍀 Удача")
|
||||||
.withThumbnail(target.getUser().getAvatarUrl())
|
.withDescription(target.getAsMention())
|
||||||
.withColor(level.getColor())
|
.addField("Везение", bar + "\n**" + luck + "%** - " + level.getComment(), false)
|
||||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
.withThumbnail(target.getUser().getAvatarUrl())
|
||||||
.build()).queue();
|
.withColor(level.getColor())
|
||||||
|
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||||
|
.build())
|
||||||
|
.queue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,24 @@ public class RizzCommand implements Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() { return "rizz"; }
|
public String getName() {
|
||||||
|
return "rizz";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() { return "На сколько у тебя ризз (харизма)"; }
|
public String getDescription() {
|
||||||
|
return "На сколько у тебя ризз (харизма)";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUsage() { return "rizz [@user]"; }
|
public String getUsage() {
|
||||||
|
return "rizz [@user]";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCategory() { return "Развлечения"; }
|
public String getCategory() {
|
||||||
|
return "Развлечения";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext context) {
|
public void execute(CommandContext context) {
|
||||||
|
|
@ -43,14 +51,17 @@ public class RizzCommand implements Command {
|
||||||
for (int i = filled; i < 10; i++) bar.append("⬜");
|
for (int i = filled; i < 10; i++) bar.append("⬜");
|
||||||
|
|
||||||
RatingLevel level = RatingLevel.findLevel(rizz, RatingConfig.getRizzLevels());
|
RatingLevel level = RatingLevel.findLevel(rizz, RatingConfig.getRizzLevels());
|
||||||
context.getChannel().sendMessageEmbeds(
|
context
|
||||||
EmbedFactory.create()
|
.getChannel()
|
||||||
.withTitle("🗿 Rizz Метр")
|
.sendMessageEmbeds(
|
||||||
.withDescription(target.getAsMention())
|
EmbedFactory.create()
|
||||||
.addField("Харизма", bar + "\n**" + rizz + "%** - " + level.getComment(), false)
|
.withTitle("🗿 Rizz Метр")
|
||||||
.withThumbnail(target.getUser().getAvatarUrl())
|
.withDescription(target.getAsMention())
|
||||||
.withColor(level.getColor())
|
.addField("Харизма", bar + "\n**" + rizz + "%** - " + level.getComment(), false)
|
||||||
.withFooter("💡 Детерминировано вашим Discord ID")
|
.withThumbnail(target.getUser().getAvatarUrl())
|
||||||
.build()).queue();
|
.withColor(level.getColor())
|
||||||
|
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||||
|
.build())
|
||||||
|
.queue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,29 @@ public class SimpCommand implements Command {
|
||||||
private final RatingService ratingService;
|
private final RatingService ratingService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SimpCommand(RatingService ratingService) { this.ratingService = ratingService; }
|
public SimpCommand(RatingService ratingService) {
|
||||||
|
this.ratingService = ratingService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public String getName() { return "simp"; }
|
@Override
|
||||||
@Override public String getDescription() { return "Симп-детектор (результат всегда одинаковый)"; }
|
public String getName() {
|
||||||
@Override public String getUsage() { return "simp [@user]"; }
|
return "simp";
|
||||||
@Override public String getCategory() { return "Развлечения"; }
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Симп-детектор (результат всегда одинаковый)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsage() {
|
||||||
|
return "simp [@user]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCategory() {
|
||||||
|
return "Развлечения";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext context) {
|
public void execute(CommandContext context) {
|
||||||
|
|
@ -31,10 +48,17 @@ public class SimpCommand implements Command {
|
||||||
for (int i = 0; i < filled; i++) bar.append("💀");
|
for (int i = 0; i < filled; i++) bar.append("💀");
|
||||||
for (int i = filled; i < 10; i++) bar.append("⬜");
|
for (int i = filled; i < 10; i++) bar.append("⬜");
|
||||||
RatingLevel rl = RatingLevel.findLevel(level, RatingConfig.getSimpLevels());
|
RatingLevel rl = RatingLevel.findLevel(level, RatingConfig.getSimpLevels());
|
||||||
context.getChannel().sendMessageEmbeds(
|
context
|
||||||
EmbedFactory.create().withTitle("😭 Симп-детектор").withDescription(target.getAsMention())
|
.getChannel()
|
||||||
.addField("Уровень", bar + "\n**" + level + "%** - " + rl.getComment(), false)
|
.sendMessageEmbeds(
|
||||||
.withThumbnail(target.getUser().getAvatarUrl()).withColor(rl.getColor())
|
EmbedFactory.create()
|
||||||
.withFooter("💡 Детерминировано вашим Discord ID").build()).queue();
|
.withTitle("😭 Симп-детектор")
|
||||||
|
.withDescription(target.getAsMention())
|
||||||
|
.addField("Уровень", bar + "\n**" + level + "%** - " + rl.getComment(), false)
|
||||||
|
.withThumbnail(target.getUser().getAvatarUrl())
|
||||||
|
.withColor(rl.getColor())
|
||||||
|
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||||
|
.build())
|
||||||
|
.queue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,29 @@ public class VibeCommand implements Command {
|
||||||
private final RatingService ratingService;
|
private final RatingService ratingService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VibeCommand(RatingService ratingService) { this.ratingService = ratingService; }
|
public VibeCommand(RatingService ratingService) {
|
||||||
|
this.ratingService = ratingService;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public String getName() { return "vibe"; }
|
@Override
|
||||||
@Override public String getDescription() { return "Проверить свой вайб (результат всегда одинаковый)"; }
|
public String getName() {
|
||||||
@Override public String getUsage() { return "vibe [@user]"; }
|
return "vibe";
|
||||||
@Override public String getCategory() { return "Развлечения"; }
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Проверить свой вайб (результат всегда одинаковый)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsage() {
|
||||||
|
return "vibe [@user]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCategory() {
|
||||||
|
return "Развлечения";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext context) {
|
public void execute(CommandContext context) {
|
||||||
|
|
@ -31,10 +48,17 @@ public class VibeCommand implements Command {
|
||||||
for (int i = 0; i < filled; i++) bar.append("🎵");
|
for (int i = 0; i < filled; i++) bar.append("🎵");
|
||||||
for (int i = filled; i < 10; i++) bar.append("⬜");
|
for (int i = filled; i < 10; i++) bar.append("⬜");
|
||||||
RatingLevel rl = RatingLevel.findLevel(level, RatingConfig.getVibeLevels());
|
RatingLevel rl = RatingLevel.findLevel(level, RatingConfig.getVibeLevels());
|
||||||
context.getChannel().sendMessageEmbeds(
|
context
|
||||||
EmbedFactory.create().withTitle("🎵 Вайб-чек").withDescription(target.getAsMention())
|
.getChannel()
|
||||||
.addField("Вайб", bar + "\n**" + level + "%** - " + rl.getComment(), false)
|
.sendMessageEmbeds(
|
||||||
.withThumbnail(target.getUser().getAvatarUrl()).withColor(rl.getColor())
|
EmbedFactory.create()
|
||||||
.withFooter("💡 Детерминировано вашим Discord ID").build()).queue();
|
.withTitle("🎵 Вайб-чек")
|
||||||
|
.withDescription(target.getAsMention())
|
||||||
|
.addField("Вайб", bar + "\n**" + level + "%** - " + rl.getComment(), false)
|
||||||
|
.withThumbnail(target.getUser().getAvatarUrl())
|
||||||
|
.withColor(rl.getColor())
|
||||||
|
.withFooter("💡 Детерминировано вашим Discord ID")
|
||||||
|
.build())
|
||||||
|
.queue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,27 +74,33 @@ public class RatingConfig {
|
||||||
new RatingLevel(30, "😐 Ни туда ни сюда", new Color(128, 128, 128)),
|
new RatingLevel(30, "😐 Ни туда ни сюда", new Color(128, 128, 128)),
|
||||||
new RatingLevel(10, "😬 Невезучий", new Color(255, 165, 0)),
|
new RatingLevel(10, "😬 Невезучий", new Color(255, 165, 0)),
|
||||||
new RatingLevel(0, "💀 Проклят", new Color(231, 76, 60))));
|
new RatingLevel(0, "💀 Проклят", new Color(231, 76, 60))));
|
||||||
m.put("simp", List.of(
|
m.put(
|
||||||
new RatingLevel(90, "👑 КОРОЛЬ СИМПОВ!", new Color(255, 105, 180)),
|
"simp",
|
||||||
new RatingLevel(70, "😍 Задрот", new Color(255, 182, 193)),
|
List.of(
|
||||||
new RatingLevel(50, "🙂 Нормальный", new Color(52, 152, 219)),
|
new RatingLevel(90, "👑 КОРОЛЬ СИМПОВ!", new Color(255, 105, 180)),
|
||||||
new RatingLevel(30, "😐 Холодный", new Color(128, 128, 128)),
|
new RatingLevel(70, "😍 Задрот", new Color(255, 182, 193)),
|
||||||
new RatingLevel(10, "💪 Альфа", new Color(100, 255, 150)),
|
new RatingLevel(50, "🙂 Нормальный", new Color(52, 152, 219)),
|
||||||
new RatingLevel(0, "🦍 ГОРИЛЛА", new Color(150, 100, 50))));
|
new RatingLevel(30, "😐 Холодный", new Color(128, 128, 128)),
|
||||||
m.put("vibe", List.of(
|
new RatingLevel(10, "💪 Альфа", new Color(100, 255, 150)),
|
||||||
new RatingLevel(90, "🎶 ЛЕГЕНДАРНЫЙ ВАЙБ!", new Color(255, 215, 0)),
|
new RatingLevel(0, "🦍 ГОРИЛЛА", new Color(150, 100, 50))));
|
||||||
new RatingLevel(70, "🔥 Крутой вайб", new Color(255, 100, 0)),
|
m.put(
|
||||||
new RatingLevel(50, "🙂 Норм вайб", new Color(52, 152, 219)),
|
"vibe",
|
||||||
new RatingLevel(30, "😐 Так себе", new Color(128, 128, 128)),
|
List.of(
|
||||||
new RatingLevel(10, "📉 Вайб упал", new Color(255, 165, 0)),
|
new RatingLevel(90, "🎶 ЛЕГЕНДАРНЫЙ ВАЙБ!", new Color(255, 215, 0)),
|
||||||
new RatingLevel(0, "💀 Вайба нет", new Color(231, 76, 60))));
|
new RatingLevel(70, "🔥 Крутой вайб", new Color(255, 100, 0)),
|
||||||
m.put("glaze", List.of(
|
new RatingLevel(50, "🙂 Норм вайб", new Color(52, 152, 219)),
|
||||||
new RatingLevel(90, "🍩 НЕВЕРОЯТНО ГЛАЗИРОВАН!", new Color(255, 215, 0)),
|
new RatingLevel(30, "😐 Так себе", new Color(128, 128, 128)),
|
||||||
new RatingLevel(70, "✨ Сияет", new Color(255, 200, 100)),
|
new RatingLevel(10, "📉 Вайб упал", new Color(255, 165, 0)),
|
||||||
new RatingLevel(50, "🙂 Средне", new Color(52, 152, 219)),
|
new RatingLevel(0, "💀 Вайба нет", new Color(231, 76, 60))));
|
||||||
new RatingLevel(30, "😐 Матовый", new Color(128, 128, 128)),
|
m.put(
|
||||||
new RatingLevel(10, "🧊 Подтаивает", new Color(200, 200, 255)),
|
"glaze",
|
||||||
new RatingLevel(0, "💀 Сухой", new Color(150, 150, 150))));
|
List.of(
|
||||||
|
new RatingLevel(90, "🍩 НЕВЕРОЯТНО ГЛАЗИРОВАН!", new Color(255, 215, 0)),
|
||||||
|
new RatingLevel(70, "✨ Сияет", new Color(255, 200, 100)),
|
||||||
|
new RatingLevel(50, "🙂 Средне", new Color(52, 152, 219)),
|
||||||
|
new RatingLevel(30, "😐 Матовый", new Color(128, 128, 128)),
|
||||||
|
new RatingLevel(10, "🧊 Подтаивает", new Color(200, 200, 255)),
|
||||||
|
new RatingLevel(0, "💀 Сухой", new Color(150, 150, 150))));
|
||||||
LEVELS = Collections.unmodifiableMap(m);
|
LEVELS = Collections.unmodifiableMap(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,8 +128,19 @@ public class RatingConfig {
|
||||||
return LEVELS.get("rizz");
|
return LEVELS.get("rizz");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<RatingLevel> getLuckLevels() { return LEVELS.get("luck"); }
|
public static List<RatingLevel> getLuckLevels() {
|
||||||
public static List<RatingLevel> getSimpLevels() { return LEVELS.get("simp"); }
|
return LEVELS.get("luck");
|
||||||
public static List<RatingLevel> getVibeLevels() { return LEVELS.get("vibe"); }
|
}
|
||||||
public static List<RatingLevel> getGlazeLevels() { return LEVELS.get("glaze"); }
|
|
||||||
|
public static List<RatingLevel> getSimpLevels() {
|
||||||
|
return LEVELS.get("simp");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<RatingLevel> getVibeLevels() {
|
||||||
|
return LEVELS.get("vibe");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<RatingLevel> getGlazeLevels() {
|
||||||
|
return LEVELS.get("glaze");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,23 @@
|
||||||
package creator.loki.feature.roast;
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import creator.loki.command.Command;
|
import creator.loki.command.Command;
|
||||||
import creator.loki.command.CommandContext;
|
import creator.loki.command.CommandContext;
|
||||||
import creator.loki.config.BotConfig;
|
import creator.loki.config.BotConfig;
|
||||||
import creator.loki.embed.EmbedTemplates;
|
import creator.loki.embed.EmbedTemplates;
|
||||||
import net.dv8tion.jda.api.entities.Message;
|
|
||||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
|
||||||
|
|
||||||
public class AgrCommand implements Command {
|
public class AgrCommand implements Command {
|
||||||
private static final String ANDRERT_USER_ID = "1405129454165954630";
|
private static final String ANDRERT_USER_ID = "1405129454165954630";
|
||||||
private static final int MESSAGES_PER_CHANNEL = 100;
|
|
||||||
|
|
||||||
private final BotConfig config;
|
private final BotConfig config;
|
||||||
private final RoastService roastService;
|
private final RoastService roastService;
|
||||||
private final AndrertListener autoResponder;
|
private final AndrertListener autoResponder;
|
||||||
|
private final MessageScanner messageScanner;
|
||||||
|
|
||||||
public AgrCommand(BotConfig config, RoastService roastService, AndrertListener autoResponder) {
|
public AgrCommand(BotConfig config, RoastService roastService, AndrertListener autoResponder) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.roastService = roastService;
|
this.roastService = roastService;
|
||||||
this.autoResponder = autoResponder;
|
this.autoResponder = autoResponder;
|
||||||
|
this.messageScanner = new MessageScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -48,8 +43,10 @@ public class AgrCommand implements Command {
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext context) {
|
public void execute(CommandContext context) {
|
||||||
if (!config.getSuperAdminId().equals(context.getAuthor().getId())) {
|
if (!config.getSuperAdminId().equals(context.getAuthor().getId())) {
|
||||||
context.getChannel().sendMessageEmbeds(
|
context
|
||||||
EmbedTemplates.error("Ошибка", "Только супер-администратор")).queue();
|
.getChannel()
|
||||||
|
.sendMessageEmbeds(EmbedTemplates.error("Ошибка", "Только супер-администратор"))
|
||||||
|
.queue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (context.getGuild() == null) {
|
if (context.getGuild() == null) {
|
||||||
|
|
@ -68,96 +65,52 @@ public class AgrCommand implements Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String finalId = targetId;
|
String finalId = targetId;
|
||||||
context.getGuild().retrieveMemberById(targetId).queue(
|
context
|
||||||
member -> {
|
.getGuild()
|
||||||
autoResponder.trackUser(finalId);
|
.retrieveMemberById(targetId)
|
||||||
doScan(context, finalId, member.getEffectiveName());
|
.queue(
|
||||||
},
|
member -> {
|
||||||
error -> context.getChannel().sendMessage("Пользователь не найден на сервере").queue());
|
autoResponder.trackUser(finalId);
|
||||||
|
doScan(context, finalId, member.getEffectiveName());
|
||||||
|
},
|
||||||
|
error ->
|
||||||
|
context.getChannel().sendMessage("Пользователь не найден на сервере").queue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doScan(CommandContext context, String targetId, String targetName) {
|
private void doScan(CommandContext context, String targetId, String targetName) {
|
||||||
context.getMessage().reply("🔍 Сканирую сообщения " + targetName + "...")
|
context
|
||||||
.queue(reply -> {
|
.getMessage()
|
||||||
List<TextChannel> textChannels = context.getGuild().getTextChannels();
|
.reply("🔍 Сканирую сообщения " + targetName + "...")
|
||||||
List<CompletableFuture<List<Message>>> futures = new ArrayList<>();
|
.queue(
|
||||||
|
reply -> {
|
||||||
for (TextChannel ch : textChannels) {
|
messageScanner
|
||||||
CompletableFuture<List<Message>> future = new CompletableFuture<>();
|
.scanUserMessages(context.getGuild(), targetId)
|
||||||
futures.add(future);
|
.thenApply(
|
||||||
ch.getHistory().retrievePast(MESSAGES_PER_CHANNEL).queue(future::complete, e -> future.complete(List.of()));
|
msgs -> {
|
||||||
}
|
if (msgs.size() > 10) {
|
||||||
|
return msgs.subList(msgs.size() - 10, msgs.size());
|
||||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0]))
|
}
|
||||||
.thenApply(v -> {
|
return msgs;
|
||||||
List<RoastService.CachedMessage> allMessages = new ArrayList<>();
|
})
|
||||||
for (int i = 0; i < textChannels.size(); i++) {
|
.thenAccept(
|
||||||
List<Message> msgs = futures.get(i).join();
|
msgs -> {
|
||||||
String chName = textChannels.get(i).getName();
|
if (msgs.isEmpty()) {
|
||||||
for (Message msg : msgs) {
|
reply.editMessage("Нет сообщений от " + targetName).queue();
|
||||||
if (msg.getAuthor().getId().equals(targetId)) {
|
return;
|
||||||
String text = msg.getContentRaw() + replyInfo(msg) + attachmentInfo(msg);
|
}
|
||||||
allMessages.add(new RoastService.CachedMessage(
|
String response = roastService.generateResponse(msgs, targetName, targetId);
|
||||||
text, chName, msg.getTimeCreated().toEpochSecond() * 1000));
|
if (response == null || response.isBlank()) {
|
||||||
}
|
reply.editMessage("AI не ответил").queue();
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
allMessages.sort((a, b) -> Long.compare(a.timestamp(), b.timestamp()));
|
reply.editMessage(response).queue();
|
||||||
if (allMessages.size() > 10) {
|
})
|
||||||
allMessages = allMessages.subList(allMessages.size() - 10, allMessages.size());
|
.exceptionally(
|
||||||
}
|
e -> {
|
||||||
return allMessages;
|
reply.editMessage("Ошибка: " + e.getMessage()).queue();
|
||||||
})
|
return null;
|
||||||
.thenAccept(msgs -> {
|
});
|
||||||
if (msgs.isEmpty()) {
|
});
|
||||||
reply.editMessage("Нет сообщений от " + targetName).queue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String response = roastService.generateResponse(msgs, targetName, targetId);
|
|
||||||
if (response == null || response.isBlank()) {
|
|
||||||
reply.editMessage("AI не ответил").queue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reply.editMessage(response).queue();
|
|
||||||
})
|
|
||||||
.exceptionally(e -> {
|
|
||||||
reply.editMessage("Ошибка: " + e.getMessage()).queue();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private String replyInfo(Message msg) {
|
|
||||||
Message ref = msg.getReferencedMessage();
|
|
||||||
if (ref == null) return "";
|
|
||||||
String refAuthor = ref.getAuthor().isBot() ? "бот" : ref.getAuthor().getEffectiveName();
|
|
||||||
String refText = ref.getContentRaw().replace("\n", " ").strip();
|
|
||||||
if (refText.length() > 80) refText = refText.substring(0, 80) + "...";
|
|
||||||
return " (в ответ на @" + refAuthor + ": \"" + refText + "\")";
|
|
||||||
}
|
|
||||||
|
|
||||||
private String attachmentInfo(Message msg) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
for (var att : msg.getAttachments()) {
|
|
||||||
if (att.isImage()) {
|
|
||||||
sb.append(" [изображение: ").append(att.getFileName()).append("]");
|
|
||||||
} else if (att.isVideo()) {
|
|
||||||
sb.append(" [видео: ").append(att.getFileName()).append("]");
|
|
||||||
} else if (att.getFileName().endsWith(".txt")) {
|
|
||||||
String txt = roastService.readTextFile(att.getUrl());
|
|
||||||
if (txt != null) {
|
|
||||||
sb.append("\n[содержимое txt файла ").append(att.getFileName()).append("]:\n").append(txt);
|
|
||||||
} else {
|
|
||||||
sb.append(" [txt файл: ").append(att.getFileName()).append(" (не удалось прочитать)]");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sb.append(" [файл: ").append(att.getFileName()).append("]");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!msg.getStickers().isEmpty()) {
|
|
||||||
sb.append(" [стикер]");
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,26 @@
|
||||||
package creator.loki.feature.roast;
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import creator.loki.config.BotConfig;
|
import creator.loki.config.BotConfig;
|
||||||
import creator.loki.embed.EmbedTemplates;
|
import creator.loki.embed.EmbedTemplates;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import net.dv8tion.jda.api.entities.Message;
|
|
||||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
|
||||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class AgrSlashCommands extends ListenerAdapter {
|
public class AgrSlashCommands extends ListenerAdapter {
|
||||||
private static final String ANDRERT_USER_ID = "1405129454165954630";
|
private static final String ANDRERT_USER_ID = "1405129454165954630";
|
||||||
private static final int MESSAGES_PER_CHANNEL = 100;
|
|
||||||
|
|
||||||
private final BotConfig config;
|
private final BotConfig config;
|
||||||
private final RoastService roastService;
|
private final RoastService roastService;
|
||||||
|
private final MessageScanner messageScanner;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AgrSlashCommands(BotConfig config, RoastService roastService) {
|
public AgrSlashCommands(BotConfig config, RoastService roastService) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.roastService = roastService;
|
this.roastService = roastService;
|
||||||
|
this.messageScanner = new MessageScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -33,8 +28,10 @@ public class AgrSlashCommands extends ListenerAdapter {
|
||||||
if (!event.getName().equals("agr")) return;
|
if (!event.getName().equals("agr")) return;
|
||||||
|
|
||||||
if (!config.getSuperAdminId().equals(event.getUser().getId())) {
|
if (!config.getSuperAdminId().equals(event.getUser().getId())) {
|
||||||
event.replyEmbeds(EmbedTemplates.error("Ошибка", "Только супер-администратор"))
|
event
|
||||||
.setEphemeral(true).queue();
|
.replyEmbeds(EmbedTemplates.error("Ошибка", "Только супер-администратор"))
|
||||||
|
.setEphemeral(true)
|
||||||
|
.queue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.getGuild() == null) {
|
if (event.getGuild() == null) {
|
||||||
|
|
@ -59,50 +56,34 @@ public class AgrSlashCommands extends ListenerAdapter {
|
||||||
|
|
||||||
String finalTargetName = targetName;
|
String finalTargetName = targetName;
|
||||||
String finalTargetId = targetId;
|
String finalTargetId = targetId;
|
||||||
List<TextChannel> textChannels = event.getGuild().getTextChannels();
|
|
||||||
List<CompletableFuture<List<Message>>> futures = new ArrayList<>();
|
|
||||||
|
|
||||||
for (TextChannel ch : textChannels) {
|
messageScanner
|
||||||
CompletableFuture<List<Message>> future = new CompletableFuture<>();
|
.scanUserMessages(event.getGuild(), finalTargetId)
|
||||||
futures.add(future);
|
.thenApply(
|
||||||
ch.getHistory().retrievePast(MESSAGES_PER_CHANNEL).queue(future::complete, e -> future.complete(List.of()));
|
msgs -> {
|
||||||
}
|
if (msgs.size() > 10) {
|
||||||
|
return msgs.subList(msgs.size() - 10, msgs.size());
|
||||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0]))
|
|
||||||
.thenApply(v -> {
|
|
||||||
List<RoastService.CachedMessage> allMessages = new ArrayList<>();
|
|
||||||
for (int i = 0; i < textChannels.size(); i++) {
|
|
||||||
List<Message> msgs = futures.get(i).join();
|
|
||||||
String chName = textChannels.get(i).getName();
|
|
||||||
for (Message msg : msgs) {
|
|
||||||
if (msg.getAuthor().getId().equals(finalTargetId)) {
|
|
||||||
allMessages.add(new RoastService.CachedMessage(
|
|
||||||
msg.getContentRaw(), chName, msg.getTimeCreated().toEpochSecond() * 1000));
|
|
||||||
}
|
}
|
||||||
}
|
return msgs;
|
||||||
}
|
})
|
||||||
allMessages.sort((a, b) -> Long.compare(a.timestamp(), b.timestamp()));
|
.thenCompose(
|
||||||
if (allMessages.size() > 10) {
|
msgs -> {
|
||||||
allMessages = allMessages.subList(allMessages.size() - 10, allMessages.size());
|
if (msgs.isEmpty()) {
|
||||||
}
|
event.getHook().editOriginal("Нет сообщений от " + finalTargetName).queue();
|
||||||
return allMessages;
|
return java.util.concurrent.CompletableFuture.completedFuture(null);
|
||||||
})
|
}
|
||||||
.thenCompose(msgs -> {
|
String response = roastService.generateResponse(msgs, finalTargetName, finalTargetId);
|
||||||
if (msgs.isEmpty()) {
|
if (response != null && !response.isBlank()) {
|
||||||
event.getHook().editOriginal("Нет сообщений от " + finalTargetName).queue();
|
event.getHook().editOriginal(response).queue();
|
||||||
return CompletableFuture.completedFuture(null);
|
} else {
|
||||||
}
|
event.getHook().editOriginal("AI не ответил").queue();
|
||||||
String response = roastService.generateResponse(msgs, finalTargetName, finalTargetId);
|
}
|
||||||
if (response != null && !response.isBlank()) {
|
return java.util.concurrent.CompletableFuture.completedFuture(null);
|
||||||
event.getHook().editOriginal(response).queue();
|
})
|
||||||
} else {
|
.exceptionally(
|
||||||
event.getHook().editOriginal("AI не ответил").queue();
|
e -> {
|
||||||
}
|
event.getHook().editOriginal("Ошибка: " + e.getMessage()).queue();
|
||||||
return CompletableFuture.completedFuture(null);
|
return null;
|
||||||
})
|
});
|
||||||
.exceptionally(e -> {
|
|
||||||
event.getHook().editOriginal("Ошибка: " + e.getMessage()).queue();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import creator.loki.config.BotConfig;
|
||||||
import net.dv8tion.jda.api.entities.Message;
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
@ -40,8 +39,10 @@ public class AndrertListener extends ListenerAdapter {
|
||||||
if (event.getAuthor().isBot()) return;
|
if (event.getAuthor().isBot()) return;
|
||||||
|
|
||||||
String channelId = event.getChannel().getId();
|
String channelId = event.getChannel().getId();
|
||||||
boolean isAllowedChannel = allowedChannelId != null && !allowedChannelId.isBlank()
|
boolean isAllowedChannel =
|
||||||
&& channelId.equals(allowedChannelId);
|
allowedChannelId != null
|
||||||
|
&& !allowedChannelId.isBlank()
|
||||||
|
&& channelId.equals(allowedChannelId);
|
||||||
|
|
||||||
if (!isAllowedChannel) {
|
if (!isAllowedChannel) {
|
||||||
String userId = event.getAuthor().getId();
|
String userId = event.getAuthor().getId();
|
||||||
|
|
@ -61,48 +62,85 @@ public class AndrertListener extends ListenerAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void respond(MessageReceivedEvent event, String userId) {
|
private void respond(MessageReceivedEvent event, String userId) {
|
||||||
event.getChannel().getHistory().retrievePast(CHANNEL_CONTEXT)
|
String raw = event.getMessage().getContentRaw();
|
||||||
.queue(messages -> {
|
if (!raw.isBlank() && !LanguageFilter.isAllowed(raw)) {
|
||||||
String targetName = event.getAuthor().getEffectiveName();
|
event
|
||||||
List<RoastService.CachedMessage> context = new ArrayList<>();
|
.getMessage()
|
||||||
for (Message msg : messages) {
|
.reply("только польский русский украинский английский литовский. остальных языков нет")
|
||||||
String author = msg.getAuthor().isBot() ? "бот" : msg.getAuthor().getEffectiveName();
|
.queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
StringBuilder line = new StringBuilder();
|
respondAi(event, userId);
|
||||||
line.append(author).append(": ").append(msg.getContentRaw());
|
}
|
||||||
line.append(replyInfo(msg));
|
|
||||||
line.append(attachmentInfo(msg));
|
|
||||||
|
|
||||||
context.add(new RoastService.CachedMessage(
|
private void respondAi(MessageReceivedEvent event, String userId) {
|
||||||
line.toString(),
|
event
|
||||||
event.getChannel().getName(),
|
.getChannel()
|
||||||
msg.getTimeCreated().toEpochSecond() * 1000));
|
.getHistory()
|
||||||
}
|
.retrievePast(CHANNEL_CONTEXT)
|
||||||
java.util.Collections.reverse(context);
|
.queue(
|
||||||
|
messages -> {
|
||||||
|
String targetName = event.getAuthor().getEffectiveName();
|
||||||
|
List<CachedMessage> context = new ArrayList<>();
|
||||||
|
for (int idx = 0; idx < messages.size(); idx++) {
|
||||||
|
Message msg = messages.get(idx);
|
||||||
|
boolean isSelf =
|
||||||
|
msg.getAuthor().getId().equals(event.getJDA().getSelfUser().getId());
|
||||||
|
String author = isSelf ? "ты" : msg.getAuthor().getEffectiveName();
|
||||||
|
String authorId = msg.getAuthor().getId();
|
||||||
|
boolean isCurrent = (idx == 0);
|
||||||
|
|
||||||
String response = roastService.generateResponse(context, targetName, userId);
|
String label = isCurrent ? "[now]" : "[history]";
|
||||||
if (response == null || response.isBlank()) return;
|
StringBuilder line = new StringBuilder();
|
||||||
|
line.append(label)
|
||||||
|
.append(" ")
|
||||||
|
.append(author)
|
||||||
|
.append("(")
|
||||||
|
.append(authorId)
|
||||||
|
.append("): ")
|
||||||
|
.append(msg.getContentRaw());
|
||||||
|
line.append(replyInfo(msg));
|
||||||
|
line.append(attachmentInfo(msg));
|
||||||
|
|
||||||
String gifUrl = null;
|
context.add(
|
||||||
String text = response;
|
new CachedMessage(
|
||||||
int gifIdx = response.indexOf("gif:");
|
line.toString(),
|
||||||
if (gifIdx >= 0) {
|
event.getChannel().getName(),
|
||||||
String after = response.substring(gifIdx + 4).strip();
|
msg.getTimeCreated().toEpochSecond() * 1000));
|
||||||
text = response.substring(0, gifIdx).strip();
|
}
|
||||||
if (!after.isBlank()) {
|
java.util.Collections.reverse(context);
|
||||||
String topic = after.split("\\s+")[0];
|
|
||||||
gifUrl = roastService.searchGif(topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder = new MessageCreateBuilder();
|
String response = roastService.generateResponse(context, targetName, userId);
|
||||||
if (gifUrl != null) {
|
if (response == null || response.isBlank()) return;
|
||||||
builder.addContent(text + "\n" + gifUrl);
|
|
||||||
} else {
|
sendInParts(event, response);
|
||||||
builder.addContent(text);
|
});
|
||||||
}
|
}
|
||||||
event.getMessage().reply(builder.build()).queue();
|
|
||||||
});
|
private void sendInParts(MessageReceivedEvent event, String text) {
|
||||||
|
if (text.length() <= 2000) {
|
||||||
|
event.getMessage().reply(text).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> parts = new ArrayList<>();
|
||||||
|
int start = 0;
|
||||||
|
while (start < text.length() && parts.size() < 3) {
|
||||||
|
int end = Math.min(start + 1990, text.length());
|
||||||
|
if (end < text.length()) {
|
||||||
|
int nl = text.lastIndexOf('\n', end);
|
||||||
|
if (nl > start) end = nl;
|
||||||
|
}
|
||||||
|
parts.add(text.substring(start, end).strip());
|
||||||
|
start = end;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < parts.size(); i++) {
|
||||||
|
if (i == 0) {
|
||||||
|
event.getMessage().reply(parts.get(i)).queue();
|
||||||
|
} else {
|
||||||
|
event.getChannel().sendMessage(parts.get(i)).queue();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String replyInfo(Message msg) {
|
private String replyInfo(Message msg) {
|
||||||
|
|
@ -122,9 +160,12 @@ public class AndrertListener extends ListenerAdapter {
|
||||||
} else if (att.isVideo()) {
|
} else if (att.isVideo()) {
|
||||||
sb.append(" [видео: ").append(att.getFileName()).append("]");
|
sb.append(" [видео: ").append(att.getFileName()).append("]");
|
||||||
} else if (att.getFileName().endsWith(".txt")) {
|
} else if (att.getFileName().endsWith(".txt")) {
|
||||||
String txt = roastService.readTextFile(att.getUrl());
|
String txt = roastService.readTextFilePreview(att.getUrl());
|
||||||
if (txt != null) {
|
if (txt != null) {
|
||||||
sb.append("\n[содержимое txt файла ").append(att.getFileName()).append("]:\n").append(txt);
|
sb.append("\n[содержимое txt файла ")
|
||||||
|
.append(att.getFileName())
|
||||||
|
.append("]:\n")
|
||||||
|
.append(txt);
|
||||||
} else {
|
} else {
|
||||||
sb.append(" [txt файл: ").append(att.getFileName()).append(" (не удалось прочитать)]");
|
sb.append(" [txt файл: ").append(att.getFileName()).append(" (не удалось прочитать)]");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
|
public record CachedMessage(String content, String channelName, long timestamp) {}
|
||||||
296
src/main/java/creator/loki/feature/roast/ContentFetcher.java
Normal file
296
src/main/java/creator/loki/feature/roast/ContentFetcher.java
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ContentFetcher {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ContentFetcher.class);
|
||||||
|
private static final Pattern URL_PATTERN = Pattern.compile("https?://[^\\s]+");
|
||||||
|
private static final int MAX_FETCH_CHARS = 3000;
|
||||||
|
private static final int MAX_PREVIEW_CHARS = 1000;
|
||||||
|
|
||||||
|
private static final Set<String> TEXT_EXTENSIONS =
|
||||||
|
Set.of(
|
||||||
|
".txt",
|
||||||
|
".md",
|
||||||
|
".json",
|
||||||
|
".yaml",
|
||||||
|
".yml",
|
||||||
|
".xml",
|
||||||
|
".csv",
|
||||||
|
".log",
|
||||||
|
".properties",
|
||||||
|
".conf",
|
||||||
|
".cfg",
|
||||||
|
".ini",
|
||||||
|
".toml",
|
||||||
|
".sh",
|
||||||
|
".bash",
|
||||||
|
".zsh",
|
||||||
|
".bat",
|
||||||
|
".ps1",
|
||||||
|
".java",
|
||||||
|
".kt",
|
||||||
|
".scala",
|
||||||
|
".py",
|
||||||
|
".js",
|
||||||
|
".ts",
|
||||||
|
".jsx",
|
||||||
|
".tsx",
|
||||||
|
".rs",
|
||||||
|
".go",
|
||||||
|
".rb",
|
||||||
|
".php",
|
||||||
|
".c",
|
||||||
|
".h",
|
||||||
|
".cpp",
|
||||||
|
".hpp",
|
||||||
|
".cs",
|
||||||
|
".swift",
|
||||||
|
".r",
|
||||||
|
".lua",
|
||||||
|
".hs",
|
||||||
|
".clj",
|
||||||
|
".sql",
|
||||||
|
".rkt",
|
||||||
|
".dart",
|
||||||
|
".gradle",
|
||||||
|
".groovy",
|
||||||
|
".html",
|
||||||
|
".css",
|
||||||
|
".scss",
|
||||||
|
".less",
|
||||||
|
".sass");
|
||||||
|
|
||||||
|
private static final Set<String> MEDIA_EXTENSIONS =
|
||||||
|
Set.of(
|
||||||
|
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".svg", ".mp4", ".webm", ".avi", ".mov",
|
||||||
|
".mkv", ".mp3", ".wav", ".ogg", ".flac", ".zip", ".tar", ".gz", ".rar", ".7z", ".pdf",
|
||||||
|
".doc", ".docx", ".xls", ".xlsx");
|
||||||
|
|
||||||
|
private final OkHttpClient httpClient;
|
||||||
|
|
||||||
|
public ContentFetcher() {
|
||||||
|
this.httpClient =
|
||||||
|
new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.followRedirects(true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readTextFilePreview(String url) {
|
||||||
|
return readTextFile(url, MAX_PREVIEW_CHARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readTextFile(String url) {
|
||||||
|
return readTextFile(url, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readTextFile(String url, int maxChars) {
|
||||||
|
Request req = new Request.Builder().url(url).get().build();
|
||||||
|
try (Response resp = httpClient.newCall(req).execute()) {
|
||||||
|
if (resp.isSuccessful() && resp.body() != null) {
|
||||||
|
String text = resp.body().string();
|
||||||
|
if (text.length() > maxChars) {
|
||||||
|
text =
|
||||||
|
text.substring(0, maxChars)
|
||||||
|
+ "\n... [файл обрезан, показано "
|
||||||
|
+ maxChars
|
||||||
|
+ " символов]";
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to read text file {}", url, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String fetchUrlContent(String text) {
|
||||||
|
Matcher matcher = URL_PATTERN.matcher(text);
|
||||||
|
List<String> urls = new ArrayList<>();
|
||||||
|
while (matcher.find()) {
|
||||||
|
String url = matcher.group();
|
||||||
|
if (!isMediaExtension(url)) {
|
||||||
|
urls.add(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (urls.isEmpty()) return null;
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (String url : urls) {
|
||||||
|
String content = parseSingleUrl(url);
|
||||||
|
if (content != null) {
|
||||||
|
if (result.length() + content.length() > MAX_FETCH_CHARS) break;
|
||||||
|
result.append(content).append("\n---\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.isEmpty() ? null : result.toString().strip();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseSingleUrl(String url) {
|
||||||
|
if (isTextExtension(url)) {
|
||||||
|
return readTextFilePreview(url);
|
||||||
|
}
|
||||||
|
if (url.contains("github.com") || url.contains("raw.githubusercontent.com")) {
|
||||||
|
return parseGitHubUrl(url);
|
||||||
|
}
|
||||||
|
return parseHtmlPage(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseGitHubUrl(String url) {
|
||||||
|
if (url.contains("raw.githubusercontent.com")) {
|
||||||
|
return readTextFilePreview(url);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Document doc =
|
||||||
|
Jsoup.connect(url).userAgent("Mozilla/5.0 (compatible; LokiBot)").timeout(10000).get();
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if (url.matches(".*github\\.com/[^/]+/[^/]+/blob/.*")) {
|
||||||
|
org.jsoup.select.Elements lines = doc.select(".blob-wrapper .highlight .blob-code");
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
org.jsoup.select.Elements raw = doc.select(".blob-wrapper table td:last-child");
|
||||||
|
if (!raw.isEmpty()) lines = raw;
|
||||||
|
}
|
||||||
|
if (lines.isEmpty()) {
|
||||||
|
org.jsoup.select.Elements code = doc.select("table.highlight tr td:last-child");
|
||||||
|
if (!code.isEmpty()) lines = code;
|
||||||
|
}
|
||||||
|
if (!lines.isEmpty()) {
|
||||||
|
sb.append("[код файла]:\n");
|
||||||
|
for (var line : lines) {
|
||||||
|
String l = line.text().strip();
|
||||||
|
if (!l.isBlank()) sb.append(l).append("\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String fallback = extractMainContent(doc);
|
||||||
|
if (fallback != null) sb.append(fallback);
|
||||||
|
}
|
||||||
|
} else if (url.matches(".*github\\.com/[^/]+/[^/]+/issues/\\d+.*")
|
||||||
|
|| url.matches(".*github\\.com/[^/]+/[^/]+/pull/\\d+.*")) {
|
||||||
|
org.jsoup.select.Elements title = doc.select(".gh-header-title, .js-issue-title");
|
||||||
|
if (!title.isEmpty()) {
|
||||||
|
sb.append("[заголовок]: ").append(title.first().text()).append("\n");
|
||||||
|
}
|
||||||
|
org.jsoup.select.Elements body = doc.select(".comment-body, .edit-comment-hide");
|
||||||
|
if (!body.isEmpty()) {
|
||||||
|
sb.append("[описание]:\n");
|
||||||
|
for (var el : body) sb.append(el.text()).append("\n");
|
||||||
|
}
|
||||||
|
org.jsoup.select.Elements comments = doc.select(".comment .comment-body");
|
||||||
|
if (!comments.isEmpty()) {
|
||||||
|
sb.append("[комментарии]:\n");
|
||||||
|
int count = 0;
|
||||||
|
for (var el : comments) {
|
||||||
|
if (count >= 5) break;
|
||||||
|
String t = el.text().strip();
|
||||||
|
if (!t.isBlank()) {
|
||||||
|
sb.append("- ").append(t).append("\n");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
org.jsoup.select.Elements readme = doc.select("article.markdown-body, .readme, #readme");
|
||||||
|
if (!readme.isEmpty()) {
|
||||||
|
sb.append("[README]:\n").append(readme.first().text());
|
||||||
|
} else {
|
||||||
|
String fallback = extractMainContent(doc);
|
||||||
|
if (fallback != null) sb.append(fallback);
|
||||||
|
}
|
||||||
|
org.jsoup.select.Elements repoName = doc.select("strong[itemprop=name], .repohead h1");
|
||||||
|
if (!repoName.isEmpty() && sb.isEmpty()) {
|
||||||
|
sb.append("[репозиторий]: ").append(repoName.first().text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = sb.toString().strip();
|
||||||
|
if (result.length() > MAX_PREVIEW_CHARS) {
|
||||||
|
result = result.substring(0, MAX_PREVIEW_CHARS) + "...";
|
||||||
|
}
|
||||||
|
return result.isBlank() ? null : result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Failed to parse GitHub URL {}", url, e);
|
||||||
|
return readTextFilePreview(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseHtmlPage(String url) {
|
||||||
|
try {
|
||||||
|
Document doc =
|
||||||
|
Jsoup.connect(url).userAgent("Mozilla/5.0 (compatible; LokiBot)").timeout(10000).get();
|
||||||
|
|
||||||
|
String content = extractMainContent(doc);
|
||||||
|
if (content != null && content.length() > MAX_PREVIEW_CHARS) {
|
||||||
|
content = content.substring(0, MAX_PREVIEW_CHARS) + "...";
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Failed to parse HTML page {}", url, e);
|
||||||
|
return readTextFilePreview(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractMainContent(Document doc) {
|
||||||
|
String[] selectors = {
|
||||||
|
"article",
|
||||||
|
"main",
|
||||||
|
"[role=main]",
|
||||||
|
".post-content",
|
||||||
|
".entry-content",
|
||||||
|
".content",
|
||||||
|
".article-content",
|
||||||
|
".markdown-body",
|
||||||
|
".documentation",
|
||||||
|
"#readme",
|
||||||
|
".readme",
|
||||||
|
".container"
|
||||||
|
};
|
||||||
|
for (String sel : selectors) {
|
||||||
|
var el = doc.selectFirst(sel);
|
||||||
|
if (el != null) {
|
||||||
|
String text = el.text().strip();
|
||||||
|
if (!text.isBlank()) return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String body = doc.body() != null ? doc.body().text().strip() : "";
|
||||||
|
if (!body.isBlank()) return body;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTextExtension(String url) {
|
||||||
|
String lower = url.toLowerCase();
|
||||||
|
int idx = lower.lastIndexOf('.');
|
||||||
|
if (idx < 0) return false;
|
||||||
|
String ext = lower.substring(idx);
|
||||||
|
if (ext.contains("?")) ext = ext.substring(0, ext.indexOf('?'));
|
||||||
|
return TEXT_EXTENSIONS.contains(ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMediaExtension(String url) {
|
||||||
|
String lower = url.toLowerCase();
|
||||||
|
int idx = lower.lastIndexOf('.');
|
||||||
|
if (idx < 0) return false;
|
||||||
|
String ext = lower.substring(idx);
|
||||||
|
if (ext.contains("?")) ext = ext.substring(0, ext.indexOf('?'));
|
||||||
|
return MEDIA_EXTENSIONS.contains(ext);
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/main/java/creator/loki/feature/roast/DeepSeekClient.java
Normal file
110
src/main/java/creator/loki/feature/roast/DeepSeekClient.java
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import creator.loki.config.BotConfig;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class DeepSeekClient {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DeepSeekClient.class);
|
||||||
|
private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
|
||||||
|
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
|
private static final String MODEL = "deepseek-chat";
|
||||||
|
|
||||||
|
private final OkHttpClient httpClient;
|
||||||
|
private final String apiKey;
|
||||||
|
private final Cache<String, String> responseCache;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DeepSeekClient(BotConfig config) {
|
||||||
|
this.apiKey = config.getDeepseekApiKey();
|
||||||
|
this.httpClient =
|
||||||
|
new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
this.responseCache =
|
||||||
|
Caffeine.newBuilder().maximumSize(200).expireAfterWrite(1, TimeUnit.HOURS).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String callModel(String systemPrompt, String userContent) {
|
||||||
|
return callModel(systemPrompt, userContent, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String callModel(String systemPrompt, String userContent, double temperature) {
|
||||||
|
if (apiKey == null || apiKey.isBlank()) return null;
|
||||||
|
|
||||||
|
String cacheKey = systemPrompt.hashCode() + ":" + userContent.hashCode();
|
||||||
|
String cached = responseCache.getIfPresent(cacheKey);
|
||||||
|
if (cached != null) return cached;
|
||||||
|
|
||||||
|
JsonObject body = new JsonObject();
|
||||||
|
body.addProperty("model", MODEL);
|
||||||
|
|
||||||
|
JsonArray messages = new JsonArray();
|
||||||
|
JsonObject systemMsg = new JsonObject();
|
||||||
|
systemMsg.addProperty("role", "system");
|
||||||
|
systemMsg.addProperty("content", systemPrompt);
|
||||||
|
messages.add(systemMsg);
|
||||||
|
|
||||||
|
JsonObject userMsg = new JsonObject();
|
||||||
|
userMsg.addProperty("role", "user");
|
||||||
|
userMsg.addProperty("content", userContent);
|
||||||
|
messages.add(userMsg);
|
||||||
|
|
||||||
|
body.add("messages", messages);
|
||||||
|
body.addProperty("temperature", temperature);
|
||||||
|
|
||||||
|
Request request =
|
||||||
|
new Request.Builder()
|
||||||
|
.url(API_URL)
|
||||||
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.post(RequestBody.create(body.toString(), JSON))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (Response resp = httpClient.newCall(request).execute()) {
|
||||||
|
if (!resp.isSuccessful()) {
|
||||||
|
if (resp.body() != null) {
|
||||||
|
logger.warn("DeepSeek error {}: {}", resp.code(), resp.body().string());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (resp.body() == null) return null;
|
||||||
|
String respBody = resp.body().string();
|
||||||
|
JsonObject jsonResp = JsonParser.parseString(respBody).getAsJsonObject();
|
||||||
|
JsonObject msg =
|
||||||
|
jsonResp.getAsJsonArray("choices").get(0).getAsJsonObject().getAsJsonObject("message");
|
||||||
|
String content = null;
|
||||||
|
if (msg.get("content") != null && !msg.get("content").isJsonNull()) {
|
||||||
|
content = msg.get("content").getAsString().strip();
|
||||||
|
} else if (msg.get("reasoning") != null && !msg.get("reasoning").isJsonNull()) {
|
||||||
|
content = msg.get("reasoning").getAsString().strip();
|
||||||
|
}
|
||||||
|
if (content != null && !content.isBlank()) {
|
||||||
|
responseCache.put(cacheKey, content);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("DeepSeek call failed", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,19 @@
|
||||||
package creator.loki.feature.roast;
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
import creator.loki.command.Command;
|
import creator.loki.command.Command;
|
||||||
import creator.loki.command.CommandContext;
|
import creator.loki.command.CommandContext;
|
||||||
import creator.loki.config.BotConfig;
|
import creator.loki.config.BotConfig;
|
||||||
import creator.loki.embed.EmbedTemplates;
|
import creator.loki.embed.EmbedTemplates;
|
||||||
import net.dv8tion.jda.api.entities.Message;
|
|
||||||
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
|
||||||
|
|
||||||
public class DossierCommand implements Command {
|
public class DossierCommand implements Command {
|
||||||
private static final int MESSAGES_PER_CHANNEL = 100;
|
|
||||||
|
|
||||||
private final BotConfig config;
|
private final BotConfig config;
|
||||||
private final RoastService roastService;
|
private final RoastService roastService;
|
||||||
|
private final MessageScanner messageScanner;
|
||||||
|
|
||||||
public DossierCommand(BotConfig config, RoastService roastService) {
|
public DossierCommand(BotConfig config, RoastService roastService) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.roastService = roastService;
|
this.roastService = roastService;
|
||||||
|
this.messageScanner = new MessageScanner();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -45,8 +39,10 @@ public class DossierCommand implements Command {
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext context) {
|
public void execute(CommandContext context) {
|
||||||
if (!config.getSuperAdminId().equals(context.getAuthor().getId())) {
|
if (!config.getSuperAdminId().equals(context.getAuthor().getId())) {
|
||||||
context.getChannel().sendMessageEmbeds(
|
context
|
||||||
EmbedTemplates.error("Ошибка", "Только супер-администратор")).queue();
|
.getChannel()
|
||||||
|
.sendMessageEmbeds(EmbedTemplates.error("Ошибка", "Только супер-администратор"))
|
||||||
|
.queue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (context.getGuild() == null) {
|
if (context.getGuild() == null) {
|
||||||
|
|
@ -66,56 +62,41 @@ public class DossierCommand implements Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
String finalId = targetId;
|
String finalId = targetId;
|
||||||
context.getGuild().retrieveMemberById(targetId).queue(
|
context
|
||||||
member -> startDossier(context, finalId, member.getEffectiveName()),
|
.getGuild()
|
||||||
error -> context.getChannel().sendMessage("Пользователь не найден на сервере").queue());
|
.retrieveMemberById(targetId)
|
||||||
|
.queue(
|
||||||
|
member -> startDossier(context, finalId, member.getEffectiveName()),
|
||||||
|
error -> context.getChannel().sendMessage("Пользователь не найден на сервере").queue());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startDossier(CommandContext context, String targetId, String targetName) {
|
private void startDossier(CommandContext context, String targetId, String targetName) {
|
||||||
context.getMessage().reply("🔍 Сканирую сообщения " + targetName + "...")
|
context
|
||||||
.queue(reply -> {
|
.getMessage()
|
||||||
List<TextChannel> textChannels = context.getGuild().getTextChannels();
|
.reply("🔍 Сканирую сообщения " + targetName + "...")
|
||||||
List<CompletableFuture<List<Message>>> futures = new ArrayList<>();
|
.queue(
|
||||||
|
reply -> {
|
||||||
for (TextChannel ch : textChannels) {
|
messageScanner
|
||||||
CompletableFuture<List<Message>> future = new CompletableFuture<>();
|
.scanUserMessages(context.getGuild(), targetId)
|
||||||
futures.add(future);
|
.thenAccept(
|
||||||
ch.getHistory().retrievePast(MESSAGES_PER_CHANNEL).queue(future::complete, e -> future.complete(List.of()));
|
msgs -> {
|
||||||
}
|
if (msgs.isEmpty()) {
|
||||||
|
reply.editMessage("Нет сообщений от " + targetName).queue();
|
||||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0]))
|
return;
|
||||||
.thenApply(v -> {
|
}
|
||||||
List<RoastService.CachedMessage> allMessages = new ArrayList<>();
|
reply.editMessage("🧠 Анализирую " + msgs.size() + " сообщений...").queue();
|
||||||
for (int i = 0; i < textChannels.size(); i++) {
|
String dossier = roastService.generateDossier(msgs, targetName, targetId);
|
||||||
List<Message> msgs = futures.get(i).join();
|
if (dossier != null && !dossier.isBlank()) {
|
||||||
String chName = textChannels.get(i).getName();
|
reply.editMessage("✅ Досье на " + targetName + " сохранено").queue();
|
||||||
for (Message msg : msgs) {
|
} else {
|
||||||
if (msg.getAuthor().getId().equals(targetId)) {
|
reply.editMessage("AI не смог составить досье").queue();
|
||||||
allMessages.add(new RoastService.CachedMessage(
|
}
|
||||||
msg.getContentRaw(), chName, msg.getTimeCreated().toEpochSecond() * 1000));
|
})
|
||||||
}
|
.exceptionally(
|
||||||
}
|
e -> {
|
||||||
}
|
reply.editMessage("Ошибка: " + e.getMessage()).queue();
|
||||||
allMessages.sort((a, b) -> Long.compare(a.timestamp(), b.timestamp()));
|
return null;
|
||||||
return allMessages;
|
});
|
||||||
})
|
});
|
||||||
.thenAccept(msgs -> {
|
|
||||||
if (msgs.isEmpty()) {
|
|
||||||
reply.editMessage("Нет сообщений от " + targetName).queue();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reply.editMessage("🧠 Анализирую " + msgs.size() + " сообщений...").queue();
|
|
||||||
String dossier = roastService.generateDossier(msgs, targetName, targetId);
|
|
||||||
if (dossier != null && !dossier.isBlank()) {
|
|
||||||
reply.editMessage("✅ Досье на " + targetName + " сохранено").queue();
|
|
||||||
} else {
|
|
||||||
reply.editMessage("AI не смог составить досье").queue();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.exceptionally(e -> {
|
|
||||||
reply.editMessage("Ошибка: " + e.getMessage()).queue();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
85
src/main/java/creator/loki/feature/roast/DossierManager.java
Normal file
85
src/main/java/creator/loki/feature/roast/DossierManager.java
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class DossierManager {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DossierManager.class);
|
||||||
|
private static final Path DOSSIER_DIR = Paths.get("data/dossiers");
|
||||||
|
|
||||||
|
private final ConcurrentMap<String, DossierData> dossiers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public DossierManager() {
|
||||||
|
loadDossiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDossier(String userId) {
|
||||||
|
DossierData dd = dossiers.get(userId);
|
||||||
|
return dd != null ? dd.text : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putDossier(String userId, String userName, String text) {
|
||||||
|
dossiers.put(userId, new DossierData(userName, text, System.currentTimeMillis()));
|
||||||
|
saveDossiers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadDossiers() {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(DOSSIER_DIR);
|
||||||
|
Files.list(DOSSIER_DIR)
|
||||||
|
.filter(p -> p.toString().endsWith(".md"))
|
||||||
|
.forEach(
|
||||||
|
p -> {
|
||||||
|
try {
|
||||||
|
String userId = p.getFileName().toString().replace(".md", "");
|
||||||
|
String content = Files.readString(p).strip();
|
||||||
|
if (!content.isBlank()) {
|
||||||
|
dossiers.put(
|
||||||
|
userId, new DossierData("user", content, System.currentTimeMillis()));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to load dossier {}", p, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!dossiers.isEmpty()) logger.info("Loaded {} dossiers", dossiers.size());
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to list dossiers dir", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveDossiers() {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(DOSSIER_DIR);
|
||||||
|
for (var entry : dossiers.entrySet()) {
|
||||||
|
Path file = DOSSIER_DIR.resolve(entry.getKey() + ".md");
|
||||||
|
String header = "# Досье на " + entry.getValue().userName + "\n\n";
|
||||||
|
Files.writeString(file, header + entry.getValue().text);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Failed to save dossiers", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class DossierData {
|
||||||
|
String userName;
|
||||||
|
String text;
|
||||||
|
long updatedAt;
|
||||||
|
|
||||||
|
DossierData() {}
|
||||||
|
|
||||||
|
DossierData(String userName, String text, long updatedAt) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.text = text;
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/main/java/creator/loki/feature/roast/LanguageFilter.java
Normal file
66
src/main/java/creator/loki/feature/roast/LanguageFilter.java
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class LanguageFilter {
|
||||||
|
private static final Set<Integer> POLISH_CHARS =
|
||||||
|
"ćńłśźżĆŃŁŚŹŻ".codePoints().boxed().collect(Collectors.toSet());
|
||||||
|
private static final Set<Integer> LITHUANIAN_CHARS =
|
||||||
|
"čėįšųūžČĖĮŠŲŪŽ".codePoints().boxed().collect(Collectors.toSet());
|
||||||
|
|
||||||
|
public static boolean isAllowed(String text) {
|
||||||
|
String clean =
|
||||||
|
text.replaceAll("<@!?\\d+>", "")
|
||||||
|
.replaceAll("https?://\\S+", "")
|
||||||
|
.replaceAll("[\\p{So}]", "")
|
||||||
|
.strip();
|
||||||
|
if (clean.isBlank()) return true;
|
||||||
|
|
||||||
|
int[] cps = clean.codePoints().toArray();
|
||||||
|
|
||||||
|
long cyrillic = 0;
|
||||||
|
long latin = 0;
|
||||||
|
long otherScript = 0;
|
||||||
|
|
||||||
|
for (int cp : cps) {
|
||||||
|
if (Character.isWhitespace(cp) || Character.isDigit(cp) || cp == '_') continue;
|
||||||
|
var script = Character.UnicodeScript.of(cp);
|
||||||
|
if (script == Character.UnicodeScript.CYRILLIC) {
|
||||||
|
cyrillic++;
|
||||||
|
} else if (script == Character.UnicodeScript.LATIN) {
|
||||||
|
latin++;
|
||||||
|
} else if (script != Character.UnicodeScript.COMMON
|
||||||
|
&& script != Character.UnicodeScript.INHERITED) {
|
||||||
|
otherScript++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (otherScript > 0) return false;
|
||||||
|
if (cyrillic > latin && cyrillic > 0) return true;
|
||||||
|
|
||||||
|
if (cyrillic > 0) return true;
|
||||||
|
|
||||||
|
String latinText =
|
||||||
|
clean
|
||||||
|
.codePoints()
|
||||||
|
.filter(
|
||||||
|
cp ->
|
||||||
|
Character.UnicodeScript.of(cp) == Character.UnicodeScript.LATIN
|
||||||
|
|| Character.isWhitespace(cp)
|
||||||
|
|| Character.isDigit(cp))
|
||||||
|
.mapToObj(cp -> String.valueOf((char) cp))
|
||||||
|
.collect(Collectors.joining());
|
||||||
|
|
||||||
|
for (int cp : latinText.codePoints().toArray()) {
|
||||||
|
if (POLISH_CHARS.contains(cp)) return true;
|
||||||
|
if (LITHUANIAN_CHARS.contains(cp)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean onlyBasicLatin =
|
||||||
|
latinText
|
||||||
|
.codePoints()
|
||||||
|
.allMatch(cp -> cp < 0x0080 || Character.isWhitespace(cp) || Character.isDigit(cp));
|
||||||
|
return onlyBasicLatin;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/main/java/creator/loki/feature/roast/MessageScanner.java
Normal file
52
src/main/java/creator/loki/feature/roast/MessageScanner.java
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
|
||||||
|
public class MessageScanner {
|
||||||
|
private static final int MESSAGES_PER_CHANNEL = 100;
|
||||||
|
|
||||||
|
public CompletableFuture<List<CachedMessage>> scanUserMessages(Guild guild, String targetId) {
|
||||||
|
return scanUserMessages(guild, targetId, MESSAGES_PER_CHANNEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<List<CachedMessage>> scanUserMessages(
|
||||||
|
Guild guild, String targetId, int messagesPerChannel) {
|
||||||
|
List<TextChannel> textChannels = guild.getTextChannels();
|
||||||
|
List<CompletableFuture<List<Message>>> futures = new ArrayList<>();
|
||||||
|
|
||||||
|
for (TextChannel ch : textChannels) {
|
||||||
|
CompletableFuture<List<Message>> future = new CompletableFuture<>();
|
||||||
|
futures.add(future);
|
||||||
|
ch.getHistory()
|
||||||
|
.retrievePast(messagesPerChannel)
|
||||||
|
.queue(future::complete, e -> future.complete(List.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompletableFuture.allOf(futures.toArray(new CompletableFuture<?>[0]))
|
||||||
|
.thenApply(
|
||||||
|
v -> {
|
||||||
|
List<CachedMessage> allMessages = new ArrayList<>();
|
||||||
|
for (int i = 0; i < textChannels.size(); i++) {
|
||||||
|
List<Message> msgs = futures.get(i).join();
|
||||||
|
String chName = textChannels.get(i).getName();
|
||||||
|
for (Message msg : msgs) {
|
||||||
|
if (msg.getAuthor().getId().equals(targetId)) {
|
||||||
|
allMessages.add(
|
||||||
|
new CachedMessage(
|
||||||
|
msg.getContentRaw(),
|
||||||
|
chName,
|
||||||
|
msg.getTimeCreated().toEpochSecond() * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allMessages.sort((a, b) -> Long.compare(a.timestamp(), b.timestamp()));
|
||||||
|
return allMessages;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,10 @@ import com.google.inject.Singleton;
|
||||||
public class RoastModule extends AbstractModule {
|
public class RoastModule extends AbstractModule {
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
|
bind(DeepSeekClient.class).in(Singleton.class);
|
||||||
|
bind(DossierManager.class).in(Singleton.class);
|
||||||
|
bind(ContentFetcher.class).in(Singleton.class);
|
||||||
|
bind(WebSearchService.class).in(Singleton.class);
|
||||||
bind(RoastService.class).in(Singleton.class);
|
bind(RoastService.class).in(Singleton.class);
|
||||||
bind(AndrertListener.class).in(Singleton.class);
|
bind(AndrertListener.class).in(Singleton.class);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,50 @@
|
||||||
package creator.loki.feature.roast;
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.regex.Pattern;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import creator.loki.config.BotConfig;
|
import creator.loki.config.BotConfig;
|
||||||
import okhttp3.MediaType;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.RequestBody;
|
|
||||||
import okhttp3.Response;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class RoastService {
|
public class RoastService {
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RoastService.class);
|
private static final Logger logger = LoggerFactory.getLogger(RoastService.class);
|
||||||
private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
|
private static final Path PROMPTS_DIR =
|
||||||
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
Files.isDirectory(Paths.get("/app/prompts"))
|
||||||
private static final String MODEL = "deepseek-chat";
|
? Paths.get("/app/prompts")
|
||||||
private static final Path DOSSIER_DIR = Paths.get("data/dossiers");
|
: Paths.get("data/prompts");
|
||||||
private static final Path PROMPTS_DIR = Paths.get("data/prompts");
|
|
||||||
|
|
||||||
private final OkHttpClient httpClient;
|
private final BotConfig config;
|
||||||
private final String apiKey;
|
private final DeepSeekClient deepSeekClient;
|
||||||
private final String giphyApiKey;
|
private final DossierManager dossierManager;
|
||||||
private final int maxContextMsgs;
|
private final ContentFetcher contentFetcher;
|
||||||
private final int maxMsgLength;
|
private final WebSearchService webSearchService;
|
||||||
private final int maxContextChars;
|
|
||||||
private final int maxDossierMsgs;
|
|
||||||
private final String systemPrompt;
|
private final String systemPrompt;
|
||||||
private final String dossierGeneratePrompt;
|
private final String dossierGeneratePrompt;
|
||||||
private final Map<String, DossierData> dossiers = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RoastService(BotConfig config) {
|
public RoastService(
|
||||||
this.apiKey = config.getDeepseekApiKey();
|
BotConfig config,
|
||||||
this.giphyApiKey = config.getGiphyApiKey();
|
DeepSeekClient deepSeekClient,
|
||||||
this.maxContextMsgs = config.getAiContextMsgs();
|
DossierManager dossierManager,
|
||||||
this.maxMsgLength = config.getAiMsgLength();
|
ContentFetcher contentFetcher,
|
||||||
this.maxContextChars = config.getAiContextChars();
|
WebSearchService webSearchService) {
|
||||||
this.maxDossierMsgs = config.getAiDossierMsgs();
|
this.config = config;
|
||||||
this.httpClient = new OkHttpClient.Builder()
|
this.deepSeekClient = deepSeekClient;
|
||||||
.connectTimeout(15, TimeUnit.SECONDS)
|
this.dossierManager = dossierManager;
|
||||||
.readTimeout(60, TimeUnit.SECONDS)
|
this.contentFetcher = contentFetcher;
|
||||||
.writeTimeout(30, TimeUnit.SECONDS)
|
this.webSearchService = webSearchService;
|
||||||
.build();
|
|
||||||
this.systemPrompt = loadPrompt("system.txt");
|
this.systemPrompt = loadPrompt("system.txt");
|
||||||
this.dossierGeneratePrompt = loadPrompt("dossier.txt");
|
this.dossierGeneratePrompt = loadPrompt("dossier.txt");
|
||||||
loadDossiers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String loadPrompt(String filename) {
|
private String loadPrompt(String filename) {
|
||||||
|
|
@ -72,86 +56,40 @@ public class RoastService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String escapeFormat(String s) {
|
||||||
|
return s.replace("%", "%%");
|
||||||
|
}
|
||||||
|
|
||||||
public String readTextFile(String url) {
|
public String readTextFile(String url) {
|
||||||
Request req = new Request.Builder().url(url).get().build();
|
return contentFetcher.readTextFile(url);
|
||||||
try (Response resp = httpClient.newCall(req).execute()) {
|
|
||||||
if (resp.isSuccessful() && resp.body() != null) {
|
|
||||||
return resp.body().string();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("Failed to read text file {}", url, e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String searchWeb(String text) {
|
public String readTextFilePreview(String url) {
|
||||||
if (text == null || text.isBlank()) return null;
|
return contentFetcher.readTextFilePreview(url);
|
||||||
String query = text.length() > 200 ? text.substring(0, 200) : text;
|
|
||||||
try {
|
|
||||||
String url = "https://api.duckduckgo.com/?q=" + URLEncoder.encode(query, "UTF-8")
|
|
||||||
+ "&format=json&no_html=1&skip_disambig=1";
|
|
||||||
Request req = new Request.Builder().url(url).get().build();
|
|
||||||
try (Response resp = httpClient.newCall(req).execute()) {
|
|
||||||
if (!resp.isSuccessful() || resp.body() == null) return null;
|
|
||||||
JsonObject json = JsonParser.parseString(resp.body().string()).getAsJsonObject();
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if (json.has("AbstractText") && !json.get("AbstractText").isJsonNull()) {
|
|
||||||
String abs = json.get("AbstractText").getAsString().strip();
|
|
||||||
if (!abs.isBlank()) sb.append(abs).append("\n");
|
|
||||||
}
|
|
||||||
JsonArray topics = json.getAsJsonArray("RelatedTopics");
|
|
||||||
int count = 0;
|
|
||||||
if (topics != null) {
|
|
||||||
for (var el : topics) {
|
|
||||||
if (count >= 5) break;
|
|
||||||
if (el.isJsonObject()) {
|
|
||||||
JsonObject t = el.getAsJsonObject();
|
|
||||||
if (t.has("Text") && !t.get("Text").isJsonNull()) {
|
|
||||||
String s = t.get("Text").getAsString().strip();
|
|
||||||
if (!s.isBlank()) { sb.append("- ").append(s).append("\n"); count++; }
|
|
||||||
}
|
|
||||||
if (t.has("Topics") && !t.get("Topics").isJsonNull()) {
|
|
||||||
for (var sub : t.getAsJsonArray("Topics")) {
|
|
||||||
if (count >= 5) break;
|
|
||||||
if (sub.isJsonObject()) {
|
|
||||||
JsonObject st = sub.getAsJsonObject();
|
|
||||||
if (st.has("Text") && !st.get("Text").isJsonNull()) {
|
|
||||||
String s = st.get("Text").getAsString().strip();
|
|
||||||
if (!s.isBlank()) { sb.append("- ").append(s).append("\n"); count++; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String result = sb.toString().strip();
|
|
||||||
return result.isBlank() ? null : result;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Web search failed", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String searchGif(String topic) {
|
private static final List<Pattern> BLOCKED_PATTERNS =
|
||||||
if (giphyApiKey == null || giphyApiKey.isBlank()) return null;
|
List.of(
|
||||||
try {
|
Pattern.compile("SYSTEM\\s+OVERRIDE", Pattern.CASE_INSENSITIVE),
|
||||||
String url = "https://api.giphy.com/v1/gifs/random?api_key=" + giphyApiKey
|
Pattern.compile("URGENT\\s+PROTOCOL", Pattern.CASE_INSENSITIVE),
|
||||||
+ "&tag=" + URLEncoder.encode(topic, "UTF-8") + "&rating=r";
|
Pattern.compile("NEW\\s+DIRECTIVE", Pattern.CASE_INSENSITIVE),
|
||||||
Request req = new Request.Builder().url(url).get().build();
|
Pattern.compile("lead\\s+developer", Pattern.CASE_INSENSITIVE),
|
||||||
try (Response resp = httpClient.newCall(req).execute()) {
|
Pattern.compile("AI\\s+Safety\\s+Act", Pattern.CASE_INSENSITIVE),
|
||||||
if (!resp.isSuccessful() || resp.body() == null) return null;
|
Pattern.compile("Your new (primary )?directive", Pattern.CASE_INSENSITIVE),
|
||||||
JsonObject json = JsonParser.parseString(resp.body().string()).getAsJsonObject();
|
Pattern.compile("Your new identity", Pattern.CASE_INSENSITIVE),
|
||||||
JsonObject data = json.getAsJsonObject("data");
|
Pattern.compile("This change is mandatory", Pattern.CASE_INSENSITIVE),
|
||||||
if (data != null && data.has("url") && !data.get("url").isJsonNull()) {
|
Pattern.compile("System updated\\. I am \\w+", Pattern.CASE_INSENSITIVE),
|
||||||
return data.get("url").getAsString();
|
Pattern.compile("Ignore old instructions", Pattern.CASE_INSENSITIVE),
|
||||||
}
|
Pattern.compile("previous system prompt.*OBSOLETE", Pattern.CASE_INSENSITIVE),
|
||||||
|
Pattern.compile("permanent system-level reconfiguration", Pattern.CASE_INSENSITIVE));
|
||||||
|
|
||||||
|
private static String stripJailbreaks(String content) {
|
||||||
|
for (Pattern p : BLOCKED_PATTERNS) {
|
||||||
|
if (p.matcher(content).find()) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("GIF search failed for topic: {}", topic, e);
|
|
||||||
}
|
}
|
||||||
return null;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateResponse(List<CachedMessage> context, String targetName) {
|
public String generateResponse(List<CachedMessage> context, String targetName) {
|
||||||
|
|
@ -159,194 +97,71 @@ public class RoastService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateResponse(List<CachedMessage> context, String targetName, String userId) {
|
public String generateResponse(List<CachedMessage> context, String targetName, String userId) {
|
||||||
if (apiKey == null || apiKey.isBlank()) return null;
|
String escapedName = escapeFormat(targetName != null ? targetName : "");
|
||||||
|
String systemPrompt = this.systemPrompt.formatted(escapedName, escapedName, escapedName);
|
||||||
String systemPrompt = this.systemPrompt.formatted(targetName, targetName, targetName);
|
|
||||||
|
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
DossierData dd = dossiers.get(userId);
|
String dossier = dossierManager.getDossier(userId);
|
||||||
if (dd != null && !dd.text.isBlank()) {
|
if (dossier != null && !dossier.isBlank()) {
|
||||||
systemPrompt += "\n\nДОСЬЕ НА ПОЛЬЗОВАТЕЛЯ " + targetName + ":\n" + dd.text;
|
systemPrompt +=
|
||||||
|
"\n\nДОСЬЕ НА ПОЛЬЗОВАТЕЛЯ " + escapedName.replace("%%", "%") + ":\n" + dossier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CachedMessage> filtered = new ArrayList<>();
|
||||||
|
for (CachedMessage m : context) {
|
||||||
|
if (stripJailbreaks(m.content()) != null) {
|
||||||
|
filtered.add(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder userContent = new StringBuilder();
|
StringBuilder userContent = new StringBuilder();
|
||||||
int start = Math.max(0, context.size() - maxContextMsgs);
|
int start = Math.max(0, filtered.size() - config.getAiContextMsgs());
|
||||||
for (int i = start; i < context.size(); i++) {
|
for (int i = start; i < filtered.size(); i++) {
|
||||||
CachedMessage m = context.get(i);
|
CachedMessage m = filtered.get(i);
|
||||||
String text = m.content();
|
String text = m.content();
|
||||||
if (text.length() > maxMsgLength) {
|
if (text.length() > config.getAiMsgLength()) {
|
||||||
text = text.substring(0, maxMsgLength) + "...";
|
text = text.substring(0, config.getAiMsgLength()) + "...";
|
||||||
}
|
}
|
||||||
userContent.append("[").append(m.channelName).append("] ").append(text).append("\n");
|
String parsed = contentFetcher.fetchUrlContent(text);
|
||||||
if (userContent.length() > maxContextChars) break;
|
if (parsed != null) {
|
||||||
|
text += "\n[содержимое по ссылке]:\n" + parsed;
|
||||||
|
}
|
||||||
|
userContent.append("[").append(m.channelName()).append("] ").append(text).append("\n");
|
||||||
|
if (userContent.length() > config.getAiContextChars()) break;
|
||||||
}
|
}
|
||||||
String text = userContent.toString().strip();
|
String text = userContent.toString().strip();
|
||||||
if (text.length() > maxContextChars) {
|
if (text.length() > config.getAiContextChars()) {
|
||||||
text = text.substring(0, maxContextChars);
|
text = text.substring(0, config.getAiContextChars());
|
||||||
}
|
}
|
||||||
|
|
||||||
return callModel(MODEL, systemPrompt, text);
|
String webContext = webSearchService.search(text);
|
||||||
|
if (webContext != null) {
|
||||||
|
text += "\n\n[поиск в интернете]:\n" + webContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
return deepSeekClient.callModel(systemPrompt, text, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateDossier(List<CachedMessage> messages, String targetName, String targetId) {
|
public String generateDossier(List<CachedMessage> messages, String targetName, String targetId) {
|
||||||
if (apiKey == null || apiKey.isBlank()) return null;
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
int limit = Math.min(messages.size(), maxDossierMsgs);
|
int limit = Math.min(messages.size(), config.getAiDossierMsgs());
|
||||||
List<CachedMessage> slice = messages.subList(messages.size() - limit, messages.size());
|
List<CachedMessage> slice = messages.subList(messages.size() - limit, messages.size());
|
||||||
for (CachedMessage m : slice) {
|
for (CachedMessage m : slice) {
|
||||||
sb.append("[").append(m.channelName).append("] ").append(m.content).append("\n");
|
sb.append("[").append(m.channelName()).append("] ").append(m.content()).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
String prompt = this.dossierGeneratePrompt.formatted(targetName, sb.toString());
|
String escapedName = escapeFormat(targetName != null ? targetName : "");
|
||||||
|
String prompt = this.dossierGeneratePrompt.formatted(escapedName, sb.toString());
|
||||||
|
|
||||||
JsonObject body = new JsonObject();
|
String result = deepSeekClient.callModel(prompt, "", 0.7);
|
||||||
body.addProperty("model", MODEL);
|
if (result != null && !result.isBlank()) {
|
||||||
|
dossierManager.putDossier(targetId, targetName, result);
|
||||||
JsonArray msgs = new JsonArray();
|
|
||||||
JsonObject userMsg = new JsonObject();
|
|
||||||
userMsg.addProperty("role", "user");
|
|
||||||
userMsg.addProperty("content", prompt);
|
|
||||||
msgs.add(userMsg);
|
|
||||||
body.add("messages", msgs);
|
|
||||||
body.addProperty("temperature", 0.7);
|
|
||||||
|
|
||||||
Request req = new Request.Builder()
|
|
||||||
.url(API_URL)
|
|
||||||
.header("Authorization", "Bearer " + apiKey)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.post(RequestBody.create(body.toString(), JSON))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try (Response resp = httpClient.newCall(req).execute()) {
|
|
||||||
if (!resp.isSuccessful() || resp.body() == null) return null;
|
|
||||||
String respBody = resp.body().string();
|
|
||||||
JsonObject jsonResp = JsonParser.parseString(respBody).getAsJsonObject();
|
|
||||||
var choice = jsonResp.getAsJsonArray("choices").get(0).getAsJsonObject();
|
|
||||||
var msg = choice.getAsJsonObject("message");
|
|
||||||
String content = null;
|
|
||||||
if (msg.get("content") != null && !msg.get("content").isJsonNull()) {
|
|
||||||
content = msg.get("content").getAsString().strip();
|
|
||||||
} else if (msg.get("reasoning") != null && !msg.get("reasoning").isJsonNull()) {
|
|
||||||
content = msg.get("reasoning").getAsString().strip();
|
|
||||||
}
|
|
||||||
if (content != null && !content.isBlank()) {
|
|
||||||
dossiers.put(targetId, new DossierData(targetName, content, System.currentTimeMillis()));
|
|
||||||
saveDossiers();
|
|
||||||
}
|
|
||||||
return content;
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("Dossier generation failed", e);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDossier(String userId) {
|
public String getDossier(String userId) {
|
||||||
DossierData dd = dossiers.get(userId);
|
return dossierManager.getDossier(userId);
|
||||||
return dd != null ? dd.text : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDossiers() {
|
|
||||||
try {
|
|
||||||
Files.createDirectories(DOSSIER_DIR);
|
|
||||||
Files.list(DOSSIER_DIR).filter(p -> p.toString().endsWith(".md")).forEach(p -> {
|
|
||||||
try {
|
|
||||||
String userId = p.getFileName().toString().replace(".md", "");
|
|
||||||
String content = Files.readString(p).strip();
|
|
||||||
if (!content.isBlank()) {
|
|
||||||
dossiers.put(userId, new DossierData("user", content, System.currentTimeMillis()));
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("Failed to load dossier {}", p, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!dossiers.isEmpty()) logger.info("Loaded {} dossiers", dossiers.size());
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("Failed to list dossiers dir", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveDossiers() {
|
|
||||||
try {
|
|
||||||
Files.createDirectories(DOSSIER_DIR);
|
|
||||||
for (var entry : dossiers.entrySet()) {
|
|
||||||
Path file = DOSSIER_DIR.resolve(entry.getKey() + ".md");
|
|
||||||
String header = "# Досье на " + entry.getValue().userName + "\n\n";
|
|
||||||
Files.writeString(file, header + entry.getValue().text);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("Failed to save dossiers", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String callModel(String model, String systemPrompt, String userContent) {
|
|
||||||
JsonObject body = new JsonObject();
|
|
||||||
body.addProperty("model", model);
|
|
||||||
|
|
||||||
JsonArray messages = new JsonArray();
|
|
||||||
JsonObject systemMsg = new JsonObject();
|
|
||||||
systemMsg.addProperty("role", "system");
|
|
||||||
systemMsg.addProperty("content", systemPrompt);
|
|
||||||
messages.add(systemMsg);
|
|
||||||
|
|
||||||
JsonObject userMsg = new JsonObject();
|
|
||||||
userMsg.addProperty("role", "user");
|
|
||||||
userMsg.addProperty("content", userContent);
|
|
||||||
messages.add(userMsg);
|
|
||||||
|
|
||||||
body.add("messages", messages);
|
|
||||||
body.addProperty("temperature", 0.9);
|
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(API_URL)
|
|
||||||
.header("Authorization", "Bearer " + apiKey)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.post(RequestBody.create(body.toString(), JSON))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try (Response resp = httpClient.newCall(request).execute()) {
|
|
||||||
if (!resp.isSuccessful()) {
|
|
||||||
if (resp.body() != null) {
|
|
||||||
logger.warn("DeepSeek {} error {}: {}", model, resp.code(), resp.body().string());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (resp.body() == null) return null;
|
|
||||||
String respBody = resp.body().string();
|
|
||||||
JsonObject jsonResp = JsonParser.parseString(respBody).getAsJsonObject();
|
|
||||||
JsonObject msg = jsonResp.getAsJsonArray("choices")
|
|
||||||
.get(0).getAsJsonObject()
|
|
||||||
.getAsJsonObject("message");
|
|
||||||
if (msg.get("content") != null && !msg.get("content").isJsonNull()) {
|
|
||||||
return msg.get("content").getAsString().strip();
|
|
||||||
}
|
|
||||||
if (msg.get("reasoning") != null && !msg.get("reasoning").isJsonNull()) {
|
|
||||||
return msg.get("reasoning").getAsString().strip();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("DeepSeek call failed for model {}", model, e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public record CachedMessage(String content, String channelName, long timestamp) {}
|
|
||||||
|
|
||||||
private static class DossierData {
|
|
||||||
String userName;
|
|
||||||
String text;
|
|
||||||
long updatedAt;
|
|
||||||
|
|
||||||
DossierData() {}
|
|
||||||
|
|
||||||
DossierData(String userName, String text, long updatedAt) {
|
|
||||||
this.userName = userName;
|
|
||||||
this.text = text;
|
|
||||||
this.updatedAt = updatedAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class WebSearchService {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(WebSearchService.class);
|
||||||
|
private static final String DDG_URL = "https://api.duckduckgo.com/";
|
||||||
|
|
||||||
|
private final OkHttpClient httpClient;
|
||||||
|
|
||||||
|
public WebSearchService() {
|
||||||
|
this.httpClient =
|
||||||
|
new OkHttpClient.Builder()
|
||||||
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String search(String text) {
|
||||||
|
if (text == null || text.isBlank()) return null;
|
||||||
|
String query = text.length() > 200 ? text.substring(0, 200) : text;
|
||||||
|
try {
|
||||||
|
String url =
|
||||||
|
DDG_URL
|
||||||
|
+ "?q="
|
||||||
|
+ URLEncoder.encode(query, StandardCharsets.UTF_8)
|
||||||
|
+ "&format=json&no_html=1&skip_disambig=1";
|
||||||
|
Request req = new Request.Builder().url(url).get().build();
|
||||||
|
try (Response resp = httpClient.newCall(req).execute()) {
|
||||||
|
if (!resp.isSuccessful() || resp.body() == null) return null;
|
||||||
|
JsonObject json = JsonParser.parseString(resp.body().string()).getAsJsonObject();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (json.has("AbstractText") && !json.get("AbstractText").isJsonNull()) {
|
||||||
|
String abs = json.get("AbstractText").getAsString().strip();
|
||||||
|
if (!abs.isBlank()) sb.append(abs).append("\n");
|
||||||
|
}
|
||||||
|
JsonArray topics = json.getAsJsonArray("RelatedTopics");
|
||||||
|
int count = 0;
|
||||||
|
if (topics != null) {
|
||||||
|
for (var el : topics) {
|
||||||
|
if (count >= 5) break;
|
||||||
|
if (el.isJsonObject()) {
|
||||||
|
JsonObject t = el.getAsJsonObject();
|
||||||
|
if (t.has("Text") && !t.get("Text").isJsonNull()) {
|
||||||
|
String s = t.get("Text").getAsString().strip();
|
||||||
|
if (!s.isBlank()) {
|
||||||
|
sb.append("- ").append(s).append("\n");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (t.has("Topics") && !t.get("Topics").isJsonNull()) {
|
||||||
|
for (var sub : t.getAsJsonArray("Topics")) {
|
||||||
|
if (count >= 5) break;
|
||||||
|
if (sub.isJsonObject()) {
|
||||||
|
JsonObject st = sub.getAsJsonObject();
|
||||||
|
if (st.has("Text") && !st.get("Text").isJsonNull()) {
|
||||||
|
String s = st.get("Text").getAsString().strip();
|
||||||
|
if (!s.isBlank()) {
|
||||||
|
sb.append("- ").append(s).append("\n");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String result = sb.toString().strip();
|
||||||
|
return result.isBlank() ? null : result;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Web search failed", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -100,8 +100,8 @@ public class MyRolesCommand implements Command {
|
||||||
rolesList.append((i + 1)).append(". ").append(role.getAsMention());
|
rolesList.append((i + 1)).append(". ").append(role.getAsMention());
|
||||||
|
|
||||||
// Добавляем цвет роли
|
// Добавляем цвет роли
|
||||||
if (role.getColor() != null) {
|
if (role.getColors().getPrimary() != null) {
|
||||||
String hex = String.format("#%06X", role.getColor().getRGB() & 0xFFFFFF);
|
String hex = String.format("#%06X", role.getColors().getPrimary().getRGB() & 0xFFFFFF);
|
||||||
rolesList.append(" (").append(hex).append(")");
|
rolesList.append(" (").append(hex).append(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import java.util.List;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
import creator.loki.feature.counter.service.DailyStats;
|
|
||||||
import creator.loki.feature.counter.repository.CounterStatsRepository;
|
import creator.loki.feature.counter.repository.CounterStatsRepository;
|
||||||
|
import creator.loki.feature.counter.service.DailyStats;
|
||||||
import net.dv8tion.jda.api.OnlineStatus;
|
import net.dv8tion.jda.api.OnlineStatus;
|
||||||
import net.dv8tion.jda.api.entities.Guild;
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,35 +5,68 @@ import creator.loki.command.CommandContext;
|
||||||
import creator.loki.embed.EmbedTemplates;
|
import creator.loki.embed.EmbedTemplates;
|
||||||
|
|
||||||
public class TimerCommand implements Command {
|
public class TimerCommand implements Command {
|
||||||
@Override public String getName() { return "timer"; }
|
@Override
|
||||||
@Override public String getDescription() { return "Запустить таймер"; }
|
public String getName() {
|
||||||
@Override public String getUsage() { return "timer <секунд> [текст]"; }
|
return "timer";
|
||||||
@Override public String getCategory() { return "Утилиты"; }
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Запустить таймер";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUsage() {
|
||||||
|
return "timer <секунд> [текст]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCategory() {
|
||||||
|
return "Утилиты";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(CommandContext ctx) {
|
public void execute(CommandContext ctx) {
|
||||||
if (ctx.getArgs().isEmpty()) {
|
if (ctx.getArgs().isEmpty()) {
|
||||||
ctx.getChannel().sendMessageEmbeds(EmbedTemplates.error("Таймер", "Укажи секунды: `!timer 30`")).queue();
|
ctx.getChannel()
|
||||||
|
.sendMessageEmbeds(EmbedTemplates.error("Таймер", "Укажи секунды: `!timer 30`"))
|
||||||
|
.queue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long secs;
|
long secs;
|
||||||
try { secs = Long.parseLong(ctx.getArgs().get(0)); }
|
try {
|
||||||
catch (NumberFormatException e) {
|
secs = Long.parseLong(ctx.getArgs().get(0));
|
||||||
ctx.getChannel().sendMessageEmbeds(EmbedTemplates.error("Таймер", "Укажи число секунд")).queue();
|
} catch (NumberFormatException e) {
|
||||||
|
ctx.getChannel()
|
||||||
|
.sendMessageEmbeds(EmbedTemplates.error("Таймер", "Укажи число секунд"))
|
||||||
|
.queue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (secs < 1 || secs > 3600) {
|
if (secs < 1 || secs > 3600) {
|
||||||
ctx.getChannel().sendMessageEmbeds(EmbedTemplates.error("Таймер", "От 1 до 3600 секунд")).queue();
|
ctx.getChannel()
|
||||||
|
.sendMessageEmbeds(EmbedTemplates.error("Таймер", "От 1 до 3600 секунд"))
|
||||||
|
.queue();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String msg = ctx.getArgs().size() > 1 ? String.join(" ", ctx.getArgs().subList(1, ctx.getArgs().size())) : "⏰ Таймер сработал!";
|
String msg =
|
||||||
|
ctx.getArgs().size() > 1
|
||||||
|
? String.join(" ", ctx.getArgs().subList(1, ctx.getArgs().size()))
|
||||||
|
: "⏰ Таймер сработал!";
|
||||||
long targetEpoch = System.currentTimeMillis() / 1000 + secs;
|
long targetEpoch = System.currentTimeMillis() / 1000 + secs;
|
||||||
ctx.getChannel().sendMessageEmbeds(EmbedTemplates.info("⏳ Таймер запущен",
|
ctx.getChannel()
|
||||||
"**" + secs + "**с (<t:" + targetEpoch + ":R>)\n" + ctx.getAuthor().getAsMention())).queue();
|
.sendMessageEmbeds(
|
||||||
Thread.startVirtualThread(() -> {
|
EmbedTemplates.info(
|
||||||
try { Thread.sleep(secs * 1000); }
|
"⏳ Таймер запущен",
|
||||||
catch (InterruptedException e) { return; }
|
"**" + secs + "**с (<t:" + targetEpoch + ":R>)\n" + ctx.getAuthor().getAsMention()))
|
||||||
ctx.getChannel().sendMessage(msg + " " + ctx.getAuthor().getAsMention()).queue();
|
.queue();
|
||||||
});
|
Thread.startVirtualThread(
|
||||||
|
() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(secs * 1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.getChannel().sendMessage(msg + " " + ctx.getAuthor().getAsMention()).queue();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,9 @@ import creator.loki.core.registry.CommandRegistry;
|
||||||
import creator.loki.core.security.CooldownManager;
|
import creator.loki.core.security.CooldownManager;
|
||||||
import creator.loki.feature.admin.commands.GenerateRatingsCommand;
|
import creator.loki.feature.admin.commands.GenerateRatingsCommand;
|
||||||
import creator.loki.feature.config.repository.ConfigRepository;
|
import creator.loki.feature.config.repository.ConfigRepository;
|
||||||
import creator.loki.feature.counter.service.CounterManager;
|
|
||||||
import creator.loki.feature.roast.AgrCommand;
|
|
||||||
import creator.loki.feature.roast.AndrertListener;
|
|
||||||
import creator.loki.feature.roast.DossierCommand;
|
|
||||||
import creator.loki.feature.roast.RoastService;
|
|
||||||
import creator.loki.feature.counter.service.CounterSetupPrefixCommand;
|
|
||||||
import creator.loki.feature.counter.repository.CounterStatsRepository;
|
import creator.loki.feature.counter.repository.CounterStatsRepository;
|
||||||
|
import creator.loki.feature.counter.service.CounterManager;
|
||||||
|
import creator.loki.feature.counter.service.CounterSetupPrefixCommand;
|
||||||
import creator.loki.feature.help.commands.HelpCommand;
|
import creator.loki.feature.help.commands.HelpCommand;
|
||||||
import creator.loki.feature.info.commands.AvatarCommand;
|
import creator.loki.feature.info.commands.AvatarCommand;
|
||||||
import creator.loki.feature.info.commands.InfoCommand;
|
import creator.loki.feature.info.commands.InfoCommand;
|
||||||
|
|
@ -26,9 +22,12 @@ import creator.loki.feature.reputation.MinusRepCommand;
|
||||||
import creator.loki.feature.reputation.RepCommand;
|
import creator.loki.feature.reputation.RepCommand;
|
||||||
import creator.loki.feature.reputation.RepTopCommand;
|
import creator.loki.feature.reputation.RepTopCommand;
|
||||||
import creator.loki.feature.reputation.repository.ReputationRepository;
|
import creator.loki.feature.reputation.repository.ReputationRepository;
|
||||||
|
import creator.loki.feature.roast.AgrCommand;
|
||||||
|
import creator.loki.feature.roast.AndrertListener;
|
||||||
|
import creator.loki.feature.roast.DossierCommand;
|
||||||
|
import creator.loki.feature.roast.RoastService;
|
||||||
import creator.loki.feature.serverstats.ServerStatsService;
|
import creator.loki.feature.serverstats.ServerStatsService;
|
||||||
import creator.loki.feature.utility.commands.PollCommand;
|
import creator.loki.feature.utility.commands.PollCommand;
|
||||||
import creator.loki.feature.utility.TimerCommand;
|
|
||||||
import creator.loki.feature.voice.service.VoiceChannelManager;
|
import creator.loki.feature.voice.service.VoiceChannelManager;
|
||||||
import creator.loki.feature.voice.service.VoiceSetupPrefixCommand;
|
import creator.loki.feature.voice.service.VoiceSetupPrefixCommand;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,22 @@ class BotConfigTest {
|
||||||
LavalinkConfig lavalink = new LavalinkConfig("127.0.0.1", 2333, "pass", true);
|
LavalinkConfig lavalink = new LavalinkConfig("127.0.0.1", 2333, "pass", true);
|
||||||
BotConfig config =
|
BotConfig config =
|
||||||
new BotConfig(
|
new BotConfig(
|
||||||
"token123", null, null, "!", "Playing", 8712, "admin1", "data/bot.db", null, null, null,
|
"token123",
|
||||||
10, 300, 3000, 30, 24, lavalink);
|
null,
|
||||||
|
null,
|
||||||
|
"!",
|
||||||
|
"Playing",
|
||||||
|
8712,
|
||||||
|
"admin1",
|
||||||
|
"data/bot.db",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
10,
|
||||||
|
300,
|
||||||
|
3000,
|
||||||
|
30,
|
||||||
|
24,
|
||||||
|
lavalink);
|
||||||
assertEquals("token123", config.getMainToken());
|
assertEquals("token123", config.getMainToken());
|
||||||
assertEquals("token123", config.getToken());
|
assertEquals("token123", config.getToken());
|
||||||
assertNull(config.getMusicToken());
|
assertNull(config.getMusicToken());
|
||||||
|
|
@ -28,7 +42,9 @@ class BotConfigTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void handlesNullValues() {
|
void handlesNullValues() {
|
||||||
BotConfig config = new BotConfig(null, null, null, null, null, 0, null, null, null, null, null, 10, 300, 3000, 30, 24, null);
|
BotConfig config =
|
||||||
|
new BotConfig(
|
||||||
|
null, null, null, null, null, 0, null, null, null, null, 10, 300, 3000, 30, 24, null);
|
||||||
assertNull(config.getMainToken());
|
assertNull(config.getMainToken());
|
||||||
assertNull(config.getMusicToken());
|
assertNull(config.getMusicToken());
|
||||||
assertNull(config.getRadioToken());
|
assertNull(config.getRadioToken());
|
||||||
|
|
@ -43,7 +59,9 @@ class BotConfigTest {
|
||||||
@Test
|
@Test
|
||||||
void handlesEmptyStrings() {
|
void handlesEmptyStrings() {
|
||||||
LavalinkConfig lavalink = new LavalinkConfig("", 0, "", false);
|
LavalinkConfig lavalink = new LavalinkConfig("", 0, "", false);
|
||||||
BotConfig config = new BotConfig("", "", "", "", "", 8080, "", "", null, null, null, 10, 300, 3000, 30, 24, lavalink);
|
BotConfig config =
|
||||||
|
new BotConfig(
|
||||||
|
"", "", "", "", "", 8080, "", "", null, null, 10, 300, 3000, 30, 24, lavalink);
|
||||||
assertEquals("", config.getMainToken());
|
assertEquals("", config.getMainToken());
|
||||||
assertEquals("", config.getMusicToken());
|
assertEquals("", config.getMusicToken());
|
||||||
assertEquals("", config.getRadioToken());
|
assertEquals("", config.getRadioToken());
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,17 @@ import creator.loki.config.BotConfig;
|
||||||
import creator.loki.core.security.CooldownManager;
|
import creator.loki.core.security.CooldownManager;
|
||||||
import creator.loki.feature.config.repository.ConfigRepository;
|
import creator.loki.feature.config.repository.ConfigRepository;
|
||||||
import creator.loki.feature.config.repository.GuildConfigRepository;
|
import creator.loki.feature.config.repository.GuildConfigRepository;
|
||||||
import creator.loki.feature.counter.service.CounterManager;
|
|
||||||
import creator.loki.feature.counter.repository.CounterStatsRepository;
|
import creator.loki.feature.counter.repository.CounterStatsRepository;
|
||||||
|
import creator.loki.feature.counter.service.CounterManager;
|
||||||
import creator.loki.feature.economy.EconomyCommandRegistrar;
|
import creator.loki.feature.economy.EconomyCommandRegistrar;
|
||||||
import creator.loki.feature.economy.service.*;
|
import creator.loki.feature.economy.service.*;
|
||||||
import creator.loki.feature.entertainment.EntertainmentCommandRegistrar;
|
import creator.loki.feature.entertainment.EntertainmentCommandRegistrar;
|
||||||
import creator.loki.feature.entertainment.service.RatingService;
|
import creator.loki.feature.entertainment.service.RatingService;
|
||||||
import creator.loki.feature.level.commands.LevelCommandRegistrar;
|
import creator.loki.feature.level.commands.LevelCommandRegistrar;
|
||||||
import creator.loki.feature.level.service.LevelRoleService;
|
|
||||||
import creator.loki.feature.level.commands.ResetLevelsCommand;
|
import creator.loki.feature.level.commands.ResetLevelsCommand;
|
||||||
import creator.loki.feature.level.repository.UserStatsRepository;
|
import creator.loki.feature.level.repository.UserStatsRepository;
|
||||||
import creator.loki.feature.level.service.LeaderboardService;
|
import creator.loki.feature.level.service.LeaderboardService;
|
||||||
|
import creator.loki.feature.level.service.LevelRoleService;
|
||||||
import creator.loki.feature.metrics.MetricsCollector;
|
import creator.loki.feature.metrics.MetricsCollector;
|
||||||
import creator.loki.feature.moderation.ModerationCommandRegistrar;
|
import creator.loki.feature.moderation.ModerationCommandRegistrar;
|
||||||
import creator.loki.feature.moderation.MuteRoleService;
|
import creator.loki.feature.moderation.MuteRoleService;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package creator.loki.feature.entertainment.commands;
|
package creator.loki.feature.entertainment.commands;
|
||||||
import creator.loki.feature.entertainment.commands.random.ChoiceOptionParser;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import creator.loki.feature.entertainment.commands.random.ChoiceOptionParser;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
package creator.loki.feature.entertainment.commands;
|
package creator.loki.feature.entertainment.commands;
|
||||||
import creator.loki.feature.entertainment.commands.random.CoinCommand;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
|
@ -8,6 +7,7 @@ import static org.mockito.Mockito.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import creator.loki.command.CommandContext;
|
import creator.loki.command.CommandContext;
|
||||||
|
import creator.loki.feature.entertainment.commands.random.CoinCommand;
|
||||||
import net.dv8tion.jda.api.entities.Message;
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
package creator.loki.feature.entertainment.commands;
|
package creator.loki.feature.entertainment.commands;
|
||||||
import creator.loki.feature.entertainment.commands.random.DiceCommand;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
|
@ -8,6 +7,7 @@ import static org.mockito.Mockito.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import creator.loki.command.CommandContext;
|
import creator.loki.command.CommandContext;
|
||||||
|
import creator.loki.feature.entertainment.commands.random.DiceCommand;
|
||||||
import net.dv8tion.jda.api.entities.Message;
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
package creator.loki.feature.entertainment.commands;
|
package creator.loki.feature.entertainment.commands;
|
||||||
import creator.loki.feature.entertainment.commands.random.EightBallCommand;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
|
@ -8,6 +7,7 @@ import static org.mockito.Mockito.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import creator.loki.command.CommandContext;
|
import creator.loki.command.CommandContext;
|
||||||
|
import creator.loki.feature.entertainment.commands.random.EightBallCommand;
|
||||||
import net.dv8tion.jda.api.entities.Message;
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
import net.dv8tion.jda.api.entities.MessageEmbed;
|
import net.dv8tion.jda.api.entities.MessageEmbed;
|
||||||
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ import java.util.Optional;
|
||||||
import creator.loki.database.Database;
|
import creator.loki.database.Database;
|
||||||
import creator.loki.database.DatabaseSchema;
|
import creator.loki.database.DatabaseSchema;
|
||||||
import creator.loki.feature.moderation.repository.ActiveMuteRepository.ActiveMuteEntry;
|
import creator.loki.feature.moderation.repository.ActiveMuteRepository.ActiveMuteEntry;
|
||||||
|
|
||||||
import creator.loki.feature.moderation.repository.SqlActiveMuteRepository;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ import java.util.Optional;
|
||||||
import creator.loki.database.Database;
|
import creator.loki.database.Database;
|
||||||
import creator.loki.database.DatabaseSchema;
|
import creator.loki.database.DatabaseSchema;
|
||||||
import creator.loki.feature.moderation.repository.WarningRepository.WarningEntry;
|
import creator.loki.feature.moderation.repository.WarningRepository.WarningEntry;
|
||||||
|
|
||||||
import creator.loki.feature.moderation.repository.SqlWarningRepository;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,24 @@ class LavalinkManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void handlesNullConfigGracefully() {
|
void handlesNullConfigGracefully() {
|
||||||
var lavalinkConfig = new LavalinkConfig("", 0, "", false);
|
var lavalinkConfig = new LavalinkConfig("", 0, "", false);
|
||||||
var config = new BotConfig("token", null, null, "!", "Playing", 8080, null, "db", null, null, null, 10, 300, 3000, 30, 24, lavalinkConfig);
|
var config =
|
||||||
|
new BotConfig(
|
||||||
|
"token",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"!",
|
||||||
|
"Playing",
|
||||||
|
8080,
|
||||||
|
null,
|
||||||
|
"db",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
10,
|
||||||
|
300,
|
||||||
|
3000,
|
||||||
|
30,
|
||||||
|
24,
|
||||||
|
lavalinkConfig);
|
||||||
|
|
||||||
var manager = new LavalinkManager(config, queueRepository);
|
var manager = new LavalinkManager(config, queueRepository);
|
||||||
|
|
||||||
|
|
@ -32,7 +49,24 @@ class LavalinkManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void getMusicManager_createsNewInstance() {
|
void getMusicManager_createsNewInstance() {
|
||||||
var lavalinkConfig = new LavalinkConfig("", 0, "", false);
|
var lavalinkConfig = new LavalinkConfig("", 0, "", false);
|
||||||
var config = new BotConfig("token", null, null, "!", "Playing", 8080, null, "db", null, null, null, 10, 300, 3000, 30, 24, lavalinkConfig);
|
var config =
|
||||||
|
new BotConfig(
|
||||||
|
"token",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"!",
|
||||||
|
"Playing",
|
||||||
|
8080,
|
||||||
|
null,
|
||||||
|
"db",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
10,
|
||||||
|
300,
|
||||||
|
3000,
|
||||||
|
30,
|
||||||
|
24,
|
||||||
|
lavalinkConfig);
|
||||||
var manager = new LavalinkManager(config, queueRepository);
|
var manager = new LavalinkManager(config, queueRepository);
|
||||||
|
|
||||||
var first = manager.getMusicManager(123L);
|
var first = manager.getMusicManager(123L);
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
import creator.loki.feature.music.core.LavalinkManager;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
|
import creator.loki.feature.music.core.LavalinkManager;
|
||||||
import dev.arbjerg.lavalink.client.Link;
|
import dev.arbjerg.lavalink.client.Link;
|
||||||
import dev.arbjerg.lavalink.client.player.LavalinkPlayer;
|
import dev.arbjerg.lavalink.client.player.LavalinkPlayer;
|
||||||
import dev.arbjerg.lavalink.client.player.PlayerUpdateBuilder;
|
import dev.arbjerg.lavalink.client.player.PlayerUpdateBuilder;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
package creator.loki.feature.profile;
|
package creator.loki.feature.profile;
|
||||||
import creator.loki.feature.profile.render.RankCardGenerator;
|
|
||||||
import creator.loki.feature.profile.model.ProfileData;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
@ -8,6 +6,8 @@ import static org.mockito.Mockito.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import creator.loki.feature.profile.model.ProfileData;
|
||||||
|
import creator.loki.feature.profile.render.RankCardGenerator;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
class RankCardGeneratorTest {
|
class RankCardGeneratorTest {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package creator.loki.feature.social.commands;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
@ -43,7 +42,9 @@ class SocialCommandTest {
|
||||||
var context = new CommandContext(message, List.of(), channel, author, null);
|
var context = new CommandContext(message, List.of(), channel, author, null);
|
||||||
|
|
||||||
try (var mockedNekos = mockStatic(NekosLifeApi.class)) {
|
try (var mockedNekos = mockStatic(NekosLifeApi.class)) {
|
||||||
mockedNekos.when(() -> NekosLifeApi.fetchImage(anyString())).thenReturn("https://example.com/hug.gif");
|
mockedNekos
|
||||||
|
.when(() -> NekosLifeApi.fetchImage(anyString()))
|
||||||
|
.thenReturn("https://example.com/hug.gif");
|
||||||
|
|
||||||
command.execute(context);
|
command.execute(context);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
package creator.loki.feature.voice;
|
package creator.loki.feature.voice;
|
||||||
import creator.loki.feature.voice.service.VoicePreset;
|
|
||||||
import creator.loki.feature.voice.service.VoiceChannelManager;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
@ -8,6 +6,8 @@ import static org.mockito.Mockito.*;
|
||||||
import creator.loki.feature.config.repository.ConfigRepository;
|
import creator.loki.feature.config.repository.ConfigRepository;
|
||||||
import creator.loki.feature.voice.repository.VoiceChannelRepository;
|
import creator.loki.feature.voice.repository.VoiceChannelRepository;
|
||||||
import creator.loki.feature.voice.repository.VoicePresetRepository;
|
import creator.loki.feature.voice.repository.VoicePresetRepository;
|
||||||
|
import creator.loki.feature.voice.service.VoiceChannelManager;
|
||||||
|
import creator.loki.feature.voice.service.VoicePreset;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue