From 2a883c25ca2c040867741370295d890524f9526a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 15 Sep 2022 02:15:35 +0600 Subject: [PATCH] complete adding of panel plugin --- .../src/commonMain/kotlin/PanelButtonsAPI.kt | 6 ++ posts/panel/src/jvmMain/kotlin/Plugin.kt | 44 +++++++++---- ratings/source/build.gradle | 1 + ratings/source/src/jvmMain/kotlin/Plugin.kt | 41 +++++++++++- triggers/command/build.gradle | 1 + triggers/command/src/jvmMain/kotlin/Plugin.kt | 66 ++++++++++++++++++- 6 files changed, 142 insertions(+), 17 deletions(-) diff --git a/posts/panel/src/commonMain/kotlin/PanelButtonsAPI.kt b/posts/panel/src/commonMain/kotlin/PanelButtonsAPI.kt index 9c9aa32..2f8c11d 100644 --- a/posts/panel/src/commonMain/kotlin/PanelButtonsAPI.kt +++ b/posts/panel/src/commonMain/kotlin/PanelButtonsAPI.kt @@ -1,6 +1,8 @@ package dev.inmo.plaguposter.posts.panel +import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton +import kotlinx.coroutines.flow.MutableSharedFlow class PanelButtonsAPI( private val preset: List, @@ -11,6 +13,7 @@ class PanelButtonsAPI( } internal val buttonsBuilders: List get() = _buttons.toList() + internal val forceRefreshFlow = MutableSharedFlow() val RootPanelButtonBuilder = PanelButtonBuilder { CallbackDataInlineKeyboardButton( @@ -21,6 +24,9 @@ class PanelButtonsAPI( fun add(button: PanelButtonBuilder) = _buttons.add(button) fun remove(button: PanelButtonBuilder) = _buttons.remove(button) + suspend fun forceRefresh(postId: PostId) { + forceRefreshFlow.emit(postId) + } companion object { internal const val openGlobalMenuData = "force_refresh_panel" diff --git a/posts/panel/src/jvmMain/kotlin/Plugin.kt b/posts/panel/src/jvmMain/kotlin/Plugin.kt index e4bff36..d06b18a 100644 --- a/posts/panel/src/jvmMain/kotlin/Plugin.kt +++ b/posts/panel/src/jvmMain/kotlin/Plugin.kt @@ -20,11 +20,13 @@ import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard import dev.inmo.tgbotapi.extensions.utils.withContentOrNull +import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.types.message.ParseMode +import dev.inmo.tgbotapi.types.message.abstracts.Message import dev.inmo.tgbotapi.types.message.content.TextContent -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.serialization.Serializable import kotlinx.serialization.json.* @@ -68,12 +70,12 @@ object Plugin : Plugin { val postsRepo = koin.get() val chatsConfig = koin.get() val config = koin.getOrNull() ?: Config() - val keeper = koin.get() + val api = koin.get() val postsMessages = PostsMessages(koin.get(), koin.get()) postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) { val firstContent = it.content.first() - val buttons = keeper.buttonsBuilders.chunked(config.buttonsPerRow).mapNotNull { row -> + val buttons = api.buttonsBuilders.chunked(config.buttonsPerRow).mapNotNull { row -> row.mapNotNull { builder -> builder.buildButton(it) }.takeIf { it.isNotEmpty() } @@ -95,22 +97,31 @@ object Plugin : Plugin { delete(chatId, messageId) } + suspend fun updatePost( + postId: PostId, + chatId: ChatId, + messageId: MessageIdentifier + ) { + val post = postsRepo.getById(postId) ?: return + val buttons = api.buttonsBuilders.chunked(config.buttonsPerRow).mapNotNull { row -> + row.mapNotNull { builder -> + builder.buildButton(post) + }.takeIf { it.isNotEmpty() } + } + edit( + chatId, + messageId, + replyMarkup = InlineKeyboardMarkup(buttons) + ) + } + onMessageDataCallbackQuery ( initialFilter = { it.data.startsWith(PanelButtonsAPI.openGlobalMenuDataPrefix) && it.message.chat.id == chatsConfig.sourceChatId } ) { val postId = it.data.removePrefix(PanelButtonsAPI.openGlobalMenuDataPrefix).let(::PostId) - val post = postsRepo.getById(postId) ?: return@onMessageDataCallbackQuery - val buttons = keeper.buttonsBuilders.chunked(config.buttonsPerRow).mapNotNull { row -> - row.mapNotNull { builder -> - builder.buildButton(post) - }.takeIf { it.isNotEmpty() } - } - edit( - it.message.withContentOrNull() ?: return@onMessageDataCallbackQuery, - replyMarkup = InlineKeyboardMarkup(buttons) - ) + updatePost(postId, it.message.chat.id, it.message.messageId) } onMessageDataCallbackQuery( initialFilter = { @@ -126,7 +137,7 @@ object Plugin : Plugin { query.message, replyMarkup = flatInlineKeyboard { dataButton("\uD83D\uDDD1", approveData) - keeper.RootPanelButtonBuilder.buildButton(post) ?.let(::add) + api.RootPanelButtonBuilder.buildButton(post) ?.let(::add) } ) @@ -138,5 +149,10 @@ object Plugin : Plugin { postsRepo.deleteById(postId) } } + + api.forceRefreshFlow.subscribeSafelyWithoutExceptions(this) { + val (chatId, messageId) = postsMessages.get(it) ?: return@subscribeSafelyWithoutExceptions + updatePost(it, chatId, messageId) + } } } diff --git a/ratings/source/build.gradle b/ratings/source/build.gradle index ea7c956..2c08fa6 100644 --- a/ratings/source/build.gradle +++ b/ratings/source/build.gradle @@ -12,6 +12,7 @@ kotlin { dependencies { api project(":plaguposter.common") api project(":plaguposter.ratings") + api project(":plaguposter.posts.panel") } } jvmMain { diff --git a/ratings/source/src/jvmMain/kotlin/Plugin.kt b/ratings/source/src/jvmMain/kotlin/Plugin.kt index a006328..1bf679d 100644 --- a/ratings/source/src/jvmMain/kotlin/Plugin.kt +++ b/ratings/source/src/jvmMain/kotlin/Plugin.kt @@ -14,17 +14,21 @@ import dev.inmo.plaguposter.inlines.models.Format import dev.inmo.plaguposter.inlines.models.OfferTemplate import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo import dev.inmo.plaguposter.posts.models.PostId +import dev.inmo.plaguposter.posts.panel.PanelButtonBuilder +import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.ratings.models.Rating import dev.inmo.plaguposter.ratings.repo.RatingsRepo import dev.inmo.plaguposter.ratings.source.models.* import dev.inmo.plaguposter.ratings.source.repos.* +import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.delete 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.triggers_handling.* +import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.message.textsources.regular import kotlinx.serialization.Serializable import kotlinx.serialization.json.* @@ -41,7 +45,8 @@ object Plugin : Plugin { @Serializable(RatingsVariantsSerializer::class) val variants: RatingsVariants, val autoAttach: Boolean, - val ratingOfferText: String + val ratingOfferText: String, + val panelButtonText: String = "Ratings" ) override fun Module.setupDI(database: Database, params: JsonObject) { @@ -66,6 +71,7 @@ object Plugin : Plugin { val ratingsRepo = koin.get() val postsRepo = koin.get() val config = koin.get() + val panelApi = koin.getOrNull() onPollUpdates (markerFactory = { it.id }) { poll -> val postId = pollsToPostsIdsRepo.get(poll.id) ?: return@onPollUpdates @@ -92,6 +98,7 @@ object Plugin : Plugin { pollsToPostsIdsRepo.set(sent.content.poll.id, postId) pollsToMessageInfoRepo.set(sent.content.poll.id, sent.short()) }.getOrNull() ?: continue + panelApi ?.forceRefresh(postId) return true } return false @@ -105,6 +112,8 @@ object Plugin : Plugin { delete(messageInfo.chatId, messageInfo.messageId) }.onFailure { this@Plugin.logger.e(it) { "Something went wrong when trying to remove ratings message ($messageInfo) for post $postId" } + }.onSuccess { + panelApi ?.forceRefresh(postId) }.isSuccess }.any().also { if (it) { @@ -209,5 +218,35 @@ object Plugin : Plugin { ) ) } + + koin.getOrNull() ?.apply { + add( + PanelButtonBuilder { + CallbackDataInlineKeyboardButton( + config.panelButtonText + if (pollsToPostsIdsRepo.keys(it.id, firstPageWithOneElementPagination).results.any()) { + SuccessfulSymbol + } else { + UnsuccessfulSymbol + }, + "toggle_ratings ${it.id.string}" + ) + } + ) + onMessageDataCallbackQuery( + initialFilter = { + it.data.startsWith("toggle_ratings ") + } + ) { + val postId = it.data.removePrefix("toggle_ratings ").let(::PostId) + + if (pollsToPostsIdsRepo.keys(postId, firstPageWithOneElementPagination).results.any()) { + detachPoll(postId) + } else { + attachPoll(postId) + } + + answer(it) + } + } } } diff --git a/triggers/command/build.gradle b/triggers/command/build.gradle index 4f8449e..3f51e34 100644 --- a/triggers/command/build.gradle +++ b/triggers/command/build.gradle @@ -13,6 +13,7 @@ kotlin { api project(":plaguposter.common") api project(":plaguposter.posts") api project(":plaguposter.ratings.selector") + api project(":plaguposter.posts.panel") } } jvmMain { diff --git a/triggers/command/src/jvmMain/kotlin/Plugin.kt b/triggers/command/src/jvmMain/kotlin/Plugin.kt index 539cd5c..5423728 100644 --- a/triggers/command/src/jvmMain/kotlin/Plugin.kt +++ b/triggers/command/src/jvmMain/kotlin/Plugin.kt @@ -1,28 +1,42 @@ package dev.inmo.plaguposter.triggers.command +import com.benasher44.uuid.uuid4 import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.fsm.common.State +import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination import dev.inmo.plagubot.Plugin import dev.inmo.plaguposter.common.SuccessfulSymbol +import dev.inmo.plaguposter.common.UnsuccessfulSymbol import dev.inmo.plaguposter.inlines.models.Format import dev.inmo.plaguposter.inlines.models.OfferTemplate import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo +import dev.inmo.plaguposter.posts.models.PostId +import dev.inmo.plaguposter.posts.panel.PanelButtonBuilder +import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.posts.sending.PostPublisher import dev.inmo.plaguposter.ratings.selector.Selector +import dev.inmo.tgbotapi.extensions.api.answers.answer 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.BehaviourContextWithFSM +import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitTextMessage import dev.inmo.tgbotapi.extensions.behaviour_builder.strictlyOn import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand -import dev.inmo.tgbotapi.extensions.utils.botCommandTextSourceOrNull -import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery +import dev.inmo.tgbotapi.extensions.utils.* +import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage +import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton +import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.MessageIdentifier +import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton +import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.types.message.textsources.regular import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first import kotlinx.serialization.Serializable import kotlinx.serialization.json.* import org.jetbrains.exposed.sql.Database @@ -36,13 +50,22 @@ object Plugin : Plugin { val sourceMessageId: MessageIdentifier, val messageInReply: MessageIdentifier ) : State + @Serializable + internal data class Config( + val panelButtonText: String? = "Publish" + ) override fun Module.setupDI(database: Database, params: JsonObject) { + params["publish_command"] ?.let { configJson -> + single { get().decodeFromJsonElement(Config.serializer(), configJson) } + } } override suspend fun BehaviourContextWithFSM.setupBotPlugin(koin: Koin) { val postsRepo = koin.get() val publisher = koin.get() val selector = koin.getOrNull() + val config = koin.getOrNull() + val panelApi = koin.getOrNull() onCommand("publish_post") { val messageInReply = it.replyTo ?.contentMessageOrNull() ?: run { @@ -87,5 +110,44 @@ object Plugin : Plugin { ) ) } + + panelApi ?.apply { + config ?.panelButtonText ?.let { text -> + add( + PanelButtonBuilder { + CallbackDataInlineKeyboardButton( + text, + "publish ${it.id.string}" + ) + } + ) + onMessageDataCallbackQuery( + initialFilter = { + it.data.startsWith("publish ") + } + ) { + val postId = it.data.removePrefix("publish ").let(::PostId) + val post = postsRepo.getById(postId) ?: return@onMessageDataCallbackQuery + + val publishData = uuid4().toString() + + val edited = edit( + it.message, + replyMarkup = flatInlineKeyboard { + dataButton(SuccessfulSymbol, publishData) + RootPanelButtonBuilder.buildButton(post) ?.let(::add) + } + ) + + val pushedButton = waitMessageDataCallbackQuery().first { + it.message.sameMessage(edited) + } + + if (pushedButton.data == publishData) { + publisher.publish(postId) + } + } + } + } } }