2022-09-04 07:27:35 +00:00
|
|
|
package dev.inmo.plaguposter.ratings.source
|
|
|
|
|
2022-09-14 20:20:59 +00:00
|
|
|
import com.benasher44.uuid.uuid4
|
2022-09-04 07:27:35 +00:00
|
|
|
import dev.inmo.kslog.common.e
|
|
|
|
import dev.inmo.kslog.common.logger
|
|
|
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
|
|
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
|
|
|
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
|
|
|
|
import dev.inmo.micro_utils.repos.id
|
|
|
|
import dev.inmo.micro_utils.repos.pagination.getAll
|
|
|
|
import dev.inmo.micro_utils.repos.set
|
|
|
|
import dev.inmo.plagubot.Plugin
|
|
|
|
import dev.inmo.plaguposter.common.*
|
2022-10-11 05:38:50 +00:00
|
|
|
import dev.inmo.plagubot.plugins.inline.queries.models.Format
|
|
|
|
import dev.inmo.plagubot.plugins.inline.queries.models.OfferTemplate
|
|
|
|
import dev.inmo.plagubot.plugins.inline.queries.repos.InlineTemplatesRepo
|
2022-09-04 07:27:35 +00:00
|
|
|
import dev.inmo.plaguposter.posts.models.PostId
|
2022-09-14 20:15:35 +00:00
|
|
|
import dev.inmo.plaguposter.posts.panel.PanelButtonBuilder
|
|
|
|
import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI
|
2022-09-04 07:27:35 +00:00
|
|
|
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
|
|
|
import dev.inmo.plaguposter.ratings.models.Rating
|
|
|
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
2022-12-08 04:28:42 +00:00
|
|
|
import dev.inmo.plaguposter.ratings.source.buttons.buildRootButtons
|
|
|
|
import dev.inmo.plaguposter.ratings.source.buttons.includeRootNavigationButtonsHandler
|
2022-09-04 07:27:35 +00:00
|
|
|
import dev.inmo.plaguposter.ratings.source.models.*
|
|
|
|
import dev.inmo.plaguposter.ratings.source.repos.*
|
2022-10-02 18:51:51 +00:00
|
|
|
import dev.inmo.plaguposter.ratings.utils.postsByRatings
|
2022-09-14 20:15:35 +00:00
|
|
|
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
2022-09-04 07:27:35 +00:00
|
|
|
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
|
2022-09-14 20:20:59 +00:00
|
|
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
|
2022-09-04 07:27:35 +00:00
|
|
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
2022-09-14 20:20:59 +00:00
|
|
|
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
|
2022-12-08 04:28:42 +00:00
|
|
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard
|
2022-09-14 20:15:35 +00:00
|
|
|
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
|
2022-10-02 18:51:51 +00:00
|
|
|
import dev.inmo.tgbotapi.types.message.textsources.bold
|
2022-09-04 07:27:35 +00:00
|
|
|
import dev.inmo.tgbotapi.types.message.textsources.regular
|
2022-10-02 18:51:51 +00:00
|
|
|
import dev.inmo.tgbotapi.utils.buildEntities
|
2022-10-25 06:51:45 +00:00
|
|
|
import kotlinx.coroutines.delay
|
2022-09-14 20:20:59 +00:00
|
|
|
import kotlinx.coroutines.flow.first
|
2022-09-04 07:27:35 +00:00
|
|
|
import kotlinx.serialization.Serializable
|
|
|
|
import kotlinx.serialization.json.*
|
|
|
|
import org.jetbrains.exposed.sql.Database
|
|
|
|
import org.koin.core.Koin
|
|
|
|
import org.koin.core.module.Module
|
|
|
|
import org.koin.core.qualifier.named
|
|
|
|
|
|
|
|
object Plugin : Plugin {
|
|
|
|
private val ratingVariantsQualifier = named("ratingsVariants")
|
|
|
|
|
|
|
|
@Serializable
|
|
|
|
internal data class Config(
|
|
|
|
@Serializable(RatingsVariantsSerializer::class)
|
|
|
|
val variants: RatingsVariants,
|
|
|
|
val autoAttach: Boolean,
|
2022-09-14 20:15:35 +00:00
|
|
|
val ratingOfferText: String,
|
|
|
|
val panelButtonText: String = "Ratings"
|
2022-09-04 07:27:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
override fun Module.setupDI(database: Database, params: JsonObject) {
|
|
|
|
single {
|
|
|
|
get<Json>().decodeFromJsonElement(Config.serializer(), params["ratingsPolls"] ?: error("Unable to load config for rating polls in $params"))
|
|
|
|
}
|
|
|
|
single<RatingsVariants>(ratingVariantsQualifier) { get<Config>().variants }
|
|
|
|
single<PollsToPostsIdsRepo> { ExposedPollsToPostsIdsRepo(database) }
|
|
|
|
single<PollsToMessagesInfoRepo> { ExposedPollsToMessagesInfoRepo(database) }
|
|
|
|
single<VariantTransformer> {
|
|
|
|
val ratingsSettings = get<RatingsVariants>(ratingVariantsQualifier)
|
|
|
|
VariantTransformer {
|
|
|
|
ratingsSettings[it]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
|
|
|
val pollsToPostsIdsRepo = koin.get<PollsToPostsIdsRepo>()
|
|
|
|
val pollsToMessageInfoRepo = koin.get<PollsToMessagesInfoRepo>()
|
|
|
|
val variantsTransformer = koin.get<VariantTransformer>()
|
|
|
|
val ratingsRepo = koin.get<RatingsRepo>()
|
|
|
|
val postsRepo = koin.get<PostsRepo>()
|
|
|
|
val config = koin.get<Config>()
|
2022-09-14 20:15:35 +00:00
|
|
|
val panelApi = koin.getOrNull<PanelButtonsAPI>()
|
2022-10-02 18:51:51 +00:00
|
|
|
val chatConfig = koin.get<ChatConfig>()
|
2022-09-04 07:27:35 +00:00
|
|
|
|
|
|
|
onPollUpdates (markerFactory = { it.id }) { poll ->
|
|
|
|
val postId = pollsToPostsIdsRepo.get(poll.id) ?: return@onPollUpdates
|
|
|
|
val newRating = poll.options.sumOf {
|
|
|
|
(variantsTransformer(it.text) ?.double ?.times(it.votes)) ?: 0.0
|
|
|
|
}
|
|
|
|
ratingsRepo.set(postId, Rating(newRating))
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun attachPoll(postId: PostId): Boolean {
|
|
|
|
if (pollsToPostsIdsRepo.keys(postId, firstPageWithOneElementPagination).results.isNotEmpty()) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
val post = postsRepo.getById(postId) ?: return false
|
2022-12-14 05:50:02 +00:00
|
|
|
ratingsRepo.set(postId, Rating(0.0))
|
2022-09-04 07:27:35 +00:00
|
|
|
for (content in post.content) {
|
|
|
|
runCatchingSafely {
|
|
|
|
val sent = send(
|
|
|
|
content.chatId,
|
|
|
|
config.ratingOfferText,
|
|
|
|
config.variants.keys.toList(),
|
|
|
|
replyToMessageId = content.messageId
|
|
|
|
)
|
|
|
|
pollsToPostsIdsRepo.set(sent.content.poll.id, postId)
|
|
|
|
pollsToMessageInfoRepo.set(sent.content.poll.id, sent.short())
|
|
|
|
}.getOrNull() ?: continue
|
2022-10-25 06:51:45 +00:00
|
|
|
|
|
|
|
delay(500L)
|
|
|
|
|
2022-09-14 20:15:35 +00:00
|
|
|
panelApi ?.forceRefresh(postId)
|
2022-09-04 07:27:35 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
suspend fun detachPoll(postId: PostId): Boolean {
|
|
|
|
val postIds = pollsToPostsIdsRepo.getAll { keys(postId, it) }.takeIf { it.isNotEmpty() } ?: return false
|
|
|
|
return postIds.map { (pollId) ->
|
|
|
|
val messageInfo = pollsToMessageInfoRepo.get(pollId) ?: return@map false
|
|
|
|
runCatchingSafely {
|
|
|
|
delete(messageInfo.chatId, messageInfo.messageId)
|
|
|
|
}.onFailure {
|
|
|
|
this@Plugin.logger.e(it) { "Something went wrong when trying to remove ratings message ($messageInfo) for post $postId" }
|
2022-09-14 20:15:35 +00:00
|
|
|
}.onSuccess {
|
|
|
|
panelApi ?.forceRefresh(postId)
|
2022-09-04 07:27:35 +00:00
|
|
|
}.isSuccess
|
|
|
|
}.any().also {
|
|
|
|
if (it) {
|
|
|
|
pollsToPostsIdsRepo.unset(postIds.map { it.id })
|
|
|
|
pollsToMessageInfoRepo.unset(postIds.map { it.id })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-14 05:50:02 +00:00
|
|
|
ratingsRepo.onValueRemoved.subscribeSafelyWithoutExceptions(this) { postId ->
|
2022-09-04 07:27:35 +00:00
|
|
|
detachPoll(postId)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.autoAttach) {
|
|
|
|
postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) {
|
2022-10-25 06:51:45 +00:00
|
|
|
delay(500L)
|
2022-09-04 07:27:35 +00:00
|
|
|
attachPoll(it.id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onCommand("attach_ratings", requireOnlyCommandInMessage = true) {
|
|
|
|
val replyTo = it.replyTo ?: run {
|
|
|
|
reply(
|
|
|
|
it,
|
|
|
|
"You should reply to post message to attach ratings"
|
|
|
|
)
|
|
|
|
return@onCommand
|
|
|
|
}
|
|
|
|
|
|
|
|
val postId = postsRepo.getIdByChatAndMessage(replyTo.chat.id, replyTo.messageId) ?: run {
|
|
|
|
reply(
|
|
|
|
it,
|
|
|
|
"Unable to find post where the message in reply is presented"
|
|
|
|
)
|
|
|
|
return@onCommand
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attachPoll(postId)) {
|
|
|
|
runCatchingSafely {
|
|
|
|
edit(
|
|
|
|
it,
|
|
|
|
it.content.textSources + regular(" $SuccessfulSymbol")
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
runCatchingSafely {
|
|
|
|
edit(
|
|
|
|
it,
|
|
|
|
it.content.textSources + regular(" $UnsuccessfulSymbol")
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
onCommand("detach_ratings", requireOnlyCommandInMessage = true) {
|
|
|
|
val replyTo = it.replyTo ?: run {
|
|
|
|
reply(
|
|
|
|
it,
|
|
|
|
"You should reply to post message to detach ratings"
|
|
|
|
)
|
|
|
|
return@onCommand
|
|
|
|
}
|
|
|
|
|
|
|
|
val postId = postsRepo.getIdByChatAndMessage(replyTo.chat.id, replyTo.messageId) ?: run {
|
|
|
|
reply(
|
|
|
|
it,
|
|
|
|
"Unable to find post where the message in reply is presented"
|
|
|
|
)
|
|
|
|
return@onCommand
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (detachPoll(postId)) {
|
|
|
|
runCatchingSafely {
|
|
|
|
edit(
|
|
|
|
it,
|
|
|
|
it.content.textSources + regular(" $SuccessfulSymbol")
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
runCatchingSafely {
|
|
|
|
edit(
|
|
|
|
it,
|
|
|
|
it.content.textSources + regular(" $UnsuccessfulSymbol")
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-02 18:51:51 +00:00
|
|
|
onCommand("ratings", requireOnlyCommandInMessage = true) {
|
|
|
|
if (it.chat.id == chatConfig.sourceChatId) {
|
|
|
|
val ratings = ratingsRepo.postsByRatings().toList().sortedByDescending { it.first }
|
|
|
|
val textSources = buildEntities {
|
|
|
|
+ "Ratings amount: " + bold("${ratings.sumOf { it.second.size }}") + "\n\n"
|
|
|
|
ratings.forEach {
|
|
|
|
+ "• " + bold("% 3.1f".format(it.first.double)) + ": " + bold(it.second.size.toString()) + "\n"
|
|
|
|
}
|
|
|
|
}
|
2022-12-08 04:28:42 +00:00
|
|
|
val keyboard = flatInlineKeyboard {
|
|
|
|
dataButton("Interactive mode", "ratings_interactive")
|
|
|
|
}
|
2022-10-02 18:51:51 +00:00
|
|
|
runCatchingSafely {
|
2022-12-08 04:28:42 +00:00
|
|
|
edit(it, textSources, replyMarkup = keyboard)
|
2022-10-02 18:51:51 +00:00
|
|
|
}.onFailure { _ ->
|
2022-12-08 04:28:42 +00:00
|
|
|
reply(it, textSources, replyMarkup = keyboard)
|
2022-10-02 18:51:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-08 04:28:42 +00:00
|
|
|
includeRootNavigationButtonsHandler(setOf(chatConfig.sourceChatId), ratingsRepo, postsRepo)
|
|
|
|
onMessageDataCallbackQuery("ratings_interactive", initialFilter = { it.message.chat.id == chatConfig.sourceChatId }) {
|
|
|
|
edit(
|
|
|
|
it.message,
|
|
|
|
ratingsRepo.buildRootButtons()
|
|
|
|
)
|
|
|
|
}
|
2022-09-09 13:46:32 +00:00
|
|
|
|
|
|
|
koin.getOrNull<InlineTemplatesRepo>() ?.apply {
|
|
|
|
addTemplate(
|
|
|
|
OfferTemplate(
|
|
|
|
"Enable ratings for post",
|
2022-09-09 14:38:06 +00:00
|
|
|
listOf(Format("/attach_ratings")),
|
2022-09-09 13:46:32 +00:00
|
|
|
"Require reply on post message"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
addTemplate(
|
|
|
|
OfferTemplate(
|
|
|
|
"Disable ratings for post",
|
2022-09-09 14:38:06 +00:00
|
|
|
listOf(Format("/detach_ratings")),
|
2022-09-09 13:46:32 +00:00
|
|
|
"Require reply on post message"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
2022-09-14 20:15:35 +00:00
|
|
|
|
2022-09-14 20:20:59 +00:00
|
|
|
panelApi ?.apply {
|
2022-09-14 20:15:35 +00:00
|
|
|
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)
|
2022-09-14 20:20:59 +00:00
|
|
|
val post = postsRepo.getById(postId) ?: return@onMessageDataCallbackQuery
|
2022-09-14 20:15:35 +00:00
|
|
|
|
2022-09-14 20:20:59 +00:00
|
|
|
val ratingPollAttached = pollsToPostsIdsRepo.keys(postId, firstPageWithOneElementPagination).results.any()
|
|
|
|
val toggleData = uuid4().toString()
|
|
|
|
|
|
|
|
val editedMessage = edit(
|
|
|
|
it.message,
|
|
|
|
replyMarkup = flatInlineKeyboard {
|
|
|
|
dataButton(
|
|
|
|
if (ratingPollAttached) {
|
|
|
|
UnsuccessfulSymbol
|
|
|
|
} else {
|
|
|
|
SuccessfulSymbol
|
|
|
|
},
|
|
|
|
toggleData
|
|
|
|
)
|
|
|
|
panelApi.RootPanelButtonBuilder.buildButton(post) ?.let(::add)
|
|
|
|
}
|
|
|
|
)
|
2022-09-14 20:15:35 +00:00
|
|
|
|
2022-09-14 20:20:59 +00:00
|
|
|
val pushedButton = waitMessageDataCallbackQuery().first { it.message.sameMessage(editedMessage) }
|
|
|
|
if (pushedButton.data == toggleData) {
|
|
|
|
if (pollsToPostsIdsRepo.keys(postId, firstPageWithOneElementPagination).results.any()) {
|
|
|
|
detachPoll(postId)
|
|
|
|
} else {
|
|
|
|
attachPoll(postId)
|
|
|
|
}
|
|
|
|
}
|
2022-09-16 10:53:57 +00:00
|
|
|
answer(it)
|
2022-09-14 20:15:35 +00:00
|
|
|
}
|
|
|
|
}
|
2022-09-04 07:27:35 +00:00
|
|
|
}
|
|
|
|
}
|