Init commit (#1)

This commit is contained in:
struga 2024-01-31 20:48:41 +07:00 committed by GitHub
parent d3d93fab1e
commit 30a0a61966
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 685 additions and 0 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
[*.{kt,kts}]
ktlint_disabled_rules=no-wildcard-imports
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site = true

2
.github/settings.yml vendored Normal file
View File

@ -0,0 +1,2 @@
# These settings are synced to GitHub by https://probot.github.io/apps/settings/
_extends: .github

10
.github/workflows/basic-linters.yml vendored Normal file
View File

@ -0,0 +1,10 @@
name: Vality basic linters
on:
pull_request:
branches:
- "*"
jobs:
lint:
uses: valitydev/base-workflows/.github/workflows/basic-linters.yml@v1

10
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,10 @@
name: Build Maven Artifact
on:
pull_request:
branches:
- '*'
jobs:
build:
uses: valitydev/java-workflow/.github/workflows/maven-service-build.yml@v2

13
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,13 @@
name: Maven Deploy Artifact
on:
push:
branches:
- 'master'
jobs:
build-and-deploy:
uses: valitydev/java-workflow/.github/workflows/maven-service-deploy.yml@v2
secrets:
github-token: ${{ secrets.GITHUB_TOKEN }}
mm-webhook-url: ${{ secrets.MATTERMOST_WEBHOOK_URL }}

78
.gitignore vendored Normal file
View File

@ -0,0 +1,78 @@
# Created by .ignore support plugin (hsz.mobi)
.eunit
deps
*.o
*.beam
*.plt
erl_crash.dump
ebin/*.beam
rel/example_project
.concrete/DEV_MODE
.rebar
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
# Gradle:
.idea/gradle.xml
.idea/libraries
# Mongo Explorer plugin:
.idea/mongoSettings.xml
*.iws
*.ipr
*.iml
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
env.list

231
pom.xml Normal file
View File

@ -0,0 +1,231 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>dev.vality</groupId>
<artifactId>service-parent-pom</artifactId>
<version>2.1.5</version>
</parent>
<artifactId>dev-vality-tg-bot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>dev-vality-tg-bot</name>
<description>Bot for vality needs</description>
<properties>
<kotlin.version>1.9.20</kotlin.version>
<jackson.version>2.16.1</jackson.version>
</properties>
<dependencies>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.7.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.7.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.7.16</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-spring-boot-starter</artifactId>
<version>6.5.0</version>
</dependency>
<!-- Third party -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
<!--kotlin-->
<dependency>
<groupId>io.github.microutils</groupId>
<artifactId>kotlin-logging-jvm</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
<version>1.9.22</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.27.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito.kotlin</groupId>
<artifactId>mockito-kotlin</artifactId>
<version>4.0.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<resources>
<resource>
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
<targetPath>${project.build.directory}</targetPath>
<includes>
<include>Dockerfile</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>Dockerfile</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
<plugin>jpa</plugin>
</compilerPlugins>
</configuration>
<executions>
<execution>
<id>kapt</id>
<goals>
<goal>kapt</goal>
</goals>
<configuration>
<sourceDirs>
<sourceDir>src/main/kotlin</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>com.github.gantsign.maven</groupId>
<artifactId>ktlint-maven-plugin</artifactId>
<version>1.16.0</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-remote-resources-plugin</artifactId>
<version>1.6.0</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-filtering</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
<configuration>
<resourceBundles>
<resourceBundle>dev.vality:shared-resources:${shared-resources.version}</resourceBundle>
</resourceBundles>
<attachToMain>false</attachToMain>
<attachToTest>false</attachToTest>
</configuration>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,15 @@
package dev.vality.tg.bot
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan
import org.springframework.boot.runApplication
import org.springframework.boot.web.servlet.ServletComponentScan
@ConfigurationPropertiesScan
@ServletComponentScan
@SpringBootApplication
class Application
fun main(args: Array<String>) {
runApplication<Application>(*args)
}

View File

@ -0,0 +1,93 @@
package dev.vality.tg.bot.bot
import dev.vality.tg.bot.constants.ActionsMenuItem.START_MENU
import dev.vality.tg.bot.service.AuthUserService
import dev.vality.tg.bot.service.CallbackCommandService
import dev.vality.tg.bot.service.MainMenuService
import dev.vality.tg.bot.utils.UserUtils
import mu.KotlinLogging
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.telegram.telegrambots.bots.TelegramLongPollingBot
import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatMember
import org.telegram.telegrambots.meta.api.methods.menubutton.SetChatMenuButton
import org.telegram.telegrambots.meta.api.methods.send.SendMessage
import org.telegram.telegrambots.meta.api.objects.Update
import org.telegram.telegrambots.meta.api.objects.chatmember.ChatMember
import org.telegram.telegrambots.meta.api.objects.menubutton.MenuButtonCommands
import org.telegram.telegrambots.meta.exceptions.TelegramApiException
private val log = KotlinLogging.logger {}
@Component
class Bot(
private val mainMenuService: MainMenuService,
private val callbackCommandService: CallbackCommandService,
private val authUserService: AuthUserService,
) : TelegramLongPollingBot() {
@Value("\${bot.username}")
lateinit var botUser: String
@Value("\${bot.token}")
lateinit var token: String
@Value("\${chats.vality.chat.id}")
lateinit var valityChatId: String
override fun onUpdateReceived(update: Update) {
try {
val userId = UserUtils.getUserId(update)
val chatMember: ChatMember = execute(GetChatMember(valityChatId, userId))
if (!authUserService.isUserPermission(chatMember)) {
val message = authUserService.createUserNotFoundMessage(update)
execute(message)
}
if (update.hasMessage()) {
if (update.message.text.equals(START_MENU)) {
handleStartMessage(update)
} else if (mainMenuService.isMenuCommand(update)) {
execute(mainMenuService.handleMenu(update))
} else {
handleCallback(update)
}
}
} catch (e: TelegramApiException) {
log.error { e }
}
}
private fun handleStartMessage(update: Update) {
try {
val createMenuCommands = mainMenuService.initMenu()
val createChatMenuButton = SetChatMenuButton().apply {
setChatId(update.message.chatId)
menuButton = MenuButtonCommands.builder()
.build()
}
execute(createMenuCommands)
execute(createChatMenuButton)
} catch (e: TelegramApiException) {
log.error { e }
}
}
private fun handleCallback(update: Update) {
try {
val callbackMessages: List<SendMessage> = callbackCommandService.handleCallbackMessage(update)
callbackMessages.forEach { item -> execute(item) }
} catch (e: TelegramApiException) {
log.error { e }
}
}
override fun getBotUsername(): String {
return botUser
}
@Deprecated("Deprecated in Java")
override fun getBotToken(): String {
return token
}
}

View File

@ -0,0 +1,23 @@
package dev.vality.tg.bot.config
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary
@Configuration
class AppConfig {
@Bean
@Primary
fun objectMapper(): ObjectMapper = jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true)
.registerModule(JavaTimeModule())
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
}

View File

@ -0,0 +1,12 @@
package dev.vality.tg.bot.constants
object ActionsMenuItem {
const val START = "start"
const val START_MENU = "/$START"
const val ASK_QUESTION_CTO = "ask_question_cto"
const val ASK_QUESTION_CTO_MENU = "/$ASK_QUESTION_CTO"
const val ASK_TECH_INFO = "ask_tech_info"
const val ASK_TECH_INFO_MENU = "/$ASK_TECH_INFO"
}

View File

@ -0,0 +1,7 @@
package dev.vality.tg.bot.constants
object ActionsMenuReactionItem {
const val CTO_QUESTION = "Напишите и отправьте вопрос для CTO"
const val TECH_VALITY_QUESTION = "Напишите и отправьте запрос на тех. описание непонятной для вас информации " +
"по продукту vality"
}

View File

@ -0,0 +1,16 @@
package dev.vality.tg.bot.constants
enum class UserStatuses(
private val value: String? = null,
) {
CREATOR("creator"), ADMINISTRATOR("administrator"), MEMBER("member");
companion object {
val ALLOWED_USER_STATUSES: Set<String?> = setOf(
CREATOR.value,
ADMINISTRATOR.value,
MEMBER.value,
)
}
}

View File

@ -0,0 +1,3 @@
package dev.vality.tg.bot.exception
class TgBotException(message: String, e: Exception?) : RuntimeException(message, e)

View File

@ -0,0 +1,26 @@
package dev.vality.tg.bot.service
import dev.vality.tg.bot.constants.UserStatuses
import dev.vality.tg.bot.utils.UserUtils
import mu.KotlinLogging
import org.springframework.stereotype.Component
import org.telegram.telegrambots.meta.api.methods.send.SendMessage
import org.telegram.telegrambots.meta.api.objects.Update
import org.telegram.telegrambots.meta.api.objects.chatmember.ChatMember
private val log = KotlinLogging.logger {}
@Component
class AuthUserService {
fun isUserPermission(chatMember: ChatMember): Boolean {
return UserStatuses.ALLOWED_USER_STATUSES.contains(chatMember.status)
}
fun createUserNotFoundMessage(update: Update): SendMessage {
log.info { "User ${UserUtils.getUserName(update)} not found in chat" }
val message = SendMessage()
message.setChatId(UserUtils.getUserId(update))
message.text = "У вас нет прав на взаимодействие с данным ботом"
return message
}
}

View File

@ -0,0 +1,54 @@
package dev.vality.tg.bot.service
import dev.vality.tg.bot.constants.ActionsMenuReactionItem.CTO_QUESTION
import dev.vality.tg.bot.constants.ActionsMenuReactionItem.TECH_VALITY_QUESTION
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.telegram.telegrambots.meta.api.methods.send.SendMessage
import org.telegram.telegrambots.meta.api.objects.Update
@Component
class CallbackCommandService {
@Value("\${chats.all.hands.chat.id}")
lateinit var allHandsChatId: String
@Value("\${chats.tech.vality.chat.id}")
lateinit var techValityChatId: String
fun handleCallbackMessage(update: Update) =
when (update.message.replyToMessage.text) {
CTO_QUESTION -> initCtoCallbackActions(update)
TECH_VALITY_QUESTION -> initTechValityCallbackActions(update)
else -> initDefaultEctions(update)
}
private fun initDefaultEctions(update: Update) = listOf(
SendMessage().apply {
setChatId(update.message.chatId)
text = "Неизвестная ошибка, попробуйте еще раз!"
},
)
private fun initTechValityCallbackActions(update: Update) = listOf(
SendMessage().apply {
setChatId(update.message.chatId)
text = "Создана задача на описание данной функции процессинга. Спасибо! \uD83D\uDE0A"
},
SendMessage().apply {
chatId = techValityChatId
text = update.message.text
},
)
private fun initCtoCallbackActions(update: Update) = listOf(
SendMessage().apply {
setChatId(update.message.chatId)
text = "Вопрос принят и будет рассмотрен на all hands. Спасибо! \uD83D\uDE0A"
},
SendMessage().apply {
chatId = allHandsChatId
text = update.message.text
},
)
}

View File

@ -0,0 +1,45 @@
package dev.vality.tg.bot.service
import dev.vality.tg.bot.constants.ActionsMenuItem
import dev.vality.tg.bot.constants.ActionsMenuReactionItem.CTO_QUESTION
import dev.vality.tg.bot.constants.ActionsMenuReactionItem.TECH_VALITY_QUESTION
import org.springframework.stereotype.Component
import org.telegram.telegrambots.meta.api.methods.commands.SetMyCommands
import org.telegram.telegrambots.meta.api.methods.send.SendMessage
import org.telegram.telegrambots.meta.api.objects.Update
import org.telegram.telegrambots.meta.api.objects.commands.BotCommand
import org.telegram.telegrambots.meta.api.objects.replykeyboard.ForceReplyKeyboard
@Component
class MainMenuService {
fun isMenuCommand(update: Update) = update.message.text.matches(Regex("^/.*"))
fun initMenu(): SetMyCommands {
val c1 = BotCommand(ActionsMenuItem.START, "Start the Bot")
val c2 = BotCommand(ActionsMenuItem.ASK_QUESTION_CTO, "Задать вопрос CTO")
val c3 = BotCommand(ActionsMenuItem.ASK_TECH_INFO, "Запросить техническое описание работы процессинга")
return SetMyCommands().apply {
commands = arrayListOf(c1, c2, c3)
}
}
fun handleMenu(update: Update): SendMessage? {
when (update.message.text) {
ActionsMenuItem.ASK_QUESTION_CTO_MENU -> return SendMessage().apply {
setChatId(update.message.chatId)
text = CTO_QUESTION
replyMarkup = ForceReplyKeyboard()
}
ActionsMenuItem.ASK_TECH_INFO_MENU -> {
return SendMessage().apply {
setChatId(update.message.chatId)
text = TECH_VALITY_QUESTION
replyMarkup = ForceReplyKeyboard()
}
}
}
throw RuntimeException("Unknown command!")
}
}

View File

@ -0,0 +1,35 @@
package dev.vality.tg.bot.utils
import org.telegram.telegrambots.meta.api.objects.Update
class UserUtils {
companion object {
fun getUserId(update: Update): Long {
return if (update.hasMessage()) {
update.message.from.id
} else if (update.hasCallbackQuery()) {
update.callbackQuery.from.id
} else if (update.hasInlineQuery()) {
update.inlineQuery.from.id
} else {
update.myChatMember.from.id
}
}
fun getUserName(update: Update): String {
return if (update.hasMessage()) {
update.message.from.userName
} else if (update.hasCallbackQuery()) {
update.callbackQuery.message.from.userName
} else if (update.hasInlineQuery()) {
update.inlineQuery.from.userName
} else {
update.myChatMember.from.userName
}
}
fun isUserInBot(update: Update): Boolean {
return update.message.chatId == update.message.from.id
}
}
}

View File

@ -0,0 +1,8 @@
bot:
username: DevValityTgBot
token: xxx
chats:
tech.vality.chat.id: "xxx"
all.hands.chat.id: "yyy"
vality.chat.id: "zzz"