summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-01-24 03:33:32 +0900
committerSho Sakuma <me@m1sk9.dev>2026-01-24 03:33:32 +0900
commitd053450b1ca68fe963b4656cf437472c435061cc (patch)
treeb007a7dda4561850b2c46a16b0065194a8a30367
parent3b935ce99257587aaae85b8b09f0396111bc7e4c (diff)
downloadLunaticChat-d053450b1ca68fe963b4656cf437472c435061cc.tar.gz
LunaticChat-d053450b1ca68fe963b4656cf437472c435061cc.tar.bz2
LunaticChat-d053450b1ca68fe963b4656cf437472c435061cc.zip
feat: Improve initializer
-rw-r--r--CHANGELOG.md4
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt206
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceContainer.kt24
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/ServiceInitializer.kt162
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/EventListenerRegistry.kt44
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,
+ )
+ }
+ }
+}