mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-12-23 00:57:13 +00:00
commit
7abaacb96d
23
CHANGELOG.md
23
CHANGELOG.md
@ -1,5 +1,28 @@
|
||||
# TelegramBotAPI changelog
|
||||
|
||||
## 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
|
||||
* 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
|
||||
* Now default `RequestException` will contain plain answer from telegram
|
||||
* Added `UnauthorizedException`
|
||||
* `RequestException` now is sealed
|
||||
* Rename `ReplyMessageNotFound` to `ReplyMessageNotFoundException`
|
||||
* Added `List<BaseMessageUpdate>#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
|
||||
|
||||
* Kotlin `1.3.11` -> `1.3.21`
|
||||
|
37
README.md
37
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
|
||||
@ -27,3 +33,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. 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).
|
||||
|
||||
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.
|
||||
|
@ -1,4 +1,4 @@
|
||||
project.version = "0.11.0"
|
||||
project.version = "0.12.0"
|
||||
project.group = "com.github.insanusmokrassar"
|
||||
|
||||
buildscript {
|
||||
@ -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'
|
||||
}
|
||||
|
@ -77,7 +77,8 @@ class KtorRequestsExecutor(
|
||||
} ?: call.let {
|
||||
throw newRequestException(
|
||||
responseObject,
|
||||
"Can't get result object"
|
||||
content,
|
||||
"Can't get result object from $content"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +0,0 @@
|
||||
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)
|
@ -5,18 +5,36 @@ 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" -> 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,
|
||||
cause: Throwable? = null
|
||||
) : IOException(
|
||||
message,
|
||||
cause
|
||||
)
|
||||
)
|
||||
|
||||
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
|
@ -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<String>? = 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<String>? = ALL_UPDATES_LIST
|
||||
): SimpleRequest<List<RawUpdate>> {
|
||||
override fun method(): String = "getUpdates"
|
||||
|
||||
|
@ -5,12 +5,13 @@ import com.github.insanusmokrassar.TelegramBotAPI.utils.toJsonWithoutNulls
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
|
||||
@Serializable(RequestSerializer::class)
|
||||
interface Request<T: Any> {
|
||||
fun method(): String
|
||||
fun resultSerializer(): KSerializer<T>
|
||||
@ImplicitReflectionSerializer
|
||||
fun json(): JsonObject = toJsonWithoutNulls()
|
||||
fun json(): JsonObject = toJsonWithoutNulls(RequestSerializer)
|
||||
}
|
||||
object RequestSerializer : KSerializer<Request<*>> by ContextSerializer(Request::class)
|
||||
|
||||
fun <T : Any> StringFormat.extractResult(
|
||||
from: String,
|
||||
|
@ -87,7 +87,7 @@ data class SendAnimationData internal constructor(
|
||||
@SerialName(replyMarkupField)
|
||||
@Optional
|
||||
override val replyMarkup: KeyboardMarkup? = null
|
||||
) : Data<RawMessage>,
|
||||
) : DataRequest<RawMessage>,
|
||||
SendMessageRequest<RawMessage>,
|
||||
ReplyingMarkupSendMessageRequest<RawMessage>,
|
||||
TextableSendMessageRequest<RawMessage>,
|
||||
|
@ -88,7 +88,7 @@ data class SendAudioData internal constructor(
|
||||
@SerialName(replyMarkupField)
|
||||
@Optional
|
||||
override val replyMarkup: KeyboardMarkup? = null
|
||||
) : Data<RawMessage>,
|
||||
) : DataRequest<RawMessage>,
|
||||
SendMessageRequest<RawMessage>,
|
||||
ReplyingMarkupSendMessageRequest<RawMessage>,
|
||||
TextableSendMessageRequest<RawMessage>,
|
||||
|
@ -72,7 +72,7 @@ data class SendDocumentData internal constructor(
|
||||
@SerialName(replyMarkupField)
|
||||
@Optional
|
||||
override val replyMarkup: KeyboardMarkup? = null
|
||||
) : Data<RawMessage>,
|
||||
) : DataRequest<RawMessage>,
|
||||
SendMessageRequest<RawMessage>,
|
||||
ReplyingMarkupSendMessageRequest<RawMessage>,
|
||||
TextableSendMessageRequest<RawMessage>,
|
||||
|
@ -66,7 +66,7 @@ data class SendMediaGroupData internal constructor(
|
||||
@SerialName(replyToMessageIdField)
|
||||
@Optional
|
||||
override val replyToMessageId: MessageIdentifier? = null
|
||||
) : Data<List<RawMessage>>,
|
||||
) : DataRequest<List<RawMessage>>,
|
||||
SendMessageRequest<List<RawMessage>>
|
||||
{
|
||||
@SerialName(mediaField)
|
||||
|
@ -58,7 +58,7 @@ data class SendPhotoData internal constructor(
|
||||
@SerialName(replyMarkupField)
|
||||
@Optional
|
||||
override val replyMarkup: KeyboardMarkup? = null
|
||||
) : Data<RawMessage>,
|
||||
) : DataRequest<RawMessage>,
|
||||
SendMessageRequest<RawMessage>,
|
||||
ReplyingMarkupSendMessageRequest<RawMessage>,
|
||||
TextableSendMessageRequest<RawMessage>
|
||||
|
@ -92,7 +92,7 @@ data class SendVideoData internal constructor(
|
||||
@SerialName(replyMarkupField)
|
||||
@Optional
|
||||
override val replyMarkup: KeyboardMarkup? = null
|
||||
) : Data<RawMessage>,
|
||||
) : DataRequest<RawMessage>,
|
||||
SendMessageRequest<RawMessage>,
|
||||
ReplyingMarkupSendMessageRequest<RawMessage>,
|
||||
TextableSendMessageRequest<RawMessage>,
|
||||
|
@ -82,7 +82,7 @@ data class SendVideoNoteData internal constructor(
|
||||
@SerialName(replyMarkupField)
|
||||
@Optional
|
||||
override val replyMarkup: KeyboardMarkup? = null
|
||||
) : Data<RawMessage>,
|
||||
) : DataRequest<RawMessage>,
|
||||
SendMessageRequest<RawMessage>,
|
||||
ReplyingMarkupSendMessageRequest<RawMessage>,
|
||||
TextableSendMessageRequest<RawMessage>,
|
||||
|
@ -77,7 +77,7 @@ data class SendVoiceData internal constructor(
|
||||
@SerialName(replyMarkupField)
|
||||
@Optional
|
||||
override val replyMarkup: KeyboardMarkup? = null
|
||||
) : Data<RawMessage>,
|
||||
) : DataRequest<RawMessage>,
|
||||
SendMessageRequest<RawMessage>,
|
||||
ReplyingMarkupSendMessageRequest<RawMessage>,
|
||||
TextableSendMessageRequest<RawMessage>,
|
||||
|
@ -1,5 +1,10 @@
|
||||
package com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.base
|
||||
|
||||
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest
|
||||
|
||||
interface Data<T: Any> : SimpleRequest<T>
|
||||
@Deprecated(
|
||||
"Renamed to DataRequest",
|
||||
ReplaceWith(
|
||||
"DataRequest",
|
||||
"com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.base.DataRequest"
|
||||
)
|
||||
)
|
||||
typealias Data<T> = DataRequest<T>
|
||||
|
@ -0,0 +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
|
||||
|
||||
@Serializable(DataRequestSerializer::class)
|
||||
interface DataRequest<T: Any> : SimpleRequest<T>
|
||||
|
||||
object DataRequestSerializer : KSerializer<DataRequest<*>> by ContextSerializer(DataRequest::class)
|
@ -9,7 +9,7 @@ import kotlinx.serialization.json.JsonObject
|
||||
/**
|
||||
* Will be used as SimpleRequest if
|
||||
*/
|
||||
class MultipartRequestImpl<D: Data<R>, F: Files, R: Any>(
|
||||
class MultipartRequestImpl<D: DataRequest<R>, F: Files, R: Any>(
|
||||
val data: D,
|
||||
val files: F
|
||||
) : MultipartRequest<R> {
|
||||
|
@ -0,0 +1,12 @@
|
||||
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.*
|
||||
|
||||
@Serializable
|
||||
class GetWebhookInfo : SimpleRequest<WebhookInfo> {
|
||||
override fun method(): String = "getWebhookInfo"
|
||||
|
||||
override fun resultSerializer(): KSerializer<WebhookInfo> = WebhookInfo.serializer()
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.github.insanusmokrassar.TelegramBotAPI.requests.webhook
|
||||
|
||||
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<String>? = null
|
||||
) : Request<Boolean> {
|
||||
val data = SetWebhook(
|
||||
url,
|
||||
(certificate as? FileId) ?.fileId,
|
||||
maxAllowedConnections,
|
||||
allowedUpdates
|
||||
)
|
||||
return when (certificate) {
|
||||
is FileId -> data
|
||||
is MultipartFile -> MultipartRequestImpl(
|
||||
data,
|
||||
mapOf(certificateField to certificate)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun SetWebhook(
|
||||
url: String,
|
||||
maxAllowedConnections: Int? = null,
|
||||
allowedUpdates: List<String>? = null
|
||||
) : Request<Boolean> = SetWebhook(
|
||||
url,
|
||||
null,
|
||||
maxAllowedConnections,
|
||||
allowedUpdates
|
||||
)
|
||||
|
||||
@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<String>? = null
|
||||
) : DataRequest<Boolean> {
|
||||
override fun method(): String = "setWebhook"
|
||||
override fun resultSerializer(): KSerializer<Boolean> = BooleanSerializer
|
||||
|
||||
init {
|
||||
maxAllowedConnections ?.let {
|
||||
if (it !in allowedConnectionsLength) {
|
||||
throw IllegalArgumentException("Allowed connection for webhook must be in $allowedConnectionsLength range (but passed $it)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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,12 @@ 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 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"
|
||||
@ -172,6 +179,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"
|
||||
|
@ -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
|
||||
)
|
@ -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<String> = 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
|
||||
}
|
@ -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
|
@ -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? = (this as? MediaGroupUpdate) ?: ((data as? MediaGroupMessage) ?.let {
|
||||
MediaGroupUpdate(updateId, it)
|
||||
})
|
@ -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 <reified T: Any> T.toJsonWithoutNulls(): JsonObject = Json.nonstrict.toJson(
|
||||
this
|
||||
|
@ -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<BaseMessageUpdate>.forwarded: ForwardedMessage?
|
||||
@ -17,3 +19,6 @@ val List<BaseMessageUpdate>.replyTo: Message?
|
||||
|
||||
val List<BaseMessageUpdate>.chat: Chat?
|
||||
get() = first().data.chat
|
||||
|
||||
val List<BaseMessageUpdate>.mediaGroupId: MediaGroupIdentifier?
|
||||
get() = (first().data as? MediaGroupMessage) ?.mediaGroupId
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
fun <T> T.asReference() = WeakReference(this)
|
@ -0,0 +1,48 @@
|
||||
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 <T: Any> RequestsExecutor.executeAsync(
|
||||
request: Request<T>,
|
||||
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 <T: Any> RequestsExecutor.executeAsync(
|
||||
request: Request<T>,
|
||||
scope: CoroutineScope = GlobalScope
|
||||
): Deferred<T> {
|
||||
return scope.async { execute(request) }
|
||||
}
|
||||
|
||||
suspend fun <T: Any> RequestsExecutor.executeUnsafe(
|
||||
request: Request<T>,
|
||||
retries: Int = 0,
|
||||
retriesDelay: Long = 1000L
|
||||
): T? {
|
||||
var leftRetries = retries
|
||||
do {
|
||||
try {
|
||||
return execute(request)
|
||||
} catch (e: RequestException) {
|
||||
leftRetries--
|
||||
delay(retriesDelay)
|
||||
}
|
||||
} while(leftRetries >= 0)
|
||||
return null
|
||||
}
|
@ -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<T> {
|
||||
abstract val value: T
|
||||
}
|
||||
|
||||
private data class AddValue<T>(override val value: T) : DebounceAction<T>()
|
||||
private data class RemoveJob<T>(override val value: T, val job: Job) : DebounceAction<T>()
|
||||
|
||||
fun <T> ReceiveChannel<T>.debounceByValue(
|
||||
delayMillis: Long,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
resultBroadcastChannelCapacity: Int = 32
|
||||
): ReceiveChannel<T> {
|
||||
val outChannel = Channel<T>(resultBroadcastChannelCapacity)
|
||||
val values = HashMap<T, Job>()
|
||||
|
||||
val channel = Channel<DebounceAction<T>>(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<K, V> = Pair<K, List<V>>
|
||||
|
||||
fun <K, V> ReceiveChannel<Pair<K, V>>.accumulateByKey(
|
||||
delayMillis: Long,
|
||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||
resultBroadcastChannelCapacity: Int = 32
|
||||
): ReceiveChannel<AccumulatedValues<K, V>> {
|
||||
val outChannel = Channel<AccumulatedValues<K, V>>(resultBroadcastChannelCapacity)
|
||||
val values = HashMap<K, MutableList<V>>()
|
||||
val jobs = HashMap<K, Job>()
|
||||
|
||||
val channel = Channel<DebounceAction<Pair<K, V>>>(Channel.UNLIMITED)
|
||||
scope.launch {
|
||||
for (action in channel) {
|
||||
val (key, value) = action.value
|
||||
when (action) {
|
||||
is AddValue -> {
|
||||
jobs[key] ?.cancel()
|
||||
(values[key] ?: mutableListOf<V>().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
|
||||
}
|
@ -1,248 +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<T> = suspend (T) -> Unit
|
||||
|
||||
fun RequestsExecutor.startGettingOfUpdates(
|
||||
requestsDelayMillis: Long = 1000,
|
||||
scope: CoroutineScope = GlobalScope,
|
||||
allowedUpdates: List<String>? = null,
|
||||
block: UpdateReceiver<Any>
|
||||
): 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<Any>()
|
||||
var mediaGroup: MutableList<Update>? = 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<MessageUpdate>? = null,
|
||||
messageMediaGroupCallback: UpdateReceiver<List<MessageUpdate>>? = null,
|
||||
editedMessageCallback: UpdateReceiver<EditMessageUpdate>? = null,
|
||||
editedMessageMediaGroupCallback: UpdateReceiver<List<EditMessageUpdate>>? = null,
|
||||
channelPostCallback: UpdateReceiver<ChannelPostUpdate>? = null,
|
||||
channelPostMediaGroupCallback: UpdateReceiver<List<ChannelPostUpdate>>? = null,
|
||||
editedChannelPostCallback: UpdateReceiver<EditChannelPostUpdate>? = null,
|
||||
editedChannelPostMediaGroupCallback: UpdateReceiver<List<EditChannelPostUpdate>>? = null,
|
||||
chosenInlineResultCallback: UpdateReceiver<ChosenInlineResultUpdate>? = null,
|
||||
inlineQueryCallback: UpdateReceiver<InlineQueryUpdate>? = null,
|
||||
callbackQueryCallback: UpdateReceiver<CallbackQueryUpdate>? = null,
|
||||
shippingQueryCallback: UpdateReceiver<ShippingQueryUpdate>? = null,
|
||||
preCheckoutQueryCallback: UpdateReceiver<PreCheckoutQueryUpdate>? = null,
|
||||
requestsDelayMillis: Long = 1000,
|
||||
scope: CoroutineScope = GlobalScope
|
||||
): Job {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun RequestsExecutor.startGettingOfUpdates(
|
||||
messageCallback: UpdateReceiver<MessageUpdate>? = null,
|
||||
mediaGroupCallback: UpdateReceiver<List<BaseMessageUpdate>>? = null,
|
||||
editedMessageCallback: UpdateReceiver<EditMessageUpdate>? = null,
|
||||
channelPostCallback: UpdateReceiver<ChannelPostUpdate>? = null,
|
||||
editedChannelPostCallback: UpdateReceiver<EditChannelPostUpdate>? = null,
|
||||
chosenInlineResultCallback: UpdateReceiver<ChosenInlineResultUpdate>? = null,
|
||||
inlineQueryCallback: UpdateReceiver<InlineQueryUpdate>? = null,
|
||||
callbackQueryCallback: UpdateReceiver<CallbackQueryUpdate>? = null,
|
||||
shippingQueryCallback: UpdateReceiver<ShippingQueryUpdate>? = null,
|
||||
preCheckoutQueryCallback: UpdateReceiver<PreCheckoutQueryUpdate>? = 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 <T: Any> RequestsExecutor.executeAsync(
|
||||
request: Request<T>,
|
||||
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 <T: Any> RequestsExecutor.executeAsync(
|
||||
request: Request<T>,
|
||||
scope: CoroutineScope = GlobalScope
|
||||
): Deferred<T> {
|
||||
return scope.async { execute(request) }
|
||||
}
|
||||
|
||||
suspend fun <T: Any> RequestsExecutor.executeUnsafe(
|
||||
request: Request<T>,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions
|
||||
|
||||
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
|
||||
|
||||
data class UpdatesFilter(
|
||||
private val messageCallback: UpdateReceiver<MessageUpdate>? = null,
|
||||
private val messageMediaGroupCallback: UpdateReceiver<List<MediaGroupUpdate>>? = null,
|
||||
private val editedMessageCallback: UpdateReceiver<EditMessageUpdate>? = null,
|
||||
private val editedMessageMediaGroupCallback: UpdateReceiver<List<MediaGroupUpdate>>? = null,
|
||||
private val channelPostCallback: UpdateReceiver<ChannelPostUpdate>? = null,
|
||||
private val channelPostMediaGroupCallback: UpdateReceiver<List<MediaGroupUpdate>>? = null,
|
||||
private val editedChannelPostCallback: UpdateReceiver<EditChannelPostUpdate>? = null,
|
||||
private val editedChannelPostMediaGroupCallback: UpdateReceiver<List<MediaGroupUpdate>>? = null,
|
||||
private val chosenInlineResultCallback: UpdateReceiver<ChosenInlineResultUpdate>? = null,
|
||||
private val inlineQueryCallback: UpdateReceiver<InlineQueryUpdate>? = null,
|
||||
private val callbackQueryCallback: UpdateReceiver<CallbackQueryUpdate>? = null,
|
||||
private val shippingQueryCallback: UpdateReceiver<ShippingQueryUpdate>? = null,
|
||||
private val preCheckoutQueryCallback: UpdateReceiver<PreCheckoutQueryUpdate>? = null
|
||||
) {
|
||||
val asUpdateReceiver: UpdateReceiver<Any> = 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.mapNotNull { it.toMediaGroupUpdate() })
|
||||
} ?: messageCallback ?.also { receiver ->
|
||||
mappedList.forEach { receiver(it) }
|
||||
}
|
||||
}
|
||||
is EditMessageUpdate -> update.mapNotNull { it as? EditMessageUpdate }.let { mappedList ->
|
||||
editedMessageMediaGroupCallback ?.also { receiver ->
|
||||
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.mapNotNull { it.toMediaGroupUpdate() })
|
||||
} ?: channelPostCallback ?.also { receiver ->
|
||||
mappedList.forEach { receiver(it) }
|
||||
}
|
||||
}
|
||||
is EditChannelPostUpdate -> update.mapNotNull { it as? EditChannelPostUpdate }.let { mappedList ->
|
||||
editedChannelPostMediaGroupCallback ?.also { receiver ->
|
||||
receiver(mappedList.mapNotNull { it.toMediaGroupUpdate() })
|
||||
} ?: 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<MessageUpdate>? = null,
|
||||
mediaGroupCallback: UpdateReceiver<List<BaseMessageUpdate>>? = null,
|
||||
editedMessageCallback: UpdateReceiver<EditMessageUpdate>? = null,
|
||||
channelPostCallback: UpdateReceiver<ChannelPostUpdate>? = null,
|
||||
editedChannelPostCallback: UpdateReceiver<EditChannelPostUpdate>? = null,
|
||||
chosenInlineResultCallback: UpdateReceiver<ChosenInlineResultUpdate>? = null,
|
||||
inlineQueryCallback: UpdateReceiver<InlineQueryUpdate>? = null,
|
||||
callbackQueryCallback: UpdateReceiver<CallbackQueryUpdate>? = null,
|
||||
shippingQueryCallback: UpdateReceiver<ShippingQueryUpdate>? = null,
|
||||
preCheckoutQueryCallback: UpdateReceiver<PreCheckoutQueryUpdate>? = 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
|
||||
)
|
@ -0,0 +1,94 @@
|
||||
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
|
||||
|
||||
class UpdatesPoller(
|
||||
private val executor: RequestsExecutor,
|
||||
private val requestsDelayMillis: Long = 1000,
|
||||
private val scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
|
||||
private val allowedUpdates: List<String>? = null,
|
||||
private val block: UpdateReceiver<Any>
|
||||
) {
|
||||
private var lastHandledUpdate: UpdateIdentifier = 0L
|
||||
private val mediaGroup: MutableList<MediaGroupUpdate> = 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<Update> {
|
||||
return executor.execute(
|
||||
GetUpdates(
|
||||
lastHandledUpdate + 1, // incremented because offset counted from 1 when updates id from 0
|
||||
allowed_updates = allowedUpdates
|
||||
)
|
||||
).map {
|
||||
it.asUpdate
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleUpdates(updates: List<Update>) {
|
||||
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 {
|
||||
val updates = getUpdates()
|
||||
handleUpdates(updates)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
pollerJob = it
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun stop() {
|
||||
pollerJob ?.cancelAndJoin()
|
||||
}
|
||||
}
|
@ -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<T> = suspend (T) -> Unit
|
||||
|
||||
fun RequestsExecutor.startGettingOfUpdates(
|
||||
requestsDelayMillis: Long = 1000,
|
||||
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
|
||||
allowedUpdates: List<String>? = null,
|
||||
block: UpdateReceiver<Any>
|
||||
): Job {
|
||||
return UpdatesPoller(this, requestsDelayMillis, scope, allowedUpdates, block).start()
|
||||
}
|
||||
|
||||
fun RequestsExecutor.startGettingOfUpdates(
|
||||
messageCallback: UpdateReceiver<MessageUpdate>? = null,
|
||||
messageMediaGroupCallback: UpdateReceiver<List<MediaGroupUpdate>>? = null,
|
||||
editedMessageCallback: UpdateReceiver<EditMessageUpdate>? = null,
|
||||
editedMessageMediaGroupCallback: UpdateReceiver<List<MediaGroupUpdate>>? = null,
|
||||
channelPostCallback: UpdateReceiver<ChannelPostUpdate>? = null,
|
||||
channelPostMediaGroupCallback: UpdateReceiver<List<MediaGroupUpdate>>? = null,
|
||||
editedChannelPostCallback: UpdateReceiver<EditChannelPostUpdate>? = null,
|
||||
editedChannelPostMediaGroupCallback: UpdateReceiver<List<MediaGroupUpdate>>? = null,
|
||||
chosenInlineResultCallback: UpdateReceiver<ChosenInlineResultUpdate>? = null,
|
||||
inlineQueryCallback: UpdateReceiver<InlineQueryUpdate>? = null,
|
||||
callbackQueryCallback: UpdateReceiver<CallbackQueryUpdate>? = null,
|
||||
shippingQueryCallback: UpdateReceiver<ShippingQueryUpdate>? = null,
|
||||
preCheckoutQueryCallback: UpdateReceiver<PreCheckoutQueryUpdate>? = 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<MessageUpdate>? = null,
|
||||
mediaGroupCallback: UpdateReceiver<List<BaseMessageUpdate>>? = null,
|
||||
editedMessageCallback: UpdateReceiver<EditMessageUpdate>? = null,
|
||||
channelPostCallback: UpdateReceiver<ChannelPostUpdate>? = null,
|
||||
editedChannelPostCallback: UpdateReceiver<EditChannelPostUpdate>? = null,
|
||||
chosenInlineResultCallback: UpdateReceiver<ChosenInlineResultUpdate>? = null,
|
||||
inlineQueryCallback: UpdateReceiver<InlineQueryUpdate>? = null,
|
||||
callbackQueryCallback: UpdateReceiver<CallbackQueryUpdate>? = null,
|
||||
shippingQueryCallback: UpdateReceiver<ShippingQueryUpdate>? = null,
|
||||
preCheckoutQueryCallback: UpdateReceiver<PreCheckoutQueryUpdate>? = 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
|
||||
)
|
@ -0,0 +1,23 @@
|
||||
package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import java.io.FileInputStream
|
||||
import java.security.KeyStore
|
||||
|
||||
@Serializable
|
||||
data class WebhookPrivateKeyConfig(
|
||||
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()
|
||||
}
|
@ -0,0 +1,154 @@
|
||||
package com.github.insanusmokrassar.TelegramBotAPI.utils.extensions
|
||||
|
||||
import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor
|
||||
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.*
|
||||
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.io.FileInputStream
|
||||
import java.security.KeyStore
|
||||
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? = null,
|
||||
privateKeyConfig: WebhookPrivateKeyConfig? = null,
|
||||
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
|
||||
allowedUpdates: List<String>? = null,
|
||||
maxAllowedConnections: Int? = null,
|
||||
engineFactory: ApplicationEngineFactory<*, *> = Netty,
|
||||
block: UpdateReceiver<Any>
|
||||
): Job {
|
||||
val executeDeferred = certificate ?.let {
|
||||
executeAsync(
|
||||
SetWebhook(
|
||||
url,
|
||||
certificate,
|
||||
maxAllowedConnections,
|
||||
allowedUpdates
|
||||
)
|
||||
)
|
||||
} ?: executeAsync(
|
||||
SetWebhook(
|
||||
url,
|
||||
maxAllowedConnections,
|
||||
allowedUpdates
|
||||
)
|
||||
)
|
||||
val updatesChannel = Channel<Update>(Channel.UNLIMITED)
|
||||
val mediaGroupChannel = Channel<Pair<MediaGroupIdentifier, Update>>(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()
|
||||
}
|
||||
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)
|
||||
|
||||
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,
|
||||
filter: UpdatesFilter,
|
||||
certificate: InputFile? = null,
|
||||
privateKeyConfig: WebhookPrivateKeyConfig? = null,
|
||||
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
|
||||
maxAllowedConnections: Int? = null,
|
||||
engineFactory: ApplicationEngineFactory<*, *> = Netty
|
||||
): Job = setWebhook(
|
||||
url,
|
||||
port,
|
||||
certificate,
|
||||
privateKeyConfig,
|
||||
scope,
|
||||
filter.allowedUpdates,
|
||||
maxAllowedConnections,
|
||||
engineFactory,
|
||||
filter.asUpdateReceiver
|
||||
)
|
Loading…
Reference in New Issue
Block a user