mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2025-12-09 15:55:51 +00:00
Compare commits
18 Commits
930507ab80
...
0.38.17
| Author | SHA1 | Date | |
|---|---|---|---|
| 682f696866 | |||
| 87fff2e5d0 | |||
| 92b4ba2ff0 | |||
| 9014cdbc99 | |||
| 37317a1055 | |||
| 60c21002e1 | |||
| 78a7a3546a | |||
| 4799617ced | |||
| c70484076d | |||
| 0c92e6eeb4 | |||
| efd1c8f83a | |||
| 7e57a0e4e0 | |||
| 6b977e67d0 | |||
| ab5937449c | |||
| 61d3131bf2 | |||
| 048f244449 | |||
| 6ebf4ff652 | |||
| b4c41d7dd8 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
||||
# TelegramBotAPI changelog
|
||||
|
||||
## 0.38.17
|
||||
|
||||
* `Core`:
|
||||
* Add `BotCommandScopeChat` as new `BotCommandScope` (fix of [#574](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/574))
|
||||
* `BotCommandScope` companion got several properties and functions for more useful scope creation
|
||||
|
||||
## 0.38.16
|
||||
|
||||
* `Core`:
|
||||
* `TelegramAPIUrlsKeeper` now have two new things: properties `webAppDataSecretKey` and fun `checkWebAppLink`
|
||||
|
||||
## 0.38.15
|
||||
|
||||
* `Common`:
|
||||
* `Version`:
|
||||
* `MicroUtils`: `0.9.20` -> `0.9.24`
|
||||
* `Core`:
|
||||
* Fixes in `MessageContent#serializationModule`
|
||||
* `BehaviourBuilder`:
|
||||
* Add triggers for `DataCallbackQuery` and subtypes with regex checking of data
|
||||
|
||||
## 0.38.14
|
||||
|
||||
__This update contains including of [Telegram Bot API 6.0](https://core.telegram.org/bots/api-changelog#april-16-2022)__
|
||||
|
||||
@@ -12,7 +12,7 @@ klock_version=2.7.0
|
||||
uuid_version=0.4.0
|
||||
ktor_version=1.6.8
|
||||
|
||||
micro_utils_version=0.9.20
|
||||
micro_utils_version=0.9.24
|
||||
|
||||
javax_activation_version=1.1.1
|
||||
|
||||
@@ -20,6 +20,6 @@ javax_activation_version=1.1.1
|
||||
dokka_version=1.6.10
|
||||
|
||||
library_group=dev.inmo
|
||||
library_version=0.38.14
|
||||
library_version=0.38.17
|
||||
|
||||
github_release_plugin_version=2.3.7
|
||||
|
||||
@@ -26,7 +26,7 @@ internal inline fun <BC, T, I1, I2> CustomBehaviourContextAndTwoTypesReceiver<BC
|
||||
): CustomBehaviourContextAndTypeReceiver<BC, T, I2> = { invoke(this, i1, it) }
|
||||
|
||||
/**
|
||||
* This class contains all necessary tools for work with bots and especially for [buildBehaviour]
|
||||
* This class contains all necessary tools for work with bots and especially [buildBehaviour]
|
||||
*
|
||||
* @see DefaultBehaviourContext
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CallbackQueryFilte
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByUserCallbackQueryMarkerFactory
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus
|
||||
import dev.inmo.tgbotapi.extensions.utils.asCallbackQueryUpdate
|
||||
import dev.inmo.tgbotapi.types.CallbackQuery.*
|
||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||
@@ -44,6 +45,62 @@ suspend fun <BC : BehaviourContext> BC.onDataCallbackQuery(
|
||||
scenarioReceiver
|
||||
)
|
||||
|
||||
/**
|
||||
* @param dataRegex Will be used with [initialFilter] as [initialFilter] for upstream [onDataCallbackQuery] to filter
|
||||
* [DataCallbackQuery] with [DataCallbackQuery.data] [String.matches] to [dataRegex]
|
||||
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
|
||||
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
|
||||
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
|
||||
* to combinate several filters
|
||||
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
|
||||
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
|
||||
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
|
||||
* data
|
||||
*/
|
||||
suspend fun <BC : BehaviourContext> BC.onDataCallbackQuery(
|
||||
dataRegex: Regex,
|
||||
initialFilter: SimpleFilter<DataCallbackQuery>? = null,
|
||||
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, DataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||
markerFactory: MarkerFactory<in DataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, DataCallbackQuery>
|
||||
) = onDataCallbackQuery(
|
||||
initialFilter = initialFilter + {
|
||||
it.data.matches(dataRegex)
|
||||
},
|
||||
subcontextUpdatesFilter,
|
||||
markerFactory,
|
||||
scenarioReceiver
|
||||
)
|
||||
|
||||
/**
|
||||
* @param data Will be converted to [Regex] via its constructor and pass it to upstream [onDataCallbackQuery]
|
||||
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
|
||||
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
|
||||
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
|
||||
* to combinate several filters
|
||||
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
|
||||
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
|
||||
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
|
||||
* data
|
||||
*/
|
||||
suspend fun <BC : BehaviourContext> BC.onDataCallbackQuery(
|
||||
data: String,
|
||||
initialFilter: SimpleFilter<DataCallbackQuery>? = null,
|
||||
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, DataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||
markerFactory: MarkerFactory<in DataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, DataCallbackQuery>
|
||||
) = onDataCallbackQuery(
|
||||
Regex(data),
|
||||
initialFilter,
|
||||
subcontextUpdatesFilter,
|
||||
markerFactory,
|
||||
scenarioReceiver
|
||||
)
|
||||
|
||||
/**
|
||||
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
|
||||
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
|
||||
@@ -116,6 +173,62 @@ suspend fun <BC : BehaviourContext> BC.onInlineMessageIdDataCallbackQuery(
|
||||
scenarioReceiver
|
||||
)
|
||||
|
||||
/**
|
||||
* @param dataRegex Will be used with [initialFilter] as [initialFilter] for upstream [onInlineMessageIdDataCallbackQuery]
|
||||
* to filter [InlineMessageIdDataCallbackQuery] with [InlineMessageIdDataCallbackQuery.data] [String.matches] to [dataRegex]
|
||||
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
|
||||
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
|
||||
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
|
||||
* to combinate several filters
|
||||
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
|
||||
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
|
||||
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
|
||||
* data
|
||||
*/
|
||||
suspend fun <BC : BehaviourContext> BC.onInlineMessageIdDataCallbackQuery(
|
||||
dataRegex: Regex,
|
||||
initialFilter: SimpleFilter<InlineMessageIdDataCallbackQuery>? = null,
|
||||
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, InlineMessageIdDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||
markerFactory: MarkerFactory<in InlineMessageIdDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, InlineMessageIdDataCallbackQuery>
|
||||
) = onInlineMessageIdDataCallbackQuery(
|
||||
initialFilter = initialFilter + {
|
||||
it.data.matches(dataRegex)
|
||||
},
|
||||
subcontextUpdatesFilter,
|
||||
markerFactory,
|
||||
scenarioReceiver
|
||||
)
|
||||
|
||||
/**
|
||||
* @param data Will be converted to [Regex] via its constructor and pass it to upstream [onInlineMessageIdDataCallbackQuery]
|
||||
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
|
||||
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
|
||||
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
|
||||
* to combinate several filters
|
||||
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
|
||||
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
|
||||
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
|
||||
* data
|
||||
*/
|
||||
suspend fun <BC : BehaviourContext> BC.onInlineMessageIdDataCallbackQuery(
|
||||
data: String,
|
||||
initialFilter: SimpleFilter<InlineMessageIdDataCallbackQuery>? = null,
|
||||
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, InlineMessageIdDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||
markerFactory: MarkerFactory<in InlineMessageIdDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, InlineMessageIdDataCallbackQuery>
|
||||
) = onInlineMessageIdDataCallbackQuery(
|
||||
Regex(data),
|
||||
initialFilter,
|
||||
subcontextUpdatesFilter,
|
||||
markerFactory,
|
||||
scenarioReceiver
|
||||
)
|
||||
|
||||
/**
|
||||
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
|
||||
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
|
||||
@@ -188,6 +301,62 @@ suspend fun <BC : BehaviourContext> BC.onMessageDataCallbackQuery(
|
||||
scenarioReceiver
|
||||
)
|
||||
|
||||
/**
|
||||
* @param dataRegex Will be used with [initialFilter] as [initialFilter] for upstream [onMessageDataCallbackQuery] to filter
|
||||
* [MessageDataCallbackQuery] with [MessageDataCallbackQuery.data] [String.matches] to [dataRegex]
|
||||
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
|
||||
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
|
||||
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
|
||||
* to combinate several filters
|
||||
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
|
||||
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
|
||||
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
|
||||
* data
|
||||
*/
|
||||
suspend fun <BC : BehaviourContext> BC.onMessageDataCallbackQuery(
|
||||
dataRegex: Regex,
|
||||
initialFilter: SimpleFilter<MessageDataCallbackQuery>? = null,
|
||||
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, MessageDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||
markerFactory: MarkerFactory<in MessageDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, MessageDataCallbackQuery>
|
||||
) = onMessageDataCallbackQuery(
|
||||
initialFilter = initialFilter + {
|
||||
it.data.matches(dataRegex)
|
||||
},
|
||||
subcontextUpdatesFilter,
|
||||
markerFactory,
|
||||
scenarioReceiver
|
||||
)
|
||||
|
||||
/**
|
||||
* @param data Will be converted to [Regex] via its constructor and pass it to upstream [onMessageDataCallbackQuery]
|
||||
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
|
||||
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
|
||||
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
|
||||
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
|
||||
* to combinate several filters
|
||||
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
|
||||
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
|
||||
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
|
||||
* data
|
||||
*/
|
||||
suspend fun <BC : BehaviourContext> BC.onMessageDataCallbackQuery(
|
||||
data: String,
|
||||
initialFilter: SimpleFilter<MessageDataCallbackQuery>? = null,
|
||||
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, MessageDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||
markerFactory: MarkerFactory<in MessageDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, MessageDataCallbackQuery>
|
||||
) = onMessageDataCallbackQuery(
|
||||
Regex(data),
|
||||
initialFilter,
|
||||
subcontextUpdatesFilter,
|
||||
markerFactory,
|
||||
scenarioReceiver
|
||||
)
|
||||
|
||||
/**
|
||||
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
|
||||
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
|
||||
|
||||
@@ -28,16 +28,20 @@ fun <T> SimpleFilter<T>.listNone() = SimpleFilter<Iterable<T>> {
|
||||
/**
|
||||
* Makes an AND (&&) operation between [this] and [other]
|
||||
*/
|
||||
operator fun <T> SimpleFilter<T>.times(other: SimpleFilter<T>): SimpleFilter<T> = {
|
||||
this(it) && other(it)
|
||||
}
|
||||
operator fun <T> SimpleFilter<T>?.times(other: SimpleFilter<T>): SimpleFilter<T> = this ?.let {
|
||||
{
|
||||
this(it) && other(it)
|
||||
}
|
||||
} ?: other
|
||||
|
||||
/**
|
||||
* Makes an OR (||) operation between [this] and [other]
|
||||
*/
|
||||
operator fun <T> SimpleFilter<T>.plus(other: SimpleFilter<T>): SimpleFilter<T> = {
|
||||
this(it) || other(it)
|
||||
}
|
||||
operator fun <T> SimpleFilter<T>?.plus(other: SimpleFilter<T>): SimpleFilter<T> = this ?.let {
|
||||
{
|
||||
this(it) || other(it)
|
||||
}
|
||||
} ?: other
|
||||
|
||||
/**
|
||||
* Reverse results of [this]
|
||||
|
||||
@@ -22,11 +22,14 @@ private class SurrogateBotCommandScope(
|
||||
BotCommandScopeAllGroupChats.type -> BotCommandScopeAllGroupChats
|
||||
BotCommandScopeAllChatAdministrators.type -> BotCommandScopeAllChatAdministrators
|
||||
BotCommandScopeChatAdministrators.type -> BotCommandScopeChatAdministrators(
|
||||
chatId ?: error("chat_administrators type must have $chatIdField field, but have no")
|
||||
chatId ?: error("${BotCommandScopeChatAdministrators.type} type must have $chatIdField field, but have no")
|
||||
)
|
||||
BotCommandScopeChatMember.type -> BotCommandScopeChatMember(
|
||||
chatId ?: error("chat_administrators type must have $chatIdField field, but have no"),
|
||||
userId ?: error("chat_administrators type must have $userIdField field, but have no")
|
||||
chatId ?: error("${BotCommandScopeChatMember.type} type must have $chatIdField field, but have no"),
|
||||
userId ?: error("${BotCommandScopeChatMember.type} type must have $userIdField field, but have no")
|
||||
)
|
||||
BotCommandScopeChat.type -> BotCommandScopeChat(
|
||||
chatId ?: error("${BotCommandScopeChat.type} type must have $chatIdField field, but have no")
|
||||
)
|
||||
else -> UnknownBotCommandScope(type)
|
||||
}
|
||||
@@ -40,6 +43,7 @@ private class SurrogateBotCommandScope(
|
||||
BotCommandScopeAllChatAdministrators -> SurrogateBotCommandScope(scope.type)
|
||||
is BotCommandScopeChatAdministrators -> SurrogateBotCommandScope(scope.type, scope.chatId)
|
||||
is BotCommandScopeChatMember -> SurrogateBotCommandScope(scope.type, scope.chatId, scope.userId)
|
||||
is BotCommandScopeChat -> SurrogateBotCommandScope(scope.type, scope.chatId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +51,16 @@ private class SurrogateBotCommandScope(
|
||||
@Serializable(BotCommandScopeSerializer::class)
|
||||
sealed interface BotCommandScope {
|
||||
val type: String
|
||||
|
||||
companion object {
|
||||
val Default = BotCommandScopeDefault
|
||||
val AllPrivateChats = BotCommandScopeAllPrivateChats
|
||||
val AllGroupChats = BotCommandScopeAllGroupChats
|
||||
val AllChatAdministrators = BotCommandScopeAllChatAdministrators
|
||||
fun ChatAdministrators(chatId: ChatIdentifier) = BotCommandScopeChatAdministrators(chatId)
|
||||
fun Chat(chatId: ChatIdentifier) = BotCommandScopeChat(chatId)
|
||||
fun ChatMember(chatId: ChatIdentifier, userId: UserId) = BotCommandScopeChatMember(chatId, userId)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -94,6 +108,17 @@ data class BotCommandScopeChatAdministrators(
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BotCommandScopeChat(
|
||||
override val chatId: ChatIdentifier
|
||||
) : ChatBotCommandScope {
|
||||
@Required
|
||||
override val type: String = BotCommandScopeChat.type
|
||||
companion object {
|
||||
const val type = "chat"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BotCommandScopeChatMember(
|
||||
override val chatId: ChatIdentifier,
|
||||
|
||||
@@ -20,8 +20,7 @@ interface MessageContent: ResendableContent {
|
||||
subclass(DiceContent::class)
|
||||
subclass(TextContent::class)
|
||||
|
||||
subclass(LiveLocationContent::class)
|
||||
subclass(StaticLocationContent::class)
|
||||
subclass(LocationContent::class, LocationContentSerializer)
|
||||
|
||||
subclass(PhotoContent::class)
|
||||
subclass(VideoContent::class)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package dev.inmo.tgbotapi.utils
|
||||
|
||||
import dev.inmo.micro_utils.crypto.SourceBytes
|
||||
import dev.inmo.micro_utils.crypto.SourceString
|
||||
|
||||
internal expect fun SourceString.hmacSha256(key: String): String
|
||||
private val HEX_ARRAY = "0123456789abcdef".toCharArray()
|
||||
|
||||
internal fun SourceBytes.hex(): String {
|
||||
val hexChars = CharArray(size * 2)
|
||||
for (j in indices) {
|
||||
val v: Int = this[j].toInt() and 0xFF
|
||||
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
|
||||
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
|
||||
}
|
||||
return hexChars.concatToString()
|
||||
}
|
||||
|
||||
internal fun SourceString.hex(): String = encodeToByteArray().hex()
|
||||
@@ -18,6 +18,10 @@ class TelegramAPIUrlsKeeper(
|
||||
token: String,
|
||||
hostUrl: String = telegramBotAPIDefaultUrl
|
||||
) {
|
||||
val webAppDataSecretKey by lazy {
|
||||
token.hmacSha256("WebAppData")
|
||||
}
|
||||
|
||||
val commonAPIUrl: String
|
||||
val fileBaseUrl: String
|
||||
|
||||
@@ -28,4 +32,10 @@ class TelegramAPIUrlsKeeper(
|
||||
}
|
||||
|
||||
fun createFileLinkUrl(filePath: String) = "${fileBaseUrl}/$filePath"
|
||||
|
||||
/**
|
||||
* @param rawData Data from [dev.inmo.tgbotapi.webapps.WebApp.initData]
|
||||
* @param hash Data from [dev.inmo.tgbotapi.webapps.WebApp.initDataUnsafe] from the field [dev.inmo.tgbotapi.webapps.WebAppInitData.hash]
|
||||
*/
|
||||
fun checkWebAppLink(rawData: String, hash: String) = rawData.hmacSha256(webAppDataSecretKey).hex() == hash
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package dev.inmo.tgbotapi.utils
|
||||
|
||||
import dev.inmo.micro_utils.crypto.CryptoJS
|
||||
import dev.inmo.micro_utils.crypto.SourceString
|
||||
|
||||
actual fun SourceString.hmacSha256(key: String) = CryptoJS.asDynamic().HmacSHA256(this, key).toString().unsafeCast<String>()
|
||||
@@ -0,0 +1,14 @@
|
||||
package dev.inmo.tgbotapi.utils
|
||||
|
||||
import dev.inmo.micro_utils.crypto.SourceString
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
actual fun SourceString.hmacSha256(key: String): String {
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
|
||||
val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
|
||||
mac.init(secretKey)
|
||||
|
||||
return mac.doFinal(toByteArray()).hex()
|
||||
}
|
||||
@@ -27,6 +27,13 @@ repositories {
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
compilations.main {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
js(IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.tgbotapi.webapps
|
||||
|
||||
import dev.inmo.micro_utils.crypto.CryptoJS
|
||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||
|
||||
external class WebApp {
|
||||
val initData: String
|
||||
@@ -76,6 +77,7 @@ fun WebApp.onMainButtonClicked(eventHandler: EventHandler) = onEvent(EventType.M
|
||||
*/
|
||||
fun WebApp.onViewportChanged(eventHandler: ViewportChangedEventHandler) = onEvent(EventType.ViewportChanged, eventHandler)
|
||||
|
||||
fun WebApp.isInitDataSafe(botToken: String) = CryptoJS.hex(
|
||||
CryptoJS.HmacSHA256(botToken, "WebAppData")
|
||||
) == initDataUnsafe.hash
|
||||
fun WebApp.isInitDataSafe(botToken: String) = TelegramAPIUrlsKeeper(botToken).checkWebAppLink(
|
||||
initData,
|
||||
initDataUnsafe.hash
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user