Compare commits
17 Commits
7b66cdedcd
...
master
Author | SHA1 | Date | |
---|---|---|---|
80d58ea4e2
|
|||
770a58fb1d
|
|||
1862ce4f65
|
|||
1c304a5108
|
|||
a902ecef8d
|
|||
3803e27a06
|
|||
339944d13a
|
|||
c2e7f613c7
|
|||
bc23269778
|
|||
8ef05de8ad
|
|||
6303d44ab4
|
|||
e77841fc09
|
|||
affe9a40f5 | |||
100d8bc747 | |||
5bdf6e07a7 | |||
582c0c45b2 | |||
d89cd6f9ce |
@@ -1,11 +1,11 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "2.1.20-Beta2"
|
kotlin("jvm") version "2.1.20-Beta2"
|
||||||
kotlin("plugin.serialization") version "2.1.10"
|
kotlin("plugin.serialization") version "2.1.10"
|
||||||
id("com.github.johnrengelman.shadow") version "8.1.1"
|
id("com.github.johnrengelman.shadow") version "8.1.1" // add shadow plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "dev.solsynth"
|
group = "dev.solsynth"
|
||||||
version = "1.0-SNAPSHOT"
|
version = "1.0"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -23,10 +23,17 @@ repositories {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT")
|
compileOnly("org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT")
|
||||||
compileOnly("com.github.MilkBowl:VaultAPI:1.7")
|
compileOnly("com.github.MilkBowl:VaultAPI:1.7")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
|
implementation("com.github.Zrips:CMI-API:9.7.14.3")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
|
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
// These will be packaged into the shadow JAR
|
||||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
|
||||||
|
// Test dependencies
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetJavaVersion = 21
|
val targetJavaVersion = 21
|
||||||
@@ -34,15 +41,26 @@ kotlin {
|
|||||||
jvmToolchain(targetJavaVersion)
|
jvmToolchain(targetJavaVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.build {
|
// Configure the shadowJar task
|
||||||
dependsOn("shadowJar")
|
tasks {
|
||||||
}
|
shadowJar {
|
||||||
|
archiveClassifier.set("") // so that the shadow JAR replaces the “normal” JAR
|
||||||
|
mergeServiceFiles()
|
||||||
|
// Optionally relocate packages to avoid conflicts with other plugins
|
||||||
|
// e.g. relocate("kotlin", "dev.solsynth.shadow.kotlin")
|
||||||
|
}
|
||||||
|
|
||||||
tasks.processResources {
|
// Make “build” produce the shadow jar
|
||||||
val props = mapOf("version" to version)
|
build {
|
||||||
inputs.properties(props)
|
dependsOn(shadowJar)
|
||||||
filteringCharset = "UTF-8"
|
}
|
||||||
filesMatching("plugin.yml") {
|
|
||||||
expand(props)
|
processResources {
|
||||||
|
val props = mapOf("version" to version)
|
||||||
|
inputs.properties(props)
|
||||||
|
filteringCharset = "UTF-8"
|
||||||
|
filesMatching("plugin.yml") {
|
||||||
|
expand(props)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +1,104 @@
|
|||||||
package dev.solsynth.snConnect
|
package dev.solsynth.snConnect
|
||||||
|
|
||||||
|
import dev.solsynth.snConnect.commands.BedrockRemoverCommand
|
||||||
|
import dev.solsynth.snConnect.commands.BedrockRemoverCompleter
|
||||||
import dev.solsynth.snConnect.commands.SnCommand
|
import dev.solsynth.snConnect.commands.SnCommand
|
||||||
import dev.solsynth.snConnect.commands.SnCommandCompleter
|
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
|
||||||
import dev.solsynth.snConnect.services.SnService
|
import dev.solsynth.snConnect.services.SnService
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import net.md_5.bungee.api.ChatColor
|
||||||
|
import net.md_5.bungee.api.chat.ClickEvent
|
||||||
|
import net.md_5.bungee.api.chat.ComponentBuilder
|
||||||
|
import net.md_5.bungee.api.chat.HoverEvent
|
||||||
|
import net.md_5.bungee.api.chat.TextComponent
|
||||||
import net.milkbowl.vault.economy.Economy
|
import net.milkbowl.vault.economy.Economy
|
||||||
import org.bukkit.Bukkit
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.Bukkit.getOnlinePlayers
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration
|
||||||
import org.bukkit.plugin.java.JavaPlugin
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
|
||||||
|
|
||||||
class SolarNetworkConnect : JavaPlugin() {
|
class SolarNetworkConnect : JavaPlugin() {
|
||||||
private var economy: Economy? = null
|
private var economy: Economy? = null
|
||||||
private var sn: SnService? = 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) {
|
||||||
|
if (config.getBoolean("debug"))
|
||||||
|
logger.info("Received WebSocket packet: type=${packet.type}")
|
||||||
|
if (packet.type.startsWith("messages") && packet.data != null) {
|
||||||
|
try {
|
||||||
|
when (packet.type) {
|
||||||
|
"messages.new" -> {
|
||||||
|
val message = SnChatMessage.fromJson(packet.data)
|
||||||
|
// Ignore automated accounts
|
||||||
|
if (message.sender.account.automatedId.isNullOrBlank().not()) return;
|
||||||
|
// Only some rooms got synced
|
||||||
|
if (syncChatRooms.isEmpty() || syncChatRooms.contains(message.chatRoomId)) {
|
||||||
|
val roomName = message.chatRoom.name ?: "DM"
|
||||||
|
val senderName = message.sender.account.nick
|
||||||
|
val profileUrl = "https://solian.app/@${message.sender.account.name}"
|
||||||
|
val componentBuilder = ComponentBuilder()
|
||||||
|
.append("☀").color(ChatColor.YELLOW)
|
||||||
|
.event(HoverEvent(HoverEvent.Action.SHOW_TEXT, arrayOf(TextComponent("Solar Network"))))
|
||||||
|
.append(" ")
|
||||||
|
.append(roomName).color(ChatColor.GOLD)
|
||||||
|
.append(" ").color(ChatColor.YELLOW)
|
||||||
|
.append(senderName).color(ChatColor.YELLOW)
|
||||||
|
.event(ClickEvent(ClickEvent.Action.OPEN_URL, profileUrl))
|
||||||
|
.append(" » ").color(ChatColor.GRAY)
|
||||||
|
if (message.content != null && message.content.isNotBlank()) {
|
||||||
|
componentBuilder.append(message.content).color(ChatColor.WHITE)
|
||||||
|
}
|
||||||
|
if (message.attachments.isNotEmpty()) {
|
||||||
|
for (attachment in message.attachments) {
|
||||||
|
val fileName = attachment.name.takeIf { it.isNotBlank() } ?: "attachment"
|
||||||
|
val url = "https://solian.app/files/${attachment.id}"
|
||||||
|
componentBuilder.append(" <$fileName>").color(ChatColor.BLUE)
|
||||||
|
.event(ClickEvent(ClickEvent.Action.OPEN_URL, url))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val component = componentBuilder.create()
|
||||||
|
for (player in getOnlinePlayers()) {
|
||||||
|
player.spigot().sendMessage(*component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warning("Failed to parse chat message: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onEnable() {
|
override fun onEnable() {
|
||||||
logger.info(String.format("Enabling Version %s", description.version));
|
logger.info(String.format("Enabling Version %s", description.version));
|
||||||
|
|
||||||
this.saveDefaultConfig()
|
this.saveDefaultConfig()
|
||||||
|
|
||||||
|
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()) {
|
if (!setupNetwork()) {
|
||||||
logger.severe("Failed to setup Solar Network Network, check your configuration.")
|
logger.severe("Failed to setup Solar Network Network, check your configuration.")
|
||||||
}
|
}
|
||||||
@@ -26,26 +108,78 @@ class SolarNetworkConnect : JavaPlugin() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Bukkit.getPluginCommand("solar")!!.setExecutor(SnCommand(this.sn!!))
|
if (messageService != null && destinationChatId != null) {
|
||||||
|
server.pluginManager.registerEvents(SnChatListener(messageService!!, destinationChatId!!, messages), this)
|
||||||
|
}
|
||||||
|
|
||||||
|
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("solar")!!.tabCompleter = SnCommandCompleter()
|
||||||
|
|
||||||
|
Bukkit.getPluginCommand("eatrock")!!.setExecutor(BedrockRemoverCommand(this.economy))
|
||||||
|
Bukkit.getPluginCommand("eatrock")!!.tabCompleter = BedrockRemoverCompleter()
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
String.format(
|
String.format(
|
||||||
"Successfully loaded Solar Network Connect connected with %s",
|
"Successfully loaded Solar Network Connect connected with %s",
|
||||||
config.getString("sn.endpoint")
|
config.getString("sn.endpoint")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Send server start message
|
||||||
|
try {
|
||||||
|
destinationChatId?.let { chatId ->
|
||||||
|
messageService?.sendMessage(chatId, messages["server_start"] ?: "🚀 Server started successfully")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warning("Failed to send server start message: ${e.message}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisable() {
|
override fun onDisable() {
|
||||||
logger.info(String.format("Disabled Version %s", description.version));
|
logger.info(String.format("Disabled Version %s", description.version));
|
||||||
|
|
||||||
|
// Send server stop message
|
||||||
|
try {
|
||||||
|
destinationChatId?.let { chatId ->
|
||||||
|
messageService?.sendMessage(chatId, messages["server_stop"] ?: "⏹️ Server stopped")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warning("Failed to send server stop message: ${e.message}")
|
||||||
|
}
|
||||||
|
|
||||||
|
sn?.disconnect()
|
||||||
|
webSocketJob?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupNetwork(): Boolean {
|
private fun setupNetwork(): Boolean {
|
||||||
val baseUrl = config.getString("sn.endpoint") ?: return false;
|
val baseUrl = config.getString("sn.endpoint") ?: return false;
|
||||||
val clientId = config.getString("sn.client_id") ?: return false;
|
val clientId = config.getString("sn.client_id") ?: return false;
|
||||||
val clientSecret = config.getString("sn.client_secret") ?: return false;
|
val clientSecret = config.getString("sn.client_secret") ?: return false;
|
||||||
sn = SnService(baseUrl, clientId, clientSecret);
|
val botApiKey = config.getString("sn.bot_secret");
|
||||||
|
val destination = config.getString("chat.outgoing_room") ?: return false;
|
||||||
|
val syncRooms = config.getStringList("chat.sync_rooms")
|
||||||
|
syncChatRooms = syncRooms
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,220 @@
|
|||||||
|
package dev.solsynth.snConnect.commands
|
||||||
|
|
||||||
|
import net.milkbowl.vault.economy.Economy
|
||||||
|
import net.milkbowl.vault.economy.EconomyResponse
|
||||||
|
import org.bukkit.ChatColor
|
||||||
|
import org.bukkit.Material
|
||||||
|
import org.bukkit.Sound
|
||||||
|
import org.bukkit.command.Command
|
||||||
|
import org.bukkit.command.CommandExecutor
|
||||||
|
import org.bukkit.command.CommandSender
|
||||||
|
import org.bukkit.command.TabCompleter
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.inventory.ItemStack
|
||||||
|
|
||||||
|
class BedrockRemoverCommand(private val economy: Economy?) : CommandExecutor {
|
||||||
|
private val paymentMethods = listOf("money", "materials")
|
||||||
|
private val requiredMaterials = listOf(
|
||||||
|
Material.PISTON,
|
||||||
|
Material.TNT,
|
||||||
|
Material.LEVER,
|
||||||
|
Material.OAK_TRAPDOOR // Representative for trapdoor group
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
|
||||||
|
if (sender !is Player) {
|
||||||
|
sender.sendMessage(ChatColor.RED.toString() + "This command can only be used by players.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.isEmpty() || args.size > 1) {
|
||||||
|
sender.sendMessage(ChatColor.RED.toString() + "Usage: /bedrockremove <money/materials>")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val paymentMethod = args[0].lowercase()
|
||||||
|
if (paymentMethod !in paymentMethods) {
|
||||||
|
sender.sendMessage(ChatColor.RED.toString() + "Invalid payment method. Use: money or materials.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the block the player is looking at
|
||||||
|
val targetBlock = sender.getTargetBlock(null, 10)
|
||||||
|
if (targetBlock == null || targetBlock.type != Material.BEDROCK) {
|
||||||
|
sender.sendMessage(ChatColor.RED.toString() + "You must be looking at a bedrock block within 10 blocks.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt payment
|
||||||
|
val success = when (paymentMethod) {
|
||||||
|
"money" -> payWithMoney(sender, 100.0)
|
||||||
|
"materials" -> payWithAllMaterials(sender)
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
// Error messages are handled in the pay methods
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the bedrock
|
||||||
|
targetBlock.type = Material.AIR
|
||||||
|
sender.sendMessage(ChatColor.GREEN.toString() + "Bedrock removed successfully!")
|
||||||
|
sender.playSound(sender.location, Sound.BLOCK_STONE_BREAK, 1.0f, 1.0f)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun payWithMoney(player: Player, amount: Double): Boolean {
|
||||||
|
if (economy == null) {
|
||||||
|
player.sendMessage(ChatColor.RED.toString() + "Economy not available.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val response = economy.withdrawPlayer(player, "Bedrock removal", amount)
|
||||||
|
if (response.type != EconomyResponse.ResponseType.SUCCESS) {
|
||||||
|
player.sendMessage(ChatColor.RED.toString() + "Insufficient funds. You need ${amount}.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
player.sendMessage(ChatColor.GREEN.toString() + "Withdrew $amount from your account.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun payWithMaterial(player: Player, material: Material): Boolean {
|
||||||
|
val inventory = player.inventory
|
||||||
|
|
||||||
|
// Special handling for materials with multiple variants
|
||||||
|
val hasItem = when (material) {
|
||||||
|
Material.OAK_TRAPDOOR -> {
|
||||||
|
// Check for any trapdoor type
|
||||||
|
inventory.contents.any { item -> item != null && item.type.name.endsWith("_TRAPDOOR") }
|
||||||
|
}
|
||||||
|
Material.PISTON -> {
|
||||||
|
// Check for any piston type
|
||||||
|
inventory.contents.any { item -> item != null && (item.type == Material.PISTON || item.type == Material.STICKY_PISTON) }
|
||||||
|
}
|
||||||
|
else -> inventory.contains(material, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasItem) {
|
||||||
|
player.sendMessage(ChatColor.RED.toString() + "You don't have a ${material.name.lowercase()} in your inventory.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the item
|
||||||
|
when (material) {
|
||||||
|
Material.OAK_TRAPDOOR -> {
|
||||||
|
// Find and remove any trapdoor
|
||||||
|
for (item in inventory.contents) {
|
||||||
|
if (item != null && item.type.name.endsWith("_TRAPDOOR")) {
|
||||||
|
val amount = item.amount
|
||||||
|
if (amount > 1) {
|
||||||
|
item.amount = amount - 1
|
||||||
|
} else {
|
||||||
|
inventory.remove(item)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Material.PISTON -> {
|
||||||
|
// Find and remove any piston
|
||||||
|
for (item in inventory.contents) {
|
||||||
|
if (item != null && (item.type == Material.PISTON || item.type == Material.STICKY_PISTON)) {
|
||||||
|
val amount = item.amount
|
||||||
|
if (amount > 1) {
|
||||||
|
item.amount = amount - 1
|
||||||
|
} else {
|
||||||
|
inventory.remove(item)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
inventory.removeItem(ItemStack(material, 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
player.sendMessage(ChatColor.GREEN.toString() + "Consumed 1 ${material.name.lowercase()}.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun payWithAllMaterials(player: Player): Boolean {
|
||||||
|
val inventory = player.inventory
|
||||||
|
|
||||||
|
// Check if player has all required materials
|
||||||
|
for (material in requiredMaterials) {
|
||||||
|
val hasItem = when (material) {
|
||||||
|
Material.OAK_TRAPDOOR -> {
|
||||||
|
// Check for any trapdoor type
|
||||||
|
inventory.contents.any { item -> item != null && item.type.name.endsWith("_TRAPDOOR") }
|
||||||
|
}
|
||||||
|
Material.PISTON -> {
|
||||||
|
// Check for any piston type
|
||||||
|
inventory.contents.any { item -> item != null && (item.type == Material.PISTON || item.type == Material.STICKY_PISTON) }
|
||||||
|
}
|
||||||
|
else -> inventory.contains(material, 1)
|
||||||
|
}
|
||||||
|
if (!hasItem) {
|
||||||
|
player.sendMessage(ChatColor.RED.toString() + "You don't have all required materials: piston, tnt, lever, and trapdoor.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove one of each material
|
||||||
|
for (material in requiredMaterials) {
|
||||||
|
when (material) {
|
||||||
|
Material.OAK_TRAPDOOR -> {
|
||||||
|
// Find and remove any trapdoor
|
||||||
|
for (item in inventory.contents) {
|
||||||
|
if (item != null && item.type.name.endsWith("_TRAPDOOR")) {
|
||||||
|
val amount = item.amount
|
||||||
|
if (amount > 1) {
|
||||||
|
item.amount = amount - 1
|
||||||
|
} else {
|
||||||
|
inventory.remove(item)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Material.PISTON -> {
|
||||||
|
// Find and remove any piston
|
||||||
|
for (item in inventory.contents) {
|
||||||
|
if (item != null && (item.type == Material.PISTON || item.type == Material.STICKY_PISTON)) {
|
||||||
|
val amount = item.amount
|
||||||
|
if (amount > 1) {
|
||||||
|
item.amount = amount - 1
|
||||||
|
} else {
|
||||||
|
inventory.remove(item)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
inventory.removeItem(ItemStack(material, 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
player.sendMessage(ChatColor.GREEN.toString() + "Consumed materials: piston, tnt, lever, trapdoor.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BedrockRemoverCompleter : TabCompleter {
|
||||||
|
private val paymentMethods = listOf("money", "materials")
|
||||||
|
|
||||||
|
override fun onTabComplete(
|
||||||
|
sender: CommandSender,
|
||||||
|
command: Command,
|
||||||
|
label: String,
|
||||||
|
args: Array<out String>
|
||||||
|
): MutableList<String> {
|
||||||
|
return when (args.size) {
|
||||||
|
1 -> paymentMethods.filter { it.startsWith(args[0], ignoreCase = true) }.toMutableList()
|
||||||
|
else -> mutableListOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,17 +1,26 @@
|
|||||||
package dev.solsynth.snConnect.commands
|
package dev.solsynth.snConnect.commands
|
||||||
|
|
||||||
import dev.solsynth.snConnect.services.SnOrderService
|
import dev.solsynth.snConnect.services.*
|
||||||
import dev.solsynth.snConnect.services.SnService
|
|
||||||
import net.md_5.bungee.api.chat.ClickEvent
|
import net.md_5.bungee.api.chat.ClickEvent
|
||||||
import net.md_5.bungee.api.chat.TextComponent
|
import net.md_5.bungee.api.chat.TextComponent
|
||||||
|
import net.milkbowl.vault.economy.Economy
|
||||||
|
import net.milkbowl.vault.economy.EconomyResponse
|
||||||
|
import org.bukkit.Bukkit.getLogger
|
||||||
import org.bukkit.ChatColor
|
import org.bukkit.ChatColor
|
||||||
|
import org.bukkit.Sound
|
||||||
import org.bukkit.command.Command
|
import org.bukkit.command.Command
|
||||||
import org.bukkit.command.CommandExecutor
|
import org.bukkit.command.CommandExecutor
|
||||||
import org.bukkit.command.CommandSender
|
import org.bukkit.command.CommandSender
|
||||||
import org.bukkit.command.TabCompleter
|
import org.bukkit.command.TabCompleter
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
|
|
||||||
class SnCommand(private val sn: SnService) : 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 {
|
override fun onCommand(p0: CommandSender, p1: Command, p2: String, p3: Array<out String>): Boolean {
|
||||||
if (p0 !is Player) {
|
if (p0 !is Player) {
|
||||||
return false;
|
return false;
|
||||||
@@ -22,26 +31,185 @@ class SnCommand(private val sn: SnService) : CommandExecutor {
|
|||||||
when (p3[0].lowercase()) {
|
when (p3[0].lowercase()) {
|
||||||
"deposit" -> {
|
"deposit" -> {
|
||||||
if (p3.size < 2) {
|
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 orderSrv = SnOrderService(sn);
|
||||||
|
|
||||||
|
if (p3[1].equals("confirm", ignoreCase = true) && p3.size >= 3) {
|
||||||
|
// Confirming order
|
||||||
|
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);
|
||||||
|
// The update validated the order is created by us
|
||||||
|
orderSrv.updateOrderStatus(orderNumber, false);
|
||||||
|
} catch (_: Exception) {
|
||||||
|
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() + (messages["command_deposit_order_not_paid"]
|
||||||
|
?: "Order was not paid yet.")
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
val bal = order.amount;
|
||||||
|
eco?.depositPlayer(p0.player, bal)
|
||||||
|
|
||||||
|
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() + (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)
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creating new order
|
||||||
|
val amount = p3[1].toDoubleOrNull();
|
||||||
|
if (amount == null) {
|
||||||
|
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() + (messages["command_deposit_creating"]
|
||||||
|
?: "Creating order, please stand by...")
|
||||||
|
);
|
||||||
|
|
||||||
|
val order: SnOrder
|
||||||
|
try {
|
||||||
|
order = orderSrv.createOrder("Deposit to GoatCraft", amount);
|
||||||
|
} catch (e: Exception) {
|
||||||
|
p0.sendMessage(
|
||||||
|
ChatColor.RED.toString() + (messages["command_deposit_create_error"]
|
||||||
|
?: "An error occurred while creating order. Try again later.")
|
||||||
|
)
|
||||||
|
e.printStackTrace();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
val linkComponent =
|
||||||
|
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://solian.app/orders/${order.id}");
|
||||||
|
val orderHintComponent =
|
||||||
|
TextComponent(
|
||||||
|
ChatColor.GRAY.toString() + (messages["command_deposit_order_created"]
|
||||||
|
?: "Order created, number ") + ChatColor.WHITE + ChatColor.BOLD + "#${order.id.take(6)}" + " "
|
||||||
|
)
|
||||||
|
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 + (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);
|
||||||
|
}
|
||||||
|
|
||||||
|
"withdraw" -> {
|
||||||
|
if (p3.size < 2) {
|
||||||
|
p0.sendMessage(
|
||||||
|
ChatColor.RED.toString() + (messages["command_deposit_no_amount"]
|
||||||
|
?: "You need to specify an amount to deposit.")
|
||||||
|
)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
val amount = p3[1].toDoubleOrNull();
|
val amount = p3[1].toDoubleOrNull();
|
||||||
if (amount == null) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
val orderSrv = SnOrderService(sn);
|
val playerUuid = p0.uniqueId.toString()
|
||||||
val order = orderSrv.createOrder("Deposit to Highland MC", amount / 100);
|
val accountId = playerSolarpassMap[playerUuid]
|
||||||
|
if (accountId == null) {
|
||||||
|
p0.sendMessage(
|
||||||
|
ChatColor.RED.toString() + "Bind your Solarpass first!"
|
||||||
|
)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
val linkComponent = TextComponent(ChatColor.GOLD.toString() + "Click here to payment page")
|
p0.sendMessage(
|
||||||
linkComponent.clickEvent =
|
ChatColor.GRAY.toString() + (messages["command_withdraw_making"]
|
||||||
ClickEvent(ClickEvent.Action.OPEN_URL, "https://solsynth.dev/orders/${order.id}");
|
?: "Making transaction, please stand by...")
|
||||||
p0.spigot().sendMessage(linkComponent);
|
);
|
||||||
|
|
||||||
|
// Takes extra 20% as fee
|
||||||
|
val fee = amount * 0.2;
|
||||||
|
val resp = eco?.withdrawPlayer(p0.player, amount + fee);
|
||||||
|
if (resp?.type != EconomyResponse.ResponseType.SUCCESS) {
|
||||||
|
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 = SnBalanceService(sn);
|
||||||
|
transactionSrv.addBalance(amount, "Withdraw from GoatCraft", accountId);
|
||||||
|
val transactionHintComponent =
|
||||||
|
TextComponent(
|
||||||
|
ChatColor.GREEN.toString() + (messages["command_withdraw_done"]
|
||||||
|
?: "Done! ")
|
||||||
|
)
|
||||||
|
|
||||||
|
p0.playSound(p0.player!!, Sound.BLOCK_ANVIL_PLACE, 1.0F, 1.0F)
|
||||||
|
p0.spigot().sendMessage(transactionHintComponent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
eco.depositPlayer(p0.player, amount + fee)
|
||||||
|
p0.sendMessage(
|
||||||
|
ChatColor.RED.toString() + (messages["command_withdraw_error"]
|
||||||
|
?: "An error occurred while making transaction. Make sure your wallet exists then try again later.")
|
||||||
|
)
|
||||||
|
e.printStackTrace()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> return false;
|
else -> {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@@ -0,0 +1,51 @@
|
|||||||
|
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
|
||||||
|
import org.bukkit.event.EventHandler
|
||||||
|
import org.bukkit.event.EventPriority
|
||||||
|
import org.bukkit.event.Listener
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent
|
||||||
|
import org.bukkit.event.player.AsyncPlayerChatEvent
|
||||||
|
import org.bukkit.event.player.PlayerAdvancementDoneEvent
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent
|
||||||
|
|
||||||
|
class SnChatListener(
|
||||||
|
private val messageService: SnMessageService,
|
||||||
|
private val destinationChatId: String,
|
||||||
|
private val messages: Map<String, String>
|
||||||
|
) : Listener {
|
||||||
|
@EventHandler(priority = EventPriority.MONITOR)
|
||||||
|
fun onPlayerChat(event: AsyncPlayerChatEvent) {
|
||||||
|
val message = "${event.player.name}: ${event.message}"
|
||||||
|
messageService.sendMessage(destinationChatId, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("SENSELESS_COMPARISON")
|
||||||
|
@EventHandler()
|
||||||
|
fun onPlayerJoin(event: PlayerJoinEvent) {
|
||||||
|
val firstTime = Bukkit.getOfflinePlayer(event.player.uniqueId) == null;
|
||||||
|
val templateKey = if (!firstTime) "join" else "join_first";
|
||||||
|
val template = messages[templateKey]
|
||||||
|
?: if (!firstTime) "➡️ {player} joined the game." else "➡️ {player} first time joined the game."
|
||||||
|
val message = template.replace("{player}", event.player.name)
|
||||||
|
messageService.sendMessage(destinationChatId, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler()
|
||||||
|
fun onPlayerQuit(event: PlayerQuitEvent) {
|
||||||
|
val template = messages["quit"] ?: "⬅️ {player} left the game."
|
||||||
|
val message = template.replace("{player}", event.player.name)
|
||||||
|
messageService.sendMessage(destinationChatId, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler()
|
||||||
|
fun onPlayerDeath(event: PlayerDeathEvent) {
|
||||||
|
val template = messages["death"] ?: "💀 {player} {message}"
|
||||||
|
val message = template.replace("{player}", event.entity.name).replace("{message}", event.deathMessage ?: "")
|
||||||
|
messageService.sendMessage(destinationChatId, message)
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
src/main/kotlin/dev/solsynth/snConnect/models/SnAccount.kt
Normal file
37
src/main/kotlin/dev/solsynth/snConnect/models/SnAccount.kt
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import dev.solsynth.snConnect.models.SnAccountProfile
|
||||||
|
import dev.solsynth.snConnect.models.SnContactMethod
|
||||||
|
import dev.solsynth.snConnect.models.SnWalletSubscriptionRef
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
@Serializable
|
||||||
|
data class SnAccount(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val nick: String,
|
||||||
|
val language: String,
|
||||||
|
val region: String = "",
|
||||||
|
@SerialName("is_superuser") val isSuperuser: Boolean,
|
||||||
|
@SerialName("automated_id") val automatedId: String?,
|
||||||
|
val profile: SnAccountProfile,
|
||||||
|
@SerialName("perk_subscription") val perkSubscription: SnWalletSubscriptionRef?,
|
||||||
|
val contacts: List<SnContactMethod> = emptyList(),
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("updated_at") val updatedAt: String,
|
||||||
|
@SerialName("deleted_at") val deletedAt: String?,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromJson(jsonElement: JsonElement): SnAccount {
|
||||||
|
return json.decodeFromJsonElement<SnAccount>(jsonElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
@Serializable
|
||||||
|
data class SnAccountProfile(
|
||||||
|
val id: String,
|
||||||
|
@SerialName("first_name") val firstName: String = "",
|
||||||
|
@SerialName("middle_name") val middleName: String = "",
|
||||||
|
@SerialName("last_name") val lastName: String = "",
|
||||||
|
val bio: String = "",
|
||||||
|
val gender: String = "",
|
||||||
|
val pronouns: String = "",
|
||||||
|
val location: String = "",
|
||||||
|
@SerialName("time_zone") val timeZone: String = "",
|
||||||
|
val birthday: String?,
|
||||||
|
@SerialName("last_seen_at") val lastSeenAt: String?,
|
||||||
|
val experience: Int,
|
||||||
|
val level: Int,
|
||||||
|
@SerialName("social_credits") val socialCredits: Double = 100.0,
|
||||||
|
@SerialName("social_credits_level") val socialCreditsLevel: Int = 0,
|
||||||
|
@SerialName("leveling_progress") val levelingProgress: Double,
|
||||||
|
val picture: SnCloudFile?,
|
||||||
|
val background: SnCloudFile?,
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("updated_at") val updatedAt: String,
|
||||||
|
@SerialName("deleted_at") val deletedAt: String?,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromJson(jsonElement: JsonElement): SnAccountProfile {
|
||||||
|
return json.decodeFromJsonElement<SnAccountProfile>(jsonElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
@Serializable
|
||||||
|
data class SnAccountStatus(
|
||||||
|
val id: String,
|
||||||
|
val attitude: Int,
|
||||||
|
@SerialName("is_online") val isOnline: Boolean,
|
||||||
|
@SerialName("is_invisible") val isInvisible: Boolean,
|
||||||
|
@SerialName("is_not_disturb") val isNotDisturb: Boolean,
|
||||||
|
@SerialName("is_customized") val isCustomized: Boolean,
|
||||||
|
val label: String = "",
|
||||||
|
@SerialName("cleared_at") val clearedAt: String?,
|
||||||
|
@SerialName("account_id") val accountId: String,
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("updated_at") val updatedAt: String,
|
||||||
|
@SerialName("deleted_at") val deletedAt: String?,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromJson(jsonElement: JsonElement): SnAccountStatus {
|
||||||
|
return json.decodeFromJsonElement<SnAccountStatus>(jsonElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import SnAccount
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
@Serializable
|
||||||
|
data class SnChatMember(
|
||||||
|
val id: String,
|
||||||
|
@SerialName("chat_room_id") val chatRoomId: String,
|
||||||
|
// val chatRoom: SnChatRoom?, // Placeholder
|
||||||
|
@SerialName("account_id") val accountId: String,
|
||||||
|
val account: SnAccount, // Placeholder
|
||||||
|
val nick: String?,
|
||||||
|
val role: Int,
|
||||||
|
val notify: Int,
|
||||||
|
@SerialName("joined_at") val joinedAt: String?,
|
||||||
|
@SerialName("break_until") val breakUntil: String? = null,
|
||||||
|
@SerialName("timeout_until") val timeoutUntil: String? = null,
|
||||||
|
@SerialName("is_bot") val isBot: Boolean,
|
||||||
|
// val status: SnAccountStatus?, // Placeholder
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("updated_at") val updatedAt: String,
|
||||||
|
@SerialName("deleted_at") val deletedAt: String?,
|
||||||
|
)
|
@@ -0,0 +1,40 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
@Serializable
|
||||||
|
data class SnChatMessage(
|
||||||
|
val id: String,
|
||||||
|
val type: String = "text",
|
||||||
|
val content: String?,
|
||||||
|
val nonce: String?,
|
||||||
|
@SerialName("members_mentioned") val membersMentioned: List<String>? = emptyList(),
|
||||||
|
@SerialName("edited_at") val editedAt: String?,
|
||||||
|
val attachments: List<SnCloudFile> = emptyList(),
|
||||||
|
// val reactions: List<SnChatReaction> = emptyList(), // Placeholder
|
||||||
|
@SerialName("replied_message_id") val repliedMessageId: String?,
|
||||||
|
@SerialName("forwarded_message_id") val forwardedMessageId: String?,
|
||||||
|
@SerialName("sender_id") val senderId: String,
|
||||||
|
val sender: SnChatMember,
|
||||||
|
@SerialName("chat_room") val chatRoom: SnChatRoom,
|
||||||
|
@SerialName("chat_room_id") val chatRoomId: String,
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("updated_at") val updatedAt: String,
|
||||||
|
@SerialName("deleted_at") val deletedAt: String?,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromJson(jsonElement: JsonElement): SnChatMessage {
|
||||||
|
return json.decodeFromJsonElement<SnChatMessage>(jsonElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,21 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
@Serializable
|
||||||
|
data class SnChatReaction(
|
||||||
|
val id: String,
|
||||||
|
@SerialName("message_id") val messageId: String,
|
||||||
|
@SerialName("sender_id") val senderId: String,
|
||||||
|
// val sender: SnChatMember, // Placeholder
|
||||||
|
val symbol: String,
|
||||||
|
val attitude: Int,
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("updated_at") val updatedAt: String,
|
||||||
|
@SerialName("deleted_at") val deletedAt: String?,
|
||||||
|
)
|
26
src/main/kotlin/dev/solsynth/snConnect/models/SnChatRoom.kt
Normal file
26
src/main/kotlin/dev/solsynth/snConnect/models/SnChatRoom.kt
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
@Serializable
|
||||||
|
data class SnChatRoom(
|
||||||
|
val id: String,
|
||||||
|
val name: String?,
|
||||||
|
val description: String?,
|
||||||
|
val type: Int,
|
||||||
|
@SerialName("is_public") val isPublic: Boolean = false,
|
||||||
|
@SerialName("is_community") val isCommunity: Boolean = false,
|
||||||
|
// val picture: SnCloudFile?, // Placeholder
|
||||||
|
// val background: SnCloudFile?, // Placeholder
|
||||||
|
val realmId: String? = null,
|
||||||
|
// val realm: SnRealm?, // Placeholder
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("updated_at") val updatedAt: String,
|
||||||
|
@SerialName("deleted_at") val deletedAt: String?,
|
||||||
|
// val members: List<SnChatMember>?, // Placeholder
|
||||||
|
)
|
22
src/main/kotlin/dev/solsynth/snConnect/models/SnCloudFile.kt
Normal file
22
src/main/kotlin/dev/solsynth/snConnect/models/SnCloudFile.kt
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SnCloudFile(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val size: Long,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromJson(jsonElement: JsonElement): SnCloudFile {
|
||||||
|
return json.decodeFromJsonElement<SnCloudFile>(jsonElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,33 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
@Serializable
|
||||||
|
data class SnContactMethod(
|
||||||
|
val id: String,
|
||||||
|
val type: Int,
|
||||||
|
@SerialName("verified_at") val verifiedAt: String?,
|
||||||
|
@SerialName("is_primary") val isPrimary: Boolean,
|
||||||
|
@SerialName("is_public") val isPublic: Boolean,
|
||||||
|
val content: String,
|
||||||
|
@SerialName("account_id") val accountId: String,
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("updated_at") val updatedAt: String,
|
||||||
|
@SerialName("deleted_at") val deletedAt: String?,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromJson(jsonElement: JsonElement): SnContactMethod {
|
||||||
|
return json.decodeFromJsonElement<SnContactMethod>(jsonElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SnWalletSubscriptionRef(
|
||||||
|
val id: String,
|
||||||
|
val identifier: String,
|
||||||
|
@SerialName("account_id") val accountId: String,
|
||||||
|
@SerialName("created_at") val createdAt: String,
|
||||||
|
@SerialName("updated_at") val updatedAt: String,
|
||||||
|
@SerialName("deleted_at") val deletedAt: String?,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromJson(jsonElement: JsonElement): SnWalletSubscriptionRef {
|
||||||
|
return json.decodeFromJsonElement<SnWalletSubscriptionRef>(jsonElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
package dev.solsynth.snConnect.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
@Serializable
|
||||||
|
data class WebSocketPacket(
|
||||||
|
val type: String,
|
||||||
|
@SerialName("data") val data: JsonElement?,
|
||||||
|
val endpoint: String?,
|
||||||
|
@SerialName("error_message") val errorMessage: String?
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
fun fromJson(jsonString: String): WebSocketPacket? {
|
||||||
|
return try {
|
||||||
|
json.decodeFromString(jsonString)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
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,
|
||||||
|
@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("account_id")
|
||||||
|
val accountID: String,
|
||||||
|
val remark: String,
|
||||||
|
val amount: Double,
|
||||||
|
val currency: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class SnBalanceService(private val sn: SnService) {
|
||||||
|
private val json = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addBalance(amount: Double, remark: String, accountID: String) {
|
||||||
|
val body = SnTransactionRequest(
|
||||||
|
amount = amount,
|
||||||
|
remark = remark,
|
||||||
|
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("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 ->
|
||||||
|
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,39 @@
|
|||||||
|
package dev.solsynth.snConnect.services
|
||||||
|
|
||||||
|
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.*
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SnMessageRequest(
|
||||||
|
val content: String,
|
||||||
|
val nonce: String
|
||||||
|
)
|
||||||
|
|
||||||
|
class SnMessageService(private val sn: SnService) {
|
||||||
|
private val json: Json = Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendMessage(chatRoomId: String, content: String) {
|
||||||
|
val body = SnMessageRequest(content, UUID.randomUUID().toString())
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(sn.getUrl("sphere", "/chat/$chatRoomId/messages"))
|
||||||
|
.post(json.encodeToString(body).toRequestBody("application/json".toMediaTypeOrNull()))
|
||||||
|
.apply {
|
||||||
|
sn.botApiKey?.let { header("Authorization", "Bearer $it") }
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
sn.client.newCall(request).execute().use { response ->
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
val responseBody = response.body?.string() ?: "No body"
|
||||||
|
throw IOException("Unexpected code $response: $responseBody")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -4,24 +4,45 @@ import kotlinx.datetime.Instant
|
|||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class SnOrderRequest(
|
data class SnOrderRequest(
|
||||||
@SerialName("client_id")
|
@SerialName("client_id")
|
||||||
val clientId: String,
|
val clientId: String,
|
||||||
@SerialName("client_secret")
|
@SerialName("client_secret")
|
||||||
val clientSecret: String,
|
val clientSecret: String,
|
||||||
val remark: String,
|
@SerialName("currency")
|
||||||
val amount: Double
|
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 SnOrderUpdateStatus(
|
||||||
|
@SerialName("client_id")
|
||||||
|
val clientId: String,
|
||||||
|
@SerialName("client_secret")
|
||||||
|
val clientSecret: String,
|
||||||
|
val status: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SnOrder(
|
data class SnOrder(
|
||||||
val id: Long,
|
val id: String,
|
||||||
@SerialName("created_at")
|
@SerialName("created_at")
|
||||||
val createdAt: Instant,
|
val createdAt: Instant,
|
||||||
@SerialName("updated_at")
|
@SerialName("updated_at")
|
||||||
@@ -29,16 +50,14 @@ data class SnOrder(
|
|||||||
@SerialName("deleted_at")
|
@SerialName("deleted_at")
|
||||||
val deletedAt: Instant? = null,
|
val deletedAt: Instant? = null,
|
||||||
val status: Long,
|
val status: Long,
|
||||||
val remark: String,
|
val remarks: String,
|
||||||
val amount: String,
|
val amount: Double,
|
||||||
@SerialName("payer_id")
|
@SerialName("payer_id")
|
||||||
val payerID: Int? = null,
|
val payerID: String? = null,
|
||||||
@SerialName("payee_id")
|
@SerialName("payee_id")
|
||||||
val payeeID: Int? = null,
|
val payeeID: String? = null,
|
||||||
@SerialName("transaction_id")
|
@SerialName("transaction_id")
|
||||||
val transactionID: Int? = null,
|
val transactionID: String? = null,
|
||||||
@SerialName("client_id")
|
|
||||||
val clientID: Long
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class SnOrderService(private val sn: SnService) {
|
class SnOrderService(private val sn: SnService) {
|
||||||
@@ -48,13 +67,14 @@ class SnOrderService(private val sn: SnService) {
|
|||||||
|
|
||||||
fun createOrder(remark: String, amount: Double): SnOrder {
|
fun createOrder(remark: String, amount: Double): SnOrder {
|
||||||
val body = SnOrderRequest(
|
val body = SnOrderRequest(
|
||||||
sn.clientId,
|
clientId = sn.clientId,
|
||||||
sn.clientSecret,
|
clientSecret = sn.clientSecret,
|
||||||
remark,
|
currency = "points",
|
||||||
amount,
|
amount = amount,
|
||||||
|
remarks = remark
|
||||||
);
|
);
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(sn.getUrl("wa", "/orders"))
|
.url(sn.getUrl("id", "/orders"))
|
||||||
.post(Json.encodeToString(body).toRequestBody("application/json".toMediaTypeOrNull()))
|
.post(Json.encodeToString(body).toRequestBody("application/json".toMediaTypeOrNull()))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@@ -66,4 +86,40 @@ class SnOrderService(private val sn: SnService) {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getOrder(id: String): SnOrder {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(sn.getUrl("id", "/orders/$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<SnOrder>(responseBody)
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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("id", "/orders/$id/status"))
|
||||||
|
.patch(Json.encodeToString(body).toRequestBody("application/json".toMediaTypeOrNull()))
|
||||||
|
.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<SnOrder>(responseBody)
|
||||||
|
|
||||||
|
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,11 +1,98 @@
|
|||||||
package dev.solsynth.snConnect.services
|
package dev.solsynth.snConnect.services
|
||||||
|
|
||||||
|
import dev.solsynth.snConnect.models.WebSocketPacket
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.channelFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.WebSocket
|
||||||
|
import okhttp3.WebSocketListener
|
||||||
|
import okhttp3.Response
|
||||||
|
import okio.ByteString
|
||||||
|
import java.util.logging.Logger
|
||||||
|
|
||||||
class SnService(private val baseUrl: String, val clientId: String, val clientSecret: String) {
|
class SnService(private val baseUrl: String, val clientId: String, val clientSecret: String, val botApiKey: String?) {
|
||||||
val client = OkHttpClient.Builder().build();
|
val client = OkHttpClient.Builder().build();
|
||||||
|
private val logger = Logger.getLogger(SnService::class.java.name)
|
||||||
|
private var websocket: WebSocket? = null
|
||||||
|
|
||||||
fun getUrl(service: String, segment: String): String {
|
fun getUrl(service: String, segment: String): String {
|
||||||
return "$baseUrl/cgi/$service$segment"
|
return "$baseUrl/$service$segment"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getWsBaseUrl(): String {
|
||||||
|
return baseUrl.replaceFirst("http", "ws")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connectWebSocket(listener: WebSocketListener): WebSocket {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("${getWsBaseUrl()}/ws")
|
||||||
|
.apply {
|
||||||
|
botApiKey?.let { header("Authorization", "Bearer $it") }
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
return client.newWebSocket(request, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connectWebSocketAsFlow(): Flow<WebSocketPacket> = channelFlow {
|
||||||
|
val url = "${getWsBaseUrl()}/ws"
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.apply {
|
||||||
|
botApiKey?.let { header("Authorization", "Bearer $it") }
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun connect() {
|
||||||
|
logger.info("Attempting WebSocket connection to $url")
|
||||||
|
websocket = client.newWebSocket(request, object : WebSocketListener() {
|
||||||
|
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||||
|
logger.info("WebSocket connection opened successfully to $url")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||||
|
WebSocketPacket.fromJson(text)?.let { trySend(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
|
||||||
|
val text = bytes.string(Charsets.UTF_8)
|
||||||
|
WebSocketPacket.fromJson(text)?.let { trySend(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||||
|
logger.severe("WebSocket connection failed: ${t.message}, response: ${response?.code}")
|
||||||
|
websocket = null
|
||||||
|
GlobalScope.launch {
|
||||||
|
delay(1000)
|
||||||
|
connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
||||||
|
logger.info("WebSocket connection closed: code=$code, reason=$reason")
|
||||||
|
websocket = null
|
||||||
|
GlobalScope.launch {
|
||||||
|
delay(1000)
|
||||||
|
connect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
connect()
|
||||||
|
|
||||||
|
awaitClose {
|
||||||
|
websocket?.close(1000, "Shutting down")
|
||||||
|
websocket = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disconnect() {
|
||||||
|
websocket?.close(1000, "Disconnecting")
|
||||||
|
websocket = null
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,12 @@
|
|||||||
|
locale: en
|
||||||
sn:
|
sn:
|
||||||
endpoint: https://api.sn.solsynth.dev
|
endpoint: https://api.solian.app
|
||||||
client_id: highland-mc
|
client_id: goatcraft
|
||||||
client_secret: 12345678
|
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
|
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."
|
||||||
|
join_first: "➡️ {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} 加入了游戏。"
|
||||||
|
join_first: "➡️ {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 失败 - 退款"
|
@@ -4,7 +4,7 @@ main: dev.solsynth.snConnect.SolarNetworkConnect
|
|||||||
api-version: '1.21'
|
api-version: '1.21'
|
||||||
authors: [ LittleSheepOvO ]
|
authors: [ LittleSheepOvO ]
|
||||||
description: Solar Network Connect provide features connect with Solar Network
|
description: Solar Network Connect provide features connect with Solar Network
|
||||||
loadbefore:
|
softdepend:
|
||||||
- CMI
|
- CMI
|
||||||
- CMIEInjector
|
- CMIEInjector
|
||||||
- CMILib
|
- CMILib
|
||||||
@@ -17,7 +17,16 @@ commands:
|
|||||||
aliases: ['sn']
|
aliases: ['sn']
|
||||||
permission: solar-network.command.sn
|
permission: solar-network.command.sn
|
||||||
permission-message: §cYou don't have the permission -> §6[solar-network.command.sn]
|
permission-message: §cYou don't have the permission -> §6[solar-network.command.sn]
|
||||||
|
eatrock:
|
||||||
|
description: Remove bedrock with payment
|
||||||
|
usage: §e/eatrock §r<money/materials>
|
||||||
|
aliases: ['eatrock']
|
||||||
|
permission: solar-network.command.eatrock
|
||||||
|
permission-message: §cYou don't have the permission -> §6[solar-network.command.eatrock]
|
||||||
permissions:
|
permissions:
|
||||||
solar-network.command.sn:
|
solar-network.command.sn:
|
||||||
description: Permission of uses Solar Network Command
|
description: Permission of uses Solar Network Command
|
||||||
default: true
|
default: true
|
||||||
|
solar-network.command.eatrock:
|
||||||
|
description: Permission to remove bedrock with payment
|
||||||
|
default: op
|
||||||
|
30
src/test/kotlin/dev/solsynth/snConnect/WebSocketTest.kt
Normal file
30
src/test/kotlin/dev/solsynth/snConnect/WebSocketTest.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package dev.solsynth.snConnect
|
||||||
|
|
||||||
|
import dev.solsynth.snConnect.services.SnService
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
|
||||||
|
suspend fun main() {
|
||||||
|
val service = SnService(
|
||||||
|
baseUrl = "https://api.solian.app", // Replace with actual test server URL
|
||||||
|
clientId = "goatcraft",
|
||||||
|
clientSecret = "testClientSecret",
|
||||||
|
botApiKey = "02-l7ARXnEimQaBY0Dj3SQ.g4UTdShR9GiNWBDjC7qm9Xu83t0Vq8mQ2WPaTO8S-_j6EuKcWd0Kqb_hEkFlahmhfAd5lcH7j_-N8knSIXjo3X7OSFck0E_ogwluZpGSzqbYOrlBAQc9Rk1VhHVNu_W4fi9NR6NnUwpAgyTIh2RuRHk98oVa2I4Ie-NXPybb26N3i9wXv3-wXlkml0IOrhs3FRKMbcJNmKIzEYP0KQNqs3w9TAx0R2fe9DAAQ8WvPW5iPGSbyxr_fF4Qm7tQ0rQvg89x0cUGmKQHtCTeKa2WZi7UBTbw_b4SiHMqpLMxIDQBEZQGqkJ5r37_YCyCNqe5Huha86GG7bT__m6z5emeow"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("server started")
|
||||||
|
// Collect from the flow for a limited time to avoid hanging on failure
|
||||||
|
val packets = withTimeoutOrNull(10000L) { // 10 seconds timeout
|
||||||
|
val collectedPackets = mutableListOf<String>()
|
||||||
|
service.connectWebSocketAsFlow().collect { packet ->
|
||||||
|
collectedPackets.add("Received packet: type=${packet.type}, endpoint=${packet.endpoint}")
|
||||||
|
print(collectedPackets.last())
|
||||||
|
// In a real test, you might check packet contents or behavior
|
||||||
|
}
|
||||||
|
collectedPackets
|
||||||
|
}
|
||||||
|
|
||||||
|
// For this test, we're mainly checking that the flow can be created and the setup doesn't crash
|
||||||
|
// In real scenarios, you'd need a WebSocket server running at the specified URL
|
||||||
|
print("WebSocket test finished. Packets received: ${packets?.size ?: 0}")
|
||||||
|
assert(packets != null) // Flow started without immediate error
|
||||||
|
}
|
Reference in New Issue
Block a user