From 2e815a4c3719749db35803f6463308baac2adf78 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 30 Jun 2023 16:35:14 +0600 Subject: [PATCH] #773 fix, improvements in updates handling --- CHANGELOG.md | 4 ++ .../tgbotapi/extensions/api/GetUpdatesRaw.kt | 27 ++++++++++++++ .../ktor/base/AbstractRequestCallFactory.kt | 4 +- .../dev/inmo/tgbotapi/requests/GetUpdates.kt | 12 +++--- .../inmo/tgbotapi/requests/GetUpdatesRaw.kt | 37 +++++++++++++++++++ .../tgbotapi/requests/GetUpdatesRequest.kt | 17 +++++++++ .../inmo/tgbotapi/types/update/RawUpdate.kt | 21 +++++------ .../tgbotapi/types/update/abstracts/Update.kt | 31 ++++++++++++---- .../extensions/utils/updates/UpdatesUtils.kt | 2 +- .../utils/updates/retrieving/LongPolling.kt | 5 ++- 10 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/GetUpdatesRaw.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdatesRaw.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdatesRequest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e0319ca71..b1b2da0212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ **THIS UPDATE CONTAINS BREAKING CHANGES: USERNAMES OF BOTS NOW BECAME NULLABLE** +* `Core`: + * All bots now have nullable usernames just like common users ([#772](https://github.com/InsanusMokrassar/ktgbotapi/issues/772)) + * Decrease possible errors in updates handling by additional handling of update deserialization wrapping ([#773](https://github.com/InsanusMokrassar/ktgbotapi/issues/773)) + * Now it is possible to get raw updates with `GetUpdatesRaw` request * `Utils`: * Improve extension `Update.sourceChat` to add opportunity to select some chats by logic different with the default diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/GetUpdatesRaw.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/GetUpdatesRaw.kt new file mode 100644 index 0000000000..040ff36ac5 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/GetUpdatesRaw.kt @@ -0,0 +1,27 @@ +package dev.inmo.tgbotapi.extensions.api + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.GetUpdates +import dev.inmo.tgbotapi.requests.GetUpdatesRaw +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.update.abstracts.Update + +suspend fun TelegramBot.getRawUpdates( + offset: UpdateIdentifier? = null, + limit: Int = getUpdatesLimit.last, + timeout: Seconds? = null, + allowed_updates: List? = ALL_UPDATES_LIST +) = execute( + GetUpdatesRaw( + offset, limit, timeout, allowed_updates + ) +) + +suspend fun TelegramBot.getRawUpdates( + lastUpdate: Update, + limit: Int = getUpdatesLimit.last, + timeout: Seconds? = null, + allowed_updates: List? = ALL_UPDATES_LIST +) = getRawUpdates( + lastUpdate.updateId + 1, limit, timeout, allowed_updates +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/AbstractRequestCallFactory.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/AbstractRequestCallFactory.kt index 5d6346236e..7d1d957aaf 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/AbstractRequestCallFactory.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/AbstractRequestCallFactory.kt @@ -3,7 +3,7 @@ package dev.inmo.tgbotapi.bot.ktor.base 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 +import dev.inmo.tgbotapi.requests.GetUpdatesRequest import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.types.Response import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper @@ -35,7 +35,7 @@ abstract class AbstractRequestCallFactory : KtorCallFactory { ) accept(ContentType.Application.Json) - if (request is GetUpdates) { + if (request is GetUpdatesRequest) { request.timeout?.times(1000L) ?.let { customTimeoutMillis -> if (customTimeoutMillis > 0) { timeout { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdates.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdates.kt index e603a53d1c..f75ba755c3 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdates.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdates.kt @@ -22,13 +22,11 @@ private val updatesListSerializer = ListSerializer( */ @Serializable data class GetUpdates( - val offset: UpdateIdentifier? = null,// set `last update id + 1` to receive next part of updates - val limit: Int = getUpdatesLimit.last, - val timeout: Seconds? = null, - val allowed_updates: List? = ALL_UPDATES_LIST -): SimpleRequest> { - override fun method(): String = "getUpdates" - + override val offset: UpdateIdentifier? = null,// set `last update id + 1` to receive next part of updates + override val limit: Int = getUpdatesLimit.last, + override val timeout: Seconds? = null, + override val allowed_updates: List? = ALL_UPDATES_LIST +): GetUpdatesRequest> { override val resultDeserializer: DeserializationStrategy> get() = updatesListSerializer diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdatesRaw.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdatesRaw.kt new file mode 100644 index 0000000000..25789b30b5 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdatesRaw.kt @@ -0,0 +1,37 @@ +package dev.inmo.tgbotapi.requests + +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.ALL_UPDATES_LIST +import dev.inmo.tgbotapi.types.Seconds +import dev.inmo.tgbotapi.types.UpdateIdentifier +import dev.inmo.tgbotapi.types.getUpdatesLimit +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.json.JsonArray + +/** + * Raw variant of [GetUpdates]. This type will be useful in case you wish to get some updates and handle them by + * yourself or with [dev.inmo.tgbotapi.utils.convertWithMediaGroupUpdates] + */ +@Serializable +data class GetUpdatesRaw( + override val offset: UpdateIdentifier? = null,// set `last update id + 1` to receive next part of updates + override val limit: Int = getUpdatesLimit.last, + override val timeout: Seconds? = null, + override val allowed_updates: List? = ALL_UPDATES_LIST +): GetUpdatesRequest { + override fun method(): String = "getUpdates" + + override val resultDeserializer: DeserializationStrategy + get() = JsonArray.serializer() + + override val requestSerializer: SerializationStrategy<*> + get() = serializer() + + init { + if (limit !in getUpdatesLimit) { + error("GetUpdates request can be called only with limit in range $getUpdatesLimit (actual value is $limit)") + } + } +} \ No newline at end of file diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdatesRequest.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdatesRequest.kt new file mode 100644 index 0000000000..4db8c1fafe --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/GetUpdatesRequest.kt @@ -0,0 +1,17 @@ +package dev.inmo.tgbotapi.requests + +import dev.inmo.tgbotapi.requests.abstracts.Request +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.ALL_UPDATES_LIST +import dev.inmo.tgbotapi.types.Seconds +import dev.inmo.tgbotapi.types.UpdateIdentifier +import dev.inmo.tgbotapi.types.getUpdatesLimit + +interface GetUpdatesRequest : SimpleRequest { + val offset: UpdateIdentifier? + val limit: Int + val timeout: Seconds? + val allowed_updates: List? + + override fun method(): String = "getUpdates" +} \ No newline at end of file diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/RawUpdate.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/RawUpdate.kt index 5f07aed6d2..20f57940c0 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/RawUpdate.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/RawUpdate.kt @@ -67,20 +67,19 @@ internal data class RawUpdate constructor( chat_join_request != null -> ChatJoinRequestUpdate(updateId, chat_join_request) else -> UnknownUpdate( updateId, - raw.toString(), raw ) } - } catch (e: Error) { - when (e) { - is SerializationException, - is NotImplementedError -> UnknownUpdate( - updateId, - raw.toString(), - raw - ) - else -> throw e - } + } catch (e: NotImplementedError) { + UnknownUpdate( + updateId, + raw + ) + } catch (e: SerializationException) { + UnknownUpdate( + updateId, + raw + ) }.also { initedUpdate = it } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/abstracts/Update.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/abstracts/Update.kt index c1f44eeb77..168eb57ebd 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/abstracts/Update.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/abstracts/Update.kt @@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.types.update.abstracts import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded import dev.inmo.tgbotapi.types.UpdateIdentifier import dev.inmo.tgbotapi.types.update.RawUpdate +import dev.inmo.tgbotapi.types.updateIdField import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.nonstrictJsonFormat import kotlinx.serialization.* @@ -10,6 +11,9 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.longOrNull @ClassCastsIncluded interface Update { @@ -19,9 +23,12 @@ interface Update { data class UnknownUpdate( override val updateId: UpdateIdentifier, - override val data: String, + override val data: JsonElement, + val throwable: Throwable? = null +) : Update { val rawJson: JsonElement -) : Update + get() = data +} @RiskFeature object UpdateSerializerWithoutSerialization : KSerializer { @@ -44,11 +51,19 @@ object UpdateDeserializationStrategy : DeserializationStrategy { override fun deserialize(decoder: Decoder): Update { val asJson = JsonElement.serializer().deserialize(decoder) - return nonstrictJsonFormat.decodeFromJsonElement( - RawUpdate.serializer(), - asJson - ).asUpdate( - asJson - ) + return runCatching { + nonstrictJsonFormat.decodeFromJsonElement( + RawUpdate.serializer(), + asJson + ).asUpdate( + asJson + ) + }.getOrElse { + UnknownUpdate( + (asJson as? JsonObject) ?.get(updateIdField) ?.jsonPrimitive ?.longOrNull ?: -1L, + asJson, + it + ) + } } } diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/UpdatesUtils.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/UpdatesUtils.kt index add4bcbefe..c6d0e4b90c 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/UpdatesUtils.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/UpdatesUtils.kt @@ -15,7 +15,7 @@ import dev.inmo.tgbotapi.utils.extensions.asMediaGroupMessage * @see [Update.lastUpdateIdentifier] */ fun List.lastUpdateIdentifier(): UpdateIdentifier? { - return maxByOrNull { it.updateId } ?.updateId + return maxByOrNull { it.updateId } ?.updateId ?.takeIf { it > -1 } } /** diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt index a891eed163..8f83d31002 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt @@ -6,6 +6,7 @@ import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.exceptions.* import dev.inmo.tgbotapi.extensions.utils.updates.convertWithMediaGroupUpdates import dev.inmo.tgbotapi.requests.GetUpdates +import dev.inmo.tgbotapi.requests.GetUpdatesRaw import dev.inmo.tgbotapi.requests.webhook.DeleteWebhook import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage @@ -91,7 +92,9 @@ fun TelegramBot.longPollingFlow( for (update in updates) { send(update) - lastUpdateIdentifier = update.updateId + if (update.updateId > -1) { + lastUpdateIdentifier = update.updateId + } } }.onFailure { cancel(it as? CancellationException ?: return@onFailure)