mirror of
				https://github.com/InsanusMokrassar/TelegramBotAPI.git
				synced 2025-10-25 09:10:07 +00:00 
			
		
		
		
	#773 fix, improvements in updates handling
This commit is contained in:
		| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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<String>? = 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<String>? = ALL_UPDATES_LIST | ||||
| ) = getRawUpdates( | ||||
|     lastUpdate.updateId + 1, limit, timeout, allowed_updates | ||||
| ) | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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<String>? = ALL_UPDATES_LIST | ||||
| ): SimpleRequest<List<Update>> { | ||||
|     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<String>? = ALL_UPDATES_LIST | ||||
| ): GetUpdatesRequest<List<Update>> { | ||||
|     override val resultDeserializer: DeserializationStrategy<List<Update>> | ||||
|         get() = updatesListSerializer | ||||
|  | ||||
|   | ||||
| @@ -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<String>? = ALL_UPDATES_LIST | ||||
| ): GetUpdatesRequest<JsonArray> { | ||||
|     override fun method(): String = "getUpdates" | ||||
|  | ||||
|     override val resultDeserializer: DeserializationStrategy<JsonArray> | ||||
|         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)") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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<T : Any> : SimpleRequest<T> { | ||||
|     val offset: UpdateIdentifier? | ||||
|     val limit: Int | ||||
|     val timeout: Seconds? | ||||
|     val allowed_updates: List<String>? | ||||
|  | ||||
|     override fun method(): String = "getUpdates" | ||||
| } | ||||
| @@ -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( | ||||
|         } catch (e: NotImplementedError) { | ||||
|             UnknownUpdate( | ||||
|                 updateId, | ||||
|                 raw | ||||
|             ) | ||||
|         } catch (e: SerializationException) { | ||||
|             UnknownUpdate( | ||||
|                 updateId, | ||||
|                     raw.toString(), | ||||
|                 raw | ||||
|             ) | ||||
|                 else -> throw e | ||||
|             } | ||||
|         }.also { | ||||
|             initedUpdate = it | ||||
|         } | ||||
|   | ||||
| @@ -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<Update> { | ||||
| @@ -44,11 +51,19 @@ object UpdateDeserializationStrategy : DeserializationStrategy<Update> { | ||||
|  | ||||
|     override fun deserialize(decoder: Decoder): Update { | ||||
|         val asJson = JsonElement.serializer().deserialize(decoder) | ||||
|         return nonstrictJsonFormat.decodeFromJsonElement( | ||||
|         return runCatching { | ||||
|             nonstrictJsonFormat.decodeFromJsonElement( | ||||
|                 RawUpdate.serializer(), | ||||
|                 asJson | ||||
|             ).asUpdate( | ||||
|                 asJson | ||||
|             ) | ||||
|         }.getOrElse { | ||||
|             UnknownUpdate( | ||||
|                 (asJson as? JsonObject) ?.get(updateIdField) ?.jsonPrimitive ?.longOrNull ?: -1L, | ||||
|                 asJson, | ||||
|                 it | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import dev.inmo.tgbotapi.utils.extensions.asMediaGroupMessage | ||||
|  * @see [Update.lastUpdateIdentifier] | ||||
|  */ | ||||
| fun List<Update>.lastUpdateIdentifier(): UpdateIdentifier? { | ||||
|     return maxByOrNull { it.updateId } ?.updateId | ||||
|     return maxByOrNull { it.updateId } ?.updateId ?.takeIf { it > -1 } | ||||
| } | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -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,8 +92,10 @@ fun TelegramBot.longPollingFlow( | ||||
|                 for (update in updates) { | ||||
|                     send(update) | ||||
|  | ||||
|                     if (update.updateId > -1) { | ||||
|                         lastUpdateIdentifier = update.updateId | ||||
|                     } | ||||
|                 } | ||||
|             }.onFailure { | ||||
|                 cancel(it as? CancellationException ?: return@onFailure) | ||||
|             } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user