From 23441294876511d19c9149ed57e26ab17b7f5e36 Mon Sep 17 00:00:00 2001 From: madhead Date: Mon, 25 Apr 2022 01:22:44 +0200 Subject: [PATCH 1/2] Voting processor for herald votes --- herald/processor/README.adoc | 3 ++ herald/processor/build.gradle.kts | 10 +++++++ .../processor/HeraldVoteUpdateProcessor.kt | 29 +++++++++++++++++++ runners/lambda/build.gradle.kts | 1 + .../bot/runners/lambda/config/pipeline.kt | 9 +++++- settings.gradle.kts | 1 + 6 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 herald/processor/README.adoc create mode 100644 herald/processor/build.gradle.kts create mode 100644 herald/processor/src/main/kotlin/by/jprof/telegram/bot/herald/processor/HeraldVoteUpdateProcessor.kt diff --git a/herald/processor/README.adoc b/herald/processor/README.adoc new file mode 100644 index 00000000..e4c3dbdd --- /dev/null +++ b/herald/processor/README.adoc @@ -0,0 +1,3 @@ += Herald / Processor + +link:../../votes/voting-processor/src/main/kotlin/by/jprof/telegram/bot/votes/voting_processor/VotingProcessor.kt[`VotingProcessor`] implementation for this scheduled messages poster. diff --git a/herald/processor/build.gradle.kts b/herald/processor/build.gradle.kts new file mode 100644 index 00000000..c75ab402 --- /dev/null +++ b/herald/processor/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + kotlin("jvm") +} + +dependencies { + api(project.projects.core) + api(project.projects.votes) + api(project.projects.votes.votingProcessor) + implementation(libs.log4j.api) +} diff --git a/herald/processor/src/main/kotlin/by/jprof/telegram/bot/herald/processor/HeraldVoteUpdateProcessor.kt b/herald/processor/src/main/kotlin/by/jprof/telegram/bot/herald/processor/HeraldVoteUpdateProcessor.kt new file mode 100644 index 00000000..cea438f6 --- /dev/null +++ b/herald/processor/src/main/kotlin/by/jprof/telegram/bot/herald/processor/HeraldVoteUpdateProcessor.kt @@ -0,0 +1,29 @@ +package by.jprof.telegram.bot.herald.processor + +import by.jprof.telegram.bot.core.UpdateProcessor +import by.jprof.telegram.bot.votes.dao.VotesDAO +import by.jprof.telegram.bot.votes.voting_processor.VotingProcessor +import dev.inmo.tgbotapi.bot.RequestsExecutor +import dev.inmo.tgbotapi.types.update.CallbackQueryUpdate +import dev.inmo.tgbotapi.types.update.abstracts.Update +import org.apache.logging.log4j.LogManager + +class HeraldVoteUpdateProcessor( + votesDAO: VotesDAO, + bot: RequestsExecutor, +) : VotingProcessor( + "HERALD", + votesDAO, + { throw UnsupportedOperationException("Votes should be constructed elsewhere!") }, + bot, +), UpdateProcessor { + companion object { + private val logger = LogManager.getLogger(HeraldVoteUpdateProcessor::class.java)!! + } + + override suspend fun process(update: Update) { + when (update) { + is CallbackQueryUpdate -> processCallbackQuery(update.data) + } + } +} diff --git a/runners/lambda/build.gradle.kts b/runners/lambda/build.gradle.kts index f4be591b..1c4b9f44 100644 --- a/runners/lambda/build.gradle.kts +++ b/runners/lambda/build.gradle.kts @@ -20,4 +20,5 @@ dependencies { implementation(project.projects.pins.dynamodb) implementation(project.projects.pins.sfn) implementation(project.projects.currencies) + implementation(project.projects.herald.processor) } diff --git a/runners/lambda/src/main/kotlin/by/jprof/telegram/bot/runners/lambda/config/pipeline.kt b/runners/lambda/src/main/kotlin/by/jprof/telegram/bot/runners/lambda/config/pipeline.kt index 41e1603f..5f5c1c4a 100644 --- a/runners/lambda/src/main/kotlin/by/jprof/telegram/bot/runners/lambda/config/pipeline.kt +++ b/runners/lambda/src/main/kotlin/by/jprof/telegram/bot/runners/lambda/config/pipeline.kt @@ -3,8 +3,8 @@ package by.jprof.telegram.bot.runners.lambda.config import by.jprof.telegram.bot.core.UpdateProcessingPipeline import by.jprof.telegram.bot.core.UpdateProcessor import by.jprof.telegram.bot.currencies.CurrenciesUpdateProcessor -import by.jprof.telegram.bot.currencies.parser.MonetaryAmountParsingPipeline import by.jprof.telegram.bot.eval.EvalUpdateProcessor +import by.jprof.telegram.bot.herald.processor.HeraldVoteUpdateProcessor import by.jprof.telegram.bot.jep.JEPUpdateProcessor import by.jprof.telegram.bot.jep.JsoupJEPSummary import by.jprof.telegram.bot.kotlin.KotlinMentionsUpdateProcessor @@ -123,4 +123,11 @@ val pipelineModule = module { bot = get(), ) } + + single(named("HeraldVoteUpdateProcessor")) { + HeraldVoteUpdateProcessor( + votesDAO = get(), + bot = get(), + ) + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 1a51f36b..e90b9d0d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,3 +31,4 @@ include(":pins:sfn") include(":runners:lambda") include(":currencies") include(":herald") +include(":herald:processor") From c02aa53dfc619b7031c2c636fc4072ec1ea7bcd8 Mon Sep 17 00:00:00 2001 From: madhead Date: Mon, 25 Apr 2022 01:27:39 +0200 Subject: [PATCH 2/2] Fix #31: Publish articles --- herald/build.gradle.kts | 4 + .../by/jprof/telegram/bot/herald/App.kt | 5 + .../by/jprof/telegram/bot/herald/impl/post.kt | 21 +++- .../telegram/bot/herald/impl/postFile.kt | 3 +- .../by/jprof/telegram/bot/herald/impl/send.kt | 100 ++++++++++++++++++ .../telegram/bot/herald/model/Frontmatter.kt | 1 - .../jprof/telegram/bot/herald/model/Post.kt | 1 + .../telegram/bot/herald/impl/PostTest.kt | 19 +++- herald/src/test/resources/posts/002.png | 0 9 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/send.kt create mode 100644 herald/src/test/resources/posts/002.png diff --git a/herald/build.gradle.kts b/herald/build.gradle.kts index ca71e9de..4a711308 100644 --- a/herald/build.gradle.kts +++ b/herald/build.gradle.kts @@ -11,6 +11,10 @@ application { dependencies { implementation(libs.kotlinx.serialization.core) implementation(libs.kaml) + implementation(libs.tgbotapi.extensions.api) + implementation(project.projects.votes.dynamodb) + implementation(project.projects.votes.tgbotapiExtensions) + implementation(project.projects.votes.dynamodb) testImplementation(libs.junit.jupiter.api) testImplementation(libs.junit.jupiter.params) diff --git a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/App.kt b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/App.kt index d9efed6c..ee57131a 100644 --- a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/App.kt +++ b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/App.kt @@ -2,6 +2,7 @@ package by.jprof.telegram.bot.herald import by.jprof.telegram.bot.herald.impl.post import by.jprof.telegram.bot.herald.impl.postFile +import by.jprof.telegram.bot.herald.impl.send suspend fun main(args: Array) { val postFile = postFile() ?: run { println("No post for today"); return } @@ -11,4 +12,8 @@ suspend fun main(args: Array) { val post = post(postFile) ?: run { println("Cannot parse the file"); return } println("Parsed the post: $post") + + send(post) + + println("Done") } diff --git a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/post.kt b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/post.kt index ad495cbf..143adf34 100644 --- a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/post.kt +++ b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/post.kt @@ -1,14 +1,20 @@ package by.jprof.telegram.bot.herald.impl +import by.jprof.telegram.bot.herald.model.Frontmatter import by.jprof.telegram.bot.herald.model.Post import com.charleskorn.kaml.Yaml import kotlinx.serialization.decodeFromString import java.nio.file.Path +import kotlin.io.path.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.nameWithoutExtension import kotlin.io.path.readText +import kotlin.io.path.relativeTo import kotlin.text.RegexOption.DOT_MATCHES_ALL import kotlin.text.RegexOption.MULTILINE fun post(path: Path): Post? { + val cwd = Path("") val text = path.readText() val regex = Regex("-{3,}\\R(?.*)\\R-{3,}\\s+(?.*)", setOf(MULTILINE, DOT_MATCHES_ALL)) @@ -17,7 +23,20 @@ fun post(path: Path): Post? { val frontmatter = groups["frontmatter"]?.value ?: return null val content = groups["content"]?.value ?: return null + val relativePath = path.relativeTo(cwd.toAbsolutePath()) - Post(Yaml.default.decodeFromString(frontmatter), content) + Post( + relativePath.parent.toString() + "/" + relativePath.nameWithoutExtension, + Yaml.default.decodeFromString(frontmatter).run { + if (this.image == null) { + this + } else { + this.copy( + image = path.resolveSibling(this.image).absolutePathString() + ) + } + }, + content, + ) } } diff --git a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/postFile.kt b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/postFile.kt index 35a9e0e2..47271e11 100644 --- a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/postFile.kt +++ b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/postFile.kt @@ -4,6 +4,7 @@ import java.nio.file.Path import java.time.LocalDate import java.time.format.DateTimeFormatter import kotlin.io.path.Path +import kotlin.io.path.absolute import kotlin.io.path.exists fun postFile(): Path? { @@ -11,5 +12,5 @@ fun postFile(): Path? { val today = LocalDate.now() val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd") - return cwd.resolve(today.format(formatter) + ".md").takeIf { it.exists() } + return cwd.resolve(today.format(formatter) + ".md").absolute().takeIf { it.exists() } } diff --git a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/send.kt b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/send.kt new file mode 100644 index 00000000..4349f05a --- /dev/null +++ b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/impl/send.kt @@ -0,0 +1,100 @@ +package by.jprof.telegram.bot.herald.impl + +import by.jprof.telegram.bot.herald.model.Post +import by.jprof.telegram.bot.votes.dynamodb.dao.VotesDAO +import by.jprof.telegram.bot.votes.model.Votes +import by.jprof.telegram.bot.votes.tgbotapi_extensions.toInlineKeyboardMarkup +import dev.inmo.tgbotapi.bot.Ktor.telegramBot +import dev.inmo.tgbotapi.extensions.api.chat.get.getChat +import dev.inmo.tgbotapi.extensions.api.send.media.sendPhoto +import dev.inmo.tgbotapi.extensions.api.send.sendMessage +import dev.inmo.tgbotapi.requests.abstracts.InputFile +import dev.inmo.tgbotapi.requests.abstracts.MultipartFile +import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2ParseMode +import dev.inmo.tgbotapi.types.chat.abstracts.UsernameChat +import dev.inmo.tgbotapi.types.toChatId +import dev.inmo.tgbotapi.utils.StorageFile +import dev.inmo.tgbotapi.utils.extensions.escapeMarkdownV2Common +import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider +import software.amazon.awssdk.regions.Region +import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient +import kotlin.io.path.Path +import kotlin.io.path.absolute +import kotlin.io.path.isRegularFile +import kotlin.system.exitProcess + +suspend fun send(post: Post) { + val image = post.image() + val votes = post.votes() + + post.frontmatter.chats.forEach { chat -> + when { + image != null -> { + println("Sending image to $chat") + + bot.sendPhoto( + chatId = chat.toChatId(), + fileId = image, + text = post.content.forChat(chat), + parseMode = MarkdownV2ParseMode, + replyMarkup = votes?.toInlineKeyboardMarkup(), + ) + } + else -> { + println("Sending text to $chat") + + bot.sendMessage( + chatId = chat.toChatId(), + text = post.content.forChat(chat), + parseMode = MarkdownV2ParseMode, + replyMarkup = votes?.toInlineKeyboardMarkup(), + disableWebPagePreview = true, + ) + } + } + } +} + +private val bot = telegramBot(System.getenv("TOKEN_TELEGRAM_BOT") ?: run { + println("TOKEN_TELEGRAM_BOT is not set!") + exitProcess(0) +}) + +private val votesDAO = VotesDAO( + DynamoDbAsyncClient + .builder() + .region(Region.US_EAST_1) + .credentialsProvider(EnvironmentVariableCredentialsProvider.create()) + .build(), + System.getenv("TABLE_VOTES") ?: run { + println("TABLE_VOTES is not set!") + exitProcess(0) + } +) + +private fun Post.image(): InputFile? { + val cwd = Path("").absolute() + val imagePath = this.frontmatter.image?.let { cwd.resolve(it) } + + return if (imagePath != null && imagePath.isRegularFile()) { + MultipartFile(StorageFile(imagePath.toFile())) + } else { + null + } +} + +private suspend fun Post.votes(): Votes? { + return if (this.frontmatter.votes == null) { + null + } else { + val votesId = "HERALD-${this.id}" + + votesDAO.get(votesId) ?: Votes(votesId, this.frontmatter.votes) + } +} + +private suspend fun String.forChat(chatId: Long): String { + val chat = (bot.getChat(chatId.toChatId()) as? UsernameChat)?.username?.username ?: return this + + return "${this.trimEnd()}\n\n${chat.escapeMarkdownV2Common()}" +} diff --git a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/model/Frontmatter.kt b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/model/Frontmatter.kt index d3ae91a5..6b90629c 100644 --- a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/model/Frontmatter.kt +++ b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/model/Frontmatter.kt @@ -6,6 +6,5 @@ import kotlinx.serialization.Serializable data class Frontmatter( val chats: List, val image: String? = null, - val disableWebPagePreview: Boolean = false, val votes: List? = null, ) diff --git a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/model/Post.kt b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/model/Post.kt index 1ae1ee69..c97c98f4 100644 --- a/herald/src/main/kotlin/by/jprof/telegram/bot/herald/model/Post.kt +++ b/herald/src/main/kotlin/by/jprof/telegram/bot/herald/model/Post.kt @@ -1,6 +1,7 @@ package by.jprof.telegram.bot.herald.model data class Post( + val id: String, val frontmatter: Frontmatter, val content: String, ) diff --git a/herald/src/test/kotlin/by/jprof/telegram/bot/herald/impl/PostTest.kt b/herald/src/test/kotlin/by/jprof/telegram/bot/herald/impl/PostTest.kt index 1a342645..eaabe5b5 100644 --- a/herald/src/test/kotlin/by/jprof/telegram/bot/herald/impl/PostTest.kt +++ b/herald/src/test/kotlin/by/jprof/telegram/bot/herald/impl/PostTest.kt @@ -9,6 +9,10 @@ import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import java.nio.file.Path import java.util.stream.Stream +import kotlin.io.path.Path +import kotlin.io.path.absolutePathString +import kotlin.io.path.nameWithoutExtension +import kotlin.io.path.relativeTo internal class PostTest { @ParameterizedTest(name = "{0}") @@ -26,6 +30,7 @@ internal class PostTest { Arguments.of( Named.of("001", "001".testResourcePath), Post( + "001".id, Frontmatter(chats = listOf(-1001146107319, -1001585354456)), "This is content.\n" ) @@ -33,7 +38,8 @@ internal class PostTest { Arguments.of( Named.of("002", "002".testResourcePath), Post( - Frontmatter(chats = listOf(-1001146107319, -1001585354456), image = "002.png"), + "002".id, + Frontmatter(chats = listOf(-1001146107319, -1001585354456), image = "002".image), "This is a\nmultiline content!\n" ) ), @@ -41,5 +47,16 @@ internal class PostTest { private val String.testResourcePath: Path get() = Path.of(this@Companion::class.java.classLoader.getResource("posts/$this.md").toURI()) + + private val String.id: String + get() { + val cwd = Path("").toAbsolutePath() + val resourcePath = Path.of(this@Companion::class.java.classLoader.getResource("posts/$this.md").toURI()).relativeTo(cwd) + + return resourcePath.parent.toString() + "/" + resourcePath.nameWithoutExtension + } + + private val String.image: String + get() = Path.of(this@Companion::class.java.classLoader.getResource("posts/$this.png").toURI()).absolutePathString() } } diff --git a/herald/src/test/resources/posts/002.png b/herald/src/test/resources/posts/002.png new file mode 100644 index 00000000..e69de29b