diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-01-24 03:33:32 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-01-24 03:33:32 +0900 |
| commit | d053450b1ca68fe963b4656cf437472c435061cc (patch) | |
| tree | b007a7dda4561850b2c46a16b0065194a8a30367 | |
| parent | 3b935ce99257587aaae85b8b09f0396111bc7e4c (diff) | |
| download | LunaticChat-d053450b1ca68fe963b4656cf437472c435061cc.tar.gz LunaticChat-d053450b1ca68fe963b4656cf437472c435061cc.tar.bz2 LunaticChat-d053450b1ca68fe963b4656cf437472c435061cc.zip | |
feat: Improve initializer
5 files changed, 300 insertions, 140 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c102f..57d61d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Added i18n support for English and Japanese languages. - Fixed a Dokka currently references only the platform-paper module. +- Improve the cache strategy for kana conversion. +- Save the cache in memory to disk when the server stops. +- Added `/lc settings` command to manage LunaticChat user settings. +- Added `/lc status` command to check the status of LunaticChat. ### v0.4.1 diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt index aa9664c..6525b55 100644 --- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt @@ -1,6 +1,5 @@ package dev.m1sk9.lunaticChat.paper -import dev.m1sk9.lunaticChat.engine.converter.GoogleIMEClient import dev.m1sk9.lunaticChat.paper.command.core.CommandRegistry import dev.m1sk9.lunaticChat.paper.command.handler.DirectMessageHandler import dev.m1sk9.lunaticChat.paper.command.impl.ReplyCommand @@ -9,206 +8,114 @@ import dev.m1sk9.lunaticChat.paper.command.impl.lc.LunaticChatCommand import dev.m1sk9.lunaticChat.paper.command.setting.SettingHandlerRegistry import dev.m1sk9.lunaticChat.paper.command.setting.handler.DirectMessageNoticeSettingHandler import dev.m1sk9.lunaticChat.paper.command.setting.handler.JapaneseConversionSettingHandler -import dev.m1sk9.lunaticChat.paper.common.SpyPermissionManager import dev.m1sk9.lunaticChat.paper.common.UpdateCheckResult import dev.m1sk9.lunaticChat.paper.common.UpdateChecker import dev.m1sk9.lunaticChat.paper.config.ConfigManager import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration -import dev.m1sk9.lunaticChat.paper.converter.ConversionCache -import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager -import dev.m1sk9.lunaticChat.paper.listener.PlayerChatListener -import dev.m1sk9.lunaticChat.paper.listener.PlayerPresenceListener -import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager -import dev.m1sk9.lunaticChat.paper.settings.YamlPlayerSettingsStorage +import dev.m1sk9.lunaticChat.paper.listener.EventListenerRegistry import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO import kotlinx.coroutines.runBlocking import org.bukkit.event.Listener import org.bukkit.plugin.java.JavaPlugin import java.util.concurrent.atomic.AtomicBoolean -import kotlin.time.Duration.Companion.milliseconds class LunaticChat : JavaPlugin(), Listener { + // Public API - accessed by commands (maintain backward compatibility) lateinit var directMessageHandler: DirectMessageHandler lateinit var languageManager: LanguageManager - private lateinit var commandRegistry: CommandRegistry + // Private services + private lateinit var services: ServiceContainer + private lateinit var configuration: LunaticChatConfiguration + private lateinit var serviceInitializer: ServiceInitializer private var updateChecker: UpdateChecker? = null - private var romajiConverter: RomanjiConverter? = null - private var playerSettingsManager: PlayerSettingsManager? = null private val updateAvailable = AtomicBoolean(false) override fun onEnable() { saveDefaultConfig() - val configuration = ConfigManager.loadConfiguration(config) + configuration = ConfigManager.loadConfiguration(config) if (configuration.debug) { logger.warning("LunaticChat is running in debug mode.") logger.info("Debug: $configuration") } - // Initialize language manager (BEFORE commands) - languageManager = - LanguageManager( + val httpClient = HttpClient(CIO) + + // Initialize all services + serviceInitializer = + ServiceInitializer( plugin = this, + configuration = configuration, + httpClient = httpClient, logger = logger, - selectedLanguage = configuration.language, ) - languageManager.initialize() - logger.info("Language system initialized: ${configuration.language.code}") - - val httpClient = HttpClient(CIO) - - // Initialize player settings manager (always needed for DM notifications) - initializePlayerSettingsManager(configuration) + services = serviceInitializer.initialize() - // Initialize features - if (configuration.features.japaneseConversion.enabled) { - initializeJapaneseConversionFeature(configuration, httpClient) - } + // Set public API properties (for command access) + directMessageHandler = services.directMessageHandler + languageManager = services.languageManager - // Initialize handlers - directMessageHandler = - DirectMessageHandler( - settingsManager = playerSettingsManager, - romanjiConverter = romajiConverter, - ) + // Schedule periodic tasks + serviceInitializer.schedulePeriodicTasks() // Register commands and listeners - registerCommands(configuration) + registerCommands() registerEventListeners() // Check for updates if (configuration.checkForUpdates) { - updateChecker = - UpdateChecker( - currentVersion = pluginMeta.version, - logger = logger, - httpClient = httpClient, - ) - server.scheduler.runTaskAsynchronously( - this, - Runnable { - runBlocking { - checkUpdates() - } - }, - ) + initializeUpdateChecker(httpClient) } logger.info("LunaticChat enabled.") } override fun onDisable() { - playerSettingsManager?.saveToDisk() + serviceInitializer.shutdown(services) logger.info("LunaticChat disabled.") } /** - * Initializes the player settings manager. - * This is always needed for features like DM notifications. - */ - private fun initializePlayerSettingsManager(configuration: LunaticChatConfiguration) { - val settingsFile = dataFolder.resolve(configuration.userSettingsFilePath).toPath() - val storage = - YamlPlayerSettingsStorage( - settingsFile = settingsFile, - plugin = this, - logger = logger, - ) - - playerSettingsManager = - PlayerSettingsManager( - storage = storage, - logger = logger, - ) - playerSettingsManager!!.initialize() - } - - /** - * Initializes the Japanese conversion feature including: - * - Conversion cache - * - Google IME API client - * - Romanji converter - * - Periodic cache saving task - */ - private fun initializeJapaneseConversionFeature( - configuration: LunaticChatConfiguration, - httpClient: HttpClient, - ) { - // Initialize conversion cache - val cache = - ConversionCache( - cacheFile = dataFolder.resolve(configuration.features.japaneseConversion.cacheFilePath).toPath(), - maxEntries = configuration.features.japaneseConversion.cacheMaxEntries, - plugin = this, - logger = logger, - ) - cache.loadFromDisk() - - // Initialize Google IME API client - val apiClient = - GoogleIMEClient( - timeout = configuration.features.japaneseConversion.apiTimeout.milliseconds, - httpClient = httpClient, - ) - - // Initialize Romanji converter - romajiConverter = - RomanjiConverter( - cache = cache, - apiClient = apiClient, - logger = logger, - debugMode = configuration.debug, - ) - - // Schedule periodic cache saving - val saveInterval = configuration.features.japaneseConversion.cacheSaveIntervalSeconds * 20L - server.scheduler.runTaskTimerAsynchronously( - this, - Runnable { - cache.saveToDisk() - }, - saveInterval, - saveInterval, - ) - - // Register event listener for Japanese conversion - server.pluginManager.registerEvents(PlayerChatListener(romajiConverter!!, playerSettingsManager!!), this) - - logger.info("Japanese conversion feature enabled.") - } - - /** * Registers all commands based on enabled features. */ - private fun registerCommands(configuration: LunaticChatConfiguration) { - commandRegistry = CommandRegistry(this) - + private fun registerCommands() { + val commandRegistry = CommandRegistry(this) val settingHandlerRegistry = SettingHandlerRegistry() - settingHandlerRegistry.register( - DirectMessageNoticeSettingHandler(playerSettingsManager!!, languageManager), - ) - commandRegistry.registerAll( - TellCommand(this, directMessageHandler, languageManager), - LunaticChatCommand(this, settingHandlerRegistry, languageManager), + // Always register DM notification setting + settingHandlerRegistry.register( + DirectMessageNoticeSettingHandler( + services.playerSettingsManager, + services.languageManager, + ), ) - if (configuration.features.japaneseConversion.enabled) { + // Conditionally register Japanese conversion setting + if (services.romajiConverter != null) { settingHandlerRegistry.register( - JapaneseConversionSettingHandler(playerSettingsManager!!, languageManager), + JapaneseConversionSettingHandler( + services.playerSettingsManager, + services.languageManager, + ), ) } - // Register /reply command if quick replies are enabled + // Register core commands + commandRegistry.registerAll( + TellCommand(this, services.directMessageHandler, services.languageManager), + LunaticChatCommand(this, settingHandlerRegistry, services.languageManager), + ) + + // Conditionally register /reply command if quick replies are enabled if (configuration.features.quickRepliesEnabled.enabled) { commandRegistry.registerAll( - ReplyCommand(this, directMessageHandler, languageManager), + ReplyCommand(this, services.directMessageHandler, services.languageManager), ) } @@ -219,8 +126,27 @@ class LunaticChat : * Registers all event listeners. */ private fun registerEventListeners() { - server.pluginManager.registerEvents(SpyPermissionManager, this) - server.pluginManager.registerEvents(PlayerPresenceListener(this, languageManager, updateAvailable), this) + EventListenerRegistry.registerAll(this, services, updateAvailable) + } + + /** + * Initializes the update checker. + */ + private fun initializeUpdateChecker(httpClient: HttpClient) { + updateChecker = + UpdateChecker( + currentVersion = pluginMeta.version, + logger = logger, + httpClient = httpClient, + ) + server.scheduler.runTaskAsynchronously( + this, + Runnable { + runBlocking { + checkUpdates() + } + }, + ) } private suspend fun checkUpdates() { diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt new file mode 100644 index 0000000..6cd700b --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt @@ -0,0 +1,24 @@ +package dev.m1sk9.lunaticChat.paper + +import dev.m1sk9.lunaticChat.paper.command.handler.DirectMessageHandler +import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager + +/** + * Container for initialized services. + * + * This data class holds all services that have been initialized during plugin startup, + * eliminating the need for null-assertion operators throughout the codebase. + * + * @property languageManager Always available (initialized first) + * @property playerSettingsManager Always available (required for DM notifications) + * @property directMessageHandler Always available (core feature) + * @property romajiConverter Optional (only when Japanese conversion feature is enabled) + */ +data class ServiceContainer( + val languageManager: LanguageManager, + val playerSettingsManager: PlayerSettingsManager, + val directMessageHandler: DirectMessageHandler, + val romajiConverter: RomanjiConverter? = null, +) diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt new file mode 100644 index 0000000..e39f04f --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt @@ -0,0 +1,162 @@ +package dev.m1sk9.lunaticChat.paper + +import dev.m1sk9.lunaticChat.engine.converter.GoogleIMEClient +import dev.m1sk9.lunaticChat.paper.command.handler.DirectMessageHandler +import dev.m1sk9.lunaticChat.paper.config.LunaticChatConfiguration +import dev.m1sk9.lunaticChat.paper.converter.ConversionCache +import dev.m1sk9.lunaticChat.paper.converter.RomanjiConverter +import dev.m1sk9.lunaticChat.paper.i18n.LanguageManager +import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager +import dev.m1sk9.lunaticChat.paper.settings.YamlPlayerSettingsStorage +import io.ktor.client.HttpClient +import org.bukkit.plugin.java.JavaPlugin +import java.util.logging.Logger +import kotlin.time.Duration.Companion.milliseconds + +/** + * Handles initialization and shutdown of all plugin services. + * + * This class centralizes service initialization logic, ensuring proper + * dependency order and eliminating the need for null-assertion operators. + */ +class ServiceInitializer( + private val plugin: JavaPlugin, + private val configuration: LunaticChatConfiguration, + private val httpClient: HttpClient, + private val logger: Logger, +) { + private var conversionCache: ConversionCache? = null + + /** + * Initializes all services in dependency order. + * + * Initialization order: + * 1. LanguageManager (required by all features) + * 2. PlayerSettingsManager (required for DM notifications) + * 3. Japanese Conversion (optional, config-dependent) + * 4. DirectMessageHandler (depends on settings manager and romaji converter) + * + * @return ServiceContainer with all initialized services + */ + fun initialize(): ServiceContainer { + // 1. Initialize language manager (BEFORE commands) + val languageManager = + LanguageManager( + plugin = plugin, + logger = logger, + selectedLanguage = configuration.language, + ) + languageManager.initialize() + logger.info("Language system initialized: ${configuration.language.code}") + + // 2. Initialize player settings manager (always needed for DM notifications) + val playerSettingsManager = initializePlayerSettingsManager() + + // 3. Initialize Japanese conversion (optional) + val romajiConverter = + if (configuration.features.japaneseConversion.enabled) { + initializeJapaneseConversion() + } else { + null + } + + // 4. Initialize handlers + val directMessageHandler = + DirectMessageHandler( + settingsManager = playerSettingsManager, + romanjiConverter = romajiConverter, + ) + + return ServiceContainer( + languageManager = languageManager, + playerSettingsManager = playerSettingsManager, + directMessageHandler = directMessageHandler, + romajiConverter = romajiConverter, + ) + } + + /** + * Initializes the player settings manager. + * This is always needed for features like DM notifications. + */ + private fun initializePlayerSettingsManager(): PlayerSettingsManager { + val settingsFile = plugin.dataFolder.resolve(configuration.userSettingsFilePath).toPath() + val storage = + YamlPlayerSettingsStorage( + settingsFile = settingsFile, + plugin = plugin, + logger = logger, + ) + + val playerSettingsManager = + PlayerSettingsManager( + storage = storage, + logger = logger, + ) + playerSettingsManager.initialize() + return playerSettingsManager + } + + /** + * Initializes the Japanese conversion feature including: + * - Conversion cache + * - Google IME API client + * - Romanji converter + */ + private fun initializeJapaneseConversion(): RomanjiConverter { + // Initialize conversion cache + val cache = + ConversionCache( + cacheFile = plugin.dataFolder.resolve(configuration.features.japaneseConversion.cacheFilePath).toPath(), + maxEntries = configuration.features.japaneseConversion.cacheMaxEntries, + plugin = plugin, + logger = logger, + ) + cache.loadFromDisk() + conversionCache = cache + + // Initialize Google IME API client + val apiClient = + GoogleIMEClient( + timeout = configuration.features.japaneseConversion.apiTimeout.milliseconds, + httpClient = httpClient, + ) + + // Initialize Romanji converter + val converter = + RomanjiConverter( + cache = cache, + apiClient = apiClient, + logger = logger, + debugMode = configuration.debug, + ) + + logger.info("Japanese conversion feature enabled.") + return converter + } + + /** + * Schedules periodic tasks such as cache saving. + */ + fun schedulePeriodicTasks() { + if (configuration.features.japaneseConversion.enabled && conversionCache != null) { + val saveInterval = configuration.features.japaneseConversion.cacheSaveIntervalSeconds * 20L + plugin.server.scheduler.runTaskTimerAsynchronously( + plugin, + Runnable { + conversionCache?.saveToDisk() + }, + saveInterval, + saveInterval, + ) + } + } + + /** + * Performs shutdown tasks, including saving all caches to disk. + */ + fun shutdown(services: ServiceContainer) { + services.playerSettingsManager.saveToDisk() + conversionCache?.saveToDisk() + } +} diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt new file mode 100644 index 0000000..8d4070d --- /dev/null +++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt @@ -0,0 +1,44 @@ +package dev.m1sk9.lunaticChat.paper.listener + +import dev.m1sk9.lunaticChat.paper.LunaticChat +import dev.m1sk9.lunaticChat.paper.ServiceContainer +import dev.m1sk9.lunaticChat.paper.common.SpyPermissionManager +import java.util.concurrent.atomic.AtomicBoolean + +/** + * Centralizes event listener registration. + * + * This class handles conditional registration of listeners based on + * available services and enabled features. + */ +object EventListenerRegistry { + /** + * Registers all event listeners based on available services. + * + * @param plugin The plugin instance + * @param services The initialized services + * @param updateAvailable Atomic flag for update availability + */ + fun registerAll( + plugin: LunaticChat, + services: ServiceContainer, + updateAvailable: AtomicBoolean, + ) { + val pluginManager = plugin.server.pluginManager + + // Always register these listeners + pluginManager.registerEvents(SpyPermissionManager, plugin) + pluginManager.registerEvents( + PlayerPresenceListener(plugin, services.languageManager, updateAvailable), + plugin, + ) + + // Conditionally register Japanese conversion listener + if (services.romajiConverter != null) { + pluginManager.registerEvents( + PlayerChatListener(services.romajiConverter, services.playerSettingsManager), + plugin, + ) + } + } +} |
