summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSho Sakuma <me@m1sk9.dev>2026-01-17 11:49:33 +0900
committerSho Sakuma <me@m1sk9.dev>2026-01-17 11:49:33 +0900
commitbc3d4f45021b0221a009a051559d271a9e681f48 (patch)
tree59b6f1433a6eeadd743ffba56b0e22e127978b44
parent1926a42d51a1f82f60535a7a6d06100b2af951ef (diff)
downloadLunaticChat-bc3d4f45021b0221a009a051559d271a9e681f48.tar.gz
LunaticChat-bc3d4f45021b0221a009a051559d271a9e681f48.tar.bz2
LunaticChat-bc3d4f45021b0221a009a051559d271a9e681f48.zip
feat: Add UpdateChecker
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/LunaticChat.kt57
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/common/UpdateChecker.kt80
-rw-r--r--platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt26
3 files changed, 158 insertions, 5 deletions
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 330c13b..4bf2fc8 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
@@ -6,6 +6,8 @@ import dev.m1sk9.lunaticChat.paper.command.impl.ReplyCommand
import dev.m1sk9.lunaticChat.paper.command.impl.RomajiConvertToggleCommand
import dev.m1sk9.lunaticChat.paper.command.impl.TellCommand
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
@@ -17,8 +19,10 @@ import dev.m1sk9.lunaticChat.paper.settings.PlayerSettingsManager
import dev.m1sk9.lunaticChat.paper.settings.YamlPlayerSettingsStorage
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 :
@@ -27,9 +31,12 @@ class LunaticChat :
lateinit var directMessageHandler: DirectMessageHandler
private lateinit var commandRegistry: CommandRegistry
+ 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)
@@ -39,9 +46,11 @@ class LunaticChat :
logger.info("Debug: $configuration")
}
+ val httpClient = HttpClient(CIO)
+
// Initialize features
if (configuration.features.japaneseConversion.enabled) {
- initializeJapaneseConversionFeature(configuration)
+ initializeJapaneseConversionFeature(configuration, httpClient)
}
// Initialize handlers
@@ -55,6 +64,24 @@ class LunaticChat :
registerCommands(configuration)
registerEventListeners()
+ // Check for updates
+ if (configuration.checkForUpdates) {
+ updateChecker =
+ UpdateChecker(
+ currentVersion = pluginMeta.version,
+ logger = logger,
+ httpClient = httpClient,
+ )
+ server.scheduler.runTaskAsynchronously(
+ this,
+ Runnable {
+ runBlocking {
+ checkUpdates()
+ }
+ },
+ )
+ }
+
logger.info("LunaticChat enabled.")
}
@@ -71,7 +98,10 @@ class LunaticChat :
* - Romanji converter
* - Periodic cache saving task
*/
- private fun initializeJapaneseConversionFeature(configuration: LunaticChatConfiguration) {
+ private fun initializeJapaneseConversionFeature(
+ configuration: LunaticChatConfiguration,
+ httpClient: HttpClient,
+ ) {
// Initialize player settings
val settingsFile = dataFolder.resolve(configuration.userSettingsFilePath).toPath()
val storage =
@@ -99,7 +129,6 @@ class LunaticChat :
cache.loadFromDisk()
// Initialize Google IME API client
- val httpClient = HttpClient(CIO)
val apiClient =
GoogleIMEClient(
timeout = configuration.features.japaneseConversion.apiTimeout.milliseconds,
@@ -165,6 +194,26 @@ class LunaticChat :
*/
private fun registerEventListeners() {
server.pluginManager.registerEvents(SpyPermissionManager, this)
- server.pluginManager.registerEvents(PlayerPresenceListener(this), this)
+ server.pluginManager.registerEvents(PlayerPresenceListener(this, updateAvailable), this)
+ }
+
+ private suspend fun checkUpdates() {
+ val result = updateChecker?.checkForUpdates()
+ when (result) {
+ is UpdateCheckResult.ExistUpdate -> {
+ logger.info("A new version of LunaticChat is available!")
+ logger.info("You can download the latest build from GitHub or Modrinth.")
+ logger.info(" GitHub: https://github.com/m1sk9/LunaticChat/releases/latest")
+ logger.info(" Modrinth: https://modrinth.com/plugin/lunaticchat/version/latest")
+ updateAvailable.set(true)
+ }
+ is UpdateCheckResult.NotUpdate -> {
+ logger.info("LunaticChat is up to date.")
+ }
+ // Include failed case for completeness
+ else -> {
+ logger.warning("Failed to check for updates.")
+ }
+ }
}
}
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/common/UpdateChecker.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/common/UpdateChecker.kt
new file mode 100644
index 0000000..49d6852
--- /dev/null
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/common/UpdateChecker.kt
@@ -0,0 +1,80 @@
+package dev.m1sk9.lunaticChat.paper.common
+
+import io.ktor.client.HttpClient
+import io.ktor.client.call.body
+import io.ktor.client.request.get
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.Json
+import java.util.logging.Logger
+
+@Serializable
+data class GitHubRelease(
+ @SerialName("tag_name")
+ val tagName: String,
+ @SerialName("name")
+ val name: String,
+ @SerialName("published_at")
+ val publishedAt: String,
+ @SerialName("html_url")
+ val htmlUrl: String,
+)
+
+class UpdateChecker(
+ private val currentVersion: String,
+ private val httpClient: HttpClient,
+ private val logger: Logger,
+) {
+ private val githubAPIURL = "https://api.github.com/repos/m1sk9/LunaticChat/releases/latest"
+ private val json = Json { ignoreUnknownKeys = true }
+
+ /**
+ * Check LunaticChat Updates
+ *
+ * @throws Exception Failed to check for updates
+ */
+ suspend fun checkForUpdates(): UpdateCheckResult {
+ return withContext(Dispatchers.IO) {
+ try {
+ val res = httpClient.get(githubAPIURL)
+ val release = json.decodeFromString<GitHubRelease>(res.body<String>())
+ val latestVersion = release.tagName.removePrefix("v")
+
+ if (!isNewer(latestVersion, currentVersion)) {
+ return@withContext UpdateCheckResult.NotUpdate
+ }
+
+ UpdateCheckResult.ExistUpdate
+ } catch (e: Exception) {
+ logger.warning("Failed to check for latest version of latest version: ${e.message}")
+ UpdateCheckResult.FailedUpdate
+ }
+ }
+ }
+
+ private fun isNewer(
+ latest: String,
+ current: String,
+ ): Boolean {
+ val latestParts = latest.split(".").map { it.toIntOrNull() ?: 0 }
+ val currentParts = current.split(".").map { it.toIntOrNull() ?: 0 }
+
+ for (i in 0 until maxOf(latestParts.size, currentParts.size)) {
+ val l = latestParts.getOrNull(i) ?: 0
+ val c = currentParts.getOrNull(i) ?: 0
+ if (l > c) return true
+ if (l < c) return false
+ }
+ return false
+ }
+}
+
+sealed class UpdateCheckResult {
+ object ExistUpdate : UpdateCheckResult()
+
+ object NotUpdate : UpdateCheckResult()
+
+ object FailedUpdate : UpdateCheckResult()
+}
diff --git a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt
index 623a544..d0873a6 100644
--- a/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt
+++ b/platform-paper/src/main/kotlin/dev/m1sk9/lunaticChat/paper/listener/PlayerPresenceListener.kt
@@ -1,16 +1,40 @@
package dev.m1sk9.lunaticChat.paper.listener
+import dev.m1sk9.lunaticChat.engine.permission.LunaticChatPermissionNode
import dev.m1sk9.lunaticChat.paper.LunaticChat
+import dev.m1sk9.lunaticChat.paper.common.hasAnyPermission
+import net.kyori.adventure.text.Component
+import net.kyori.adventure.text.event.ClickEvent
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
+import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerQuitEvent
+import java.util.concurrent.atomic.AtomicBoolean
class PlayerPresenceListener(
private val lunaticChat: LunaticChat,
+ private val updateCheckerFlag: AtomicBoolean,
) : Listener {
@EventHandler(ignoreCancelled = true)
+ fun onJoin(event: PlayerJoinEvent) {
+ val player = event.player
+ if (!updateCheckerFlag.get() || !player.hasAnyPermission { +LunaticChatPermissionNode.NoticeUpdate }) return
+
+ player.sendMessage {
+ Component
+ .text(
+ listOf(
+ "§6[§eLunaticChat§6] §aA new update is available!",
+ "§aYou can download the latest build from §bGitHub §aor §bModrinth.",
+ ).joinToString("\n"),
+ ).clickEvent(
+ ClickEvent.openUrl("https://modrinth.com/plugin/lunaticchat/version/latest"),
+ )
+ }
+ }
+
+ @EventHandler(ignoreCancelled = true)
fun onQuit(event: PlayerQuitEvent) {
lunaticChat.directMessageHandler.clearPlayer(event.player)
- // Settings remain in memory, no unloading needed
}
}