complete adding of panel plugin

This commit is contained in:
InsanusMokrassar 2022-09-15 02:15:35 +06:00
parent 849df78238
commit 2a883c25ca
6 changed files with 142 additions and 17 deletions

View File

@ -1,6 +1,8 @@
package dev.inmo.plaguposter.posts.panel package dev.inmo.plaguposter.posts.panel
import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
import kotlinx.coroutines.flow.MutableSharedFlow
class PanelButtonsAPI( class PanelButtonsAPI(
private val preset: List<PanelButtonBuilder>, private val preset: List<PanelButtonBuilder>,
@ -11,6 +13,7 @@ class PanelButtonsAPI(
} }
internal val buttonsBuilders: List<PanelButtonBuilder> internal val buttonsBuilders: List<PanelButtonBuilder>
get() = _buttons.toList() get() = _buttons.toList()
internal val forceRefreshFlow = MutableSharedFlow<PostId>()
val RootPanelButtonBuilder = PanelButtonBuilder { val RootPanelButtonBuilder = PanelButtonBuilder {
CallbackDataInlineKeyboardButton( CallbackDataInlineKeyboardButton(
@ -21,6 +24,9 @@ class PanelButtonsAPI(
fun add(button: PanelButtonBuilder) = _buttons.add(button) fun add(button: PanelButtonBuilder) = _buttons.add(button)
fun remove(button: PanelButtonBuilder) = _buttons.remove(button) fun remove(button: PanelButtonBuilder) = _buttons.remove(button)
suspend fun forceRefresh(postId: PostId) {
forceRefreshFlow.emit(postId)
}
companion object { companion object {
internal const val openGlobalMenuData = "force_refresh_panel" internal const val openGlobalMenuData = "force_refresh_panel"

View File

@ -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.dataButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull 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.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
import dev.inmo.tgbotapi.types.message.ParseMode import dev.inmo.tgbotapi.types.message.ParseMode
import dev.inmo.tgbotapi.types.message.abstracts.Message
import dev.inmo.tgbotapi.types.message.content.TextContent import dev.inmo.tgbotapi.types.message.content.TextContent
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
@ -68,12 +70,12 @@ object Plugin : Plugin {
val postsRepo = koin.get<PostsRepo>() val postsRepo = koin.get<PostsRepo>()
val chatsConfig = koin.get<ChatConfig>() val chatsConfig = koin.get<ChatConfig>()
val config = koin.getOrNull<Config>() ?: Config() val config = koin.getOrNull<Config>() ?: Config()
val keeper = koin.get<PanelButtonsAPI>() val api = koin.get<PanelButtonsAPI>()
val postsMessages = PostsMessages(koin.get(), koin.get()) val postsMessages = PostsMessages(koin.get(), koin.get())
postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) { postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) {
val firstContent = it.content.first() 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 -> row.mapNotNull { builder ->
builder.buildButton(it) builder.buildButton(it)
}.takeIf { it.isNotEmpty() } }.takeIf { it.isNotEmpty() }
@ -95,22 +97,31 @@ object Plugin : Plugin {
delete(chatId, messageId) 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 ( onMessageDataCallbackQuery (
initialFilter = { initialFilter = {
it.data.startsWith(PanelButtonsAPI.openGlobalMenuDataPrefix) && it.message.chat.id == chatsConfig.sourceChatId it.data.startsWith(PanelButtonsAPI.openGlobalMenuDataPrefix) && it.message.chat.id == chatsConfig.sourceChatId
} }
) { ) {
val postId = it.data.removePrefix(PanelButtonsAPI.openGlobalMenuDataPrefix).let(::PostId) val postId = it.data.removePrefix(PanelButtonsAPI.openGlobalMenuDataPrefix).let(::PostId)
val post = postsRepo.getById(postId) ?: return@onMessageDataCallbackQuery updatePost(postId, it.message.chat.id, it.message.messageId)
val buttons = keeper.buttonsBuilders.chunked(config.buttonsPerRow).mapNotNull { row ->
row.mapNotNull { builder ->
builder.buildButton(post)
}.takeIf { it.isNotEmpty() }
}
edit(
it.message.withContentOrNull<TextContent>() ?: return@onMessageDataCallbackQuery,
replyMarkup = InlineKeyboardMarkup(buttons)
)
} }
onMessageDataCallbackQuery( onMessageDataCallbackQuery(
initialFilter = { initialFilter = {
@ -126,7 +137,7 @@ object Plugin : Plugin {
query.message, query.message,
replyMarkup = flatInlineKeyboard { replyMarkup = flatInlineKeyboard {
dataButton("\uD83D\uDDD1", approveData) 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) postsRepo.deleteById(postId)
} }
} }
api.forceRefreshFlow.subscribeSafelyWithoutExceptions(this) {
val (chatId, messageId) = postsMessages.get(it) ?: return@subscribeSafelyWithoutExceptions
updatePost(it, chatId, messageId)
}
} }
} }

View File

@ -12,6 +12,7 @@ kotlin {
dependencies { dependencies {
api project(":plaguposter.common") api project(":plaguposter.common")
api project(":plaguposter.ratings") api project(":plaguposter.ratings")
api project(":plaguposter.posts.panel")
} }
} }
jvmMain { jvmMain {

View File

@ -14,17 +14,21 @@ import dev.inmo.plaguposter.inlines.models.Format
import dev.inmo.plaguposter.inlines.models.OfferTemplate import dev.inmo.plaguposter.inlines.models.OfferTemplate
import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo
import dev.inmo.plaguposter.posts.models.PostId 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.repo.PostsRepo
import dev.inmo.plaguposter.ratings.models.Rating import dev.inmo.plaguposter.ratings.models.Rating
import dev.inmo.plaguposter.ratings.repo.RatingsRepo import dev.inmo.plaguposter.ratings.repo.RatingsRepo
import dev.inmo.plaguposter.ratings.source.models.* import dev.inmo.plaguposter.ratings.source.models.*
import dev.inmo.plaguposter.ratings.source.repos.* 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.delete
import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.* 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 dev.inmo.tgbotapi.types.message.textsources.regular
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
@ -41,7 +45,8 @@ object Plugin : Plugin {
@Serializable(RatingsVariantsSerializer::class) @Serializable(RatingsVariantsSerializer::class)
val variants: RatingsVariants, val variants: RatingsVariants,
val autoAttach: Boolean, val autoAttach: Boolean,
val ratingOfferText: String val ratingOfferText: String,
val panelButtonText: String = "Ratings"
) )
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {
@ -66,6 +71,7 @@ object Plugin : Plugin {
val ratingsRepo = koin.get<RatingsRepo>() val ratingsRepo = koin.get<RatingsRepo>()
val postsRepo = koin.get<PostsRepo>() val postsRepo = koin.get<PostsRepo>()
val config = koin.get<Config>() val config = koin.get<Config>()
val panelApi = koin.getOrNull<PanelButtonsAPI>()
onPollUpdates (markerFactory = { it.id }) { poll -> onPollUpdates (markerFactory = { it.id }) { poll ->
val postId = pollsToPostsIdsRepo.get(poll.id) ?: return@onPollUpdates val postId = pollsToPostsIdsRepo.get(poll.id) ?: return@onPollUpdates
@ -92,6 +98,7 @@ object Plugin : Plugin {
pollsToPostsIdsRepo.set(sent.content.poll.id, postId) pollsToPostsIdsRepo.set(sent.content.poll.id, postId)
pollsToMessageInfoRepo.set(sent.content.poll.id, sent.short()) pollsToMessageInfoRepo.set(sent.content.poll.id, sent.short())
}.getOrNull() ?: continue }.getOrNull() ?: continue
panelApi ?.forceRefresh(postId)
return true return true
} }
return false return false
@ -105,6 +112,8 @@ object Plugin : Plugin {
delete(messageInfo.chatId, messageInfo.messageId) delete(messageInfo.chatId, messageInfo.messageId)
}.onFailure { }.onFailure {
this@Plugin.logger.e(it) { "Something went wrong when trying to remove ratings message ($messageInfo) for post $postId" } this@Plugin.logger.e(it) { "Something went wrong when trying to remove ratings message ($messageInfo) for post $postId" }
}.onSuccess {
panelApi ?.forceRefresh(postId)
}.isSuccess }.isSuccess
}.any().also { }.any().also {
if (it) { if (it) {
@ -209,5 +218,35 @@ object Plugin : Plugin {
) )
) )
} }
koin.getOrNull<PanelButtonsAPI>() ?.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)
}
}
} }
} }

View File

@ -13,6 +13,7 @@ kotlin {
api project(":plaguposter.common") api project(":plaguposter.common")
api project(":plaguposter.posts") api project(":plaguposter.posts")
api project(":plaguposter.ratings.selector") api project(":plaguposter.ratings.selector")
api project(":plaguposter.posts.panel")
} }
} }
jvmMain { jvmMain {

View File

@ -1,28 +1,42 @@
package dev.inmo.plaguposter.triggers.command package dev.inmo.plaguposter.triggers.command
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plaguposter.common.SuccessfulSymbol 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.Format
import dev.inmo.plaguposter.inlines.models.OfferTemplate import dev.inmo.plaguposter.inlines.models.OfferTemplate
import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo 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.repo.PostsRepo
import dev.inmo.plaguposter.posts.sending.PostPublisher import dev.inmo.plaguposter.posts.sending.PostPublisher
import dev.inmo.plaguposter.ratings.selector.Selector 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.edit.edit
import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextWithFSM 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.expectations.waitTextMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.strictlyOn import dev.inmo.tgbotapi.extensions.behaviour_builder.strictlyOn
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.extensions.utils.botCommandTextSourceOrNull import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull 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.ChatId
import dev.inmo.tgbotapi.types.MessageIdentifier 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 dev.inmo.tgbotapi.types.message.textsources.regular
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
@ -36,13 +50,22 @@ object Plugin : Plugin {
val sourceMessageId: MessageIdentifier, val sourceMessageId: MessageIdentifier,
val messageInReply: MessageIdentifier val messageInReply: MessageIdentifier
) : State ) : State
@Serializable
internal data class Config(
val panelButtonText: String? = "Publish"
)
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {
params["publish_command"] ?.let { configJson ->
single { get<Json>().decodeFromJsonElement(Config.serializer(), configJson) }
}
} }
override suspend fun BehaviourContextWithFSM<State>.setupBotPlugin(koin: Koin) { override suspend fun BehaviourContextWithFSM<State>.setupBotPlugin(koin: Koin) {
val postsRepo = koin.get<PostsRepo>() val postsRepo = koin.get<PostsRepo>()
val publisher = koin.get<PostPublisher>() val publisher = koin.get<PostPublisher>()
val selector = koin.getOrNull<Selector>() val selector = koin.getOrNull<Selector>()
val config = koin.getOrNull<Config>()
val panelApi = koin.getOrNull<PanelButtonsAPI>()
onCommand("publish_post") { onCommand("publish_post") {
val messageInReply = it.replyTo ?.contentMessageOrNull() ?: run { 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)
}
}
}
}
} }
} }