diff --git a/common/src/commonMain/kotlin/Symbols.kt b/common/src/commonMain/kotlin/Symbols.kt index 903505c..28bb130 100644 --- a/common/src/commonMain/kotlin/Symbols.kt +++ b/common/src/commonMain/kotlin/Symbols.kt @@ -1,4 +1,4 @@ package dev.inmo.plaguposter.common -const val SuccessfulSymbol = "✓" +const val SuccessfulSymbol = "✅" const val UnsuccessfulSymbol = "❌" diff --git a/gradle.properties b/gradle.properties index 31a7499..dbcb044 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,5 @@ kotlin.code.style=official +org.gradle.jvmargs=-Xmx1024m org.gradle.parallel=true kotlin.js.generate.externals=true kotlin.incremental=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index af515ac..62b9baf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "1.7.10" kotlin-serialization = "1.4.0" -plagubot = "2.2.0" +plagubot = "2.3.0" tgbotapi = "3.2.0" microutils = "0.12.6" kslog = "0.5.1" diff --git a/posts/src/commonMain/kotlin/repo/WritePostsRepo.kt b/posts/src/commonMain/kotlin/repo/WritePostsRepo.kt index c9f1be4..0731c40 100644 --- a/posts/src/commonMain/kotlin/repo/WritePostsRepo.kt +++ b/posts/src/commonMain/kotlin/repo/WritePostsRepo.kt @@ -3,5 +3,8 @@ package dev.inmo.plaguposter.posts.repo import dev.inmo.micro_utils.repos.ReadCRUDRepo import dev.inmo.micro_utils.repos.WriteCRUDRepo import dev.inmo.plaguposter.posts.models.* +import kotlinx.coroutines.flow.Flow -interface WritePostsRepo : WriteCRUDRepo +interface WritePostsRepo : WriteCRUDRepo { + val removedPostsFlow: Flow +} diff --git a/posts/src/jvmMain/kotlin/Plugin.kt b/posts/src/jvmMain/kotlin/Plugin.kt index 3ef45b2..41d8ea6 100644 --- a/posts/src/jvmMain/kotlin/Plugin.kt +++ b/posts/src/jvmMain/kotlin/Plugin.kt @@ -2,20 +2,37 @@ package dev.inmo.plaguposter.posts import dev.inmo.kslog.common.logger import dev.inmo.kslog.common.w +import dev.inmo.micro_utils.coroutines.runCatchingSafely +import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.repos.deleteById import dev.inmo.plagubot.Plugin +import dev.inmo.plaguposter.common.SuccessfulSymbol +import dev.inmo.plaguposter.common.UnsuccessfulSymbol import dev.inmo.plaguposter.posts.exposed.ExposedPostsRepo import dev.inmo.plaguposter.posts.models.ChatConfig import dev.inmo.plaguposter.posts.repo.* import dev.inmo.plaguposter.posts.sending.PostPublisher +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.behaviour_builder.BehaviourContext +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.message.textsources.regular import kotlinx.serialization.SerialName 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.dsl.binds object Plugin : Plugin { + @Serializable + data class Config( + val chats: ChatConfig, + val autoRemoveMessages: Boolean = true + ) override fun Module.setupDI(database: Database, params: JsonObject) { val configJson = params["posts"] ?: this@Plugin.let { it.logger.w { @@ -23,7 +40,8 @@ object Plugin : Plugin { } return } - single { get().decodeFromJsonElement(ChatConfig.serializer(), configJson) } + single { get().decodeFromJsonElement(Config.serializer(), configJson) } + single { get().chats } single { ExposedPostsRepo(database) } binds arrayOf( PostsRepo::class, ReadPostsRepo::class, @@ -34,4 +52,39 @@ object Plugin : Plugin { PostPublisher(get(), get(), config.cacheChatId, config.targetChatId) } } + + override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { + val postsRepo = koin.get() + val config = koin.get() + + if (config.autoRemoveMessages) { + postsRepo.removedPostsFlow.subscribeSafelyWithoutExceptions(this) { + it.content.forEach { + runCatchingSafely { + delete(it.chatId, it.messageId) + } + } + } + } + + onCommand("delete_post", requireOnlyCommandInMessage = true) { + val messageInReply = it.replyTo ?: run { + reply(it, "Reply some message of post to delete it") + return@onCommand + } + + val postId = postsRepo.getIdByChatAndMessage(messageInReply.chat.id, messageInReply.messageId) ?: run { + reply(it, "Unable to find post id by message") + return@onCommand + } + + postsRepo.deleteById(postId) + + if (postsRepo.contains(postId)) { + edit(it, it.content.textSources + regular(UnsuccessfulSymbol)) + } else { + edit(it, it.content.textSources + regular(SuccessfulSymbol)) + } + } + } } diff --git a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt index f941075..3897213 100644 --- a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt +++ b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt @@ -8,6 +8,7 @@ import dev.inmo.plaguposter.posts.models.* import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.MessageIdentifier +import kotlinx.coroutines.flow.* import kotlinx.serialization.json.Json import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.InsertStatement @@ -45,6 +46,9 @@ class ExposedPostsRepo( ) } + private val _removedPostsFlow = MutableSharedFlow() + override val removedPostsFlow: Flow = _removedPostsFlow.asSharedFlow() + init { initTable() } @@ -93,24 +97,29 @@ class ExposedPostsRepo( override suspend fun deleteById(ids: List) { onBeforeDelete(ids) + val posts = ids.mapNotNull { + getById(it) + }.associateBy { it.id } + val existsIds = posts.keys.toList() transaction(db = database) { val deleted = deleteWhere(null, null) { - selectByIds(ids) + selectByIds(existsIds) } with(contentRepo) { deleteWhere { - postIdColumn.inList(ids.map { it.string }) + postIdColumn.inList(existsIds.map { it.string }) } } - if (deleted == ids.size) { - ids + if (deleted == existsIds.size) { + existsIds } else { - ids.filter { + existsIds.filter { select { selectById(it) }.limit(1).none() } } }.forEach { _deletedObjectsIdsFlow.emit(it) + _removedPostsFlow.emit(posts[it] ?: return@forEach) } } diff --git a/posts_registrar/src/jvmMain/kotlin/Plugin.kt b/posts_registrar/src/jvmMain/kotlin/Plugin.kt index 0500fca..d4fd7c8 100644 --- a/posts_registrar/src/jvmMain/kotlin/Plugin.kt +++ b/posts_registrar/src/jvmMain/kotlin/Plugin.kt @@ -1,19 +1,17 @@ package dev.inmo.plaguposter.posts.registrar -import com.benasher44.uuid.uuid4 -import dev.inmo.kslog.common.logger -import dev.inmo.kslog.common.w -import dev.inmo.micro_utils.coroutines.firstOf -import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.* import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.repos.create +import dev.inmo.micro_utils.repos.deleteById import dev.inmo.plagubot.Plugin -import dev.inmo.plaguposter.common.FirstSourceIsCommandsFilter +import dev.inmo.plaguposter.common.* import dev.inmo.plaguposter.posts.models.* import dev.inmo.plaguposter.posts.registrar.state.RegistrationState import dev.inmo.plaguposter.posts.repo.PostsRepo 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.BehaviourContextWithFSM import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.* @@ -30,6 +28,7 @@ import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.content.MessageContent import dev.inmo.tgbotapi.types.message.content.TextContent +import dev.inmo.tgbotapi.types.message.textsources.regular import kotlinx.coroutines.flow.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/ratings/src/jvmMain/kotlin/Plugin.kt b/ratings/src/jvmMain/kotlin/Plugin.kt index 9955074..f603605 100644 --- a/ratings/src/jvmMain/kotlin/Plugin.kt +++ b/ratings/src/jvmMain/kotlin/Plugin.kt @@ -1,20 +1,32 @@ package dev.inmo.plaguposter.ratings +import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.repos.unset import dev.inmo.plagubot.Plugin import dev.inmo.plaguposter.posts.exposed.ExposedPostsRepo +import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.ratings.exposed.ExposedRatingsRepo import dev.inmo.plaguposter.ratings.repo.* +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import kotlinx.serialization.json.* import org.jetbrains.exposed.sql.Database +import org.koin.core.Koin import org.koin.core.module.Module import org.koin.dsl.binds object Plugin : Plugin { override fun Module.setupDI(database: Database, params: JsonObject) { - single { ExposedRatingsRepo(database, get().idColumn) } binds arrayOf( + single { ExposedRatingsRepo(database) } binds arrayOf( RatingsRepo::class, ReadRatingsRepo::class, WriteRatingsRepo::class, ) } + + override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { + val ratingsRepo = koin.get() + koin.get().deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(this) { + ratingsRepo.unset(it) + } + } } diff --git a/ratings/src/jvmMain/kotlin/exposed/ExposedRatingsRepo.kt b/ratings/src/jvmMain/kotlin/exposed/ExposedRatingsRepo.kt index a0877b6..58f2a6c 100644 --- a/ratings/src/jvmMain/kotlin/exposed/ExposedRatingsRepo.kt +++ b/ratings/src/jvmMain/kotlin/exposed/ExposedRatingsRepo.kt @@ -10,11 +10,10 @@ import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.Database class ExposedRatingsRepo( - database: Database, - postIdColumnReference: Column + database: Database ) : RatingsRepo, KeyValueRepo by ExposedKeyValueRepo( database, - { text("post_id") references postIdColumnReference }, + { text("post_id") }, { double("rating") }, "ratings" ).withMapper( diff --git a/runner/Dockerfile b/runner/Dockerfile new file mode 100644 index 0000000..04ac022 --- /dev/null +++ b/runner/Dockerfile @@ -0,0 +1,7 @@ +FROM adoptopenjdk/openjdk11 + +USER 1000 + +ENTRYPOINT ["/plaguposter.runner/bin/plaguposter.runner", "/config.json"] + +ADD ./build/distributions/plaguposter.runner.tar / diff --git a/runner/build.gradle b/runner/build.gradle index 550251b..b678689 100644 --- a/runner/build.gradle +++ b/runner/build.gradle @@ -4,6 +4,8 @@ plugins { id 'application' } +project.version = null + dependencies { implementation libs.kotlin api libs.plagubot.bot diff --git a/runner/deploy.sh b/runner/deploy.sh new file mode 100755 index 0000000..75e7e41 --- /dev/null +++ b/runner/deploy.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +function send_notification() { + echo "$1" +} + +function assert_success() { + "${@}" + local status=${?} + if [ ${status} -ne 0 ]; then + send_notification "### Error ${status} at: ${BASH_LINENO[*]} ###" + exit ${status} + fi +} + +app=plaguposter +version=0.0.1 +server=docker.inmo.dev + +assert_success ../gradlew build +# scp ./build/distributions/AutoPostTestTelegramBot-1.0.0.zip ./config.json developer@insanusmokrassar.dev:/tmp/ +assert_success sudo docker build -t $app:"$version" . +assert_success sudo docker tag $app:"$version" $server/$app:$version +assert_success sudo docker tag $app:"$version" $server/$app:latest +assert_success sudo docker push $server/$app:$version +assert_success sudo docker push $server/$app:latest