refactor: BotModule SRP, BlackjackService, FeatureTables, DatabaseMigration

- BotModule 149→63: @Provides вынесены в DatabaseModule
- BlackjackService 172→152, FeatureTables 193→147, DatabaseMigration 193→149
- ShutdownManager (118) уже ≤150
This commit is contained in:
loki5512344 2026-07-04 19:19:13 +02:00
parent 0d916fbd96
commit f6b037921b
11 changed files with 435 additions and 503 deletions

View file

@ -1,11 +1,9 @@
package creator.loki.database; package creator.loki.database;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Map; import java.util.Map;
@ -15,7 +13,6 @@ import creator.loki.util.LoggerDecorator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** Миграция данных из JSON в SQLite */
public class DatabaseMigration { public class DatabaseMigration {
private static final Logger logger = LoggerFactory.getLogger(DatabaseMigration.class); private static final Logger logger = LoggerFactory.getLogger(DatabaseMigration.class);
private final Database database; private final Database database;
@ -25,80 +22,51 @@ public class DatabaseMigration {
this.database = database; this.database = database;
} }
/** Мигрировать данные из JSON */
public void migrateFromJson(String jsonPath) { public void migrateFromJson(String jsonPath) {
LoggerDecorator.database(logger, "Начало миграции данных из JSON: " + jsonPath); LoggerDecorator.database(logger, "Начало миграции данных из JSON: " + jsonPath);
if (!new java.io.File(jsonPath).exists()) { if (!new java.io.File(jsonPath).exists()) {
logger.info("No legacy data file found at {}, skipping migration", jsonPath); logger.info("No legacy data file found at {}, skipping migration", jsonPath);
return; return;
} }
var data = parseJson(jsonPath);
try (InputStreamReader reader = if (data == null) return;
new InputStreamReader(new FileInputStream(jsonPath), StandardCharsets.UTF_8)) { try (var chk = database.prepare("SELECT value FROM config WHERE key = 'migrated'")) {
Type type = new TypeToken<Map<String, Object>>() {}.getType(); var rs = chk.executeQuery();
Map<String, Object> data = gson.fromJson(reader, type); if (rs.next() && "true".equals(rs.getString("value"))) return;
} catch (SQLException ignored) {
if (data == null) {
LoggerDecorator.warn(logger, "JSON файл пустой или не существует");
return;
}
if (isMigrated()) {
LoggerDecorator.database(logger, "Данные уже мигрированы, пропускаем");
return;
}
migrateUserStats(data);
migrateReputation(data);
migrateShipPairs(data);
migrateCustomRoles(data);
markMigrated();
LoggerDecorator.success(logger, "Миграция завершена успешно");
} catch (Exception e) {
LoggerDecorator.error(logger, "Ошибка миграции данных", e);
} }
} migrateUserStats(data);
migrateReputation(data);
private boolean isMigrated() { migrateShipPairs(data);
try (PreparedStatement stmt = migrateCustomRoles(data);
database.prepare("SELECT value FROM config WHERE key = 'migrated'")) { var sql = "INSERT OR REPLACE INTO config (key, value) VALUES ('migrated', 'true')";
ResultSet rs = stmt.executeQuery(); try (var p = database.prepare(sql)) {
return rs.next() && "true".equals(rs.getString("value")); p.executeUpdate();
} catch (SQLException e) {
return false;
}
}
private void markMigrated() {
try (PreparedStatement stmt =
database.prepare(
"INSERT OR REPLACE INTO config (key, value) VALUES ('migrated', 'true')")) {
stmt.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
LoggerDecorator.error(logger, "Ошибка сохранения статуса миграции", e); LoggerDecorator.error(logger, "Ошибка сохранения статуса миграции", e);
} }
LoggerDecorator.success(logger, "Миграция завершена успешно");
}
private Map<String, Object> parseJson(String path) {
try (var reader = new InputStreamReader(new FileInputStream(path), UTF_8)) {
var type = new TypeToken<Map<String, Object>>() {}.getType();
return (Map<String, Object>) gson.fromJson(reader, type);
} catch (Exception e) {
LoggerDecorator.error(logger, "Ошибка миграции данных", e);
return null;
}
} }
@SuppressWarnings("unchecked")
private void migrateUserStats(Map<String, Object> data) { private void migrateUserStats(Map<String, Object> data) {
Map<String, Object> userStats = (Map<String, Object>) data.get("userStats"); var userStats = (Map<String, Object>) data.get("userStats");
if (userStats == null) return; if (userStats == null) return;
LoggerDecorator.database(logger, "Миграция userStats: " + userStats.size() + " записей"); LoggerDecorator.database(logger, "Миграция userStats: " + userStats.size() + " записей");
try (var stmt =
String sql = database.prepare(
""" "INSERT OR REPLACE INTO user_stats (user_id, level, total_xp, message_count, voice_minutes, last_message_time, last_voice_join) VALUES (?, ?, ?, ?, ?, ?, ?)")) {
INSERT OR REPLACE INTO user_stats for (var entry : userStats.entrySet()) {
(user_id, level, total_xp, message_count, voice_minutes, last_message_time, last_voice_join) var stats = (Map<String, Object>) entry.getValue();
VALUES (?, ?, ?, ?, ?, ?, ?)""";
try (PreparedStatement stmt = database.prepare(sql)) {
for (Map.Entry<String, Object> entry : userStats.entrySet()) {
Map<String, Object> stats = (Map<String, Object>) entry.getValue();
stmt.setString(1, (String) stats.get("userId")); stmt.setString(1, (String) stats.get("userId"));
stmt.setInt(2, ((Number) stats.get("level")).intValue()); stmt.setInt(2, ((Number) stats.get("level")).intValue());
stmt.setInt(3, ((Number) stats.get("totalXP")).intValue()); stmt.setInt(3, ((Number) stats.get("totalXP")).intValue());
@ -106,7 +74,6 @@ public class DatabaseMigration {
stmt.setLong(5, ((Number) stats.get("voiceMinutes")).longValue()); stmt.setLong(5, ((Number) stats.get("voiceMinutes")).longValue());
stmt.setLong(6, ((Number) stats.get("lastMessageTime")).longValue()); stmt.setLong(6, ((Number) stats.get("lastMessageTime")).longValue());
stmt.setLong(7, ((Number) stats.get("lastVoiceJoin")).longValue()); stmt.setLong(7, ((Number) stats.get("lastVoiceJoin")).longValue());
stmt.executeUpdate(); stmt.executeUpdate();
} }
LoggerDecorator.success(logger, "userStats мигрированы"); LoggerDecorator.success(logger, "userStats мигрированы");
@ -115,19 +82,16 @@ public class DatabaseMigration {
} }
} }
@SuppressWarnings("unchecked")
private void migrateReputation(Map<String, Object> data) { private void migrateReputation(Map<String, Object> data) {
Map<String, Object> reputation = (Map<String, Object>) data.get("reputation"); var reputation = (Map<String, Object>) data.get("reputation");
if (reputation == null) return; if (reputation == null) return;
LoggerDecorator.database(logger, "Миграция reputation: " + reputation.size() + " записей"); LoggerDecorator.database(logger, "Миграция reputation: " + reputation.size() + " записей");
try (var a =
try (PreparedStatement stmt =
database.prepare("INSERT OR REPLACE INTO reputation (user_id, reputation) VALUES (?, ?)")) { database.prepare("INSERT OR REPLACE INTO reputation (user_id, reputation) VALUES (?, ?)")) {
for (Map.Entry<String, Object> entry : reputation.entrySet()) { for (var entry : reputation.entrySet()) {
stmt.setString(1, entry.getKey()); a.setString(1, entry.getKey());
stmt.setDouble(2, ((Number) entry.getValue()).doubleValue()); a.setDouble(2, ((Number) entry.getValue()).doubleValue());
stmt.executeUpdate(); a.executeUpdate();
} }
LoggerDecorator.success(logger, "reputation мигрирована"); LoggerDecorator.success(logger, "reputation мигрирована");
} catch (SQLException e) { } catch (SQLException e) {
@ -135,23 +99,20 @@ public class DatabaseMigration {
} }
} }
@SuppressWarnings("unchecked")
private void migrateShipPairs(Map<String, Object> data) { private void migrateShipPairs(Map<String, Object> data) {
Map<String, Object> shipPairs = (Map<String, Object>) data.get("shipPairs"); var shipPairs = (Map<String, Object>) data.get("shipPairs");
if (shipPairs == null) return; if (shipPairs == null) return;
LoggerDecorator.database(logger, "Миграция shipPairs: " + shipPairs.size() + " записей"); LoggerDecorator.database(logger, "Миграция shipPairs: " + shipPairs.size() + " записей");
try (var b =
try (PreparedStatement stmt =
database.prepare( database.prepare(
"INSERT OR REPLACE INTO ship_pairs (user1_id, user2_id, compatibility) VALUES (?, ?, ?)")) { "INSERT OR REPLACE INTO ship_pairs (user1_id, user2_id, compatibility) VALUES (?, ?, ?)")) {
for (Map.Entry<String, Object> entry : shipPairs.entrySet()) { for (var entry : shipPairs.entrySet()) {
String[] users = entry.getKey().split("-"); var users = entry.getKey().split("-");
if (users.length == 2) { if (users.length == 2) {
stmt.setString(1, users[0]); b.setString(1, users[0]);
stmt.setString(2, users[1]); b.setString(2, users[1]);
stmt.setDouble(3, ((Number) entry.getValue()).doubleValue()); b.setDouble(3, ((Number) entry.getValue()).doubleValue());
stmt.executeUpdate(); b.executeUpdate();
} }
} }
LoggerDecorator.success(logger, "shipPairs мигрированы"); LoggerDecorator.success(logger, "shipPairs мигрированы");
@ -160,30 +121,22 @@ public class DatabaseMigration {
} }
} }
@SuppressWarnings("unchecked")
private void migrateCustomRoles(Map<String, Object> data) { private void migrateCustomRoles(Map<String, Object> data) {
Map<String, Object> 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;
LoggerDecorator.database(logger, "Миграция custom_roles"); LoggerDecorator.database(logger, "Миграция custom_roles");
try (var c =
try (PreparedStatement stmt =
database.prepare( database.prepare(
"INSERT OR REPLACE INTO custom_roles (guild_id, user_id, role_id) VALUES (?, ?, ?)")) { "INSERT OR REPLACE INTO custom_roles (guild_id, user_id, role_id) VALUES (?, ?, ?)")) {
for (Map.Entry<String, Object> guildEntry : customRoles.entrySet()) { for (var guildEntry : customRoles.entrySet()) {
String guildId = guildEntry.getKey(); var users = (Map<String, Object>) guildEntry.getValue();
Map<String, Object> users = (Map<String, Object>) guildEntry.getValue(); for (var userEntry : users.entrySet()) {
if (userEntry.getValue() instanceof java.util.List<?> rolesList) {
for (Map.Entry<String, Object> userEntry : users.entrySet()) { for (var roleId : rolesList) {
String userId = userEntry.getKey(); c.setString(1, guildEntry.getKey());
Object rolesObj = userEntry.getValue(); c.setString(2, userEntry.getKey());
c.setString(3, roleId.toString());
if (rolesObj instanceof java.util.List<?> rolesList) { c.executeUpdate();
for (Object roleId : rolesList) {
stmt.setString(1, guildId);
stmt.setString(2, userId);
stmt.setString(3, roleId.toString());
stmt.executeUpdate();
} }
} }
} }

View file

@ -0,0 +1,54 @@
package creator.loki.database;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import creator.loki.config.BotConfig;
import creator.loki.feature.config.repository.ConfigRepository;
import creator.loki.feature.config.repository.GuildConfigRepository;
import creator.loki.feature.config.repository.PrefixedGuildConfigRepository;
import creator.loki.feature.music.repository.QueueRepository;
import creator.loki.feature.rating.repository.RatingsRepository;
import creator.loki.feature.reputation.repository.ReputationRepository;
import creator.loki.feature.ship.repository.ShipPairRepository;
public class DatabaseModule extends AbstractModule {
@Provides @Singleton
Database provideDatabase(BotConfig config) {
Database database = new Database(config.getDbPath());
database.connect();
new DatabaseSchema(database).createTables();
new DatabaseMigration(database).migrateFromJson("data/bot_data.json");
return database;
}
@Provides @Singleton
ConfigRepository provideConfigRepository(Database database) {
return new ConfigRepository(database);
}
@Provides @Singleton
GuildConfigRepository provideGuildConfigRepository(ConfigRepository cr) {
return new PrefixedGuildConfigRepository(cr);
}
@Provides @Singleton
RatingsRepository provideRatingsRepository(Database database) {
return new RatingsRepository(database);
}
@Provides @Singleton
ReputationRepository provideReputationRepository(Database database) {
return new ReputationRepository(database);
}
@Provides @Singleton
ShipPairRepository provideShipPairRepository(Database database) {
return new ShipPairRepository(database);
}
@Provides @Singleton
QueueRepository provideQueueRepository(Database database) {
return new QueueRepository(database);
}
}

View file

@ -3,191 +3,145 @@ package creator.loki.database.schema;
import creator.loki.database.Database; import creator.loki.database.Database;
public final class FeatureTables { public final class FeatureTables {
private static final String ECONOMY =
"""
CREATE TABLE IF NOT EXISTS economy (
user_id TEXT PRIMARY KEY,
cash INTEGER NOT NULL DEFAULT 0,
bank INTEGER NOT NULL DEFAULT 0,
total_earned INTEGER NOT NULL DEFAULT 0,
total_spent INTEGER NOT NULL DEFAULT 0,
last_daily TEXT,
daily_streak INTEGER DEFAULT 0,
fishing_luck INTEGER DEFAULT 0,
efficiency INTEGER DEFAULT 0,
last_fish TEXT,
job TEXT,
last_work TEXT
)""";
private static final String MUSIC_QUEUE =
"""
CREATE TABLE IF NOT EXISTS music_queue (
guild_id TEXT NOT NULL,
position INTEGER NOT NULL,
track_encoded TEXT NOT NULL,
track_title TEXT,
track_author TEXT,
track_uri TEXT,
track_duration BIGINT,
PRIMARY KEY (guild_id, position)
)""";
private static final String WARNINGS =
"""
CREATE TABLE IF NOT EXISTS warnings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
moderator_id TEXT NOT NULL,
reason TEXT NOT NULL,
created_at INTEGER NOT NULL
)""";
private static final String MODERATION_CASES =
"""
CREATE TABLE IF NOT EXISTS moderation_cases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL,
action TEXT NOT NULL,
target_id TEXT NOT NULL,
moderator_id TEXT NOT NULL,
reason TEXT NOT NULL,
created_at INTEGER NOT NULL,
duration_sec INTEGER,
until_ms INTEGER
)""";
private static final String ACTIVE_MUTES =
"""
CREATE TABLE IF NOT EXISTS active_mutes (
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
ends_at_ms INTEGER NOT NULL,
case_id INTEGER NOT NULL,
PRIMARY KEY (guild_id, user_id)
)""";
private static final String CUSTOM_ROLES =
"""
CREATE TABLE IF NOT EXISTS custom_roles (
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
role_id TEXT NOT NULL,
PRIMARY KEY (guild_id, user_id, role_id)
)""";
private static final String VOICE_CHANNELS =
"""
CREATE TABLE IF NOT EXISTS voice_channels (
channel_id TEXT PRIMARY KEY,
owner_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
name TEXT,
user_limit INTEGER DEFAULT 0,
bitrate INTEGER DEFAULT 64000,
locked INTEGER DEFAULT 0,
text_channel_id TEXT
)""";
private static final String VOICE_PRESETS =
"""
CREATE TABLE IF NOT EXISTS voice_presets (
user_id TEXT PRIMARY KEY,
name TEXT,
user_limit INTEGER DEFAULT 0,
bitrate INTEGER DEFAULT 64000,
region TEXT DEFAULT 'auto',
locked INTEGER DEFAULT 0,
allowed_role_id TEXT,
denied_role_id TEXT
)""";
private static final String COUNTER_STATS =
"""
CREATE TABLE IF NOT EXISTS counter_stats (
date TEXT PRIMARY KEY,
joined INTEGER DEFAULT 0,
left_count INTEGER DEFAULT 0,
difference INTEGER DEFAULT 0
)""";
private static final String RATINGS =
"""
CREATE TABLE IF NOT EXISTS ratings (
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
rating_type TEXT NOT NULL,
value INTEGER NOT NULL,
PRIMARY KEY (guild_id, user_id, rating_type)
)""";
public static void create(Database database) { public static void create(Database database) {
createEconomy(database); database.execute(ECONOMY);
createMusicQueue(database); database.execute(MUSIC_QUEUE);
createWarnings(database); database.execute(WARNINGS);
createModerationCases(database); database.execute(MODERATION_CASES);
createActiveMutes(database); database.execute(ACTIVE_MUTES);
createCustomRoles(database); database.execute(CUSTOM_ROLES);
createVoiceChannels(database); database.execute(VOICE_CHANNELS);
createVoicePresets(database); database.execute(VOICE_PRESETS);
createCounterStats(database); database.execute(COUNTER_STATS);
createRatings(database); database.execute(RATINGS);
createIndexes(database); createIndexes(database);
} }
private static void createEconomy(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS economy (
user_id TEXT PRIMARY KEY,
cash INTEGER NOT NULL DEFAULT 0,
bank INTEGER NOT NULL DEFAULT 0,
total_earned INTEGER NOT NULL DEFAULT 0,
total_spent INTEGER NOT NULL DEFAULT 0,
last_daily TEXT,
daily_streak INTEGER DEFAULT 0,
fishing_luck INTEGER DEFAULT 0,
efficiency INTEGER DEFAULT 0,
last_fish TEXT,
job TEXT,
last_work TEXT
)
""");
}
private static void createMusicQueue(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS music_queue (
guild_id TEXT NOT NULL,
position INTEGER NOT NULL,
track_encoded TEXT NOT NULL,
track_title TEXT,
track_author TEXT,
track_uri TEXT,
track_duration BIGINT,
PRIMARY KEY (guild_id, position)
)
""");
}
private static void createWarnings(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS warnings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
moderator_id TEXT NOT NULL,
reason TEXT NOT NULL,
created_at INTEGER NOT NULL
)
""");
}
private static void createModerationCases(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS moderation_cases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
guild_id TEXT NOT NULL,
action TEXT NOT NULL,
target_id TEXT NOT NULL,
moderator_id TEXT NOT NULL,
reason TEXT NOT NULL,
created_at INTEGER NOT NULL,
duration_sec INTEGER,
until_ms INTEGER
)
""");
}
private static void createActiveMutes(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS active_mutes (
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
ends_at_ms INTEGER NOT NULL,
case_id INTEGER NOT NULL,
PRIMARY KEY (guild_id, user_id)
)
""");
}
private static void createCustomRoles(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS custom_roles (
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
role_id TEXT NOT NULL,
PRIMARY KEY (guild_id, user_id, role_id)
)
""");
}
private static void createVoiceChannels(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS voice_channels (
channel_id TEXT PRIMARY KEY,
owner_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
name TEXT,
user_limit INTEGER DEFAULT 0,
bitrate INTEGER DEFAULT 64000,
locked INTEGER DEFAULT 0,
text_channel_id TEXT
)
""");
}
private static void createVoicePresets(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS voice_presets (
user_id TEXT PRIMARY KEY,
name TEXT,
user_limit INTEGER DEFAULT 0,
bitrate INTEGER DEFAULT 64000,
region TEXT DEFAULT 'auto',
locked INTEGER DEFAULT 0,
allowed_role_id TEXT,
denied_role_id TEXT
)
""");
}
private static void createCounterStats(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS counter_stats (
date TEXT PRIMARY KEY,
joined INTEGER DEFAULT 0,
left_count INTEGER DEFAULT 0,
difference INTEGER DEFAULT 0
)
""");
}
private static void createRatings(Database database) {
database.execute(
"""
CREATE TABLE IF NOT EXISTS ratings (
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
rating_type TEXT NOT NULL,
value INTEGER NOT NULL,
PRIMARY KEY (guild_id, user_id, rating_type)
)
""");
}
private static void createIndexes(Database database) { private static void createIndexes(Database database) {
database.execute( for (var sql :
"CREATE INDEX IF NOT EXISTS idx_ratings_guild_type ON ratings(guild_id, rating_type)"); new String[] {
database.execute("CREATE INDEX IF NOT EXISTS idx_ratings_value ON ratings(value DESC)"); "CREATE INDEX IF NOT EXISTS idx_ratings_guild_type ON ratings(guild_id, rating_type)",
database.execute( "CREATE INDEX IF NOT EXISTS idx_ratings_value ON ratings(value DESC)",
"CREATE INDEX IF NOT EXISTS idx_voice_channels_owner ON voice_channels(owner_id)"); "CREATE INDEX IF NOT EXISTS idx_voice_channels_owner ON voice_channels(owner_id)",
database.execute( "CREATE INDEX IF NOT EXISTS idx_voice_channels_created ON voice_channels(created_at)",
"CREATE INDEX IF NOT EXISTS idx_voice_channels_created ON voice_channels(created_at)"); "CREATE INDEX IF NOT EXISTS idx_custom_roles_guild ON custom_roles(guild_id)",
database.execute("CREATE INDEX IF NOT EXISTS idx_custom_roles_guild ON custom_roles(guild_id)"); "CREATE INDEX IF NOT EXISTS idx_custom_roles_user ON custom_roles(user_id)",
database.execute("CREATE INDEX IF NOT EXISTS idx_custom_roles_user ON custom_roles(user_id)"); "CREATE INDEX IF NOT EXISTS idx_counter_stats_date ON counter_stats(date DESC)",
database.execute( "CREATE INDEX IF NOT EXISTS idx_warnings_guild_user ON warnings(guild_id, user_id)",
"CREATE INDEX IF NOT EXISTS idx_counter_stats_date ON counter_stats(date DESC)"); "CREATE INDEX IF NOT EXISTS idx_warnings_guild_time ON warnings(guild_id, created_at DESC)",
database.execute( "CREATE INDEX IF NOT EXISTS idx_mod_cases_guild_id ON moderation_cases(guild_id, id DESC)",
"CREATE INDEX IF NOT EXISTS idx_warnings_guild_user ON warnings(guild_id, user_id)"); "CREATE INDEX IF NOT EXISTS idx_active_mutes_ends ON active_mutes(ends_at_ms)",
database.execute( "CREATE INDEX IF NOT EXISTS idx_economy_balance ON economy((cash + bank) DESC)"
"CREATE INDEX IF NOT EXISTS idx_warnings_guild_time ON warnings(guild_id, created_at DESC)"); }) database.execute(sql);
database.execute(
"CREATE INDEX IF NOT EXISTS idx_mod_cases_guild_id ON moderation_cases(guild_id, id DESC)");
database.execute(
"CREATE INDEX IF NOT EXISTS idx_active_mutes_ends ON active_mutes(ends_at_ms)");
database.execute(
"CREATE INDEX IF NOT EXISTS idx_economy_balance ON economy((cash + bank) DESC)");
} }
} }

View file

@ -15,12 +15,7 @@ import creator.loki.core.registry.CommandRegistry;
import creator.loki.core.registry.PrefixCommandRegistrar; import creator.loki.core.registry.PrefixCommandRegistrar;
import creator.loki.core.security.CooldownManager; import creator.loki.core.security.CooldownManager;
import creator.loki.core.security.RateLimiter; import creator.loki.core.security.RateLimiter;
import creator.loki.database.Database; import creator.loki.database.DatabaseModule;
import creator.loki.database.DatabaseMigration;
import creator.loki.database.DatabaseSchema;
import creator.loki.feature.config.repository.ConfigRepository;
import creator.loki.feature.config.repository.GuildConfigRepository;
import creator.loki.feature.config.repository.PrefixedGuildConfigRepository;
import creator.loki.feature.counter.CounterModule; 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;
@ -34,20 +29,15 @@ import creator.loki.feature.moderation.ModerationCommandRegistrar;
import creator.loki.feature.moderation.ModerationModule; import creator.loki.feature.moderation.ModerationModule;
import creator.loki.feature.music.MusicCommandRegistrar; import creator.loki.feature.music.MusicCommandRegistrar;
import creator.loki.feature.music.MusicModule; import creator.loki.feature.music.MusicModule;
import creator.loki.feature.music.repository.QueueRepository;
import creator.loki.feature.profile.ProfileModule; import creator.loki.feature.profile.ProfileModule;
import creator.loki.feature.radio.RadioCommandRegistrar; import creator.loki.feature.radio.RadioCommandRegistrar;
import creator.loki.feature.radio.RadioModule; import creator.loki.feature.radio.RadioModule;
import creator.loki.feature.rating.repository.RatingsRepository;
import creator.loki.feature.reputation.repository.ReputationRepository;
import creator.loki.feature.role.RoleModule; import creator.loki.feature.role.RoleModule;
import creator.loki.feature.ship.repository.ShipPairRepository;
import creator.loki.feature.utility.UtilityCommandRegistrar; import creator.loki.feature.utility.UtilityCommandRegistrar;
import creator.loki.feature.voice.VoiceModule; import creator.loki.feature.voice.VoiceModule;
import creator.loki.feature.welcome.WelcomeModule; import creator.loki.feature.welcome.WelcomeModule;
public class BotModule extends AbstractModule { public class BotModule extends AbstractModule {
@Override @Override
protected void configure() { protected void configure() {
bind(CommandRegistry.class).in(Singleton.class); bind(CommandRegistry.class).in(Singleton.class);
@ -60,6 +50,7 @@ public class BotModule extends AbstractModule {
bind(ShutdownManager.class).in(Singleton.class); bind(ShutdownManager.class).in(Singleton.class);
bind(PrefixCommandRegistrar.class).in(Singleton.class); bind(PrefixCommandRegistrar.class).in(Singleton.class);
install(new DatabaseModule());
install(new VoiceModule()); install(new VoiceModule());
install(new MusicModule()); install(new MusicModule());
install(new LevelModule()); install(new LevelModule());
@ -83,67 +74,12 @@ public class BotModule extends AbstractModule {
commandBinder.addBinding().to(RadioCommandRegistrar.class); commandBinder.addBinding().to(RadioCommandRegistrar.class);
} }
@Provides @Provides @Singleton
@Singleton BotConfig provideBotConfig() { return ConfigLoader.load(); }
BotConfig provideBotConfig() {
return ConfigLoader.load();
}
@Provides @Provides @Singleton
@Singleton
EventDispatcher provideEventDispatcher( EventDispatcher provideEventDispatcher(
BotConfig config, BotConfig config, CommandRegistry registry, RateLimiter rateLimiter, MetricsCollector metrics) {
CommandRegistry registry,
RateLimiter rateLimiter,
MetricsCollector metrics) {
return new EventDispatcher(config, registry, rateLimiter, metrics); return new EventDispatcher(config, registry, rateLimiter, metrics);
} }
@Provides
@Singleton
Database provideDatabase(BotConfig config) {
Database database = new Database(config.getDbPath());
database.connect();
DatabaseSchema schema = new DatabaseSchema(database);
schema.createTables();
DatabaseMigration migration = new DatabaseMigration(database);
migration.migrateFromJson("data/bot_data.json");
return database;
}
@Provides
@Singleton
ConfigRepository provideConfigRepository(Database database) {
return new ConfigRepository(database);
}
@Provides
@Singleton
GuildConfigRepository provideGuildConfigRepository(ConfigRepository configRepository) {
return new PrefixedGuildConfigRepository(configRepository);
}
@Provides
@Singleton
RatingsRepository provideRatingsRepository(Database database) {
return new RatingsRepository(database);
}
@Provides
@Singleton
ReputationRepository provideReputationRepository(Database database) {
return new ReputationRepository(database);
}
@Provides
@Singleton
ShipPairRepository provideShipPairRepository(Database database) {
return new ShipPairRepository(database);
}
@Provides
@Singleton
QueueRepository provideQueueRepository(Database database) {
return new QueueRepository(database);
}
} }

View file

@ -5,7 +5,6 @@ import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import creator.loki.feature.economy.domain.EconomyAccount;
import creator.loki.feature.economy.repository.EconomyRepository; import creator.loki.feature.economy.repository.EconomyRepository;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -30,109 +29,72 @@ public class BlackjackService {
public BlackjackState startGame(String userId, long bet) { public BlackjackState startGame(String userId, long bet) {
if (bet <= 0 || activeGames.containsKey(userId)) return null; if (bet <= 0 || activeGames.containsKey(userId)) return null;
EconomyAccount account = repository.getAccount(userId); var account = repository.getAccount(userId);
if (account.getCash() < bet) return null; if (account.getCash() < bet) return null;
repository.save(account.withCash(account.getCash() - bet)); repository.save(account.withCash(account.getCash() - bet));
List<String> playerCards = new ArrayList<>(List.of(drawCard(), drawCard())); var playerCards = new ArrayList<>(List.of(drawCard(), drawCard()));
List<String> dealerCards = new ArrayList<>(List.of(drawCard(), drawCard())); var dealerCards = new ArrayList<>(List.of(drawCard(), drawCard()));
int playerSum = calculateSum(playerCards); var playerSum = calculateSum(playerCards);
int dealerSum = calculateSum(dealerCards); var dealerSum = calculateSum(dealerCards);
boolean playerBj = playerSum == 21; var playerBj = playerSum == 21;
boolean dealerBj = dealerSum == 21; var dealerBj = dealerSum == 21;
if (playerBj || dealerBj) { if (playerBj || dealerBj) {
long payout; var payout = playerBj && !dealerBj ? (long) (bet * 2.5) : 0;
if (playerBj && !dealerBj) { creditUser(userId, payout);
payout = (long) (bet * 2.5); return buildState(playerCards, dealerCards, playerSum, dealerSum, bet, true, payout);
} else {
payout = 0;
}
repository.save(
repository.getAccount(userId).withCash(repository.getAccount(userId).getCash() + payout));
return new BlackjackState(
playerCards.toArray(String[]::new),
dealerCards.toArray(String[]::new),
playerSum,
dealerSum,
bet,
true,
payout);
} }
ActiveBlackjack game = new ActiveBlackjack(playerCards, dealerCards, bet, userId); activeGames.put(userId, new ActiveBlackjack(playerCards, dealerCards, bet, userId));
activeGames.put(userId, game); return buildState(playerCards, dealerCards, playerSum, dealerSum, bet, false, 0);
return new BlackjackState(
playerCards.toArray(String[]::new),
dealerCards.toArray(String[]::new),
playerSum,
dealerSum,
bet,
false,
0);
} }
public BlackjackState hit(String userId) { public BlackjackState hit(String userId) {
ActiveBlackjack game = activeGames.get(userId); var game = activeGames.get(userId);
if (game == null) return null; if (game == null) return null;
game.playerCards().add(drawCard()); game.playerCards().add(drawCard());
int sum = calculateSum(game.playerCards()); var sum = calculateSum(game.playerCards());
if (sum > 21) { var over = sum > 21;
activeGames.remove(userId); if (over) activeGames.remove(userId);
return new BlackjackState(
game.playerCards().toArray(String[]::new),
game.dealerCards().toArray(String[]::new),
sum,
calculateSum(game.dealerCards()),
game.bet(),
true,
0);
}
return new BlackjackState( return buildState(
game.playerCards().toArray(String[]::new), game.playerCards(),
game.dealerCards().toArray(String[]::new), game.dealerCards(),
sum, sum,
calculateSum(game.dealerCards()), calculateSum(game.dealerCards()),
game.bet(), game.bet(),
false, over,
0); 0);
} }
public BlackjackState stand(String userId) { public BlackjackState stand(String userId) {
ActiveBlackjack game = activeGames.remove(userId); var game = activeGames.remove(userId);
if (game == null) return null; if (game == null) return null;
List<String> dealerCards = new ArrayList<>(game.dealerCards()); var dealerCards = new ArrayList<>(game.dealerCards());
while (calculateSum(dealerCards) < 16) { while (calculateSum(dealerCards) < 16) {
dealerCards.add(drawCard()); dealerCards.add(drawCard());
} }
int playerSum = calculateSum(game.playerCards()); var playerSum = calculateSum(game.playerCards());
int dealerSum = calculateSum(dealerCards); var dealerSum = calculateSum(dealerCards);
long payout; var payout = dealerSum > 21 || playerSum > dealerSum ? game.bet() * 2 : 0;
if (dealerSum > 21 || playerSum > dealerSum) { creditUser(userId, payout);
payout = game.bet() * 2;
} else {
payout = 0;
}
EconomyAccount account = repository.getAccount(userId); return buildState(
repository.save(account.withCash(account.getCash() + payout)); game.playerCards(), dealerCards, playerSum, dealerSum, game.bet(), true, payout);
}
return new BlackjackState( private void creditUser(String userId, long amount) {
game.playerCards().toArray(String[]::new), if (amount <= 0) return;
dealerCards.toArray(String[]::new), var account = repository.getAccount(userId);
playerSum, repository.save(account.withCash(account.getCash() + amount));
dealerSum,
game.bet(),
true,
payout);
} }
private String drawCard() { private String drawCard() {
@ -140,9 +102,9 @@ public class BlackjackService {
} }
private int calculateSum(List<String> cards) { private int calculateSum(List<String> cards) {
int sum = 0; var sum = 0;
int aces = 0; var aces = 0;
for (String c : cards) { for (var c : cards) {
switch (c) { switch (c) {
case "J", "Q", "K" -> sum += 10; case "J", "Q", "K" -> sum += 10;
case "A" -> { case "A" -> {
@ -159,6 +121,24 @@ public class BlackjackService {
return sum; return sum;
} }
private BlackjackState buildState(
List<String> playerCards,
List<String> dealerCards,
int playerSum,
int dealerSum,
long bet,
boolean isOver,
long payout) {
return new BlackjackState(
playerCards.toArray(String[]::new),
dealerCards.toArray(String[]::new),
playerSum,
dealerSum,
bet,
isOver,
payout);
}
public record BlackjackState( public record BlackjackState(
String[] playerCards, String[] playerCards,
String[] dealerCards, String[] dealerCards,

View file

@ -23,8 +23,10 @@ public class MessageListener extends ListenerAdapter {
@Inject @Inject
public MessageListener( public MessageListener(
EventDispatcher dispatcher, LevelService levelService, EventDispatcher dispatcher,
EconomyService economyService, LevelUpNotifier levelUpNotifier) { LevelService levelService,
EconomyService economyService,
LevelUpNotifier levelUpNotifier) {
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
this.levelService = levelService; this.levelService = levelService;
this.economyService = economyService; this.economyService = economyService;
@ -39,8 +41,7 @@ public class MessageListener extends ListenerAdapter {
LevelUpResult levelUp = levelService.addMessageXP(userId); LevelUpResult levelUp = levelService.addMessageXP(userId);
if (levelUp != null && event.getMember() != null) { if (levelUp != null && event.getMember() != null) {
levelUpNotifier.sendLevelUpNotification( levelUpNotifier.sendLevelUpNotification(
event.getMember(), levelUp.getOldLevel(), levelUp.getNewLevel(), event.getMember(), levelUp.getOldLevel(), levelUp.getNewLevel(), event.getChannel());
event.getChannel());
} }
int lcAmount = ThreadLocalRandom.current().nextInt(1, 4); int lcAmount = ThreadLocalRandom.current().nextInt(1, 4);
economyService.earnCash(userId, lcAmount); economyService.earnCash(userId, lcAmount);

View file

@ -32,38 +32,53 @@ public class ResetLevelsButtonHandler extends ListenerAdapter {
String authorId = buttonId.substring(PREFIX_CONFIRM.length()); String authorId = buttonId.substring(PREFIX_CONFIRM.length());
if (authorId.isEmpty()) return; if (authorId.isEmpty()) return;
if (!event.getUser().getId().equals(authorId)) { if (!event.getUser().getId().equals(authorId)) {
event.reply("❌ Только инициатор команды может подтвердить действие!").setEphemeral(true).queue(); event
.reply("❌ Только инициатор команды может подтвердить действие!")
.setEphemeral(true)
.queue();
return; return;
} }
event.deferReply().queue(); event.deferReply().queue();
try { try {
resetCommand.executeReset(event.getGuild().getId(), event.getUser().getId()); resetCommand.executeReset(event.getGuild().getId(), event.getUser().getId());
event.getMessage().delete().queue(); event.getMessage().delete().queue();
event.getHook().sendMessageEmbeds( event
EmbedFactory.create() .getHook()
.withTitle("✅ Сброс уровней выполнен") .sendMessageEmbeds(
.withDescription("Уровни и XP всех пользователей успешно сброшены!\n\n" EmbedFactory.create()
+ "**Сохранено:**\n• Время в голосовых каналах\n" .withTitle("✅ Сброс уровней выполнен")
+ "• Количество сообщений\n• Репутация\n\n" .withDescription(
+ "**Сброшено:**\n• Уровень → 0\n• XP → 0\n\n" "Уровни и XP всех пользователей успешно сброшены!\n\n"
+ "Теперь используется новая система уровней!") + "**Сохранено:**\n• Время в голосовых каналах\n"
.withColor(Color.GREEN) + "• Количество сообщений\n• Репутация\n\n"
.withFooter("Инициатор: " + event.getUser().getName(), event.getUser().getAvatarUrl()) + "**Сброшено:**\n• Уровень → 0\n• XP → 0\n\n"
.build()).queue(); + "Теперь используется новая система уровней!")
.withColor(Color.GREEN)
.withFooter(
"Инициатор: " + event.getUser().getName(), event.getUser().getAvatarUrl())
.build())
.queue();
LoggerDecorator.bot(logger, "Сброс уровней подтверждён: " + event.getUser().getName()); LoggerDecorator.bot(logger, "Сброс уровней подтверждён: " + event.getUser().getName());
} catch (Exception e) { } catch (Exception e) {
LoggerDecorator.error(logger, "Ошибка при выполнении сброса", e); LoggerDecorator.error(logger, "Ошибка при выполнении сброса", e);
event.getHook().sendMessageEmbeds( event
EmbedFactory.create() .getHook()
.withTitle("❌ Ошибка") .sendMessageEmbeds(
.withDescription("Произошла ошибка при сбросе уровней: " + e.getMessage()) EmbedFactory.create()
.withColor(Color.RED).build()).queue(); .withTitle("❌ Ошибка")
.withDescription("Произошла ошибка при сбросе уровней: " + e.getMessage())
.withColor(Color.RED)
.build())
.queue();
} }
} else if (buttonId.startsWith(PREFIX_CANCEL)) { } else if (buttonId.startsWith(PREFIX_CANCEL)) {
String authorId = buttonId.substring(PREFIX_CANCEL.length()); String authorId = buttonId.substring(PREFIX_CANCEL.length());
if (authorId.isEmpty()) return; if (authorId.isEmpty()) return;
if (!event.getUser().getId().equals(authorId)) { if (!event.getUser().getId().equals(authorId)) {
event.reply("❌ Только инициатор команды может отменить действие!").setEphemeral(true).queue(); event
.reply("❌ Только инициатор команды может отменить действие!")
.setEphemeral(true)
.queue();
return; return;
} }
event.getMessage().delete().queue(); event.getMessage().delete().queue();

View file

@ -28,8 +28,8 @@ public class VoiceTracker extends ListenerAdapter {
private final Object lock = new Object(); private final Object lock = new Object();
@Inject @Inject
public VoiceTracker(LevelService levelService, EconomyService economyService, public VoiceTracker(
LevelUpNotifier levelUpNotifier) { LevelService levelService, EconomyService economyService, LevelUpNotifier levelUpNotifier) {
this.levelService = levelService; this.levelService = levelService;
this.economyService = economyService; this.economyService = economyService;
this.levelUpNotifier = levelUpNotifier; this.levelUpNotifier = levelUpNotifier;
@ -47,7 +47,9 @@ public class VoiceTracker extends ListenerAdapter {
voiceJoinTimes.put(userId, System.currentTimeMillis()); voiceJoinTimes.put(userId, System.currentTimeMillis());
} else if (event.getChannelLeft() != null && event.getChannelJoined() == null) { } else if (event.getChannelLeft() != null && event.getChannelJoined() == null) {
Long joinTime; Long joinTime;
synchronized (lock) { joinTime = voiceJoinTimes.remove(userId); } synchronized (lock) {
joinTime = voiceJoinTimes.remove(userId);
}
if (joinTime != null) { if (joinTime != null) {
long minutes = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - joinTime); long minutes = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - joinTime);
if (minutes > 0) { if (minutes > 0) {
@ -55,13 +57,17 @@ public class VoiceTracker extends ListenerAdapter {
economyService.earnCash(userId, minutes / 2); economyService.earnCash(userId, minutes / 2);
if (lr != null && event.getMember() != null) { if (lr != null && event.getMember() != null) {
levelUpNotifier.sendLevelUpNotification( levelUpNotifier.sendLevelUpNotification(
event.getMember(), lr.getOldLevel(), lr.getNewLevel(), event.getMember(),
lr.getOldLevel(),
lr.getNewLevel(),
event.getGuild().getSystemChannel()); event.getGuild().getSystemChannel());
} }
} }
} }
} else if (event.getChannelJoined() != null && event.getChannelLeft() != null) { } else if (event.getChannelJoined() != null && event.getChannelLeft() != null) {
synchronized (lock) { voiceJoinTimes.put(userId, System.currentTimeMillis()); } synchronized (lock) {
voiceJoinTimes.put(userId, System.currentTimeMillis());
}
} }
} }
@ -82,7 +88,11 @@ public class VoiceTracker extends ListenerAdapter {
public void shutdown() { public void shutdown() {
scheduler.shutdown(); scheduler.shutdown();
try { if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) scheduler.shutdownNow(); } try {
catch (InterruptedException e) { scheduler.shutdownNow(); Thread.currentThread().interrupt(); } if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) scheduler.shutdownNow();
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
} }
} }

View file

@ -3,7 +3,6 @@ package creator.loki.feature.level.service;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import creator.loki.feature.level.domain.UserStats;
import creator.loki.feature.level.repository.UserStatsRepository; import creator.loki.feature.level.repository.UserStatsRepository;
public class MessageCooldownService { public class MessageCooldownService {

View file

@ -7,11 +7,11 @@ import creator.loki.command.Command;
import creator.loki.command.CommandContext; import creator.loki.command.CommandContext;
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.moderation.util.ModerationHelper;
import creator.loki.feature.moderation.MuteRoleService; import creator.loki.feature.moderation.MuteRoleService;
import creator.loki.feature.moderation.MuteScheduler; import creator.loki.feature.moderation.MuteScheduler;
import creator.loki.feature.moderation.repository.ActiveMuteRepository; 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.util.ModerationHelper;
import creator.loki.util.MuteDurationParser; import creator.loki.util.MuteDurationParser;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;

View file

@ -34,16 +34,22 @@ public class RadioManager {
this.queueRepository = queueRepository; this.queueRepository = queueRepository;
} }
public void setLavalinkManager(LavalinkManager lm) { this.lavalinkManager = lm; } public void setLavalinkManager(LavalinkManager lm) {
this.lavalinkManager = lm;
}
public void setRadioJda(JDA radioJda) { this.radioJda = radioJda; } public void setRadioJda(JDA radioJda) {
this.radioJda = radioJda;
}
public void setRadio(long guildId, String streamUrl) { public void setRadio(long guildId, String streamUrl) {
radioStates.compute(guildId, (id, state) -> { radioStates.compute(
if (state == null) state = new GuildRadioState(); guildId,
state.streamUrl = streamUrl; (id, state) -> {
return state; if (state == null) state = new GuildRadioState();
}); state.streamUrl = streamUrl;
return state;
});
LoggerDecorator.music(logger, "Radio URL set for guild " + guildId + ": " + streamUrl); LoggerDecorator.music(logger, "Radio URL set for guild " + guildId + ": " + streamUrl);
} }
@ -65,12 +71,14 @@ public class RadioManager {
if (guild == null) return; if (guild == null) return;
guild.getJDA().getDirectAudioController().connect(guild.getVoiceChannelById(channelId)); guild.getJDA().getDirectAudioController().connect(guild.getVoiceChannelById(channelId));
var self = guild.getMember(guild.getJDA().getSelfUser()); var self = guild.getMember(guild.getJDA().getSelfUser());
if (!VoiceHelper.waitForCondition(() -> self.getVoiceState().inAudioChannel(), VOICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { if (!VoiceHelper.waitForCondition(
() -> self.getVoiceState().inAudioChannel(), VOICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
LoggerDecorator.warn(logger, "Radio: voice connection timeout for guild " + guildId); LoggerDecorator.warn(logger, "Radio: voice connection timeout for guild " + guildId);
return; return;
} }
var link = lavalinkManager.getOrCreateLink(guildId); var link = lavalinkManager.getOrCreateLink(guildId);
if (!VoiceHelper.waitForCondition(() -> link.getCachedPlayer() != null, VOICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { if (!VoiceHelper.waitForCondition(
() -> link.getCachedPlayer() != null, VOICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
LoggerDecorator.warn(logger, "Radio: Lavalink player timeout for guild " + guildId); LoggerDecorator.warn(logger, "Radio: Lavalink player timeout for guild " + guildId);
return; return;
} }
@ -78,43 +86,60 @@ public class RadioManager {
int vol = finalState.volume; int vol = finalState.volume;
var url = finalState.streamUrl; var url = finalState.streamUrl;
finalState.musicManager = mm; finalState.musicManager = mm;
lavalinkManager.getOrCreateLink(guildId).loadItem(url) lavalinkManager
.subscribe(new AbstractAudioLoadResultHandler() { .getOrCreateLink(guildId)
@Override .loadItem(url)
public void ontrackLoaded(TrackLoaded r) { .subscribe(
if (vol != 100) link.createOrUpdatePlayer().setVolume(vol).subscribe(); new AbstractAudioLoadResultHandler() {
mm.getScheduler().enqueue(r.getTrack()); @Override
LoggerDecorator.music(logger, "Radio started in guild " + guildId); public void ontrackLoaded(TrackLoaded r) {
} if (vol != 100) link.createOrUpdatePlayer().setVolume(vol).subscribe();
@Override mm.getScheduler().enqueue(r.getTrack());
public void onPlaylistLoaded(dev.arbjerg.lavalink.client.player.PlaylistLoaded r) { LoggerDecorator.music(logger, "Radio started in guild " + guildId);
if (!r.getTracks().isEmpty()) { }
if (vol != 100) link.createOrUpdatePlayer().setVolume(vol).subscribe();
mm.getScheduler().enqueue(r.getTracks().get(0)); @Override
LoggerDecorator.music(logger, "Radio started in guild " + guildId); public void onPlaylistLoaded(dev.arbjerg.lavalink.client.player.PlaylistLoaded r) {
} if (!r.getTracks().isEmpty()) {
} if (vol != 100) link.createOrUpdatePlayer().setVolume(vol).subscribe();
@Override mm.getScheduler().enqueue(r.getTracks().get(0));
public void onSearchResultLoaded(dev.arbjerg.lavalink.client.player.SearchResult r) { LoggerDecorator.music(logger, "Radio started in guild " + guildId);
if (!r.getTracks().isEmpty()) { }
if (vol != 100) link.createOrUpdatePlayer().setVolume(vol).subscribe(); }
mm.getScheduler().enqueue(r.getTracks().get(0));
LoggerDecorator.music(logger, "Radio started in guild " + guildId); @Override
} public void onSearchResultLoaded(dev.arbjerg.lavalink.client.player.SearchResult r) {
} if (!r.getTracks().isEmpty()) {
@Override if (vol != 100) link.createOrUpdatePlayer().setVolume(vol).subscribe();
public void noMatches() { LoggerDecorator.warn(logger, "No radio stream: " + url); } mm.getScheduler().enqueue(r.getTracks().get(0));
@Override LoggerDecorator.music(logger, "Radio started in guild " + guildId);
public void loadFailed(dev.arbjerg.lavalink.client.player.LoadFailed e) { }
LoggerDecorator.warn(logger, "Failed to load radio: " + (e.getException().getMessage() != null ? e.getException().getMessage() : "unknown")); }
}
}); @Override
public void noMatches() {
LoggerDecorator.warn(logger, "No radio stream: " + url);
}
@Override
public void loadFailed(dev.arbjerg.lavalink.client.player.LoadFailed e) {
LoggerDecorator.warn(
logger,
"Failed to load radio: "
+ (e.getException().getMessage() != null
? e.getException().getMessage()
: "unknown"));
}
});
} }
public void stopRadio(long guildId) { public void stopRadio(long guildId) {
GuildRadioState state = radioStates.remove(guildId); GuildRadioState state = radioStates.remove(guildId);
if (state == null) return; if (state == null) return;
if (state.musicManager != null) { state.musicManager.stop(); state.musicManager.destroy(); } if (state.musicManager != null) {
state.musicManager.stop();
state.musicManager.destroy();
}
if (lavalinkManager != null) { if (lavalinkManager != null) {
Link link = lavalinkManager.getClient().getLinkIfCached(guildId); Link link = lavalinkManager.getClient().getLinkIfCached(guildId);
if (link != null) link.destroy().subscribe(); if (link != null) link.destroy().subscribe();
@ -131,9 +156,13 @@ public class RadioManager {
return state != null && state.streamUrl != null; return state != null && state.streamUrl != null;
} }
public boolean hasRadioBot() { return radioJda != null; } public boolean hasRadioBot() {
return radioJda != null;
}
public JDA getRadioJda() { return radioJda; } public JDA getRadioJda() {
return radioJda;
}
public int getVolume(long guildId) { public int getVolume(long guildId) {
GuildRadioState state = radioStates.get(guildId); GuildRadioState state = radioStates.get(guildId);
@ -147,7 +176,8 @@ public class RadioManager {
if (lavalinkManager != null) { if (lavalinkManager != null) {
var link = lavalinkManager.getClient().getLinkIfCached(guildId); var link = lavalinkManager.getClient().getLinkIfCached(guildId);
if (link != null && link.getCachedPlayer() != null) { if (link != null && link.getCachedPlayer() != null) {
link.createOrUpdatePlayer().setVolume(state.volume) link.createOrUpdatePlayer()
.setVolume(state.volume)
.subscribe(null, err -> logger.warn("Lavalink: {}", err.getMessage())); .subscribe(null, err -> logger.warn("Lavalink: {}", err.getMessage()));
} }
} }