From 676ce0df8009d3caea843262e0f3cbf3aeba2f44 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 25 Sep 2023 00:19:34 +0600 Subject: [PATCH] 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()