Skip to content

Commit ba13bd4

Browse files
committed
v0.2.2
1 parent 3f63f60 commit ba13bd4

File tree

13 files changed

+145
-72
lines changed

13 files changed

+145
-72
lines changed

build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ plugins {
1010
}
1111

1212
group = "com.github.smaugfm"
13-
version = "0.2.1-alpha"
13+
version = "0.2.2-alpha"
1414

1515
val myMavenRepoReadUrl: String by project
1616
val myMavenRepoReadUsername: String by project

detektBaseline.xml

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<CurrentIssues>
55
<ID>ComplexMethod:PayeeSuggestor.kt$PayeeSuggestor$private fun jaroSimilarity(s1: String, s2: String): Double</ID>
66
<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>
7-
<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>
7+
<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>
88
<ID>LongParameterList:Util.kt$( description: String, mcc: String, amount: String, category: String, payee: String, id: String, )</ID>
99
<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>
1010
<ID>MagicNumber:CallbackQueryHandler.kt$CallbackQueryHandler$3</ID>
@@ -13,7 +13,6 @@
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>NewLineAtEndOfFile:CurrencyAsStringSerializer.kt$com.github.smaugfm.serializers.CurrencyAsStringSerializer.kt</ID>
1716
<ID>ReturnCount:PayeeSuggestor.kt$PayeeSuggestor$private fun jaroSimilarity(s1: String, s2: String): Double</ID>
1817
</CurrentIssues>
1918
</SmellBaseline>

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.github.ajalt.clikt.parameters.options.required
99
import com.github.ajalt.clikt.parameters.types.int
1010
import com.github.smaugfm.events.EventDispatcher
1111
import com.github.smaugfm.mono.MonoApi
12-
import com.github.smaugfm.mono.MonoApi.Companion.setupWebhook
12+
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
@@ -48,14 +48,13 @@ class YnabMono : CliktCommand() {
4848
val telegramApi = TelegramApi(
4949
settings.telegramBotUsername,
5050
settings.telegramBotToken,
51-
settings.mappings.getTelegramChatIds(),
5251
)
5352
logger.info("Created telegram api.")
5453
val ynabApi = YnabApi(settings.ynabToken, settings.ynabBudgetId)
5554
logger.info("Created ynab api.")
5655

5756
if (!dontSetWebhook) {
58-
monoApis.setupWebhook(monoWebhookUrl, monoWebhookPort ?: monoWebhookUrl.port)
57+
monoApis.setupWebhookAll(monoWebhookUrl, monoWebhookPort ?: monoWebhookUrl.port)
5958
logger.info("Mono webhook setup successful. $monoWebhookUrl")
6059
} else {
6160
logger.info("Skipping mono webhook setup.")
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.smaugfm.events
22

3+
import com.elbekD.bot.types.CallbackQuery
34
import com.elbekD.bot.types.Message
45
import com.github.smaugfm.mono.MonoWebHookResponseData
56
import com.github.smaugfm.telegram.TransactionActionType
@@ -12,7 +13,7 @@ sealed class Event {
1213

1314
sealed class Ynab : Event() {
1415
data class TransactionAction(
15-
val type: TransactionActionType
16+
val type: TransactionActionType,
1617
) : Ynab(), IEvent<YnabTransactionDetail>
1718
}
1819

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

25-
data class CallbackQueryReceived(
26-
val callbackQueryId: String,
27-
val data: String,
28-
val message: Message
29-
) :
30-
Telegram(), UnitEvent
26+
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
3129
}
3230
}

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

+16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@ interface IEventsHandlerRegistrar {
88
fun registerEvents(builder: HandlersBuilder)
99
}
1010

11+
open class CompositeHandler(val handlers: Collection<IEventsHandlerRegistrar>) : IEventsHandlerRegistrar {
12+
override fun registerEvents(builder: HandlersBuilder) {
13+
handlers.forEach { it.registerEvents(builder) }
14+
}
15+
}
16+
17+
abstract class Handler : IEventsHandlerRegistrar {
18+
final override fun registerEvents(builder: HandlersBuilder) {
19+
builder.apply {
20+
registerHandlerFunctions()
21+
}
22+
}
23+
24+
abstract fun HandlersBuilder.registerHandlerFunctions()
25+
}
26+
1127
interface IEvent<out T>
1228

1329
typealias UnitEvent = IEvent<Unit>

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

+12-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.ktor.client.request.header
1515
import io.ktor.client.request.post
1616
import io.ktor.client.statement.readText
1717
import io.ktor.features.ContentNegotiation
18+
import io.ktor.features.origin
1819
import io.ktor.http.ContentType
1920
import io.ktor.http.HttpStatusCode
2021
import io.ktor.request.receive
@@ -67,7 +68,7 @@ class MonoApi(private val token: String) {
6768

6869
val waitForWebhook = CompletableDeferred<Unit>()
6970
val json = defaultSerializer()
70-
val server = embeddedServer(Netty, port = port) {
71+
val tempServer = embeddedServer(Netty, port = port) {
7172
routing {
7273
get(url.path) {
7374
call.response.status(HttpStatusCode.OK)
@@ -78,7 +79,7 @@ class MonoApi(private val token: String) {
7879
}
7980
}
8081
logger.info("Starting webhook setup server...")
81-
server.start(wait = false)
82+
tempServer.start(wait = false)
8283

8384
val statusString = try {
8485
httpClient.post<String>(url("personal/webhook")) {
@@ -89,7 +90,7 @@ class MonoApi(private val token: String) {
8990
throw e
9091
}
9192
waitForWebhook.await()
92-
server.stop(serverStopGracePeriod, serverStopGracePeriod)
93+
tempServer.stop(serverStopGracePeriod, serverStopGracePeriod)
9394
return Json.decodeFromString(statusString)
9495
}
9596

@@ -133,19 +134,24 @@ class MonoApi(private val token: String) {
133134
}
134135
routing {
135136
post(webhook.path) {
136-
logger.info("Webhook queried. Uri: ${call.request.uri}")
137+
call.request.origin.host
138+
logger.info(
139+
"Webhook queried. " +
140+
"Host: ${call.request.origin.remoteHost} " +
141+
"Uri: ${call.request.uri}"
142+
)
137143
val response = call.receive<MonoWebhookResponse>()
138144
call.response.status(HttpStatusCode.OK)
139145
dispatcher(Event.Mono.NewStatementReceived(response.data))
140146
}
141147
}
142148
}
143149
return GlobalScope.launch(context) {
144-
server.start(wait = true).let { Unit }
150+
server.start(wait = true)
145151
}
146152
}
147153

148-
suspend fun Collection<MonoApi>.setupWebhook(webhook: URI, port: Int) =
154+
suspend fun Collection<MonoApi>.setupWebhookAll(webhook: URI, port: Int) =
149155
this.forEach { it.setWebHook(webhook, port) }
150156
}
151157
}

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

+10-9
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ private val logger = KotlinLogging.logger {}
1717
class TelegramApi(
1818
botUsername: String,
1919
botToken: String,
20-
val allowedChatIds: Set<Int>,
2120
) {
2221
private val bot: Bot =
2322
Bot.createPolling(botUsername, botToken)
@@ -51,7 +50,7 @@ class TelegramApi(
5150
text: String,
5251
parseMode: String? = null,
5352
disableWebPagePreview: Boolean? = null,
54-
markup: InlineKeyboardMarkup? = null
53+
markup: InlineKeyboardMarkup? = null,
5554
) {
5655
bot.editMessageText(
5756
chatId,
@@ -76,13 +75,15 @@ class TelegramApi(
7675
): Job {
7776
bot.onCallbackQuery {
7877
logger.info("Received callbackQuery.\n\t$it")
79-
val chatId = it.from.id
80-
if (chatId !in allowedChatIds)
81-
return@onCallbackQuery
82-
83-
it.data?.let { data ->
84-
dispatcher(Event.Telegram.CallbackQueryReceived(it.id, data, it.message!!))
85-
} ?: logger.error("Received callback query without callback_data.\n$it")
78+
dispatcher(Event.Telegram.CallbackQueryReceived(it))
79+
}
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))
8687
}
8788

8889
return GlobalScope.launch(context) { bot.start() }

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

+38-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package com.github.smaugfm.telegram.handlers
22

3+
import com.elbekD.bot.types.CallbackQuery
34
import com.elbekD.bot.types.InlineKeyboardMarkup
45
import com.elbekD.bot.types.Message
56
import com.elbekD.bot.types.MessageEntity
67
import com.github.smaugfm.events.Event
8+
import com.github.smaugfm.events.Handler
79
import com.github.smaugfm.events.HandlersBuilder
810
import com.github.smaugfm.events.IEventDispatcher
9-
import com.github.smaugfm.events.IEventsHandlerRegistrar
1011
import com.github.smaugfm.settings.Mappings
1112
import com.github.smaugfm.telegram.TelegramApi
1213
import com.github.smaugfm.telegram.TransactionActionType
@@ -20,37 +21,46 @@ private val logger = KotlinLogging.logger {}
2021
class CallbackQueryHandler(
2122
private val telegram: TelegramApi,
2223
val mappings: Mappings,
23-
) : IEventsHandlerRegistrar {
24-
override fun registerEvents(builder: HandlersBuilder) {
25-
builder.apply {
26-
registerUnit(this@CallbackQueryHandler::handle)
27-
}
24+
) : Handler() {
25+
26+
override fun HandlersBuilder.registerHandlerFunctions() {
27+
registerUnit(this@CallbackQueryHandler::handle)
2828
}
2929

3030
suspend fun handle(
3131
dispatch: IEventDispatcher,
3232
event: Event.Telegram.CallbackQueryReceived,
3333
) {
34-
val type = TransactionActionType.deserialize(event.data, event.message)
34+
val callbackQuery = event.callbackQuery
35+
if (callbackQuery.from.id !in mappings.getTelegramChatIds()) {
36+
logger.warn("Received Telegram callbackQuery from unknown chatId: ${callbackQuery.from.id}")
37+
return
38+
}
39+
40+
val (callbackQueryId, data, message) =
41+
extractFromCallbackQuery(callbackQuery) ?: return
42+
43+
val type = TransactionActionType.deserialize(data, message)
3544
?: return Unit.also {
3645
telegram.answerCallbackQuery(
37-
event.callbackQueryId,
46+
callbackQueryId,
3847
TelegramHandler.UNKNOWN_ERROR_MSG
3948
)
4049
}
50+
4151
logger.info("Found callbackQuery action type $type")
4252

4353
val updatedTransaction = dispatch(Event.Ynab.TransactionAction(type)).also {
44-
telegram.answerCallbackQuery(event.callbackQueryId)
54+
telegram.answerCallbackQuery(callbackQueryId)
4555
}
4656

47-
val updatedText = updateHTMLStatementMessage(updatedTransaction, event.message)
48-
val updatedMarkup = updateMarkupKeyboard(type, event.message.reply_markup!!)
57+
val updatedText = updateHTMLStatementMessage(updatedTransaction, message)
58+
val updatedMarkup = updateMarkupKeyboard(type, message.reply_markup!!)
4959

50-
if (stripHTMLTagsFromMessage(updatedText) != event.message.text ||
51-
updatedMarkup != event.message.reply_markup
60+
if (stripHTMLTagsFromMessage(updatedText) != message.text ||
61+
updatedMarkup != message.reply_markup
5262
) {
53-
with(event.message) {
63+
with(message) {
5464
telegram.editMessage(
5565
chat.id,
5666
message_id,
@@ -64,7 +74,7 @@ class CallbackQueryHandler(
6474

6575
private fun updateHTMLStatementMessage(
6676
updatedTransaction: YnabTransactionDetail,
67-
oldMessage: Message
77+
oldMessage: Message,
6878
): String {
6979
val oldText = oldMessage.text!!
7080
val oldTextLines = oldText.split("\n").filter { it.isNotBlank() }
@@ -85,6 +95,18 @@ class CallbackQueryHandler(
8595
)
8696
}
8797

98+
private fun extractFromCallbackQuery(callbackQuery: CallbackQuery): Triple<String, String, Message>? {
99+
val callbackQueryId = callbackQuery.id
100+
val data = callbackQuery.data.takeUnless { it.isNullOrBlank() }
101+
?: logger.warn("Received Telegram callbackQuery with empty data.\n$callbackQuery")
102+
.let { return null }
103+
val message =
104+
callbackQuery.message ?: logger.warn("Received Telegram callbacQuery with empty message")
105+
.let { return null }
106+
107+
return Triple(callbackQueryId, data, message)
108+
}
109+
88110
private fun pressedButtons(oldKeyboard: InlineKeyboardMarkup): Set<KClass<out TransactionActionType>> =
89111
oldKeyboard
90112
.inline_keyboard
@@ -98,7 +120,7 @@ class CallbackQueryHandler(
98120

99121
private fun updateMarkupKeyboard(
100122
type: TransactionActionType,
101-
oldKeyboard: InlineKeyboardMarkup
123+
oldKeyboard: InlineKeyboardMarkup,
102124
): InlineKeyboardMarkup =
103125
formatInlineKeyboard(pressedButtons(oldKeyboard) + type::class)
104126
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 mu.KotlinLogging
7+
import kotlin.system.exitProcess
8+
9+
private val logger = KotlinLogging.logger {}
10+
11+
class MessagesHandler : Handler() {
12+
override fun HandlersBuilder.registerHandlerFunctions() {
13+
registerUnit(this@MessagesHandler::handleRestart)
14+
registerUnit(this@MessagesHandler::handleStop)
15+
}
16+
17+
private fun handleRestart(event: Event.Telegram.RestartCommandReceived) {
18+
logger.info("Exiting with exit code 1 due to restart command.")
19+
exitProcess(1)
20+
}
21+
22+
private fun handleStop(event: Event.Telegram.StopCommandReceived) {
23+
logger.info("Exiting with exit code 0 due to stop command.")
24+
exitProcess(0)
25+
}
26+
}

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

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.github.smaugfm.telegram.handlers
22

33
import com.github.smaugfm.events.Event
4+
import com.github.smaugfm.events.Handler
45
import com.github.smaugfm.events.HandlersBuilder
5-
import com.github.smaugfm.events.IEventsHandlerRegistrar
66
import com.github.smaugfm.settings.Mappings
77
import com.github.smaugfm.telegram.TelegramApi
88
import mu.KotlinLogging
@@ -11,11 +11,9 @@ private val logger = KotlinLogging.logger {}
1111
class SendStatementMessageHandler(
1212
private val telegram: TelegramApi,
1313
val mappings: Mappings,
14-
) : IEventsHandlerRegistrar {
15-
override fun registerEvents(builder: HandlersBuilder) {
16-
builder.apply {
17-
registerUnit(this@SendStatementMessageHandler::handle)
18-
}
14+
) : Handler() {
15+
override fun HandlersBuilder.registerHandlerFunctions() {
16+
registerUnit(this@SendStatementMessageHandler::handle)
1917
}
2018

2119
suspend fun handle(

0 commit comments

Comments
 (0)