mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-11-22 08:13:47 +00:00
commit
6fe8e73fca
4
.github/workflows/packages_publishing.yml
vendored
4
.github/workflows/packages_publishing.yml
vendored
@ -8,12 +8,16 @@ jobs:
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Setup LibCurl
|
||||
run: sudo apt install -y libcurl4-openssl-dev
|
||||
- name: Rewrite version
|
||||
run: |
|
||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||
cat gradle.properties | sed -e "s/^library_version=\([0-9\.]*\)/library_version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
|
||||
rm gradle.properties
|
||||
mv gradle.properties.tmp gradle.properties
|
||||
- name: KotlinSymbolProcessing execution
|
||||
run: ./gradlew ksp
|
||||
- name: Build
|
||||
run: ./gradlew build
|
||||
- name: Publish to Gitea
|
||||
|
15
CHANGELOG.md
15
CHANGELOG.md
@ -1,5 +1,20 @@
|
||||
# TelegramBotAPI changelog
|
||||
|
||||
## 7.0.2
|
||||
|
||||
_This update brings experimental support of `linuxX64` and `mingwX64` platforms_
|
||||
|
||||
* `Versions`:
|
||||
* `Kotlin`: `1.8.10` -> `1.8.20`
|
||||
* `MicroUtils`: `0.17.5` -> `0.17.8`
|
||||
* `Ktor`: `2.2.4` -> `2.3.0`
|
||||
* `Core`:
|
||||
* New `RequestsExecutor` - `MultipleClientKtorRequestsExecutor`
|
||||
* Old `KtorRequestsExecutor` has been renamed to `DefaultKtorRequestsExecutor`
|
||||
* `KtorRequestsExecutor` now is `expect class`
|
||||
* On `JS`, `JVM` and `MinGWX64` platforms it is `DefaultKtorRequestsExecutor`
|
||||
* On `LinuxX64` platform it is `MultipleClientKtorRequestsExecutor`
|
||||
|
||||
## 7.0.1
|
||||
|
||||
* `Core`:
|
||||
|
10
README.md
10
README.md
@ -1,9 +1,11 @@
|
||||
# TelegramBotAPI [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi) [![Supported version](https://img.shields.io/badge/Telegram%20Bot%20API-6.6-blue)](https://core.telegram.org/bots/api-changelog#march-9-2023)
|
||||
|
||||
| Docs | [![KDocs](https://img.shields.io/static/v1?label=Dokka&message=KDocs&color=blue&logo=kotlin)](https://tgbotapi.inmo.dev/index.html) [![Mini tutorial](https://img.shields.io/static/v1?label=Bookstack&message=Tutorial&color=blue&logo=bookstack)](https://bookstack.inmo.dev/books/telegrambotapi/chapter/introduction-tutorial) |
|
||||
|:---:|:---:|
|
||||
| Useful repos | [![Create bot](https://img.shields.io/static/v1?label=Github&message=Template&color=blue&logo=github)](https://github.com/InsanusMokrassar/TelegramBotAPI-bot_template/generate) [![Examples](https://img.shields.io/static/v1?label=Github&message=Examples&color=blue&logo=github)](https://github.com/InsanusMokrassar/TelegramBotAPI-examples/) |
|
||||
| Misc | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) [![Small survey](https://img.shields.io/static/v1?label=Google&message=Survey&color=blue&logo=google-sheets)](https://docs.google.com/forms/d/e/1FAIpQLSctdJHT_aEniyYT0-IUAEfo1hsIlezX2owlkEAYX4KPl2V2_A/viewform?usp=sf_link) |
|
||||
| Docs | [![KDocs](https://img.shields.io/static/v1?label=Dokka&message=KDocs&color=blue&logo=kotlin)](https://tgbotapi.inmo.dev/index.html) [![Mini tutorial](https://img.shields.io/static/v1?label=Bookstack&message=Tutorial&color=blue&logo=bookstack)](https://bookstack.inmo.dev/books/telegrambotapi/chapter/introduction-tutorial) |
|
||||
|:----------------------:|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
|
||||
| Useful repos | [![Create bot](https://img.shields.io/static/v1?label=Github&message=Template&color=blue&logo=github)](https://github.com/InsanusMokrassar/TelegramBotAPI-bot_template/generate) [![Examples](https://img.shields.io/static/v1?label=Github&message=Examples&color=blue&logo=github)](https://github.com/InsanusMokrassar/TelegramBotAPI-examples/) |
|
||||
| Misc | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) [![Small survey](https://img.shields.io/static/v1?label=Google&message=Survey&color=blue&logo=google-sheets)](https://docs.google.com/forms/d/e/1FAIpQLSctdJHT_aEniyYT0-IUAEfo1hsIlezX2owlkEAYX4KPl2V2_A/viewform?usp=sf_link) |
|
||||
| Platforms | ![JVM](https://img.shields.io/badge/JVM-red?style=plastic&logo=openjdk&logoColor=white) ![Js](https://img.shields.io/badge/JavaScript-323330?style=plastic&logo=javascript&logoColor=F7DF1E) |
|
||||
| Experimental Platforms | ![Linux x64](https://img.shields.io/badge/LinuxX64-FCC624?style=plastic&logo=linux&logoColor=black) ![MinGW x64](https://img.shields.io/badge/MinGWX64-black?style=plastic&logo=windows&logoColor=green) |
|
||||
|
||||
<!--- [![Telegram Channel](./resources/tg_channel_qr.jpg)](https://t.me/ktgbotapi) --->
|
||||
|
||||
|
@ -6,4 +6,4 @@ kotlin.incremental=true
|
||||
kotlin.incremental.js=true
|
||||
|
||||
library_group=dev.inmo
|
||||
library_version=7.0.1
|
||||
library_version=7.0.2
|
||||
|
@ -1,6 +1,6 @@
|
||||
[versions]
|
||||
|
||||
kotlin = "1.8.10"
|
||||
kotlin = "1.8.20"
|
||||
kotlin-serialization = "1.5.0"
|
||||
kotlin-coroutines = "1.6.4"
|
||||
|
||||
@ -8,12 +8,12 @@ javax-activation = "1.1.1"
|
||||
|
||||
korlibs = "3.4.0"
|
||||
uuid = "0.7.0"
|
||||
ktor = "2.2.4"
|
||||
ktor = "2.3.0"
|
||||
|
||||
ksp = "1.8.10-1.0.9"
|
||||
kotlin-poet = "1.12.0"
|
||||
ksp = "1.8.20-1.0.11"
|
||||
kotlin-poet = "1.13.0"
|
||||
|
||||
microutils = "0.17.5"
|
||||
microutils = "0.17.8"
|
||||
|
||||
github-release-plugin = "2.4.1"
|
||||
dokka = "1.8.10"
|
||||
@ -31,6 +31,8 @@ kotlin-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref =
|
||||
|
||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
||||
ktor-client-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor" }
|
||||
ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" }
|
||||
ktor-server = { module = "io.ktor:ktor-server", version.ref = "ktor" }
|
||||
ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" }
|
||||
|
||||
|
@ -13,6 +13,8 @@ kotlin {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
linuxX64()
|
||||
mingwX64()
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
|
@ -93,8 +93,9 @@ interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T
|
||||
behaviourContext: BehaviourContext,
|
||||
handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>,
|
||||
statesManager: StatesManager<T>,
|
||||
fallbackHandler: BehaviourWithFSMStateHandlerHolder<T, T>? = null,
|
||||
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
|
||||
) = DefaultBehaviourContextWithFSM<T>(behaviourContext, statesManager, handlers, onStateHandlingErrorHandler)
|
||||
) = DefaultBehaviourContextWithFSM<T>(behaviourContext, statesManager, handlers, fallbackHandler, onStateHandlingErrorHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,11 +130,12 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
||||
private val behaviourContext: BehaviourContext,
|
||||
private val statesManager: StatesManager<T>,
|
||||
private val handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>,
|
||||
private val fallbackHandler: BehaviourWithFSMStateHandlerHolder<T, T>? = null,
|
||||
private val onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler()
|
||||
) : BehaviourContext by behaviourContext, BehaviourContextWithFSM<T> {
|
||||
private val updatesFlows = mutableMapOf<Any, DefaultBehaviourContextWithFSM<T>>()
|
||||
private val additionalHandlers = mutableListOf<BehaviourWithFSMStateHandlerHolder<*, T>>()
|
||||
private var actualHandlersList = additionalHandlers + handlers
|
||||
private var actualHandlersList = additionalHandlers + handlers + listOfNotNull(fallbackHandler)
|
||||
|
||||
protected val statesJobs = mutableMapOf<T, Job>()
|
||||
protected val statesJobsMutex = Mutex()
|
||||
@ -250,6 +252,7 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
||||
behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder),
|
||||
handlers,
|
||||
statesManager,
|
||||
fallbackHandler,
|
||||
onStateHandlingErrorHandler
|
||||
)
|
||||
|
||||
@ -265,6 +268,7 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
||||
behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder),
|
||||
handlers,
|
||||
statesManager,
|
||||
fallbackHandler,
|
||||
onStateHandlingErrorHandler
|
||||
)
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||
fallbackHandler: BehaviourWithFSMStateHandlerHolder<T, T>? = null,
|
||||
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler(),
|
||||
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
|
||||
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
|
||||
@ -41,6 +42,7 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
||||
),
|
||||
presetHandlers,
|
||||
statesManager,
|
||||
fallbackHandler,
|
||||
onStateHandlingErrorHandler
|
||||
).apply { block() }
|
||||
|
||||
@ -59,6 +61,7 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
|
||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||
fallbackHandler: BehaviourWithFSMStateHandlerHolder<T, T>? = null,
|
||||
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler(),
|
||||
timeoutSeconds: Seconds = 30,
|
||||
autoDisableWebhooks: Boolean = true,
|
||||
@ -71,6 +74,7 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
|
||||
defaultExceptionsHandler,
|
||||
statesManager,
|
||||
presetHandlers,
|
||||
fallbackHandler,
|
||||
onStateHandlingErrorHandler,
|
||||
block
|
||||
).run {
|
||||
@ -104,6 +108,7 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||
fallbackHandler: BehaviourWithFSMStateHandlerHolder<T, T>? = null,
|
||||
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler(),
|
||||
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
|
||||
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
|
||||
@ -114,6 +119,7 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
||||
),
|
||||
presetHandlers,
|
||||
statesManager,
|
||||
fallbackHandler,
|
||||
onStateHandlingErrorHandler
|
||||
).apply { block() }
|
||||
|
||||
@ -137,6 +143,7 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
|
||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||
fallbackHandler: BehaviourWithFSMStateHandlerHolder<T, T>? = null,
|
||||
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler(),
|
||||
timeoutSeconds: Seconds = 30,
|
||||
autoDisableWebhooks: Boolean = true,
|
||||
@ -150,6 +157,7 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
|
||||
defaultExceptionsHandler,
|
||||
statesManager,
|
||||
presetHandlers,
|
||||
fallbackHandler,
|
||||
onStateHandlingErrorHandler,
|
||||
block
|
||||
).run {
|
||||
|
@ -46,6 +46,7 @@ suspend fun <T : State> telegramBotWithBehaviourAndFSM(
|
||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||
fallbackHandler: BehaviourWithFSMStateHandlerHolder<T, T>? = null,
|
||||
testServer: Boolean = false,
|
||||
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler(),
|
||||
timeoutSeconds: Seconds = 30,
|
||||
@ -65,6 +66,7 @@ suspend fun <T : State> telegramBotWithBehaviourAndFSM(
|
||||
defaultExceptionsHandler,
|
||||
statesManager,
|
||||
presetHandlers,
|
||||
fallbackHandler,
|
||||
onStateHandlingErrorHandler,
|
||||
timeoutSeconds,
|
||||
autoDisableWebhooks,
|
||||
@ -97,6 +99,7 @@ suspend fun <T : State> telegramBotWithBehaviourAndFSMAndStartLongPolling(
|
||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||
fallbackHandler: BehaviourWithFSMStateHandlerHolder<T, T>? = null,
|
||||
testServer: Boolean = false,
|
||||
onStateHandlingErrorHandler: StateHandlingErrorHandler<T> = defaultStateHandlingErrorHandler(),
|
||||
timeoutSeconds: Seconds = 30,
|
||||
@ -116,6 +119,7 @@ suspend fun <T : State> telegramBotWithBehaviourAndFSMAndStartLongPolling(
|
||||
defaultExceptionsHandler,
|
||||
statesManager,
|
||||
presetHandlers,
|
||||
fallbackHandler,
|
||||
onStateHandlingErrorHandler,
|
||||
timeoutSeconds,
|
||||
autoDisableWebhooks,
|
||||
|
@ -5,6 +5,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder
|
||||
import dev.inmo.micro_utils.coroutines.*
|
||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.TriggersHolder
|
||||
import dev.inmo.tgbotapi.types.UpdateIdentifier
|
||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||
import dev.inmo.tgbotapi.updateshandlers.*
|
||||
import kotlinx.coroutines.*
|
||||
@ -71,9 +72,17 @@ class DefaultBehaviourContext(
|
||||
private val additionalUpdatesSharedFlow = MutableSharedFlow<Update>(0, broadcastChannelsSize, onBufferOverflow)
|
||||
override val allUpdatesFlow: Flow<Update> = (additionalUpdatesSharedFlow.asSharedFlow()).let {
|
||||
if (upstreamUpdatesFlow != null) {
|
||||
var lastHandledUpdate = -1L
|
||||
val handledUpdates = mutableSetOf<UpdateIdentifier>()
|
||||
(it + upstreamUpdatesFlow).filter {
|
||||
(it.updateId > lastHandledUpdate).also { passed -> if (passed) { lastHandledUpdate = it.updateId } }
|
||||
val passed = handledUpdates.add(it.updateId)
|
||||
(passed).also { passed ->
|
||||
val needToDropCount = handledUpdates.size - broadcastChannelsSize
|
||||
if (needToDropCount > 0) {
|
||||
handledUpdates.removeAll(
|
||||
handledUpdates.take(needToDropCount).ifEmpty { return@also }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
it
|
||||
|
@ -0,0 +1,6 @@
|
||||
package dev.inmo.tgbotapi.extensions.behaviour_builder
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
actual var defaultCoroutineScopeProvider: () -> CoroutineScope = { CoroutineScope(Dispatchers.Default) }
|
@ -0,0 +1,6 @@
|
||||
package dev.inmo.tgbotapi.extensions.behaviour_builder
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
actual var defaultCoroutineScopeProvider: () -> CoroutineScope = { CoroutineScope(Dispatchers.Default) }
|
@ -48,11 +48,23 @@ kotlin {
|
||||
api libs.javax.activation
|
||||
}
|
||||
}
|
||||
|
||||
linuxX64Main {
|
||||
dependencies {
|
||||
api libs.ktor.client.curl
|
||||
}
|
||||
}
|
||||
|
||||
mingwX64Main {
|
||||
dependencies {
|
||||
api libs.ktor.client.winhttp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
add("kspJvm", project(":tgbotapi.ksp"))
|
||||
add("kspCommonMainMetadata", project(":tgbotapi.ksp"))
|
||||
}
|
||||
|
||||
ksp {
|
||||
|
@ -1,134 +1,25 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||
import dev.inmo.tgbotapi.bot.exceptions.*
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.*
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
import dev.inmo.tgbotapi.types.Response
|
||||
import dev.inmo.tgbotapi.utils.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.client.statement.readText
|
||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||
import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
|
||||
import io.ktor.client.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@RiskFeature
|
||||
fun createTelegramBotDefaultKtorCallRequestsFactories() = listOf(
|
||||
SimpleRequestCallFactory(),
|
||||
MultipartRequestCallFactory(),
|
||||
DownloadFileRequestCallFactory,
|
||||
DownloadFileChannelRequestCallFactory
|
||||
)
|
||||
|
||||
class KtorRequestsExecutor(
|
||||
/**
|
||||
* Represents default [BaseRequestsExecutor] working on [Ktor](https://ktor.io) [HttpClient]
|
||||
*
|
||||
* * On JS, JVM and MingwX64 platforms it is [dev.inmo.tgbotapi.bot.ktor.base.DefaultKtorRequestsExecutor]
|
||||
* * On LinuxX64 it is [dev.inmo.tgbotapi.bot.ktor.base.MultipleClientKtorRequestsExecutor]
|
||||
*/
|
||||
expect class KtorRequestsExecutor (
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
client: HttpClient = HttpClient(),
|
||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||
excludeDefaultFactories: Boolean = false,
|
||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
||||
private val jsonFormatter: Json = nonstrictJsonFormat,
|
||||
private val pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
|
||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
||||
if (!excludeDefaultFactories) {
|
||||
this + createTelegramBotDefaultKtorCallRequestsFactories()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private val client = client.config {
|
||||
if (client.pluginOrNull(HttpTimeout) == null) {
|
||||
install(HttpTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> execute(request: Request<T>): T {
|
||||
return runCatchingSafely {
|
||||
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
|
||||
requestsLimiter.limit(request) {
|
||||
var result: T? = null
|
||||
lateinit var factoryHandledRequest: KtorCallFactory
|
||||
for (potentialFactory in callsFactories) {
|
||||
pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory)
|
||||
result = potentialFactory.makeCall(
|
||||
client,
|
||||
telegramAPIUrlsKeeper,
|
||||
request,
|
||||
jsonFormatter
|
||||
)
|
||||
result = pipelineStepsHolder.onAfterCallFactoryMakeCall(result, request, potentialFactory)
|
||||
if (result != null) {
|
||||
factoryHandledRequest = potentialFactory
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
result ?.let {
|
||||
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
|
||||
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
|
||||
}
|
||||
}.let {
|
||||
val result = it.exceptionOrNull() ?.let { e ->
|
||||
pipelineStepsHolder.onRequestException(request, e) ?.let { return@let it }
|
||||
|
||||
when (e) {
|
||||
is ClientRequestException -> {
|
||||
val exceptionResult = runCatchingSafely {
|
||||
val content = e.response.bodyAsText()
|
||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||
newRequestException(
|
||||
responseObject,
|
||||
content,
|
||||
"Can't get result object from $content"
|
||||
)
|
||||
}
|
||||
exceptionResult.exceptionOrNull() ?.let {
|
||||
CommonBotException(cause = e)
|
||||
} ?: exceptionResult.getOrThrow()
|
||||
}
|
||||
is BotException -> e
|
||||
else -> CommonBotException(cause = e)
|
||||
}
|
||||
} ?.let { Result.failure(it) } ?: it
|
||||
pipelineStepsHolder.onRequestReturnResult(result, request, callsFactories)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
client.close()
|
||||
}
|
||||
}
|
||||
|
||||
class KtorRequestsExecutorBuilder(
|
||||
var telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper
|
||||
) {
|
||||
var client: HttpClient = HttpClient()
|
||||
var callsFactories: List<KtorCallFactory> = emptyList()
|
||||
var excludeDefaultFactories: Boolean = false
|
||||
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter
|
||||
var jsonFormatter: Json = nonstrictJsonFormat
|
||||
|
||||
fun build() = KtorRequestsExecutor(telegramAPIUrlsKeeper, client, callsFactories, excludeDefaultFactories, requestsLimiter, jsonFormatter)
|
||||
}
|
||||
|
||||
inline fun telegramBot(
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||
): TelegramBot = KtorRequestsExecutorBuilder(telegramAPIUrlsKeeper).apply(builder).build()
|
||||
|
||||
/**
|
||||
* Shortcut for [telegramBot]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun telegramBot(
|
||||
token: String,
|
||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||
testServer: Boolean = false,
|
||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, testServer, apiUrl), builder)
|
||||
requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
||||
jsonFormatter: Json = nonstrictJsonFormat,
|
||||
pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
|
||||
) : BaseRequestsExecutor
|
||||
|
@ -0,0 +1,54 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.*
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||
import dev.inmo.tgbotapi.utils.*
|
||||
import io.ktor.client.HttpClient
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
@RiskFeature
|
||||
fun createTelegramBotDefaultKtorCallRequestsFactories() = listOf(
|
||||
SimpleRequestCallFactory(),
|
||||
MultipartRequestCallFactory(),
|
||||
DownloadFileRequestCallFactory,
|
||||
DownloadFileChannelRequestCallFactory
|
||||
)
|
||||
|
||||
class KtorRequestsExecutorBuilder(
|
||||
var telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper
|
||||
) {
|
||||
var client: HttpClient = HttpClient()
|
||||
var callsFactories: List<KtorCallFactory> = emptyList()
|
||||
var excludeDefaultFactories: Boolean = false
|
||||
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter
|
||||
var jsonFormatter: Json = nonstrictJsonFormat
|
||||
|
||||
fun build() = KtorRequestsExecutor(
|
||||
telegramAPIUrlsKeeper,
|
||||
client,
|
||||
callsFactories,
|
||||
excludeDefaultFactories,
|
||||
requestsLimiter,
|
||||
jsonFormatter
|
||||
)
|
||||
}
|
||||
|
||||
inline fun telegramBot(
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||
): TelegramBot = KtorRequestsExecutorBuilder(telegramAPIUrlsKeeper).apply(builder).build()
|
||||
|
||||
/**
|
||||
* Shortcut for [telegramBot]
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun telegramBot(
|
||||
token: String,
|
||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||
testServer: Boolean = false,
|
||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, testServer, apiUrl), builder)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.safelyWithResult
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
|
||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
||||
import dev.inmo.tgbotapi.requests.GetUpdates
|
||||
@ -56,7 +56,7 @@ abstract class AbstractRequestCallFactory : KtorCallFactory {
|
||||
val content = response.bodyAsText()
|
||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||
|
||||
return safelyWithResult {
|
||||
return runCatchingSafely {
|
||||
(responseObject.result?.let {
|
||||
jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it)
|
||||
} ?: response.let {
|
||||
|
@ -0,0 +1,100 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||
import dev.inmo.tgbotapi.bot.exceptions.BotException
|
||||
import dev.inmo.tgbotapi.bot.exceptions.CommonBotException
|
||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorPipelineStepsHolder
|
||||
import dev.inmo.tgbotapi.bot.ktor.createTelegramBotDefaultKtorCallRequestsFactories
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
import dev.inmo.tgbotapi.types.Response
|
||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||
import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.statement.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class DefaultKtorRequestsExecutor(
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
client: HttpClient = HttpClient(),
|
||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||
excludeDefaultFactories: Boolean = false,
|
||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
|
||||
private val jsonFormatter: Json = nonstrictJsonFormat,
|
||||
private val pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
|
||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
||||
if (!excludeDefaultFactories) {
|
||||
this + createTelegramBotDefaultKtorCallRequestsFactories()
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private val client = client.config {
|
||||
if (client.pluginOrNull(HttpTimeout) == null) {
|
||||
install(HttpTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> execute(request: Request<T>): T {
|
||||
return runCatchingSafely {
|
||||
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
|
||||
requestsLimiter.limit(request) {
|
||||
var result: T? = null
|
||||
lateinit var factoryHandledRequest: KtorCallFactory
|
||||
for (potentialFactory in callsFactories) {
|
||||
pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory)
|
||||
result = potentialFactory.makeCall(
|
||||
client,
|
||||
telegramAPIUrlsKeeper,
|
||||
request,
|
||||
jsonFormatter
|
||||
)
|
||||
result = pipelineStepsHolder.onAfterCallFactoryMakeCall(result, request, potentialFactory)
|
||||
if (result != null) {
|
||||
factoryHandledRequest = potentialFactory
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
result ?.let {
|
||||
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
|
||||
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
|
||||
}
|
||||
}.let {
|
||||
val result = it.exceptionOrNull() ?.let { e ->
|
||||
pipelineStepsHolder.onRequestException(request, e) ?.let { return@let it }
|
||||
|
||||
when (e) {
|
||||
is ClientRequestException -> {
|
||||
val exceptionResult = runCatchingSafely {
|
||||
val content = e.response.bodyAsText()
|
||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||
newRequestException(
|
||||
responseObject,
|
||||
content,
|
||||
"Can't get result object from $content"
|
||||
)
|
||||
}
|
||||
exceptionResult.exceptionOrNull() ?.let {
|
||||
CommonBotException(cause = e)
|
||||
} ?: exceptionResult.getOrThrow()
|
||||
}
|
||||
is BotException -> e
|
||||
else -> CommonBotException(cause = e)
|
||||
}
|
||||
} ?.let { Result.failure(it) } ?: it
|
||||
pipelineStepsHolder.onRequestReturnResult(result, request, callsFactories)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
client.close()
|
||||
}
|
||||
}
|
@ -4,8 +4,7 @@ import dev.inmo.tgbotapi.requests.abstracts.*
|
||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||
import dev.inmo.tgbotapi.utils.mapWithCommonValues
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
|
||||
|
@ -0,0 +1,118 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
|
||||
import dev.inmo.tgbotapi.bot.ktor.KtorPipelineStepsHolder
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||
import io.ktor.client.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms and MingwX64 should return [client]
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64
|
||||
*/
|
||||
internal expect inline fun platformClientCopy(client: HttpClient): HttpClient
|
||||
|
||||
/**
|
||||
* Will use its parameters of constructor to create several [DefaultKtorRequestsExecutor] and use them in [execute]
|
||||
* and [close] operations
|
||||
*
|
||||
* This [BaseRequestsExecutor] has been created for LinuxX64 target due to its inability of requests paralleling
|
||||
*
|
||||
* Under the hood on each [execute] it will take [DefaultKtorRequestsExecutor] and mark it as busy, execute
|
||||
* [Request], free up taken [DefaultKtorRequestsExecutor] and return (or throw) the result of execution
|
||||
*
|
||||
* @param requestExecutorsCount Amount of [DefaultKtorRequestsExecutor] which will be created and used under the
|
||||
* hood
|
||||
*/
|
||||
class MultipleClientKtorRequestsExecutor (
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
callsFactories: List<KtorCallFactory>,
|
||||
excludeDefaultFactories: Boolean,
|
||||
requestsLimiter: RequestLimiter,
|
||||
jsonFormatter: Json,
|
||||
pipelineStepsHolder: KtorPipelineStepsHolder,
|
||||
requestExecutorsCount: Int,
|
||||
clientFactory: () -> HttpClient
|
||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||
private val requestExecutors = (0 until requestExecutorsCount).map {
|
||||
DefaultKtorRequestsExecutor(
|
||||
telegramAPIUrlsKeeper,
|
||||
clientFactory(),
|
||||
callsFactories,
|
||||
excludeDefaultFactories,
|
||||
requestsLimiter,
|
||||
jsonFormatter,
|
||||
pipelineStepsHolder
|
||||
)
|
||||
}.toSet()
|
||||
private val freeClients = MutableStateFlow<Set<DefaultKtorRequestsExecutor>>(requestExecutors)
|
||||
private val clientAllocationMutex = Mutex()
|
||||
private val takerFlow = freeClients.mapNotNull {
|
||||
clientAllocationMutex.withLock {
|
||||
freeClients.value.firstOrNull() ?.also {
|
||||
freeClients.value -= it
|
||||
} ?: return@mapNotNull null
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||
client: HttpClient,
|
||||
callsFactories: List<KtorCallFactory>,
|
||||
excludeDefaultFactories: Boolean,
|
||||
requestsLimiter: RequestLimiter,
|
||||
jsonFormatter: Json,
|
||||
pipelineStepsHolder: KtorPipelineStepsHolder
|
||||
) : this(
|
||||
telegramAPIUrlsKeeper,
|
||||
callsFactories,
|
||||
excludeDefaultFactories,
|
||||
requestsLimiter,
|
||||
jsonFormatter,
|
||||
pipelineStepsHolder,
|
||||
client.engineConfig.threadsCount,
|
||||
{ platformClientCopy(client) }
|
||||
)
|
||||
|
||||
private suspend fun prepareRequestsExecutor(): DefaultKtorRequestsExecutor {
|
||||
return takerFlow.first()
|
||||
}
|
||||
|
||||
private suspend fun freeRequestsExecutor(client: DefaultKtorRequestsExecutor) {
|
||||
clientAllocationMutex.withLock {
|
||||
freeClients.value += client
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> withRequestExecutor(block: suspend (DefaultKtorRequestsExecutor) -> T): T {
|
||||
val requestsExecutor = prepareRequestsExecutor()
|
||||
val result = runCatchingSafely {
|
||||
block(requestsExecutor)
|
||||
}
|
||||
freeRequestsExecutor(requestsExecutor)
|
||||
return result.getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun <T : Any> execute(request: Request<T>): T = withRequestExecutor {
|
||||
it.execute(request)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
requestExecutors.forEach {
|
||||
it.close()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,27 @@
|
||||
package dev.inmo.tgbotapi.bot.settings.limiters
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.tgbotapi.bot.exceptions.TooMuchRequestsException
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* Simple limiter which will lock any request when [TooMuchRequestsExceptions] is thrown and rerun request after lock time
|
||||
* Simple limiter which will lock any request when [TooMuchRequestsException] is thrown and rerun request after lock time
|
||||
*/
|
||||
object ExceptionsOnlyLimiter : RequestLimiter {
|
||||
override suspend fun <T> limit(block: suspend () -> T): T {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: TooMuchRequestsException) {
|
||||
delay(e.retryAfter.leftToRetry)
|
||||
limit(block)
|
||||
var result: Result<T>? = null
|
||||
while (result == null || result.isFailure) {
|
||||
result = runCatchingSafely {
|
||||
block()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
if (it is TooMuchRequestsException) {
|
||||
delay(it.retryAfter.leftToRetry)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ data class GetChat(
|
||||
override val resultDeserializer: DeserializationStrategy<ExtendedChat> = if (chatId is ChatIdWithThreadId) {
|
||||
ExtendedChatSerializer.BasedOnForumThread(chatId.threadId)
|
||||
} else {
|
||||
ExtendedChatSerializer
|
||||
ExtendedChatSerializer.Companion
|
||||
}
|
||||
override val requestSerializer: SerializationStrategy<*>
|
||||
get() = serializer()
|
||||
|
@ -5,17 +5,17 @@ import dev.inmo.tgbotapi.types.message.abstracts.Message
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.TelegramBotAPIMessageDeserializeOnlySerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable(ExtendedChatSerializer::class)
|
||||
@Serializable(ExtendedChatSerializer.Companion::class)
|
||||
sealed interface ExtendedChannelChat : ChannelChat, ExtendedPublicChat, ExtendedChatWithUsername {
|
||||
val linkedGroupChatId: IdChatIdentifier?
|
||||
}
|
||||
|
||||
@Serializable(ExtendedChatSerializer::class)
|
||||
@Serializable(ExtendedChatSerializer.Companion::class)
|
||||
sealed interface ExtendedGroupChat : GroupChat, ExtendedPublicChat {
|
||||
val permissions: ChatPermissions
|
||||
}
|
||||
|
||||
@Serializable(ExtendedChatSerializer::class)
|
||||
@Serializable(ExtendedChatSerializer.Companion::class)
|
||||
sealed interface ExtendedPrivateChat : PrivateChat, ExtendedChatWithUsername {
|
||||
val bio: String
|
||||
val hasPrivateForwards: Boolean
|
||||
@ -34,7 +34,7 @@ sealed interface ExtendedPublicChat : ExtendedChat, PublicChat {
|
||||
val membersHidden: Boolean
|
||||
}
|
||||
|
||||
@Serializable(ExtendedChatSerializer::class)
|
||||
@Serializable(ExtendedChatSerializer.Companion::class)
|
||||
sealed interface ExtendedSupergroupChat : SupergroupChat, ExtendedGroupChat, ExtendedChatWithUsername {
|
||||
val slowModeDelay: Long?
|
||||
val stickerSetName: StickerSetName?
|
||||
@ -58,15 +58,15 @@ sealed interface ExtendedSupergroupChat : SupergroupChat, ExtendedGroupChat, Ext
|
||||
val isAggressiveAntiSpamEnabled: Boolean
|
||||
}
|
||||
|
||||
@Serializable(ExtendedChatSerializer::class)
|
||||
@Serializable(ExtendedChatSerializer.Companion::class)
|
||||
sealed interface ExtendedForumChat : ExtendedSupergroupChat, ForumChat
|
||||
|
||||
@Serializable(ExtendedChatSerializer::class)
|
||||
@Serializable(ExtendedChatSerializer.Companion::class)
|
||||
sealed interface ExtendedChat : Chat {
|
||||
val chatPhoto: ChatPhoto?
|
||||
}
|
||||
|
||||
@Serializable(ExtendedChatSerializer::class)
|
||||
@Serializable(ExtendedChatSerializer.Companion::class)
|
||||
sealed interface ExtendedChatWithUsername : UsernameChat, ExtendedChat {
|
||||
val activeUsernames: List<Username>
|
||||
}
|
||||
|
@ -1,37 +1,78 @@
|
||||
package dev.inmo.tgbotapi.types.message.textsources
|
||||
|
||||
import dev.inmo.micro_utils.serialization.mapper.MapperSerializer
|
||||
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.encoding.CompositeEncoder
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
private val baseSerializers: Map<String, KSerializer<out TextSource>> = mapOf(
|
||||
"regular" to RegularTextSource.serializer(),
|
||||
"text_link" to TextLinkTextSource.serializer(),
|
||||
"code" to CodeTextSource.serializer(),
|
||||
"url" to URLTextSource.serializer(),
|
||||
"pre" to PreTextSource.serializer(),
|
||||
"bot_command" to BotCommandTextSource.serializer(),
|
||||
"strikethrough" to StrikethroughTextSource.serializer(),
|
||||
"italic" to ItalicTextSource.serializer(),
|
||||
"bold" to BoldTextSource.serializer(),
|
||||
"email" to EMailTextSource.serializer(),
|
||||
"underline" to UnderlineTextSource.serializer(),
|
||||
"mention" to MentionTextSource.serializer(),
|
||||
"phone_number" to PhoneNumberTextSource.serializer(),
|
||||
"text_mention" to TextMentionTextSource.serializer(),
|
||||
"hashtag" to HashTagTextSource.serializer(),
|
||||
"cashtag" to CashTagTextSource.serializer(),
|
||||
"spoiler" to SpoilerTextSource.serializer(),
|
||||
"custom_emoji" to CustomEmojiTextSource.serializer(),
|
||||
)
|
||||
//private val baseSerializers: Map<String, KSerializer<out TextSource>> = mapOf(
|
||||
// "regular" to RegularTextSource.serializer(),
|
||||
// "text_link" to TextLinkTextSource.serializer(),
|
||||
// "code" to CodeTextSource.serializer(),
|
||||
// "url" to URLTextSource.serializer(),
|
||||
// "pre" to PreTextSource.serializer(),
|
||||
// "bot_command" to BotCommandTextSource.serializer(),
|
||||
// "strikethrough" to StrikethroughTextSource.serializer(),
|
||||
// "italic" to ItalicTextSource.serializer(),
|
||||
// "bold" to BoldTextSource.serializer(),
|
||||
// "email" to EMailTextSource.serializer(),
|
||||
// "underline" to UnderlineTextSource.serializer(),
|
||||
// "mention" to MentionTextSource.serializer(),
|
||||
// "phone_number" to PhoneNumberTextSource.serializer(),
|
||||
// "text_mention" to TextMentionTextSource.serializer(),
|
||||
// "hashtag" to HashTagTextSource.serializer(),
|
||||
// "cashtag" to CashTagTextSource.serializer(),
|
||||
// "spoiler" to SpoilerTextSource.serializer(),
|
||||
// "custom_emoji" to CustomEmojiTextSource.serializer(),
|
||||
//)
|
||||
|
||||
object TextSourceSerializer : TypedSerializer<TextSource>(TextSource::class, emptyMap()) {
|
||||
private val baseSerializers: Map<String, KSerializer<out TextSource>> by lazy {
|
||||
mapOf(
|
||||
"regular" to RegularTextSource.serializer(),
|
||||
"text_link" to TextLinkTextSource.serializer(),
|
||||
"code" to CodeTextSource.serializer(),
|
||||
"url" to URLTextSource.serializer(),
|
||||
"pre" to PreTextSource.serializer(),
|
||||
"bot_command" to BotCommandTextSource.serializer(),
|
||||
"strikethrough" to StrikethroughTextSource.serializer(),
|
||||
"italic" to ItalicTextSource.serializer(),
|
||||
"bold" to BoldTextSource.serializer(),
|
||||
"email" to EMailTextSource.serializer(),
|
||||
"underline" to UnderlineTextSource.serializer(),
|
||||
"mention" to MentionTextSource.serializer(),
|
||||
"phone_number" to PhoneNumberTextSource.serializer(),
|
||||
"text_mention" to TextMentionTextSource.serializer(),
|
||||
"hashtag" to HashTagTextSource.serializer(),
|
||||
"cashtag" to CashTagTextSource.serializer(),
|
||||
"spoiler" to SpoilerTextSource.serializer(),
|
||||
"custom_emoji" to CustomEmojiTextSource.serializer(),
|
||||
).also {
|
||||
it.forEach { (k, s) ->
|
||||
include(k, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: TextSource) {
|
||||
baseSerializers // init base serializers
|
||||
super.serialize(encoder, value)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): TextSource {
|
||||
baseSerializers // init base serializers
|
||||
return super.deserialize(decoder)
|
||||
}
|
||||
|
||||
object TextSourceSerializer : TypedSerializer<TextSource>(TextSource::class, baseSerializers) {
|
||||
override fun <T: TextSource> include(type: String, serializer: KSerializer<T>) {
|
||||
require(type !in baseSerializers.keys)
|
||||
require(type !in serializers.keys)
|
||||
super.include(type, serializer)
|
||||
}
|
||||
|
||||
override fun exclude(type: String) {
|
||||
require(type !in baseSerializers.keys)
|
||||
require(type !in serializers.keys)
|
||||
super.exclude(type)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.DefaultKtorRequestsExecutor
|
||||
|
||||
actual typealias KtorRequestsExecutor = DefaultKtorRequestsExecutor
|
@ -0,0 +1,13 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import io.ktor.client.*
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms and MingwX64 should return [client]
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64
|
||||
*/
|
||||
internal actual inline fun platformClientCopy(client: HttpClient): HttpClient = client.config { }
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.DefaultKtorRequestsExecutor
|
||||
|
||||
actual typealias KtorRequestsExecutor = DefaultKtorRequestsExecutor
|
@ -0,0 +1,13 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import io.ktor.client.*
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms and MingwX64 should return [client]
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64
|
||||
*/
|
||||
internal actual inline fun platformClientCopy(client: HttpClient): HttpClient = client.config { }
|
1
tgbotapi.core/src/linuxX64Main/kotlin/PackageInfo.kt
Normal file
1
tgbotapi.core/src/linuxX64Main/kotlin/PackageInfo.kt
Normal file
@ -0,0 +1 @@
|
||||
package dev.inmo.tgbotapi
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.MultipleClientKtorRequestsExecutor
|
||||
|
||||
actual typealias KtorRequestsExecutor = MultipleClientKtorRequestsExecutor
|
@ -0,0 +1,22 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.curl.*
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms and MingwX64 should return [client]
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64
|
||||
*/
|
||||
internal actual inline fun platformClientCopy(client: HttpClient): HttpClient = (client.engineConfig as? CurlClientEngineConfig) ?.let {
|
||||
lateinit var config: HttpClientConfig<out CurlClientEngineConfig>
|
||||
client.config {
|
||||
config = this as HttpClientConfig<out CurlClientEngineConfig>
|
||||
}.close()
|
||||
HttpClient(Curl) {
|
||||
this.plusAssign(config)
|
||||
}
|
||||
} ?: throw IllegalArgumentException("On LinuxX64 TelegramBotAPI currently support only Curl Ktor HttpClient engine")
|
@ -0,0 +1,9 @@
|
||||
package dev.inmo.tgbotapi.requests.abstracts
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.ktor.common.input
|
||||
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
|
||||
|
||||
actual fun MPPFile.asMultipartFile(): MultipartFile = MultipartFile(this.name) {
|
||||
input()
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package dev.inmo.tgbotapi.utils
|
||||
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import io.ktor.utils.io.core.Input
|
||||
import io.ktor.utils.io.readRemaining
|
||||
|
||||
actual suspend fun ByteReadChannel.asInput(): Input = readRemaining()
|
@ -0,0 +1,11 @@
|
||||
package dev.inmo.tgbotapi.utils
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
//actual typealias MimeType = MimeType
|
||||
|
||||
@Serializable(MimeTypeSerializer::class)
|
||||
actual data class MimeType(
|
||||
actual val raw: String
|
||||
)
|
||||
internal actual fun createMimeType(raw: String): MimeType = MimeType(raw)
|
1
tgbotapi.core/src/mingwX64Main/kotlin/PackageInfo.kt
Normal file
1
tgbotapi.core/src/mingwX64Main/kotlin/PackageInfo.kt
Normal file
@ -0,0 +1 @@
|
||||
package dev.inmo.tgbotapi
|
@ -0,0 +1,5 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor
|
||||
|
||||
import dev.inmo.tgbotapi.bot.ktor.base.DefaultKtorRequestsExecutor
|
||||
|
||||
actual typealias KtorRequestsExecutor = DefaultKtorRequestsExecutor
|
@ -0,0 +1,14 @@
|
||||
package dev.inmo.tgbotapi.bot.ktor.base
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.winhttp.*
|
||||
|
||||
/**
|
||||
* This function is used in default constructor of [MultipleClientKtorRequestsExecutor] and on all non-native
|
||||
* platforms and MingwX64 should return [client]
|
||||
*
|
||||
* On LinuxX64 it will create copy with Curl engine or throw an exception if engine is different with Curl
|
||||
*
|
||||
* @throws IllegalArgumentException When pass non Curl-based [HttpClient] on LinuxX64
|
||||
*/
|
||||
internal actual inline fun platformClientCopy(client: HttpClient): HttpClient = client.config { }
|
@ -0,0 +1,9 @@
|
||||
package dev.inmo.tgbotapi.requests.abstracts
|
||||
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.ktor.common.input
|
||||
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
|
||||
|
||||
actual fun MPPFile.asMultipartFile(): MultipartFile = MultipartFile(this.name) {
|
||||
input()
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package dev.inmo.tgbotapi.utils
|
||||
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import io.ktor.utils.io.core.Input
|
||||
import io.ktor.utils.io.readRemaining
|
||||
|
||||
actual suspend fun ByteReadChannel.asInput(): Input = readRemaining()
|
@ -0,0 +1,11 @@
|
||||
package dev.inmo.tgbotapi.utils
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
//actual typealias MimeType = MimeType
|
||||
|
||||
@Serializable(MimeTypeSerializer::class)
|
||||
actual data class MimeType(
|
||||
actual val raw: String
|
||||
)
|
||||
internal actual fun createMimeType(raw: String): MimeType = MimeType(raw)
|
@ -160,6 +160,7 @@ import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
|
||||
import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMemberImpl
|
||||
import dev.inmo.tgbotapi.types.chat.member.BannedChatMember
|
||||
import dev.inmo.tgbotapi.types.chat.member.ChatMember
|
||||
import dev.inmo.tgbotapi.types.chat.member.ChatMemberUpdated
|
||||
import dev.inmo.tgbotapi.types.chat.member.KickedChatMember
|
||||
import dev.inmo.tgbotapi.types.chat.member.LeftChatMember
|
||||
import dev.inmo.tgbotapi.types.chat.member.LeftChatMemberImpl
|
||||
@ -659,6 +660,15 @@ public inline fun WithUser.chatMemberOrThrow(): ChatMember = this as
|
||||
public inline fun <T> WithUser.ifChatMember(block: (ChatMember) -> T): T? = chatMemberOrNull()
|
||||
?.let(block)
|
||||
|
||||
public inline fun WithUser.chatMemberUpdatedOrNull(): ChatMemberUpdated? = this as?
|
||||
dev.inmo.tgbotapi.types.chat.member.ChatMemberUpdated
|
||||
|
||||
public inline fun WithUser.chatMemberUpdatedOrThrow(): ChatMemberUpdated = this as
|
||||
dev.inmo.tgbotapi.types.chat.member.ChatMemberUpdated
|
||||
|
||||
public inline fun <T> WithUser.ifChatMemberUpdated(block: (ChatMemberUpdated) -> T): T? =
|
||||
chatMemberUpdatedOrNull() ?.let(block)
|
||||
|
||||
public inline fun WithUser.kickedChatMemberOrNull(): KickedChatMember? = this as?
|
||||
dev.inmo.tgbotapi.types.chat.member.KickedChatMember
|
||||
|
||||
|
5
tgbotapi.webapps/src/commonMain/kotlin/PackageInfo.kt
Normal file
5
tgbotapi.webapps/src/commonMain/kotlin/PackageInfo.kt
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* This file has been created to fix problem with native targets which didn't compile empty project klib file. This problem
|
||||
* leads to impossible project publishing
|
||||
*/
|
||||
package dev.inmo.tgbotapi.webapps
|
5
tgbotapi/src/commonMain/kotlin/PackageInfo.kt
Normal file
5
tgbotapi/src/commonMain/kotlin/PackageInfo.kt
Normal file
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* This file has been created to fix problem with native targets which didn't compile empty project klib file. This problem
|
||||
* leads to impossible project publishing
|
||||
*/
|
||||
package dev.inmo.tgbotapi
|
Loading…
Reference in New Issue
Block a user