🚀 Finishing up the Chat Sync

This commit is contained in:
2025-10-05 03:14:20 +08:00
parent 6303d44ab4
commit 8ef05de8ad
4 changed files with 90 additions and 33 deletions

View File

@@ -7,8 +7,7 @@ import dev.solsynth.snConnect.models.SnChatMessage
import dev.solsynth.snConnect.models.WebSocketPacket import dev.solsynth.snConnect.models.WebSocketPacket
import dev.solsynth.snConnect.services.SnMessageService import dev.solsynth.snConnect.services.SnMessageService
import dev.solsynth.snConnect.services.SnService import dev.solsynth.snConnect.services.SnService
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.*
import kotlinx.coroutines.launch
import net.md_5.bungee.api.ChatColor import net.md_5.bungee.api.ChatColor
import net.md_5.bungee.api.chat.ClickEvent import net.md_5.bungee.api.chat.ClickEvent
import net.md_5.bungee.api.chat.ComponentBuilder import net.md_5.bungee.api.chat.ComponentBuilder
@@ -26,6 +25,8 @@ class SolarNetworkConnect : JavaPlugin() {
private var messageService: SnMessageService? = null private var messageService: SnMessageService? = null
private var syncChatRooms: List<String> = emptyList() private var syncChatRooms: List<String> = emptyList()
private var destinationChatId: String? = null private var destinationChatId: String? = null
private var webSocketJob: Job? = null
private var messages: Map<String, String> = mapOf()
private fun handleWebSocketPacket(packet: WebSocketPacket) { private fun handleWebSocketPacket(packet: WebSocketPacket) {
// logger.info("Received WebSocket packet: type=${packet.type}") // logger.info("Received WebSocket packet: type=${packet.type}")
@@ -79,6 +80,13 @@ class SolarNetworkConnect : JavaPlugin() {
this.saveDefaultConfig() this.saveDefaultConfig()
messages = mapOf(
"join" to "➡️ {player} joined the game.",
"quit" to "⬅️ {player} left the game.",
"death" to "💀 {player} {message}",
"advancement" to "🎉 {player} unlocked advancement: {advancement}"
) + (config.getConfigurationSection("messages")?.getValues(false) as? Map<String, String> ?: emptyMap())
if (!setupNetwork()) { if (!setupNetwork()) {
logger.severe("Failed to setup Solar Network Network, check your configuration.") logger.severe("Failed to setup Solar Network Network, check your configuration.")
} }
@@ -89,7 +97,7 @@ class SolarNetworkConnect : JavaPlugin() {
} }
if (messageService != null && destinationChatId != null) { if (messageService != null && destinationChatId != null) {
server.pluginManager.registerEvents(SnChatListener(messageService!!, destinationChatId!!), this) server.pluginManager.registerEvents(SnChatListener(messageService!!, destinationChatId!!, messages), this)
} }
Bukkit.getPluginCommand("solar")!!.setExecutor(SnCommand(this.sn!!, this.economy)) Bukkit.getPluginCommand("solar")!!.setExecutor(SnCommand(this.sn!!, this.economy))
@@ -105,6 +113,8 @@ class SolarNetworkConnect : JavaPlugin() {
override fun onDisable() { override fun onDisable() {
logger.info(String.format("Disabled Version %s", description.version)); logger.info(String.format("Disabled Version %s", description.version));
sn?.disconnect()
webSocketJob?.cancel()
} }
private fun setupNetwork(): Boolean { private fun setupNetwork(): Boolean {
@@ -118,7 +128,7 @@ class SolarNetworkConnect : JavaPlugin() {
destinationChatId = destination destinationChatId = destination
sn = SnService(baseUrl, clientId, clientSecret, botApiKey); sn = SnService(baseUrl, clientId, clientSecret, botApiKey);
messageService = SnMessageService(sn!!) messageService = SnMessageService(sn!!)
GlobalScope.launch { webSocketJob = GlobalScope.launch {
sn!!.connectWebSocketAsFlow().collect { packet -> sn!!.connectWebSocketAsFlow().collect { packet ->
handleWebSocketPacket(packet) handleWebSocketPacket(packet)
} }

View File

@@ -1,13 +1,16 @@
package dev.solsynth.snConnect.listeners package dev.solsynth.snConnect.listeners
import dev.solsynth.snConnect.services.SnMessageService import dev.solsynth.snConnect.services.SnMessageService
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler import org.bukkit.event.EventHandler
import org.bukkit.event.Listener import org.bukkit.event.Listener
import org.bukkit.event.entity.PlayerDeathEvent
import org.bukkit.event.player.AsyncPlayerChatEvent import org.bukkit.event.player.AsyncPlayerChatEvent
import org.bukkit.event.player.PlayerAdvancementDoneEvent
import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.event.player.PlayerQuitEvent
class SnChatListener(private val messageService: SnMessageService, private val destinationChatId: String) : Listener { class SnChatListener(private val messageService: SnMessageService, private val destinationChatId: String, private val messages: Map<String, String>) : Listener {
@EventHandler @EventHandler
fun onPlayerChat(event: AsyncPlayerChatEvent) { fun onPlayerChat(event: AsyncPlayerChatEvent) {
@@ -17,13 +20,30 @@ class SnChatListener(private val messageService: SnMessageService, private val d
@EventHandler @EventHandler
fun onPlayerJoin(event: PlayerJoinEvent) { fun onPlayerJoin(event: PlayerJoinEvent) {
val message = "${event.player.name} joined the game." val template = messages["join"] ?: "➡️ {player} joined the game."
val message = template.replace("{player}", event.player.name)
messageService.sendMessage(destinationChatId, message) messageService.sendMessage(destinationChatId, message)
} }
@EventHandler @EventHandler
fun onPlayerQuit(event: PlayerQuitEvent) { fun onPlayerQuit(event: PlayerQuitEvent) {
val message = "${event.player.name} left the game." val template = messages["quit"] ?: "⬅️ {player} left the game."
val message = template.replace("{player}", event.player.name)
messageService.sendMessage(destinationChatId, message)
}
@EventHandler
fun onPlayerDeath(event: PlayerDeathEvent) {
val template = messages["death"] ?: "💀 {player} {message}"
val message = template.replace("{player}", event.entity.name).replace("{message}", event.deathMessage ?: "")
messageService.sendMessage(destinationChatId, message)
}
@EventHandler
fun onPlayerAdvancement(event: PlayerAdvancementDoneEvent) {
val advancement = event.advancement.key.toString().substringAfter("minecraft:")
val template = messages["advancement"] ?: "🎉 {player} unlocked advancement: {advancement}"
val message = template.replace("{player}", event.player.name).replace("{advancement}", advancement)
messageService.sendMessage(destinationChatId, message) messageService.sendMessage(destinationChatId, message)
} }
} }

View File

@@ -1,9 +1,11 @@
package dev.solsynth.snConnect.services package dev.solsynth.snConnect.services
import dev.solsynth.snConnect.models.WebSocketPacket import dev.solsynth.snConnect.models.WebSocketPacket
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.WebSocket import okhttp3.WebSocket
@@ -15,6 +17,7 @@ import java.util.logging.Logger
class SnService(private val baseUrl: String, val clientId: String, val clientSecret: String, val botApiKey: String?) { class SnService(private val baseUrl: String, val clientId: String, val clientSecret: String, val botApiKey: String?) {
val client = OkHttpClient.Builder().build(); val client = OkHttpClient.Builder().build();
private val logger = Logger.getLogger(SnService::class.java.name) private val logger = Logger.getLogger(SnService::class.java.name)
private var websocket: WebSocket? = null
fun getUrl(service: String, segment: String): String { fun getUrl(service: String, segment: String): String {
return "$baseUrl/$service$segment" return "$baseUrl/$service$segment"
@@ -34,40 +37,60 @@ class SnService(private val baseUrl: String, val clientId: String, val clientSec
return client.newWebSocket(request, listener) return client.newWebSocket(request, listener)
} }
fun connectWebSocketAsFlow(): Flow<WebSocketPacket> = callbackFlow { fun connectWebSocketAsFlow(): Flow<WebSocketPacket> = channelFlow {
val url = "${getWsBaseUrl()}/ws"; val url = "${getWsBaseUrl()}/ws"
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.apply { .apply {
botApiKey?.let { header("Authorization", "Bearer $it") } botApiKey?.let { header("Authorization", "Bearer $it") }
} }
.build() .build()
val websocket = client.newWebSocket(request, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
logger.info("WebSocket connection opened to $url")
}
override fun onMessage(webSocket: WebSocket, text: String) { fun connect() {
WebSocketPacket.fromJson(text)?.let { trySend(it) } websocket = client.newWebSocket(request, object : WebSocketListener() {
} override fun onOpen(webSocket: WebSocket, response: Response) {
logger.info("WebSocket connection opened to $url")
}
override fun onMessage(webSocket: WebSocket, bytes: ByteString) { override fun onMessage(webSocket: WebSocket, text: String) {
val text = bytes.string(Charsets.UTF_8) WebSocketPacket.fromJson(text)?.let { trySend(it) }
WebSocketPacket.fromJson(text)?.let { trySend(it) } }
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
logger.severe("WebSocket connection failed: ${t.message}, response: ${response?.code}") val text = bytes.string(Charsets.UTF_8)
close(t) WebSocketPacket.fromJson(text)?.let { trySend(it) }
} }
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
logger.severe("WebSocket connection failed: ${t.message}, response: ${response?.code}")
websocket = null
GlobalScope.launch {
delay(1000)
connect()
}
}
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
logger.info("WebSocket connection closed: code=$code, reason=$reason")
websocket = null
GlobalScope.launch {
delay(1000)
connect()
}
}
})
}
connect()
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
logger.info("WebSocket connection closed: code=$code, reason=$reason")
close()
}
})
awaitClose { awaitClose {
websocket.close(1000, "Flow closed") websocket?.close(1000, "Shutting down")
websocket = null
} }
} }
fun disconnect() {
websocket?.close(1000, "Disconnecting")
websocket = null
}
} }

View File

@@ -3,7 +3,11 @@ sn:
client_id: goatcraft client_id: goatcraft
client_secret: 12345678 client_secret: 12345678
bot_secret: 114.514.19198 bot_secret: 114.514.19198
destination_chat_id: some_chat_id
chat: chat:
sync_rooms: [] sync_rooms: []
outgoing_room: 00000000-0000-0000-0000-00000000008b outgoing_room: 00000000-0000-0000-0000-00000000008b
messages:
join: "➡️ {player} joined the game."
quit: "⬅️ {player} left the game."
death: "💀 {player} {message}"
advancement: "🎉 {player} unlocked advancement: {advancement}"