From 3c837cf1d18ca98679ec9a1e70bcff772a0d63e7 Mon Sep 17 00:00:00 2001 From: Alexander Buntakov Date: Mon, 12 Dec 2022 14:14:13 +0300 Subject: [PATCH] Feature/telegram bot (#106) * add telegram bot * remove unused dependencies * add hasCommand --- .../ru/touchin/common/string/StringUtils.kt | 10 +-- settings.gradle.kts | 1 + telegram-bot-spring/.gitignore | 37 ++++++++++ telegram-bot-spring/README.md | 35 ++++++++++ telegram-bot-spring/build.gradle | 18 +++++ .../telegram/bot/EnableSpringTelegramBot.kt | 6 ++ .../telegram/bot/TelegramBotConfiguration.kt | 12 ++++ .../bot/messages/AbstractMessageHandler.kt | 22 ++++++ .../telegram/bot/messages/HelpMessage.kt | 7 ++ .../telegram/bot/messages/MessageFilter.kt | 9 +++ .../telegram/bot/messages/MessageHandler.kt | 10 +++ .../spring/telegram/bot/messages/Order.kt | 8 +++ .../bot/messages/dto/MessageCommand.kt | 19 ++++++ .../bot/messages/dto/MessageContext.kt | 17 +++++ .../TelegramMessageCommandResolver.kt | 9 +++ .../TelegramMessageCommandResolverImpl.kt | 23 +++++++ .../spring/telegram/bot/TestApplication.kt | 6 ++ .../TelegramMessageCommandResolverImplTest.kt | 67 +++++++++++++++++++ 18 files changed, 311 insertions(+), 5 deletions(-) create mode 100644 telegram-bot-spring/.gitignore create mode 100644 telegram-bot-spring/README.md create mode 100644 telegram-bot-spring/build.gradle create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/EnableSpringTelegramBot.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/TelegramBotConfiguration.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/AbstractMessageHandler.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/HelpMessage.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/MessageFilter.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/MessageHandler.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/Order.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/dto/MessageCommand.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/dto/MessageContext.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/services/TelegramMessageCommandResolver.kt create mode 100644 telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/services/impl/TelegramMessageCommandResolverImpl.kt create mode 100644 telegram-bot-spring/src/test/kotlin/ru/touchin/spring/telegram/bot/TestApplication.kt create mode 100644 telegram-bot-spring/src/test/kotlin/ru/touchin/spring/telegram/bot/messages/services/impl/TelegramMessageCommandResolverImplTest.kt diff --git a/common/src/main/kotlin/ru/touchin/common/string/StringUtils.kt b/common/src/main/kotlin/ru/touchin/common/string/StringUtils.kt index 2ac3dea..f056bba 100644 --- a/common/src/main/kotlin/ru/touchin/common/string/StringUtils.kt +++ b/common/src/main/kotlin/ru/touchin/common/string/StringUtils.kt @@ -19,12 +19,12 @@ object StringUtils { } nextUpperCase -> { - this.append(char.toUpperCase()) + this.append(char.uppercase()) nextUpperCase = false } - !nextUpperCase -> { - this.append(char.toLowerCase()) + else -> { + this.append(char.lowercase()) } } } @@ -34,8 +34,8 @@ object StringUtils { fun String.removeNonPrintableCharacters(): String { return this .transliterateCyrillic() - .replace("[\\p{Cntrl}&&[^\r\n\t]]".toRegex(), "")// erases all the ASCII control characters - .replace("\\p{C}".toRegex(), "")// removes non-printable characters from Unicode + .replace("\\p{Cntrl}&&[^\r\n\t]".toRegex(), "") // erases all the ASCII control characters + .replace("\\p{C}".toRegex(), "") // removes non-printable characters from Unicode .trim() } diff --git a/settings.gradle.kts b/settings.gradle.kts index f5581b4..5636de6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -61,3 +61,4 @@ include("server-info-spring-web") include("geoip-core") include("user-agent") include("smart-migration") +include("telegram-bot-spring") diff --git a/telegram-bot-spring/.gitignore b/telegram-bot-spring/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/telegram-bot-spring/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/telegram-bot-spring/README.md b/telegram-bot-spring/README.md new file mode 100644 index 0000000..7a4584f --- /dev/null +++ b/telegram-bot-spring/README.md @@ -0,0 +1,35 @@ +## Библиотека написания телеграмм-ботов + +```kotlin +@Component +class MyTelegramBot( + private val messageHandlers: List, +) : TelegramLongPollingBot() { + + override fun onUpdateReceived(update: Update?) { + // create MessageContext + // messageHandlers.takeWhile { it.process(ctx, this) } + } + +} + +@Component +class HelloMessageHandler: AbstractMessageHandler { + + override fun isSupported(ctx: MessageContext): Booleat { + ctx.messageCommand.message.equals("hi") + } + + override fun process(ctx: MessageContext, sender: AbsSender): Boolean { + val message = SendMessage().apply { + this.chatId = ctx.chatId + this.text = "Hello" + } + + sender.execute(message) + + return true + } + +} +``` diff --git a/telegram-bot-spring/build.gradle b/telegram-bot-spring/build.gradle new file mode 100644 index 0000000..6537758 --- /dev/null +++ b/telegram-bot-spring/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'kotlin' + id 'kotlin-spring' +} + +dependencies { + api 'org.telegram:telegrambots-spring-boot-starter:6.3.0' + + implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' + implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin' + implementation 'org.springframework.boot:spring-boot' + + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin") + testImplementation("org.mockito:mockito-inline") + testImplementation 'junit:junit' +} diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/EnableSpringTelegramBot.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/EnableSpringTelegramBot.kt new file mode 100644 index 0000000..c78b4f9 --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/EnableSpringTelegramBot.kt @@ -0,0 +1,6 @@ +package ru.touchin.spring.telegram.bot + +import org.springframework.context.annotation.Import + +@Import(value = [TelegramBotConfiguration::class]) +annotation class EnableSpringTelegramBot diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/TelegramBotConfiguration.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/TelegramBotConfiguration.kt new file mode 100644 index 0000000..e207b55 --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/TelegramBotConfiguration.kt @@ -0,0 +1,12 @@ +package ru.touchin.spring.telegram.bot + +import org.springframework.boot.context.properties.ConfigurationPropertiesScan +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@EnableConfigurationProperties +@ConfigurationPropertiesScan +@ComponentScan +class TelegramBotConfiguration diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/AbstractMessageHandler.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/AbstractMessageHandler.kt new file mode 100644 index 0000000..0a5dfc8 --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/AbstractMessageHandler.kt @@ -0,0 +1,22 @@ +package ru.touchin.spring.telegram.bot.messages + +import org.telegram.telegrambots.meta.bots.AbsSender +import ru.touchin.spring.telegram.bot.messages.dto.MessageContext + +abstract class AbstractMessageHandler, U, S>: + MessageHandler, + MessageFilter, + HelpMessage +{ + + override fun process(context: C, sender: AbsSender): Boolean { + if (!isSupported(context)) { + return false + } + + return internalProcess(context, sender) + } + + abstract fun internalProcess(context: C, sender: AbsSender): Boolean + +} diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/HelpMessage.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/HelpMessage.kt new file mode 100644 index 0000000..37c6e4a --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/HelpMessage.kt @@ -0,0 +1,7 @@ +package ru.touchin.spring.telegram.bot.messages + +interface HelpMessage { + + fun helpMessage(): String? = null + +} diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/MessageFilter.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/MessageFilter.kt new file mode 100644 index 0000000..f2554a5 --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/MessageFilter.kt @@ -0,0 +1,9 @@ +package ru.touchin.spring.telegram.bot.messages + +import ru.touchin.spring.telegram.bot.messages.dto.MessageContext + +interface MessageFilter, U, S> { + + fun isSupported(context: C): Boolean + +} diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/MessageHandler.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/MessageHandler.kt new file mode 100644 index 0000000..c397329 --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/MessageHandler.kt @@ -0,0 +1,10 @@ +package ru.touchin.spring.telegram.bot.messages + +import org.telegram.telegrambots.meta.bots.AbsSender +import ru.touchin.spring.telegram.bot.messages.dto.MessageContext + +interface MessageHandler, U, S> { + + fun process(context: C, sender: AbsSender): Boolean + +} diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/Order.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/Order.kt new file mode 100644 index 0000000..9d1433e --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/Order.kt @@ -0,0 +1,8 @@ +package ru.touchin.spring.telegram.bot.messages + +@Suppress("unused") +object Order { + const val HIGH_PRIORITY = 1024 + const val NORMAL_PRIORITY = 2048 + const val LOW_PRIORITY = 3072 +} diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/dto/MessageCommand.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/dto/MessageCommand.kt new file mode 100644 index 0000000..f4b2c28 --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/dto/MessageCommand.kt @@ -0,0 +1,19 @@ +package ru.touchin.spring.telegram.bot.messages.dto + +data class MessageCommand( + val command: String, + val message: String +) { + + fun hasCommand() = command != NO_COMMAND + + companion object { + private const val NO_COMMAND = "" + + fun createNoCommandMessage(message: String) = MessageCommand( + command = NO_COMMAND, + message = message + ) + + } +} diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/dto/MessageContext.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/dto/MessageContext.kt new file mode 100644 index 0000000..73ecc3a --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/dto/MessageContext.kt @@ -0,0 +1,17 @@ +package ru.touchin.spring.telegram.bot.messages.dto + +import org.telegram.telegrambots.meta.api.objects.Update + +open class MessageContext( + val origin: Update, + val user: U, + val messageCommand: MessageCommand, + val state: S +) { + + fun hasCommand(command: String) = messageCommand.command == command + + val chatId: String + get() = origin.message?.chatId?.toString() + ?: origin.callbackQuery.message.chatId.toString() +} diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/services/TelegramMessageCommandResolver.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/services/TelegramMessageCommandResolver.kt new file mode 100644 index 0000000..2253c5b --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/services/TelegramMessageCommandResolver.kt @@ -0,0 +1,9 @@ +package ru.touchin.spring.telegram.bot.messages.services + +import ru.touchin.spring.telegram.bot.messages.dto.MessageCommand + +interface TelegramMessageCommandResolver { + + fun resolve(rawMessage: String): MessageCommand + +} diff --git a/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/services/impl/TelegramMessageCommandResolverImpl.kt b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/services/impl/TelegramMessageCommandResolverImpl.kt new file mode 100644 index 0000000..a318fd7 --- /dev/null +++ b/telegram-bot-spring/src/main/kotlin/ru/touchin/spring/telegram/bot/messages/services/impl/TelegramMessageCommandResolverImpl.kt @@ -0,0 +1,23 @@ +package ru.touchin.spring.telegram.bot.messages.services.impl + +import org.springframework.stereotype.Service +import ru.touchin.spring.telegram.bot.messages.dto.MessageCommand +import ru.touchin.spring.telegram.bot.messages.services.TelegramMessageCommandResolver + +@Service +class TelegramMessageCommandResolverImpl : TelegramMessageCommandResolver { + + override fun resolve(rawMessage: String): MessageCommand { + val message = rawMessage.trim() + + if (message.isBlank() || !message.startsWith("/")) { + return MessageCommand.createNoCommandMessage(rawMessage) + } + + return MessageCommand( + command = message.substringBefore(' ').trim().lowercase(), + message = message.substringAfter(' ', missingDelimiterValue = "") + ) + } + +} diff --git a/telegram-bot-spring/src/test/kotlin/ru/touchin/spring/telegram/bot/TestApplication.kt b/telegram-bot-spring/src/test/kotlin/ru/touchin/spring/telegram/bot/TestApplication.kt new file mode 100644 index 0000000..9da7cc9 --- /dev/null +++ b/telegram-bot-spring/src/test/kotlin/ru/touchin/spring/telegram/bot/TestApplication.kt @@ -0,0 +1,6 @@ +package ru.touchin.spring.telegram.bot + +import org.springframework.boot.autoconfigure.SpringBootApplication + +@SpringBootApplication +open class TestApplication diff --git a/telegram-bot-spring/src/test/kotlin/ru/touchin/spring/telegram/bot/messages/services/impl/TelegramMessageCommandResolverImplTest.kt b/telegram-bot-spring/src/test/kotlin/ru/touchin/spring/telegram/bot/messages/services/impl/TelegramMessageCommandResolverImplTest.kt new file mode 100644 index 0000000..2f31cd0 --- /dev/null +++ b/telegram-bot-spring/src/test/kotlin/ru/touchin/spring/telegram/bot/messages/services/impl/TelegramMessageCommandResolverImplTest.kt @@ -0,0 +1,67 @@ +package ru.touchin.spring.telegram.bot.messages.services.impl + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ActiveProfiles +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import ru.touchin.spring.telegram.bot.messages.services.TelegramMessageCommandResolver + +@ActiveProfiles("test") +@SpringBootTest +internal class TelegramMessageCommandResolverImplTest { + + @Autowired + private lateinit var telegramMessageCommandResolver: TelegramMessageCommandResolver + + @Test + fun shouldBeEmptyCommand() { + telegramMessageCommandResolver.resolve("hello").also { + assertFalse(it.hasCommand()) + assertEquals("hello", it.message) + } + + telegramMessageCommandResolver.resolve("").also { + assertFalse(it.hasCommand()) + assertEquals("", it.message) + } + + telegramMessageCommandResolver.resolve(" ").also { + assertFalse(it.hasCommand()) + assertEquals(" ", it.message) + } + + telegramMessageCommandResolver.resolve("hello world").also { + assertFalse(it.hasCommand()) + assertEquals("hello world", it.message) + } + } + + @Test + fun shouldResolveCommand() { + telegramMessageCommandResolver.resolve("/hello").also { + assertTrue(it.hasCommand()) + assertEquals("/hello", it.command) + assertEquals("", it.message) + } + + telegramMessageCommandResolver.resolve("/hello world").also { + assertTrue(it.hasCommand()) + assertEquals("/hello", it.command) + assertEquals("world", it.message) + } + + telegramMessageCommandResolver.resolve("/hello /world").also { + assertTrue(it.hasCommand()) + assertEquals("/hello", it.command) + assertEquals("/world", it.message) + } + + telegramMessageCommandResolver.resolve(" /hello /world").also { + assertTrue(it.hasCommand()) + assertEquals("/hello", it.command) + assertEquals(" /world", it.message) + } + } + +}