From d97c2a0562a73a2494dfa7af805ca25330f95000 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 23 Sep 2023 01:51:04 +0600 Subject: [PATCH 1/7] start migration onto 9.2.0 --- WebApp/src/jsMain/kotlin/main.kt | 32 ++++++++++++++++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/WebApp/src/jsMain/kotlin/main.kt b/WebApp/src/jsMain/kotlin/main.kt index e7af166..0088ade 100644 --- a/WebApp/src/jsMain/kotlin/main.kt +++ b/WebApp/src/jsMain/kotlin/main.kt @@ -110,6 +110,32 @@ fun main() { appendText("Alert") } ?: window.alert("Unable to load body") + document.body ?.appendElement("p", {}) + + document.body ?.appendElement("button") { + addEventListener("click", { webApp.requestWriteAccess() }) + appendText("Request write access without callback") + } ?: window.alert("Unable to load body") + + document.body ?.appendElement("button") { + addEventListener("click", { webApp.requestWriteAccess { document.body ?.log("Write access request result: $it") } }) + appendText("Request write access with callback") + } ?: window.alert("Unable to load body") + + document.body ?.appendElement("p", {}) + + document.body ?.appendElement("button") { + addEventListener("click", { webApp.requestContact() }) + appendText("Request contact without callback") + } ?: window.alert("Unable to load body") + + document.body ?.appendElement("button") { + addEventListener("click", { webApp.requestContact { document.body ?.log("Contact request result: $it") } }) + appendText("Request contact with callback") + } ?: window.alert("Unable to load body") + + document.body ?.appendElement("p", {}) + document.body ?.appendElement("button") { addEventListener("click", { webApp.showConfirm( @@ -171,6 +197,12 @@ fun main() { onSettingsButtonClicked { document.body ?.log("Settings button clicked") } + onWriteAccessRequested { + document.body ?.log("Write access request result: $it") + } + onContactRequested { + document.body ?.log("Contact request result: $it") + } } webApp.ready() }.onFailure { diff --git a/gradle.properties b/gradle.properties index 1141fbd..c0bea7a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.jvmargs=-Xmx2g kotlin_version=1.8.22 -telegram_bot_api_version=9.1.2 +telegram_bot_api_version=9.2.0 micro_utils_version=0.19.9 serialization_version=1.5.1 ktor_version=2.3.3 From 676ce0df8009d3caea843262e0f3cbf3aeba2f44 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 25 Sep 2023 00:19:34 +0600 Subject: [PATCH 2/7] start adding channels rights changer --- RightsChangerBot/build.gradle | 1 + .../src/main/kotlin/RightsChanger.kt | 337 +++++++++++++++--- 2 files changed, 290 insertions(+), 48 deletions(-) diff --git a/RightsChangerBot/build.gradle b/RightsChangerBot/build.gradle index 1a34bb6..0d022c7 100644 --- a/RightsChangerBot/build.gradle +++ b/RightsChangerBot/build.gradle @@ -18,4 +18,5 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "dev.inmo:tgbotapi:$telegram_bot_api_version" + implementation 'io.ktor:ktor-client-logging-jvm:2.3.3' } diff --git a/RightsChangerBot/src/main/kotlin/RightsChanger.kt b/RightsChangerBot/src/main/kotlin/RightsChanger.kt index 859f81e..6d8e8ad 100644 --- a/RightsChangerBot/src/main/kotlin/RightsChanger.kt +++ b/RightsChangerBot/src/main/kotlin/RightsChanger.kt @@ -1,34 +1,52 @@ -import dev.inmo.micro_utils.coroutines.runCatchingSafely +import dev.inmo.micro_utils.coroutines.firstOf +import dev.inmo.micro_utils.fsm.common.State import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands import dev.inmo.tgbotapi.extensions.api.chat.get.getChat import dev.inmo.tgbotapi.extensions.api.chat.members.getChatMember +import dev.inmo.tgbotapi.extensions.api.chat.members.promoteChannelAdministrator import dev.inmo.tgbotapi.extensions.api.chat.members.restrictChatMember import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.send.reply +import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext -import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling +import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithFSMAndStartLongPolling +import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.* import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery -import dev.inmo.tgbotapi.extensions.utils.asContentMessage -import dev.inmo.tgbotapi.extensions.utils.asPossiblyReplyMessage -import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull -import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull -import dev.inmo.tgbotapi.extensions.utils.extendedGroupChatOrNull -import dev.inmo.tgbotapi.extensions.utils.fromUserMessageOrNull -import dev.inmo.tgbotapi.extensions.utils.restrictedChatMemberOrNull -import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton -import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard -import dev.inmo.tgbotapi.extensions.utils.whenMemberChatMember -import dev.inmo.tgbotapi.types.BotCommand -import dev.inmo.tgbotapi.types.ChatId -import dev.inmo.tgbotapi.types.UserId +import dev.inmo.tgbotapi.extensions.utils.* +import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat +import dev.inmo.tgbotapi.extensions.utils.types.buttons.* +import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup +import dev.inmo.tgbotapi.types.chat.ChannelChat import dev.inmo.tgbotapi.types.chat.ChatPermissions import dev.inmo.tgbotapi.types.chat.PublicChat +import dev.inmo.tgbotapi.types.chat.member.* import dev.inmo.tgbotapi.types.commands.BotCommandScope -import dev.inmo.tgbotapi.types.toChatId +import dev.inmo.tgbotapi.types.request.RequestId +import dev.inmo.tgbotapi.utils.botCommand +import dev.inmo.tgbotapi.utils.mention +import dev.inmo.tgbotapi.utils.regular import dev.inmo.tgbotapi.utils.row +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.mapNotNull + +sealed interface UserRetrievingStep : State { + data class RetrievingChannelChatState( + override val context: ChatId + ) : UserRetrievingStep + data class RetrievingUserIdChatState( + override val context: ChatId, + val channelId: ChatId + ) : UserRetrievingStep + data class RetrievingChatInfoDoneState( + override val context: ChatId, + val channelId: ChatId, + val userId: UserId + ) : UserRetrievingStep +} suspend fun main(args: Array) { val botToken = args.first() @@ -60,20 +78,30 @@ suspend fun main(args: Array) { val otherMessagesToggleCommonData = "$commonDataPrefix other messages" val webPagePreviewToggleCommonData = "$commonDataPrefix web page preview" + val adminRightsDataPrefix = "admin" + val postMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_post_messages" + val editMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_edit_messages" + val deleteMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_delete_messages" + val editStoriesToggleAdminRightsData = "${adminRightsDataPrefix}_edit_stories" + val deleteStoriesToggleAdminRightsData = "${adminRightsDataPrefix}_delete_stories" + val postStoriesToggleAdminRightsData = "${adminRightsDataPrefix}_post_stories" + suspend fun BehaviourContext.getUserChatPermissions(chatId: ChatId, userId: UserId): ChatPermissions? { val chatMember = getChatMember(chatId, userId) return chatMember.restrictedChatMemberOrNull() ?: chatMember.whenMemberChatMember { getChat(chatId).extendedGroupChatOrNull() ?.permissions } } - - suspend fun BehaviourContext.buildGranularKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? { - val permissions = getUserChatPermissions(chatId, userId) ?: return null - + fun buildGranularKeyboard( + permissions: ChatPermissions + ): InlineKeyboardMarkup { return inlineKeyboard { row { dataButton("Send messages${permissions.canSendMessages.allowedSymbol()}", messagesToggleGranularData) - dataButton("Send other messages${permissions.canSendOtherMessages.allowedSymbol()}", otherMessagesToggleGranularData) + dataButton( + "Send other messages${permissions.canSendOtherMessages.allowedSymbol()}", + otherMessagesToggleGranularData + ) } row { dataButton("Send audios${permissions.canSendAudios.allowedSymbol()}", audiosToggleGranularData) @@ -81,11 +109,17 @@ suspend fun main(args: Array) { } row { dataButton("Send videos${permissions.canSendVideos.allowedSymbol()}", videosToggleGranularData) - dataButton("Send video notes${permissions.canSendVideoNotes.allowedSymbol()}", videoNotesToggleGranularData) + dataButton( + "Send video notes${permissions.canSendVideoNotes.allowedSymbol()}", + videoNotesToggleGranularData + ) } row { dataButton("Send photos${permissions.canSendPhotos.allowedSymbol()}", photosToggleGranularData) - dataButton("Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}", webPagePreviewToggleGranularData) + dataButton( + "Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}", + webPagePreviewToggleGranularData + ) } row { dataButton("Send polls${permissions.canSendPolls.allowedSymbol()}", pollsToggleGranularData) @@ -93,6 +127,34 @@ suspend fun main(args: Array) { } } } + fun buildAdminRightsKeyboard( + permissions: AdministratorChatMember, + channelId: ChatId, + userId: UserId + ): InlineKeyboardMarkup { + return inlineKeyboard { + row { + dataButton("Edit messages${permissions.canEditMessages.allowedSymbol()}", "$editMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + dataButton("Delete messages${permissions.canRemoveMessages.allowedSymbol()}", "$deleteMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + } + row { + dataButton("Post messages${permissions.canPostMessages.allowedSymbol()}", "$postMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + } + row { + dataButton("Edit stories${permissions.canEditStories.allowedSymbol()}", "$editStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + dataButton("Delete stories${permissions.canDeleteStories.allowedSymbol()}", "$deleteStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + } + row { + dataButton("Post stories${permissions.canPostStories.allowedSymbol()}", "$postStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + } + } + } + + suspend fun BehaviourContext.buildGranularKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? { + return buildGranularKeyboard( + getUserChatPermissions(chatId, userId) ?: return null + ) + } suspend fun BehaviourContext.buildCommonKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? { val permissions = getUserChatPermissions(chatId, userId) ?: return null @@ -110,23 +172,32 @@ suspend fun main(args: Array) { } } - bot.buildBehaviourWithLongPolling( + bot.buildBehaviourWithFSMAndStartLongPolling( defaultExceptionsHandler = { it.printStackTrace() - } + }, ) { - onCommand("simple", initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull() ?.user ?.id == allowedAdmin }) { + onCommand( + "simple", + initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull()?.user?.id == allowedAdmin }) { val replyMessage = it.replyTo - val userInReply = replyMessage ?.fromUserMessageOrNull() ?.user ?.id ?: return@onCommand + val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand reply( replyMessage, "Manage keyboard:", replyMarkup = buildCommonKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand ) } - onCommand("granular", initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull() ?.user ?.id == allowedAdmin }) { + onCommand( + "granular", + initialFilter = { + it.chat is ChannelChat || (it.chat is PublicChat && it.fromUserMessageOrNull()?.user?.id == allowedAdmin) + } + ) { val replyMessage = it.replyTo - val userInReply = replyMessage ?.fromUserMessageOrNull() ?.user ?.id ?: return@onCommand + val usernameInText = it.content.textSources.firstNotNullOfOrNull { it.mentionTextSourceOrNull() } ?.username + val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand + reply( replyMessage, "Manage keyboard:", @@ -138,60 +209,72 @@ suspend fun main(args: Array) { Regex("^${granularDataPrefix}.*"), initialFilter = { it.user.id == allowedAdmin } ) { - val messageReply = it.message.commonMessageOrNull() ?.replyTo ?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery + val messageReply = + it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery val userId = messageReply.user.id - val permissions = getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery + val permissions = + getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery val newPermission = when (it.data) { messagesToggleGranularData -> { permissions.copyGranular( - canSendMessages = permissions.canSendMessages ?.let { !it } ?: false + canSendMessages = permissions.canSendMessages?.let { !it } ?: false ) } + otherMessagesToggleGranularData -> { permissions.copyGranular( - canSendOtherMessages = permissions.canSendOtherMessages ?.let { !it } ?: false + canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false ) } + audiosToggleGranularData -> { permissions.copyGranular( - canSendAudios = permissions.canSendAudios ?.let { !it } ?: false + canSendAudios = permissions.canSendAudios?.let { !it } ?: false ) } + voicesToggleGranularData -> { permissions.copyGranular( - canSendVoiceNotes = permissions.canSendVoiceNotes ?.let { !it } ?: false + canSendVoiceNotes = permissions.canSendVoiceNotes?.let { !it } ?: false ) } + videosToggleGranularData -> { permissions.copyGranular( - canSendVideos = permissions.canSendVideos ?.let { !it } ?: false + canSendVideos = permissions.canSendVideos?.let { !it } ?: false ) } + videoNotesToggleGranularData -> { permissions.copyGranular( - canSendVideoNotes = permissions.canSendVideoNotes ?.let { !it } ?: false + canSendVideoNotes = permissions.canSendVideoNotes?.let { !it } ?: false ) } + photosToggleGranularData -> { permissions.copyGranular( - canSendPhotos = permissions.canSendPhotos ?.let { !it } ?: false + canSendPhotos = permissions.canSendPhotos?.let { !it } ?: false ) } + webPagePreviewToggleGranularData -> { permissions.copyGranular( - canAddWebPagePreviews = permissions.canAddWebPagePreviews ?.let { !it } ?: false + canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false ) } + pollsToggleGranularData -> { permissions.copyGranular( - canSendPolls = permissions.canSendPolls ?.let { !it } ?: false + canSendPolls = permissions.canSendPolls?.let { !it } ?: false ) } + documentsToggleGranularData -> { permissions.copyGranular( - canSendDocuments = permissions.canSendDocuments ?.let { !it } ?: false + canSendDocuments = permissions.canSendDocuments?.let { !it } ?: false ) } + else -> permissions.copyGranular() } @@ -204,7 +287,8 @@ suspend fun main(args: Array) { edit( it.message, - replyMarkup = buildGranularKeyboard(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery + replyMarkup = buildGranularKeyboard(it.message.chat.id.toChatId(), userId) + ?: return@onMessageDataCallbackQuery ) } @@ -212,25 +296,30 @@ suspend fun main(args: Array) { Regex("^${commonDataPrefix}.*"), initialFilter = { it.user.id == allowedAdmin } ) { - val messageReply = it.message.commonMessageOrNull() ?.replyTo ?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery + val messageReply = + it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery val userId = messageReply.user.id - val permissions = getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery + val permissions = + getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery val newPermission = when (it.data) { pollsToggleCommonData -> { permissions.copyCommon( - canSendPolls = permissions.canSendPolls ?.let { !it } ?: false + canSendPolls = permissions.canSendPolls?.let { !it } ?: false ) } + otherMessagesToggleCommonData -> { permissions.copyCommon( - canSendOtherMessages = permissions.canSendOtherMessages ?.let { !it } ?: false + canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false ) } + webPagePreviewToggleCommonData -> { permissions.copyCommon( - canAddWebPagePreviews = permissions.canAddWebPagePreviews ?.let { !it } ?: false + canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false ) } + else -> permissions.copyCommon() } @@ -243,13 +332,165 @@ suspend fun main(args: Array) { edit( it.message, - replyMarkup = buildCommonKeyboard(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery + replyMarkup = buildCommonKeyboard(it.message.chat.id.toChatId(), userId) + ?: return@onMessageDataCallbackQuery ) } + onMessageDataCallbackQuery( + Regex("^${adminRightsDataPrefix}.*"), + initialFilter = { it.user.id == allowedAdmin } + ) { + val (channelIdString, userIdString) = it.data.split(" ").drop(1) + val channelId = ChatId(channelIdString.toLong()) + val userId = ChatId(userIdString.toLong()) + val chatMember = getChatMember(channelId, userId).administratorChatMemberOrNull() ?: return@onMessageDataCallbackQuery + + val realData = it.data.takeWhile { it != ' ' } + + fun Boolean.toggleIfData(data: String) = if (realData == data) { + !this + } else { + null + } + val chat = getChat(userId) + + promoteChannelAdministrator( + channelId, + userId, + canPostMessages = chatMember.canPostMessages.toggleIfData(postMessagesToggleAdminRightsData), + canEditMessages = chatMember.canEditMessages.toggleIfData(editMessagesToggleAdminRightsData), + canDeleteMessages = chatMember.canRemoveMessages.toggleIfData(deleteMessagesToggleAdminRightsData), + canEditStories = chatMember.canEditStories.toggleIfData(editStoriesToggleAdminRightsData), + canDeleteStories = chatMember.canDeleteStories.toggleIfData(deleteStoriesToggleAdminRightsData), + canPostStories = chatMember.canPostStories.toggleIfData(postStoriesToggleAdminRightsData), + ) + + edit( + it.message, + replyMarkup = buildAdminRightsKeyboard( + getChatMember( + channelId, + userId + ).administratorChatMemberOrNull() ?: return@onMessageDataCallbackQuery, + channelId, + userId + ) + ) + } + + strictlyOn { state -> + val requestId = RequestId.random() + send( + state.context, + replyMarkup = replyKeyboard( + oneTimeKeyboard = true, + resizeKeyboard = true + ) { + row { + requestChatButton( + "Choose channel", + requestId = requestId, + isChannel = true, + botIsMember = true, + botRightsInChat = ChatCommonAdministratorRights( + canPromoteMembers = true, + canRestrictMembers = true + ), + userRightsInChat = ChatCommonAdministratorRights( + canPromoteMembers = true, + canRestrictMembers = true + ) + ) + } + } + ) { + regular("Ok, send me the channel in which you wish to manage user, or use ") + botCommand("cancel") + regular(" to cancel the request") + } + firstOf { + include { + val chatId = waitChatSharedEventsMessages().mapNotNull { + it.chatEvent.chatId.takeIf { _ -> + it.chatEvent.requestId == requestId && it.sameChat(state.context) + } + }.first() + UserRetrievingStep.RetrievingUserIdChatState(state.context, chatId) + } + include { + waitCommandMessage("cancel").filter { it.sameChat(state.context) }.first() + null + } + } + } + strictlyOn { state -> + val requestId = RequestId.random() + send( + state.context, + replyMarkup = replyKeyboard( + oneTimeKeyboard = true, + resizeKeyboard = true + ) { + row { + requestUserButton( + "Choose user", + requestId = requestId + ) + } + } + ) { + regular("Ok, send me the user for which you wish to change rights, or use ") + botCommand("cancel") + regular(" to cancel the request") + } + + firstOf { + include { + val userContactChatId = waitUserSharedEventsMessages().filter { + it.sameChat(state.context) + }.first().chatEvent.chatId + UserRetrievingStep.RetrievingChatInfoDoneState( + state.context, + state.channelId, + userContactChatId + ) + } + include { + waitCommandMessage("cancel").filter { it.sameChat(state.context) }.first() + null + } + } + } + + strictlyOn { state -> + val chatMember = getChatMember(state.channelId, state.userId).administratorChatMemberOrNull() + if (chatMember == null) { + + return@strictlyOn null + } + send( + state.context, + replyMarkup = buildAdminRightsKeyboard( + chatMember, + state.channelId, + state.userId + ) + ) { + regular("Rights of ") + mention(chatMember.user) + } + null + } + + onCommand("rights_in_channel") { + startChain(UserRetrievingStep.RetrievingChannelChatState(it.chat.id.toChatId())) + } + setMyCommands( BotCommand("simple", "Trigger simple keyboard. Use with reply to user"), BotCommand("granular", "Trigger granular keyboard. Use with reply to user"), + BotCommand("rights_in_channel", "Trigger granular keyboard. Use with reply to user"), scope = BotCommandScope.AllGroupChats ) }.join() From 6922a6d667ccc24206fb16c72397d127546ea3dd Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 25 Sep 2023 14:55:37 +0600 Subject: [PATCH 3/7] fixes in rights bot --- .../src/main/kotlin/RightsChanger.kt | 76 +++++++++++-------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/RightsChangerBot/src/main/kotlin/RightsChanger.kt b/RightsChangerBot/src/main/kotlin/RightsChanger.kt index 6d8e8ad..5245696 100644 --- a/RightsChangerBot/src/main/kotlin/RightsChanger.kt +++ b/RightsChangerBot/src/main/kotlin/RightsChanger.kt @@ -25,10 +25,8 @@ import dev.inmo.tgbotapi.types.chat.PublicChat import dev.inmo.tgbotapi.types.chat.member.* import dev.inmo.tgbotapi.types.commands.BotCommandScope import dev.inmo.tgbotapi.types.request.RequestId -import dev.inmo.tgbotapi.utils.botCommand +import dev.inmo.tgbotapi.utils.* import dev.inmo.tgbotapi.utils.mention -import dev.inmo.tgbotapi.utils.regular -import dev.inmo.tgbotapi.utils.row import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.mapNotNull @@ -79,6 +77,7 @@ suspend fun main(args: Array) { val webPagePreviewToggleCommonData = "$commonDataPrefix web page preview" val adminRightsDataPrefix = "admin" + val refreshAdminRightsData = "${adminRightsDataPrefix}_refresh" val postMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_post_messages" val editMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_edit_messages" val deleteMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_delete_messages" @@ -128,24 +127,31 @@ suspend fun main(args: Array) { } } fun buildAdminRightsKeyboard( - permissions: AdministratorChatMember, + permissions: AdministratorChatMember?, channelId: ChatId, userId: UserId ): InlineKeyboardMarkup { return inlineKeyboard { - row { - dataButton("Edit messages${permissions.canEditMessages.allowedSymbol()}", "$editMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") - dataButton("Delete messages${permissions.canRemoveMessages.allowedSymbol()}", "$deleteMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") - } - row { - dataButton("Post messages${permissions.canPostMessages.allowedSymbol()}", "$postMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") - } - row { - dataButton("Edit stories${permissions.canEditStories.allowedSymbol()}", "$editStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") - dataButton("Delete stories${permissions.canDeleteStories.allowedSymbol()}", "$deleteStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") - } - row { - dataButton("Post stories${permissions.canPostStories.allowedSymbol()}", "$postStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + permissions ?.also { + row { + dataButton("Refresh", "$refreshAdminRightsData ${channelId.chatId} ${userId.chatId}") + } + row { + dataButton("Edit messages${permissions.canEditMessages.allowedSymbol()}", "$editMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + dataButton("Delete messages${permissions.canRemoveMessages.allowedSymbol()}", "$deleteMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + } + row { + dataButton("Post messages${permissions.canPostMessages.allowedSymbol()}", "$postMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + } + row { + dataButton("Edit stories${permissions.canEditStories.allowedSymbol()}", "$editStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + dataButton("Delete stories${permissions.canDeleteStories.allowedSymbol()}", "$deleteStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + } + row { + dataButton("Post stories${permissions.canPostStories.allowedSymbol()}", "$postStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") + } + } ?: row { + dataButton("Promote to admin", "$postMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}") } } } @@ -344,27 +350,30 @@ suspend fun main(args: Array) { val (channelIdString, userIdString) = it.data.split(" ").drop(1) val channelId = ChatId(channelIdString.toLong()) val userId = ChatId(userIdString.toLong()) - val chatMember = getChatMember(channelId, userId).administratorChatMemberOrNull() ?: return@onMessageDataCallbackQuery + val chatMember = getChatMember(channelId, userId) + val asAdmin = chatMember.administratorChatMemberOrNull() + val asMember = chatMember.memberChatMemberOrNull() val realData = it.data.takeWhile { it != ' ' } - fun Boolean.toggleIfData(data: String) = if (realData == data) { - !this + fun Boolean?.toggleIfData(data: String) = if (realData == data) { + !(this ?: false) } else { null } - val chat = getChat(userId) - promoteChannelAdministrator( - channelId, - userId, - canPostMessages = chatMember.canPostMessages.toggleIfData(postMessagesToggleAdminRightsData), - canEditMessages = chatMember.canEditMessages.toggleIfData(editMessagesToggleAdminRightsData), - canDeleteMessages = chatMember.canRemoveMessages.toggleIfData(deleteMessagesToggleAdminRightsData), - canEditStories = chatMember.canEditStories.toggleIfData(editStoriesToggleAdminRightsData), - canDeleteStories = chatMember.canDeleteStories.toggleIfData(deleteStoriesToggleAdminRightsData), - canPostStories = chatMember.canPostStories.toggleIfData(postStoriesToggleAdminRightsData), - ) + if (realData != refreshAdminRightsData) { + promoteChannelAdministrator( + channelId, + userId, + canPostMessages = asAdmin ?.canPostMessages.toggleIfData(postMessagesToggleAdminRightsData), + canEditMessages = asAdmin ?.canEditMessages.toggleIfData(editMessagesToggleAdminRightsData), + canDeleteMessages = asAdmin ?.canRemoveMessages.toggleIfData(deleteMessagesToggleAdminRightsData), + canEditStories = asAdmin ?.canEditStories.toggleIfData(editStoriesToggleAdminRightsData), + canDeleteStories = asAdmin ?.canDeleteStories.toggleIfData(deleteStoriesToggleAdminRightsData), + canPostStories = asAdmin ?.canPostStories.toggleIfData(postStoriesToggleAdminRightsData), + ) + } edit( it.message, @@ -372,7 +381,7 @@ suspend fun main(args: Array) { getChatMember( channelId, userId - ).administratorChatMemberOrNull() ?: return@onMessageDataCallbackQuery, + ).administratorChatMemberOrNull(), channelId, userId ) @@ -478,7 +487,8 @@ suspend fun main(args: Array) { ) ) { regular("Rights of ") - mention(chatMember.user) + mentionln(chatMember.user) + regular("Please, remember, that to be able to change user rights bot must promote user by itself to admin") } null } From 48d1077ce4d8a49d2a7895d3b8ab69ba846009d4 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 25 Sep 2023 16:20:25 +0600 Subject: [PATCH 4/7] update webapp --- WebApp/src/jsMain/kotlin/main.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/WebApp/src/jsMain/kotlin/main.kt b/WebApp/src/jsMain/kotlin/main.kt index 0088ade..6dc5a96 100644 --- a/WebApp/src/jsMain/kotlin/main.kt +++ b/WebApp/src/jsMain/kotlin/main.kt @@ -15,7 +15,10 @@ import kotlinx.coroutines.* import kotlinx.dom.appendElement import kotlinx.dom.appendText import kotlinx.serialization.json.Json +import org.w3c.dom.HTMLButtonElement import org.w3c.dom.HTMLElement +import kotlin.random.Random +import kotlin.random.nextUBytes fun HTMLElement.log(text: String) { appendText(text) @@ -70,6 +73,9 @@ fun main() { appendText("Exit button") } ?: window.alert("Unable to load body") + document.body ?.appendElement("p", {}) + document.body ?.appendText("Allow to write in private messages: ${webApp.initDataUnsafe.user ?.allowsWriteToPM ?: "User unavailable"}") + document.body ?.appendElement("p", {}) document.body ?.appendText("Alerts:") @@ -166,6 +172,19 @@ fun main() { updateText() } ?: window.alert("Unable to load body") + document.body ?.appendElement("button") { + fun updateHeaderColor() { + val (r, g, b) = Random.nextUBytes(3) + val hex = Color.Hex("#${r.toString(16)}${g.toString(16)}${b.toString(16)}") + webApp.setHeaderColor(hex) + (this as? HTMLButtonElement) ?.style ?.backgroundColor = hex.value + } + addEventListener("click", { + updateHeaderColor() + }) + updateHeaderColor() + } ?: window.alert("Unable to load body") + document.body ?.appendElement("p", {}) webApp.apply { From 8f80b7e0668b1b203119822e39a06217fc3571b3 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 25 Sep 2023 16:28:49 +0600 Subject: [PATCH 5/7] small fixes and improvements --- WebApp/src/jsMain/kotlin/main.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WebApp/src/jsMain/kotlin/main.kt b/WebApp/src/jsMain/kotlin/main.kt index 6dc5a96..774ccbf 100644 --- a/WebApp/src/jsMain/kotlin/main.kt +++ b/WebApp/src/jsMain/kotlin/main.kt @@ -172,12 +172,15 @@ fun main() { updateText() } ?: window.alert("Unable to load body") + document.body ?.appendElement("p", {}) + document.body ?.appendElement("button") { fun updateHeaderColor() { val (r, g, b) = Random.nextUBytes(3) val hex = Color.Hex("#${r.toString(16)}${g.toString(16)}${b.toString(16)}") webApp.setHeaderColor(hex) (this as? HTMLButtonElement) ?.style ?.backgroundColor = hex.value + textContent = hex.value } addEventListener("click", { updateHeaderColor() From 0ad8e61c0cbc2c8464c53bc7418f2fe29e9d57a6 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 25 Sep 2023 16:30:17 +0600 Subject: [PATCH 6/7] one more improvement --- WebApp/src/jsMain/kotlin/main.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebApp/src/jsMain/kotlin/main.kt b/WebApp/src/jsMain/kotlin/main.kt index 774ccbf..1d35337 100644 --- a/WebApp/src/jsMain/kotlin/main.kt +++ b/WebApp/src/jsMain/kotlin/main.kt @@ -180,7 +180,7 @@ fun main() { val hex = Color.Hex("#${r.toString(16)}${g.toString(16)}${b.toString(16)}") webApp.setHeaderColor(hex) (this as? HTMLButtonElement) ?.style ?.backgroundColor = hex.value - textContent = hex.value + textContent = "Header color: ${hex.value.uppercase()} (click to change)" } addEventListener("click", { updateHeaderColor() From 02c3d3da1aab4dda30cb00d01c639ea1ff3a7401 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 25 Sep 2023 23:20:48 +0600 Subject: [PATCH 7/7] update webapp sample to use cloud storage --- WebApp/src/jsMain/kotlin/main.kt | 80 ++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/WebApp/src/jsMain/kotlin/main.kt b/WebApp/src/jsMain/kotlin/main.kt index 1d35337..c6d6bb2 100644 --- a/WebApp/src/jsMain/kotlin/main.kt +++ b/WebApp/src/jsMain/kotlin/main.kt @@ -1,6 +1,7 @@ import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions import dev.inmo.tgbotapi.types.webAppQueryIdField import dev.inmo.tgbotapi.webapps.* +import dev.inmo.tgbotapi.webapps.cloud.* import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackStyle import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackType import dev.inmo.tgbotapi.webapps.popup.* @@ -14,9 +15,9 @@ import kotlinx.browser.window import kotlinx.coroutines.* import kotlinx.dom.appendElement import kotlinx.dom.appendText +import kotlinx.dom.clear import kotlinx.serialization.json.Json -import org.w3c.dom.HTMLButtonElement -import org.w3c.dom.HTMLElement +import org.w3c.dom.* import kotlin.random.Random import kotlin.random.nextUBytes @@ -177,7 +178,7 @@ fun main() { document.body ?.appendElement("button") { fun updateHeaderColor() { val (r, g, b) = Random.nextUBytes(3) - val hex = Color.Hex("#${r.toString(16)}${g.toString(16)}${b.toString(16)}") + val hex = Color.Hex(r, g, b) webApp.setHeaderColor(hex) (this as? HTMLButtonElement) ?.style ?.backgroundColor = hex.value textContent = "Header color: ${hex.value.uppercase()} (click to change)" @@ -190,6 +191,75 @@ fun main() { document.body ?.appendElement("p", {}) + fun Element.updateCloudStorageContent() { + clear() + webApp.cloudStorage.getAll { + it.onSuccess { + document.body ?.log(it.toString()) + appendElement("label") { textContent = "Cloud storage" } + + appendElement("p", {}) + + it.forEach { (k, v) -> + appendElement("div") { + val kInput = appendElement("input", {}) as HTMLInputElement + val vInput = appendElement("input", {}) as HTMLInputElement + + kInput.value = k.key + vInput.value = v.value + + appendElement("button") { + addEventListener("click", { + if (k.key == kInput.value) { + webApp.cloudStorage.set(k.key, vInput.value) { + document.body ?.log(it.toString()) + this@updateCloudStorageContent.updateCloudStorageContent() + } + } else { + webApp.cloudStorage.remove(k.key) { + it.onSuccess { + webApp.cloudStorage.set(kInput.value, vInput.value) { + document.body ?.log(it.toString()) + this@updateCloudStorageContent.updateCloudStorageContent() + } + } + } + } + }) + this.textContent = "Save" + } + } + + appendElement("p", {}) + } + appendElement("label") { textContent = "Cloud storage: add new" } + + appendElement("p", {}) + + appendElement("div") { + val kInput = appendElement("input", {}) as HTMLInputElement + + appendElement("button") { + textContent = "Add key" + addEventListener("click", { + webApp.cloudStorage.set(kInput.value, kInput.value) { + document.body ?.log(it.toString()) + this@updateCloudStorageContent.updateCloudStorageContent() + } + }) + } + } + + appendElement("p", {}) + }.onFailure { + document.body ?.log(it.stackTraceToString()) + } + } + } + val cloudStorageContentDiv = document.body ?.appendElement("div") {} as HTMLDivElement + + document.body ?.appendElement("p", {}) + webApp.apply { onThemeChanged { document.body ?.log("Theme changed: ${webApp.themeParams}") @@ -227,6 +297,10 @@ fun main() { } } webApp.ready() + document.body ?.appendElement("input", { + (this as HTMLInputElement).value = window.location.href + }) + cloudStorageContentDiv.updateCloudStorageContent() }.onFailure { window.alert(it.stackTraceToString()) }