diff --git a/common/build.gradle b/common/build.gradle index 36f733d..a210d1d 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -17,6 +17,8 @@ kotlin { } jvmMain { dependencies { + api libs.microutils.repos.exposed + api libs.plagubot.plugin } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 067737b..122871e 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.1.0" +plagubot = "2.1.1" tgbotapi = "3.1.1" microutils = "0.12.2" kslog = "0.5.1" diff --git a/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt b/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt index 83c9200..870f8e0 100644 --- a/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt +++ b/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt @@ -2,5 +2,9 @@ package dev.inmo.plaguposter.posts.repo import dev.inmo.micro_utils.repos.ReadCRUDRepo import dev.inmo.plaguposter.posts.models.* +import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.MessageIdentifier -interface ReadPostsRepo : ReadCRUDRepo +interface ReadPostsRepo : ReadCRUDRepo { + suspend fun getIdByChatAndMessage(chatId: ChatId, messageId: MessageIdentifier): PostId? +} diff --git a/posts/src/commonMain/kotlin/sending/PostPublisher.kt b/posts/src/commonMain/kotlin/sending/PostPublisher.kt index 3419807..74ee48a 100644 --- a/posts/src/commonMain/kotlin/sending/PostPublisher.kt +++ b/posts/src/commonMain/kotlin/sending/PostPublisher.kt @@ -2,6 +2,7 @@ package dev.inmo.plaguposter.posts.sending import dev.inmo.kslog.common.logger import dev.inmo.kslog.common.w +import dev.inmo.micro_utils.repos.deleteById import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.tgbotapi.bot.TelegramBot @@ -14,12 +15,13 @@ import dev.inmo.tgbotapi.types.message.content.MediaGroupContent class PostPublisher( private val bot: TelegramBot, - private val repo: PostsRepo, + private val postsRepo: PostsRepo, private val cachingChatId: ChatId, - private val targetChatId: ChatId + private val targetChatId: ChatId, + private val deleteAfterPosting: Boolean = true ) { suspend fun publish(postId: PostId) { - val messagesInfo = repo.getById(postId) ?: let { + val messagesInfo = postsRepo.getById(postId) ?: let { logger.w { "Unable to get post with id $postId for publishing" } return } @@ -56,5 +58,9 @@ class PostPublisher( } } + if (deleteAfterPosting) { + postsRepo.deleteById(postId) + } + } } diff --git a/posts/src/jvmMain/kotlin/Plugin.kt b/posts/src/jvmMain/kotlin/Plugin.kt index 4a43961..4bc3908 100644 --- a/posts/src/jvmMain/kotlin/Plugin.kt +++ b/posts/src/jvmMain/kotlin/Plugin.kt @@ -30,7 +30,7 @@ object Plugin : Plugin { return } single { get().decodeFromJsonElement(Config.serializer(), configJson) } - single { ExposedPostsRepo(database, get()) } + single { ExposedPostsRepo(database) } single { val config = get() PostPublisher(get(), get(), config.cacheChatId, config.targetChatId) diff --git a/posts/src/jvmMain/kotlin/exposed/ExposedContentInfoRepo.kt b/posts/src/jvmMain/kotlin/exposed/ExposedContentInfoRepo.kt index b31f6a6..7738806 100644 --- a/posts/src/jvmMain/kotlin/exposed/ExposedContentInfoRepo.kt +++ b/posts/src/jvmMain/kotlin/exposed/ExposedContentInfoRepo.kt @@ -11,7 +11,7 @@ internal class ExposedContentInfoRepo( override val database: Database, postIdColumnReference: Column ) : ExposedRepo, Table(name = "posts_content") { - val postIdColumn = (text("post_id") references postIdColumnReference).index() + val postIdColumn = text("post_id").references(postIdColumnReference, ReferenceOption.CASCADE, ReferenceOption.CASCADE) val chatIdColumn = long("chat_id") val messageIdColumn = long("message_id") val groupColumn = text("group").nullable() diff --git a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt index 7243e30..f941075 100644 --- a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt +++ b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt @@ -6,14 +6,16 @@ import dev.inmo.micro_utils.repos.exposed.AbstractExposedCRUDRepo import dev.inmo.micro_utils.repos.exposed.initTable 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.serialization.json.Json import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.InsertStatement import org.jetbrains.exposed.sql.statements.UpdateStatement +import org.jetbrains.exposed.sql.transactions.transaction class ExposedPostsRepo( - override val database: Database, - json: Json + override val database: Database ) : PostsRepo, AbstractExposedCRUDRepo( tableName = "posts" ) { @@ -88,4 +90,35 @@ class ExposedPostsRepo( } override fun insert(value: NewPost, it: InsertStatement) {} + + override suspend fun deleteById(ids: List) { + onBeforeDelete(ids) + transaction(db = database) { + val deleted = deleteWhere(null, null) { + selectByIds(ids) + } + with(contentRepo) { + deleteWhere { + postIdColumn.inList(ids.map { it.string }) + } + } + if (deleted == ids.size) { + ids + } else { + ids.filter { + select { selectById(it) }.limit(1).none() + } + } + }.forEach { + _deletedObjectsIdsFlow.emit(it) + } + } + + override suspend fun getIdByChatAndMessage(chatId: ChatId, messageId: MessageIdentifier): PostId? { + return transaction(database) { + with(contentRepo) { + select { chatIdColumn.eq(chatId.chatId).and(messageIdColumn.eq(messageId)) }.limit(1).firstOrNull() ?.get(postIdColumn) + } ?.let(::PostId) + } + } } diff --git a/runner/build.gradle b/runner/build.gradle index 4343e2c..0b00030 100644 --- a/runner/build.gradle +++ b/runner/build.gradle @@ -10,6 +10,7 @@ dependencies { api project(":plaguposter.posts") api project(":plaguposter.posts_registrar") + api project(":plaguposter.triggers.command") } application { diff --git a/settings.gradle b/settings.gradle index 84e05ba..60105c3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ String[] includes = [ ":common", ":posts", ":posts_registrar", + ":triggers:command", ":runner" ] diff --git a/triggers/command/build.gradle b/triggers/command/build.gradle new file mode 100644 index 0000000..779ca41 --- /dev/null +++ b/triggers/command/build.gradle @@ -0,0 +1,22 @@ +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.posts") + } + } + jvmMain { + dependencies { + } + } + } +} diff --git a/triggers/command/src/commonMain/kotlin/PackageInfo.kt b/triggers/command/src/commonMain/kotlin/PackageInfo.kt new file mode 100644 index 0000000..c0d364a --- /dev/null +++ b/triggers/command/src/commonMain/kotlin/PackageInfo.kt @@ -0,0 +1 @@ +package dev.inmo.plaguposter.triggers.command diff --git a/triggers/command/src/jvmMain/kotlin/Plugin.kt b/triggers/command/src/jvmMain/kotlin/Plugin.kt new file mode 100644 index 0000000..daa07e5 --- /dev/null +++ b/triggers/command/src/jvmMain/kotlin/Plugin.kt @@ -0,0 +1,66 @@ +package dev.inmo.plaguposter.triggers.command + +import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.fsm.common.State +import dev.inmo.plagubot.Plugin +import dev.inmo.plaguposter.posts.repo.PostsRepo +import dev.inmo.plaguposter.posts.sending.PostPublisher +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.waitTextMessage +import dev.inmo.tgbotapi.extensions.behaviour_builder.strictlyOn +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand +import dev.inmo.tgbotapi.extensions.utils.botCommandTextSourceOrNull +import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull +import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.MessageIdentifier +import kotlinx.coroutines.flow.filter +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 + private data class PublishState( + override val context: ChatId, + val sourceMessageId: MessageIdentifier, + val messageInReply: MessageIdentifier + ) : State + override fun Module.setupDI(database: Database, params: JsonObject) { + } + + override suspend fun BehaviourContextWithFSM.setupBotPlugin(koin: Koin) { + val postsRepo = koin.get() + val publisher = koin.get() + strictlyOn { state: PublishState -> + + null + } + + onCommand("publish_post") { + val messageInReply = it.replyTo ?.contentMessageOrNull() ?: let { _ -> + reply(it, "You should reply some message of post to trigger it for posting") + + return@onCommand + } + val postId = postsRepo.getIdByChatAndMessage(messageInReply.chat.id, messageInReply.messageId) + if (postId == null) { + reply( + it, + "Unable to find any post related to the message in reply" + ) + + return@onCommand + } + + publisher.publish(postId) + reply( + it, + "Successfully triggered publishing" + ) + } + } +} diff --git a/triggers/command/src/main/AndroidManifest.xml b/triggers/command/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a1a4db3 --- /dev/null +++ b/triggers/command/src/main/AndroidManifest.xml @@ -0,0 +1 @@ +