From 0dcf402e8e4c8796703c0f0de4f03b5fe6c04a73 Mon Sep 17 00:00:00 2001 From: justuser-31 Date: Sun, 23 Nov 2025 20:15:33 +0300 Subject: [PATCH] =?UTF-8?q?=D0=95=D1=89=D1=91=20=D1=81=D1=8B=D1=80=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BD=D0=B0=D0=B1=D1=80=D0=BE=D1=81=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/vp_server_integration/DataManager.kt | 57 ++++ .../Vp_server_integration.kt | 275 ++++++++++++++++-- .../main/vp_server_integration/VpcApi.kt | 122 +++++++- 3 files changed, 414 insertions(+), 40 deletions(-) create mode 100644 src/main/kotlin/main/vp_server_integration/DataManager.kt diff --git a/src/main/kotlin/main/vp_server_integration/DataManager.kt b/src/main/kotlin/main/vp_server_integration/DataManager.kt new file mode 100644 index 0000000..1a11a44 --- /dev/null +++ b/src/main/kotlin/main/vp_server_integration/DataManager.kt @@ -0,0 +1,57 @@ +package main.vp_server_integration + +import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.plugin.java.JavaPlugin +import java.io.File + +class DataManager(private val dataFolder: File) { + companion object { + private lateinit var dataFile: File + private var playerData: MutableMap = mutableMapOf() + + fun initialize(dataFolder: File) { + dataFile = File(dataFolder, "player_data.yml") + if (!dataFile.exists()) { + dataFile.parentFile?.mkdirs() + dataFile.createNewFile() + } + loadData() + } + + fun getPlayerVPCUsername(playerName: String): String? { + return playerData[playerName] + } + + fun setPlayerVPCUsername(playerName: String, vpcUsername: String) { + playerData[playerName] = vpcUsername + saveData() + } + + fun removePlayerVPCUsername(playerName: String) { + playerData.remove(playerName) + saveData() + } + + fun saveData() { + val config = YamlConfiguration.loadConfiguration(dataFile) + playerData.forEach { (playerName, vpcUsername) -> + config.set(playerName, vpcUsername) + } + try { + config.save(dataFile) + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun loadData() { + val config = YamlConfiguration.loadConfiguration(dataFile) + playerData.clear() + config.getKeys(false).forEach { key: String -> + config.getString(key)?.let { value: String -> + playerData[key] = value + } + } + } + } +} \ 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 46f02e2..779a8a9 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 @@ -16,15 +16,17 @@ 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.plugin.java.JavaPluginLoader +import org.bukkit.scheduler.BukkitRunnable import java.io.File import java.io.FileInputStream import java.util.Properties +import kotlin.collections.mutableMapOf import kotlin.math.abs class Vp_server_integration() : JavaPlugin(), CommandExecutor { companion object { - lateinit var LOGGER: Logger - lateinit var SERVER: org.bukkit.Server + // A lot of needed configurations lateinit var USERNAME: String lateinit var USER_TOKEN: String lateinit var USER_API_URL: String @@ -33,10 +35,10 @@ class Vp_server_integration() : JavaPlugin(), CommandExecutor { 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 + var COURSE_STATIC_VALUE: Double = DEFAULT_COURSE_STATIC_VALUE lateinit var COURSE_DYNAMIC_COMMAND: String var COURSE_COMMISSION: Float = DEFAULT_COURSE_COMMISSION - + // A lot of needed configurations 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/" @@ -45,9 +47,26 @@ class Vp_server_integration() : JavaPlugin(), CommandExecutor { 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_STATIC_VALUE: Double = 1000.0 const val DEFAULT_COURSE_DYNAMIC_COMMAND: String = "baltop force" const val DEFAULT_COURSE_COMMISSION: Float = 5.0f + + const val PREFIX = "&9[&bVPC&7-&6I&9] &3" + + // Helpful variables + lateinit var LOGGER: Logger + lateinit var SERVER: org.bukkit.Server + // For background checks + @JvmStatic + var TO_AUTH_PLAYERS: MutableMap = mutableMapOf() // Pair: {player: vpc_username} + @JvmStatic + var TO_AUTH_PLAYERS_INVOICES: MutableMap = mutableMapOf() // Pair: {player: invoice_id} + @JvmStatic + var TO_PAY_INVOICES: MutableMap = mutableMapOf() // Pair: {player: invoice_id} + @JvmStatic + var INVOICES_AMOUNT: MutableMap = mutableMapOf() // To re-send message when user rejoin/etc. + @JvmStatic + var INVOICES_REWARDS: MutableMap = mutableMapOf() // How many should we pay to player after invoice? } private fun loadConfiguration() { @@ -116,19 +135,19 @@ course_commission=$DEFAULT_COURSE_COMMISSION 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() + USERNAME = properties.getProperty("username", DEFAULT_USERNAME).replace("'", "") + USER_TOKEN = properties.getProperty("user_token", DEFAULT_USER_TOKEN).replace("'", "") + USER_API_URL = properties.getProperty("user_api_url", DEFAULT_USER_API_URL).replace("'", "") + COMMAND_ADD_COINS = properties.getProperty("command_add_coins", DEFAULT_COMMAND_ADD_COINS).replace("'", "") + COMMAND_REMOVE_MODE = properties.getProperty("command_remove_mode", DEFAULT_COMMAND_REMOVE_MODE).replace("'", "") + COMMAND_REMOVE_COINS = properties.getProperty("command_remove_coins", DEFAULT_COMMAND_REMOVE_COINS).replace("'", "") + COMMAND_REMOVE_ERROR = properties.getProperty("command_remove_error", DEFAULT_COMMAND_REMOVE_ERROR).replace("'", "") + COURSE_MODE = properties.getProperty("course_mode", DEFAULT_COURSE_MODE).replace("'", "") + COURSE_STATIC_VALUE = properties.getProperty("course_static_value", DEFAULT_COURSE_STATIC_VALUE.toString()).replace("'", "").toDouble() + COURSE_DYNAMIC_COMMAND = properties.getProperty("course_dynamic_command", DEFAULT_COURSE_DYNAMIC_COMMAND).replace("'", "") + COURSE_COMMISSION = properties.getProperty("course_commission", DEFAULT_COURSE_COMMISSION.toString()).replace("'", "").toFloat() - logger.info("Loaded configuration, username: $USERNAME, user_api_url: $USER_API_URL") + 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}") @@ -149,6 +168,11 @@ course_commission=$DEFAULT_COURSE_COMMISSION getCommand("vpi")?.setExecutor(this) + // Background checks such a auth, invoice check, ... + startBackgroundChecks() + // Init data manager + DataManager.initialize(getDataFolder()) + // Plugin startup logic LOGGER.info("VP Server Integration plugin enabled!") } @@ -168,54 +192,143 @@ course_commission=$DEFAULT_COURSE_COMMISSION // VpcApi.send(sender, TotalBalanceModules.getEssentialsBalance().toString()) // return true - if (args.size == 3 && args[0] == "convert") { + if ((args.size == 3 || args.size == 4) && args[0] == "convert") { + logger.severe("Step 1") + val vpcUsername = DataManager.getPlayerVPCUsername(sender.name) + if (vpcUsername == null) { + VpcApi.send(sender, "&cНеобходимо авторизоваться через: /vpi auth") + return true + } + logger.severe("Step 2") val direction = args[1] - val amount = abs(args[2].toFloat()) + val amount = abs(args[2].toDouble()) if (!listOf("vpc", "lc").contains(direction)) { VpcApi.send(sender, "&cНе существует такого направление, правильные: vpc/lc") return true } - - val course: Float // VPC to LC + logger.severe("Step 3") + val course: Double // VPC to LC if (COURSE_MODE == "static") { course = COURSE_STATIC_VALUE } else { if (COURSE_DYNAMIC_COMMAND == "baltop") { + logger.severe("Step 4.1") val globalBalance = TotalBalanceModules.getEssentialsBalance() + logger.severe("Step 4.1.2") val vpcUser = VpcApi.user_in_db(username=USERNAME) - val vpcBalance: Float + logger.severe("Step 4.1.3") + val vpcBalance: Double + logger.severe("Step 4.1.4") if (vpcUser == null) { throw Exception("null vpcUser") } - vpcBalance = vpcUser["balance"] as Float + logger.severe("Step 4.1.6") + logger.info(vpcUser["balance"].toString()) + vpcBalance = vpcUser["balance"].toString().toDouble() + logger.severe("Step 4.1.7") + logger.info("globalBalance: $globalBalance") + logger.info("vpcBalance: $vpcBalance") course = globalBalance/vpcBalance + logger.info("course: $course") } else { - val globalBalance = CommandCapture.execute(COURSE_DYNAMIC_COMMAND)[0].toFloat() + logger.severe("Step 4.2") + val globalBalance = CommandCapture.execute(COURSE_DYNAMIC_COMMAND).toString().toDouble() + logger.severe("$globalBalance") val vpcUser = VpcApi.user_in_db(username=USERNAME) - val vpcBalance: Float + val vpcBalance: Double if (vpcUser == null) { throw Exception("null vpcUser") } - vpcBalance = vpcUser["balance"] as Float + vpcBalance = vpcUser["balance"].toString().toDouble() 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Ошибка, возможно недостаточно средств.") + val amountVPC: Double = amount/course * (1 + COURSE_COMMISSION/100) + if (args.size == 4) { + logger.severe("Step 5") + val result: String = CommandCapture.execute(COMMAND_REMOVE_COINS.replace("%player%", sender.name).replace("%amount%", amount.toString()))[0] + logger.severe("Step 6") + if (result.contains(COMMAND_REMOVE_ERROR)) { + VpcApi.send(sender, "&cОшибка, возможно недостаточно средств.") + return true + } + logger.severe("Step 8") + VpcApi.transfer_coins(vpcUsername, amountVPC) + } else { + val colored = ChatColor.translateAlternateColorCodes('&', "${PREFIX}Кликните здесь, чтобы конвертировать &6$amount&3 в &6${String.format("%.4f", amountVPC)} VPC") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpi convert vpc $amount confirm") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentBuilder("/vpi convert vpc $amount confirm").create()) + sender.spigot().sendMessage(message) + } + } else if (direction == "lc") { + if (sender.name in TO_PAY_INVOICES) { + val invoice_id = TO_PAY_INVOICES[sender.name] + val amount = INVOICES_AMOUNT[invoice_id] + if (amount == null) return true // Stupid Kotlin, it's not null + val amountLC: Double = amount*course * (1 - COURSE_COMMISSION/100) + val colored = ChatColor.translateAlternateColorCodes('&', "${PREFIX}Кликните здесь, чтобы перевести &6$amount VPC &3в &6${String.format("%.4f", amountLC)}") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc pay $USERNAME $amount $invoice_id") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentBuilder("/vpc pay $USERNAME $amount $invoice_id").create()) + sender.spigot().sendMessage(message) return true } -// VpcApi.transfer_coins() - } else if (direction == "lc") { + logger.severe("amount: $amount") + logger.severe("course: $course") + val amountLC: Double = amount*course * (1 - COURSE_COMMISSION/100) + logger.severe("amountLC: $amountLC") + val invoice_id = VpcApi.create_invoice(amount).toString() + TO_PAY_INVOICES[sender.name] = invoice_id + INVOICES_AMOUNT[invoice_id] = amountLC + val colored = ChatColor.translateAlternateColorCodes('&', "${PREFIX}Кликните здесь, чтобы перевести &6$amount VPC &3в &6${String.format("%.4f", amountLC)}") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc pay $USERNAME $amount $invoice_id") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentBuilder("/vpc pay $USERNAME $amount $invoice_id").create()) + sender.spigot().sendMessage(message) } } else if (args.isNotEmpty() && args[0] == "convert") { VpcApi.send(sender, "/vpi convert <сумма>") + + } else if (args.size == 2 && args[0] == "auth") { + val vpcUsername = DataManager.getPlayerVPCUsername(sender.name) + if (vpcUsername != null) { + VpcApi.send(sender, "Вы уже авторизованы!") + return true + } + if (sender.name in TO_AUTH_PLAYERS) { + val colored = ChatColor.translateAlternateColorCodes('&', "${PREFIX}Кликните здесь, чтобы перевести &60.001 VPC") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc pay $USERNAME 0.001 ${TO_AUTH_PLAYERS_INVOICES[sender.name]}") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentBuilder("/vpc pay $USERNAME 0.001 ${TO_AUTH_PLAYERS_INVOICES[sender.name]}").create()) + sender.spigot().sendMessage(message) + return true + } + val invoice_id = VpcApi.create_invoice(0.001) + val vpc_username = args[1] + logger.severe("Adding to lists") + synchronized(TO_AUTH_PLAYERS) { + TO_AUTH_PLAYERS[sender.name] = vpc_username + TO_AUTH_PLAYERS_INVOICES[sender.name] = invoice_id.toString() + } + logger.severe("TO_AUTH_PLAYERS: $TO_AUTH_PLAYERS") + logger.severe("TO_AUTH_PLAYERS_INVOICES: $TO_AUTH_PLAYERS_INVOICES") + val colored = ChatColor.translateAlternateColorCodes('&', "${PREFIX}Кликните здесь, чтобы перевести 0.001 VPC") + val message = TextComponent(colored) + message.clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "/vpc pay $USERNAME 0.001 $invoice_id") + message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, ComponentBuilder("/vpc pay $USERNAME 0.001 $invoice_id").create()) + sender.spigot().sendMessage(message) + + } else if (args.isNotEmpty() && args[0] == "auth") { + VpcApi.send(sender, "/vpi auth <ник>") + } else { VpcApi.send( sender, """Использование команд: +/vpi auth <ник> - Авторизация /vpi convert <куда: vpc/lc> <сумма> - Обмен VPC на локальную валюту или наоборот Почему 'VPC-I'? Потому что это интеграция на конечном сервере - 'VPC Integration' @@ -228,6 +341,15 @@ course_commission=$DEFAULT_COURSE_COMMISSION } 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 false } @@ -237,7 +359,7 @@ course_commission=$DEFAULT_COURSE_COMMISSION if (command.name.equals("vpi", ignoreCase = true)) { when (args.size) { 1 -> { - completions.addAll(listOf("help", "convert")) + completions.addAll(listOf("help", "convert", "auth")) } 2 -> { if (args[0].equals("convert", ignoreCase = true)) { @@ -254,4 +376,93 @@ course_commission=$DEFAULT_COURSE_COMMISSION return completions.filter { it.startsWith(args.lastOrNull()?.lowercase() ?: "") }.sorted() } + + + private fun startBackgroundChecks() { + object : BukkitRunnable() { + override fun run() { + // Safely check and process TO_AUTH_PLAYERS_INVOICES + synchronized(TO_AUTH_PLAYERS_INVOICES) { + if (TO_AUTH_PLAYERS_INVOICES.isNotEmpty()) { + val iterator = TO_AUTH_PLAYERS_INVOICES.iterator() + while (iterator.hasNext()) { + val entry = iterator.next() + try { + val result = VpcApi.get_invoice(entry.value) as Map<*, *>? + + // Add null safety checks + if (result != null) { + val status = result["status"] + logger.severe("Invoice ${entry.value} status: $status") + + if (status != null && status.toString() == "true") { + logger.severe("DELETE!!!!!!!!!!!!!!!!") + VpcApi.delete_invoice(entry.value) + + DataManager.setPlayerVPCUsername(entry.key, TO_AUTH_PLAYERS[entry.key].toString()) + VpcApi.send(Bukkit.getPlayer(entry.key.toString()), "&aВы успешно авторизованы!") + + // Safely remove from both maps + TO_AUTH_PLAYERS.remove(entry.key) + iterator.remove() // Safe removal during iteration + } + } else { + logger.warning("Received null result for invoice ${entry.value}") + } + } catch (e: Exception) { + logger.severe("Error processing invoice ${entry.value}: ${e.message}") + e.printStackTrace() + } + } + } + } + + // Safely check and process TO_PAY_INVOICES + synchronized(TO_PAY_INVOICES) { + if (TO_PAY_INVOICES.isNotEmpty()) { + val iterator = TO_PAY_INVOICES.iterator() + while (iterator.hasNext()) { + val entry = iterator.next() + try { + val result = VpcApi.get_invoice(entry.value) as Map<*, *>? + + // Add null safety checks + if (result != null) { + val status = result["status"] + logger.severe("Invoice ${entry.value} status: $status") + + if (status != null && status.toString() == "true") { + logger.severe("DELETE!!!!!!!!!!!!!!!!") + VpcApi.delete_invoice(entry.value) + + val amountLC = INVOICES_AMOUNT[entry.value] + + // Fix: Schedule command execution on main thread + val commandToAddCoins = COMMAND_ADD_COINS.replace("%player%", entry.key).replace("%amount%", amountLC.toString()) + Bukkit.getScheduler().runTask(this@Vp_server_integration, Runnable { + try { + CommandCapture.execute(commandToAddCoins) + } catch (e: Exception) { + logger.severe("Error executing command: ${e.message}") + e.printStackTrace() + } + }) + + // Safely remove from both maps + INVOICES_AMOUNT.remove(entry.key) + iterator.remove() // Safe removal during iteration + } + } else { + logger.warning("Received null result for invoice ${entry.value}") + } + } catch (e: Exception) { + logger.severe("Error processing invoice ${entry.value}: ${e.message}") + e.printStackTrace() + } + } + } + } + } + }.runTaskTimerAsynchronously(this, 0L, 20L) // Changed to 20L (1 second) for better performance + } } diff --git a/src/main/kotlin/main/vp_server_integration/VpcApi.kt b/src/main/kotlin/main/vp_server_integration/VpcApi.kt index af9b286..bb75fb3 100644 --- a/src/main/kotlin/main/vp_server_integration/VpcApi.kt +++ b/src/main/kotlin/main/vp_server_integration/VpcApi.kt @@ -8,7 +8,9 @@ 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.PREFIX import main.vp_server_integration.Vp_server_integration.Companion.USERNAME +import main.vp_server_integration.Vp_server_integration.Companion.USER_API_URL import main.vp_server_integration.Vp_server_integration.Companion.USER_TOKEN import org.bukkit.ChatColor import org.bukkit.command.CommandSender @@ -20,9 +22,16 @@ import java.net.HttpURLConnection import java.net.URI import java.net.URISyntaxException import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue +import kotlin.math.max class VpcApi { companion object { + // Rate limiting: track timestamps of requests + private val requestTimestamps = ConcurrentLinkedQueue() + private const val MAX_REQUESTS_PER_MINUTE = 60 + private const val MINUTE_IN_MILLIS = 60 * 1000L + // Using Gson for JSON serialization - much cleaner than manual string building fun jsonify(args: List): String { val map = mutableMapOf() @@ -43,13 +52,50 @@ class VpcApi { operator fun get(key: String): Any? = data?.get(key) } + /** + * Enforces rate limiting by checking if we've exceeded the request limit. + * If so, sleeps until we're within the limit again. + */ + private fun enforceRateLimit() { + val now = System.currentTimeMillis() + + // Remove timestamps older than 1 minute + while (requestTimestamps.isNotEmpty() && now - requestTimestamps.peek() > MINUTE_IN_MILLIS) { + requestTimestamps.poll() + } + + // If we've hit the limit, calculate sleep time + if (requestTimestamps.size >= MAX_REQUESTS_PER_MINUTE) { + val oldestRequestTime = requestTimestamps.peek() + val sleepTime = max(0, MINUTE_IN_MILLIS - (now - oldestRequestTime)) + 100 // Add small buffer + + try { + Thread.sleep(sleepTime) + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } + + // After sleeping, clean up old timestamps again + val afterSleep = System.currentTimeMillis() + while (requestTimestamps.isNotEmpty() && afterSleep - requestTimestamps.peek() > MINUTE_IN_MILLIS) { + requestTimestamps.poll() + } + } + + // Add current timestamp + requestTimestamps.offer(now) + } + fun sendPost(urlPoint: String, json: String, customUrl: Boolean = false, baseurl: String = ""): ApiResponse { + // Enforce rate limiting before making the request + enforceRateLimit() + var uri: URI try { uri = if (customUrl) { URI.create(baseurl + urlPoint) } else { - URI.create(Vp_server_integration.DEFAULT_USER_API_URL + urlPoint) + URI.create(USER_API_URL + urlPoint) } } catch (e: URISyntaxException) { return ApiResponse(-1, null, "Error: Invalid URL syntax") @@ -110,7 +156,7 @@ class VpcApi { } } - fun send(source: CommandSender, message: String, prefix: String = "&9[&bVPC&7-&6I&9] &3") { + fun send(source: CommandSender, message: String, prefix: String = PREFIX) { // Translate color codes for Spigot val coloredMessage = ChatColor.translateAlternateColorCodes('&', prefix + message) source.sendMessage(coloredMessage) @@ -128,6 +174,10 @@ class VpcApi { return "None" } } + // Simple hash function (you might want to replace this with a proper hashing algorithm) + private fun hash(input: String): String { + return UUID.nameUUIDFromBytes(input.toByteArray()).toString() + } fun user_in_db(source: CommandSender? = null, username: String? = null): Map? { var mine_uuid: String? = null @@ -141,7 +191,7 @@ class VpcApi { val response = sendPost( "user_in_db/", jsonify( listOf( - "token", Vp_server_integration.DEFAULT_USER_TOKEN, + "user_token", USER_TOKEN, userParam.first, userParam.second ) ) @@ -155,12 +205,12 @@ class VpcApi { } } - fun transfer_coins(dst_username: String, amount: Float): Any? { + fun transfer_coins(dst_username: String, amount: Double): Any? { val response = sendPost( "transfer_coins/", jsonify( listOf( + "username", USERNAME, "user_token", USER_TOKEN, - "src_username", USERNAME, "dst_username", dst_username, "amount", amount.toString() ) @@ -174,9 +224,65 @@ class VpcApi { } } - // Simple hash function (you might want to replace this with a proper hashing algorithm) - private fun hash(input: String): String { - return UUID.nameUUIDFromBytes(input.toByteArray()).toString() + fun create_invoice(amount: Double): Any? { + val response = sendPost( + "create_invoice/", jsonify( + listOf( + "username", USERNAME, + "user_token", USER_TOKEN, + "amount", amount.toString() + ) + ) + ) + + return if (response.status == 200) { + response.rawResponse + } else { + response.data?.get("detail") + } + } + + fun delete_invoice(invoice_id: String): Any? { + val response = sendPost( + "delete_invoice/", jsonify( + listOf( + "username", USERNAME, + "user_token", USER_TOKEN, + "invoice_id", invoice_id + ) + ) + ) + + return if (response.status == 200) { + response.rawResponse + } else { + response.data?.get("detail") + } + } + + fun get_invoice(invoice_id: String): Any? { + val response = sendPost( + "get_invoice/", jsonify( + listOf( + "username", USERNAME, + "user_token", USER_TOKEN, + "invoice_id", invoice_id + ) + ) + ) + + // response["someKey"] (map-like access) + // OUT: { + // "id": str, + // "dst_username": str, + // "amount": null / float, + // "status": false/true + //} + return if (response.status == 200) { + response.data + } else { + response.data?.get("detail") + } } } } \ No newline at end of file