diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b0b281..a3dab43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,15 @@ # PlaguPoster +## 0.1.1 + +* Update dependencies +* `Triggers` + * `SelectorWithTimer` + * Opportunity to get schedule of posts using `publishing_autoschedule` command + ## 0.0.10 ## 0.0.9 -* Update depedencies +* Update dependencies diff --git a/gradle.properties b/gradle.properties index 5c07492..7a47a53 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ android.enableJetifier=true # Project data group=dev.inmo -version=0.1.0 +version=0.1.1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9d06886..da466c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,13 +3,13 @@ kotlin = "1.8.10" kotlin-serialization = "1.5.0" -plagubot = "5.0.0" -tgbotapi = "7.0.0" +plagubot = "5.0.1" +tgbotapi = "7.0.1" microutils = "0.17.5" kslog = "1.0.0" -krontab = "0.9.0" -tgbotapi-libraries = "0.10.0" -plagubot-plugins = "0.10.0" +krontab = "0.10.0" +tgbotapi-libraries = "0.10.1" +plagubot-plugins = "0.10.1" dokka = "1.8.10" diff --git a/ratings/gc/src/jvmMain/kotlin/Plugin.kt b/ratings/gc/src/jvmMain/kotlin/Plugin.kt index 1f06e5f..d8a297a 100644 --- a/ratings/gc/src/jvmMain/kotlin/Plugin.kt +++ b/ratings/gc/src/jvmMain/kotlin/Plugin.kt @@ -1,10 +1,9 @@ package dev.inmo.plaguposter.ratings.gc -import com.soywiz.klock.milliseconds import com.soywiz.klock.seconds import dev.inmo.krontab.KrontabTemplate import dev.inmo.krontab.toSchedule -import dev.inmo.krontab.utils.asFlow +import dev.inmo.krontab.utils.asFlowWithDelays import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.repos.* import dev.inmo.plagubot.Plugin @@ -12,7 +11,6 @@ import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.ratings.models.Rating import dev.inmo.plaguposter.ratings.repo.RatingsRepo import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext -import dev.inmo.tgbotapi.types.MilliSeconds import dev.inmo.tgbotapi.types.Seconds import kotlinx.serialization.Serializable import kotlinx.serialization.json.* @@ -50,7 +48,7 @@ object Plugin : Plugin { } } config.autoclear ?.let { autoclear -> - autoclear.autoClearKrontab.toSchedule().asFlow().subscribeSafelyWithoutExceptions(scope) { + autoclear.autoClearKrontab.toSchedule().asFlowWithDelays().subscribeSafelyWithoutExceptions(scope) { val dropCreatedBefore = it - (autoclear.skipPostAge ?: 0).seconds ratingsRepo.getPostsWithRatingLessEq(autoclear.rating).keys.forEach { if ((postsRepo.getPostCreationTime(it) ?: return@forEach) < dropCreatedBefore) { diff --git a/ratings/selector/src/commonMain/kotlin/DefaultSelector.kt b/ratings/selector/src/commonMain/kotlin/DefaultSelector.kt index 75bad45..fafeb3f 100644 --- a/ratings/selector/src/commonMain/kotlin/DefaultSelector.kt +++ b/ratings/selector/src/commonMain/kotlin/DefaultSelector.kt @@ -11,11 +11,11 @@ class DefaultSelector ( private val ratingsRepo: RatingsRepo, private val postsRepo: PostsRepo ) : Selector { - override suspend fun take(n: Int, now: DateTime): List { + override suspend fun take(n: Int, now: DateTime, exclude: List): List { val result = mutableListOf() do { - val selected = config.active(now.time) ?.rating ?.select(ratingsRepo, postsRepo, result, now) ?: break + val selected = config.active(now.time) ?.rating ?.select(ratingsRepo, postsRepo, result + exclude, now) ?: break result.add(selected) } while (result.size < n) diff --git a/ratings/selector/src/commonMain/kotlin/Selector.kt b/ratings/selector/src/commonMain/kotlin/Selector.kt index 813aa20..134f6b1 100644 --- a/ratings/selector/src/commonMain/kotlin/Selector.kt +++ b/ratings/selector/src/commonMain/kotlin/Selector.kt @@ -4,5 +4,5 @@ import com.soywiz.klock.DateTime import dev.inmo.plaguposter.posts.models.PostId interface Selector { - suspend fun take(n: Int = 1, now: DateTime = DateTime.now()): List + suspend fun take(n: Int = 1, now: DateTime = DateTime.now(), exclude: List = emptyList()): List } diff --git a/settings.gradle b/settings.gradle index 1bb37bc..f356c8c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,7 +11,6 @@ String[] includes = [ ":ratings:gc", ":triggers:command", ":triggers:selector_with_timer", - ":triggers:selector_with_scheduling", ":triggers:timer", ":triggers:timer:disablers:ratings", ":triggers:timer:disablers:autoposts", diff --git a/triggers/selector_with_scheduling/build.gradle b/triggers/selector_with_scheduling/build.gradle deleted file mode 100644 index f95f579..0000000 --- a/triggers/selector_with_scheduling/build.gradle +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" - id "org.jetbrains.kotlin.plugin.serialization" -} - -apply from: "$mppProjectWithSerializationPresetPath" - -kotlin { - sourceSets { - commonMain { - dependencies { - api project(":plaguposter.common") - } - } - } -} diff --git a/triggers/selector_with_scheduling/src/commonMain/kotlin/PackageInfo.kt b/triggers/selector_with_scheduling/src/commonMain/kotlin/PackageInfo.kt deleted file mode 100644 index 6b58a49..0000000 --- a/triggers/selector_with_scheduling/src/commonMain/kotlin/PackageInfo.kt +++ /dev/null @@ -1 +0,0 @@ -package dev.inmo.plaguposter.triggers.selector_with_scheduling diff --git a/triggers/selector_with_scheduling/src/jvmMain/kotlin/Plugin.kt b/triggers/selector_with_scheduling/src/jvmMain/kotlin/Plugin.kt deleted file mode 100644 index d360aa8..0000000 --- a/triggers/selector_with_scheduling/src/jvmMain/kotlin/Plugin.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.inmo.plaguposter.triggers.selector_with_scheduling - -import dev.inmo.plagubot.Plugin -import kotlinx.serialization.json.* -import org.jetbrains.exposed.sql.Database -import org.koin.core.module.Module - -object Plugin : Plugin { - override fun Module.setupDI(database: Database, params: JsonObject) { - } -} diff --git a/triggers/selector_with_scheduling/src/main/AndroidManifest.xml b/triggers/selector_with_scheduling/src/main/AndroidManifest.xml deleted file mode 100644 index acdf85d..0000000 --- a/triggers/selector_with_scheduling/src/main/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/triggers/selector_with_timer/src/jvmMain/kotlin/Plugin.kt b/triggers/selector_with_timer/src/jvmMain/kotlin/Plugin.kt index 33f324c..79c8487 100644 --- a/triggers/selector_with_timer/src/jvmMain/kotlin/Plugin.kt +++ b/triggers/selector_with_timer/src/jvmMain/kotlin/Plugin.kt @@ -1,14 +1,43 @@ package dev.inmo.plaguposter.triggers.selector_with_timer +import com.soywiz.klock.DateFormat import dev.inmo.krontab.KrontabTemplate import dev.inmo.krontab.toSchedule -import dev.inmo.krontab.utils.asFlow +import dev.inmo.krontab.utils.asFlowWithDelays +import dev.inmo.krontab.utils.asFlowWithoutDelays +import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.koin.singleWithRandomQualifier +import dev.inmo.micro_utils.pagination.FirstPagePagination +import dev.inmo.micro_utils.pagination.Pagination +import dev.inmo.micro_utils.pagination.firstIndex +import dev.inmo.micro_utils.pagination.lastIndexExclusive import dev.inmo.plagubot.Plugin +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 +import dev.inmo.plaguposter.common.ChatConfig +import dev.inmo.plaguposter.posts.models.PostId +import dev.inmo.plaguposter.posts.repo.ReadPostsRepo 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.send import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +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.extensions.sameChat +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.BotCommand +import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup +import dev.inmo.tgbotapi.utils.row import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.collectIndexed +import kotlinx.coroutines.flow.take import kotlinx.serialization.* import kotlinx.serialization.json.* import org.jetbrains.exposed.sql.Database @@ -16,15 +45,21 @@ import org.koin.core.Koin import org.koin.core.module.Module object Plugin : Plugin { - @Serializable + private const val pageCallbackDataQueryPrefix = "publishing_autoschedule page" + private const val pageCallbackDataQuerySize = 5 + @Serializable internal data class Config( @SerialName("krontab") - val krontabTemplate: KrontabTemplate + val krontabTemplate: KrontabTemplate, + val dateTimeFormat: String = "HH:mm:ss, dd.MM.yyyy" ) { @Transient val krontab by lazy { krontabTemplate.toSchedule() } + + @Transient + val format: DateFormat = DateFormat(dateTimeFormat) } override fun Module.setupDI(database: Database, params: JsonObject) { single { get().decodeFromJsonElement(Config.serializer(), params["timer_trigger"] ?: return@single null) } @@ -35,12 +70,103 @@ object Plugin : Plugin { val publisher = koin.get() val selector = koin.get() val filters = koin.getAll().distinct() - koin.get().krontab.asFlow().subscribeSafelyWithoutExceptions(this) { dateTime -> + val chatConfig = koin.get() + val postsRepo = koin.get() + + koin.getOrNull() ?.apply { + addTemplate( + OfferTemplate( + "Autoschedule buttons", + listOf( + Format( + "/autoschedule_panel" + ) + ), + "Show autoscheduling publishing info" + ) + ) + } + + val krontab = koin.get().krontab + val dateTimeFormat = koin.get().format + krontab.asFlowWithDelays().subscribeSafelyWithoutExceptions(this) { dateTime -> selector.take(now = dateTime).forEach { postId -> if (filters.all { it.check(postId, dateTime) }) { publisher.publish(postId) } } } + + suspend fun buildPage(pagination: Pagination = FirstPagePagination(size = pageCallbackDataQuerySize)): InlineKeyboardMarkup { + return inlineKeyboard { + row { + if (pagination.page > 1) { + dataButton("⬅️", "${pageCallbackDataQueryPrefix}0") + } + if (pagination.page > 0) { + dataButton("◀️", "${pageCallbackDataQueryPrefix}${pagination.page - 1}") + } + + dataButton("\uD83D\uDD04 ${pagination.page}", "${pageCallbackDataQueryPrefix}${pagination.page}") + dataButton("▶️", "${pageCallbackDataQueryPrefix}${pagination.page + 1}") + } + + val selected = mutableListOf() + krontab.asFlowWithoutDelays().take(pagination.lastIndexExclusive).collectIndexed { i, dateTime -> + val postId = selector.take(now = dateTime, exclude = selected).firstOrNull() ?.also { postId -> + if (filters.all { it.check(postId, dateTime) }) { + selected.add(postId) + } else { + return@collectIndexed + } + } + + val post = postsRepo.getFirstMessageInfo(postId ?: return@collectIndexed) + if (i < pagination.firstIndex || post == null) { + return@collectIndexed + } + + row { + urlButton( + dateTime.local.format(dateTimeFormat), + makeLinkToMessage(post.chatId, post.messageId) + ) + } + } + } + } + + onCommand("autoschedule_panel", initialFilter = { it.sameChat(chatConfig.sourceChatId) }) { + val keyboard = buildPage() + + runCatchingSafely { + edit(it, replyMarkup = keyboard) { + +"Your schedule:" + } + }.onFailure { _ -> + send(it.chat, replyMarkup = keyboard) { + +"Your schedule:" + } + } + } + + onMessageDataCallbackQuery( + Regex("^$pageCallbackDataQueryPrefix\\d+"), + initialFilter = { it.message.sameChat(chatConfig.sourceChatId) } + ) { + val page = it.data.removePrefix(pageCallbackDataQueryPrefix).toIntOrNull() ?: let { _ -> + answer(it) + return@onMessageDataCallbackQuery + } + + runCatchingSafely { + edit( + it.message, + replyMarkup = buildPage(Pagination(page, size = pageCallbackDataQuerySize)) + ) + }.onFailure { _ -> + answer(it) + } + } } }