add linking chat accounts to uuid

implement global timeouts for all commands
improved permission requests
This commit is contained in:
nikky 2018-07-08 02:02:38 +02:00
parent 254c990d76
commit 0057b3037b
34 changed files with 1012 additions and 275 deletions

View File

@ -1,9 +1,11 @@
package matterlink package matterlink
import matterlink.command.CommandMatterlink import com.mojang.authlib.GameProfile
import matterlink.command.MatterLinkCommand
import matterlink.command.MatterLinkCommandSender import matterlink.command.MatterLinkCommandSender
import matterlink.config.BaseConfig import matterlink.config.BaseConfig
import matterlink.config.cfg import matterlink.config.cfg
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
import net.minecraftforge.common.ForgeVersion import net.minecraftforge.common.ForgeVersion
import net.minecraftforge.fml.common.FMLCommonHandler import net.minecraftforge.fml.common.FMLCommonHandler
@ -14,6 +16,7 @@ import net.minecraftforge.fml.common.event.FMLServerStartingEvent
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent import net.minecraftforge.fml.common.event.FMLServerStoppingEvent
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
import java.util.*
lateinit var logger: Logger lateinit var logger: Logger
@ -47,7 +50,7 @@ object MatterLink : IMatterLink() {
@Mod.EventHandler @Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) { fun serverStarting(event: FMLServerStartingEvent) {
logger.debug("Registering server commands") logger.debug("Registering server commands")
event.registerServerCommand(CommandMatterlink()) event.registerServerCommand(MatterLinkCommand())
start() start()
} }
@ -61,10 +64,55 @@ object MatterLink : IMatterLink() {
FMLCommonHandler.instance().minecraftServerInstance.playerList.sendChatMsg(TextComponentString(msg)) FMLCommonHandler.instance().minecraftServerInstance.playerList.sendChatMsg(TextComponentString(msg))
} }
override fun wrappedSendToPlayer(user: String, msg: String) {
val profile = profileByName(user) ?: profileByUUID(user) ?: run {
error("cannot find player by name or uuid $user")
return
}
val player = playerByProfile(profile) ?: run {
error("${profile.name} is not online")
return
}
player.sendMessage(TextComponentString(msg))
}
override fun isOnline(username: String) = FMLCommonHandler.instance().minecraftServerInstance.onlinePlayerNames.contains(username)
private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? = FMLCommonHandler.instance().minecraftServerInstance.playerList.getPlayerByUUID(gameProfile.id)
private fun profileByUUID(uuid: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getProfileByUUID(UUID.fromString(uuid))
} catch (e: IllegalArgumentException) {
warn("cannot find profile by uuid $uuid")
null
}
private fun profileByName(username: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getGameProfileForUsername(username)
} catch (e: IllegalArgumentException) {
warn("cannot find profile by username $username")
null
}
override fun nameToUUID(username: String) = profileByName(username)?.id?.toString()
override fun uuidToName(uuid: String?): String? {
return uuid?.let { profileByUUID(it)?.name }
}
override fun log(level: String, formatString: String, vararg data: Any) = override fun log(level: String, formatString: String, vararg data: Any) =
logger.log(Level.toLevel(level, Level.INFO), formatString, *data) logger.log(Level.toLevel(level, Level.INFO), formatString, *data)
override fun commandSenderFor(user: String, userId: String, server: String, op: Boolean) = MatterLinkCommandSender(user, userId, server, op)
override fun commandSenderFor(
user: String,
userId: String,
server: String,
uuid: String?,
username: String?,
op: Boolean
) = MatterLinkCommandSender(user, userId, server, uuid, username, op)
override val mcVersion: String = MCVERSION override val mcVersion: String = MCVERSION
override val modVersion: String = MODVERSION override val modVersion: String = MODVERSION

View File

@ -0,0 +1,42 @@
package matterlink.command
import matterlink.logger
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString
class AuthCommand : CommandBase() {
override fun getName(): String {
return CommandCoreAuth.name
}
override fun getUsage(sender: ICommandSender): String {
return CommandCoreAuth.usage
}
override fun getAliases(): List<String> {
return CommandCoreAuth.aliases
}
override fun getRequiredPermissionLevel(): Int {
return 0
}
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}")
}
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreAuth.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply))
}
}
}

View File

@ -3,21 +3,22 @@ package matterlink.command
import net.minecraft.command.CommandBase import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.server.MinecraftServer import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
class CommandMatterlink : CommandBase() { class MatterLinkCommand : CommandBase() {
override fun getName(): String { override fun getName(): String {
return CommandCore.getName() return CommandCoreML.name
} }
override fun getUsage(sender: ICommandSender): String { override fun getUsage(sender: ICommandSender): String {
return CommandCore.getUsage() return CommandCoreML.usage
} }
override fun getAliases(): List<String> { override fun getAliases(): List<String> {
return CommandCore.getAliases() return CommandCoreML.aliases
} }
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) { override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
@ -25,7 +26,8 @@ class CommandMatterlink : CommandBase() {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}") throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}")
} }
val reply = CommandCore.execute(args, sender.name) val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreML.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) { if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply)) sender.sendMessage(TextComponentString(reply))

View File

@ -13,8 +13,13 @@ import net.minecraft.world.World
import net.minecraftforge.fml.common.FMLCommonHandler import net.minecraftforge.fml.common.FMLCommonHandler
import javax.annotation.Nonnull import javax.annotation.Nonnull
class MatterLinkCommandSender(user: String, userId: String, server: String, op: Boolean) : IMinecraftCommandSender(user, userId, server, op), ICommandSender { class MatterLinkCommandSender(
private var level: Int = 0 user: String,
userId: String,
server: String,
uuid: String?,
username: String?,
op: Boolean) : IMinecraftCommandSender(user, userId, server, uuid, username, op), ICommandSender {
override fun execute(cmdString: String): Boolean { override fun execute(cmdString: String): Boolean {
return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand( return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand(
@ -24,7 +29,7 @@ class MatterLinkCommandSender(user: String, userId: String, server: String, op:
} }
override fun getDisplayName(): ITextComponent { override fun getDisplayName(): ITextComponent {
return TextComponentString(user) return TextComponentString(displayName)
} }
override fun getName() = accountName override fun getName() = accountName

View File

@ -1,9 +1,11 @@
package matterlink package matterlink
import matterlink.command.CommandMatterlink import com.mojang.authlib.GameProfile
import matterlink.command.MatterLinkCommand
import matterlink.command.MatterLinkCommandSender import matterlink.command.MatterLinkCommandSender
import matterlink.config.BaseConfig import matterlink.config.BaseConfig
import matterlink.config.cfg import matterlink.config.cfg
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
import net.minecraftforge.common.ForgeVersion import net.minecraftforge.common.ForgeVersion
import net.minecraftforge.fml.common.FMLCommonHandler import net.minecraftforge.fml.common.FMLCommonHandler
@ -14,6 +16,7 @@ import net.minecraftforge.fml.common.event.FMLServerStartingEvent
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent import net.minecraftforge.fml.common.event.FMLServerStoppingEvent
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
import java.util.*
lateinit var logger: Logger lateinit var logger: Logger
@ -47,7 +50,7 @@ object MatterLink : IMatterLink() {
@Mod.EventHandler @Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) { fun serverStarting(event: FMLServerStartingEvent) {
logger.debug("Registering server commands") logger.debug("Registering server commands")
event.registerServerCommand(CommandMatterlink()) event.registerServerCommand(MatterLinkCommand())
start() start()
} }
@ -61,10 +64,54 @@ object MatterLink : IMatterLink() {
FMLCommonHandler.instance().minecraftServerInstance.playerList.sendMessage(TextComponentString(msg)) FMLCommonHandler.instance().minecraftServerInstance.playerList.sendMessage(TextComponentString(msg))
} }
override fun wrappedSendToPlayer(user: String, msg: String) {
val profile = profileByName(user) ?: profileByUUID(user) ?: run {
error("cannot find player by name or uuid $user")
return
}
val player = playerByProfile(profile) ?: run {
error("${profile.name} is not online")
return
}
player.sendMessage(TextComponentString(msg))
}
override fun isOnline(username: String) = FMLCommonHandler.instance().minecraftServerInstance.onlinePlayerNames.contains(username)
private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? = FMLCommonHandler.instance().minecraftServerInstance.playerList.getPlayerByUUID(gameProfile.id)
private fun profileByUUID(uuid: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getProfileByUUID(UUID.fromString(uuid))
} catch (e: IllegalArgumentException) {
warn("cannot find profile by uuid $uuid")
null
}
private fun profileByName(username: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getGameProfileForUsername(username)
} catch (e: IllegalArgumentException) {
warn("cannot find profile by username $username")
null
}
override fun nameToUUID(username: String) = profileByName(username)?.id?.toString()
override fun uuidToName(uuid: String?): String? {
return uuid?.let { profileByUUID(it)?.name }
}
override fun log(level: String, formatString: String, vararg data: Any) = override fun log(level: String, formatString: String, vararg data: Any) =
logger.log(Level.toLevel(level, Level.INFO), formatString, *data) logger.log(Level.toLevel(level, Level.INFO), formatString, *data)
override fun commandSenderFor(user: String, userId: String, server: String, op: Boolean) = MatterLinkCommandSender(user, userId, server, op) override fun commandSenderFor(
user: String,
userId: String,
server: String,
uuid: String?,
username: String?,
op: Boolean
) = MatterLinkCommandSender(user, userId, server, uuid, username, op)
override val mcVersion: String = MCVERSION override val mcVersion: String = MCVERSION
override val modVersion: String = MODVERSION override val modVersion: String = MODVERSION

View File

@ -0,0 +1,42 @@
package matterlink.command
import matterlink.logger
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString
class AuthCommand : CommandBase() {
override fun getName(): String {
return CommandCoreAuth.name
}
override fun getUsage(sender: ICommandSender): String {
return CommandCoreAuth.usage
}
override fun getAliases(): List<String> {
return CommandCoreAuth.aliases
}
override fun getRequiredPermissionLevel(): Int {
return 0
}
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}")
}
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreAuth.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply))
}
}
}

View File

@ -3,21 +3,22 @@ package matterlink.command
import net.minecraft.command.CommandBase import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.server.MinecraftServer import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
class CommandMatterlink : CommandBase() { class MatterLinkCommand : CommandBase() {
override fun getName(): String { override fun getName(): String {
return CommandCore.getName() return CommandCoreML.name
} }
override fun getUsage(sender: ICommandSender): String { override fun getUsage(sender: ICommandSender): String {
return CommandCore.getUsage() return CommandCoreML.usage
} }
override fun getAliases(): List<String> { override fun getAliases(): List<String> {
return CommandCore.getAliases() return CommandCoreML.aliases
} }
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) { override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
@ -25,11 +26,11 @@ class CommandMatterlink : CommandBase() {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}") throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}")
} }
val reply = CommandCore.execute(args, sender.name) val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreML.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) { if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply)) sender.sendMessage(TextComponentString(reply))
} }
} }
}
}

View File

@ -13,7 +13,13 @@ import net.minecraft.world.World
import net.minecraftforge.fml.common.FMLCommonHandler import net.minecraftforge.fml.common.FMLCommonHandler
import javax.annotation.Nonnull import javax.annotation.Nonnull
class MatterLinkCommandSender(user: String, userId: String, server: String, op: Boolean) : IMinecraftCommandSender(user, userId, server, op), ICommandSender { class MatterLinkCommandSender(
user: String,
userId: String,
server: String,
uuid: String?,
username: String?,
op: Boolean) : IMinecraftCommandSender(user, userId, server, uuid, username, op), ICommandSender {
override fun execute(cmdString: String): Boolean { override fun execute(cmdString: String): Boolean {
return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand( return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand(
@ -23,7 +29,7 @@ class MatterLinkCommandSender(user: String, userId: String, server: String, op:
} }
override fun getDisplayName(): ITextComponent { override fun getDisplayName(): ITextComponent {
return TextComponentString(user) return TextComponentString(displayName)
} }
override fun getName() = accountName override fun getName() = accountName

View File

@ -1,9 +1,13 @@
package matterlink package matterlink
import matterlink.command.CommandMatterlink import com.mojang.authlib.GameProfile
import jline.internal.Log.warn
import matterlink.command.AuthCommand
import matterlink.command.MatterLinkCommand
import matterlink.command.MatterLinkCommandSender import matterlink.command.MatterLinkCommandSender
import matterlink.config.BaseConfig import matterlink.config.BaseConfig
import matterlink.config.cfg import matterlink.config.cfg
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
import net.minecraftforge.common.ForgeVersion import net.minecraftforge.common.ForgeVersion
import net.minecraftforge.fml.common.FMLCommonHandler import net.minecraftforge.fml.common.FMLCommonHandler
@ -14,6 +18,7 @@ import net.minecraftforge.fml.common.event.FMLServerStartingEvent
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent import net.minecraftforge.fml.common.event.FMLServerStoppingEvent
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
import java.util.*
lateinit var logger: Logger lateinit var logger: Logger
@ -27,6 +32,7 @@ lateinit var logger: Logger
dependencies = DEPENDENCIES dependencies = DEPENDENCIES
) )
object MatterLink : IMatterLink() { object MatterLink : IMatterLink() {
init { init {
instance = this instance = this
} }
@ -47,7 +53,8 @@ object MatterLink : IMatterLink() {
@Mod.EventHandler @Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) { fun serverStarting(event: FMLServerStartingEvent) {
log("DEBUG", "Registering server commands") log("DEBUG", "Registering server commands")
event.registerServerCommand(CommandMatterlink()) event.registerServerCommand(MatterLinkCommand())
event.registerServerCommand(AuthCommand())
start() start()
} }
@ -61,10 +68,55 @@ object MatterLink : IMatterLink() {
FMLCommonHandler.instance().minecraftServerInstance.playerList.sendMessage(TextComponentString(msg)) FMLCommonHandler.instance().minecraftServerInstance.playerList.sendMessage(TextComponentString(msg))
} }
override fun wrappedSendToPlayer(user: String, msg: String) {
val profile = profileByName(user) ?: profileByUUID(user) ?: run {
error("cannot find player by name or uuid $user")
return
}
val player = playerByProfile(profile) ?: run {
error("${profile.name} is not online")
return
}
player.sendMessage(TextComponentString(msg))
}
override fun isOnline(username: String) = FMLCommonHandler.instance().minecraftServerInstance.onlinePlayerNames.contains(username)
private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP?
= FMLCommonHandler.instance().minecraftServerInstance.playerList.getPlayerByUUID(gameProfile.id)
private fun profileByUUID(uuid: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getProfileByUUID(UUID.fromString(uuid))
} catch (e: IllegalArgumentException) {
warn("cannot find profile by uuid $uuid")
null
}
private fun profileByName(username: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getGameProfileForUsername(username)
} catch (e: IllegalArgumentException) {
warn("cannot find profile by username $username")
null
}
override fun nameToUUID(username: String) = profileByName(username)?.id?.toString()
override fun uuidToName(uuid: String?): String? {
return uuid?.let { profileByUUID(it)?.name }
}
override fun log(level: String, formatString: String, vararg data: Any) = override fun log(level: String, formatString: String, vararg data: Any) =
logger.log(Level.toLevel(level, Level.INFO), formatString, *data) logger.log(Level.toLevel(level, Level.INFO), formatString, *data)
override fun commandSenderFor(user: String, userId: String, server: String, op: Boolean) = MatterLinkCommandSender(user, userId, server, op) override fun commandSenderFor(
user: String,
userId: String,
server: String,
uuid: String?,
username: String?,
op: Boolean
) = MatterLinkCommandSender(user, userId, server, uuid, username, op)
override val mcVersion: String = MCVERSION override val mcVersion: String = MCVERSION
override val modVersion: String = MODVERSION override val modVersion: String = MODVERSION

View File

@ -0,0 +1,42 @@
package matterlink.command
import matterlink.logger
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString
class AuthCommand : CommandBase() {
override fun getName(): String {
return CommandCoreAuth.name
}
override fun getUsage(sender: ICommandSender): String {
return CommandCoreAuth.usage
}
override fun getAliases(): List<String> {
return CommandCoreAuth.aliases
}
override fun getRequiredPermissionLevel(): Int {
return 0
}
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}")
}
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreAuth.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply))
}
}
}

View File

@ -3,21 +3,22 @@ package matterlink.command
import net.minecraft.command.CommandBase import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.server.MinecraftServer import net.minecraft.server.MinecraftServer
import net.minecraft.util.text.TextComponentString import net.minecraft.util.text.TextComponentString
class CommandMatterlink : CommandBase() { class MatterLinkCommand : CommandBase() {
override fun getName(): String { override fun getName(): String {
return CommandCore.getName() return CommandCoreML.name
} }
override fun getUsage(sender: ICommandSender): String { override fun getUsage(sender: ICommandSender): String {
return CommandCore.getUsage() return CommandCoreML.usage
} }
override fun getAliases(): List<String> { override fun getAliases(): List<String> {
return CommandCore.getAliases() return CommandCoreML.aliases
} }
override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) { override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array<String>) {
@ -25,7 +26,8 @@ class CommandMatterlink : CommandBase() {
throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}") throw WrongUsageException("Invalid command! Valid uses: ${this.getUsage(sender)}")
} }
val reply = CommandCore.execute(args, sender.name) val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreML.execute(args, sender.name, uuid)
if (reply.isNotEmpty() && sender.sendCommandFeedback()) { if (reply.isNotEmpty() && sender.sendCommandFeedback()) {
sender.sendMessage(TextComponentString(reply)) sender.sendMessage(TextComponentString(reply))

View File

@ -9,7 +9,14 @@ import net.minecraft.world.World
import net.minecraftforge.fml.common.FMLCommonHandler import net.minecraftforge.fml.common.FMLCommonHandler
import javax.annotation.Nonnull import javax.annotation.Nonnull
class MatterLinkCommandSender(user: String, userId: String, server: String, op: Boolean) : IMinecraftCommandSender(user, userId, server, op), ICommandSender { class MatterLinkCommandSender(
user: String,
userId: String,
server: String,
uuid: String?,
username: String?,
op: Boolean) : IMinecraftCommandSender(user, userId, server, uuid, username, op), ICommandSender {
override fun execute(cmdString: String): Boolean { override fun execute(cmdString: String): Boolean {
return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand( return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand(
this, this,
@ -18,7 +25,7 @@ class MatterLinkCommandSender(user: String, userId: String, server: String, op:
} }
override fun getDisplayName(): ITextComponent { override fun getDisplayName(): ITextComponent {
return TextComponentString(user) return TextComponentString(displayName)
} }
override fun getName() = accountName override fun getName() = accountName

View File

@ -1,21 +1,24 @@
package matterlink package matterlink
import com.mojang.authlib.GameProfile
import cpw.mods.fml.common.FMLCommonHandler import cpw.mods.fml.common.FMLCommonHandler
import cpw.mods.fml.common.Mod import cpw.mods.fml.common.Mod
import cpw.mods.fml.common.event.FMLInitializationEvent import cpw.mods.fml.common.event.FMLInitializationEvent
import cpw.mods.fml.common.event.FMLPreInitializationEvent import cpw.mods.fml.common.event.FMLPreInitializationEvent
import cpw.mods.fml.common.event.FMLServerStartingEvent import cpw.mods.fml.common.event.FMLServerStartingEvent
import cpw.mods.fml.common.event.FMLServerStoppingEvent import cpw.mods.fml.common.event.FMLServerStoppingEvent
import matterlink.command.CommandMatterlink import matterlink.command.MatterLinkCommand
import matterlink.command.MatterLinkCommandSender import matterlink.command.MatterLinkCommandSender
import matterlink.config.BaseConfig import matterlink.config.BaseConfig
import matterlink.config.cfg import matterlink.config.cfg
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraft.server.MinecraftServer import net.minecraft.server.MinecraftServer
import net.minecraft.util.ChatComponentText import net.minecraft.util.ChatComponentText
import net.minecraftforge.common.ForgeVersion import net.minecraftforge.common.ForgeVersion
import net.minecraftforge.common.MinecraftForge import net.minecraftforge.common.MinecraftForge
import org.apache.logging.log4j.Level import org.apache.logging.log4j.Level
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
import java.util.*
lateinit var logger: Logger lateinit var logger: Logger
@ -49,7 +52,7 @@ class MatterLink : IMatterLink() {
@Mod.EventHandler @Mod.EventHandler
fun serverStarting(event: FMLServerStartingEvent) { fun serverStarting(event: FMLServerStartingEvent) {
logger.debug("Registering server commands") logger.debug("Registering server commands")
event.registerServerCommand(CommandMatterlink()) event.registerServerCommand(MatterLinkCommand())
start() start()
} }
@ -63,10 +66,56 @@ class MatterLink : IMatterLink() {
MinecraftServer.getServer().configurationManager.sendChatMsg(ChatComponentText(msg)) MinecraftServer.getServer().configurationManager.sendChatMsg(ChatComponentText(msg))
} }
override fun wrappedSendToPlayer(user: String, msg: String) {
val profile = profileByName(user) ?: profileByUUID(user) ?: run {
error("cannot find player by name or uuid $user")
return
}
val player = playerByProfile(profile) ?: run {
error("${profile.name} is not online")
return
}
player.addChatMessage(ChatComponentText(msg))
}
override fun isOnline(username: String) = (FMLCommonHandler.instance()
.minecraftServerInstance.configurationManager.getPlayerByUsername(username) ?: null) != null
private fun playerByProfile(gameProfile: GameProfile): EntityPlayerMP? {
return FMLCommonHandler.instance().minecraftServerInstance.configurationManager.createPlayerForUser(gameProfile)
}
private fun profileByUUID(uuid: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.func_152652_a(UUID.fromString(uuid))
} catch (e: IllegalArgumentException) {
warn("cannot find profile by uuid $uuid")
null
}
private fun profileByName(username: String): GameProfile? = try {
FMLCommonHandler.instance().minecraftServerInstance.playerProfileCache.getGameProfileForUsername(username)
} catch (e: IllegalArgumentException) {
warn("cannot find profile by username $username")
null
}
override fun nameToUUID(username: String) = profileByName(username)?.id?.toString()
override fun uuidToName(uuid: String?): String? {
return uuid?.let { profileByUUID(it)?.name }
}
override fun log(level: String, formatString: String, vararg data: Any) = override fun log(level: String, formatString: String, vararg data: Any) =
logger.log(Level.toLevel(level, Level.INFO), formatString, *data) logger.log(Level.toLevel(level, Level.INFO), formatString, *data)
override fun commandSenderFor(user: String, userId: String, server: String, op: Boolean) = MatterLinkCommandSender(user, userId, server, op) override fun commandSenderFor(
user: String,
userId: String,
server: String,
uuid: String?,
username: String?,
op: Boolean
) = MatterLinkCommandSender(user, userId, server, uuid, username, op)
override val mcVersion: String = MCVERSION override val mcVersion: String = MCVERSION
override val modVersion: String = MODVERSION override val modVersion: String = MODVERSION

View File

@ -0,0 +1,35 @@
package matterlink.command
import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.ChatComponentText
class AuthCommand : CommandBase() {
override fun getCommandName(): String {
return CommandCoreAuth.name
}
override fun getCommandUsage(sender: ICommandSender): String {
return CommandCoreAuth.usage
}
override fun getCommandAliases(): List<String> {
return CommandCoreAuth.aliases
}
override fun processCommand(sender: ICommandSender, args: Array<String>) {
if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getCommandUsage(sender)}")
}
val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreAuth.execute(args, sender.commandSenderName, uuid)
if (reply.isNotEmpty()) {
sender.addChatMessage(ChatComponentText(reply))
}
}
}

View File

@ -3,31 +3,33 @@ package matterlink.command
import net.minecraft.command.CommandBase import net.minecraft.command.CommandBase
import net.minecraft.command.ICommandSender import net.minecraft.command.ICommandSender
import net.minecraft.command.WrongUsageException import net.minecraft.command.WrongUsageException
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.util.ChatComponentText import net.minecraft.util.ChatComponentText
class CommandMatterlink : CommandBase() { class MatterLinkCommand : CommandBase() {
override fun getCommandName(): String {
return CommandCoreML.name
}
override fun getCommandUsage(sender: ICommandSender): String {
return CommandCoreML.usage
}
override fun getCommandAliases(): List<String> {
return CommandCoreML.aliases
}
override fun processCommand(sender: ICommandSender, args: Array<String>) { override fun processCommand(sender: ICommandSender, args: Array<String>) {
if (args.isEmpty()) { if (args.isEmpty()) {
throw WrongUsageException("Invalid command! Valid uses: ${this.getCommandUsage(sender)}") throw WrongUsageException("Invalid command! Valid uses: ${this.getCommandUsage(sender)}")
} }
val reply = CommandCore.execute(args, sender.commandSenderName) val uuid = (sender as? EntityPlayer)?.uniqueID?.toString()
val reply = CommandCoreML.execute(args, sender.commandSenderName, uuid)
if (reply.isNotEmpty()) { if (reply.isNotEmpty()) {
sender.addChatMessage(ChatComponentText(reply)) sender.addChatMessage(ChatComponentText(reply))
} }
} }
override fun getCommandName(): String {
return CommandCore.getName()
}
override fun getCommandUsage(sender: ICommandSender): String {
return CommandCore.getUsage()
}
override fun getCommandAliases(): List<String> {
return CommandCore.getAliases()
}
} }

View File

@ -8,9 +8,13 @@ import net.minecraft.util.ChunkCoordinates
import net.minecraft.util.IChatComponent import net.minecraft.util.IChatComponent
import net.minecraft.world.World import net.minecraft.world.World
class MatterLinkCommandSender(user: String, userId: String, server: String, op: Boolean) : IMinecraftCommandSender(user, userId, server, op), ICommandSender { class MatterLinkCommandSender(
user: String,
private var level: Int = 0 userId: String,
server: String,
uuid: String?,
username: String?,
op: Boolean) : IMinecraftCommandSender(user, userId, server, uuid, username, op), ICommandSender {
override fun execute(cmdString: String): Boolean { override fun execute(cmdString: String): Boolean {
return 0 < MinecraftServer.getServer().commandManager.executeCommand( return 0 < MinecraftServer.getServer().commandManager.executeCommand(
@ -20,7 +24,7 @@ class MatterLinkCommandSender(user: String, userId: String, server: String, op:
} }
override fun getFormattedCommandSenderName(): IChatComponent { override fun getFormattedCommandSenderName(): IChatComponent {
return ChatComponentText(user) return ChatComponentText(displayName)
} }
override fun getCommandSenderName() = accountName override fun getCommandSenderName() = accountName

View File

@ -28,6 +28,7 @@ dependencies {
compile group: 'commons-logging', name: 'commons-logging', version: '1.1.3' compile group: 'commons-logging', name: 'commons-logging', version: '1.1.3'
compile group: 'com.google.code.gson', name: 'gson', version: '+' compile group: 'com.google.code.gson', name: 'gson', version: '+'
compile group: 'com.google.guava', name: 'guava', version: '+'
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: project.kotlin_version compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: project.kotlin_version
} }

View File

@ -13,10 +13,15 @@ abstract class IMatterLink {
abstract val modVersion: String abstract val modVersion: String
abstract val forgeVersion: String abstract val forgeVersion: String
abstract fun commandSenderFor(user: String, userId: String, server: String, op: Boolean): IMinecraftCommandSender abstract fun commandSenderFor(user: String, userId: String, server: String, uuid: String?, username: String?, op: Boolean): IMinecraftCommandSender
abstract fun wrappedSendToPlayers(msg: String) abstract fun wrappedSendToPlayers(msg: String)
abstract fun wrappedSendToPlayer(user: String, msg: String)
abstract fun isOnline(username: String): Boolean
abstract fun nameToUUID(username: String): String?
abstract fun uuidToName(uuid: String?): String?
fun start() { fun start() {
MessageHandlerInst.logger = { level, msg -> MessageHandlerInst.logger = { level, msg ->
when (level) { when (level) {
@ -85,4 +90,5 @@ abstract class IMatterLink {
fun registerBridgeCommands() { fun registerBridgeCommands() {
BridgeCommandRegistry.reloadCommands() BridgeCommandRegistry.reloadCommands()
} }
} }

View File

@ -1,10 +1,15 @@
package matterlink package matterlink
import blue.endless.jankson.Jankson import blue.endless.jankson.Jankson
import blue.endless.jankson.JsonArray
import blue.endless.jankson.JsonElement
import blue.endless.jankson.JsonObject import blue.endless.jankson.JsonObject
import blue.endless.jankson.impl.Marshaller
import matterlink.config.cfg import matterlink.config.cfg
import java.io.PrintWriter import java.io.PrintWriter
import java.io.StringWriter import java.io.StringWriter
import java.util.*
import kotlin.streams.asSequence
private const val ZWSP: Char = '\u200b' private const val ZWSP: Char = '\u200b'
@ -57,11 +62,39 @@ val Exception.stackTraceString: String
return sw.toString() return sw.toString()
} }
fun randomString(length: Long = 6, source: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ") = Random().ints(length, 0, source.length)
.asSequence()
.map(source::get)
.joinToString("")
fun <T : Any> JsonObject.getOrDefault(key: String, default: T, comment: String? = null): T { fun <T : Any> JsonObject.getOrDefault(key: String, default: T, comment: String? = null): T {
// instance.info("type: ${default.javaClass.name} key: $key json: >>>${this.getObject(key)?.toJson()}<<< default: $default") // instance.info("type: ${default.javaClass.name} key: $key json: >>>${this.getObject(key)?.toJson()}<<< default: $default")
return putDefault(key, default, comment)!! return putDefault(key, default, comment)!!
} }
inline fun <reified T: Any> Jankson.Builder.registerTypeAdapter(noinline adapter: (JsonObject) -> T) = this.registerTypeAdapter(T::class.java, adapter) inline fun <reified T : Any> Jankson.fromJson(obj: JsonObject): T = this.fromJson(obj, T::class.java)
inline fun <reified T: Any> Jankson.Builder.registerPrimitiveTypeAdapter(noinline adapter: (Any) -> T) = this.registerPrimitiveTypeAdapter(T::class.java, adapter) inline fun <reified T : Any> Jankson.Builder.registerTypeAdapter(noinline adapter: (JsonObject) -> T) = this.registerTypeAdapter(T::class.java, adapter)
inline fun <reified T : Any> Jankson.Builder.registerPrimitiveTypeAdapter(noinline adapter: (Any) -> T) = this.registerPrimitiveTypeAdapter(T::class.java, adapter)
inline fun <reified T : Any> Jankson.Builder.registerSerializer(noinline serializer: (T, Marshaller) -> JsonElement) = this.registerSerializer(T::class.java, serializer)
inline fun <reified T : Any> Marshaller.registerSerializer(noinline serializer: (T) -> JsonElement) = this.registerSerializer(T::class.java, serializer)
inline fun <reified T : Any> Marshaller.registerSerializer(noinline serializer: (T, Marshaller) -> JsonElement) = this.registerSerializer(T::class.java, serializer)
inline fun <reified T : Any> JsonObject.getReified(key: String): T? = this.get(T::class.java, key)
inline fun <reified T : Any> JsonObject.getList(key: String): List<T>? {
return this[key]?.let { array ->
when (array) {
is JsonArray -> {
array.indices.map { i ->
array.get(T::class.java, i) ?: throw NullPointerException("cannot parse ${array.get(i)}")
}
}
else -> null
}
}
}

View File

@ -0,0 +1,68 @@
package matterlink.bridge.command
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.AuthRequest
import matterlink.config.IdentitiesConfig
import matterlink.config.cfg
import matterlink.instance
import matterlink.randomString
object AuthBridgeCommand : IBridgeCommand() {
override val help: String = "Requests authentication on the bridge. Syntax: auth [username]"
override val permLevel: Double
get() = cfg.command.defaultPermUnauthenticated
override fun execute(alias: String, user: String, userId: String, platform: String, uuid: String?, args: String): Boolean {
if (uuid != null) {
val name = instance.uuidToName(uuid)
respond("you are already authenticated as name: $name uuid: $uuid")
return true
}
val argList = args.split(' ', limit = 2)
val target = argList.getOrNull(0) ?: run {
respond("no username/uuid provided")
return true
}
var targetUserName = target
val targetUUid: String = instance.nameToUUID(target) ?: run {
targetUserName = instance.uuidToName(target) ?: run {
respond("cannot find player by username/uuid $target")
return true
}
target
}
val online = instance.isOnline(targetUserName)
if (!online) {
respond("$targetUserName is not online, please log in and try again to send instructions")
return true
}
val nonce = randomString(length = 3).toUpperCase()
val requestId = user.toLowerCase()
instance.wrappedSendToPlayer(targetUserName, "have you requested authentication with the MatterLink system?")
instance.wrappedSendToPlayer(targetUserName, "if yes please execute /auth accept $user $nonce")
instance.wrappedSendToPlayer(targetUserName, "otherwise you may ignore this message")
IdentitiesConfig.authRequests.put(requestId, AuthRequest(username = targetUserName, uuid = targetUUid, nonce = nonce, platform = platform, userid = userId))
respond("please accept the authentication request ingame, do not share the code")
return true
}
private fun respond(text: String) {
MessageHandlerInst.transmit(
ApiMessage(
text = text
)
)
}
}

View File

@ -1,16 +1,23 @@
package matterlink.bridge.command package matterlink.bridge.command
import matterlink.api.ApiMessage import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.CommandConfig import matterlink.config.CommandConfig
import matterlink.config.IdentitiesConfig
import matterlink.config.PermissionConfig import matterlink.config.PermissionConfig
import matterlink.config.cfg import matterlink.config.cfg
import matterlink.instance import matterlink.instance
import matterlink.stripColorOut
import java.util.* import java.util.*
object BridgeCommandRegistry { object BridgeCommandRegistry {
private val commandMap: HashMap<String, IBridgeCommand> = hashMapOf() private val commandMap: HashMap<String, IBridgeCommand> = hashMapOf()
/**
*
* @return consume message flag
*/
fun handleCommand(input: ApiMessage): Boolean { fun handleCommand(input: ApiMessage): Boolean {
if (!cfg.command.enable || input.text.isBlank()) return false if (!cfg.command.enable || input.text.isBlank()) return false
@ -19,7 +26,24 @@ object BridgeCommandRegistry {
val cmd = input.text.substring(1).split(' ', ignoreCase = false, limit = 2) val cmd = input.text.substring(1).split(' ', ignoreCase = false, limit = 2)
val args = if (cmd.size == 2) cmd[1] else "" val args = if (cmd.size == 2) cmd[1] else ""
return commandMap[cmd[0]]?.execute(cmd[0], input.username, input.userid, input.account, args) ?: false val uuid = IdentitiesConfig.getUUID(input.account, input.userid)
return commandMap[cmd[0]]?.let {
if (!it.reachedTimeout()) {
instance.debug("dropped command ${it.alias}")
return false
}
it.preExecute() // resets the tickCounter
if (!it.canExecute(uuid)) {
MessageHandlerInst.transmit(
ApiMessage(
text = "${input.username} is not permitted to perform command: ${cmd[0]}".stripColorOut
)
)
return false
}
it.execute(cmd[0], input.username, input.userid, input.account, uuid, args)
} ?: false
} }
fun register(alias: String, cmd: IBridgeCommand): Boolean { fun register(alias: String, cmd: IBridgeCommand): Boolean {
@ -56,11 +80,15 @@ object BridgeCommandRegistry {
fun reloadCommands() { fun reloadCommands() {
commandMap.clear() commandMap.clear()
val permStatus = PermissionConfig.loadPermFile()
register("help", HelpCommand) register("help", HelpCommand)
if(cfg.command.permissionRequests) if (cfg.command.authRequests)
register("req", PermCommand) register("auth", AuthBridgeCommand)
val cmdStatus = CommandConfig.readConfig() if (cfg.command.permisionRequests)
register("request", RequestPermissionsCommand)
PermissionConfig.loadFile()
CommandConfig.loadFile()
IdentitiesConfig.loadFile()
CommandConfig.commands.forEach { (alias, command) -> CommandConfig.commands.forEach { (alias, command) ->
register(alias, command) register(alias, command)
} }

View File

@ -2,7 +2,6 @@ package matterlink.bridge.command
import matterlink.api.ApiMessage import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst import matterlink.bridge.MessageHandlerInst
import matterlink.handlers.TickHandler
import matterlink.instance import matterlink.instance
import matterlink.lazyFormat import matterlink.lazyFormat
import matterlink.stripColorIn import matterlink.stripColorIn
@ -14,21 +13,16 @@ data class CustomCommand(
val response: String? = null, val response: String? = null,
override val permLevel: Double = 0.0, override val permLevel: Double = 0.0,
override val help: String = "", override val help: String = "",
val timeout: Int = 20, override val timeout: Int = 20,
val defaultCommand: Boolean? = null, val defaultCommand: Boolean? = null,
val execOp: Boolean? = null, val execOp: Boolean? = null,
val argumentsRegex: Regex? = null val argumentsRegex: Regex? = null
) : IBridgeCommand { ) : IBridgeCommand() {
val alias: String
get() = BridgeCommandRegistry.getName(this)!!
@Transient override fun execute(alias: String, user: String, userId: String, platform: String, uuid: String?, args: String): Boolean {
private var lastUsed: Int = 0 if (argumentsRegex != null) {
override fun execute(alias: String, user: String, userId: String, server: String, args: String): Boolean {
if(argumentsRegex != null) {
instance.debug("testing '$args' against '${argumentsRegex.pattern}'") instance.debug("testing '$args' against '${argumentsRegex.pattern}'")
if(!argumentsRegex.matches(args)) { if (!argumentsRegex.matches(args)) {
MessageHandlerInst.transmit( MessageHandlerInst.transmit(
ApiMessage( ApiMessage(
text = "$user sent invalid input to command $alias".stripColorOut text = "$user sent invalid input to command $alias".stripColorOut
@ -38,34 +32,20 @@ data class CustomCommand(
} }
} }
if (TickHandler.tickCounter - lastUsed < timeout) { val username = instance.uuidToName(uuid)
instance.debug("dropped command $alias")
return true //eat command silently
}
if (!canExecute(userId, server)) {
MessageHandlerInst.transmit(
ApiMessage(
text = "$user is not permitted to perform command: $alias".stripColorOut
)
)
return false
}
lastUsed = TickHandler.tickCounter
return when (type) { return when (type) {
CommandType.EXECUTE -> { CommandType.EXECUTE -> {
// uses a new commandsender for each use // uses a new commandsender for each use
val commandSender = instance.commandSenderFor(user, userId, server, execOp ?: false) val commandSender = instance.commandSenderFor(user, userId, platform, uuid, username, execOp ?: false)
val cmd = execute?.lazyFormat(getReplacements(user, userId, server, args))?.stripColorIn ?: return false val cmd = execute?.lazyFormat(getReplacements(user, userId, platform, uuid, args))?.stripColorIn
?: return false
commandSender.execute(cmd) || commandSender.reply.isNotBlank() commandSender.execute(cmd) || commandSender.reply.isNotBlank()
} }
CommandType.RESPONSE -> { CommandType.RESPONSE -> {
MessageHandlerInst.transmit( MessageHandlerInst.transmit(
ApiMessage( ApiMessage(
text = (response?.lazyFormat(getReplacements(user, userId, server, args)) text = (response?.lazyFormat(getReplacements(user, userId, platform, uuid, args))
?: "") ?: "")
) )
) )
@ -90,11 +70,13 @@ data class CustomCommand(
companion object { companion object {
val DEFAULT = CustomCommand() val DEFAULT = CustomCommand()
fun getReplacements(user: String, userId: String, server: String, args: String): Map<String, () -> String> = mapOf( fun getReplacements(user: String, userId: String, platform: String, uuid: String?, args: String): Map<String, () -> String> = mapOf(
"{uptime}" to instance::getUptimeAsString, "{uptime}" to instance::getUptimeAsString,
"{user}" to { user }, "{user}" to { user },
"{userid}" to { userId }, "{userid}" to { userId },
"{server}" to { server }, "{uuid}" to { uuid.toString() },
"{username}" to { uuid?.let { instance.uuidToName(it) }.toString() },
"{platform}" to { platform },
"{args}" to { args } "{args}" to { args }
) )
} }

View File

@ -2,15 +2,18 @@ package matterlink.bridge.command
import matterlink.api.ApiMessage import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst import matterlink.bridge.MessageHandlerInst
import matterlink.config.cfg
import matterlink.stripColorOut import matterlink.stripColorOut
object HelpCommand : IBridgeCommand { object HelpCommand : IBridgeCommand() {
override val help: String = "Returns the help string for the given command. Syntax: help <command>" override val help: String = "Returns the help string for the given command. Syntax: help <command>"
override val permLevel = 0.0 override val permLevel: Double
override fun execute(alias: String, user: String, userId: String, server: String, args: String): Boolean { get() = cfg.command.defaultPermUnauthenticated
override fun execute(alias: String, user: String, userId: String, platform: String, uuid: String?, args: String): Boolean {
val msg: String = when { val msg: String = when {
args.isEmpty() -> args.isEmpty() ->
"Available commands: ${BridgeCommandRegistry.getCommandList(IBridgeCommand.getPermLevel(userId, server))}" "Available commands: ${BridgeCommandRegistry.getCommandList(IBridgeCommand.getPermLevel(uuid))}"
else -> args.split(" ", ignoreCase = false) else -> args.split(" ", ignoreCase = false)
.joinToString(separator = "\n") { .joinToString(separator = "\n") {
"$it: ${BridgeCommandRegistry.getHelpString(it)}" "$it: ${BridgeCommandRegistry.getHelpString(it)}"

View File

@ -1,27 +1,50 @@
package matterlink.bridge.command package matterlink.bridge.command
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.PermissionConfig import matterlink.config.PermissionConfig
import matterlink.config.cfg
import matterlink.handlers.TickHandler
import matterlink.instance import matterlink.instance
interface IBridgeCommand { abstract class IBridgeCommand {
val help: String abstract val help: String
val permLevel: Double abstract val permLevel: Double
open val timeout: Int = 20
fun execute(alias: String, user: String, userId: String, server: String, args: String): Boolean protected var lastUsed: Int = 0
fun canExecute(userId: String, server: String): Boolean { val alias: String
instance.trace("canExecute this: $this canExecute: $userId server: $server permLevel: $permLevel") get() = BridgeCommandRegistry.getName(this)!!
val canExec = getPermLevel(userId, server) >= permLevel
fun reachedTimeout(): Boolean {
return (TickHandler.tickCounter - lastUsed > timeout)
}
fun preExecute() {
lastUsed = TickHandler.tickCounter
}
/**
*
* @return consume message flag
*/
abstract fun execute(alias: String, user: String, userId: String, platform: String, uuid: String?, args: String): Boolean
fun canExecute(uuid: String?): Boolean {
instance.trace("canExecute this: $this uuid: $uuid permLevel: $permLevel")
val canExec = getPermLevel(uuid) >= permLevel
instance.trace("canExecute return $canExec") instance.trace("canExecute return $canExec")
return canExec return canExec
} }
fun validate() = true open fun validate() = true
companion object { companion object {
fun getPermLevel(userId: String, server: String): Double { fun getPermLevel(uuid: String?): Double {
val serverMap = PermissionConfig.perms[server] ?: return 0.0 if(uuid == null) return cfg.command.defaultPermUnauthenticated
return serverMap[userId] ?: 0.0 return PermissionConfig.perms[uuid] ?: cfg.command.defaultPermAuthenticated
} }
} }
} }

View File

@ -4,7 +4,7 @@ import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst import matterlink.bridge.MessageHandlerInst
import matterlink.stripColorOut import matterlink.stripColorOut
abstract class IMinecraftCommandSender(val user: String, val userId: String, val server: String, val op: Boolean) { abstract class IMinecraftCommandSender(val user: String, val userId: String, val server: String, val uuid: String?, val username: String?, val op: Boolean) {
/** /**
* @param cmdString The command to execute with its arguments * @param cmdString The command to execute with its arguments
* *
@ -12,12 +12,13 @@ abstract class IMinecraftCommandSender(val user: String, val userId: String, val
*/ */
abstract fun execute(cmdString: String): Boolean abstract fun execute(cmdString: String): Boolean
val accountName = "$user (id=$userId server=$server)" val displayName = username ?: user
val accountName = "$user (id=$userId server=$server uuid=$uuid)"
fun canExecute(commandName: String): Boolean { fun canExecute(commandName: String): Boolean {
if(op) return true if (op) return true
val command = BridgeCommandRegistry[commandName] ?: return false val command = BridgeCommandRegistry[commandName] ?: return false
return command.canExecute(userId, server) return command.canExecute(uuid)
} }
var reply: String = "" var reply: String = ""

View File

@ -1,57 +0,0 @@
package matterlink.bridge.command
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.PermissionConfig
import matterlink.config.PermissionRequest
object PermCommand : IBridgeCommand {
override val help: String = "Requests permissions on the bridge. Syntax: req [powerlevel] [key]"
override val permLevel = 0.0
override fun execute(alias: String, user: String, userId: String, server: String, args: String): Boolean {
val argList = args.split(' ', limit = 2)
val requestedLevel = args.toDoubleOrNull() ?: 20.0
var unfilteredKey = user
var key = unfilteredKey.replace("[^A-Za-z0-9 ]".toRegex(), "").toLowerCase()
if (argList.size > 1) {
unfilteredKey = argList[1]
key = unfilteredKey.replace("[^A-Za-z0-9 ]".toRegex(), "").toLowerCase()
}
if (key.isBlank()) {
MessageHandlerInst.transmit(
ApiMessage(
text = "$unfilteredKey is made up of invalid characters.. please specifiy a key for tracking this request"
)
)
return true
} else if (PermissionConfig.permissionRequests.containsKey(key)) {
MessageHandlerInst.transmit(
ApiMessage(
text = "there is already a permission request for $key"
)
)
return true
}
val currentPowerlevel = IBridgeCommand.getPermLevel(userId, server)
if(currentPowerlevel < 0.0) {
MessageHandlerInst.transmit(
ApiMessage(
text = "Your level is $currentPowerlevel seems like someone banned you from making any more requests"
)
)
return true
}
MessageHandlerInst.transmit(
ApiMessage(
text = "requet for powerlevel $requestedLevel from user $user userID: $userId server: $server\n" +
"accept this by executing `bridge acceptPerm $key <level>`\n" +
"setting a negative level will prevent people from sending any more requests"
)
)
PermissionConfig.permissionRequests[key.toLowerCase()] = PermissionRequest(user, server, userId, requestedLevel)
// PermissionConfig.add(server, userId, requestedlevel, "authorized by $user")
return true
}
}

View File

@ -0,0 +1,50 @@
package matterlink.bridge.command
import matterlink.api.ApiMessage
import matterlink.bridge.MessageHandlerInst
import matterlink.config.PermissionConfig
import matterlink.config.PermissionRequest
import matterlink.config.cfg
import matterlink.randomString
object RequestPermissionsCommand : IBridgeCommand() {
override val help: String = "Requests permissions on the bridge. Syntax: request [permissionLevel]"
override val permLevel: Double
get() = cfg.command.defaultPermAuthenticated
override fun execute(alias: String, user: String, userId: String, platform: String, uuid: String?, args: String): Boolean {
if (uuid == null) {
respond("$user is not authenticated (userid: $userId platform: $platform)")
return true
}
val argList = args.split(' ', limit = 2)
val requestedLevelArg = argList.getOrNull(0)
val requestedlevel = requestedLevelArg?.let {
it.toDoubleOrNull() ?: run {
respond("cannot parse permlevel")
return true
}
}
val nonce = randomString(length = 3).toUpperCase()
val requestId = user.toLowerCase()
PermissionConfig.permissionRequests.put(requestId, PermissionRequest(uuid = uuid, user = user, nonce = nonce, powerlevel = requestedlevel))
respond("please ask a op to accept your permission elevation with `/ml permAccept $requestId $nonce [powerlevel]`")
return true
}
private fun respond(text: String) {
MessageHandlerInst.transmit(
ApiMessage(
text = text
)
)
}
}

View File

@ -1,52 +0,0 @@
package matterlink.command
import matterlink.bridge.MessageHandlerInst
import matterlink.bridge.command.BridgeCommandRegistry
import matterlink.config.PermissionConfig
import matterlink.config.baseCfg
import matterlink.config.cfg
object CommandCore {
fun getName() = "bridge"
fun getAliases() = listOf("BRIDGE", "bridge")
fun getUsage() = "bridge <connect|disconnect|reload|acceptPerm>"
fun execute(args: Array<String>, user: String): String {
val cmd = args[0].toLowerCase()
return when (cmd) {
"connect" -> {
MessageHandlerInst.start( "Bridge connected by console", true)
"Attempting bridge connection!"
}
"disconnect" -> {
MessageHandlerInst.stop("Bridge disconnected by console")
"Bridge disconnected!"
}
"reload" -> {
// if (MessageHandlerInst.connected)
MessageHandlerInst.stop("Bridge restarting (reload command issued by console)")
cfg = baseCfg.load()
BridgeCommandRegistry.reloadCommands()
// if (!MessageHandlerInst.connected)
MessageHandlerInst.start("Bridge reconnected", false)
"Bridge config reloaded!"
}
"acceptperm" -> {
val key = args.getOrNull(1)?.toLowerCase() ?: return "No argument providing they request key"
val request = PermissionConfig.permissionRequests[key] ?: return "No request found for key $key"
val powerLevelArg = args.getOrNull(2)?.toDoubleOrNull()
val powerLevel = powerLevelArg ?: request.powerlevel ?: return "no powerLevel provided or it cannot be parsed"
PermissionConfig.add(request.platform, request.userId, powerLevel, "${request.user} Authorized by $user")
PermissionConfig.permissionRequests.remove(key)
"added ${request.user} (platform: ${request.platform} userId: ${request.userId}) with power level: $powerLevel"
}
else -> {
"Invalid arguments for command!"
}
}
}
}

View File

@ -0,0 +1,77 @@
package matterlink.command
import matterlink.bridge.MessageHandlerInst
import matterlink.bridge.command.BridgeCommandRegistry
import matterlink.config.IdentitiesConfig
import matterlink.config.PermissionConfig
import matterlink.config.baseCfg
import matterlink.config.cfg
object CommandCoreAuth {
val name = "auth"
val aliases = listOf("authenticate")
val usage = "auth <accept|reject> <id> <code>"
fun execute(args: Array<String>, user: String, uuid: String?): String {
val cmd = args[0].toLowerCase()
return when (cmd) {
"accept" -> {
val requestId = args.getOrNull(1)?.toLowerCase() ?: run {
return "no requestId passed"
}
val request = IdentitiesConfig.authRequests.getIfPresent(requestId.toLowerCase()) ?: run {
return "No request available"
}
val nonce = args.getOrNull(2)?.toUpperCase() ?: run {
return "no code passed"
}
if(request.nonce != nonce) {
return "nonce in request does not match"
}
if(request.username != user) {
return "username in request does not match ${request.username} != $user"
}
if(request.uuid != uuid) {
return "uuid in request does not match ${request.uuid} != $uuid"
}
IdentitiesConfig.add(request.uuid, request.username, request.platform, request.userid, "Accepted by $user")
IdentitiesConfig.authRequests.invalidate(requestId)
"${request.userid} on ${request.platform} is now identified as $user"
}
"reject" -> {
val requestId = args.getOrNull(1)?.toLowerCase() ?: run {
return "no requestId passed"
}
val request = IdentitiesConfig.authRequests.getIfPresent(requestId.toLowerCase()) ?: run {
return "No request available"
}
val nonce = args.getOrNull(2)?.toUpperCase() ?: run {
return "no code passed"
}
if(request.nonce != nonce) {
return "nonce in request does not match"
}
if(request.username != user) {
return "username in request does not match ${request.username} != $user"
}
if(request.uuid != uuid) {
return "uuid in request does not match ${request.uuid} != $uuid"
}
IdentitiesConfig.authRequests.invalidate(requestId)
"request $nonce for ${request.userid} on ${request.platform} was invalidated"
}
else -> {
"Invalid arguments for command! \n" +
"usage: $usage"
}
}
}
}

View File

@ -0,0 +1,65 @@
package matterlink.command
import matterlink.bridge.MessageHandlerInst
import matterlink.bridge.command.BridgeCommandRegistry
import matterlink.config.PermissionConfig
import matterlink.config.baseCfg
import matterlink.config.cfg
object CommandCoreML {
val name = "ml"
val aliases = listOf("matterlink")
val usage = "ml <connect|disconnect|reload|auth>"
fun execute(args: Array<String>, user: String, uuid: String?): String {
val cmd = args[0].toLowerCase()
return when (cmd) {
"connect" -> {
MessageHandlerInst.start("Bridge connected by console", true)
"Attempting bridge connection!"
}
"disconnect" -> {
MessageHandlerInst.stop("Bridge disconnected by console")
"Bridge disconnected!"
}
"reload" -> {
// if (MessageHandlerInst.connected)
MessageHandlerInst.stop("Bridge restarting (reload command issued by console)")
cfg = baseCfg.load()
BridgeCommandRegistry.reloadCommands()
// if (!MessageHandlerInst.connected)
MessageHandlerInst.start("Bridge reconnected", false)
"Bridge config reloaded!"
}
"permaccept" -> {
val requestId = args.getOrNull(1)?.toLowerCase() ?: run {
return "no requestId passed"
}
val request = PermissionConfig.permissionRequests.getIfPresent(requestId.toLowerCase()) ?: run {
return "No request available"
}
val nonce = args.getOrNull(2)?.toUpperCase() ?: run {
return "no code passed"
}
if (request.nonce != nonce) {
return "nonce in request does not match"
}
val powerLevelArg = args.getOrNull(2)?.toDoubleOrNull()
val powerLevel = powerLevelArg ?: run { return "permLevel cannot be parsed" }
?: request.powerlevel
?: return "no permLevel provided"
PermissionConfig.add(request.uuid, powerLevel, "${request.user} Authorized by $user")
PermissionConfig.permissionRequests.invalidate(requestId)
"added ${request.user} (uuid: ${request.uuid}) with power level: $powerLevel"
}
else -> {
"Invalid arguments for command! \n" +
"usage: ${CommandCoreAuth.usage}"
}
}
}
}

View File

@ -37,7 +37,10 @@ data class BaseConfig(val rootDir: File) {
data class CommandOptions( data class CommandOptions(
val prefix: Char = '!', val prefix: Char = '!',
val enable: Boolean = true, val enable: Boolean = true,
val permissionRequests: Boolean = true val authRequests: Boolean = true,
val permisionRequests: Boolean = true,
val defaultPermUnauthenticated: Double = 0.0,
val defaultPermAuthenticated: Double = 1.0
) )
data class ConnectOptions( data class ConnectOptions(
@ -175,10 +178,25 @@ data class BaseConfig(val rootDir: File) {
prefix, prefix,
"Prefix for MC bridge commands. Accepts a single character (not alphanumeric or /)" "Prefix for MC bridge commands. Accepts a single character (not alphanumeric or /)"
), ),
permissionRequests = it.getOrDefault( authRequests = it.getOrDefault(
"permissionRequests", "authRequests",
permissionRequests, authRequests,
"Enable the 'req' command for requestion permissions from chat" "Enable the 'auth' command for linking chat accounts to uuid / ingame account"
),
permisionRequests = it.getOrDefault(
"permisionRequests",
authRequests,
"Enable the 'request' command for requestion permissions from chat"
),
defaultPermUnauthenticated = it.getOrDefault(
"defaultPermUnauthenticated",
defaultPermUnauthenticated,
"default permission level for unauthenticated players"
),
defaultPermAuthenticated = it.getOrDefault(
"defaultPermAuthenticated",
defaultPermAuthenticated,
"default permission level for players that hve linked their accounts"
) )
) )
} }

View File

@ -54,7 +54,7 @@ object CommandConfig {
"whoami" to ("this shows you some of the other response macros" "whoami" to ("this shows you some of the other response macros"
to CustomCommand( to CustomCommand(
type = CommandType.RESPONSE, type = CommandType.RESPONSE,
response = "server: `{server}` userid: `{userid}` user: `{user}`", response = "name: `{user}` userid: `{userid}` platform: `{platform}` username: `{username}` uuid: `{uuid}`",
help = "Print debug user data", help = "Print debug user data",
timeout = 200, timeout = 200,
defaultCommand = true defaultCommand = true
@ -73,7 +73,7 @@ object CommandConfig {
val commands: CommandMap = hashMapOf() val commands: CommandMap = hashMapOf()
fun readConfig(): Boolean { fun loadFile() {
val jankson = Jankson val jankson = Jankson
.builder() .builder()
.registerTypeAdapter { jsonObj -> .registerTypeAdapter { jsonObj ->
@ -96,7 +96,7 @@ object CommandConfig {
} }
.build() .build()
jankson.marshaller.registerSerializer(Regex::class.java) { regex, marshaller -> jankson.marshaller.registerSerializer(Regex::class.java) { regex, _ ->
JsonPrimitive(regex.pattern) JsonPrimitive(regex.pattern)
} }
@ -142,7 +142,5 @@ object CommandConfig {
} }
} }
configFile.writeText(nonDefaultJsonObj.toJson(true, true)) configFile.writeText(nonDefaultJsonObj.toJson(true, true))
return true
} }
} }

View File

@ -0,0 +1,126 @@
package matterlink.config
import blue.endless.jankson.Jankson
import blue.endless.jankson.JsonObject
import blue.endless.jankson.impl.Marshaller
import blue.endless.jankson.impl.SyntaxError
import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder
import matterlink.getList
import matterlink.getOrDefault
import matterlink.instance
import java.io.File
import java.io.FileNotFoundException
import java.util.concurrent.TimeUnit
typealias IdentMap = Map<String, Map<String, List<String>>>
data class AuthRequest(
val username: String,
val uuid: String,
val nonce: String,
val platform: String,
val userid: String
)
object IdentitiesConfig {
val authRequests: Cache<String, AuthRequest> = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build()
private val jankson = Jankson
.builder()
.build()
private val configFile: File = baseCfg.cfgDirectory.resolve("identities.hjson")
private val default = mapOf(
("edd31c45-b095-49c5-a9f5-59cec4cfed8c" to mapOf(
"discord.game" to (listOf("112228624366575616") to "discord id")
) to "username: NikkyAi")
)
var idents: IdentMap = mapOf()
private set
private var jsonObject: JsonObject = JsonObject()
fun loadFile() {
val defaultJsonObject = JsonObject().apply {
default.forEach { (uuid, userMap), uuidComment ->
val jsonUserMap = this.putDefault(uuid, JsonObject(), uuidComment)
if (jsonUserMap is JsonObject) {
userMap.forEach { platform, (user, comment) ->
instance.trace("loading uuid: $uuid platform: $platform user: $user")
val element = Marshaller.getFallback().serialize(user)
jsonUserMap.putDefault(platform, element, comment.takeUnless { it.isBlank() })
}
this[uuid] = jsonUserMap
} else {
instance.error("cannot parse uuid: $uuid , value: '$jsonUserMap' as Map, skipping")
}
}
}
var save = true
jsonObject = try {
jankson.load(configFile)
} catch (e: SyntaxError) {
instance.error("error parsing config: ${e.completeMessage}")
save = false
defaultJsonObject
} catch (e: FileNotFoundException) {
instance.error("cannot find config: $configFile .. creating sample permissions mapping")
configFile.createNewFile()
defaultJsonObject
}
load(save)
}
private fun load(save: Boolean = true) {
val tmpIdents: MutableMap<String, Map<String, List<String>>> = mutableMapOf()
jsonObject.forEach { uuid, jsonIdentifier ->
val identMap: MutableMap<String, List<String>> = tmpIdents[uuid]?.toMutableMap() ?: mutableMapOf()
if (jsonIdentifier is JsonObject) {
jsonIdentifier.forEach { platform, user ->
instance.info("$uuid $platform $user")
identMap[platform] = jsonIdentifier.getList(platform) ?: emptyList()
}
}
tmpIdents[uuid] = identMap.toMap()
}
idents = tmpIdents.toMap()
instance.info("Identities loaded")
if (save)
configFile.writeText(jsonObject.toJson(true, true))
}
fun add(uuid: String, username: String, platform: String, userid: String, comment: String? = null) {
val platformObject = jsonObject.getObject(uuid) ?: JsonObject()
platformObject.putDefault(platform, userid, comment)
val userIdList = platformObject.getList<String>(platform) ?: emptyList()
platformObject[platform] = platformObject.marshaller.serialize(userIdList + userid)
jsonObject[uuid] = platformObject
if (jsonObject.getComment(uuid) == null) {
jsonObject.setComment(uuid, "Username: $username")
}
load()
}
//TODO: rewrite, store ident map differently in memory
fun getUUID(platform: String, userid: String): String? {
return idents.entries.firstOrNull { (uuid, usermap) ->
usermap.entries.any { (_platform, userids) ->
if (platform.equals(_platform, true))
instance.info("platform: $_platform userids: $userids")
platform.equals(_platform, true) && userids.contains(userid)
}
}?.key
}
}

View File

@ -2,23 +2,28 @@ package matterlink.config
import blue.endless.jankson.Jankson import blue.endless.jankson.Jankson
import blue.endless.jankson.JsonObject import blue.endless.jankson.JsonObject
import blue.endless.jankson.impl.Marshaller
import blue.endless.jankson.impl.SyntaxError import blue.endless.jankson.impl.SyntaxError
import com.google.common.cache.Cache
import com.google.common.cache.CacheBuilder
import matterlink.getReified
import matterlink.instance import matterlink.instance
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.concurrent.TimeUnit
typealias PermissionMap = MutableMap<String, MutableMap<String, Double>> typealias PermissionMap = Map<String, Double>
data class PermissionRequest( data class PermissionRequest(
val uuid: String,
val user: String, val user: String,
val platform: String, val nonce: String,
val userId: String,
val powerlevel: Double? = null val powerlevel: Double? = null
) )
object PermissionConfig { object PermissionConfig {
val permissionRequests = mutableMapOf<String, PermissionRequest>() val permissionRequests: Cache<String, PermissionRequest> = CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.build()
private val jankson = Jankson private val jankson = Jankson
.builder() .builder()
.build() .build()
@ -26,34 +31,16 @@ object PermissionConfig {
private val configFile: File = baseCfg.cfgDirectory.resolve("permissions.hjson") private val configFile: File = baseCfg.cfgDirectory.resolve("permissions.hjson")
private val default = mapOf( private val default = mapOf(
"irc.esper" to mapOf( "edd31c45-b095-49c5-a9f5-59cec4cfed8c" to 9000.0 to "Superuser"
"~nikky@nikky.moe" to (0.0 to "IRC users are identified by their username and hostmask"),
"user@example.com" to (0.0 to "")
),
"discord.game" to mapOf(
"112228624366575616" to (0.0 to "thats a discord user id")
)
) )
val perms: PermissionMap = mutableMapOf() val perms: PermissionMap = mutableMapOf()
private var jsonObject: JsonObject = JsonObject() private var jsonObject: JsonObject = JsonObject()
fun loadPermFile(): Boolean { fun loadFile() {
permissionRequests.clear()
val defaultJsonObject = JsonObject().apply { val defaultJsonObject = JsonObject().apply {
default.forEach { platform, userMap -> default.forEach { (uuid, level), comment ->
val jsonUserMap = this.getOrDefault(platform, JsonObject()) jsonObject.putDefault(uuid, level, comment)
if (jsonUserMap is JsonObject) {
userMap.forEach { user, (powerlevel, comment) ->
instance.trace("loading platform: $platform user: $user powerlevel: $powerlevel")
val element = Marshaller.getFallback().serialize(powerlevel)
jsonUserMap.putDefault(user, element, comment.takeUnless { it.isBlank() })
}
this[platform] = jsonUserMap
} else {
instance.error("cannot parse platform: $platform , value: '$jsonUserMap' as Map, skipping")
}
} }
} }
@ -71,33 +58,27 @@ object PermissionConfig {
} }
load(save) load(save)
return true
} }
private fun load(save: Boolean = true) { private fun load(save: Boolean = true) {
perms.clear() val tmpPerms = mutableMapOf<String, Double>()
jsonObject.forEach { platform, jsonUserMap -> for ((uuid, powerlevel) in jsonObject) {
val userMap = perms[platform] ?: mutableMapOf() val tmpLevel = jsonObject.getReified<Double>(uuid)
if (jsonUserMap is JsonObject) { if (tmpLevel == null) {
jsonUserMap.forEach { user, powerlevel -> instance.warn("cannot parse permission uuid: $uuid level: $powerlevel")
instance.info("$platform $user $powerlevel") continue
userMap[user] = jsonUserMap.get(Double::class.java, user) ?: 0.0
}
} }
perms[platform] = userMap tmpPerms[uuid] = tmpLevel
} }
instance.info("Permissions reloaded") instance.info("Permissions reloaded")
if (save) if (save)
configFile.writeText(jsonObject.toJson(true, true)) configFile.writeText(jsonObject.toJson(true, true))
} }
fun add(platform: String, userid: String, powerlevel: Double, comment: String? = null) { fun add(uuid: String, powerlevel: Double, comment: String? = null) {
val platformObject = jsonObject.getObject(platform) ?: JsonObject() jsonObject.putDefault(uuid, powerlevel, comment)
platformObject.putDefault(userid, powerlevel, comment)
jsonObject[platform] = platformObject
load() load()
} }
} }