From de039c39096e3d2158b088e44ae6c04f83ae6a8a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 26 Feb 2019 09:15:52 +0800 Subject: [PATCH 01/23] start 0.12.0 --- CHANGELOG.md | 2 ++ build.gradle | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2181001752..89cb348c14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # TelegramBotAPI changelog +## 0.12.0 + ## 0.11.0 * Kotlin `1.3.11` -> `1.3.21` diff --git a/build.gradle b/build.gradle index 78c107f9c6..1937b4dfc1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -project.version = "0.11.0" +project.version = "0.12.0" project.group = "com.github.insanusmokrassar" buildscript { From ff2b56b3a9e00534cb0f5a1c8730c0c472852f7f Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 26 Feb 2019 09:45:56 +0800 Subject: [PATCH 02/23] add SetWebhook --- CHANGELOG.md | 5 ++ .../TelegramBotAPI/requests/SetWebhook.kt | 54 +++++++++++++++++++ .../requests/send/media/SendAnimation.kt | 2 +- .../requests/send/media/SendAudio.kt | 2 +- .../requests/send/media/SendDocument.kt | 2 +- .../requests/send/media/SendMediaGroup.kt | 2 +- .../requests/send/media/SendPhoto.kt | 2 +- .../requests/send/media/SendVideo.kt | 2 +- .../requests/send/media/SendVideoNote.kt | 2 +- .../requests/send/media/SendVoice.kt | 2 +- .../requests/send/media/base/Data.kt | 11 ++-- .../requests/send/media/base/DataRequest.kt | 5 ++ .../send/media/base/MultipartRequestImpl.kt | 2 +- .../TelegramBotAPI/types/Common.kt | 4 ++ 14 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/SetWebhook.kt create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/DataRequest.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 89cb348c14..2623171d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## 0.12.0 +* Added `DataRequest` interface which replace `Data` interface +* `MultipartRequestImpl` now use `DataRequest` +* All requests which implements `Data` now implement `DataRequest` +* Added class `SetWebhook` and its factory + ## 0.11.0 * Kotlin `1.3.11` -> `1.3.21` diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/SetWebhook.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/SetWebhook.kt new file mode 100644 index 0000000000..11aed14b52 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/SetWebhook.kt @@ -0,0 +1,54 @@ +package com.github.insanusmokrassar.TelegramBotAPI.requests + +import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.* +import com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.base.* +import com.github.insanusmokrassar.TelegramBotAPI.types.* +import kotlinx.serialization.* +import kotlinx.serialization.internal.BooleanSerializer + +fun SetWebhook( + url: String, + certificate: InputFile, + maxAllowedConnections: Int? = null, + allowedUpdates: List? = null +) : Request { + val data = SetWebhook( + url, + certificate.fileId, + maxAllowedConnections, + allowedUpdates + ) + return when (certificate) { + is FileId -> data + is MultipartFile -> MultipartRequestImpl( + data, + mapOf(certificateField to certificate) + ) + } +} + +@Serializable +data class SetWebhook internal constructor( + @SerialName(urlField) + val url: String, + @SerialName(certificateField) + @Optional + val certificateFile: String? = null, + @SerialName(maxAllowedConnectionsField) + @Optional + val maxAllowedConnections: Int? = null, + @SerialName(allowedUpdatesField) + @Optional + val allowedUpdates: List? = null +) : DataRequest { + override fun method(): String = "setWebhook" + override fun resultSerializer(): KSerializer = BooleanSerializer + + init { + maxAllowedConnections ?.let { + if (it !in allowedConnectionsLength) { + throw IllegalArgumentException("Allowed connection for webhook must be in $allowedConnectionsLength range (but passed $it)") + } + } + } +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendAnimation.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendAnimation.kt index 175a63a7f6..7974b242a9 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendAnimation.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendAnimation.kt @@ -87,7 +87,7 @@ data class SendAnimationData internal constructor( @SerialName(replyMarkupField) @Optional override val replyMarkup: KeyboardMarkup? = null -) : Data, +) : DataRequest, SendMessageRequest, ReplyingMarkupSendMessageRequest, TextableSendMessageRequest, diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendAudio.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendAudio.kt index 9599b3a532..2e086813dd 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendAudio.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendAudio.kt @@ -88,7 +88,7 @@ data class SendAudioData internal constructor( @SerialName(replyMarkupField) @Optional override val replyMarkup: KeyboardMarkup? = null -) : Data, +) : DataRequest, SendMessageRequest, ReplyingMarkupSendMessageRequest, TextableSendMessageRequest, diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendDocument.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendDocument.kt index 55e2c67357..dca1fc11c9 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendDocument.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendDocument.kt @@ -72,7 +72,7 @@ data class SendDocumentData internal constructor( @SerialName(replyMarkupField) @Optional override val replyMarkup: KeyboardMarkup? = null -) : Data, +) : DataRequest, SendMessageRequest, ReplyingMarkupSendMessageRequest, TextableSendMessageRequest, diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendMediaGroup.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendMediaGroup.kt index f13de99995..a945196b74 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendMediaGroup.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendMediaGroup.kt @@ -66,7 +66,7 @@ data class SendMediaGroupData internal constructor( @SerialName(replyToMessageIdField) @Optional override val replyToMessageId: MessageIdentifier? = null -) : Data>, +) : DataRequest>, SendMessageRequest> { @SerialName(mediaField) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendPhoto.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendPhoto.kt index 2d5abe34c7..92336917c0 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendPhoto.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendPhoto.kt @@ -58,7 +58,7 @@ data class SendPhotoData internal constructor( @SerialName(replyMarkupField) @Optional override val replyMarkup: KeyboardMarkup? = null -) : Data, +) : DataRequest, SendMessageRequest, ReplyingMarkupSendMessageRequest, TextableSendMessageRequest diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVideo.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVideo.kt index 884c22c12a..3097bf07c9 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVideo.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVideo.kt @@ -92,7 +92,7 @@ data class SendVideoData internal constructor( @SerialName(replyMarkupField) @Optional override val replyMarkup: KeyboardMarkup? = null -) : Data, +) : DataRequest, SendMessageRequest, ReplyingMarkupSendMessageRequest, TextableSendMessageRequest, diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVideoNote.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVideoNote.kt index 61d876b225..c3199c0c2f 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVideoNote.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVideoNote.kt @@ -82,7 +82,7 @@ data class SendVideoNoteData internal constructor( @SerialName(replyMarkupField) @Optional override val replyMarkup: KeyboardMarkup? = null -) : Data, +) : DataRequest, SendMessageRequest, ReplyingMarkupSendMessageRequest, TextableSendMessageRequest, diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVoice.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVoice.kt index 175135b727..16ee8a3a20 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVoice.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/SendVoice.kt @@ -77,7 +77,7 @@ data class SendVoiceData internal constructor( @SerialName(replyMarkupField) @Optional override val replyMarkup: KeyboardMarkup? = null -) : Data, +) : DataRequest, SendMessageRequest, ReplyingMarkupSendMessageRequest, TextableSendMessageRequest, diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/Data.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/Data.kt index e2bd40e3d6..d8ff64cd37 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/Data.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/Data.kt @@ -1,5 +1,10 @@ package com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.base -import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest - -interface Data : SimpleRequest \ No newline at end of file +@Deprecated( + "Renamed to DataRequest", + ReplaceWith( + "DataRequest", + "com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.base.DataRequest" + ) +) +typealias Data = DataRequest diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/DataRequest.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/DataRequest.kt new file mode 100644 index 0000000000..996e3227ee --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/DataRequest.kt @@ -0,0 +1,5 @@ +package com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.base + +import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest + +interface DataRequest : SimpleRequest \ No newline at end of file diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/MultipartRequestImpl.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/MultipartRequestImpl.kt index bcb68d8306..bc5488a504 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/MultipartRequestImpl.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/MultipartRequestImpl.kt @@ -9,7 +9,7 @@ import kotlinx.serialization.json.JsonObject /** * Will be used as SimpleRequest if */ -class MultipartRequestImpl, F: Files, R: Any>( +class MultipartRequestImpl, F: Files, R: Any>( val data: D, val files: F ) : MultipartRequest { diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt index 49fc39a4df..8f82575c3c 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt @@ -23,6 +23,7 @@ val userProfilePhotosRequestLimit = 0 .. 100 val chatTitleLength = 1 until 255 val chatDescriptionLength = 0 until 256 val inlineResultQueryIdLingth = 1 until 64 +val allowedConnectionsLength = 1 .. 100 val invoiceTitleLimit = 1 until 32 val invoiceDescriptionLimit = 1 until 256 @@ -68,6 +69,8 @@ const val isPersonalField = "is_personal" const val nextOffsetField = "next_offset" const val switchPmTextField = "switch_pm_text" const val switchPmParameterField = "switch_pm_parameter" +const val maxAllowedConnectionsField = "max_connections" +const val allowedUpdatesField = "allowed_updates" const val photoUrlField = "photo_url" @@ -172,6 +175,7 @@ const val pricesField = "prices" const val payloadField = "payload" const val vcardField = "vcard" const val resultsField = "results" +const val certificateField = "certificate" const val pointField = "point" const val xShiftField = "x_shift" From 1520271777f17dcb07844d41882b9c1823317c7f Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 26 Feb 2019 16:01:26 +0800 Subject: [PATCH 03/23] add main webhooks functionality for using with reverse proxy --- CHANGELOG.md | 5 +- build.gradle | 4 + .../utils/extensions/ReceiveChannel.kt | 101 ++++++++++++++ .../utils/extensions/RequestsExecutor.kt | 72 +++------- .../utils/extensions/UpdatesFilter.kt | 105 +++++++++++++++ .../utils/extensions/Webhooks.kt | 127 ++++++++++++++++++ 6 files changed, 359 insertions(+), 55 deletions(-) create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/ReceiveChannel.kt create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2623171d98..cdd5e88750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,14 @@ # TelegramBotAPI changelog -## 0.12.0 +## 0.12.0 Webhooks * Added `DataRequest` interface which replace `Data` interface * `MultipartRequestImpl` now use `DataRequest` * All requests which implements `Data` now implement `DataRequest` * Added class `SetWebhook` and its factory +* Added class `UpdatesFilter` which can help to filter updates by categories +* Added function `accumulateByKey` which work as debounce for keys and send list of received values +* Added webhooks functions and workaround for `Reverse Proxy` mode ## 0.11.0 diff --git a/build.gradle b/build.gradle index 1937b4dfc1..0a44792233 100644 --- a/build.gradle +++ b/build.gradle @@ -34,9 +34,13 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$kotlin_serialisation_runtime_version" implementation "joda-time:joda-time:$joda_time_version" + implementation "io.ktor:ktor-client-core:$ktor_version" implementation "io.ktor:ktor-client-okhttp:$ktor_version" + implementation "io.ktor:ktor-server-core:$ktor_version" + implementation "io.ktor:ktor-server-netty:$ktor_version" + // Use JUnit test framework testImplementation 'junit:junit:4.12' } diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/ReceiveChannel.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/ReceiveChannel.kt new file mode 100644 index 0000000000..0cbc0421a9 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/ReceiveChannel.kt @@ -0,0 +1,101 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel + +private sealed class DebounceAction { + abstract val value: T +} + +private data class AddValue(override val value: T) : DebounceAction() +private data class RemoveJob(override val value: T, val job: Job) : DebounceAction() + +fun ReceiveChannel.debounceByValue( + delayMillis: Long, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + resultBroadcastChannelCapacity: Int = 32 +): ReceiveChannel { + val outChannel = Channel(resultBroadcastChannelCapacity) + val values = HashMap() + + val channel = Channel>(Channel.UNLIMITED) + scope.launch { + for (action in channel) { + when (action) { + is AddValue -> { + val msg = action.value + values[msg] ?.cancel() + lateinit var job: Job + job = launch { + delay(delayMillis) + + outChannel.send(msg) + channel.send(RemoveJob(msg, job)) + } + values[msg] = job + } + is RemoveJob -> if (values[action.value] == action.job) { + values.remove(action.value) + } + } + + } + } + + scope.launch { + for (msg in this@debounceByValue) { + channel.send(AddValue(msg)) + } + } + + return outChannel +} + +typealias AccumulatedValues = Pair> + +fun ReceiveChannel>.accumulateByKey( + delayMillis: Long, + scope: CoroutineScope = CoroutineScope(Dispatchers.Default), + resultBroadcastChannelCapacity: Int = 32 +): ReceiveChannel> { + val outChannel = Channel>(resultBroadcastChannelCapacity) + val values = HashMap>() + val jobs = HashMap() + + val channel = Channel>>(Channel.UNLIMITED) + scope.launch { + for (action in channel) { + val (key, value) = action.value + when (action) { + is AddValue -> { + jobs[key] ?.cancel() + (values[key] ?: mutableListOf().also { values[key] = it }).add(value) + lateinit var job: Job + job = launch { + delay(delayMillis) + + values[key] ?.let { + outChannel.send(key to it) + channel.send(RemoveJob(key to value, job)) + } + } + jobs[key] = job + } + is RemoveJob -> if (values[key] == action.job) { + values.remove(key) + jobs.remove(key) + } + } + + } + } + + scope.launch { + for (msg in this@accumulateByKey) { + channel.send(AddValue(msg)) + } + } + + return outChannel +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt index d44cdb5871..8b1b61026e 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt @@ -114,63 +114,27 @@ fun RequestsExecutor.startGettingOfUpdates( requestsDelayMillis: Long = 1000, scope: CoroutineScope = GlobalScope ): Job { + val filter = UpdatesFilter( + messageCallback, + messageMediaGroupCallback, + editedMessageCallback, + editedMessageMediaGroupCallback, + channelPostCallback, + channelPostMediaGroupCallback, + editedChannelPostCallback, + editedChannelPostMediaGroupCallback, + chosenInlineResultCallback, + inlineQueryCallback, + callbackQueryCallback, + shippingQueryCallback, + preCheckoutQueryCallback + ) return startGettingOfUpdates( requestsDelayMillis, scope, - listOfNotNull( - (messageCallback ?: messageMediaGroupCallback) ?.let { UPDATE_MESSAGE }, - (editedMessageCallback ?: editedMessageMediaGroupCallback) ?.let { UPDATE_EDITED_MESSAGE }, - (channelPostCallback ?: channelPostMediaGroupCallback) ?.let { UPDATE_CHANNEL_POST }, - (editedChannelPostCallback ?: editedChannelPostMediaGroupCallback) ?.let { UPDATE_EDITED_CHANNEL_POST }, - chosenInlineResultCallback ?.let { UPDATE_CHOSEN_INLINE_RESULT }, - inlineQueryCallback ?.let { UPDATE_INLINE_QUERY }, - callbackQueryCallback ?.let { UPDATE_CALLBACK_QUERY }, - shippingQueryCallback ?.let { UPDATE_SHIPPING_QUERY }, - preCheckoutQueryCallback ?.let { UPDATE_PRE_CHECKOUT_QUERY } - ) - ) { update -> - when (update) { - is MessageUpdate -> messageCallback ?.invoke(update) - is List<*> -> when (update.firstOrNull()) { - is MessageUpdate -> update.mapNotNull { it as? MessageUpdate }.let { mappedList -> - messageMediaGroupCallback ?.also { receiver -> - receiver(mappedList) - } ?: messageCallback ?.also { receiver -> - mappedList.forEach { receiver(it) } - } - } - is EditMessageUpdate -> update.mapNotNull { it as? EditMessageUpdate }.let { mappedList -> - editedMessageMediaGroupCallback ?.also { receiver -> - receiver(mappedList) - } ?: editedMessageCallback ?.also { receiver -> - mappedList.forEach { receiver(it) } - } - } - is ChannelPostUpdate -> update.mapNotNull { it as? ChannelPostUpdate }.let { mappedList -> - channelPostMediaGroupCallback ?.also { receiver -> - receiver(mappedList) - } ?: channelPostCallback ?.also { receiver -> - mappedList.forEach { receiver(it) } - } - } - is EditChannelPostUpdate -> update.mapNotNull { it as? EditChannelPostUpdate }.let { mappedList -> - editedChannelPostMediaGroupCallback ?.also { receiver -> - receiver(mappedList) - } ?: editedChannelPostCallback ?.also { receiver -> - mappedList.forEach { receiver(it) } - } - } - } - is EditMessageUpdate -> editedMessageCallback ?.invoke(update) - is ChannelPostUpdate -> channelPostCallback ?.invoke(update) - is EditChannelPostUpdate -> editedChannelPostCallback ?.invoke(update) - is ChosenInlineResultUpdate -> chosenInlineResultCallback ?.invoke(update) - is InlineQueryUpdate -> inlineQueryCallback ?.invoke(update) - is CallbackQueryUpdate -> callbackQueryCallback ?.invoke(update) - is ShippingQueryUpdate -> shippingQueryCallback ?.invoke(update) - is PreCheckoutQueryUpdate -> preCheckoutQueryCallback ?.invoke(update) - } - } + filter.allowedUpdates, + filter.asUpdateReceiver + ) } fun RequestsExecutor.startGettingOfUpdates( diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt new file mode 100644 index 0000000000..7c7ee726c2 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt @@ -0,0 +1,105 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions + +import com.github.insanusmokrassar.TelegramBotAPI.requests.* +import com.github.insanusmokrassar.TelegramBotAPI.types.update.* +import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate + +data class UpdatesFilter( + private val messageCallback: UpdateReceiver? = null, + private val messageMediaGroupCallback: UpdateReceiver>? = null, + private val editedMessageCallback: UpdateReceiver? = null, + private val editedMessageMediaGroupCallback: UpdateReceiver>? = null, + private val channelPostCallback: UpdateReceiver? = null, + private val channelPostMediaGroupCallback: UpdateReceiver>? = null, + private val editedChannelPostCallback: UpdateReceiver? = null, + private val editedChannelPostMediaGroupCallback: UpdateReceiver>? = null, + private val chosenInlineResultCallback: UpdateReceiver? = null, + private val inlineQueryCallback: UpdateReceiver? = null, + private val callbackQueryCallback: UpdateReceiver? = null, + private val shippingQueryCallback: UpdateReceiver? = null, + private val preCheckoutQueryCallback: UpdateReceiver? = null +) { + val asUpdateReceiver: UpdateReceiver = this::invoke + val allowedUpdates = listOfNotNull( + (messageCallback ?: messageMediaGroupCallback) ?.let { UPDATE_MESSAGE }, + (editedMessageCallback ?: editedMessageMediaGroupCallback) ?.let { UPDATE_EDITED_MESSAGE }, + (channelPostCallback ?: channelPostMediaGroupCallback) ?.let { UPDATE_CHANNEL_POST }, + (editedChannelPostCallback ?: editedChannelPostMediaGroupCallback) ?.let { UPDATE_EDITED_CHANNEL_POST }, + chosenInlineResultCallback ?.let { UPDATE_CHOSEN_INLINE_RESULT }, + inlineQueryCallback ?.let { UPDATE_INLINE_QUERY }, + callbackQueryCallback ?.let { UPDATE_CALLBACK_QUERY }, + shippingQueryCallback ?.let { UPDATE_SHIPPING_QUERY }, + preCheckoutQueryCallback ?.let { UPDATE_PRE_CHECKOUT_QUERY } + ) + + suspend fun invoke(update: Any) { + when (update) { + is MessageUpdate -> messageCallback ?.invoke(update) + is List<*> -> when (update.firstOrNull()) { + is MessageUpdate -> update.mapNotNull { it as? MessageUpdate }.let { mappedList -> + messageMediaGroupCallback ?.also { receiver -> + receiver(mappedList) + } ?: messageCallback ?.also { receiver -> + mappedList.forEach { receiver(it) } + } + } + is EditMessageUpdate -> update.mapNotNull { it as? EditMessageUpdate }.let { mappedList -> + editedMessageMediaGroupCallback ?.also { receiver -> + receiver(mappedList) + } ?: editedMessageCallback ?.also { receiver -> + mappedList.forEach { receiver(it) } + } + } + is ChannelPostUpdate -> update.mapNotNull { it as? ChannelPostUpdate }.let { mappedList -> + channelPostMediaGroupCallback ?.also { receiver -> + receiver(mappedList) + } ?: channelPostCallback ?.also { receiver -> + mappedList.forEach { receiver(it) } + } + } + is EditChannelPostUpdate -> update.mapNotNull { it as? EditChannelPostUpdate }.let { mappedList -> + editedChannelPostMediaGroupCallback ?.also { receiver -> + receiver(mappedList) + } ?: editedChannelPostCallback ?.also { receiver -> + mappedList.forEach { receiver(it) } + } + } + } + is EditMessageUpdate -> editedMessageCallback ?.invoke(update) + is ChannelPostUpdate -> channelPostCallback ?.invoke(update) + is EditChannelPostUpdate -> editedChannelPostCallback ?.invoke(update) + is ChosenInlineResultUpdate -> chosenInlineResultCallback ?.invoke(update) + is InlineQueryUpdate -> inlineQueryCallback ?.invoke(update) + is CallbackQueryUpdate -> callbackQueryCallback ?.invoke(update) + is ShippingQueryUpdate -> shippingQueryCallback ?.invoke(update) + is PreCheckoutQueryUpdate -> preCheckoutQueryCallback ?.invoke(update) + } + } +} + +fun createSimpleUpdateFilter( + messageCallback: UpdateReceiver? = null, + mediaGroupCallback: UpdateReceiver>? = null, + editedMessageCallback: UpdateReceiver? = null, + channelPostCallback: UpdateReceiver? = null, + editedChannelPostCallback: UpdateReceiver? = null, + chosenInlineResultCallback: UpdateReceiver? = null, + inlineQueryCallback: UpdateReceiver? = null, + callbackQueryCallback: UpdateReceiver? = null, + shippingQueryCallback: UpdateReceiver? = null, + preCheckoutQueryCallback: UpdateReceiver? = null +): UpdatesFilter = UpdatesFilter( + messageCallback = messageCallback, + messageMediaGroupCallback = mediaGroupCallback, + editedMessageCallback = editedMessageCallback, + editedMessageMediaGroupCallback = mediaGroupCallback, + channelPostCallback = channelPostCallback, + channelPostMediaGroupCallback = mediaGroupCallback, + editedChannelPostCallback = editedChannelPostCallback, + editedChannelPostMediaGroupCallback = mediaGroupCallback, + chosenInlineResultCallback = chosenInlineResultCallback, + inlineQueryCallback = inlineQueryCallback, + callbackQueryCallback = callbackQueryCallback, + shippingQueryCallback = shippingQueryCallback, + preCheckoutQueryCallback = preCheckoutQueryCallback +) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt new file mode 100644 index 0000000000..53778666ca --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt @@ -0,0 +1,127 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions + +import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor +import com.github.insanusmokrassar.TelegramBotAPI.requests.* +import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.InputFile +import com.github.insanusmokrassar.TelegramBotAPI.types.MediaGroupIdentifier +import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage +import com.github.insanusmokrassar.TelegramBotAPI.types.update.* +import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update +import io.ktor.application.Application +import io.ktor.application.call +import io.ktor.request.receiveText +import io.ktor.response.respond +import io.ktor.routing.* +import io.ktor.server.engine.* +import io.ktor.server.netty.Netty +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.serialization.json.Json +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +/** + * Reverse proxy webhook. + * + * @param url URL of webhook WITHOUT including of [port] + * @param port port which will be listen by bot + * @param certificate [com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.MultipartFile] or [com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.FileId] + * which will be used by telegram to send encrypted messages + * @param scope Scope which will be used for + */ +suspend fun RequestsExecutor.setWebhook( + url: String, + port: Int, + certificate: InputFile, + scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), + allowedUpdates: List? = null, + maxAllowedConnections: Int? = null, + engineFactory: ApplicationEngineFactory<*, *> = Netty, + block: UpdateReceiver +): Job { + val executeDeferred = executeAsync( + SetWebhook( + url, + certificate, + maxAllowedConnections, + allowedUpdates + ) + ) + val updatesChannel = Channel(Channel.UNLIMITED) + val mediaGroupChannel = Channel>(Channel.UNLIMITED) + val mediaGroupAccumulatedChannel = mediaGroupChannel.accumulateByKey( + 1000L, + scope = scope + ) + val env = applicationEngineEnvironment { + module { + fun Application.main() { + routing { + post("/") { + val deserialized = call.receiveText() + val update = Json.nonstrict.parse( + RawUpdate.serializer(), + deserialized + ) + updatesChannel.send(update.asUpdate) + call.respond("Ok") + } + } + } + main() + } + connector { + host = "0.0.0.0" + this.port = port + } + } + val engine = embeddedServer(engineFactory, env) + + try { + executeDeferred.await() + } catch (e: Exception) { + env.stop() + throw e + } + + return scope.launch { + launch { + for (update in updatesChannel) { + val data = update.data + when (data) { + is MediaGroupMessage -> mediaGroupChannel.send(data.mediaGroupId to update) + else -> block(update) + } + } + } + launch { + for (mediaGroupUpdate in mediaGroupAccumulatedChannel) { + block(mediaGroupUpdate.second) + } + } + engine.start(false) + }.also { + it.invokeOnCompletion { + engine.stop(1000L, 0L, TimeUnit.MILLISECONDS) + } + } +} + +suspend fun RequestsExecutor.setWebhook( + url: String, + port: Int, + certificate: InputFile, + scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), + maxAllowedConnections: Int? = null, + engineFactory: ApplicationEngineFactory<*, *> = Netty, + filter: UpdatesFilter +): Job = setWebhook( + url, + port, + certificate, + scope, + filter.allowedUpdates, + maxAllowedConnections, + engineFactory, + filter.asUpdateReceiver +) From 8997454a46b930cdc13677d236bbf1e2435b51a1 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 26 Feb 2019 16:11:37 +0800 Subject: [PATCH 04/23] update order of arguments in setWebhook --- .../TelegramBotAPI/utils/extensions/Webhooks.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt index 53778666ca..d32e13a163 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt @@ -111,10 +111,10 @@ suspend fun RequestsExecutor.setWebhook( url: String, port: Int, certificate: InputFile, + filter: UpdatesFilter, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), maxAllowedConnections: Int? = null, - engineFactory: ApplicationEngineFactory<*, *> = Netty, - filter: UpdatesFilter + engineFactory: ApplicationEngineFactory<*, *> = Netty ): Job = setWebhook( url, port, From f1836ec2386dabddb3fc69b603750c1ee3611f15 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 27 Feb 2019 08:52:22 +0800 Subject: [PATCH 05/23] add contextual serializer to DataRequest and Request to avoid exceptions --- .../TelegramBotAPI/requests/abstracts/Request.kt | 5 +++-- .../requests/send/media/base/DataRequest.kt | 8 +++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/abstracts/Request.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/abstracts/Request.kt index 7ab7129b23..3776c1c69d 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/abstracts/Request.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/abstracts/Request.kt @@ -5,12 +5,13 @@ import com.github.insanusmokrassar.TelegramBotAPI.utils.toJsonWithoutNulls import kotlinx.serialization.* import kotlinx.serialization.json.JsonObject +@Serializable(RequestSerializer::class) interface Request { fun method(): String fun resultSerializer(): KSerializer - @ImplicitReflectionSerializer - fun json(): JsonObject = toJsonWithoutNulls() + fun json(): JsonObject = toJsonWithoutNulls(RequestSerializer) } +object RequestSerializer : KSerializer> by ContextSerializer(Request::class) fun StringFormat.extractResult( from: String, diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/DataRequest.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/DataRequest.kt index 996e3227ee..d9b3f3213b 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/DataRequest.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/send/media/base/DataRequest.kt @@ -1,5 +1,11 @@ package com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.base import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest +import kotlinx.serialization.ContextSerializer +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable -interface DataRequest : SimpleRequest \ No newline at end of file +@Serializable(DataRequestSerializer::class) +interface DataRequest : SimpleRequest + +object DataRequestSerializer : KSerializer> by ContextSerializer(DataRequest::class) \ No newline at end of file From b1d732c99a9d57d9e55812d736312334f63ec5ca Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 27 Feb 2019 10:51:25 +0800 Subject: [PATCH 06/23] deprecate toJsonWithoutNulls --- .../com/github/insanusmokrassar/TelegramBotAPI/utils/JSON.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/JSON.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/JSON.kt index 45bc692189..b2bcc701e5 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/JSON.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/JSON.kt @@ -3,6 +3,7 @@ package com.github.insanusmokrassar.TelegramBotAPI.utils import kotlinx.serialization.* import kotlinx.serialization.json.* +@Deprecated("This method can throw exceptions") @ImplicitReflectionSerializer inline fun T.toJsonWithoutNulls(): JsonObject = Json.nonstrict.toJson( this From f4e58375f225454fe752e1771732163de47575ff Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 27 Feb 2019 11:01:32 +0800 Subject: [PATCH 07/23] add MediaGroupUpdate --- CHANGELOG.md | 2 ++ .../types/update/MediaGroupUpdate.kt | 10 ++++++++++ .../BaseMessageUpdateToMediaGroupUpdate.kt | 9 +++++++++ .../utils/extensions/RequestsExecutor.kt | 8 ++++---- .../utils/extensions/UpdatesFilter.kt | 17 +++++++++-------- 5 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/MediaGroupUpdate.kt create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/BaseMessageUpdateToMediaGroupUpdate.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index cdd5e88750..9ca9c608de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ * Added class `UpdatesFilter` which can help to filter updates by categories * Added function `accumulateByKey` which work as debounce for keys and send list of received values * Added webhooks functions and workaround for `Reverse Proxy` mode +* Added new type of updates `MediaGroupUpdate`, which can be received only from filters +* `UpdatesFilter` now use new type of updates for mediagroups ## 0.11.0 diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/MediaGroupUpdate.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/MediaGroupUpdate.kt new file mode 100644 index 0000000000..cd77aec304 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/update/MediaGroupUpdate.kt @@ -0,0 +1,10 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types.update + +import com.github.insanusmokrassar.TelegramBotAPI.types.UpdateIdentifier +import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage +import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate + +data class MediaGroupUpdate( + override val updateId: UpdateIdentifier, + override val data: MediaGroupMessage +) : BaseMessageUpdate diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/BaseMessageUpdateToMediaGroupUpdate.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/BaseMessageUpdateToMediaGroupUpdate.kt new file mode 100644 index 0000000000..1e9767e9f6 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/BaseMessageUpdateToMediaGroupUpdate.kt @@ -0,0 +1,9 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils + +import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage +import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdate +import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate + +fun BaseMessageUpdate.toMediaGroupUpdate(): MediaGroupUpdate? = (data as? MediaGroupMessage) ?.let { + MediaGroupUpdate(updateId, it) +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt index 8b1b61026e..1204e2f2b2 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt @@ -99,13 +99,13 @@ fun RequestsExecutor.startGettingOfUpdates( fun RequestsExecutor.startGettingOfUpdates( messageCallback: UpdateReceiver? = null, - messageMediaGroupCallback: UpdateReceiver>? = null, + messageMediaGroupCallback: UpdateReceiver>? = null, editedMessageCallback: UpdateReceiver? = null, - editedMessageMediaGroupCallback: UpdateReceiver>? = null, + editedMessageMediaGroupCallback: UpdateReceiver>? = null, channelPostCallback: UpdateReceiver? = null, - channelPostMediaGroupCallback: UpdateReceiver>? = null, + channelPostMediaGroupCallback: UpdateReceiver>? = null, editedChannelPostCallback: UpdateReceiver? = null, - editedChannelPostMediaGroupCallback: UpdateReceiver>? = null, + editedChannelPostMediaGroupCallback: UpdateReceiver>? = null, chosenInlineResultCallback: UpdateReceiver? = null, inlineQueryCallback: UpdateReceiver? = null, callbackQueryCallback: UpdateReceiver? = null, diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt index 7c7ee726c2..17acee1bff 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt @@ -3,16 +3,17 @@ package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions import com.github.insanusmokrassar.TelegramBotAPI.requests.* import com.github.insanusmokrassar.TelegramBotAPI.types.update.* import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate +import com.github.insanusmokrassar.TelegramBotAPI.utils.toMediaGroupUpdate data class UpdatesFilter( private val messageCallback: UpdateReceiver? = null, - private val messageMediaGroupCallback: UpdateReceiver>? = null, + private val messageMediaGroupCallback: UpdateReceiver>? = null, private val editedMessageCallback: UpdateReceiver? = null, - private val editedMessageMediaGroupCallback: UpdateReceiver>? = null, + private val editedMessageMediaGroupCallback: UpdateReceiver>? = null, private val channelPostCallback: UpdateReceiver? = null, - private val channelPostMediaGroupCallback: UpdateReceiver>? = null, + private val channelPostMediaGroupCallback: UpdateReceiver>? = null, private val editedChannelPostCallback: UpdateReceiver? = null, - private val editedChannelPostMediaGroupCallback: UpdateReceiver>? = null, + private val editedChannelPostMediaGroupCallback: UpdateReceiver>? = null, private val chosenInlineResultCallback: UpdateReceiver? = null, private val inlineQueryCallback: UpdateReceiver? = null, private val callbackQueryCallback: UpdateReceiver? = null, @@ -38,28 +39,28 @@ data class UpdatesFilter( is List<*> -> when (update.firstOrNull()) { is MessageUpdate -> update.mapNotNull { it as? MessageUpdate }.let { mappedList -> messageMediaGroupCallback ?.also { receiver -> - receiver(mappedList) + receiver(mappedList.mapNotNull { it.toMediaGroupUpdate() }) } ?: messageCallback ?.also { receiver -> mappedList.forEach { receiver(it) } } } is EditMessageUpdate -> update.mapNotNull { it as? EditMessageUpdate }.let { mappedList -> editedMessageMediaGroupCallback ?.also { receiver -> - receiver(mappedList) + receiver(mappedList.mapNotNull { it.toMediaGroupUpdate() }) } ?: editedMessageCallback ?.also { receiver -> mappedList.forEach { receiver(it) } } } is ChannelPostUpdate -> update.mapNotNull { it as? ChannelPostUpdate }.let { mappedList -> channelPostMediaGroupCallback ?.also { receiver -> - receiver(mappedList) + receiver(mappedList.mapNotNull { it.toMediaGroupUpdate() }) } ?: channelPostCallback ?.also { receiver -> mappedList.forEach { receiver(it) } } } is EditChannelPostUpdate -> update.mapNotNull { it as? EditChannelPostUpdate }.let { mappedList -> editedChannelPostMediaGroupCallback ?.also { receiver -> - receiver(mappedList) + receiver(mappedList.mapNotNull { it.toMediaGroupUpdate() }) } ?: editedChannelPostCallback ?.also { receiver -> mappedList.forEach { receiver(it) } } From f3ba288ad83005327a3b8c07b5ebabbdd88a1c19 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 27 Feb 2019 14:01:04 +0800 Subject: [PATCH 08/23] WebhookInfo --- CHANGELOG.md | 2 + .../TelegramBotAPI/requests/GetUpdates.kt | 47 ++++++++----------- .../requests/webhook/GetWebhookInfo.kt | 13 +++++ .../requests/{ => webhook}/SetWebhook.kt | 2 +- .../TelegramBotAPI/types/Common.kt | 4 ++ .../TelegramBotAPI/types/UpdateTypes.kt | 23 +++++++++ .../TelegramBotAPI/types/WebhookInfo.kt | 32 +++++++++++++ .../utils/extensions/UpdatesFilter.kt | 2 +- .../utils/extensions/Webhooks.kt | 2 +- 9 files changed, 97 insertions(+), 30 deletions(-) create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/GetWebhookInfo.kt rename src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/{ => webhook}/SetWebhook.kt (95%) create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UpdateTypes.kt create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/WebhookInfo.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ca9c608de..e297c1d8be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ * Added webhooks functions and workaround for `Reverse Proxy` mode * Added new type of updates `MediaGroupUpdate`, which can be received only from filters * `UpdatesFilter` now use new type of updates for mediagroups +* Add `GetWebhookInfo` request and `WebhookInfo` type +* Replace updates types into separated place in types ## 0.11.0 diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetUpdates.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetUpdates.kt index a4800f45c1..a491568b30 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetUpdates.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetUpdates.kt @@ -2,27 +2,30 @@ package com.github.insanusmokrassar.TelegramBotAPI.requests import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest import com.github.insanusmokrassar.TelegramBotAPI.types.UpdateIdentifier +import com.github.insanusmokrassar.TelegramBotAPI.types.ALL_UPDATES_LIST import com.github.insanusmokrassar.TelegramBotAPI.types.update.RawUpdate import kotlinx.serialization.* import kotlinx.serialization.internal.ArrayListSerializer -const val UPDATE_MESSAGE = "message" -const val UPDATE_EDITED_MESSAGE = "edited_message" -const val UPDATE_CHANNEL_POST = "channel_post" -const val UPDATE_EDITED_CHANNEL_POST = "edited_channel_post" -const val UPDATE_CHOSEN_INLINE_RESULT = "chosen_inline_result" -const val UPDATE_INLINE_QUERY = "inline_query" -const val UPDATE_CALLBACK_QUERY = "callback_query" -const val UPDATE_SHIPPING_QUERY = "shipping_query" -const val UPDATE_PRE_CHECKOUT_QUERY = "pre_checkout_query" -/* +@Deprecated("Replaced to other package", ReplaceWith("UPDATE_MESSAGE", "com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_MESSAGE")) +const val UPDATE_MESSAGE = com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_MESSAGE +@Deprecated("Replaced to other package", ReplaceWith("UPDATE_EDITED_MESSAGE", "com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_EDITED_MESSAGE")) +const val UPDATE_EDITED_MESSAGE = com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_EDITED_MESSAGE +@Deprecated("Replaced to other package", ReplaceWith("UPDATE_CHANNEL_POST", "com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_CHANNEL_POST")) +const val UPDATE_CHANNEL_POST = com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_CHANNEL_POST +@Deprecated("Replaced to other package", ReplaceWith("UPDATE_EDITED_CHANNEL_POST", "com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_EDITED_CHANNEL_POST")) +const val UPDATE_EDITED_CHANNEL_POST = com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_EDITED_CHANNEL_POST +@Deprecated("Replaced to other package", ReplaceWith("UPDATE_CHOSEN_INLINE_RESULT", "com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_CHOSEN_INLINE_RESULT")) +const val UPDATE_CHOSEN_INLINE_RESULT = com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_CHOSEN_INLINE_RESULT +@Deprecated("Replaced to other package", ReplaceWith("UPDATE_INLINE_QUERY", "com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_INLINE_QUERY")) +const val UPDATE_INLINE_QUERY = com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_INLINE_QUERY +@Deprecated("Replaced to other package", ReplaceWith("UPDATE_CALLBACK_QUERY", "com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_CALLBACK_QUERY")) +const val UPDATE_CALLBACK_QUERY = com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_CALLBACK_QUERY +@Deprecated("Replaced to other package", ReplaceWith("UPDATE_SHIPPING_QUERY", "com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_SHIPPING_QUERY")) +const val UPDATE_SHIPPING_QUERY = com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_SHIPPING_QUERY +@Deprecated("Replaced to other package", ReplaceWith("UPDATE_PRE_CHECKOUT_QUERY", "com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_PRE_CHECKOUT_QUERY")) +const val UPDATE_PRE_CHECKOUT_QUERY = com.github.insanusmokrassar.TelegramBotAPI.types.UPDATE_PRE_CHECKOUT_QUERY - @Optional private val inline_query: RawInlineQuery? = null, - @Optional private val chosen_inline_result: Unit? = null, - @Optional private val callback_query: RawCallbackQuery? = null, - @Optional private val shipping_query: Unit? = null, - @Optional private val pre_checkout_query: Unit? = null - */ @Serializable data class GetUpdates( @Optional @@ -32,17 +35,7 @@ data class GetUpdates( @Optional val timeout: Int? = null, @Optional - val allowed_updates: List? = listOf( - UPDATE_MESSAGE, - UPDATE_EDITED_MESSAGE, - UPDATE_CHANNEL_POST, - UPDATE_EDITED_CHANNEL_POST, - UPDATE_CHOSEN_INLINE_RESULT, - UPDATE_INLINE_QUERY, - UPDATE_CALLBACK_QUERY, - UPDATE_SHIPPING_QUERY, - UPDATE_PRE_CHECKOUT_QUERY - ) + val allowed_updates: List? = ALL_UPDATES_LIST ): SimpleRequest> { override fun method(): String = "getUpdates" diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/GetWebhookInfo.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/GetWebhookInfo.kt new file mode 100644 index 0000000000..92d4c72462 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/GetWebhookInfo.kt @@ -0,0 +1,13 @@ +package com.github.insanusmokrassar.TelegramBotAPI.requests.webhook + +import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest +import com.github.insanusmokrassar.TelegramBotAPI.types.WebhookInfo +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable + +@Serializable +object GetWebhookInfo : SimpleRequest { + override fun method(): String = "getWebhookInfo" + + override fun resultSerializer(): KSerializer = WebhookInfo.serializer() +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/SetWebhook.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt similarity index 95% rename from src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/SetWebhook.kt rename to src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt index 11aed14b52..852018d5ca 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/SetWebhook.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt @@ -1,4 +1,4 @@ -package com.github.insanusmokrassar.TelegramBotAPI.requests +package com.github.insanusmokrassar.TelegramBotAPI.requests.webhook import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.* import com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.base.* diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt index 8f82575c3c..8aa06288d6 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/Common.kt @@ -71,6 +71,10 @@ const val switchPmTextField = "switch_pm_text" const val switchPmParameterField = "switch_pm_parameter" const val maxAllowedConnectionsField = "max_connections" const val allowedUpdatesField = "allowed_updates" +const val hasCustomCertificateField = "has_custom_certificate" +const val pendingUpdateCountField = "pending_update_count" +const val lastErrorDateField = "last_error_date" +const val lastErrorMessageField = "last_error_message" const val photoUrlField = "photo_url" diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UpdateTypes.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UpdateTypes.kt new file mode 100644 index 0000000000..e5d477db3f --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/UpdateTypes.kt @@ -0,0 +1,23 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types + +const val UPDATE_MESSAGE = "message" +const val UPDATE_EDITED_MESSAGE = "edited_message" +const val UPDATE_CHANNEL_POST = "channel_post" +const val UPDATE_EDITED_CHANNEL_POST = "edited_channel_post" +const val UPDATE_CHOSEN_INLINE_RESULT = "chosen_inline_result" +const val UPDATE_INLINE_QUERY = "inline_query" +const val UPDATE_CALLBACK_QUERY = "callback_query" +const val UPDATE_SHIPPING_QUERY = "shipping_query" +const val UPDATE_PRE_CHECKOUT_QUERY = "pre_checkout_query" + +val ALL_UPDATES_LIST = listOf( + UPDATE_MESSAGE, + UPDATE_EDITED_MESSAGE, + UPDATE_CHANNEL_POST, + UPDATE_EDITED_CHANNEL_POST, + UPDATE_CHOSEN_INLINE_RESULT, + UPDATE_INLINE_QUERY, + UPDATE_CALLBACK_QUERY, + UPDATE_SHIPPING_QUERY, + UPDATE_PRE_CHECKOUT_QUERY +) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/WebhookInfo.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/WebhookInfo.kt new file mode 100644 index 0000000000..84fa832908 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/WebhookInfo.kt @@ -0,0 +1,32 @@ +package com.github.insanusmokrassar.TelegramBotAPI.types + +import kotlinx.serialization.* + +@Serializable +data class WebhookInfo( + @SerialName(urlField) + val url: String, + @SerialName(pendingUpdateCountField) + val awaitDeliery: Int, + @SerialName(maxAllowedConnectionsField) + @Optional + val maxConnections: Int = 40, // default count according to documentation + @SerialName(hasCustomCertificateField) + @Optional + val customCertificate: Boolean = false, + @SerialName(allowedUpdatesField) + @Optional + val allowedUpdates: List = ALL_UPDATES_LIST, + @SerialName(lastErrorDateField) + @Optional + val lastErrorDate: TelegramDate? = null, + @SerialName(lastErrorMessageField) + @Optional + val lastErrorMessage: String? = null +) { + @Transient + val isNotUseWebhook: Boolean = url.isEmpty() + + @Transient + val hasError: Boolean = lastErrorMessage != null +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt index 17acee1bff..d008a3b971 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesFilter.kt @@ -1,6 +1,6 @@ package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions -import com.github.insanusmokrassar.TelegramBotAPI.requests.* +import com.github.insanusmokrassar.TelegramBotAPI.types.* import com.github.insanusmokrassar.TelegramBotAPI.types.update.* import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate import com.github.insanusmokrassar.TelegramBotAPI.utils.toMediaGroupUpdate diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt index d32e13a163..7e54d4f084 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt @@ -1,8 +1,8 @@ package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor -import com.github.insanusmokrassar.TelegramBotAPI.requests.* import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.InputFile +import com.github.insanusmokrassar.TelegramBotAPI.requests.webhook.SetWebhook import com.github.insanusmokrassar.TelegramBotAPI.types.MediaGroupIdentifier import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage import com.github.insanusmokrassar.TelegramBotAPI.types.update.* From ce9e141680b2319a4c2a34ed8a2134d9a88dbf27 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 27 Feb 2019 14:12:39 +0800 Subject: [PATCH 09/23] rewrite GetWebhookInfo as class for better work with serialization --- .../TelegramBotAPI/requests/webhook/GetWebhookInfo.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/GetWebhookInfo.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/GetWebhookInfo.kt index 92d4c72462..8b4decd01b 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/GetWebhookInfo.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/GetWebhookInfo.kt @@ -2,12 +2,11 @@ package com.github.insanusmokrassar.TelegramBotAPI.requests.webhook import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest import com.github.insanusmokrassar.TelegramBotAPI.types.WebhookInfo -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable +import kotlinx.serialization.* @Serializable -object GetWebhookInfo : SimpleRequest { +class GetWebhookInfo : SimpleRequest { override fun method(): String = "getWebhookInfo" override fun resultSerializer(): KSerializer = WebhookInfo.serializer() -} \ No newline at end of file +} From 09ecea4064f53a8668c6a1dc5d3340accdbbfcfd Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 27 Feb 2019 14:39:49 +0800 Subject: [PATCH 10/23] update readme with getting updates instructions --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 100c8cece8..7b1e9ad081 100644 --- a/README.md +++ b/README.md @@ -27,3 +27,34 @@ executor.execute(GetMe()) As a result you will receive `User` object. This object used as is now (as in API documentation), but it is possible that this class will be renamed to `RawUser` and you will be able to get real realisation of this object like `Bot` (in cases when `isBot` == `true`) or `User` (otherwise) + +## Getting updates + +In this library currently realised two ways to get updates from telegram: + +* Polling - in this case bot will request updates from time to time (you can set up delay between requests) +* Webhook via reverse proxy or something like this + +### Updates filters + +Currently webhook method contains `UpdatesFilter` as necessary argument for getting updates. +`UpdatesFilter` will sort updates and throw their into different callbacks. Currently supporting +separate getting updates for media groups - they are accumulating with debounce in one second +(for being sure that all objects of media group was received). + +Updates polling also support `UpdatesFilter` but you must not use it and can get updates directly +in `UpdateReceiver`, which you will provide to `startGettingOfUpdates` method + +### Webhook set up + +If you wish to use webhook method, you will need: + +* White IP - your IP address or host, which available for calling. [TelegramBotAPI](https://core.telegram.org/bots/api#setwebhook) +recommend to use some unique address for each bot which you are using +* SSL certificate. You can obtain address using your provider, [Let'sEncrypt](https://letsencrypt.org/) or [create it](https://core.telegram.org/bots/self-signed) +* Nginx or something like this + +Template for Nginx server config you can find in [this gist](https://gist.github.com/InsanusMokrassar/fcc6e09cebd07e46e8f0fdec234750c4#file-nginxssl-conf) + +For webhook you must provide `File` with public part of certificate, `URL` where bot placed and inner `PORT` which +will be used to start receiving of updates. From 95d07fce9b10452e7a35d75e8a6b09d0ecc91719 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 27 Feb 2019 15:13:14 +0800 Subject: [PATCH 11/23] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b1e9ad081..66540d64c8 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,10 @@ If you wish to use webhook method, you will need: * White IP - your IP address or host, which available for calling. [TelegramBotAPI](https://core.telegram.org/bots/api#setwebhook) recommend to use some unique address for each bot which you are using -* SSL certificate. You can obtain address using your provider, [Let'sEncrypt](https://letsencrypt.org/) or [create it](https://core.telegram.org/bots/self-signed) +* SSL certificate. Usually you can obtain the certificate using your domain provider, [Let'sEncrypt](https://letsencrypt.org/) or [create it](https://core.telegram.org/bots/self-signed) * Nginx or something like this -Template for Nginx server config you can find in [this gist](https://gist.github.com/InsanusMokrassar/fcc6e09cebd07e46e8f0fdec234750c4#file-nginxssl-conf) +Template for Nginx server config you can find in [this gist](https://gist.github.com/InsanusMokrassar/fcc6e09cebd07e46e8f0fdec234750c4#file-nginxssl-conf). For webhook you must provide `File` with public part of certificate, `URL` where bot placed and inner `PORT` which will be used to start receiving of updates. From ad700f5f339b625f1f6b7bdc61dd35ad1249781b Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 27 Feb 2019 15:30:43 +0800 Subject: [PATCH 12/23] update readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 66540d64c8..ac44d17ff3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ It is one more project which wish to be useful and full Telegram Bots API bridge for Kotlin. Most part of some specific solves or unuseful moments are describing by official [Telegram Bot API](https://core.telegram.org/bots/api). +## Compatibility + +This version compatible with [July 2018 update of TelegramBotAPI](https://core.telegram.org/bots/api#july-26-2018). That means that +most part of API has been implemented (according to last [August 2018 update of TelegramBotAPI](https://core.telegram.org/bots/api#august-27-2018)) +except the Passport API which will be included as soon as possible. + ## How to work with library? By default in any documentation will be meaning that you have variable in scope with names From f4b7bb42d693c1e73af42cd471c3130f6ab6e49c Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 4 Mar 2019 10:32:26 +0800 Subject: [PATCH 13/23] add plain answer to default exception --- CHANGELOG.md | 1 + .../TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt | 3 ++- .../TelegramBotAPI/bot/exceptions/ReplyMessageNotFound.kt | 4 ++-- .../TelegramBotAPI/bot/exceptions/RequestException.kt | 6 ++++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e297c1d8be..0b939bfef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * `UpdatesFilter` now use new type of updates for mediagroups * Add `GetWebhookInfo` request and `WebhookInfo` type * Replace updates types into separated place in types +* Now default `RequestException` will contain plain answer from telegram ## 0.11.0 diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt index 15ae5c6537..285bf033e9 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt @@ -77,7 +77,8 @@ class KtorRequestsExecutor( } ?: call.let { throw newRequestException( responseObject, - "Can't get result object" + content, + "Can't get result object from $content" ) } } diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/ReplyMessageNotFound.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/ReplyMessageNotFound.kt index 0208179047..ae66d2d920 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/ReplyMessageNotFound.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/ReplyMessageNotFound.kt @@ -2,5 +2,5 @@ package com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions import com.github.insanusmokrassar.TelegramBotAPI.types.Response -open class ReplyMessageNotFound(response: Response<*>, message: String?, cause: Throwable?) : - RequestException(response, message, cause) \ No newline at end of file +open class ReplyMessageNotFound(response: Response<*>, plainAnswer: String, message: String?, cause: Throwable?) : + RequestException(response, plainAnswer, message, cause) \ No newline at end of file diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/RequestException.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/RequestException.kt index 1783631604..4f0d627927 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/RequestException.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/RequestException.kt @@ -5,15 +5,17 @@ import java.io.IOException fun newRequestException( response: Response<*>, + plainAnswer: String, message: String? = null, cause: Throwable? = null ) = when (response.description) { - "Bad Request: reply message not found" -> ReplyMessageNotFound(response, message, cause) - else -> RequestException(response, message, cause) + "Bad Request: reply message not found" -> ReplyMessageNotFound(response, plainAnswer, message, cause) + else -> RequestException(response, plainAnswer, message, cause) } open class RequestException internal constructor( val response: Response<*>, + val plainAnswer: String, message: String? = null, cause: Throwable? = null ) : IOException( From 057d902883ef450649e0be17edb967a49c3b9113 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 4 Mar 2019 10:49:37 +0800 Subject: [PATCH 14/23] add SetWebhook factory which can be used without certificate file --- .../TelegramBotAPI/requests/webhook/SetWebhook.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt index 852018d5ca..e7e8500499 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt @@ -27,6 +27,17 @@ fun SetWebhook( } } +fun SetWebhook( + url: String, + maxAllowedConnections: Int? = null, + allowedUpdates: List? = null +) : Request = SetWebhook( + url, + null, + maxAllowedConnections, + allowedUpdates +) + @Serializable data class SetWebhook internal constructor( @SerialName(urlField) From a393382a7c50f94ebeecadea2cf2155d06534f61 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 4 Mar 2019 11:13:01 +0800 Subject: [PATCH 15/23] fixes for webhooks --- .../requests/webhook/SetWebhook.kt | 2 +- .../utils/extensions/Webhooks.kt | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt index e7e8500499..7649006554 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/webhook/SetWebhook.kt @@ -14,7 +14,7 @@ fun SetWebhook( ) : Request { val data = SetWebhook( url, - certificate.fileId, + (certificate as? FileId) ?.fileId, maxAllowedConnections, allowedUpdates ) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt index 7e54d4f084..2b4338dedf 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt @@ -32,17 +32,25 @@ import java.util.concurrent.TimeUnit suspend fun RequestsExecutor.setWebhook( url: String, port: Int, - certificate: InputFile, + certificate: InputFile? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), allowedUpdates: List? = null, maxAllowedConnections: Int? = null, engineFactory: ApplicationEngineFactory<*, *> = Netty, block: UpdateReceiver ): Job { - val executeDeferred = executeAsync( + val executeDeferred = certificate ?.let { + executeAsync( + SetWebhook( + url, + certificate, + maxAllowedConnections, + allowedUpdates + ) + ) + } ?: executeAsync( SetWebhook( url, - certificate, maxAllowedConnections, allowedUpdates ) @@ -57,7 +65,7 @@ suspend fun RequestsExecutor.setWebhook( module { fun Application.main() { routing { - post("/") { + post { val deserialized = call.receiveText() val update = Json.nonstrict.parse( RawUpdate.serializer(), @@ -71,7 +79,7 @@ suspend fun RequestsExecutor.setWebhook( main() } connector { - host = "0.0.0.0" + host = "localhost" this.port = port } } From 8bb2b5c218851a687ccb069c75274febd6d47e4f Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 4 Mar 2019 14:24:07 +0800 Subject: [PATCH 16/23] fixes in nullability of certificate for webhook --- .../TelegramBotAPI/utils/extensions/Webhooks.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt index 2b4338dedf..b86ff55bda 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt @@ -79,7 +79,7 @@ suspend fun RequestsExecutor.setWebhook( main() } connector { - host = "localhost" + host = "0.0.0.0" this.port = port } } @@ -118,8 +118,8 @@ suspend fun RequestsExecutor.setWebhook( suspend fun RequestsExecutor.setWebhook( url: String, port: Int, - certificate: InputFile, filter: UpdatesFilter, + certificate: InputFile? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), maxAllowedConnections: Int? = null, engineFactory: ApplicationEngineFactory<*, *> = Netty From 7b78dbdb3e712edff246ffdd7f1a492393819da1 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 6 Mar 2019 08:10:29 +0800 Subject: [PATCH 17/23] improvement of exceptions --- CHANGELOG.md | 3 +++ .../bot/exceptions/ReplyMessageNotFound.kt | 6 ----- .../bot/exceptions/RequestException.kt | 24 +++++++++++++++---- 3 files changed, 23 insertions(+), 10 deletions(-) delete mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/ReplyMessageNotFound.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b939bfef7..3ad725bd37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ * Add `GetWebhookInfo` request and `WebhookInfo` type * Replace updates types into separated place in types * Now default `RequestException` will contain plain answer from telegram +* Added `UnauthorizedException` +* `RequestException` now is sealed +* Rename `ReplyMessageNotFound` to `ReplyMessageNotFoundException` ## 0.11.0 diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/ReplyMessageNotFound.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/ReplyMessageNotFound.kt deleted file mode 100644 index ae66d2d920..0000000000 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/ReplyMessageNotFound.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions - -import com.github.insanusmokrassar.TelegramBotAPI.types.Response - -open class ReplyMessageNotFound(response: Response<*>, plainAnswer: String, message: String?, cause: Throwable?) : - RequestException(response, plainAnswer, message, cause) \ No newline at end of file diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/RequestException.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/RequestException.kt index 4f0d627927..777ccec5d7 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/RequestException.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/exceptions/RequestException.kt @@ -9,11 +9,12 @@ fun newRequestException( message: String? = null, cause: Throwable? = null ) = when (response.description) { - "Bad Request: reply message not found" -> ReplyMessageNotFound(response, plainAnswer, message, cause) - else -> RequestException(response, plainAnswer, message, cause) + "Bad Request: reply message not found" -> ReplyMessageNotFoundException(response, plainAnswer, message, cause) + "Unauthorized" -> UnauthorizedException(response, plainAnswer, message, cause) + else -> CommonRequestException(response, plainAnswer, message, cause) } -open class RequestException internal constructor( +sealed class RequestException constructor( val response: Response<*>, val plainAnswer: String, message: String? = null, @@ -21,4 +22,19 @@ open class RequestException internal constructor( ) : IOException( message, cause -) \ No newline at end of file +) + +class CommonRequestException(response: Response<*>, plainAnswer: String, message: String?, cause: Throwable?) : + RequestException(response, plainAnswer, message, cause) + +class UnauthorizedException(response: Response<*>, plainAnswer: String, message: String?, cause: Throwable?) : + RequestException(response, plainAnswer, message, cause) + +class ReplyMessageNotFoundException(response: Response<*>, plainAnswer: String, message: String?, cause: Throwable?) : + RequestException(response, plainAnswer, message, cause) + +@Deprecated( + "Replaced by ReplyMessageNotFoundException", + ReplaceWith("ReplyMessageNotFoundException", "com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.ReplyMessageNotFoundException") +) +typealias ReplyMessageNotFound = ReplyMessageNotFoundException \ No newline at end of file From 336e76450f076ef1f7434ced087dd8e21e250958 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 6 Mar 2019 10:09:45 +0800 Subject: [PATCH 18/23] UpdatesPoller, asReference, mediaGroupId for list and separate executes and update polling extensions --- CHANGELOG.md | 5 + .../BaseMessageUpdateToMediaGroupUpdate.kt | 4 +- .../TelegramBotAPI/utils/MediaGroupList.kt | 5 + .../utils/extensions/AsReference.kt | 5 + .../utils/extensions/Executes.kt | 51 +++++ .../utils/extensions/RequestsExecutor.kt | 212 ------------------ .../utils/extensions/UpdatesPoller.kt | 99 ++++++++ .../utils/extensions/UpdatesPolling.kt | 89 ++++++++ 8 files changed, 256 insertions(+), 214 deletions(-) create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/AsReference.kt create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Executes.kt delete mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPolling.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ad725bd37..e027e10444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ * Added `UnauthorizedException` * `RequestException` now is sealed * Rename `ReplyMessageNotFound` to `ReplyMessageNotFoundException` +* Added `List#mediaGroupId` extension +* Added utility `T#asReference(): WeakReference(T)` extension +* Added `UpdatesPoller` class which can be instantiated for manage updates polling +* Separated execute extensions (now they are in file `Executes`) and poller creating extensions +* `BaseMessageUpdate#toMediaGroupUpdate()` will also check condition when update-receiver already is `MediaGroupUpdate` ## 0.11.0 diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/BaseMessageUpdateToMediaGroupUpdate.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/BaseMessageUpdateToMediaGroupUpdate.kt index 1e9767e9f6..595e9cc7ad 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/BaseMessageUpdateToMediaGroupUpdate.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/BaseMessageUpdateToMediaGroupUpdate.kt @@ -4,6 +4,6 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaG import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdate import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate -fun BaseMessageUpdate.toMediaGroupUpdate(): MediaGroupUpdate? = (data as? MediaGroupMessage) ?.let { +fun BaseMessageUpdate.toMediaGroupUpdate(): MediaGroupUpdate? = (this as? MediaGroupUpdate) ?: ((data as? MediaGroupMessage) ?.let { MediaGroupUpdate(updateId, it) -} +}) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/MediaGroupList.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/MediaGroupList.kt index 08866b8335..e43da3f2ef 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/MediaGroupList.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/MediaGroupList.kt @@ -1,8 +1,10 @@ package com.github.insanusmokrassar.TelegramBotAPI.utils +import com.github.insanusmokrassar.TelegramBotAPI.types.MediaGroupIdentifier import com.github.insanusmokrassar.TelegramBotAPI.types.chat.Chat import com.github.insanusmokrassar.TelegramBotAPI.types.message.ForwardedMessage import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.* +import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdate import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate val List.forwarded: ForwardedMessage? @@ -17,3 +19,6 @@ val List.replyTo: Message? val List.chat: Chat? get() = first().data.chat + +val List.mediaGroupId: MediaGroupIdentifier? + get() = (first().data as? MediaGroupMessage) ?.mediaGroupId diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/AsReference.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/AsReference.kt new file mode 100644 index 0000000000..b8cb0b5eae --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/AsReference.kt @@ -0,0 +1,5 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions + +import java.lang.ref.WeakReference + +fun T.asReference() = WeakReference(this) \ No newline at end of file diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Executes.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Executes.kt new file mode 100644 index 0000000000..789ca33c49 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Executes.kt @@ -0,0 +1,51 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions + +import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor +import com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.RequestException +import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request +import com.github.insanusmokrassar.TelegramBotAPI.types.Response +import kotlinx.coroutines.* + + +fun RequestsExecutor.executeAsync( + request: Request, + onFail: (suspend (Response<*>) -> Unit)? = null, + scope: CoroutineScope = GlobalScope, + onSuccess: (suspend (T) -> Unit)? = null +): Job { + return scope.launch { + try { + val result = execute(request) + onSuccess ?.invoke(result) + } catch (e: RequestException) { + onFail ?.invoke(e.response) + } + } +} + +fun RequestsExecutor.executeAsync( + request: Request, + scope: CoroutineScope = GlobalScope +): Deferred { + return scope.async { execute(request) } +} + +suspend fun RequestsExecutor.executeUnsafe( + request: Request, + retries: Int = 0, + retriesDelay: Long = 1000L +): T? { + var leftRetries = retries + while(true) { + try { + return execute(request) + } catch (e: RequestException) { + if (leftRetries > 0) { + leftRetries-- + delay(retriesDelay) + } else { + return null + } + } + } +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt deleted file mode 100644 index 1204e2f2b2..0000000000 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/RequestsExecutor.kt +++ /dev/null @@ -1,212 +0,0 @@ -package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions - -import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor -import com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.RequestException -import com.github.insanusmokrassar.TelegramBotAPI.requests.* -import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request -import com.github.insanusmokrassar.TelegramBotAPI.types.Response -import com.github.insanusmokrassar.TelegramBotAPI.types.UpdateIdentifier -import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage -import com.github.insanusmokrassar.TelegramBotAPI.types.update.* -import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate -import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update -import kotlinx.coroutines.* - -typealias UpdateReceiver = suspend (T) -> Unit - -fun RequestsExecutor.startGettingOfUpdates( - requestsDelayMillis: Long = 1000, - scope: CoroutineScope = GlobalScope, - allowedUpdates: List? = null, - block: UpdateReceiver -): Job { - return scope.launch { - var lastHandledUpdate: UpdateIdentifier = 0L - while (isActive) { - delay(requestsDelayMillis) - try { - val updates = execute( - GetUpdates( - lastHandledUpdate + 1, - allowed_updates = allowedUpdates - ) - ) - - val adaptedUpdates = mutableListOf() - var mediaGroup: MutableList? = null - - fun pushMediaGroup() { - mediaGroup ?.also { - adaptedUpdates.add(it) - mediaGroup = null - } - } - - updates.map { - it.asUpdate - }.forEach { update -> - val data = update.data - if (data is MediaGroupMessage) { - mediaGroup ?.let { - val message = it.first().data as MediaGroupMessage - if (message.mediaGroupId == data.mediaGroupId) { - it.add(update) - } else { - null - } - } ?: data.also { - pushMediaGroup() - mediaGroup = mutableListOf() - mediaGroup ?.add(update) - } - } else { - pushMediaGroup() - adaptedUpdates.add(update) - } - } - - mediaGroup ?.also { - adaptedUpdates.add(it) - mediaGroup = null - } - - for (update in adaptedUpdates) { - - try { - block(update) - lastHandledUpdate = when (update) { - is Update -> update.updateId - is List<*> -> (update.last() as? Update) ?.updateId ?: throw IllegalStateException( - "Found non-updates oriented list" - ) - else -> throw IllegalStateException( - "Unknown type of data" - ) - } - } catch (e: Exception) { - // TODO:: add exception handling - e.printStackTrace() - break - } - } - } catch (e: Exception) { - // TODO:: add exception handling - e.printStackTrace() - } - } - } -} - -fun RequestsExecutor.startGettingOfUpdates( - messageCallback: UpdateReceiver? = null, - messageMediaGroupCallback: UpdateReceiver>? = null, - editedMessageCallback: UpdateReceiver? = null, - editedMessageMediaGroupCallback: UpdateReceiver>? = null, - channelPostCallback: UpdateReceiver? = null, - channelPostMediaGroupCallback: UpdateReceiver>? = null, - editedChannelPostCallback: UpdateReceiver? = null, - editedChannelPostMediaGroupCallback: UpdateReceiver>? = null, - chosenInlineResultCallback: UpdateReceiver? = null, - inlineQueryCallback: UpdateReceiver? = null, - callbackQueryCallback: UpdateReceiver? = null, - shippingQueryCallback: UpdateReceiver? = null, - preCheckoutQueryCallback: UpdateReceiver? = null, - requestsDelayMillis: Long = 1000, - scope: CoroutineScope = GlobalScope -): Job { - val filter = UpdatesFilter( - messageCallback, - messageMediaGroupCallback, - editedMessageCallback, - editedMessageMediaGroupCallback, - channelPostCallback, - channelPostMediaGroupCallback, - editedChannelPostCallback, - editedChannelPostMediaGroupCallback, - chosenInlineResultCallback, - inlineQueryCallback, - callbackQueryCallback, - shippingQueryCallback, - preCheckoutQueryCallback - ) - return startGettingOfUpdates( - requestsDelayMillis, - scope, - filter.allowedUpdates, - filter.asUpdateReceiver - ) -} - -fun RequestsExecutor.startGettingOfUpdates( - messageCallback: UpdateReceiver? = null, - mediaGroupCallback: UpdateReceiver>? = null, - editedMessageCallback: UpdateReceiver? = null, - channelPostCallback: UpdateReceiver? = null, - editedChannelPostCallback: UpdateReceiver? = null, - chosenInlineResultCallback: UpdateReceiver? = null, - inlineQueryCallback: UpdateReceiver? = null, - callbackQueryCallback: UpdateReceiver? = null, - shippingQueryCallback: UpdateReceiver? = null, - preCheckoutQueryCallback: UpdateReceiver? = null, - requestsDelayMillis: Long = 1000, - scope: CoroutineScope = GlobalScope -): Job = startGettingOfUpdates( - messageCallback = messageCallback, - messageMediaGroupCallback = mediaGroupCallback, - editedMessageCallback = editedMessageCallback, - editedMessageMediaGroupCallback = mediaGroupCallback, - channelPostCallback = channelPostCallback, - channelPostMediaGroupCallback = mediaGroupCallback, - editedChannelPostCallback = editedChannelPostCallback, - editedChannelPostMediaGroupCallback = mediaGroupCallback, - chosenInlineResultCallback = chosenInlineResultCallback, - inlineQueryCallback = inlineQueryCallback, - callbackQueryCallback = callbackQueryCallback, - shippingQueryCallback = shippingQueryCallback, - preCheckoutQueryCallback = preCheckoutQueryCallback, - requestsDelayMillis = requestsDelayMillis, - scope = scope -) - -fun RequestsExecutor.executeAsync( - request: Request, - onFail: (suspend (Response<*>) -> Unit)? = null, - scope: CoroutineScope = GlobalScope, - onSuccess: (suspend (T) -> Unit)? = null -): Job { - return scope.launch { - try { - val result = execute(request) - onSuccess ?.invoke(result) - } catch (e: RequestException) { - onFail ?.invoke(e.response) - } - } -} - -fun RequestsExecutor.executeAsync( - request: Request, - scope: CoroutineScope = GlobalScope -): Deferred { - return scope.async { execute(request) } -} - -suspend fun RequestsExecutor.executeUnsafe( - request: Request, - retries: Int = 0, - retriesDelay: Long = 1000L -): T? { - var leftRetries = retries - while(true) { - try { - return execute(request) - } catch (e: RequestException) { - if (leftRetries > 0) { - leftRetries-- - delay(retriesDelay) - } else { - return null - } - } - } -} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt new file mode 100644 index 0000000000..7d19100ddc --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt @@ -0,0 +1,99 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions + +import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor +import com.github.insanusmokrassar.TelegramBotAPI.requests.GetUpdates +import com.github.insanusmokrassar.TelegramBotAPI.types.UpdateIdentifier +import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdate +import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate +import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update +import com.github.insanusmokrassar.TelegramBotAPI.utils.mediaGroupId +import com.github.insanusmokrassar.TelegramBotAPI.utils.toMediaGroupUpdate +import kotlinx.coroutines.* +import java.util.concurrent.Executors + +private val updatesPollerRequestExecutorCollectedException = IllegalStateException("RequestsExecutor was collected by GC. Can't continue getting updates by polling") + +class UpdatesPoller( + requestsExecutor: RequestsExecutor, + private val requestsDelayMillis: Long = 1000, + private val scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), + private val allowedUpdates: List? = null, + private val block: UpdateReceiver +) { + private val executor = requestsExecutor.asReference() + private var lastHandledUpdate: UpdateIdentifier = 0L + private val mediaGroup: MutableList = mutableListOf() + + private var pollerJob: Job? = null + + private suspend fun sendToBlock(data: Any) { + block(data) + lastHandledUpdate = when (data) { + is Update -> data.updateId + is List<*> -> (data.last() as? Update) ?.updateId ?: throw IllegalStateException( + "Found non-updates oriented list" + ) + else -> throw IllegalStateException( + "Unknown type of data" + ) + } + } + + private suspend fun pushMediaGroupUpdate(mediaGroupUpdate: MediaGroupUpdate? = null) { + val inputMediaGroupId = mediaGroupUpdate ?.data ?.mediaGroupId + if (mediaGroup.isNotEmpty() && inputMediaGroupId ?.equals(mediaGroup.mediaGroupId) != true) { + sendToBlock(listOf(*mediaGroup.toTypedArray())) + mediaGroup.clear() + } + mediaGroupUpdate ?.let { + mediaGroup.add(it) + } + } + + private suspend fun getUpdates(): List { + return executor.get() ?.execute( + GetUpdates( + lastHandledUpdate + 1, // incremented because offset counted from 1 when updates id from 0 + allowed_updates = allowedUpdates + ) + ) ?.map { + it.asUpdate + } ?: throw updatesPollerRequestExecutorCollectedException + } + + private suspend fun handleUpdates(updates: List) { + updates.forEach { update -> + val mediaGroupUpdate = (update as? BaseMessageUpdate) ?.toMediaGroupUpdate() + mediaGroupUpdate ?.let { _ -> + pushMediaGroupUpdate(mediaGroupUpdate) + } ?: let { + pushMediaGroupUpdate() + sendToBlock(update) + } + } + + pushMediaGroupUpdate() + } + + fun start(): Job { + return pollerJob ?: scope.launch { + while (isActive) { + delay(requestsDelayMillis) + try { + handleUpdates(getUpdates()) + } catch (e: Exception) { + if (e == updatesPollerRequestExecutorCollectedException) { + throw IllegalArgumentException(e.message) + } + e.printStackTrace() + } + } + }.also { + pollerJob = it + } + } + + suspend fun stop() { + pollerJob ?.cancelAndJoin() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPolling.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPolling.kt new file mode 100644 index 0000000000..f4f6480035 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPolling.kt @@ -0,0 +1,89 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions + +import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor +import com.github.insanusmokrassar.TelegramBotAPI.types.update.* +import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate +import kotlinx.coroutines.* +import java.util.concurrent.Executors + +typealias UpdateReceiver = suspend (T) -> Unit + +fun RequestsExecutor.startGettingOfUpdates( + requestsDelayMillis: Long = 1000, + scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), + allowedUpdates: List? = null, + block: UpdateReceiver +): Job { + return UpdatesPoller(this, requestsDelayMillis, scope, allowedUpdates, block).start() +} + +fun RequestsExecutor.startGettingOfUpdates( + messageCallback: UpdateReceiver? = null, + messageMediaGroupCallback: UpdateReceiver>? = null, + editedMessageCallback: UpdateReceiver? = null, + editedMessageMediaGroupCallback: UpdateReceiver>? = null, + channelPostCallback: UpdateReceiver? = null, + channelPostMediaGroupCallback: UpdateReceiver>? = null, + editedChannelPostCallback: UpdateReceiver? = null, + editedChannelPostMediaGroupCallback: UpdateReceiver>? = null, + chosenInlineResultCallback: UpdateReceiver? = null, + inlineQueryCallback: UpdateReceiver? = null, + callbackQueryCallback: UpdateReceiver? = null, + shippingQueryCallback: UpdateReceiver? = null, + preCheckoutQueryCallback: UpdateReceiver? = null, + requestsDelayMillis: Long = 1000, + scope: CoroutineScope = GlobalScope +): Job { + val filter = UpdatesFilter( + messageCallback, + messageMediaGroupCallback, + editedMessageCallback, + editedMessageMediaGroupCallback, + channelPostCallback, + channelPostMediaGroupCallback, + editedChannelPostCallback, + editedChannelPostMediaGroupCallback, + chosenInlineResultCallback, + inlineQueryCallback, + callbackQueryCallback, + shippingQueryCallback, + preCheckoutQueryCallback + ) + return startGettingOfUpdates( + requestsDelayMillis, + scope, + filter.allowedUpdates, + filter.asUpdateReceiver + ) +} + +fun RequestsExecutor.startGettingOfUpdates( + messageCallback: UpdateReceiver? = null, + mediaGroupCallback: UpdateReceiver>? = null, + editedMessageCallback: UpdateReceiver? = null, + channelPostCallback: UpdateReceiver? = null, + editedChannelPostCallback: UpdateReceiver? = null, + chosenInlineResultCallback: UpdateReceiver? = null, + inlineQueryCallback: UpdateReceiver? = null, + callbackQueryCallback: UpdateReceiver? = null, + shippingQueryCallback: UpdateReceiver? = null, + preCheckoutQueryCallback: UpdateReceiver? = null, + requestsDelayMillis: Long = 1000, + scope: CoroutineScope = GlobalScope +): Job = startGettingOfUpdates( + messageCallback = messageCallback, + messageMediaGroupCallback = mediaGroupCallback, + editedMessageCallback = editedMessageCallback, + editedMessageMediaGroupCallback = mediaGroupCallback, + channelPostCallback = channelPostCallback, + channelPostMediaGroupCallback = mediaGroupCallback, + editedChannelPostCallback = editedChannelPostCallback, + editedChannelPostMediaGroupCallback = mediaGroupCallback, + chosenInlineResultCallback = chosenInlineResultCallback, + inlineQueryCallback = inlineQueryCallback, + callbackQueryCallback = callbackQueryCallback, + shippingQueryCallback = shippingQueryCallback, + preCheckoutQueryCallback = preCheckoutQueryCallback, + requestsDelayMillis = requestsDelayMillis, + scope = scope +) From 49fb020678049e103172154e2b010b301a3e73dd Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 6 Mar 2019 12:23:28 +0800 Subject: [PATCH 19/23] add private key config for webhook and opportunity to work with ssl directly in bot --- .../extensions/WebhookPrivateKeyConfig.kt | 25 +++++++++++++++++++ .../utils/extensions/Webhooks.kt | 23 +++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/WebhookPrivateKeyConfig.kt diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/WebhookPrivateKeyConfig.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/WebhookPrivateKeyConfig.kt new file mode 100644 index 0000000000..db2bfc3a02 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/WebhookPrivateKeyConfig.kt @@ -0,0 +1,25 @@ +package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import java.io.FileInputStream +import java.security.KeyStore + +@Serializable +data class WebhookPrivateKeyConfig( + @SerialName("keyStore") + private val keyStorePath: String, + private val keyStorePassword: String, + val aliasName: String, + private val aliasPassword: String +) { + @Transient + val keyStore = KeyStore.getInstance("JKS").apply { + load(FileInputStream(keyStorePath), keyStorePassword()) + } + + fun keyStorePassword(): CharArray = keyStorePassword.toCharArray() + + fun aliasPassword(): CharArray = aliasPassword.toCharArray() +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt index b86ff55bda..e7124e23c6 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Webhooks.kt @@ -17,9 +17,13 @@ import io.ktor.server.netty.Netty import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.serialization.json.Json +import java.io.FileInputStream +import java.security.KeyStore import java.util.concurrent.Executors import java.util.concurrent.TimeUnit + + /** * Reverse proxy webhook. * @@ -33,6 +37,7 @@ suspend fun RequestsExecutor.setWebhook( url: String, port: Int, certificate: InputFile? = null, + privateKeyConfig: WebhookPrivateKeyConfig? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), allowedUpdates: List? = null, maxAllowedConnections: Int? = null, @@ -62,6 +67,7 @@ suspend fun RequestsExecutor.setWebhook( scope = scope ) val env = applicationEngineEnvironment { + module { fun Application.main() { routing { @@ -78,10 +84,21 @@ suspend fun RequestsExecutor.setWebhook( } main() } - connector { - host = "0.0.0.0" + privateKeyConfig ?.let { + sslConnector( + privateKeyConfig.keyStore, + privateKeyConfig.aliasName, + privateKeyConfig::keyStorePassword, + privateKeyConfig::aliasPassword + ) { + host = "0.0.0.0" + this.port = port + } + } ?: connector { + host = "localhost" this.port = port } + } val engine = embeddedServer(engineFactory, env) @@ -120,6 +137,7 @@ suspend fun RequestsExecutor.setWebhook( port: Int, filter: UpdatesFilter, certificate: InputFile? = null, + privateKeyConfig: WebhookPrivateKeyConfig? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), maxAllowedConnections: Int? = null, engineFactory: ApplicationEngineFactory<*, *> = Netty @@ -127,6 +145,7 @@ suspend fun RequestsExecutor.setWebhook( url, port, certificate, + privateKeyConfig, scope, filter.allowedUpdates, maxAllowedConnections, From 7d028f854d7c91520629d4d6bf89e79a72101adc Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 7 Mar 2019 11:09:37 +0800 Subject: [PATCH 20/23] rename keyStorePath in serialize/deserialize variants --- .../TelegramBotAPI/utils/extensions/WebhookPrivateKeyConfig.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/WebhookPrivateKeyConfig.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/WebhookPrivateKeyConfig.kt index db2bfc3a02..bb5f4810c4 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/WebhookPrivateKeyConfig.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/WebhookPrivateKeyConfig.kt @@ -1,6 +1,5 @@ package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import java.io.FileInputStream @@ -8,7 +7,6 @@ import java.security.KeyStore @Serializable data class WebhookPrivateKeyConfig( - @SerialName("keyStore") private val keyStorePath: String, private val keyStorePassword: String, val aliasName: String, From 1afa870f6dcf3f1a43c9e14b326f638648bd6aa2 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 11 Mar 2019 11:00:38 +0800 Subject: [PATCH 21/23] now requests executor in updates poller is strong --- .../TelegramBotAPI/utils/extensions/UpdatesPoller.kt | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt index 7d19100ddc..dd1698d6b8 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt @@ -11,16 +11,13 @@ import com.github.insanusmokrassar.TelegramBotAPI.utils.toMediaGroupUpdate import kotlinx.coroutines.* import java.util.concurrent.Executors -private val updatesPollerRequestExecutorCollectedException = IllegalStateException("RequestsExecutor was collected by GC. Can't continue getting updates by polling") - class UpdatesPoller( - requestsExecutor: RequestsExecutor, + private val executor: RequestsExecutor, private val requestsDelayMillis: Long = 1000, private val scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), private val allowedUpdates: List? = null, private val block: UpdateReceiver ) { - private val executor = requestsExecutor.asReference() private var lastHandledUpdate: UpdateIdentifier = 0L private val mediaGroup: MutableList = mutableListOf() @@ -51,14 +48,14 @@ class UpdatesPoller( } private suspend fun getUpdates(): List { - return executor.get() ?.execute( + return executor.execute( GetUpdates( lastHandledUpdate + 1, // incremented because offset counted from 1 when updates id from 0 allowed_updates = allowedUpdates ) ) ?.map { it.asUpdate - } ?: throw updatesPollerRequestExecutorCollectedException + } } private suspend fun handleUpdates(updates: List) { @@ -82,9 +79,6 @@ class UpdatesPoller( try { handleUpdates(getUpdates()) } catch (e: Exception) { - if (e == updatesPollerRequestExecutorCollectedException) { - throw IllegalArgumentException(e.message) - } e.printStackTrace() } } From c167c556aefc95ad5989a1da71499442cf63fd76 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 12 Mar 2019 08:20:40 +0800 Subject: [PATCH 22/23] a little fixes --- .../TelegramBotAPI/utils/extensions/UpdatesPoller.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt index dd1698d6b8..fa604d42ab 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/UpdatesPoller.kt @@ -53,7 +53,7 @@ class UpdatesPoller( lastHandledUpdate + 1, // incremented because offset counted from 1 when updates id from 0 allowed_updates = allowedUpdates ) - ) ?.map { + ).map { it.asUpdate } } @@ -77,7 +77,8 @@ class UpdatesPoller( while (isActive) { delay(requestsDelayMillis) try { - handleUpdates(getUpdates()) + val updates = getUpdates() + handleUpdates(updates) } catch (e: Exception) { e.printStackTrace() } From 0cc03d757eb60cc835a0cc624869caa2b5c6efd6 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 12 Mar 2019 10:50:06 +0800 Subject: [PATCH 23/23] RequestsExecutor#executeUnsafe use do-while --- .../TelegramBotAPI/utils/extensions/Executes.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Executes.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Executes.kt index 789ca33c49..eb8b2f2679 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Executes.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/utils/extensions/Executes.kt @@ -36,16 +36,13 @@ suspend fun RequestsExecutor.executeUnsafe( retriesDelay: Long = 1000L ): T? { var leftRetries = retries - while(true) { + do { try { return execute(request) } catch (e: RequestException) { - if (leftRetries > 0) { - leftRetries-- - delay(retriesDelay) - } else { - return null - } + leftRetries-- + delay(retriesDelay) } - } + } while(leftRetries >= 0) + return null }