Skip to content

Commit d118d9b

Browse files
committed
error handling
1 parent ba13bd4 commit d118d9b

File tree

15 files changed

+134
-109
lines changed

15 files changed

+134
-109
lines changed

.idea/codeStyles/Project.xml

-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

detektBaseline.xml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<ID>MagicNumber:PayeeSuggestor.kt$PayeeSuggestor$0.8</ID>
1414
<ID>MagicNumber:PayeeSuggestor.kt$PayeeSuggestor$3.0</ID>
1515
<ID>MagicNumber:Util.kt$10.0</ID>
16+
<ID>ReturnCount:CallbackQueryHandler.kt$CallbackQueryHandler$suspend fun handle( dispatch: IEventDispatcher, event: Event.Telegram.CallbackQueryReceived, )</ID>
1617
<ID>ReturnCount:PayeeSuggestor.kt$PayeeSuggestor$private fun jaroSimilarity(s1: String, s2: String): Double</ID>
1718
</CurrentIssues>
1819
</SmellBaseline>

src/main/kotlin/com/github/smaugfm/YnabMono.kt

+41-43
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.github.smaugfm.mono.MonoApi.Companion.setupWebhookAll
1313
import com.github.smaugfm.settings.Settings
1414
import com.github.smaugfm.telegram.TelegramApi
1515
import com.github.smaugfm.telegram.handlers.TelegramHandler
16+
import com.github.smaugfm.telegram.handlers.errorHandler
1617
import com.github.smaugfm.ynab.YnabApi
1718
import com.github.smaugfm.ynab.YnabHandler
1819
import kotlinx.coroutines.asCoroutineDispatcher
@@ -25,7 +26,7 @@ import java.util.concurrent.Executors
2526
private val logger = KotlinLogging.logger {}
2627

2728
class YnabMono : CliktCommand() {
28-
val dontSetWebhook by option().flag(default = false)
29+
val setWebhook by option().flag(default = true)
2930
val monoWebhookUrl by option().convert { URI(it) }.required()
3031
val monoWebhookPort by option().int()
3132
val settings by option("--settings").convert { Settings.load(Paths.get(it)) }.default(Settings.loadDefault())
@@ -38,56 +39,53 @@ class YnabMono : CliktCommand() {
3839
"${this::settings.name}: $settings\n\t" +
3940
"${this::monoWebhookUrl.name}: $monoWebhookUrl\n\t" +
4041
"${this::monoWebhookPort.name}: $monoWebhookPort\n\t" +
41-
"${this::dontSetWebhook.name}: $dontSetWebhook",
42+
"${this::setWebhook.name}: $setWebhook",
4243
)
4344

44-
try {
45-
runBlocking {
46-
val monoApis = settings.monoTokens.map(::MonoApi)
47-
logger.info("Created monobank apis. ")
48-
val telegramApi = TelegramApi(
49-
settings.telegramBotUsername,
50-
settings.telegramBotToken,
51-
)
52-
logger.info("Created telegram api.")
53-
val ynabApi = YnabApi(settings.ynabToken, settings.ynabBudgetId)
54-
logger.info("Created ynab api.")
45+
runBlocking {
46+
val monoApis = settings.monoTokens.map(::MonoApi)
47+
logger.info("Created monobank apis. ")
48+
val telegramApi = TelegramApi(
49+
settings.telegramBotUsername,
50+
settings.telegramBotToken,
51+
)
52+
logger.info("Created telegram api.")
53+
val ynabApi = YnabApi(settings.ynabToken, settings.ynabBudgetId)
54+
logger.info("Created ynab api.")
5555

56-
if (!dontSetWebhook) {
57-
monoApis.setupWebhookAll(monoWebhookUrl, monoWebhookPort ?: monoWebhookUrl.port)
58-
logger.info("Mono webhook setup successful. $monoWebhookUrl")
59-
} else {
60-
logger.info("Skipping mono webhook setup.")
61-
}
56+
if (setWebhook) {
57+
logger.info("Setting up mono webhooks.")
58+
monoApis.setupWebhookAll(monoWebhookUrl, monoWebhookPort ?: monoWebhookUrl.port)
59+
} else {
60+
logger.info("Skipping mono webhook setup.")
61+
}
6262

63-
val dispatcher = EventDispatcher(
64-
YnabHandler(ynabApi, settings.mappings),
65-
TelegramHandler(telegramApi, settings.mappings)
66-
)
63+
val dispatcher = EventDispatcher(
64+
{ dispatcher, _, _ ->
65+
errorHandler(dispatcher, settings.mappings)
66+
},
67+
YnabHandler(ynabApi, settings.mappings),
68+
TelegramHandler(telegramApi, settings.mappings)
69+
)
6770

68-
logger.info("Events dispatcher created.")
71+
logger.info("Events dispatcher created.")
6972

70-
val telegramServerJob = telegramApi
71-
.startServer(serversCoroutinesContext, dispatcher)
73+
val telegramServerJob = telegramApi
74+
.startServer(serversCoroutinesContext, dispatcher)
7275

73-
logger.info("Telegram bot started.")
74-
val monoWebhookServer =
75-
MonoApi.startMonoWebhookServerAsync(
76-
serversCoroutinesContext,
77-
monoWebhookUrl,
78-
monoWebhookPort ?: monoWebhookUrl.port,
79-
dispatcher
80-
)
81-
logger.info("Mono webhook listener started.")
82-
logger.info("Setup completed. Listening...\n")
76+
logger.info("Telegram bot started.")
77+
val monoWebhookServer =
78+
MonoApi.startMonoWebhookServerAsync(
79+
serversCoroutinesContext,
80+
monoWebhookUrl,
81+
monoWebhookPort ?: monoWebhookUrl.port,
82+
dispatcher
83+
)
84+
logger.info("Mono webhook listener started.")
85+
logger.info("Setup completed. Listening...\n")
8386

84-
telegramServerJob.join()
85-
monoWebhookServer.join()
86-
}
87-
} catch (e: Throwable) {
88-
logger.error(e) {
89-
"Unhandled exception"
90-
}
87+
telegramServerJob.join()
88+
monoWebhookServer.join()
9189
}
9290
}
9391
}

src/main/kotlin/com/github/smaugfm/events/Event.kt

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.github.smaugfm.events
22

33
import com.elbekD.bot.types.CallbackQuery
4-
import com.elbekD.bot.types.Message
4+
import com.elbekD.bot.types.ReplyKeyboard
55
import com.github.smaugfm.mono.MonoWebHookResponseData
66
import com.github.smaugfm.telegram.TransactionActionType
77
import com.github.smaugfm.ynab.YnabTransactionDetail
@@ -22,9 +22,11 @@ sealed class Event {
2222
val mono: MonoWebHookResponseData,
2323
val transaction: YnabTransactionDetail,
2424
) : Telegram(), UnitEvent
25-
2625
data class CallbackQueryReceived(val callbackQuery: CallbackQuery) : Telegram(), UnitEvent
27-
data class RestartCommandReceived(val message: Message, val args: String?) : Telegram(), UnitEvent
28-
data class StopCommandReceived(val message: Message, val args: String?) : Telegram(), UnitEvent
26+
data class SendHTMLMessage(
27+
val chatId: Int,
28+
val msg: String,
29+
val keyboard: ReplyKeyboard?
30+
) : Telegram(), UnitEvent
2931
}
3032
}

src/main/kotlin/com/github/smaugfm/events/EventDispatcher.kt

+10-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import mu.KotlinLogging
55
private val logger = KotlinLogging.logger {}
66

77
open class EventDispatcher(
8+
private val errorHandler: suspend (dispatcher: IEventDispatcher, event: IEvent<*>, exception: Throwable) -> Unit,
89
vararg registrars: IEventsHandlerRegistrar,
910
) : IEventDispatcher {
1011
private val handlers =
@@ -14,14 +15,20 @@ open class EventDispatcher(
1415
}
1516
}
1617

17-
override suspend fun <R, E : IEvent<R>> invoke(event: E): R {
18+
override suspend fun <R, E : IEvent<R>> invoke(event: E): R? {
1819
@Suppress("UNCHECKED_CAST")
1920
logger.info("Event dispatched: $event")
2021
val handler = handlers[event.javaClass] as? IEventHandler<R, E>
2122
?: throw IllegalStateException("No handler found for event $event, ${event.javaClass}")
2223

23-
return handler.handle(this, event).also {
24-
logger.info("Event handled: $it")
24+
return try {
25+
handler.handle(this, event).also {
26+
logger.info("Event handled: $it")
27+
}
28+
} catch (e: Throwable) {
29+
logger.error("Error handling event.", e)
30+
errorHandler(this, event, e)
31+
null
2532
}
2633
}
2734
}

src/main/kotlin/com/github/smaugfm/events/models.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,5 @@ interface IEvent<out T>
2929
typealias UnitEvent = IEvent<Unit>
3030

3131
interface IEventDispatcher {
32-
suspend operator fun <R, E : IEvent<R>> invoke(event: E): R
32+
suspend operator fun <R, E : IEvent<R>> invoke(event: E): R?
3333
}

src/main/kotlin/com/github/smaugfm/mono/MonoApi.kt

+9-2
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,14 @@ class MonoApi(private val token: String) {
151151
}
152152
}
153153

154-
suspend fun Collection<MonoApi>.setupWebhookAll(webhook: URI, port: Int) =
155-
this.forEach { it.setWebHook(webhook, port) }
154+
suspend fun Collection<MonoApi>.setupWebhookAll(webhook: URI, port: Int) {
155+
for (it in this) {
156+
try {
157+
it.setWebHook(webhook, port)
158+
} catch (e: Throwable) {
159+
logger.error("Error setting webhook.", e)
160+
}
161+
}
162+
}
156163
}
157164
}

src/main/kotlin/com/github/smaugfm/telegram/TelegramApi.kt

-9
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,6 @@ class TelegramApi(
7777
logger.info("Received callbackQuery.\n\t$it")
7878
dispatcher(Event.Telegram.CallbackQueryReceived(it))
7979
}
80-
bot.onCommand("/restart") { msg, args ->
81-
logger.info("Received message.\n\t$msg")
82-
dispatcher(Event.Telegram.RestartCommandReceived(msg, args))
83-
}
84-
bot.onCommand("/stop") { msg, args ->
85-
logger.info("Received message.\n\t$msg")
86-
dispatcher(Event.Telegram.StopCommandReceived(msg, args))
87-
}
88-
8980
return GlobalScope.launch(context) { bot.start() }
9081
}
9182
}

src/main/kotlin/com/github/smaugfm/telegram/handlers/CallbackQueryHandler.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class CallbackQueryHandler(
5252

5353
val updatedTransaction = dispatch(Event.Ynab.TransactionAction(type)).also {
5454
telegram.answerCallbackQuery(callbackQueryId)
55-
}
55+
} ?: return
5656

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

src/main/kotlin/com/github/smaugfm/telegram/handlers/MessagesHandler.kt

-26
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.github.smaugfm.telegram.handlers
2+
3+
import com.github.smaugfm.events.Event
4+
import com.github.smaugfm.events.Handler
5+
import com.github.smaugfm.events.HandlersBuilder
6+
import com.github.smaugfm.settings.Mappings
7+
import com.github.smaugfm.telegram.TelegramApi
8+
9+
class SendHTMLMessageHandler(
10+
private val telegram: TelegramApi,
11+
val mappings: Mappings,
12+
) : Handler() {
13+
14+
override fun HandlersBuilder.registerHandlerFunctions() {
15+
registerUnit(this@SendHTMLMessageHandler::handle)
16+
}
17+
18+
private suspend fun handle(
19+
event: Event.Telegram.SendHTMLMessage,
20+
) {
21+
val (chatId, msg, markup) = event
22+
23+
telegram.sendMessage(
24+
chatId,
25+
msg,
26+
"HTML",
27+
markup = markup
28+
)
29+
}
30+
}

src/main/kotlin/com/github/smaugfm/telegram/handlers/SendStatementMessageHandler.kt

+9-7
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,34 @@ package com.github.smaugfm.telegram.handlers
33
import com.github.smaugfm.events.Event
44
import com.github.smaugfm.events.Handler
55
import com.github.smaugfm.events.HandlersBuilder
6+
import com.github.smaugfm.events.IEventDispatcher
67
import com.github.smaugfm.settings.Mappings
7-
import com.github.smaugfm.telegram.TelegramApi
88
import mu.KotlinLogging
99

1010
private val logger = KotlinLogging.logger {}
11+
1112
class SendStatementMessageHandler(
12-
private val telegram: TelegramApi,
1313
val mappings: Mappings,
1414
) : Handler() {
1515
override fun HandlersBuilder.registerHandlerFunctions() {
1616
registerUnit(this@SendStatementMessageHandler::handle)
1717
}
1818

1919
suspend fun handle(
20+
dispatcher: IEventDispatcher,
2021
event: Event.Telegram.SendStatementMessage,
2122
) {
2223
val monoResponse = event.mono
2324
val accountCurrency = mappings.getAccountCurrency(event.mono.account)!!
2425
val transaction = event.transaction
2526
val telegramChatId = mappings.getTelegramChatIdAccByMono(monoResponse.account) ?: return
2627

27-
telegram.sendMessage(
28-
telegramChatId,
29-
formatHTMLStatementMessage(accountCurrency, monoResponse.statementItem, transaction),
30-
"HTML",
31-
markup = formatInlineKeyboard(emptySet())
28+
dispatcher(
29+
Event.Telegram.SendHTMLMessage(
30+
telegramChatId,
31+
formatHTMLStatementMessage(accountCurrency, monoResponse.statementItem, transaction),
32+
formatInlineKeyboard(emptySet()),
33+
)
3234
)
3335
}
3436
}

src/main/kotlin/com/github/smaugfm/telegram/handlers/TelegramHandler.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import com.github.smaugfm.settings.Mappings
55
import com.github.smaugfm.telegram.TelegramApi
66

77
class TelegramHandler(
8-
private val telegram: TelegramApi,
8+
telegram: TelegramApi,
99
val mappings: Mappings,
1010
) : CompositeHandler(
1111
listOf(
12-
SendStatementMessageHandler(telegram, mappings),
12+
SendStatementMessageHandler(mappings),
1313
CallbackQueryHandler(telegram, mappings),
14-
MessagesHandler()
14+
SendHTMLMessageHandler(telegram, mappings),
1515
),
1616
) {
1717
companion object {

src/main/kotlin/com/github/smaugfm/telegram/handlers/Util.kt

+20
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package com.github.smaugfm.telegram.handlers
22

33
import com.elbekD.bot.types.InlineKeyboardButton
44
import com.elbekD.bot.types.InlineKeyboardMarkup
5+
import com.github.smaugfm.events.Event
6+
import com.github.smaugfm.events.IEventDispatcher
57
import com.github.smaugfm.mono.MonoStatementItem
8+
import com.github.smaugfm.settings.Mappings
69
import com.github.smaugfm.telegram.TransactionActionType
710
import com.github.smaugfm.telegram.TransactionActionType.Companion.buttonText
811
import com.github.smaugfm.telegram.TransactionActionType.Companion.serialize
@@ -86,3 +89,20 @@ internal inline fun <reified T : TransactionActionType> button(pressed: Set<KCla
8689
callback_data = serialize<T>()
8790
)
8891
}
92+
93+
suspend fun errorHandler(
94+
dispatcher: IEventDispatcher,
95+
mappings: Mappings,
96+
) {
97+
mappings
98+
.getTelegramChatIds()
99+
.forEach { chatId ->
100+
dispatcher(
101+
Event.Telegram.SendHTMLMessage(
102+
chatId,
103+
TelegramHandler.UNKNOWN_ERROR_MSG,
104+
null
105+
)
106+
)
107+
}
108+
}

0 commit comments

Comments
 (0)