diff --git a/build.gradle b/build.gradle index 16e9d74..25542a8 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,7 @@ dependencies { implementation("net.dv8tion:JDA:5.0.0-alpha.3") { exclude module: 'opus-java' } + implementation('com.zaxxer:HikariCP:5.0.1') implementation 'club.minnced:discord-webhooks:0.7.4' implementation 'com.fasterxml.jackson.core:jackson-core:2.13.1' implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.1' @@ -50,7 +51,7 @@ shadowJar { group = 'work.novablog.mcplugin' version = '3.1' description = 'DiscordConnect' -archivesBaseName = 'DiscordConnect-spigot' +archivesBaseName = 'DiscordConnect-old-spigot' java.sourceCompatibility = JavaVersion.VERSION_1_8 tasks.withType(JavaCompile) { diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/DiscordConnect.java b/src/main/java/work/novablog/mcplugin/discordconnect/DiscordConnect.java index 2891126..6ee00bc 100644 --- a/src/main/java/work/novablog/mcplugin/discordconnect/DiscordConnect.java +++ b/src/main/java/work/novablog/mcplugin/discordconnect/DiscordConnect.java @@ -7,12 +7,13 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import work.novablog.mcplugin.discordconnect.account.AccountManager; +import work.novablog.mcplugin.discordconnect.account.db.DatabaseConfig; import work.novablog.mcplugin.discordconnect.command.BukkitCommand; import work.novablog.mcplugin.discordconnect.command.DiscordCommandExecutor; import work.novablog.mcplugin.discordconnect.command.DiscordStandardCommand; import work.novablog.mcplugin.discordconnect.listener.BukkitListener; import work.novablog.mcplugin.discordconnect.listener.LunaChatListener; -import work.novablog.mcplugin.discordconnect.util.AccountManager; import work.novablog.mcplugin.discordconnect.util.ConfigManager; import work.novablog.mcplugin.discordconnect.util.GithubAPI; import work.novablog.mcplugin.discordconnect.util.discord.BotManager; @@ -20,7 +21,6 @@ import javax.annotation.Nullable; import javax.security.auth.login.LoginException; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Optional; @@ -38,7 +38,7 @@ public final class DiscordConnect extends JavaPlugin { private UUIDCacheData uuidCacheData; private LunaChatListener lunaChatListener; - private final AccountManager accountManager = new AccountManager(new File(getDataFolder(), "accounts.yml")); + private @Nullable AccountManager accountManager; /** * インスタンスを返します @@ -56,7 +56,7 @@ public static DiscordConnect getInstance() { return botManager; } - public AccountManager getAccountManager() { + public @Nullable AccountManager getAccountManager() { return accountManager; } @@ -105,7 +105,12 @@ public void onEnable() { discordCommandExecutor = new DiscordCommandExecutor(); discordCommandExecutor.registerCommand(new DiscordStandardCommand()); - init(); + try { + init(); + } catch (Throwable e) { + e.printStackTrace(); + setEnabled(false); + } } /** @@ -115,20 +120,17 @@ public void onEnable() { * 複数回呼び出した場合、新しいconfigデータが読み出されます *

*/ - public void init() { + public void init() throws IOException { if(botManager != null) botManager.botShutdown(true); if(discordWebhookSenders != null) discordWebhookSenders.forEach(DiscordWebhookSender::shutdown); if(bukkitListener != null) HandlerList.unregisterAll(bukkitListener); if(lunaChatListener != null) HandlerList.unregisterAll(lunaChatListener); - ConfigManager configManager; - try { - configManager = new ConfigManager(this); - } catch (IOException e) { - e.printStackTrace(); - return; - } - accountManager.loadFile(); + ConfigManager configManager = new ConfigManager(this); + + DatabaseConfig dbConfig = configManager.getAccountsDatabaseConfig(); + accountManager = AccountManager.createManager(dbConfig, this); + accountManager.connect(); discordCommandExecutor.setAdminRole(configManager.adminRole); @@ -160,7 +162,7 @@ public void init() { } //BungeecordイベントのListenerを登録 - bukkitListener = new BukkitListener(configManager.fromMinecraftToDiscordName); + bukkitListener = new BukkitListener(configManager); getServer().getPluginManager().registerEvents(bukkitListener, this); if(lunaChatAPI != null) { lunaChatListener = new LunaChatListener( @@ -203,5 +205,13 @@ public void init() { public void onDisable() { if(botManager != null) botManager.botShutdown(false); if(discordWebhookSenders != null) discordWebhookSenders.forEach(DiscordWebhookSender::shutdown); + + if (accountManager != null) { + try { + accountManager.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } } diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/account/AccountManager.java b/src/main/java/work/novablog/mcplugin/discordconnect/account/AccountManager.java new file mode 100644 index 0000000..c7186fc --- /dev/null +++ b/src/main/java/work/novablog/mcplugin/discordconnect/account/AccountManager.java @@ -0,0 +1,97 @@ +package work.novablog.mcplugin.discordconnect.account; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import work.novablog.mcplugin.discordconnect.account.db.DatabaseConfig; +import work.novablog.mcplugin.discordconnect.account.db.MySQLAccountManager; +import work.novablog.mcplugin.discordconnect.account.db.SQLiteAccountManager; +import work.novablog.mcplugin.discordconnect.account.db.YamlAccountManager; + +import java.io.IOException; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadLocalRandom; + +public abstract class AccountManager { + private final Map linkingCodes = new ConcurrentHashMap<>(); + private final Map names = new ConcurrentHashMap<>(); + + + public abstract void connect() throws IOException; + + public abstract void close() throws IOException; + + + public final String generateCode(UUID playerUuid, String playerName) { + names.put(playerUuid, playerName); + String codeString; + do { + int code = ThreadLocalRandom.current().nextInt(10000); + codeString = String.format("%04d", code); + + } while (linkingCodes.putIfAbsent(codeString, playerUuid) != null); + return codeString; + } + + public @Nullable String getLinkingPlayerName(UUID playerId) { + return names.get(playerId); + } + + public final @Nullable UUID removeMinecraftIdByLinkCode(@NotNull String code) { + return linkingCodes.remove(code); + } + + public final Map linkingCodes() { + return linkingCodes; + } + + + public abstract CompletableFuture<@NotNull Boolean> isLinkedDiscord(@NotNull UUID minecraftId); + + public abstract CompletableFuture<@Nullable Long> getLinkedDiscordId(@NotNull UUID minecraftId); + + public abstract CompletableFuture linkDiscordId(@NotNull UUID minecraftId, long discordId); + + public abstract CompletableFuture<@NotNull Boolean> isLinkedMinecraft(long discordId); + + public abstract CompletableFuture<@Nullable UUID> getLinkedMinecraftId(long discordId); + + public abstract CompletableFuture unlinkByMinecraftId(@NotNull UUID minecraftId); + + public abstract CompletableFuture unlinkByDiscordId(long discordId); + + + public abstract CompletableFuture getLinkedAccountAll(); + + public abstract CompletableFuture getLinkedAccountCount(); + + + public static DatabaseConfig createDatabaseConfig(String dbType, ConfigurationSection config) { + switch (dbType.toLowerCase(Locale.ROOT)) { + case "yaml": + return new YamlAccountManager.DatabaseConfig(config); + case "sqlite": + return new SQLiteAccountManager.DatabaseConfig(config); + case "mysql": + return new MySQLAccountManager.DatabaseConfig(config); + } + throw new IllegalArgumentException("Unknown database type: " + dbType); + } + + public static AccountManager createManager(DatabaseConfig config, Plugin plugin) { + if (config instanceof YamlAccountManager.DatabaseConfig) { + return new YamlAccountManager(plugin.getDataFolder(), ((YamlAccountManager.DatabaseConfig) config)); + } else if (config instanceof SQLiteAccountManager.DatabaseConfig) { + return new SQLiteAccountManager(plugin.getDataFolder(), ((SQLiteAccountManager.DatabaseConfig) config)); + } else if (config instanceof MySQLAccountManager.DatabaseConfig) { + return new MySQLAccountManager(((MySQLAccountManager.DatabaseConfig) config)); + } + throw new IllegalArgumentException("Unknown database config: " + config.getClass().getSimpleName()); + } + +} diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/account/LinkedAccount.java b/src/main/java/work/novablog/mcplugin/discordconnect/account/LinkedAccount.java new file mode 100644 index 0000000..defbd42 --- /dev/null +++ b/src/main/java/work/novablog/mcplugin/discordconnect/account/LinkedAccount.java @@ -0,0 +1,25 @@ +package work.novablog.mcplugin.discordconnect.account; + +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public class LinkedAccount { + + private final @NotNull UUID minecraftId; + private final long discordId; + + public LinkedAccount(@NotNull UUID minecraftId, long discordId) { + this.minecraftId = minecraftId; + this.discordId = discordId; + } + + public @NotNull UUID getMinecraftId() { + return minecraftId; + } + + public long getDiscordId() { + return discordId; + } + +} diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/account/db/DatabaseConfig.java b/src/main/java/work/novablog/mcplugin/discordconnect/account/db/DatabaseConfig.java new file mode 100644 index 0000000..ab99a1f --- /dev/null +++ b/src/main/java/work/novablog/mcplugin/discordconnect/account/db/DatabaseConfig.java @@ -0,0 +1,19 @@ +package work.novablog.mcplugin.discordconnect.account.db; + +import org.bukkit.configuration.ConfigurationSection; + +public abstract class DatabaseConfig { + + protected final ConfigurationSection config; + + public DatabaseConfig(ConfigurationSection config) { + this.config = config; + } + + public abstract String getType(); + + public ConfigurationSection getConfig() { + return config; + } + +} diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/account/db/MySQLAccountManager.java b/src/main/java/work/novablog/mcplugin/discordconnect/account/db/MySQLAccountManager.java new file mode 100644 index 0000000..c73479d --- /dev/null +++ b/src/main/java/work/novablog/mcplugin/discordconnect/account/db/MySQLAccountManager.java @@ -0,0 +1,291 @@ +package work.novablog.mcplugin.discordconnect.account.db; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import work.novablog.mcplugin.discordconnect.account.AccountManager; +import work.novablog.mcplugin.discordconnect.account.LinkedAccount; + +import java.io.IOException; +import java.sql.*; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +public class MySQLAccountManager extends AccountManager { + + private final DatabaseConfig config; + private @Nullable HikariDataSource hikari; + + public MySQLAccountManager(DatabaseConfig config) { + this.config = config; + } + + public DatabaseConfig getConfig() { + return config; + } + + @Override + public void connect() throws IOException { + HikariConfig config = new HikariConfig(); + config.setDriverClassName("com.mysql.jdbc.Driver"); + config.setJdbcUrl("jdbc:mysql://" + this.config.getAddress() + "/" + this.config.getDatabase()); + config.setConnectionInitSql("SELECT 1"); + config.setAutoCommit(true); + this.config.getProperties().forEach(config::addDataSourceProperty); + hikari = new HikariDataSource(config); + initDatabase(); + } + + @Override + public void close() { + if (hikari != null) { + hikari.close(); + } + hikari = null; + } + + public void initDatabase() throws IOException { + if (hikari == null) + throw new IOException("database closed"); + + try (Connection conn = hikari.getConnection(); + Statement stmt = conn.createStatement()) { + String sql = "CREATE TABLE IF NOT EXISTS linked (mc_uuid VARCHAR(36) UNIQUE, discord_id BIGINT, link_time BIGINT)"; + stmt.execute(sql); + } catch (SQLException e) { + throw new IOException(e); + } + } + + + @Override + public CompletableFuture<@NotNull Boolean> isLinkedDiscord(@NotNull UUID minecraftId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT 1 FROM `linked` WHERE `mc_uuid` = ? LIMIT 1"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, minecraftId.toString().toLowerCase(Locale.ROOT)); + ResultSet rs = stmt.executeQuery(); + return rs.next(); + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture<@Nullable Long> getLinkedDiscordId(@NotNull UUID minecraftId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT `discord_id` FROM `linked` WHERE `mc_uuid` = ? LIMIT 1"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, minecraftId.toString().toLowerCase(Locale.ROOT)); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) + return rs.getLong("discord_id"); + return null; + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture linkDiscordId(@NotNull UUID minecraftId, long discordId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + try (Connection conn = hikari.getConnection()) { + try (PreparedStatement stmt = conn.prepareStatement("DELETE FROM `linked` WHERE `discord_id` = ?")) { + stmt.setLong(1, discordId); + stmt.execute(); + } + + try (PreparedStatement stmt = conn.prepareStatement("REPLACE INTO `linked` VALUES (?, ?, ?)")) { + stmt.setString(1, minecraftId.toString().toLowerCase(Locale.ROOT)); + stmt.setLong(2, discordId); + stmt.setLong(3, System.currentTimeMillis()); + stmt.executeUpdate(); + } + + } catch (SQLException e) { + throw new CompletionException(e); + } + return null; + }); + } + + @Override + public CompletableFuture<@NotNull Boolean> isLinkedMinecraft(long discordId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT 1 FROM `linked` WHERE `discord_id` = ? LIMIT 1"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setLong(1, discordId); + ResultSet rs = stmt.executeQuery(); + return rs.next(); + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture<@Nullable UUID> getLinkedMinecraftId(long discordId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT `mc_uuid` FROM `linked` WHERE `discord_id` = ? LIMIT 1"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setLong(1, discordId); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) + return UUID.fromString(rs.getString("mc_uuid")); + return null; + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture unlinkByMinecraftId(@NotNull UUID minecraftId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "DELETE FROM `linked` WHERE `mc_uuid` = ?"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, minecraftId.toString().toLowerCase(Locale.ROOT)); + stmt.executeUpdate(); + + } catch (SQLException e) { + throw new CompletionException(e); + } + return null; + }); + } + + @Override + public CompletableFuture unlinkByDiscordId(long discordId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "DELETE FROM `linked` WHERE `discord_id` = ?"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setLong(1, discordId); + stmt.executeUpdate(); + + } catch (SQLException e) { + throw new CompletionException(e); + } + return null; + }); + } + + @Override + public CompletableFuture getLinkedAccountAll() { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT `mc_uuid`, `discord_id` FROM `linked`"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + + List data = Lists.newArrayList(); + while (rs.next()) + data.add(new LinkedAccount(UUID.fromString(rs.getString(1)), rs.getLong(2))); + + return data.toArray(new LinkedAccount[0]); + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture getLinkedAccountCount() { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT COUNT(*) AS `total` FROM `linked`"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + + if (rs.next()) + return rs.getInt("total"); + return 0; + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + + public static class DatabaseConfig extends work.novablog.mcplugin.discordconnect.account.db.DatabaseConfig { + + public DatabaseConfig(ConfigurationSection config) { + super(config); + } + + @Override + public String getType() { + return "mysql"; + } + + public String getAddress() { + return config.getString("address", "localhost:3306"); + } + + public String getDatabase() { + return config.getString("database", "discordconnect_accounts"); + } + + public Map getProperties() { + ConfigurationSection props = config.getConfigurationSection("properties"); + if (props == null) + return Collections.emptyMap(); + + Map values = Maps.newHashMap(); + for (String key : props.getKeys(false)) { + values.put(key, props.get(key)); + } + + return values; + } + + } + +} diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/account/db/SQLiteAccountManager.java b/src/main/java/work/novablog/mcplugin/discordconnect/account/db/SQLiteAccountManager.java new file mode 100644 index 0000000..f28c472 --- /dev/null +++ b/src/main/java/work/novablog/mcplugin/discordconnect/account/db/SQLiteAccountManager.java @@ -0,0 +1,290 @@ +package work.novablog.mcplugin.discordconnect.account.db; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.bukkit.configuration.ConfigurationSection; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import work.novablog.mcplugin.discordconnect.account.AccountManager; +import work.novablog.mcplugin.discordconnect.account.LinkedAccount; + +import java.io.File; +import java.io.IOException; +import java.sql.*; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +public class SQLiteAccountManager extends AccountManager { + + private final DatabaseConfig config; + private final File dataDir; + private @Nullable HikariDataSource hikari; + + public SQLiteAccountManager(File dataDir, DatabaseConfig config) { + this.dataDir = dataDir; + this.config = config; + } + + public DatabaseConfig getConfig() { + return config; + } + + @Override + public void connect() throws IOException { + HikariConfig config = new HikariConfig(); + config.setDriverClassName("org.sqlite.JDBC"); + config.setJdbcUrl("jdbc:sqlite:" + new File(dataDir, this.config.getFile())); + config.setConnectionInitSql("SELECT 1"); + config.setAutoCommit(true); + this.config.getProperties().forEach(config::addDataSourceProperty); + hikari = new HikariDataSource(config); + initDatabase(); + } + + @Override + public void close() { + if (hikari != null) { + hikari.close(); + } + hikari = null; + } + + public void initDatabase() throws IOException { + if (hikari == null) + throw new IOException("database closed"); + + try (Connection conn = hikari.getConnection(); + Statement stmt = conn.createStatement()) { + String sql = "CREATE TABLE IF NOT EXISTS linked (mc_uuid VARCHAR(36) UNIQUE, discord_id BIGINT, link_time BIGINT)"; + stmt.execute(sql); + } catch (SQLException e) { + throw new IOException(e); + } + } + + + @Override + public CompletableFuture<@NotNull Boolean> isLinkedDiscord(@NotNull UUID minecraftId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT 1 FROM `linked` WHERE `mc_uuid` = ? LIMIT 1"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, minecraftId.toString().toLowerCase(Locale.ROOT)); + ResultSet rs = stmt.executeQuery(); + return rs.next(); + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture<@Nullable Long> getLinkedDiscordId(@NotNull UUID minecraftId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT `discord_id` FROM `linked` WHERE `mc_uuid` = ? LIMIT 1"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, minecraftId.toString().toLowerCase(Locale.ROOT)); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) + return rs.getLong("discord_id"); + return null; + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture linkDiscordId(@NotNull UUID minecraftId, long discordId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + try (Connection conn = hikari.getConnection()) { + try (PreparedStatement stmt = conn.prepareStatement("DELETE FROM `linked` WHERE `discord_id` = ?")) { + stmt.setLong(1, discordId); + stmt.execute(); + } + + try (PreparedStatement stmt = conn.prepareStatement("REPLACE INTO `linked` VALUES (?, ?, ?)")) { + stmt.setString(1, minecraftId.toString().toLowerCase(Locale.ROOT)); + stmt.setLong(2, discordId); + stmt.setLong(3, System.currentTimeMillis()); + stmt.executeUpdate(); + } + + } catch (SQLException e) { + throw new CompletionException(e); + } + return null; + }); + } + + @Override + public CompletableFuture<@NotNull Boolean> isLinkedMinecraft(long discordId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT 1 FROM `linked` WHERE `discord_id` = ? LIMIT 1"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setLong(1, discordId); + ResultSet rs = stmt.executeQuery(); + return rs.next(); + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture<@Nullable UUID> getLinkedMinecraftId(long discordId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT `mc_uuid` FROM `linked` WHERE `discord_id` = ? LIMIT 1"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setLong(1, discordId); + ResultSet rs = stmt.executeQuery(); + + if (rs.next()) + return UUID.fromString(rs.getString("mc_uuid")); + return null; + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture unlinkByMinecraftId(@NotNull UUID minecraftId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "DELETE FROM `linked` WHERE `mc_uuid` = ?"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setString(1, minecraftId.toString().toLowerCase(Locale.ROOT)); + stmt.executeUpdate(); + + } catch (SQLException e) { + throw new CompletionException(e); + } + return null; + }); + } + + @Override + public CompletableFuture unlinkByDiscordId(long discordId) { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "DELETE FROM `linked` WHERE `discord_id` = ?"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql)) { + stmt.setLong(1, discordId); + stmt.executeUpdate(); + + } catch (SQLException e) { + throw new CompletionException(e); + } + return null; + }); + } + + @Override + public CompletableFuture getLinkedAccountAll() { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT `mc_uuid`, `discord_id` FROM `linked`"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + + List data = Lists.newArrayList(); + while (rs.next()) + data.add(new LinkedAccount(UUID.fromString(rs.getString(1)), rs.getLong(2))); + + return data.toArray(new LinkedAccount[0]); + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + @Override + public CompletableFuture getLinkedAccountCount() { + return CompletableFuture.supplyAsync(() -> { + if (hikari == null) + throw new CompletionException(new IOException("database closed")); + + String sql = "SELECT COUNT(*) AS `total` FROM `linked`"; + try (Connection conn = hikari.getConnection(); + PreparedStatement stmt = conn.prepareStatement(sql); + ResultSet rs = stmt.executeQuery()) { + + if (rs.next()) + return rs.getInt("total"); + return 0; + + } catch (SQLException e) { + throw new CompletionException(e); + } + }); + } + + + public static class DatabaseConfig extends work.novablog.mcplugin.discordconnect.account.db.DatabaseConfig { + + public DatabaseConfig(ConfigurationSection config) { + super(config); + } + + @Override + public String getType() { + return "sqlite"; + } + + public String getFile() { + return config.getString("file", "./accounts.yml"); + } + + public Map getProperties() { + ConfigurationSection props = config.getConfigurationSection("properties"); + if (props == null) + return Collections.emptyMap(); + + Map values = Maps.newHashMap(); + for (String key : props.getKeys(false)) { + values.put(key, props.get(key)); + } + + return values; + } + + } + +} diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/account/db/YamlAccountManager.java b/src/main/java/work/novablog/mcplugin/discordconnect/account/db/YamlAccountManager.java new file mode 100644 index 0000000..efe4696 --- /dev/null +++ b/src/main/java/work/novablog/mcplugin/discordconnect/account/db/YamlAccountManager.java @@ -0,0 +1,184 @@ +package work.novablog.mcplugin.discordconnect.account.db; + +import com.google.common.collect.Maps; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import work.novablog.mcplugin.discordconnect.account.AccountManager; +import work.novablog.mcplugin.discordconnect.account.LinkedAccount; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +public class YamlAccountManager extends AccountManager { + private final Map discordAccounts = Maps.newConcurrentMap(); + private final File dataFilePath; + private final DatabaseConfig config; + + public YamlAccountManager(File dataDir, DatabaseConfig config) { + this.config = config; + this.dataFilePath = new File(dataDir, config.getFile()); + } + + + public DatabaseConfig getConfig() { + return config; + } + + public void loadFile() { + discordAccounts.clear(); + if (dataFilePath.isFile()) { + ConfigurationSection ids = YamlConfiguration.loadConfiguration(dataFilePath).getConfigurationSection("ids"); + if (ids != null) { + for (String key : ids.getKeys(false)) { + UUID uuid; + try { + uuid = UUID.fromString(key); + } catch (IllegalArgumentException e) { + continue; + } + long discordId = ids.getLong(key); + discordAccounts.put(uuid, discordId); + } + } + } + } + + public void saveFile() throws IOException { + YamlConfiguration config; + if (dataFilePath.isFile()) { + config = YamlConfiguration.loadConfiguration(dataFilePath); + } else { + config = new YamlConfiguration(); + } + + config.set("ids", null); + discordAccounts.forEach((uuid, accountId) -> + config.set("ids." + uuid.toString(), accountId)); + + config.save(dataFilePath); + } + + + @Override + public void connect() { + loadFile(); + } + + @Override + public void close() throws IOException { + saveFile(); + discordAccounts.clear(); + } + + + @Override + public CompletableFuture<@NotNull Boolean> isLinkedDiscord(@NotNull UUID minecraftId) { + return runCurrent(() -> discordAccounts.containsKey(minecraftId)); + } + + @Override + public CompletableFuture<@Nullable Long> getLinkedDiscordId(@NotNull UUID minecraftId) { + return runCurrent(() -> discordAccounts.get(minecraftId)); + } + + @Override + public CompletableFuture linkDiscordId(@NotNull UUID minecraftId, long discordId) { + return runCurrent(() -> { + discordAccounts.put(minecraftId, discordId); + saveFile(); + }); + } + + @Override + public CompletableFuture<@NotNull Boolean> isLinkedMinecraft(long discordId) { + return runCurrent(() -> discordAccounts.containsValue(discordId)); + } + + @Override + public CompletableFuture<@Nullable UUID> getLinkedMinecraftId(long discordId) { + return runCurrent(() -> discordAccounts.entrySet().stream() + .filter(e -> e.getValue() == discordId) + .map(Map.Entry::getKey) + .findFirst() + .orElse(null)); + } + + @Override + public CompletableFuture unlinkByMinecraftId(@NotNull UUID minecraftId) { + return runCurrent(() -> { + if (discordAccounts.remove(minecraftId) != null) + saveFile(); + }); + } + + @Override + public CompletableFuture unlinkByDiscordId(long discordId) { + return runCurrent(() -> { + if (discordAccounts.values().removeIf(dId -> dId == discordId)) + saveFile(); + }); + } + + + @Override + public CompletableFuture getLinkedAccountAll() { + return runCurrent(() -> discordAccounts.entrySet().stream() + .map(e -> new LinkedAccount(e.getKey(), e.getValue())) + .toArray(LinkedAccount[]::new)); + } + + @Override + public CompletableFuture getLinkedAccountCount() { + return CompletableFuture.completedFuture(discordAccounts.size()); + } + + + private CompletableFuture runCurrent(Supplier runnable) { + try { + return CompletableFuture.completedFuture(runnable.get()); + } catch (Throwable e) { + CompletableFuture f = new CompletableFuture<>(); + f.completeExceptionally(e); + return f; + } + } + + private CompletableFuture runCurrent(ThrowRunnable runnable) { + try { + runnable.run(); + return CompletableFuture.completedFuture(null); + } catch (Throwable e) { + CompletableFuture f = new CompletableFuture<>(); + f.completeExceptionally(e); + return f; + } + } + + private interface ThrowRunnable { + void run() throws Throwable; + } + + + public static class DatabaseConfig extends work.novablog.mcplugin.discordconnect.account.db.DatabaseConfig { + public DatabaseConfig(ConfigurationSection config) { + super(config); + } + + @Override + public String getType() { + return "yaml"; + } + + public String getFile() { + return config.getString("file", "./accounts.yml"); + } + + } + +} diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/command/BukkitCommand.java b/src/main/java/work/novablog/mcplugin/discordconnect/command/BukkitCommand.java index f2d61b0..1a52bac 100644 --- a/src/main/java/work/novablog/mcplugin/discordconnect/command/BukkitCommand.java +++ b/src/main/java/work/novablog/mcplugin/discordconnect/command/BukkitCommand.java @@ -38,7 +38,7 @@ public void linkCmd(CommandSender sender, String[] args) { } Player player = (Player) sender; String botName = instance.getBotManager().getBotUser().getName(); - String code = instance.getAccountManager().generateCode(player.getUniqueId()); + String code = instance.getAccountManager().generateCode(player.getUniqueId(), player.getName()); sender.sendMessage(ConfigManager.Message.accountLinkShowCode.toString() .replaceAll("\\{bot}", botName) @@ -46,7 +46,13 @@ public void linkCmd(CommandSender sender, String[] args) { } public void reloadCmd(CommandSender sender, String[] args) { - DiscordConnect.getInstance().init(); + try { + DiscordConnect.getInstance().init(); + } catch (Throwable e) { + e.printStackTrace(); + sender.sendMessage(ConfigManager.Message.configReloadFailed.toString()); + return; + } sender.sendMessage(ConfigManager.Message.configReloaded.toString()); } diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/command/DiscordStandardCommand.java b/src/main/java/work/novablog/mcplugin/discordconnect/command/DiscordStandardCommand.java index 72b76c6..0601f91 100644 --- a/src/main/java/work/novablog/mcplugin/discordconnect/command/DiscordStandardCommand.java +++ b/src/main/java/work/novablog/mcplugin/discordconnect/command/DiscordStandardCommand.java @@ -103,6 +103,10 @@ public void reloadCmd(Member member, DiscordBotSender channel, String[] args) { eb.setDescription(ConfigManager.Message.discordCommandReloading.toString()); channel.addQueue(eb.build()); - DiscordConnect.getInstance().init(); + try { + DiscordConnect.getInstance().init(); + } catch (Throwable e) { + e.printStackTrace(); + } } } diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/listener/BukkitListener.java b/src/main/java/work/novablog/mcplugin/discordconnect/listener/BukkitListener.java index 7f89406..be716a5 100644 --- a/src/main/java/work/novablog/mcplugin/discordconnect/listener/BukkitListener.java +++ b/src/main/java/work/novablog/mcplugin/discordconnect/listener/BukkitListener.java @@ -8,10 +8,12 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.jetbrains.annotations.NotNull; import work.novablog.mcplugin.discordconnect.DiscordConnect; +import work.novablog.mcplugin.discordconnect.account.AccountManager; import work.novablog.mcplugin.discordconnect.util.ConfigManager; import work.novablog.mcplugin.discordconnect.util.ConvertUtil; import work.novablog.mcplugin.discordconnect.util.discord.BotManager; @@ -19,16 +21,21 @@ import java.awt.*; import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; public class BukkitListener implements Listener { + private final ConfigManager config; private final String fromMinecraftToDiscordName; /** * bungeecordのイベントを受け取るインスタンスを生成します - * @param fromMinecraftToDiscordName マイクラからDiscordへ転送するときの名前欄のフォーマット + * @param config プラグイン設定 */ - public BukkitListener(@NotNull String fromMinecraftToDiscordName) { - this.fromMinecraftToDiscordName = fromMinecraftToDiscordName; + public BukkitListener(@NotNull ConfigManager config) { + this.config = config; + this.fromMinecraftToDiscordName = config.fromMinecraftToDiscordName; } /** @@ -60,6 +67,45 @@ public void onChat(AsyncPlayerChatEvent event) { } } + @EventHandler + public void onPreLogin(AsyncPlayerPreLoginEvent event) { + if (!config.isAccountLinkRequired() || !AsyncPlayerPreLoginEvent.Result.ALLOWED.equals(event.getLoginResult())) + return; + + DiscordConnect plugin = DiscordConnect.getInstance(); + UUID playerId = event.getUniqueId(); + String playerName = event.getName(); + AccountManager accountManager = plugin.getAccountManager(); + + try { + Objects.requireNonNull(accountManager, "Account Manager not loaded"); + Boolean linked = accountManager.isLinkedDiscord(playerId).get(); + + if (Boolean.TRUE.equals(linked)) { + return; // linked + } + + // create code + BotManager botManager = Objects.requireNonNull(plugin.getBotManager(), "Bot Manager not loaded"); + String botName = botManager.getBotUser().getName(); + + String code = accountManager.linkingCodes().entrySet().stream() + .filter(e -> playerId.equals(e.getValue())) + .map(Map.Entry::getKey) + .findAny() + .orElseGet(() -> accountManager.generateCode(playerId, playerName)); + + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, ConfigManager.Message.accountLinkRequired.toString() + .replaceAll("\\{bot}", botName) + .replaceAll("\\{code}", code)); + + + } catch (Throwable e) { + e.printStackTrace(); + event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, ConfigManager.Message.accountLinkProcessError.toString()); + } + } + /** * プレイヤーがログインしたら実行されます * @param e ログイン情報 diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/listener/DiscordListener.java b/src/main/java/work/novablog/mcplugin/discordconnect/listener/DiscordListener.java index e04fde5..382f7ce 100644 --- a/src/main/java/work/novablog/mcplugin/discordconnect/listener/DiscordListener.java +++ b/src/main/java/work/novablog/mcplugin/discordconnect/listener/DiscordListener.java @@ -3,6 +3,7 @@ import com.gmail.necnionch.myapp.markdownconverter.MarkComponent; import com.gmail.necnionch.myapp.markdownconverter.MarkdownConverter; import net.dv8tion.jda.api.entities.ChannelType; +import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; @@ -11,12 +12,11 @@ import net.md_5.bungee.api.chat.ComponentBuilder; import net.md_5.bungee.api.chat.TextComponent; import org.bukkit.Bukkit; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import work.novablog.mcplugin.discordconnect.DiscordConnect; +import work.novablog.mcplugin.discordconnect.account.AccountManager; import work.novablog.mcplugin.discordconnect.command.DiscordCommandExecutor; -import work.novablog.mcplugin.discordconnect.util.AccountManager; import work.novablog.mcplugin.discordconnect.util.ConfigManager; import work.novablog.mcplugin.discordconnect.util.discord.BotManager; @@ -171,6 +171,8 @@ public void onMessageReceived(@NotNull MessageReceivedEvent receivedMessage) { private boolean processLinkCodeMessage(MessageReceivedEvent event, long userId) { String content = event.getMessage().getContentStripped(); AccountManager mgr = DiscordConnect.getInstance().getAccountManager(); + if (mgr == null) + return false; Matcher matcher = Pattern.compile("(\\d+)").matcher(content); while (matcher.find()) { @@ -179,46 +181,51 @@ private boolean processLinkCodeMessage(MessageReceivedEvent event, long userId) if (uuid == null) continue; - Player player = Optional.ofNullable(Bukkit.getOfflinePlayer(uuid).getPlayer()).orElse(null); - if (player == null) - return true; + mgr.linkDiscordId(uuid, userId).whenComplete((v, th) -> { + if (th != null) { + th.printStackTrace(); + } else { + Bukkit.getScheduler().callSyncMethod(DiscordConnect.getInstance(), () -> { + String playerName = Objects.requireNonNull(mgr.getLinkingPlayerName(uuid)); + processLinkedPlayer(uuid, playerName, event.getAuthor(), event.getChannel()); + return null; + }); + } + }); + return true; + } + return false; + } - mgr.setLinkedDiscordId(uuid, userId); - mgr.saveFile(); + private void processLinkedPlayer(UUID playerId, String playerName, User user, MessageChannel channel) { + try { + Optional.ofNullable(Bukkit.getPlayer(playerId)).ifPresent(p -> + p.sendMessage(ConfigManager.Message.accountLinkLinked.toString() + .replaceAll("\\{user}", user.getAsTag())) + ); - String mcid = player.getName(); + channel.sendMessage(ConfigManager.Message.accountLinkLinkedToDiscord.toString() + .replaceAll("\\{mcid}", playerName)) + .queue(); - User author = event.getAuthor(); - try { - player.sendMessage(ConfigManager.Message.accountLinkLinked.toString() - .replaceAll("\\{user}", author.getAsTag())); + } catch (Throwable e) { + e.printStackTrace(); + } - event.getChannel().sendMessage(ConfigManager.Message.accountLinkLinkedToDiscord.toString() - .replaceAll("\\{mcid}", mcid)) - .queue(); + String linkedCommand = linkedToConsoleCommand; + if (linkedCommand != null && !linkedCommand.isEmpty()) { + try { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), linkedCommand + .replaceAll("\\{playerId}", playerId.toString()) + .replaceAll("\\{discordId}", user.getId()) + .replaceAll("\\{player}", playerName) + .replaceAll("\\{discord}", user.getAsTag()) + ); } catch (Throwable e) { e.printStackTrace(); } - - String linkedCommand = linkedToConsoleCommand; - if (linkedCommand != null && !linkedCommand.isEmpty()) { - Bukkit.getScheduler().callSyncMethod(DiscordConnect.getInstance(), () -> { - try { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), linkedCommand - .replaceAll("\\{playerId}", player.getUniqueId().toString()) - .replaceAll("\\{discordId}", author.getId()) - .replaceAll("\\{player}", player.getName()) - .replaceAll("\\{discord}", author.getAsTag()) - ); - } catch (Throwable e) { - e.printStackTrace(); - } - return null; - }); - } - return true; } - return false; } + } diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/util/AccountManager.java b/src/main/java/work/novablog/mcplugin/discordconnect/util/AccountManager.java deleted file mode 100644 index 4f066c5..0000000 --- a/src/main/java/work/novablog/mcplugin/discordconnect/util/AccountManager.java +++ /dev/null @@ -1,127 +0,0 @@ -package work.novablog.mcplugin.discordconnect.util; - -import com.google.common.collect.Maps; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.YamlConfiguration; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ThreadLocalRandom; - -public class AccountManager { - private final Map discordAccounts = Maps.newHashMap(); - private final Map linkingCodes = new ConcurrentHashMap<>(); - private final File dataFilePath; - - public AccountManager(File dataFilePath) { - this.dataFilePath = dataFilePath; - } - - - public String generateCode(UUID playerUuid) { - String codeString; - do { - int code = ThreadLocalRandom.current().nextInt(10000); - codeString = String.format("%04d", code); - - } while (linkingCodes.putIfAbsent(codeString, playerUuid) != null); - return codeString; - } - - public @Nullable UUID removeMinecraftIdByLinkCode(String code) { - return linkingCodes.remove(code); - } - - - public File getFilePath() { - return dataFilePath; - } - - public boolean saveFile() { - YamlConfiguration config; - if (dataFilePath.isFile()) { - config = YamlConfiguration.loadConfiguration(dataFilePath); - } else { - config = new YamlConfiguration(); - } - - config.set("ids", null); - discordAccounts.forEach((uuid, accountId) -> - config.set("ids." + uuid.toString(), accountId)); - - try { - config.save(dataFilePath); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - return true; - } - - public void loadFile() { - discordAccounts.clear(); - if (dataFilePath.isFile()) { - ConfigurationSection ids = YamlConfiguration.loadConfiguration(dataFilePath).getConfigurationSection("ids"); - if (ids != null) { - for (String key : ids.getKeys(false)) { - UUID uuid; - try { - uuid = UUID.fromString(key); - } catch (IllegalArgumentException e) { - continue; - } - long discordId = ids.getLong(key); - discordAccounts.put(uuid, discordId); - } - } - } - } - - - public boolean isLinkedDiscord(UUID minecraftId) { - return discordAccounts.containsKey(minecraftId); - } - - public @Nullable Long getLinkedDiscordId(UUID minecraftId) { - return discordAccounts.get(minecraftId); - } - - public void setLinkedDiscordId(UUID minecraftId, long discordId) { - discordAccounts.put(minecraftId, discordId); - } - - public boolean isLinkedMinecraft(long discordId) { - return discordAccounts.containsValue(discordId); - } - - public @Nullable UUID getLinkedMinecraftId(long discordId) { - return discordAccounts.entrySet().stream() - .filter(e -> e.getValue() == discordId) - .map(Map.Entry::getKey) - .findFirst() - .orElse(null); - } - - public void unlinkByMinecraftId(UUID minecraftId) { - discordAccounts.remove(minecraftId); - } - - public void unlinkByDiscordId(long discordId) { - discordAccounts.values().removeIf(dId -> dId == discordId); - } - - - public Map discordAccounts() { - return discordAccounts; - } - - public Map getDiscordAccounts() { - return Collections.unmodifiableMap(discordAccounts); - } - -} diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/util/ConfigManager.java b/src/main/java/work/novablog/mcplugin/discordconnect/util/ConfigManager.java index 4fd7735..62d2f5f 100644 --- a/src/main/java/work/novablog/mcplugin/discordconnect/util/ConfigManager.java +++ b/src/main/java/work/novablog/mcplugin/discordconnect/util/ConfigManager.java @@ -1,8 +1,11 @@ package work.novablog.mcplugin.discordconnect.util; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; +import work.novablog.mcplugin.discordconnect.account.AccountManager; +import work.novablog.mcplugin.discordconnect.account.db.DatabaseConfig; import java.io.File; import java.io.IOException; @@ -10,11 +13,14 @@ import java.nio.file.Files; import java.util.List; import java.util.Locale; +import java.util.Optional; public class ConfigManager { private static final int CONFIG_LATEST = 3; private static YamlConfiguration langData; + private final DatabaseConfig accountsDatabaseConfig; + private final boolean accountLinkRequired; public String botToken; public List botWebhookURLs; @@ -83,6 +89,14 @@ public ConfigManager(@NotNull Plugin plugin) throws IOException { lunaChatJapanizeFormat = pluginConfig.getString("japanizeFormat"); linkedToConsoleCommand = pluginConfig.getString("linkedToConsoleCommand"); + + String dbType = Optional.ofNullable(pluginConfig.getString("accounts.dbType")).orElse("yaml"); + ConfigurationSection dbSection = pluginConfig.getConfigurationSection("accounts.database." + dbType); + if (dbSection == null) + dbSection = new YamlConfiguration(); + accountLinkRequired = pluginConfig.getBoolean("accounts.requireLink", false); + + accountsDatabaseConfig = AccountManager.createDatabaseConfig(dbType, dbSection); } private YamlConfiguration getConfigData(Plugin plugin) throws IOException { @@ -108,6 +122,14 @@ private YamlConfiguration getLangData(Plugin plugin) throws IOException { return YamlConfiguration.loadConfiguration(langFile); } + public DatabaseConfig getAccountsDatabaseConfig() { + return accountsDatabaseConfig; + } + + public boolean isAccountLinkRequired() { + return accountLinkRequired; + } + private void backupOldFile(Plugin plugin, String targetFileName) throws IOException { File oldFile = new File(plugin.getDataFolder(), targetFileName + "_old"); Files.deleteIfExists(oldFile.toPath()); @@ -127,6 +149,7 @@ public enum Message { botIsReady, botRestarted, configReloaded, + configReloadFailed, configPropertyIsNull, dispatchedCommand, bungeeCommandPlayerOnly, @@ -139,6 +162,8 @@ public enum Message { accountLinkLinkedToDiscord, accountLinkLinked, accountLinkShowCode, + accountLinkRequired, + accountLinkProcessError, bungeeCommandDenied, bungeeCommandNotFound, diff --git a/src/main/java/work/novablog/mcplugin/discordconnect/util/ConvertUtil.java b/src/main/java/work/novablog/mcplugin/discordconnect/util/ConvertUtil.java index b30b1be..de902d8 100644 --- a/src/main/java/work/novablog/mcplugin/discordconnect/util/ConvertUtil.java +++ b/src/main/java/work/novablog/mcplugin/discordconnect/util/ConvertUtil.java @@ -5,7 +5,7 @@ import java.util.UUID; public class ConvertUtil { - private static final String AVATAR_IMG_URL = "https://crafatar.com/avatars/{uuid}?size=512&default=MHF_Steve&overlay"; + private static final String AVATAR_IMG_URL = "https://mineskin.eu/helm/{uuid}/100.png"; /** * MinecraftプレイヤーのUUIDからアバターのURLを取得します @@ -14,7 +14,7 @@ public class ConvertUtil { *

* @param uuid プレイヤーのUUID * @return プレイヤーのアバターURL - * @see crafatarを利用させていただいています + * @see Mineskinを利用させていただいています */ public static String getMinecraftAvatarURL(@NotNull UUID uuid) { String uuidText = uuid.toString(); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 06063be..91b0d3b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -30,5 +30,23 @@ japanizeFormat: "__JP__: {japanized}" # {discordId} --- DiscordユーザーID linkedToConsoleCommand: "" +# アカウントに関するデータベース設定 +accounts: + # 参加するためにリンクが必要 + requireLink: false + dbType: yaml + database: + yaml: + file: ./accounts.yml + sqlite: + file: ./accounts.db + properties: {} + mysql: + address: localhost:3306 + database: "discordconnect_accounts" + properties: + user: root + password: password + # この値は変更しないでください! (Don't change the value!) configVersion: 4 \ No newline at end of file diff --git a/src/main/resources/ja_JP.yml b/src/main/resources/ja_JP.yml index ae16e02..58bbb0b 100644 --- a/src/main/resources/ja_JP.yml +++ b/src/main/resources/ja_JP.yml @@ -7,6 +7,7 @@ normalShutdown: "botのシャットダウンを行います" botIsReady: "botのログインが完了しました" botRestarted: "botの再起動が完了しました" configReloaded: "§2configの再読込が完了しました" +configReloadFailed: "§cconfigの再読込中にエラーが発生しました" configPropertyIsNull: "コンフィグの {property} プロパティが定義されていません。" dispatchedCommand: "ユーザーID {authorId} がコマンド {commandLine} を実行しました。" @@ -18,6 +19,8 @@ pluginIsLatest: "プラグインは最新バージョンです({current})" accountLinkLinkedToDiscord: "Minecraftアカウント {mcid} と連携しました!" accountLinkLinked: "§6Discordアカウント §f{user}§6 と連携しました!" accountLinkShowCode: "§eDiscordボット §f{bot}§e に §6{code}§e を送信してください" +accountLinkRequired: "§7このサーバーに接続するには、§fDiscord §7とのリンクが必要です\n§eDiscordボット §f{bot} §eに §6{code} §eを送信してください" +accountLinkProcessError: "§cアカウント処理に失敗しました。管理者にお問い合わせください。" bungeeCommandDenied: "§cあなたはこのコマンドを実行する権限を持っていません!" bungeeCommandNotFound: "§cコマンドが見つかりません!"