recurrents
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
package space.luminic.finance.services.telegram
|
||||
|
||||
import com.github.kotlintelegrambot.Bot
|
||||
import com.github.kotlintelegrambot.dispatch
|
||||
import com.github.kotlintelegrambot.dispatcher.callbackQuery
|
||||
import com.github.kotlintelegrambot.dispatcher.command
|
||||
import com.github.kotlintelegrambot.dispatcher.message
|
||||
import com.github.kotlintelegrambot.entities.ChatId
|
||||
import com.github.kotlintelegrambot.entities.InlineKeyboardMarkup
|
||||
import com.github.kotlintelegrambot.entities.ParseMode
|
||||
import com.github.kotlintelegrambot.entities.keyboard.InlineKeyboardButton
|
||||
import com.github.kotlintelegrambot.entities.keyboard.WebAppInfo
|
||||
import com.github.kotlintelegrambot.entities.reaction.ReactionType
|
||||
import com.github.kotlintelegrambot.extensions.filters.Filter
|
||||
import com.github.kotlintelegrambot.logging.LogLevel
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.models.State
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.models.User
|
||||
import space.luminic.finance.repos.BotRepo
|
||||
import space.luminic.finance.services.UserService
|
||||
import java.time.LocalDate
|
||||
|
||||
@Service
|
||||
class BotService(
|
||||
@Value("\${telegram.bot.token}") private val botToken: String,
|
||||
private val userService: UserService,
|
||||
@Qualifier("spaceServiceTelegram") private val spaceService: SpaceService,
|
||||
private val botRepo: BotRepo,
|
||||
@Qualifier("transactionsServiceTelegram") private val transactionService: TransactionService
|
||||
) {
|
||||
|
||||
|
||||
private fun buildSpaceSelector(userId: Int): InlineKeyboardMarkup {
|
||||
val spaces = spaceService.getSpaces(userId)
|
||||
|
||||
val keyboard = mutableListOf<List<InlineKeyboardButton>>()
|
||||
val row = mutableListOf<InlineKeyboardButton>()
|
||||
if (spaces.isNotEmpty()) {
|
||||
for ((index, space) in spaces.withIndex()) {
|
||||
val button =
|
||||
InlineKeyboardButton.CallbackData(text = space.name, callbackData = "select_space_${space.id}")
|
||||
|
||||
row.add(button)
|
||||
|
||||
// Если 2 кнопки в строке — отправляем строку и очищаем
|
||||
if (row.size == 2) {
|
||||
keyboard.add(ArrayList(row))
|
||||
row.clear()
|
||||
}
|
||||
}
|
||||
|
||||
// Если осталась 1 кнопка — добавляем последнюю строку
|
||||
if (row.isNotEmpty()) {
|
||||
keyboard.add(ArrayList(row))
|
||||
}
|
||||
} else {
|
||||
row.add(InlineKeyboardButton.CallbackData("Создать пространство!", callbackData = "create_space"))
|
||||
keyboard.add(ArrayList(row))
|
||||
}
|
||||
|
||||
return InlineKeyboardMarkup.Companion.create(keyboard)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun selectSpace(tgUserId: Long, selectedSpaceId: Int) {
|
||||
val user = userService.getUserByTelegramId(tgUserId)
|
||||
botRepo.setState(
|
||||
user.id!!, State.StateCode.SPACE_SELECTED, mapOf(
|
||||
"selected_space" to selectedSpaceId.toString(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun buildRegister() {
|
||||
|
||||
}
|
||||
|
||||
private fun buildMenu(tgUserId: Long): InlineKeyboardMarkup {
|
||||
val user = userService.getUserByTelegramId(tgUserId)
|
||||
val userId = requireNotNull(user.id) { "User must have id" }
|
||||
|
||||
val state = botRepo.getState(tgUserId)
|
||||
|
||||
val spaceId = state?.data?.get("selected_space")?.toIntOrNull()
|
||||
val space = spaceId?.let { id -> spaceService.getSpace(spaceId, userId) }
|
||||
|
||||
val keyboard = mutableListOf<List<InlineKeyboardButton>>()
|
||||
|
||||
// Кнопка с названием выбранного space (или плейсхолдером)
|
||||
keyboard.add(
|
||||
listOf(
|
||||
InlineKeyboardButton.CallbackData(
|
||||
text = space?.name ?: "Select space",
|
||||
"select_space"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// Если нужен второй баттон — сделай другой смысл/текст; иначе этот блок можно убрать
|
||||
keyboard.add(
|
||||
listOf(
|
||||
InlineKeyboardButton.WebApp(
|
||||
text = "Открыть WebApp",
|
||||
webApp = WebAppInfo(url = "https://app.luminic.space")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return InlineKeyboardMarkup.Companion.create(keyboard)
|
||||
}
|
||||
|
||||
@Bean
|
||||
fun bot(): Bot {
|
||||
val bot = com.github.kotlintelegrambot.bot {
|
||||
logLevel = LogLevel.None
|
||||
token = botToken
|
||||
dispatch {
|
||||
message(Filter.Text) {
|
||||
val fromId = message.from?.id ?: throw IllegalArgumentException("user is empty")
|
||||
val user = userService.getUserByTelegramId(fromId)
|
||||
val state = botRepo.getState(message.from?.id ?: throw IllegalArgumentException("user is empty"))
|
||||
when (state?.state) {
|
||||
State.StateCode.SPACE_SELECTED -> {
|
||||
try {
|
||||
val parts = message.text!!.trim().split(" ", limit = 2)
|
||||
if (parts.isEmpty()) {
|
||||
bot.sendMessage(
|
||||
chatId = ChatId.fromId(message.chat.id),
|
||||
text = "Введите сумму и комментарий, например: `250 обед`",
|
||||
parseMode = ParseMode.MARKDOWN
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
val amount = parts[0].toIntOrNull()
|
||||
?: throw IllegalArgumentException("Сумма транзакции не число!")
|
||||
if (amount <= 0) {
|
||||
throw IllegalArgumentException("Сумма не может быть меньше 1.")
|
||||
}
|
||||
val comment = parts.getOrNull(1)?.trim().orEmpty()
|
||||
if (comment.isEmpty()) throw IllegalArgumentException("Комментарий не может быть пустым.")
|
||||
|
||||
|
||||
// bot.sendMessage(
|
||||
// chatId = ChatId.fromId(message.chat.id),
|
||||
// text = "Принято: сумма = $amount, комментарий = \"$comment\""
|
||||
// )
|
||||
|
||||
try {
|
||||
transactionService.createTransaction(
|
||||
state.data["selected_space"]?.toInt()
|
||||
?: throw IllegalArgumentException("selected space is empty"),
|
||||
user.id!!,
|
||||
TransactionDTO.CreateTransactionDTO(
|
||||
Transaction.TransactionType.EXPENSE,
|
||||
Transaction.TransactionKind.INSTANT,
|
||||
comment = comment,
|
||||
amount = amount.toBigDecimal(),
|
||||
date = LocalDate.now(),
|
||||
),
|
||||
message.chat.id,
|
||||
message.messageId
|
||||
)
|
||||
bot.setMessageReaction(
|
||||
chatId = ChatId.fromId(message.chat.id),
|
||||
messageId = message.messageId,
|
||||
reaction = listOf(ReactionType.Emoji("🤝")),
|
||||
isBig = false
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
bot.sendMessage(
|
||||
ChatId.Companion.fromId(message.chat.id),
|
||||
text = "Кажется у вас не выбран Space",
|
||||
replyMarkup = buildSpaceSelector(user.id!!)
|
||||
)
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
bot.sendMessage(
|
||||
chatId = ChatId.Companion.fromId(message.chat.id),
|
||||
text = "Ошибка: ${e.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
callbackQuery {
|
||||
if (callbackQuery.data.startsWith("select_space_")) {
|
||||
val spaceId = callbackQuery.data.substringAfter("select_space_").toInt()
|
||||
println(spaceId)
|
||||
try {
|
||||
selectSpace(callbackQuery.from.id, spaceId)
|
||||
bot.editMessageText(
|
||||
chatId = ChatId.Companion.fromId(callbackQuery.message!!.chat.id),
|
||||
messageId = callbackQuery.message!!.messageId,
|
||||
text = "Успешно!\n\nМы готовы принимать Ваши транзакции.\n\nПросто пишите их в формате:\n\n <i>сумма комментарий</i>\n\n <b>Первой обязательно должна быть сумма!</b>",
|
||||
parseMode = ParseMode.HTML,
|
||||
replyMarkup = buildMenu(callbackQuery.from.id)
|
||||
)
|
||||
} catch (e: NotFoundException) {
|
||||
e.printStackTrace()
|
||||
bot.sendMessage(
|
||||
ChatId.Companion.fromId(callbackQuery.message!!.chat.id),
|
||||
text = "Мы кажется не знакомы"
|
||||
)
|
||||
}
|
||||
|
||||
} else if (callbackQuery.data.equals("select_space", ignoreCase = true)) {
|
||||
bot.editMessageText(
|
||||
ChatId.Companion.fromId(callbackQuery.message!!.chat.id),
|
||||
callbackQuery.message!!.messageId,
|
||||
text = "Выберите новое пространство",
|
||||
replyMarkup = buildSpaceSelector(
|
||||
userService.getUserByTelegramId(callbackQuery.from.id).id!!
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
command("start") {
|
||||
val user: User
|
||||
try {
|
||||
user = userService.getUserByTelegramId(
|
||||
message.from?.id ?: throw IllegalArgumentException("User not found")
|
||||
)
|
||||
bot.sendMessage(
|
||||
ChatId.Companion.fromId(message.chat.id),
|
||||
text = "Привет!\n\nРады тебя снова видеть!\n\nНачнем с выбора пространства:",
|
||||
replyMarkup = buildSpaceSelector(user.id!!)
|
||||
)
|
||||
|
||||
} catch (e: NotFoundException) {
|
||||
bot.sendMessage(ChatId.Companion.fromId(message.chat.id), text = "Кажется, мы еще не знакомы.")
|
||||
bot.sendMessage(ChatId.Companion.fromId(message.chat.id), text = "Давайте зарегистрируемся? ")
|
||||
bot.sendMessage(ChatId.Companion.fromId(message.chat.id), text = "")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
bot.startPolling()
|
||||
return bot
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package space.luminic.finance.services.telegram
|
||||
|
||||
import space.luminic.finance.models.Space
|
||||
|
||||
interface SpaceService {
|
||||
fun getSpaces(userId: Int): List<Space>
|
||||
fun getSpace(spaceId: Int, userId: Int): Space?
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package space.luminic.finance.services.telegram
|
||||
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.models.NotFoundException
|
||||
import space.luminic.finance.models.Space
|
||||
import space.luminic.finance.repos.SpaceRepo
|
||||
|
||||
@Service("spaceServiceTelegram")
|
||||
class SpaceServiceImpl(
|
||||
private val spaceRepo: SpaceRepo
|
||||
) : SpaceService {
|
||||
override fun getSpaces(userId: Int): List<Space> {
|
||||
val spaces = spaceRepo.findSpacesAvailableForUser(userId)
|
||||
return spaces
|
||||
}
|
||||
|
||||
override fun getSpace(spaceId: Int, userId: Int): Space? {
|
||||
val space =
|
||||
spaceRepo.findSpaceById(spaceId, userId) ?: throw NotFoundException("Space with id $spaceId not found")
|
||||
return space
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package space.luminic.finance.services.telegram
|
||||
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
|
||||
interface TransactionService {
|
||||
|
||||
fun createTransaction(spaceId: Int, userId: Int, transaction: TransactionDTO.CreateTransactionDTO, chatId: Long, messageId: Long ): Int
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package space.luminic.finance.services.telegram
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.stereotype.Service
|
||||
import space.luminic.finance.dtos.TransactionDTO
|
||||
import space.luminic.finance.models.Transaction
|
||||
import space.luminic.finance.repos.TransactionRepo
|
||||
import space.luminic.finance.services.CategoryServiceImpl
|
||||
|
||||
@Service("transactionsServiceTelegram")
|
||||
class TransactionsServiceImpl(
|
||||
private val transactionRepo: TransactionRepo,
|
||||
@Qualifier("spaceServiceTelegram") private val spaceService: SpaceService,
|
||||
private val categoryService: CategoryServiceImpl
|
||||
): TransactionService {
|
||||
|
||||
override fun createTransaction(
|
||||
spaceId: Int,
|
||||
userId: Int,
|
||||
transaction: TransactionDTO.CreateTransactionDTO,
|
||||
chatId: Long,
|
||||
messageId: Long
|
||||
): Int {
|
||||
val space = spaceService.getSpace(spaceId, userId)
|
||||
val category = transaction.categoryId?.let { categoryService.getCategory(spaceId, it) }
|
||||
val transaction = Transaction(
|
||||
space = space,
|
||||
type = transaction.type,
|
||||
kind = transaction.kind,
|
||||
category = category,
|
||||
comment = transaction.comment,
|
||||
amount = transaction.amount,
|
||||
fees = transaction.fees,
|
||||
date = transaction.date,
|
||||
tgChatId = chatId,
|
||||
tgMessageId = messageId,
|
||||
)
|
||||
print(transaction)
|
||||
return transactionRepo.create(transaction, userId)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user