18 Commits

Author SHA1 Message Date
b6fa2a6cd3 Merge pull request #23 from InsanusMokrassar/0.5.5
0.5.5
2024-04-24 12:15:59 +06:00
e24237beb3 update dependencies 2024-04-24 12:14:33 +06:00
d2d3665c5e start 0.5.5 2024-04-24 11:58:27 +06:00
9ef9de2b8a Merge pull request #22 from InsanusMokrassar/0.5.4
0.5.4
2024-02-18 22:45:21 +06:00
cef350667b update dependencies 2024-02-18 22:41:33 +06:00
80ed679241 start 0.5.4 2024-02-18 21:26:02 +06:00
39391636ac Merge pull request #20 from InsanusMokrassar/several_sources
Several sources
2024-02-15 20:38:17 +06:00
29a19df7fc update dependencies and version 2024-02-15 20:30:10 +06:00
250f88e2fe preview adding of several sources chats 2024-02-15 20:21:48 +06:00
bb433a6441 Merge pull request #21 from InsanusMokrassar/0.5.2
0.5.2
2023-12-11 00:21:28 +06:00
7a8166153f update dependencies 2023-12-11 00:01:05 +06:00
114add0391 start 0.5.2 2023-12-10 23:58:00 +06:00
58b1f26502 0.5.1 2023-11-12 21:57:21 +06:00
ba3d054f0f Update docker-compose.yml 2023-11-09 01:55:24 +06:00
eef2bfce14 add support of inline messages with data callback query in common posts gc 2023-11-06 22:02:54 +06:00
fe96101631 change check of yes/no in checking of messages 2023-11-06 21:56:37 +06:00
7abb6efba3 add force posts check shortcut in inline mode 2023-11-06 21:48:03 +06:00
2f0a823f7c Merge pull request #19 from InsanusMokrassar/0.5.0
0.5.0
2023-11-06 21:31:25 +06:00
35 changed files with 223 additions and 138 deletions

View File

@@ -1,5 +1,25 @@
# PlaguPoster # PlaguPoster
## 0.5.5
* Dependencies update
## 0.5.4
* Dependencies update
## 0.5.3
* Dependencies update
## 0.5.2
* Dependencies update
## 0.5.1
* Add opportunity to set unique
## 0.5.0 ## 0.5.0
* Dependencies update * Dependencies update

View File

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

View File

@@ -12,15 +12,20 @@ data class ChatConfig(
val targetChatId: IdChatIdentifier? = null, val targetChatId: IdChatIdentifier? = null,
@SerialName("sourceChat") @SerialName("sourceChat")
@Serializable(FullChatIdentifierSerializer::class) @Serializable(FullChatIdentifierSerializer::class)
val sourceChatId: IdChatIdentifier, val sourceChatId: IdChatIdentifier?,
@SerialName("cacheChat") @SerialName("cacheChat")
@Serializable(FullChatIdentifierSerializer::class) @Serializable(FullChatIdentifierSerializer::class)
val cacheChatId: IdChatIdentifier, val cacheChatId: IdChatIdentifier,
@SerialName("targetChats") @SerialName("targetChats")
val targetChatIds: List<@Serializable(FullChatIdentifierSerializer::class) IdChatIdentifier> = emptyList(), val targetChatIds: List<@Serializable(FullChatIdentifierSerializer::class) IdChatIdentifier> = emptyList(),
@SerialName("sourceChats")
val sourceChatIds: List<@Serializable(FullChatIdentifierSerializer::class) IdChatIdentifier> = emptyList(),
) { ) {
val allTargetChatIds by lazy { val allTargetChatIds by lazy {
listOfNotNull(targetChatId) + targetChatIds (listOfNotNull(targetChatId) + targetChatIds).toSet()
}
val allSourceChatIds by lazy {
(listOfNotNull(sourceChatId) + sourceChatIds).toSet()
} }
init { init {
@@ -30,8 +35,8 @@ data class ChatConfig(
} }
fun check(chatId: IdChatIdentifier) = when (chatId) { fun check(chatId: IdChatIdentifier) = when (chatId) {
targetChatId, in allTargetChatIds,
sourceChatId, in allSourceChatIds,
cacheChatId -> true cacheChatId -> true
else -> false else -> false
} }

View File

@@ -1,9 +1,6 @@
package dev.inmo.plaguposter.common package dev.inmo.plaguposter.common
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.*
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 dev.inmo.tgbotapi.types.message.abstracts.Message
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -11,7 +8,7 @@ import kotlinx.serialization.Serializable
data class ShortMessageInfo( data class ShortMessageInfo(
@Serializable(FullChatIdentifierSerializer::class) @Serializable(FullChatIdentifierSerializer::class)
val chatId: IdChatIdentifier, val chatId: IdChatIdentifier,
val messageId: MessageIdentifier val messageId: MessageId
) )
fun Message.short() = ShortMessageInfo(chat.id, messageId) fun Message.short() = ShortMessageInfo(chat.id, messageId)

View File

@@ -27,7 +27,7 @@ object CommonPlugin : Plugin {
val config = koin.get<ChatConfig>() val config = koin.get<ChatConfig>()
Log.iS { "Target chats info: ${config.allTargetChatIds.map { getChat(it) }.joinToString()}" } Log.iS { "Target chats info: ${config.allTargetChatIds.map { getChat(it) }.joinToString()}" }
Log.iS { "Source chat info: ${getChat(config.sourceChatId)}" } Log.iS { "Source chats info: ${config.allSourceChatIds.map { getChat(it) }.joinToString()}" }
Log.iS { "Cache chat info: ${getChat(config.cacheChatId)}" } Log.iS { "Cache chat info: ${getChat(config.cacheChatId)}" }
} }
} }

View File

@@ -10,4 +10,4 @@ android.enableJetifier=true
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.5.0 version=0.5.5

View File

@@ -1,16 +1,16 @@
[versions] [versions]
kotlin = "1.9.20" kotlin = "1.9.23"
kotlin-serialization = "1.6.0" kotlin-serialization = "1.6.3"
plagubot = "7.3.0" plagubot = "8.3.0"
tgbotapi = "9.3.0" tgbotapi = "12.0.1"
microutils = "0.20.12" microutils = "0.20.45"
kslog = "1.2.4" kslog = "1.3.3"
krontab = "2.2.3" krontab = "2.2.9"
plagubot-plugins = "0.16.0" plagubot-plugins = "0.18.3"
dokka = "1.9.10" dokka = "1.9.20"
psql = "42.6.0" psql = "42.6.0"

View File

@@ -11,6 +11,9 @@ import dev.inmo.micro_utils.coroutines.actor
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.repos.deleteById import dev.inmo.micro_utils.repos.deleteById
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
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
import dev.inmo.plaguposter.common.ChatConfig import dev.inmo.plaguposter.common.ChatConfig
import dev.inmo.plaguposter.posts.models.NewPost import dev.inmo.plaguposter.posts.models.NewPost
import dev.inmo.plaguposter.posts.models.PostContentInfo import dev.inmo.plaguposter.posts.models.PostContentInfo
@@ -20,7 +23,10 @@ import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.api.forwardMessage import dev.inmo.tgbotapi.extensions.api.forwardMessage
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.waitInlineMessageIdDataCallbackQuery
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.oneOf
import dev.inmo.tgbotapi.extensions.behaviour_builder.parallel
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.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
@@ -98,10 +104,12 @@ object Plugin : Plugin {
return@forEach return@forEach
} }
send( runCatching {
chatsConfig.sourceChatId, send(
"Can't find any messages for post $postId. So, deleting it" chatsConfig.cacheChatId,
) "Can't find any messages for post $postId. So, deleting it"
)
}
runCatching { runCatching {
postsRepo.deleteById(postId) postsRepo.deleteById(postId)
} }
@@ -147,9 +155,18 @@ object Plugin : Plugin {
} }
) )
val answer = waitMessageDataCallbackQuery().filter { val answer = oneOf(
it.message.sameMessage(message) parallel {
}.first() waitMessageDataCallbackQuery().filter {
it.message.sameMessage(message)
}.first()
},
parallel {
waitInlineMessageIdDataCallbackQuery().filter {
it.data == yesData || it.data == noData
}.first()
}
)
if (answer.data == yesData) { if (answer.data == yesData) {
if (recheckActor.trySend(Unit).isSuccess) { if (recheckActor.trySend(Unit).isSuccess) {
@@ -164,5 +181,14 @@ object Plugin : Plugin {
} }
} }
} }
koin.getOrNull<InlineTemplatesRepo>() ?.addTemplate(
OfferTemplate(
"Force posts check",
listOf(
Format("/force_garbage_collection")
),
"Force check posts without exists messages"
)
)
} }
} }

View File

@@ -30,7 +30,8 @@ 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.IdChatIdentifier import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageId
import dev.inmo.tgbotapi.types.ReplyParameters
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
@@ -112,7 +113,7 @@ object Plugin : Plugin {
firstContent.chatId, firstContent.chatId,
text = config.text, text = config.text,
parseMode = config.parseMode, parseMode = config.parseMode,
replyToMessageId = firstContent.messageId, replyParameters = ReplyParameters(firstContent.chatId, firstContent.messageId),
replyMarkup = InlineKeyboardMarkup(buttons), replyMarkup = InlineKeyboardMarkup(buttons),
disableNotification = true disableNotification = true
).also { sentMessage -> ).also { sentMessage ->
@@ -128,7 +129,7 @@ object Plugin : Plugin {
suspend fun refreshPostMessage( suspend fun refreshPostMessage(
postId: PostId, postId: PostId,
chatId: IdChatIdentifier, chatId: IdChatIdentifier,
messageId: MessageIdentifier messageId: MessageId
) { ) {
val post = postsRepo.getById(postId) ?: return val post = postsRepo.getById(postId) ?: return
val buttons = api.buttonsBuilders.chunked(config.buttonsPerRow).mapNotNull { row -> val buttons = api.buttonsBuilders.chunked(config.buttonsPerRow).mapNotNull { row ->
@@ -146,7 +147,7 @@ object Plugin : Plugin {
onMessageDataCallbackQuery ( onMessageDataCallbackQuery (
initialFilter = { initialFilter = {
it.data.startsWith(PanelButtonsAPI.openGlobalMenuDataPrefix) && it.message.chat.id == chatsConfig.sourceChatId it.data.startsWith(PanelButtonsAPI.openGlobalMenuDataPrefix) && it.message.chat.id in chatsConfig.allSourceChatIds
} }
) { ) {
val postId = it.data.removePrefix(PanelButtonsAPI.openGlobalMenuDataPrefix).let(::PostId) val postId = it.data.removePrefix(PanelButtonsAPI.openGlobalMenuDataPrefix).let(::PostId)
@@ -155,7 +156,7 @@ object Plugin : Plugin {
} }
onMessageDataCallbackQuery( onMessageDataCallbackQuery(
initialFilter = { initialFilter = {
it.data.startsWith("delete ") && it.message.chat.id == chatsConfig.sourceChatId it.data.startsWith("delete ") && it.message.chat.id in chatsConfig.allSourceChatIds
} }
) { query -> ) { query ->
val postId = query.data.removePrefix("delete ").let(::PostId) val postId = query.data.removePrefix("delete ").let(::PostId)
@@ -182,7 +183,7 @@ object Plugin : Plugin {
} }
onMessageDataCallbackQuery( onMessageDataCallbackQuery(
initialFilter = { initialFilter = {
it.data.startsWith("refresh ") && it.message.chat.id == chatsConfig.sourceChatId it.data.startsWith("refresh ") && it.message.chat.id in chatsConfig.allSourceChatIds
} }
) { query -> ) { query ->
val postId = query.data.removePrefix("refresh ").let(::PostId) val postId = query.data.removePrefix("refresh ").let(::PostId)

View File

@@ -4,21 +4,18 @@ import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo 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.*
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.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(FullChatIdentifierSerializer, MessageIdentifier.serializer()) private val ChatIdToMessageSerializer = PairSerializer(FullChatIdentifierSerializer, MessageId.serializer())
fun PostsMessages( fun PostsMessages(
database: Database, database: Database,
json: Json json: Json
): KeyValueRepo<PostId, Pair<IdChatIdentifier, MessageIdentifier>> = ExposedKeyValueRepo<String, String>( ): KeyValueRepo<PostId, Pair<IdChatIdentifier, MessageId>> = ExposedKeyValueRepo<String, String>(
database, database,
{ text("postId") }, { text("postId") },
{ text("chatToMessage") }, { text("chatToMessage") },

View File

@@ -1,10 +1,7 @@
package dev.inmo.plaguposter.posts.models package dev.inmo.plaguposter.posts.models
import dev.inmo.tgbotapi.extensions.utils.possiblyMediaGroupMessageOrNull import dev.inmo.tgbotapi.extensions.utils.possiblyMediaGroupMessageOrNull
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.*
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.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.MediaGroupContent import dev.inmo.tgbotapi.types.message.content.MediaGroupContent
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -13,8 +10,8 @@ import kotlinx.serialization.Serializable
data class PostContentInfo( data class PostContentInfo(
@Serializable(FullChatIdentifierSerializer::class) @Serializable(FullChatIdentifierSerializer::class)
val chatId: IdChatIdentifier, val chatId: IdChatIdentifier,
val messageId: MessageIdentifier, val messageId: MessageId,
val group: String?, val group: MediaGroupId?,
val order: Int val order: Int
) { ) {
companion object { companion object {

View File

@@ -5,10 +5,10 @@ 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.IdChatIdentifier
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageId
interface ReadPostsRepo : ReadCRUDRepo<RegisteredPost, PostId> { interface ReadPostsRepo : ReadCRUDRepo<RegisteredPost, PostId> {
suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId? suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageId): PostId?
suspend fun getPostCreationTime(postId: PostId): DateTime? suspend fun getPostCreationTime(postId: PostId): DateTime?
suspend fun getFirstMessageInfo(postId: PostId): PostContentInfo? suspend fun getFirstMessageInfo(postId: PostId): PostContentInfo?
} }

View File

@@ -58,7 +58,7 @@ object Plugin : Plugin {
} }
single { single {
val config = get<Config>() val config = get<Config>()
PostPublisher(get(), get(), config.chats.cacheChatId, config.chats.allTargetChatIds, config.deleteAfterPublishing) PostPublisher(get(), get(), config.chats.cacheChatId, config.chats.allTargetChatIds.toList(), config.deleteAfterPublishing)
} }
} }

View File

@@ -16,7 +16,7 @@ import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.posts.models.RegisteredPost import dev.inmo.plaguposter.posts.models.RegisteredPost
import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.posts.repo.PostsRepo
import dev.inmo.tgbotapi.types.IdChatIdentifier import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageId
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -34,7 +34,7 @@ class CachedPostsRepo(
) { ) {
override val removedPostsFlow: Flow<RegisteredPost> by parentRepo::removedPostsFlow override val removedPostsFlow: Flow<RegisteredPost> by parentRepo::removedPostsFlow
override suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId? { override suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageId): PostId? {
doForAllWithNextPaging(firstPageWithOneElementPagination) { doForAllWithNextPaging(firstPageWithOneElementPagination) {
kvCache.values(it).also { kvCache.values(it).also {
it.results.forEach { it.results.forEach {

View File

@@ -4,8 +4,7 @@ import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.repos.KeyValuesRepo 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.*
import dev.inmo.tgbotapi.types.IdChatIdentifier
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
internal class ExposedContentInfoRepo( internal class ExposedContentInfoRepo(
@@ -21,9 +20,9 @@ internal class ExposedContentInfoRepo(
val ResultRow.asObject val ResultRow.asObject
get() = PostContentInfo( get() = PostContentInfo(
IdChatIdentifier(get(chatIdColumn), get(threadIdColumn)), IdChatIdentifier(RawChatId(get(chatIdColumn)), get(threadIdColumn) ?.let(::MessageThreadId)),
get(messageIdColumn), MessageId(get(messageIdColumn)),
get(groupColumn), get(groupColumn) ?.let(::MediaGroupId),
get(orderColumn) get(orderColumn)
) )

View File

@@ -10,7 +10,7 @@ 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.IdChatIdentifier
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageId
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.*
@@ -93,10 +93,10 @@ class ExposedPostsRepo(
post.content.forEach { contentInfo -> post.content.forEach { contentInfo ->
insert { insert {
it[postIdColumn] = post.id.string it[postIdColumn] = post.id.string
it[chatIdColumn] = contentInfo.chatId.chatId it[chatIdColumn] = contentInfo.chatId.chatId.long
it[threadIdColumn] = contentInfo.chatId.threadId it[threadIdColumn] = contentInfo.chatId.threadId ?.long
it[messageIdColumn] = contentInfo.messageId it[messageIdColumn] = contentInfo.messageId.long
it[groupColumn] = contentInfo.group it[groupColumn] = contentInfo.group ?.string
it[orderColumn] = contentInfo.order it[orderColumn] = contentInfo.order
} }
} }
@@ -153,13 +153,13 @@ class ExposedPostsRepo(
} }
} }
override suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId? { override suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageId): PostId? {
return transaction(database) { return transaction(database) {
with(contentRepo) { with(contentRepo) {
select { select {
chatIdColumn.eq(chatId.chatId) chatIdColumn.eq(chatId.chatId.long)
.and(chatId.threadId ?.let { threadIdColumn.eq(it) } ?: threadIdColumn.isNull()) .and(chatId.threadId ?.let { threadIdColumn.eq(it.long) } ?: threadIdColumn.isNull())
.and(messageIdColumn.eq(messageId)) .and(messageIdColumn.eq(messageId.long))
}.limit(1).firstOrNull() ?.get(postIdColumn) }.limit(1).firstOrNull() ?.get(postIdColumn)
} ?.let(::PostId) } ?.let(::PostId)
} }

View File

@@ -119,12 +119,12 @@ object Plugin : Plugin {
null null
} }
onCommand("start_post", initialFilter = { it.sameChat(config.sourceChatId) }) { onCommand("start_post", initialFilter = { config.allSourceChatIds.any { chatId -> it.sameChat(chatId) } }) {
startChain(RegistrationState.InProcess(it.chat.id, emptyList())) startChain(RegistrationState.InProcess(it.chat.id, emptyList()))
} }
onContentMessage( onContentMessage(
initialFilter = { it.sameChat(config.sourceChatId) && !FirstSourceIsCommandsFilter(it) } initialFilter = { config.allSourceChatIds.any { chatId -> it.sameChat(chatId) } && !FirstSourceIsCommandsFilter(it) }
) { ) {
startChain(RegistrationState.Finish(it.chat.id, PostContentInfo.fromMessage(it))) startChain(RegistrationState.Finish(it.chat.id, PostContentInfo.fromMessage(it)))
} }

View File

@@ -1,5 +1,6 @@
package dev.inmo.plaguposter.ratings.selector package dev.inmo.plaguposter.ratings.selector
import dev.inmo.micro_utils.repos.KeyValueRepo
import korlibs.time.DateTime import korlibs.time.DateTime
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.posts.repo.PostsRepo
@@ -9,13 +10,14 @@ import dev.inmo.plaguposter.ratings.selector.models.SelectorConfig
class DefaultSelector ( class DefaultSelector (
private val config: SelectorConfig, private val config: SelectorConfig,
private val ratingsRepo: RatingsRepo, private val ratingsRepo: RatingsRepo,
private val postsRepo: PostsRepo private val postsRepo: PostsRepo,
private val latestChosenRepo: KeyValueRepo<PostId, DateTime>
) : Selector { ) : Selector {
override suspend fun take(n: Int, now: DateTime, exclude: List<PostId>): List<PostId> { override suspend fun take(n: Int, now: DateTime, exclude: List<PostId>): List<PostId> {
val result = mutableListOf<PostId>() val result = mutableListOf<PostId>()
do { do {
val selected = config.active(now.time) ?.rating ?.select(ratingsRepo, postsRepo, result + exclude, now) ?: break val selected = config.active(now.time) ?.rating ?.select(ratingsRepo, postsRepo, result + exclude, now, latestChosenRepo) ?: break
result.add(selected) result.add(selected)
} while (result.size < n) } while (result.size < n)

View File

@@ -2,11 +2,9 @@ package dev.inmo.plaguposter.ratings.selector.models
import korlibs.time.DateTime import korlibs.time.DateTime
import korlibs.time.seconds import korlibs.time.seconds
import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.Pagination
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
import dev.inmo.micro_utils.repos.pagination.getAll import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.plaguposter.common.DateTimeSerializer import dev.inmo.micro_utils.repos.unset
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.posts.repo.PostsRepo
import dev.inmo.plaguposter.ratings.models.Rating import dev.inmo.plaguposter.ratings.models.Rating
@@ -25,17 +23,23 @@ data class RatingConfig(
val max: Rating? = null, val max: Rating? = null,
val prefer: Prefer = Prefer.Random, val prefer: Prefer = Prefer.Random,
val otherwise: RatingConfig? = null, val otherwise: RatingConfig? = null,
val postAge: Seconds? = null val postAge: Seconds? = null,
val uniqueCount: Int? = null
) { ) {
suspend fun select( suspend fun select(
ratingsRepo: RatingsRepo, ratingsRepo: RatingsRepo,
postsRepo: PostsRepo, postsRepo: PostsRepo,
exclude: List<PostId>, exclude: List<PostId>,
now: DateTime now: DateTime,
latestChosenRepo: KeyValueRepo<PostId, DateTime>
): PostId? { ): PostId? {
var reversed: Boolean = false var reversed: Boolean = false
var count: Int? = null var count: Int? = null
val allowedCreationTime = now - (postAge ?: 0).seconds val allowedCreationTime = now - (postAge ?: 0).seconds
val excludedByRepo = uniqueCount ?.let {
latestChosenRepo.getAll().toList().sortedBy { it.second }.takeLast(uniqueCount).map { it.first }
} ?: emptyList()
val resultExcluded = exclude + excludedByRepo
when (prefer) { when (prefer) {
Prefer.Max -> { Prefer.Max -> {
@@ -59,40 +63,53 @@ data class RatingConfig(
ratingsRepo.getAllByWithNextPaging { keys(it) } ratingsRepo.getAllByWithNextPaging { keys(it) }
} }
else -> { else -> {
ratingsRepo.getPostsWithRatingLessEq(max, exclude = exclude).keys ratingsRepo.getPostsWithRatingLessEq(max, exclude = resultExcluded).keys
} }
} }
} }
else -> { else -> {
when (max) { when (max) {
null -> { null -> {
ratingsRepo.getPostsWithRatingGreaterEq(min, exclude = exclude).keys ratingsRepo.getPostsWithRatingGreaterEq(min, exclude = resultExcluded).keys
} }
else -> { else -> {
ratingsRepo.getPosts(min .. max, reversed, count, exclude = exclude).keys ratingsRepo.getPosts(min .. max, reversed, count, exclude = resultExcluded).keys
} }
} }
} }
}.filter { }.filter {
it !in exclude && (postsRepo.getPostCreationTime(it) ?.let { it < allowedCreationTime } ?: true) it !in resultExcluded && (postsRepo.getPostCreationTime(it) ?.let { it < allowedCreationTime } ?: true)
} }
return when (prefer) { val resultPosts: PostId = when (prefer) {
Prefer.Max, Prefer.Max,
Prefer.Min -> posts.firstOrNull() Prefer.Min -> posts.firstOrNull()
Prefer.Random -> posts.randomOrNull() Prefer.Random -> posts.randomOrNull()
} ?: otherwise ?.select(ratingsRepo, postsRepo, exclude, now) } ?: otherwise ?.select(ratingsRepo, postsRepo, resultExcluded, now, latestChosenRepo) ?: return null
val postsToKeep = uniqueCount ?.let {
(excludedByRepo + resultPosts).takeLast(it)
} ?: return resultPosts
val postsToRemoveFromKeep = excludedByRepo.filter { it !in postsToKeep }
latestChosenRepo.unset(postsToRemoveFromKeep)
val postsToAdd = postsToKeep.filter { it !in excludedByRepo }
latestChosenRepo.set(
postsToAdd.associateWith { DateTime.now() }
)
return resultPosts
} }
@Serializable(Prefer.Serializer::class) @Serializable(Prefer.Serializer::class)
sealed interface Prefer { sealed interface Prefer {
val type: String val type: String
@Serializable(Serializer::class) @Serializable(Serializer::class)
object Max : Prefer { override val type: String = "max" } data object Max : Prefer { override val type: String = "max" }
@Serializable(Serializer::class) @Serializable(Serializer::class)
object Min : Prefer { override val type: String = "min" } data object Min : Prefer { override val type: String = "min" }
@Serializable(Serializer::class) @Serializable(Serializer::class)
object Random : Prefer { override val type: String = "random" } data object Random : Prefer { override val type: String = "random" }
object Serializer : KSerializer<Prefer> { object Serializer : KSerializer<Prefer> {
override val descriptor: SerialDescriptor = String.serializer().descriptor override val descriptor: SerialDescriptor = String.serializer().descriptor

View File

@@ -1,14 +1,33 @@
package dev.inmo.plaguposter.ratings.selector package dev.inmo.plaguposter.ratings.selector
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.ratings.selector.models.SelectorConfig import dev.inmo.plaguposter.ratings.selector.models.SelectorConfig
import korlibs.time.DateTime
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.core.qualifier.qualifier
object Plugin : Plugin { object Plugin : Plugin {
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {
single { get<Json>().decodeFromJsonElement(SelectorConfig.serializer(), params["selector"] ?: return@single null) } single { get<Json>().decodeFromJsonElement(SelectorConfig.serializer(), params["selector"] ?: return@single null) }
single<Selector> { DefaultSelector(get(), get(), get()) } single<KeyValueRepo<PostId, DateTime>>(qualifier("latestChosenRepo")) {
ExposedKeyValueRepo(
get(),
{ text("post_id") },
{ double("date_time") },
"LatestChosenRepo"
).withMapper(
{ string },
{ unixMillis },
{ PostId(this) },
{ DateTime(this) }
)
}
single<Selector> { DefaultSelector(get(), get(), get(), get(qualifier("latestChosenRepo"))) }
} }
} }

View File

@@ -1,14 +1,15 @@
package dev.inmo.plaguposter.ratings.source.repos package dev.inmo.plaguposter.ratings.source.repos
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.MapKeyValueRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.full.fullyCached import dev.inmo.micro_utils.repos.cache.full.fullyCached
import dev.inmo.plaguposter.common.ShortMessageInfo import dev.inmo.plaguposter.common.ShortMessageInfo
import dev.inmo.tgbotapi.types.PollIdentifier import dev.inmo.tgbotapi.types.PollId
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
class CachedPollsToMessagesInfoRepo( class CachedPollsToMessagesInfoRepo(
private val repo: PollsToMessagesInfoRepo, private val repo: PollsToMessagesInfoRepo,
private val scope: CoroutineScope, private val scope: CoroutineScope,
private val kvCache: FullKVCache<PollIdentifier, ShortMessageInfo> = FullKVCache() private val kvCache: KeyValueRepo<PollId, ShortMessageInfo> = MapKeyValueRepo()
) : PollsToMessagesInfoRepo, KeyValueRepo<PollIdentifier, ShortMessageInfo> by repo.fullyCached(kvCache, scope) ) : PollsToMessagesInfoRepo, KeyValueRepo<PollId, ShortMessageInfo> by repo.fullyCached(kvCache, scope)

View File

@@ -1,14 +1,15 @@
package dev.inmo.plaguposter.ratings.source.repos package dev.inmo.plaguposter.ratings.source.repos
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.MapKeyValueRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.full.fullyCached import dev.inmo.micro_utils.repos.cache.full.fullyCached
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.tgbotapi.types.PollIdentifier import dev.inmo.tgbotapi.types.PollId
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
class CachedPollsToPostsIdsRepo( class CachedPollsToPostsIdsRepo(
private val repo: PollsToPostsIdsRepo, private val repo: PollsToPostsIdsRepo,
private val scope: CoroutineScope, private val scope: CoroutineScope,
private val kvCache: FullKVCache<PollIdentifier, PostId> = FullKVCache() private val kvCache: KeyValueRepo<PollId, PostId> = MapKeyValueRepo()
) : PollsToPostsIdsRepo, KeyValueRepo<PollIdentifier, PostId> by repo.fullyCached(kvCache, scope) ) : PollsToPostsIdsRepo, KeyValueRepo<PollId, PostId> by repo.fullyCached(kvCache, scope)

View File

@@ -3,6 +3,6 @@ package dev.inmo.plaguposter.ratings.source.repos
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.plaguposter.common.ShortMessageInfo import dev.inmo.plaguposter.common.ShortMessageInfo
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.tgbotapi.types.PollIdentifier import dev.inmo.tgbotapi.types.PollId
interface PollsToMessagesInfoRepo : KeyValueRepo<PollIdentifier, ShortMessageInfo> interface PollsToMessagesInfoRepo : KeyValueRepo<PollId, ShortMessageInfo>

View File

@@ -2,6 +2,6 @@ package dev.inmo.plaguposter.ratings.source.repos
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.tgbotapi.types.PollIdentifier import dev.inmo.tgbotapi.types.PollId
interface PollsToPostsIdsRepo : KeyValueRepo<PollIdentifier, PostId> interface PollsToPostsIdsRepo : KeyValueRepo<PollId, PostId>

View File

@@ -37,6 +37,7 @@ 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.extensions.utils.types.buttons.inlineKeyboard import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard
import dev.inmo.tgbotapi.types.ReplyParameters
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
import dev.inmo.tgbotapi.types.message.textsources.bold import dev.inmo.tgbotapi.types.message.textsources.bold
import dev.inmo.tgbotapi.types.message.textsources.regular import dev.inmo.tgbotapi.types.message.textsources.regular
@@ -129,7 +130,7 @@ object Plugin : Plugin {
content.chatId, content.chatId,
config.ratingOfferText, config.ratingOfferText,
config.variants.keys.toList(), config.variants.keys.toList(),
replyToMessageId = content.messageId replyParameters = ReplyParameters(content.chatId, content.messageId)
) )
pollsToPostsIdsRepo.set(sent.content.poll.id, postId) pollsToPostsIdsRepo.set(sent.content.poll.id, postId)
pollsToMessageInfoRepo.set(sent.content.poll.id, sent.short()) pollsToMessageInfoRepo.set(sent.content.poll.id, sent.short())
@@ -242,7 +243,7 @@ object Plugin : Plugin {
} }
} }
onCommand("ratings", requireOnlyCommandInMessage = true) { onCommand("ratings", requireOnlyCommandInMessage = true) {
if (it.chat.id == chatConfig.sourceChatId) { if (it.chat.id in chatConfig.allSourceChatIds) {
val ratings = ratingsRepo.postsByRatings().toList().sortedByDescending { it.first } val ratings = ratingsRepo.postsByRatings().toList().sortedByDescending { it.first }
val textSources = buildEntities { val textSources = buildEntities {
+ "Ratings amount: " + bold("${ratings.sumOf { it.second.size }}") + "\n\n" + "Ratings amount: " + bold("${ratings.sumOf { it.second.size }}") + "\n\n"
@@ -260,8 +261,8 @@ object Plugin : Plugin {
} }
} }
} }
includeRootNavigationButtonsHandler(setOf(chatConfig.sourceChatId), ratingsRepo, postsRepo) includeRootNavigationButtonsHandler(chatConfig.allSourceChatIds, ratingsRepo, postsRepo)
onMessageDataCallbackQuery("ratings_interactive", initialFilter = { it.message.chat.id == chatConfig.sourceChatId }) { onMessageDataCallbackQuery("ratings_interactive", initialFilter = { it.message.chat.id in chatConfig.allSourceChatIds }) {
edit( edit(
it.message, it.message,
ratingsRepo.buildRootButtons() ratingsRepo.buildRootButtons()

View File

@@ -3,9 +3,7 @@ package dev.inmo.plaguposter.ratings.source.repos
import dev.inmo.micro_utils.repos.exposed.initTable 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.*
import dev.inmo.tgbotapi.types.IdChatIdentifier
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.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNull import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNull
@@ -13,7 +11,7 @@ import org.jetbrains.exposed.sql.statements.*
class ExposedPollsToMessagesInfoRepo( class ExposedPollsToMessagesInfoRepo(
database: Database database: Database
) : PollsToMessagesInfoRepo, AbstractExposedKeyValueRepo<PollIdentifier, ShortMessageInfo>( ) : PollsToMessagesInfoRepo, AbstractExposedKeyValueRepo<PollId, ShortMessageInfo>(
database, database,
"polls_to_their_messages_info" "polls_to_their_messages_info"
) { ) {
@@ -21,31 +19,32 @@ class ExposedPollsToMessagesInfoRepo(
private val chatIdColumn = long("chat_id") private val chatIdColumn = long("chat_id")
private val threadIdColumn = long("thread_id").nullable().default(null) private val threadIdColumn = long("thread_id").nullable().default(null)
private val messageIdColumn = long("message_id") private val messageIdColumn = long("message_id")
override val selectById: ISqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) } override val selectById: ISqlExpressionBuilder.(PollId) -> Op<Boolean> = { keyColumn.eq(it.string) }
override val selectByValue: ISqlExpressionBuilder.(ShortMessageInfo) -> Op<Boolean> = { override val selectByValue: ISqlExpressionBuilder.(ShortMessageInfo) -> Op<Boolean> = {
chatIdColumn.eq(it.chatId.chatId).and(it.chatId.threadId ?.let { threadIdColumn.eq(it) } ?: threadIdColumn.isNull()).and( chatIdColumn.eq(it.chatId.chatId.long)
messageIdColumn.eq(it.messageId) .and(it.chatId.threadId?.let { threadIdColumn.eq(it.long) } ?: threadIdColumn.isNull()).and(
messageIdColumn.eq(it.messageId.long)
) )
} }
override val ResultRow.asKey: PollIdentifier override val ResultRow.asKey: PollId
get() = get(keyColumn) get() = PollId(get(keyColumn))
override val ResultRow.asObject: ShortMessageInfo override val ResultRow.asObject: ShortMessageInfo
get() = ShortMessageInfo( get() = ShortMessageInfo(
IdChatIdentifier(get(chatIdColumn), get(threadIdColumn)), IdChatIdentifier(RawChatId(get(chatIdColumn)), get(threadIdColumn) ?.let(::MessageThreadId)),
get(messageIdColumn) MessageId(get(messageIdColumn))
) )
init { init {
initTable() initTable()
} }
override fun update(k: PollIdentifier, v: ShortMessageInfo, it: UpdateBuilder<Int>) { override fun update(k: PollId, v: ShortMessageInfo, it: UpdateBuilder<Int>) {
it[chatIdColumn] = v.chatId.chatId it[chatIdColumn] = v.chatId.chatId.long
it[threadIdColumn] = v.chatId.threadId it[threadIdColumn] = v.chatId.threadId ?.long
it[messageIdColumn] = v.messageId it[messageIdColumn] = v.messageId.long
} }
override fun insertKey(k: PollIdentifier, v: ShortMessageInfo, it: InsertStatement<Number>) { override fun insertKey(k: PollId, v: ShortMessageInfo, it: InsertStatement<Number>) {
it[keyColumn] = k it[keyColumn] = k.string
} }
} }

View File

@@ -3,19 +3,19 @@ package dev.inmo.plaguposter.ratings.source.repos
import dev.inmo.micro_utils.repos.exposed.initTable 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.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.tgbotapi.types.PollIdentifier import dev.inmo.tgbotapi.types.PollId
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.statements.* import org.jetbrains.exposed.sql.statements.*
class ExposedPollsToPostsIdsRepo( class ExposedPollsToPostsIdsRepo(
database: Database database: Database
) : PollsToPostsIdsRepo, AbstractExposedKeyValueRepo<PollIdentifier, PostId>(database, "polls_to_posts") { ) : PollsToPostsIdsRepo, AbstractExposedKeyValueRepo<PollId, 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: ISqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) } override val selectById: ISqlExpressionBuilder.(PollId) -> Op<Boolean> = { keyColumn.eq(it.string) }
override val selectByValue: ISqlExpressionBuilder.(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: PollId
get() = get(keyColumn) get() = PollId(get(keyColumn))
override val ResultRow.asObject: PostId override val ResultRow.asObject: PostId
get() = get(postIdColumn).let(::PostId) get() = get(postIdColumn).let(::PostId)
@@ -23,11 +23,11 @@ class ExposedPollsToPostsIdsRepo(
initTable() initTable()
} }
override fun update(k: PollIdentifier, v: PostId, it: UpdateBuilder<Int>) { override fun update(k: PollId, v: PostId, it: UpdateBuilder<Int>) {
it[postIdColumn] = v.string it[postIdColumn] = v.string
} }
override fun insertKey(k: PollIdentifier, v: PostId, it: InsertStatement<Number>) { override fun insertKey(k: PollId, v: PostId, it: InsertStatement<Number>) {
it[keyColumn] = k it[keyColumn] = k.string
} }
} }

View File

@@ -2,7 +2,7 @@ package dev.inmo.plaguposter.ratings.repo
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache import dev.inmo.micro_utils.repos.MapKeyValueRepo
import dev.inmo.micro_utils.repos.cache.full.FullKeyValueCacheRepo import dev.inmo.micro_utils.repos.cache.full.FullKeyValueCacheRepo
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.ratings.models.Rating import dev.inmo.plaguposter.ratings.models.Rating
@@ -11,7 +11,7 @@ import kotlinx.coroutines.CoroutineScope
class CachedRatingsRepo( class CachedRatingsRepo(
private val base: RatingsRepo, private val base: RatingsRepo,
private val scope: CoroutineScope, private val scope: CoroutineScope,
private val kvCache: FullKVCache<PostId, Rating> = FullKVCache() private val kvCache: MapKeyValueRepo<PostId, Rating> = MapKeyValueRepo()
) : RatingsRepo, KeyValueRepo<PostId, Rating> by FullKeyValueCacheRepo(base, kvCache, scope) { ) : RatingsRepo, KeyValueRepo<PostId, Rating> by FullKeyValueCacheRepo(base, kvCache, scope) {
override suspend fun getPosts( override suspend fun getPosts(
range: ClosedRange<Rating>, range: ClosedRange<Rating>,

View File

@@ -5,7 +5,6 @@ import dev.inmo.micro_utils.koin.singleWithBinds
import dev.inmo.micro_utils.repos.unset import dev.inmo.micro_utils.repos.unset
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plaguposter.common.useCache import dev.inmo.plaguposter.common.useCache
import dev.inmo.plaguposter.posts.exposed.ExposedPostsRepo
import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.posts.repo.PostsRepo
import dev.inmo.plaguposter.ratings.exposed.ExposedRatingsRepo import dev.inmo.plaguposter.ratings.exposed.ExposedRatingsRepo
import dev.inmo.plaguposter.ratings.repo.* import dev.inmo.plaguposter.ratings.repo.*
@@ -14,7 +13,6 @@ import kotlinx.serialization.json.*
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.dsl.binds
object Plugin : Plugin { object Plugin : Plugin {
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {

View File

@@ -49,7 +49,7 @@ class ExposedRatingsRepo (
count: Int?, count: Int?,
exclude: List<PostId> exclude: List<PostId>
): Map<PostId, Rating> = transaction(database) { ): Map<PostId, Rating> = transaction(database) {
select { selectAll().where {
ratingsColumn.greaterEq(range.start.double).and( ratingsColumn.greaterEq(range.start.double).and(
ratingsColumn.lessEq(range.endInclusive.double) ratingsColumn.lessEq(range.endInclusive.double)
).and( ).and(
@@ -66,7 +66,7 @@ class ExposedRatingsRepo (
count: Int?, count: Int?,
exclude: List<PostId> exclude: List<PostId>
) = transaction(database) { ) = transaction(database) {
select { ratingsColumn.greaterEq(then.double).and(keyColumn.notInList(exclude.map { it.string })) }.optionallyLimit(count).optionallyReverse(reversed).map { selectAll().where { ratingsColumn.greaterEq(then.double).and(keyColumn.notInList(exclude.map { it.string })) }.optionallyLimit(count).optionallyReverse(reversed).map {
it.asKey to it.asObject it.asKey to it.asObject
} }
}.toMap() }.toMap()
@@ -77,7 +77,7 @@ class ExposedRatingsRepo (
count: Int?, count: Int?,
exclude: List<PostId> exclude: List<PostId>
): Map<PostId, Rating> = transaction(database) { ): Map<PostId, Rating> = transaction(database) {
select { ratingsColumn.lessEq(then.double).and(keyColumn.notInList(exclude.map { it.string })) }.optionallyLimit(count).optionallyReverse(reversed).map { selectAll().where { ratingsColumn.lessEq(then.double).and(keyColumn.notInList(exclude.map { it.string })) }.optionallyLimit(count).optionallyReverse(reversed).map {
it.asKey to it.asObject it.asKey to it.asObject
} }
}.toMap() }.toMap()

View File

@@ -51,7 +51,8 @@
"to": "23:59" "to": "23:59"
}, },
"rating": { "rating": {
"prefer": "max" "prefer": "max",
"uniqueCount": 1
} }
}, },
{ {
@@ -60,7 +61,8 @@
"to": "00:00" "to": "00:00"
}, },
"rating": { "rating": {
"prefer": "max" "prefer": "max",
"uniqueCount": 1
} }
} }
] ]

View File

@@ -11,12 +11,14 @@ services:
POSTGRES_DB: "test" POSTGRES_DB: "test"
volumes: volumes:
- "./db/:/var/lib/postgresql/data" - "./db/:/var/lib/postgresql/data"
- "/etc/timezone:/etc/timezone:ro"
plaguposter: plaguposter:
image: insanusmokrassar/plaguposter:latest image: insanusmokrassar/plaguposter:latest
container_name: "plaguposter" container_name: "plaguposter"
restart: "unless-stopped" restart: "unless-stopped"
volumes: volumes:
- "./config.json:/config.json" - "./config.json:/config.json"
- "/etc/timezone:/etc/timezone:ro"
depends_on: depends_on:
- "plaguposter_postgres" - "plaguposter_postgres"
links: links:

View File

@@ -24,7 +24,7 @@ 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.ChatId
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageId
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
import dev.inmo.tgbotapi.types.message.textsources.regular import dev.inmo.tgbotapi.types.message.textsources.regular
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@@ -38,8 +38,8 @@ object Plugin : Plugin {
@Serializable @Serializable
private data class PublishState( private data class PublishState(
override val context: ChatId, override val context: ChatId,
val sourceMessageId: MessageIdentifier, val sourceMessageId: MessageId,
val messageInReply: MessageIdentifier val messageInReply: MessageId
) : State ) : State
@Serializable @Serializable
internal data class Config( internal data class Config(

View File

@@ -151,7 +151,7 @@ object Plugin : Plugin {
} }
} }
onCommand("autoschedule_panel", initialFilter = { it.sameChat(chatConfig.sourceChatId) }) { onCommand("autoschedule_panel", initialFilter = { chatConfig.allSourceChatIds.any { chatId -> it.sameChat(chatId) } }) {
val keyboard = buildPage() val keyboard = buildPage()
runCatchingSafely { runCatchingSafely {
@@ -167,7 +167,7 @@ object Plugin : Plugin {
onMessageDataCallbackQuery( onMessageDataCallbackQuery(
Regex("^$pageCallbackDataQueryPrefix\\d+"), Regex("^$pageCallbackDataQueryPrefix\\d+"),
initialFilter = { it.message.sameChat(chatConfig.sourceChatId) } initialFilter = { chatConfig.allSourceChatIds.any { sourceChatId -> it.message.sameChat(sourceChatId) } }
) { ) {
val page = it.data.removePrefix(pageCallbackDataQueryPrefix).toIntOrNull() ?: let { _ -> val page = it.data.removePrefix(pageCallbackDataQueryPrefix).toIntOrNull() ?: let { _ ->
answer(it) answer(it)

View File

@@ -7,6 +7,7 @@ import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.repos.unset import dev.inmo.micro_utils.repos.unset
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.posts.sending.PostPublisher import dev.inmo.plaguposter.posts.sending.PostPublisher
import korlibs.time.millisecondsLong
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay