mirror of
https://github.com/InsanusMokrassar/PlaguPoster.git
synced 2025-12-31 09:09:13 +00:00
Compare commits
23 Commits
0.4.0
...
several_so
| Author | SHA1 | Date | |
|---|---|---|---|
| 29a19df7fc | |||
| 250f88e2fe | |||
| bb433a6441 | |||
| 7a8166153f | |||
| 114add0391 | |||
| 58b1f26502 | |||
| ba3d054f0f | |||
| eef2bfce14 | |||
| fe96101631 | |||
| 7abb6efba3 | |||
| 2f0a823f7c | |||
| 730e3c50e9 | |||
| 0cc0510876 | |||
| 947bd7c2c4 | |||
| 7f54e86962 | |||
| db419165a7 | |||
| a5b0f429a0 | |||
| 9c161b6dab | |||
| f6067bb096 | |||
| 248740f246 | |||
| 3ae3cabd80 | |||
| 5fd4042fe3 | |||
| 12635c654a |
4
.github/workflows/build_and_publish.yml
vendored
4
.github/workflows/build_and_publish.yml
vendored
@@ -8,10 +8,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 17
|
||||||
- name: Rewrite version
|
- name: Rewrite version
|
||||||
run: |
|
run: |
|
||||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
|
|||||||
3
.github/workflows/docker-publish.yml
vendored
3
.github/workflows/docker-publish.yml
vendored
@@ -11,6 +11,9 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
- name: Rewrite version
|
- name: Rewrite version
|
||||||
run: |
|
run: |
|
||||||
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
|
|||||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,5 +1,22 @@
|
|||||||
# PlaguPoster
|
# PlaguPoster
|
||||||
|
|
||||||
|
## 0.5.3
|
||||||
|
|
||||||
|
* Dependencies update
|
||||||
|
|
||||||
|
## 0.5.2
|
||||||
|
|
||||||
|
* Dependencies update
|
||||||
|
|
||||||
|
## 0.5.1
|
||||||
|
|
||||||
|
* Add opportunity to set unique
|
||||||
|
|
||||||
|
## 0.5.0
|
||||||
|
|
||||||
|
* Dependencies update
|
||||||
|
* Since this update bots will require **`JDK` 17+**
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
||||||
* `Versions`:
|
* `Versions`:
|
||||||
|
|||||||
@@ -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/" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ android.enableJetifier=true
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.4.0
|
version=0.5.3
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kotlin = "1.8.22"
|
kotlin = "1.9.22"
|
||||||
kotlin-serialization = "1.5.1"
|
kotlin-serialization = "1.6.2"
|
||||||
|
|
||||||
plagubot = "7.2.1"
|
plagubot = "8.1.1"
|
||||||
tgbotapi = "9.2.1"
|
tgbotapi = "10.0.1"
|
||||||
microutils = "0.19.9"
|
microutils = "0.20.34"
|
||||||
kslog = "1.1.2"
|
kslog = "1.3.2"
|
||||||
krontab = "2.1.2"
|
krontab = "2.2.7"
|
||||||
plagubot-plugins = "0.15.1"
|
plagubot-plugins = "0.18.1"
|
||||||
|
|
||||||
dokka = "1.8.20"
|
dokka = "1.9.10"
|
||||||
|
|
||||||
psql = "42.6.0"
|
psql = "42.6.0"
|
||||||
|
|
||||||
|
|||||||
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.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ kotlin {
|
|||||||
jvm {
|
jvm {
|
||||||
compilations.main {
|
compilations.main {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,6 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|||||||
24
posts/gc/build.gradle
Normal file
24
posts/gc/build.gradle
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
sourceSets {
|
||||||
|
commonMain {
|
||||||
|
dependencies {
|
||||||
|
api project(":plaguposter.common")
|
||||||
|
api project(":plaguposter.posts")
|
||||||
|
api libs.microutils.koin
|
||||||
|
api libs.krontab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jvmMain {
|
||||||
|
dependencies {
|
||||||
|
api libs.plagubot.plugins.inline.queries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
posts/gc/src/commonMain/kotlin/PackageInfo.kt
Normal file
1
posts/gc/src/commonMain/kotlin/PackageInfo.kt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package dev.inmo.plaguposter.posts.gc
|
||||||
194
posts/gc/src/jvmMain/kotlin/Plugin.kt
Normal file
194
posts/gc/src/jvmMain/kotlin/Plugin.kt
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
package dev.inmo.plaguposter.posts.gc
|
||||||
|
|
||||||
|
import com.benasher44.uuid.uuid4
|
||||||
|
import dev.inmo.krontab.KrontabTemplate
|
||||||
|
import dev.inmo.krontab.toKronScheduler
|
||||||
|
import dev.inmo.krontab.utils.asFlowWithDelays
|
||||||
|
import dev.inmo.kslog.common.KSLog
|
||||||
|
import dev.inmo.kslog.common.i
|
||||||
|
import dev.inmo.kslog.common.iS
|
||||||
|
import dev.inmo.micro_utils.coroutines.actor
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.repos.deleteById
|
||||||
|
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.NewPost
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostContentInfo
|
||||||
|
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.delete
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.forwardMessage
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||||
|
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.oneOf
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.parallel
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
|
||||||
|
import dev.inmo.tgbotapi.types.MilliSeconds
|
||||||
|
import dev.inmo.tgbotapi.utils.bold
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.*
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.koin.core.Koin
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
|
object Plugin : Plugin {
|
||||||
|
@Serializable
|
||||||
|
internal data class Config (
|
||||||
|
val krontab: KrontabTemplate? = null,
|
||||||
|
val throttlingMillis: MilliSeconds = 1000,
|
||||||
|
val doFullCheck: Boolean = false
|
||||||
|
)
|
||||||
|
override fun Module.setupDI(database: Database, params: JsonObject) {
|
||||||
|
params["messagesChecker"] ?.let { element ->
|
||||||
|
single { get<Json>().decodeFromJsonElement(Config.serializer(), element) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val gcLogger = KSLog("GarbageCollector")
|
||||||
|
private suspend fun BehaviourContext.doRecheck(
|
||||||
|
throttlingMillis: MilliSeconds,
|
||||||
|
doFullCheck: Boolean,
|
||||||
|
postsRepo: PostsRepo,
|
||||||
|
chatsConfig: ChatConfig
|
||||||
|
) {
|
||||||
|
val posts = postsRepo.getAll()
|
||||||
|
gcLogger.i {
|
||||||
|
"Start garbage collecting of posts. Initial posts count: ${posts.size}"
|
||||||
|
}
|
||||||
|
posts.forEach { (postId, post) ->
|
||||||
|
val surelyAbsentMessages = mutableListOf<PostContentInfo>()
|
||||||
|
for (content in post.content) {
|
||||||
|
try {
|
||||||
|
forwardMessage(
|
||||||
|
toChatId = chatsConfig.cacheChatId,
|
||||||
|
fromChatId = content.chatId,
|
||||||
|
messageId = content.messageId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!doFullCheck) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
if (e.message ?.contains("message to forward not found") == true) {
|
||||||
|
surelyAbsentMessages.add(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delay(throttlingMillis)
|
||||||
|
}
|
||||||
|
val existsPostMessages = post.content.filter {
|
||||||
|
it !in surelyAbsentMessages
|
||||||
|
}
|
||||||
|
if (existsPostMessages.isNotEmpty() && surelyAbsentMessages.isNotEmpty()) {
|
||||||
|
runCatching {
|
||||||
|
postsRepo.update(
|
||||||
|
postId,
|
||||||
|
NewPost(
|
||||||
|
content = existsPostMessages
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsPostMessages.isNotEmpty()) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
send(
|
||||||
|
chatsConfig.cacheChatId,
|
||||||
|
"Can't find any messages for post $postId. So, deleting it"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
runCatching {
|
||||||
|
postsRepo.deleteById(postId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gcLogger.iS {
|
||||||
|
"Complete garbage collecting of posts. Result posts count: ${postsRepo.count()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
||||||
|
val postsRepo = koin.get<PostsRepo>()
|
||||||
|
val chatsConfig = koin.get<ChatConfig>()
|
||||||
|
val config = koin.getOrNull<Config>() ?: Config()
|
||||||
|
|
||||||
|
val scope = koin.get<CoroutineScope>()
|
||||||
|
|
||||||
|
val recheckActor = scope.actor<Unit>(0) {
|
||||||
|
runCatching {
|
||||||
|
doRecheck(
|
||||||
|
config.throttlingMillis,
|
||||||
|
config.doFullCheck,
|
||||||
|
postsRepo,
|
||||||
|
chatsConfig
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.krontab ?.toKronScheduler() ?.asFlowWithDelays() ?.subscribeSafelyWithoutExceptions(koin.get()) {
|
||||||
|
recheckActor.trySend(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
onCommand("force_garbage_collection") { message ->
|
||||||
|
launch {
|
||||||
|
val prefix = uuid4().toString()
|
||||||
|
val yesData = "${prefix}yes"
|
||||||
|
val noData = "${prefix}no"
|
||||||
|
edit(
|
||||||
|
message,
|
||||||
|
text = "Are you sure want to trigger posts garbage collecting?",
|
||||||
|
replyMarkup = flatInlineKeyboard {
|
||||||
|
dataButton("Sure", yesData)
|
||||||
|
dataButton("No", noData)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val answer = oneOf(
|
||||||
|
parallel {
|
||||||
|
waitMessageDataCallbackQuery().filter {
|
||||||
|
it.message.sameMessage(message)
|
||||||
|
}.first()
|
||||||
|
},
|
||||||
|
parallel {
|
||||||
|
waitInlineMessageIdDataCallbackQuery().filter {
|
||||||
|
it.data == yesData || it.data == noData
|
||||||
|
}.first()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (answer.data == yesData) {
|
||||||
|
if (recheckActor.trySend(Unit).isSuccess) {
|
||||||
|
edit(message, "Checking of posts without exists messages triggered")
|
||||||
|
} else {
|
||||||
|
edit(message) {
|
||||||
|
+"Checking of posts without exists messages has been triggered " + bold("earlier")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
koin.getOrNull<InlineTemplatesRepo>() ?.addTemplate(
|
||||||
|
OfferTemplate(
|
||||||
|
"Force posts check",
|
||||||
|
listOf(
|
||||||
|
Format("/force_garbage_collection")
|
||||||
|
),
|
||||||
|
"Force check posts without exists messages"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,11 @@ import com.benasher44.uuid.uuid4
|
|||||||
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.getAllDistinct
|
import dev.inmo.micro_utils.koin.getAllDistinct
|
||||||
|
import dev.inmo.micro_utils.repos.*
|
||||||
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.cached
|
import dev.inmo.micro_utils.repos.cache.cached
|
||||||
import dev.inmo.micro_utils.repos.cache.full.cached
|
import dev.inmo.micro_utils.repos.cache.full.cached
|
||||||
import dev.inmo.micro_utils.repos.deleteById
|
import dev.inmo.micro_utils.repos.cache.full.fullyCached
|
||||||
import dev.inmo.micro_utils.repos.id
|
|
||||||
import dev.inmo.micro_utils.repos.set
|
|
||||||
import dev.inmo.micro_utils.repos.unset
|
|
||||||
import dev.inmo.micro_utils.repos.value
|
|
||||||
import dev.inmo.plagubot.Plugin
|
import dev.inmo.plagubot.Plugin
|
||||||
import dev.inmo.plaguposter.common.ChatConfig
|
import dev.inmo.plaguposter.common.ChatConfig
|
||||||
import dev.inmo.plaguposter.common.UnsuccessfulSymbol
|
import dev.inmo.plaguposter.common.UnsuccessfulSymbol
|
||||||
@@ -34,6 +31,7 @@ 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.MessageIdentifier
|
||||||
|
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
|
||||||
@@ -99,7 +97,7 @@ object Plugin : Plugin {
|
|||||||
val api = koin.get<PanelButtonsAPI>()
|
val api = koin.get<PanelButtonsAPI>()
|
||||||
val basePostsMessages = PostsMessages(koin.get(), koin.get())
|
val basePostsMessages = PostsMessages(koin.get(), koin.get())
|
||||||
val postsMessages = if (koin.useCache) {
|
val postsMessages = if (koin.useCache) {
|
||||||
basePostsMessages.cached(FullKVCache(), koin.get())
|
basePostsMessages.fullyCached(MapKeyValueRepo(), koin.get())
|
||||||
} else {
|
} else {
|
||||||
basePostsMessages
|
basePostsMessages
|
||||||
}
|
}
|
||||||
@@ -115,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 ->
|
||||||
@@ -149,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)
|
||||||
@@ -158,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)
|
||||||
@@ -185,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)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import dev.inmo.tgbotapi.extensions.api.send.copyMessage
|
|||||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||||
import dev.inmo.tgbotapi.extensions.utils.*
|
import dev.inmo.tgbotapi.extensions.utils.*
|
||||||
import dev.inmo.tgbotapi.types.*
|
import dev.inmo.tgbotapi.types.*
|
||||||
import dev.inmo.tgbotapi.types.message.content.MediaGroupContent
|
|
||||||
import dev.inmo.tgbotapi.types.message.content.MediaGroupPartContent
|
import dev.inmo.tgbotapi.types.message.content.MediaGroupPartContent
|
||||||
|
|
||||||
class PostPublisher(
|
class PostPublisher(
|
||||||
@@ -21,10 +20,10 @@ class PostPublisher(
|
|||||||
private val targetChatIds: List<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): Boolean {
|
||||||
val messagesInfo = postsRepo.getById(postId) ?: let {
|
val messagesInfo = postsRepo.getById(postId) ?: let {
|
||||||
logger.w { "Unable to get post with id $postId for publishing" }
|
logger.w { "Unable to get post with id $postId for publishing" }
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
val sortedMessagesContents = messagesInfo.content.groupBy { it.group }.flatMap { (group, list) ->
|
val sortedMessagesContents = messagesInfo.content.groupBy { it.group }.flatMap { (group, list) ->
|
||||||
if (group == null) {
|
if (group == null) {
|
||||||
@@ -35,6 +34,7 @@ class PostPublisher(
|
|||||||
listOf(list.first().order to list)
|
listOf(list.first().order to list)
|
||||||
}
|
}
|
||||||
}.sortedBy { it.first }
|
}.sortedBy { it.first }
|
||||||
|
var haveSentMessages = false
|
||||||
|
|
||||||
sortedMessagesContents.forEach { (_, contents) ->
|
sortedMessagesContents.forEach { (_, contents) ->
|
||||||
contents.singleOrNull() ?.also {
|
contents.singleOrNull() ?.also {
|
||||||
@@ -44,13 +44,16 @@ class PostPublisher(
|
|||||||
}.onFailure { _ ->
|
}.onFailure { _ ->
|
||||||
runCatching {
|
runCatching {
|
||||||
bot.forwardMessage(
|
bot.forwardMessage(
|
||||||
it.chatId,
|
fromChatId = it.chatId,
|
||||||
targetChatId,
|
toChatId = cachingChatId,
|
||||||
it.messageId
|
messageId = it.messageId
|
||||||
)
|
)
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
bot.copyMessage(targetChatId, it)
|
bot.copyMessage(targetChatId, it)
|
||||||
|
haveSentMessages = true
|
||||||
}
|
}
|
||||||
|
}.onSuccess {
|
||||||
|
haveSentMessages = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@forEach
|
return@forEach
|
||||||
@@ -61,12 +64,14 @@ class PostPublisher(
|
|||||||
forwardedMessage.withContentOrNull<MediaGroupPartContent>() ?: null.also { _ ->
|
forwardedMessage.withContentOrNull<MediaGroupPartContent>() ?: null.also { _ ->
|
||||||
targetChatIds.forEach { targetChatId ->
|
targetChatIds.forEach { targetChatId ->
|
||||||
bot.copyMessage(targetChatId, forwardedMessage)
|
bot.copyMessage(targetChatId, forwardedMessage)
|
||||||
|
haveSentMessages = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultContents.singleOrNull() ?.also {
|
resultContents.singleOrNull() ?.also {
|
||||||
targetChatIds.forEach { targetChatId ->
|
targetChatIds.forEach { targetChatId ->
|
||||||
bot.copyMessage(targetChatId, it)
|
bot.copyMessage(targetChatId, it)
|
||||||
|
haveSentMessages = true
|
||||||
}
|
}
|
||||||
return@forEach
|
return@forEach
|
||||||
} ?: resultContents.chunked(mediaCountInMediaGroup.last).forEach {
|
} ?: resultContents.chunked(mediaCountInMediaGroup.last).forEach {
|
||||||
@@ -75,6 +80,7 @@ class PostPublisher(
|
|||||||
targetChatId,
|
targetChatId,
|
||||||
it.map { it.content.toMediaGroupMemberTelegramMedia() }
|
it.map { it.content.toMediaGroupMemberTelegramMedia() }
|
||||||
)
|
)
|
||||||
|
haveSentMessages = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,5 +89,6 @@ class PostPublisher(
|
|||||||
postsRepo.deleteById(postId)
|
postsRepo.deleteById(postId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return haveSentMessages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package dev.inmo.plaguposter.posts.cached
|
package dev.inmo.plaguposter.posts.cached
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
import korlibs.time.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
|
||||||
import dev.inmo.micro_utils.repos.CRUDRepo
|
import dev.inmo.micro_utils.repos.CRUDRepo
|
||||||
|
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.FullCRUDCacheRepo
|
import dev.inmo.micro_utils.repos.cache.full.FullCRUDCacheRepo
|
||||||
import dev.inmo.plaguposter.posts.models.NewPost
|
import dev.inmo.plaguposter.posts.models.NewPost
|
||||||
@@ -20,12 +23,13 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
class CachedPostsRepo(
|
class CachedPostsRepo(
|
||||||
private val parentRepo: PostsRepo,
|
private val parentRepo: PostsRepo,
|
||||||
private val scope: CoroutineScope,
|
private val scope: CoroutineScope,
|
||||||
private val kvCache: FullKVCache<PostId, RegisteredPost> = FullKVCache()
|
private val kvCache: KeyValueRepo<PostId, RegisteredPost> = MapKeyValueRepo()
|
||||||
) : PostsRepo, CRUDRepo<RegisteredPost, PostId, NewPost> by FullCRUDCacheRepo(
|
) : PostsRepo, CRUDRepo<RegisteredPost, PostId, NewPost> by FullCRUDCacheRepo(
|
||||||
parentRepo,
|
parentRepo,
|
||||||
kvCache,
|
kvCache,
|
||||||
scope,
|
scope,
|
||||||
skipStartInvalidate = false,
|
skipStartInvalidate = false,
|
||||||
|
locker = SmartRWLocker(),
|
||||||
{ it.id }
|
{ it.id }
|
||||||
) {
|
) {
|
||||||
override val removedPostsFlow: Flow<RegisteredPost> by parentRepo::removedPostsFlow
|
override val removedPostsFlow: Flow<RegisteredPost> by parentRepo::removedPostsFlow
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class ExposedPostsRepo(
|
|||||||
) {
|
) {
|
||||||
val idColumn = text("id")
|
val idColumn = text("id")
|
||||||
val createdColumn = double("datetime").default(0.0)
|
val createdColumn = double("datetime").default(0.0)
|
||||||
|
val latestUpdateColumn = double("latest_update").default(0.0)
|
||||||
|
|
||||||
private val contentRepo by lazy {
|
private val contentRepo by lazy {
|
||||||
ExposedContentInfoRepo(
|
ExposedContentInfoRepo(
|
||||||
@@ -81,7 +82,9 @@ class ExposedPostsRepo(
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(id: PostId?, value: NewPost, it: UpdateBuilder<Int>) {}
|
override fun update(id: PostId?, value: NewPost, it: UpdateBuilder<Int>) {
|
||||||
|
it[latestUpdateColumn] = DateTime.now().unixMillis
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateContent(post: RegisteredPost) {
|
private fun updateContent(post: RegisteredPost) {
|
||||||
transaction(database) {
|
transaction(database) {
|
||||||
|
|||||||
@@ -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)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
apply plugin: 'maven-publish'
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
task javadocsJar(type: Jar) {
|
task javadocsJar(type: Jar) {
|
||||||
classifier = 'javadoc'
|
archiveClassifier = 'javadoc'
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
@@ -79,4 +79,27 @@ if (project.hasProperty("signing.gnupg.keyName")) {
|
|||||||
dependsOn(it)
|
dependsOn(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Workaround to make android sign operations depend on signing tasks
|
||||||
|
project.getTasks().withType(AbstractPublishToMaven.class).configureEach {
|
||||||
|
def signingTasks = project.getTasks().withType(Sign.class)
|
||||||
|
mustRunAfter(signingTasks)
|
||||||
|
}
|
||||||
|
// Workaround to make test tasks use sign
|
||||||
|
project.getTasks().withType(Sign.class).configureEach { signTask ->
|
||||||
|
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
|
||||||
|
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
|
||||||
|
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
|
||||||
|
|
||||||
|
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
|
||||||
|
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
|
||||||
|
if (debugTestTask != null) {
|
||||||
|
signTask.mustRunAfter(debugTestTask)
|
||||||
|
}
|
||||||
|
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
|
||||||
|
def testTask = tasks.findByName("compileTestKotlin$pubName")
|
||||||
|
if (testTask != null) {
|
||||||
|
signTask.mustRunAfter(testTask)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,9 @@ import dev.inmo.plaguposter.posts.models.PostId
|
|||||||
|
|
||||||
interface Selector {
|
interface Selector {
|
||||||
suspend fun take(n: Int = 1, now: DateTime = DateTime.now(), exclude: List<PostId> = emptyList()): List<PostId>
|
suspend fun take(n: Int = 1, now: DateTime = DateTime.now(), exclude: List<PostId> = emptyList()): List<PostId>
|
||||||
|
suspend fun takeOneOrNull(now: DateTime = DateTime.now(), exclude: List<PostId> = emptyList()): PostId? = take(
|
||||||
|
n = 1,
|
||||||
|
now = now,
|
||||||
|
exclude = exclude
|
||||||
|
).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"))) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ dependencies {
|
|||||||
api project(":plaguposter.ratings.source")
|
api project(":plaguposter.ratings.source")
|
||||||
api project(":plaguposter.ratings.selector")
|
api project(":plaguposter.ratings.selector")
|
||||||
api project(":plaguposter.ratings.gc")
|
api project(":plaguposter.ratings.gc")
|
||||||
|
api project(":plaguposter.posts.gc")
|
||||||
api project(":plaguposter.inlines")
|
api project(":plaguposter.inlines")
|
||||||
|
|
||||||
api libs.psql
|
api libs.psql
|
||||||
@@ -32,6 +33,6 @@ application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
{
|
|
||||||
"database": {
|
|
||||||
"url": "jdbc:postgresql://127.0.0.1:8091/test",
|
|
||||||
"username": "test",
|
|
||||||
"password": "test",
|
|
||||||
"driver": "org.postgresql.Driver"
|
|
||||||
},
|
|
||||||
"botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678",
|
|
||||||
"plugins": [
|
|
||||||
"dev.inmo.plaguposter.posts.Plugin",
|
|
||||||
"dev.inmo.plaguposter.posts.registrar.Plugin",
|
|
||||||
"dev.inmo.plaguposter.ratings.Plugin",
|
|
||||||
"dev.inmo.plaguposter.ratings.source.Plugin",
|
|
||||||
"dev.inmo.plaguposter.ratings.selector.Plugin",
|
|
||||||
"dev.inmo.plaguposter.ratings.gc.Plugin",
|
|
||||||
"dev.inmo.plaguposter.triggers.selector_with_timer.Plugin",
|
|
||||||
"dev.inmo.plagubot.plugins.inline.queries.Plugin",
|
|
||||||
"dev.inmo.plaguposter.triggers.command.Plugin",
|
|
||||||
"dev.inmo.plaguposter.posts.panel.Plugin",
|
|
||||||
"dev.inmo.plaguposter.common.CommonPlugin",
|
|
||||||
"dev.inmo.plaguposter.triggers.timer.Plugin",
|
|
||||||
"dev.inmo.plaguposter.triggers.timer.disablers.ratings.Plugin",
|
|
||||||
"dev.inmo.plaguposter.triggers.timer.disablers.autoposts.Plugin"
|
|
||||||
],
|
|
||||||
"posts": {
|
|
||||||
"chats": {
|
|
||||||
"targetChat": 12345678,
|
|
||||||
"cacheChat": 12345678,
|
|
||||||
"sourceChat": 12345678,
|
|
||||||
"targetChats": [12345678],
|
|
||||||
"_note": "You must set targetChat or targetChats with at least one object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ratingsPolls": {
|
|
||||||
"variants": {
|
|
||||||
"Cool": 2,
|
|
||||||
"Ok": 1,
|
|
||||||
"Not ok": -1,
|
|
||||||
"Inappropriate": -2,
|
|
||||||
"Results": 0
|
|
||||||
},
|
|
||||||
"autoAttach": true,
|
|
||||||
"ratingOfferText": "What do you think about it?"
|
|
||||||
},
|
|
||||||
"selector": {
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"time": {
|
|
||||||
"from": "00:00",
|
|
||||||
"to": "23:59"
|
|
||||||
},
|
|
||||||
"rating": {
|
|
||||||
"prefer": "max"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"time": {
|
|
||||||
"from": "23:59",
|
|
||||||
"to": "00:00"
|
|
||||||
},
|
|
||||||
"rating": {
|
|
||||||
"prefer": "max"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"timer_trigger": {
|
|
||||||
"krontab": "0 30 2/4 * *"
|
|
||||||
},
|
|
||||||
"panel": {
|
|
||||||
"textPrefix": "Post management:",
|
|
||||||
"buttonsPerRow": 2,
|
|
||||||
"parseMode": "MarkdownV2",
|
|
||||||
"deleteButtonText": "Delete"
|
|
||||||
},
|
|
||||||
"publish_command": {
|
|
||||||
"panelButtonText": "Publish"
|
|
||||||
},
|
|
||||||
"gc": {
|
|
||||||
"autoclear": {
|
|
||||||
"rating": -1,
|
|
||||||
"autoClearKrontab": "0 0 0 * *",
|
|
||||||
"skipPostAge": 86400
|
|
||||||
},
|
|
||||||
"immediateDrop": -6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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"
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
DATA_PATH=.
|
|
||||||
|
|
||||||
PG_USER=test_user
|
|
||||||
PG_PASSWORD=test_password
|
|
||||||
PG_DB=test_db
|
|
||||||
@@ -1,131 +1,85 @@
|
|||||||
{
|
{
|
||||||
"database": {
|
"database": {
|
||||||
"url": "jdbc:postgresql://postgres/test_db",
|
"url": "jdbc:postgresql://postgres:5432/test",
|
||||||
"username": "test_user",
|
"username": "test",
|
||||||
"password": "test_password",
|
"password": "test",
|
||||||
"driver": "org.postgresql.Driver"
|
"driver": "org.postgresql.Driver"
|
||||||
},
|
},
|
||||||
"botToken": "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi",
|
"botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678",
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"dev.inmo.plaguposter.posts.Plugin",
|
"dev.inmo.plaguposter.posts.Plugin",
|
||||||
"dev.inmo.plaguposter.posts.registrar.Plugin",
|
"dev.inmo.plaguposter.posts.registrar.Plugin",
|
||||||
"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.triggers.selector_with_timer.Plugin",
|
|
||||||
"dev.inmo.plaguposter.ratings.gc.Plugin",
|
"dev.inmo.plaguposter.ratings.gc.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",
|
||||||
|
"dev.inmo.plaguposter.posts.panel.Plugin",
|
||||||
|
"dev.inmo.plaguposter.common.CommonPlugin",
|
||||||
|
"dev.inmo.plaguposter.triggers.timer.Plugin",
|
||||||
|
"dev.inmo.plaguposter.triggers.timer.disablers.ratings.Plugin",
|
||||||
|
"dev.inmo.plaguposter.triggers.timer.disablers.autoposts.Plugin",
|
||||||
|
"dev.inmo.plaguposter.posts.gc.Plugin"
|
||||||
],
|
],
|
||||||
"posts": {
|
"posts": {
|
||||||
"chats": {
|
"chats": {
|
||||||
"targetChat": -1001234567890,
|
"targetChat": 12345678,
|
||||||
"cacheChat": -1001234567890,
|
"cacheChat": 12345678,
|
||||||
"sourceChat": -1001234567890
|
"sourceChat": 12345678,
|
||||||
},
|
"targetChats": [12345678],
|
||||||
"autoRemoveMessages": true
|
"_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": "How do you like it?"
|
"ratingOfferText": "What do you think about it?"
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"time": {
|
"time": {
|
||||||
"from": "23:00",
|
"from": "00:00",
|
||||||
"to": "23:59"
|
"to": "23:59"
|
||||||
},
|
},
|
||||||
"rating": {
|
"rating": {
|
||||||
"min": -1.0,
|
|
||||||
"max": 2.0,
|
|
||||||
"prefer": "max",
|
"prefer": "max",
|
||||||
"otherwise": {
|
"uniqueCount": 1
|
||||||
"rating": {
|
|
||||||
"min": 2.0,
|
|
||||||
"prefer": "min",
|
|
||||||
"postAge": 86400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"postAge": 86400
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"time": {
|
"time": {
|
||||||
"from": "00:00",
|
"from": "23:59",
|
||||||
"to": "06:59"
|
"to": "00:00"
|
||||||
},
|
|
||||||
"rating": {
|
|
||||||
"min": -1.0,
|
|
||||||
"max": 2.0,
|
|
||||||
"prefer": "max",
|
|
||||||
"otherwise": {
|
|
||||||
"rating": {
|
|
||||||
"min": 2.0,
|
|
||||||
"prefer": "min",
|
|
||||||
"postAge": 86400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"postAge": 86400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"time": {
|
|
||||||
"from": "07:00",
|
|
||||||
"to": "12:00"
|
|
||||||
},
|
|
||||||
"rating": {
|
|
||||||
"min": 1.0,
|
|
||||||
"prefer": "min",
|
|
||||||
"otherwise": {
|
|
||||||
"rating": {
|
|
||||||
"max": 1.0,
|
|
||||||
"prefer": "max",
|
|
||||||
"postAge": 86400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"postAge": 86400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"time": {
|
|
||||||
"from": "12:00",
|
|
||||||
"to": "16:00"
|
|
||||||
},
|
|
||||||
"rating": {
|
|
||||||
"min": 2.0,
|
|
||||||
"prefer": "min",
|
|
||||||
"otherwise": {
|
|
||||||
"rating": {
|
|
||||||
"max": 2.0,
|
|
||||||
"prefer": "max",
|
|
||||||
"postAge": 86400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"postAge": 86400
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"time": {
|
|
||||||
"from": "16:00",
|
|
||||||
"to": "23:00"
|
|
||||||
},
|
},
|
||||||
"rating": {
|
"rating": {
|
||||||
"prefer": "max",
|
"prefer": "max",
|
||||||
"postAge": 86400
|
"uniqueCount": 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"timer_trigger": {
|
"timer_trigger": {
|
||||||
"krontab": "0 30 */5 * *"
|
"krontab": "0 30 2/4 * *",
|
||||||
|
"retryOnPostFailureTimes": 0,
|
||||||
|
"_note": "retryOnPostFailureTimes will retry to publish one or several posts if posting has been failed"
|
||||||
|
},
|
||||||
|
"panel": {
|
||||||
|
"textPrefix": "Post management:",
|
||||||
|
"buttonsPerRow": 2,
|
||||||
|
"parseMode": "MarkdownV2",
|
||||||
|
"deleteButtonText": "Delete"
|
||||||
|
},
|
||||||
|
"publish_command": {
|
||||||
|
"panelButtonText": "Publish"
|
||||||
},
|
},
|
||||||
"gc": {
|
"gc": {
|
||||||
"autoclear": {
|
"autoclear": {
|
||||||
@@ -133,6 +87,11 @@
|
|||||||
"autoClearKrontab": "0 0 0 * *",
|
"autoClearKrontab": "0 0 0 * *",
|
||||||
"skipPostAge": 86400
|
"skipPostAge": 86400
|
||||||
},
|
},
|
||||||
"immediateDrop": -2
|
"immediateDrop": -6
|
||||||
|
},
|
||||||
|
"messagesChecker": {
|
||||||
|
"krontab": "0 0 0 * *",
|
||||||
|
"throttlingMillis": 1000,
|
||||||
|
"doFullCheck": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,22 +2,24 @@ version: "3.4"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
plaguposter_postgres:
|
plaguposter_postgres:
|
||||||
image: postgres
|
image: postgres:15.4-bullseye
|
||||||
container_name: "plaguposter_postgres"
|
container_name: "plaguposter_postgres"
|
||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: "${PG_USER}"
|
POSTGRES_USER: "test"
|
||||||
POSTGRES_PASSWORD: "${PG_PASSWORD}"
|
POSTGRES_PASSWORD: "test"
|
||||||
POSTGRES_DB: "${PG_DB}"
|
POSTGRES_DB: "test"
|
||||||
volumes:
|
volumes:
|
||||||
- "${DATA_PATH}/db/:/var/lib/postgresql/"
|
- "./db/:/var/lib/postgresql/data"
|
||||||
|
- "/etc/timezone:/etc/timezone:ro"
|
||||||
plaguposter:
|
plaguposter:
|
||||||
image: insanusmokrassar/plaguposter
|
image: insanusmokrassar/plaguposter:latest
|
||||||
container_name: "plaguposter"
|
container_name: "plaguposter"
|
||||||
restart: "unless-stopped"
|
restart: "unless-stopped"
|
||||||
volumes:
|
volumes:
|
||||||
- "${DATA_PATH}/config.json:/config.json"
|
- "./config.json:/config.json"
|
||||||
links:
|
- "/etc/timezone:/etc/timezone:ro"
|
||||||
- "plaguposter_postgres:postgres"
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- "plaguposter_postgres"
|
- "plaguposter_postgres"
|
||||||
|
links:
|
||||||
|
- "plaguposter_postgres:postgres"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ String[] includes = [
|
|||||||
":common",
|
":common",
|
||||||
":posts",
|
":posts",
|
||||||
":posts:panel",
|
":posts:panel",
|
||||||
|
":posts:gc",
|
||||||
":posts_registrar",
|
":posts_registrar",
|
||||||
":ratings",
|
":ratings",
|
||||||
":ratings:source",
|
":ratings:source",
|
||||||
@@ -29,5 +30,3 @@ includes.each { originalName ->
|
|||||||
project.name = projectName
|
project.name = projectName
|
||||||
project.projectDir = new File(projectDirectory)
|
project.projectDir = new File(projectDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
enableFeaturePreview("VERSION_CATALOGS")
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ object Plugin : Plugin {
|
|||||||
|
|
||||||
return@onCommand
|
return@onCommand
|
||||||
}
|
}
|
||||||
} ?: selector ?.take(1) ?.firstOrNull()
|
} ?: selector ?.takeOneOrNull()
|
||||||
if (postId == null) {
|
if (postId == null) {
|
||||||
reply(
|
reply(
|
||||||
it,
|
it,
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ object Plugin : Plugin {
|
|||||||
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"
|
val dateTimeFormat: String = "HH:mm:ss, dd.MM.yyyy",
|
||||||
|
val retryOnPostFailureTimes: Int = 0
|
||||||
) {
|
) {
|
||||||
@Transient
|
@Transient
|
||||||
val krontab by lazy {
|
val krontab by lazy {
|
||||||
@@ -88,13 +89,27 @@ object Plugin : Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val krontab = koin.get<Config>().krontab
|
val krontab = koin.get<Config>().krontab
|
||||||
|
val retryOnPostFailureTimes = koin.get<Config>().retryOnPostFailureTimes
|
||||||
val dateTimeFormat = koin.get<Config>().format
|
val dateTimeFormat = koin.get<Config>().format
|
||||||
krontab.asFlowWithDelays().subscribeSafelyWithoutExceptions(this) { dateTime ->
|
krontab.asFlowWithDelays().subscribeSafelyWithoutExceptions(this) { dateTime ->
|
||||||
selector.take(now = dateTime).forEach { postId ->
|
var leftRetries = retryOnPostFailureTimes
|
||||||
if (filters.all { it.check(postId, dateTime) }) {
|
do {
|
||||||
publisher.publish(postId)
|
val success = runCatching {
|
||||||
|
selector.takeOneOrNull(now = dateTime) ?.let { postId ->
|
||||||
|
if (filters.all { it.check(postId, dateTime) }) {
|
||||||
|
publisher.publish(postId)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} ?: false
|
||||||
|
}.getOrElse {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
if (success) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
leftRetries--;
|
||||||
|
} while (leftRetries > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun buildPage(pagination: Pagination = FirstPagePagination(size = pageCallbackDataQuerySize)): InlineKeyboardMarkup {
|
suspend fun buildPage(pagination: Pagination = FirstPagePagination(size = pageCallbackDataQuerySize)): InlineKeyboardMarkup {
|
||||||
@@ -113,7 +128,7 @@ object Plugin : Plugin {
|
|||||||
|
|
||||||
val selected = mutableListOf<PostId>()
|
val selected = mutableListOf<PostId>()
|
||||||
krontab.asFlowWithoutDelays().take(pagination.lastIndexExclusive).collectIndexed { i, dateTime ->
|
krontab.asFlowWithoutDelays().take(pagination.lastIndexExclusive).collectIndexed { i, dateTime ->
|
||||||
val postId = selector.take(now = dateTime, exclude = selected).firstOrNull() ?.also { postId ->
|
val postId = selector.takeOneOrNull(now = dateTime, exclude = selected) ?.also { postId ->
|
||||||
if (filters.all { it.check(postId, dateTime) }) {
|
if (filters.all { it.check(postId, dateTime) }) {
|
||||||
selected.add(postId)
|
selected.add(postId)
|
||||||
} else {
|
} else {
|
||||||
@@ -136,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 {
|
||||||
@@ -152,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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user