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:
parent
0d916fbd96
commit
f6b037921b
11 changed files with 435 additions and 503 deletions
|
|
@ -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);
|
migrateUserStats(data);
|
||||||
migrateReputation(data);
|
migrateReputation(data);
|
||||||
migrateShipPairs(data);
|
migrateShipPairs(data);
|
||||||
migrateCustomRoles(data);
|
migrateCustomRoles(data);
|
||||||
markMigrated();
|
var sql = "INSERT OR REPLACE INTO config (key, value) VALUES ('migrated', 'true')";
|
||||||
|
try (var p = database.prepare(sql)) {
|
||||||
LoggerDecorator.success(logger, "Миграция завершена успешно");
|
p.executeUpdate();
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
LoggerDecorator.error(logger, "Ошибка миграции данных", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isMigrated() {
|
|
||||||
try (PreparedStatement stmt =
|
|
||||||
database.prepare("SELECT value FROM config WHERE key = 'migrated'")) {
|
|
||||||
ResultSet rs = stmt.executeQuery();
|
|
||||||
return rs.next() && "true".equals(rs.getString("value"));
|
|
||||||
} catch (SQLException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
54
src/main/java/creator/loki/database/DatabaseModule.java
Normal file
54
src/main/java/creator/loki/database/DatabaseModule.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,23 +3,7 @@ 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 =
|
||||||
public static void create(Database database) {
|
|
||||||
createEconomy(database);
|
|
||||||
createMusicQueue(database);
|
|
||||||
createWarnings(database);
|
|
||||||
createModerationCases(database);
|
|
||||||
createActiveMutes(database);
|
|
||||||
createCustomRoles(database);
|
|
||||||
createVoiceChannels(database);
|
|
||||||
createVoicePresets(database);
|
|
||||||
createCounterStats(database);
|
|
||||||
createRatings(database);
|
|
||||||
createIndexes(database);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void createEconomy(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS economy (
|
CREATE TABLE IF NOT EXISTS economy (
|
||||||
user_id TEXT PRIMARY KEY,
|
user_id TEXT PRIMARY KEY,
|
||||||
|
|
@ -34,12 +18,8 @@ public final class FeatureTables {
|
||||||
last_fish TEXT,
|
last_fish TEXT,
|
||||||
job TEXT,
|
job TEXT,
|
||||||
last_work TEXT
|
last_work TEXT
|
||||||
)
|
)""";
|
||||||
""");
|
private static final String MUSIC_QUEUE =
|
||||||
}
|
|
||||||
|
|
||||||
private static void createMusicQueue(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS music_queue (
|
CREATE TABLE IF NOT EXISTS music_queue (
|
||||||
guild_id TEXT NOT NULL,
|
guild_id TEXT NOT NULL,
|
||||||
|
|
@ -50,12 +30,8 @@ public final class FeatureTables {
|
||||||
track_uri TEXT,
|
track_uri TEXT,
|
||||||
track_duration BIGINT,
|
track_duration BIGINT,
|
||||||
PRIMARY KEY (guild_id, position)
|
PRIMARY KEY (guild_id, position)
|
||||||
)
|
)""";
|
||||||
""");
|
private static final String WARNINGS =
|
||||||
}
|
|
||||||
|
|
||||||
private static void createWarnings(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS warnings (
|
CREATE TABLE IF NOT EXISTS warnings (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
@ -64,12 +40,8 @@ public final class FeatureTables {
|
||||||
moderator_id TEXT NOT NULL,
|
moderator_id TEXT NOT NULL,
|
||||||
reason TEXT NOT NULL,
|
reason TEXT NOT NULL,
|
||||||
created_at INTEGER NOT NULL
|
created_at INTEGER NOT NULL
|
||||||
)
|
)""";
|
||||||
""");
|
private static final String MODERATION_CASES =
|
||||||
}
|
|
||||||
|
|
||||||
private static void createModerationCases(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS moderation_cases (
|
CREATE TABLE IF NOT EXISTS moderation_cases (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
@ -81,12 +53,8 @@ public final class FeatureTables {
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
duration_sec INTEGER,
|
duration_sec INTEGER,
|
||||||
until_ms INTEGER
|
until_ms INTEGER
|
||||||
)
|
)""";
|
||||||
""");
|
private static final String ACTIVE_MUTES =
|
||||||
}
|
|
||||||
|
|
||||||
private static void createActiveMutes(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS active_mutes (
|
CREATE TABLE IF NOT EXISTS active_mutes (
|
||||||
guild_id TEXT NOT NULL,
|
guild_id TEXT NOT NULL,
|
||||||
|
|
@ -94,24 +62,16 @@ public final class FeatureTables {
|
||||||
ends_at_ms INTEGER NOT NULL,
|
ends_at_ms INTEGER NOT NULL,
|
||||||
case_id INTEGER NOT NULL,
|
case_id INTEGER NOT NULL,
|
||||||
PRIMARY KEY (guild_id, user_id)
|
PRIMARY KEY (guild_id, user_id)
|
||||||
)
|
)""";
|
||||||
""");
|
private static final String CUSTOM_ROLES =
|
||||||
}
|
|
||||||
|
|
||||||
private static void createCustomRoles(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS custom_roles (
|
CREATE TABLE IF NOT EXISTS custom_roles (
|
||||||
guild_id TEXT NOT NULL,
|
guild_id TEXT NOT NULL,
|
||||||
user_id TEXT NOT NULL,
|
user_id TEXT NOT NULL,
|
||||||
role_id TEXT NOT NULL,
|
role_id TEXT NOT NULL,
|
||||||
PRIMARY KEY (guild_id, user_id, role_id)
|
PRIMARY KEY (guild_id, user_id, role_id)
|
||||||
)
|
)""";
|
||||||
""");
|
private static final String VOICE_CHANNELS =
|
||||||
}
|
|
||||||
|
|
||||||
private static void createVoiceChannels(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS voice_channels (
|
CREATE TABLE IF NOT EXISTS voice_channels (
|
||||||
channel_id TEXT PRIMARY KEY,
|
channel_id TEXT PRIMARY KEY,
|
||||||
|
|
@ -122,12 +82,8 @@ public final class FeatureTables {
|
||||||
bitrate INTEGER DEFAULT 64000,
|
bitrate INTEGER DEFAULT 64000,
|
||||||
locked INTEGER DEFAULT 0,
|
locked INTEGER DEFAULT 0,
|
||||||
text_channel_id TEXT
|
text_channel_id TEXT
|
||||||
)
|
)""";
|
||||||
""");
|
private static final String VOICE_PRESETS =
|
||||||
}
|
|
||||||
|
|
||||||
private static void createVoicePresets(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS voice_presets (
|
CREATE TABLE IF NOT EXISTS voice_presets (
|
||||||
user_id TEXT PRIMARY KEY,
|
user_id TEXT PRIMARY KEY,
|
||||||
|
|
@ -138,24 +94,16 @@ public final class FeatureTables {
|
||||||
locked INTEGER DEFAULT 0,
|
locked INTEGER DEFAULT 0,
|
||||||
allowed_role_id TEXT,
|
allowed_role_id TEXT,
|
||||||
denied_role_id TEXT
|
denied_role_id TEXT
|
||||||
)
|
)""";
|
||||||
""");
|
private static final String COUNTER_STATS =
|
||||||
}
|
|
||||||
|
|
||||||
private static void createCounterStats(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS counter_stats (
|
CREATE TABLE IF NOT EXISTS counter_stats (
|
||||||
date TEXT PRIMARY KEY,
|
date TEXT PRIMARY KEY,
|
||||||
joined INTEGER DEFAULT 0,
|
joined INTEGER DEFAULT 0,
|
||||||
left_count INTEGER DEFAULT 0,
|
left_count INTEGER DEFAULT 0,
|
||||||
difference INTEGER DEFAULT 0
|
difference INTEGER DEFAULT 0
|
||||||
)
|
)""";
|
||||||
""");
|
private static final String RATINGS =
|
||||||
}
|
|
||||||
|
|
||||||
private static void createRatings(Database database) {
|
|
||||||
database.execute(
|
|
||||||
"""
|
"""
|
||||||
CREATE TABLE IF NOT EXISTS ratings (
|
CREATE TABLE IF NOT EXISTS ratings (
|
||||||
guild_id TEXT NOT NULL,
|
guild_id TEXT NOT NULL,
|
||||||
|
|
@ -163,31 +111,37 @@ public final class FeatureTables {
|
||||||
rating_type TEXT NOT NULL,
|
rating_type TEXT NOT NULL,
|
||||||
value INTEGER NOT NULL,
|
value INTEGER NOT NULL,
|
||||||
PRIMARY KEY (guild_id, user_id, rating_type)
|
PRIMARY KEY (guild_id, user_id, rating_type)
|
||||||
)
|
)""";
|
||||||
""");
|
|
||||||
|
public static void create(Database database) {
|
||||||
|
database.execute(ECONOMY);
|
||||||
|
database.execute(MUSIC_QUEUE);
|
||||||
|
database.execute(WARNINGS);
|
||||||
|
database.execute(MODERATION_CASES);
|
||||||
|
database.execute(ACTIVE_MUTES);
|
||||||
|
database.execute(CUSTOM_ROLES);
|
||||||
|
database.execute(VOICE_CHANNELS);
|
||||||
|
database.execute(VOICE_PRESETS);
|
||||||
|
database.execute(COUNTER_STATS);
|
||||||
|
database.execute(RATINGS);
|
||||||
|
createIndexes(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
return buildState(
|
||||||
game.dealerCards().toArray(String[]::new),
|
game.playerCards(),
|
||||||
|
game.dealerCards(),
|
||||||
sum,
|
sum,
|
||||||
calculateSum(game.dealerCards()),
|
calculateSum(game.dealerCards()),
|
||||||
game.bet(),
|
game.bet(),
|
||||||
true,
|
over,
|
||||||
0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BlackjackState(
|
|
||||||
game.playerCards().toArray(String[]::new),
|
|
||||||
game.dealerCards().toArray(String[]::new),
|
|
||||||
sum,
|
|
||||||
calculateSum(game.dealerCards()),
|
|
||||||
game.bet(),
|
|
||||||
false,
|
|
||||||
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 {
|
return buildState(
|
||||||
payout = 0;
|
game.playerCards(), dealerCards, playerSum, dealerSum, game.bet(), true, payout);
|
||||||
}
|
}
|
||||||
|
|
||||||
EconomyAccount account = repository.getAccount(userId);
|
private void creditUser(String userId, long amount) {
|
||||||
repository.save(account.withCash(account.getCash() + payout));
|
if (amount <= 0) return;
|
||||||
|
var account = repository.getAccount(userId);
|
||||||
return new BlackjackState(
|
repository.save(account.withCash(account.getCash() + amount));
|
||||||
game.playerCards().toArray(String[]::new),
|
|
||||||
dealerCards.toArray(String[]::new),
|
|
||||||
playerSum,
|
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
.getHook()
|
||||||
|
.sendMessageEmbeds(
|
||||||
EmbedFactory.create()
|
EmbedFactory.create()
|
||||||
.withTitle("✅ Сброс уровней выполнен")
|
.withTitle("✅ Сброс уровней выполнен")
|
||||||
.withDescription("Уровни и XP всех пользователей успешно сброшены!\n\n"
|
.withDescription(
|
||||||
|
"Уровни и XP всех пользователей успешно сброшены!\n\n"
|
||||||
+ "**Сохранено:**\n• Время в голосовых каналах\n"
|
+ "**Сохранено:**\n• Время в голосовых каналах\n"
|
||||||
+ "• Количество сообщений\n• Репутация\n\n"
|
+ "• Количество сообщений\n• Репутация\n\n"
|
||||||
+ "**Сброшено:**\n• Уровень → 0\n• XP → 0\n\n"
|
+ "**Сброшено:**\n• Уровень → 0\n• XP → 0\n\n"
|
||||||
+ "Теперь используется новая система уровней!")
|
+ "Теперь используется новая система уровней!")
|
||||||
.withColor(Color.GREEN)
|
.withColor(Color.GREEN)
|
||||||
.withFooter("Инициатор: " + event.getUser().getName(), event.getUser().getAvatarUrl())
|
.withFooter(
|
||||||
.build()).queue();
|
"Инициатор: " + 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
|
||||||
|
.getHook()
|
||||||
|
.sendMessageEmbeds(
|
||||||
EmbedFactory.create()
|
EmbedFactory.create()
|
||||||
.withTitle("❌ Ошибка")
|
.withTitle("❌ Ошибка")
|
||||||
.withDescription("Произошла ошибка при сбросе уровней: " + e.getMessage())
|
.withDescription("Произошла ошибка при сбросе уровней: " + e.getMessage())
|
||||||
.withColor(Color.RED).build()).queue();
|
.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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,18 @@ 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(
|
||||||
|
guildId,
|
||||||
|
(id, state) -> {
|
||||||
if (state == null) state = new GuildRadioState();
|
if (state == null) state = new GuildRadioState();
|
||||||
state.streamUrl = streamUrl;
|
state.streamUrl = streamUrl;
|
||||||
return state;
|
return state;
|
||||||
|
|
@ -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,14 +86,18 @@ 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)
|
||||||
|
.loadItem(url)
|
||||||
|
.subscribe(
|
||||||
|
new AbstractAudioLoadResultHandler() {
|
||||||
@Override
|
@Override
|
||||||
public void ontrackLoaded(TrackLoaded r) {
|
public void ontrackLoaded(TrackLoaded r) {
|
||||||
if (vol != 100) link.createOrUpdatePlayer().setVolume(vol).subscribe();
|
if (vol != 100) link.createOrUpdatePlayer().setVolume(vol).subscribe();
|
||||||
mm.getScheduler().enqueue(r.getTrack());
|
mm.getScheduler().enqueue(r.getTrack());
|
||||||
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaylistLoaded(dev.arbjerg.lavalink.client.player.PlaylistLoaded r) {
|
public void onPlaylistLoaded(dev.arbjerg.lavalink.client.player.PlaylistLoaded r) {
|
||||||
if (!r.getTracks().isEmpty()) {
|
if (!r.getTracks().isEmpty()) {
|
||||||
|
|
@ -94,6 +106,7 @@ public class RadioManager {
|
||||||
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSearchResultLoaded(dev.arbjerg.lavalink.client.player.SearchResult r) {
|
public void onSearchResultLoaded(dev.arbjerg.lavalink.client.player.SearchResult r) {
|
||||||
if (!r.getTracks().isEmpty()) {
|
if (!r.getTracks().isEmpty()) {
|
||||||
|
|
@ -102,11 +115,20 @@ public class RadioManager {
|
||||||
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
LoggerDecorator.music(logger, "Radio started in guild " + guildId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void noMatches() { LoggerDecorator.warn(logger, "No radio stream: " + url); }
|
public void noMatches() {
|
||||||
|
LoggerDecorator.warn(logger, "No radio stream: " + url);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void loadFailed(dev.arbjerg.lavalink.client.player.LoadFailed e) {
|
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"));
|
LoggerDecorator.warn(
|
||||||
|
logger,
|
||||||
|
"Failed to load radio: "
|
||||||
|
+ (e.getException().getMessage() != null
|
||||||
|
? e.getException().getMessage()
|
||||||
|
: "unknown"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +136,10 @@ public class RadioManager {
|
||||||
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()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue