ottobot-v3/src/main/kotlin/nl/voidcorp/ottobot/command/Command.kt

220 lines
8.7 KiB
Kotlin

package nl.voidcorp.ottobot.command
import net.dv8tion.jda.api.Permission
import net.dv8tion.jda.api.entities.ChannelType
import net.dv8tion.jda.api.entities.Member
import net.dv8tion.jda.api.entities.TextChannel
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import nl.voidcorp.ottobot.commands.HelpCommand
import nl.voidcorp.ottobot.commands.`fun`.*
import nl.voidcorp.ottobot.commands.debug.DebugCommand
import nl.voidcorp.ottobot.commands.debug.Flex
import nl.voidcorp.ottobot.commands.debug.PermissionLevelCommand
import nl.voidcorp.ottobot.commands.general.ATQCommand
import nl.voidcorp.ottobot.commands.general.SpillKerrieCommand
import nl.voidcorp.ottobot.commands.general.XYProblemCommand
import nl.voidcorp.ottobot.commands.management.*
import nl.voidcorp.ottobot.commands.roles.*
import nl.voidcorp.ottobot.creator
import nl.voidcorp.ottobot.database.GuildStore
import nl.voidcorp.ottobot.database.GuildStores
import org.jetbrains.exposed.sql.transactions.transaction
import java.util.*
abstract class Command(
val name: String,
val helpMesage: String = "",
val usage: String = "",
val commandLevel: CommandLevel = CommandLevel.VERIFIED,
val aliases: List<String> = emptyList(),
val location: CommandSource = CommandSource.BOTH,
val group: CommandGroup = CommandGroup.GENERAL,
val allowAnywhere: Boolean = false
) {
fun getStore(guildId: Long) = transaction {
GuildStore.find { GuildStores.guildId eq guildId }
.firstOrNull() ?: GuildStore.new { this.guildId = guildId }
}
fun onCommand(event: MessageReceivedEvent, prefix: String): CommandResult {
val starts =
(event.message.contentRaw.drop(prefix.length).trim().split("\\s".toRegex())
.first() == name) or (aliases.any {
event.message.contentRaw.drop(prefix.length).trim().split("\\s".toRegex())
.first() == it
})
return if (!starts) CommandResult.NOPE else when (location) {
CommandSource.PRIVATE -> if (event.channelType == ChannelType.PRIVATE) guildStuff(
event,
event.message.contentRaw.drop(prefix.length).trim()
) else CommandResult.NOPE
CommandSource.GUILD -> if (event.channelType == ChannelType.TEXT) privateStuff(
event,
event.message.contentRaw.drop(prefix.length).trim()
) else CommandResult.NOPE
CommandSource.BOTH ->
if (event.channelType == ChannelType.TEXT) guildStuff(
event,
event.message.contentRaw.drop(prefix.length).trim()
)
else privateStuff(event, event.message.contentRaw.drop(prefix.length).trim())
}
}
private fun guildStuff(event: MessageReceivedEvent, str: String) =
when {
!(isBotChannel(event.textChannel) or
isPermOK(CommandLevel.MODERATOR, getLevel(event.member))) -> CommandResult.CHANNEL
isPermOK(commandLevel, getLevel(event.member)) -> try {
handle(CommandMessage(event, translateCommandline(str)))
} catch (e: Exception) {
e.printStackTrace()
CommandResult.ERROR
}
else -> CommandResult.PERMISSIONS
}
private fun privateStuff(event: MessageReceivedEvent, str: String) =
if (isPermOK(commandLevel, getLevel(event.member))) try {
handle(CommandMessage(event, translateCommandline(str)))
} catch (e: Exception) {
e.printStackTrace()
CommandResult.ERROR
} else {
CommandResult.PERMISSIONS
}
abstract fun handle(event: CommandMessage): CommandResult
fun getLevel(member: Member?): CommandLevel {
if (member == null) return CommandLevel.ADMIN
val guildStore = getStore(member.guild.idLong)
return when {
member.user.idLong == creator -> CommandLevel.MAINTAINER
member.hasPermission(Permission.ADMINISTRATOR)
or transaction {
guildStore.adminRoles.map { it.adminRoleId }.intersect(member.roles.map { it.idLong }).isNotEmpty()
}
-> CommandLevel.ADMIN
transaction {
guildStore.moderatorRoles.map { it.moderatorRoleId }.intersect(member.roles.map { it.idLong })
.isNotEmpty()
}
-> CommandLevel.MODERATOR
member.roles.isNotEmpty() or guildStore.defaultVerified -> CommandLevel.VERIFIED
else -> CommandLevel.ALL
}
}
private fun isBotChannel(channel: TextChannel): Boolean {
val store = getStore(channel.guild.idLong)
return when {
allowAnywhere -> true
transaction { store.botChannels.empty() } and channel.name.contains("bot") -> true
else -> transaction { store.botChannels.map { it.botchannel }.contains(channel.idLong) }
}
}
override fun toString(): String {
return "Command(name=$name, level=$commandLevel, group=$group)"
}
companion object {
fun isPermOK(required: CommandLevel, user: CommandLevel): Boolean {
var test: CommandLevel? = required
while (test != null) {
if (test == user) {
return true
}
test = test.parent
}
return false
}
/**
* [code borrowed from ant.jar]
* Crack a command line.
* @param toProcess the command line to process.
* @return the command line broken into strings.
* An empty or null toProcess parameter results in a zero sized array.
*/
private fun translateCommandline(toProcess: String): List<String> {
if (toProcess.isEmpty()) {
//no command? no string
return emptyList()
}
// parse with a simple finite state machine
val normal = 0
val inQuote = 1
val inDoubleQuote = 2
var state = normal
val tok = StringTokenizer(toProcess, "\"\' ", true)
val result = mutableListOf<String>()
val current = StringBuilder()
var lastTokenHasBeenQuoted = false
while (tok.hasMoreTokens()) {
val nextTok = tok.nextToken()
when (state) {
inQuote -> if ("\'" == nextTok) {
lastTokenHasBeenQuoted = true
state = normal
} else {
current.append(nextTok)
}
inDoubleQuote -> if ("\"" == nextTok) {
lastTokenHasBeenQuoted = true
state = normal
} else {
current.append(nextTok)
}
else -> {
if ("\'" == nextTok) {
state = inQuote
} else if ("\"" == nextTok) {
state = inDoubleQuote
} else if (" " == nextTok) {
if (lastTokenHasBeenQuoted || current.isNotEmpty()) {
result.add(current.toString())
current.setLength(0)
}
} else {
current.append(nextTok)
}
lastTokenHasBeenQuoted = false
}
}
}
if (lastTokenHasBeenQuoted || current.isNotEmpty()) {
result.add(current.toString())
}
return result
}
private val debugCommands = setOf(DebugCommand, Flex, PermissionLevelCommand)
private val funCommands =
setOf(CatCommand, DogCommand, Echo, Nice, WeatherCommand, WoolooCommand, XKCDComicCommand)
private val generalCommands = setOf(ATQCommand, SpillKerrieCommand, XYProblemCommand)
private val managementCommands = setOf(
AddBotChannelCommand,
AdminRoleCommand,
ModeratorRoleCommand,
RemoveAdminRoleCommand,
RemoveBotChannelCommand,
RemoveModeratorRoleCommand,
SetPrefixCommand,
SetVerifiedCommand
)
private val rolesCommands =
setOf(AddRoleCommand, JoinRoleCommand, LeaveRole, MoveRoleCommand, RemoveRoleCommand)
val allCommands =
setOf<Command>(HelpCommand).union(debugCommands).union(funCommands).union(generalCommands).union(
managementCommands
).union(rolesCommands)
}
}