All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.freya022.botcommands.internal.commands.text.CommandEventImpl.kt Maven / Gradle / Ivy

Go to download

A Kotlin-first (and Java) framework that makes creating Discord bots a piece of cake, using the JDA library.

There is a newer version: 3.0.0-alpha.18
Show newest version
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() })
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy