From 687f9e95fad2ed3be7270bd9a36845548a6c2581 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 5 Feb 2023 20:46:12 +0600 Subject: [PATCH] support of user shared/chat shared --- .../expectations/WaitEventAction.kt | 18 +++ .../expectations/WaitEventActionMessages.kt | 38 +++++++ .../triggers_handling/EventTriggers.kt | 65 +++++++++++ .../kotlin/dev/inmo/tgbotapi/types/Common.kt | 13 +++ .../tgbotapi/types/buttons/KeyboardButton.kt | 52 +++++++++ .../buttons/KeyboardButtonRequestChat.kt | 38 +++++++ .../buttons/KeyboardButtonRequestUser.kt | 89 +++++++++++++++ .../reply/ReplyKeyboardButtonsShortcuts.kt | 98 +++++++++++++++++ .../inmo/tgbotapi/types/message/RawMessage.kt | 7 ++ .../inmo/tgbotapi/types/request/ChatShared.kt | 17 +++ .../types/request/ChatSharedRequest.kt | 8 ++ .../inmo/tgbotapi/types/request/RequestId.kt | 10 ++ .../tgbotapi/types/request/RequestResponse.kt | 5 + .../inmo/tgbotapi/types/request/UserShared.kt | 19 ++++ .../types/buttons/ReplyKeyboardBuilder.kt | 104 ++++++++++++++++++ 15 files changed, 581 insertions(+) create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButtonRequestChat.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButtonRequestUser.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/ChatShared.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/ChatSharedRequest.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/RequestId.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/RequestResponse.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/UserShared.kt diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitEventAction.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitEventAction.kt index ec6e5f8572..2fb21d12f3 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitEventAction.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitEventAction.kt @@ -17,6 +17,9 @@ import dev.inmo.tgbotapi.types.message.ChatEvents.forum.WriteAccessAllowed import dev.inmo.tgbotapi.types.message.ChatEvents.voice.* import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage import dev.inmo.tgbotapi.types.message.payments.SuccessfulPaymentEvent +import dev.inmo.tgbotapi.types.request.ChatShared +import dev.inmo.tgbotapi.types.request.ChatSharedRequest +import dev.inmo.tgbotapi.types.request.UserShared import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.lowLevelRiskFeatureMessage import kotlinx.coroutines.flow.Flow @@ -171,3 +174,18 @@ suspend fun BehaviourContext.waitWriteAccessAllowed( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null } ) = waitEvents(initRequest, errorFactory) + +suspend fun BehaviourContext.waitChatSharedRequest( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEvents(initRequest, errorFactory) + +suspend fun BehaviourContext.waitUserShared( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEvents(initRequest, errorFactory) + +suspend fun BehaviourContext.waitChatShared( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEvents(initRequest, errorFactory) diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitEventActionMessages.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitEventActionMessages.kt index a26a4215ad..9539f3d2fd 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitEventActionMessages.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitEventActionMessages.kt @@ -9,10 +9,17 @@ import dev.inmo.tgbotapi.types.message.ChatEvents.* import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.* import dev.inmo.tgbotapi.types.message.ChatEvents.forum.ForumTopicClosed import dev.inmo.tgbotapi.types.message.ChatEvents.forum.ForumTopicCreated +import dev.inmo.tgbotapi.types.message.ChatEvents.forum.ForumTopicEdited import dev.inmo.tgbotapi.types.message.ChatEvents.forum.ForumTopicReopened +import dev.inmo.tgbotapi.types.message.ChatEvents.forum.GeneralForumTopicHidden +import dev.inmo.tgbotapi.types.message.ChatEvents.forum.GeneralForumTopicUnhidden +import dev.inmo.tgbotapi.types.message.ChatEvents.forum.WriteAccessAllowed import dev.inmo.tgbotapi.types.message.ChatEvents.voice.* import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage import dev.inmo.tgbotapi.types.message.payments.SuccessfulPaymentEvent +import dev.inmo.tgbotapi.types.request.ChatShared +import dev.inmo.tgbotapi.types.request.ChatSharedRequest +import dev.inmo.tgbotapi.types.request.UserShared import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.lowLevelRiskFeatureMessage import kotlinx.coroutines.flow.Flow @@ -148,3 +155,34 @@ suspend fun BehaviourContext.waitForumTopicReopenedEventsMessages( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null } ) = waitEventsMessages(initRequest, errorFactory) +suspend fun BehaviourContext.waitForumTopicEditedEventsMessages( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEventsMessages(initRequest, errorFactory) +suspend fun BehaviourContext.waitGeneralForumTopicHiddenEventsMessages( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEventsMessages(initRequest, errorFactory) +suspend fun BehaviourContext.waitGeneralForumTopicUnhiddenEventsMessages( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEventsMessages(initRequest, errorFactory) +suspend fun BehaviourContext.waitWriteAccessAllowedEventsMessages( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEventsMessages(initRequest, errorFactory) + +suspend fun BehaviourContext.waitChatSharedRequestEventsMessages( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEventsMessages(initRequest, errorFactory) + +suspend fun BehaviourContext.waitUserSharedEventsMessages( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEventsMessages(initRequest, errorFactory) + +suspend fun BehaviourContext.waitChatSharedEventsMessages( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null } +) = waitEventsMessages(initRequest, errorFactory) diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/EventTriggers.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/EventTriggers.kt index 06662e8a59..645ac7effb 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/EventTriggers.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/EventTriggers.kt @@ -23,6 +23,9 @@ import dev.inmo.tgbotapi.types.message.PrivateEventMessage import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage import dev.inmo.tgbotapi.types.message.abstracts.SupergroupEventMessage import dev.inmo.tgbotapi.types.message.payments.SuccessfulPaymentEvent +import dev.inmo.tgbotapi.types.request.ChatShared +import dev.inmo.tgbotapi.types.request.ChatSharedRequest +import dev.inmo.tgbotapi.types.request.UserShared import dev.inmo.tgbotapi.types.update.abstracts.Update internal suspend inline fun BC.onEvent( @@ -657,3 +660,65 @@ suspend fun BC.onWriteAccessAllowed( markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, scenarioReceiver: CustomBehaviourContextAndTypeReceiver> ) = onEventWithCustomChatEventMessage(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) + + + +/** + * @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.onChatSharedRequest( + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +) = onEventWithCustomChatEventMessage(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) + + +/** + * @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.onUserShared( + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +) = onEventWithCustomChatEventMessage(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) + + + +/** + * @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.onChatShared( + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +) = onEventWithCustomChatEventMessage(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) 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 b993b32d8b..de1bdcbf47 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 @@ -261,6 +261,19 @@ const val iconColorField = "icon_color" const val requestContactField = "request_contact" const val requestLocationField = "request_location" const val requestPollField = "request_poll" +const val requestUserField = "request_user" +const val requestChatField = "request_chat" +const val requestIdField = "request_id" + +const val userIsBotField = "user_is_bot" +const val userIsPremiumField = "user_is_premium" +const val chatIsChannelField = "chat_is_channel" +const val chatIsForumField = "chat_is_forum" +const val chatHasUsernameField = "chat_has_username" +const val chatIsCreatedField = "chat_is_created" +const val userAdministratorRightsField = "user_administrator_rights" +const val botAdministratorRightsField = "bot_administrator_rights" +const val botIsMemberField = "bot_is_member" const val fileNameField = "file_name" const val mimeTypeField = "mime_type" diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButton.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButton.kt index 4226bc94d2..64c326637d 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButton.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButton.kt @@ -94,6 +94,42 @@ data class RequestPollKeyboardButton( val requestPoll: KeyboardButtonPollType ) : KeyboardButton +/** + * Private chats only. When user will tap on this button, he will be asked for the chat with [requestChat] options. You + * will be able to catch this [ChatId] in updates and data using + * [dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onUserShared] in case you are using Behaviour + * Builder OR with [dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter.messagesFlow] + * and [kotlinx.coroutines.flow.filterIsInstance]. + * + * In case you will use [dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onUserShared] it is + * recommended to use [kotlinx.coroutines.flow.Flow] [kotlinx.coroutines.flow.filter] with checking of incoming + * [dev.inmo.tgbotapi.types.request.UserShared.requestId] + */ +@Serializable +data class RequestUserKeyboardButton( + override val text: String, + @SerialName(requestUserField) + val requestUser: KeyboardButtonRequestUser +) : KeyboardButton + +/** + * Private chats only. When user will tap on this button, he will be asked for the chat with [requestChat] options. You + * will be able to catch this [ChatId] in updates and data using + * [dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatShared] in case you are using Behaviour + * Builder OR with [dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter.messagesFlow] + * and [kotlinx.coroutines.flow.filterIsInstance]. + * + * In case you will use [dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatShared] it is + * recommended to use [kotlinx.coroutines.flow.Flow] [kotlinx.coroutines.flow.filter] with checking of incoming + * [dev.inmo.tgbotapi.types.request.ChatShared.requestId] + */ +@Serializable +data class RequestChatKeyboardButton( + override val text: String, + @SerialName(requestChatField) + val requestChat: KeyboardButtonRequestChat +) : KeyboardButton + @RiskFeature object KeyboardButtonSerializer : KSerializer { private val internalSerializer = JsonElement.serializer() @@ -124,6 +160,20 @@ object KeyboardButtonSerializer : KSerializer { asJson[requestPollField] ?.jsonObject ?: buildJsonObject { } ) ) + asJson is JsonObject && asJson[requestUserField] != null -> RequestUserKeyboardButton( + asJson[textField]!!.jsonPrimitive.content, + nonstrictJsonFormat.decodeFromJsonElement( + KeyboardButtonRequestUser.serializer(), + asJson[requestUserField] ?.jsonObject ?: buildJsonObject { } + ) + ) + asJson is JsonObject && asJson[requestChatField] != null -> RequestChatKeyboardButton( + asJson[textField]!!.jsonPrimitive.content, + nonstrictJsonFormat.decodeFromJsonElement( + KeyboardButtonRequestChat.serializer(), + asJson[requestChatField] ?.jsonObject ?: buildJsonObject { } + ) + ) else -> UnknownKeyboardButton( when (asJson) { is JsonObject -> asJson[textField]!!.jsonPrimitive.content @@ -142,6 +192,8 @@ object KeyboardButtonSerializer : KSerializer { is WebAppKeyboardButton -> WebAppKeyboardButton.serializer().serialize(encoder, value) is RequestPollKeyboardButton -> RequestPollKeyboardButton.serializer().serialize(encoder, value) is SimpleKeyboardButton -> encoder.encodeString(value.text) + is RequestUserKeyboardButton -> RequestUserKeyboardButton.serializer().serialize(encoder, value) + is RequestChatKeyboardButton -> RequestChatKeyboardButton.serializer().serialize(encoder, value) is UnknownKeyboardButton -> JsonElement.serializer().serialize(encoder, nonstrictJsonFormat.parseToJsonElement(value.raw)) } } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButtonRequestChat.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButtonRequestChat.kt new file mode 100644 index 0000000000..e423daeb57 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButtonRequestChat.kt @@ -0,0 +1,38 @@ +package dev.inmo.tgbotapi.types.buttons + +import dev.inmo.tgbotapi.types.botAdministratorRightsField +import dev.inmo.tgbotapi.types.botIsMemberField +import dev.inmo.tgbotapi.types.chat.member.ChatAdministratorRights +import dev.inmo.tgbotapi.types.chatHasUsernameField +import dev.inmo.tgbotapi.types.chatIsChannelField +import dev.inmo.tgbotapi.types.chatIsCreatedField +import dev.inmo.tgbotapi.types.chatIsForumField +import dev.inmo.tgbotapi.types.request.RequestId +import dev.inmo.tgbotapi.types.requestIdField +import dev.inmo.tgbotapi.types.userAdministratorRightsField +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +data class KeyboardButtonRequestChat( + @SerialName(requestIdField) + val requestId: RequestId, + @SerialName(chatIsChannelField) + val isChannel: Boolean? = null, + @SerialName(chatIsForumField) + val isForum: Boolean? = null, + @SerialName(chatHasUsernameField) + val withUsername: Boolean? = null, + @SerialName(chatIsCreatedField) + val ownedBy: Boolean? = null, + @SerialName(userAdministratorRightsField) + val userRightsInChat: ChatAdministratorRights? = null, + @SerialName(botAdministratorRightsField) + val botRightsInChat: ChatAdministratorRights? = null, + @SerialName(botIsMemberField) + val botIsMember: Boolean = false +) + diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButtonRequestUser.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButtonRequestUser.kt new file mode 100644 index 0000000000..ff7483b011 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/KeyboardButtonRequestUser.kt @@ -0,0 +1,89 @@ +package dev.inmo.tgbotapi.types.buttons + +import dev.inmo.tgbotapi.types.request.RequestId +import dev.inmo.tgbotapi.types.requestIdField +import dev.inmo.tgbotapi.types.userIsBotField +import dev.inmo.tgbotapi.types.userIsPremiumField +import kotlinx.serialization.EncodeDefault +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +@Serializable(KeyboardButtonRequestUser.Companion::class) +sealed interface KeyboardButtonRequestUser { + val requestId: RequestId + val isBot: Boolean? + + @Serializable + data class Any( + @SerialName(requestIdField) + override val requestId: RequestId + ) : KeyboardButtonRequestUser { + @SerialName(userIsBotField) + @EncodeDefault + override val isBot: Boolean? = null + } + + @Serializable + data class Common( + @SerialName(requestIdField) + override val requestId: RequestId, + @SerialName(userIsPremiumField) + val isPremium: Boolean? = null + ) : KeyboardButtonRequestUser { + @SerialName(userIsBotField) + @EncodeDefault + override val isBot: Boolean = false + } + + @Serializable + data class Bot( + @SerialName(requestIdField) + override val requestId: RequestId + ) : KeyboardButtonRequestUser { + @SerialName(userIsBotField) + @EncodeDefault + override val isBot: Boolean = true + } + + @Serializer(KeyboardButtonRequestUser::class) + companion object : KSerializer { + @Serializable + private data class Surrogate( + @SerialName(requestIdField) + val requestId: RequestId, + @SerialName(userIsBotField) + val userIsBot: Boolean? = null, + @SerialName(userIsPremiumField) + val userIsPremium: Boolean? = null + ) + private val realSerializer = Surrogate.serializer() + + override val descriptor: SerialDescriptor = realSerializer.descriptor + + override fun deserialize(decoder: Decoder): KeyboardButtonRequestUser { + val surrogate = realSerializer.deserialize(decoder) + + return when (surrogate.userIsBot) { + true -> Bot(surrogate.requestId) + false -> Common(surrogate.requestId, surrogate.userIsPremium) + null -> Any(surrogate.requestId) + } + } + + override fun serialize(encoder: Encoder, value: KeyboardButtonRequestUser) { + realSerializer.serialize( + encoder, + Surrogate( + value.requestId, + value.isBot, + (value as? Common) ?.isPremium + ) + ) + } + } +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/reply/ReplyKeyboardButtonsShortcuts.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/reply/ReplyKeyboardButtonsShortcuts.kt index d87ed524f4..dba7cac982 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/reply/ReplyKeyboardButtonsShortcuts.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/reply/ReplyKeyboardButtonsShortcuts.kt @@ -2,6 +2,8 @@ package dev.inmo.tgbotapi.types.buttons.reply import dev.inmo.tgbotapi.types.buttons.* import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.* +import dev.inmo.tgbotapi.types.chat.member.ChatAdministratorRights +import dev.inmo.tgbotapi.types.request.RequestId import dev.inmo.tgbotapi.types.webapps.WebAppInfo @@ -67,3 +69,99 @@ inline fun webAppReplyButton( text: String, url: String ) = webAppReplyButton(text, WebAppInfo(url)) + + +/** + * Creates and put [RequestUserKeyboardButton] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun requestUserReplyButton( + text: String, + requestUser: KeyboardButtonRequestUser +) = RequestUserKeyboardButton( + text, + requestUser +) + +/** + * Creates and put [RequestUserKeyboardButton] with [KeyboardButtonRequestUser.Bot] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun requestBotReplyButton( + text: String, + requestId: RequestId +) = requestUserReplyButton( + text, + KeyboardButtonRequestUser.Bot(requestId) +) + +/** + * Creates and put [RequestUserKeyboardButton] with [KeyboardButtonRequestUser.Common] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun requestUserReplyButton( + text: String, + requestId: RequestId, + premiumUser: Boolean? = null +) = requestUserReplyButton( + text, + KeyboardButtonRequestUser.Common(requestId, premiumUser) +) + +/** + * Creates and put [RequestUserKeyboardButton] with [KeyboardButtonRequestUser.Any] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun requestUserOrBotReplyButton( + text: String, + requestId: RequestId +) = requestUserReplyButton( + text, + KeyboardButtonRequestUser.Any(requestId) +) + + +/** + * Creates and put [RequestChatKeyboardButton] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun requestChatReplyButton( + text: String, + requestChat: KeyboardButtonRequestChat +) = RequestChatKeyboardButton( + text, + requestChat +) + +/** + * Creates and put [RequestChatKeyboardButton] with [KeyboardButtonRequestChat] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun requestChatReplyButton( + text: String, + requestId: RequestId, + isChannel: Boolean? = null, + isForum: Boolean? = null, + withUsername: Boolean? = null, + ownedBy: Boolean? = null, + userRightsInChat: ChatAdministratorRights? = null, + botRightsInChat: ChatAdministratorRights? = null, + botIsMember: Boolean = false +) = requestChatReplyButton( + text, + KeyboardButtonRequestChat( + requestId, isChannel, isForum, withUsername, ownedBy, userRightsInChat, botRightsInChat, botIsMember + ) +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/RawMessage.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/RawMessage.kt index c5c6277daa..684b74757f 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/RawMessage.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/RawMessage.kt @@ -29,6 +29,8 @@ import dev.inmo.tgbotapi.types.passport.PassportData import dev.inmo.tgbotapi.types.payments.Invoice import dev.inmo.tgbotapi.types.payments.SuccessfulPayment import dev.inmo.tgbotapi.types.polls.Poll +import dev.inmo.tgbotapi.types.request.ChatShared +import dev.inmo.tgbotapi.types.request.UserShared import dev.inmo.tgbotapi.types.venue.Venue import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -93,6 +95,9 @@ internal data class RawMessage( private val dice: Dice? = null, private val successful_payment: SuccessfulPayment? = null, + private val user_shared: UserShared? = null, + private val chat_shared: ChatShared? = null, + // Voice Chat Service Messages private val video_chat_scheduled: VideoChatScheduled? = null, private val video_chat_started: VideoChatStarted? = null, @@ -249,6 +254,8 @@ internal data class RawMessage( successful_payment != null -> SuccessfulPaymentEvent(successful_payment) connected_website != null -> UserLoggedIn(connected_website) web_app_data != null -> web_app_data + user_shared != null -> user_shared + chat_shared != null -> chat_shared else -> null } } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/ChatShared.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/ChatShared.kt new file mode 100644 index 0000000000..f222e5b380 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/ChatShared.kt @@ -0,0 +1,17 @@ +package dev.inmo.tgbotapi.types.request + +import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.ChatIdentifier +import dev.inmo.tgbotapi.types.UserId +import dev.inmo.tgbotapi.types.chatIdField +import dev.inmo.tgbotapi.types.requestIdField +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ChatShared( + @SerialName(requestIdField) + override val requestId: RequestId, + @SerialName(chatIdField) + override val chatId: ChatId +) : ChatSharedRequest diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/ChatSharedRequest.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/ChatSharedRequest.kt new file mode 100644 index 0000000000..5dbe702013 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/ChatSharedRequest.kt @@ -0,0 +1,8 @@ +package dev.inmo.tgbotapi.types.request + +import dev.inmo.tgbotapi.types.ChatIdentifier +import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.PrivateEvent + +sealed interface ChatSharedRequest : RequestResponse, PrivateEvent { + val chatId: ChatIdentifier +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/RequestId.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/RequestId.kt new file mode 100644 index 0000000000..689db02a1d --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/RequestId.kt @@ -0,0 +1,10 @@ +package dev.inmo.tgbotapi.types.request + +import kotlinx.serialization.Serializable +import kotlin.jvm.JvmInline + +@Serializable +@JvmInline +value class RequestId( + val long: Long +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/RequestResponse.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/RequestResponse.kt new file mode 100644 index 0000000000..2c4c846942 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/RequestResponse.kt @@ -0,0 +1,5 @@ +package dev.inmo.tgbotapi.types.request + +sealed interface RequestResponse { + val requestId: RequestId +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/UserShared.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/UserShared.kt new file mode 100644 index 0000000000..74b6f55667 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/request/UserShared.kt @@ -0,0 +1,19 @@ +package dev.inmo.tgbotapi.types.request + +import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.UserId +import dev.inmo.tgbotapi.types.requestIdField +import dev.inmo.tgbotapi.types.userIdField +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserShared( + @SerialName(requestIdField) + override val requestId: RequestId, + @SerialName(userIdField) + val userId: UserId +) : ChatSharedRequest { + override val chatId: ChatId + get() = userId +} diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/types/buttons/ReplyKeyboardBuilder.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/types/buttons/ReplyKeyboardBuilder.kt index 9bd0909aa1..3b8cce77cf 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/types/buttons/ReplyKeyboardBuilder.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/types/buttons/ReplyKeyboardBuilder.kt @@ -1,6 +1,10 @@ package dev.inmo.tgbotapi.extensions.utils.types.buttons import dev.inmo.tgbotapi.types.buttons.* +import dev.inmo.tgbotapi.types.buttons.reply.requestChatReplyButton +import dev.inmo.tgbotapi.types.buttons.reply.requestUserReplyButton +import dev.inmo.tgbotapi.types.chat.member.ChatAdministratorRights +import dev.inmo.tgbotapi.types.request.RequestId import dev.inmo.tgbotapi.types.webapps.WebAppInfo import dev.inmo.tgbotapi.utils.* @@ -138,3 +142,103 @@ inline fun ReplyKeyboardRowBuilder.webAppButton( text: String, url: String ) = webAppButton(text, WebAppInfo(url)) + + +/** + * Creates and put [RequestUserKeyboardButton] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun ReplyKeyboardRowBuilder.requestUserButton( + text: String, + requestUser: KeyboardButtonRequestUser +) = add( + requestUserReplyButton( + text, + requestUser + ) +) + +/** + * Creates and put [RequestUserKeyboardButton] with [KeyboardButtonRequestUser.Bot] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun ReplyKeyboardRowBuilder.requestBotButton( + text: String, + requestId: RequestId +) = requestUserButton( + text, + KeyboardButtonRequestUser.Bot(requestId) +) + +/** + * Creates and put [RequestUserKeyboardButton] with [KeyboardButtonRequestUser.Common] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun ReplyKeyboardRowBuilder.requestUserButton( + text: String, + requestId: RequestId, + premiumUser: Boolean? = null +) = requestUserButton( + text, + KeyboardButtonRequestUser.Common(requestId, premiumUser) +) + +/** + * Creates and put [RequestUserKeyboardButton] with [KeyboardButtonRequestUser.Any] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun ReplyKeyboardRowBuilder.requestUserOrBotButton( + text: String, + requestId: RequestId +) = requestUserButton( + text, + KeyboardButtonRequestUser.Any(requestId) +) + + +/** + * Creates and put [RequestChatKeyboardButton] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun ReplyKeyboardRowBuilder.requestChatButton( + text: String, + requestChat: KeyboardButtonRequestChat +) = add( + requestChatReplyButton( + text, + requestChat + ) +) + +/** + * Creates and put [RequestChatKeyboardButton] with [KeyboardButtonRequestChat] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun ReplyKeyboardRowBuilder.requestChatButton( + text: String, + requestId: RequestId, + isChannel: Boolean? = null, + isForum: Boolean? = null, + withUsername: Boolean? = null, + ownedBy: Boolean? = null, + userRightsInChat: ChatAdministratorRights? = null, + botRightsInChat: ChatAdministratorRights? = null, + botIsMember: Boolean = false +) = requestChatButton( + text, + KeyboardButtonRequestChat( + requestId, isChannel, isForum, withUsername, ownedBy, userRightsInChat, botRightsInChat, botIsMember + ) +)