mirror of
https://github.com/InsanusMokrassar/PlaguPoster.git
synced 2026-06-01 10:57:19 +00:00
Compare commits
27 Commits
ee80d8a3a1
...
0.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 6df4546b81 | |||
| 15bd013eaa | |||
| 39b607c4e7 | |||
| 98f3e2a461 | |||
| 0d31d90efd | |||
| 0ce202a5f6 | |||
| 077f8c30a6 | |||
| 1e9559a2c9 | |||
| feef8efee1 | |||
| 54eb7515d3 | |||
| 1bb12bee0e | |||
| 467525e48d | |||
| 29e5a04135 | |||
| 6eb43055a7 | |||
| 57eebb61d5 | |||
| 87957dba30 | |||
| 20148c02f0 | |||
| e17cfa1c7c | |||
| 0a5ffee808 | |||
| 847b285ce3 | |||
| c449457d86 | |||
| 1b3a632d7b | |||
| ebfa79cf64 | |||
| e59c7b0f7e | |||
| 7a4fb05bfb | |||
| 7bc7bf6e8c | |||
| c64faf75d0 |
55
CHANGELOG.md
55
CHANGELOG.md
@@ -1,8 +1,61 @@
|
|||||||
# PlaguPoster
|
# PlaguPoster
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `tgbotapi`: `9.1.0`
|
||||||
|
* `plagubot`: `7.1.0`
|
||||||
|
* `plagubot-plugins`: `0.14.0`
|
||||||
|
|
||||||
|
## 0.2.3
|
||||||
|
|
||||||
|
* Add opportunity to use several target chat ids
|
||||||
|
* Update dependencies
|
||||||
|
|
||||||
|
## 0.2.2
|
||||||
|
|
||||||
|
* `GarbageCollector`:
|
||||||
|
* Now on start will all clearing job done
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `kotlin`: `1.8.21`
|
||||||
|
* `tgbotapi`: `7.1.2`
|
||||||
|
* `plagubot`: `5.1.2`
|
||||||
|
* `microutils`: `0.18.1`
|
||||||
|
* `kslog`: `1.1.1`
|
||||||
|
* `plagubot.plugins`: `0.11.2`
|
||||||
|
* `psql`: `42.6.0`
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `tgbotapi`: `7.1.0`
|
||||||
|
* `plagubot`: `5.1.0`
|
||||||
|
* `krontab`: `1.0.0`
|
||||||
|
* `plagubot.plugins`: `0.11.0`
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `kotlin`: `1.8.20`
|
||||||
|
* `plagubot`: `5.0.2`
|
||||||
|
* `microutils`: `0.17.8`
|
||||||
|
* `kslog`: `1.1.1`
|
||||||
|
* `plagubot.plugins`: `0.10.2`
|
||||||
|
* `psql`: `42.6.0`
|
||||||
|
|
||||||
|
## 0.1.1
|
||||||
|
|
||||||
|
* Update dependencies
|
||||||
|
* `Triggers`
|
||||||
|
* `SelectorWithTimer`
|
||||||
|
* Opportunity to get schedule of posts using `publishing_autoschedule` command
|
||||||
|
|
||||||
## 0.0.10
|
## 0.0.10
|
||||||
|
|
||||||
## 0.0.9
|
## 0.0.9
|
||||||
|
|
||||||
* Update depedencies
|
* Update dependencies
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package dev.inmo.plaguposter.common
|
package dev.inmo.plaguposter.common
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
|
||||||
import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer
|
import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer
|
||||||
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
@@ -10,14 +9,26 @@ import kotlinx.serialization.Serializable
|
|||||||
data class ChatConfig(
|
data class ChatConfig(
|
||||||
@SerialName("targetChat")
|
@SerialName("targetChat")
|
||||||
@Serializable(FullChatIdentifierSerializer::class)
|
@Serializable(FullChatIdentifierSerializer::class)
|
||||||
val targetChatId: IdChatIdentifier,
|
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")
|
||||||
|
val targetChatIds: List<@Serializable(FullChatIdentifierSerializer::class) IdChatIdentifier> = emptyList(),
|
||||||
) {
|
) {
|
||||||
|
val allTargetChatIds by lazy {
|
||||||
|
listOfNotNull(targetChatId) + targetChatIds
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(targetChatId != null || targetChatIds.isNotEmpty()) {
|
||||||
|
"One of fields, 'targetChat' or 'targetChats' should be presented"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun check(chatId: IdChatIdentifier) = when (chatId) {
|
fun check(chatId: IdChatIdentifier) = when (chatId) {
|
||||||
targetChatId,
|
targetChatId,
|
||||||
sourceChatId,
|
sourceChatId,
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package dev.inmo.plaguposter.common
|
|||||||
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull
|
||||||
import dev.inmo.tgbotapi.extensions.utils.textContentOrNull
|
import dev.inmo.tgbotapi.extensions.utils.textContentOrNull
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
||||||
import dev.inmo.tgbotapi.types.BotCommand
|
import dev.inmo.tgbotapi.types.BotCommand
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.*
|
import dev.inmo.tgbotapi.types.message.abstracts.*
|
||||||
|
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||||
import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource
|
import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource
|
||||||
|
|
||||||
val FirstSourceIsCommandsFilter = SimpleFilter<Message> {
|
val FirstSourceIsCommandsFilter = SimpleFilter<Message> {
|
||||||
it is ContentMessage<*> && it.content.textContentOrNull() ?.textSources ?.firstOrNull {
|
it.contentMessageOrNull() ?.withContentOrNull<TextContent>() ?.content ?.textSources ?.firstOrNull() is BotCommandTextSource
|
||||||
it is BotCommandTextSource
|
|
||||||
} != null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.common
|
package dev.inmo.plaguposter.common
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.Serializer
|
import kotlinx.serialization.Serializer
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package dev.inmo.plaguposter.common
|
package dev.inmo.plaguposter.common
|
||||||
|
|
||||||
import dev.inmo.kslog.common.i
|
|
||||||
import dev.inmo.kslog.common.iS
|
import dev.inmo.kslog.common.iS
|
||||||
import dev.inmo.kslog.common.logger
|
import dev.inmo.kslog.common.logger
|
||||||
import dev.inmo.plagubot.Plugin
|
import dev.inmo.plagubot.Plugin
|
||||||
@@ -27,7 +26,7 @@ object CommonPlugin : Plugin {
|
|||||||
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
||||||
val config = koin.get<ChatConfig>()
|
val config = koin.get<ChatConfig>()
|
||||||
|
|
||||||
Log.iS { "Target chat info: ${getChat(config.targetChatId)}" }
|
Log.iS { "Target chats info: ${config.allTargetChatIds.map { getChat(it) }.joinToString()}" }
|
||||||
Log.iS { "Source chat info: ${getChat(config.sourceChatId)}" }
|
Log.iS { "Source chat info: ${getChat(config.sourceChatId)}" }
|
||||||
Log.iS { "Cache chat info: ${getChat(config.cacheChatId)}" }
|
Log.iS { "Cache chat info: ${getChat(config.cacheChatId)}" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ android.enableJetifier=true
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.1.0
|
version=0.4.0
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kotlin = "1.8.10"
|
kotlin = "1.8.22"
|
||||||
kotlin-serialization = "1.5.0"
|
kotlin-serialization = "1.5.1"
|
||||||
|
|
||||||
plagubot = "5.0.0"
|
plagubot = "7.2.1"
|
||||||
tgbotapi = "7.0.0"
|
tgbotapi = "9.2.1"
|
||||||
microutils = "0.17.5"
|
microutils = "0.19.9"
|
||||||
kslog = "1.0.0"
|
kslog = "1.1.2"
|
||||||
krontab = "0.9.0"
|
krontab = "2.1.2"
|
||||||
tgbotapi-libraries = "0.10.0"
|
plagubot-plugins = "0.15.1"
|
||||||
plagubot-plugins = "0.10.0"
|
|
||||||
|
|
||||||
dokka = "1.8.10"
|
dokka = "1.8.20"
|
||||||
|
|
||||||
psql = "42.5.0"
|
psql = "42.6.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.posts.models
|
package dev.inmo.plaguposter.posts.models
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import dev.inmo.plaguposter.common.DateTimeSerializer
|
import dev.inmo.plaguposter.common.DateTimeSerializer
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.posts.repo
|
package dev.inmo.plaguposter.posts.repo
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.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
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class PostPublisher(
|
|||||||
private val bot: TelegramBot,
|
private val bot: TelegramBot,
|
||||||
private val postsRepo: PostsRepo,
|
private val postsRepo: PostsRepo,
|
||||||
private val cachingChatId: IdChatIdentifier,
|
private val cachingChatId: IdChatIdentifier,
|
||||||
private val targetChatId: IdChatIdentifier,
|
private val targetChatIds: List<IdChatIdentifier>,
|
||||||
private val deleteAfterPosting: Boolean = true
|
private val deleteAfterPosting: Boolean = true
|
||||||
) {
|
) {
|
||||||
suspend fun publish(postId: PostId) {
|
suspend fun publish(postId: PostId) {
|
||||||
@@ -38,17 +38,19 @@ class PostPublisher(
|
|||||||
|
|
||||||
sortedMessagesContents.forEach { (_, contents) ->
|
sortedMessagesContents.forEach { (_, contents) ->
|
||||||
contents.singleOrNull() ?.also {
|
contents.singleOrNull() ?.also {
|
||||||
runCatching {
|
targetChatIds.forEach { targetChatId ->
|
||||||
bot.copyMessage(targetChatId, it.chatId, it.messageId)
|
|
||||||
}.onFailure { _ ->
|
|
||||||
runCatching {
|
runCatching {
|
||||||
bot.forwardMessage(
|
bot.copyMessage(targetChatId, it.chatId, it.messageId)
|
||||||
it.chatId,
|
}.onFailure { _ ->
|
||||||
targetChatId,
|
runCatching {
|
||||||
it.messageId
|
bot.forwardMessage(
|
||||||
)
|
it.chatId,
|
||||||
}.onSuccess {
|
targetChatId,
|
||||||
bot.copyMessage(targetChatId, it)
|
it.messageId
|
||||||
|
)
|
||||||
|
}.onSuccess {
|
||||||
|
bot.copyMessage(targetChatId, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@forEach
|
return@forEach
|
||||||
@@ -57,17 +59,23 @@ class PostPublisher(
|
|||||||
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 { (_, forwardedMessage) ->
|
}.sortedBy { it.first }.mapNotNull { (_, forwardedMessage) ->
|
||||||
forwardedMessage.withContentOrNull<MediaGroupPartContent>() ?: null.also { _ ->
|
forwardedMessage.withContentOrNull<MediaGroupPartContent>() ?: null.also { _ ->
|
||||||
bot.copyMessage(targetChatId, forwardedMessage)
|
targetChatIds.forEach { targetChatId ->
|
||||||
|
bot.copyMessage(targetChatId, forwardedMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultContents.singleOrNull() ?.also {
|
resultContents.singleOrNull() ?.also {
|
||||||
bot.copyMessage(targetChatId, it)
|
targetChatIds.forEach { targetChatId ->
|
||||||
|
bot.copyMessage(targetChatId, it)
|
||||||
|
}
|
||||||
return@forEach
|
return@forEach
|
||||||
} ?: resultContents.chunked(mediaCountInMediaGroup.last).forEach {
|
} ?: resultContents.chunked(mediaCountInMediaGroup.last).forEach {
|
||||||
bot.send(
|
targetChatIds.forEach { targetChatId ->
|
||||||
targetChatId,
|
bot.send(
|
||||||
it.map { it.content.toMediaGroupMemberTelegramMedia() }
|
targetChatId,
|
||||||
)
|
it.map { it.content.toMediaGroupMemberTelegramMedia() }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,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 {
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -59,7 +58,7 @@ object Plugin : Plugin {
|
|||||||
}
|
}
|
||||||
single {
|
single {
|
||||||
val config = get<Config>()
|
val config = get<Config>()
|
||||||
PostPublisher(get(), get(), config.chats.cacheChatId, config.chats.targetChatId, config.deleteAfterPublishing)
|
PostPublisher(get(), get(), config.chats.cacheChatId, config.chats.allTargetChatIds, config.deleteAfterPublishing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.posts.cached
|
package dev.inmo.plaguposter.posts.cached
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
|
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
|
||||||
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
@@ -25,6 +25,7 @@ class CachedPostsRepo(
|
|||||||
parentRepo,
|
parentRepo,
|
||||||
kvCache,
|
kvCache,
|
||||||
scope,
|
scope,
|
||||||
|
skipStartInvalidate = false,
|
||||||
{ it.id }
|
{ it.id }
|
||||||
) {
|
) {
|
||||||
override val removedPostsFlow: Flow<RegisteredPost> by parentRepo::removedPostsFlow
|
override val removedPostsFlow: Flow<RegisteredPost> by parentRepo::removedPostsFlow
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.plaguposter.posts.exposed
|
package dev.inmo.plaguposter.posts.exposed
|
||||||
|
|
||||||
import com.benasher44.uuid.uuid4
|
import com.benasher44.uuid.uuid4
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
import dev.inmo.micro_utils.repos.KeyValuesRepo
|
||||||
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
import dev.inmo.micro_utils.repos.UpdatedValuePair
|
||||||
import dev.inmo.micro_utils.repos.exposed.AbstractExposedCRUDRepo
|
import dev.inmo.micro_utils.repos.exposed.AbstractExposedCRUDRepo
|
||||||
|
|||||||
@@ -18,84 +18,95 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextWithFSM
|
|||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.*
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.*
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.strictlyOn
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.strictlyOn
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
||||||
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.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.content.MediaGroupContent
|
|
||||||
import dev.inmo.tgbotapi.types.message.content.MessageContent
|
|
||||||
import dev.inmo.tgbotapi.utils.regular
|
import dev.inmo.tgbotapi.utils.regular
|
||||||
|
import kotlinx.coroutines.async
|
||||||
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
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object Plugin : Plugin {
|
object Plugin : Plugin {
|
||||||
|
@Serializable
|
||||||
|
data class Config(
|
||||||
|
val useInlineFinishingOpportunity: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun BehaviourContextWithFSM<State>.setupBotPlugin(koin: Koin) {
|
override suspend fun BehaviourContextWithFSM<State>.setupBotPlugin(koin: Koin) {
|
||||||
val config = koin.get<ChatConfig>()
|
val config = koin.get<ChatConfig>()
|
||||||
val postsRepo = koin.get<PostsRepo>()
|
val postsRepo = koin.get<PostsRepo>()
|
||||||
|
|
||||||
strictlyOn {state: RegistrationState.InProcess ->
|
strictlyOn { state: RegistrationState.InProcess ->
|
||||||
val buttonUuid = "finish"
|
val buttonUuid = "finish"
|
||||||
|
|
||||||
val messageToDelete = send(
|
val suggestionMessageDeferred = async {
|
||||||
state.context,
|
send(
|
||||||
dev.inmo.tgbotapi.utils.buildEntities {
|
state.context,
|
||||||
if (state.messages.isNotEmpty()) {
|
dev.inmo.tgbotapi.utils.buildEntities {
|
||||||
regular("Your message(s) has been registered. You may send new ones or push \"Finish\" to finalize your post")
|
if (state.messages.isNotEmpty()) {
|
||||||
|
regular("Your message(s) has been registered. You may send new ones or push \"Finish\" to finalize your post")
|
||||||
|
} else {
|
||||||
|
regular("Ok, send me your messages for new post")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
replyMarkup = if (state.messages.isNotEmpty()) {
|
||||||
|
flatInlineKeyboard {
|
||||||
|
dataButton(
|
||||||
|
"Finish",
|
||||||
|
buttonUuid
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
regular("Ok, send me your messages for new post")
|
null
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
replyMarkup = if (state.messages.isNotEmpty()) {
|
}
|
||||||
flatInlineKeyboard {
|
|
||||||
dataButton(
|
|
||||||
"Finish",
|
|
||||||
buttonUuid
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val newMessagesInfo = firstOf {
|
firstOf {
|
||||||
add {
|
add {
|
||||||
listOf(
|
val receivedMessage = waitAnyContentMessage().filter {
|
||||||
waitAnyContentMessage().filter {
|
it.sameChat(state.context)
|
||||||
it.chat.id == state.context && it.content.textContentOrNull() ?.text != "/finish_post"
|
}.first()
|
||||||
}.take(1).first()
|
|
||||||
)
|
when {
|
||||||
|
receivedMessage.content.textContentOrNull() ?.text == "/finish_post" -> {
|
||||||
|
val messageToDelete = suggestionMessageDeferred.await()
|
||||||
|
edit(messageToDelete, "Ok, finishing your request")
|
||||||
|
RegistrationState.Finish(
|
||||||
|
state.context,
|
||||||
|
state.messages
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
RegistrationState.InProcess(
|
||||||
|
state.context,
|
||||||
|
state.messages + PostContentInfo.fromMessage(receivedMessage)
|
||||||
|
).also {
|
||||||
|
runCatchingSafely {
|
||||||
|
suggestionMessageDeferred.cancel()
|
||||||
|
}
|
||||||
|
runCatchingSafely {
|
||||||
|
delete(suggestionMessageDeferred.await())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
add {
|
add {
|
||||||
|
val messageToDelete = suggestionMessageDeferred.await()
|
||||||
val finishPressed = waitMessageDataCallbackQuery().filter {
|
val finishPressed = waitMessageDataCallbackQuery().filter {
|
||||||
it.message.sameMessage(messageToDelete) && it.data == buttonUuid
|
it.message.sameMessage(messageToDelete) && it.data == buttonUuid
|
||||||
}.first()
|
}.first()
|
||||||
emptyList<ContentMessage<MessageContent>>()
|
|
||||||
}
|
|
||||||
add {
|
|
||||||
val finishPressed = waitTextMessage().filter {
|
|
||||||
it.sameChat(messageToDelete) && it.content.text == "/finish_post"
|
|
||||||
}.first()
|
|
||||||
emptyList<ContentMessage<MessageContent>>()
|
|
||||||
}
|
|
||||||
}.ifEmpty {
|
|
||||||
edit(messageToDelete, "Ok, finishing your request")
|
|
||||||
return@strictlyOn RegistrationState.Finish(
|
|
||||||
state.context,
|
|
||||||
state.messages
|
|
||||||
)
|
|
||||||
}.flatMap {
|
|
||||||
PostContentInfo.fromMessage(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
RegistrationState.InProcess(
|
edit(messageToDelete, "Ok, finishing your request")
|
||||||
state.context,
|
RegistrationState.Finish(
|
||||||
state.messages + newMessagesInfo
|
state.context,
|
||||||
).also {
|
state.messages
|
||||||
delete(messageToDelete)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +119,12 @@ object Plugin : Plugin {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
onCommand("start_post", initialFilter = { it.chat.id == config.sourceChatId }) {
|
onCommand("start_post", initialFilter = { it.sameChat(config.sourceChatId) }) {
|
||||||
startChain(RegistrationState.InProcess(it.chat.id, emptyList()))
|
startChain(RegistrationState.InProcess(it.chat.id, emptyList()))
|
||||||
}
|
}
|
||||||
|
|
||||||
onContentMessage(
|
onContentMessage(
|
||||||
initialFilter = { it.chat.id == config.sourceChatId && !FirstSourceIsCommandsFilter(it) }
|
initialFilter = { it.sameChat(config.sourceChatId) && !FirstSourceIsCommandsFilter(it) }
|
||||||
) {
|
) {
|
||||||
startChain(RegistrationState.Finish(it.chat.id, PostContentInfo.fromMessage(it)))
|
startChain(RegistrationState.Finish(it.chat.id, PostContentInfo.fromMessage(it)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
package dev.inmo.plaguposter.ratings.gc
|
package dev.inmo.plaguposter.ratings.gc
|
||||||
|
|
||||||
import com.soywiz.klock.milliseconds
|
import korlibs.time.DateTime
|
||||||
import com.soywiz.klock.seconds
|
import korlibs.time.seconds
|
||||||
import dev.inmo.krontab.KrontabTemplate
|
import dev.inmo.krontab.KrontabTemplate
|
||||||
import dev.inmo.krontab.toSchedule
|
import dev.inmo.krontab.toSchedule
|
||||||
import dev.inmo.krontab.utils.asFlow
|
import dev.inmo.krontab.utils.asFlowWithDelays
|
||||||
|
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.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.plagubot.Plugin
|
import dev.inmo.plagubot.Plugin
|
||||||
|
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
|
||||||
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
|
||||||
import dev.inmo.tgbotapi.types.MilliSeconds
|
|
||||||
import dev.inmo.tgbotapi.types.Seconds
|
import dev.inmo.tgbotapi.types.Seconds
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
@@ -43,21 +44,35 @@ object Plugin : Plugin {
|
|||||||
val config = koin.get<Config>()
|
val config = koin.get<Config>()
|
||||||
|
|
||||||
config.immediateDrop ?.let { toDrop ->
|
config.immediateDrop ?.let { toDrop ->
|
||||||
ratingsRepo.onNewValue.subscribeSafelyWithoutExceptions(this) {
|
suspend fun checkAndOptionallyDrop(postId: PostId, rating: Rating) {
|
||||||
if (it.value <= toDrop) {
|
if (rating <= toDrop) {
|
||||||
postsRepo.deleteById(it.id)
|
postsRepo.deleteById(postId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ratingsRepo.getAll().forEach {
|
||||||
|
runCatchingSafely {
|
||||||
|
checkAndOptionallyDrop(it.key, it.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ratingsRepo.onNewValue.subscribeSafelyWithoutExceptions(this) {
|
||||||
|
checkAndOptionallyDrop(it.first, it.second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
config.autoclear ?.let { autoclear ->
|
config.autoclear ?.let { autoclear ->
|
||||||
autoclear.autoClearKrontab.toSchedule().asFlow().subscribeSafelyWithoutExceptions(scope) {
|
suspend fun doAutoClear() {
|
||||||
val dropCreatedBefore = it - (autoclear.skipPostAge ?: 0).seconds
|
val dropCreatedBefore = DateTime.now() - (autoclear.skipPostAge ?: 0).seconds
|
||||||
ratingsRepo.getPostsWithRatingLessEq(autoclear.rating).keys.forEach {
|
ratingsRepo.getPostsWithRatingLessEq(autoclear.rating).keys.forEach {
|
||||||
if ((postsRepo.getPostCreationTime(it) ?: return@forEach) < dropCreatedBefore) {
|
if ((postsRepo.getPostCreationTime(it) ?: return@forEach) < dropCreatedBefore) {
|
||||||
postsRepo.deleteById(it)
|
postsRepo.deleteById(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
runCatchingSafely {
|
||||||
|
doAutoClear()
|
||||||
|
}
|
||||||
|
autoclear.autoClearKrontab.toSchedule().asFlowWithDelays().subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
doAutoClear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.ratings.selector
|
package dev.inmo.plaguposter.ratings.selector
|
||||||
|
|
||||||
import com.soywiz.klock.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
|
||||||
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
||||||
@@ -11,11 +11,11 @@ class DefaultSelector (
|
|||||||
private val ratingsRepo: RatingsRepo,
|
private val ratingsRepo: RatingsRepo,
|
||||||
private val postsRepo: PostsRepo
|
private val postsRepo: PostsRepo
|
||||||
) : Selector {
|
) : Selector {
|
||||||
override suspend fun take(n: Int, now: DateTime): 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, now) ?: break
|
val selected = config.active(now.time) ?.rating ?.select(ratingsRepo, postsRepo, result + exclude, now) ?: break
|
||||||
result.add(selected)
|
result.add(selected)
|
||||||
} while (result.size < n)
|
} while (result.size < n)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package dev.inmo.plaguposter.ratings.selector
|
package dev.inmo.plaguposter.ratings.selector
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
|
||||||
interface Selector {
|
interface Selector {
|
||||||
suspend fun take(n: Int = 1, now: DateTime = DateTime.now()): List<PostId>
|
suspend fun take(n: Int = 1, now: DateTime = DateTime.now(), exclude: List<PostId> = emptyList()): List<PostId>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.plaguposter.ratings.selector.models
|
package dev.inmo.plaguposter.ratings.selector.models
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import com.soywiz.klock.seconds
|
import korlibs.time.seconds
|
||||||
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
|
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.plaguposter.ratings.selector.models
|
package dev.inmo.plaguposter.ratings.selector.models
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import com.soywiz.klock.Time
|
import korlibs.time.Time
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.ratings.selector.models
|
package dev.inmo.plaguposter.ratings.selector.models
|
||||||
|
|
||||||
import com.soywiz.klock.*
|
import korlibs.time.*
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.ratings.source.buttons
|
package dev.inmo.plaguposter.ratings.source.buttons
|
||||||
|
|
||||||
import com.soywiz.klock.DateFormat
|
import korlibs.time.DateFormat
|
||||||
import dev.inmo.kslog.common.TagLogger
|
import dev.inmo.kslog.common.TagLogger
|
||||||
import dev.inmo.kslog.common.d
|
import dev.inmo.kslog.common.d
|
||||||
import dev.inmo.kslog.common.i
|
import dev.inmo.kslog.common.i
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
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.cache.KeyValueCacheRepo
|
|
||||||
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.cached
|
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.PollIdentifier
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -12,4 +11,4 @@ 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: FullKVCache<PollIdentifier, ShortMessageInfo> = FullKVCache()
|
||||||
) : PollsToMessagesInfoRepo, KeyValueRepo<PollIdentifier, ShortMessageInfo> by repo.cached(kvCache, scope)
|
) : PollsToMessagesInfoRepo, KeyValueRepo<PollIdentifier, ShortMessageInfo> by repo.fullyCached(kvCache, scope)
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ 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.cache.cache.FullKVCache
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
import dev.inmo.micro_utils.repos.cache.full.cached
|
import dev.inmo.micro_utils.repos.cache.full.fullyCached
|
||||||
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.PollIdentifier
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -12,4 +11,4 @@ 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: FullKVCache<PollIdentifier, PostId> = FullKVCache()
|
||||||
) : PollsToPostsIdsRepo, KeyValueRepo<PollIdentifier, PostId> by repo.cached(kvCache, scope)
|
) : PollsToPostsIdsRepo, KeyValueRepo<PollIdentifier, PostId> by repo.fullyCached(kvCache, scope)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"dev.inmo.plaguposter.ratings.Plugin",
|
"dev.inmo.plaguposter.ratings.Plugin",
|
||||||
"dev.inmo.plaguposter.ratings.source.Plugin",
|
"dev.inmo.plaguposter.ratings.source.Plugin",
|
||||||
"dev.inmo.plaguposter.ratings.selector.Plugin",
|
"dev.inmo.plaguposter.ratings.selector.Plugin",
|
||||||
|
"dev.inmo.plaguposter.ratings.gc.Plugin",
|
||||||
"dev.inmo.plaguposter.triggers.selector_with_timer.Plugin",
|
"dev.inmo.plaguposter.triggers.selector_with_timer.Plugin",
|
||||||
"dev.inmo.plagubot.plugins.inline.queries.Plugin",
|
"dev.inmo.plagubot.plugins.inline.queries.Plugin",
|
||||||
"dev.inmo.plaguposter.triggers.command.Plugin",
|
"dev.inmo.plaguposter.triggers.command.Plugin",
|
||||||
@@ -25,16 +26,18 @@
|
|||||||
"chats": {
|
"chats": {
|
||||||
"targetChat": 12345678,
|
"targetChat": 12345678,
|
||||||
"cacheChat": 12345678,
|
"cacheChat": 12345678,
|
||||||
"sourceChat": 12345678
|
"sourceChat": 12345678,
|
||||||
|
"targetChats": [12345678],
|
||||||
|
"_note": "You must set targetChat or targetChats with at least one object"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ratingsPolls": {
|
"ratingsPolls": {
|
||||||
"variants": {
|
"variants": {
|
||||||
"Круть": 2,
|
"Cool": 2,
|
||||||
"Ок": 1,
|
"Ok": 1,
|
||||||
"Не ок": -1,
|
"Not ok": -1,
|
||||||
"Совсем не ок": -2,
|
"Inappropriate": -2,
|
||||||
"Посмотреть результаты": 0
|
"Results": 0
|
||||||
},
|
},
|
||||||
"autoAttach": true,
|
"autoAttach": true,
|
||||||
"ratingOfferText": "What do you think about it?"
|
"ratingOfferText": "What do you think about it?"
|
||||||
@@ -72,5 +75,13 @@
|
|||||||
},
|
},
|
||||||
"publish_command": {
|
"publish_command": {
|
||||||
"panelButtonText": "Publish"
|
"panelButtonText": "Publish"
|
||||||
|
},
|
||||||
|
"gc": {
|
||||||
|
"autoclear": {
|
||||||
|
"rating": -1,
|
||||||
|
"autoClearKrontab": "0 0 0 * *",
|
||||||
|
"skipPostAge": 86400
|
||||||
|
},
|
||||||
|
"immediateDrop": -6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function assert_success() {
|
|||||||
|
|
||||||
app=plaguposter
|
app=plaguposter
|
||||||
version="`grep ../gradle.properties -e "^version=" | sed -e "s/version=\(.*\)/\1/"`"
|
version="`grep ../gradle.properties -e "^version=" | sed -e "s/version=\(.*\)/\1/"`"
|
||||||
server=docker.io/insanusmokrassar
|
server=insanusmokrassar
|
||||||
|
|
||||||
assert_success ../gradlew build
|
assert_success ../gradlew build
|
||||||
assert_success sudo docker build -t $app:"$version" .
|
assert_success sudo docker build -t $app:"$version" .
|
||||||
|
|||||||
23
runner/sample.docker-compose.yml
Normal file
23
runner/sample.docker-compose.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
version: "3.4"
|
||||||
|
|
||||||
|
services:
|
||||||
|
plaguposter_postgres:
|
||||||
|
image: postgres:15.4-bullseye
|
||||||
|
container_name: "plaguposter_postgres"
|
||||||
|
restart: "unless-stopped"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: "test"
|
||||||
|
POSTGRES_PASSWORD: "test"
|
||||||
|
POSTGRES_DB: "test"
|
||||||
|
volumes:
|
||||||
|
- "./db/:/var/lib/postgresql/data"
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:8091:5432"
|
||||||
|
plaguposter:
|
||||||
|
image: insanusmokrassar/plaguposter:latest
|
||||||
|
container_name: "plaguposter"
|
||||||
|
restart: "unless-stopped"
|
||||||
|
volumes:
|
||||||
|
- "./config.json:/config.json"
|
||||||
|
depends_on:
|
||||||
|
- "plaguposter_postgres"
|
||||||
@@ -11,7 +11,6 @@ String[] includes = [
|
|||||||
":ratings:gc",
|
":ratings:gc",
|
||||||
":triggers:command",
|
":triggers:command",
|
||||||
":triggers:selector_with_timer",
|
":triggers:selector_with_timer",
|
||||||
":triggers:selector_with_scheduling",
|
|
||||||
":triggers:timer",
|
":triggers:timer",
|
||||||
":triggers:timer:disablers:ratings",
|
":triggers:timer:disablers:ratings",
|
||||||
":triggers:timer:disablers:autoposts",
|
":triggers:timer:disablers:autoposts",
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
sourceSets {
|
|
||||||
commonMain {
|
|
||||||
dependencies {
|
|
||||||
api project(":plaguposter.common")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
package dev.inmo.plaguposter.triggers.selector_with_scheduling
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package dev.inmo.plaguposter.triggers.selector_with_scheduling
|
|
||||||
|
|
||||||
import dev.inmo.plagubot.Plugin
|
|
||||||
import kotlinx.serialization.json.*
|
|
||||||
import org.jetbrains.exposed.sql.Database
|
|
||||||
import org.koin.core.module.Module
|
|
||||||
|
|
||||||
object Plugin : Plugin {
|
|
||||||
override fun Module.setupDI(database: Database, params: JsonObject) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<manifest package="dev.inmo.plaguposter.triggers.selector_with_scheduling"/>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.triggers.selector_with_timer
|
package dev.inmo.plaguposter.triggers.selector_with_timer
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
|
||||||
fun interface AutopostFilter {
|
fun interface AutopostFilter {
|
||||||
|
|||||||
@@ -1,14 +1,43 @@
|
|||||||
package dev.inmo.plaguposter.triggers.selector_with_timer
|
package dev.inmo.plaguposter.triggers.selector_with_timer
|
||||||
|
|
||||||
|
import korlibs.time.DateFormat
|
||||||
import dev.inmo.krontab.KrontabTemplate
|
import dev.inmo.krontab.KrontabTemplate
|
||||||
import dev.inmo.krontab.toSchedule
|
import dev.inmo.krontab.toSchedule
|
||||||
import dev.inmo.krontab.utils.asFlow
|
import dev.inmo.krontab.utils.asFlowWithDelays
|
||||||
|
import dev.inmo.krontab.utils.asFlowWithoutDelays
|
||||||
|
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.singleWithRandomQualifier
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.firstIndex
|
||||||
|
import dev.inmo.micro_utils.pagination.lastIndexExclusive
|
||||||
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.posts.models.PostId
|
||||||
|
import dev.inmo.plaguposter.posts.repo.ReadPostsRepo
|
||||||
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.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.triggers_handling.onCommand
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.formatting.makeLinkToMessage
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.urlButton
|
||||||
|
import dev.inmo.tgbotapi.types.BotCommand
|
||||||
|
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
|
||||||
|
import dev.inmo.tgbotapi.utils.row
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.collectIndexed
|
||||||
|
import kotlinx.coroutines.flow.take
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
@@ -16,15 +45,21 @@ import org.koin.core.Koin
|
|||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
object Plugin : Plugin {
|
object Plugin : Plugin {
|
||||||
@Serializable
|
private const val pageCallbackDataQueryPrefix = "publishing_autoschedule page"
|
||||||
|
private const val pageCallbackDataQuerySize = 5
|
||||||
|
@Serializable
|
||||||
internal data class Config(
|
internal data class Config(
|
||||||
@SerialName("krontab")
|
@SerialName("krontab")
|
||||||
val krontabTemplate: KrontabTemplate
|
val krontabTemplate: KrontabTemplate,
|
||||||
|
val dateTimeFormat: String = "HH:mm:ss, dd.MM.yyyy"
|
||||||
) {
|
) {
|
||||||
@Transient
|
@Transient
|
||||||
val krontab by lazy {
|
val krontab by lazy {
|
||||||
krontabTemplate.toSchedule()
|
krontabTemplate.toSchedule()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
val format: DateFormat = DateFormat(dateTimeFormat)
|
||||||
}
|
}
|
||||||
override fun Module.setupDI(database: Database, params: JsonObject) {
|
override fun Module.setupDI(database: Database, params: JsonObject) {
|
||||||
single { get<Json>().decodeFromJsonElement(Config.serializer(), params["timer_trigger"] ?: return@single null) }
|
single { get<Json>().decodeFromJsonElement(Config.serializer(), params["timer_trigger"] ?: return@single null) }
|
||||||
@@ -35,12 +70,103 @@ object Plugin : Plugin {
|
|||||||
val publisher = koin.get<PostPublisher>()
|
val publisher = koin.get<PostPublisher>()
|
||||||
val selector = koin.get<Selector>()
|
val selector = koin.get<Selector>()
|
||||||
val filters = koin.getAll<AutopostFilter>().distinct()
|
val filters = koin.getAll<AutopostFilter>().distinct()
|
||||||
koin.get<Config>().krontab.asFlow().subscribeSafelyWithoutExceptions(this) { dateTime ->
|
val chatConfig = koin.get<ChatConfig>()
|
||||||
|
val postsRepo = koin.get<ReadPostsRepo>()
|
||||||
|
|
||||||
|
koin.getOrNull<InlineTemplatesRepo>() ?.apply {
|
||||||
|
addTemplate(
|
||||||
|
OfferTemplate(
|
||||||
|
"Autoschedule buttons",
|
||||||
|
listOf(
|
||||||
|
Format(
|
||||||
|
"/autoschedule_panel"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"Show autoscheduling publishing info"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val krontab = koin.get<Config>().krontab
|
||||||
|
val dateTimeFormat = koin.get<Config>().format
|
||||||
|
krontab.asFlowWithDelays().subscribeSafelyWithoutExceptions(this) { dateTime ->
|
||||||
selector.take(now = dateTime).forEach { postId ->
|
selector.take(now = dateTime).forEach { postId ->
|
||||||
if (filters.all { it.check(postId, dateTime) }) {
|
if (filters.all { it.check(postId, dateTime) }) {
|
||||||
publisher.publish(postId)
|
publisher.publish(postId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun buildPage(pagination: Pagination = FirstPagePagination(size = pageCallbackDataQuerySize)): InlineKeyboardMarkup {
|
||||||
|
return inlineKeyboard {
|
||||||
|
row {
|
||||||
|
if (pagination.page > 1) {
|
||||||
|
dataButton("⬅️", "${pageCallbackDataQueryPrefix}0")
|
||||||
|
}
|
||||||
|
if (pagination.page > 0) {
|
||||||
|
dataButton("◀️", "${pageCallbackDataQueryPrefix}${pagination.page - 1}")
|
||||||
|
}
|
||||||
|
|
||||||
|
dataButton("\uD83D\uDD04 ${pagination.page}", "${pageCallbackDataQueryPrefix}${pagination.page}")
|
||||||
|
dataButton("▶️", "${pageCallbackDataQueryPrefix}${pagination.page + 1}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val selected = mutableListOf<PostId>()
|
||||||
|
krontab.asFlowWithoutDelays().take(pagination.lastIndexExclusive).collectIndexed { i, dateTime ->
|
||||||
|
val postId = selector.take(now = dateTime, exclude = selected).firstOrNull() ?.also { postId ->
|
||||||
|
if (filters.all { it.check(postId, dateTime) }) {
|
||||||
|
selected.add(postId)
|
||||||
|
} else {
|
||||||
|
return@collectIndexed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val post = postsRepo.getFirstMessageInfo(postId ?: return@collectIndexed)
|
||||||
|
if (i < pagination.firstIndex || post == null) {
|
||||||
|
return@collectIndexed
|
||||||
|
}
|
||||||
|
|
||||||
|
row {
|
||||||
|
urlButton(
|
||||||
|
dateTime.local.format(dateTimeFormat),
|
||||||
|
makeLinkToMessage(post.chatId, post.messageId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onCommand("autoschedule_panel", initialFilter = { it.sameChat(chatConfig.sourceChatId) }) {
|
||||||
|
val keyboard = buildPage()
|
||||||
|
|
||||||
|
runCatchingSafely {
|
||||||
|
edit(it, replyMarkup = keyboard) {
|
||||||
|
+"Your schedule:"
|
||||||
|
}
|
||||||
|
}.onFailure { _ ->
|
||||||
|
send(it.chat, replyMarkup = keyboard) {
|
||||||
|
+"Your schedule:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageDataCallbackQuery(
|
||||||
|
Regex("^$pageCallbackDataQueryPrefix\\d+"),
|
||||||
|
initialFilter = { it.message.sameChat(chatConfig.sourceChatId) }
|
||||||
|
) {
|
||||||
|
val page = it.data.removePrefix(pageCallbackDataQueryPrefix).toIntOrNull() ?: let { _ ->
|
||||||
|
answer(it)
|
||||||
|
return@onMessageDataCallbackQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
runCatchingSafely {
|
||||||
|
edit(
|
||||||
|
it.message,
|
||||||
|
replyMarkup = buildPage(Pagination(page, size = pageCallbackDataQuerySize))
|
||||||
|
)
|
||||||
|
}.onFailure { _ ->
|
||||||
|
answer(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package dev.inmo.plaguposter.triggers.timer
|
package dev.inmo.plaguposter.triggers.timer
|
||||||
|
|
||||||
import com.soywiz.klock.DateFormat
|
import korlibs.time.DateFormat
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import com.soywiz.klock.DateTimeTz
|
import korlibs.time.DateTimeTz
|
||||||
import com.soywiz.klock.Month
|
import korlibs.time.Month
|
||||||
import com.soywiz.klock.Year
|
import korlibs.time.Year
|
||||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
import dev.inmo.micro_utils.repos.unset
|
import dev.inmo.micro_utils.repos.unset
|
||||||
import dev.inmo.plaguposter.common.SuccessfulSymbol
|
import dev.inmo.plaguposter.common.SuccessfulSymbol
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.plaguposter.triggers.timer
|
package dev.inmo.plaguposter.triggers.timer
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import com.soywiz.klock.minutes
|
import korlibs.time.minutes
|
||||||
|
|
||||||
fun nearestAvailableTimerTime() = (DateTime.now() + 1.minutes).copyDayOfMonth(
|
fun nearestAvailableTimerTime() = (DateTime.now() + 1.minutes).copyDayOfMonth(
|
||||||
milliseconds = 0,
|
milliseconds = 0,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.triggers.timer
|
package dev.inmo.plaguposter.triggers.timer
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.coroutines.plus
|
import dev.inmo.micro_utils.coroutines.plus
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.triggers.timer
|
package dev.inmo.plaguposter.triggers.timer
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.triggers.timer
|
package dev.inmo.plaguposter.triggers.timer
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
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.singleWithRandomQualifierAndBinds
|
import dev.inmo.micro_utils.koin.singleWithRandomQualifierAndBinds
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.plaguposter.triggers.timer.repo
|
package dev.inmo.plaguposter.triggers.timer.repo
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import korlibs.time.DateTime
|
||||||
import dev.inmo.micro_utils.common.firstNotNull
|
import dev.inmo.micro_utils.common.firstNotNull
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.pagination.paginate
|
import dev.inmo.micro_utils.pagination.paginate
|
||||||
|
|||||||
Reference in New Issue
Block a user