diff --git a/gradle.properties b/gradle.properties index 0693423..114a8d3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,5 +10,5 @@ android.enableJetifier=true # Project data group=dev.inmo -version=0.0.6 -android_code_version=6 +version=0.0.7 +android_code_version=7 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bacd557..ed6924d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,15 @@ [versions] -kotlin = "1.7.21" +kotlin = "1.7.22" kotlin-serialization = "1.4.1" -plagubot = "3.1.3" -tgbotapi = "4.1.2" -microutils = "0.14.2" -kslog = "0.5.3" -krontab = "0.8.3" -tgbotapi-libraries = "0.6.3" -plagubot-plugins = "0.6.1" +plagubot = "3.2.0" +tgbotapi = "4.2.1" +microutils = "0.16.0" +kslog = "0.5.4" +krontab = "0.8.4" +tgbotapi-libraries = "0.6.5" +plagubot-plugins = "0.6.4" dokka = "1.7.20" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661..070cb70 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt b/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt index b4391a0..7d0c5d1 100644 --- a/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt +++ b/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt @@ -10,4 +10,5 @@ import dev.inmo.tgbotapi.types.MessageIdentifier interface ReadPostsRepo : ReadCRUDRepo { suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId? suspend fun getPostCreationTime(postId: PostId): DateTime? + suspend fun getFirstMessageInfo(postId: PostId): PostContentInfo? } diff --git a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt index 8d7ef0c..170b505 100644 --- a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt +++ b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt @@ -38,9 +38,11 @@ class ExposedPostsRepo( override val selectById: ISqlExpressionBuilder.(PostId) -> Op = { idColumn.eq(it.string) } override val selectByIds: ISqlExpressionBuilder.(List) -> Op = { idColumn.inList(it.map { it.string }) } + override val ResultRow.asId: PostId + get() = PostId(get(idColumn)) override val ResultRow.asObject: RegisteredPost get() { - val id = PostId(get(idColumn)) + val id = asId return RegisteredPost( id, DateTime(get(createdColumn)), @@ -163,4 +165,10 @@ class ExposedPostsRepo( override suspend fun getPostCreationTime(postId: PostId): DateTime? = transaction(database) { select { selectById(postId) }.limit(1).firstOrNull() ?.get(createdColumn) ?.let(::DateTime) } + + override suspend fun getFirstMessageInfo(postId: PostId): PostContentInfo? = transaction(database) { + with(contentRepo) { + select { postIdColumn.eq(postId.string) }.limit(1).firstOrNull() ?.asObject + } + } } diff --git a/ratings/source/src/commonMain/kotlin/buttons/RootButtons.kt b/ratings/source/src/commonMain/kotlin/buttons/RootButtons.kt new file mode 100644 index 0000000..606d398 --- /dev/null +++ b/ratings/source/src/commonMain/kotlin/buttons/RootButtons.kt @@ -0,0 +1,160 @@ +package dev.inmo.plaguposter.ratings.source.buttons + +import com.soywiz.klock.DateFormat +import dev.inmo.micro_utils.coroutines.runCatchingSafely +import dev.inmo.micro_utils.pagination.FirstPagePagination +import dev.inmo.micro_utils.pagination.Pagination +import dev.inmo.micro_utils.pagination.SimplePagination +import dev.inmo.micro_utils.pagination.utils.paginate +import dev.inmo.plaguposter.posts.repo.ReadPostsRepo +import dev.inmo.plaguposter.ratings.models.Rating +import dev.inmo.plaguposter.ratings.repo.RatingsRepo +import dev.inmo.plaguposter.ratings.utils.postsByRatings +import dev.inmo.tgbotapi.extensions.api.answers.answer +import dev.inmo.tgbotapi.extensions.api.edit.edit +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery +import dev.inmo.tgbotapi.extensions.utils.formatting.makeLinkToMessage +import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton +import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard +import dev.inmo.tgbotapi.extensions.utils.types.buttons.urlButton +import dev.inmo.tgbotapi.types.ChatIdentifier +import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup +import dev.inmo.tgbotapi.utils.row + +const val RootButtonsShowRatingData = "ratings_buttons_show" +const val RootButtonsShowRatingPageData = "ratings_buttons_show_page" +const val RootButtonsToPageData = "ratings_buttons_to_page" + +suspend fun RatingsRepo.buildRootButtons( + pagination: Pagination = FirstPagePagination(16), + rowSize: Int = 4 +): InlineKeyboardMarkup { + val postsByRatings = postsByRatings().toList().paginate(pagination) + return inlineKeyboard { + if (postsByRatings.pagesNumber > 1) { + row { + if (postsByRatings.page > 0) { + dataButton("<", "$RootButtonsToPageData ${postsByRatings.page - 1} ${postsByRatings.size}") + } + dataButton("${postsByRatings.page}: \uD83D\uDD04", "$RootButtonsToPageData ${postsByRatings.page} ${postsByRatings.size}") + if (postsByRatings.pagesNumber - postsByRatings.page > 1) { + dataButton(">", "$RootButtonsToPageData ${postsByRatings.page + 1} ${postsByRatings.size}") + } + } + } + postsByRatings.results.chunked(rowSize).map { + row { + it.forEach { (rating, posts) -> + dataButton("${rating.double}: ${posts.size}", "$RootButtonsShowRatingData ${rating.double}") + } + } + } + } +} + +val defaultPostCreationTimeFormat: DateFormat = DateFormat("dd.MM.yy HH:mm") + +suspend fun RatingsRepo.buildRatingButtons( + postsRepo: ReadPostsRepo, + rating: Rating, + pagination: Pagination = FirstPagePagination(8), + rowSize: Int = 2, + postCreationTimeFormat: DateFormat = defaultPostCreationTimeFormat +): InlineKeyboardMarkup { + val postsByRatings = getPosts(rating .. rating, true).keys.paginate(pagination) + return inlineKeyboard { + if (postsByRatings.pagesNumber > 1) { + row { + if (postsByRatings.page > 0) { + dataButton("<", "$RootButtonsShowRatingPageData ${postsByRatings.page - 1} ${postsByRatings.size} ${rating.double}") + } + dataButton("${postsByRatings.page}: \uD83D\uDD04", "$RootButtonsShowRatingPageData ${postsByRatings.page} ${postsByRatings.size} ${rating.double}") + if (postsByRatings.pagesNumber - postsByRatings.page > 1) { + dataButton(">", "$RootButtonsShowRatingPageData ${postsByRatings.page + 1} ${postsByRatings.size} ${rating.double}") + } + } + } + postsByRatings.results.chunked(rowSize).map { + row { + it.forEach { postId -> + val firstMessageInfo = postsRepo.getFirstMessageInfo(postId) ?: return@forEach + val postCreationTime = postsRepo.getPostCreationTime(postId) ?: return@forEach + urlButton( + postCreationTime.format(postCreationTimeFormat), + makeLinkToMessage( + firstMessageInfo.chatId, + firstMessageInfo.messageId + ) + ) + } + } + } + row { + dataButton("↩️", "$RootButtonsToPageData 0 16") + } + } +} + +suspend fun BehaviourContext.includeRootNavigationButtonsHandler( + allowedChats: Set, + ratingsRepo: RatingsRepo, + postsRepo: ReadPostsRepo +) { + suspend fun registerPageQueryListener( + dataPrefix: String, + onPageUpdate: suspend (pagination: Pagination, additionalParams: Array) -> InlineKeyboardMarkup? + ) { + onMessageDataCallbackQuery( + initialFilter = { it.message.chat.id in allowedChats } + ) { + val args = it.data.split(" ").takeIf { it.size >= 3 } ?: return@onMessageDataCallbackQuery + val (prefix, pageRaw, sizeRaw) = args + + if (prefix == dataPrefix) { + runCatchingSafely { + val page = pageRaw.toIntOrNull() ?: return@runCatchingSafely + val size = sizeRaw.toIntOrNull() ?: return@runCatchingSafely + + edit( + it.message, + onPageUpdate(SimplePagination(page, size), args.drop(3).toTypedArray()) ?: return@runCatchingSafely + ) + } + + answer(it) + } + } + } + suspend fun registerPageQueryListener( + dataPrefix: String, + onPageUpdate: suspend (pagination: Pagination) -> InlineKeyboardMarkup? + ) = registerPageQueryListener(dataPrefix) { pagination, _ -> + onPageUpdate(pagination) + } + registerPageQueryListener( + RootButtonsToPageData, + ratingsRepo::buildRootButtons + ) + registerPageQueryListener( + RootButtonsShowRatingPageData + ) { pagination, params -> + params.firstOrNull() ?.toDoubleOrNull() ?.let { rating -> + ratingsRepo.buildRatingButtons(postsRepo, Rating(rating), pagination) + } + } + onMessageDataCallbackQuery( + initialFilter = { it.message.chat.id in allowedChats } + ) { + val (prefix, ratingRaw) = it.data.split(" ").takeIf { it.size == 2 } ?: return@onMessageDataCallbackQuery + + if (prefix == RootButtonsShowRatingData) { + runCatchingSafely { + val rating = ratingRaw.toDoubleOrNull() ?: return@runCatchingSafely + edit(it.message, ratingsRepo.buildRatingButtons(postsRepo, Rating(rating))) + } + + answer(it) + } + } +} diff --git a/ratings/source/src/jvmMain/kotlin/Plugin.kt b/ratings/source/src/jvmMain/kotlin/Plugin.kt index 0f78266..9706fa9 100644 --- a/ratings/source/src/jvmMain/kotlin/Plugin.kt +++ b/ratings/source/src/jvmMain/kotlin/Plugin.kt @@ -20,6 +20,8 @@ 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.buttons.buildRootButtons +import dev.inmo.plaguposter.ratings.source.buttons.includeRootNavigationButtonsHandler import dev.inmo.plaguposter.ratings.source.models.* import dev.inmo.plaguposter.ratings.source.repos.* import dev.inmo.plaguposter.ratings.utils.postsByRatings @@ -34,6 +36,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.* 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.types.buttons.inlineKeyboard import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.message.textsources.bold import dev.inmo.tgbotapi.types.message.textsources.regular @@ -225,13 +228,23 @@ object Plugin : Plugin { + "• " + bold("% 3.1f".format(it.first.double)) + ": " + bold(it.second.size.toString()) + "\n" } } + val keyboard = flatInlineKeyboard { + dataButton("Interactive mode", "ratings_interactive") + } runCatchingSafely { - edit(it, textSources) + edit(it, textSources, replyMarkup = keyboard) }.onFailure { _ -> - reply(it, textSources) + reply(it, textSources, replyMarkup = keyboard) } } } + includeRootNavigationButtonsHandler(setOf(chatConfig.sourceChatId), ratingsRepo, postsRepo) + onMessageDataCallbackQuery("ratings_interactive", initialFilter = { it.message.chat.id == chatConfig.sourceChatId }) { + edit( + it.message, + ratingsRepo.buildRootButtons() + ) + } koin.getOrNull() ?.apply { addTemplate(