diff options
| author | Sho Sakuma <me@m1sk9.dev> | 2026-01-17 11:49:33 +0900 |
|---|---|---|
| committer | Sho Sakuma <me@m1sk9.dev> | 2026-01-17 11:49:33 +0900 |
| commit | bc3d4f45021b0221a009a051559d271a9e681f48 (patch) | |
| tree | 59b6f1433a6eeadd743ffba56b0e22e127978b44 | |
| parent | 1926a42d51a1f82f60535a7a6d06100b2af951ef (diff) | |
| download | LunaticChat-bc3d4f45021b0221a009a051559d271a9e681f48.tar.gz LunaticChat-bc3d4f45021b0221a009a051559d271a9e681f48.tar.bz2 LunaticChat-bc3d4f45021b0221a009a051559d271a9e681f48.zip | |
feat: Add UpdateChecker
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 } } |
