package dev.inmo.tgbotapi.extensions.utils.updates.retrieving import dev.inmo.micro_utils.coroutines.* import dev.inmo.tgbotapi.bot.RequestsExecutor import dev.inmo.tgbotapi.extensions.utils.nonstrictJsonFormat import dev.inmo.tgbotapi.extensions.utils.updates.flowsUpdatesFilter import dev.inmo.tgbotapi.requests.webhook.SetWebhookRequest import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy import dev.inmo.tgbotapi.updateshandlers.* import dev.inmo.tgbotapi.updateshandlers.webhook.WebhookPrivateKeyConfig import io.ktor.http.HttpStatusCode import io.ktor.server.application.call import io.ktor.server.engine.* import io.ktor.server.request.receiveText import io.ktor.server.response.respond import io.ktor.server.routing.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.asCoroutineDispatcher import java.util.concurrent.Executors /** * Allows to include webhook in custom route everywhere in your server * * @param [scope] Will be used for mapping of media groups * @param [exceptionsHandler] Pass this parameter to set custom exception handler for getting updates * @param [block] Some receiver block like [dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter] * * @see dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter * @see UpdatesFilter * @see UpdatesFilter.asUpdateReceiver */ fun Route.includeWebhookHandlingInRoute( scope: CoroutineScope, exceptionsHandler: ExceptionHandler? = null, mediaGroupsDebounceTimeMillis: Long = 1000L, block: UpdateReceiver ) { val transformer = scope.updateHandlerWithMediaGroupsAdaptation(block, mediaGroupsDebounceTimeMillis) post { try { runCatchingSafely { val update = nonstrictJsonFormat.decodeFromString( UpdateDeserializationStrategy, call.receiveText() ) transformer(update) }.onSuccess { call.respond(HttpStatusCode.OK) }.onFailure { call.respond(HttpStatusCode.InternalServerError) }.getOrThrow() } catch (e: Throwable) { exceptionsHandler ?.invoke(e) } } } fun Route.includeWebhookHandlingInRouteWithFlows( scope: CoroutineScope, exceptionsHandler: ExceptionHandler? = null, mediaGroupsDebounceTimeMillis: Long = 1000L, block: FlowsUpdatesFilter.() -> Unit ) = includeWebhookHandlingInRoute( scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, flowsUpdatesFilter(block = block).asUpdateReceiver ) /** * Setting up ktor server * * @param listenPort port which will be listen by bot * @param listenRoute address to listen by bot. If null - will be set up in root of host * @param scope Scope which will be used for * @param privateKeyConfig If configured - server will be created with [sslConnector]. [connector] will be used otherwise * * @see dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter * @see UpdatesFilter * @see UpdatesFilter.asUpdateReceiver */ fun startListenWebhooks( listenPort: Int, engineFactory: ApplicationEngineFactory<*, *>, exceptionsHandler: ExceptionHandler, listenHost: String = "0.0.0.0", listenRoute: String? = null, privateKeyConfig: WebhookPrivateKeyConfig? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), mediaGroupsDebounceTimeMillis: Long = 1000L, additionalApplicationEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, block: UpdateReceiver ): ApplicationEngine { val env = applicationEngineEnvironment { module { routing { listenRoute ?.also { createRouteFromPath(it).includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block) } ?: includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block) } } privateKeyConfig ?.let { sslConnector( privateKeyConfig.keyStore, privateKeyConfig.aliasName, privateKeyConfig::keyStorePassword, privateKeyConfig::aliasPassword ) { host = listenHost port = listenPort } } ?: connector { host = listenHost port = listenPort } additionalApplicationEngineEnvironmentConfigurator() } return embeddedServer(engineFactory, env).also { it.start(false) } } /** * Setting up ktor server, set webhook info via [SetWebhookRequest] request. * * @param listenPort port which will be listen by bot * @param listenRoute address to listen by bot * @param scope Scope which will be used for * * @see dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter * @see UpdatesFilter * @see UpdatesFilter.asUpdateReceiver */ @Suppress("unused") suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks( listenPort: Int, engineFactory: ApplicationEngineFactory<*, *>, setWebhookRequest: SetWebhookRequest, exceptionsHandler: ExceptionHandler = {}, listenHost: String = "0.0.0.0", listenRoute: String = "/", privateKeyConfig: WebhookPrivateKeyConfig? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), mediaGroupsDebounceTimeMillis: Long = 1000L, additionalApplicationEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, block: UpdateReceiver ): ApplicationEngine = try { execute(setWebhookRequest) startListenWebhooks(listenPort, engineFactory, exceptionsHandler, listenHost, listenRoute, privateKeyConfig, scope, mediaGroupsDebounceTimeMillis, additionalApplicationEngineEnvironmentConfigurator, block) } catch (e: Exception) { throw e }