io.github.freya022.botcommands.internal.parameters.resolvers.ChannelResolverFactory.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.parameters.resolvers
import dev.minn.jda.ktx.messages.reply_
import io.github.freya022.botcommands.api.commands.text.BaseCommandEvent
import io.github.freya022.botcommands.api.core.BContext
import io.github.freya022.botcommands.api.core.reflect.ParameterWrapper
import io.github.freya022.botcommands.api.core.service.annotations.ResolverFactory
import io.github.freya022.botcommands.api.core.utils.enumSetOf
import io.github.freya022.botcommands.api.core.utils.isSubclassOf
import io.github.freya022.botcommands.api.core.utils.simpleNestedName
import io.github.freya022.botcommands.api.parameters.ClassParameterResolver
import io.github.freya022.botcommands.api.parameters.ParameterResolverFactory
import io.github.freya022.botcommands.api.parameters.resolvers.ComponentParameterResolver
import io.github.freya022.botcommands.api.parameters.resolvers.SlashParameterResolver
import io.github.freya022.botcommands.api.parameters.resolvers.TextParameterResolver
import io.github.freya022.botcommands.internal.commands.application.slash.SlashCommandInfo
import io.github.freya022.botcommands.internal.commands.text.TextCommandVariation
import io.github.freya022.botcommands.internal.components.handler.ComponentDescriptor
import io.github.freya022.botcommands.internal.utils.throwInternal
import io.github.oshai.kotlinlogging.KotlinLogging
import net.dv8tion.jda.api.entities.channel.Channel
import net.dv8tion.jda.api.entities.channel.ChannelType
import net.dv8tion.jda.api.entities.channel.ChannelType.UNKNOWN
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel
import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent
import net.dv8tion.jda.api.events.message.MessageReceivedEvent
import net.dv8tion.jda.api.interactions.commands.CommandInteractionPayload
import net.dv8tion.jda.api.interactions.commands.OptionMapping
import net.dv8tion.jda.api.interactions.commands.OptionType
import java.util.*
import java.util.regex.Pattern
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
interface IChannelResolver {
val channelTypes: EnumSet
}
@ResolverFactory
internal class ChannelResolverFactory(private val context: BContext) : ParameterResolverFactory(LimitedChannelResolver::class) {
// Only for slash commands where Discord always provides the data
// Channels such as threads are not easily resolvable when used in text commands / components,
// let the user use the channel id + thread id to find threads themselves
internal open class LimitedChannelResolver(
protected val type: Class,
override val channelTypes: EnumSet
) : ClassParameterResolver(GuildChannel::class),
SlashParameterResolver,
IChannelResolver {
//region Slash
override val optionType: OptionType = OptionType.CHANNEL
override suspend fun resolveSuspend(
info: SlashCommandInfo,
event: CommandInteractionPayload,
optionMapping: OptionMapping
): GuildChannel {
val channel = optionMapping.asChannel
if (type.isInstance(channel)) {
return type.cast(channel)
} else {
throwInternal("A ${optionMapping.channelType} channel option could not be cast into ${type.simpleNestedName}, channel: $channel")
}
}
//endregion
}
internal class ChannelResolver(private val context: BContext, type: Class, channelTypes: EnumSet) :
LimitedChannelResolver(type, channelTypes),
TextParameterResolver,
ComponentParameterResolver {
//region Text
override val pattern: Pattern = channelPattern
override val testExample: String = "<#1234>"
override fun getHelpExample(parameter: KParameter, event: BaseCommandEvent, isID: Boolean): String =
event.channel.asMention
override suspend fun resolveSuspend(
variation: TextCommandVariation,
event: MessageReceivedEvent,
args: Array
): GuildChannel? = event.guild.getChannelById(type, args[0]!!)
//endregion
//region Component
override suspend fun resolveSuspend(
descriptor: ComponentDescriptor,
event: GenericComponentInteractionCreateEvent,
arg: String
): GuildChannel? {
val guild = event.guild ?: throwInternal(descriptor.function, "Cannot resolve a Channel outside of a Guild")
val channel = guild.getChannelById(type, arg)
if (channel == null) {
logger.trace { "Could not find channel of type ${type.simpleNestedName} and id $arg" }
event.reply_(context.getDefaultMessages(event).resolverChannelNotFoundMsg, ephemeral = true).queue()
}
return channel
}
//endregion
companion object {
private val channelPattern = Pattern.compile("(?:<#)?(\\d+)>?")
private val logger = KotlinLogging.logger { }
}
}
override val supportedTypesStr: List = listOf("")
@Suppress("UNCHECKED_CAST")
override fun isResolvable(parameter: ParameterWrapper): Boolean {
val erasure = parameter.erasure
if (!erasure.isSubclassOf()) return false
erasure as KClass
//TODO future versions of JDA may have a way to disable channel caches (types would be configurable)
// Only empty if the type is a GuildChannel but is not a concrete interface
return erasure == GuildChannel::class || channelTypesFrom(erasure.java).isNotEmpty()
}
@Suppress("UNCHECKED_CAST")
override fun get(parameter: ParameterWrapper): LimitedChannelResolver {
val erasure = parameter.erasure as KClass
val channelTypes = channelTypesFrom(erasure.java)
if (erasure.isSubclassOf()) {
return LimitedChannelResolver(erasure.java, channelTypes)
}
return ChannelResolver(context, erasure.java, channelTypes)
}
}
private fun channelTypesFrom(clazz: Class): EnumSet {
return ChannelType.entries.filterTo(enumSetOf()) { type -> type.getInterface() == clazz && type != UNKNOWN }
}