Add remaining role commands. Finalize help command. Move commands into proper packages. Make the levels into an enum. Properly implement settings.

merge-requests/1/head
Julius de Jeu 2019-05-23 22:29:36 +02:00
parent 94970a3e6a
commit 003f27c5e3
22 changed files with 224 additions and 117 deletions

View File

@ -15,9 +15,10 @@ abstract class Command(
val name: String,
val helpMesage: String = "",
val usage: String = "",
val commandLevel: CommandLevel = CommandGroup.VERIFIED,
val commandLevel: CommandLevel = CommandLevel.VERIFIED,
val aliases: List<String> = emptyList(),
val location: CommandSource = CommandSource.BOTH
val location: CommandSource = CommandSource.BOTH,
val group: CommandGroup = CommandGroup.GENERAL
) {
@Autowired
lateinit var repo: GuildRepo
@ -69,23 +70,21 @@ abstract class Command(
abstract fun handle(event: CommandMessage): CommandResult
fun getLevel(member: Member?): CommandLevel {
if (member == null) return CommandGroup.ADMIN
if (member == null) return CommandLevel.ADMIN
val guildStore = repo.findByGuildId(member.guild.idLong) ?: GuildStore(-1)
return when {
member.user.idLong == creator.idLong -> CommandGroup.MAINTAINER
member.user.idLong == creator.idLong -> CommandLevel.MAINTAINER
member.hasPermission(Permission.ADMINISTRATOR)
or guildStore.adminRoles.intersect(member.roles.map { it.idLong }).isNotEmpty()
-> CommandGroup.ADMIN
-> CommandLevel.ADMIN
guildStore.moderatorRoles.intersect(member.roles.map { it.idLong }).isNotEmpty()
-> CommandGroup.MODERATOR
member.roles.isNotEmpty() or guildStore.defaultVerified -> CommandGroup.VERIFIED
else -> CommandGroup.ALL
-> CommandLevel.MODERATOR
member.roles.isNotEmpty() or guildStore.defaultVerified -> CommandLevel.VERIFIED
else -> CommandLevel.ALL
}
}
companion object {
var settings = CommandSettings()
fun isPermOK(required: CommandLevel, user: CommandLevel): Boolean {
var test: CommandLevel? = required

View File

@ -1,9 +1,9 @@
package nl.voidcorp.discord.command
object CommandGroup {
val MAINTAINER = CommandLevel("Maintainer")
val ADMIN = CommandLevel("Administrator", MAINTAINER)
val MODERATOR = CommandLevel("Moderator", ADMIN)
val VERIFIED = CommandLevel("Verified", MODERATOR)
val ALL = CommandLevel("Unverified", VERIFIED)
enum class CommandGroup {
GENERAL,
FUN,
ROLES,
ADMIN,
VeRY_hIdden_CaTegoRY_LoL,
}

View File

@ -1,3 +1,9 @@
package nl.voidcorp.discord.command
data class CommandLevel(val levelName: String, val parent: CommandLevel? = null)
enum class CommandLevel(val levelName: String, val parent: CommandLevel? = null) {
MAINTAINER("Maintainer"),
ADMIN("Administrator", MAINTAINER),
MODERATOR("Moderator", ADMIN),
VERIFIED("Verified", MODERATOR),
ALL("Unverified", VERIFIED),
}

View File

@ -16,7 +16,8 @@ data class CommandMessage(
fun reply(message: Message) = event.channel.sendMessage(message).queue()
fun reply(message: String) = reply(MessageBuilder(message).build())
fun reply(message: String) =
MessageBuilder(message).buildAll(MessageBuilder.SplitPolicy.SPACE).forEach { reply(it) }
fun reply(messageEmbed: MessageEmbed) = event.channel.sendMessage(messageEmbed).queue()

View File

@ -2,6 +2,7 @@ package nl.voidcorp.discord.command
import net.dv8tion.jda.api.entities.Guild
open class CommandSettings(val prefix: String = "?") {
open fun getPrefix(guild: Guild) = prefix
}
abstract class CommandSettings(open val prefix: String) {
abstract fun getPrefix(guild: Guild): String
}

View File

@ -0,0 +1,18 @@
package nl.voidcorp.discord.command
import net.dv8tion.jda.api.entities.Guild
import nl.voidcorp.discord.storage.GuildRepo
import nl.voidcorp.discord.storage.GuildStore
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
@Service
class CommandSettingsImpl(override val prefix: String = "?") : CommandSettings(prefix) {
@Autowired
lateinit var guildRepo: GuildRepo
override fun getPrefix(guild: Guild): String {
val store = guildRepo.findByGuildId(guild.idLong) ?: GuildStore(guild.idLong)
return store.prefix
}
}

View File

@ -1,38 +0,0 @@
package nl.voidcorp.discord.commands
import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.storage.GuildRepo
import nl.voidcorp.discord.storage.GuildStore
import org.springframework.stereotype.Service
@Service
class AddRoleCommand(val guildRepo: GuildRepo) : Command(
"addrole",
usage = "addrole rolename:id [rolename:id...]",
commandLevel = CommandGroup.MODERATOR,
location = CommandSource.GUILD
) {
val regex = "([\\w\\d-_+]+):(?:<@&!?)?(\\d+)>?".toRegex()
override fun handle(event: CommandMessage): CommandResult {
val guild = guildRepo.findByGuildId(event.guild.idLong) ?: GuildStore(event.guild.idLong)
if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS
val l = mutableListOf<String>()
for (p in event.params.drop(1)) {
val res = regex.matchEntire(p)
if (res != null && res.groupValues.size == 3) {
if (event.guild.getRoleById(res.groupValues[2]) != null) {
guild.roleMap[res.groupValues[1]] = res.groupValues[2].toLong()
l += res.groupValues[1]
} else {
event.reply("There is no role with id `${res.groupValues[2]}`")
}
}
}
guildRepo.save(guild)
if (l.isNotEmpty())
event.reply(l.joinToString(prefix = "Added the following groups: ") { "`$it`" })
else
event.reply("Added no groups...")
return CommandResult.SUCCESS
}
}

View File

@ -1,17 +1,45 @@
package nl.voidcorp.discord.commands
import net.dv8tion.jda.api.EmbedBuilder
import nl.voidcorp.discord.command.Command
import nl.voidcorp.discord.command.CommandLevel
import nl.voidcorp.discord.command.CommandMessage
import nl.voidcorp.discord.command.CommandResult
import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Service
@Service
class HelpCommand(@Lazy private val list: List<Command>) : Command("help") {
class HelpCommand(@Lazy private val list: List<Command>) : Command("help", commandLevel = CommandLevel.ALL) {
override fun handle(event: CommandMessage): CommandResult {
event.reply("cursed_help")
val commands = list.filter { isPermOK(it.commandLevel, getLevel(event.member)) }
event.reply(commands.joinToString { "`${it.name}`" })
val builder =
EmbedBuilder().setAuthor(event.message.jda.selfUser.name, null, event.message.jda.selfUser.avatarUrl)
.setColor(event.guild.selfMember.color)
if (event.params.drop(1).isEmpty()) {
builder.setTitle("Available Commands")
list.filter { isPermOK(it.commandLevel, getLevel(event.member)) }
.groupBy({ it.group }, { it.name }).toSortedMap()
.forEach { (k, v) ->
builder.addField(k.name.capitalize(), v.joinToString(separator = "\n"), false)
}
event.reply(builder.build())
} else {
val command = event.params.drop(1).first()
if (list.none { it.name == command }) {
event.reply("I have never heard of the command $command...")
} else if (list.filter { isPermOK(it.commandLevel, getLevel(event.member)) }.none { it.name == command }) {
event.reply("Sorry, I can't tell you about a command you shouldn't have access to...")
} else {
val cmd = list.filter { isPermOK(it.commandLevel, getLevel(event.member)) }.first { it.name == command }
builder.setTitle(command).addField("Info", cmd.helpMesage, false)
.addField("Usage", "`${cmd.usage.ifBlank { command }}`", true)
.addField("Aliases", cmd.aliases.joinToString(), true)
.addField("Minimum access level", cmd.commandLevel.levelName, true)
.addField("Group", cmd.group.name.capitalize(), true)
event.reply(builder.build())
}
}
return CommandResult.SUCCESS
}
}

View File

@ -1,19 +0,0 @@
package nl.voidcorp.discord.commands
import nl.voidcorp.discord.command.Command
import nl.voidcorp.discord.command.CommandMessage
import nl.voidcorp.discord.command.CommandResult
import nl.voidcorp.discord.storage.GuildRepo
import nl.voidcorp.discord.storage.GuildStore
import org.springframework.stereotype.Service
class ListRolesCommand : Command("listroles", aliases = listOf("roles")) {
override fun handle(event: CommandMessage): CommandResult {
val guild = repo.findByGuildId(event.guild.idLong) ?: GuildStore(event.guild.idLong)
if (guild.roleMap.isNotEmpty())
event.reply(guild.roleMap.keys.joinToString(prefix = "The available roles are: ") { "`$it`" })
else
event.reply("There are no roles to pick here...")
return CommandResult.SUCCESS
}
}

View File

@ -1,14 +0,0 @@
package nl.voidcorp.discord.commands
import nl.voidcorp.discord.command.Command
import nl.voidcorp.discord.command.CommandMessage
import nl.voidcorp.discord.command.CommandResult
import org.springframework.stereotype.Service
@Service
class Nice : Command("nice") {
override fun handle(event: CommandMessage): CommandResult {
event.reply("_nice_")
return CommandResult.SUCCESS
}
}

View File

@ -1,14 +1,12 @@
package nl.voidcorp.discord.commands
package nl.voidcorp.discord.commands.debug
import nl.voidcorp.discord.command.Command
import nl.voidcorp.discord.command.CommandGroup
import nl.voidcorp.discord.command.CommandMessage
import nl.voidcorp.discord.command.CommandResult
import nl.voidcorp.discord.command.*
import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Service
@Service
class DebugCommand(@Lazy private val list: List<Command>) : Command("debug", commandLevel = CommandGroup.MAINTAINER) {
class DebugCommand(@Lazy private val list: List<Command>) :
Command("debug", commandLevel = CommandLevel.MAINTAINER, group = CommandGroup.`VERY HIDDEN CATEGORY lol`) {
override fun handle(event: CommandMessage): CommandResult {
event.reply("DebugInfo")
event.reply("Commands found: ${list.size + 1}")

View File

@ -1,11 +1,11 @@
package nl.voidcorp.discord.commands
package nl.voidcorp.discord.commands.debug
import nl.voidcorp.discord.command.*
import org.springframework.stereotype.Service
@Service
class PermissionLevelCommand :
Command("permissions", location = CommandSource.GUILD, commandLevel = CommandGroup.ALL) {
Command("permissions", location = CommandSource.GUILD, commandLevel = CommandLevel.ALL) {
override fun handle(event: CommandMessage): CommandResult {
event.reply("Your highest permission level is `${getLevel(event.member!!).levelName}`")
return CommandResult.SUCCESS

View File

@ -1,12 +1,13 @@
package nl.voidcorp.discord.commands
package nl.voidcorp.discord.commands.`fun`
import nl.voidcorp.discord.command.Command
import nl.voidcorp.discord.command.CommandGroup
import nl.voidcorp.discord.command.CommandMessage
import nl.voidcorp.discord.command.CommandResult
import org.springframework.stereotype.Service
@Service
class Echo : Command("echo", usage = "echo whatever") {
class Echo : Command("echo", usage = "echo whatever", group = CommandGroup.FUN) {
override fun handle(event: CommandMessage): CommandResult {
val msg = event.params.drop(1).joinToString(" ")
if (msg.isEmpty())

View File

@ -0,0 +1,20 @@
package nl.voidcorp.discord.commands.`fun`
import nl.voidcorp.discord.command.Command
import nl.voidcorp.discord.command.CommandGroup
import nl.voidcorp.discord.command.CommandMessage
import nl.voidcorp.discord.command.CommandResult
import org.springframework.stereotype.Service
import kotlin.random.Random
@Service
class Nice : Command("nice", group = CommandGroup.FUN, helpMesage = "_nice_") {
override fun handle(event: CommandMessage): CommandResult {
if (Random(System.currentTimeMillis()).nextInt(100) < 90) {
event.reply("_nice_")
} else {
event.reply("0")
}
return CommandResult.SUCCESS
}
}

View File

@ -0,0 +1,44 @@
package nl.voidcorp.discord.commands.roles
import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.storage.GuildStore
import org.springframework.stereotype.Service
@Service
class AddRoleCommand : Command(
"addrole",
usage = "addrole rolename:id [rolename:id...]",
commandLevel = CommandLevel.MODERATOR,
location = CommandSource.GUILD,
group = CommandGroup.ADMIN
) {
val regex = "([\\w\\d-_+]+):(?:<@&!?)?(\\d+)>?".toRegex()
override fun handle(event: CommandMessage): CommandResult {
val guild = repo.findByGuildId(event.guild.idLong) ?: GuildStore(event.guild.idLong)
if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS
val l = mutableListOf<String>()
for (p in event.params.drop(1)) {
val res = regex.matchEntire(p)
if (res != null && res.groupValues.size == 3) {
when {
res.groupValues[1].length > 200 -> event.reply("Please use a shorter role name in the future (200 char max)")
guild.roleMap.containsKey(res.groupValues[1]) ->
event.reply(
"A role with the key `${res.groupValues[1]
}` is already mapped, if you want to remap it use `${guild.prefix}removerole ${res.groupValues[1]}` first..."
)
event.guild.getRoleById(res.groupValues[2]) != null -> {
guild.roleMap[res.groupValues[1]] = res.groupValues[2].toLong()
l += res.groupValues[1]
}
else -> event.reply("There is no role with id `${res.groupValues[2]}`")
}
}
}
repo.save(guild)
if (l.isNotEmpty())
event.reply(l.joinToString(prefix = "Added the following groups: ") { "`$it`" })
return CommandResult.SUCCESS
}
}

View File

@ -1,4 +1,4 @@
package nl.voidcorp.discord.commands
package nl.voidcorp.discord.commands.roles
import net.dv8tion.jda.api.entities.Role
import nl.voidcorp.discord.command.*
@ -10,9 +10,10 @@ class JoinRoleCommand :
Command(
"joinrole",
aliases = listOf("role"),
commandLevel = CommandGroup.ALL,
commandLevel = CommandLevel.ALL,
usage = "joinrole [rolename]",
location = CommandSource.GUILD
location = CommandSource.GUILD,
group = CommandGroup.ROLES
) {
override fun handle(event: CommandMessage): CommandResult {
val guild = repo.findByGuildId(event.guild.idLong) ?: GuildStore(event.guild.idLong)

View File

@ -0,0 +1,30 @@
package nl.voidcorp.discord.commands.roles
import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.storage.GuildStore
import org.springframework.stereotype.Service
@Service
class LeaveRole : Command(
"leaverole",
aliases = listOf("derole"),
location = CommandSource.GUILD,
usage = "leaverole rolename",
group = CommandGroup.ROLES,
commandLevel = CommandLevel.VERIFIED
) {
override fun handle(event: CommandMessage): CommandResult {
if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS
val guild = repo.findByGuildId(event.guild.idLong) ?: GuildStore(event.guild.idLong)
val toRemove = guild.roleMap.filterKeys { it in event.params.drop(1) }
// toRemove.forEach { guild.roleMap.remove(it) }
// repo.save(guild)
val roleLongs =
event.member!!.roles.map { it.idLong }.intersect(toRemove.values)
val remove = roleLongs.map { event.guild.getRoleById(it) }.filter { it != null }
val remmed = toRemove.filterValues { it in roleLongs }.keys
event.guild.controller.removeRolesFromMember(event.member, remove).queue()
event.reply("Removed the roles ${remmed.joinToString { "`$it`" }}")
return CommandResult.SUCCESS
}
}

View File

@ -0,0 +1,25 @@
package nl.voidcorp.discord.commands.roles
import nl.voidcorp.discord.command.*
import nl.voidcorp.discord.storage.GuildStore
import org.springframework.stereotype.Service
@Service
class RemoveRoleCommand : Command(
"removerole",
usage = "removerole rolename",
commandLevel = CommandLevel.MODERATOR,
location = CommandSource.GUILD,
group = CommandGroup.ADMIN
) {
override fun handle(event: CommandMessage): CommandResult {
if (event.params.drop(1).isEmpty()) return CommandResult.PARAMETERS
val guild = repo.findByGuildId(event.guild.idLong) ?: GuildStore(event.guild.idLong)
val toRemove = guild.roleMap.keys.intersect(event.params.drop(1))
toRemove.forEach { guild.roleMap.remove(it) }
repo.save(guild)
event.reply("Removed ${toRemove.size} roles from the list. ")
event.reply(toRemove.joinToString(prefix = "(", postfix = ")") { "`$it`" })
return CommandResult.SUCCESS
}
}

View File

@ -7,6 +7,7 @@ import net.dv8tion.jda.api.hooks.ListenerAdapter
import nl.voidcorp.discord.command.Command
import nl.voidcorp.discord.command.CommandMessage
import nl.voidcorp.discord.command.CommandResult
import nl.voidcorp.discord.command.CommandSettings
import nl.voidcorp.discord.creator
import nl.voidcorp.discord.logger
import nl.voidcorp.discord.storage.GuildRepo
@ -16,7 +17,8 @@ import org.springframework.stereotype.Service
@Service
class CommandListener(
@Autowired val guildRepo: GuildRepo,
@Autowired final val commands: Set<Command>
@Autowired final val commands: Set<Command>,
@Autowired val commandSettings: CommandSettings
) : ListenerAdapter() {
init {
@ -28,13 +30,13 @@ class CommandListener(
val prefix: String = when {
event.message.contentRaw.startsWith("<@${event.jda.selfUser.id}>") -> "<@${event.jda.selfUser.id}>"
event.message.contentRaw.startsWith("<@!${event.jda.selfUser.id}>") -> "<@!${event.jda.selfUser.id}>"
event.channelType == ChannelType.TEXT -> Command.settings.getPrefix(event.guild)
event.channelType == ChannelType.PRIVATE -> Command.settings.prefix
event.channelType == ChannelType.TEXT -> commandSettings.getPrefix(event.guild)
event.channelType == ChannelType.PRIVATE -> commandSettings.prefix
else -> return
}
if (!event.message.contentRaw.startsWith(prefix)) return
if (!event.message.contentRaw.startsWith(prefix) or (event.message.contentRaw.length == prefix.length)) return
val res = commands.map { it to it.onCommand(event, prefix) }.filter { it.second != CommandResult.NOPE }
if (res.size > 1) {

View File

@ -2,7 +2,10 @@ package nl.voidcorp.discord.storage
import org.hibernate.annotations.LazyCollection
import org.hibernate.annotations.LazyCollectionOption
import javax.persistence.*
import javax.persistence.ElementCollection
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id
@Entity
@ -12,6 +15,7 @@ data class GuildStore(
@ElementCollection @LazyCollection(LazyCollectionOption.FALSE) var adminRoles: MutableList<Long> = mutableListOf(),
var defaultVerified: Boolean = false,
@ElementCollection @LazyCollection(LazyCollectionOption.FALSE) var roleMap: MutableMap<String, Long> = mutableMapOf(),
var prefix: String = "?",
@Id
@GeneratedValue
var id: Long? = null

View File

@ -3,7 +3,7 @@ spring.datasource.platform=h2
spring.datasource.url=jdbc:h2:mem:
#spring.datasource.username=root
#spring.datasource.password=changeme
spring.jpa.show-sql=true
#spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

View File

@ -1,7 +1,7 @@
package nl.voidcorp.discord
import nl.voidcorp.discord.command.Command
import nl.voidcorp.discord.command.CommandGroup
import nl.voidcorp.discord.command.CommandLevel
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@ -9,6 +9,6 @@ class CommandTest {
@Test
fun `Test permission slide Verified into Admin`() {
Assertions.assertTrue(Command.isPermOK(CommandGroup.VERIFIED, CommandGroup.ADMIN))
Assertions.assertTrue(Command.isPermOK(CommandLevel.VERIFIED, CommandLevel.ADMIN))
}
}