diff --git a/CHANGELOG.md b/CHANGELOG.md index 1268a58871..0266904fce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # TelegramBotAPI changelog +## 0.23.0 TelegramBotAPI 4.6 + +* `Poll` now is sealed class + * `RegularPoll` type was added to represent polls with type `regular` + * `QuizPoll` type was added to represent polls with type `quiz` + * `UnknownPollType` type was added to represent polls which are unknown in current version +* `AnonymousPollOption` was renamed to `SimplePollOption` +* `SendPoll` was rewritten as sealed class + * `SendRegularPoll` was created and represent `sendPoll` method with type `regular` + * `SendQuizPoll` was created and represent `sendPoll` method with type `quiz` +* `Poll#createRequest` extension was added +* `PollAnswerUpdate` type of update was added + * `PollAnswer` type was added + * `UpdatesFilter` now support work with `PollAnswerUpdate` +* `language` field in PreTextSource now correctly passed from telegram MessageEntities +* `KeyboardButton` now is sealed class + * Fixed problem of incorrect representation of this class (any type of request can be created separately) + * Added new types of `KeyboardButton`: + * `UnknownKeyboardButton` + * `SimpleKeyboardButton` + * `RequestContactKeyboardButton` + * `RequestLocationKeyboardButton` + * `RequestPollKeyboardButton` + * Added new type `KeyboardButtonPollType`: + * `UnknownKeyboardButtonPollType` + * `RegularKeyboardButtonPollType` + * `QuizKeyboardButtonPollType` +* `User` now is sealed class + * `CommonUser` was added as representation of default `User` + * `Bot` was added as representation of bot user (it is sealed class) + * `ExtendedBot` with additional info + * `CommonBot` with simple info + * `GetMe` now return `ExtendedBot` object + * Now extension `javaLocale` is extension for `CommonUser` + ## 0.22.0 * **`KtorCallFactory` must return `HttpStatement` instead of `HttpClientCall`** diff --git a/README.md b/README.md index 23033972eb..5adb92ff4e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ moments are describing by official [Telegram Bot API](https://core.telegram.org/ ## Compatibility -This version compatible with [31th of December 2019 update of TelegramBotAPI (version 4.5)](https://core.telegram.org/bots/api#december-31-2019). +This version compatible with [23th of January 2020 update of TelegramBotAPI (version 4.6)](https://core.telegram.org/bots/api#january-23-2020). There is Telegram Passport API exception of implemented functionality, which was presented in [August 2018 update of TelegramBotAPI](https://core.telegram.org/bots/api#august-27-2018) update. It will be implemented as soon as possible. All APIs that are not included are presented @@ -96,10 +96,9 @@ val requestsExecutor: RequestsExecutor = ... requestsExecutor.execute(GetMe()) ``` -The result type of [GetMe](https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt) request is -[User](https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/User.kt). In fact, in this result must contain -`isBot` equal to `true` always. - +The result type of [GetMe](https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt) +request is +[ExtendedBot](https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/User.kt). ### RequestsExecutor diff --git a/build.gradle b/build.gradle index 19b1d76f40..4c9e3f55a0 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ plugins { id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version" } -project.version = "0.22.2" +project.version = "0.23.0" project.group = "com.github.insanusmokrassar" apply from: "publish.gradle" diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt index 143b9b4bd0..2f19df4909 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt @@ -1,14 +1,14 @@ package com.github.insanusmokrassar.TelegramBotAPI.requests import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest -import com.github.insanusmokrassar.TelegramBotAPI.types.User +import com.github.insanusmokrassar.TelegramBotAPI.types.* import kotlinx.serialization.* @Serializable -class GetMe : SimpleRequest { +class GetMe : SimpleRequest { override fun method(): String = "getMe" - override val resultDeserializer: DeserializationStrategy - get() = User.serializer() + override val resultDeserializer: DeserializationStrategy + get() = ExtendedBot.serializer() override val requestSerializer: SerializationStrategy<*> get() = serializer() } \ No newline at end of file diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/SendPoll.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/SendPoll.kt deleted file mode 100644 index d640043256..0000000000 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/SendPoll.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.github.insanusmokrassar.TelegramBotAPI.requests.send - -import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.ReplyingMarkupSendMessageRequest -import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.SendMessageRequest -import com.github.insanusmokrassar.TelegramBotAPI.types.* -import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup -import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage -import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass -import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.PollContent -import kotlinx.serialization.* - -private val commonResultDeserializer: DeserializationStrategy> - = TelegramBotAPIMessageDeserializationStrategyClass() - -@Serializable -data class SendPoll( - @SerialName(chatIdField) - override val chatId: ChatIdentifier, - @SerialName(questionField) - val question: String, - @SerialName(optionsField) - val options: List, - @SerialName(disableNotificationField) - override val disableNotification: Boolean = false, - @SerialName(replyToMessageIdField) - override val replyToMessageId: MessageIdentifier? = null, - @SerialName(replyMarkupField) - override val replyMarkup: KeyboardMarkup? = null -) : SendMessageRequest>, - ReplyingMarkupSendMessageRequest> { - - init { - if (question.length !in pollQuestionTextLength) { - throw IllegalArgumentException("The length of questions for polls must be in $pollQuestionTextLength range, but was ${question.length}") - } - options.forEach { - if (it.length !in pollOptionTextLength) { - throw IllegalArgumentException("The length of question option text for polls must be in $pollOptionTextLength range, but was ${it.length}") - } - } - if (options.size !in pollOptionsLimit) { - throw IllegalArgumentException("The amount of question options for polls must be in $pollOptionsLimit range, but was ${options.size}") - } - } - - override fun method(): String = "sendPoll" - override val resultDeserializer: DeserializationStrategy> - get() = commonResultDeserializer - override val requestSerializer: SerializationStrategy<*> - get() = serializer() -} \ No newline at end of file diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/polls/SendPoll.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/polls/SendPoll.kt new file mode 100644 index 0000000000..831d874ec7 --- /dev/null +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/polls/SendPoll.kt @@ -0,0 +1,185 @@ +package com.github.insanusmokrassar.TelegramBotAPI.requests.send.polls + +import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.ReplyingMarkupSendMessageRequest +import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.SendMessageRequest +import com.github.insanusmokrassar.TelegramBotAPI.types.* +import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup +import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage +import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass +import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.PollContent +import com.github.insanusmokrassar.TelegramBotAPI.types.polls.* +import kotlinx.serialization.* + +private val commonResultDeserializer: DeserializationStrategy> = TelegramBotAPIMessageDeserializationStrategyClass() + +private fun checkPollInfo( + question: String, + options: List +) { + if (question.length !in pollQuestionTextLength) { + throw IllegalArgumentException("The length of questions for polls must be in $pollQuestionTextLength range, but was ${question.length}") + } + options.forEach { + if (it.length !in pollOptionTextLength) { + throw IllegalArgumentException("The length of question option text for polls must be in $pollOptionTextLength range, but was ${it.length}") + } + } + if (options.size !in pollOptionsLimit) { + throw IllegalArgumentException("The amount of question options for polls must be in $pollOptionsLimit range, but was ${options.size}") + } +} + +fun SendPoll( + chatId: ChatIdentifier, + question: String, + options: List, + isAnonymous: Boolean = true, + isClosed: Boolean = false, + disableNotification: Boolean = false, + replyToMessageId: MessageIdentifier? = null, + replyMarkup: KeyboardMarkup? = null +) = SendRegularPoll( + chatId, + question, + options, + isAnonymous, + isClosed, + disableNotification = disableNotification, + replyToMessageId = replyToMessageId, + replyMarkup = replyMarkup +) + +/** + * @return [SendPoll] in case when all is right. It can return [SendRegularPoll] for [QuizPoll] in case if + * [QuizPoll.correctOptionId] equal to null + */ +fun Poll.createRequest( + chatId: ChatIdentifier, + disableNotification: Boolean = false, + replyToMessageId: MessageIdentifier? = null, + replyMarkup: KeyboardMarkup? = null +) = when (this) { + is RegularPoll -> SendRegularPoll( + chatId, + question, + options.map { it.text }, + isAnonymous, + isClosed, + allowMultipleAnswers, + disableNotification, + replyToMessageId, + replyMarkup + ) + is QuizPoll -> correctOptionId ?.let { correctOptionId -> + SendQuizPoll( + chatId, + question, + options.map { it.text }, + correctOptionId, + isAnonymous, + isClosed, + disableNotification, + replyToMessageId, + replyMarkup + ) + } ?: SendRegularPoll( + chatId, + question, + options.map { it.text }, + isAnonymous, + isClosed, + false, + disableNotification, + replyToMessageId, + replyMarkup + ) + is UnknownPollType -> SendRegularPoll( + chatId, + question, + options.map { it.text }, + isAnonymous, + isClosed, + false, + disableNotification, + replyToMessageId, + replyMarkup + ) +} + +sealed class SendPoll : SendMessageRequest>, + ReplyingMarkupSendMessageRequest> { + abstract val question: String + abstract val options: List + abstract val isAnonymous: Boolean + abstract val isClosed: Boolean + abstract val type: String + + override fun method(): String = "sendPoll" + override val resultDeserializer: DeserializationStrategy> + get() = commonResultDeserializer +} + +@Serializable +data class SendRegularPoll( + @SerialName(chatIdField) + override val chatId: ChatIdentifier, + @SerialName(questionField) + override val question: String, + @SerialName(optionsField) + override val options: List, + @SerialName(isAnonymousField) + override val isAnonymous: Boolean = true, + @SerialName(isClosedField) + override val isClosed: Boolean = false, + @SerialName(allowsMultipleAnswersField) + val allowMultipleAnswers: Boolean = false, + @SerialName(disableNotificationField) + override val disableNotification: Boolean = false, + @SerialName(replyToMessageIdField) + override val replyToMessageId: MessageIdentifier? = null, + @SerialName(replyMarkupField) + override val replyMarkup: KeyboardMarkup? = null +) : SendPoll() { + override val type: String = regularPollType + override val requestSerializer: SerializationStrategy<*> + get() = serializer() + + init { + checkPollInfo(question, options) + } +} + +@Serializable +data class SendQuizPoll( + @SerialName(chatIdField) + override val chatId: ChatIdentifier, + @SerialName(questionField) + override val question: String, + @SerialName(optionsField) + override val options: List, + @SerialName(correctOptionIdField) + val correctOptionId: Int, + @SerialName(isAnonymousField) + override val isAnonymous: Boolean = true, + @SerialName(isClosedField) + override val isClosed: Boolean = false, + @SerialName(disableNotificationField) + override val disableNotification: Boolean = false, + @SerialName(replyToMessageIdField) + override val replyToMessageId: MessageIdentifier? = null, + @SerialName(replyMarkupField) + override val replyMarkup: KeyboardMarkup? = null +) : SendPoll() { + override val type: String = quizPollType + override val requestSerializer: SerializationStrategy<*> + get() = serializer() + + init { + checkPollInfo(question, options) + val correctOptionIdRange = 0 .. options.size + if (correctOptionId !in correctOptionIdRange) { + throw IllegalArgumentException("Correct option id must be in range of $correctOptionIdRange, but actual " + + "value is $correctOptionId") + } + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt index 873e7c49de..a2d146825b 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt @@ -57,6 +57,9 @@ const val isBotField = "is_bot" const val firstNameField = "first_name" const val lastNameField = "last_name" const val languageCodeField = "language_code" +const val canJoinGroupsField = "can_join_groups" +const val canReadAllGroupMessagesField = "can_read_all_group_messages" +const val supportInlineQueriesField = "supports_inline_queries" const val textEntitiesField = "text_entities" const val stickerSetNameField = "set_name" const val stickerSetNameFullField = "sticker_set_name" @@ -90,6 +93,10 @@ const val lastErrorDateField = "last_error_date" const val lastErrorMessageField = "last_error_message" const val votesCountField = "voter_count" const val isClosedField = "is_closed" +const val totalVoterCountField = "total_voter_count" +const val correctOptionIdField = "correct_option_id" +const val allowsMultipleAnswersField = "allows_multiple_answers" +const val isAnonymousField = "is_anonymous" const val loginUrlField = "login_url" const val forwardTextField = "forward_text" const val botUsernameField = "bot_username" @@ -99,6 +106,11 @@ const val isAnimatedField = "is_animated" const val inviteLinkField = "invite_link" const val pinnedMessageField = "pinned_message" const val customTitleField = "custom_title" +const val optionIdsField = "option_ids" + +const val requestContactField = "request_contact" +const val requestLocationField = "request_location" +const val requestPollField = "request_poll" const val requestWriteAccessField = "request_write_access" @@ -173,6 +185,7 @@ const val pngStickerField = "png_sticker" const val okField = "ok" const val captionField = "caption" const val idField = "id" +const val pollIdField = "poll_id" const val textField = "text" const val thumbField = "thumb" const val emojiField = "emoji" @@ -273,3 +286,6 @@ const val mediaField = "media" const val disableEditMessageField = "disable_edit_message" const val scoreField = "score" const val forceField = "force" + +const val regularPollType = "regular" +const val quizPollType = "quiz" diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/MessageEntity/RawMessageEntity.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/MessageEntity/RawMessageEntity.kt index bf161e2192..c7023471e4 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/MessageEntity/RawMessageEntity.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/MessageEntity/RawMessageEntity.kt @@ -13,7 +13,8 @@ internal data class RawMessageEntity( val offset: Int, val length: Int, val url: String? = null, - val user: User? = null + val user: User? = null, + val language: String? = null ) internal fun RawMessageEntity.asTextParts(source: String, subParts: List): List { @@ -31,7 +32,7 @@ internal fun RawMessageEntity.asTextParts(source: String, subParts: List BoldTextSource(sourceSubstring, shiftedSubParts) "italic" -> ItalicTextSource(sourceSubstring, shiftedSubParts) "code" -> CodeTextSource(sourceSubstring) - "pre" -> PreTextSource(sourceSubstring) + "pre" -> PreTextSource(sourceSubstring, language) "text_link" -> TextLinkTextSource(sourceSubstring, url ?: throw IllegalStateException("URL must not be null for text link")) "text_mention" -> TextMentionTextSource(sourceSubstring, user ?: throw IllegalStateException("User must not be null for text mention"), shiftedSubParts) "underline" -> UnderlineTextSource(sourceSubstring, shiftedSubParts) diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UpdateTypes.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UpdateTypes.kt index a24e00d4eb..ce3e08b5c4 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UpdateTypes.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UpdateTypes.kt @@ -10,6 +10,7 @@ const val UPDATE_CALLBACK_QUERY = "callback_query" const val UPDATE_SHIPPING_QUERY = "shipping_query" const val UPDATE_PRE_CHECKOUT_QUERY = "pre_checkout_query" const val UPDATE_POLL = "poll" +const val UPDATE_POLL_ANSWER = "poll_answer" val ALL_UPDATES_LIST = listOf( UPDATE_MESSAGE, @@ -21,5 +22,6 @@ val ALL_UPDATES_LIST = listOf( UPDATE_CALLBACK_QUERY, UPDATE_SHIPPING_QUERY, UPDATE_PRE_CHECKOUT_QUERY, - UPDATE_POLL + UPDATE_POLL, + UPDATE_POLL_ANSWER ) diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/User.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/User.kt index 7e2e291df5..d23a1ce1d0 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/User.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/User.kt @@ -1,14 +1,16 @@ package com.github.insanusmokrassar.TelegramBotAPI.types import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.PrivateChat -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable +import kotlinx.serialization.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObjectSerializer + +@Serializable(UserSerializer::class) +sealed class User : PrivateChat @Serializable -data class User( +data class CommonUser( override val id: ChatId, - @SerialName(isBotField) - val isBot: Boolean = false, @SerialName(firstNameField) override val firstName: String, @SerialName(lastNameField) @@ -17,4 +19,80 @@ data class User( override val username: Username? = null, @SerialName(languageCodeField) val languageCode: String? = null -) : PrivateChat +) : User() + +@Serializable(UserSerializer::class) +sealed class Bot : User() + +@Serializable +data class CommonBot( + override val id: ChatId, + @SerialName(firstNameField) + override val firstName: String, + @SerialName(lastNameField) + override val lastName: String = "", + @SerialName(usernameField) + override val username: Username? = null +) : Bot() { + @SerialName(isBotField) + private val isBot = true +} + +@Serializable +data class ExtendedBot( + override val id: ChatId, + @SerialName(firstNameField) + override val firstName: String, + @SerialName(lastNameField) + override val lastName: String = "", + @SerialName(usernameField) + override val username: Username? = null, + @SerialName(canJoinGroupsField) + val canJoinGroups: Boolean = false, + @SerialName(canReadAllGroupMessagesField) + val canReadAllGroupMessages: Boolean = false, + @SerialName(supportInlineQueriesField) + val supportsInlineQueries: Boolean = false +) : Bot() { + @SerialName(isBotField) + private val isBot = true +} + + +@Serializer(User::class) +internal object UserSerializer : KSerializer { + override fun deserialize(decoder: Decoder): User { + val asJson = JsonObjectSerializer.deserialize(decoder) + + return when { + asJson.getPrimitiveOrNull(isBotField) ?.booleanOrNull != true -> Json.nonstrict.fromJson( + CommonUser.serializer(), + asJson + ) + else -> { + if ((asJson.get(canJoinGroupsField) + ?: asJson.get(canReadAllGroupMessagesField) + ?: asJson.get(supportInlineQueriesField)) != null + ) { + Json.nonstrict.fromJson( + ExtendedBot.serializer(), + asJson + ) + } else { + Json.nonstrict.fromJson( + CommonBot.serializer(), + asJson + ) + } + } + } + } + + override fun serialize(encoder: Encoder, obj: User) { + when (obj) { + is CommonUser -> CommonUser.serializer().serialize(encoder, obj) + is CommonBot -> CommonBot.serializer().serialize(encoder, obj) + is ExtendedBot -> ExtendedBot.serializer().serialize(encoder, obj) + } + } +} diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/buttons/KeyboardButton.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/buttons/KeyboardButton.kt index 86bceb79f7..edaa83095a 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/buttons/KeyboardButton.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/buttons/KeyboardButton.kt @@ -1,13 +1,88 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.buttons -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable +import com.github.insanusmokrassar.TelegramBotAPI.types.* +import kotlinx.serialization.* +import kotlinx.serialization.internal.StringDescriptor +import kotlinx.serialization.json.* + +@Serializable(KeyboardButtonSerializer::class) +sealed class KeyboardButton { + abstract val text: String +} @Serializable -data class KeyboardButton( - val text: String, - @SerialName("request_contact") - val requestContact: Boolean? = null, - @SerialName("request_location") - val requestLocation: Boolean? = null -) +data class SimpleKeyboardButton( + override val text: String +) : KeyboardButton() + +@Serializable +data class UnknownKeyboardButton internal constructor( + override val text: String, + val raw: String +) : KeyboardButton() + +@Serializable +data class RequestContactKeyboardButton( + override val text: String +) : KeyboardButton() { + @SerialName(requestContactField) + val requestContact: Boolean = true +} + +@Serializable +data class RequestLocationKeyboardButton( + override val text: String +) : KeyboardButton() { + @SerialName(requestLocationField) + val requestLocation: Boolean = true +} + +@Serializable +data class RequestPollKeyboardButton( + override val text: String, + @SerialName(requestPollField) + val requestPoll: KeyboardButtonPollType +) : KeyboardButton() + +@Serializer(KeyboardButton::class) +internal object KeyboardButtonSerializer : KSerializer { + override fun deserialize(decoder: Decoder): KeyboardButton { + val asJson = JsonElementSerializer.deserialize(decoder) + + return when { + asJson is JsonPrimitive -> SimpleKeyboardButton(asJson.content) + asJson is JsonObject && asJson.getPrimitiveOrNull(requestContactField) != null -> RequestContactKeyboardButton( + asJson.getPrimitive(textField).content + ) + asJson is JsonObject && asJson.getPrimitiveOrNull(requestLocationField) != null -> RequestLocationKeyboardButton( + asJson.getPrimitive(textField).content + ) + asJson is JsonObject && asJson.getObjectOrNull(requestPollField) != null -> RequestPollKeyboardButton( + asJson.getPrimitive(textField).content, + Json.nonstrict.fromJson( + KeyboardButtonPollType.serializer(), + asJson.getObject(requestPollField) + ) + ) + else -> UnknownKeyboardButton( + when (asJson) { + is JsonObject -> asJson.getPrimitive(textField).content + is JsonArray -> "" + is JsonPrimitive -> asJson.content + }, + asJson.toString() + ) + } + } + + override fun serialize(encoder: Encoder, obj: KeyboardButton) { + when (obj) { + is RequestContactKeyboardButton -> RequestContactKeyboardButton.serializer().serialize(encoder, obj) + is RequestLocationKeyboardButton -> RequestLocationKeyboardButton.serializer().serialize(encoder, obj) + is RequestPollKeyboardButton -> RequestPollKeyboardButton.serializer().serialize(encoder, obj) + is SimpleKeyboardButton -> encoder.encodeString(obj.text) + is UnknownKeyboardButton -> JsonElementSerializer.serialize(encoder, Json.nonstrict.parseJson(obj.raw)) + } + } +} + diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/buttons/KeyboardButtonPollType.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/buttons/KeyboardButtonPollType.kt new file mode 100644 index 0000000000..b4140699f3 --- /dev/null +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/buttons/KeyboardButtonPollType.kt @@ -0,0 +1,55 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types.buttons + +import com.github.insanusmokrassar.TelegramBotAPI.types.* +import kotlinx.serialization.* +import kotlinx.serialization.json.* + +@Serializable(KeyboardButtonPollTypeSerializer::class) +sealed class KeyboardButtonPollType { + abstract val type: String +} + +@Serializable +class UnknownKeyboardButtonPollType internal constructor(override val type: String): KeyboardButtonPollType() + +@Serializable +object RegularKeyboardButtonPollType : KeyboardButtonPollType() { + override val type: String = regularPollType +} + +@Serializable +object QuizKeyboardButtonPollType : KeyboardButtonPollType() { + override val type: String = quizPollType +} + +@Serializer(KeyboardButtonPollType::class) +internal object KeyboardButtonPollTypeSerializer : KSerializer { + override fun deserialize(decoder: Decoder): KeyboardButtonPollType { + val asJson = JsonElementSerializer.deserialize(decoder) + + val type = when (asJson) { + is JsonPrimitive -> asJson.content + else -> asJson.jsonObject.getPrimitive(typeField).content + } + + return when (type) { + regularPollType -> RegularKeyboardButtonPollType + quizPollType -> QuizKeyboardButtonPollType + else -> UnknownKeyboardButtonPollType(type) + } + } + + /** + * Crutch due to the fact that direct serialization of objects currently does not work perfectly + */ + override fun serialize(encoder: Encoder, obj: KeyboardButtonPollType) { + JsonObjectSerializer.serialize( + encoder, + JsonObject( + mapOf( + typeField to JsonPrimitive(obj.type) + ) + ) + ) + } +} diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/message/content/PollContent.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/message/content/PollContent.kt index 8f89750809..c60177186d 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/message/content/PollContent.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/message/content/PollContent.kt @@ -1,13 +1,15 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.message.content import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request -import com.github.insanusmokrassar.TelegramBotAPI.requests.send.SendPoll +import com.github.insanusmokrassar.TelegramBotAPI.requests.send.polls.SendPoll +import com.github.insanusmokrassar.TelegramBotAPI.requests.send.polls.createRequest import com.github.insanusmokrassar.TelegramBotAPI.types.ChatIdentifier import com.github.insanusmokrassar.TelegramBotAPI.types.MessageIdentifier import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.abstracts.MessageContent import com.github.insanusmokrassar.TelegramBotAPI.types.polls.Poll +import com.github.insanusmokrassar.TelegramBotAPI.types.polls.RegularPoll data class PollContent( val poll: Poll @@ -17,10 +19,8 @@ data class PollContent( disableNotification: Boolean, replyToMessageId: MessageIdentifier?, replyMarkup: KeyboardMarkup? - ): Request> = SendPoll( + ): Request> = poll.createRequest( chatId, - poll.question, - poll.options.map { it.text }, disableNotification, replyToMessageId, replyMarkup diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/Poll.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/Poll.kt index 5d15b1a762..5334927df4 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/Poll.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/Poll.kt @@ -1,17 +1,119 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.polls import com.github.insanusmokrassar.TelegramBotAPI.types.* -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable +import kotlinx.serialization.* +import kotlinx.serialization.internal.ArrayListSerializer +import kotlinx.serialization.json.* + +@Serializable(PollSerializer::class) +sealed class Poll { + abstract val id: PollIdentifier + abstract val question: String + abstract val options: List + abstract val votesCount: Int + abstract val isClosed: Boolean + abstract val isAnonymous: Boolean +} @Serializable -data class Poll( +data class UnknownPollType internal constructor( @SerialName(idField) - val id: PollIdentifier, + override val id: PollIdentifier, @SerialName(questionField) - val question: String, + override val question: String, @SerialName(optionsField) - val options: List, + override val options: List, + @SerialName(totalVoterCountField) + override val votesCount: Int, @SerialName(isClosedField) - val closed: Boolean = false -) + override val isClosed: Boolean = false, + @SerialName(isAnonymousField) + override val isAnonymous: Boolean = false, + val raw: String +) : Poll() + +@Serializable +data class RegularPoll( + @SerialName(idField) + override val id: PollIdentifier, + @SerialName(questionField) + override val question: String, + @SerialName(optionsField) + override val options: List, + @SerialName(totalVoterCountField) + override val votesCount: Int, + @SerialName(isClosedField) + override val isClosed: Boolean = false, + @SerialName(isAnonymousField) + override val isAnonymous: Boolean = false, + @SerialName(allowsMultipleAnswersField) + val allowMultipleAnswers: Boolean = false +) : Poll() + +@Serializable +data class QuizPoll( + @SerialName(idField) + override val id: PollIdentifier, + @SerialName(questionField) + override val question: String, + @SerialName(optionsField) + override val options: List, + @SerialName(totalVoterCountField) + override val votesCount: Int, + /** + * Nullable due to documentation (https://core.telegram.org/bots/api#poll) + */ + @SerialName(correctOptionIdField) + val correctOptionId: Int? = null, + @SerialName(isClosedField) + override val isClosed: Boolean = false, + @SerialName(isAnonymousField) + override val isAnonymous: Boolean = false +) : Poll() + +@Serializer(Poll::class) +internal object PollSerializer : KSerializer { + private val pollOptionsSerializer = ArrayListSerializer(PollOption.serializer()) + override fun deserialize(decoder: Decoder): Poll { + val asJson = JsonObjectSerializer.deserialize(decoder) + + return when (asJson.getPrimitive(typeField).content) { + regularPollType -> Json.nonstrict.fromJson( + RegularPoll.serializer(), + asJson + ) + quizPollType -> Json.nonstrict.fromJson( + QuizPoll.serializer(), + asJson + ) + else -> UnknownPollType( + asJson.getPrimitive(idField).content, + asJson.getPrimitive(questionField).content, + Json.nonstrict.fromJson( + pollOptionsSerializer, + asJson.getArray(optionsField) + ), + asJson.getPrimitive(totalVoterCountField).int, + asJson.getPrimitiveOrNull(isClosedField) ?.booleanOrNull ?: false, + asJson.getPrimitiveOrNull(isAnonymousField) ?.booleanOrNull ?: true, + asJson.toString() + ) + } + } + + override fun serialize(encoder: Encoder, obj: Poll) { + val asJson = when (obj) { + is RegularPoll -> Json.nonstrict.toJson(RegularPoll.serializer(), obj) + is QuizPoll -> Json.nonstrict.toJson(QuizPoll.serializer(), obj) + is UnknownPollType -> throw IllegalArgumentException("Currently unable to correctly serialize object of poll $obj") + } + val resultJson = JsonObject( + asJson.jsonObject + (typeField to when (obj) { + is RegularPoll -> JsonPrimitive(regularPollType) + is QuizPoll -> JsonPrimitive(quizPollType) + is UnknownPollType -> throw IllegalArgumentException("Currently unable to correctly serialize object of poll $obj") + }) + ) + JsonObjectSerializer.serialize(encoder, resultJson) + } +} diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/PollAnswer.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/PollAnswer.kt new file mode 100644 index 0000000000..bc8bfaac6e --- /dev/null +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/PollAnswer.kt @@ -0,0 +1,15 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types.polls + +import com.github.insanusmokrassar.TelegramBotAPI.types.* +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PollAnswer( + @SerialName(pollIdField) + val pollId: PollIdentifier, + @SerialName(userField) + val user: User, + @SerialName(optionIdsField) + val chosen: List +) diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/PollOption.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/PollOption.kt index 63eef91eaf..247a7567aa 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/PollOption.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/polls/PollOption.kt @@ -12,7 +12,7 @@ sealed class PollOption { } @Serializable -data class AnonymousPollOption ( +data class SimplePollOption ( @SerialName(textField) override val text: String, @SerialName(votesCountField) @@ -22,13 +22,13 @@ data class AnonymousPollOption ( internal object PollOptionSerializer : KSerializer { override val descriptor: SerialDescriptor = StringDescriptor.withName(PollOption::class.simpleName ?: "PollOption") - override fun deserialize(decoder: Decoder): PollOption = AnonymousPollOption.serializer().deserialize( + override fun deserialize(decoder: Decoder): PollOption = SimplePollOption.serializer().deserialize( decoder ) override fun serialize(encoder: Encoder, obj: PollOption) { when (obj) { - is AnonymousPollOption -> AnonymousPollOption.serializer().serialize( + is SimplePollOption -> SimplePollOption.serializer().serialize( encoder, obj ) diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/PollAnswerUpdate.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/PollAnswerUpdate.kt new file mode 100644 index 0000000000..680ee33ad8 --- /dev/null +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/PollAnswerUpdate.kt @@ -0,0 +1,10 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types.update + +import com.github.insanusmokrassar.TelegramBotAPI.types.UpdateIdentifier +import com.github.insanusmokrassar.TelegramBotAPI.types.polls.PollAnswer +import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update + +data class PollAnswerUpdate( + override val updateId: UpdateIdentifier, + override val data: PollAnswer +) : Update diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/RawUpdate.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/RawUpdate.kt index 287dbeb4d8..5ccd666095 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/RawUpdate.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/RawUpdate.kt @@ -9,6 +9,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Telegr import com.github.insanusmokrassar.TelegramBotAPI.types.payments.PreCheckoutQuery import com.github.insanusmokrassar.TelegramBotAPI.types.payments.ShippingQuery import com.github.insanusmokrassar.TelegramBotAPI.types.polls.Poll +import com.github.insanusmokrassar.TelegramBotAPI.types.polls.PollAnswer import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.UnknownUpdateType import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update import com.github.insanusmokrassar.TelegramBotAPI.types.updateIdField @@ -33,7 +34,8 @@ internal data class RawUpdate constructor( private val callback_query: RawCallbackQuery? = null, private val shipping_query: ShippingQuery? = null, private val pre_checkout_query: PreCheckoutQuery? = null, - private val poll: Poll? = null + private val poll: Poll? = null, + private val poll_answer: PollAnswer? = null ) { private var initedUpdate: Update? = null /** @@ -55,6 +57,7 @@ internal data class RawUpdate constructor( shipping_query != null -> ShippingQueryUpdate(updateId, shipping_query) pre_checkout_query != null -> PreCheckoutQueryUpdate(updateId, pre_checkout_query) poll != null -> PollUpdate(updateId, poll) + poll_answer != null -> PollAnswerUpdate(updateId, poll_answer) else -> UnknownUpdateType( updateId, raw.toString() diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/updateshandlers/UpdatesFilter.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/updateshandlers/UpdatesFilter.kt index c1a765f9f7..91ccfd24eb 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/updateshandlers/UpdatesFilter.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/updateshandlers/UpdatesFilter.kt @@ -22,6 +22,7 @@ data class UpdatesFilter( private val shippingQueryCallback: UpdateReceiver? = null, private val preCheckoutQueryCallback: UpdateReceiver? = null, private val pollUpdateCallback: UpdateReceiver? = null, + private val pollAnswerUpdateCallback: UpdateReceiver? = null, private val unknownUpdateTypeCallback: UpdateReceiver? = null ) { val asUpdateReceiver: UpdateReceiver = this::invoke @@ -35,7 +36,8 @@ data class UpdatesFilter( callbackQueryCallback ?.let { UPDATE_CALLBACK_QUERY }, shippingQueryCallback ?.let { UPDATE_SHIPPING_QUERY }, preCheckoutQueryCallback ?.let { UPDATE_PRE_CHECKOUT_QUERY }, - pollUpdateCallback ?.let { UPDATE_POLL } + pollUpdateCallback ?.let { UPDATE_POLL }, + pollAnswerUpdateCallback ?.let { UPDATE_POLL_ANSWER } ) suspend fun invoke(update: Update) { @@ -74,6 +76,7 @@ data class UpdatesFilter( is ShippingQueryUpdate -> shippingQueryCallback ?.invoke(update) is PreCheckoutQueryUpdate -> preCheckoutQueryCallback ?.invoke(update) is PollUpdate -> pollUpdateCallback ?.invoke(update) + is PollAnswerUpdate -> pollAnswerUpdateCallback ?.invoke(update) is UnknownUpdateType -> unknownUpdateTypeCallback ?.invoke(update) } } @@ -91,6 +94,7 @@ fun createSimpleUpdateFilter( shippingQueryCallback: UpdateReceiver? = null, preCheckoutQueryCallback: UpdateReceiver? = null, pollCallback: UpdateReceiver? = null, + pollAnswerCallback: UpdateReceiver? = null, unknownCallback: UpdateReceiver? = null ): UpdatesFilter = UpdatesFilter( messageCallback = messageCallback, @@ -107,5 +111,6 @@ fun createSimpleUpdateFilter( shippingQueryCallback = shippingQueryCallback, preCheckoutQueryCallback = preCheckoutQueryCallback, pollUpdateCallback = pollCallback, + pollAnswerUpdateCallback = pollAnswerCallback, unknownUpdateTypeCallback = unknownCallback ) diff --git a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPolling.kt b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPolling.kt index 903967173b..3131f1d858 100644 --- a/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPolling.kt +++ b/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPolling.kt @@ -43,6 +43,7 @@ fun RequestsExecutor.startGettingOfUpdates( shippingQueryCallback: UpdateReceiver? = null, preCheckoutQueryCallback: UpdateReceiver? = null, pollCallback: UpdateReceiver? = null, + pollAnswerCallback: UpdateReceiver? = null, timeoutMillis: Long = 30 * 1000, scope: CoroutineScope = GlobalScope ): UpdatesPoller { @@ -60,7 +61,8 @@ fun RequestsExecutor.startGettingOfUpdates( callbackQueryCallback, shippingQueryCallback, preCheckoutQueryCallback, - pollCallback + pollCallback, + pollAnswerCallback ) return startGettingOfUpdates( timeoutMillis, @@ -82,6 +84,7 @@ fun RequestsExecutor.startGettingOfUpdates( shippingQueryCallback: UpdateReceiver? = null, preCheckoutQueryCallback: UpdateReceiver? = null, pollCallback: UpdateReceiver? = null, + pollAnswerCallback: UpdateReceiver? = null, timeoutMillis: Long = 30 * 1000, scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ): UpdatesPoller = startGettingOfUpdates( @@ -99,6 +102,7 @@ fun RequestsExecutor.startGettingOfUpdates( shippingQueryCallback = shippingQueryCallback, preCheckoutQueryCallback = preCheckoutQueryCallback, pollCallback = pollCallback, + pollAnswerCallback = pollAnswerCallback, timeoutMillis = timeoutMillis, scope = scope ) diff --git a/src/jvmMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UserLocale.kt b/src/jvmMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UserLocale.kt index e61d97e2ea..b54e6be14a 100644 --- a/src/jvmMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UserLocale.kt +++ b/src/jvmMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UserLocale.kt @@ -2,6 +2,6 @@ package com.github.insanusmokrassar.TelegramBotAPI.types import java.util.* -fun User.javaLocale(): Locale? = languageCode ?.let { +fun CommonUser.javaLocale(): Locale? = languageCode ?.let { Locale.forLanguageTag(it) }