diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b177dc752..25ff0bf2be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # TelegramBotAPI changelog +## 0.38.14 + +__This update contains including of [Telegram Bot API 6.0](https://core.telegram.org/bots/api-changelog#april-16-2022)__ + +* `Core`: + * Constructor of `UnknownInlineKeyboardButton` is not internal and can be created with any `json` +* `WebApps`: + * Created 🎉 + ## 0.38.13 * `Core`: diff --git a/README.md b/README.md index 09e7522d64..9da7c4e6e6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# TelegramBotAPI [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi) [![Supported version](https://img.shields.io/badge/Telegram%20Bot%20API-5.7-blue)](https://core.telegram.org/bots/api-changelog#january-31-2022) +# TelegramBotAPI [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi) [![Supported version](https://img.shields.io/badge/Telegram%20Bot%20API-6.0-blue)](https://core.telegram.org/bots/api-changelog#april-16-2022) | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) [![Build Status](https://github.com/InsanusMokrassar/TelegramBotAPI/workflows/Build/badge.svg)](https://github.com/InsanusMokrassar/TelegramBotAPI/actions) [![Small survey](https://img.shields.io/static/v1?label=Google&message=Survey&color=blue&logo=google-sheets)](https://docs.google.com/forms/d/e/1FAIpQLSctdJHT_aEniyYT0-IUAEfo1hsIlezX2owlkEAYX4KPl2V2_A/viewform?usp=sf_link) [![Chat in Telegram](https://img.shields.io/static/v1?label=Telegram&message=Chat&color=blue&logo=telegram)](https://t.me/InMoTelegramBotAPI) | |:---:| diff --git a/docs/build.gradle b/docs/build.gradle index bb40a510c1..460bce0fc3 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -40,11 +40,17 @@ kotlin { dependencies { implementation kotlin('stdlib') - rootProject.subprojects.forEach { - if (it != project) { - api it - } - } + api project(":tgbotapi.core") + api project(":tgbotapi.api") + api project(":tgbotapi.utils") + api project(":tgbotapi.behaviour_builder") + api project(":tgbotapi.behaviour_builder.fsm") + api project(":tgbotapi") + } + } + jsMain { + dependencies { + api project(":tgbotapi.webapps") } } } diff --git a/gradle.properties b/gradle.properties index cde0eb7683..6dc6597a22 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,6 +20,6 @@ javax_activation_version=1.1.1 dokka_version=1.6.10 library_group=dev.inmo -library_version=0.38.13 +library_version=0.38.14 github_release_plugin_version=2.3.7 diff --git a/settings.gradle b/settings.gradle index f33377257e..0e5ecc4426 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,4 +21,5 @@ include ":tgbotapi.extensions.utils" include ":tgbotapi.extensions.behaviour_builder" include ":tgbotapi.extensions.behaviour_builder.fsm" include ":tgbotapi" +include ":tgbotapi.webapps" include ":docs" diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt index c704fc94b2..4390ab619b 100644 --- a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt @@ -13,7 +13,7 @@ import io.ktor.client.engine.* */ fun telegramBot( urlsKeeper: TelegramAPIUrlsKeeper, - client: HttpClient + client: HttpClient = HttpClient() ): TelegramBot = telegramBot(urlsKeeper) { this.client = client } @@ -66,7 +66,7 @@ inline fun telegramBot( inline fun telegramBot( token: String, apiUrl: String = telegramBotAPIDefaultUrl, - client: HttpClient + client: HttpClient = HttpClient() ): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), client) @Suppress("NOTHING_TO_INLINE") diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/answers/AnswerWebAppQuery.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/answers/AnswerWebAppQuery.kt new file mode 100644 index 0000000000..edd92b0089 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/answers/AnswerWebAppQuery.kt @@ -0,0 +1,16 @@ +package dev.inmo.tgbotapi.extensions.api.answers + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.answers.AnswerWebAppQuery +import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.InlineQueryResult +import dev.inmo.tgbotapi.types.WebAppQueryId + +suspend fun TelegramBot.answerWebAppQuery( + webAppQueryId: WebAppQueryId, + result: InlineQueryResult +) = execute(AnswerWebAppQuery(webAppQueryId, result)) + +suspend fun TelegramBot.answer( + webAppQueryId: WebAppQueryId, + result: InlineQueryResult +) = execute(AnswerWebAppQuery(webAppQueryId, result)) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/bot/ClearMyDefaultAdministratorRights.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/bot/ClearMyDefaultAdministratorRights.kt new file mode 100644 index 0000000000..098d576213 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/bot/ClearMyDefaultAdministratorRights.kt @@ -0,0 +1,13 @@ +package dev.inmo.tgbotapi.extensions.api.bot + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.bot.ClearMyDefaultAdministratorRights +import dev.inmo.tgbotapi.types.ChatAdministratorRightsImpl + +suspend fun TelegramBot.clearMyDefaultAdministratorRights( + forChannels: Boolean? = null +) = execute(ClearMyDefaultAdministratorRights(forChannels)) + +suspend fun TelegramBot.clearMyDefaultAdministratorRightsForChannels() = clearMyDefaultAdministratorRights(forChannels = true) + +suspend fun TelegramBot.clearMyDefaultAdministratorRightsForGroupsAndSupergroups() = clearMyDefaultAdministratorRights(forChannels = false) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/bot/GetMyDefaultAdministratorRights.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/bot/GetMyDefaultAdministratorRights.kt new file mode 100644 index 0000000000..74eae1d136 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/bot/GetMyDefaultAdministratorRights.kt @@ -0,0 +1,12 @@ +package dev.inmo.tgbotapi.extensions.api.bot + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.bot.GetMyDefaultAdministratorRights + +suspend fun TelegramBot.getMyDefaultAdministratorRights( + forChannels: Boolean? = null +) = execute(GetMyDefaultAdministratorRights(forChannels)) + +suspend fun TelegramBot.getMyDefaultAdministratorRightsForChannels() = getMyDefaultAdministratorRights(forChannels = true) + +suspend fun TelegramBot.getMyDefaultAdministratorRightsForGroupsAndSupergroups() = getMyDefaultAdministratorRights(forChannels = false) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/bot/SetMyDefaultAdministratorRights.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/bot/SetMyDefaultAdministratorRights.kt new file mode 100644 index 0000000000..6f14be2a1e --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/bot/SetMyDefaultAdministratorRights.kt @@ -0,0 +1,18 @@ +package dev.inmo.tgbotapi.extensions.api.bot + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.bot.SetMyDefaultAdministratorRights +import dev.inmo.tgbotapi.types.ChatAdministratorRightsImpl + +suspend fun TelegramBot.setMyDefaultAdministratorRights( + rights: ChatAdministratorRightsImpl, + forChannels: Boolean? = null +) = execute(SetMyDefaultAdministratorRights(rights, forChannels)) + +suspend fun TelegramBot.setMyDefaultAdministratorRightsForChannels( + rights: ChatAdministratorRightsImpl +) = setMyDefaultAdministratorRights(rights, forChannels = true) + +suspend fun TelegramBot.setMyDefaultAdministratorRightsForGroupsAndSupergroups( + rights: ChatAdministratorRightsImpl +) = setMyDefaultAdministratorRights(rights, forChannels = false) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/get/GetChatMenuButton.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/get/GetChatMenuButton.kt new file mode 100644 index 0000000000..ef8404b98f --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/get/GetChatMenuButton.kt @@ -0,0 +1,15 @@ +package dev.inmo.tgbotapi.extensions.api.chat.get + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.chat.get.GetChatMenuButton +import dev.inmo.tgbotapi.requests.chat.modify.* +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.chat.abstracts.PrivateChat + +suspend fun TelegramBot.getChatMenuButton( + chatId: ChatId +) = execute(GetChatMenuButton(chatId)) + +suspend fun TelegramBot.getChatMenuButton( + chat: PrivateChat +) = getChatMenuButton(chat.id) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/get/GetDefaultChatMenuButton.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/get/GetDefaultChatMenuButton.kt new file mode 100644 index 0000000000..e03a4f88e0 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/get/GetDefaultChatMenuButton.kt @@ -0,0 +1,8 @@ +package dev.inmo.tgbotapi.extensions.api.chat.get + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.chat.get.GetDefaultChatMenuButton +import dev.inmo.tgbotapi.requests.chat.modify.SetDefaultChatMenuButton +import dev.inmo.tgbotapi.types.MenuButton + +suspend fun TelegramBot.getDefaultChatMenuButton() = execute(GetDefaultChatMenuButton) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/members/PromoteChatMember.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/members/PromoteChatMember.kt index 865fc0ee80..cae8d96c1a 100644 --- a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/members/PromoteChatMember.kt +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/members/PromoteChatMember.kt @@ -18,7 +18,7 @@ suspend fun TelegramBot.promoteChatMember( canRestrictMembers: Boolean? = null, canPinMessages: Boolean? = null, canPromoteMembers: Boolean? = null, - canManageVoiceChats: Boolean? = null, + canManageVideoChats: Boolean? = null, canManageChat: Boolean? ) = execute( PromoteChatMember( @@ -34,7 +34,7 @@ suspend fun TelegramBot.promoteChatMember( canRestrictMembers, canPinMessages, canPromoteMembers, - canManageVoiceChats, + canManageVideoChats, canManageChat ) ) @@ -52,7 +52,7 @@ suspend fun TelegramBot.promoteChatMember( canRestrictMembers: Boolean? = null, canPinMessages: Boolean? = null, canPromoteMembers: Boolean? = null, - canManageVoiceChats: Boolean? = null, + canManageVideoChats: Boolean? = null, canManageChat: Boolean? = null ) = promoteChatMember( chat.id, @@ -67,7 +67,7 @@ suspend fun TelegramBot.promoteChatMember( canRestrictMembers, canPinMessages, canPromoteMembers, - canManageVoiceChats, + canManageVideoChats, canManageChat ) @@ -84,7 +84,7 @@ suspend fun TelegramBot.promoteChatMember( canRestrictMembers: Boolean? = null, canPinMessages: Boolean? = null, canPromoteMembers: Boolean? = null, - canManageVoiceChats: Boolean? = null, + canManageVideoChats: Boolean? = null, canManageChat: Boolean? = null ) = promoteChatMember( chatId, @@ -99,7 +99,7 @@ suspend fun TelegramBot.promoteChatMember( canRestrictMembers, canPinMessages, canPromoteMembers, - canManageVoiceChats, + canManageVideoChats, canManageChat ) @@ -116,7 +116,7 @@ suspend fun TelegramBot.promoteChatMember( canRestrictMembers: Boolean? = null, canPinMessages: Boolean? = null, canPromoteMembers: Boolean? = null, - canManageVoiceChats: Boolean? = null, + canManageVideoChats: Boolean? = null, canManageChat: Boolean? = null ) = promoteChatMember( chat.id, @@ -131,6 +131,6 @@ suspend fun TelegramBot.promoteChatMember( canRestrictMembers, canPinMessages, canPromoteMembers, - canManageVoiceChats, + canManageVideoChats, canManageChat ) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/modify/SetChatMenuButton.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/modify/SetChatMenuButton.kt new file mode 100644 index 0000000000..ed06222c50 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/modify/SetChatMenuButton.kt @@ -0,0 +1,16 @@ +package dev.inmo.tgbotapi.extensions.api.chat.modify + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.chat.modify.* +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.chat.abstracts.PrivateChat + +suspend fun TelegramBot.setChatMenuButton( + chatId: ChatId, + menuButton: MenuButton +) = execute(SetChatMenuButton(chatId, menuButton)) + +suspend fun TelegramBot.setChatMenuButton( + chat: PrivateChat, + menuButton: MenuButton +) = setChatMenuButton(chat.id, menuButton) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/modify/SetDefaultChatMenuButton.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/modify/SetDefaultChatMenuButton.kt new file mode 100644 index 0000000000..1239825d00 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/chat/modify/SetDefaultChatMenuButton.kt @@ -0,0 +1,9 @@ +package dev.inmo.tgbotapi.extensions.api.chat.modify + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.chat.modify.SetDefaultChatMenuButton +import dev.inmo.tgbotapi.types.MenuButton + +suspend fun TelegramBot.setDefaultChatMenuButton( + menuButton: MenuButton +) = execute(SetDefaultChatMenuButton(menuButton)) 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 841f9bcf06..2dfe353049 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 @@ -10,6 +10,7 @@ import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.types.message.ChatEvents.* import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.* import dev.inmo.tgbotapi.types.message.ChatEvents.voice.* +import dev.inmo.tgbotapi.types.message.PrivateEventMessage import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage import dev.inmo.tgbotapi.types.message.payments.SuccessfulPaymentEvent import kotlinx.coroutines.flow.toList @@ -89,33 +90,66 @@ suspend fun BehaviourContext.waitChatEvents( mapper: EventMessageToEventMapper? = null ) = waitEvents(count, initRequest, errorFactory, filter, mapper) +@Deprecated("Renamed as Video instead of Voice") suspend fun BehaviourContext.waitVoiceChatEvents( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, - filter: SimpleFilter>? = null, - mapper: EventMessageToEventMapper? = null + filter: SimpleFilter>? = null, + mapper: EventMessageToEventMapper? = null ) = waitEvents(count, initRequest, errorFactory, filter, mapper) +@Deprecated("Renamed as Video instead of Voice") suspend fun BehaviourContext.waitVoiceChatStartedEvents( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, - filter: SimpleFilter>? = null, - mapper: EventMessageToEventMapper? = null + filter: SimpleFilter>? = null, + mapper: EventMessageToEventMapper? = null ) = waitEvents(count, initRequest, errorFactory, filter, mapper) +@Deprecated("Renamed as Video instead of Voice") suspend fun BehaviourContext.waitVoiceChatEndedEvents( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, - filter: SimpleFilter>? = null, - mapper: EventMessageToEventMapper? = null + filter: SimpleFilter>? = null, + mapper: EventMessageToEventMapper? = null ) = waitEvents(count, initRequest, errorFactory, filter, mapper) +@Deprecated("Renamed as Video instead of Voice") suspend fun BehaviourContext.waitVoiceChatParticipantsInvitedEvents( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, - filter: SimpleFilter>? = null, - mapper: EventMessageToEventMapper? = null + filter: SimpleFilter>? = null, + mapper: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter, mapper) + +suspend fun BehaviourContext.waitVideoChatEvents( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + count: Int = 1, + filter: SimpleFilter>? = null, + mapper: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter, mapper) +suspend fun BehaviourContext.waitVideoChatStartedEvents( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + count: Int = 1, + filter: SimpleFilter>? = null, + mapper: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter, mapper) +suspend fun BehaviourContext.waitVideoChatEndedEvents( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + count: Int = 1, + filter: SimpleFilter>? = null, + mapper: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter, mapper) +suspend fun BehaviourContext.waitVideoChatParticipantsInvitedEvents( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + count: Int = 1, + filter: SimpleFilter>? = null, + mapper: EventMessageToEventMapper? = null ) = waitEvents(count, initRequest, errorFactory, filter, mapper) suspend fun BehaviourContext.waitMessageAutoDeleteTimerChangedEvents( @@ -241,3 +275,10 @@ suspend fun BehaviourContext.waitUserLoggedInEvents( filter: SimpleFilter>? = null, mapper: EventMessageToEventMapper? = null ) = waitEvents(count, initRequest, errorFactory, filter, mapper) +suspend fun BehaviourContext.waitWebAppDataEvents( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + count: Int = 1, + filter: SimpleFilter>? = null, + mapper: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter ?.let { { it is PrivateEventMessage && filter(it) } }, mapper) 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 f39245b11d..8c04544d87 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 @@ -12,6 +12,7 @@ import dev.inmo.tgbotapi.extensions.utils.asChatEventMessage import dev.inmo.tgbotapi.types.message.ChatEvents.* import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.* import dev.inmo.tgbotapi.types.message.ChatEvents.voice.* +import dev.inmo.tgbotapi.types.message.PrivateEventMessage import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage import dev.inmo.tgbotapi.types.message.payments.SuccessfulPaymentEvent import dev.inmo.tgbotapi.types.update.abstracts.Update @@ -94,11 +95,12 @@ suspend fun BC.onChatEvent( * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that * data */ +@Deprecated("Renamed as Video instead of Voice") suspend fun BC.onVoiceChatEvent( - initialFilter: SimpleFilter>? = null, - subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, - markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, - scenarioReceiver: CustomBehaviourContextAndTypeReceiver> + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> ) = onEvent(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) /** @@ -113,11 +115,12 @@ suspend fun BC.onVoiceChatEvent( * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that * data */ +@Deprecated("Renamed as Video instead of Voice") suspend fun BC.onVoiceChatStartedEvent( - initialFilter: SimpleFilter>? = null, - subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, - markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, - scenarioReceiver: CustomBehaviourContextAndTypeReceiver> + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> ) = onEvent(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) /** @@ -132,11 +135,12 @@ suspend fun BC.onVoiceChatStartedEvent( * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that * data */ +@Deprecated("Renamed as Video instead of Voice") suspend fun BC.onVoiceChatEndedEvent( - initialFilter: SimpleFilter>? = null, - subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, - markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, - scenarioReceiver: CustomBehaviourContextAndTypeReceiver> + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> ) = onEvent(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) /** @@ -151,11 +155,88 @@ suspend fun BC.onVoiceChatEndedEvent( * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that * data */ +@Deprecated("Renamed as Video instead of Voice") suspend fun BC.onVoiceChatParticipantsInvitedEvent( - initialFilter: SimpleFilter>? = null, - subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, - markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, - scenarioReceiver: CustomBehaviourContextAndTypeReceiver> + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +) = onEvent(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.onVideoChatEvent( + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +) = onEvent(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.onVideoChatStartedEvent( + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +) = onEvent(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.onVideoChatEndedEvent( + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +) = onEvent(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.onVideoChatParticipantsInvitedEvent( + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> ) = onEvent(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) /** @@ -483,3 +564,30 @@ suspend fun BC.onUserLoggedIn( markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, scenarioReceiver: CustomBehaviourContextAndTypeReceiver> ) = onEvent(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.onWebAppData( + initialFilter: SimpleFilter>? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update>? = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +) = onEvent( + initialFilter ?.let { { it is PrivateEventMessage && initialFilter(it) } }, + subcontextUpdatesFilter ?.let { { it: ChatEventMessage, update: Update -> it is PrivateEventMessage && subcontextUpdatesFilter(it, update) } }, + markerFactory +) { + if (it is PrivateEventMessage) { + scenarioReceiver(it) + } +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/CommonAbstracts/types/ChatRequest.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/CommonAbstracts/types/ChatRequest.kt index bdf3ba4e1b..f314edaceb 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/CommonAbstracts/types/ChatRequest.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/CommonAbstracts/types/ChatRequest.kt @@ -2,6 +2,6 @@ package dev.inmo.tgbotapi.CommonAbstracts.types import dev.inmo.tgbotapi.types.ChatIdentifier -interface ChatRequest { - val chatId: ChatIdentifier -} \ No newline at end of file +interface ChatRequest : OptionalChatRequest { + override val chatId: ChatIdentifier +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/CommonAbstracts/types/OptionalChatRequest.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/CommonAbstracts/types/OptionalChatRequest.kt new file mode 100644 index 0000000000..42bf118d28 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/CommonAbstracts/types/OptionalChatRequest.kt @@ -0,0 +1,7 @@ +package dev.inmo.tgbotapi.CommonAbstracts.types + +import dev.inmo.tgbotapi.types.ChatIdentifier + +interface OptionalChatRequest { + val chatId: ChatIdentifier? +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt index c168c982c5..1e7da06255 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt @@ -16,33 +16,6 @@ import io.ktor.client.features.* import io.ktor.client.statement.readText import kotlinx.serialization.json.Json -class KtorRequestsExecutorBuilder( - var telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper -) { - var client: HttpClient = HttpClient() - var callsFactories: List = emptyList() - var excludeDefaultFactories: Boolean = false - var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter() - var jsonFormatter: Json = nonstrictJsonFormat - - fun build() = KtorRequestsExecutor(telegramAPIUrlsKeeper, client, callsFactories, excludeDefaultFactories, requestsLimiter, jsonFormatter) -} - -inline fun telegramBot( - telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper, - crossinline builder: KtorRequestsExecutorBuilder.() -> Unit = {} -): TelegramBot = KtorRequestsExecutorBuilder(telegramAPIUrlsKeeper).apply(builder).build() - -/** - * Shortcut for [telegramBot] - */ -@Suppress("NOTHING_TO_INLINE") -inline fun telegramBot( - token: String, - apiUrl: String = telegramBotAPIDefaultUrl, - crossinline builder: KtorRequestsExecutorBuilder.() -> Unit = {} -): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), builder) - @RiskFeature fun createTelegramBotDefaultKtorCallRequestsFactories() = listOf( SimpleRequestCallFactory(), @@ -127,3 +100,30 @@ class KtorRequestsExecutor( client.close() } } + +class KtorRequestsExecutorBuilder( + var telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper +) { + var client: HttpClient = HttpClient() + var callsFactories: List = emptyList() + var excludeDefaultFactories: Boolean = false + var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter() + var jsonFormatter: Json = nonstrictJsonFormat + + fun build() = KtorRequestsExecutor(telegramAPIUrlsKeeper, client, callsFactories, excludeDefaultFactories, requestsLimiter, jsonFormatter) +} + +inline fun telegramBot( + telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper, + builder: KtorRequestsExecutorBuilder.() -> Unit = {} +): TelegramBot = KtorRequestsExecutorBuilder(telegramAPIUrlsKeeper).apply(builder).build() + +/** + * Shortcut for [telegramBot] + */ +@Suppress("NOTHING_TO_INLINE") +inline fun telegramBot( + token: String, + apiUrl: String = telegramBotAPIDefaultUrl, + builder: KtorRequestsExecutorBuilder.() -> Unit = {} +): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), builder) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/answers/AnswerWebAppQuery.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/answers/AnswerWebAppQuery.kt new file mode 100644 index 0000000000..ceacda665f --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/answers/AnswerWebAppQuery.kt @@ -0,0 +1,21 @@ +package dev.inmo.tgbotapi.requests.answers + +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.InlineQueryResult +import dev.inmo.tgbotapi.types.webapps.query.SentWebAppMessage +import kotlinx.serialization.* + +@Serializable +data class AnswerWebAppQuery( + @SerialName(webAppQueryIdField) + val webAppQueryId: WebAppQueryId, + @SerialName(resultField) + val result: InlineQueryResult +) : SimpleRequest { + override fun method(): String = "answerWebAppQuery" + override val resultDeserializer: DeserializationStrategy + get() = SentWebAppMessage.serializer() + override val requestSerializer: SerializationStrategy<*> + get() = serializer() +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/bot/ClearMyDefaultAdministratorRights.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/bot/ClearMyDefaultAdministratorRights.kt new file mode 100644 index 0000000000..7e60e578ab --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/bot/ClearMyDefaultAdministratorRights.kt @@ -0,0 +1,18 @@ +package dev.inmo.tgbotapi.requests.bot + +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer + +@Serializable +class ClearMyDefaultAdministratorRights( + @SerialName(forChannelsField) + val forChannels: Boolean? = null +) : SimpleRequest { + override fun method(): String = "setMyDefaultAdministratorRights" + override val resultDeserializer: DeserializationStrategy + get() = Boolean.serializer() + override val requestSerializer: SerializationStrategy<*> + get() = serializer() +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/bot/GetMyDefaultAdministratorRights.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/bot/GetMyDefaultAdministratorRights.kt new file mode 100644 index 0000000000..e59dcc5be1 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/bot/GetMyDefaultAdministratorRights.kt @@ -0,0 +1,23 @@ +package dev.inmo.tgbotapi.requests.bot + +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.ChatMember.AdministratorChatMemberImpl +import kotlinx.serialization.* + +@Serializable +data class GetMyDefaultAdministratorRights( + @SerialName(forChannelsField) + val forChannels: Boolean? = null +) : SimpleRequest { + override fun method(): String = "getMyDefaultAdministratorRights" + override val resultDeserializer: DeserializationStrategy + get() = AdministratorChatMemberImpl.serializer() + override val requestSerializer: SerializationStrategy<*> + get() = serializer() + + companion object { + val ForChannels = GetMyDefaultAdministratorRights(true) + val ForGroups = GetMyDefaultAdministratorRights(false) + } +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/bot/SetMyDefaultAdministratorRights.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/bot/SetMyDefaultAdministratorRights.kt new file mode 100644 index 0000000000..cc30b7070a --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/bot/SetMyDefaultAdministratorRights.kt @@ -0,0 +1,20 @@ +package dev.inmo.tgbotapi.requests.bot + +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer + +@Serializable +class SetMyDefaultAdministratorRights( + @SerialName(rightsField) + val rights: ChatAdministratorRightsImpl, + @SerialName(forChannelsField) + val forChannels: Boolean? = null +) : SimpleRequest { + override fun method(): String = "setMyDefaultAdministratorRights" + override val resultDeserializer: DeserializationStrategy + get() = Boolean.serializer() + override val requestSerializer: SerializationStrategy<*> + get() = serializer() +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/get/GetChatMenuButton.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/get/GetChatMenuButton.kt new file mode 100644 index 0000000000..b4740a353c --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/get/GetChatMenuButton.kt @@ -0,0 +1,21 @@ +package dev.inmo.tgbotapi.requests.chat.get + +import dev.inmo.tgbotapi.CommonAbstracts.types.ChatRequest +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer + +@Serializable +data class GetChatMenuButton( + @SerialName(chatIdField) + override val chatId: ChatIdentifier +) : ChatRequest, SimpleRequest { + override val requestSerializer: SerializationStrategy<*> + get() = serializer() + + override fun method(): String = GetDefaultChatMenuButton.method() + + override val resultDeserializer: DeserializationStrategy + get() = MenuButtonSerializer +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/get/GetDefaultChatMenuButton.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/get/GetDefaultChatMenuButton.kt new file mode 100644 index 0000000000..0c942acece --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/get/GetDefaultChatMenuButton.kt @@ -0,0 +1,21 @@ +package dev.inmo.tgbotapi.requests.chat.get + +import dev.inmo.tgbotapi.CommonAbstracts.types.ChatRequest +import dev.inmo.tgbotapi.CommonAbstracts.types.OptionalChatRequest +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer + +@Serializable +object GetDefaultChatMenuButton : OptionalChatRequest, SimpleRequest { + override val chatId: ChatIdentifier? + get() = null + override val requestSerializer: SerializationStrategy<*> + get() = serializer() + + override fun method(): String = "getChatMenuButton" + + override val resultDeserializer: DeserializationStrategy + get() = MenuButtonSerializer +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/members/PromoteChatMember.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/members/PromoteChatMember.kt index 91e18f7381..4079eec591 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/members/PromoteChatMember.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/members/PromoteChatMember.kt @@ -32,8 +32,8 @@ data class PromoteChatMember( private val canPinMessages: Boolean? = null, @SerialName(canPromoteMembersField) private val canPromoteMembers: Boolean? = null, - @SerialName(canManageVoiceChatsField) - private val canManageVoiceChats: Boolean? = null, + @SerialName(canManageVideoChatsField) + private val canManageVideoChats: Boolean? = null, @SerialName(canManageChatField) private val canManageChat: Boolean? = null ) : ChatMemberRequest, UntilDate { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/modify/SetChatMenuButton.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/modify/SetChatMenuButton.kt new file mode 100644 index 0000000000..6c8cc9dd3d --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/modify/SetChatMenuButton.kt @@ -0,0 +1,24 @@ +package dev.inmo.tgbotapi.requests.chat.modify + +import dev.inmo.tgbotapi.CommonAbstracts.types.ChatRequest +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer + +@Serializable +data class SetChatMenuButton( + @SerialName(chatIdField) + override val chatId: ChatIdentifier, + @Serializable(MenuButtonSerializer::class) + @SerialName(menuButtonField) + val menuButton: MenuButton +) : ChatRequest, SimpleRequest { + override val requestSerializer: SerializationStrategy<*> + get() = serializer() + + override fun method(): String = SetDefaultChatMenuButton.method() + + override val resultDeserializer: DeserializationStrategy + get() = Boolean.serializer() +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/modify/SetDefaultChatMenuButton.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/modify/SetDefaultChatMenuButton.kt new file mode 100644 index 0000000000..7ce92e14ed --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/chat/modify/SetDefaultChatMenuButton.kt @@ -0,0 +1,25 @@ +package dev.inmo.tgbotapi.requests.chat.modify + +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer + +@Serializable +data class SetDefaultChatMenuButton( + @Serializable(MenuButtonSerializer::class) + @SerialName(menuButtonField) + val menuButton: MenuButton +) : SimpleRequest { + override val requestSerializer: SerializationStrategy<*> + get() = serializer() + + override fun method(): String = Companion.method() + + override val resultDeserializer: DeserializationStrategy + get() = Boolean.serializer() + + companion object { + fun method() = "setChatMenuButton" + } +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatAdministratorRightsImpl.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatAdministratorRightsImpl.kt new file mode 100644 index 0000000000..b7398fa49d --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatAdministratorRightsImpl.kt @@ -0,0 +1,31 @@ +package dev.inmo.tgbotapi.types + +import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatAdministratorRights +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ChatAdministratorRightsImpl( + @SerialName(canChangeInfoField) + override val canChangeInfo: Boolean = false, + @SerialName(canPostMessagesField) + override val canPostMessages: Boolean = false, + @SerialName(canEditMessagesField) + override val canEditMessages: Boolean = false, + @SerialName(canDeleteMessagesField) + override val canRemoveMessages: Boolean = false, + @SerialName(canInviteUsersField) + override val canInviteUsers: Boolean = false, + @SerialName(canRestrictMembersField) + override val canRestrictMembers: Boolean = false, + @SerialName(canPinMessagesField) + override val canPinMessages: Boolean = false, + @SerialName(canPromoteMembersField) + override val canPromoteMembers: Boolean = false, + @SerialName(canManageVideoChatsField) + override val canManageVideoChats: Boolean = false, + @SerialName(canManageChatField) + override val canManageChat: Boolean = false, + @SerialName(isAnonymousField) + override val isAnonymous: Boolean = false +) : ChatAdministratorRights diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/AdministratorChatMemberImpl.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/AdministratorChatMemberImpl.kt index b3bfd4cd18..b79d44145d 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/AdministratorChatMemberImpl.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/AdministratorChatMemberImpl.kt @@ -26,8 +26,8 @@ data class AdministratorChatMemberImpl( override val canPinMessages: Boolean = false, @SerialName(canPromoteMembersField) override val canPromoteMembers: Boolean = false, - @SerialName(canManageVoiceChatsField) - override val canManageVoiceChats: Boolean = false, + @SerialName(canManageVideoChatsField) + override val canManageVideoChats: Boolean = false, @SerialName(canManageChatField) override val canManageChat: Boolean = false, @SerialName(isAnonymousField) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/CreatorChatMember.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/CreatorChatMember.kt index 8344c7bb22..36da996d9a 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/CreatorChatMember.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/CreatorChatMember.kt @@ -31,7 +31,7 @@ data class CreatorChatMember( @Transient override val canPromoteMembers: Boolean = true @Transient - override val canManageVoiceChats: Boolean = true + override val canManageVideoChats: Boolean = true @Transient override val canManageChat: Boolean = true @SerialName(statusField) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/AdministratorChatMember.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/AdministratorChatMember.kt index 3664311d96..9d48b9f9ad 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/AdministratorChatMember.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/AdministratorChatMember.kt @@ -8,17 +8,12 @@ import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @Serializable(AdministratorChatMemberSerializer::class) -interface AdministratorChatMember : SpecialRightsChatMember { +interface AdministratorChatMember : SpecialRightsChatMember, ChatAdministratorRights { val canBeEdited: Boolean - val canPostMessages: Boolean - val canEditMessages: Boolean - val canRemoveMessages: Boolean - val canRestrictMembers: Boolean - val canPromoteMembers: Boolean - val canManageVoiceChats: Boolean - val canManageChat: Boolean - val isAnonymous: Boolean val customTitle: String? + + val canManageVoiceChats: Boolean + get() = canManageVideoChats } @RiskFeature diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/ChatAdministratorRights.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/ChatAdministratorRights.kt new file mode 100644 index 0000000000..61a3889778 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/ChatAdministratorRights.kt @@ -0,0 +1,18 @@ +package dev.inmo.tgbotapi.types.ChatMember.abstracts + +sealed interface SpecialChatAdministratorRights { + val canChangeInfo: Boolean + val canInviteUsers: Boolean + val canPinMessages: Boolean +} + +interface ChatAdministratorRights : SpecialChatAdministratorRights { + val isAnonymous: Boolean + val canManageChat: Boolean + val canRemoveMessages: Boolean + val canManageVideoChats: Boolean + val canRestrictMembers: Boolean + val canPromoteMembers: Boolean + val canPostMessages: Boolean + val canEditMessages: Boolean +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/SpecialRightsChatMember.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/SpecialRightsChatMember.kt index 796c6fa69b..79cea14abb 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/SpecialRightsChatMember.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatMember/abstracts/SpecialRightsChatMember.kt @@ -3,8 +3,4 @@ package dev.inmo.tgbotapi.types.ChatMember.abstracts import kotlinx.serialization.Serializable @Serializable(ChatMemberSerializer::class) -interface SpecialRightsChatMember : ChatMember { - val canChangeInfo: Boolean - val canInviteUsers: Boolean - val canPinMessages: Boolean -} +interface SpecialRightsChatMember : ChatMember, SpecialChatAdministratorRights 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 0f432f60a8..3c69c82260 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 @@ -27,6 +27,7 @@ typealias FoursquareType = String typealias GooglePlaceId = String typealias GooglePlaceType = String typealias MembersLimit = Int +typealias WebAppQueryId = String typealias Seconds = Int typealias MilliSeconds = Long @@ -133,6 +134,7 @@ const val inlineMessageIdField = "inline_message_id" const val callbackDataField = "callback_data" const val callbackGameField = "callback_game" const val callbackQueryIdField = "callback_query_id" +const val webAppQueryIdField = "web_app_query_id" const val inlineQueryIdField = "inline_query_id" const val inlineKeyboardField = "inline_keyboard" const val showAlertField = "show_alert" @@ -154,6 +156,7 @@ const val dropPendingUpdatesField = "drop_pending_updates" const val hasCustomCertificateField = "has_custom_certificate" const val pendingUpdateCountField = "pending_update_count" const val lastErrorDateField = "last_error_date" +const val lastSynchronizationErrorDateField = "last_synchronization_error_date" const val lastErrorMessageField = "last_error_message" const val votesCountField = "voter_count" const val isClosedField = "is_closed" @@ -263,6 +266,9 @@ const val canRestrictMembersField = "can_restrict_members" const val canPinMessagesField = "can_pin_messages" const val canPromoteMembersField = "can_promote_members" const val canManageVoiceChatsField = "can_manage_voice_chats" +const val canManageVideoChatsField = "can_manage_video_chats" +const val rightsField = "rights" +const val forChannelsField = "for_channels" const val canManageChatField = "can_manage_chat" const val pngStickerField = "png_sticker" const val tgsStickerField = "tgs_sticker" @@ -313,6 +319,7 @@ const val pricesField = "prices" const val payloadField = "payload" const val vcardField = "vcard" const val resultsField = "results" +const val resultField = "result" const val certificateField = "certificate" const val questionField = "question" const val optionsField = "options" @@ -430,3 +437,7 @@ const val bankStatementField = "bank_statement" const val rentalAgreementField = "rental_agreement" const val passportRegistrationField = "passport_registration" const val temporaryRegistrationField = "temporary_registration" + +const val buttonTextField = "button_text" +const val webAppField = "web_app" +const val menuButtonField = "menu_button" diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MenuButton.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MenuButton.kt new file mode 100644 index 0000000000..3ce057ce7c --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MenuButton.kt @@ -0,0 +1,122 @@ +package dev.inmo.tgbotapi.types + +import dev.inmo.tgbotapi.types.webapps.WebAppInfo +import dev.inmo.tgbotapi.utils.RiskFeature +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.* + +@Serializable(MenuButtonSerializer::class) +sealed interface MenuButton { + @Required + val type: String + + @Serializable + object Commands : MenuButton { + @Required + override val type: String + get() = "commands" + } + + @Serializable + data class WebApp( + val text: String, + @SerialName(webAppField) + val webApp: WebAppInfo + ) : MenuButton { + @Required + override val type: String + get() = Companion.type + + companion object { + val type: String + get() = "web_app" + } + } + + @Serializable + object Default : MenuButton { + @Required + override val type: String + get() = "default" + } + + @Serializable + @RiskFeature + data class Unknown ( + override val type: String, + val rawJson: JsonElement + ) : MenuButton + + companion object { + fun serializer(): KSerializer = MenuButtonSerializer + } +} + +@Serializable +internal data class MenuButtonSurrogate( + val type: String, + val text: String? = null, + @SerialName(webAppField) + val webApp: WebAppInfo? = null, + val srcJsonElement: JsonElement? = null +) + +@Serializer(MenuButton::class) +object MenuButtonSerializer : KSerializer { + override val descriptor: SerialDescriptor + get() = MenuButtonSurrogate.serializer().descriptor + + override fun deserialize(decoder: Decoder): MenuButton { + val surrogate = if (decoder is JsonDecoder) { + val json = JsonElement.serializer().deserialize(decoder) + runCatching { + decoder.json.decodeFromJsonElement(MenuButtonSurrogate.serializer(), json) + }.onFailure { + return MenuButton.Unknown( + runCatching { json.jsonObject[typeField] ?.jsonPrimitive ?.content }.getOrNull() ?: "", + json + ) + }.getOrThrow().copy( + srcJsonElement = json + ) + } else { + MenuButtonSurrogate.serializer().deserialize(decoder) + } + + return when (surrogate.type) { + MenuButton.Commands.type -> MenuButton.Commands + MenuButton.Default.type -> MenuButton.Default + MenuButton.WebApp.type -> if (surrogate.text != null && surrogate.webApp != null) { + MenuButton.WebApp(surrogate.text, surrogate.webApp) + } else { + null + } + else -> null + } ?: MenuButton.Unknown( + surrogate.type, + surrogate.srcJsonElement ?: buildJsonObject { } + ) + } + + override fun serialize(encoder: Encoder, value: MenuButton) { + encoder.encodeSerializableValue( + MenuButtonSurrogate.serializer(), + when (value) { + MenuButton.Default, + MenuButton.Commands -> MenuButtonSurrogate(value.type) + is MenuButton.WebApp -> MenuButtonSurrogate(value.type, value.text, value.webApp) + is MenuButton.Unknown -> { + encoder.encodeSerializableValue( + JsonElement.serializer(), + value.rawJson + ) + return + } + } + ) + } + +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/WebhookInfo.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/WebhookInfo.kt index 3596d56b12..f4b92e169f 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/WebhookInfo.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/WebhookInfo.kt @@ -16,6 +16,8 @@ data class WebhookInfo( val allowedUpdates: List = ALL_UPDATES_LIST, @SerialName(lastErrorDateField) val lastErrorDate: TelegramDate? = null, + @SerialName(lastSynchronizationErrorDateField) + val lastSynchronizationErrorDate: TelegramDate? = null, @SerialName(lastErrorMessageField) val lastErrorMessage: String? = null ) { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/InlineKeyboardButtons/InlineKeyboardButton.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/InlineKeyboardButtons/InlineKeyboardButton.kt index 098e8c582a..a3f95bce73 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/InlineKeyboardButtons/InlineKeyboardButton.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/InlineKeyboardButtons/InlineKeyboardButton.kt @@ -2,8 +2,9 @@ package dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.games.CallbackGame +import dev.inmo.tgbotapi.types.webapps.WebAppInfo import kotlinx.serialization.* -import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.* /** * Some button of [dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup]. See inheritors and visit @@ -15,10 +16,14 @@ sealed interface InlineKeyboardButton { } @Serializable -data class UnknownInlineKeyboardButton internal constructor( - override val text: String, +data class UnknownInlineKeyboardButton ( val rawData: JsonElement -) : InlineKeyboardButton +) : InlineKeyboardButton { + override val text: String + get() = runCatching { + rawData.jsonObject[textField] ?.jsonPrimitive ?.content + }.getOrNull() ?: "" +} /** * This type of button must always be the first button in the first row. Visit @@ -116,3 +121,14 @@ data class URLInlineKeyboardButton( @SerialName(urlField) val url: String ) : InlineKeyboardButton + +/** + * Button with [WebAppInfo]. Web App will be launched when the button is pressed. The Web App will be able to send a + * `web_app_data` service message. **Available in private chats only**. + */ +@Serializable +data class WebAppInlineKeyboardButton( + override val text: String, + @SerialName(webAppField) + val webApp: WebAppInfo +) : InlineKeyboardButton diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/InlineKeyboardButtons/InlineKeyboardButtonSerializer.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/InlineKeyboardButtons/InlineKeyboardButtonSerializer.kt index 86695d33b0..70005d12c7 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/InlineKeyboardButtons/InlineKeyboardButtonSerializer.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/buttons/InlineKeyboardButtons/InlineKeyboardButtonSerializer.kt @@ -37,7 +37,7 @@ object InlineKeyboardButtonSerializer : KSerializer { return (json as? JsonObject) ?.let { resolveSerializer(it) } ?.let { nonstrictJsonFormat.decodeFromJsonElement(it, json) - } ?: UnknownInlineKeyboardButton("", json) + } ?: UnknownInlineKeyboardButton(json) } override fun serialize(encoder: Encoder, value: InlineKeyboardButton) { @@ -48,6 +48,7 @@ object InlineKeyboardButtonSerializer : KSerializer { is SwitchInlineQueryInlineKeyboardButton -> SwitchInlineQueryInlineKeyboardButton.serializer().serialize(encoder, value) is SwitchInlineQueryCurrentChatInlineKeyboardButton -> SwitchInlineQueryCurrentChatInlineKeyboardButton.serializer().serialize(encoder, value) is URLInlineKeyboardButton -> URLInlineKeyboardButton.serializer().serialize(encoder, value) + is WebAppInlineKeyboardButton -> WebAppInlineKeyboardButton.serializer().serialize(encoder, value) is CallbackGameInlineKeyboardButton -> CallbackGameInlineKeyboardButton.serializer().serialize(encoder, value) is UnknownInlineKeyboardButton -> JsonElement.serializer().serialize(encoder, value.rawData) } 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 688dd3da03..4226bc94d2 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 @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.types.buttons import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.webapps.WebAppInfo import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.nonstrictJsonFormat import kotlinx.serialization.* @@ -63,10 +64,22 @@ data class RequestLocationKeyboardButton( override val text: String ) : KeyboardButton { @SerialName(requestLocationField) - @EncodeDefault + @Required val requestLocation: Boolean = true } +/** + * Private chats only. Description of the Web App that will be launched when the user presses the button. The Web App + * will be able to send an arbitrary message on behalf of the user using the method `answerWebAppQuery`. Available only + * in private chats between a user and the bot. + */ +@Serializable +data class WebAppKeyboardButton( + override val text: String, + @SerialName(webAppField) + val webApp: WebAppInfo +) : KeyboardButton + /** * Private chats only. When user will tap on this button, he will be asked for the poll with [requestPoll] options. You will be able * to catch this poll in updates and data using [dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPoll] in @@ -97,6 +110,13 @@ object KeyboardButtonSerializer : KSerializer { asJson is JsonObject && asJson[requestLocationField] != null -> RequestLocationKeyboardButton( asJson[textField]!!.jsonPrimitive.content ) + asJson is JsonObject && asJson[webAppField] != null -> WebAppKeyboardButton( + asJson[textField]!!.jsonPrimitive.content, + nonstrictJsonFormat.decodeFromJsonElement( + WebAppInfo.serializer(), + asJson[webAppField]!! + ) + ) asJson is JsonObject && asJson[requestPollField] != null -> RequestPollKeyboardButton( asJson[textField]!!.jsonPrimitive.content, nonstrictJsonFormat.decodeFromJsonElement( @@ -119,6 +139,7 @@ object KeyboardButtonSerializer : KSerializer { when (value) { is RequestContactKeyboardButton -> RequestContactKeyboardButton.serializer().serialize(encoder, value) is RequestLocationKeyboardButton -> RequestLocationKeyboardButton.serializer().serialize(encoder, value) + is WebAppKeyboardButton -> WebAppKeyboardButton.serializer().serialize(encoder, value) is RequestPollKeyboardButton -> RequestPollKeyboardButton.serializer().serialize(encoder, value) is SimpleKeyboardButton -> encoder.encodeString(value.text) is UnknownKeyboardButton -> JsonElement.serializer().serialize(encoder, nonstrictJsonFormat.parseToJsonElement(value.raw)) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/WebAppData.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/WebAppData.kt new file mode 100644 index 0000000000..11d1d2eb96 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/WebAppData.kt @@ -0,0 +1,15 @@ +package dev.inmo.tgbotapi.types.message.ChatEvents + +import dev.inmo.tgbotapi.types.buttonTextField +import dev.inmo.tgbotapi.types.dataField +import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.PrivateEvent +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WebAppData( + @SerialName(dataField) + val data: String, + @SerialName(buttonTextField) + val buttonText: String +) : PrivateEvent diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/abstracts/VideoChatEvent.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/abstracts/VideoChatEvent.kt new file mode 100644 index 0000000000..077f9d32ba --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/abstracts/VideoChatEvent.kt @@ -0,0 +1,8 @@ +package dev.inmo.tgbotapi.types.message.ChatEvents.abstracts + +import dev.inmo.tgbotapi.types.message.ChatEvents.voice.VideoChatScheduled + +interface VideoChatEvent : PublicChatEvent + +@Deprecated("Renamed", ReplaceWith("VideoChatEvent", "dev.inmo.tgbotapi.types.message.ChatEvents.voice.VideoChatEvent")) +typealias VoiceChatEvent = VideoChatEvent diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/abstracts/VoiceChatEvent.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/abstracts/VoiceChatEvent.kt deleted file mode 100644 index f06f52d765..0000000000 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/abstracts/VoiceChatEvent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package dev.inmo.tgbotapi.types.message.ChatEvents.abstracts - -interface VoiceChatEvent : PublicChatEvent diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatEnded.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatEnded.kt similarity index 60% rename from tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatEnded.kt rename to tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatEnded.kt index 3969e603fd..d13204b791 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatEnded.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatEnded.kt @@ -4,15 +4,18 @@ import com.soywiz.klock.TimeSpan import com.soywiz.klock.seconds import dev.inmo.tgbotapi.types.Seconds import dev.inmo.tgbotapi.types.durationField -import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.VoiceChatEvent +import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.VideoChatEvent import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class VoiceChatEnded( +data class VideoChatEnded( @SerialName(durationField) val duration: Seconds -) : VoiceChatEvent { +) : VideoChatEvent { val timeSpan: TimeSpan get() = TimeSpan(duration.seconds.milliseconds) } + +@Deprecated("Renamed", ReplaceWith("VideoChatEnded", "dev.inmo.tgbotapi.types.message.ChatEvents.voice.VideoChatEnded")) +typealias VoiceChatEnded = VideoChatEnded diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatParticipantsInvited.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatParticipantsInvited.kt new file mode 100644 index 0000000000..aa856c9f5e --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatParticipantsInvited.kt @@ -0,0 +1,16 @@ +package dev.inmo.tgbotapi.types.message.ChatEvents.voice + +import dev.inmo.tgbotapi.types.User +import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.VideoChatEvent +import dev.inmo.tgbotapi.types.usersField +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class VideoChatParticipantsInvited( + @SerialName(usersField) + val users: List = emptyList() +) : VideoChatEvent + +@Deprecated("Renamed", ReplaceWith("VideoChatParticipantsInvited", "dev.inmo.tgbotapi.types.message.ChatEvents.voice.VideoChatParticipantsInvited")) +typealias VoiceChatParticipantsInvited = VideoChatParticipantsInvited diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatScheduled.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatScheduled.kt similarity index 50% rename from tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatScheduled.kt rename to tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatScheduled.kt index 8db852aecc..5bb18b6ec2 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatScheduled.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatScheduled.kt @@ -1,13 +1,16 @@ package dev.inmo.tgbotapi.types.message.ChatEvents.voice import dev.inmo.tgbotapi.types.TelegramDate -import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.VoiceChatEvent +import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.VideoChatEvent import dev.inmo.tgbotapi.types.startDateField import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class VoiceChatScheduled( +data class VideoChatScheduled( @SerialName(startDateField) val startDate: TelegramDate -) : VoiceChatEvent +) : VideoChatEvent + +@Deprecated("Renamed", ReplaceWith("VideoChatScheduled", "dev.inmo.tgbotapi.types.message.ChatEvents.voice.VideoChatScheduled")) +typealias VoiceChatScheduled = VideoChatScheduled diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatStarted.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatStarted.kt new file mode 100644 index 0000000000..0c9d08546a --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VideoChatStarted.kt @@ -0,0 +1,10 @@ +package dev.inmo.tgbotapi.types.message.ChatEvents.voice + +import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.VideoChatEvent +import kotlinx.serialization.Serializable + +@Serializable +object VideoChatStarted : VideoChatEvent + +@Deprecated("Renamed", ReplaceWith("VideoChatStarted", "dev.inmo.tgbotapi.types.message.ChatEvents.voice.VideoChatStarted")) +typealias VoiceChatStarted = VideoChatStarted diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatParticipantsInvited.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatParticipantsInvited.kt deleted file mode 100644 index 3df3f3d711..0000000000 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatParticipantsInvited.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.inmo.tgbotapi.types.message.ChatEvents.voice - -import dev.inmo.tgbotapi.types.User -import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.VoiceChatEvent -import dev.inmo.tgbotapi.types.usersField -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class VoiceChatParticipantsInvited( - @SerialName(usersField) - val users: List = emptyList() -) : VoiceChatEvent diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatStarted.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatStarted.kt deleted file mode 100644 index 9b72796ac1..0000000000 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/ChatEvents/voice/VoiceChatStarted.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.inmo.tgbotapi.types.message.ChatEvents.voice - -import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.VoiceChatEvent -import kotlinx.serialization.Serializable - -@Serializable -object VoiceChatStarted : VoiceChatEvent 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 4022e0ddae..2ad44c8845 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 @@ -84,10 +84,10 @@ internal data class RawMessage( private val successful_payment: SuccessfulPayment? = null, // Voice Chat Service Messages - private val voice_chat_scheduled: VoiceChatScheduled? = null, - private val voice_chat_started: VoiceChatStarted? = null, - private val voice_chat_ended: VoiceChatEnded? = null, - private val voice_chat_participants_invited: VoiceChatParticipantsInvited? = null, + private val video_chat_scheduled: VideoChatScheduled? = null, + private val video_chat_started: VideoChatStarted? = null, + private val video_chat_ended: VideoChatEnded? = null, + private val video_chat_participants_invited: VideoChatParticipantsInvited? = null, // AutoDelete Message time changed private val message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged? = null, @@ -95,6 +95,9 @@ internal data class RawMessage( // login property private val connected_website: String? = null, + // login property + private val web_app_data: WebAppData? = null, + // passport property private val passport_data: PassportData? = null, private val proximity_alert_triggered: ProximityAlertTriggered? = null, @@ -183,11 +186,11 @@ internal data class RawMessage( left_chat_member != null -> LeftChatMember(left_chat_member) new_chat_title != null -> NewChatTitle(new_chat_title) new_chat_photo != null -> NewChatPhoto(new_chat_photo.toList()) - voice_chat_started != null -> voice_chat_started - voice_chat_scheduled != null -> voice_chat_scheduled + video_chat_started != null -> video_chat_started + video_chat_scheduled != null -> video_chat_scheduled message_auto_delete_timer_changed != null -> message_auto_delete_timer_changed - voice_chat_ended != null -> voice_chat_ended - voice_chat_participants_invited != null -> voice_chat_participants_invited + video_chat_ended != null -> video_chat_ended + video_chat_participants_invited != null -> video_chat_participants_invited delete_chat_photo -> DeleteChatPhoto() group_chat_created -> GroupChatCreated( migrate_to_chat_id @@ -203,6 +206,7 @@ internal data class RawMessage( proximity_alert_triggered != null -> proximity_alert_triggered successful_payment != null -> SuccessfulPaymentEvent(successful_payment) connected_website != null -> UserLoggedIn(connected_website) + web_app_data != null -> web_app_data else -> null } } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/webapps/WebAppInfo.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/webapps/WebAppInfo.kt new file mode 100644 index 0000000000..09ede53453 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/webapps/WebAppInfo.kt @@ -0,0 +1,11 @@ +package dev.inmo.tgbotapi.types.webapps + +import dev.inmo.tgbotapi.types.urlField +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class WebAppInfo( + @SerialName(urlField) + val url: String +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/webapps/query/SentWebAppMessage.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/webapps/query/SentWebAppMessage.kt new file mode 100644 index 0000000000..800041c005 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/webapps/query/SentWebAppMessage.kt @@ -0,0 +1,9 @@ +package dev.inmo.tgbotapi.types.webapps.query + +import dev.inmo.tgbotapi.types.InlineMessageIdentifier +import kotlinx.serialization.Serializable + +@Serializable +data class SentWebAppMessage( + val inlineMessageId: InlineMessageIdentifier? = null +) diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt index d505b89d6e..05fe5aaa98 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt @@ -2516,6 +2516,16 @@ inline fun InlineKeyboardButton.asURLInlineKeyboardButton(): URLInlineKeyboardBu inline fun InlineKeyboardButton.requireURLInlineKeyboardButton(): URLInlineKeyboardButton = this as URLInlineKeyboardButton +@PreviewFeature +inline fun InlineKeyboardButton.whenWebAppKeyboardButton(block: (WebAppKeyboardButton) -> T) = asWebAppKeyboardButton() ?.let(block) + +@PreviewFeature +inline fun InlineKeyboardButton.asWebAppKeyboardButton(): WebAppKeyboardButton? = this as? WebAppKeyboardButton + +@PreviewFeature +inline fun InlineKeyboardButton.requireWebAppKeyboardButton(): WebAppKeyboardButton = + this as WebAppKeyboardButton + @PreviewFeature inline fun InlineKeyboardButton.whenUnknownInlineKeyboardButton(block: (UnknownInlineKeyboardButton) -> T) = asUnknownInlineKeyboardButton() ?.let(block) @@ -3191,51 +3201,113 @@ inline fun ChatEvent.asSupergroupEvent(): SupergroupEvent? = this as? Supergroup inline fun ChatEvent.requireSupergroupEvent(): SupergroupEvent = this as SupergroupEvent @PreviewFeature -inline fun ChatEvent.whenVoiceChatEvent(block: (VoiceChatEvent) -> T) = asVoiceChatEvent() ?.let(block) +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.whenVoiceChatEvent(block: (VideoChatEvent) -> T) = asVoiceChatEvent() ?.let(block) @PreviewFeature -inline fun ChatEvent.asVoiceChatEvent(): VoiceChatEvent? = this as? VoiceChatEvent +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.asVoiceChatEvent(): VideoChatEvent? = this as? VideoChatEvent @PreviewFeature -inline fun ChatEvent.requireVoiceChatEvent(): VoiceChatEvent = this as VoiceChatEvent +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.requireVoiceChatEvent(): VideoChatEvent = this as VideoChatEvent @PreviewFeature -inline fun ChatEvent.whenVoiceChatEnded(block: (VoiceChatEnded) -> T) = asVoiceChatEnded() ?.let(block) +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.whenVoiceChatEnded(block: (VideoChatEnded) -> T) = asVoiceChatEnded() ?.let(block) @PreviewFeature -inline fun ChatEvent.asVoiceChatEnded(): VoiceChatEnded? = this as? VoiceChatEnded +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.asVoiceChatEnded(): VideoChatEnded? = this as? VideoChatEnded @PreviewFeature -inline fun ChatEvent.requireVoiceChatEnded(): VoiceChatEnded = this as VoiceChatEnded +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.requireVoiceChatEnded(): VideoChatEnded = this as VideoChatEnded @PreviewFeature -inline fun ChatEvent.whenVoiceChatParticipantsInvited(block: (VoiceChatParticipantsInvited) -> T) = asVoiceChatParticipantsInvited() ?.let(block) +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.whenVoiceChatParticipantsInvited(block: (VideoChatParticipantsInvited) -> T) = asVoiceChatParticipantsInvited() ?.let(block) @PreviewFeature -inline fun ChatEvent.asVoiceChatParticipantsInvited(): VoiceChatParticipantsInvited? = - this as? VoiceChatParticipantsInvited +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.asVoiceChatParticipantsInvited(): VideoChatParticipantsInvited? = + this as? VideoChatParticipantsInvited @PreviewFeature -inline fun ChatEvent.requireVoiceChatParticipantsInvited(): VoiceChatParticipantsInvited = - this as VoiceChatParticipantsInvited +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.requireVoiceChatParticipantsInvited(): VideoChatParticipantsInvited = + this as VideoChatParticipantsInvited @PreviewFeature -inline fun ChatEvent.whenVoiceChatStarted(block: (VoiceChatStarted) -> T) = asVoiceChatStarted() ?.let(block) +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.whenVoiceChatStarted(block: (VideoChatStarted) -> T) = asVoiceChatStarted() ?.let(block) @PreviewFeature -inline fun ChatEvent.asVoiceChatStarted(): VoiceChatStarted? = this as? VoiceChatStarted +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.asVoiceChatStarted(): VideoChatStarted? = this as? VideoChatStarted @PreviewFeature -inline fun ChatEvent.requireVoiceChatStarted(): VoiceChatStarted = this as VoiceChatStarted +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.requireVoiceChatStarted(): VideoChatStarted = this as VideoChatStarted @PreviewFeature -inline fun ChatEvent.whenVoiceChatScheduled(block: (VoiceChatScheduled) -> T) = asVoiceChatScheduled() ?.let(block) +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.whenVoiceChatScheduled(block: (VideoChatScheduled) -> T) = asVoiceChatScheduled() ?.let(block) @PreviewFeature -inline fun ChatEvent.asVoiceChatScheduled(): VoiceChatScheduled? = this as? VoiceChatScheduled +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.asVoiceChatScheduled(): VideoChatScheduled? = this as? VideoChatScheduled @PreviewFeature -inline fun ChatEvent.requireVoiceChatScheduled(): VoiceChatScheduled = this as VoiceChatScheduled +@Deprecated("Renamed as Video instead of Voice") +inline fun ChatEvent.requireVoiceChatScheduled(): VideoChatScheduled = this as VideoChatScheduled + +@PreviewFeature +inline fun ChatEvent.whenVideoChatEvent(block: (VideoChatEvent) -> T) = asVideoChatEvent() ?.let(block) + +@PreviewFeature +inline fun ChatEvent.asVideoChatEvent(): VideoChatEvent? = this as? VideoChatEvent + +@PreviewFeature +inline fun ChatEvent.requireVideoChatEvent(): VideoChatEvent = this as VideoChatEvent + +@PreviewFeature +inline fun ChatEvent.whenVideoChatEnded(block: (VideoChatEnded) -> T) = asVideoChatEnded() ?.let(block) + +@PreviewFeature +inline fun ChatEvent.asVideoChatEnded(): VideoChatEnded? = this as? VideoChatEnded + +@PreviewFeature +inline fun ChatEvent.requireVideoChatEnded(): VideoChatEnded = this as VideoChatEnded + +@PreviewFeature +inline fun ChatEvent.whenVideoChatParticipantsInvited(block: (VideoChatParticipantsInvited) -> T) = asVideoChatParticipantsInvited() ?.let(block) + +@PreviewFeature +inline fun ChatEvent.asVideoChatParticipantsInvited(): VideoChatParticipantsInvited? = + this as? VideoChatParticipantsInvited + +@PreviewFeature +inline fun ChatEvent.requireVideoChatParticipantsInvited(): VideoChatParticipantsInvited = + this as VideoChatParticipantsInvited + +@PreviewFeature +inline fun ChatEvent.whenVideoChatStarted(block: (VideoChatStarted) -> T) = asVideoChatStarted() ?.let(block) + +@PreviewFeature +inline fun ChatEvent.asVideoChatStarted(): VideoChatStarted? = this as? VideoChatStarted + +@PreviewFeature +inline fun ChatEvent.requireVideoChatStarted(): VideoChatStarted = this as VideoChatStarted + +@PreviewFeature +inline fun ChatEvent.whenVideoChatScheduled(block: (VideoChatScheduled) -> T) = asVideoChatScheduled() ?.let(block) + +@PreviewFeature +inline fun ChatEvent.asVideoChatScheduled(): VideoChatScheduled? = this as? VideoChatScheduled + +@PreviewFeature +inline fun ChatEvent.requireVideoChatScheduled(): VideoChatScheduled = this as VideoChatScheduled @PreviewFeature inline fun ChatEvent.whenUserLoggedIn(block: (UserLoggedIn) -> T) = asUserLoggedIn() ?.let(block) diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/raw/Message.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/raw/Message.kt index 70cce93332..e264605390 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/raw/Message.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/raw/Message.kt @@ -177,17 +177,33 @@ inline val Message.successful_payment: SuccessfulPayment? get() = asChatEventMessage() ?.chatEvent ?.asSuccessfulPaymentEvent() ?.payment @RiskFeature(RawFieldsUsageWarning) -inline val Message.voice_chat_scheduled: VoiceChatScheduled? +@Deprecated("Renamed as video instead of voice") +inline val Message.voice_chat_scheduled: VideoChatScheduled? get() = asChatEventMessage() ?.chatEvent ?.asVoiceChatScheduled() @RiskFeature(RawFieldsUsageWarning) -inline val Message.voice_chat_started: VoiceChatStarted? +@Deprecated("Renamed as video instead of voice") +inline val Message.voice_chat_started: VideoChatStarted? get() = asChatEventMessage() ?.chatEvent ?.asVoiceChatStarted() @RiskFeature(RawFieldsUsageWarning) -inline val Message.voice_chat_ended: VoiceChatEnded? +@Deprecated("Renamed as video instead of voice") +inline val Message.voice_chat_ended: VideoChatEnded? get() = asChatEventMessage() ?.chatEvent ?.asVoiceChatEnded() @RiskFeature(RawFieldsUsageWarning) -inline val Message.voice_chat_participants_invited: VoiceChatParticipantsInvited? +@Deprecated("Renamed as video instead of voice") +inline val Message.voice_chat_participants_invited: VideoChatParticipantsInvited? get() = asChatEventMessage() ?.chatEvent ?.asVoiceChatParticipantsInvited() +@RiskFeature(RawFieldsUsageWarning) +inline val Message.video_chat_scheduled: VideoChatScheduled? + get() = asChatEventMessage() ?.chatEvent ?.asVideoChatScheduled() +@RiskFeature(RawFieldsUsageWarning) +inline val Message.video_chat_started: VideoChatStarted? + get() = asChatEventMessage() ?.chatEvent ?.asVideoChatStarted() +@RiskFeature(RawFieldsUsageWarning) +inline val Message.video_chat_ended: VideoChatEnded? + get() = asChatEventMessage() ?.chatEvent ?.asVideoChatEnded() +@RiskFeature(RawFieldsUsageWarning) +inline val Message.video_chat_participants_invited: VideoChatParticipantsInvited? + get() = asChatEventMessage() ?.chatEvent ?.asVideoChatParticipantsInvited() @RiskFeature(RawFieldsUsageWarning) inline val Message.message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged? diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/types/buttons/InlineKeyboardBuilder.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/types/buttons/InlineKeyboardBuilder.kt index 1d348269b8..24ff8e370c 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/types/buttons/InlineKeyboardBuilder.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/types/buttons/InlineKeyboardBuilder.kt @@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.extensions.utils.types.buttons import dev.inmo.tgbotapi.types.LoginURL import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.* import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup +import dev.inmo.tgbotapi.types.webapps.WebAppInfo import dev.inmo.tgbotapi.utils.MatrixBuilder import dev.inmo.tgbotapi.utils.RowBuilder @@ -128,3 +129,25 @@ inline fun InlineKeyboardRowBuilder.urlButton( text: String, url: String ) = add(URLInlineKeyboardButton(text, url)) + +/** + * Creates and put [WebAppInlineKeyboardButton]. Please, remember that this button is available in private chats only + * + * @see inlineKeyboard + * @see InlineKeyboardBuilder.row + */ +inline fun InlineKeyboardRowBuilder.webAppButton( + text: String, + webApp: WebAppInfo +) = add(WebAppInlineKeyboardButton(text, webApp)) + +/** + * Creates and put [WebAppInlineKeyboardButton]. Please, remember that this button is available in private chats only + * + * @see inlineKeyboard + * @see InlineKeyboardBuilder.row + */ +inline fun InlineKeyboardRowBuilder.webAppButton( + text: String, + url: String +) = webAppButton(text, WebAppInfo(url)) 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 ea36d5cfc4..78e86fb8d8 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,7 @@ package dev.inmo.tgbotapi.extensions.utils.types.buttons import dev.inmo.tgbotapi.types.buttons.* +import dev.inmo.tgbotapi.types.webapps.WebAppInfo import dev.inmo.tgbotapi.utils.MatrixBuilder import dev.inmo.tgbotapi.utils.RowBuilder @@ -98,3 +99,14 @@ inline fun ReplyKeyboardRowBuilder.requestPollButton( text: String, pollType: KeyboardButtonPollType ) = add(RequestPollKeyboardButton(text, pollType)) + +/** + * Creates and put [WebAppKeyboardButton] + * + * @see replyKeyboard + * @see ReplyKeyboardBuilder.row + */ +inline fun ReplyKeyboardRowBuilder.webAppButton( + text: String, + webApp: WebAppInfo +) = add(WebAppKeyboardButton(text, webApp)) diff --git a/tgbotapi.webapps/build.gradle b/tgbotapi.webapps/build.gradle new file mode 100644 index 0000000000..257fcf8f02 --- /dev/null +++ b/tgbotapi.webapps/build.gradle @@ -0,0 +1,49 @@ +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" + } +} + +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" +} + +project.version = "$library_version" +project.group = "$library_group" +project.description = "Web App bindings for the Telegram Web Apps API" + +apply from: "../publish.gradle" + +repositories { + mavenLocal() + mavenCentral() +} + +kotlin { + js(IR) { + browser() + nodejs() + } + + sourceSets { + commonMain { + dependencies { + implementation kotlin('stdlib') + api project(":tgbotapi.core") + } + } + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ColorScheme.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ColorScheme.kt new file mode 100644 index 0000000000..2b30dec7da --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ColorScheme.kt @@ -0,0 +1,6 @@ +package dev.inmo.tgbotapi.webapps + +enum class ColorScheme { + LIGHT, + DARK +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/CryptoJSExtensions.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/CryptoJSExtensions.kt new file mode 100644 index 0000000000..4e24f9e9e0 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/CryptoJSExtensions.kt @@ -0,0 +1,7 @@ +package dev.inmo.tgbotapi.webapps + +import dev.inmo.micro_utils.crypto.CryptoJs + +fun CryptoJs.HmacSHA256(text: String, key: String) = this.asDynamic().HmacSHA256(text, key).unsafeCast() + +fun CryptoJs.hex(text: String) = this.asDynamic().format.Hex(text).unsafeCast() diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventHandler.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventHandler.kt new file mode 100644 index 0000000000..4d4e82a125 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventHandler.kt @@ -0,0 +1,4 @@ +package dev.inmo.tgbotapi.webapps + +typealias EventHandler = WebApp.() -> Unit +typealias ViewportChangedEventHandler = WebApp.(ViewportChangedData) -> Unit diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventType.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventType.kt new file mode 100644 index 0000000000..1771966db3 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/EventType.kt @@ -0,0 +1,7 @@ +package dev.inmo.tgbotapi.webapps + +sealed class EventType(val typeName: String) { + object ThemeChanged : EventType("themeChanged") + object ViewportChanged : EventType("viewportChanged") + object MainButtonClicked : EventType("mainButtonClicked") +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/MainButton.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/MainButton.kt new file mode 100644 index 0000000000..3fabb5a2e7 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/MainButton.kt @@ -0,0 +1,55 @@ +package dev.inmo.tgbotapi.webapps + +import kotlin.js.Json +import kotlin.js.json + +external class MainButton { + val text: String + fun setText(text: String): MainButton + + var color: String + var textColor: String + + val isVisible: Boolean + fun show(): MainButton + fun hide(): MainButton + + val isActive: Boolean + fun enable(): MainButton + fun disable(): MainButton + + val isProgressVisible: Boolean + fun showProgress(leaveActive: Boolean = definedExternally): MainButton + fun hideProgress(): MainButton + + internal fun onClick(eventHandler: () -> Unit): MainButton + + internal fun setParams(params: Json): MainButton +} + +data class MainButtonParams( + val text: String? = null, + val color: String? = null, + val textColor: String? = null, + val isActive: Boolean? = null, + val isVisible: Boolean? = null +) + +fun MainButton.onClick(eventHandler: EventHandler) = onClick { + val that = js("this").unsafeCast() + that.eventHandler() +} + +fun MainButton.setParams(params: MainButtonParams) = setParams( + json( + *listOfNotNull( + params.text ?.let { "text" to params.text }, + params.color ?.let { "color" to params.color }, + params.textColor ?.let { "text_color" to params.textColor }, + params.isActive ?.let { "is_active" to params.isActive }, + params.isVisible ?.let { "is_visible" to params.isVisible }, + ).toTypedArray() + ) +) + + diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/Telegram.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/Telegram.kt new file mode 100644 index 0000000000..aaca486fe6 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/Telegram.kt @@ -0,0 +1,17 @@ +package dev.inmo.tgbotapi.webapps + +import kotlinx.browser.window +import org.w3c.dom.Window + +external interface Telegram { + val WebApp: WebApp +} + +val Window.Telegram + get() = asDynamic().Telegram.unsafeCast() + +val telegram: Telegram + get() = window.Telegram + +val webApp: WebApp + get() = telegram.WebApp diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ThemeParams.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ThemeParams.kt new file mode 100644 index 0000000000..a8db758c55 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ThemeParams.kt @@ -0,0 +1,16 @@ +package dev.inmo.tgbotapi.webapps + +external interface ThemeParams { + @JsName("bg_color") + val backgroundColor: String? + @JsName("text_color") + val textColor: String? + @JsName("hint_color") + val hintColor: String? + @JsName("link_color") + val linkColor: String? + @JsName("button_color") + val buttonColor: String? + @JsName("button_text_color") + val buttonTextColor: String? +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ViewportChangedData.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ViewportChangedData.kt new file mode 100644 index 0000000000..6f4dd3054a --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/ViewportChangedData.kt @@ -0,0 +1,5 @@ +package dev.inmo.tgbotapi.webapps + +external interface ViewportChangedData { + val isStateStable: Boolean +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebApp.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebApp.kt new file mode 100644 index 0000000000..5422c340c2 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebApp.kt @@ -0,0 +1,81 @@ +package dev.inmo.tgbotapi.webapps + +import dev.inmo.micro_utils.crypto.CryptoJS + +external class WebApp { + val initData: String + val initDataUnsafe: WebAppInitData + + @JsName("colorScheme") + val colorSchemeRaw: String + val themeParams: ThemeParams + + val isExpanded: Boolean + val viewportHeight: Float + val viewportStableHeight: Float + + @JsName("MainButton") + val mainButton: MainButton + + internal fun onEvent(type: String, callback: () -> Unit) + @JsName("onEvent") + internal fun onEventWithBoolean(type: String, callback: (ViewportChangedData) -> Unit) + + fun offEvent(type: String, callback: () -> Unit) + @JsName("offEvent") + fun offEventWithBoolean(type: String, callback: (ViewportChangedData) -> Unit) + + fun sendData(data: String) + + fun ready() + fun expand() + fun close() +} + +val WebApp.colorScheme: ColorScheme + get() = when (colorSchemeRaw) { + "light" -> ColorScheme.LIGHT + "dark" -> ColorScheme.DARK + else -> ColorScheme.LIGHT + } + +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onEvent(type: EventType, eventHandler: EventHandler) = { + eventHandler(js("this").unsafeCast()) +}.also { + onEvent( + type.typeName, + callback = it + ) +} + +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onEvent(type: EventType.ViewportChanged, eventHandler: ViewportChangedEventHandler) = { it: ViewportChangedData -> + eventHandler(js("this").unsafeCast(), it) +}.also { + onEventWithBoolean( + type.typeName, + callback = it + ) +} + +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onThemeChanged(eventHandler: EventHandler) = onEvent(EventType.ThemeChanged, eventHandler) +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onMainButtonClicked(eventHandler: EventHandler) = onEvent(EventType.MainButtonClicked, eventHandler) +/** + * @return The callback which should be used in case you want to turn off events handling + */ +fun WebApp.onViewportChanged(eventHandler: ViewportChangedEventHandler) = onEvent(EventType.ViewportChanged, eventHandler) + +fun WebApp.isInitDataSafe(botToken: String) = CryptoJS.hex( + CryptoJS.HmacSHA256(botToken, "WebAppData") +) == initDataUnsafe.hash diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppInitData.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppInitData.kt new file mode 100644 index 0000000000..a00181ab91 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppInitData.kt @@ -0,0 +1,19 @@ +package dev.inmo.tgbotapi.webapps + +import dev.inmo.tgbotapi.types.MilliSeconds +import dev.inmo.tgbotapi.types.WebAppQueryId + +external interface WebAppInitData { + val queryId: WebAppQueryId? + + val user: WebAppUser? + val receiver: WebAppUser? + + @JsName("start_param") + val startParam: String? + + @JsName("auth_date") + val authDate: MilliSeconds + + val hash: String +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppUser.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppUser.kt new file mode 100644 index 0000000000..a192b422ff --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/WebAppUser.kt @@ -0,0 +1,37 @@ +package dev.inmo.tgbotapi.webapps + +import dev.inmo.micro_utils.language_codes.IetfLanguageCode +import dev.inmo.tgbotapi.types.* + +external interface WebAppUser { + val id: Identifier + @JsName(isBotField) + val isBot: Boolean? + @JsName(firstNameField) + val firstName: String + @JsName(lastNameField) + val lastName: String? + @JsName(usernameField) + val username: String? + @JsName(languageCodeField) + val languageCode: String? + @JsName(photoUrlField) + val photoUrl: String? +} + +fun WebAppUser.asUser() = if (isBot == true) { + CommonBot( + UserId(id), + username ?.let(::Username) ?: error("Username is absent for bot, but must exists"), + firstName, + lastName ?: "" + ) +} else { + CommonUser( + UserId(id), + firstName, + lastName ?: "", + username ?.let(::Username), + languageCode ?.let(::IetfLanguageCode) + ) +}