diff --git a/posts/src/commonMain/kotlin/models/PostContentInfo.kt b/posts/src/commonMain/kotlin/models/PostContentInfo.kt index 7a572d5..995dfd1 100644 --- a/posts/src/commonMain/kotlin/models/PostContentInfo.kt +++ b/posts/src/commonMain/kotlin/models/PostContentInfo.kt @@ -1,7 +1,10 @@ package dev.inmo.plaguposter.posts.models +import dev.inmo.tgbotapi.extensions.utils.mediaGroupMessageOrNull import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.MessageIdentifier +import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage +import dev.inmo.tgbotapi.types.message.content.MessageContent import kotlinx.serialization.Serializable @Serializable @@ -10,4 +13,13 @@ data class PostContentInfo( val messageId: MessageIdentifier, val group: String?, val order: Int -) +) { + companion object { + fun fromMessage(message: ContentMessage<*>, order: Int) = PostContentInfo( + message.chat.id, + message.messageId, + message.mediaGroupMessageOrNull() ?.mediaGroupId, + order + ) + } +} diff --git a/posts/src/jvmMain/kotlin/Plugin.kt b/posts/src/jvmMain/kotlin/Plugin.kt index 2b860a2..4a43961 100644 --- a/posts/src/jvmMain/kotlin/Plugin.kt +++ b/posts/src/jvmMain/kotlin/Plugin.kt @@ -15,7 +15,7 @@ import org.koin.core.module.Module object Plugin : Plugin { @Serializable - data class Config( + private data class Config( @SerialName("targetChat") val targetChatId: ChatId, @SerialName("cacheChat") diff --git a/posts_registrar/build.gradle b/posts_registrar/build.gradle new file mode 100644 index 0000000..054bb15 --- /dev/null +++ b/posts_registrar/build.gradle @@ -0,0 +1,21 @@ +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.posts") + } + } + jvmMain { + dependencies { + } + } + } +} diff --git a/posts_registrar/src/commonMain/kotlin/PackageInfo.kt b/posts_registrar/src/commonMain/kotlin/PackageInfo.kt new file mode 100644 index 0000000..181f201 --- /dev/null +++ b/posts_registrar/src/commonMain/kotlin/PackageInfo.kt @@ -0,0 +1 @@ +package dev.inmo.plaguposter.posts.registrar diff --git a/posts_registrar/src/commonMain/kotlin/state/RegistrationState.kt b/posts_registrar/src/commonMain/kotlin/state/RegistrationState.kt new file mode 100644 index 0000000..bce4f66 --- /dev/null +++ b/posts_registrar/src/commonMain/kotlin/state/RegistrationState.kt @@ -0,0 +1,22 @@ +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 kotlinx.serialization.Serializable + +interface RegistrationState : State { + override val context: ChatId + + @Serializable + data class InProcess( + override val context: ChatId, + val messages: List + ) : RegistrationState + + @Serializable + data class Finish( + override val context: ChatId, + val messages: List + ) : RegistrationState +} diff --git a/posts_registrar/src/jvmMain/kotlin/Plugin.kt b/posts_registrar/src/jvmMain/kotlin/Plugin.kt new file mode 100644 index 0000000..72b7444 --- /dev/null +++ b/posts_registrar/src/jvmMain/kotlin/Plugin.kt @@ -0,0 +1,166 @@ +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.fsm.common.State +import dev.inmo.micro_utils.repos.create +import dev.inmo.plagubot.Plugin +import dev.inmo.plaguposter.posts.models.NewPost +import dev.inmo.plaguposter.posts.models.PostContentInfo +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.send +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextWithFSM +import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.* +import dev.inmo.tgbotapi.extensions.behaviour_builder.strictlyOn +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.* +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.types.buttons.* +import dev.inmo.tgbotapi.extensions.utils.withContentOrNull +import dev.inmo.tgbotapi.requests.send.SendTextMessage +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 kotlinx.coroutines.flow.* +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 + +@Serializable +object Plugin : Plugin { + @Serializable + private data class Config( + @SerialName("sourceChat") + val sourceChatId: ChatId + ) + + override fun Module.setupDI(database: Database, params: JsonObject) { + val configJson = params["registrar"] ?: this@Plugin.let { + it.logger.w { + "Unable to load posts plugin due to absence of `registrar` key in config" + } + return + } + single { get().decodeFromJsonElement(Config.serializer(), configJson) } + } + + override suspend fun BehaviourContextWithFSM.setupBotPlugin(koin: Koin) { + val config = koin.get() + val postsRepo = koin.get() + + strictlyOn {state: RegistrationState.InProcess -> + val buttonUuid = "finish" + + val messageToDelete = send( + state.context, + 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 { + regular("Ok, send me your messages for new post") + } + }, + replyMarkup = if (state.messages.isNotEmpty()) { + flatInlineKeyboard { + dataButton( + "Finish", + buttonUuid + ) + } + } else { + null + } + ) + + val newMessagesInfo = firstOf { + add { + listOf( + waitContentMessage( + includeMediaGroups = false + ).filter { + it.chat.id == state.context + }.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 + }.first() + emptyList>() + } + }.ifEmpty { + edit(messageToDelete, "Ok, finishing your request") + return@strictlyOn RegistrationState.Finish( + state.context, + state.messages + ) + }.map { + PostContentInfo.fromMessage(it, state.messages.size) + } + + RegistrationState.InProcess( + state.context, + state.messages + newMessagesInfo + ).also { + delete(messageToDelete) + } + } + + strictlyOn { state: RegistrationState.Finish -> + postsRepo.create( + NewPost( + state.messages + ) + ).firstOrNull() ?.let { + send(state.context, "Ok, you have registered ${it.content.size} messages as new post") + } ?: send( + state.context, + "Sorry, for some reason I was unable to register your post" + ) + null + } + + onCommand("start_post", initialFilter = { it.chat.id == config.sourceChatId }) { + startChain(RegistrationState.InProcess(it.chat.id, emptyList())) + } + + onContentMessage( + initialFilter = { it.chat.id == config.sourceChatId && it.mediaGroupMessageOrNull() ?.mediaGroupId == null } + ) { + 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 + ) + } + ) + ) + } + } +} diff --git a/posts_registrar/src/main/AndroidManifest.xml b/posts_registrar/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f07854c --- /dev/null +++ b/posts_registrar/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/settings.gradle b/settings.gradle index b8e2f35..d625beb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,8 @@ rootProject.name = 'plaguposter' String[] includes = [ - ":posts" + ":posts", + ":posts_registrar" ] diff --git a/template/build.gradle b/template/build.gradle new file mode 100644 index 0000000..36f733d --- /dev/null +++ b/template/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 libs.tgbotapi + api libs.microutils.repos.common + api libs.kslog + } + } + jvmMain { + dependencies { + } + } + } +} diff --git a/template/src/commonMain/kotlin/PackageInfo.kt b/template/src/commonMain/kotlin/PackageInfo.kt new file mode 100644 index 0000000..03f7114 --- /dev/null +++ b/template/src/commonMain/kotlin/PackageInfo.kt @@ -0,0 +1 @@ +package dev.inmo.plaguposter.template diff --git a/template/src/jvmMain/kotlin/Plugin.kt b/template/src/jvmMain/kotlin/Plugin.kt new file mode 100644 index 0000000..a6df2fd --- /dev/null +++ b/template/src/jvmMain/kotlin/Plugin.kt @@ -0,0 +1,11 @@ +package dev.inmo.plaguposter.template + +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/template/src/main/AndroidManifest.xml b/template/src/main/AndroidManifest.xml new file mode 100644 index 0000000..53c7778 --- /dev/null +++ b/template/src/main/AndroidManifest.xml @@ -0,0 +1 @@ +