✨ Balance exchange source points, link account with Solarpass with drasl
This commit is contained in:
@@ -23,6 +23,7 @@ repositories {
|
||||
dependencies {
|
||||
compileOnly("org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT")
|
||||
compileOnly("com.github.MilkBowl:VaultAPI:1.7")
|
||||
implementation("com.github.Zrips:CMI-API:9.7.14.3")
|
||||
|
||||
// These will be packaged into the shadow JAR
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
@@ -5,6 +5,9 @@ import dev.solsynth.snConnect.commands.BedrockRemoverCompleter
|
||||
import dev.solsynth.snConnect.commands.SnCommand
|
||||
import dev.solsynth.snConnect.commands.SnCommandCompleter
|
||||
import dev.solsynth.snConnect.listeners.SnChatListener
|
||||
import dev.solsynth.snConnect.listeners.SolarpassCheckListener
|
||||
import dev.solsynth.snConnect.services.AuthService
|
||||
import dev.solsynth.snConnect.services.AuthUserService
|
||||
import dev.solsynth.snConnect.models.SnChatMessage
|
||||
import dev.solsynth.snConnect.models.WebSocketPacket
|
||||
import dev.solsynth.snConnect.services.SnMessageService
|
||||
@@ -18,17 +21,22 @@ import net.md_5.bungee.api.chat.TextComponent
|
||||
import net.milkbowl.vault.economy.Economy
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Bukkit.getOnlinePlayers
|
||||
import org.bukkit.configuration.file.YamlConfiguration
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
import java.io.InputStreamReader
|
||||
|
||||
|
||||
class SolarNetworkConnect : JavaPlugin() {
|
||||
private var economy: Economy? = null
|
||||
private var sn: SnService? = null
|
||||
private var authService: AuthService? = null
|
||||
private var authUserService: AuthUserService? = null
|
||||
private var messageService: SnMessageService? = null
|
||||
private var syncChatRooms: List<String> = emptyList()
|
||||
private var destinationChatId: String? = null
|
||||
private var webSocketJob: Job? = null
|
||||
private var messages: Map<String, String> = mapOf()
|
||||
private var playerSolarpassMap: MutableMap<String, String?> = mutableMapOf()
|
||||
|
||||
private fun handleWebSocketPacket(packet: WebSocketPacket) {
|
||||
logger.info("Received WebSocket packet: type=${packet.type}")
|
||||
@@ -82,15 +90,13 @@ class SolarNetworkConnect : JavaPlugin() {
|
||||
|
||||
this.saveDefaultConfig()
|
||||
|
||||
messages = mapOf(
|
||||
"join" to "➡️ {player} joined the game.",
|
||||
"joinFirst" to "➡️ {player} first time joined the game.",
|
||||
"quit" to "⬅️ {player} left the game.",
|
||||
"death" to "💀 {player} {message}",
|
||||
"advancement" to "🎉 {player} unlocked advancement: {advancement}",
|
||||
"serverStart" to "🚀 Server started successfully",
|
||||
"serverStop" to "⏹️ Server stopped"
|
||||
) + (config.getConfigurationSection("messages")?.getValues(false) as? Map<String, String> ?: emptyMap())
|
||||
val locale = config.getString("locale") ?: "en"
|
||||
val messageFile = if (locale == "en") "messages.yml" else "messages_$locale.yml"
|
||||
val messagesStream = getResource(messageFile) ?: throw RuntimeException("$messageFile not found in JAR")
|
||||
val messagesConfig = YamlConfiguration.loadConfiguration(InputStreamReader(messagesStream, "UTF-8"))
|
||||
messages = messagesConfig.getValues(false)
|
||||
.mapValues { it.value as String } + (config.getConfigurationSection("messages")
|
||||
?.getValues(false) as? Map<String, String> ?: emptyMap())
|
||||
|
||||
if (!setupNetwork()) {
|
||||
logger.severe("Failed to setup Solar Network Network, check your configuration.")
|
||||
@@ -105,7 +111,14 @@ class SolarNetworkConnect : JavaPlugin() {
|
||||
server.pluginManager.registerEvents(SnChatListener(messageService!!, destinationChatId!!, messages), this)
|
||||
}
|
||||
|
||||
Bukkit.getPluginCommand("solar")!!.setExecutor(SnCommand(this.sn!!, this.economy))
|
||||
if (authUserService != null) {
|
||||
server.pluginManager.registerEvents(
|
||||
SolarpassCheckListener(authUserService!!, messages, playerSolarpassMap),
|
||||
this
|
||||
)
|
||||
}
|
||||
|
||||
Bukkit.getPluginCommand("solar")!!.setExecutor(SnCommand(this.sn!!, this.economy, messages, playerSolarpassMap))
|
||||
Bukkit.getPluginCommand("solar")!!.tabCompleter = SnCommandCompleter()
|
||||
|
||||
Bukkit.getPluginCommand("eatrock")!!.setExecutor(BedrockRemoverCommand(this.economy))
|
||||
@@ -120,7 +133,7 @@ class SolarNetworkConnect : JavaPlugin() {
|
||||
|
||||
// Send server start message
|
||||
destinationChatId?.let { chatId ->
|
||||
messageService?.sendMessage(chatId, messages["serverStart"] ?: "🚀 Server started successfully")
|
||||
messageService?.sendMessage(chatId, messages["server_start"] ?: "🚀 Server started successfully")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +142,7 @@ class SolarNetworkConnect : JavaPlugin() {
|
||||
|
||||
// Send server stop message
|
||||
destinationChatId?.let { chatId ->
|
||||
messageService?.sendMessage(chatId, messages["serverStop"] ?: "⏹️ Server stopped")
|
||||
messageService?.sendMessage(chatId, messages["server_stop"] ?: "⏹️ Server stopped")
|
||||
}
|
||||
|
||||
sn?.disconnect()
|
||||
@@ -147,6 +160,12 @@ class SolarNetworkConnect : JavaPlugin() {
|
||||
destinationChatId = destination
|
||||
sn = SnService(baseUrl, clientId, clientSecret, botApiKey);
|
||||
messageService = SnMessageService(sn!!)
|
||||
|
||||
val authUrl = config.getString("auth.endpoint") ?: return false;
|
||||
val siteSecret = config.getString("auth.site_secret")
|
||||
authService = AuthService(authUrl, siteSecret);
|
||||
authUserService = AuthUserService(authService!!)
|
||||
|
||||
webSocketJob = GlobalScope.launch {
|
||||
sn!!.connectWebSocketAsFlow().collect { packet ->
|
||||
handleWebSocketPacket(packet)
|
||||
|
@@ -13,7 +13,8 @@ import org.bukkit.command.CommandSender
|
||||
import org.bukkit.command.TabCompleter
|
||||
import org.bukkit.entity.Player
|
||||
|
||||
class SnCommand(private val sn: SnService, private val eco: Economy?) : CommandExecutor {
|
||||
class SnCommand(private val sn: SnService, private val eco: Economy?, private val messages: Map<String, String>, private val playerSolarpassMap: MutableMap<String, String?>) :
|
||||
CommandExecutor {
|
||||
override fun onCommand(p0: CommandSender, p1: Command, p2: String, p3: Array<out String>): Boolean {
|
||||
if (p0 !is Player) {
|
||||
return false;
|
||||
@@ -24,7 +25,10 @@ class SnCommand(private val sn: SnService, private val eco: Economy?) : CommandE
|
||||
when (p3[0].lowercase()) {
|
||||
"deposit" -> {
|
||||
if (p3.size < 2) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "You need to specific an amount to deposit.")
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + (messages["command_deposit_no_amount"]
|
||||
?: "You need to specify an amount to deposit.")
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -32,35 +36,43 @@ class SnCommand(private val sn: SnService, private val eco: Economy?) : CommandE
|
||||
|
||||
if (p3[1].equals("confirm", ignoreCase = true) && p3.size >= 3) {
|
||||
// Confirming order
|
||||
val orderNumber = p3[2].toLongOrNull();
|
||||
if (orderNumber == null) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "Invalid order number, it must be a number.");
|
||||
return true;
|
||||
}
|
||||
|
||||
p0.sendMessage(ChatColor.GRAY.toString() + "Confirming payment, please stand by...");
|
||||
val orderNumber = p3[2];
|
||||
p0.sendMessage(
|
||||
ChatColor.GRAY.toString() + (messages["command_deposit_confirming"]
|
||||
?: "Confirming payment, please stand by...")
|
||||
);
|
||||
|
||||
val order: SnOrder
|
||||
try {
|
||||
order = orderSrv.getOrder(orderNumber);
|
||||
orderSrv.cancelOrder(orderNumber);
|
||||
orderSrv.updateOrderStatus(orderNumber, false);
|
||||
} catch (_: Exception) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "An error occurred while pulling transaction. Make sure the order is exists then try again later.")
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + (messages["command_deposit_pull_error"]
|
||||
?: "An error occurred while pulling transaction. Make sure the order exists then try again later.")
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (order.status != 1L) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "Order was not paid yet.");
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + (messages["command_deposit_order_not_paid"]
|
||||
?: "Order was not paid yet.")
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
val bal = order.amount.toDouble() * 100;
|
||||
eco?.depositPlayer(p0.player, bal)
|
||||
|
||||
val doneComponent = TextComponent(ChatColor.GREEN.toString() + "Done!")
|
||||
val doneComponent =
|
||||
TextComponent(ChatColor.GREEN.toString() + (messages["command_deposit_done"] ?: "Done!"))
|
||||
val moneyComponent = TextComponent(ChatColor.GOLD.toString() + ChatColor.BOLD + " $bal$")
|
||||
val suffixComponent =
|
||||
TextComponent(ChatColor.GREEN.toString() + " has been added to your balance.")
|
||||
TextComponent(
|
||||
ChatColor.GREEN.toString() + (messages["command_deposit_added_balance"]
|
||||
?: " has been added to your balance.")
|
||||
)
|
||||
|
||||
p0.playSound(p0.player!!, Sound.BLOCK_ANVIL_PLACE, 1.0F, 1.0F)
|
||||
p0.spigot().sendMessage(doneComponent, moneyComponent, suffixComponent)
|
||||
@@ -71,32 +83,52 @@ class SnCommand(private val sn: SnService, private val eco: Economy?) : CommandE
|
||||
// Creating new order
|
||||
val amount = p3[1].toDoubleOrNull();
|
||||
if (amount == null) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "You need to specific an amount of number to deposit.")
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + (messages["command_deposit_invalid_amount"]
|
||||
?: "You need to specify an amount as a number to deposit.")
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
p0.sendMessage(ChatColor.GRAY.toString() + "Creating order, please stand by...");
|
||||
p0.sendMessage(
|
||||
ChatColor.GRAY.toString() + (messages["command_deposit_creating"]
|
||||
?: "Creating order, please stand by...")
|
||||
);
|
||||
|
||||
val order: SnOrder
|
||||
try {
|
||||
order = orderSrv.createOrder("Deposit to Highland MC", amount / 100);
|
||||
} catch (_: Exception) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "An error occurred while creating order. Try again later.")
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + (messages["command_deposit_create_error"]
|
||||
?: "An error occurred while creating order. Try again later.")
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
val linkComponent =
|
||||
TextComponent(ChatColor.GOLD.toString() + ChatColor.UNDERLINE.toString() + ChatColor.BOLD.toString() + "click here")
|
||||
TextComponent(
|
||||
ChatColor.GOLD.toString() + ChatColor.UNDERLINE.toString() + ChatColor.BOLD.toString() + (messages["command_deposit_click_here"]
|
||||
?: "click here")
|
||||
)
|
||||
linkComponent.clickEvent =
|
||||
ClickEvent(ClickEvent.Action.OPEN_URL, "https://solsynth.dev/orders/${order.id}");
|
||||
ClickEvent(ClickEvent.Action.OPEN_URL, "https://solian.app/orders/${order.id}");
|
||||
val orderHintComponent =
|
||||
TextComponent(ChatColor.GRAY.toString() + "Order created, number " + ChatColor.WHITE + ChatColor.BOLD + "#${order.id}")
|
||||
val followingComponent = TextComponent(ChatColor.GRAY.toString() + " to the payment page.")
|
||||
TextComponent(
|
||||
ChatColor.GRAY.toString() + (messages["command_deposit_order_created"]
|
||||
?: "Order created, number ") + ChatColor.WHITE + ChatColor.BOLD + "#${order.id}"
|
||||
)
|
||||
val followingComponent = TextComponent(
|
||||
ChatColor.GRAY.toString() + (messages["command_deposit_to_payment_page"] ?: " to the payment page.")
|
||||
)
|
||||
p0.spigot()
|
||||
.sendMessage(orderHintComponent, linkComponent, followingComponent);
|
||||
|
||||
val afterPaidComponent =
|
||||
TextComponent(ChatColor.UNDERLINE.toString() + ChatColor.YELLOW + "After you paid, click here to confirm payment.")
|
||||
TextComponent(
|
||||
ChatColor.UNDERLINE.toString() + ChatColor.YELLOW + (messages["command_deposit_after_paid"]
|
||||
?: "After you paid, click here to confirm payment.")
|
||||
)
|
||||
afterPaidComponent.clickEvent =
|
||||
ClickEvent(ClickEvent.Action.RUN_COMMAND, "/sn deposit confirm ${order.id}")
|
||||
p0.spigot().sendMessage(afterPaidComponent);
|
||||
@@ -104,47 +136,67 @@ class SnCommand(private val sn: SnService, private val eco: Economy?) : CommandE
|
||||
|
||||
"withdraw" -> {
|
||||
if (p3.size < 2) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "You need to specific an amount to deposit.")
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + (messages["command_deposit_no_amount"]
|
||||
?: "You need to specify an amount to deposit.")
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
val amount = p3[1].toDoubleOrNull();
|
||||
if (amount == null) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "You need to specific an amount of number to deposit.")
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + (messages["command_deposit_invalid_amount"]
|
||||
?: "You need to specify an amount as a number to deposit.")
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p3.size < 3) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "You need to specific a wallet account to deposit.")
|
||||
val playerUuid = p0.uniqueId.toString()
|
||||
val accountId = playerSolarpassMap[playerUuid]
|
||||
if (accountId == null) {
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + "Bind your Solarpass first!"
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
val walletId = p3[2].toLongOrNull();
|
||||
if (walletId == null) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "You need to specific a wallet account to deposit.")
|
||||
return true;
|
||||
}
|
||||
|
||||
p0.sendMessage(ChatColor.GRAY.toString() + "Making transaction, please stand by...");
|
||||
p0.sendMessage(
|
||||
ChatColor.GRAY.toString() + (messages["command_withdraw_making"]
|
||||
?: "Making transaction, please stand by...")
|
||||
);
|
||||
|
||||
val bal = amount / 100;
|
||||
val resp = eco?.withdrawPlayer(p0.player, "Withdraw to Source Point - $bal SRC", amount);
|
||||
if (resp?.type != EconomyResponse.ResponseType.SUCCESS) {
|
||||
p0.sendMessage(ChatColor.RED.toString() + "Your in game account has no enough money for that.")
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + (messages["command_withdraw_no_money"]
|
||||
?: "Your in-game account does not have enough money for that.")
|
||||
)
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
val transactionSrv = SnTransactionService(sn);
|
||||
val transaction = transactionSrv.makeTransaction(bal, "Withdraw from Highland MC", walletId);
|
||||
val transactionSrv = SnBalanceService(sn);
|
||||
val transaction = transactionSrv.addBalance(bal, "Withdraw from GoatCraft", accountId);
|
||||
val transactionHintComponent =
|
||||
TextComponent(ChatColor.GREEN.toString() + "Done! transaction number " + ChatColor.WHITE + ChatColor.BOLD + "#${transaction.id}")
|
||||
TextComponent(
|
||||
ChatColor.GREEN.toString() + (messages["command_withdraw_done"]
|
||||
?: "Done! transaction number ") + ChatColor.WHITE + ChatColor.BOLD + "#${transaction.id}"
|
||||
)
|
||||
|
||||
p0.playSound(p0.player!!, Sound.BLOCK_ANVIL_PLACE, 1.0F, 1.0F)
|
||||
p0.spigot().sendMessage(transactionHintComponent)
|
||||
} catch (_: Exception) {
|
||||
eco?.depositPlayer(p0.player, "Withdraw to Source Point Failed - Refund", amount)
|
||||
p0.sendMessage(ChatColor.RED.toString() + "An error occurred while making transaction. Make sure your wallet is exists then try again later.")
|
||||
eco?.depositPlayer(
|
||||
p0.player,
|
||||
(messages["command_withdraw_refund_reason"] ?: "Withdraw to Source Point Failed - Refund"),
|
||||
amount
|
||||
)
|
||||
p0.sendMessage(
|
||||
ChatColor.RED.toString() + (messages["command_withdraw_error"]
|
||||
?: "An error occurred while making transaction. Make sure your wallet exists then try again later.")
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -172,4 +224,4 @@ class SnCommandCompleter : TabCompleter {
|
||||
else -> mutableListOf();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package dev.solsynth.snConnect.listeners
|
||||
|
||||
import com.Zrips.CMI.CMI
|
||||
import dev.solsynth.snConnect.services.SnMessageService
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.entity.Player
|
||||
@@ -26,7 +27,8 @@ class SnChatListener(
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
@EventHandler()
|
||||
fun onPlayerJoin(event: PlayerJoinEvent) {
|
||||
val firstTime = Bukkit.getOfflinePlayer(event.player.uniqueId) == null;
|
||||
val user = CMI.getInstance().playerManager.getUser(event.player);
|
||||
val firstTime = user.playerTime == 0L;
|
||||
val templateKey = if (!firstTime) "join" else "joinFirst";
|
||||
val template = messages[templateKey]
|
||||
?: if (!firstTime) "➡️ {player} joined the game." else "➡️ {player} first time joined the game."
|
||||
|
@@ -0,0 +1,37 @@
|
||||
package dev.solsynth.snConnect.listeners
|
||||
|
||||
import dev.solsynth.snConnect.services.AuthUserService
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.bukkit.ChatColor
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.player.PlayerJoinEvent
|
||||
|
||||
class SolarpassCheckListener(
|
||||
private val authUserService: AuthUserService,
|
||||
private val messages: Map<String, String>,
|
||||
private val playerSolarpassMap: MutableMap<String, String?>
|
||||
) : Listener {
|
||||
|
||||
@EventHandler()
|
||||
fun onPlayerJoin(event: PlayerJoinEvent) {
|
||||
GlobalScope.launch {
|
||||
try {
|
||||
val playerUuid = event.player.uniqueId.toString()
|
||||
var solarpassId = playerSolarpassMap[playerUuid]
|
||||
if (solarpassId == null) {
|
||||
solarpassId = authUserService.getSnByPlayer(playerUuid)
|
||||
playerSolarpassMap[playerUuid] = solarpassId
|
||||
}
|
||||
if (solarpassId == null) {
|
||||
// Send suggestion message
|
||||
val message = messages["solarpass_bind_suggestion"] ?: "${ChatColor.YELLOW}To get more features, please bind your Solarpass account!"
|
||||
event.player.sendMessage(message)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// Optionally log the error or handle it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package dev.solsynth.snConnect.services
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import java.util.logging.Logger
|
||||
|
||||
class AuthService(private val baseUrl: String, val siteSecret: String?) {
|
||||
val client = OkHttpClient.Builder().build();
|
||||
private val logger = Logger.getLogger(AuthService::class.java.name)
|
||||
|
||||
fun getUrl(segment: String): String {
|
||||
return "$baseUrl$segment"
|
||||
}
|
||||
}
|
@@ -0,0 +1,132 @@
|
||||
package dev.solsynth.snConnect.services
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Request
|
||||
import java.io.IOException
|
||||
|
||||
@Serializable
|
||||
data class DraslPlayer(
|
||||
@SerialName("userUuid")
|
||||
val userUuid: String,
|
||||
val name: String,
|
||||
val uuid: String,
|
||||
@SerialName("offlineUuid")
|
||||
val offlineUuid: String,
|
||||
@SerialName("fallbackPlayer")
|
||||
val fallbackPlayer: String,
|
||||
@SerialName("skinModel")
|
||||
val skinModel: String,
|
||||
@SerialName("skinUrl")
|
||||
val skinUrl: String?,
|
||||
@SerialName("capeUrl")
|
||||
val capeUrl: String?,
|
||||
@SerialName("createdAt")
|
||||
val createdAt: String, // ISO string
|
||||
@SerialName("nameLastChangedAt")
|
||||
val nameLastChangedAt: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DraslOIDCIdentity(
|
||||
@SerialName("userUuid")
|
||||
val userUuid: String,
|
||||
@SerialName("oidcProviderName")
|
||||
val oidcProviderName: String,
|
||||
val issuer: String,
|
||||
val subject: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DraslUser(
|
||||
@SerialName("isAdmin")
|
||||
val isAdmin: Boolean,
|
||||
@SerialName("isLocked")
|
||||
val isLocked: Boolean,
|
||||
val uuid: String,
|
||||
val username: String,
|
||||
@SerialName("preferredLanguage")
|
||||
val preferredLanguage: String,
|
||||
@SerialName("maxPlayerCount")
|
||||
val maxPlayerCount: Int,
|
||||
val players: List<DraslPlayer>,
|
||||
@SerialName("oidcIdentities")
|
||||
val oidcIdentities: List<DraslOIDCIdentity>
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class APILoginRequest(
|
||||
val username: String,
|
||||
val password: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class APILoginResponse(
|
||||
val user: DraslUser,
|
||||
val token: String,
|
||||
@SerialName("expires_at")
|
||||
val expiresAt: Instant
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class APIOIDCTokenRequest(
|
||||
@SerialName("provider")
|
||||
val provider: String,
|
||||
val code: String,
|
||||
val state: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class APIOIDCTokenResponse(
|
||||
val user: DraslUser,
|
||||
val token: String,
|
||||
@SerialName("expires_at")
|
||||
val expiresAt: Instant
|
||||
)
|
||||
|
||||
class AuthUserService(private val sn: AuthService) {
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
private fun addAuthHeader(builder: Request.Builder): Request.Builder {
|
||||
sn.siteSecret?.let { builder.header("Authorization", "Bearer $it") }
|
||||
return builder
|
||||
}
|
||||
|
||||
fun getUser(id: String): DraslUser {
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(sn.getUrl("/drasl/api/v2/users/$id"))
|
||||
.get()
|
||||
|
||||
sn.client.newCall(addAuthHeader(requestBuilder).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
val responseBody = response.body!!.string()
|
||||
return json.decodeFromString<DraslUser>(responseBody)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlayerByUuid(uuid: String): DraslPlayer {
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(sn.getUrl("/drasl/api/v2/players/$uuid"))
|
||||
.get()
|
||||
|
||||
sn.client.newCall(addAuthHeader(requestBuilder).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
val responseBody = response.body!!.string()
|
||||
return json.decodeFromString<DraslPlayer>(responseBody)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSnByPlayer(playerUuid: String): String? {
|
||||
return try {
|
||||
val player = getPlayerByUuid(playerUuid)
|
||||
val user = getUser(player.userUuid)
|
||||
user.oidcIdentities.find { it.oidcProviderName == "Solarpass" }?.subject
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,54 +8,59 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.IOException
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Serializable
|
||||
data class SnTransaction (
|
||||
val id: Long,
|
||||
data class SnTransaction(
|
||||
val id: String,
|
||||
@SerialName("created_at")
|
||||
val createdAt: Instant,
|
||||
@SerialName("updated_at")
|
||||
val updatedAt: Instant,
|
||||
@SerialName("deleted_at")
|
||||
val deletedAt: Instant? = null,
|
||||
val remark: String,
|
||||
val amount: String,
|
||||
@SerialName("payer_id")
|
||||
val payerID: Long? = null,
|
||||
@SerialName("payee_id")
|
||||
val payeeID: Long? = null
|
||||
@SerialName("currency")
|
||||
val currency: String,
|
||||
@SerialName("amount")
|
||||
val amount: Double,
|
||||
@SerialName("remarks")
|
||||
val remarks: String?,
|
||||
@SerialName("type")
|
||||
val type: String,
|
||||
@SerialName("payer_wallet_id")
|
||||
val payerWalletId: String?,
|
||||
@SerialName("payee_wallet_id")
|
||||
val payeeWalletId: String?
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SnTransactionRequest(
|
||||
@SerialName("client_id")
|
||||
val clientId: String,
|
||||
@SerialName("client_secret")
|
||||
val clientSecret: String,
|
||||
@SerialName("payee_id")
|
||||
val payeeID: Long? = null,
|
||||
@SerialName("payer_id")
|
||||
val payerID: Long? = null,
|
||||
@SerialName("account_id")
|
||||
val accountID: String,
|
||||
val remark: String,
|
||||
val amount: Double
|
||||
val amount: Double,
|
||||
val currency: String
|
||||
)
|
||||
|
||||
class SnTransactionService(private val sn: SnService) {
|
||||
class SnBalanceService(private val sn: SnService) {
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
fun makeTransaction(amount: Double, remark: String, payeeID: Long): SnTransaction {
|
||||
fun addBalance(amount: Double, remark: String, accountID: String): SnTransaction {
|
||||
val body = SnTransactionRequest(
|
||||
sn.clientId,
|
||||
sn.clientSecret,
|
||||
amount = amount,
|
||||
remark = remark,
|
||||
payeeID = payeeID,
|
||||
accountID = accountID,
|
||||
currency = "points"
|
||||
);
|
||||
// Notice, the balance modification is admin API, the bot need have the admin privilege to do so.
|
||||
val request = Request.Builder()
|
||||
.url(sn.getUrl("wa", "/transactions"))
|
||||
.url(sn.getUrl("id", "/wallets/balance"))
|
||||
.post(Json.encodeToString(body).toRequestBody("application/json".toMediaTypeOrNull()))
|
||||
.apply {
|
||||
sn.botApiKey?.let { header("Authorization", "Bearer $it") }
|
||||
}
|
||||
.build()
|
||||
|
||||
sn.client.newCall(request).execute().use { response ->
|
||||
@@ -66,18 +71,4 @@ class SnTransactionService(private val sn: SnService) {
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
fun getTransaction(id: Long): SnTransaction {
|
||||
val request = Request.Builder()
|
||||
.url(sn.getUrl("wa", "/transactions/$id"))
|
||||
.get()
|
||||
.build()
|
||||
sn.client.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
val responseBody = response.body!!.string()
|
||||
val out = json.decodeFromString<SnTransaction>(responseBody)
|
||||
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,13 +1,12 @@
|
||||
package dev.solsynth.snConnect.services
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.IOException
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class SnMessageRequest(
|
||||
@@ -16,7 +15,7 @@ data class SnMessageRequest(
|
||||
)
|
||||
|
||||
class SnMessageService(private val sn: SnService) {
|
||||
private val json = Json {
|
||||
private val json: Json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
|
@@ -4,10 +4,12 @@ import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.IOException
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Serializable
|
||||
data class SnOrderRequest(
|
||||
@@ -15,21 +17,32 @@ data class SnOrderRequest(
|
||||
val clientId: String,
|
||||
@SerialName("client_secret")
|
||||
val clientSecret: String,
|
||||
val remark: String,
|
||||
val amount: Double
|
||||
@SerialName("currency")
|
||||
val currency: String,
|
||||
@SerialName("amount")
|
||||
val amount: Double,
|
||||
@SerialName("remarks")
|
||||
val remarks: String? = null,
|
||||
@SerialName("product_identifier")
|
||||
val productIdentifier: String? = null,
|
||||
@SerialName("meta")
|
||||
val meta: JsonElement? = null,
|
||||
@SerialName("duration_hours")
|
||||
val durationHours: Int = 24
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SnOrderCancelRequest(
|
||||
data class SnOrderUpdateStatus(
|
||||
@SerialName("client_id")
|
||||
val clientId: String,
|
||||
@SerialName("client_secret")
|
||||
val clientSecret: String,
|
||||
val status: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SnOrder(
|
||||
val id: Long,
|
||||
val id: String,
|
||||
@SerialName("created_at")
|
||||
val createdAt: Instant,
|
||||
@SerialName("updated_at")
|
||||
@@ -56,13 +69,14 @@ class SnOrderService(private val sn: SnService) {
|
||||
|
||||
fun createOrder(remark: String, amount: Double): SnOrder {
|
||||
val body = SnOrderRequest(
|
||||
sn.clientId,
|
||||
sn.clientSecret,
|
||||
remark,
|
||||
amount,
|
||||
clientId = sn.clientId,
|
||||
clientSecret = sn.clientSecret,
|
||||
currency = "points",
|
||||
amount = amount,
|
||||
remarks = remark
|
||||
);
|
||||
val request = Request.Builder()
|
||||
.url(sn.getUrl("wa", "/orders"))
|
||||
.url(sn.getUrl("id", "/orders"))
|
||||
.post(Json.encodeToString(body).toRequestBody("application/json".toMediaTypeOrNull()))
|
||||
.build()
|
||||
|
||||
@@ -75,9 +89,9 @@ class SnOrderService(private val sn: SnService) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrder(id: Long): SnOrder {
|
||||
fun getOrder(id: String): SnOrder {
|
||||
val request = Request.Builder()
|
||||
.url(sn.getUrl("wa", "/orders/$id"))
|
||||
.url(sn.getUrl("id", "/orders/$id"))
|
||||
.get()
|
||||
.build()
|
||||
sn.client.newCall(request).execute().use { response ->
|
||||
@@ -89,14 +103,17 @@ class SnOrderService(private val sn: SnService) {
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelOrder(id: Long): SnOrder {
|
||||
val body = SnOrderCancelRequest(
|
||||
// If the isCancelled is true, set the order status to be canceled, otherwise set it to finished
|
||||
fun updateOrderStatus(id: String, isCancelled: Boolean = false): SnOrder {
|
||||
val status = if (isCancelled) 2 else 3;
|
||||
val body = SnOrderUpdateStatus(
|
||||
sn.clientId,
|
||||
sn.clientSecret,
|
||||
status
|
||||
);
|
||||
val request = Request.Builder()
|
||||
.url(sn.getUrl("wa", "/orders/$id/cancel"))
|
||||
.post(Json.encodeToString(body).toRequestBody("application/json".toMediaTypeOrNull()))
|
||||
.url(sn.getUrl("id", "/orders/$id/status"))
|
||||
.patch(Json.encodeToString(body).toRequestBody("application/json".toMediaTypeOrNull()))
|
||||
.build()
|
||||
|
||||
sn.client.newCall(request).execute().use { response ->
|
||||
@@ -107,4 +124,4 @@ class SnOrderService(private val sn: SnService) {
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,140 @@
|
||||
package dev.solsynth.snConnect.services
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.IOException
|
||||
|
||||
@Serializable
|
||||
data class APIPlayer(
|
||||
val id: String,
|
||||
@SerialName("user_id")
|
||||
val userId: String,
|
||||
@SerialName("display_name")
|
||||
val displayName: String?,
|
||||
@SerialName("uuid")
|
||||
val uuid: String?, // Minecraft UUID
|
||||
@SerialName("created_at")
|
||||
val createdAt: Instant,
|
||||
@SerialName("updated_at")
|
||||
val updatedAt: Instant,
|
||||
@SerialName("last_login")
|
||||
val lastLogin: Instant?
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class APIPlayerCreate(
|
||||
@SerialName("user_id")
|
||||
val userId: String,
|
||||
@SerialName("display_name")
|
||||
val displayName: String?,
|
||||
@SerialName("uuid")
|
||||
val uuid: String?
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class APIPlayerUpdate(
|
||||
@SerialName("display_name")
|
||||
val displayName: String?,
|
||||
@SerialName("uuid")
|
||||
val uuid: String?
|
||||
)
|
||||
|
||||
class SnPlayerService(private val sn: AuthService) {
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
private fun addAuthHeader(builder: Request.Builder): Request.Builder {
|
||||
sn.siteSecret?.let { builder.header("Authorization", "Bearer $it") }
|
||||
return builder
|
||||
}
|
||||
|
||||
fun getPlayers(): List<APIPlayer> {
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(sn.getUrl("/drasl/api/v2/players"))
|
||||
.get()
|
||||
|
||||
sn.client.newCall(addAuthHeader(requestBuilder).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
val responseBody = response.body!!.string()
|
||||
return json.decodeFromString(responseBody)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlayer(id: String): APIPlayer {
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(sn.getUrl("/drasl/api/v2/players/$id"))
|
||||
.get()
|
||||
|
||||
sn.client.newCall(addAuthHeader(requestBuilder).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
val responseBody = response.body!!.string()
|
||||
return json.decodeFromString(responseBody)
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlayerByUuid(uuid: String): APIPlayer {
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(sn.getUrl("/drasl/api/v2/players/uuid/$uuid"))
|
||||
.get()
|
||||
|
||||
sn.client.newCall(addAuthHeader(requestBuilder).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
val responseBody = response.body!!.string()
|
||||
return json.decodeFromString(responseBody)
|
||||
}
|
||||
}
|
||||
|
||||
fun createPlayer(request: APIPlayerCreate): APIPlayer {
|
||||
val body = Json.encodeToString(request).toRequestBody("application/json".toMediaTypeOrNull())
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(sn.getUrl("/drasl/api/v2/players"))
|
||||
.post(body)
|
||||
|
||||
sn.client.newCall(addAuthHeader(requestBuilder).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
val responseBody = response.body!!.string()
|
||||
return json.decodeFromString(responseBody)
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePlayer(id: String, request: APIPlayerUpdate): APIPlayer {
|
||||
val body = Json.encodeToString(request).toRequestBody("application/json".toMediaTypeOrNull())
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(sn.getUrl("/drasl/api/v2/players/$id"))
|
||||
.patch(body)
|
||||
|
||||
sn.client.newCall(addAuthHeader(requestBuilder).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
val responseBody = response.body!!.string()
|
||||
return json.decodeFromString(responseBody)
|
||||
}
|
||||
}
|
||||
|
||||
fun deletePlayer(id: String) {
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(sn.getUrl("/drasl/api/v2/players/$id"))
|
||||
.delete()
|
||||
|
||||
sn.client.newCall(addAuthHeader(requestBuilder).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
}
|
||||
}
|
||||
|
||||
fun getPlayerByUser(userId: String): APIPlayer {
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(sn.getUrl("/drasl/api/v2/players/user/$userId"))
|
||||
.get()
|
||||
|
||||
sn.client.newCall(addAuthHeader(requestBuilder).build()).execute().use { response ->
|
||||
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||
val responseBody = response.body!!.string()
|
||||
return json.decodeFromString(responseBody)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,15 +1,12 @@
|
||||
locale: en
|
||||
sn:
|
||||
endpoint: https://api.solian.app
|
||||
client_id: goatcraft
|
||||
client_secret: 12345678
|
||||
bot_secret: 114.514.19198
|
||||
auth:
|
||||
endpoint: https://authmc.solsynth.dev
|
||||
site_secret: hereissecret
|
||||
chat:
|
||||
sync_rooms: []
|
||||
outgoing_room: 00000000-0000-0000-0000-00000000008b
|
||||
messages:
|
||||
join: "➡️ {player} joined the game."
|
||||
joinFirst: "➡️ {player} first time joined the game."
|
||||
quit: "⬅️ {player} left the game."
|
||||
death: "💀 {player} {message}"
|
||||
serverStart: "🚀 Server started successfully"
|
||||
serverStop: "⏹️ Server stopped"
|
||||
outgoing_room: 00000000-0000-0000-0000-00000000008b
|
29
src/main/resources/messages.yml
Normal file
29
src/main/resources/messages.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
join: "➡️ {player} joined the game."
|
||||
joinFirst: "➡️ {player} first time joined the game."
|
||||
quit: "⬅️ {player} left the game."
|
||||
death: "💀 {player} {message}"
|
||||
advancement: "🎉 {player} unlocked advancement: {advancement}"
|
||||
server_start: "🚀 Server started successfully"
|
||||
server_stop: "⏹️ Server stopped"
|
||||
|
||||
# Command messages
|
||||
command_deposit_no_amount: "You need to specify an amount to deposit."
|
||||
command_deposit_invalid_order: "Invalid order number, it must be a uuid."
|
||||
command_deposit_confirming: "Confirming payment, please stand by..."
|
||||
command_deposit_pull_error: "An error occurred while pulling transaction. Make sure the order exists then try again later."
|
||||
command_deposit_order_not_paid: "Order was not paid yet."
|
||||
command_deposit_done: "Done!"
|
||||
command_deposit_added_balance: " has been added to your balance."
|
||||
command_deposit_invalid_amount: "You need to specify an amount as a number to deposit."
|
||||
command_deposit_creating: "Creating order, please stand by..."
|
||||
command_deposit_create_error: "An error occurred while creating order. Try again later."
|
||||
command_deposit_click_here: "click here"
|
||||
command_deposit_order_created: "Order created, number "
|
||||
command_deposit_to_payment_page: " to the payment page."
|
||||
command_deposit_after_paid: "After you paid, click here to confirm payment."
|
||||
command_withdraw_no_wallet: "You need to specify a wallet account to withdraw to."
|
||||
command_withdraw_making: "Making transaction, please stand by..."
|
||||
command_withdraw_no_money: "Your in-game account does not have enough money for that."
|
||||
command_withdraw_done: "Done! transaction number "
|
||||
command_withdraw_error: "An error occurred while making transaction. Make sure your wallet exists then try again later."
|
||||
command_withdraw_refund_reason: "Withdraw to Source Point Failed - Refund"
|
29
src/main/resources/messages_zh.yml
Normal file
29
src/main/resources/messages_zh.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
join: "➡️ {player} 加入了游戏。"
|
||||
joinFirst: "➡️ {player} 首次加入了游戏。"
|
||||
quit: "⬅️ {player} 离开了游戏。"
|
||||
death: "💀 {player} {message}"
|
||||
advancement: "🎉 {player} 解锁成就:{advancement}"
|
||||
server_start: "🚀 服务器成功启动"
|
||||
server_stop: "⏹️ 服务器已停止"
|
||||
|
||||
# Command messages
|
||||
command_deposit_no_amount: "您需要指定存款金额。"
|
||||
command_deposit_invalid_order: "无效的订单号,它必须是一个 UUID。"
|
||||
command_deposit_confirming: "正在确认付款,请稍等..."
|
||||
command_deposit_pull_error: "拉取交易时发生错误。请确保订单存在,然后稍后再试。"
|
||||
command_deposit_order_not_paid: "订单尚未付款。"
|
||||
command_deposit_done: "完成!"
|
||||
command_deposit_added_balance: " 已添加到您的余额。"
|
||||
command_deposit_invalid_amount: "您需要将金额指定为数字进行存款。"
|
||||
command_deposit_creating: "正在创建订单,请稍等..."
|
||||
command_deposit_create_error: "创建订单时发生错误。请稍后再试。"
|
||||
command_deposit_click_here: "点击这里"
|
||||
command_deposit_order_created: "订单已创建,编号 "
|
||||
command_deposit_to_payment_page: "到付款页面。"
|
||||
command_deposit_after_paid: "付款后,点击这里确认付款。"
|
||||
command_withdraw_no_wallet: "您需要指定一个提取钱包账户。"
|
||||
command_withdraw_making: "正在进行交易,请稍等..."
|
||||
command_withdraw_no_money: "您的游戏内账户没有足够的钱。"
|
||||
command_withdraw_done: "完成!交易编号 "
|
||||
command_withdraw_error: "进行交易时发生错误。请确保您的钱包存在,然后稍后再试。"
|
||||
command_withdraw_refund_reason: "提取到 Source Point 失败 - 退款"
|
Reference in New Issue
Block a user