diff --git a/build.gradle b/build.gradle index 80d7b89..723a7c4 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,7 @@ allprojects { mavenLocal() mavenCentral() google() + maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" } } } diff --git a/common/src/commonMain/kotlin/ShortMessageInfo.kt b/common/src/commonMain/kotlin/ShortMessageInfo.kt index 2426f06..586f893 100644 --- a/common/src/commonMain/kotlin/ShortMessageInfo.kt +++ b/common/src/commonMain/kotlin/ShortMessageInfo.kt @@ -1,13 +1,16 @@ package dev.inmo.plaguposter.common import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer +import dev.inmo.tgbotapi.types.IdChatIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.message.abstracts.Message import kotlinx.serialization.Serializable @Serializable data class ShortMessageInfo( - val chatId: ChatId, + @Serializable(FullChatIdentifierSerializer::class) + val chatId: IdChatIdentifier, val messageId: MessageIdentifier ) diff --git a/common/src/jvmMain/kotlin/CommonPlugin.kt b/common/src/jvmMain/kotlin/CommonPlugin.kt new file mode 100644 index 0000000..e5de8bd --- /dev/null +++ b/common/src/jvmMain/kotlin/CommonPlugin.kt @@ -0,0 +1,20 @@ +package dev.inmo.plaguposter.common + +import dev.inmo.kslog.common.i +import dev.inmo.kslog.common.iS +import dev.inmo.kslog.common.logger +import dev.inmo.plagubot.Plugin +import dev.inmo.tgbotapi.extensions.api.chat.get.getChat +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +import org.koin.core.Koin + +object CommonPlugin : Plugin { + private val Log = logger + override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { + val config = koin.get() + + Log.iS { "Target chat info: ${getChat(config.targetChatId)}" } + Log.iS { "Source chat info: ${getChat(config.sourceChatId)}" } + Log.iS { "Cache chat info: ${getChat(config.cacheChatId)}" } + } +} diff --git a/gradle.properties b/gradle.properties index 09448b3..0693423 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,5 +10,5 @@ android.enableJetifier=true # Project data group=dev.inmo -version=0.0.5 -android_code_version=5 +version=0.0.6 +android_code_version=6 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 419af72..bacd557 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,17 @@ [versions] -kotlin = "1.7.20" +kotlin = "1.7.21" kotlin-serialization = "1.4.1" -plagubot = "2.4.0" -tgbotapi = "3.3.0" -microutils = "0.13.1" -kslog = "0.5.2" -krontab = "0.8.1" -tgbotapi-libraries = "0.5.6" -plagubot-plugins = "0.5.0" +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" + +dokka = "1.7.20" psql = "42.5.0" @@ -26,6 +28,7 @@ tgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "tgbotapi" } plagubot-plugin = { module = "dev.inmo:plagubot.plugin", version.ref = "plagubot" } plagubot-bot = { module = "dev.inmo:plagubot.bot", version.ref = "plagubot" } plagubot-plugins-inline-queries = { module = "dev.inmo:plagubot.plugins.inline.queries", version.ref = "plagubot-plugins" } +plagubot-plugins-inline-buttons = { module = "dev.inmo:plagubot.plugins.inline.buttons", version.ref = "plagubot-plugins" } microutils-repos-common = { module = "dev.inmo:micro_utils.repos.common", version.ref = "microutils" } microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" } microutils-repos-cache = { module = "dev.inmo:micro_utils.repos.cache", version.ref = "microutils" } @@ -39,7 +42,7 @@ psql = { module = "org.postgresql:postgresql", version.ref = "psql" } kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } -kotlin-dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "kotlin" } +kotlin-dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } [plugins] diff --git a/inlines/src/jvmMain/kotlin/Plugin.kt b/inlines/src/jvmMain/kotlin/Plugin.kt index 0b94aa7..a0a37bb 100644 --- a/inlines/src/jvmMain/kotlin/Plugin.kt +++ b/inlines/src/jvmMain/kotlin/Plugin.kt @@ -4,12 +4,20 @@ import dev.inmo.kslog.common.TagLogger import dev.inmo.kslog.common.w import dev.inmo.plagubot.Plugin import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +import kotlinx.serialization.json.JsonObject +import org.jetbrains.exposed.sql.Database import org.koin.core.Koin +import org.koin.core.module.Module private val actualPlugin = dev.inmo.plagubot.plugins.inline.queries.Plugin object Plugin : Plugin by actualPlugin { private val log = TagLogger("InlinePlugin") + + override fun Module.setupDI(database: Database, params: JsonObject) { + single { actualPlugin } + } + override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { log.w { "Built-in inline plugin has been deprecated. Use \"${actualPlugin::class.qualifiedName}\" instead" diff --git a/posts/panel/build.gradle b/posts/panel/build.gradle index eda422f..32f902f 100644 --- a/posts/panel/build.gradle +++ b/posts/panel/build.gradle @@ -14,5 +14,10 @@ kotlin { api libs.microutils.koin } } + jvmMain { + dependencies { + api libs.plagubot.plugins.inline.queries + } + } } } diff --git a/posts/panel/src/commonMain/kotlin/PanelButtonBuilder.kt b/posts/panel/src/commonMain/kotlin/PanelButtonBuilder.kt index 74960f0..85c3296 100644 --- a/posts/panel/src/commonMain/kotlin/PanelButtonBuilder.kt +++ b/posts/panel/src/commonMain/kotlin/PanelButtonBuilder.kt @@ -3,6 +3,21 @@ package dev.inmo.plaguposter.posts.panel import dev.inmo.plaguposter.posts.models.RegisteredPost import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.InlineKeyboardButton -fun interface PanelButtonBuilder { +interface PanelButtonBuilder { + val weight: Int suspend fun buildButton(post: RegisteredPost): InlineKeyboardButton? + + class Default(override val weight: Int = 0, private val block: suspend (RegisteredPost) -> InlineKeyboardButton?) : PanelButtonBuilder { + override suspend fun buildButton(post: RegisteredPost): InlineKeyboardButton? = block(post) + } + + companion object { + operator fun invoke(block: suspend (RegisteredPost) -> InlineKeyboardButton?) = Default( + block = block + ) + operator fun invoke(weight: Int, block: suspend (RegisteredPost) -> InlineKeyboardButton?) = Default( + weight, + block + ) + } } diff --git a/posts/panel/src/commonMain/kotlin/PanelButtonsAPI.kt b/posts/panel/src/commonMain/kotlin/PanelButtonsAPI.kt index 2f8c11d..7e66c2a 100644 --- a/posts/panel/src/commonMain/kotlin/PanelButtonsAPI.kt +++ b/posts/panel/src/commonMain/kotlin/PanelButtonsAPI.kt @@ -5,14 +5,14 @@ import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineK import kotlinx.coroutines.flow.MutableSharedFlow class PanelButtonsAPI( - private val preset: List, + private val preset: Map>, private val rootPanelButtonText: String ) { - private val _buttons = mutableSetOf().also { - it.addAll(preset) + private val _buttonsMap = mutableMapOf>().also { + it.putAll(preset.map { it.key to it.value.toMutableList() }) } internal val buttonsBuilders: List - get() = _buttons.toList() + get() = _buttonsMap.toList().sortedBy { it.first }.flatMap { it.second } internal val forceRefreshFlow = MutableSharedFlow() val RootPanelButtonBuilder = PanelButtonBuilder { @@ -22,8 +22,13 @@ class PanelButtonsAPI( ) } - fun add(button: PanelButtonBuilder) = _buttons.add(button) - fun remove(button: PanelButtonBuilder) = _buttons.remove(button) + fun add(button: PanelButtonBuilder, weight: Int = button.weight) = _buttonsMap.getOrPut(weight) { mutableListOf() }.add(button) + fun remove(button: PanelButtonBuilder) = _buttonsMap.mapNotNull { (k, v) -> + v.remove(button) + k.takeIf { v.isEmpty() } + }.forEach { + _buttonsMap.remove(it) + } suspend fun forceRefresh(postId: PostId) { forceRefreshFlow.emit(postId) } diff --git a/posts/panel/src/jvmMain/kotlin/Plugin.kt b/posts/panel/src/jvmMain/kotlin/Plugin.kt index b2676fa..a267893 100644 --- a/posts/panel/src/jvmMain/kotlin/Plugin.kt +++ b/posts/panel/src/jvmMain/kotlin/Plugin.kt @@ -5,9 +5,13 @@ import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.koin.getAllDistinct import dev.inmo.micro_utils.repos.deleteById +import dev.inmo.micro_utils.repos.id import dev.inmo.micro_utils.repos.set +import dev.inmo.micro_utils.repos.unset +import dev.inmo.micro_utils.repos.value import dev.inmo.plagubot.Plugin import dev.inmo.plaguposter.common.ChatConfig +import dev.inmo.plaguposter.common.UnsuccessfulSymbol import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.panel.repos.PostsMessages import dev.inmo.plaguposter.posts.repo.PostsRepo @@ -15,18 +19,23 @@ import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.delete import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.edit.text.editMessageText +import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.command import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery 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.types.ChatId +import dev.inmo.tgbotapi.types.IdChatIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.types.message.ParseMode +import dev.inmo.tgbotapi.utils.bold +import dev.inmo.tgbotapi.utils.buildEntities +import dev.inmo.tgbotapi.utils.italic import kotlinx.coroutines.flow.first import kotlinx.serialization.Serializable import kotlinx.serialization.json.* @@ -69,9 +78,13 @@ object Plugin : Plugin { } ) PanelButtonsAPI( - getAllDistinct() + builtInButtons, + emptyMap(), config.rootButtonText - ) + ).apply { + (getAllDistinct() + builtInButtons).forEach { + add(it) + } + } } } @@ -108,7 +121,7 @@ object Plugin : Plugin { suspend fun refreshPostMessage( postId: PostId, - chatId: ChatId, + chatId: IdChatIdentifier, messageId: MessageIdentifier ) { val post = postsRepo.getById(postId) ?: return @@ -183,5 +196,59 @@ object Plugin : Plugin { val (chatId, messageId) = postsMessages.get(it) ?: return@subscribeSafelyWithoutExceptions refreshPostMessage(it, chatId, messageId) } + + command("panel") { + val reply = it.replyTo + + if (reply == null) { + runCatchingSafely { + edit( + it, + it.content.textSources + buildEntities { + +"${UnsuccessfulSymbol}\n" + bold("Result") + ": " + italic("You should reply post content to trigger panel retrieving") + } + ) + }.onFailure { _ -> + reply( + it, + buildEntities { + bold("Result") + ": " + italic("You should reply post content to trigger panel retrieving") + } + ) + } + + return@command + } + + val postId = postsRepo.getIdByChatAndMessage(reply.chat.id, reply.messageId) + if (postId == null) { + runCatchingSafely { + edit( + it, + it.content.textSources + buildEntities { + +"${UnsuccessfulSymbol}\n" + bold("Result") + ": " + italic("Unable to find post related to replied message") + } + ) + }.onFailure { _ -> + reply( + it, + buildEntities { + bold("Result") + ": " + italic("Unable to find post related to replied message") + } + ) + } + + return@command + } + + postsMessages.get(postId) ?.let { + runCatchingSafely { delete(it.id, it.value) } + postsMessages.unset(postId) + } + + refreshPostMessage(postId, it.chat.id, it.messageId) + + postsMessages.set(postId, it.chat.id to it.messageId) + } } } diff --git a/posts/panel/src/jvmMain/kotlin/repos/PostsMessages.kt b/posts/panel/src/jvmMain/kotlin/repos/PostsMessages.kt index 984a6e2..64015aa 100644 --- a/posts/panel/src/jvmMain/kotlin/repos/PostsMessages.kt +++ b/posts/panel/src/jvmMain/kotlin/repos/PostsMessages.kt @@ -5,18 +5,20 @@ import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo import dev.inmo.micro_utils.repos.mappers.withMapper import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer +import dev.inmo.tgbotapi.types.IdChatIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier import kotlinx.serialization.builtins.PairSerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.json.Json import org.jetbrains.exposed.sql.Database -private val ChatIdToMessageSerializer = PairSerializer(ChatId.serializer(), MessageIdentifier.serializer()) +private val ChatIdToMessageSerializer = PairSerializer(FullChatIdentifierSerializer, MessageIdentifier.serializer()) fun PostsMessages( database: Database, json: Json -): KeyValueRepo> = ExposedKeyValueRepo( +): KeyValueRepo> = ExposedKeyValueRepo( database, { text("postId") }, { text("chatToMessage") }, @@ -25,5 +27,5 @@ fun PostsMessages( { string }, { json.encodeToString(ChatIdToMessageSerializer, this) }, { PostId(this) }, - { json.decodeFromString(ChatIdToMessageSerializer, this) } + { json.decodeFromString(ChatIdToMessageSerializer, this).let { (it.first as IdChatIdentifier) to it.second } } ) diff --git a/posts/src/commonMain/kotlin/models/PostContentInfo.kt b/posts/src/commonMain/kotlin/models/PostContentInfo.kt index 995dfd1..583290e 100644 --- a/posts/src/commonMain/kotlin/models/PostContentInfo.kt +++ b/posts/src/commonMain/kotlin/models/PostContentInfo.kt @@ -1,25 +1,39 @@ package dev.inmo.plaguposter.posts.models -import dev.inmo.tgbotapi.extensions.utils.mediaGroupMessageOrNull +import dev.inmo.tgbotapi.extensions.utils.possiblyMediaGroupMessageOrNull import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer +import dev.inmo.tgbotapi.types.IdChatIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage -import dev.inmo.tgbotapi.types.message.content.MessageContent +import dev.inmo.tgbotapi.types.message.content.MediaGroupContent import kotlinx.serialization.Serializable @Serializable data class PostContentInfo( - val chatId: ChatId, + @Serializable(FullChatIdentifierSerializer::class) + val chatId: IdChatIdentifier, val messageId: MessageIdentifier, val group: String?, val order: Int ) { companion object { - fun fromMessage(message: ContentMessage<*>, order: Int) = PostContentInfo( + private fun fromMessage(message: ContentMessage<*>, order: Int) = PostContentInfo( message.chat.id, message.messageId, - message.mediaGroupMessageOrNull() ?.mediaGroupId, + message.possiblyMediaGroupMessageOrNull() ?.mediaGroupId, order ) + fun fromMessage(message: ContentMessage<*>): List { + val content = message.content + + return if (content is MediaGroupContent<*>) { + content.group.mapIndexed { i, it -> + fromMessage(it.sourceMessage, i) + } + } else { + listOf(fromMessage(message, 0)) + } + } } } diff --git a/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt b/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt index 60bfe1f..b4391a0 100644 --- a/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt +++ b/posts/src/commonMain/kotlin/repo/ReadPostsRepo.kt @@ -4,9 +4,10 @@ import com.soywiz.klock.DateTime import dev.inmo.micro_utils.repos.ReadCRUDRepo import dev.inmo.plaguposter.posts.models.* import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.IdChatIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier interface ReadPostsRepo : ReadCRUDRepo { - suspend fun getIdByChatAndMessage(chatId: ChatId, messageId: MessageIdentifier): PostId? + suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId? suspend fun getPostCreationTime(postId: PostId): DateTime? } diff --git a/posts/src/commonMain/kotlin/sending/PostPublisher.kt b/posts/src/commonMain/kotlin/sending/PostPublisher.kt index 1521c55..b603a3d 100644 --- a/posts/src/commonMain/kotlin/sending/PostPublisher.kt +++ b/posts/src/commonMain/kotlin/sending/PostPublisher.kt @@ -12,6 +12,7 @@ import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.message.content.MediaGroupContent +import dev.inmo.tgbotapi.types.message.content.MediaGroupPartContent class PostPublisher( private val bot: TelegramBot, @@ -37,14 +38,26 @@ class PostPublisher( sortedMessagesContents.forEach { (_, contents) -> contents.singleOrNull() ?.also { - bot.copyMessage(targetChatId, it.chatId, it.messageId) + runCatching { + bot.copyMessage(targetChatId, it.chatId, it.messageId) + }.onFailure { _ -> + runCatching { + bot.forwardMessage( + it.chatId, + targetChatId, + it.messageId + ) + }.onSuccess { + bot.copyMessage(targetChatId, it) + } + } return@forEach } val resultContents = contents.mapNotNull { it.order to (bot.forwardMessage(toChatId = cachingChatId, fromChatId = it.chatId, messageId = it.messageId).contentMessageOrNull() ?: return@mapNotNull null) - }.sortedBy { it.first }.mapNotNull { (_, it) -> - it.withContentOrNull() ?: null.also { _ -> - bot.copyMessage(targetChatId, it) + }.sortedBy { it.first }.mapNotNull { (_, forwardedMessage) -> + forwardedMessage.withContentOrNull() ?: null.also { _ -> + bot.copyMessage(targetChatId, forwardedMessage) } } resultContents.singleOrNull() ?.also { diff --git a/posts/src/jvmMain/kotlin/exposed/ExposedContentInfoRepo.kt b/posts/src/jvmMain/kotlin/exposed/ExposedContentInfoRepo.kt index 7738806..52e9c70 100644 --- a/posts/src/jvmMain/kotlin/exposed/ExposedContentInfoRepo.kt +++ b/posts/src/jvmMain/kotlin/exposed/ExposedContentInfoRepo.kt @@ -5,6 +5,7 @@ import dev.inmo.micro_utils.repos.KeyValuesRepo import dev.inmo.micro_utils.repos.exposed.* import dev.inmo.plaguposter.posts.models.* import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.IdChatIdentifier import org.jetbrains.exposed.sql.* internal class ExposedContentInfoRepo( @@ -13,13 +14,14 @@ internal class ExposedContentInfoRepo( ) : ExposedRepo, Table(name = "posts_content") { val postIdColumn = text("post_id").references(postIdColumnReference, ReferenceOption.CASCADE, ReferenceOption.CASCADE) val chatIdColumn = long("chat_id") + val threadIdColumn = long("thread_id").nullable().default(null) val messageIdColumn = long("message_id") val groupColumn = text("group").nullable() val orderColumn = integer("order") val ResultRow.asObject get() = PostContentInfo( - ChatId(get(chatIdColumn)), + IdChatIdentifier(get(chatIdColumn), get(threadIdColumn)), get(messageIdColumn), get(groupColumn), get(orderColumn) diff --git a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt index 05103a9..8d7ef0c 100644 --- a/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt +++ b/posts/src/jvmMain/kotlin/exposed/ExposedPostsRepo.kt @@ -9,10 +9,13 @@ 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.IdChatIdentifier 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.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList import org.jetbrains.exposed.sql.statements.* import org.jetbrains.exposed.sql.transactions.transaction @@ -33,8 +36,8 @@ class ExposedPostsRepo( override val primaryKey: PrimaryKey = PrimaryKey(idColumn) - override val selectById: SqlExpressionBuilder.(PostId) -> Op = { idColumn.eq(it.string) } - override val selectByIds: SqlExpressionBuilder.(List) -> Op = { idColumn.inList(it.map { it.string }) } + 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.asObject: RegisteredPost get() { val id = PostId(get(idColumn)) @@ -86,6 +89,7 @@ class ExposedPostsRepo( insert { it[postIdColumn] = post.id.string it[chatIdColumn] = contentInfo.chatId.chatId + it[threadIdColumn] = contentInfo.chatId.threadId it[messageIdColumn] = contentInfo.messageId it[groupColumn] = contentInfo.group it[orderColumn] = contentInfo.order @@ -101,17 +105,19 @@ class ExposedPostsRepo( } override suspend fun onAfterCreate(values: List>): List { - values.forEach { - updateContent(it.second) + return values.map { + val actual = it.second.copy(content = it.first.content) + updateContent(actual) + actual } - return super.onAfterCreate(values) } override suspend fun onAfterUpdate(value: List>): List { - value.forEach { - updateContent(it.second) + return value.map { + val actual = it.second.copy(content = it.first.content) + updateContent(actual) + actual } - return super.onAfterUpdate(value) } override suspend fun deleteById(ids: List) { @@ -122,7 +128,7 @@ class ExposedPostsRepo( val existsIds = posts.keys.toList() transaction(db = database) { val deleted = deleteWhere(null, null) { - selectByIds(existsIds) + selectByIds(it, existsIds) } with(contentRepo) { deleteWhere { @@ -142,10 +148,14 @@ class ExposedPostsRepo( } } - override suspend fun getIdByChatAndMessage(chatId: ChatId, messageId: MessageIdentifier): PostId? { + override suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId? { return transaction(database) { with(contentRepo) { - select { chatIdColumn.eq(chatId.chatId).and(messageIdColumn.eq(messageId)) }.limit(1).firstOrNull() ?.get(postIdColumn) + select { + chatIdColumn.eq(chatId.chatId) + .and(chatId.threadId ?.let { threadIdColumn.eq(it) } ?: threadIdColumn.isNull()) + .and(messageIdColumn.eq(messageId)) + }.limit(1).firstOrNull() ?.get(postIdColumn) } ?.let(::PostId) } } diff --git a/posts_registrar/src/commonMain/kotlin/state/RegistrationState.kt b/posts_registrar/src/commonMain/kotlin/state/RegistrationState.kt index bce4f66..fcaafd6 100644 --- a/posts_registrar/src/commonMain/kotlin/state/RegistrationState.kt +++ b/posts_registrar/src/commonMain/kotlin/state/RegistrationState.kt @@ -3,20 +3,24 @@ package dev.inmo.plaguposter.posts.registrar.state import dev.inmo.micro_utils.fsm.common.State import dev.inmo.plaguposter.posts.models.PostContentInfo import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer +import dev.inmo.tgbotapi.types.IdChatIdentifier import kotlinx.serialization.Serializable interface RegistrationState : State { - override val context: ChatId + override val context: IdChatIdentifier @Serializable data class InProcess( - override val context: ChatId, + @Serializable(FullChatIdentifierSerializer::class) + override val context: IdChatIdentifier, val messages: List ) : RegistrationState @Serializable data class Finish( - override val context: ChatId, + @Serializable(FullChatIdentifierSerializer::class) + override val context: IdChatIdentifier, val messages: List ) : RegistrationState } diff --git a/posts_registrar/src/jvmMain/kotlin/Plugin.kt b/posts_registrar/src/jvmMain/kotlin/Plugin.kt index 3eef447..e12c72d 100644 --- a/posts_registrar/src/jvmMain/kotlin/Plugin.kt +++ b/posts_registrar/src/jvmMain/kotlin/Plugin.kt @@ -22,12 +22,12 @@ import dev.inmo.tgbotapi.extensions.utils.extensions.raw.text import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage import dev.inmo.tgbotapi.extensions.utils.formatting.buildEntities -import dev.inmo.tgbotapi.extensions.utils.formatting.regular -import dev.inmo.tgbotapi.extensions.utils.mediaGroupMessageOrNull import dev.inmo.tgbotapi.extensions.utils.textContentOrNull import dev.inmo.tgbotapi.extensions.utils.types.buttons.* import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage +import dev.inmo.tgbotapi.types.message.content.MediaGroupContent import dev.inmo.tgbotapi.types.message.content.MessageContent +import dev.inmo.tgbotapi.utils.regular import kotlinx.coroutines.flow.* import kotlinx.serialization.Serializable import org.koin.core.Koin @@ -43,7 +43,7 @@ object Plugin : Plugin { val messageToDelete = send( state.context, - buildEntities { + dev.inmo.tgbotapi.utils.buildEntities { if (state.messages.isNotEmpty()) { regular("Your message(s) has been registered. You may send new ones or push \"Finish\" to finalize your post") } else { @@ -65,18 +65,11 @@ object Plugin : Plugin { val newMessagesInfo = firstOf { add { listOf( - waitContentMessage( - includeMediaGroups = false - ).filter { + waitContentMessage().filter { it.chat.id == state.context && it.content.textContentOrNull() ?.text != "/finish_post" }.take(1).first() ) } - add { - waitMediaGroupMessages().filter { - it.first().chat.id == state.context - }.take(1).first() - } add { val finishPressed = waitMessageDataCallbackQuery().filter { it.message.sameMessage(messageToDelete) && it.data == buttonUuid @@ -95,8 +88,8 @@ object Plugin : Plugin { state.context, state.messages ) - }.map { - PostContentInfo.fromMessage(it, state.messages.size) + }.flatMap { + PostContentInfo.fromMessage(it) } RegistrationState.InProcess( @@ -121,25 +114,9 @@ object Plugin : Plugin { } onContentMessage( - initialFilter = { it.chat.id == config.sourceChatId && it.mediaGroupMessageOrNull() ?.mediaGroupId == null && !FirstSourceIsCommandsFilter(it) } + initialFilter = { it.chat.id == config.sourceChatId && !FirstSourceIsCommandsFilter(it) } ) { - startChain(RegistrationState.Finish(it.chat.id, listOf(PostContentInfo.fromMessage(it, 0)))) - } - - onMediaGroup( - initialFilter = { it.first().chat.id == config.sourceChatId } - ) { - startChain( - RegistrationState.Finish( - it.first().chat.id, - it.map { - PostContentInfo.fromMessage( - it, - 0 - ) - } - ) - ) + startChain(RegistrationState.Finish(it.chat.id, PostContentInfo.fromMessage(it))) } koin.getOrNull() ?.apply { addTemplate( diff --git a/ratings/source/src/jvmMain/kotlin/repos/ExposedPollsToMessagesInfoRepo.kt b/ratings/source/src/jvmMain/kotlin/repos/ExposedPollsToMessagesInfoRepo.kt index 7f73962..d66a00f 100644 --- a/ratings/source/src/jvmMain/kotlin/repos/ExposedPollsToMessagesInfoRepo.kt +++ b/ratings/source/src/jvmMain/kotlin/repos/ExposedPollsToMessagesInfoRepo.kt @@ -4,8 +4,11 @@ import dev.inmo.micro_utils.repos.exposed.initTable import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo import dev.inmo.plaguposter.common.ShortMessageInfo import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.IdChatIdentifier import dev.inmo.tgbotapi.types.PollIdentifier import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNull import org.jetbrains.exposed.sql.statements.* class ExposedPollsToMessagesInfoRepo( @@ -16,10 +19,11 @@ class ExposedPollsToMessagesInfoRepo( ) { override val keyColumn = text("poll_id") private val chatIdColumn = long("chat_id") + private val threadIdColumn = long("thread_id").nullable().default(null) private val messageIdColumn = long("message_id") - override val selectById: SqlExpressionBuilder.(PollIdentifier) -> Op = { keyColumn.eq(it) } - override val selectByValue: SqlExpressionBuilder.(ShortMessageInfo) -> Op = { - chatIdColumn.eq(it.chatId.chatId).and( + override val selectById: ISqlExpressionBuilder.(PollIdentifier) -> Op = { keyColumn.eq(it) } + override val selectByValue: ISqlExpressionBuilder.(ShortMessageInfo) -> Op = { + chatIdColumn.eq(it.chatId.chatId).and(it.chatId.threadId ?.let { threadIdColumn.eq(it) } ?: threadIdColumn.isNull()).and( messageIdColumn.eq(it.messageId) ) } @@ -27,7 +31,7 @@ class ExposedPollsToMessagesInfoRepo( get() = get(keyColumn) override val ResultRow.asObject: ShortMessageInfo get() = ShortMessageInfo( - get(chatIdColumn).let(::ChatId), + IdChatIdentifier(get(chatIdColumn), get(threadIdColumn)), get(messageIdColumn) ) @@ -37,6 +41,7 @@ class ExposedPollsToMessagesInfoRepo( override fun update(k: PollIdentifier, v: ShortMessageInfo, it: UpdateBuilder) { it[chatIdColumn] = v.chatId.chatId + it[threadIdColumn] = v.chatId.threadId it[messageIdColumn] = v.messageId } diff --git a/ratings/source/src/jvmMain/kotlin/repos/ExposedPollsToPostsIdsRepo.kt b/ratings/source/src/jvmMain/kotlin/repos/ExposedPollsToPostsIdsRepo.kt index fce2b48..d68ed9c 100644 --- a/ratings/source/src/jvmMain/kotlin/repos/ExposedPollsToPostsIdsRepo.kt +++ b/ratings/source/src/jvmMain/kotlin/repos/ExposedPollsToPostsIdsRepo.kt @@ -12,8 +12,8 @@ class ExposedPollsToPostsIdsRepo( ) : PollsToPostsIdsRepo, AbstractExposedKeyValueRepo(database, "polls_to_posts") { override val keyColumn = text("poll_id") val postIdColumn = text("postId") - override val selectById: SqlExpressionBuilder.(PollIdentifier) -> Op = { keyColumn.eq(it) } - override val selectByValue: SqlExpressionBuilder.(PostId) -> Op = { postIdColumn.eq(it.string) } + override val selectById: ISqlExpressionBuilder.(PollIdentifier) -> Op = { keyColumn.eq(it) } + override val selectByValue: ISqlExpressionBuilder.(PostId) -> Op = { postIdColumn.eq(it.string) } override val ResultRow.asKey: PollIdentifier get() = get(keyColumn) override val ResultRow.asObject: PostId diff --git a/ratings/src/jvmMain/kotlin/exposed/ExposedRatingsRepo.kt b/ratings/src/jvmMain/kotlin/exposed/ExposedRatingsRepo.kt index 0f0799f..0c7f14e 100644 --- a/ratings/src/jvmMain/kotlin/exposed/ExposedRatingsRepo.kt +++ b/ratings/src/jvmMain/kotlin/exposed/ExposedRatingsRepo.kt @@ -17,8 +17,8 @@ class ExposedRatingsRepo ( ) { override val keyColumn = text("post_id") val ratingsColumn = double("rating") - override val selectById: SqlExpressionBuilder.(PostId) -> Op = { keyColumn.eq(it.string) } - override val selectByValue: SqlExpressionBuilder.(Rating) -> Op = { ratingsColumn.eq(it.double) } + override val selectById: ISqlExpressionBuilder.(PostId) -> Op = { keyColumn.eq(it.string) } + override val selectByValue: ISqlExpressionBuilder.(Rating) -> Op = { ratingsColumn.eq(it.double) } override val ResultRow.asKey: PostId get() = get(keyColumn).let(::PostId) override val ResultRow.asObject: Rating diff --git a/settings/build.gradle b/settings/build.gradle index a0d2361..d101bd6 100644 --- a/settings/build.gradle +++ b/settings/build.gradle @@ -1,6 +1,7 @@ plugins { id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" } apply from: "$mppProjectWithSerializationPresetPath" diff --git a/triggers/command/src/jvmMain/kotlin/Plugin.kt b/triggers/command/src/jvmMain/kotlin/Plugin.kt index 23e86e1..a789fd7 100644 --- a/triggers/command/src/jvmMain/kotlin/Plugin.kt +++ b/triggers/command/src/jvmMain/kotlin/Plugin.kt @@ -1,12 +1,9 @@ package dev.inmo.plaguposter.triggers.command import com.benasher44.uuid.uuid4 -import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.fsm.common.State -import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination import dev.inmo.plagubot.Plugin import dev.inmo.plaguposter.common.SuccessfulSymbol -import dev.inmo.plaguposter.common.UnsuccessfulSymbol 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 @@ -16,14 +13,10 @@ import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI import dev.inmo.plaguposter.posts.repo.PostsRepo 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.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.waitMessageDataCallbackQuery -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.behaviour_builder.triggers_handling.onMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.utils.* @@ -33,9 +26,7 @@ import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton -import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.types.message.textsources.regular -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.serialization.Serializable import kotlinx.serialization.json.* @@ -78,7 +69,14 @@ object Plugin : Plugin { } } val postId = messageInReply ?.let { - postsRepo.getIdByChatAndMessage(messageInReply.chat.id, messageInReply.messageId) + postsRepo.getIdByChatAndMessage(messageInReply.chat.id, messageInReply.messageId) ?: let { _ -> + reply( + it, + "Unable to find any post related to the message in reply" + ) + + return@onCommand + } } ?: selector ?.take(1) ?.firstOrNull() if (postId == null) { reply(