diff --git a/common/src/commonMain/kotlin/DateTimeSerializer.kt b/common/src/commonMain/kotlin/DateTimeSerializer.kt new file mode 100644 index 0000000..4597d3d --- /dev/null +++ b/common/src/commonMain/kotlin/DateTimeSerializer.kt @@ -0,0 +1,7 @@ +package dev.inmo.plaguposter.common + +import com.soywiz.klock.DateTime +import kotlinx.serialization.Serializer + +@Serializer(DateTime::class) +object DateTimeSerializer diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 15df308..718a53c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,8 +4,8 @@ kotlin = "1.7.10" kotlin-serialization = "1.4.0" plagubot = "2.3.0" -tgbotapi = "3.2.0" -microutils = "0.12.6" +tgbotapi = "3.2.1" +microutils = "0.12.11" kslog = "0.5.1" krontab = "0.8.0" diff --git a/posts/src/commonMain/kotlin/models/Post.kt b/posts/src/commonMain/kotlin/models/Post.kt index 0e97632..d624964 100644 --- a/posts/src/commonMain/kotlin/models/Post.kt +++ b/posts/src/commonMain/kotlin/models/Post.kt @@ -1,5 +1,7 @@ package dev.inmo.plaguposter.posts.models +import com.soywiz.klock.DateTime +import dev.inmo.plaguposter.common.DateTimeSerializer import dev.inmo.tgbotapi.types.ChatId import kotlinx.serialization.Serializable @@ -16,5 +18,7 @@ data class NewPost( @Serializable data class RegisteredPost( val id: PostId, + @Serializable(DateTimeSerializer::class) + val created: DateTime, override val content: List ) : Post diff --git a/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt b/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt index 870f8e0..60bfe1f 100644 --- a/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt +++ b/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt @@ -1,5 +1,6 @@ package dev.inmo.plaguposter.posts.repo +import com.soywiz.klock.DateTime import dev.inmo.micro_utils.repos.ReadCRUDRepo import dev.inmo.plaguposter.posts.models.* import dev.inmo.tgbotapi.types.ChatId @@ -7,4 +8,5 @@ import dev.inmo.tgbotapi.types.MessageIdentifier interface ReadPostsRepo : ReadCRUDRepo { suspend fun getIdByChatAndMessage(chatId: ChatId, messageId: MessageIdentifier): PostId? + suspend fun getPostCreationTime(postId: PostId): DateTime? } diff --git a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt index 3897213..91f9462 100644 --- a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt +++ b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt @@ -1,6 +1,7 @@ package dev.inmo.plaguposter.posts.exposed import com.benasher44.uuid.uuid4 +import com.soywiz.klock.DateTime import dev.inmo.micro_utils.repos.KeyValuesRepo import dev.inmo.micro_utils.repos.exposed.AbstractExposedCRUDRepo import dev.inmo.micro_utils.repos.exposed.initTable @@ -21,6 +22,9 @@ class ExposedPostsRepo( tableName = "posts" ) { val idColumn = text("id").clientDefault { uuid4().toString() } + val createdColumn = double("datetime").default(0.0).clientDefault { + DateTime.nowUnix() + } private val contentRepo by lazy { ExposedContentInfoRepo( @@ -38,6 +42,7 @@ class ExposedPostsRepo( val id = PostId(get(idColumn)) return RegisteredPost( id, + DateTime(get(createdColumn)), with(contentRepo) { select { postIdColumn.eq(id.string) }.map { it.asObject @@ -70,6 +75,7 @@ class ExposedPostsRepo( return RegisteredPost( id, + DateTime(get(createdColumn)), with(contentRepo) { select { postIdColumn.eq(id.string) }.map { it.asObject @@ -130,4 +136,8 @@ class ExposedPostsRepo( } ?.let(::PostId) } } + + override suspend fun getPostCreationTime(postId: PostId): DateTime? = transaction(database) { + select { selectById(postId) }.limit(1).firstOrNull() ?.get(createdColumn) ?.let(::DateTime) + } } diff --git a/ratings/gc/build.gradle b/ratings/gc/build.gradle new file mode 100644 index 0000000..ebfe6d4 --- /dev/null +++ b/ratings/gc/build.gradle @@ -0,0 +1,23 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" +} + +apply from: "$mppProjectWithSerializationPresetPath" + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":plaguposter.common") + api project(":plaguposter.ratings") + api libs.krontab + } + } + jvmMain { + dependencies { + } + } + } +} diff --git a/ratings/gc/src/commonMain/kotlin/PackageInfo.kt b/ratings/gc/src/commonMain/kotlin/PackageInfo.kt new file mode 100644 index 0000000..0b3391d --- /dev/null +++ b/ratings/gc/src/commonMain/kotlin/PackageInfo.kt @@ -0,0 +1 @@ +package dev.inmo.plaguposter.ratings.gc diff --git a/ratings/gc/src/jvmMain/kotlin/Plugin.kt b/ratings/gc/src/jvmMain/kotlin/Plugin.kt new file mode 100644 index 0000000..373e984 --- /dev/null +++ b/ratings/gc/src/jvmMain/kotlin/Plugin.kt @@ -0,0 +1,62 @@ +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.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.repos.deleteById +import dev.inmo.micro_utils.repos.id +import dev.inmo.plagubot.Plugin +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.* +import org.jetbrains.exposed.sql.Database +import org.koin.core.Koin +import org.koin.core.module.Module + +object Plugin : Plugin { + @Serializable + internal data class Config( + val autoclear: AutoClearOptions? = null, + val immediateDrop: Rating? = null, + ) { + @Serializable + data class AutoClearOptions( + val rating: Rating, + val autoClearKrontab: KrontabTemplate, + val skipPostAge: Seconds? = null + ) + } + override fun Module.setupDI(database: Database, params: JsonObject) { + single { get().decodeFromJsonElement(Config.serializer(), params["gc"] ?: return@single null) } + } + + override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { + val ratingsRepo = koin.get() + val postsRepo = koin.get() + val config = koin.get() + + config.immediateDrop ?.let { toDrop -> + ratingsRepo.onNewValue.subscribeSafelyWithoutExceptions(this) { + postsRepo.deleteById(it.id) + } + } + config.autoclear ?.let { autoclear -> + autoclear.autoClearKrontab.toSchedule().asFlow().subscribeSafelyWithoutExceptions(scope) { + val dropCreatedBefore = it - (autoclear.skipPostAge ?: 0).seconds + ratingsRepo.getPostsWithRatingLessEq(autoclear.rating).keys.forEach { + if ((postsRepo.getPostCreationTime(it) ?: return@forEach) < dropCreatedBefore) { + postsRepo.deleteById(it) + } + } + } + } + } +} diff --git a/ratings/gc/src/main/AndroidManifest.xml b/ratings/gc/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a9dc65d --- /dev/null +++ b/ratings/gc/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/ratings/selector/src/commonMain/kotlin/models/RatingConfig.kt b/ratings/selector/src/commonMain/kotlin/models/RatingConfig.kt index 0d38475..f024e35 100644 --- a/ratings/selector/src/commonMain/kotlin/models/RatingConfig.kt +++ b/ratings/selector/src/commonMain/kotlin/models/RatingConfig.kt @@ -17,7 +17,8 @@ import kotlin.random.Random data class RatingConfig( val min: Rating?, val max: Rating?, - val prefer: Prefer + val prefer: Prefer, + val otherwise: RatingConfig? = null ) { suspend fun select(repo: RatingsRepo, exclude: List): PostId? { var reversed: Boolean = false @@ -70,7 +71,7 @@ data class RatingConfig( Prefer.Max, Prefer.Min -> posts.firstOrNull() Prefer.Random -> posts.randomOrNull() - } + } ?: otherwise ?.select(repo, exclude) } @Serializable(Prefer.Serializer::class) diff --git a/runner/build.gradle b/runner/build.gradle index 20a93bd..3dbcdb0 100644 --- a/runner/build.gradle +++ b/runner/build.gradle @@ -16,6 +16,7 @@ dependencies { api project(":plaguposter.ratings") api project(":plaguposter.ratings.source") api project(":plaguposter.ratings.selector") + api project(":plaguposter.ratings.gc") api libs.psql } diff --git a/runner/config.json b/runner/config.json index 7af286a..1fd132b 100644 --- a/runner/config.json +++ b/runner/config.json @@ -12,7 +12,8 @@ "dev.inmo.plaguposter.ratings.Plugin", "dev.inmo.plaguposter.ratings.source.Plugin", "dev.inmo.plaguposter.ratings.selector.Plugin", - "dev.inmo.plaguposter.triggers.selector_with_timer.Plugin" + "dev.inmo.plaguposter.triggers.selector_with_timer.Plugin", + "dev.inmo.plaguposter.ratings.gc.Plugin" ], "posts": { "targetChat": 12345678, @@ -66,5 +67,12 @@ }, "timer_trigger": { "krontab": "0 30 2/4 * *" + }, + "gc": { + "autoclear": { + "rating": -2, + "autoClearKrontab": "0 0 0 * *", + "skipPostAge": 86400 + } } } diff --git a/settings.gradle b/settings.gradle index c7199d7..6b4cfad 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ String[] includes = [ ":ratings", ":ratings:source", ":ratings:selector", + ":ratings:gc", ":triggers:command", ":triggers:selector_with_timer", // ":settings",