diff --git a/pom.xml b/pom.xml index 03f4569..02b0217 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ - ${java.version} + 1.8 @@ -106,10 +106,10 @@ - + - + @@ -124,12 +124,18 @@ install - - + --> + --> - - + + + + + + + + diff --git a/src/main/kotlin/main/vp_server_integration/CommandCapture.kt b/src/main/kotlin/main/vp_server_integration/CommandCapture.kt new file mode 100644 index 0000000..3930347 --- /dev/null +++ b/src/main/kotlin/main/vp_server_integration/CommandCapture.kt @@ -0,0 +1,49 @@ +package main.vp_server_integration + +// -------------- Capture output ---------------- +// Here is implemented capture of output command (for dynamic course) +// ---------------------------------------------- + +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender + +class CommandCapture { + companion object { + private fun createCapturingSender(onMessage: (String) -> Unit): CommandSender { + return object : CommandSender { + override fun sendMessage(message: String) = onMessage(message) + override fun sendMessage(messages: Array) = messages.forEach(onMessage) + override fun getName() = "VPCIntegrationCapture" + override fun isOp() = true + override fun setOp(value: Boolean) {} + override fun hasPermission(permission: String) = true + override fun hasPermission(permission: org.bukkit.permissions.Permission) = true + override fun getServer() = Bukkit.getServer() + override fun addAttachment(plugin: org.bukkit.plugin.Plugin) = null + override fun addAttachment(plugin: org.bukkit.plugin.Plugin, ticks: Int) = null + override fun addAttachment(plugin: org.bukkit.plugin.Plugin, name: String, value: Boolean) = null + override fun addAttachment(plugin: org.bukkit.plugin.Plugin, name: String, value: Boolean, ticks: Int) = + null + + override fun removeAttachment(attachment: org.bukkit.permissions.PermissionAttachment) {} + override fun recalculatePermissions() {} + override fun getEffectivePermissions() = emptySet() + override fun isPermissionSet(name: String) = true + override fun isPermissionSet(perm: org.bukkit.permissions.Permission) = true + override fun spigot() = object : CommandSender.Spigot() {} + } + } + + fun execute(command: String): List { + val output = mutableListOf() + + val customSender = createCapturingSender { message -> + output.add(message) + } + + Bukkit.dispatchCommand(customSender, command) + + return output + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/main/vp_server_integration/TotalBalanceModules.kt b/src/main/kotlin/main/vp_server_integration/TotalBalanceModules.kt new file mode 100644 index 0000000..83990a8 --- /dev/null +++ b/src/main/kotlin/main/vp_server_integration/TotalBalanceModules.kt @@ -0,0 +1,24 @@ +package main.vp_server_integration + +import main.vp_server_integration.Vp_server_integration.Companion.LOGGER + +class TotalBalanceModules { + companion object { + fun getEssentialsBalance(): Float { + val output: List = CommandCapture.execute("baltop") + var total: Float = 0.0f + var startOfList: Boolean = false + for (el in output) { + if (startOfList) { + LOGGER.info(el.split(" ")[2].replace(Regex("[^0-9.]"), "")) + total += el.split(" ")[2].replace(Regex("[^0-9.]"), "").toFloat() + } else if (el.contains("1.")) { + startOfList = true + LOGGER.info(el.split(" ")[2].replace(Regex("[^0-9.]"), "")) + total += el.split(" ")[2].replace(Regex("[^0-9.]"), "").toFloat() + } + } + return total + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/main/vp_server_integration/Vp_server_integration.kt b/src/main/kotlin/main/vp_server_integration/Vp_server_integration.kt index 0b23f4b..e893fcd 100644 --- a/src/main/kotlin/main/vp_server_integration/Vp_server_integration.kt +++ b/src/main/kotlin/main/vp_server_integration/Vp_server_integration.kt @@ -1,29 +1,162 @@ package main.vp_server_integration +// ---------------- MAIN FILE ------------------- +// Here you will see the main logic +// ---------------------------------------------- + +import main.vp_server_integration.VpcApi import org.bukkit.plugin.java.JavaPlugin import org.bukkit.Bukkit import java.util.logging.Logger +import org.bukkit.command.PluginCommand +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import net.md_5.bungee.api.chat.ComponentBuilder +import net.md_5.bungee.api.chat.HoverEvent +import net.md_5.bungee.api.chat.ClickEvent +import net.md_5.bungee.api.chat.TextComponent +import org.bukkit.ChatColor +import org.bukkit.permissions.Permission +import org.bukkit.permissions.PermissionAttachment +import org.bukkit.permissions.PermissionAttachmentInfo +import org.bukkit.plugin.Plugin +import java.io.File +import java.io.FileInputStream +import java.nio.file.Files +import java.util.Properties +import java.util.UUID +import kotlin.collections.get +import kotlin.math.abs -class Vp_server_integration : JavaPlugin() { +class Vp_server_integration() : JavaPlugin(), CommandExecutor { companion object { lateinit var LOGGER: Logger lateinit var SERVER: org.bukkit.Server - var PLUGIN_API_PORT: Int = 8010 lateinit var USERNAME: String - lateinit var TOKEN: String + lateinit var USER_TOKEN: String lateinit var USER_API_URL: String + lateinit var COMMAND_ADD_COINS: String + lateinit var COMMAND_REMOVE_MODE: String + lateinit var COMMAND_REMOVE_COINS: String + lateinit var COMMAND_REMOVE_ERROR: String + lateinit var COURSE_MODE: String + var COURSE_STATIC_VALUE: Float = DEFAULT_COURSE_STATIC_VALUE + lateinit var COURSE_DYNAMIC_COMMAND: String + var COURSE_COMMISSION: Float = DEFAULT_COURSE_COMMISSION + const val DEFAULT_USERNAME: String = "test" + const val DEFAULT_USER_TOKEN: String = "test" const val DEFAULT_USER_API_URL: String = "http://127.0.0.1:8010/api/" - const val DEFAULT_TOKEN: String = "test" - const val COLOR_CHAR: Char = '&' + const val DEFAULT_COMMAND_ADD_COINS: String = "eco give %player% %amount%" + const val DEFAULT_COMMAND_REMOVE_MODE: String = "console" + const val DEFAULT_COMMAND_REMOVE_COINS: String = "eco take %player% %amount%" + const val DEFAULT_COMMAND_REMOVE_ERROR: String = "Error:" + const val DEFAULT_COURSE_MODE: String = "dynamic" + const val DEFAULT_COURSE_STATIC_VALUE: Float = 1000.0f + const val DEFAULT_COURSE_DYNAMIC_COMMAND: String = "baltop force" + const val DEFAULT_COURSE_COMMISSION: Float = 5.0f + } + + private fun loadConfiguration() { + try { + // In Spigot, we use getDataFolder() instead of dataDirectory + val dataFolder = getDataFolder() + if (!dataFolder.exists()) { + dataFolder.mkdirs() + } + + val configFile = File(dataFolder, "config.properties") + if (!configFile.exists()) { + // Create default config file + configFile.writeText( + """ +# VPC Integration Configuration + +# ---------- Part for work with UserAPI ----------- +# Username from your VPC wallet +username=$DEFAULT_USERNAME + +# Token for UserAPI +user_token=$DEFAULT_USER_TOKEN + +# UserAPI URL +user_api_url='http://127.0.0.1:8010/api/' +# -------------------- END ------------------------ + + +# ---------- Part for work with server ------------ +# Which command run to add coins? (will run from console) +command_add_coins='$DEFAULT_COMMAND_ADD_COINS' + +# From who we will run remove coins command? (player/console) +command_remove_mode=$DEFAULT_COMMAND_REMOVE_MODE + +# Which command run to remove coins? +command_remove_coins='$DEFAULT_COMMAND_REMOVE_COINS' + +# What shouldn't be in response after executing command +command_remove_error='$DEFAULT_COMMAND_REMOVE_ERROR' +# -------------------- END ------------------------ + + +# --------- Part with configure course ------------ +# Which mode use (dynamic/static, dynamic - set course based +course_mode=$DEFAULT_COURSE_MODE + +# Course VPC to LC (Local Currency) +course_static_value=$DEFAULT_COURSE_STATIC_VALUE + +# Which command will run for getting global balance +# If command not produce CLEAR Float/Int like 32.15 - do not set this +# For UNCLEAR global balance we need module +# Supported modules: baltop (Essentials) +course_dynamic_command='$DEFAULT_COURSE_DYNAMIC_COMMAND' + +# For dynamic course recommended 5% and higher (avoid dupe), for static course you can set 0% +course_commission=$DEFAULT_COURSE_COMMISSION +# -------------------- END ------------------------ +""".trimIndent() + ) + } + + // Load properties + val properties = Properties() + FileInputStream(configFile).use { properties.load(it) } + + USERNAME = properties.getProperty("username", DEFAULT_USERNAME) + USER_TOKEN = properties.getProperty("user_token", DEFAULT_USER_TOKEN) + USER_API_URL = properties.getProperty("user_api_url", DEFAULT_USER_API_URL) + COMMAND_ADD_COINS = properties.getProperty("command_add_coins", DEFAULT_COMMAND_ADD_COINS) + COMMAND_REMOVE_MODE = properties.getProperty("command_remove_mode", DEFAULT_COMMAND_REMOVE_MODE) + COMMAND_REMOVE_COINS = properties.getProperty("command_remove_coins", DEFAULT_COMMAND_REMOVE_COINS) + COMMAND_REMOVE_ERROR = properties.getProperty("command_remove_error", DEFAULT_COMMAND_REMOVE_ERROR) + COURSE_MODE = properties.getProperty("course_mode", DEFAULT_COURSE_MODE) + COURSE_STATIC_VALUE = properties.getProperty("course_static_value", DEFAULT_COURSE_STATIC_VALUE.toString()).toFloat() + COURSE_DYNAMIC_COMMAND = properties.getProperty("course_dynamic_command", DEFAULT_COURSE_DYNAMIC_COMMAND) + COURSE_COMMISSION = properties.getProperty("course_commission", DEFAULT_COURSE_COMMISSION.toString()).toFloat() + + logger.info("Loaded configuration, username: $USERNAME, user_api_url: $USER_API_URL") + } catch (e: Exception) { + // In Spigot, we use severe() for error logging + logger.severe("Failed to load configuration, using defaults: ${e.message}") + USERNAME = DEFAULT_USERNAME + USER_TOKEN = DEFAULT_USER_TOKEN + USER_API_URL = DEFAULT_USER_API_URL + } } override fun onEnable() { // Initialize logger LOGGER = this.logger - // Get server instance SERVER = Bukkit.getServer() + // Load configuration + loadConfiguration() + + getCommand("vpi")?.setExecutor(this) + // Plugin startup logic LOGGER.info("VP Server Integration plugin enabled!") } @@ -32,4 +165,112 @@ class Vp_server_integration : JavaPlugin() { // Plugin shutdown logic LOGGER.info("VP Server Integration plugin disabled!") } + + override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { + if (command.name.equals("vpi", ignoreCase = true)) { + if (sender !is Player) { + sender.sendMessage("&cТолько игроки могут выполнять команды.") + return true + } +// val mess = CommandCapture.execute("baltop") +// VpcApi.send(sender, TotalBalanceModules.getEssentialsBalance().toString()) +// return true + + if (args.size == 3 && args[0] == "convert") { + val direction = args[1] + val amount = abs(args[2].toFloat()) + if (!listOf("vpc", "lc").contains(direction)) { + VpcApi.send(sender, "&cНе существует такого направление, правильные: vpc/lc") + return true + } + + val course: Float // VPC to LC + if (COURSE_MODE == "static") { + course = COURSE_STATIC_VALUE + } else { + if (COURSE_DYNAMIC_COMMAND == "baltop") { + val globalBalance = TotalBalanceModules.getEssentialsBalance() + val vpcUser = VpcApi.user_in_db(username=USERNAME) + val vpcBalance: Float + if (vpcUser == null) { + throw Exception("null vpcUser") + } + vpcBalance = vpcUser["balance"] as Float + course = globalBalance/vpcBalance + } else { + val globalBalance = CommandCapture.execute(COURSE_DYNAMIC_COMMAND)[0].toFloat() + val vpcUser = VpcApi.user_in_db(username=USERNAME) + val vpcBalance: Float + if (vpcUser == null) { + throw Exception("null vpcUser") + } + vpcBalance = vpcUser["balance"] as Float + course = globalBalance/vpcBalance + } + } + + if (direction == "vpc") { + val result: String = CommandCapture.execute(COURSE_DYNAMIC_COMMAND)[0] + if (result.contains(COMMAND_REMOVE_ERROR)) { + VpcApi.send(sender, "&cОшибка, возможно недостаточно средств.") + return true + } +// VpcApi.transfer_coins() + } else if (direction == "lc") { + + } + } else if (args.isNotEmpty() && args[0] == "convert") { + VpcApi.send(sender, "/vpi convert <сумма>") + } else { + VpcApi.send( + sender, """Использование команд: +/vpi convert <куда: vpc/lc> <сумма> - Обмен VPC на локальную валюту или наоборот + +Почему 'VPC-I'? Потому что это интеграция на конечном сервере - 'VPC Integration' + +Соглашение: voidproject.del.pw/vpc_agreement +Группа ТГ: @void_project_mc +Группа ДС: discord.gg/zwNt5DJj6J +""".trimIndent() + ) + } + + return true + + // Create a clickable message that runs /help when clicked +// val message = TextComponent("Click here to execute command") + val colored = ChatColor.translateAlternateColorCodes('&', "&6Testing") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc bal") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentBuilder("Click here to execute command").create()) + + sender.spigot().sendMessage(message) + + return true + } + return false + } + + override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array): List { + val completions = mutableListOf() + if (command.name.equals("vpi", ignoreCase = true)) { + when (args.size) { + 1 -> { + completions.addAll(listOf("help", "convert")) + } + 2 -> { + if (args[0].equals("convert", ignoreCase = true)) { + completions.add("<куда: vpc/lc>") + } + } + 3 -> { + if (args[0].equals("convert", ignoreCase = true)) { + completions.add("<сумма>") + } + } + } + } + + return completions.filter { it.startsWith(args.lastOrNull()?.lowercase() ?: "") }.sorted() + } } diff --git a/src/main/kotlin/main/vp_server_integration/VpcApi.kt b/src/main/kotlin/main/vp_server_integration/VpcApi.kt index a885f81..af9b286 100644 --- a/src/main/kotlin/main/vp_server_integration/VpcApi.kt +++ b/src/main/kotlin/main/vp_server_integration/VpcApi.kt @@ -1,9 +1,15 @@ package main.vp_server_integration +// ----------------- VPC API -------------------- +// Here is implemented calls to VPC UserAPI +// ---------------------------------------------- + import com.google.gson.Gson import com.google.gson.JsonElement import com.google.gson.JsonParser import com.google.gson.JsonSyntaxException +import main.vp_server_integration.Vp_server_integration.Companion.USERNAME +import main.vp_server_integration.Vp_server_integration.Companion.USER_TOKEN import org.bukkit.ChatColor import org.bukkit.command.CommandSender import org.bukkit.entity.Player @@ -104,7 +110,7 @@ class VpcApi { } } - fun send(source: CommandSender, message: String, prefix: String = "&9[&bVPC&9] &3") { + fun send(source: CommandSender, message: String, prefix: String = "&9[&bVPC&7-&6I&9] &3") { // Translate color codes for Spigot val coloredMessage = ChatColor.translateAlternateColorCodes('&', prefix + message) source.sendMessage(coloredMessage) @@ -128,14 +134,14 @@ class VpcApi { // Determine which parameter to use val userParam = when { username != null -> "username" to username - source is Player -> "username" to source.name + source is Player -> "mine_name" to source.name else -> throw IllegalArgumentException("Either source or username must be provided") } val response = sendPost( "user_in_db/", jsonify( listOf( - "token", Vp_server_integration.DEFAULT_TOKEN, + "token", Vp_server_integration.DEFAULT_USER_TOKEN, userParam.first, userParam.second ) ) @@ -149,12 +155,12 @@ class VpcApi { } } - fun transfer_coins(src_username: String, dst_username: String, amount: Float): Any? { + fun transfer_coins(dst_username: String, amount: Float): Any? { val response = sendPost( "transfer_coins/", jsonify( listOf( - "token", Vp_server_integration.DEFAULT_TOKEN, - "src_username", src_username, + "user_token", USER_TOKEN, + "src_username", USERNAME, "dst_username", dst_username, "amount", amount.toString() ) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 38801bd..a3674f5 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,3 +5,7 @@ load: STARTUP authors: [ _SAN5_SkeLet0n_ ] description: Integrate VPC into your server website: voidproject.del.pw +commands: + vpi: + description: Main command + usage: /vpi \ No newline at end of file