diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/get/GetUserChatBoosts.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/get/GetUserChatBoosts.kt new file mode 100644 index 0000000000..23b4d42111 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/get/GetUserChatBoosts.kt @@ -0,0 +1,19 @@ +package dev.inmo.tgbotapi.extensions.api.get + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.get.GetUserChatBoosts +import dev.inmo.tgbotapi.types.ChatIdentifier +import dev.inmo.tgbotapi.types.UserId +import dev.inmo.tgbotapi.types.chat.Chat + +suspend fun TelegramBot.getUserChatBoosts( + chatId: ChatIdentifier, + userId: UserId +) = execute( + GetUserChatBoosts(chatId = chatId, userId = userId) +) + +suspend fun TelegramBot.getUserChatBoosts( + chat: Chat, + userId: UserId +) = getUserChatBoosts(chatId = chat.id, userId = userId) \ No newline at end of file diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitChatBoostRemoved.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitChatBoostRemoved.kt new file mode 100644 index 0000000000..aefb0cad80 --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitChatBoostRemoved.kt @@ -0,0 +1,17 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations + +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +import dev.inmo.tgbotapi.extensions.utils.chatBoostRemovedUpdateOrNull +import dev.inmo.tgbotapi.requests.abstracts.Request +import dev.inmo.tgbotapi.types.boosts.ChatBoostRemoved +import kotlinx.coroutines.flow.Flow + +suspend fun BehaviourContext.waitChatBoostRemoved( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +): Flow = expectFlow( + initRequest, + errorFactory +) { + it.chatBoostRemovedUpdateOrNull() ?.data.let(::listOfNotNull) +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitChatBoostUpdated.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitChatBoostUpdated.kt new file mode 100644 index 0000000000..926f7ad712 --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitChatBoostUpdated.kt @@ -0,0 +1,17 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations + +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +import dev.inmo.tgbotapi.extensions.utils.chatBoostUpdatedUpdateOrNull +import dev.inmo.tgbotapi.requests.abstracts.Request +import dev.inmo.tgbotapi.types.boosts.ChatBoostUpdated +import kotlinx.coroutines.flow.Flow + +suspend fun BehaviourContext.waitChatBoostUpdated( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +): Flow = expectFlow( + initRequest, + errorFactory +) { + it.chatBoostUpdatedUpdateOrNull() ?.data.let(::listOfNotNull) +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ChatBoostRemovedTriggers.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ChatBoostRemovedTriggers.kt new file mode 100644 index 0000000000..3dae6f99ef --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ChatBoostRemovedTriggers.kt @@ -0,0 +1,34 @@ +@file:Suppress("unused") + +package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling + +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +import dev.inmo.tgbotapi.extensions.behaviour_builder.CustomBehaviourContextAndTwoTypesReceiver +import dev.inmo.tgbotapi.extensions.behaviour_builder.CustomBehaviourContextAndTypeReceiver +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByIdChatBoostRemovedMarkerFactory +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory +import dev.inmo.tgbotapi.extensions.utils.chatBoostRemovedUpdateOrNull +import dev.inmo.tgbotapi.types.boosts.ChatBoostRemoved +import dev.inmo.tgbotapi.types.update.abstracts.Update + +/** + * @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call + * @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example, + * this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage]. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times] + * to combinate several filters + * @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously + * in one "stream". Output of [markerFactory] will be used as a key for "stream" + * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that + * data + */ +suspend fun BC.onChatBoostRemoved( + initialFilter: SimpleFilter? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = null, + markerFactory: MarkerFactory = ByIdChatBoostRemovedMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver +) = on(markerFactory, initialFilter, subcontextUpdatesFilter, scenarioReceiver) { + (it.chatBoostRemovedUpdateOrNull() ?.data) ?.let(::listOfNotNull) +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ChatBoostUpdatedTriggers.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ChatBoostUpdatedTriggers.kt new file mode 100644 index 0000000000..5e94dab7ce --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ChatBoostUpdatedTriggers.kt @@ -0,0 +1,34 @@ +@file:Suppress("unused") + +package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling + +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +import dev.inmo.tgbotapi.extensions.behaviour_builder.CustomBehaviourContextAndTwoTypesReceiver +import dev.inmo.tgbotapi.extensions.behaviour_builder.CustomBehaviourContextAndTypeReceiver +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByIdChatBoostUpdatedMarkerFactory +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory +import dev.inmo.tgbotapi.extensions.utils.chatBoostUpdatedUpdateOrNull +import dev.inmo.tgbotapi.types.boosts.ChatBoostUpdated +import dev.inmo.tgbotapi.types.update.abstracts.Update + +/** + * @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call + * @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example, + * this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage]. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times] + * to combinate several filters + * @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously + * in one "stream". Output of [markerFactory] will be used as a key for "stream" + * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that + * data + */ +suspend fun BC.onChatBoostUpdated( + initialFilter: SimpleFilter? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = null, + markerFactory: MarkerFactory = ByIdChatBoostUpdatedMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver +) = on(markerFactory, initialFilter, subcontextUpdatesFilter, scenarioReceiver) { + (it.chatBoostUpdatedUpdateOrNull() ?.data) ?.let(::listOfNotNull) +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/ByIdChatBoostRemovedMarkerFactory.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/ByIdChatBoostRemovedMarkerFactory.kt new file mode 100644 index 0000000000..ad9957e859 --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/ByIdChatBoostRemovedMarkerFactory.kt @@ -0,0 +1,9 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories + +import dev.inmo.tgbotapi.types.boosts.ChatBoostRemoved +import dev.inmo.tgbotapi.types.boosts.ChatBoostUpdated +import dev.inmo.tgbotapi.types.polls.PollAnswer + +object ByIdChatBoostRemovedMarkerFactory : MarkerFactory { + override suspend fun invoke(data: ChatBoostRemoved) = data.chat.id +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/ByIdChatBoostUpdatedMarkerFactory.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/ByIdChatBoostUpdatedMarkerFactory.kt new file mode 100644 index 0000000000..a72040ff8d --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/marker_factories/ByIdChatBoostUpdatedMarkerFactory.kt @@ -0,0 +1,8 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories + +import dev.inmo.tgbotapi.types.boosts.ChatBoostUpdated +import dev.inmo.tgbotapi.types.polls.PollAnswer + +object ByIdChatBoostUpdatedMarkerFactory : MarkerFactory { + override suspend fun invoke(data: ChatBoostUpdated) = data.chat.id +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/WithMessageId.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/WithMessageId.kt new file mode 100644 index 0000000000..681f869ef3 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/WithMessageId.kt @@ -0,0 +1,10 @@ +package dev.inmo.tgbotapi.abstracts + +import dev.inmo.tgbotapi.types.MessageId + +/** + * All inheritors of this interface have [messageId] field and related to this [messageId] + */ +interface WithMessageId { + val messageId: MessageId +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/WithPreviewChatAndMessageId.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/WithPreviewChatAndMessageId.kt new file mode 100644 index 0000000000..5465e73650 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/WithPreviewChatAndMessageId.kt @@ -0,0 +1,3 @@ +package dev.inmo.tgbotapi.abstracts + +interface WithPreviewChatAndMessageId : WithPreviewChat, WithMessageId diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/MessageAction.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/MessageAction.kt index 16ac3bd26e..4e38baa5bd 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/MessageAction.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/MessageAction.kt @@ -1,7 +1,5 @@ package dev.inmo.tgbotapi.abstracts.types -import dev.inmo.tgbotapi.types.MessageId +import dev.inmo.tgbotapi.abstracts.WithMessageId -interface MessageAction: ChatRequest { - val messageId: MessageId -} +interface MessageAction : ChatRequest, WithMessageId diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/get/GetUserChatBoosts.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/get/GetUserChatBoosts.kt new file mode 100644 index 0000000000..0e4e08b8de --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/get/GetUserChatBoosts.kt @@ -0,0 +1,24 @@ +package dev.inmo.tgbotapi.requests.get + +import dev.inmo.tgbotapi.abstracts.types.ChatRequest +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.boosts.UserChatBoosts +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationStrategy + +@Serializable +data class GetUserChatBoosts( + @SerialName(chatIdField) + override val chatId: ChatIdentifier, + @SerialName(userIdField) + val userId: UserId +) : SimpleRequest, ChatRequest { + override fun method(): String = "getUserChatBoosts" + override val resultDeserializer: DeserializationStrategy + get() = UserChatBoosts.serializer() + override val requestSerializer: SerializationStrategy<*> + get() = serializer() +} \ No newline at end of file diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt index 0b703caf8d..e66e407779 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt @@ -202,6 +202,7 @@ const val tgWebAppStartParamField = "tgWebAppStartParam" const val chatIdField = "chat_id" const val senderChatIdField = "sender_chat_id" const val messageIdField = "message_id" +const val giveawayMessageIdField = "giveaway_message_id" const val messageIdsField = "message_ids" const val actorChatField = "actor_chat" const val messageThreadIdField = "message_thread_id" @@ -609,6 +610,7 @@ const val secretField = "secret" const val errorsField = "errors" const val sourceField = "source" +const val isUnclaimedField = "is_unclaimed" const val fieldNameField = "field_name" const val dataHashField = "data_hash" const val fileHashField = "file_hash" @@ -634,3 +636,10 @@ const val buttonTextField = "button_text" const val webAppField = "web_app" const val webAppNameField = "web_app_name" const val menuButtonField = "menu_button" + +const val boostIdField = "boost_id" +const val boostField = "boost" +const val addDateField = "add_date" +const val expirationDateField = "expiration_date" +const val removeDateField = "remove_date" +const val boostsField = "boosts" diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/BoostId.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/BoostId.kt new file mode 100644 index 0000000000..a26387039c --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/BoostId.kt @@ -0,0 +1,10 @@ +package dev.inmo.tgbotapi.types.boosts + +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline + +@Serializable +@JvmInline +value class BoostId( + val string: String +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoost.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoost.kt new file mode 100644 index 0000000000..5ed94eafbc --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoost.kt @@ -0,0 +1,17 @@ +package dev.inmo.tgbotapi.types.boosts + +import dev.inmo.tgbotapi.types.* +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ChatBoost( + @SerialName(boostIdField) + val id: BoostId, + @SerialName(addDateField) + val addDate: TelegramDate, + @SerialName(expirationDateField) + val expirationDate: TelegramDate, + @SerialName(sourceField) + val source: ChatBoostSource +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoostRemoved.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoostRemoved.kt new file mode 100644 index 0000000000..070d326a5e --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoostRemoved.kt @@ -0,0 +1,19 @@ +package dev.inmo.tgbotapi.types.boosts + +import dev.inmo.tgbotapi.abstracts.WithPreviewChat +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.chat.PreviewChat +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ChatBoostRemoved( + @SerialName(chatField) + override val chat: PreviewChat, + @SerialName(boostIdField) + val boostId: BoostId, + @SerialName(removeDateField) + val removeDate: TelegramDate, + @SerialName(sourceField) + val source: ChatBoostSource +) : WithPreviewChat diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoostSource.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoostSource.kt new file mode 100644 index 0000000000..1fc74ec9c8 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoostSource.kt @@ -0,0 +1,171 @@ +package dev.inmo.tgbotapi.types.boosts + +import dev.inmo.tgbotapi.abstracts.WithMessageId +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.chat.PreviewUser +import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Required +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.decodeFromJsonElement + +@Serializable(ChatBoostSource.Companion::class) +@ClassCastsIncluded +sealed interface ChatBoostSource { + val sourceName: String + val user: PreviewUser? + + sealed interface ByUser : ChatBoostSource { + override val user: PreviewUser + } + + @Serializable(ChatBoostSource.Companion::class) + data class Premium( + @SerialName(userField) + override val user: PreviewUser + ) : ByUser { + @Required + @SerialName(sourceField) + override val sourceName: String = sourceCode + + companion object { + const val sourceCode = "premium" + } + } + + @Serializable(ChatBoostSource.Companion::class) + data class GiftCode( + @SerialName(userField) + override val user: PreviewUser + ) : ByUser { + @Required + @SerialName(sourceField) + override val sourceName: String = sourceCode + + companion object { + const val sourceCode = "gift_code" + } + } + + @Serializable(ChatBoostSource.Companion::class) + sealed interface Giveaway : ChatBoostSource, WithMessageId { + val unclaimed: Boolean + val claimed: Boolean + get() = !unclaimed + + @Serializable(ChatBoostSource.Companion::class) + data class Claimed( + @SerialName(giveawayMessageIdField) + override val messageId: MessageId, + @SerialName(userField) + override val user: PreviewUser + ) : Giveaway, ByUser { + @Required + @SerialName(sourceField) + override val sourceName: String = Giveaway.sourceCode + @Required + @SerialName(isUnclaimedField) + override val unclaimed: Boolean = false + } + + @Serializable(ChatBoostSource.Companion::class) + data class Unclaimed( + @SerialName(giveawayMessageIdField) + override val messageId: MessageId + ) : Giveaway { + @Required + @SerialName(sourceField) + override val sourceName: String = Giveaway.sourceCode + @Required + @SerialName(isUnclaimedField) + override val unclaimed: Boolean = true + @SerialName(userField) + override val user: PreviewUser? = null + } + + companion object { + val sourceCode = "giveaway" + } + } + + @Serializable(ChatBoostSource.Companion::class) + data class Unknown( + override val sourceName: String, + override val user: PreviewUser?, + val json: JsonElement? + ) : ChatBoostSource + + @Serializable + private data class Surrogate( + @Required + @SerialName(sourceField) + val sourceName: String, + @SerialName(userField) + val user: PreviewUser? = null, + @SerialName(giveawayMessageIdField) + val messageId: MessageId? = null, + @SerialName(isUnclaimedField) + val unclaimed: Boolean? = null + ) + + companion object : KSerializer { + override val descriptor: SerialDescriptor + get() = Surrogate.serializer().descriptor + + override fun deserialize(decoder: Decoder): ChatBoostSource { + val (surrogate, json) = when { + decoder is JsonDecoder -> { + val json = decoder.decodeJsonElement() + val surrogate = decoder.json.decodeFromJsonElement(Surrogate.serializer(), json) + surrogate to json + } + else -> Surrogate.serializer().deserialize(decoder) to null + } + + return when { + surrogate.sourceName == Premium.sourceCode && surrogate.user != null -> { + Premium(surrogate.user) + } + surrogate.sourceName == GiftCode.sourceCode && surrogate.user != null -> { + GiftCode(surrogate.user) + } + surrogate.sourceName == Giveaway.sourceCode && surrogate.messageId != null -> { + when { + surrogate.user != null && surrogate.unclaimed == false -> Giveaway.Claimed( + surrogate.messageId, + surrogate.user + ) + surrogate.unclaimed == true -> Giveaway.Unclaimed( + surrogate.messageId + ) + else -> null + } + } + else -> null + } ?: Unknown(surrogate.sourceName, surrogate.user, json) + } + + override fun serialize(encoder: Encoder, value: ChatBoostSource) { + if (value is Unknown && value.json != null) { + JsonElement.serializer().serialize(encoder, value.json) + return + } + + val surrogate = Surrogate( + value.sourceName, + value.user, + (value as? Giveaway) ?.messageId, + (value as? Giveaway) ?.unclaimed, + ) + + Surrogate.serializer().serialize(encoder, surrogate) + } + + } +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoostUpdated.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoostUpdated.kt new file mode 100644 index 0000000000..7f2180ea40 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/ChatBoostUpdated.kt @@ -0,0 +1,16 @@ +package dev.inmo.tgbotapi.types.boosts + +import dev.inmo.tgbotapi.abstracts.WithPreviewChat +import dev.inmo.tgbotapi.types.boostField +import dev.inmo.tgbotapi.types.chat.PreviewChat +import dev.inmo.tgbotapi.types.chatField +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ChatBoostUpdated( + @SerialName(chatField) + override val chat: PreviewChat, + @SerialName(boostField) + val boost: ChatBoost +) : WithPreviewChat diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/UserChatBoosts.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/UserChatBoosts.kt new file mode 100644 index 0000000000..caac5cbc5c --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/boosts/UserChatBoosts.kt @@ -0,0 +1,11 @@ +package dev.inmo.tgbotapi.types.boosts + +import dev.inmo.tgbotapi.types.boostsField +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserChatBoosts( + @SerialName(boostsField) + val boosts: List +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/chat/ChatMessageReactionUpdated.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/chat/ChatMessageReactionUpdated.kt index ba919d69bb..96ecb4a24b 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/chat/ChatMessageReactionUpdated.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/chat/ChatMessageReactionUpdated.kt @@ -1,5 +1,7 @@ package dev.inmo.tgbotapi.types.chat +import dev.inmo.tgbotapi.abstracts.WithPreviewChat +import dev.inmo.tgbotapi.abstracts.WithPreviewChatAndMessageId import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.reactions.Reaction import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded @@ -14,9 +16,7 @@ import kotlinx.serialization.json.JsonElement @Serializable(ChatMessageReactionUpdated.Companion::class) @ClassCastsIncluded -sealed interface ChatMessageReactionUpdated { - val chat: PreviewChat - val messageId: MessageIdentifier +sealed interface ChatMessageReactionUpdated : WithPreviewChatAndMessageId { val reactedUser: PreviewUser? val reactedChat: PreviewChat? val date: TelegramDate diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/chat/ChatMessageReactionsCountUpdated.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/chat/ChatMessageReactionsCountUpdated.kt index 28f440fbd0..313b754fb1 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/chat/ChatMessageReactionsCountUpdated.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/chat/ChatMessageReactionsCountUpdated.kt @@ -1,5 +1,7 @@ package dev.inmo.tgbotapi.types.chat +import dev.inmo.tgbotapi.abstracts.WithPreviewChat +import dev.inmo.tgbotapi.abstracts.WithPreviewChatAndMessageId import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.reactions.ReactionsCount import kotlinx.serialization.SerialName @@ -8,12 +10,12 @@ import kotlinx.serialization.Serializable @Serializable data class ChatMessageReactionsCountUpdated( @SerialName(chatField) - val chat: PreviewChat, + override val chat: PreviewChat, @SerialName(messageIdField) - val messageId: MessageIdentifier, + override val messageId: MessageIdentifier, @Serializable(TelegramDateSerializer::class) @SerialName(dateField) val date: TelegramDate, @SerialName(reactionsField) val reactions: List -) +) : WithPreviewChatAndMessageId diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/abstracts/Message.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/abstracts/Message.kt index 0b34ea85a1..5e1fcba29c 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/abstracts/Message.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/abstracts/Message.kt @@ -1,7 +1,9 @@ package dev.inmo.tgbotapi.types.message.abstracts +import dev.inmo.tgbotapi.abstracts.WithMessageId import korlibs.time.DateTime import dev.inmo.tgbotapi.abstracts.WithPreviewChat +import dev.inmo.tgbotapi.abstracts.WithPreviewChatAndMessageId import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded import dev.inmo.tgbotapi.types.MessageId import dev.inmo.tgbotapi.types.chat.Chat @@ -13,8 +15,7 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @ClassCastsIncluded(excludeRegex = ".*Impl") -interface Message : WithPreviewChat { - val messageId: MessageId +interface Message : WithPreviewChatAndMessageId { val date: DateTime } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/ChatBoostRemovedUpdate.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/ChatBoostRemovedUpdate.kt new file mode 100644 index 0000000000..2e13161d9f --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/ChatBoostRemovedUpdate.kt @@ -0,0 +1,14 @@ +package dev.inmo.tgbotapi.types.update + +import dev.inmo.tgbotapi.types.UpdateIdentifier +import dev.inmo.tgbotapi.types.boosts.ChatBoostRemoved +import dev.inmo.tgbotapi.types.boosts.ChatBoostUpdated +import dev.inmo.tgbotapi.types.chat.ChatMessageReactionUpdated +import dev.inmo.tgbotapi.types.update.abstracts.Update +import kotlinx.serialization.Serializable + +@Serializable +data class ChatBoostRemovedUpdate( + override val updateId: UpdateIdentifier, + override val data: ChatBoostRemoved +) : Update \ No newline at end of file diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/ChatBoostUpdatedUpdate.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/ChatBoostUpdatedUpdate.kt new file mode 100644 index 0000000000..e23e963550 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/ChatBoostUpdatedUpdate.kt @@ -0,0 +1,13 @@ +package dev.inmo.tgbotapi.types.update + +import dev.inmo.tgbotapi.types.UpdateIdentifier +import dev.inmo.tgbotapi.types.boosts.ChatBoostUpdated +import dev.inmo.tgbotapi.types.chat.ChatMessageReactionUpdated +import dev.inmo.tgbotapi.types.update.abstracts.Update +import kotlinx.serialization.Serializable + +@Serializable +data class ChatBoostUpdatedUpdate( + override val updateId: UpdateIdentifier, + override val data: ChatBoostUpdated +) : Update \ 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 61cee808e6..c7894c973c 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 @@ -4,6 +4,8 @@ import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.queries.callback.RawCallbackQuery import dev.inmo.tgbotapi.types.InlineQueries.ChosenInlineResult.RawChosenInlineResult import dev.inmo.tgbotapi.types.InlineQueries.query.RawInlineQuery +import dev.inmo.tgbotapi.types.boosts.ChatBoostRemoved +import dev.inmo.tgbotapi.types.boosts.ChatBoostUpdated import dev.inmo.tgbotapi.types.chat.ChatJoinRequest import dev.inmo.tgbotapi.types.chat.ChatMessageReactionUpdated import dev.inmo.tgbotapi.types.chat.ChatMessageReactionsCountUpdated @@ -42,7 +44,9 @@ internal data class RawUpdate constructor( private val chat_member: ChatMemberUpdated? = null, private val chat_join_request: ChatJoinRequest? = null, private val message_reaction: ChatMessageReactionUpdated? = null, - private val message_reaction_count: ChatMessageReactionsCountUpdated? = null + private val message_reaction_count: ChatMessageReactionsCountUpdated? = null, + private val chat_boost: ChatBoostUpdated? = null, + private val removed_chat_boost: ChatBoostRemoved? = null ) { private var initedUpdate: Update? = null /** @@ -71,6 +75,8 @@ internal data class RawUpdate constructor( chat_join_request != null -> ChatJoinRequestUpdate(updateId, chat_join_request) message_reaction != null -> ChatMessageReactionUpdatedUpdate(updateId, message_reaction) message_reaction_count != null -> ChatMessageReactionsCountUpdatedUpdate(updateId, message_reaction_count) + chat_boost != null -> ChatBoostUpdatedUpdate(updateId, chat_boost) + removed_chat_boost != null -> ChatBoostRemovedUpdate(updateId, removed_chat_boost) else -> UnknownUpdate( updateId, raw diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt index 40e1bfd106..b2886f2cf8 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt @@ -39,6 +39,8 @@ interface FlowsUpdatesFilter : UpdatesFilter { val chatJoinRequestUpdateFlow: Flow val chatMessageReactionUpdatedUpdateFlow: Flow val chatMessageReactionsCountUpdatedUpdateFlow: Flow + val chatBoostUpdatedUpdateFlow: Flow + val chatBoostRemovedUpdateFlow: Flow val unknownUpdatesFlow: Flow } @@ -60,6 +62,8 @@ abstract class AbstractFlowsUpdatesFilter : FlowsUpdatesFilter { override val chatMessageReactionUpdatedUpdateFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } override val chatMessageReactionsCountUpdatedUpdateFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } override val unknownUpdatesFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } + override val chatBoostUpdatedUpdateFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } + override val chatBoostRemovedUpdateFlow: Flow by lazy { allUpdatesFlow.filterIsInstance() } } /** diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCastsNew.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCastsNew.kt index 99730c0981..b4fc81fc39 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCastsNew.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCastsNew.kt @@ -90,6 +90,7 @@ import dev.inmo.tgbotapi.types.actions.UploadPhotoAction import dev.inmo.tgbotapi.types.actions.UploadVideoAction import dev.inmo.tgbotapi.types.actions.UploadVideoNoteAction import dev.inmo.tgbotapi.types.actions.UploadVoiceAction +import dev.inmo.tgbotapi.types.boosts.ChatBoostSource import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackGameInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.InlineKeyboardButton @@ -424,6 +425,8 @@ import dev.inmo.tgbotapi.types.request.RequestResponse import dev.inmo.tgbotapi.types.request.UsersShared import dev.inmo.tgbotapi.types.update.CallbackQueryUpdate import dev.inmo.tgbotapi.types.update.ChannelPostUpdate +import dev.inmo.tgbotapi.types.update.ChatBoostRemovedUpdate +import dev.inmo.tgbotapi.types.update.ChatBoostUpdatedUpdate import dev.inmo.tgbotapi.types.update.ChatJoinRequestUpdate import dev.inmo.tgbotapi.types.update.ChatMessageReactionUpdatedUpdate import dev.inmo.tgbotapi.types.update.ChatMessageReactionsCountUpdatedUpdate @@ -1620,6 +1623,69 @@ public inline fun BotAction.customBotActionOrThrow(): CustomBotAction = this as public inline fun BotAction.ifCustomBotAction(block: (CustomBotAction) -> T): T? = customBotActionOrNull() ?.let(block) +public inline fun ChatBoostSource.byUserOrNull(): ChatBoostSource.ByUser? = this as? + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.ByUser + +public inline fun ChatBoostSource.byUserOrThrow(): ChatBoostSource.ByUser = this as + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.ByUser + +public inline fun ChatBoostSource.ifByUser(block: (ChatBoostSource.ByUser) -> T): T? = + byUserOrNull() ?.let(block) + +public inline fun ChatBoostSource.giftCodeOrNull(): ChatBoostSource.GiftCode? = this as? + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.GiftCode + +public inline fun ChatBoostSource.giftCodeOrThrow(): ChatBoostSource.GiftCode = this as + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.GiftCode + +public inline fun ChatBoostSource.ifGiftCode(block: (ChatBoostSource.GiftCode) -> T): T? = + giftCodeOrNull() ?.let(block) + +public inline fun ChatBoostSource.claimedOrNull(): ChatBoostSource.Giveaway.Claimed? = this as? + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Giveaway.Claimed + +public inline fun ChatBoostSource.claimedOrThrow(): ChatBoostSource.Giveaway.Claimed = this as + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Giveaway.Claimed + +public inline fun ChatBoostSource.ifClaimed(block: (ChatBoostSource.Giveaway.Claimed) -> T): T? + = claimedOrNull() ?.let(block) + +public inline fun ChatBoostSource.premiumOrNull(): ChatBoostSource.Premium? = this as? + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Premium + +public inline fun ChatBoostSource.premiumOrThrow(): ChatBoostSource.Premium = this as + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Premium + +public inline fun ChatBoostSource.ifPremium(block: (ChatBoostSource.Premium) -> T): T? = + premiumOrNull() ?.let(block) + +public inline fun ChatBoostSource.giveawayOrNull(): ChatBoostSource.Giveaway? = this as? + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Giveaway + +public inline fun ChatBoostSource.giveawayOrThrow(): ChatBoostSource.Giveaway = this as + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Giveaway + +public inline fun ChatBoostSource.ifGiveaway(block: (ChatBoostSource.Giveaway) -> T): T? = + giveawayOrNull() ?.let(block) + +public inline fun ChatBoostSource.unclaimedOrNull(): ChatBoostSource.Giveaway.Unclaimed? = this as? + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Giveaway.Unclaimed + +public inline fun ChatBoostSource.unclaimedOrThrow(): ChatBoostSource.Giveaway.Unclaimed = this as + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Giveaway.Unclaimed + +public inline fun ChatBoostSource.ifUnclaimed(block: (ChatBoostSource.Giveaway.Unclaimed) -> T): + T? = unclaimedOrNull() ?.let(block) + +public inline fun ChatBoostSource.unknownOrNull(): ChatBoostSource.Unknown? = this as? + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Unknown + +public inline fun ChatBoostSource.unknownOrThrow(): ChatBoostSource.Unknown = this as + dev.inmo.tgbotapi.types.boosts.ChatBoostSource.Unknown + +public inline fun ChatBoostSource.ifUnknown(block: (ChatBoostSource.Unknown) -> T): T? = + unknownOrNull() ?.let(block) + public inline fun InlineKeyboardButton.unknownInlineKeyboardButtonOrNull(): UnknownInlineKeyboardButton? = this as? dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.UnknownInlineKeyboardButton @@ -4603,6 +4669,24 @@ public inline fun Update.channelPostUpdateOrThrow(): ChannelPostUpdate = this as public inline fun Update.ifChannelPostUpdate(block: (ChannelPostUpdate) -> T): T? = channelPostUpdateOrNull() ?.let(block) +public inline fun Update.chatBoostRemovedUpdateOrNull(): ChatBoostRemovedUpdate? = this as? + dev.inmo.tgbotapi.types.update.ChatBoostRemovedUpdate + +public inline fun Update.chatBoostRemovedUpdateOrThrow(): ChatBoostRemovedUpdate = this as + dev.inmo.tgbotapi.types.update.ChatBoostRemovedUpdate + +public inline fun Update.ifChatBoostRemovedUpdate(block: (ChatBoostRemovedUpdate) -> T): T? = + chatBoostRemovedUpdateOrNull() ?.let(block) + +public inline fun Update.chatBoostUpdatedUpdateOrNull(): ChatBoostUpdatedUpdate? = this as? + dev.inmo.tgbotapi.types.update.ChatBoostUpdatedUpdate + +public inline fun Update.chatBoostUpdatedUpdateOrThrow(): ChatBoostUpdatedUpdate = this as + dev.inmo.tgbotapi.types.update.ChatBoostUpdatedUpdate + +public inline fun Update.ifChatBoostUpdatedUpdate(block: (ChatBoostUpdatedUpdate) -> T): T? = + chatBoostUpdatedUpdateOrNull() ?.let(block) + public inline fun Update.chatJoinRequestUpdateOrNull(): ChatJoinRequestUpdate? = this as? dev.inmo.tgbotapi.types.update.ChatJoinRequestUpdate diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt index c92600b96a..b7831999b9 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt @@ -42,6 +42,8 @@ fun Update.sourceChatWithConverters( myChatMemberUpdatedUpdateConverter: (MyChatMemberUpdatedUpdate) -> Chat? = { it.data.chat }, chatMessageReactionUpdatedUpdateConverter: (ChatMessageReactionUpdatedUpdate) -> Chat? = { it.data.chat }, chatMessageReactionsCountUpdatedUpdateConverter: (ChatMessageReactionsCountUpdatedUpdate) -> Chat? = { it.data.chat }, + chatBoostUpdatedUpdateFlow: (ChatBoostUpdatedUpdate) -> Chat? = { it.data.chat }, + chatBoostRemovedUpdateFlow: (ChatBoostRemovedUpdate) -> Chat? = { it.data.chat }, commonChatMemberUpdatedUpdateConverter: (CommonChatMemberUpdatedUpdate) -> Chat? = { it.data.chat } ): Chat? = when (this) { is BaseMessageUpdate -> baseMessageUpdateConverter(this) @@ -61,6 +63,8 @@ fun Update.sourceChatWithConverters( is CommonChatMemberUpdatedUpdate -> commonChatMemberUpdatedUpdateConverter(this) is ChatMessageReactionUpdatedUpdate -> chatMessageReactionUpdatedUpdateConverter(this) is ChatMessageReactionsCountUpdatedUpdate -> chatMessageReactionsCountUpdatedUpdateConverter(this) + is ChatBoostUpdatedUpdate -> chatBoostUpdatedUpdateFlow(this) + is ChatBoostRemovedUpdate -> chatBoostRemovedUpdateFlow(this) else -> { when (val data = data) { is FromUser -> data.from