This commit is contained in:
2021-02-02 19:15:42 +06:00
commit 3f4dacc129
22 changed files with 789 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
package dev.inmo.configurable_inline_telegram_bot
import dev.inmo.tgbotapi.bot.exceptions.RequestException
import dev.inmo.tgbotapi.extensions.api.answers.answerInlineQuery
import dev.inmo.tgbotapi.extensions.api.webhook.deleteWebhook
import dev.inmo.configurable_inline_telegram_bot.config.BotConfig
import dev.inmo.configurable_inline_telegram_bot.config.Restrictions
import dev.inmo.configurable_inline_telegram_bot.models.OfferTemplate
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviour
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onBaseInlineQuery
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.longPolling
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import kotlinx.coroutines.*
import kotlinx.serialization.Serializable
suspend fun BehaviourContext.enableFormatterBot(
templates: List<OfferTemplate>,
restrictions: Restrictions? = null
) {
onBaseInlineQuery { query ->
if (restrictions ?.check(query) == false) {
answerInlineQuery(query, cachedTime = 0)
return@onBaseInlineQuery
}
try {
answerInlineQuery(
query,
templates.mapIndexedNotNull { index, offerTemplate ->
offerTemplate.createArticleResult(
index.toString(),
query.query
)
},
cachedTime = 0
)
} catch (e: RequestException) {
bot.answerInlineQuery(
query,
cachedTime = 0
)
}
}
}
@Serializable
data class FormatterBot(
private val botConfig: BotConfig,
private val templates: List<OfferTemplate>,
private val restrictions: Restrictions? = null
) {
suspend fun start(scope: CoroutineScope = GlobalScope) {
val bot = botConfig.createBot()
val filter = FlowsUpdatesFilter()
bot.buildBehaviour(
scope,
filter
) {
enableFormatterBot(templates, restrictions)
}
botConfig.webhookConfig ?.setWebhookAndCreateServer(
bot,
filter,
scope
) ?.start(false) ?: bot.apply {
scope.launch {
deleteWebhook()
longPolling(
filter,
exceptionsHandler = { it.printStackTrace() },
scope = scope
)
}
}
}
}

View File

@@ -0,0 +1,9 @@
package dev.inmo.configurable_inline_telegram_bot
import kotlinx.serialization.json.Json
val serialFormat = Json {
ignoreUnknownKeys = true
allowSpecialFloatingPointValues = true
useArrayPolymorphism = true
}

View File

@@ -0,0 +1,28 @@
package dev.inmo.configurable_inline_telegram_bot
import kotlinx.coroutines.*
import java.io.File
fun main(vararg args: String) {
val config = args.first()
val bot = try {
serialFormat.decodeFromString(
FormatterBot.serializer(),
config
)
} catch (e: Throwable) {
File(config).readText().let {
serialFormat.decodeFromString(
FormatterBot.serializer(),
it
)
}
}
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
bot.start(scope)
}
runBlocking {
scope.coroutineContext[Job]!!.join()
}
}

View File

@@ -0,0 +1,27 @@
package dev.inmo.configurable_inline_telegram_bot.config
import dev.inmo.tgbotapi.bot.Ktor.telegramBot
import dev.inmo.tgbotapi.bot.RequestsExecutor
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import kotlinx.serialization.Serializable
@Serializable
data class BotConfig(
val botToken: String,
val apiUrl: String = telegramBotAPIDefaultUrl,
val clientConfig: HttpClientConfig? = null,
val webhookConfig: WebhookConfig? = null
) {
fun createBot(): RequestsExecutor = telegramBot(
botToken
) {
client = HttpClient(OkHttp.create(clientConfig ?.builder ?: {}))
telegramAPIUrlsKeeper = TelegramAPIUrlsKeeper(
botToken,
apiUrl
)
}
}

View File

@@ -0,0 +1,51 @@
package dev.inmo.configurable_inline_telegram_bot.config
import io.ktor.client.engine.okhttp.OkHttpConfig
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import okhttp3.*
import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.TimeUnit
@Serializable
data class HttpClientConfig(
val proxy: ProxySettings? = null,
val connectTimeout: Long = 0,
val writeTimeout: Long = 0,
val readTimeout: Long = 0
) {
@Transient
val builder: OkHttpConfig.() -> Unit = {
config {
this@HttpClientConfig.proxy ?.let {
proxy(
Proxy(
Proxy.Type.SOCKS,
InetSocketAddress(
it.host,
it.port
)
)
)
it.password ?.let { password ->
proxyAuthenticator (
object : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return response.request.newBuilder().apply {
addHeader(
"Proxy-Authorization",
Credentials.basic(it.username ?: "", password)
)
}.build()
}
}
)
}
}
connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
readTimeout(readTimeout, TimeUnit.MILLISECONDS)
}
}
}

View File

@@ -0,0 +1,11 @@
package dev.inmo.configurable_inline_telegram_bot.config
import kotlinx.serialization.Serializable
@Serializable
data class ProxySettings(
val host: String = "localhost",
val port: Int = 1080,
val username: String? = null,
val password: String? = null
)

View File

@@ -0,0 +1,14 @@
package dev.inmo.configurable_inline_telegram_bot.config
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.InlineQueries.abstracts.InlineQuery
import kotlinx.serialization.Serializable
@Serializable
data class Restrictions(
val allowedUsers: List<ChatIdentifier> = emptyList()
) {
fun check(query: InlineQuery): Boolean {
return query.from.id in allowedUsers || query.from.username ?.let { it in allowedUsers } ?: false
}
}

View File

@@ -0,0 +1,71 @@
package dev.inmo.configurable_inline_telegram_bot.config
import dev.inmo.tgbotapi.bot.RequestsExecutor
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.setWebhookInfoAndStartListenWebhooks
import dev.inmo.tgbotapi.requests.abstracts.toInputFile
import dev.inmo.tgbotapi.requests.webhook.SetWebhook
import dev.inmo.tgbotapi.updateshandlers.UpdatesFilter
import dev.inmo.tgbotapi.updateshandlers.webhook.WebhookPrivateKeyConfig
import io.ktor.server.engine.ApplicationEngine
import io.ktor.server.tomcat.Tomcat
import kotlinx.coroutines.*
import kotlinx.serialization.Serializable
import java.io.File
import java.util.concurrent.Executors
@Serializable
data class WebhookConfig(
val url: String,
val listenHost: String = "0.0.0.0",
val listenRoute: String = "/",
val port: Int = System.getenv("PORT").toInt(),
val certificatePath: String? = null,
val maxConnections: Int = 40,
val privateKeyConfig: WebhookPrivateKeyConfig? = null
) {
init {
println(this)
}
suspend fun setWebhookAndCreateServer(
requestsExecutor: RequestsExecutor,
filter: UpdatesFilter,
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(maxConnections / 2).asCoroutineDispatcher())
): ApplicationEngine = (certificatePath ?.let {
requestsExecutor.setWebhookInfoAndStartListenWebhooks(
port,
Tomcat,
SetWebhook(url, File(it).toInputFile(), maxAllowedConnections = maxConnections, allowedUpdates = filter.allowedUpdates),
{ throwable: Throwable ->
throwable.printStackTrace()
},
listenHost,
listenRoute,
privateKeyConfig = privateKeyConfig,
scope = scope,
block = filter.asUpdateReceiver
)
} ?: requestsExecutor.setWebhookInfoAndStartListenWebhooks(
port,
Tomcat,
SetWebhook(
url,
maxAllowedConnections = maxConnections,
allowedUpdates = filter.allowedUpdates
),
{ throwable: Throwable ->
throwable.printStackTrace()
},
listenHost,
listenRoute,
privateKeyConfig = privateKeyConfig,
scope = scope,
block = filter.asUpdateReceiver
)
).also {
it.environment.connectors.forEach {
println(it)
}
it.start(false)
}
}

View File

@@ -0,0 +1,40 @@
package dev.inmo.configurable_inline_telegram_bot.models
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent
import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
data class Format(
val template: String,
val regexTemplate: String = "^$",
val splitBy: String? = null,
val enableMarkdownSupport: Boolean = false
) {
@Transient
val queryRegex = Regex(regexTemplate, RegexOption.DOT_MATCHES_ALL)
init {
println(queryRegex)
}
fun formatByRegex(with: String): String? {
return if (queryRegex.matches(with)) {
template.format(*(splitBy ?.let { with.split(it).toTypedArray() } ?: arrayOf(with)))
} else {
null
}
}
fun createContent(with: String): InputTextMessageContent? {
return if (queryRegex.matches(with)) {
InputTextMessageContent(
template.format(*(splitBy ?.let { with.split(it).toTypedArray() } ?: arrayOf(with))),
if (enableMarkdownSupport) MarkdownV2 else null
)
} else {
null
}
}
}

View File

@@ -0,0 +1,22 @@
package dev.inmo.configurable_inline_telegram_bot.models
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
import kotlinx.serialization.Serializable
@Serializable
data class OfferTemplate(
val title: String,
val formats: List<Format> = emptyList(),
val description: String? = null
) {
fun createArticleResult(id: String, query: String): InlineQueryResultArticle? = formats.firstOrNull {
it.queryRegex.matches(query)
} ?.createContent(query) ?.let { content ->
InlineQueryResultArticle(
id,
title,
content,
description = description
)
}
}