Skip to content

Commit

Permalink
v0.2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
smaugfm committed Nov 15, 2020
1 parent 3f63f60 commit ba13bd4
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 72 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
}

group = "com.github.smaugfm"
version = "0.2.1-alpha"
version = "0.2.2-alpha"

val myMavenRepoReadUrl: String by project
val myMavenRepoReadUsername: String by project
Expand Down
3 changes: 1 addition & 2 deletions detektBaseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<CurrentIssues>
<ID>ComplexMethod:PayeeSuggestor.kt$PayeeSuggestor$private fun jaroSimilarity(s1: String, s2: String): Double</ID>
<ID>LongParameterList:TelegramApi.kt$TelegramApi$( chatId: Any, text: String, parseMode: String? = null, disableWebPagePreview: Boolean? = null, disableNotification: Boolean? = null, replyTo: Int? = null, markup: ReplyKeyboard? = null, )</ID>
<ID>LongParameterList:TelegramApi.kt$TelegramApi$( chatId: Any? = null, messageId: Int? = null, inlineMessageId: String? = null, text: String, parseMode: String? = null, disableWebPagePreview: Boolean? = null, markup: InlineKeyboardMarkup? = null )</ID>
<ID>LongParameterList:TelegramApi.kt$TelegramApi$( chatId: Any? = null, messageId: Int? = null, inlineMessageId: String? = null, text: String, parseMode: String? = null, disableWebPagePreview: Boolean? = null, markup: InlineKeyboardMarkup? = null, )</ID>
<ID>LongParameterList:Util.kt$( description: String, mcc: String, amount: String, category: String, payee: String, id: String, )</ID>
<ID>LoopWithTooManyJumpStatements:PayeeSuggestor.kt$PayeeSuggestor$for (j in start..end) { val c2 = s2[j] if (c1 != c2 || s2Consumed[j]) continue s2Consumed[j] = true matches += 1 if (j &lt; s2MatchIndex) transpositions += 1 s2MatchIndex = j break }</ID>
<ID>MagicNumber:CallbackQueryHandler.kt$CallbackQueryHandler$3</ID>
Expand All @@ -13,7 +13,6 @@
<ID>MagicNumber:PayeeSuggestor.kt$PayeeSuggestor$0.8</ID>
<ID>MagicNumber:PayeeSuggestor.kt$PayeeSuggestor$3.0</ID>
<ID>MagicNumber:Util.kt$10.0</ID>
<ID>NewLineAtEndOfFile:CurrencyAsStringSerializer.kt$com.github.smaugfm.serializers.CurrencyAsStringSerializer.kt</ID>
<ID>ReturnCount:PayeeSuggestor.kt$PayeeSuggestor$private fun jaroSimilarity(s1: String, s2: String): Double</ID>
</CurrentIssues>
</SmellBaseline>
5 changes: 2 additions & 3 deletions src/main/kotlin/com/github/smaugfm/YnabMono.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import com.github.ajalt.clikt.parameters.options.required
import com.github.ajalt.clikt.parameters.types.int
import com.github.smaugfm.events.EventDispatcher
import com.github.smaugfm.mono.MonoApi
import com.github.smaugfm.mono.MonoApi.Companion.setupWebhook
import com.github.smaugfm.mono.MonoApi.Companion.setupWebhookAll
import com.github.smaugfm.settings.Settings
import com.github.smaugfm.telegram.TelegramApi
import com.github.smaugfm.telegram.handlers.TelegramHandler
Expand Down Expand Up @@ -48,14 +48,13 @@ class YnabMono : CliktCommand() {
val telegramApi = TelegramApi(
settings.telegramBotUsername,
settings.telegramBotToken,
settings.mappings.getTelegramChatIds(),
)
logger.info("Created telegram api.")
val ynabApi = YnabApi(settings.ynabToken, settings.ynabBudgetId)
logger.info("Created ynab api.")

if (!dontSetWebhook) {
monoApis.setupWebhook(monoWebhookUrl, monoWebhookPort ?: monoWebhookUrl.port)
monoApis.setupWebhookAll(monoWebhookUrl, monoWebhookPort ?: monoWebhookUrl.port)
logger.info("Mono webhook setup successful. $monoWebhookUrl")
} else {
logger.info("Skipping mono webhook setup.")
Expand Down
12 changes: 5 additions & 7 deletions src/main/kotlin/com/github/smaugfm/events/Event.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.smaugfm.events

import com.elbekD.bot.types.CallbackQuery
import com.elbekD.bot.types.Message
import com.github.smaugfm.mono.MonoWebHookResponseData
import com.github.smaugfm.telegram.TransactionActionType
Expand All @@ -12,7 +13,7 @@ sealed class Event {

sealed class Ynab : Event() {
data class TransactionAction(
val type: TransactionActionType
val type: TransactionActionType,
) : Ynab(), IEvent<YnabTransactionDetail>
}

Expand All @@ -22,11 +23,8 @@ sealed class Event {
val transaction: YnabTransactionDetail,
) : Telegram(), UnitEvent

data class CallbackQueryReceived(
val callbackQueryId: String,
val data: String,
val message: Message
) :
Telegram(), UnitEvent
data class CallbackQueryReceived(val callbackQuery: CallbackQuery) : Telegram(), UnitEvent
data class RestartCommandReceived(val message: Message, val args: String?) : Telegram(), UnitEvent
data class StopCommandReceived(val message: Message, val args: String?) : Telegram(), UnitEvent
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/com/github/smaugfm/events/models.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ interface IEventsHandlerRegistrar {
fun registerEvents(builder: HandlersBuilder)
}

open class CompositeHandler(val handlers: Collection<IEventsHandlerRegistrar>) : IEventsHandlerRegistrar {
override fun registerEvents(builder: HandlersBuilder) {
handlers.forEach { it.registerEvents(builder) }
}
}

abstract class Handler : IEventsHandlerRegistrar {
final override fun registerEvents(builder: HandlersBuilder) {
builder.apply {
registerHandlerFunctions()
}
}

abstract fun HandlersBuilder.registerHandlerFunctions()
}

interface IEvent<out T>

typealias UnitEvent = IEvent<Unit>
Expand Down
18 changes: 12 additions & 6 deletions src/main/kotlin/com/github/smaugfm/mono/MonoApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import io.ktor.client.request.header
import io.ktor.client.request.post
import io.ktor.client.statement.readText
import io.ktor.features.ContentNegotiation
import io.ktor.features.origin
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.request.receive
Expand Down Expand Up @@ -67,7 +68,7 @@ class MonoApi(private val token: String) {

val waitForWebhook = CompletableDeferred<Unit>()
val json = defaultSerializer()
val server = embeddedServer(Netty, port = port) {
val tempServer = embeddedServer(Netty, port = port) {
routing {
get(url.path) {
call.response.status(HttpStatusCode.OK)
Expand All @@ -78,7 +79,7 @@ class MonoApi(private val token: String) {
}
}
logger.info("Starting webhook setup server...")
server.start(wait = false)
tempServer.start(wait = false)

val statusString = try {
httpClient.post<String>(url("personal/webhook")) {
Expand All @@ -89,7 +90,7 @@ class MonoApi(private val token: String) {
throw e
}
waitForWebhook.await()
server.stop(serverStopGracePeriod, serverStopGracePeriod)
tempServer.stop(serverStopGracePeriod, serverStopGracePeriod)
return Json.decodeFromString(statusString)
}

Expand Down Expand Up @@ -133,19 +134,24 @@ class MonoApi(private val token: String) {
}
routing {
post(webhook.path) {
logger.info("Webhook queried. Uri: ${call.request.uri}")
call.request.origin.host
logger.info(
"Webhook queried. " +
"Host: ${call.request.origin.remoteHost} " +
"Uri: ${call.request.uri}"
)
val response = call.receive<MonoWebhookResponse>()
call.response.status(HttpStatusCode.OK)
dispatcher(Event.Mono.NewStatementReceived(response.data))
}
}
}
return GlobalScope.launch(context) {
server.start(wait = true).let { Unit }
server.start(wait = true)
}
}

suspend fun Collection<MonoApi>.setupWebhook(webhook: URI, port: Int) =
suspend fun Collection<MonoApi>.setupWebhookAll(webhook: URI, port: Int) =
this.forEach { it.setWebHook(webhook, port) }
}
}
19 changes: 10 additions & 9 deletions src/main/kotlin/com/github/smaugfm/telegram/TelegramApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ private val logger = KotlinLogging.logger {}
class TelegramApi(
botUsername: String,
botToken: String,
val allowedChatIds: Set<Int>,
) {
private val bot: Bot =
Bot.createPolling(botUsername, botToken)
Expand Down Expand Up @@ -51,7 +50,7 @@ class TelegramApi(
text: String,
parseMode: String? = null,
disableWebPagePreview: Boolean? = null,
markup: InlineKeyboardMarkup? = null
markup: InlineKeyboardMarkup? = null,
) {
bot.editMessageText(
chatId,
Expand All @@ -76,13 +75,15 @@ class TelegramApi(
): Job {
bot.onCallbackQuery {
logger.info("Received callbackQuery.\n\t$it")
val chatId = it.from.id
if (chatId !in allowedChatIds)
return@onCallbackQuery

it.data?.let { data ->
dispatcher(Event.Telegram.CallbackQueryReceived(it.id, data, it.message!!))
} ?: logger.error("Received callback query without callback_data.\n$it")
dispatcher(Event.Telegram.CallbackQueryReceived(it))
}
bot.onCommand("/restart") { msg, args ->
logger.info("Received message.\n\t$msg")
dispatcher(Event.Telegram.RestartCommandReceived(msg, args))
}
bot.onCommand("/stop") { msg, args ->
logger.info("Received message.\n\t$msg")
dispatcher(Event.Telegram.StopCommandReceived(msg, args))
}

return GlobalScope.launch(context) { bot.start() }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.github.smaugfm.telegram.handlers

import com.elbekD.bot.types.CallbackQuery
import com.elbekD.bot.types.InlineKeyboardMarkup
import com.elbekD.bot.types.Message
import com.elbekD.bot.types.MessageEntity
import com.github.smaugfm.events.Event
import com.github.smaugfm.events.Handler
import com.github.smaugfm.events.HandlersBuilder
import com.github.smaugfm.events.IEventDispatcher
import com.github.smaugfm.events.IEventsHandlerRegistrar
import com.github.smaugfm.settings.Mappings
import com.github.smaugfm.telegram.TelegramApi
import com.github.smaugfm.telegram.TransactionActionType
Expand All @@ -20,37 +21,46 @@ private val logger = KotlinLogging.logger {}
class CallbackQueryHandler(
private val telegram: TelegramApi,
val mappings: Mappings,
) : IEventsHandlerRegistrar {
override fun registerEvents(builder: HandlersBuilder) {
builder.apply {
registerUnit(this@CallbackQueryHandler::handle)
}
) : Handler() {

override fun HandlersBuilder.registerHandlerFunctions() {
registerUnit(this@CallbackQueryHandler::handle)
}

suspend fun handle(
dispatch: IEventDispatcher,
event: Event.Telegram.CallbackQueryReceived,
) {
val type = TransactionActionType.deserialize(event.data, event.message)
val callbackQuery = event.callbackQuery
if (callbackQuery.from.id !in mappings.getTelegramChatIds()) {
logger.warn("Received Telegram callbackQuery from unknown chatId: ${callbackQuery.from.id}")
return
}

val (callbackQueryId, data, message) =
extractFromCallbackQuery(callbackQuery) ?: return

val type = TransactionActionType.deserialize(data, message)
?: return Unit.also {
telegram.answerCallbackQuery(
event.callbackQueryId,
callbackQueryId,
TelegramHandler.UNKNOWN_ERROR_MSG
)
}

logger.info("Found callbackQuery action type $type")

val updatedTransaction = dispatch(Event.Ynab.TransactionAction(type)).also {
telegram.answerCallbackQuery(event.callbackQueryId)
telegram.answerCallbackQuery(callbackQueryId)
}

val updatedText = updateHTMLStatementMessage(updatedTransaction, event.message)
val updatedMarkup = updateMarkupKeyboard(type, event.message.reply_markup!!)
val updatedText = updateHTMLStatementMessage(updatedTransaction, message)
val updatedMarkup = updateMarkupKeyboard(type, message.reply_markup!!)

if (stripHTMLTagsFromMessage(updatedText) != event.message.text ||
updatedMarkup != event.message.reply_markup
if (stripHTMLTagsFromMessage(updatedText) != message.text ||
updatedMarkup != message.reply_markup
) {
with(event.message) {
with(message) {
telegram.editMessage(
chat.id,
message_id,
Expand All @@ -64,7 +74,7 @@ class CallbackQueryHandler(

private fun updateHTMLStatementMessage(
updatedTransaction: YnabTransactionDetail,
oldMessage: Message
oldMessage: Message,
): String {
val oldText = oldMessage.text!!
val oldTextLines = oldText.split("\n").filter { it.isNotBlank() }
Expand All @@ -85,6 +95,18 @@ class CallbackQueryHandler(
)
}

private fun extractFromCallbackQuery(callbackQuery: CallbackQuery): Triple<String, String, Message>? {
val callbackQueryId = callbackQuery.id
val data = callbackQuery.data.takeUnless { it.isNullOrBlank() }
?: logger.warn("Received Telegram callbackQuery with empty data.\n$callbackQuery")
.let { return null }
val message =
callbackQuery.message ?: logger.warn("Received Telegram callbacQuery with empty message")
.let { return null }

return Triple(callbackQueryId, data, message)
}

private fun pressedButtons(oldKeyboard: InlineKeyboardMarkup): Set<KClass<out TransactionActionType>> =
oldKeyboard
.inline_keyboard
Expand All @@ -98,7 +120,7 @@ class CallbackQueryHandler(

private fun updateMarkupKeyboard(
type: TransactionActionType,
oldKeyboard: InlineKeyboardMarkup
oldKeyboard: InlineKeyboardMarkup,
): InlineKeyboardMarkup =
formatInlineKeyboard(pressedButtons(oldKeyboard) + type::class)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.github.smaugfm.telegram.handlers

import com.github.smaugfm.events.Event
import com.github.smaugfm.events.Handler
import com.github.smaugfm.events.HandlersBuilder
import mu.KotlinLogging
import kotlin.system.exitProcess

private val logger = KotlinLogging.logger {}

class MessagesHandler : Handler() {
override fun HandlersBuilder.registerHandlerFunctions() {
registerUnit(this@MessagesHandler::handleRestart)
registerUnit(this@MessagesHandler::handleStop)
}

private fun handleRestart(event: Event.Telegram.RestartCommandReceived) {
logger.info("Exiting with exit code 1 due to restart command.")
exitProcess(1)
}

private fun handleStop(event: Event.Telegram.StopCommandReceived) {
logger.info("Exiting with exit code 0 due to stop command.")
exitProcess(0)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.smaugfm.telegram.handlers

import com.github.smaugfm.events.Event
import com.github.smaugfm.events.Handler
import com.github.smaugfm.events.HandlersBuilder
import com.github.smaugfm.events.IEventsHandlerRegistrar
import com.github.smaugfm.settings.Mappings
import com.github.smaugfm.telegram.TelegramApi
import mu.KotlinLogging
Expand All @@ -11,11 +11,9 @@ private val logger = KotlinLogging.logger {}
class SendStatementMessageHandler(
private val telegram: TelegramApi,
val mappings: Mappings,
) : IEventsHandlerRegistrar {
override fun registerEvents(builder: HandlersBuilder) {
builder.apply {
registerUnit(this@SendStatementMessageHandler::handle)
}
) : Handler() {
override fun HandlersBuilder.registerHandlerFunctions() {
registerUnit(this@SendStatementMessageHandler::handle)
}

suspend fun handle(
Expand Down
Loading

0 comments on commit ba13bd4

Please sign in to comment.