#773 fix, improvements in updates handling

This commit is contained in:
InsanusMokrassar 2023-06-30 16:35:14 +06:00
parent 81ed820602
commit 2e815a4c37
10 changed files with 130 additions and 30 deletions

View File

@ -4,6 +4,10 @@
**THIS UPDATE CONTAINS BREAKING CHANGES: USERNAMES OF BOTS NOW BECAME NULLABLE** **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`: * `Utils`:
* Improve extension `Update.sourceChat` to add opportunity to select some chats by logic different with the default * Improve extension `Update.sourceChat` to add opportunity to select some chats by logic different with the default

View File

@ -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
)

View File

@ -3,7 +3,7 @@ package dev.inmo.tgbotapi.bot.ktor.base
import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory
import dev.inmo.tgbotapi.bot.exceptions.newRequestException 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.requests.abstracts.Request
import dev.inmo.tgbotapi.types.Response import dev.inmo.tgbotapi.types.Response
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
@ -35,7 +35,7 @@ abstract class AbstractRequestCallFactory : KtorCallFactory {
) )
accept(ContentType.Application.Json) accept(ContentType.Application.Json)
if (request is GetUpdates) { if (request is GetUpdatesRequest) {
request.timeout?.times(1000L) ?.let { customTimeoutMillis -> request.timeout?.times(1000L) ?.let { customTimeoutMillis ->
if (customTimeoutMillis > 0) { if (customTimeoutMillis > 0) {
timeout { timeout {

View File

@ -22,13 +22,11 @@ private val updatesListSerializer = ListSerializer(
*/ */
@Serializable @Serializable
data class GetUpdates( data class GetUpdates(
val offset: UpdateIdentifier? = null,// set `last update id + 1` to receive next part of updates override val offset: UpdateIdentifier? = null,// set `last update id + 1` to receive next part of updates
val limit: Int = getUpdatesLimit.last, override val limit: Int = getUpdatesLimit.last,
val timeout: Seconds? = null, override val timeout: Seconds? = null,
val allowed_updates: List<String>? = ALL_UPDATES_LIST override val allowed_updates: List<String>? = ALL_UPDATES_LIST
): SimpleRequest<List<Update>> { ): GetUpdatesRequest<List<Update>> {
override fun method(): String = "getUpdates"
override val resultDeserializer: DeserializationStrategy<List<Update>> override val resultDeserializer: DeserializationStrategy<List<Update>>
get() = updatesListSerializer get() = updatesListSerializer

View File

@ -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)")
}
}
}

View File

@ -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"
}

View File

@ -67,20 +67,19 @@ internal data class RawUpdate constructor(
chat_join_request != null -> ChatJoinRequestUpdate(updateId, chat_join_request) chat_join_request != null -> ChatJoinRequestUpdate(updateId, chat_join_request)
else -> UnknownUpdate( else -> UnknownUpdate(
updateId, updateId,
raw.toString(),
raw raw
) )
} }
} catch (e: Error) { } catch (e: NotImplementedError) {
when (e) { UnknownUpdate(
is SerializationException, updateId,
is NotImplementedError -> UnknownUpdate( raw
updateId, )
raw.toString(), } catch (e: SerializationException) {
raw UnknownUpdate(
) updateId,
else -> throw e raw
} )
}.also { }.also {
initedUpdate = it initedUpdate = it
} }

View File

@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.types.update.abstracts
import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded
import dev.inmo.tgbotapi.types.UpdateIdentifier import dev.inmo.tgbotapi.types.UpdateIdentifier
import dev.inmo.tgbotapi.types.update.RawUpdate import dev.inmo.tgbotapi.types.update.RawUpdate
import dev.inmo.tgbotapi.types.updateIdField
import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.nonstrictJsonFormat import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
import kotlinx.serialization.* import kotlinx.serialization.*
@ -10,6 +11,9 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.longOrNull
@ClassCastsIncluded @ClassCastsIncluded
interface Update { interface Update {
@ -19,9 +23,12 @@ interface Update {
data class UnknownUpdate( data class UnknownUpdate(
override val updateId: UpdateIdentifier, override val updateId: UpdateIdentifier,
override val data: String, override val data: JsonElement,
val throwable: Throwable? = null
) : Update {
val rawJson: JsonElement val rawJson: JsonElement
) : Update get() = data
}
@RiskFeature @RiskFeature
object UpdateSerializerWithoutSerialization : KSerializer<Update> { object UpdateSerializerWithoutSerialization : KSerializer<Update> {
@ -44,11 +51,19 @@ object UpdateDeserializationStrategy : DeserializationStrategy<Update> {
override fun deserialize(decoder: Decoder): Update { override fun deserialize(decoder: Decoder): Update {
val asJson = JsonElement.serializer().deserialize(decoder) val asJson = JsonElement.serializer().deserialize(decoder)
return nonstrictJsonFormat.decodeFromJsonElement( return runCatching {
RawUpdate.serializer(), nonstrictJsonFormat.decodeFromJsonElement(
asJson RawUpdate.serializer(),
).asUpdate( asJson
asJson ).asUpdate(
) asJson
)
}.getOrElse {
UnknownUpdate(
(asJson as? JsonObject) ?.get(updateIdField) ?.jsonPrimitive ?.longOrNull ?: -1L,
asJson,
it
)
}
} }
} }

View File

@ -15,7 +15,7 @@ import dev.inmo.tgbotapi.utils.extensions.asMediaGroupMessage
* @see [Update.lastUpdateIdentifier] * @see [Update.lastUpdateIdentifier]
*/ */
fun List<Update>.lastUpdateIdentifier(): UpdateIdentifier? { fun List<Update>.lastUpdateIdentifier(): UpdateIdentifier? {
return maxByOrNull { it.updateId } ?.updateId return maxByOrNull { it.updateId } ?.updateId ?.takeIf { it > -1 }
} }
/** /**

View File

@ -6,6 +6,7 @@ import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.bot.exceptions.* import dev.inmo.tgbotapi.bot.exceptions.*
import dev.inmo.tgbotapi.extensions.utils.updates.convertWithMediaGroupUpdates import dev.inmo.tgbotapi.extensions.utils.updates.convertWithMediaGroupUpdates
import dev.inmo.tgbotapi.requests.GetUpdates import dev.inmo.tgbotapi.requests.GetUpdates
import dev.inmo.tgbotapi.requests.GetUpdatesRaw
import dev.inmo.tgbotapi.requests.webhook.DeleteWebhook import dev.inmo.tgbotapi.requests.webhook.DeleteWebhook
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
@ -91,7 +92,9 @@ fun TelegramBot.longPollingFlow(
for (update in updates) { for (update in updates) {
send(update) send(update)
lastUpdateIdentifier = update.updateId if (update.updateId > -1) {
lastUpdateIdentifier = update.updateId
}
} }
}.onFailure { }.onFailure {
cancel(it as? CancellationException ?: return@onFailure) cancel(it as? CancellationException ?: return@onFailure)