mirror of
https://github.com/InsanusMokrassar/PlaguPoster.git
synced 2025-12-31 09:09:13 +00:00
Compare commits
155 Commits
0.0.2
...
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 | |||
| 6df4546b81 | |||
| 12635c654a | |||
| 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 | |||
| ee80d8a3a1 | |||
| 22c94a4c43 | |||
| c6bcfc0068 | |||
| 8a648cb066 | |||
| 345a156334 | |||
| 7fb7f923f7 | |||
| 3b858a3c00 | |||
| f09e80b8bd | |||
| fea25743d5 | |||
| 86183f5f74 | |||
| bc8d0b26bd | |||
| b05844737b | |||
| d1b597d2c9 | |||
| aef864d5fd | |||
| f438ede791 | |||
| dc5833c407 | |||
| 2e7a1b83c5 | |||
| b603fa8822 | |||
| 8206131425 | |||
| 12d3d5eeea | |||
| 2c335b43ab | |||
| 2b84c224ec | |||
| 46adc04a9b | |||
| cffc6a62c2 | |||
| d6b684a17e | |||
| 5cd3a6fb35 | |||
| b453401c33 | |||
| 01b9d0b2ab | |||
| 5f65095698 | |||
| 2baaac8e6d | |||
| 8899fb299f | |||
| bc0324a34f | |||
| b9c78982b5 | |||
| c632a2ba14 | |||
| 9403b133f9 | |||
| 00803fa933 | |||
| 74f3503413 | |||
| 34e253a12e | |||
| 65dfe8abd0 | |||
| 8c42f2e879 | |||
| 2b41082a48 | |||
| 0dc459d5dc | |||
| 4024b040e0 | |||
| 74cf8c1a9a | |||
| 82640a0c5d | |||
| 2052d003e5 | |||
| 791a161f8c | |||
| d6bd90267d | |||
| bea7fb7e46 | |||
| 44b2b849e4 | |||
| 8730c67084 | |||
| 3242810ef6 | |||
| 4423eba1d9 | |||
| 1f6dd7aad1 | |||
| 5366dcdba1 | |||
| 18ed638bcc | |||
| 6673b6c69b | |||
| 65f613fd97 | |||
| d605c8c650 | |||
| c47c7b09f4 | |||
| 4c8c93c9f8 | |||
| a3ee0d4c3b | |||
| c8085701d4 | |||
| 2daaf8a6b3 | |||
| 100ee1520f | |||
| 94b3c97efa | |||
| bf8436fa4b | |||
| 5ea80ca1e5 | |||
| a563267da0 | |||
| 4aae1230bc | |||
| 8f29810291 | |||
| 851f3a1c55 | |||
| b398ad43ab | |||
| 1ad7988ae5 | |||
| e114d74c74 | |||
| 6b82879a3e | |||
| c55376da88 | |||
| 6389912b1d | |||
| ad98ca339d | |||
| 3e62eca8ac | |||
| f4e097f7d1 | |||
| 3d5e15d545 | |||
| eef167422e | |||
| 5ae07394dc | |||
| 4ac30cc667 | |||
| 6a43cb32c6 | |||
| e285cc9ec6 | |||
| ceec312208 | |||
| 089be36601 | |||
| af1bddcc85 | |||
| 47f9e09bd2 | |||
| 1d88423b03 | |||
| 3634b9a2a2 | |||
| 4b7c2451cd | |||
| 1cd7c07d29 | |||
| 9c4b49df22 | |||
| 963d33a452 | |||
| 7a05b56af2 | |||
| 39d49f5d58 | |||
| b9700a8fd9 | |||
| 79c195f534 | |||
| 91221d865c | |||
| 5808f45677 | |||
| 29d6d11d87 | |||
| 609c6b97fc |
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -1,19 +0,0 @@
|
|||||||
name: Build
|
|
||||||
|
|
||||||
on: [push]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Set up JDK 11
|
|
||||||
uses: actions/setup-java@v1
|
|
||||||
with:
|
|
||||||
java-version: 11
|
|
||||||
- name: Fix android 32.0.0 dx
|
|
||||||
continue-on-error: true
|
|
||||||
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
|
|
||||||
- name: Build with Gradle
|
|
||||||
run: ./gradlew build
|
|
||||||
27
.github/workflows/build_and_publish.yml
vendored
Normal file
27
.github/workflows/build_and_publish.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
- name: Rewrite version
|
||||||
|
run: |
|
||||||
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
|
cat gradle.properties | sed -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
|
||||||
|
rm gradle.properties
|
||||||
|
mv gradle.properties.tmp gradle.properties
|
||||||
|
- name: Build
|
||||||
|
run: ./gradlew build
|
||||||
|
- name: Publish
|
||||||
|
continue-on-error: true
|
||||||
|
run: ./gradlew publishAllPublicationsToGiteaRepository
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
31
.github/workflows/docker-publish.yml
vendored
Normal file
31
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Docker
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publishing:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
- name: Rewrite version
|
||||||
|
run: |
|
||||||
|
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"
|
||||||
|
if [[ "$branch" != "master" ]]; then
|
||||||
|
cat gradle.properties | sed -e "s/^version=\([0-9\.]*\)/version=\1-branch_$branch-build${{ github.run_number }}/" > gradle.properties.tmp
|
||||||
|
rm gradle.properties
|
||||||
|
mv gradle.properties.tmp gradle.properties
|
||||||
|
fi
|
||||||
|
- name: Log into registry
|
||||||
|
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_LOGIN }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
- name: Deploy
|
||||||
|
run: ./gradlew build && cd ./runner && ./nonsudo_deploy.sh
|
||||||
78
CHANGELOG.md
Normal file
78
CHANGELOG.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
* `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.9
|
||||||
|
|
||||||
|
* Update dependencies
|
||||||
|
|
||||||
@@ -7,8 +7,6 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath libs.android.tools.build
|
|
||||||
classpath libs.android.dexcount
|
|
||||||
classpath libs.kotlin.gradle.plugin
|
classpath libs.kotlin.gradle.plugin
|
||||||
classpath libs.kotlin.serialization.plugin
|
classpath libs.kotlin.serialization.plugin
|
||||||
classpath libs.kotlin.dokka.plugin
|
classpath libs.kotlin.dokka.plugin
|
||||||
@@ -20,8 +18,9 @@ allprojects {
|
|||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
|
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "./extensions.gradle"
|
apply from: "./extensions.gradle"
|
||||||
// apply from: "./github_release.gradle"
|
apply from: "./github_release.gradle"
|
||||||
|
|||||||
24
changelog_parser.sh
Normal file
24
changelog_parser.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function parse() {
|
||||||
|
version="$1"
|
||||||
|
|
||||||
|
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+ $version"`" ]
|
||||||
|
do
|
||||||
|
: # do nothing
|
||||||
|
done
|
||||||
|
|
||||||
|
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+"`" ]
|
||||||
|
do
|
||||||
|
echo "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
version="$1"
|
||||||
|
file="$2"
|
||||||
|
|
||||||
|
if [ -n "$file" ]; then
|
||||||
|
parse "$version" < "$file"
|
||||||
|
else
|
||||||
|
parse "$version"
|
||||||
|
fi
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -12,7 +11,9 @@ kotlin {
|
|||||||
dependencies {
|
dependencies {
|
||||||
api libs.tgbotapi
|
api libs.tgbotapi
|
||||||
api libs.microutils.repos.common
|
api libs.microutils.repos.common
|
||||||
|
api libs.microutils.repos.cache
|
||||||
api libs.kslog
|
api libs.kslog
|
||||||
|
api libs.microutils.koin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
jvmMain {
|
||||||
|
|||||||
@@ -1,15 +1,43 @@
|
|||||||
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.IdChatIdentifier
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ChatConfig(
|
data class ChatConfig(
|
||||||
@SerialName("targetChat")
|
@SerialName("targetChat")
|
||||||
val targetChatId: ChatId,
|
@Serializable(FullChatIdentifierSerializer::class)
|
||||||
|
val targetChatId: IdChatIdentifier? = null,
|
||||||
@SerialName("sourceChat")
|
@SerialName("sourceChat")
|
||||||
val sourceChatId: ChatId,
|
@Serializable(FullChatIdentifierSerializer::class)
|
||||||
|
val sourceChatId: IdChatIdentifier?,
|
||||||
@SerialName("cacheChat")
|
@SerialName("cacheChat")
|
||||||
val cacheChatId: ChatId
|
@Serializable(FullChatIdentifierSerializer::class)
|
||||||
)
|
val cacheChatId: IdChatIdentifier,
|
||||||
|
@SerialName("targetChats")
|
||||||
|
val targetChatIds: List<@Serializable(FullChatIdentifierSerializer::class) IdChatIdentifier> = emptyList(),
|
||||||
|
@SerialName("sourceChats")
|
||||||
|
val sourceChatIds: List<@Serializable(FullChatIdentifierSerializer::class) IdChatIdentifier> = emptyList(),
|
||||||
|
) {
|
||||||
|
val allTargetChatIds by lazy {
|
||||||
|
(listOfNotNull(targetChatId) + targetChatIds).toSet()
|
||||||
|
}
|
||||||
|
val allSourceChatIds by lazy {
|
||||||
|
(listOfNotNull(sourceChatId) + sourceChatIds).toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
require(targetChatId != null || targetChatIds.isNotEmpty()) {
|
||||||
|
"One of fields, 'targetChat' or 'targetChats' should be presented"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun check(chatId: IdChatIdentifier) = when (chatId) {
|
||||||
|
in allTargetChatIds,
|
||||||
|
in allSourceChatIds,
|
||||||
|
cacheChatId -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -8,7 +8,6 @@ import kotlinx.serialization.descriptors.SerialDescriptor
|
|||||||
import kotlinx.serialization.encoding.Decoder
|
import kotlinx.serialization.encoding.Decoder
|
||||||
import kotlinx.serialization.encoding.Encoder
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
|
||||||
@Serializer(DateTime::class)
|
|
||||||
object DateTimeSerializer : KSerializer<DateTime> {
|
object DateTimeSerializer : KSerializer<DateTime> {
|
||||||
override val descriptor: SerialDescriptor = Double.serializer().descriptor
|
override val descriptor: SerialDescriptor = Double.serializer().descriptor
|
||||||
override fun deserialize(decoder: Decoder): DateTime = DateTime(decoder.decodeDouble())
|
override fun deserialize(decoder: Decoder): DateTime = DateTime(decoder.decodeDouble())
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package dev.inmo.plaguposter.common
|
package dev.inmo.plaguposter.common
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
|
import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer
|
||||||
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import dev.inmo.tgbotapi.types.MessageIdentifier
|
import dev.inmo.tgbotapi.types.MessageIdentifier
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.Message
|
import dev.inmo.tgbotapi.types.message.abstracts.Message
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ShortMessageInfo(
|
data class ShortMessageInfo(
|
||||||
val chatId: ChatId,
|
@Serializable(FullChatIdentifierSerializer::class)
|
||||||
|
val chatId: IdChatIdentifier,
|
||||||
val messageId: MessageIdentifier
|
val messageId: MessageIdentifier
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
16
common/src/commonMain/kotlin/UseCache.kt
Normal file
16
common/src/commonMain/kotlin/UseCache.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package dev.inmo.plaguposter.common
|
||||||
|
|
||||||
|
import org.koin.core.Koin
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
|
import org.koin.core.scope.Scope
|
||||||
|
|
||||||
|
val Scope.useCache: Boolean
|
||||||
|
get() = getOrNull(named("useCache")) ?: false
|
||||||
|
|
||||||
|
val Koin.useCache: Boolean
|
||||||
|
get() = getOrNull(named("useCache")) ?: false
|
||||||
|
|
||||||
|
fun Module.useCache(useCache: Boolean) {
|
||||||
|
single(named("useCache")) { useCache }
|
||||||
|
}
|
||||||
33
common/src/jvmMain/kotlin/CommonPlugin.kt
Normal file
33
common/src/jvmMain/kotlin/CommonPlugin.kt
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package dev.inmo.plaguposter.common
|
||||||
|
|
||||||
|
import dev.inmo.kslog.common.iS
|
||||||
|
import dev.inmo.kslog.common.logger
|
||||||
|
import dev.inmo.plagubot.Plugin
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
import kotlinx.serialization.json.booleanOrNull
|
||||||
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
import org.koin.core.Koin
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
|
||||||
|
object CommonPlugin : Plugin {
|
||||||
|
private val Log = logger
|
||||||
|
override fun Module.setupDI(database: Database, params: JsonObject) {
|
||||||
|
single { CoroutineScope(Dispatchers.Default + SupervisorJob()) }
|
||||||
|
val useCache = (params["useCache"] as? JsonPrimitive) ?.booleanOrNull ?: true
|
||||||
|
useCache(useCache)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
||||||
|
val config = koin.get<ChatConfig>()
|
||||||
|
|
||||||
|
Log.iS { "Target chats info: ${config.allTargetChatIds.map { getChat(it) }.joinToString()}" }
|
||||||
|
Log.iS { "Source chats info: ${config.allSourceChatIds.map { getChat(it) }.joinToString()}" }
|
||||||
|
Log.iS { "Cache chat info: ${getChat(config.cacheChatId)}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
apply plugin: 'com.getkeepsafe.dexcount'
|
|
||||||
|
|
||||||
android {
|
|
||||||
ext {
|
|
||||||
jvmKotlinFolderFile = {
|
|
||||||
String sep = File.separator
|
|
||||||
return new File("${project.projectDir}${sep}src${sep}jvmMain${sep}kotlin")
|
|
||||||
}
|
|
||||||
|
|
||||||
enableIncludingJvmCodeInAndroidPart = {
|
|
||||||
File jvmKotlinFolder = jvmKotlinFolderFile()
|
|
||||||
if (jvmKotlinFolder.exists()) {
|
|
||||||
android.sourceSets.main.java.srcDirs += jvmKotlinFolder.path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
disableIncludingJvmCodeInAndroidPart = {
|
|
||||||
File jvmKotlinFolder = jvmKotlinFolderFile()
|
|
||||||
String[] oldDirs = android.sourceSets.main.java.srcDirs
|
|
||||||
android.sourceSets.main.java.srcDirs = []
|
|
||||||
for (oldDir in oldDirs) {
|
|
||||||
if (oldDir != jvmKotlinFolder.path) {
|
|
||||||
android.sourceSets.main.java.srcDirs += oldDir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileSdkVersion libs.versions.android.compileSdk.get().toInteger()
|
|
||||||
buildToolsVersion libs.versions.android.buildTools.get()
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdkVersion libs.versions.android.minSdk.get().toInteger()
|
|
||||||
targetSdkVersion libs.versions.android.compileSdk.get().toInteger()
|
|
||||||
versionCode "${android_code_version}".toInteger()
|
|
||||||
versionName "$version"
|
|
||||||
}
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
minifyEnabled false
|
|
||||||
}
|
|
||||||
debug {
|
|
||||||
debuggable true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
packagingOptions {
|
|
||||||
exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
|
|
||||||
exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
|
|
||||||
exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
String sep = File.separator
|
|
||||||
main.java.srcDirs += "src${sep}main${sep}kotlin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,6 +20,6 @@ allprojects {
|
|||||||
|
|
||||||
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
|
||||||
|
|
||||||
// publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
github_release.gradle
Normal file
31
github_release.gradle
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
private String getCurrentVersionChangelog() {
|
||||||
|
OutputStream changelogDataOS = new ByteArrayOutputStream()
|
||||||
|
exec {
|
||||||
|
commandLine 'chmod', "+x", './changelog_parser.sh'
|
||||||
|
}
|
||||||
|
exec {
|
||||||
|
standardOutput = changelogDataOS
|
||||||
|
commandLine './changelog_parser.sh', "${project.version}", 'CHANGELOG.md'
|
||||||
|
}
|
||||||
|
|
||||||
|
return changelogDataOS.toString().trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new File(projectDir, "secret.gradle").exists()) {
|
||||||
|
apply from: './secret.gradle'
|
||||||
|
apply plugin: "com.github.breadmoirai.github-release"
|
||||||
|
|
||||||
|
githubRelease {
|
||||||
|
token "${project.property('GITHUB_RELEASE_TOKEN')}"
|
||||||
|
|
||||||
|
owner "InsanusMokrassar"
|
||||||
|
repo "PlaguPoster"
|
||||||
|
|
||||||
|
tagName "v${project.version}"
|
||||||
|
releaseName "${project.version}"
|
||||||
|
targetCommitish "${project.version}"
|
||||||
|
|
||||||
|
body getCurrentVersionChangelog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -10,5 +10,4 @@ android.enableJetifier=true
|
|||||||
# Project data
|
# Project data
|
||||||
|
|
||||||
group=dev.inmo
|
group=dev.inmo
|
||||||
version=0.0.2
|
version=0.5.3
|
||||||
android_code_version=2
|
|
||||||
|
|||||||
@@ -1,26 +1,18 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kotlin = "1.7.10"
|
kotlin = "1.9.22"
|
||||||
kotlin-serialization = "1.4.0"
|
kotlin-serialization = "1.6.2"
|
||||||
|
|
||||||
plagubot = "2.3.1"
|
plagubot = "8.1.1"
|
||||||
tgbotapi = "3.2.1"
|
tgbotapi = "10.0.1"
|
||||||
microutils = "0.12.13"
|
microutils = "0.20.34"
|
||||||
kslog = "0.5.2"
|
kslog = "1.3.2"
|
||||||
krontab = "0.8.1"
|
krontab = "2.2.7"
|
||||||
tgbotapi-libraries = "0.5.3"
|
plagubot-plugins = "0.18.1"
|
||||||
|
|
||||||
psql = "42.3.6"
|
dokka = "1.9.10"
|
||||||
|
|
||||||
dexcount = "3.1.0"
|
psql = "42.6.0"
|
||||||
junit_version = "4.12"
|
|
||||||
test_ext_junit_version = "1.1.3"
|
|
||||||
espresso_core = "3.4.0"
|
|
||||||
|
|
||||||
android-gradle-plugin = "7.2.2"
|
|
||||||
android-minSdk = "21"
|
|
||||||
android-compileSdk = "32"
|
|
||||||
android-buildTools = "32.0.0"
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
||||||
@@ -30,12 +22,12 @@ kotlin-test-common = { module = "org.jetbrains.kotlin:kotlin-test-common", versi
|
|||||||
kotlin-test-annotations-common = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" }
|
kotlin-test-annotations-common = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "kotlin" }
|
||||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
kotlin-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kotlin" }
|
kotlin-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kotlin" }
|
||||||
android-test-junit = { module = "androidx.test.ext:junit", version.ref = "test_ext_junit_version" }
|
|
||||||
android-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso_core" }
|
|
||||||
|
|
||||||
tgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "tgbotapi" }
|
tgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "tgbotapi" }
|
||||||
plagubot-plugin = { module = "dev.inmo:plagubot.plugin", version.ref = "plagubot" }
|
plagubot-plugin = { module = "dev.inmo:plagubot.plugin", version.ref = "plagubot" }
|
||||||
plagubot-bot = { module = "dev.inmo:plagubot.bot", version.ref = "plagubot" }
|
plagubot-bot = { module = "dev.inmo:plagubot.bot", version.ref = "plagubot" }
|
||||||
|
plagubot-plugins-inline-queries = { module = "dev.inmo:plagubot.plugins.inline.queries", version.ref = "plagubot-plugins" }
|
||||||
|
plagubot-plugins-inline-buttons = { module = "dev.inmo:plagubot.plugins.inline.buttons", version.ref = "plagubot-plugins" }
|
||||||
microutils-repos-common = { module = "dev.inmo:micro_utils.repos.common", version.ref = "microutils" }
|
microutils-repos-common = { module = "dev.inmo:micro_utils.repos.common", version.ref = "microutils" }
|
||||||
microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" }
|
microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" }
|
||||||
microutils-repos-cache = { module = "dev.inmo:micro_utils.repos.cache", version.ref = "microutils" }
|
microutils-repos-cache = { module = "dev.inmo:micro_utils.repos.cache", version.ref = "microutils" }
|
||||||
@@ -47,11 +39,9 @@ psql = { module = "org.postgresql:postgresql", version.ref = "psql" }
|
|||||||
|
|
||||||
# buildscript classpaths
|
# buildscript classpaths
|
||||||
|
|
||||||
android-tools-build = { module = "com.android.tools.build:gradle", version.ref = "android-gradle-plugin" }
|
|
||||||
android-dexcount = { module = "com.getkeepsafe.dexcount:dexcount-gradle-plugin", version.ref = "dexcount" }
|
|
||||||
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
|
kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
|
||||||
kotlin-dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "kotlin" }
|
kotlin-dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
|
||||||
|
|||||||
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.5.1-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
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -15,6 +14,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api libs.plagubot.plugins.inline.queries
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
package dev.inmo.plaguposter.inlines
|
package dev.inmo.plagubot.plugins.inline.queries
|
||||||
|
|||||||
@@ -1,81 +1,29 @@
|
|||||||
package dev.inmo.plaguposter.inlines
|
package dev.inmo.plaguposter.inlines
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.Pagination
|
import dev.inmo.kslog.common.TagLogger
|
||||||
import dev.inmo.micro_utils.pagination.utils.paginate
|
import dev.inmo.kslog.common.w
|
||||||
import dev.inmo.plagubot.Plugin
|
import dev.inmo.plagubot.Plugin
|
||||||
import dev.inmo.plaguposter.common.ChatConfig
|
|
||||||
import dev.inmo.plaguposter.inlines.models.Format
|
|
||||||
import dev.inmo.plaguposter.inlines.models.OfferTemplate
|
|
||||||
import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo
|
|
||||||
import dev.inmo.tgbotapi.bot.exceptions.RequestException
|
|
||||||
import dev.inmo.tgbotapi.extensions.api.answers.answerInlineQuery
|
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
|
||||||
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.onBaseInlineQuery
|
import kotlinx.serialization.json.JsonObject
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
|
||||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
|
|
||||||
import dev.inmo.tgbotapi.types.inlineQueryAnswerResultsLimit
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
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
|
||||||
|
|
||||||
object Plugin : Plugin {
|
private val actualPlugin = dev.inmo.plagubot.plugins.inline.queries.Plugin
|
||||||
@Serializable
|
|
||||||
internal data class Config(
|
object Plugin : Plugin by actualPlugin {
|
||||||
val preset: List<OfferTemplate>
|
private val log = TagLogger("InlinePlugin")
|
||||||
)
|
|
||||||
override fun Module.setupDI(database: Database, params: JsonObject) {
|
override fun Module.setupDI(database: Database, params: JsonObject) {
|
||||||
single { get<Json>().decodeFromJsonElement(Config.serializer(), params["inlines"] ?: return@single Config(emptyList())) }
|
single { actualPlugin }
|
||||||
single { InlineTemplatesRepo(getOrNull<Config>() ?.preset ?.toMutableSet() ?: mutableSetOf()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
||||||
val templatesRepo = koin.get<InlineTemplatesRepo>()
|
log.w {
|
||||||
onBaseInlineQuery { query ->
|
"Built-in inline plugin has been deprecated. Use \"${actualPlugin::class.qualifiedName}\" instead"
|
||||||
val page = query.offset.toIntOrNull() ?: 0
|
|
||||||
val queryString = query.query.trim()
|
|
||||||
try {
|
|
||||||
answerInlineQuery(
|
|
||||||
query,
|
|
||||||
templatesRepo.templates.paginate(
|
|
||||||
Pagination(
|
|
||||||
page,
|
|
||||||
inlineQueryAnswerResultsLimit.last + 1
|
|
||||||
)
|
|
||||||
).results.mapIndexedNotNull { index, offerTemplate ->
|
|
||||||
offerTemplate.createArticleResult(
|
|
||||||
index.toString(),
|
|
||||||
queryString
|
|
||||||
)
|
|
||||||
},
|
|
||||||
nextOffset = (page + 1).toString(),
|
|
||||||
cachedTime = 0
|
|
||||||
)
|
|
||||||
} catch (e: RequestException) {
|
|
||||||
bot.answerInlineQuery(
|
|
||||||
query,
|
|
||||||
cachedTime = 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
onCommand("help", requireOnlyCommandInMessage = true) {
|
with(actualPlugin) {
|
||||||
reply(
|
setupBotPlugin(koin)
|
||||||
it,
|
|
||||||
"Push the button above to see available commands",
|
|
||||||
replyMarkup = flatInlineKeyboard {
|
|
||||||
inlineQueryInCurrentChatButton("Toggle commands", "")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
koin.getOrNull<InlineTemplatesRepo>() ?.apply {
|
|
||||||
addTemplate(
|
|
||||||
OfferTemplate(
|
|
||||||
"Trigger help button",
|
|
||||||
listOf(Format("/help"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package dev.inmo.plaguposter.inlines.models
|
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent
|
|
||||||
import dev.inmo.tgbotapi.types.message.MarkdownV2
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Format(
|
|
||||||
val template: String,
|
|
||||||
val regexTemplate: String = "^$",
|
|
||||||
val splitBy: String? = null,
|
|
||||||
val enableMarkdownSupport: Boolean = false
|
|
||||||
) {
|
|
||||||
@Transient
|
|
||||||
val queryRegex = Regex(regexTemplate, RegexOption.DOT_MATCHES_ALL)
|
|
||||||
|
|
||||||
init {
|
|
||||||
println(queryRegex)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun formatByRegex(with: String): String? {
|
|
||||||
return if (queryRegex.matches(with)) {
|
|
||||||
template.format(*(splitBy ?.let { with.split(it).toTypedArray() } ?: arrayOf(with)))
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createContent(with: String): InputTextMessageContent? {
|
|
||||||
return if (queryRegex.matches(with)) {
|
|
||||||
InputTextMessageContent(
|
|
||||||
template.format(*(splitBy ?.let { with.split(it).toTypedArray() } ?: arrayOf(with))),
|
|
||||||
if (enableMarkdownSupport) MarkdownV2 else null
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package dev.inmo.plaguposter.inlines.models
|
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class OfferTemplate(
|
|
||||||
val title: String,
|
|
||||||
val formats: List<Format> = emptyList(),
|
|
||||||
val description: String? = null
|
|
||||||
) {
|
|
||||||
fun createArticleResult(id: String, query: String): InlineQueryResultArticle? = formats.firstOrNull {
|
|
||||||
it.queryRegex.matches(query)
|
|
||||||
} ?.createContent(query) ?.let { content ->
|
|
||||||
InlineQueryResultArticle(
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
content,
|
|
||||||
description = description
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package dev.inmo.plaguposter.inlines.repos
|
|
||||||
|
|
||||||
import dev.inmo.plaguposter.inlines.models.OfferTemplate
|
|
||||||
|
|
||||||
class InlineTemplatesRepo(
|
|
||||||
private val _templates: MutableSet<OfferTemplate>
|
|
||||||
) {
|
|
||||||
internal val templates
|
|
||||||
get() = _templates.toList()
|
|
||||||
suspend fun addTemplate(offerTemplate: OfferTemplate): Boolean {
|
|
||||||
return _templates.add(offerTemplate)
|
|
||||||
}
|
|
||||||
suspend fun dropTemplate(offerTemplate: OfferTemplate): Boolean {
|
|
||||||
return _templates.remove(offerTemplate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +1 @@
|
|||||||
<manifest package="dev.inmo.plaguposter.inlines"/>
|
<manifest package="dev.inmo.plagubot.plugins.inline.queries"/>
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
project.version = "$version"
|
|
||||||
project.group = "$group"
|
|
||||||
|
|
||||||
// apply from: "$publishGradlePath"
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
android {
|
|
||||||
publishAllLibraryVariants()
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
commonMain {
|
|
||||||
dependencies {
|
|
||||||
implementation libs.kotlin
|
|
||||||
api libs.kotlin.serialization
|
|
||||||
}
|
|
||||||
}
|
|
||||||
commonTest {
|
|
||||||
dependencies {
|
|
||||||
implementation libs.kotlin.test.common
|
|
||||||
implementation libs.kotlin.test.annotations.common
|
|
||||||
}
|
|
||||||
}
|
|
||||||
androidTest {
|
|
||||||
dependencies {
|
|
||||||
implementation libs.kotlin.test.junit
|
|
||||||
implementation libs.android.test.junit
|
|
||||||
implementation libs.android.test.espresso.core
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
project.version = "$version"
|
project.version = "$version"
|
||||||
project.group = "$group"
|
project.group = "$group"
|
||||||
|
|
||||||
// apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
kotlin {
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
project.version = "$version"
|
project.version = "$version"
|
||||||
project.group = "$group"
|
project.group = "$group"
|
||||||
|
|
||||||
// apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
js (IR) {
|
js (IR) {
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
project.version = "$version"
|
project.version = "$version"
|
||||||
project.group = "$group"
|
project.group = "$group"
|
||||||
|
|
||||||
// apply from: "$publishGradlePath"
|
apply from: "$publishGradlePath"
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvm {
|
jvm()
|
||||||
compilations.main {
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
js (IR) {
|
js (IR) {
|
||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
}
|
}
|
||||||
android {
|
|
||||||
publishAllLibraryVariants()
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -43,19 +34,7 @@ kotlin {
|
|||||||
implementation libs.kotlin.test.junit
|
implementation libs.kotlin.test.junit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
androidTest {
|
|
||||||
dependencies {
|
|
||||||
implementation libs.kotlin.test.junit
|
|
||||||
implementation libs.android.test.junit
|
|
||||||
implementation libs.android.test.espresso.core
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$defaultAndroidSettingsPresetPath"
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
|
|||||||
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"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -15,5 +14,10 @@ kotlin {
|
|||||||
api libs.microutils.koin
|
api libs.microutils.koin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
jvmMain {
|
||||||
|
dependencies {
|
||||||
|
api libs.plagubot.plugins.inline.queries
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,21 @@ package dev.inmo.plaguposter.posts.panel
|
|||||||
import dev.inmo.plaguposter.posts.models.RegisteredPost
|
import dev.inmo.plaguposter.posts.models.RegisteredPost
|
||||||
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.InlineKeyboardButton
|
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.InlineKeyboardButton
|
||||||
|
|
||||||
fun interface PanelButtonBuilder {
|
interface PanelButtonBuilder {
|
||||||
|
val weight: Int
|
||||||
suspend fun buildButton(post: RegisteredPost): InlineKeyboardButton?
|
suspend fun buildButton(post: RegisteredPost): InlineKeyboardButton?
|
||||||
|
|
||||||
|
class Default(override val weight: Int = 0, private val block: suspend (RegisteredPost) -> InlineKeyboardButton?) : PanelButtonBuilder {
|
||||||
|
override suspend fun buildButton(post: RegisteredPost): InlineKeyboardButton? = block(post)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
operator fun invoke(block: suspend (RegisteredPost) -> InlineKeyboardButton?) = Default(
|
||||||
|
block = block
|
||||||
|
)
|
||||||
|
operator fun invoke(weight: Int, block: suspend (RegisteredPost) -> InlineKeyboardButton?) = Default(
|
||||||
|
weight,
|
||||||
|
block
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineK
|
|||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
class PanelButtonsAPI(
|
class PanelButtonsAPI(
|
||||||
private val preset: List<PanelButtonBuilder>,
|
private val preset: Map<Int, List<PanelButtonBuilder>>,
|
||||||
private val rootPanelButtonText: String
|
private val rootPanelButtonText: String
|
||||||
) {
|
) {
|
||||||
private val _buttons = mutableSetOf<PanelButtonBuilder>().also {
|
private val _buttonsMap = mutableMapOf<Int, MutableList<PanelButtonBuilder>>().also {
|
||||||
it.addAll(preset)
|
it.putAll(preset.map { it.key to it.value.toMutableList() })
|
||||||
}
|
}
|
||||||
internal val buttonsBuilders: List<PanelButtonBuilder>
|
internal val buttonsBuilders: List<PanelButtonBuilder>
|
||||||
get() = _buttons.toList()
|
get() = _buttonsMap.toList().sortedBy { it.first }.flatMap { it.second }
|
||||||
internal val forceRefreshFlow = MutableSharedFlow<PostId>()
|
internal val forceRefreshFlow = MutableSharedFlow<PostId>()
|
||||||
|
|
||||||
val RootPanelButtonBuilder = PanelButtonBuilder {
|
val RootPanelButtonBuilder = PanelButtonBuilder {
|
||||||
@@ -22,8 +22,13 @@ class PanelButtonsAPI(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(button: PanelButtonBuilder) = _buttons.add(button)
|
fun add(button: PanelButtonBuilder, weight: Int = button.weight) = _buttonsMap.getOrPut(weight) { mutableListOf() }.add(button)
|
||||||
fun remove(button: PanelButtonBuilder) = _buttons.remove(button)
|
fun remove(button: PanelButtonBuilder) = _buttonsMap.mapNotNull { (k, v) ->
|
||||||
|
v.remove(button)
|
||||||
|
k.takeIf { v.isEmpty() }
|
||||||
|
}.forEach {
|
||||||
|
_buttonsMap.remove(it)
|
||||||
|
}
|
||||||
suspend fun forceRefresh(postId: PostId) {
|
suspend fun forceRefresh(postId: PostId) {
|
||||||
forceRefreshFlow.emit(postId)
|
forceRefreshFlow.emit(postId)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,43 @@
|
|||||||
package dev.inmo.plaguposter.posts.panel
|
package dev.inmo.plaguposter.posts.panel
|
||||||
|
|
||||||
import com.benasher44.uuid.uuid4
|
import com.benasher44.uuid.uuid4
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.koin.getAllDistinct
|
import dev.inmo.micro_utils.koin.getAllDistinct
|
||||||
import dev.inmo.micro_utils.repos.deleteById
|
import dev.inmo.micro_utils.repos.*
|
||||||
import dev.inmo.micro_utils.repos.set
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
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.fullyCached
|
||||||
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.useCache
|
||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
import dev.inmo.plaguposter.posts.panel.repos.PostsMessages
|
import dev.inmo.plaguposter.posts.panel.repos.PostsMessages
|
||||||
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
||||||
import dev.inmo.tgbotapi.extensions.api.delete
|
import dev.inmo.tgbotapi.extensions.api.delete
|
||||||
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.edit.text.editMessageText
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.command
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
|
||||||
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
|
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
|
||||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
|
||||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
|
||||||
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
|
||||||
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
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.Message
|
import dev.inmo.tgbotapi.utils.bold
|
||||||
import dev.inmo.tgbotapi.types.message.content.TextContent
|
import dev.inmo.tgbotapi.utils.buildEntities
|
||||||
|
import dev.inmo.tgbotapi.utils.italic
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
@@ -41,7 +52,8 @@ object Plugin : Plugin {
|
|||||||
val parseMode: ParseMode? = null,
|
val parseMode: ParseMode? = null,
|
||||||
val buttonsPerRow: Int = 4,
|
val buttonsPerRow: Int = 4,
|
||||||
val deleteButtonText: String? = null,
|
val deleteButtonText: String? = null,
|
||||||
val rootButtonText: String = "Return to panel"
|
val rootButtonText: String = "◀️",
|
||||||
|
val refreshButtonText: String? = "\uD83D\uDD04"
|
||||||
)
|
)
|
||||||
override fun Module.setupDI(database: Database, params: JsonObject) {
|
override fun Module.setupDI(database: Database, params: JsonObject) {
|
||||||
params["panel"] ?.let { element ->
|
params["panel"] ?.let { element ->
|
||||||
@@ -57,12 +69,24 @@ object Plugin : Plugin {
|
|||||||
"delete ${it.id.string}"
|
"delete ${it.id.string}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
config.refreshButtonText ?.let { text ->
|
||||||
|
PanelButtonBuilder {
|
||||||
|
CallbackDataInlineKeyboardButton(
|
||||||
|
text,
|
||||||
|
"refresh ${it.id.string}"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
PanelButtonsAPI(
|
PanelButtonsAPI(
|
||||||
getAllDistinct<PanelButtonBuilder>() + builtInButtons,
|
emptyMap(),
|
||||||
config.rootButtonText
|
config.rootButtonText
|
||||||
)
|
).apply {
|
||||||
|
(getAllDistinct<PanelButtonBuilder>() + builtInButtons).forEach {
|
||||||
|
add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +95,12 @@ object Plugin : Plugin {
|
|||||||
val chatsConfig = koin.get<ChatConfig>()
|
val chatsConfig = koin.get<ChatConfig>()
|
||||||
val config = koin.getOrNull<Config>() ?: Config()
|
val config = koin.getOrNull<Config>() ?: Config()
|
||||||
val api = koin.get<PanelButtonsAPI>()
|
val api = koin.get<PanelButtonsAPI>()
|
||||||
val postsMessages = PostsMessages(koin.get(), koin.get())
|
val basePostsMessages = PostsMessages(koin.get(), koin.get())
|
||||||
|
val postsMessages = if (koin.useCache) {
|
||||||
|
basePostsMessages.fullyCached(MapKeyValueRepo(), koin.get())
|
||||||
|
} else {
|
||||||
|
basePostsMessages
|
||||||
|
}
|
||||||
|
|
||||||
postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) {
|
postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) {
|
||||||
val firstContent = it.content.first()
|
val firstContent = it.content.first()
|
||||||
@@ -84,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 ->
|
||||||
@@ -97,9 +126,9 @@ object Plugin : Plugin {
|
|||||||
delete(chatId, messageId)
|
delete(chatId, messageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updatePost(
|
suspend fun refreshPostMessage(
|
||||||
postId: PostId,
|
postId: PostId,
|
||||||
chatId: ChatId,
|
chatId: IdChatIdentifier,
|
||||||
messageId: MessageIdentifier
|
messageId: MessageIdentifier
|
||||||
) {
|
) {
|
||||||
val post = postsRepo.getById(postId) ?: return
|
val post = postsRepo.getById(postId) ?: return
|
||||||
@@ -108,24 +137,26 @@ object Plugin : Plugin {
|
|||||||
builder.buildButton(post)
|
builder.buildButton(post)
|
||||||
}.takeIf { it.isNotEmpty() }
|
}.takeIf { it.isNotEmpty() }
|
||||||
}
|
}
|
||||||
edit(
|
editMessageText(
|
||||||
chatId,
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
|
text = config.text,
|
||||||
replyMarkup = InlineKeyboardMarkup(buttons)
|
replyMarkup = InlineKeyboardMarkup(buttons)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
updatePost(postId, it.message.chat.id, it.message.messageId)
|
refreshPostMessage(postId, it.message.chat.id, it.message.messageId)
|
||||||
|
answer(it)
|
||||||
}
|
}
|
||||||
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)
|
||||||
@@ -140,6 +171,7 @@ object Plugin : Plugin {
|
|||||||
api.RootPanelButtonBuilder.buildButton(post) ?.let(::add)
|
api.RootPanelButtonBuilder.buildButton(post) ?.let(::add)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
answer(query)
|
||||||
|
|
||||||
val pushedButton = waitMessageDataCallbackQuery().first {
|
val pushedButton = waitMessageDataCallbackQuery().first {
|
||||||
it.message.sameMessage(query.message)
|
it.message.sameMessage(query.message)
|
||||||
@@ -149,10 +181,81 @@ object Plugin : Plugin {
|
|||||||
postsRepo.deleteById(postId)
|
postsRepo.deleteById(postId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onMessageDataCallbackQuery(
|
||||||
|
initialFilter = {
|
||||||
|
it.data.startsWith("refresh ") && it.message.chat.id in chatsConfig.allSourceChatIds
|
||||||
|
}
|
||||||
|
) { query ->
|
||||||
|
val postId = query.data.removePrefix("refresh ").let(::PostId)
|
||||||
|
val (chatId, messageId) = postsMessages.get(postId) ?: return@onMessageDataCallbackQuery
|
||||||
|
|
||||||
|
runCatchingSafely {
|
||||||
|
refreshPostMessage(
|
||||||
|
postId,
|
||||||
|
chatId,
|
||||||
|
messageId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
answer(query)
|
||||||
|
}
|
||||||
|
|
||||||
api.forceRefreshFlow.subscribeSafelyWithoutExceptions(this) {
|
api.forceRefreshFlow.subscribeSafelyWithoutExceptions(this) {
|
||||||
val (chatId, messageId) = postsMessages.get(it) ?: return@subscribeSafelyWithoutExceptions
|
val (chatId, messageId) = postsMessages.get(it) ?: return@subscribeSafelyWithoutExceptions
|
||||||
updatePost(it, chatId, messageId)
|
refreshPostMessage(it, chatId, messageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
command("panel") {
|
||||||
|
val reply = it.replyTo
|
||||||
|
|
||||||
|
if (reply == null) {
|
||||||
|
runCatchingSafely {
|
||||||
|
edit(
|
||||||
|
it,
|
||||||
|
it.content.textSources + buildEntities {
|
||||||
|
+"${UnsuccessfulSymbol}\n" + bold("Result") + ": " + italic("You should reply post content to trigger panel retrieving")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.onFailure { _ ->
|
||||||
|
reply(
|
||||||
|
it,
|
||||||
|
buildEntities {
|
||||||
|
bold("Result") + ": " + italic("You should reply post content to trigger panel retrieving")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@command
|
||||||
|
}
|
||||||
|
|
||||||
|
val postId = postsRepo.getIdByChatAndMessage(reply.chat.id, reply.messageId)
|
||||||
|
if (postId == null) {
|
||||||
|
runCatchingSafely {
|
||||||
|
edit(
|
||||||
|
it,
|
||||||
|
it.content.textSources + buildEntities {
|
||||||
|
+"${UnsuccessfulSymbol}\n" + bold("Result") + ": " + italic("Unable to find post related to replied message")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}.onFailure { _ ->
|
||||||
|
reply(
|
||||||
|
it,
|
||||||
|
buildEntities {
|
||||||
|
bold("Result") + ": " + italic("Unable to find post related to replied message")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@command
|
||||||
|
}
|
||||||
|
|
||||||
|
postsMessages.get(postId) ?.let {
|
||||||
|
runCatchingSafely { delete(it.id, it.value) }
|
||||||
|
postsMessages.unset(postId)
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshPostMessage(postId, it.chat.id, it.messageId)
|
||||||
|
|
||||||
|
postsMessages.set(postId, it.chat.id to it.messageId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,20 @@ import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
|
|||||||
import dev.inmo.micro_utils.repos.mappers.withMapper
|
import dev.inmo.micro_utils.repos.mappers.withMapper
|
||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
|
import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer
|
||||||
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import dev.inmo.tgbotapi.types.MessageIdentifier
|
import dev.inmo.tgbotapi.types.MessageIdentifier
|
||||||
import kotlinx.serialization.builtins.PairSerializer
|
import kotlinx.serialization.builtins.PairSerializer
|
||||||
import kotlinx.serialization.builtins.serializer
|
import kotlinx.serialization.builtins.serializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.Database
|
||||||
|
|
||||||
private val ChatIdToMessageSerializer = PairSerializer(ChatId.serializer(), MessageIdentifier.serializer())
|
private val ChatIdToMessageSerializer = PairSerializer(FullChatIdentifierSerializer, MessageIdentifier.serializer())
|
||||||
|
|
||||||
fun PostsMessages(
|
fun PostsMessages(
|
||||||
database: Database,
|
database: Database,
|
||||||
json: Json
|
json: Json
|
||||||
): KeyValueRepo<PostId, Pair<ChatId, MessageIdentifier>> = ExposedKeyValueRepo<String, String>(
|
): KeyValueRepo<PostId, Pair<IdChatIdentifier, MessageIdentifier>> = ExposedKeyValueRepo<String, String>(
|
||||||
database,
|
database,
|
||||||
{ text("postId") },
|
{ text("postId") },
|
||||||
{ text("chatToMessage") },
|
{ text("chatToMessage") },
|
||||||
@@ -25,5 +27,5 @@ fun PostsMessages(
|
|||||||
{ string },
|
{ string },
|
||||||
{ json.encodeToString(ChatIdToMessageSerializer, this) },
|
{ json.encodeToString(ChatIdToMessageSerializer, this) },
|
||||||
{ PostId(this) },
|
{ PostId(this) },
|
||||||
{ json.decodeFromString(ChatIdToMessageSerializer, this) }
|
{ json.decodeFromString(ChatIdToMessageSerializer, this).let { (it.first as IdChatIdentifier) to it.second } }
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,25 +1,39 @@
|
|||||||
package dev.inmo.plaguposter.posts.models
|
package dev.inmo.plaguposter.posts.models
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.extensions.utils.mediaGroupMessageOrNull
|
import dev.inmo.tgbotapi.extensions.utils.possiblyMediaGroupMessageOrNull
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
|
import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer
|
||||||
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import dev.inmo.tgbotapi.types.MessageIdentifier
|
import dev.inmo.tgbotapi.types.MessageIdentifier
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
|
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
|
||||||
import dev.inmo.tgbotapi.types.message.content.MessageContent
|
import dev.inmo.tgbotapi.types.message.content.MediaGroupContent
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PostContentInfo(
|
data class PostContentInfo(
|
||||||
val chatId: ChatId,
|
@Serializable(FullChatIdentifierSerializer::class)
|
||||||
|
val chatId: IdChatIdentifier,
|
||||||
val messageId: MessageIdentifier,
|
val messageId: MessageIdentifier,
|
||||||
val group: String?,
|
val group: String?,
|
||||||
val order: Int
|
val order: Int
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromMessage(message: ContentMessage<*>, order: Int) = PostContentInfo(
|
private fun fromMessage(message: ContentMessage<*>, order: Int) = PostContentInfo(
|
||||||
message.chat.id,
|
message.chat.id,
|
||||||
message.messageId,
|
message.messageId,
|
||||||
message.mediaGroupMessageOrNull() ?.mediaGroupId,
|
message.possiblyMediaGroupMessageOrNull() ?.mediaGroupId,
|
||||||
order
|
order
|
||||||
)
|
)
|
||||||
|
fun fromMessage(message: ContentMessage<*>): List<PostContentInfo> {
|
||||||
|
val content = message.content
|
||||||
|
|
||||||
|
return if (content is MediaGroupContent<*>) {
|
||||||
|
content.group.mapIndexed { i, it ->
|
||||||
|
fromMessage(it.sourceMessage, i)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
listOf(fromMessage(message, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,6 @@ import kotlin.jvm.JvmInline
|
|||||||
@JvmInline
|
@JvmInline
|
||||||
value class PostId(
|
value class PostId(
|
||||||
val string: String
|
val string: String
|
||||||
)
|
) {
|
||||||
|
override fun toString(): String = string
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
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
|
||||||
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import dev.inmo.tgbotapi.types.MessageIdentifier
|
import dev.inmo.tgbotapi.types.MessageIdentifier
|
||||||
|
|
||||||
interface ReadPostsRepo : ReadCRUDRepo<RegisteredPost, PostId> {
|
interface ReadPostsRepo : ReadCRUDRepo<RegisteredPost, PostId> {
|
||||||
suspend fun getIdByChatAndMessage(chatId: ChatId, messageId: MessageIdentifier): PostId?
|
suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId?
|
||||||
suspend fun getPostCreationTime(postId: PostId): DateTime?
|
suspend fun getPostCreationTime(postId: PostId): DateTime?
|
||||||
|
suspend fun getFirstMessageInfo(postId: PostId): PostContentInfo?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,19 +11,19 @@ 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
|
||||||
|
|
||||||
class PostPublisher(
|
class PostPublisher(
|
||||||
private val bot: TelegramBot,
|
private val bot: TelegramBot,
|
||||||
private val postsRepo: PostsRepo,
|
private val postsRepo: PostsRepo,
|
||||||
private val cachingChatId: ChatId,
|
private val cachingChatId: IdChatIdentifier,
|
||||||
private val targetChatId: ChatId,
|
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) {
|
||||||
@@ -34,27 +34,54 @@ 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 {
|
||||||
bot.copyMessage(targetChatId, it.chatId, it.messageId)
|
targetChatIds.forEach { targetChatId ->
|
||||||
|
runCatching {
|
||||||
|
bot.copyMessage(targetChatId, it.chatId, it.messageId)
|
||||||
|
}.onFailure { _ ->
|
||||||
|
runCatching {
|
||||||
|
bot.forwardMessage(
|
||||||
|
fromChatId = it.chatId,
|
||||||
|
toChatId = cachingChatId,
|
||||||
|
messageId = it.messageId
|
||||||
|
)
|
||||||
|
}.onSuccess {
|
||||||
|
bot.copyMessage(targetChatId, it)
|
||||||
|
haveSentMessages = true
|
||||||
|
}
|
||||||
|
}.onSuccess {
|
||||||
|
haveSentMessages = true
|
||||||
|
}
|
||||||
|
}
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
val resultContents = contents.mapNotNull {
|
val resultContents = contents.mapNotNull {
|
||||||
it.order to (bot.forwardMessage(toChatId = cachingChatId, fromChatId = it.chatId, messageId = it.messageId).contentMessageOrNull() ?: return@mapNotNull null)
|
it.order to (bot.forwardMessage(toChatId = cachingChatId, fromChatId = it.chatId, messageId = it.messageId).contentMessageOrNull() ?: return@mapNotNull null)
|
||||||
}.sortedBy { it.first }.mapNotNull { (_, it) ->
|
}.sortedBy { it.first }.mapNotNull { (_, forwardedMessage) ->
|
||||||
it.withContentOrNull<MediaGroupContent>() ?: null.also { _ ->
|
forwardedMessage.withContentOrNull<MediaGroupPartContent>() ?: null.also { _ ->
|
||||||
bot.copyMessage(targetChatId, it)
|
targetChatIds.forEach { targetChatId ->
|
||||||
|
bot.copyMessage(targetChatId, forwardedMessage)
|
||||||
|
haveSentMessages = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultContents.singleOrNull() ?.also {
|
resultContents.singleOrNull() ?.also {
|
||||||
bot.copyMessage(targetChatId, it)
|
targetChatIds.forEach { targetChatId ->
|
||||||
|
bot.copyMessage(targetChatId, it)
|
||||||
|
haveSentMessages = true
|
||||||
|
}
|
||||||
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() }
|
||||||
|
)
|
||||||
|
haveSentMessages = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,5 +89,6 @@ class PostPublisher(
|
|||||||
postsRepo.deleteById(postId)
|
postsRepo.deleteById(postId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return haveSentMessages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,18 @@ import dev.inmo.kslog.common.logger
|
|||||||
import dev.inmo.kslog.common.w
|
import dev.inmo.kslog.common.w
|
||||||
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.singleWithBinds
|
||||||
import dev.inmo.micro_utils.repos.deleteById
|
import dev.inmo.micro_utils.repos.deleteById
|
||||||
import dev.inmo.plagubot.Plugin
|
import dev.inmo.plagubot.Plugin
|
||||||
import dev.inmo.plaguposter.common.SuccessfulSymbol
|
import dev.inmo.plaguposter.common.SuccessfulSymbol
|
||||||
import dev.inmo.plaguposter.common.UnsuccessfulSymbol
|
import dev.inmo.plaguposter.common.UnsuccessfulSymbol
|
||||||
import dev.inmo.plaguposter.posts.exposed.ExposedPostsRepo
|
import dev.inmo.plaguposter.posts.exposed.ExposedPostsRepo
|
||||||
import dev.inmo.plaguposter.common.ChatConfig
|
import dev.inmo.plaguposter.common.ChatConfig
|
||||||
import dev.inmo.plaguposter.inlines.models.Format
|
import dev.inmo.plagubot.plugins.inline.queries.models.Format
|
||||||
import dev.inmo.plaguposter.inlines.models.OfferTemplate
|
import dev.inmo.plagubot.plugins.inline.queries.models.OfferTemplate
|
||||||
import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo
|
import dev.inmo.plagubot.plugins.inline.queries.repos.InlineTemplatesRepo
|
||||||
|
import dev.inmo.plaguposter.common.useCache
|
||||||
|
import dev.inmo.plaguposter.posts.cached.CachedPostsRepo
|
||||||
import dev.inmo.plaguposter.posts.repo.*
|
import dev.inmo.plaguposter.posts.repo.*
|
||||||
import dev.inmo.plaguposter.posts.sending.PostPublisher
|
import dev.inmo.plaguposter.posts.sending.PostPublisher
|
||||||
import dev.inmo.tgbotapi.extensions.api.delete
|
import dev.inmo.tgbotapi.extensions.api.delete
|
||||||
@@ -26,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
|
||||||
@@ -44,14 +46,19 @@ object Plugin : Plugin {
|
|||||||
}
|
}
|
||||||
single { get<Json>().decodeFromJsonElement(Config.serializer(), configJson) }
|
single { get<Json>().decodeFromJsonElement(Config.serializer(), configJson) }
|
||||||
single { get<Config>().chats }
|
single { get<Config>().chats }
|
||||||
single { ExposedPostsRepo(database) } binds arrayOf(
|
single { ExposedPostsRepo(database) }
|
||||||
PostsRepo::class,
|
singleWithBinds<PostsRepo> {
|
||||||
ReadPostsRepo::class,
|
val base = get<ExposedPostsRepo>()
|
||||||
WritePostsRepo::class,
|
|
||||||
)
|
if (useCache) {
|
||||||
|
CachedPostsRepo(base, get())
|
||||||
|
} else {
|
||||||
|
base
|
||||||
|
}
|
||||||
|
}
|
||||||
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.toList(), config.deleteAfterPublishing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
54
posts/src/jvmMain/kotlin/cached/CachedPostsRepo.kt
Normal file
54
posts/src/jvmMain/kotlin/cached/CachedPostsRepo.kt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package dev.inmo.plaguposter.posts.cached
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.SmartRWLocker
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
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.full.FullCRUDCacheRepo
|
||||||
|
import dev.inmo.plaguposter.posts.models.NewPost
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostContentInfo
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
import dev.inmo.plaguposter.posts.models.RegisteredPost
|
||||||
|
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||||
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
|
import dev.inmo.tgbotapi.types.MessageIdentifier
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class CachedPostsRepo(
|
||||||
|
private val parentRepo: PostsRepo,
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
private val kvCache: KeyValueRepo<PostId, RegisteredPost> = MapKeyValueRepo()
|
||||||
|
) : PostsRepo, CRUDRepo<RegisteredPost, PostId, NewPost> by FullCRUDCacheRepo(
|
||||||
|
parentRepo,
|
||||||
|
kvCache,
|
||||||
|
scope,
|
||||||
|
skipStartInvalidate = false,
|
||||||
|
locker = SmartRWLocker(),
|
||||||
|
{ it.id }
|
||||||
|
) {
|
||||||
|
override val removedPostsFlow: Flow<RegisteredPost> by parentRepo::removedPostsFlow
|
||||||
|
|
||||||
|
override suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId? {
|
||||||
|
doForAllWithNextPaging(firstPageWithOneElementPagination) {
|
||||||
|
kvCache.values(it).also {
|
||||||
|
it.results.forEach {
|
||||||
|
return it.takeIf {
|
||||||
|
it.content.any { it.chatId == chatId && it.messageId == messageId }
|
||||||
|
} ?.id ?: return@forEach
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPostCreationTime(postId: PostId): DateTime? = getById(postId) ?.created
|
||||||
|
|
||||||
|
override suspend fun getFirstMessageInfo(postId: PostId): PostContentInfo? = getById(postId) ?.content ?.firstOrNull()
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import dev.inmo.micro_utils.repos.KeyValuesRepo
|
|||||||
import dev.inmo.micro_utils.repos.exposed.*
|
import dev.inmo.micro_utils.repos.exposed.*
|
||||||
import dev.inmo.plaguposter.posts.models.*
|
import dev.inmo.plaguposter.posts.models.*
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
|
|
||||||
internal class ExposedContentInfoRepo(
|
internal class ExposedContentInfoRepo(
|
||||||
@@ -13,13 +14,14 @@ internal class ExposedContentInfoRepo(
|
|||||||
) : ExposedRepo, Table(name = "posts_content") {
|
) : ExposedRepo, Table(name = "posts_content") {
|
||||||
val postIdColumn = text("post_id").references(postIdColumnReference, ReferenceOption.CASCADE, ReferenceOption.CASCADE)
|
val postIdColumn = text("post_id").references(postIdColumnReference, ReferenceOption.CASCADE, ReferenceOption.CASCADE)
|
||||||
val chatIdColumn = long("chat_id")
|
val chatIdColumn = long("chat_id")
|
||||||
|
val threadIdColumn = long("thread_id").nullable().default(null)
|
||||||
val messageIdColumn = long("message_id")
|
val messageIdColumn = long("message_id")
|
||||||
val groupColumn = text("group").nullable()
|
val groupColumn = text("group").nullable()
|
||||||
val orderColumn = integer("order")
|
val orderColumn = integer("order")
|
||||||
|
|
||||||
val ResultRow.asObject
|
val ResultRow.asObject
|
||||||
get() = PostContentInfo(
|
get() = PostContentInfo(
|
||||||
ChatId(get(chatIdColumn)),
|
IdChatIdentifier(get(chatIdColumn), get(threadIdColumn)),
|
||||||
get(messageIdColumn),
|
get(messageIdColumn),
|
||||||
get(groupColumn),
|
get(groupColumn),
|
||||||
get(orderColumn)
|
get(orderColumn)
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
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.exposed.AbstractExposedCRUDRepo
|
import dev.inmo.micro_utils.repos.exposed.AbstractExposedCRUDRepo
|
||||||
import dev.inmo.micro_utils.repos.exposed.initTable
|
import dev.inmo.micro_utils.repos.exposed.initTable
|
||||||
import dev.inmo.plaguposter.posts.models.*
|
import dev.inmo.plaguposter.posts.models.*
|
||||||
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import dev.inmo.tgbotapi.types.MessageIdentifier
|
import dev.inmo.tgbotapi.types.MessageIdentifier
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||||
|
import org.jetbrains.exposed.sql.statements.*
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
class ExposedPostsRepo(
|
class ExposedPostsRepo(
|
||||||
@@ -21,8 +24,9 @@ class ExposedPostsRepo(
|
|||||||
) : PostsRepo, AbstractExposedCRUDRepo<RegisteredPost, PostId, NewPost>(
|
) : PostsRepo, AbstractExposedCRUDRepo<RegisteredPost, PostId, NewPost>(
|
||||||
tableName = "posts"
|
tableName = "posts"
|
||||||
) {
|
) {
|
||||||
val idColumn = text("id").clientDefault { uuid4().toString() }
|
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(
|
||||||
@@ -33,11 +37,13 @@ class ExposedPostsRepo(
|
|||||||
|
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(idColumn)
|
override val primaryKey: PrimaryKey = PrimaryKey(idColumn)
|
||||||
|
|
||||||
override val selectById: SqlExpressionBuilder.(PostId) -> Op<Boolean> = { idColumn.eq(it.string) }
|
override val selectById: ISqlExpressionBuilder.(PostId) -> Op<Boolean> = { idColumn.eq(it.string) }
|
||||||
override val selectByIds: SqlExpressionBuilder.(List<PostId>) -> Op<Boolean> = { idColumn.inList(it.map { it.string }) }
|
override val selectByIds: ISqlExpressionBuilder.(List<PostId>) -> Op<Boolean> = { idColumn.inList(it.map { it.string }) }
|
||||||
|
override val ResultRow.asId: PostId
|
||||||
|
get() = PostId(get(idColumn))
|
||||||
override val ResultRow.asObject: RegisteredPost
|
override val ResultRow.asObject: RegisteredPost
|
||||||
get() {
|
get() {
|
||||||
val id = PostId(get(idColumn))
|
val id = asId
|
||||||
return RegisteredPost(
|
return RegisteredPost(
|
||||||
id,
|
id,
|
||||||
DateTime(get(createdColumn)),
|
DateTime(get(createdColumn)),
|
||||||
@@ -59,18 +65,6 @@ class ExposedPostsRepo(
|
|||||||
override fun InsertStatement<Number>.asObject(value: NewPost): RegisteredPost {
|
override fun InsertStatement<Number>.asObject(value: NewPost): RegisteredPost {
|
||||||
val id = PostId(get(idColumn))
|
val id = PostId(get(idColumn))
|
||||||
|
|
||||||
with(contentRepo) {
|
|
||||||
value.content.forEach { contentInfo ->
|
|
||||||
insert {
|
|
||||||
it[postIdColumn] = id.string
|
|
||||||
it[chatIdColumn] = contentInfo.chatId.chatId
|
|
||||||
it[messageIdColumn] = contentInfo.messageId
|
|
||||||
it[groupColumn] = contentInfo.group
|
|
||||||
it[orderColumn] = contentInfo.order
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return RegisteredPost(
|
return RegisteredPost(
|
||||||
id,
|
id,
|
||||||
DateTime(get(createdColumn)),
|
DateTime(get(createdColumn)),
|
||||||
@@ -82,25 +76,55 @@ class ExposedPostsRepo(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(id: PostId, value: NewPost, it: UpdateStatement) {
|
override fun createAndInsertId(value: NewPost, it: InsertStatement<Number>): PostId {
|
||||||
with(contentRepo) {
|
val id = PostId(uuid4().toString())
|
||||||
deleteWhere { postIdColumn.eq(id.string) }
|
it[idColumn] = id.string
|
||||||
value.content.forEach { contentInfo ->
|
return id
|
||||||
insert {
|
}
|
||||||
it[postIdColumn] = id.string
|
|
||||||
it[chatIdColumn] = contentInfo.chatId.chatId
|
override fun update(id: PostId?, value: NewPost, it: UpdateBuilder<Int>) {
|
||||||
it[messageIdColumn] = contentInfo.messageId
|
it[latestUpdateColumn] = DateTime.now().unixMillis
|
||||||
it[groupColumn] = contentInfo.group
|
}
|
||||||
it[orderColumn] = contentInfo.order
|
|
||||||
|
private fun updateContent(post: RegisteredPost) {
|
||||||
|
transaction(database) {
|
||||||
|
with(contentRepo) {
|
||||||
|
deleteWhere { postIdColumn.eq(post.id.string) }
|
||||||
|
post.content.forEach { contentInfo ->
|
||||||
|
insert {
|
||||||
|
it[postIdColumn] = post.id.string
|
||||||
|
it[chatIdColumn] = contentInfo.chatId.chatId
|
||||||
|
it[threadIdColumn] = contentInfo.chatId.threadId
|
||||||
|
it[messageIdColumn] = contentInfo.messageId
|
||||||
|
it[groupColumn] = contentInfo.group
|
||||||
|
it[orderColumn] = contentInfo.order
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun insert(value: NewPost, it: InsertStatement<Number>) {
|
override fun insert(value: NewPost, it: InsertStatement<Number>) {
|
||||||
|
super.insert(value, it)
|
||||||
it[createdColumn] = DateTime.now().unixMillis
|
it[createdColumn] = DateTime.now().unixMillis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun onAfterCreate(values: List<Pair<NewPost, RegisteredPost>>): List<RegisteredPost> {
|
||||||
|
return values.map {
|
||||||
|
val actual = it.second.copy(content = it.first.content)
|
||||||
|
updateContent(actual)
|
||||||
|
actual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun onAfterUpdate(value: List<UpdatedValuePair<NewPost, RegisteredPost>>): List<RegisteredPost> {
|
||||||
|
return value.map {
|
||||||
|
val actual = it.second.copy(content = it.first.content)
|
||||||
|
updateContent(actual)
|
||||||
|
actual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun deleteById(ids: List<PostId>) {
|
override suspend fun deleteById(ids: List<PostId>) {
|
||||||
onBeforeDelete(ids)
|
onBeforeDelete(ids)
|
||||||
val posts = ids.mapNotNull {
|
val posts = ids.mapNotNull {
|
||||||
@@ -109,7 +133,7 @@ class ExposedPostsRepo(
|
|||||||
val existsIds = posts.keys.toList()
|
val existsIds = posts.keys.toList()
|
||||||
transaction(db = database) {
|
transaction(db = database) {
|
||||||
val deleted = deleteWhere(null, null) {
|
val deleted = deleteWhere(null, null) {
|
||||||
selectByIds(existsIds)
|
selectByIds(it, existsIds)
|
||||||
}
|
}
|
||||||
with(contentRepo) {
|
with(contentRepo) {
|
||||||
deleteWhere {
|
deleteWhere {
|
||||||
@@ -129,10 +153,14 @@ class ExposedPostsRepo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getIdByChatAndMessage(chatId: ChatId, messageId: MessageIdentifier): PostId? {
|
override suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId? {
|
||||||
return transaction(database) {
|
return transaction(database) {
|
||||||
with(contentRepo) {
|
with(contentRepo) {
|
||||||
select { chatIdColumn.eq(chatId.chatId).and(messageIdColumn.eq(messageId)) }.limit(1).firstOrNull() ?.get(postIdColumn)
|
select {
|
||||||
|
chatIdColumn.eq(chatId.chatId)
|
||||||
|
.and(chatId.threadId ?.let { threadIdColumn.eq(it) } ?: threadIdColumn.isNull())
|
||||||
|
.and(messageIdColumn.eq(messageId))
|
||||||
|
}.limit(1).firstOrNull() ?.get(postIdColumn)
|
||||||
} ?.let(::PostId)
|
} ?.let(::PostId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,4 +168,10 @@ class ExposedPostsRepo(
|
|||||||
override suspend fun getPostCreationTime(postId: PostId): DateTime? = transaction(database) {
|
override suspend fun getPostCreationTime(postId: PostId): DateTime? = transaction(database) {
|
||||||
select { selectById(postId) }.limit(1).firstOrNull() ?.get(createdColumn) ?.let(::DateTime)
|
select { selectById(postId) }.limit(1).firstOrNull() ?.get(createdColumn) ?.let(::DateTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getFirstMessageInfo(postId: PostId): PostContentInfo? = transaction(database) {
|
||||||
|
with(contentRepo) {
|
||||||
|
select { postIdColumn.eq(postId.string) }.limit(1).firstOrNull() ?.asObject
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -13,9 +12,5 @@ kotlin {
|
|||||||
api project(":plaguposter.posts")
|
api project(":plaguposter.posts")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,24 @@ package dev.inmo.plaguposter.posts.registrar.state
|
|||||||
import dev.inmo.micro_utils.fsm.common.State
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
import dev.inmo.plaguposter.posts.models.PostContentInfo
|
import dev.inmo.plaguposter.posts.models.PostContentInfo
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
|
import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer
|
||||||
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
interface RegistrationState : State {
|
interface RegistrationState : State {
|
||||||
override val context: ChatId
|
override val context: IdChatIdentifier
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class InProcess(
|
data class InProcess(
|
||||||
override val context: ChatId,
|
@Serializable(FullChatIdentifierSerializer::class)
|
||||||
|
override val context: IdChatIdentifier,
|
||||||
val messages: List<PostContentInfo>
|
val messages: List<PostContentInfo>
|
||||||
) : RegistrationState
|
) : RegistrationState
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Finish(
|
data class Finish(
|
||||||
override val context: ChatId,
|
@Serializable(FullChatIdentifierSerializer::class)
|
||||||
|
override val context: IdChatIdentifier,
|
||||||
val messages: List<PostContentInfo>
|
val messages: List<PostContentInfo>
|
||||||
) : RegistrationState
|
) : RegistrationState
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import dev.inmo.micro_utils.fsm.common.State
|
|||||||
import dev.inmo.micro_utils.repos.create
|
import dev.inmo.micro_utils.repos.create
|
||||||
import dev.inmo.plagubot.Plugin
|
import dev.inmo.plagubot.Plugin
|
||||||
import dev.inmo.plaguposter.common.*
|
import dev.inmo.plaguposter.common.*
|
||||||
import dev.inmo.plaguposter.inlines.models.Format
|
import dev.inmo.plagubot.plugins.inline.queries.models.Format
|
||||||
import dev.inmo.plaguposter.inlines.models.OfferTemplate
|
import dev.inmo.plagubot.plugins.inline.queries.models.OfferTemplate
|
||||||
import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo
|
import dev.inmo.plagubot.plugins.inline.queries.repos.InlineTemplatesRepo
|
||||||
import dev.inmo.plaguposter.posts.models.*
|
import dev.inmo.plaguposter.posts.models.*
|
||||||
import dev.inmo.plaguposter.posts.registrar.state.RegistrationState
|
import dev.inmo.plaguposter.posts.registrar.state.RegistrationState
|
||||||
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||||
@@ -18,83 +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.sameChat
|
||||||
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
|
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
|
||||||
import dev.inmo.tgbotapi.extensions.utils.formatting.buildEntities
|
import dev.inmo.tgbotapi.extensions.utils.textContentOrNull
|
||||||
import dev.inmo.tgbotapi.extensions.utils.formatting.regular
|
|
||||||
import dev.inmo.tgbotapi.extensions.utils.mediaGroupMessageOrNull
|
|
||||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
|
import dev.inmo.tgbotapi.utils.regular
|
||||||
import dev.inmo.tgbotapi.types.message.content.MessageContent
|
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(
|
||||||
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 {
|
||||||
waitContentMessage(
|
it.sameChat(state.context)
|
||||||
includeMediaGroups = false
|
}.first()
|
||||||
).filter {
|
|
||||||
it.chat.id == state.context
|
when {
|
||||||
}.take(1).first()
|
receivedMessage.content.textContentOrNull() ?.text == "/finish_post" -> {
|
||||||
)
|
val messageToDelete = suggestionMessageDeferred.await()
|
||||||
}
|
edit(messageToDelete, "Ok, finishing your request")
|
||||||
add {
|
RegistrationState.Finish(
|
||||||
waitMediaGroupMessages().filter {
|
state.context,
|
||||||
it.first().chat.id == state.context
|
state.messages
|
||||||
}.take(1).first()
|
)
|
||||||
|
}
|
||||||
|
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>>()
|
|
||||||
}
|
|
||||||
}.ifEmpty {
|
|
||||||
edit(messageToDelete, "Ok, finishing your request")
|
|
||||||
return@strictlyOn RegistrationState.Finish(
|
|
||||||
state.context,
|
|
||||||
state.messages
|
|
||||||
)
|
|
||||||
}.map {
|
|
||||||
PostContentInfo.fromMessage(it, state.messages.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
RegistrationState.InProcess(
|
edit(messageToDelete, "Ok, finishing your request")
|
||||||
state.context,
|
RegistrationState.Finish(
|
||||||
state.messages + newMessagesInfo
|
state.context,
|
||||||
).also {
|
state.messages
|
||||||
delete(messageToDelete)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,36 +119,28 @@ object Plugin : Plugin {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
onCommand("start_post", initialFilter = { it.chat.id == 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.chat.id == config.sourceChatId && it.mediaGroupMessageOrNull() ?.mediaGroupId == null && !FirstSourceIsCommandsFilter(it) }
|
initialFilter = { config.allSourceChatIds.any { chatId -> it.sameChat(chatId) } && !FirstSourceIsCommandsFilter(it) }
|
||||||
) {
|
) {
|
||||||
startChain(RegistrationState.Finish(it.chat.id, listOf(PostContentInfo.fromMessage(it, 0))))
|
startChain(RegistrationState.Finish(it.chat.id, PostContentInfo.fromMessage(it)))
|
||||||
}
|
|
||||||
|
|
||||||
onMediaGroup(
|
|
||||||
initialFilter = { it.first().chat.id == config.sourceChatId }
|
|
||||||
) {
|
|
||||||
startChain(
|
|
||||||
RegistrationState.Finish(
|
|
||||||
it.first().chat.id,
|
|
||||||
it.map {
|
|
||||||
PostContentInfo.fromMessage(
|
|
||||||
it,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
koin.getOrNull<InlineTemplatesRepo>() ?.apply {
|
koin.getOrNull<InlineTemplatesRepo>() ?.apply {
|
||||||
addTemplate(
|
addTemplate(
|
||||||
OfferTemplate(
|
OfferTemplate(
|
||||||
"Start post",
|
"Start post creating",
|
||||||
listOf(Format("/start_post"))
|
listOf(Format("/start_post")),
|
||||||
|
"Use this command to start creating of complex post with several messages"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
addTemplate(
|
||||||
|
OfferTemplate(
|
||||||
|
"Finish post creating",
|
||||||
|
listOf(Format("/finish_post")),
|
||||||
|
"Finish creating of complex post"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
105
publish.gradle
Normal file
105
publish.gradle
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
|
task javadocsJar(type: Jar) {
|
||||||
|
archiveClassifier = 'javadoc'
|
||||||
|
}
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications.all {
|
||||||
|
artifact javadocsJar
|
||||||
|
|
||||||
|
pom {
|
||||||
|
description = "${project.name}"
|
||||||
|
name = "${project.name}"
|
||||||
|
url = "https://github.com/InsanusMokrassar/PlaguPoster"
|
||||||
|
|
||||||
|
scm {
|
||||||
|
developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/PlaguPoster.git[push=]https://github.com/InsanusMokrassar/PlaguPoster.git"
|
||||||
|
url = "https://github.com/InsanusMokrassar/PlaguPoster.git"
|
||||||
|
}
|
||||||
|
|
||||||
|
developers {
|
||||||
|
|
||||||
|
developer {
|
||||||
|
id = "InsanusMokrassar"
|
||||||
|
name = "Aleksei Ovsiannikov"
|
||||||
|
email = "ovsyannikov.alexey95@gmail.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) {
|
||||||
|
maven {
|
||||||
|
name = "Gitea"
|
||||||
|
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven")
|
||||||
|
|
||||||
|
credentials(HttpHeaderCredentials) {
|
||||||
|
name = "Authorization"
|
||||||
|
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN')
|
||||||
|
}
|
||||||
|
|
||||||
|
authentication {
|
||||||
|
header(HttpHeaderAuthentication)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
|
||||||
|
maven {
|
||||||
|
name = "sonatype"
|
||||||
|
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||||
|
|
||||||
|
credentials {
|
||||||
|
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
|
||||||
|
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.hasProperty("signing.gnupg.keyName")) {
|
||||||
|
apply plugin: 'signing'
|
||||||
|
|
||||||
|
signing {
|
||||||
|
useGpgCmd()
|
||||||
|
|
||||||
|
sign publishing.publications
|
||||||
|
}
|
||||||
|
|
||||||
|
task signAll {
|
||||||
|
tasks.withType(Sign).forEach {
|
||||||
|
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
publish.kpsb
Normal file
1
publish.kpsb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"licenses":[],"mavenConfig":{"name":"${project.name}","description":"${project.name}","url":"https://github.com/InsanusMokrassar/PlaguPoster","vcsUrl":"https://github.com/InsanusMokrassar/PlaguPoster.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -14,9 +13,5 @@ kotlin {
|
|||||||
api project(":plaguposter.posts")
|
api project(":plaguposter.posts")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -15,9 +14,5 @@ kotlin {
|
|||||||
api libs.krontab
|
api libs.krontab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -14,9 +13,5 @@ kotlin {
|
|||||||
api project(":plaguposter.ratings")
|
api project(":plaguposter.ratings")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package dev.inmo.plaguposter.ratings.selector
|
package dev.inmo.plaguposter.ratings.selector
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
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
|
||||||
@@ -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): 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, latestChosenRepo) ?: break
|
||||||
result.add(selected)
|
result.add(selected)
|
||||||
} while (result.size < n)
|
} while (result.size < n)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
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>
|
||||||
|
suspend fun takeOneOrNull(now: DateTime = DateTime.now(), exclude: List<PostId> = emptyList()): PostId? = take(
|
||||||
|
n = 1,
|
||||||
|
now = now,
|
||||||
|
exclude = exclude
|
||||||
|
).firstOrNull()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
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.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,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,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"))) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -15,9 +14,5 @@ kotlin {
|
|||||||
api project(":plaguposter.posts.panel")
|
api project(":plaguposter.posts.panel")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
164
ratings/source/src/commonMain/kotlin/buttons/RootButtons.kt
Normal file
164
ratings/source/src/commonMain/kotlin/buttons/RootButtons.kt
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.source.buttons
|
||||||
|
|
||||||
|
import korlibs.time.DateFormat
|
||||||
|
import dev.inmo.kslog.common.TagLogger
|
||||||
|
import dev.inmo.kslog.common.d
|
||||||
|
import dev.inmo.kslog.common.i
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.Pagination
|
||||||
|
import dev.inmo.micro_utils.pagination.SimplePagination
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.paginate
|
||||||
|
import dev.inmo.plaguposter.posts.repo.ReadPostsRepo
|
||||||
|
import dev.inmo.plaguposter.ratings.models.Rating
|
||||||
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
||||||
|
import dev.inmo.plaguposter.ratings.utils.postsByRatings
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
|
||||||
|
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.ChatIdentifier
|
||||||
|
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
|
||||||
|
import dev.inmo.tgbotapi.utils.row
|
||||||
|
|
||||||
|
const val RootButtonsShowRatingData = "ratings_buttons_show"
|
||||||
|
const val RootButtonsShowRatingPageData = "ratings_buttons_show_page"
|
||||||
|
const val RootButtonsToPageData = "ratings_buttons_to_page"
|
||||||
|
|
||||||
|
suspend fun RatingsRepo.buildRootButtons(
|
||||||
|
pagination: Pagination = FirstPagePagination(16),
|
||||||
|
rowSize: Int = 4
|
||||||
|
): InlineKeyboardMarkup {
|
||||||
|
val postsByRatings = postsByRatings().toList().paginate(pagination)
|
||||||
|
return inlineKeyboard {
|
||||||
|
if (postsByRatings.pagesNumber > 1) {
|
||||||
|
row {
|
||||||
|
if (postsByRatings.page > 0) {
|
||||||
|
dataButton("<", "$RootButtonsToPageData ${postsByRatings.page - 1} ${postsByRatings.size}")
|
||||||
|
}
|
||||||
|
dataButton("${postsByRatings.page}: \uD83D\uDD04", "$RootButtonsToPageData ${postsByRatings.page} ${postsByRatings.size}")
|
||||||
|
if (postsByRatings.pagesNumber - postsByRatings.page > 1) {
|
||||||
|
dataButton(">", "$RootButtonsToPageData ${postsByRatings.page + 1} ${postsByRatings.size}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
postsByRatings.results.chunked(rowSize).map {
|
||||||
|
row {
|
||||||
|
it.forEach { (rating, posts) ->
|
||||||
|
dataButton("${rating.double}: ${posts.size}", "$RootButtonsShowRatingData ${rating.double}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val defaultPostCreationTimeFormat: DateFormat = DateFormat("dd.MM.yy HH:mm")
|
||||||
|
|
||||||
|
suspend fun RatingsRepo.buildRatingButtons(
|
||||||
|
postsRepo: ReadPostsRepo,
|
||||||
|
rating: Rating,
|
||||||
|
pagination: Pagination = FirstPagePagination(8),
|
||||||
|
rowSize: Int = 2,
|
||||||
|
postCreationTimeFormat: DateFormat = defaultPostCreationTimeFormat
|
||||||
|
): InlineKeyboardMarkup {
|
||||||
|
val postsByRatings = getPosts(rating .. rating, true).keys.paginate(pagination)
|
||||||
|
TagLogger("RatingsButtonsBuilder").i { postsByRatings.results }
|
||||||
|
return inlineKeyboard {
|
||||||
|
if (postsByRatings.pagesNumber > 1) {
|
||||||
|
row {
|
||||||
|
if (postsByRatings.page > 0) {
|
||||||
|
dataButton("<", "$RootButtonsShowRatingPageData ${postsByRatings.page - 1} ${postsByRatings.size} ${rating.double}")
|
||||||
|
}
|
||||||
|
dataButton("${postsByRatings.page}: \uD83D\uDD04", "$RootButtonsShowRatingPageData ${postsByRatings.page} ${postsByRatings.size} ${rating.double}")
|
||||||
|
if (postsByRatings.pagesNumber - postsByRatings.page > 1) {
|
||||||
|
dataButton(">", "$RootButtonsShowRatingPageData ${postsByRatings.page + 1} ${postsByRatings.size} ${rating.double}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
postsByRatings.results.chunked(rowSize).forEach {
|
||||||
|
row {
|
||||||
|
it.forEach { postId ->
|
||||||
|
val firstMessageInfo = postsRepo.getFirstMessageInfo(postId) ?: return@forEach
|
||||||
|
val postCreationTime = postsRepo.getPostCreationTime(postId) ?: return@forEach
|
||||||
|
urlButton(
|
||||||
|
postCreationTime.format(postCreationTimeFormat),
|
||||||
|
makeLinkToMessage(
|
||||||
|
firstMessageInfo.chatId,
|
||||||
|
firstMessageInfo.messageId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
row {
|
||||||
|
dataButton("↩️", "$RootButtonsToPageData 0 16")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun BehaviourContext.includeRootNavigationButtonsHandler(
|
||||||
|
allowedChats: Set<ChatIdentifier>,
|
||||||
|
ratingsRepo: RatingsRepo,
|
||||||
|
postsRepo: ReadPostsRepo
|
||||||
|
) {
|
||||||
|
suspend fun registerPageQueryListener(
|
||||||
|
dataPrefix: String,
|
||||||
|
onPageUpdate: suspend (pagination: Pagination, additionalParams: Array<String>) -> InlineKeyboardMarkup?
|
||||||
|
) {
|
||||||
|
onMessageDataCallbackQuery(
|
||||||
|
initialFilter = { it.message.chat.id in allowedChats }
|
||||||
|
) {
|
||||||
|
val args = it.data.split(" ").takeIf { it.size >= 3 } ?: return@onMessageDataCallbackQuery
|
||||||
|
val (prefix, pageRaw, sizeRaw) = args
|
||||||
|
|
||||||
|
if (prefix == dataPrefix) {
|
||||||
|
runCatchingSafely {
|
||||||
|
val page = pageRaw.toIntOrNull() ?: return@runCatchingSafely
|
||||||
|
val size = sizeRaw.toIntOrNull() ?: return@runCatchingSafely
|
||||||
|
|
||||||
|
edit(
|
||||||
|
it.message,
|
||||||
|
onPageUpdate(SimplePagination(page, size), args.drop(3).toTypedArray()) ?: return@runCatchingSafely
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
answer(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suspend fun registerPageQueryListener(
|
||||||
|
dataPrefix: String,
|
||||||
|
onPageUpdate: suspend (pagination: Pagination) -> InlineKeyboardMarkup?
|
||||||
|
) = registerPageQueryListener(dataPrefix) { pagination, _ ->
|
||||||
|
onPageUpdate(pagination)
|
||||||
|
}
|
||||||
|
registerPageQueryListener(
|
||||||
|
RootButtonsToPageData,
|
||||||
|
ratingsRepo::buildRootButtons
|
||||||
|
)
|
||||||
|
registerPageQueryListener(
|
||||||
|
RootButtonsShowRatingPageData
|
||||||
|
) { pagination, params ->
|
||||||
|
params.firstOrNull() ?.toDoubleOrNull() ?.let { rating ->
|
||||||
|
ratingsRepo.buildRatingButtons(postsRepo, Rating(rating), pagination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMessageDataCallbackQuery(
|
||||||
|
initialFilter = { it.message.chat.id in allowedChats }
|
||||||
|
) {
|
||||||
|
val (prefix, ratingRaw) = it.data.split(" ").takeIf { it.size == 2 } ?: return@onMessageDataCallbackQuery
|
||||||
|
|
||||||
|
if (prefix == RootButtonsShowRatingData) {
|
||||||
|
runCatchingSafely {
|
||||||
|
val rating = ratingRaw.toDoubleOrNull() ?: return@runCatchingSafely
|
||||||
|
edit(it.message, ratingsRepo.buildRatingButtons(postsRepo, Rating(rating)))
|
||||||
|
}
|
||||||
|
|
||||||
|
answer(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.source.repos
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.full.fullyCached
|
||||||
|
import dev.inmo.plaguposter.common.ShortMessageInfo
|
||||||
|
import dev.inmo.tgbotapi.types.PollIdentifier
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
class CachedPollsToMessagesInfoRepo(
|
||||||
|
private val repo: PollsToMessagesInfoRepo,
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
private val kvCache: FullKVCache<PollIdentifier, ShortMessageInfo> = FullKVCache()
|
||||||
|
) : PollsToMessagesInfoRepo, KeyValueRepo<PollIdentifier, ShortMessageInfo> by repo.fullyCached(kvCache, scope)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.source.repos
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.full.fullyCached
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
import dev.inmo.tgbotapi.types.PollIdentifier
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
class CachedPollsToPostsIdsRepo(
|
||||||
|
private val repo: PollsToPostsIdsRepo,
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
private val kvCache: FullKVCache<PollIdentifier, PostId> = FullKVCache()
|
||||||
|
) : PollsToPostsIdsRepo, KeyValueRepo<PollIdentifier, PostId> by repo.fullyCached(kvCache, scope)
|
||||||
@@ -11,17 +11,20 @@ import dev.inmo.micro_utils.repos.pagination.getAll
|
|||||||
import dev.inmo.micro_utils.repos.set
|
import dev.inmo.micro_utils.repos.set
|
||||||
import dev.inmo.plagubot.Plugin
|
import dev.inmo.plagubot.Plugin
|
||||||
import dev.inmo.plaguposter.common.*
|
import dev.inmo.plaguposter.common.*
|
||||||
import dev.inmo.plaguposter.inlines.models.Format
|
import dev.inmo.plagubot.plugins.inline.queries.models.Format
|
||||||
import dev.inmo.plaguposter.inlines.models.OfferTemplate
|
import dev.inmo.plagubot.plugins.inline.queries.models.OfferTemplate
|
||||||
import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo
|
import dev.inmo.plagubot.plugins.inline.queries.repos.InlineTemplatesRepo
|
||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
import dev.inmo.plaguposter.posts.panel.PanelButtonBuilder
|
import dev.inmo.plaguposter.posts.panel.PanelButtonBuilder
|
||||||
import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI
|
import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI
|
||||||
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||||
import dev.inmo.plaguposter.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.plaguposter.ratings.source.buttons.buildRootButtons
|
||||||
|
import dev.inmo.plaguposter.ratings.source.buttons.includeRootNavigationButtonsHandler
|
||||||
import dev.inmo.plaguposter.ratings.source.models.*
|
import dev.inmo.plaguposter.ratings.source.models.*
|
||||||
import dev.inmo.plaguposter.ratings.source.repos.*
|
import dev.inmo.plaguposter.ratings.source.repos.*
|
||||||
|
import dev.inmo.plaguposter.ratings.utils.postsByRatings
|
||||||
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
||||||
import dev.inmo.tgbotapi.extensions.api.delete
|
import dev.inmo.tgbotapi.extensions.api.delete
|
||||||
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
||||||
@@ -33,9 +36,13 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
|||||||
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
|
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
|
||||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
|
||||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
|
||||||
|
import dev.inmo.tgbotapi.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.regular
|
import dev.inmo.tgbotapi.types.message.textsources.regular
|
||||||
import kotlinx.coroutines.flow.filter
|
import dev.inmo.tgbotapi.utils.buildEntities
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
@@ -61,8 +68,29 @@ object Plugin : Plugin {
|
|||||||
get<Json>().decodeFromJsonElement(Config.serializer(), params["ratingsPolls"] ?: error("Unable to load config for rating polls in $params"))
|
get<Json>().decodeFromJsonElement(Config.serializer(), params["ratingsPolls"] ?: error("Unable to load config for rating polls in $params"))
|
||||||
}
|
}
|
||||||
single<RatingsVariants>(ratingVariantsQualifier) { get<Config>().variants }
|
single<RatingsVariants>(ratingVariantsQualifier) { get<Config>().variants }
|
||||||
single<PollsToPostsIdsRepo> { ExposedPollsToPostsIdsRepo(database) }
|
|
||||||
single<PollsToMessagesInfoRepo> { ExposedPollsToMessagesInfoRepo(database) }
|
single { ExposedPollsToPostsIdsRepo(database) }
|
||||||
|
single<PollsToPostsIdsRepo> {
|
||||||
|
val base = get<ExposedPollsToPostsIdsRepo>()
|
||||||
|
|
||||||
|
if (useCache) {
|
||||||
|
CachedPollsToPostsIdsRepo(base, get())
|
||||||
|
} else {
|
||||||
|
base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
single { ExposedPollsToMessagesInfoRepo(database) }
|
||||||
|
single<PollsToMessagesInfoRepo> {
|
||||||
|
val base = get<ExposedPollsToMessagesInfoRepo>()
|
||||||
|
|
||||||
|
if (useCache) {
|
||||||
|
CachedPollsToMessagesInfoRepo(base, get())
|
||||||
|
} else {
|
||||||
|
base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
single<VariantTransformer> {
|
single<VariantTransformer> {
|
||||||
val ratingsSettings = get<RatingsVariants>(ratingVariantsQualifier)
|
val ratingsSettings = get<RatingsVariants>(ratingVariantsQualifier)
|
||||||
VariantTransformer {
|
VariantTransformer {
|
||||||
@@ -79,6 +107,7 @@ object Plugin : Plugin {
|
|||||||
val postsRepo = koin.get<PostsRepo>()
|
val postsRepo = koin.get<PostsRepo>()
|
||||||
val config = koin.get<Config>()
|
val config = koin.get<Config>()
|
||||||
val panelApi = koin.getOrNull<PanelButtonsAPI>()
|
val panelApi = koin.getOrNull<PanelButtonsAPI>()
|
||||||
|
val chatConfig = koin.get<ChatConfig>()
|
||||||
|
|
||||||
onPollUpdates (markerFactory = { it.id }) { poll ->
|
onPollUpdates (markerFactory = { it.id }) { poll ->
|
||||||
val postId = pollsToPostsIdsRepo.get(poll.id) ?: return@onPollUpdates
|
val postId = pollsToPostsIdsRepo.get(poll.id) ?: return@onPollUpdates
|
||||||
@@ -94,17 +123,21 @@ object Plugin : Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val post = postsRepo.getById(postId) ?: return false
|
val post = postsRepo.getById(postId) ?: return false
|
||||||
|
ratingsRepo.set(postId, Rating(0.0))
|
||||||
for (content in post.content) {
|
for (content in post.content) {
|
||||||
runCatchingSafely {
|
runCatchingSafely {
|
||||||
val sent = send(
|
val sent = send(
|
||||||
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())
|
||||||
}.getOrNull() ?: continue
|
}.getOrNull() ?: continue
|
||||||
|
|
||||||
|
delay(500L)
|
||||||
|
|
||||||
panelApi ?.forceRefresh(postId)
|
panelApi ?.forceRefresh(postId)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -130,12 +163,13 @@ object Plugin : Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
postsRepo.deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(this) { postId ->
|
ratingsRepo.onValueRemoved.subscribeSafelyWithoutExceptions(this) { postId ->
|
||||||
detachPoll(postId)
|
detachPoll(postId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.autoAttach) {
|
if (config.autoAttach) {
|
||||||
postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) {
|
postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) {
|
||||||
|
delay(500L)
|
||||||
attachPoll(it.id)
|
attachPoll(it.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,6 +242,32 @@ object Plugin : Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onCommand("ratings", requireOnlyCommandInMessage = true) {
|
||||||
|
if (it.chat.id in chatConfig.allSourceChatIds) {
|
||||||
|
val ratings = ratingsRepo.postsByRatings().toList().sortedByDescending { it.first }
|
||||||
|
val textSources = buildEntities {
|
||||||
|
+ "Ratings amount: " + bold("${ratings.sumOf { it.second.size }}") + "\n\n"
|
||||||
|
ratings.forEach {
|
||||||
|
+ "• " + bold("% 3.1f".format(it.first.double)) + ": " + bold(it.second.size.toString()) + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val keyboard = flatInlineKeyboard {
|
||||||
|
dataButton("Interactive mode", "ratings_interactive")
|
||||||
|
}
|
||||||
|
runCatchingSafely {
|
||||||
|
edit(it, textSources, replyMarkup = keyboard)
|
||||||
|
}.onFailure { _ ->
|
||||||
|
reply(it, textSources, replyMarkup = keyboard)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
includeRootNavigationButtonsHandler(chatConfig.allSourceChatIds, ratingsRepo, postsRepo)
|
||||||
|
onMessageDataCallbackQuery("ratings_interactive", initialFilter = { it.message.chat.id in chatConfig.allSourceChatIds }) {
|
||||||
|
edit(
|
||||||
|
it.message,
|
||||||
|
ratingsRepo.buildRootButtons()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
koin.getOrNull<InlineTemplatesRepo>() ?.apply {
|
koin.getOrNull<InlineTemplatesRepo>() ?.apply {
|
||||||
addTemplate(
|
addTemplate(
|
||||||
@@ -273,6 +333,7 @@ object Plugin : Plugin {
|
|||||||
attachPoll(postId)
|
attachPoll(postId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
answer(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import dev.inmo.micro_utils.repos.exposed.initTable
|
|||||||
import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo
|
import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo
|
||||||
import dev.inmo.plaguposter.common.ShortMessageInfo
|
import dev.inmo.plaguposter.common.ShortMessageInfo
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import dev.inmo.tgbotapi.types.PollIdentifier
|
import dev.inmo.tgbotapi.types.PollIdentifier
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNull
|
||||||
|
import org.jetbrains.exposed.sql.statements.*
|
||||||
|
|
||||||
class ExposedPollsToMessagesInfoRepo(
|
class ExposedPollsToMessagesInfoRepo(
|
||||||
database: Database
|
database: Database
|
||||||
@@ -17,10 +19,11 @@ class ExposedPollsToMessagesInfoRepo(
|
|||||||
) {
|
) {
|
||||||
override val keyColumn = text("poll_id")
|
override val keyColumn = text("poll_id")
|
||||||
private val chatIdColumn = long("chat_id")
|
private val chatIdColumn = long("chat_id")
|
||||||
|
private val threadIdColumn = long("thread_id").nullable().default(null)
|
||||||
private val messageIdColumn = long("message_id")
|
private val messageIdColumn = long("message_id")
|
||||||
override val selectById: SqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) }
|
override val selectById: ISqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) }
|
||||||
override val selectByValue: SqlExpressionBuilder.(ShortMessageInfo) -> Op<Boolean> = {
|
override val selectByValue: ISqlExpressionBuilder.(ShortMessageInfo) -> Op<Boolean> = {
|
||||||
chatIdColumn.eq(it.chatId.chatId).and(
|
chatIdColumn.eq(it.chatId.chatId).and(it.chatId.threadId ?.let { threadIdColumn.eq(it) } ?: threadIdColumn.isNull()).and(
|
||||||
messageIdColumn.eq(it.messageId)
|
messageIdColumn.eq(it.messageId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -28,7 +31,7 @@ class ExposedPollsToMessagesInfoRepo(
|
|||||||
get() = get(keyColumn)
|
get() = get(keyColumn)
|
||||||
override val ResultRow.asObject: ShortMessageInfo
|
override val ResultRow.asObject: ShortMessageInfo
|
||||||
get() = ShortMessageInfo(
|
get() = ShortMessageInfo(
|
||||||
get(chatIdColumn).let(::ChatId),
|
IdChatIdentifier(get(chatIdColumn), get(threadIdColumn)),
|
||||||
get(messageIdColumn)
|
get(messageIdColumn)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,14 +39,13 @@ class ExposedPollsToMessagesInfoRepo(
|
|||||||
initTable()
|
initTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(k: PollIdentifier, v: ShortMessageInfo, it: UpdateStatement) {
|
override fun update(k: PollIdentifier, v: ShortMessageInfo, it: UpdateBuilder<Int>) {
|
||||||
it[chatIdColumn] = v.chatId.chatId
|
it[chatIdColumn] = v.chatId.chatId
|
||||||
|
it[threadIdColumn] = v.chatId.threadId
|
||||||
it[messageIdColumn] = v.messageId
|
it[messageIdColumn] = v.messageId
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun insert(k: PollIdentifier, v: ShortMessageInfo, it: InsertStatement<Number>) {
|
override fun insertKey(k: PollIdentifier, v: ShortMessageInfo, it: InsertStatement<Number>) {
|
||||||
it[keyColumn] = k
|
it[keyColumn] = k
|
||||||
it[chatIdColumn] = v.chatId.chatId
|
|
||||||
it[messageIdColumn] = v.messageId
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,15 @@ import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo
|
|||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
import dev.inmo.tgbotapi.types.PollIdentifier
|
import dev.inmo.tgbotapi.types.PollIdentifier
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.statements.*
|
||||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
|
||||||
|
|
||||||
class ExposedPollsToPostsIdsRepo(
|
class ExposedPollsToPostsIdsRepo(
|
||||||
database: Database
|
database: Database
|
||||||
) : PollsToPostsIdsRepo, AbstractExposedKeyValueRepo<PollIdentifier, PostId>(database, "polls_to_posts") {
|
) : PollsToPostsIdsRepo, AbstractExposedKeyValueRepo<PollIdentifier, PostId>(database, "polls_to_posts") {
|
||||||
override val keyColumn = text("poll_id")
|
override val keyColumn = text("poll_id")
|
||||||
val postIdColumn = text("postId")
|
val postIdColumn = text("postId")
|
||||||
override val selectById: SqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) }
|
override val selectById: ISqlExpressionBuilder.(PollIdentifier) -> Op<Boolean> = { keyColumn.eq(it) }
|
||||||
override val selectByValue: SqlExpressionBuilder.(PostId) -> Op<Boolean> = { postIdColumn.eq(it.string) }
|
override val selectByValue: ISqlExpressionBuilder.(PostId) -> Op<Boolean> = { postIdColumn.eq(it.string) }
|
||||||
override val ResultRow.asKey: PollIdentifier
|
override val ResultRow.asKey: PollIdentifier
|
||||||
get() = get(keyColumn)
|
get() = get(keyColumn)
|
||||||
override val ResultRow.asObject: PostId
|
override val ResultRow.asObject: PostId
|
||||||
@@ -24,12 +23,11 @@ class ExposedPollsToPostsIdsRepo(
|
|||||||
initTable()
|
initTable()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun update(k: PollIdentifier, v: PostId, it: UpdateStatement) {
|
override fun update(k: PollIdentifier, v: PostId, it: UpdateBuilder<Int>) {
|
||||||
it[postIdColumn] = v.string
|
it[postIdColumn] = v.string
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun insert(k: PollIdentifier, v: PostId, it: InsertStatement<Number>) {
|
override fun insertKey(k: PollIdentifier, v: PostId, it: InsertStatement<Number>) {
|
||||||
it[keyColumn] = k
|
it[keyColumn] = k
|
||||||
it[postIdColumn] = v.string
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
ratings/src/commonMain/kotlin/repo/CachedRatingsRepo.kt
Normal file
61
ratings/src/commonMain/kotlin/repo/CachedRatingsRepo.kt
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.repo
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
|
import dev.inmo.micro_utils.repos.cache.full.FullKeyValueCacheRepo
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
import dev.inmo.plaguposter.ratings.models.Rating
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
class CachedRatingsRepo(
|
||||||
|
private val base: RatingsRepo,
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
private val kvCache: FullKVCache<PostId, Rating> = FullKVCache()
|
||||||
|
) : RatingsRepo, KeyValueRepo<PostId, Rating> by FullKeyValueCacheRepo(base, kvCache, scope) {
|
||||||
|
override suspend fun getPosts(
|
||||||
|
range: ClosedRange<Rating>,
|
||||||
|
reversed: Boolean,
|
||||||
|
count: Int?,
|
||||||
|
exclude: List<PostId>
|
||||||
|
): Map<PostId, Rating> {
|
||||||
|
val result = mutableMapOf<PostId, Rating>()
|
||||||
|
|
||||||
|
doForAllWithNextPaging {
|
||||||
|
kvCache.keys(it).also {
|
||||||
|
it.results.forEach {
|
||||||
|
val rating = get(it) ?: return@forEach
|
||||||
|
if (it !in exclude && rating in range) {
|
||||||
|
result[it] = rating
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPostsWithRatingGreaterEq(
|
||||||
|
then: Rating,
|
||||||
|
reversed: Boolean,
|
||||||
|
count: Int?,
|
||||||
|
exclude: List<PostId>
|
||||||
|
): Map<PostId, Rating> = getPosts(
|
||||||
|
then .. Rating(Double.MAX_VALUE),
|
||||||
|
reversed,
|
||||||
|
count,
|
||||||
|
exclude
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun getPostsWithRatingLessEq(
|
||||||
|
then: Rating,
|
||||||
|
reversed: Boolean,
|
||||||
|
count: Int?,
|
||||||
|
exclude: List<PostId>
|
||||||
|
): Map<PostId, Rating> = getPosts(
|
||||||
|
Rating(Double.MIN_VALUE) .. then,
|
||||||
|
reversed,
|
||||||
|
count,
|
||||||
|
exclude
|
||||||
|
)
|
||||||
|
}
|
||||||
15
ratings/src/commonMain/kotlin/utils/PostsByRatings.kt
Normal file
15
ratings/src/commonMain/kotlin/utils/PostsByRatings.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package dev.inmo.plaguposter.ratings.utils
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.getAll
|
||||||
|
import dev.inmo.micro_utils.repos.pagination.getAll
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
import dev.inmo.plaguposter.ratings.models.Rating
|
||||||
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
||||||
|
|
||||||
|
suspend fun RatingsRepo.postsByRatings(): Map<Rating, List<PostId>> {
|
||||||
|
return getAll { keys(it) }.groupBy {
|
||||||
|
it.second
|
||||||
|
}.map {
|
||||||
|
it.key to it.value.map { it.first }
|
||||||
|
}.toMap()
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
package dev.inmo.plaguposter.ratings
|
package dev.inmo.plaguposter.ratings
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.koin.singleWithBinds
|
||||||
import dev.inmo.micro_utils.repos.unset
|
import dev.inmo.micro_utils.repos.unset
|
||||||
import dev.inmo.plagubot.Plugin
|
import dev.inmo.plagubot.Plugin
|
||||||
|
import dev.inmo.plaguposter.common.useCache
|
||||||
import dev.inmo.plaguposter.posts.exposed.ExposedPostsRepo
|
import dev.inmo.plaguposter.posts.exposed.ExposedPostsRepo
|
||||||
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||||
import dev.inmo.plaguposter.ratings.exposed.ExposedRatingsRepo
|
import dev.inmo.plaguposter.ratings.exposed.ExposedRatingsRepo
|
||||||
@@ -16,11 +18,16 @@ import org.koin.dsl.binds
|
|||||||
|
|
||||||
object Plugin : Plugin {
|
object Plugin : Plugin {
|
||||||
override fun Module.setupDI(database: Database, params: JsonObject) {
|
override fun Module.setupDI(database: Database, params: JsonObject) {
|
||||||
single { ExposedRatingsRepo(database) } binds arrayOf(
|
single { ExposedRatingsRepo(database) }
|
||||||
RatingsRepo::class,
|
singleWithBinds<RatingsRepo> {
|
||||||
ReadRatingsRepo::class,
|
val base = get<ExposedRatingsRepo>()
|
||||||
WriteRatingsRepo::class,
|
|
||||||
)
|
if (useCache) {
|
||||||
|
CachedRatingsRepo(base, get())
|
||||||
|
} else {
|
||||||
|
base
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package dev.inmo.plaguposter.ratings.exposed
|
package dev.inmo.plaguposter.ratings.exposed
|
||||||
|
|
||||||
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
import dev.inmo.micro_utils.pagination.utils.optionallyReverse
|
||||||
|
import dev.inmo.micro_utils.repos.exposed.initTable
|
||||||
import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo
|
import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo
|
||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
import dev.inmo.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 org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.statements.*
|
||||||
import org.jetbrains.exposed.sql.statements.UpdateStatement
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
class ExposedRatingsRepo (
|
class ExposedRatingsRepo (
|
||||||
@@ -18,20 +18,23 @@ class ExposedRatingsRepo (
|
|||||||
) {
|
) {
|
||||||
override val keyColumn = text("post_id")
|
override val keyColumn = text("post_id")
|
||||||
val ratingsColumn = double("rating")
|
val ratingsColumn = double("rating")
|
||||||
override val selectById: SqlExpressionBuilder.(PostId) -> Op<Boolean> = { keyColumn.eq(it.string) }
|
override val selectById: ISqlExpressionBuilder.(PostId) -> Op<Boolean> = { keyColumn.eq(it.string) }
|
||||||
override val selectByValue: SqlExpressionBuilder.(Rating) -> Op<Boolean> = { ratingsColumn.eq(it.double) }
|
override val selectByValue: ISqlExpressionBuilder.(Rating) -> Op<Boolean> = { ratingsColumn.eq(it.double) }
|
||||||
override val ResultRow.asKey: PostId
|
override val ResultRow.asKey: PostId
|
||||||
get() = get(keyColumn).let(::PostId)
|
get() = get(keyColumn).let(::PostId)
|
||||||
override val ResultRow.asObject: Rating
|
override val ResultRow.asObject: Rating
|
||||||
get() = get(ratingsColumn).let(::Rating)
|
get() = get(ratingsColumn).let(::Rating)
|
||||||
|
|
||||||
override fun update(k: PostId, v: Rating, it: UpdateStatement) {
|
init {
|
||||||
|
initTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update(k: PostId, v: Rating, it: UpdateBuilder<Int>) {
|
||||||
it[ratingsColumn] = v.double
|
it[ratingsColumn] = v.double
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun insert(k: PostId, v: Rating, it: InsertStatement<Number>) {
|
override fun insertKey(k: PostId, v: Rating, it: InsertStatement<Number>) {
|
||||||
it[keyColumn] = k.string
|
it[keyColumn] = k.string
|
||||||
it[ratingsColumn] = v.double
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Query.optionallyLimit(limit: Int?) = if (limit == null) {
|
private fun Query.optionallyLimit(limit: Int?) = if (limit == null) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM adoptopenjdk/openjdk11
|
FROM bellsoft/liberica-openjdk-alpine:19
|
||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,14 @@ dependencies {
|
|||||||
api project(":plaguposter.posts_registrar")
|
api project(":plaguposter.posts_registrar")
|
||||||
api project(":plaguposter.triggers.command")
|
api project(":plaguposter.triggers.command")
|
||||||
api project(":plaguposter.triggers.selector_with_timer")
|
api project(":plaguposter.triggers.selector_with_timer")
|
||||||
|
api project(":plaguposter.triggers.timer")
|
||||||
|
api project(":plaguposter.triggers.timer.disablers.autoposts")
|
||||||
|
api project(":plaguposter.triggers.timer.disablers.ratings")
|
||||||
api project(":plaguposter.ratings")
|
api project(":plaguposter.ratings")
|
||||||
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
|
||||||
@@ -29,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,80 +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.triggers.selector_with_timer.Plugin",
|
|
||||||
"dev.inmo.plaguposter.ratings.gc.Plugin",
|
|
||||||
"dev.inmo.plaguposter.inlines.Plugin",
|
|
||||||
"dev.inmo.plaguposter.triggers.command.Plugin"
|
|
||||||
],
|
|
||||||
"posts": {
|
|
||||||
"targetChat": 12345678,
|
|
||||||
"cacheChat": 12345678,
|
|
||||||
"sourceChat": 12345678
|
|
||||||
},
|
|
||||||
"ratingsPolls": {
|
|
||||||
"variants": {
|
|
||||||
"Ok": 1,
|
|
||||||
"Not ok": -1
|
|
||||||
},
|
|
||||||
"autoAttach": true,
|
|
||||||
"ratingOfferText": "What do you think about it?"
|
|
||||||
},
|
|
||||||
"selector": {
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"time": {
|
|
||||||
"from": "00:00",
|
|
||||||
"to": "00:00"
|
|
||||||
},
|
|
||||||
"rating": {
|
|
||||||
"min": 0.0,
|
|
||||||
"max": 1.0,
|
|
||||||
"prefer": "random"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"time": {
|
|
||||||
"from": "00:00",
|
|
||||||
"to": "00:00"
|
|
||||||
},
|
|
||||||
"rating": {
|
|
||||||
"min": 0.0,
|
|
||||||
"max": 1.0,
|
|
||||||
"prefer": "min"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"time": {
|
|
||||||
"from": "00:00",
|
|
||||||
"to": "00:00"
|
|
||||||
},
|
|
||||||
"rating": {
|
|
||||||
"min": 0.0,
|
|
||||||
"max": 1.0,
|
|
||||||
"prefer": "max"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"timer_trigger": {
|
|
||||||
"krontab": "0 30 2/4 * *"
|
|
||||||
},
|
|
||||||
"gc": {
|
|
||||||
"autoclear": {
|
|
||||||
"rating": -2,
|
|
||||||
"autoClearKrontab": "0 0 0 * *",
|
|
||||||
"skipPostAge": 86400
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,8 +14,8 @@ function assert_success() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app=plaguposter
|
app=plaguposter
|
||||||
version="`grep ../gradle.properties -e "^version=" | grep -e "[0-9.]*" -o`"
|
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" .
|
||||||
|
|||||||
25
runner/nonsudo_deploy.sh
Executable file
25
runner/nonsudo_deploy.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function send_notification() {
|
||||||
|
echo "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert_success() {
|
||||||
|
"${@}"
|
||||||
|
local status=${?}
|
||||||
|
if [ ${status} -ne 0 ]; then
|
||||||
|
send_notification "### Error ${status} at: ${BASH_LINENO[*]} ###"
|
||||||
|
exit ${status}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
app=plaguposter
|
||||||
|
version="`grep ../gradle.properties -e "^version=" | sed -e "s/version=\(.*\)/\1/"`"
|
||||||
|
server=insanusmokrassar
|
||||||
|
|
||||||
|
assert_success ../gradlew build
|
||||||
|
assert_success docker build -t $app:"$version" .
|
||||||
|
assert_success docker tag $app:"$version" $server/$app:$version
|
||||||
|
assert_success docker tag $app:"$version" $server/$app:latest
|
||||||
|
assert_success docker push $server/$app:$version
|
||||||
|
assert_success docker push $server/$app:latest
|
||||||
@@ -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.inlines.Plugin",
|
"dev.inmo.plaguposter.triggers.selector_with_timer.Plugin",
|
||||||
"dev.inmo.plaguposter.triggers.command.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",
|
||||||
|
"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",
|
||||||
@@ -11,6 +12,9 @@ String[] includes = [
|
|||||||
":ratings:gc",
|
":ratings:gc",
|
||||||
":triggers:command",
|
":triggers:command",
|
||||||
":triggers:selector_with_timer",
|
":triggers:selector_with_timer",
|
||||||
|
":triggers:timer",
|
||||||
|
":triggers:timer:disablers:ratings",
|
||||||
|
":triggers:timer:disablers:autoposts",
|
||||||
":inlines",
|
":inlines",
|
||||||
// ":settings",
|
// ":settings",
|
||||||
":runner"
|
":runner"
|
||||||
@@ -26,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")
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -13,9 +12,5 @@ kotlin {
|
|||||||
api project(":plaguposter.common")
|
api project(":plaguposter.common")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -16,9 +15,5 @@ kotlin {
|
|||||||
api project(":plaguposter.posts.panel")
|
api project(":plaguposter.posts.panel")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,22 @@
|
|||||||
package dev.inmo.plaguposter.triggers.command
|
package dev.inmo.plaguposter.triggers.command
|
||||||
|
|
||||||
import com.benasher44.uuid.uuid4
|
import com.benasher44.uuid.uuid4
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
|
||||||
import dev.inmo.micro_utils.fsm.common.State
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
|
|
||||||
import dev.inmo.plagubot.Plugin
|
import dev.inmo.plagubot.Plugin
|
||||||
import dev.inmo.plaguposter.common.SuccessfulSymbol
|
import dev.inmo.plaguposter.common.SuccessfulSymbol
|
||||||
import dev.inmo.plaguposter.common.UnsuccessfulSymbol
|
import dev.inmo.plagubot.plugins.inline.queries.models.Format
|
||||||
import dev.inmo.plaguposter.inlines.models.Format
|
import dev.inmo.plagubot.plugins.inline.queries.models.OfferTemplate
|
||||||
import dev.inmo.plaguposter.inlines.models.OfferTemplate
|
import dev.inmo.plagubot.plugins.inline.queries.repos.InlineTemplatesRepo
|
||||||
import dev.inmo.plaguposter.inlines.repos.InlineTemplatesRepo
|
|
||||||
import dev.inmo.plaguposter.posts.models.PostId
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
import dev.inmo.plaguposter.posts.panel.PanelButtonBuilder
|
import dev.inmo.plaguposter.posts.panel.PanelButtonBuilder
|
||||||
import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI
|
import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI
|
||||||
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||||
import dev.inmo.plaguposter.posts.sending.PostPublisher
|
import dev.inmo.plaguposter.posts.sending.PostPublisher
|
||||||
import dev.inmo.plaguposter.ratings.selector.Selector
|
import dev.inmo.plaguposter.ratings.selector.Selector
|
||||||
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
|
||||||
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextWithFSM
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextWithFSM
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitTextMessage
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.strictlyOn
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
|
||||||
import dev.inmo.tgbotapi.extensions.utils.*
|
import dev.inmo.tgbotapi.extensions.utils.*
|
||||||
@@ -33,9 +26,7 @@ import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
|
|||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
import dev.inmo.tgbotapi.types.MessageIdentifier
|
import dev.inmo.tgbotapi.types.MessageIdentifier
|
||||||
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
|
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
|
||||||
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
|
|
||||||
import dev.inmo.tgbotapi.types.message.textsources.regular
|
import dev.inmo.tgbotapi.types.message.textsources.regular
|
||||||
import kotlinx.coroutines.flow.filter
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
@@ -78,8 +69,15 @@ object Plugin : Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val postId = messageInReply ?.let {
|
val postId = messageInReply ?.let {
|
||||||
postsRepo.getIdByChatAndMessage(messageInReply.chat.id, messageInReply.messageId)
|
postsRepo.getIdByChatAndMessage(messageInReply.chat.id, messageInReply.messageId) ?: let { _ ->
|
||||||
} ?: selector ?.take(1) ?.firstOrNull()
|
reply(
|
||||||
|
it,
|
||||||
|
"Unable to find any post related to the message in reply"
|
||||||
|
)
|
||||||
|
|
||||||
|
return@onCommand
|
||||||
|
}
|
||||||
|
} ?: selector ?.takeOneOrNull()
|
||||||
if (postId == null) {
|
if (postId == null) {
|
||||||
reply(
|
reply(
|
||||||
it,
|
it,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@@ -16,9 +16,5 @@ kotlin {
|
|||||||
api libs.krontab
|
api libs.krontab
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jvmMain {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package dev.inmo.plaguposter.triggers.selector_with_timer
|
||||||
|
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
|
||||||
|
fun interface AutopostFilter {
|
||||||
|
suspend fun check(postId: PostId, dateTime: DateTime): Boolean
|
||||||
|
}
|
||||||
@@ -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,22 @@ 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",
|
||||||
|
val retryOnPostFailureTimes: Int = 0
|
||||||
) {
|
) {
|
||||||
@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) }
|
||||||
@@ -34,9 +70,117 @@ object Plugin : Plugin {
|
|||||||
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
||||||
val publisher = koin.get<PostPublisher>()
|
val publisher = koin.get<PostPublisher>()
|
||||||
val selector = koin.get<Selector>()
|
val selector = koin.get<Selector>()
|
||||||
koin.get<Config>().krontab.asFlow().subscribeSafelyWithoutExceptions(this) {
|
val filters = koin.getAll<AutopostFilter>().distinct()
|
||||||
selector.take(now = it).forEach { postId ->
|
val chatConfig = koin.get<ChatConfig>()
|
||||||
publisher.publish(postId)
|
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 retryOnPostFailureTimes = koin.get<Config>().retryOnPostFailureTimes
|
||||||
|
val dateTimeFormat = koin.get<Config>().format
|
||||||
|
krontab.asFlowWithDelays().subscribeSafelyWithoutExceptions(this) { dateTime ->
|
||||||
|
var leftRetries = retryOnPostFailureTimes
|
||||||
|
do {
|
||||||
|
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 {
|
||||||
|
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.takeOneOrNull(now = dateTime, exclude = selected) ?.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 = { chatConfig.allSourceChatIds.any { chatId -> it.sameChat(chatId) } }) {
|
||||||
|
val keyboard = buildPage()
|
||||||
|
|
||||||
|
runCatchingSafely {
|
||||||
|
edit(it, replyMarkup = keyboard) {
|
||||||
|
+"Your schedule:"
|
||||||
|
}
|
||||||
|
}.onFailure { _ ->
|
||||||
|
send(it.chat, replyMarkup = keyboard) {
|
||||||
|
+"Your schedule:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageDataCallbackQuery(
|
||||||
|
Regex("^$pageCallbackDataQueryPrefix\\d+"),
|
||||||
|
initialFilter = { chatConfig.allSourceChatIds.any { sourceChatId -> it.message.sameChat(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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
triggers/timer/build.gradle
Normal file
18
triggers/timer/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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 project(":plaguposter.posts.panel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
triggers/timer/disablers/autoposts/build.gradle
Normal file
18
triggers/timer/disablers/autoposts/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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.triggers.timer")
|
||||||
|
api project(":plaguposter.triggers.selector_with_timer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package dev.inmo.plaguposter.triggers.timer.disablers.autoposts
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package dev.inmo.plaguposter.triggers.timer.disablers.autoposts
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.koin.singleWithRandomQualifier
|
||||||
|
import dev.inmo.micro_utils.koin.singleWithRandomQualifierAndBinds
|
||||||
|
import dev.inmo.micro_utils.pagination.FirstPagePagination
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import dev.inmo.plagubot.Plugin
|
||||||
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
||||||
|
import dev.inmo.plaguposter.triggers.selector_with_timer.AutopostFilter
|
||||||
|
import dev.inmo.plaguposter.triggers.timer.TimersRepo
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
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) {
|
||||||
|
singleWithRandomQualifier<AutopostFilter> {
|
||||||
|
val timersRepo = get<TimersRepo>()
|
||||||
|
AutopostFilter { _, dateTime ->
|
||||||
|
val result = timersRepo.keys(dateTime, FirstPagePagination(1))
|
||||||
|
result.results.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.plaguposter.triggers.timer.disablers.autoposts"/>
|
||||||
18
triggers/timer/disablers/ratings/build.gradle
Normal file
18
triggers/timer/disablers/ratings/build.gradle
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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.triggers.timer")
|
||||||
|
api project(":plaguposter.ratings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
package dev.inmo.plaguposter.triggers.timer.disablers.ratings
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package dev.inmo.plaguposter.triggers.timer.disablers.ratings
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.koin.singleWithRandomQualifier
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import dev.inmo.plagubot.Plugin
|
||||||
|
import dev.inmo.plaguposter.ratings.repo.RatingsRepo
|
||||||
|
import dev.inmo.plaguposter.triggers.timer.TimersRepo
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
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) {
|
||||||
|
singleWithRandomQualifier(createdAtStart = true) {
|
||||||
|
val timersRepo = get<TimersRepo>()
|
||||||
|
val ratingsRepo = get<RatingsRepo>()
|
||||||
|
val scope = get<CoroutineScope>()
|
||||||
|
|
||||||
|
timersRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
ratingsRepo.unset(it.first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<manifest package="dev.inmo.plaguposter.triggers.timer.disablers.ratings"/>
|
||||||
287
triggers/timer/src/commonMain/kotlin/ButtonsBuilder.kt
Normal file
287
triggers/timer/src/commonMain/kotlin/ButtonsBuilder.kt
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package dev.inmo.plaguposter.triggers.timer
|
||||||
|
|
||||||
|
import korlibs.time.DateFormat
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import korlibs.time.DateTimeTz
|
||||||
|
import korlibs.time.Month
|
||||||
|
import korlibs.time.Year
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
|
import dev.inmo.plaguposter.common.SuccessfulSymbol
|
||||||
|
import dev.inmo.plaguposter.common.UnsuccessfulSymbol
|
||||||
|
import dev.inmo.plaguposter.posts.models.PostId
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.delete
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
||||||
|
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
|
||||||
|
import dev.inmo.tgbotapi.utils.bold
|
||||||
|
import dev.inmo.tgbotapi.utils.buildEntities
|
||||||
|
import dev.inmo.tgbotapi.utils.row
|
||||||
|
|
||||||
|
object ButtonsBuilder {
|
||||||
|
private const val changeTimeData = "timer_time_hint"
|
||||||
|
private const val changeDateData = "timer_date_hint"
|
||||||
|
private const val changeHoursDataPrefix = "timer_h"
|
||||||
|
private const val changeMinutesDataPrefix = "timer_m"
|
||||||
|
private const val changeDayDataPrefix = "timer_d"
|
||||||
|
private const val changeMonthDataPrefix = "timer_M"
|
||||||
|
private const val changeYearDataPrefix = "timer_y"
|
||||||
|
private const val changeDateDataPrefix = "timer_s"
|
||||||
|
private const val cancelDateData = "timer_c"
|
||||||
|
private const val deleteDateDataPrefix = "timer_r"
|
||||||
|
val datePrintFormat = DateFormat("HH:mm, dd.MM.yyyy, zzz")
|
||||||
|
|
||||||
|
fun buildTimerButtons(
|
||||||
|
postId: PostId,
|
||||||
|
dateTime: DateTimeTz,
|
||||||
|
exists: Boolean
|
||||||
|
) = inlineKeyboard {
|
||||||
|
val unixMillis = dateTime.utc.unixMillisLong
|
||||||
|
row {
|
||||||
|
dataButton("Time (hh:mm):", changeTimeData)
|
||||||
|
dataButton(dateTime.hours.toString(), "$changeHoursDataPrefix $postId $unixMillis")
|
||||||
|
dataButton(dateTime.minutes.toString(), "$changeMinutesDataPrefix $postId $unixMillis")
|
||||||
|
}
|
||||||
|
row {
|
||||||
|
dataButton("Date (dd.mm.yyyy):", changeDateData)
|
||||||
|
dataButton("${dateTime.dayOfMonth}", "$changeDayDataPrefix $postId $unixMillis")
|
||||||
|
dataButton("${dateTime.month1}", "$changeMonthDataPrefix $postId $unixMillis")
|
||||||
|
dataButton("${dateTime.yearInt}", "$changeYearDataPrefix $postId $unixMillis")
|
||||||
|
}
|
||||||
|
|
||||||
|
row {
|
||||||
|
if (exists) {
|
||||||
|
dataButton("\uD83D\uDDD1", "$deleteDateDataPrefix $postId")
|
||||||
|
}
|
||||||
|
dataButton(UnsuccessfulSymbol, cancelDateData)
|
||||||
|
dataButton(SuccessfulSymbol, "$changeDateDataPrefix $postId $unixMillis")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildTimerTextSources(
|
||||||
|
currentDateTime: DateTime,
|
||||||
|
previousTime: DateTime?
|
||||||
|
) = buildEntities {
|
||||||
|
previousTime ?.let {
|
||||||
|
+ "Previous timer time: " + bold(it.local.toString(datePrintFormat)) + "\n"
|
||||||
|
}
|
||||||
|
+"Currently editing time: " + bold(currentDateTime.local.toString(datePrintFormat))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun BehaviourContext.includeKeyboardHandling(
|
||||||
|
timersRepo: TimersRepo,
|
||||||
|
onSavePublishingTime: suspend (PostId, DateTime) -> Boolean
|
||||||
|
) {
|
||||||
|
fun buildKeyboard(
|
||||||
|
prefix: String,
|
||||||
|
postId: PostId,
|
||||||
|
values: Iterable<Int>,
|
||||||
|
min: DateTime = nearestAvailableTimerTime(),
|
||||||
|
dateConverter: (Int) -> DateTimeTz
|
||||||
|
): InlineKeyboardMarkup {
|
||||||
|
return inlineKeyboard {
|
||||||
|
values.chunked(6).forEach {
|
||||||
|
row {
|
||||||
|
it.forEach {
|
||||||
|
dataButton(it.toString(), "$prefix $postId ${dateConverter(it).utc.unixMillisLong.coerceAtLeast(min.unixMillisLong)}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun buildStandardDataCallbackQuery(
|
||||||
|
name: String,
|
||||||
|
prefix: String,
|
||||||
|
possibleValues: (DateTimeTz) -> Iterable<Int>,
|
||||||
|
dateTimeConverter: (Int, DateTimeTz) -> DateTimeTz
|
||||||
|
) {
|
||||||
|
val setPrefix = "${prefix}s"
|
||||||
|
onMessageDataCallbackQuery(Regex("$prefix .+")) {
|
||||||
|
val (_, rawPostId, rawDateTimeMillis) = it.data.split(" ")
|
||||||
|
val currentMillis = rawDateTimeMillis.toLongOrNull() ?: return@onMessageDataCallbackQuery
|
||||||
|
val currentDateTime = DateTime(currentMillis)
|
||||||
|
val currentDateTimeLocal = DateTime(currentMillis).local
|
||||||
|
val postId = PostId(rawPostId)
|
||||||
|
val previousTime = timersRepo.get(postId)
|
||||||
|
|
||||||
|
edit (
|
||||||
|
it.message.withContentOrNull() ?: return@onMessageDataCallbackQuery,
|
||||||
|
replyMarkup = buildKeyboard(
|
||||||
|
setPrefix,
|
||||||
|
postId,
|
||||||
|
possibleValues(currentDateTimeLocal)
|
||||||
|
) {
|
||||||
|
dateTimeConverter(it, currentDateTimeLocal)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
+buildTimerTextSources(currentDateTime, previousTime) + "\n"
|
||||||
|
+"You are about to edit $name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageDataCallbackQuery(Regex("$setPrefix .+")) {
|
||||||
|
val (_, rawPostId, rawDateTimeMillis) = it.data.split(" ")
|
||||||
|
|
||||||
|
val currentMillis = rawDateTimeMillis.toLongOrNull() ?: return@onMessageDataCallbackQuery
|
||||||
|
val currentDateTime = DateTime(currentMillis)
|
||||||
|
val postId = PostId(rawPostId)
|
||||||
|
val previousTime = timersRepo.get(postId)
|
||||||
|
edit(
|
||||||
|
it.message.withContentOrNull() ?: return@onMessageDataCallbackQuery,
|
||||||
|
replyMarkup = buildTimerButtons(
|
||||||
|
postId,
|
||||||
|
currentDateTime.local,
|
||||||
|
timersRepo.contains(postId)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
+buildTimerTextSources(currentDateTime, previousTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun DateTimeTz.dateEq(other: DateTimeTz) = yearInt == other.yearInt && month0 == other.month0 && dayOfMonth == other.dayOfMonth
|
||||||
|
|
||||||
|
buildStandardDataCallbackQuery(
|
||||||
|
"hour",
|
||||||
|
changeHoursDataPrefix,
|
||||||
|
{
|
||||||
|
val now = nearestAvailableTimerTime().local
|
||||||
|
|
||||||
|
if (now.dateEq(it)) {
|
||||||
|
now.hours .. 23
|
||||||
|
} else {
|
||||||
|
0 .. 23
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { newValue, oldDateTime ->
|
||||||
|
DateTimeTz.local(
|
||||||
|
oldDateTime.local.copyDayOfMonth(hours = newValue),
|
||||||
|
oldDateTime.offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildStandardDataCallbackQuery(
|
||||||
|
"minute",
|
||||||
|
changeMinutesDataPrefix,
|
||||||
|
{
|
||||||
|
val now = nearestAvailableTimerTime().local
|
||||||
|
|
||||||
|
if (now.dateEq(it) && now.hours >= it.hours) {
|
||||||
|
now.minutes until 60
|
||||||
|
} else {
|
||||||
|
0 until 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { newValue, oldDateTime ->
|
||||||
|
DateTimeTz.local(
|
||||||
|
oldDateTime.local.copyDayOfMonth(minutes = newValue),
|
||||||
|
oldDateTime.offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildStandardDataCallbackQuery(
|
||||||
|
"day",
|
||||||
|
changeDayDataPrefix,
|
||||||
|
{
|
||||||
|
val now = nearestAvailableTimerTime().local
|
||||||
|
|
||||||
|
if (now.yearInt == it.yearInt && now.month0 == it.month0) {
|
||||||
|
now.dayOfMonth .. it.month.days(it.year)
|
||||||
|
} else {
|
||||||
|
1 .. it.month.days(it.year)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { newValue, oldDateTime ->
|
||||||
|
DateTimeTz.local(
|
||||||
|
oldDateTime.local.copyDayOfMonth(dayOfMonth = newValue),
|
||||||
|
oldDateTime.offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildStandardDataCallbackQuery(
|
||||||
|
"month",
|
||||||
|
changeMonthDataPrefix,
|
||||||
|
{
|
||||||
|
val now = nearestAvailableTimerTime().local
|
||||||
|
|
||||||
|
if (now.year == it.year) {
|
||||||
|
now.month1 .. 12
|
||||||
|
} else {
|
||||||
|
1 .. 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { newValue, oldDateTime ->
|
||||||
|
DateTimeTz.local(
|
||||||
|
oldDateTime.local.copyDayOfMonth(month = Month(newValue)),
|
||||||
|
oldDateTime.offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildStandardDataCallbackQuery(
|
||||||
|
"year",
|
||||||
|
changeYearDataPrefix,
|
||||||
|
{
|
||||||
|
val now = nearestAvailableTimerTime().local
|
||||||
|
(now.year.year .. (now.year.year + 5))
|
||||||
|
}
|
||||||
|
) { newValue, oldDateTime ->
|
||||||
|
DateTimeTz.local(
|
||||||
|
oldDateTime.local.copyDayOfMonth(year = Year(newValue)),
|
||||||
|
oldDateTime.offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageDataCallbackQuery(changeTimeData) {
|
||||||
|
answer(it, "Use the buttons to the right to set post publishing time (hh:mm)", showAlert = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageDataCallbackQuery(changeDateData) {
|
||||||
|
answer(it, "Use the buttons to the right to set post publishing date (dd.MM.yyyy)", showAlert = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageDataCallbackQuery(Regex("$changeDateDataPrefix .*")) {
|
||||||
|
val (_, rawPostId, rawDateTimeMillis) = it.data.split(" ")
|
||||||
|
val currentMillis = rawDateTimeMillis.toLongOrNull() ?: return@onMessageDataCallbackQuery
|
||||||
|
val currentDateTime = DateTime(currentMillis)
|
||||||
|
val postId = PostId(rawPostId)
|
||||||
|
|
||||||
|
val success = runCatchingSafely {
|
||||||
|
onSavePublishingTime(postId, currentDateTime)
|
||||||
|
}.getOrElse { false }
|
||||||
|
|
||||||
|
answer(
|
||||||
|
it,
|
||||||
|
if (success) "Successfully set timer" else "Unable to set timer"
|
||||||
|
)
|
||||||
|
|
||||||
|
it.message.delete(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageDataCallbackQuery(Regex("$deleteDateDataPrefix .*")) {
|
||||||
|
val (_, rawPostId) = it.data.split(" ")
|
||||||
|
val postId = PostId(rawPostId)
|
||||||
|
|
||||||
|
val success = runCatchingSafely {
|
||||||
|
timersRepo.unset(postId)
|
||||||
|
true
|
||||||
|
}.getOrElse { false }
|
||||||
|
|
||||||
|
answer(
|
||||||
|
it,
|
||||||
|
if (success) "Successfully unset timer" else "Unable to unset timer"
|
||||||
|
)
|
||||||
|
|
||||||
|
it.message.delete(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessageDataCallbackQuery(cancelDateData) {
|
||||||
|
delete(it.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.inmo.plaguposter.triggers.timer
|
||||||
|
|
||||||
|
import korlibs.time.DateTime
|
||||||
|
import korlibs.time.minutes
|
||||||
|
|
||||||
|
fun nearestAvailableTimerTime() = (DateTime.now() + 1.minutes).copyDayOfMonth(
|
||||||
|
milliseconds = 0,
|
||||||
|
seconds = 0
|
||||||
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user