From eb92d3e689e566783d1eab93b6cfaf43709bf7ae Mon Sep 17 00:00:00 2001 From: Yuzu Date: Thu, 2 Jan 2025 12:16:41 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Velocity=20plugin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/MinikuraVelocity/.gitignore | 38 +++ .../.run/minikura-velocity.run.xml | 32 +++ .../dependency-reduced-pom.xml | 232 +++++++++++++++ plugins/MinikuraVelocity/pom.xml | 272 ++++++++++++++++++ .../cafe/kirameki/minikuraVelocity/Main.kt | 203 +++++++++++++ .../MinikuraWebSocketClient.kt | 26 ++ .../datastore/ServerDataStore.kt | 54 ++++ .../listeners/ServerConnectionHandler.kt | 35 +++ .../models/ReverseProxyServerData.kt | 12 + .../minikuraVelocity/models/ServerData.kt | 14 + .../minikuraVelocity/utils/WebSocketUtils.kt | 12 + 11 files changed, 930 insertions(+) create mode 100644 plugins/MinikuraVelocity/.gitignore create mode 100644 plugins/MinikuraVelocity/.run/minikura-velocity.run.xml create mode 100644 plugins/MinikuraVelocity/dependency-reduced-pom.xml create mode 100644 plugins/MinikuraVelocity/pom.xml create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/Main.kt create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/MinikuraWebSocketClient.kt create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/datastore/ServerDataStore.kt create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/listeners/ServerConnectionHandler.kt create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ReverseProxyServerData.kt create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ServerData.kt create mode 100644 plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/utils/WebSocketUtils.kt diff --git a/plugins/MinikuraVelocity/.gitignore b/plugins/MinikuraVelocity/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/plugins/MinikuraVelocity/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/plugins/MinikuraVelocity/.run/minikura-velocity.run.xml b/plugins/MinikuraVelocity/.run/minikura-velocity.run.xml new file mode 100644 index 0000000..20d58b0 --- /dev/null +++ b/plugins/MinikuraVelocity/.run/minikura-velocity.run.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/plugins/MinikuraVelocity/dependency-reduced-pom.xml b/plugins/MinikuraVelocity/dependency-reduced-pom.xml new file mode 100644 index 0000000..ec214e9 --- /dev/null +++ b/plugins/MinikuraVelocity/dependency-reduced-pom.xml @@ -0,0 +1,232 @@ + + + 4.0.0 + cafe.kirameki + minikura-velocity + minikura-velocity + 1.0 + + src/main/kotlin + clean package + + + true + ${project.basedir}/src/main/resources + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + kapt + + kapt + + + + src/main/kotlin + src/main/java + + + + com.velocitypowered + velocity-api + 3.4.0-SNAPSHOT + + + + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + + + + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + + maven-compiler-plugin + 3.13.0 + + + default-compile + none + + + default-testCompile + none + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + ${java.version} + ${java.version} + + + + org.codehaus.mojo + templating-maven-plugin + 1.0.0 + + + filter-src + + filter-sources + + + + + + maven-site-plugin + 3.12.1 + + + net.trajano.wagon + wagon-git + 2.0.4 + + + org.apache.maven.doxia + doxia-module-markdown + 1.12.0 + + + + + maven-release-plugin + 3.0.1 + + true + @{project.version} + [RELEASE] + install deploy site-deploy + release + + + + + + + release + + + + true + src/main/resources + + + + + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + + + + maven-javadoc-plugin + 3.6.3 + + + attach-javadocs + + jar + + + + + + maven-gpg-plugin + 3.2.4 + + + sign-artifacts + verify + + sign + + + + + + + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + + + com.velocitypowered + velocity-api + 3.4.0-SNAPSHOT + provided + + + org.jetbrains.kotlin + kotlin-test + 2.1.0 + test + + + + + + maven-javadoc-plugin + 3.6.3 + + + + + 2.1.0 + 17 + UTF-8 + + diff --git a/plugins/MinikuraVelocity/pom.xml b/plugins/MinikuraVelocity/pom.xml new file mode 100644 index 0000000..63afc96 --- /dev/null +++ b/plugins/MinikuraVelocity/pom.xml @@ -0,0 +1,272 @@ + + + 4.0.0 + + cafe.kirameki + minikura-velocity + 1.0 + jar + + minikura-velocity + + + 17 + 2.1.0 + UTF-8 + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.3 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.4 + + + sign-artifacts + verify + + sign + + + + + + + + src/main/resources + true + + + + + + + + clean package + src/main/kotlin + + + ${project.basedir}/src/main/resources + true + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + kapt + + kapt + + + + src/main/kotlin + src/main/java + + + + com.velocitypowered + velocity-api + 3.4.0-SNAPSHOT + + + + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + default-compile + none + + + default-testCompile + none + + + compile + compile + + compile + + + + testCompile + test-compile + + testCompile + + + + + ${java.version} + ${java.version} + + + + org.codehaus.mojo + templating-maven-plugin + 1.0.0 + + + filter-src + + filter-sources + + + + + + org.apache.maven.plugins + maven-site-plugin + 3.12.1 + + + net.trajano.wagon + wagon-git + 2.0.4 + + + org.apache.maven.doxia + doxia-module-markdown + 1.12.0 + + + + + org.apache.maven.plugins + maven-release-plugin + 3.0.1 + + true + @{project.version} + [RELEASE] + install deploy site-deploy + + release + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.6.3 + + + + + + + papermc-repo + https://repo.papermc.io/repository/maven-public/ + + + + + + com.velocitypowered + velocity-api + 3.4.0-SNAPSHOT + provided + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + org.java-websocket + Java-WebSocket + 1.6.0 + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/Main.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/Main.kt new file mode 100644 index 0000000..5ecf37e --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/Main.kt @@ -0,0 +1,203 @@ +package cafe.kirameki.minikuraVelocity + +import cafe.kirameki.minikuraVelocity.listeners.ServerConnectionHandler +import cafe.kirameki.minikuraVelocity.models.ReverseProxyServerData +import cafe.kirameki.minikuraVelocity.models.ServerData +import cafe.kirameki.minikuraVelocity.store.ServerDataStore +import cafe.kirameki.minikuraVelocity.utils.createWebSocketClient +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.google.inject.Inject +import com.velocitypowered.api.command.CommandManager +import com.velocitypowered.api.command.CommandMeta +import com.velocitypowered.api.command.SimpleCommand +import com.velocitypowered.api.event.Subscribe +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent +import com.velocitypowered.api.plugin.Plugin +import com.velocitypowered.api.proxy.ProxyServer +import com.velocitypowered.api.proxy.server.RegisteredServer +import com.velocitypowered.api.proxy.server.ServerInfo +import net.kyori.adventure.text.Component +import okhttp3.OkHttpClient +import okhttp3.Request +import org.slf4j.Logger +import java.net.InetSocketAddress +import java.util.concurrent.Executors + +@Plugin(id = "minikura-velocity", name = "MinikuraVelocity", version = "1.0") +class Main @Inject constructor(private val logger: Logger, private val server: ProxyServer) { + private val servers: MutableMap = HashMap() + private val client = OkHttpClient() + private val apiKey: String = System.getenv("MINIKURA_API_KEY") ?: "" + private val apiUrl: String = System.getenv("MINIKURA_API_URL") ?: "http://localhost:3000" + private val websocketUrl: String = System.getenv("MINIKURA_WEBSOCKET_URL") ?: "ws://localhost:3000/ws" + + @Subscribe + fun onProxyInitialization(event: ProxyInitializeEvent?) { + logger.info("Minikura-Velocity is initializing...") + + val client = createWebSocketClient(this, server, websocketUrl) + client.connect() + + val commandManager: CommandManager = server.commandManager + + val serversCommandMeta: CommandMeta = commandManager.metaBuilder("servers") + .plugin(this) + .aliases("listservers", "serverlist") + .build() + + val serversCommand = SimpleCommand { p -> + val source = p.source() + source.sendMessage(Component.text("Available servers:")) + for ((name, server) in servers) { + source.sendMessage(Component.text(" - $name (${server.serverInfo.address})")) + } + } + + commandManager.register(serversCommandMeta, serversCommand) + + val refreshCommandMeta: CommandMeta = commandManager.metaBuilder("refresh") + .plugin(this) + .build() + + val refreshCommand = SimpleCommand { p -> + val source = p.source() + source.sendMessage(Component.text("Refreshing server list...")) + fetchServers() + fetchReverseProxyServers() + source.sendMessage(Component.text("Server list refreshed successfully!")) + } + + commandManager.register(refreshCommandMeta, refreshCommand) + + val migrateCommandMeta: CommandMeta = commandManager.metaBuilder("migrate") + .plugin(this) + .build() + + val migrateCommand = SimpleCommand { p -> + val source = p.source() + val args = p.arguments() + + if (args.isEmpty()) { + source.sendMessage(Component.text("Please specify a server to migrate players to.")) + return@SimpleCommand + } + + val targetServerName = args[0] + val targetServer = ServerDataStore.getReverseProxyServer(targetServerName) + + if (targetServer == null) { + source.sendMessage(Component.text("Server '$targetServerName' not found.")) + return@SimpleCommand + } + + migratePlayersToServer(targetServer) + source.sendMessage(Component.text("Migrating players to server '$targetServerName'...")) + } + + commandManager.register(migrateCommandMeta, migrateCommand) + + val connectionHandler = ServerConnectionHandler(servers, logger) + server.eventManager.register(this, connectionHandler) + + fetchServers() + fetchReverseProxyServers() + + logger.info("Minikura-Velocity has been initialized.") + } + + private fun fetchReverseProxyServers() { + val request = Request.Builder() + .url("$apiUrl/reverse_proxy_servers") + .header("Authorization", "Bearer $apiKey") + .build() + + Executors.newSingleThreadExecutor().submit { + try { + val response = client.newCall(request).execute() + if (response.isSuccessful) { + val responseBody = response.body?.string() + val fetchedServers = parseReverseProxyServersData(responseBody) + + server.scheduler.buildTask(this, Runnable { + synchronized(ServerDataStore) { + ServerDataStore.clearReverseProxyServers() + ServerDataStore.addAllReverseProxyServers(fetchedServers) + } + }).schedule() + } else { + logger.error("Failed to fetch reverse proxy servers: ${response.message}") + } + } catch (e: Exception) { + logger.error("Error fetching reverse proxy servers: ${e.message}", e) + } + } + } + + private fun fetchServers() { + server.allServers.forEach { server.unregisterServer(it.serverInfo) } + val request = Request.Builder() + .url("$apiUrl/servers") + .header("Authorization", "Bearer $apiKey") + .build() + + Executors.newSingleThreadExecutor().submit { + try { + val response = client.newCall(request).execute() + if (response.isSuccessful) { + val responseBody = response.body?.string() + val fetchedServers = parseServersData(responseBody) + + server.scheduler.buildTask(this, Runnable { + synchronized(ServerDataStore) { + ServerDataStore.clearServers() + ServerDataStore.addAllServers(fetchedServers) + } + populateServers(fetchedServers) + }).schedule() + } else { + logger.error("Failed to fetch servers: ${response.message}") + } + } catch (e: Exception) { + logger.error("Error fetching servers: ${e.message}", e) + } + } + } + + private fun parseReverseProxyServersData(responseBody: String?): List { + if (responseBody.isNullOrEmpty()) return emptyList() + + val gson = Gson() + val reverseProxyServerListType = object : TypeToken>() {}.type + return gson.fromJson(responseBody, reverseProxyServerListType) + } + + private fun parseServersData(responseBody: String?): List { + if (responseBody.isNullOrEmpty()) return emptyList() + + val gson = Gson() + + val serverListType = object : TypeToken>() {}.type + return gson.fromJson(responseBody, serverListType) + } + + private fun populateServers(serversData: List) { + servers.clear() + + for (data in serversData) { + val serverInfo = ServerInfo(data.name, InetSocketAddress(data.address, data.port)) + val registeredServer = server.createRawRegisteredServer(serverInfo) + servers[data.name] = registeredServer + this.server.registerServer(registeredServer.serverInfo) + } + } + + private fun migratePlayersToServer(targetServer: ReverseProxyServerData) { + val targetAddress = InetSocketAddress(targetServer.address, targetServer.port) + + server.allPlayers.forEach { player -> + player.transferToHost(targetAddress) + } + } + +} diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/MinikuraWebSocketClient.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/MinikuraWebSocketClient.kt new file mode 100644 index 0000000..9cb3683 --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/MinikuraWebSocketClient.kt @@ -0,0 +1,26 @@ +package cafe.kirameki.minikuraVelocity + +import com.velocitypowered.api.proxy.ProxyServer +import org.java_websocket.client.WebSocketClient +import org.java_websocket.handshake.ServerHandshake +import java.net.URI +import java.time.Duration + +class MinikuraWebSocketClient(private val plugin: Main, private val server: ProxyServer, serverUri: URI?) : WebSocketClient(serverUri) { + override fun onOpen(handshakedata: ServerHandshake) { + println("Connected to server") + } + + override fun onMessage(message: String) { + println("Received: $message") + } + + override fun onError(ex: Exception) { + ex.printStackTrace() + } + + override fun onClose(code: Int, reason: String, remote: Boolean) { + println("Disconnected from websocket, reconnecting...") + server.scheduler.buildTask(plugin, Runnable { reconnect() }).delay(Duration.ofMillis(5000)).schedule() + } +} \ No newline at end of file diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/datastore/ServerDataStore.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/datastore/ServerDataStore.kt new file mode 100644 index 0000000..e278d05 --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/datastore/ServerDataStore.kt @@ -0,0 +1,54 @@ +package cafe.kirameki.minikuraVelocity.store + +import cafe.kirameki.minikuraVelocity.models.ReverseProxyServerData +import cafe.kirameki.minikuraVelocity.models.ServerData +import com.velocitypowered.api.proxy.ProxyServer + +object ServerDataStore { + private val servers: MutableList = mutableListOf() + private val reverseProxyServers: MutableList = mutableListOf() + + fun initialize(server: ProxyServer) { + + } + + fun addServer(serverData: ServerData) { + servers.add(serverData) + } + + fun addReverseProxyServer(reverseProxyServerData: ReverseProxyServerData) { + reverseProxyServers.add(reverseProxyServerData) + } + + fun addAllServers(serverData: List) { + servers.addAll(serverData) + } + + fun addAllReverseProxyServers(reverseProxyServerData: List) { + reverseProxyServers.addAll(reverseProxyServerData) + } + + fun getServer(name: String): ServerData? { + return servers.find { it.name == name } + } + + fun getReverseProxyServer(name: String): ReverseProxyServerData? { + return reverseProxyServers.find { it.name == name } + } + + fun getServers(): List { + return servers + } + + fun getReverseProxyServers(): List { + return reverseProxyServers + } + + fun clearServers() { + servers.clear() + } + + fun clearReverseProxyServers() { + reverseProxyServers.clear() + } +} diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/listeners/ServerConnectionHandler.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/listeners/ServerConnectionHandler.kt new file mode 100644 index 0000000..2ae9505 --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/listeners/ServerConnectionHandler.kt @@ -0,0 +1,35 @@ +package cafe.kirameki.minikuraVelocity.listeners + +import cafe.kirameki.minikuraVelocity.store.ServerDataStore +import com.velocitypowered.api.event.Subscribe +import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent +import com.velocitypowered.api.proxy.server.RegisteredServer +import org.slf4j.Logger + +class ServerConnectionHandler( + private val servers: Map, + private val logger: Logger +) { + @Subscribe + fun onPlayerChooseInitialServer(event: PlayerChooseInitialServerEvent) { + val player = event.player + val currentServer = event.initialServer; + + val sortedServers = ServerDataStore.getServers() + .filter { it.join_priority != null } + .sortedBy { it.join_priority } + .mapNotNull { servers[it.name] } + + System.out.println("Sorted servers: ${sortedServers.map { it.serverInfo.name }}") + + for (server in sortedServers) { + if (server != currentServer) { + logger.info("Attempting to connect ${player.username} to server ${server.serverInfo.name}") + event.setInitialServer(server) + return + } + } + + logger.warn("No available servers with valid join_priority for ${player.username}") + } +} \ No newline at end of file diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ReverseProxyServerData.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ReverseProxyServerData.kt new file mode 100644 index 0000000..f3fb034 --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ReverseProxyServerData.kt @@ -0,0 +1,12 @@ +package cafe.kirameki.minikuraVelocity.models + +data class ReverseProxyServerData( + val id: String, + val name: String, + val description: String?, + val address: String, + val port: Int, + val api_key: String, + val created_at: String, + val updated_at: String +) \ No newline at end of file diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ServerData.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ServerData.kt new file mode 100644 index 0000000..f0c5481 --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/models/ServerData.kt @@ -0,0 +1,14 @@ +package cafe.kirameki.minikuraVelocity.models + +data class ServerData( + val id: String, + val name: String, + val description: String?, + val address: String, + val port: Int, + val type: String, + val join_priority: Int?, + val api_key: String, + val created_at: String, + val updated_at: String +) \ No newline at end of file diff --git a/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/utils/WebSocketUtils.kt b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/utils/WebSocketUtils.kt new file mode 100644 index 0000000..a2e95c6 --- /dev/null +++ b/plugins/MinikuraVelocity/src/main/kotlin/cafe/kirameki/minikuraVelocity/utils/WebSocketUtils.kt @@ -0,0 +1,12 @@ +package cafe.kirameki.minikuraVelocity.utils + +import cafe.kirameki.minikuraVelocity.Main +import cafe.kirameki.minikuraVelocity.MinikuraWebSocketClient +import com.velocitypowered.api.proxy.ProxyServer +import java.net.URI + +fun createWebSocketClient(plugin: Main, server: ProxyServer, websocketUrl: String): MinikuraWebSocketClient { + val uri = URI(websocketUrl) + val client = MinikuraWebSocketClient(plugin, server, uri) + return client +}