mirror of
https://github.com/InsanusMokrassar/PlaguPoster.git
synced 2025-12-05 12:35:39 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -1,16 +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: 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 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- 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 }}
|
||||
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# PlaguPoster
|
||||
|
||||
## 0.0.9
|
||||
|
||||
* Update depedencies
|
||||
|
||||
@@ -23,4 +23,4 @@ allprojects {
|
||||
}
|
||||
|
||||
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,15 +1,27 @@
|
||||
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.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ChatConfig(
|
||||
@SerialName("targetChat")
|
||||
val targetChatId: ChatId,
|
||||
@Serializable(FullChatIdentifierSerializer::class)
|
||||
val targetChatId: IdChatIdentifier,
|
||||
@SerialName("sourceChat")
|
||||
val sourceChatId: ChatId,
|
||||
@Serializable(FullChatIdentifierSerializer::class)
|
||||
val sourceChatId: IdChatIdentifier,
|
||||
@SerialName("cacheChat")
|
||||
val cacheChatId: ChatId
|
||||
)
|
||||
@Serializable(FullChatIdentifierSerializer::class)
|
||||
val cacheChatId: IdChatIdentifier
|
||||
) {
|
||||
fun check(chatId: IdChatIdentifier) = when (chatId) {
|
||||
targetChatId,
|
||||
sourceChatId,
|
||||
cacheChatId -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,20 @@ 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 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()) }
|
||||
}
|
||||
|
||||
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
||||
val config = koin.get<ChatConfig>()
|
||||
|
||||
|
||||
@@ -20,6 +20,6 @@ allprojects {
|
||||
|
||||
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
|
||||
|
||||
group=dev.inmo
|
||||
version=0.0.6
|
||||
android_code_version=6
|
||||
version=0.0.9
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
[versions]
|
||||
|
||||
kotlin = "1.7.21"
|
||||
kotlin = "1.7.22"
|
||||
kotlin-serialization = "1.4.1"
|
||||
|
||||
plagubot = "3.1.3"
|
||||
tgbotapi = "4.1.2"
|
||||
microutils = "0.14.2"
|
||||
kslog = "0.5.3"
|
||||
krontab = "0.8.3"
|
||||
tgbotapi-libraries = "0.6.3"
|
||||
plagubot-plugins = "0.6.1"
|
||||
plagubot = "3.3.0"
|
||||
tgbotapi = "5.0.0"
|
||||
microutils = "0.16.4"
|
||||
kslog = "0.5.4"
|
||||
krontab = "0.8.5"
|
||||
tgbotapi-libraries = "0.7.0"
|
||||
plagubot-plugins = "0.7.0"
|
||||
|
||||
dokka = "1.7.20"
|
||||
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
project.version = "$version"
|
||||
project.group = "$group"
|
||||
|
||||
// apply from: "$publishGradlePath"
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
project.version = "$version"
|
||||
project.group = "$group"
|
||||
|
||||
// apply from: "$publishGradlePath"
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
js (IR) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
project.version = "$version"
|
||||
project.group = "$group"
|
||||
|
||||
// apply from: "$publishGradlePath"
|
||||
apply from: "$publishGradlePath"
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
@@ -7,4 +7,6 @@ import kotlin.jvm.JvmInline
|
||||
@JvmInline
|
||||
value class PostId(
|
||||
val string: String
|
||||
)
|
||||
) {
|
||||
override fun toString(): String = string
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@ import dev.inmo.tgbotapi.types.MessageIdentifier
|
||||
interface ReadPostsRepo : ReadCRUDRepo<RegisteredPost, PostId> {
|
||||
suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId?
|
||||
suspend fun getPostCreationTime(postId: PostId): DateTime?
|
||||
suspend fun getFirstMessageInfo(postId: PostId): PostContentInfo?
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ import dev.inmo.tgbotapi.types.message.content.MediaGroupPartContent
|
||||
class PostPublisher(
|
||||
private val bot: TelegramBot,
|
||||
private val postsRepo: PostsRepo,
|
||||
private val cachingChatId: ChatId,
|
||||
private val targetChatId: ChatId,
|
||||
private val cachingChatId: IdChatIdentifier,
|
||||
private val targetChatId: IdChatIdentifier,
|
||||
private val deleteAfterPosting: Boolean = true
|
||||
) {
|
||||
suspend fun publish(postId: PostId) {
|
||||
|
||||
@@ -38,9 +38,11 @@ class ExposedPostsRepo(
|
||||
|
||||
override val selectById: ISqlExpressionBuilder.(PostId) -> Op<Boolean> = { idColumn.eq(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
|
||||
get() {
|
||||
val id = PostId(get(idColumn))
|
||||
val id = asId
|
||||
return RegisteredPost(
|
||||
id,
|
||||
DateTime(get(createdColumn)),
|
||||
@@ -163,4 +165,10 @@ class ExposedPostsRepo(
|
||||
override suspend fun getPostCreationTime(postId: PostId): DateTime? = transaction(database) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
82
publish.gradle
Normal file
82
publish.gradle
Normal file
@@ -0,0 +1,82 @@
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
task javadocsJar(type: Jar) {
|
||||
classifier = '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)
|
||||
}
|
||||
}
|
||||
}
|
||||
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"}}}
|
||||
160
ratings/source/src/commonMain/kotlin/buttons/RootButtons.kt
Normal file
160
ratings/source/src/commonMain/kotlin/buttons/RootButtons.kt
Normal file
@@ -0,0 +1,160 @@
|
||||
package dev.inmo.plaguposter.ratings.source.buttons
|
||||
|
||||
import com.soywiz.klock.DateFormat
|
||||
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)
|
||||
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).map {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI
|
||||
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||
import dev.inmo.plaguposter.ratings.models.Rating
|
||||
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.repos.*
|
||||
import dev.inmo.plaguposter.ratings.utils.postsByRatings
|
||||
@@ -34,6 +36,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
||||
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.extensions.utils.types.buttons.inlineKeyboard
|
||||
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
|
||||
import dev.inmo.tgbotapi.types.message.textsources.bold
|
||||
import dev.inmo.tgbotapi.types.message.textsources.regular
|
||||
@@ -98,6 +101,7 @@ object Plugin : Plugin {
|
||||
}
|
||||
|
||||
val post = postsRepo.getById(postId) ?: return false
|
||||
ratingsRepo.set(postId, Rating(0.0))
|
||||
for (content in post.content) {
|
||||
runCatchingSafely {
|
||||
val sent = send(
|
||||
@@ -137,7 +141,7 @@ object Plugin : Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
postsRepo.deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(this) { postId ->
|
||||
ratingsRepo.onValueRemoved.subscribeSafelyWithoutExceptions(this) { postId ->
|
||||
detachPoll(postId)
|
||||
}
|
||||
|
||||
@@ -225,13 +229,23 @@ object Plugin : Plugin {
|
||||
+ "• " + 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)
|
||||
edit(it, textSources, replyMarkup = keyboard)
|
||||
}.onFailure { _ ->
|
||||
reply(it, textSources)
|
||||
reply(it, textSources, replyMarkup = keyboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
includeRootNavigationButtonsHandler(setOf(chatConfig.sourceChatId), ratingsRepo, postsRepo)
|
||||
onMessageDataCallbackQuery("ratings_interactive", initialFilter = { it.message.chat.id == chatConfig.sourceChatId }) {
|
||||
edit(
|
||||
it.message,
|
||||
ratingsRepo.buildRootButtons()
|
||||
)
|
||||
}
|
||||
|
||||
koin.getOrNull<InlineTemplatesRepo>() ?.apply {
|
||||
addTemplate(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.plaguposter.ratings.exposed
|
||||
|
||||
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.plaguposter.posts.models.PostId
|
||||
import dev.inmo.plaguposter.ratings.models.Rating
|
||||
@@ -24,6 +25,10 @@ class ExposedRatingsRepo (
|
||||
override val ResultRow.asObject: Rating
|
||||
get() = get(ratingsColumn).let(::Rating)
|
||||
|
||||
init {
|
||||
initTable()
|
||||
}
|
||||
|
||||
override fun update(k: PostId, v: Rating, it: UpdateBuilder<Int>) {
|
||||
it[ratingsColumn] = v.double
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ dependencies {
|
||||
api project(":plaguposter.posts_registrar")
|
||||
api project(":plaguposter.triggers.command")
|
||||
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.source")
|
||||
api project(":plaguposter.ratings.selector")
|
||||
|
||||
@@ -7,15 +7,19 @@
|
||||
},
|
||||
"botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678",
|
||||
"plugins": [
|
||||
"dev.inmo.plagubot.plugins.inline.queries.Plugin",
|
||||
"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.plagubot.plugins.inline.queries.Plugin",
|
||||
"dev.inmo.plaguposter.triggers.command.Plugin",
|
||||
"dev.inmo.plaguposter.posts.panel.Plugin"
|
||||
"dev.inmo.plaguposter.posts.panel.Plugin",
|
||||
"dev.inmo.plaguposter.common.CommonPlugin",
|
||||
"dev.inmo.plaguposter.triggers.timer.Plugin",
|
||||
"dev.inmo.plaguposter.triggers.timer.disablers.ratings.Plugin",
|
||||
"dev.inmo.plaguposter.triggers.timer.disablers.autoposts.Plugin"
|
||||
],
|
||||
"posts": {
|
||||
"chats": {
|
||||
|
||||
@@ -12,6 +12,9 @@ String[] includes = [
|
||||
":triggers:command",
|
||||
":triggers:selector_with_timer",
|
||||
":triggers:selector_with_scheduling",
|
||||
":triggers:timer",
|
||||
":triggers:timer:disablers:ratings",
|
||||
":triggers:timer:disablers:autoposts",
|
||||
":inlines",
|
||||
// ":settings",
|
||||
":runner"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package dev.inmo.plaguposter.triggers.selector_with_timer
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.plaguposter.posts.models.PostId
|
||||
|
||||
fun interface AutopostFilter {
|
||||
suspend fun check(postId: PostId, dateTime: DateTime): Boolean
|
||||
}
|
||||
@@ -34,9 +34,12 @@ object Plugin : Plugin {
|
||||
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
||||
val publisher = koin.get<PostPublisher>()
|
||||
val selector = koin.get<Selector>()
|
||||
koin.get<Config>().krontab.asFlow().subscribeSafelyWithoutExceptions(this) {
|
||||
selector.take(now = it).forEach { postId ->
|
||||
publisher.publish(postId)
|
||||
val filters = koin.getAll<AutopostFilter>().distinct()
|
||||
koin.get<Config>().krontab.asFlow().subscribeSafelyWithoutExceptions(this) { dateTime ->
|
||||
selector.take(now = dateTime).forEach { postId ->
|
||||
if (filters.all { it.check(postId, dateTime) }) {
|
||||
publisher.publish(postId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 com.soywiz.klock.DateFormat
|
||||
import com.soywiz.klock.DateTime
|
||||
import com.soywiz.klock.DateTimeTz
|
||||
import com.soywiz.klock.Month
|
||||
import com.soywiz.klock.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 com.soywiz.klock.DateTime
|
||||
import com.soywiz.klock.minutes
|
||||
|
||||
fun nearestAvailableTimerTime() = (DateTime.now() + 1.minutes).copyDayOfMonth(
|
||||
milliseconds = 0,
|
||||
seconds = 0
|
||||
)
|
||||
1
triggers/timer/src/commonMain/kotlin/PackageInfo.kt
Normal file
1
triggers/timer/src/commonMain/kotlin/PackageInfo.kt
Normal file
@@ -0,0 +1 @@
|
||||
package dev.inmo.plaguposter.triggers.timer
|
||||
28
triggers/timer/src/commonMain/kotlin/TimerPanelButton.kt
Normal file
28
triggers/timer/src/commonMain/kotlin/TimerPanelButton.kt
Normal file
@@ -0,0 +1,28 @@
|
||||
package dev.inmo.plaguposter.triggers.timer
|
||||
|
||||
import dev.inmo.plaguposter.common.SuccessfulSymbol
|
||||
import dev.inmo.plaguposter.common.UnsuccessfulSymbol
|
||||
import dev.inmo.plaguposter.posts.models.RegisteredPost
|
||||
import dev.inmo.plaguposter.posts.panel.PanelButtonBuilder
|
||||
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
|
||||
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.InlineKeyboardButton
|
||||
|
||||
class TimerPanelButton(
|
||||
private val timersRepo: TimersRepo
|
||||
) : PanelButtonBuilder {
|
||||
override val weight: Int
|
||||
get() = 0
|
||||
|
||||
override suspend fun buildButton(post: RegisteredPost): InlineKeyboardButton? {
|
||||
val publishingTime = timersRepo.get(post.id)
|
||||
|
||||
return CallbackDataInlineKeyboardButton(
|
||||
"⏰ ${ if (publishingTime == null) UnsuccessfulSymbol else SuccessfulSymbol }",
|
||||
"$timerSetPrefix ${post.id}"
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val timerSetPrefix = "timer_set_init"
|
||||
}
|
||||
}
|
||||
57
triggers/timer/src/commonMain/kotlin/TimersHandler.kt
Normal file
57
triggers/timer/src/commonMain/kotlin/TimersHandler.kt
Normal file
@@ -0,0 +1,57 @@
|
||||
package dev.inmo.plaguposter.triggers.timer
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.coroutines.plus
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.repos.unset
|
||||
import dev.inmo.plaguposter.posts.models.PostId
|
||||
import dev.inmo.plaguposter.posts.sending.PostPublisher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
class TimersHandler(
|
||||
private val timersRepo: TimersRepo,
|
||||
private val publisher: PostPublisher,
|
||||
private val scope: CoroutineScope
|
||||
) {
|
||||
private var currentPostAndJob: Pair<PostId, Job>? = null
|
||||
private val currentJobMutex = Mutex()
|
||||
|
||||
init {
|
||||
(flowOf(Unit) + timersRepo.onNewValue + timersRepo.onValueRemoved).subscribeSafelyWithoutExceptions(scope) {
|
||||
refreshPublishingJob()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun refreshPublishingJob() {
|
||||
val minimal = timersRepo.getMinimalDateTimePost()
|
||||
|
||||
currentJobMutex.withLock {
|
||||
if (minimal ?.first == currentPostAndJob ?.first) {
|
||||
return@withLock
|
||||
}
|
||||
|
||||
currentPostAndJob ?.second ?.cancel()
|
||||
|
||||
currentPostAndJob = minimal ?.let { (postId, dateTime) ->
|
||||
postId to scope.launchSafelyWithoutExceptions {
|
||||
val now = DateTime.now()
|
||||
val span = dateTime - now
|
||||
|
||||
delay(span.millisecondsLong)
|
||||
|
||||
publisher.publish(postId)
|
||||
|
||||
timersRepo.unset(postId)
|
||||
|
||||
refreshPublishingJob()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
triggers/timer/src/commonMain/kotlin/TimersRepo.kt
Normal file
9
triggers/timer/src/commonMain/kotlin/TimersRepo.kt
Normal file
@@ -0,0 +1,9 @@
|
||||
package dev.inmo.plaguposter.triggers.timer
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||
import dev.inmo.plaguposter.posts.models.PostId
|
||||
|
||||
interface TimersRepo : KeyValueRepo<PostId, DateTime> {
|
||||
suspend fun getMinimalDateTimePost(): Pair<PostId, DateTime>?
|
||||
}
|
||||
80
triggers/timer/src/jvmMain/kotlin/Plugin.kt
Normal file
80
triggers/timer/src/jvmMain/kotlin/Plugin.kt
Normal file
@@ -0,0 +1,80 @@
|
||||
package dev.inmo.plaguposter.triggers.timer
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.koin.singleWithRandomQualifierAndBinds
|
||||
import dev.inmo.micro_utils.repos.set
|
||||
import dev.inmo.plagubot.Plugin
|
||||
import dev.inmo.plaguposter.common.ChatConfig
|
||||
import dev.inmo.plaguposter.posts.models.PostId
|
||||
import dev.inmo.plaguposter.posts.panel.PanelButtonsAPI
|
||||
import dev.inmo.plaguposter.posts.repo.ReadPostsRepo
|
||||
import dev.inmo.plaguposter.triggers.timer.repo.ExposedTimersRepo
|
||||
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
||||
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.serialization.json.*
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.binds
|
||||
|
||||
object Plugin : Plugin {
|
||||
override fun Module.setupDI(database: Database, params: JsonObject) {
|
||||
single { ExposedTimersRepo(get(), get(), get()) } binds arrayOf(TimersRepo::class)
|
||||
single(createdAtStart = true) { TimersHandler(get(), get(), get()) }
|
||||
singleWithRandomQualifierAndBinds { TimerPanelButton(get()) }
|
||||
}
|
||||
|
||||
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
|
||||
val timersRepo = koin.get<TimersRepo>()
|
||||
val chatsConfig = koin.get<ChatConfig>()
|
||||
val panelApi = koin.get<PanelButtonsAPI>()
|
||||
val scope = koin.get<CoroutineScope>()
|
||||
with(ButtonsBuilder) {
|
||||
includeKeyboardHandling(timersRepo) { postId, dateTime ->
|
||||
timersRepo.set(postId, dateTime)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
timersRepo.onNewValue.subscribeSafelyWithoutExceptions(scope) {
|
||||
panelApi.forceRefresh(it.first)
|
||||
}
|
||||
|
||||
timersRepo.onValueRemoved.subscribeSafelyWithoutExceptions(scope) {
|
||||
panelApi.forceRefresh(it)
|
||||
}
|
||||
|
||||
onMessageDataCallbackQuery(
|
||||
Regex("${TimerPanelButton.timerSetPrefix} [^\\s]+"),
|
||||
initialFilter = {
|
||||
chatsConfig.check(it.message.chat.id)
|
||||
}
|
||||
) {
|
||||
val (_, postIdRaw) = it.data.split(" ")
|
||||
val postId = PostId(postIdRaw)
|
||||
val now = nearestAvailableTimerTime()
|
||||
val exists = timersRepo.get(postId)
|
||||
val textSources = ButtonsBuilder.buildTimerTextSources(now, exists)
|
||||
val buttons = ButtonsBuilder.buildTimerButtons(
|
||||
postId,
|
||||
now.local,
|
||||
exists != null
|
||||
)
|
||||
reply(
|
||||
it.message,
|
||||
textSources,
|
||||
replyMarkup = buttons
|
||||
)
|
||||
|
||||
answer(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
62
triggers/timer/src/jvmMain/kotlin/repo/ExposedTimersRepo.kt
Normal file
62
triggers/timer/src/jvmMain/kotlin/repo/ExposedTimersRepo.kt
Normal file
@@ -0,0 +1,62 @@
|
||||
package dev.inmo.plaguposter.triggers.timer.repo
|
||||
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.micro_utils.common.firstNotNull
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.pagination.paginate
|
||||
import dev.inmo.micro_utils.repos.exposed.initTable
|
||||
import dev.inmo.micro_utils.repos.exposed.keyvalue.AbstractExposedKeyValueRepo
|
||||
import dev.inmo.micro_utils.repos.unset
|
||||
import dev.inmo.plaguposter.posts.models.PostId
|
||||
import dev.inmo.plaguposter.posts.repo.PostsRepo
|
||||
import dev.inmo.plaguposter.triggers.timer.TimersRepo
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jetbrains.exposed.sql.Column
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.ISqlExpressionBuilder
|
||||
import org.jetbrains.exposed.sql.Op
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import org.jetbrains.exposed.sql.statements.UpdateBuilder
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
class ExposedTimersRepo(
|
||||
database: Database,
|
||||
postsRepo: PostsRepo,
|
||||
scope: CoroutineScope
|
||||
) : TimersRepo, AbstractExposedKeyValueRepo<PostId, DateTime>(
|
||||
database,
|
||||
"timers"
|
||||
) {
|
||||
override val keyColumn = text("post_id")
|
||||
private val dateTimeColumn = long("date_time")
|
||||
override val selectById: ISqlExpressionBuilder.(PostId) -> Op<Boolean> = { keyColumn.eq(it.string) }
|
||||
override val selectByValue: ISqlExpressionBuilder.(DateTime) -> Op<Boolean> = { dateTimeColumn.eq(it.unixMillisLong) }
|
||||
override val ResultRow.asKey: PostId
|
||||
get() = PostId(get(keyColumn))
|
||||
override val ResultRow.asObject: DateTime
|
||||
get() = DateTime(get(dateTimeColumn))
|
||||
|
||||
val postsRepoListeningJob = postsRepo.deletedObjectsIdsFlow.subscribeSafelyWithoutExceptions(scope) {
|
||||
unset(it)
|
||||
}
|
||||
|
||||
init {
|
||||
initTable()
|
||||
}
|
||||
|
||||
override fun update(k: PostId, v: DateTime, it: UpdateBuilder<Int>) {
|
||||
it[dateTimeColumn] = v.unixMillisLong
|
||||
}
|
||||
|
||||
override fun insertKey(k: PostId, v: DateTime, it: InsertStatement<Number>) {
|
||||
it[keyColumn] = k.string
|
||||
}
|
||||
|
||||
override suspend fun getMinimalDateTimePost(): Pair<PostId, DateTime>? = transaction(database) {
|
||||
selectAll().orderBy(dateTimeColumn).limit(1).firstOrNull() ?.let {
|
||||
PostId(it[keyColumn]) to DateTime(it[dateTimeColumn])
|
||||
}
|
||||
}
|
||||
}
|
||||
1
triggers/timer/src/main/AndroidManifest.xml
Normal file
1
triggers/timer/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest package="dev.inmo.plaguposter.triggers.timer"/>
|
||||
Reference in New Issue
Block a user