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

eu.vendeli.tgbot.utils.BotUtils.kt Maven / Gradle / Ivy

@file:Suppress("MatchingDeclarationName")

package eu.vendeli.tgbot.utils

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import eu.vendeli.tgbot.core.TelegramUpdateHandler
import eu.vendeli.tgbot.core.TelegramUpdateHandler.Companion.logger
import eu.vendeli.tgbot.interfaces.Action
import eu.vendeli.tgbot.interfaces.MultipleResponse
import eu.vendeli.tgbot.interfaces.SimpleAction
import eu.vendeli.tgbot.interfaces.TgAction
import eu.vendeli.tgbot.types.internal.Activity
import eu.vendeli.tgbot.types.internal.StructuredRequest
import eu.vendeli.tgbot.types.internal.UpdateType
import eu.vendeli.tgbot.types.internal.configuration.RateLimits
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import java.lang.reflect.Method
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn

/**
 * Creates new coroutine context from parent one and adds supervisor job.
 *
 * @param parentContext Context that will be merged with the created one.
 */
internal class NewCoroutineContext(parentContext: CoroutineContext) : CoroutineScope {
    override val coroutineContext: CoroutineContext =
        parentContext + SupervisorJob(parentContext[Job]) + CoroutineName("TgBot")
}

@Suppress("CyclomaticComplexMethod", "NestedBlockDepth")
internal fun TelegramUpdateHandler.parseCommand(
    text: String,
): StructuredRequest = with(bot.config.commandParsing) {
    var state = ParserState.READING_COMMAND
    var command = ""
    val params = mutableMapOf()

    var paramNameBuffer = ""
    var paramValBuffer = ""

    text.forEach { i ->
        when (state) {
            ParserState.READING_COMMAND -> {
                if (i == commandDelimiter || (restrictSpacesInCommands && i == ' ')) {
                    state = ParserState.READING_PARAM_NAME
                } else command += i
            }

            ParserState.READING_PARAM_NAME -> {
                when (i) {
                    parameterValueDelimiter -> {
                        state = ParserState.READING_PARAM_VALUE
                    }

                    parametersDelimiter -> {
                        params["param_${params.size + 1}"] = paramNameBuffer
                        paramNameBuffer = ""
                    }

                    else -> paramNameBuffer += i
                }
            }

            ParserState.READING_PARAM_VALUE -> {
                if (i == parametersDelimiter) {
                    params[paramNameBuffer] = paramValBuffer
                    paramNameBuffer = ""
                    paramValBuffer = ""
                    state = ParserState.READING_PARAM_NAME
                } else paramValBuffer += i
            }
        }
    }
    if (state == ParserState.READING_PARAM_VALUE) {
        params[paramNameBuffer] = paramValBuffer
    } else if (state == ParserState.READING_PARAM_NAME) {
        params["param_${params.size + 1}"] = paramNameBuffer
    }

    return StructuredRequest(command = command, params = params)
}

internal suspend inline fun Method.invokeSuspend(obj: Any, vararg args: Any?): Any? =
    suspendCoroutineUninterceptedOrReturn { cont ->
        invoke(obj, *args, cont)
    }

internal suspend inline fun TelegramUpdateHandler.checkIsLimited(
    limits: RateLimits,
    telegramId: Long?,
    actionId: String? = null,
): Boolean {
    if (limits.period == 0L && limits.rate == 0L || telegramId == null) return false

    logger.debug { "Checking the request for exceeding the limits${if (actionId != null) " for $actionId}" else ""}." }
    if (rateLimiter.isLimited(limits, telegramId, actionId)) {
        val loggingTail = if (actionId != null) " for $actionId}" else ""
        logger.info { "User #$telegramId has exceeded the request limit$loggingTail." }
        rateLimiter.exceededLimitResponse(telegramId, bot)
        return true
    }
    return false
}

/**
 * Function for mapping text with a specific command or input.
 *
 * @param text
 * @param command true to search in commands or false to search among inputs. Default - true.
 * @return [Activity] if actions was found or null.
 */
internal fun TelegramUpdateHandler.findAction(
    text: String,
    command: Boolean = true,
    updateType: UpdateType,
): Activity? {
    val message = parseCommand(text)
    val invocation = if (command) actions?.commands else {
        actions?.inputs
    }?.get(message.command)
    if (invocation != null && command && updateType.scope !in invocation.scope)
        return null

    return if (invocation != null) Activity(
        id = message.command,
        invocation = invocation,
        parameters = message.params,
        rateLimits = invocation.rateLimits,
    ) else null
}

@Suppress("UnusedReceiverParameter")
internal inline fun  SimpleAction>.getInnerType(): Class =
    Type::class.java

@Suppress("UnusedReceiverParameter")
internal inline fun  Action>.getInnerType(): Class =
    Type::class.java

@Suppress("UnusedReceiverParameter")
internal inline fun  TgAction.getReturnType(): Class = Type::class.java

internal var mu.KLogger.level: Level
    get() = (underlyingLogger as Logger).level
    set(value) {
        (underlyingLogger as Logger).level = value
    }

private enum class ParserState {
    READING_COMMAND,
    READING_PARAM_NAME,
    READING_PARAM_VALUE,
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy