Merge pull request #9 from InsanusMokrassar/0.0.6

0.0.6
This commit is contained in:
InsanusMokrassar 2022-11-25 15:07:54 +06:00 committed by GitHub
commit bea7fb7e46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 254 additions and 100 deletions

View File

@ -18,6 +18,7 @@ allprojects {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
google() google()
maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" }
} }
} }

View File

@ -1,13 +1,16 @@
package dev.inmo.plaguposter.common package dev.inmo.plaguposter.common
import dev.inmo.tgbotapi.types.ChatId 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.MessageIdentifier
import dev.inmo.tgbotapi.types.message.abstracts.Message import dev.inmo.tgbotapi.types.message.abstracts.Message
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable @Serializable
data class ShortMessageInfo( data class ShortMessageInfo(
val chatId: ChatId, @Serializable(FullChatIdentifierSerializer::class)
val chatId: IdChatIdentifier,
val messageId: MessageIdentifier val messageId: MessageIdentifier
) )

View File

@ -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<ChatConfig>()
Log.iS { "Target chat info: ${getChat(config.targetChatId)}" }
Log.iS { "Source chat info: ${getChat(config.sourceChatId)}" }
Log.iS { "Cache chat info: ${getChat(config.cacheChatId)}" }
}
}

View File

@ -10,5 +10,5 @@ android.enableJetifier=true
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.0.5 version=0.0.6
android_code_version=5 android_code_version=6

View File

@ -1,15 +1,17 @@
[versions] [versions]
kotlin = "1.7.20" kotlin = "1.7.21"
kotlin-serialization = "1.4.1" kotlin-serialization = "1.4.1"
plagubot = "2.4.0" plagubot = "3.1.3"
tgbotapi = "3.3.0" tgbotapi = "4.1.2"
microutils = "0.13.1" microutils = "0.14.2"
kslog = "0.5.2" kslog = "0.5.3"
krontab = "0.8.1" krontab = "0.8.3"
tgbotapi-libraries = "0.5.6" tgbotapi-libraries = "0.6.3"
plagubot-plugins = "0.5.0" plagubot-plugins = "0.6.1"
dokka = "1.7.20"
psql = "42.5.0" 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-plugin = { module = "dev.inmo:plagubot.plugin", version.ref = "plagubot" }
plagubot-bot = { module = "dev.inmo:plagubot.bot", 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-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-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-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" }
microutils-repos-cache = { module = "dev.inmo:micro_utils.repos.cache", 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-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-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] [plugins]

View File

@ -4,12 +4,20 @@ import dev.inmo.kslog.common.TagLogger
import dev.inmo.kslog.common.w import dev.inmo.kslog.common.w
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext 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.Koin
import org.koin.core.module.Module
private val actualPlugin = dev.inmo.plagubot.plugins.inline.queries.Plugin private val actualPlugin = dev.inmo.plagubot.plugins.inline.queries.Plugin
object Plugin : Plugin by actualPlugin { object Plugin : Plugin by actualPlugin {
private val log = TagLogger("InlinePlugin") private val log = TagLogger("InlinePlugin")
override fun Module.setupDI(database: Database, params: JsonObject) {
single { actualPlugin }
}
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
log.w { log.w {
"Built-in inline plugin has been deprecated. Use \"${actualPlugin::class.qualifiedName}\" instead" "Built-in inline plugin has been deprecated. Use \"${actualPlugin::class.qualifiedName}\" instead"

View File

@ -14,5 +14,10 @@ kotlin {
api libs.microutils.koin api libs.microutils.koin
} }
} }
jvmMain {
dependencies {
api libs.plagubot.plugins.inline.queries
}
}
} }
} }

View File

@ -3,6 +3,21 @@ package dev.inmo.plaguposter.posts.panel
import dev.inmo.plaguposter.posts.models.RegisteredPost import dev.inmo.plaguposter.posts.models.RegisteredPost
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.InlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.InlineKeyboardButton
fun interface PanelButtonBuilder { interface PanelButtonBuilder {
val weight: Int
suspend fun buildButton(post: RegisteredPost): InlineKeyboardButton? 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
)
}
} }

View File

@ -5,14 +5,14 @@ import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineK
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
class PanelButtonsAPI( class PanelButtonsAPI(
private val preset: List<PanelButtonBuilder>, private val preset: Map<Int, List<PanelButtonBuilder>>,
private val rootPanelButtonText: String private val rootPanelButtonText: String
) { ) {
private val _buttons = mutableSetOf<PanelButtonBuilder>().also { private val _buttonsMap = mutableMapOf<Int, MutableList<PanelButtonBuilder>>().also {
it.addAll(preset) it.putAll(preset.map { it.key to it.value.toMutableList() })
} }
internal val buttonsBuilders: List<PanelButtonBuilder> internal val buttonsBuilders: List<PanelButtonBuilder>
get() = _buttons.toList() get() = _buttonsMap.toList().sortedBy { it.first }.flatMap { it.second }
internal val forceRefreshFlow = MutableSharedFlow<PostId>() internal val forceRefreshFlow = MutableSharedFlow<PostId>()
val RootPanelButtonBuilder = PanelButtonBuilder { val RootPanelButtonBuilder = PanelButtonBuilder {
@ -22,8 +22,13 @@ class PanelButtonsAPI(
) )
} }
fun add(button: PanelButtonBuilder) = _buttons.add(button) fun add(button: PanelButtonBuilder, weight: Int = button.weight) = _buttonsMap.getOrPut(weight) { mutableListOf() }.add(button)
fun remove(button: PanelButtonBuilder) = _buttons.remove(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) { suspend fun forceRefresh(postId: PostId) {
forceRefreshFlow.emit(postId) forceRefreshFlow.emit(postId)
} }

View File

@ -5,9 +5,13 @@ import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.koin.getAllDistinct import dev.inmo.micro_utils.koin.getAllDistinct
import dev.inmo.micro_utils.repos.deleteById 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.set
import dev.inmo.micro_utils.repos.unset
import dev.inmo.micro_utils.repos.value
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plaguposter.common.ChatConfig import dev.inmo.plaguposter.common.ChatConfig
import dev.inmo.plaguposter.common.UnsuccessfulSymbol
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.posts.panel.repos.PostsMessages import dev.inmo.plaguposter.posts.panel.repos.PostsMessages
import dev.inmo.plaguposter.posts.repo.PostsRepo 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.delete
import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.api.edit.text.editMessageText 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.api.send.send
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery 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.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage 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.dataButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard 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.MessageIdentifier
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
import dev.inmo.tgbotapi.types.message.ParseMode 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.coroutines.flow.first
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
@ -69,9 +78,13 @@ object Plugin : Plugin {
} }
) )
PanelButtonsAPI( PanelButtonsAPI(
getAllDistinct<PanelButtonBuilder>() + builtInButtons, emptyMap(),
config.rootButtonText config.rootButtonText
) ).apply {
(getAllDistinct<PanelButtonBuilder>() + builtInButtons).forEach {
add(it)
}
}
} }
} }
@ -108,7 +121,7 @@ object Plugin : Plugin {
suspend fun refreshPostMessage( suspend fun refreshPostMessage(
postId: PostId, postId: PostId,
chatId: ChatId, chatId: IdChatIdentifier,
messageId: MessageIdentifier messageId: MessageIdentifier
) { ) {
val post = postsRepo.getById(postId) ?: return val post = postsRepo.getById(postId) ?: return
@ -183,5 +196,59 @@ object Plugin : Plugin {
val (chatId, messageId) = postsMessages.get(it) ?: return@subscribeSafelyWithoutExceptions val (chatId, messageId) = postsMessages.get(it) ?: return@subscribeSafelyWithoutExceptions
refreshPostMessage(it, chatId, messageId) 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)
}
} }
} }

View File

@ -5,18 +5,20 @@ import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
import dev.inmo.micro_utils.repos.mappers.withMapper import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.tgbotapi.types.ChatId 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.MessageIdentifier
import kotlinx.serialization.builtins.PairSerializer import kotlinx.serialization.builtins.PairSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
private val ChatIdToMessageSerializer = PairSerializer(ChatId.serializer(), MessageIdentifier.serializer()) private val ChatIdToMessageSerializer = PairSerializer(FullChatIdentifierSerializer, MessageIdentifier.serializer())
fun PostsMessages( fun PostsMessages(
database: Database, database: Database,
json: Json json: Json
): KeyValueRepo<PostId, Pair<ChatId, MessageIdentifier>> = ExposedKeyValueRepo<String, String>( ): KeyValueRepo<PostId, Pair<IdChatIdentifier, MessageIdentifier>> = ExposedKeyValueRepo<String, String>(
database, database,
{ text("postId") }, { text("postId") },
{ text("chatToMessage") }, { text("chatToMessage") },
@ -25,5 +27,5 @@ fun PostsMessages(
{ string }, { string },
{ json.encodeToString(ChatIdToMessageSerializer, this) }, { json.encodeToString(ChatIdToMessageSerializer, this) },
{ PostId(this) }, { PostId(this) },
{ json.decodeFromString(ChatIdToMessageSerializer, this) } { json.decodeFromString(ChatIdToMessageSerializer, this).let { (it.first as IdChatIdentifier) to it.second } }
) )

View File

@ -1,25 +1,39 @@
package dev.inmo.plaguposter.posts.models 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.ChatId
import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage 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 import kotlinx.serialization.Serializable
@Serializable @Serializable
data class PostContentInfo( data class PostContentInfo(
val chatId: ChatId, @Serializable(FullChatIdentifierSerializer::class)
val chatId: IdChatIdentifier,
val messageId: MessageIdentifier, val messageId: MessageIdentifier,
val group: String?, val group: String?,
val order: Int val order: Int
) { ) {
companion object { companion object {
fun fromMessage(message: ContentMessage<*>, order: Int) = PostContentInfo( private fun fromMessage(message: ContentMessage<*>, order: Int) = PostContentInfo(
message.chat.id, message.chat.id,
message.messageId, message.messageId,
message.mediaGroupMessageOrNull() ?.mediaGroupId, message.possiblyMediaGroupMessageOrNull() ?.mediaGroupId,
order order
) )
fun fromMessage(message: ContentMessage<*>): List<PostContentInfo> {
val content = message.content
return if (content is MediaGroupContent<*>) {
content.group.mapIndexed { i, it ->
fromMessage(it.sourceMessage, i)
}
} else {
listOf(fromMessage(message, 0))
}
}
} }
} }

View File

@ -4,9 +4,10 @@ import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.repos.ReadCRUDRepo import dev.inmo.micro_utils.repos.ReadCRUDRepo
import dev.inmo.plaguposter.posts.models.* import dev.inmo.plaguposter.posts.models.*
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
interface ReadPostsRepo : ReadCRUDRepo<RegisteredPost, PostId> { interface ReadPostsRepo : ReadCRUDRepo<RegisteredPost, PostId> {
suspend fun getIdByChatAndMessage(chatId: ChatId, messageId: MessageIdentifier): PostId? suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId?
suspend fun getPostCreationTime(postId: PostId): DateTime? suspend fun getPostCreationTime(postId: PostId): DateTime?
} }

View File

@ -12,6 +12,7 @@ import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.message.content.MediaGroupContent import dev.inmo.tgbotapi.types.message.content.MediaGroupContent
import dev.inmo.tgbotapi.types.message.content.MediaGroupPartContent
class PostPublisher( class PostPublisher(
private val bot: TelegramBot, private val bot: TelegramBot,
@ -37,14 +38,26 @@ class PostPublisher(
sortedMessagesContents.forEach { (_, contents) -> sortedMessagesContents.forEach { (_, contents) ->
contents.singleOrNull() ?.also { 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 return@forEach
} }
val resultContents = contents.mapNotNull { val resultContents = contents.mapNotNull {
it.order to (bot.forwardMessage(toChatId = cachingChatId, fromChatId = it.chatId, messageId = it.messageId).contentMessageOrNull() ?: return@mapNotNull null) it.order to (bot.forwardMessage(toChatId = cachingChatId, fromChatId = it.chatId, messageId = it.messageId).contentMessageOrNull() ?: return@mapNotNull null)
}.sortedBy { it.first }.mapNotNull { (_, it) -> }.sortedBy { it.first }.mapNotNull { (_, forwardedMessage) ->
it.withContentOrNull<MediaGroupContent>() ?: null.also { _ -> forwardedMessage.withContentOrNull<MediaGroupPartContent>() ?: null.also { _ ->
bot.copyMessage(targetChatId, it) bot.copyMessage(targetChatId, forwardedMessage)
} }
} }
resultContents.singleOrNull() ?.also { resultContents.singleOrNull() ?.also {

View File

@ -5,6 +5,7 @@ import dev.inmo.micro_utils.repos.KeyValuesRepo
import dev.inmo.micro_utils.repos.exposed.* import dev.inmo.micro_utils.repos.exposed.*
import dev.inmo.plaguposter.posts.models.* import dev.inmo.plaguposter.posts.models.*
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.IdChatIdentifier
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
internal class ExposedContentInfoRepo( internal class ExposedContentInfoRepo(
@ -13,13 +14,14 @@ internal class ExposedContentInfoRepo(
) : ExposedRepo, Table(name = "posts_content") { ) : ExposedRepo, Table(name = "posts_content") {
val postIdColumn = text("post_id").references(postIdColumnReference, ReferenceOption.CASCADE, ReferenceOption.CASCADE) val postIdColumn = text("post_id").references(postIdColumnReference, ReferenceOption.CASCADE, ReferenceOption.CASCADE)
val chatIdColumn = long("chat_id") val chatIdColumn = long("chat_id")
val threadIdColumn = long("thread_id").nullable().default(null)
val messageIdColumn = long("message_id") val messageIdColumn = long("message_id")
val groupColumn = text("group").nullable() val groupColumn = text("group").nullable()
val orderColumn = integer("order") val orderColumn = integer("order")
val ResultRow.asObject val ResultRow.asObject
get() = PostContentInfo( get() = PostContentInfo(
ChatId(get(chatIdColumn)), IdChatIdentifier(get(chatIdColumn), get(threadIdColumn)),
get(messageIdColumn), get(messageIdColumn),
get(groupColumn), get(groupColumn),
get(orderColumn) get(orderColumn)

View File

@ -9,10 +9,13 @@ import dev.inmo.micro_utils.repos.exposed.initTable
import dev.inmo.plaguposter.posts.models.* import dev.inmo.plaguposter.posts.models.*
import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.posts.repo.PostsRepo
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.jetbrains.exposed.sql.* 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.statements.*
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
@ -33,8 +36,8 @@ class ExposedPostsRepo(
override val primaryKey: PrimaryKey = PrimaryKey(idColumn) override val primaryKey: PrimaryKey = PrimaryKey(idColumn)
override val selectById: SqlExpressionBuilder.(PostId) -> Op<Boolean> = { idColumn.eq(it.string) } override val selectById: ISqlExpressionBuilder.(PostId) -> Op<Boolean> = { idColumn.eq(it.string) }
override val selectByIds: SqlExpressionBuilder.(List<PostId>) -> Op<Boolean> = { idColumn.inList(it.map { it.string }) } override val selectByIds: ISqlExpressionBuilder.(List<PostId>) -> Op<Boolean> = { idColumn.inList(it.map { it.string }) }
override val ResultRow.asObject: RegisteredPost override val ResultRow.asObject: RegisteredPost
get() { get() {
val id = PostId(get(idColumn)) val id = PostId(get(idColumn))
@ -86,6 +89,7 @@ class ExposedPostsRepo(
insert { insert {
it[postIdColumn] = post.id.string it[postIdColumn] = post.id.string
it[chatIdColumn] = contentInfo.chatId.chatId it[chatIdColumn] = contentInfo.chatId.chatId
it[threadIdColumn] = contentInfo.chatId.threadId
it[messageIdColumn] = contentInfo.messageId it[messageIdColumn] = contentInfo.messageId
it[groupColumn] = contentInfo.group it[groupColumn] = contentInfo.group
it[orderColumn] = contentInfo.order it[orderColumn] = contentInfo.order
@ -101,17 +105,19 @@ class ExposedPostsRepo(
} }
override suspend fun onAfterCreate(values: List<Pair<NewPost, RegisteredPost>>): List<RegisteredPost> { override suspend fun onAfterCreate(values: List<Pair<NewPost, RegisteredPost>>): List<RegisteredPost> {
values.forEach { return values.map {
updateContent(it.second) val actual = it.second.copy(content = it.first.content)
updateContent(actual)
actual
} }
return super.onAfterCreate(values)
} }
override suspend fun onAfterUpdate(value: List<UpdatedValuePair<NewPost, RegisteredPost>>): List<RegisteredPost> { override suspend fun onAfterUpdate(value: List<UpdatedValuePair<NewPost, RegisteredPost>>): List<RegisteredPost> {
value.forEach { return value.map {
updateContent(it.second) val actual = it.second.copy(content = it.first.content)
updateContent(actual)
actual
} }
return super.onAfterUpdate(value)
} }
override suspend fun deleteById(ids: List<PostId>) { override suspend fun deleteById(ids: List<PostId>) {
@ -122,7 +128,7 @@ class ExposedPostsRepo(
val existsIds = posts.keys.toList() val existsIds = posts.keys.toList()
transaction(db = database) { transaction(db = database) {
val deleted = deleteWhere(null, null) { val deleted = deleteWhere(null, null) {
selectByIds(existsIds) selectByIds(it, existsIds)
} }
with(contentRepo) { with(contentRepo) {
deleteWhere { 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) { return transaction(database) {
with(contentRepo) { 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) } ?.let(::PostId)
} }
} }

View File

@ -3,20 +3,24 @@ package dev.inmo.plaguposter.posts.registrar.state
import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.plaguposter.posts.models.PostContentInfo import dev.inmo.plaguposter.posts.models.PostContentInfo
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer
import dev.inmo.tgbotapi.types.IdChatIdentifier
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
interface RegistrationState : State { interface RegistrationState : State {
override val context: ChatId override val context: IdChatIdentifier
@Serializable @Serializable
data class InProcess( data class InProcess(
override val context: ChatId, @Serializable(FullChatIdentifierSerializer::class)
override val context: IdChatIdentifier,
val messages: List<PostContentInfo> val messages: List<PostContentInfo>
) : RegistrationState ) : RegistrationState
@Serializable @Serializable
data class Finish( data class Finish(
override val context: ChatId, @Serializable(FullChatIdentifierSerializer::class)
override val context: IdChatIdentifier,
val messages: List<PostContentInfo> val messages: List<PostContentInfo>
) : RegistrationState ) : RegistrationState
} }

View File

@ -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.sameChat
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
import dev.inmo.tgbotapi.extensions.utils.formatting.buildEntities 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.textContentOrNull
import dev.inmo.tgbotapi.extensions.utils.types.buttons.* import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage 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.types.message.content.MessageContent
import dev.inmo.tgbotapi.utils.regular
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.koin.core.Koin import org.koin.core.Koin
@ -43,7 +43,7 @@ object Plugin : Plugin {
val messageToDelete = send( val messageToDelete = send(
state.context, state.context,
buildEntities { dev.inmo.tgbotapi.utils.buildEntities {
if (state.messages.isNotEmpty()) { if (state.messages.isNotEmpty()) {
regular("Your message(s) has been registered. You may send new ones or push \"Finish\" to finalize your post") regular("Your message(s) has been registered. You may send new ones or push \"Finish\" to finalize your post")
} else { } else {
@ -65,18 +65,11 @@ object Plugin : Plugin {
val newMessagesInfo = firstOf { val newMessagesInfo = firstOf {
add { add {
listOf( listOf(
waitContentMessage( waitContentMessage().filter {
includeMediaGroups = false
).filter {
it.chat.id == state.context && it.content.textContentOrNull() ?.text != "/finish_post" it.chat.id == state.context && it.content.textContentOrNull() ?.text != "/finish_post"
}.take(1).first() }.take(1).first()
) )
} }
add {
waitMediaGroupMessages().filter {
it.first().chat.id == state.context
}.take(1).first()
}
add { add {
val finishPressed = waitMessageDataCallbackQuery().filter { val finishPressed = waitMessageDataCallbackQuery().filter {
it.message.sameMessage(messageToDelete) && it.data == buttonUuid it.message.sameMessage(messageToDelete) && it.data == buttonUuid
@ -95,8 +88,8 @@ object Plugin : Plugin {
state.context, state.context,
state.messages state.messages
) )
}.map { }.flatMap {
PostContentInfo.fromMessage(it, state.messages.size) PostContentInfo.fromMessage(it)
} }
RegistrationState.InProcess( RegistrationState.InProcess(
@ -121,25 +114,9 @@ object Plugin : Plugin {
} }
onContentMessage( 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)))) startChain(RegistrationState.Finish(it.chat.id, PostContentInfo.fromMessage(it)))
}
onMediaGroup(
initialFilter = { it.first().chat.id == config.sourceChatId }
) {
startChain(
RegistrationState.Finish(
it.first().chat.id,
it.map {
PostContentInfo.fromMessage(
it,
0
)
}
)
)
} }
koin.getOrNull<InlineTemplatesRepo>() ?.apply { koin.getOrNull<InlineTemplatesRepo>() ?.apply {
addTemplate( addTemplate(

View File

@ -4,8 +4,11 @@ import dev.inmo.micro_utils.repos.exposed.initTable
import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo
import dev.inmo.plaguposter.common.ShortMessageInfo import dev.inmo.plaguposter.common.ShortMessageInfo
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.PollIdentifier import dev.inmo.tgbotapi.types.PollIdentifier
import org.jetbrains.exposed.sql.* 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.* import org.jetbrains.exposed.sql.statements.*
class ExposedPollsToMessagesInfoRepo( class ExposedPollsToMessagesInfoRepo(
@ -16,10 +19,11 @@ class ExposedPollsToMessagesInfoRepo(
) { ) {
override val keyColumn = text("poll_id") override val keyColumn = text("poll_id")
private val chatIdColumn = long("chat_id") private val chatIdColumn = long("chat_id")
private val threadIdColumn = long("thread_id").nullable().default(null)
private val messageIdColumn = long("message_id") private val messageIdColumn = long("message_id")
override val selectById: SqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) } override val selectById: ISqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) }
override val selectByValue: SqlExpressionBuilder.(ShortMessageInfo) -> Op<Boolean> = { override val selectByValue: ISqlExpressionBuilder.(ShortMessageInfo) -> Op<Boolean> = {
chatIdColumn.eq(it.chatId.chatId).and( chatIdColumn.eq(it.chatId.chatId).and(it.chatId.threadId ?.let { threadIdColumn.eq(it) } ?: threadIdColumn.isNull()).and(
messageIdColumn.eq(it.messageId) messageIdColumn.eq(it.messageId)
) )
} }
@ -27,7 +31,7 @@ class ExposedPollsToMessagesInfoRepo(
get() = get(keyColumn) get() = get(keyColumn)
override val ResultRow.asObject: ShortMessageInfo override val ResultRow.asObject: ShortMessageInfo
get() = ShortMessageInfo( get() = ShortMessageInfo(
get(chatIdColumn).let(::ChatId), IdChatIdentifier(get(chatIdColumn), get(threadIdColumn)),
get(messageIdColumn) get(messageIdColumn)
) )
@ -37,6 +41,7 @@ class ExposedPollsToMessagesInfoRepo(
override fun update(k: PollIdentifier, v: ShortMessageInfo, it: UpdateBuilder<Int>) { override fun update(k: PollIdentifier, v: ShortMessageInfo, it: UpdateBuilder<Int>) {
it[chatIdColumn] = v.chatId.chatId it[chatIdColumn] = v.chatId.chatId
it[threadIdColumn] = v.chatId.threadId
it[messageIdColumn] = v.messageId it[messageIdColumn] = v.messageId
} }

View File

@ -12,8 +12,8 @@ class ExposedPollsToPostsIdsRepo(
) : PollsToPostsIdsRepo, AbstractExposedKeyValueRepo<PollIdentifier, PostId>(database, "polls_to_posts") { ) : PollsToPostsIdsRepo, AbstractExposedKeyValueRepo<PollIdentifier, PostId>(database, "polls_to_posts") {
override val keyColumn = text("poll_id") override val keyColumn = text("poll_id")
val postIdColumn = text("postId") val postIdColumn = text("postId")
override val selectById: SqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) } override val selectById: ISqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) }
override val selectByValue: SqlExpressionBuilder.(PostId) -> Op<Boolean> = { postIdColumn.eq(it.string) } override val selectByValue: ISqlExpressionBuilder.(PostId) -> Op<Boolean> = { postIdColumn.eq(it.string) }
override val ResultRow.asKey: PollIdentifier override val ResultRow.asKey: PollIdentifier
get() = get(keyColumn) get() = get(keyColumn)
override val ResultRow.asObject: PostId override val ResultRow.asObject: PostId

View File

@ -17,8 +17,8 @@ class ExposedRatingsRepo (
) { ) {
override val keyColumn = text("post_id") override val keyColumn = text("post_id")
val ratingsColumn = double("rating") val ratingsColumn = double("rating")
override val selectById: SqlExpressionBuilder.(PostId) -> Op<Boolean> = { keyColumn.eq(it.string) } override val selectById: ISqlExpressionBuilder.(PostId) -> Op<Boolean> = { keyColumn.eq(it.string) }
override val selectByValue: SqlExpressionBuilder.(Rating) -> Op<Boolean> = { ratingsColumn.eq(it.double) } override val selectByValue: ISqlExpressionBuilder.(Rating) -> Op<Boolean> = { ratingsColumn.eq(it.double) }
override val ResultRow.asKey: PostId override val ResultRow.asKey: PostId
get() = get(keyColumn).let(::PostId) get() = get(keyColumn).let(::PostId)
override val ResultRow.asObject: Rating override val ResultRow.asObject: Rating

View File

@ -1,6 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
} }
apply from: "$mppProjectWithSerializationPresetPath" apply from: "$mppProjectWithSerializationPresetPath"

View File

@ -1,12 +1,9 @@
package dev.inmo.plaguposter.triggers.command package dev.inmo.plaguposter.triggers.command
import com.benasher44.uuid.uuid4 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.fsm.common.State
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plaguposter.common.SuccessfulSymbol 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.Format
import dev.inmo.plagubot.plugins.inline.queries.models.OfferTemplate import dev.inmo.plagubot.plugins.inline.queries.models.OfferTemplate
import dev.inmo.plagubot.plugins.inline.queries.repos.InlineTemplatesRepo 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.repo.PostsRepo
import dev.inmo.plaguposter.posts.sending.PostPublisher import dev.inmo.plaguposter.posts.sending.PostPublisher
import dev.inmo.plaguposter.ratings.selector.Selector 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.edit.edit
import dev.inmo.tgbotapi.extensions.api.send.reply 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.BehaviourContextWithFSM
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery 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.onCommand
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.utils.* 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.ChatId
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
import dev.inmo.tgbotapi.types.message.textsources.regular import dev.inmo.tgbotapi.types.message.textsources.regular
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
@ -78,7 +69,14 @@ object Plugin : Plugin {
} }
} }
val postId = messageInReply ?.let { 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() } ?: selector ?.take(1) ?.firstOrNull()
if (postId == null) { if (postId == null) {
reply( reply(