diff --git a/1.10.2/src/main/kotlin/matterlink/MatterLink.kt b/1.10.2/src/main/kotlin/matterlink/MatterLink.kt index 5b07cc5..d5ddc39 100644 --- a/1.10.2/src/main/kotlin/matterlink/MatterLink.kt +++ b/1.10.2/src/main/kotlin/matterlink/MatterLink.kt @@ -1,9 +1,11 @@ package matterlink -import matterlink.command.CommandMatterlink +import com.mojang.authlib.GameProfile +import matterlink.command.MatterLinkCommand import matterlink.command.MatterLinkCommandSender import matterlink.config.BaseConfig import matterlink.config.cfg +import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.util.text.TextComponentString import net.minecraftforge.common.ForgeVersion 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 org.apache.logging.log4j.Level import org.apache.logging.log4j.Logger +import java.util.* lateinit var logger: Logger @@ -47,7 +50,7 @@ object MatterLink : IMatterLink() { @Mod.EventHandler fun serverStarting(event: FMLServerStartingEvent) { logger.debug("Registering server commands") - event.registerServerCommand(CommandMatterlink()) + event.registerServerCommand(MatterLinkCommand()) start() } @@ -61,10 +64,55 @@ object MatterLink : IMatterLink() { 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) = 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 modVersion: String = MODVERSION diff --git a/1.10.2/src/main/kotlin/matterlink/command/AuthCommand.kt b/1.10.2/src/main/kotlin/matterlink/command/AuthCommand.kt new file mode 100644 index 0000000..2d3df09 --- /dev/null +++ b/1.10.2/src/main/kotlin/matterlink/command/AuthCommand.kt @@ -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 { + return CommandCoreAuth.aliases + } + + override fun getRequiredPermissionLevel(): Int { + return 0 + } + + override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array) { + 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)) + } + } + +} diff --git a/1.10.2/src/main/kotlin/matterlink/command/CommandMatterlink.kt b/1.10.2/src/main/kotlin/matterlink/command/MatterLinkCommand.kt similarity index 70% rename from 1.10.2/src/main/kotlin/matterlink/command/CommandMatterlink.kt rename to 1.10.2/src/main/kotlin/matterlink/command/MatterLinkCommand.kt index ac2f57d..ba933ff 100644 --- a/1.10.2/src/main/kotlin/matterlink/command/CommandMatterlink.kt +++ b/1.10.2/src/main/kotlin/matterlink/command/MatterLinkCommand.kt @@ -3,21 +3,22 @@ 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.server.MinecraftServer import net.minecraft.util.text.TextComponentString -class CommandMatterlink : CommandBase() { +class MatterLinkCommand : CommandBase() { override fun getName(): String { - return CommandCore.getName() + return CommandCoreML.name } override fun getUsage(sender: ICommandSender): String { - return CommandCore.getUsage() + return CommandCoreML.usage } override fun getAliases(): List { - return CommandCore.getAliases() + return CommandCoreML.aliases } override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array) { @@ -25,7 +26,8 @@ class CommandMatterlink : CommandBase() { 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()) { sender.sendMessage(TextComponentString(reply)) diff --git a/1.10.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt b/1.10.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt index 614212a..959ad6f 100644 --- a/1.10.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt +++ b/1.10.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt @@ -13,8 +13,13 @@ import net.minecraft.world.World import net.minecraftforge.fml.common.FMLCommonHandler import javax.annotation.Nonnull -class MatterLinkCommandSender(user: String, userId: String, server: String, op: Boolean) : IMinecraftCommandSender(user, userId, server, op), ICommandSender { - private var level: Int = 0 +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 { return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand( @@ -24,7 +29,7 @@ class MatterLinkCommandSender(user: String, userId: String, server: String, op: } override fun getDisplayName(): ITextComponent { - return TextComponentString(user) + return TextComponentString(displayName) } override fun getName() = accountName diff --git a/1.11.2/src/main/kotlin/matterlink/MatterLink.kt b/1.11.2/src/main/kotlin/matterlink/MatterLink.kt index 7aeb6cb..22b4740 100644 --- a/1.11.2/src/main/kotlin/matterlink/MatterLink.kt +++ b/1.11.2/src/main/kotlin/matterlink/MatterLink.kt @@ -1,9 +1,11 @@ package matterlink -import matterlink.command.CommandMatterlink +import com.mojang.authlib.GameProfile +import matterlink.command.MatterLinkCommand import matterlink.command.MatterLinkCommandSender import matterlink.config.BaseConfig import matterlink.config.cfg +import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.util.text.TextComponentString import net.minecraftforge.common.ForgeVersion 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 org.apache.logging.log4j.Level import org.apache.logging.log4j.Logger +import java.util.* lateinit var logger: Logger @@ -47,7 +50,7 @@ object MatterLink : IMatterLink() { @Mod.EventHandler fun serverStarting(event: FMLServerStartingEvent) { logger.debug("Registering server commands") - event.registerServerCommand(CommandMatterlink()) + event.registerServerCommand(MatterLinkCommand()) start() } @@ -61,10 +64,54 @@ object MatterLink : IMatterLink() { 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) = 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 modVersion: String = MODVERSION diff --git a/1.11.2/src/main/kotlin/matterlink/command/AuthCommand.kt b/1.11.2/src/main/kotlin/matterlink/command/AuthCommand.kt new file mode 100644 index 0000000..2d3df09 --- /dev/null +++ b/1.11.2/src/main/kotlin/matterlink/command/AuthCommand.kt @@ -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 { + return CommandCoreAuth.aliases + } + + override fun getRequiredPermissionLevel(): Int { + return 0 + } + + override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array) { + 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)) + } + } + +} diff --git a/1.12.2/src/main/kotlin/matterlink/command/CommandMatterlink.kt b/1.11.2/src/main/kotlin/matterlink/command/MatterLinkCommand.kt similarity index 70% rename from 1.12.2/src/main/kotlin/matterlink/command/CommandMatterlink.kt rename to 1.11.2/src/main/kotlin/matterlink/command/MatterLinkCommand.kt index 9b97497..0bfda99 100644 --- a/1.12.2/src/main/kotlin/matterlink/command/CommandMatterlink.kt +++ b/1.11.2/src/main/kotlin/matterlink/command/MatterLinkCommand.kt @@ -3,21 +3,22 @@ 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.server.MinecraftServer import net.minecraft.util.text.TextComponentString -class CommandMatterlink : CommandBase() { +class MatterLinkCommand : CommandBase() { override fun getName(): String { - return CommandCore.getName() + return CommandCoreML.name } override fun getUsage(sender: ICommandSender): String { - return CommandCore.getUsage() + return CommandCoreML.usage } override fun getAliases(): List { - return CommandCore.getAliases() + return CommandCoreML.aliases } override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array) { @@ -25,11 +26,11 @@ class CommandMatterlink : CommandBase() { 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()) { sender.sendMessage(TextComponentString(reply)) } } - -} +} \ No newline at end of file diff --git a/1.11.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt b/1.11.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt index af8fd33..baeb0e1 100644 --- a/1.11.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt +++ b/1.11.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt @@ -13,7 +13,13 @@ import net.minecraft.world.World import net.minecraftforge.fml.common.FMLCommonHandler 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 { return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand( @@ -23,7 +29,7 @@ class MatterLinkCommandSender(user: String, userId: String, server: String, op: } override fun getDisplayName(): ITextComponent { - return TextComponentString(user) + return TextComponentString(displayName) } override fun getName() = accountName diff --git a/1.12.2/src/main/kotlin/matterlink/MatterLink.kt b/1.12.2/src/main/kotlin/matterlink/MatterLink.kt index f32c8a4..51073e3 100644 --- a/1.12.2/src/main/kotlin/matterlink/MatterLink.kt +++ b/1.12.2/src/main/kotlin/matterlink/MatterLink.kt @@ -1,9 +1,13 @@ 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.config.BaseConfig import matterlink.config.cfg +import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.util.text.TextComponentString import net.minecraftforge.common.ForgeVersion 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 org.apache.logging.log4j.Level import org.apache.logging.log4j.Logger +import java.util.* lateinit var logger: Logger @@ -27,6 +32,7 @@ lateinit var logger: Logger dependencies = DEPENDENCIES ) object MatterLink : IMatterLink() { + init { instance = this } @@ -47,7 +53,8 @@ object MatterLink : IMatterLink() { @Mod.EventHandler fun serverStarting(event: FMLServerStartingEvent) { log("DEBUG", "Registering server commands") - event.registerServerCommand(CommandMatterlink()) + event.registerServerCommand(MatterLinkCommand()) + event.registerServerCommand(AuthCommand()) start() } @@ -61,10 +68,55 @@ object MatterLink : IMatterLink() { 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) = 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 modVersion: String = MODVERSION diff --git a/1.12.2/src/main/kotlin/matterlink/command/AuthCommand.kt b/1.12.2/src/main/kotlin/matterlink/command/AuthCommand.kt new file mode 100644 index 0000000..2d3df09 --- /dev/null +++ b/1.12.2/src/main/kotlin/matterlink/command/AuthCommand.kt @@ -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 { + return CommandCoreAuth.aliases + } + + override fun getRequiredPermissionLevel(): Int { + return 0 + } + + override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array) { + 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)) + } + } + +} diff --git a/1.11.2/src/main/kotlin/matterlink/command/CommandMatterlink.kt b/1.12.2/src/main/kotlin/matterlink/command/MatterLinkCommand.kt similarity index 70% rename from 1.11.2/src/main/kotlin/matterlink/command/CommandMatterlink.kt rename to 1.12.2/src/main/kotlin/matterlink/command/MatterLinkCommand.kt index ac2f57d..ba933ff 100644 --- a/1.11.2/src/main/kotlin/matterlink/command/CommandMatterlink.kt +++ b/1.12.2/src/main/kotlin/matterlink/command/MatterLinkCommand.kt @@ -3,21 +3,22 @@ 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.server.MinecraftServer import net.minecraft.util.text.TextComponentString -class CommandMatterlink : CommandBase() { +class MatterLinkCommand : CommandBase() { override fun getName(): String { - return CommandCore.getName() + return CommandCoreML.name } override fun getUsage(sender: ICommandSender): String { - return CommandCore.getUsage() + return CommandCoreML.usage } override fun getAliases(): List { - return CommandCore.getAliases() + return CommandCoreML.aliases } override fun execute(server: MinecraftServer, sender: ICommandSender, args: Array) { @@ -25,7 +26,8 @@ class CommandMatterlink : CommandBase() { 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()) { sender.sendMessage(TextComponentString(reply)) diff --git a/1.12.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt b/1.12.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt index e813999..1998872 100644 --- a/1.12.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt +++ b/1.12.2/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt @@ -9,7 +9,14 @@ import net.minecraft.world.World import net.minecraftforge.fml.common.FMLCommonHandler 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 { return 0 < FMLCommonHandler.instance().minecraftServerInstance.commandManager.executeCommand( this, @@ -18,7 +25,7 @@ class MatterLinkCommandSender(user: String, userId: String, server: String, op: } override fun getDisplayName(): ITextComponent { - return TextComponentString(user) + return TextComponentString(displayName) } override fun getName() = accountName diff --git a/1.7.10/src/main/kotlin/matterlink/MatterLink.kt b/1.7.10/src/main/kotlin/matterlink/MatterLink.kt index b0b080d..2e7f50e 100644 --- a/1.7.10/src/main/kotlin/matterlink/MatterLink.kt +++ b/1.7.10/src/main/kotlin/matterlink/MatterLink.kt @@ -1,21 +1,24 @@ package matterlink +import com.mojang.authlib.GameProfile import cpw.mods.fml.common.FMLCommonHandler import cpw.mods.fml.common.Mod import cpw.mods.fml.common.event.FMLInitializationEvent import cpw.mods.fml.common.event.FMLPreInitializationEvent import cpw.mods.fml.common.event.FMLServerStartingEvent import cpw.mods.fml.common.event.FMLServerStoppingEvent -import matterlink.command.CommandMatterlink +import matterlink.command.MatterLinkCommand import matterlink.command.MatterLinkCommandSender import matterlink.config.BaseConfig import matterlink.config.cfg +import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.server.MinecraftServer import net.minecraft.util.ChatComponentText import net.minecraftforge.common.ForgeVersion import net.minecraftforge.common.MinecraftForge import org.apache.logging.log4j.Level import org.apache.logging.log4j.Logger +import java.util.* lateinit var logger: Logger @@ -49,7 +52,7 @@ class MatterLink : IMatterLink() { @Mod.EventHandler fun serverStarting(event: FMLServerStartingEvent) { logger.debug("Registering server commands") - event.registerServerCommand(CommandMatterlink()) + event.registerServerCommand(MatterLinkCommand()) start() } @@ -63,10 +66,56 @@ class MatterLink : IMatterLink() { 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) = 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 modVersion: String = MODVERSION diff --git a/1.7.10/src/main/kotlin/matterlink/command/AuthCommand.kt b/1.7.10/src/main/kotlin/matterlink/command/AuthCommand.kt new file mode 100644 index 0000000..881cc9a --- /dev/null +++ b/1.7.10/src/main/kotlin/matterlink/command/AuthCommand.kt @@ -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 { + return CommandCoreAuth.aliases + } + + override fun processCommand(sender: ICommandSender, args: Array) { + 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)) + } + } +} diff --git a/1.7.10/src/main/kotlin/matterlink/command/CommandMatterlink.kt b/1.7.10/src/main/kotlin/matterlink/command/MatterLinkCommand.kt similarity index 67% rename from 1.7.10/src/main/kotlin/matterlink/command/CommandMatterlink.kt rename to 1.7.10/src/main/kotlin/matterlink/command/MatterLinkCommand.kt index 2f3fdc8..592b00c 100644 --- a/1.7.10/src/main/kotlin/matterlink/command/CommandMatterlink.kt +++ b/1.7.10/src/main/kotlin/matterlink/command/MatterLinkCommand.kt @@ -3,31 +3,33 @@ 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 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 { + return CommandCoreML.aliases + } + override fun processCommand(sender: ICommandSender, args: Array) { if (args.isEmpty()) { 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()) { sender.addChatMessage(ChatComponentText(reply)) } } - - override fun getCommandName(): String { - return CommandCore.getName() - } - - override fun getCommandUsage(sender: ICommandSender): String { - return CommandCore.getUsage() - } - - override fun getCommandAliases(): List { - return CommandCore.getAliases() - } } diff --git a/1.7.10/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt b/1.7.10/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt index 1be5ef7..d13d24b 100644 --- a/1.7.10/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt +++ b/1.7.10/src/main/kotlin/matterlink/command/MatterLinkCommandSender.kt @@ -8,9 +8,13 @@ import net.minecraft.util.ChunkCoordinates import net.minecraft.util.IChatComponent import net.minecraft.world.World -class MatterLinkCommandSender(user: String, userId: String, server: String, op: Boolean) : IMinecraftCommandSender(user, userId, server, op), ICommandSender { - - private var level: Int = 0 +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 { return 0 < MinecraftServer.getServer().commandManager.executeCommand( @@ -20,7 +24,7 @@ class MatterLinkCommandSender(user: String, userId: String, server: String, op: } override fun getFormattedCommandSenderName(): IChatComponent { - return ChatComponentText(user) + return ChatComponentText(displayName) } override fun getCommandSenderName() = accountName diff --git a/core/build.gradle b/core/build.gradle index 7bc3123..5ad0456 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -28,6 +28,7 @@ dependencies { compile group: 'commons-logging', name: 'commons-logging', version: '1.1.3' 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 } diff --git a/core/src/main/kotlin/matterlink/IMatterLink.kt b/core/src/main/kotlin/matterlink/IMatterLink.kt index f860ddb..0d3385b 100644 --- a/core/src/main/kotlin/matterlink/IMatterLink.kt +++ b/core/src/main/kotlin/matterlink/IMatterLink.kt @@ -13,10 +13,15 @@ abstract class IMatterLink { abstract val modVersion: 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 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() { MessageHandlerInst.logger = { level, msg -> when (level) { @@ -85,4 +90,5 @@ abstract class IMatterLink { fun registerBridgeCommands() { BridgeCommandRegistry.reloadCommands() } + } \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/Util.kt b/core/src/main/kotlin/matterlink/Util.kt index 97c6928..436e9ee 100644 --- a/core/src/main/kotlin/matterlink/Util.kt +++ b/core/src/main/kotlin/matterlink/Util.kt @@ -1,10 +1,15 @@ package matterlink import blue.endless.jankson.Jankson +import blue.endless.jankson.JsonArray +import blue.endless.jankson.JsonElement import blue.endless.jankson.JsonObject +import blue.endless.jankson.impl.Marshaller import matterlink.config.cfg import java.io.PrintWriter import java.io.StringWriter +import java.util.* +import kotlin.streams.asSequence private const val ZWSP: Char = '\u200b' @@ -57,11 +62,39 @@ val Exception.stackTraceString: String return sw.toString() } +fun randomString(length: Long = 6, source: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ") = Random().ints(length, 0, source.length) + .asSequence() + .map(source::get) + .joinToString("") + fun 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") return putDefault(key, default, comment)!! } -inline fun Jankson.Builder.registerTypeAdapter(noinline adapter: (JsonObject) -> T) = this.registerTypeAdapter(T::class.java, adapter) +inline fun Jankson.fromJson(obj: JsonObject): T = this.fromJson(obj, T::class.java) -inline fun Jankson.Builder.registerPrimitiveTypeAdapter(noinline adapter: (Any) -> T) = this.registerPrimitiveTypeAdapter(T::class.java, adapter) +inline fun Jankson.Builder.registerTypeAdapter(noinline adapter: (JsonObject) -> T) = this.registerTypeAdapter(T::class.java, adapter) + +inline fun Jankson.Builder.registerPrimitiveTypeAdapter(noinline adapter: (Any) -> T) = this.registerPrimitiveTypeAdapter(T::class.java, adapter) + +inline fun Jankson.Builder.registerSerializer(noinline serializer: (T, Marshaller) -> JsonElement) = this.registerSerializer(T::class.java, serializer) + +inline fun Marshaller.registerSerializer(noinline serializer: (T) -> JsonElement) = this.registerSerializer(T::class.java, serializer) + +inline fun Marshaller.registerSerializer(noinline serializer: (T, Marshaller) -> JsonElement) = this.registerSerializer(T::class.java, serializer) + +inline fun JsonObject.getReified(key: String): T? = this.get(T::class.java, key) + +inline fun JsonObject.getList(key: String): List? { + 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 + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/bridge/command/AuthBridgeCommand.kt b/core/src/main/kotlin/matterlink/bridge/command/AuthBridgeCommand.kt new file mode 100644 index 0000000..201da37 --- /dev/null +++ b/core/src/main/kotlin/matterlink/bridge/command/AuthBridgeCommand.kt @@ -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 + ) + ) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/bridge/command/BridgeCommandRegistry.kt b/core/src/main/kotlin/matterlink/bridge/command/BridgeCommandRegistry.kt index 483459a..2a5a1b8 100644 --- a/core/src/main/kotlin/matterlink/bridge/command/BridgeCommandRegistry.kt +++ b/core/src/main/kotlin/matterlink/bridge/command/BridgeCommandRegistry.kt @@ -1,16 +1,23 @@ package matterlink.bridge.command import matterlink.api.ApiMessage +import matterlink.bridge.MessageHandlerInst import matterlink.config.CommandConfig +import matterlink.config.IdentitiesConfig import matterlink.config.PermissionConfig import matterlink.config.cfg import matterlink.instance +import matterlink.stripColorOut import java.util.* object BridgeCommandRegistry { private val commandMap: HashMap = hashMapOf() + /** + * + * @return consume message flag + */ fun handleCommand(input: ApiMessage): Boolean { 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 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 { @@ -56,11 +80,15 @@ object BridgeCommandRegistry { fun reloadCommands() { commandMap.clear() - val permStatus = PermissionConfig.loadPermFile() register("help", HelpCommand) - if(cfg.command.permissionRequests) - register("req", PermCommand) - val cmdStatus = CommandConfig.readConfig() + if (cfg.command.authRequests) + register("auth", AuthBridgeCommand) + if (cfg.command.permisionRequests) + register("request", RequestPermissionsCommand) + PermissionConfig.loadFile() + CommandConfig.loadFile() + IdentitiesConfig.loadFile() + CommandConfig.commands.forEach { (alias, command) -> register(alias, command) } diff --git a/core/src/main/kotlin/matterlink/bridge/command/CustomCommand.kt b/core/src/main/kotlin/matterlink/bridge/command/CustomCommand.kt index 7614692..b616c36 100644 --- a/core/src/main/kotlin/matterlink/bridge/command/CustomCommand.kt +++ b/core/src/main/kotlin/matterlink/bridge/command/CustomCommand.kt @@ -2,7 +2,6 @@ package matterlink.bridge.command import matterlink.api.ApiMessage import matterlink.bridge.MessageHandlerInst -import matterlink.handlers.TickHandler import matterlink.instance import matterlink.lazyFormat import matterlink.stripColorIn @@ -14,21 +13,16 @@ data class CustomCommand( val response: String? = null, override val permLevel: Double = 0.0, override val help: String = "", - val timeout: Int = 20, + override val timeout: Int = 20, val defaultCommand: Boolean? = null, val execOp: Boolean? = null, val argumentsRegex: Regex? = null -) : IBridgeCommand { - val alias: String - get() = BridgeCommandRegistry.getName(this)!! +) : IBridgeCommand() { - @Transient - private var lastUsed: Int = 0 - - override fun execute(alias: String, user: String, userId: String, server: String, args: String): Boolean { - if(argumentsRegex != null) { + override fun execute(alias: String, user: String, userId: String, platform: String, uuid: String?, args: String): Boolean { + if (argumentsRegex != null) { instance.debug("testing '$args' against '${argumentsRegex.pattern}'") - if(!argumentsRegex.matches(args)) { + if (!argumentsRegex.matches(args)) { MessageHandlerInst.transmit( ApiMessage( text = "$user sent invalid input to command $alias".stripColorOut @@ -38,34 +32,20 @@ data class CustomCommand( } } - if (TickHandler.tickCounter - lastUsed < timeout) { - 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 - + val username = instance.uuidToName(uuid) return when (type) { CommandType.EXECUTE -> { // uses a new commandsender for each use - val commandSender = instance.commandSenderFor(user, userId, server, execOp ?: false) - val cmd = execute?.lazyFormat(getReplacements(user, userId, server, args))?.stripColorIn ?: return false + val commandSender = instance.commandSenderFor(user, userId, platform, uuid, username, execOp ?: false) + val cmd = execute?.lazyFormat(getReplacements(user, userId, platform, uuid, args))?.stripColorIn + ?: return false commandSender.execute(cmd) || commandSender.reply.isNotBlank() } CommandType.RESPONSE -> { MessageHandlerInst.transmit( 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 { val DEFAULT = CustomCommand() - fun getReplacements(user: String, userId: String, server: String, args: String): Map String> = mapOf( + fun getReplacements(user: String, userId: String, platform: String, uuid: String?, args: String): Map String> = mapOf( "{uptime}" to instance::getUptimeAsString, "{user}" to { user }, "{userid}" to { userId }, - "{server}" to { server }, + "{uuid}" to { uuid.toString() }, + "{username}" to { uuid?.let { instance.uuidToName(it) }.toString() }, + "{platform}" to { platform }, "{args}" to { args } ) } diff --git a/core/src/main/kotlin/matterlink/bridge/command/HelpCommand.kt b/core/src/main/kotlin/matterlink/bridge/command/HelpCommand.kt index b1bf228..2b05691 100644 --- a/core/src/main/kotlin/matterlink/bridge/command/HelpCommand.kt +++ b/core/src/main/kotlin/matterlink/bridge/command/HelpCommand.kt @@ -2,15 +2,18 @@ package matterlink.bridge.command import matterlink.api.ApiMessage import matterlink.bridge.MessageHandlerInst +import matterlink.config.cfg import matterlink.stripColorOut -object HelpCommand : IBridgeCommand { +object HelpCommand : IBridgeCommand() { override val help: String = "Returns the help string for the given command. Syntax: help " - override val permLevel = 0.0 - override fun execute(alias: String, user: String, userId: String, server: String, args: String): Boolean { + override val permLevel: Double + get() = cfg.command.defaultPermUnauthenticated + + override fun execute(alias: String, user: String, userId: String, platform: String, uuid: String?, args: String): Boolean { val msg: String = when { args.isEmpty() -> - "Available commands: ${BridgeCommandRegistry.getCommandList(IBridgeCommand.getPermLevel(userId, server))}" + "Available commands: ${BridgeCommandRegistry.getCommandList(IBridgeCommand.getPermLevel(uuid))}" else -> args.split(" ", ignoreCase = false) .joinToString(separator = "\n") { "$it: ${BridgeCommandRegistry.getHelpString(it)}" diff --git a/core/src/main/kotlin/matterlink/bridge/command/IBridgeCommand.kt b/core/src/main/kotlin/matterlink/bridge/command/IBridgeCommand.kt index fe4f8be..ec1fea7 100644 --- a/core/src/main/kotlin/matterlink/bridge/command/IBridgeCommand.kt +++ b/core/src/main/kotlin/matterlink/bridge/command/IBridgeCommand.kt @@ -1,27 +1,50 @@ package matterlink.bridge.command +import matterlink.api.ApiMessage +import matterlink.bridge.MessageHandlerInst import matterlink.config.PermissionConfig +import matterlink.config.cfg +import matterlink.handlers.TickHandler import matterlink.instance -interface IBridgeCommand { - val help: String - val permLevel: Double +abstract class IBridgeCommand { + abstract val help: String + 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 { - instance.trace("canExecute this: $this canExecute: $userId server: $server permLevel: $permLevel") - val canExec = getPermLevel(userId, server) >= permLevel + val alias: String + get() = BridgeCommandRegistry.getName(this)!! + + + 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") return canExec } - fun validate() = true + open fun validate() = true companion object { - fun getPermLevel(userId: String, server: String): Double { - val serverMap = PermissionConfig.perms[server] ?: return 0.0 - return serverMap[userId] ?: 0.0 + fun getPermLevel(uuid: String?): Double { + if(uuid == null) return cfg.command.defaultPermUnauthenticated + return PermissionConfig.perms[uuid] ?: cfg.command.defaultPermAuthenticated } } } \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/bridge/command/IMinecraftCommandSender.kt b/core/src/main/kotlin/matterlink/bridge/command/IMinecraftCommandSender.kt index 2775019..cac9fb2 100644 --- a/core/src/main/kotlin/matterlink/bridge/command/IMinecraftCommandSender.kt +++ b/core/src/main/kotlin/matterlink/bridge/command/IMinecraftCommandSender.kt @@ -4,7 +4,7 @@ import matterlink.api.ApiMessage import matterlink.bridge.MessageHandlerInst 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 * @@ -12,12 +12,13 @@ abstract class IMinecraftCommandSender(val user: String, val userId: String, val */ 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 { - if(op) return true + if (op) return true val command = BridgeCommandRegistry[commandName] ?: return false - return command.canExecute(userId, server) + return command.canExecute(uuid) } var reply: String = "" diff --git a/core/src/main/kotlin/matterlink/bridge/command/PermCommand.kt b/core/src/main/kotlin/matterlink/bridge/command/PermCommand.kt deleted file mode 100644 index 4b5f552..0000000 --- a/core/src/main/kotlin/matterlink/bridge/command/PermCommand.kt +++ /dev/null @@ -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 `\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 - } - -} \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/bridge/command/RequestPermissionsCommand.kt b/core/src/main/kotlin/matterlink/bridge/command/RequestPermissionsCommand.kt new file mode 100644 index 0000000..47bf457 --- /dev/null +++ b/core/src/main/kotlin/matterlink/bridge/command/RequestPermissionsCommand.kt @@ -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 + ) + ) + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/command/CommandCore.kt b/core/src/main/kotlin/matterlink/command/CommandCore.kt deleted file mode 100644 index 8b78ddb..0000000 --- a/core/src/main/kotlin/matterlink/command/CommandCore.kt +++ /dev/null @@ -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 " - - fun execute(args: Array, 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!" - } - } - } - -} \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/command/CommandCoreAuth.kt b/core/src/main/kotlin/matterlink/command/CommandCoreAuth.kt new file mode 100644 index 0000000..0718b05 --- /dev/null +++ b/core/src/main/kotlin/matterlink/command/CommandCoreAuth.kt @@ -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 " + + fun execute(args: Array, 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" + } + } + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/command/CommandCoreML.kt b/core/src/main/kotlin/matterlink/command/CommandCoreML.kt new file mode 100644 index 0000000..89b91a1 --- /dev/null +++ b/core/src/main/kotlin/matterlink/command/CommandCoreML.kt @@ -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 " + + fun execute(args: Array, 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}" + } + } + } + +} \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/config/BaseConfig.kt b/core/src/main/kotlin/matterlink/config/BaseConfig.kt index 5b6f820..f9cef61 100644 --- a/core/src/main/kotlin/matterlink/config/BaseConfig.kt +++ b/core/src/main/kotlin/matterlink/config/BaseConfig.kt @@ -37,7 +37,10 @@ data class BaseConfig(val rootDir: File) { data class CommandOptions( val prefix: Char = '!', 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( @@ -175,10 +178,25 @@ data class BaseConfig(val rootDir: File) { prefix, "Prefix for MC bridge commands. Accepts a single character (not alphanumeric or /)" ), - permissionRequests = it.getOrDefault( - "permissionRequests", - permissionRequests, - "Enable the 'req' command for requestion permissions from chat" + authRequests = it.getOrDefault( + "authRequests", + authRequests, + "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" ) ) } diff --git a/core/src/main/kotlin/matterlink/config/CommandConfig.kt b/core/src/main/kotlin/matterlink/config/CommandConfig.kt index 79499dd..e21ef8a 100644 --- a/core/src/main/kotlin/matterlink/config/CommandConfig.kt +++ b/core/src/main/kotlin/matterlink/config/CommandConfig.kt @@ -54,7 +54,7 @@ object CommandConfig { "whoami" to ("this shows you some of the other response macros" to CustomCommand( 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", timeout = 200, defaultCommand = true @@ -73,7 +73,7 @@ object CommandConfig { val commands: CommandMap = hashMapOf() - fun readConfig(): Boolean { + fun loadFile() { val jankson = Jankson .builder() .registerTypeAdapter { jsonObj -> @@ -96,7 +96,7 @@ object CommandConfig { } .build() - jankson.marshaller.registerSerializer(Regex::class.java) { regex, marshaller -> + jankson.marshaller.registerSerializer(Regex::class.java) { regex, _ -> JsonPrimitive(regex.pattern) } @@ -142,7 +142,5 @@ object CommandConfig { } } configFile.writeText(nonDefaultJsonObj.toJson(true, true)) - - return true } } \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/config/IdentitiesConfig.kt b/core/src/main/kotlin/matterlink/config/IdentitiesConfig.kt new file mode 100644 index 0000000..3869bbe --- /dev/null +++ b/core/src/main/kotlin/matterlink/config/IdentitiesConfig.kt @@ -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>> + +data class AuthRequest( + val username: String, + val uuid: String, + val nonce: String, + val platform: String, + val userid: String +) + +object IdentitiesConfig { + val authRequests: Cache = 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>> = mutableMapOf() + jsonObject.forEach { uuid, jsonIdentifier -> + val identMap: MutableMap> = 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(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 + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/matterlink/config/PermissionConfig.kt b/core/src/main/kotlin/matterlink/config/PermissionConfig.kt index 02bebd4..62b399c 100644 --- a/core/src/main/kotlin/matterlink/config/PermissionConfig.kt +++ b/core/src/main/kotlin/matterlink/config/PermissionConfig.kt @@ -2,23 +2,28 @@ 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.getReified import matterlink.instance import java.io.File import java.io.FileNotFoundException +import java.util.concurrent.TimeUnit -typealias PermissionMap = MutableMap> +typealias PermissionMap = Map data class PermissionRequest( + val uuid: String, val user: String, - val platform: String, - val userId: String, + val nonce: String, val powerlevel: Double? = null ) object PermissionConfig { - val permissionRequests = mutableMapOf() + val permissionRequests: Cache = CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .build() private val jankson = Jankson .builder() .build() @@ -26,34 +31,16 @@ object PermissionConfig { private val configFile: File = baseCfg.cfgDirectory.resolve("permissions.hjson") private val default = mapOf( - "irc.esper" to mapOf( - "~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") - ) + "edd31c45-b095-49c5-a9f5-59cec4cfed8c" to 9000.0 to "Superuser" ) val perms: PermissionMap = mutableMapOf() private var jsonObject: JsonObject = JsonObject() - fun loadPermFile(): Boolean { - permissionRequests.clear() - + fun loadFile() { val defaultJsonObject = JsonObject().apply { - default.forEach { platform, userMap -> - val jsonUserMap = this.getOrDefault(platform, JsonObject()) - 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") - } + default.forEach { (uuid, level), comment -> + jsonObject.putDefault(uuid, level, comment) } } @@ -71,33 +58,27 @@ object PermissionConfig { } load(save) - - return true } private fun load(save: Boolean = true) { - perms.clear() - jsonObject.forEach { platform, jsonUserMap -> - val userMap = perms[platform] ?: mutableMapOf() - if (jsonUserMap is JsonObject) { - jsonUserMap.forEach { user, powerlevel -> - instance.info("$platform $user $powerlevel") - userMap[user] = jsonUserMap.get(Double::class.java, user) ?: 0.0 - } + val tmpPerms = mutableMapOf() + for ((uuid, powerlevel) in jsonObject) { + val tmpLevel = jsonObject.getReified(uuid) + if (tmpLevel == null) { + instance.warn("cannot parse permission uuid: $uuid level: $powerlevel") + continue } - perms[platform] = userMap + tmpPerms[uuid] = tmpLevel } + instance.info("Permissions reloaded") if (save) configFile.writeText(jsonObject.toJson(true, true)) } - fun add(platform: String, userid: String, powerlevel: Double, comment: String? = null) { - val platformObject = jsonObject.getObject(platform) ?: JsonObject() - platformObject.putDefault(userid, powerlevel, comment) - jsonObject[platform] = platformObject - + fun add(uuid: String, powerlevel: Double, comment: String? = null) { + jsonObject.putDefault(uuid, powerlevel, comment) load() } } \ No newline at end of file