io.github.freya022.botcommands.internal.commands.text.CommandEventImpl.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of BotCommands Show documentation
Show all versions of BotCommands Show documentation
A Kotlin-first (and Java) framework that makes creating Discord bots a piece of cake, using the JDA library.
package io.github.freya022.botcommands.internal.commands.text
import dev.minn.jda.ktx.coroutines.await
import io.github.freya022.botcommands.api.commands.ratelimit.CancellableRateLimit
import io.github.freya022.botcommands.api.commands.text.CommandEvent
import io.github.freya022.botcommands.api.commands.text.exceptions.BadIdException
import io.github.freya022.botcommands.api.commands.text.exceptions.NoIdException
import io.github.freya022.botcommands.api.core.BContext
import io.github.freya022.botcommands.api.core.utils.loggerOf
import io.github.freya022.botcommands.api.localization.text.LocalizableTextCommand
import io.github.freya022.botcommands.api.utils.RichTextFinder
import io.github.freya022.botcommands.api.utils.RichTextFinder.RichText
import io.github.freya022.botcommands.api.utils.RichTextType
import io.github.freya022.botcommands.internal.commands.text.TextUtils.findEntity
import io.github.freya022.botcommands.internal.utils.throwArgument
import io.github.oshai.kotlinlogging.KotlinLogging
import net.dv8tion.jda.api.entities.*
import net.dv8tion.jda.api.entities.Message.MentionType
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel
import net.dv8tion.jda.api.entities.emoji.CustomEmoji
import net.dv8tion.jda.api.entities.emoji.Emoji
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.exceptions.ErrorResponseException
import net.dv8tion.jda.api.requests.ErrorResponse
import net.dv8tion.jda.internal.utils.Helpers
private val logger = KotlinLogging.loggerOf()
internal class CommandEventImpl private constructor(
context: BContext,
private val event: MessageReceivedEvent,
argumentsStr: String,
private val arguments: MutableList,
cancellableRateLimit: CancellableRateLimit,
localizableTextCommand: LocalizableTextCommand
) : CommandEvent(context, event, argumentsStr, cancellableRateLimit, localizableTextCommand) {
override fun getArguments(): List = arguments
override fun hasNext(clazz: Class): Boolean {
if (arguments.isEmpty()) return false
val o = arguments.first()
return clazz.isAssignableFrom(o.javaClass)
}
@Suppress("UNCHECKED_CAST")
override fun peekArgument(clazz: Class): T {
if (arguments.isEmpty()) throw NoSuchElementException()
val o = arguments.first()
return when {
clazz.isAssignableFrom(o.javaClass) -> o as T
else -> throw NoSuchElementException()
}
}
@Suppress("UNCHECKED_CAST")
override fun nextArgument(clazz: Class): T {
if (arguments.isEmpty()) throw NoSuchElementException()
val o = arguments.removeFirst()
return when {
clazz.isAssignableFrom(o.javaClass) -> o as T
else -> throw NoSuchElementException()
}
}
@Suppress("UNCHECKED_CAST")
@Throws(NoIdException::class, BadIdException::class)
override fun resolveNext(vararg classes: Class<*>): T {
if (arguments.isEmpty()) {
throw NoIdException()
}
val o = arguments.removeFirst()
for (c in classes) {
if (c.isAssignableFrom(o.javaClass)) {
return o as T
}
}
if (o !is String) throw NoIdException()
for (clazz in classes) {
try {
//See net.dv8tion.jda.internal.utils.Checks#isSnowflake(String)
if (o.length > 20 || !Helpers.isNumeric(o)) {
throw BadIdException()
}
val id = o.toLong()
val mentionable = when (clazz) {
Role::class.java -> guild.getRoleById(id)
User::class.java -> findEntity(id, event.message.mentions.users) { jda.retrieveUserById(id).complete() }
Member::class.java -> findEntity(id, event.message.mentions.members) { guild.retrieveMemberById(id).complete() }
TextChannel::class.java -> guild.getTextChannelById(id)
CustomEmoji::class.java -> jda.getEmojiById(id)
else -> throwArgument("${clazz.simpleName} is not a valid IMentionable class")
}
if (mentionable != null) {
return mentionable as T
}
} catch (ignored: NumberFormatException) {
throw BadIdException()
} catch (e: ErrorResponseException) {
if (e.errorResponse == ErrorResponse.UNKNOWN_USER || e.errorResponse == ErrorResponse.UNKNOWN_MEMBER) {
throw BadIdException()
} else {
throw e
}
}
}
throw BadIdException()
}
companion object {
private val idRegex = Regex("(\\d+)")
private operator fun RichText.component1(): String = substring
private suspend fun tryGetId(mention: String, idToMentionableFunc: suspend (Long) -> IMentionable?): IMentionable? {
return idRegex.find(mention)
?.value
?.toLong()
?.let { idToMentionableFunc(it) }
}
private operator fun RichText.component2(): RichTextType = type
internal suspend fun create(
context: BContext,
event: MessageReceivedEvent,
argumentsStr: String,
cancellableRateLimit: CancellableRateLimit,
localizableTextCommand: LocalizableTextCommand
): CommandEventImpl {
val arguments: MutableList = arrayListOf()
RichTextFinder(argumentsStr, true, false, true, false)
.normalMentionMap
.values
.forEach { (substring, type) ->
processText(arguments, event.guild, substring, type)
}
return CommandEventImpl(context, event, argumentsStr, arguments, cancellableRateLimit, localizableTextCommand)
}
private suspend fun processText(arguments: MutableList, guild: Guild, substring: String, type: RichTextType) {
if (substring.isBlank()) return
val mentionType = type.mentionType
if (mentionType != null || type == RichTextType.UNICODE_EMOTE) {
val mentionable: Any? = when {
type == RichTextType.UNICODE_EMOTE -> Emoji.fromUnicode(substring)
mentionType == MentionType.ROLE -> tryGetId(substring) { id: Long ->
guild.getRoleById(id)
}
mentionType == MentionType.CHANNEL -> tryGetId(substring) { id: Long ->
guild.getTextChannelById(id)
}
mentionType == MentionType.EMOJI -> MentionType.EMOJI.pattern.toRegex().find(substring)?.let {
it.groups[2]?.value?.let { id -> guild.getEmojiById(id) }
}
mentionType == MentionType.USER -> tryGetId(substring) { id: Long ->
guild.jda.retrieveUserById(id).await()
}
else -> null
}
if (mentionable != null) {
arguments.add(mentionable)
} else {
logger.error {
"Unresolved mentionable : '${substring}' of type ${type.name}, maybe you haven't enabled a cache flag / intent ?"
}
}
} else if (substring.isNotEmpty()) {
arguments.addAll(substring.split(' ').dropLastWhile { it.isEmpty() })
}
}
}
}