1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2025-12-29 09:29:23 +00:00

Compare commits

...

38 Commits

Author SHA1 Message Date
f539a24740 Update CHANGELOG.md 2022-09-08 14:36:10 +06:00
1d7b4ea862 Update libs.versions.toml 2022-09-08 14:35:17 +06:00
2d6f296c20 update dependencies 2022-09-07 16:33:29 +06:00
0ba1aa1127 start 3.2.1 2022-09-07 16:32:37 +06:00
ae8a461e9d Merge pull request #648 from InsanusMokrassar/3.2.0
3.2.0
2022-08-26 16:36:27 +06:00
99f16e33a6 Update CHANGELOG.md 2022-08-26 12:44:11 +06:00
e029b29f7f fixes 2022-08-26 12:20:20 +06:00
c04f795fdd migrate onto 3.2.0 from 3.1.2 2022-08-26 11:00:36 +06:00
b873898100 wrap client request exception into exception handling 2022-08-25 13:35:15 +06:00
61ac9df5e3 BotException 2022-08-25 13:32:12 +06:00
b1931900e7 Update BotCommandTextSource.kt 2022-08-25 02:11:04 +06:00
fab3af48d6 Update WaitCommandsMessages.kt 2022-08-25 02:04:19 +06:00
8d7563b6e4 Update WaitCommandsMessages.kt 2022-08-25 02:02:45 +06:00
05112afe0c add deeplinks triggers 2022-08-24 15:32:34 +06:00
9ea06de27c add waitCommands* expectations 2022-08-24 15:03:54 +06:00
fff05a40d9 revert adding of mention waiters 2022-08-24 13:29:57 +06:00
5c6428f220 update microutils 2022-08-23 13:13:06 +06:00
766b7ca205 add waiters for mentions 2022-08-23 13:12:22 +06:00
41e6c52369 allowedUpdates is ALL_UPDATES_LIST by default everywhere 2022-08-23 11:55:28 +06:00
06013f624f update dependencies 2022-08-23 11:48:55 +06:00
d8bba89f3f start 3.1.2 2022-08-23 11:47:31 +06:00
141281f96d Merge pull request #643 from InsanusMokrassar/3.1.1
3.1.1
2022-08-15 01:31:59 +06:00
be7aaa7845 fixes and improvements in webapps 2022-08-15 01:29:24 +06:00
78c224ffa8 fix on asUser fun 2022-08-15 01:21:10 +06:00
a08d07f7b3 fixes in popup params and buttons 2022-08-15 01:20:29 +06:00
69dde19543 Update CHANGELOG.md 2022-08-14 22:13:03 +06:00
e8a3b93831 Update Extended.kt 2022-08-14 22:11:55 +06:00
e10caa3171 add support of has_restricted_voice_and_video_messages 2022-08-14 21:56:25 +06:00
d4fe4e09fc add support of Bot API 6.2 in webapp parts 2022-08-14 21:52:26 +06:00
ad453afba2 start 3.1.1 2022-08-14 18:55:08 +06:00
a0daca28b1 Merge pull request #642 from InsanusMokrassar/3.1.0
3.1.0
2022-08-13 14:44:00 +06:00
cd62a9ef3c fixes 2022-08-13 14:40:00 +06:00
3c084d70e5 fix of #588 2022-08-13 13:13:07 +06:00
2611d4ecc1 fill changelog with dependnecies updates 2022-08-13 13:06:18 +06:00
b4d853dfa0 update bot api support info 2022-08-13 13:03:34 +06:00
5044075adf add support of sticker_type for new sticker sets and rework create new sticker set requests 2022-08-13 13:01:09 +06:00
5f2660b804 fill raw sticker data 2022-08-13 12:20:28 +06:00
c72dccc0f9 rework of stickers and stickersets 2022-08-13 11:58:42 +06:00
50 changed files with 1719 additions and 247 deletions

View File

@@ -1,9 +1,41 @@
# TelegramBotAPI changelog # TelegramBotAPI changelog
## 3.2.1
* `Versions`:
* `Ktor`: `2.1.0` -> `2.1.1`
* `Korlibs`: `3.0.0` -> `3.1.0`
* `MicroUtils`: `0.12.4` -> `0.12.10`
## 3.2.0
**Since this update, `RequestsExecutor#execute` may throw only `BotException`. In case you wish to handle some exceptions from `execute` you must catch `BotException` and handle its `cause`**
* `Versions`:
* `Serialization`: `1.4.0-RC` -> `1.4.0`
* `MicroUtils`: `0.12.1` -> `0.12.4`
* `Core`:
* `SetWebhook#allowedUpdates` now is `ALL_UPDATES_LIST` by default instead of `null`
* `API`:
* Extension `TelegramBot#setWebhook` parameter `allowedUpdates` now is `ALL_UPDATES_LIST` by default instead of `null`
* `Utils`:
* All related to long polling extensions parameters `allowedUpdates` now are `ALL_UPDATES_LIST` by default instead of `null`
## 3.1.1
* `Common`:
* Complete Bot API 6.2 implementation
## 3.1.0 ## 3.1.0
**This update contains including of Bot API 6.2**
* `Versions`:
* `Ktor`: `2.0.3` -> `2.1.0`
* `MicroUtils`: `0.12.0` -> `0.12.1`
* `Core`: * `Core`:
* Add support of `custom emoji`s * Add support of `custom emoji`s
* Add support of `sticker_type`
## 3.0.2 ## 3.0.2

View File

@@ -1,4 +1,4 @@
# TelegramBotAPI [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi) [![Supported version](https://img.shields.io/badge/Telegram%20Bot%20API-6.1-blue)](https://core.telegram.org/bots/api-changelog#june-20-2022) # TelegramBotAPI [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi) [![Supported version](https://img.shields.io/badge/Telegram%20Bot%20API-6.2-blue)](https://core.telegram.org/bots/api-changelog#august-12-2022)
| Docs | [![KDocs](https://img.shields.io/static/v1?label=Dokka&message=KDocs&color=blue&logo=kotlin)](https://tgbotapi.inmo.dev/index.html) [![Mini tutorial](https://img.shields.io/static/v1?label=Bookstack&message=Tutorial&color=blue&logo=bookstack)](https://bookstack.inmo.dev/books/telegrambotapi/chapter/introduction-tutorial) | | Docs | [![KDocs](https://img.shields.io/static/v1?label=Dokka&message=KDocs&color=blue&logo=kotlin)](https://tgbotapi.inmo.dev/index.html) [![Mini tutorial](https://img.shields.io/static/v1?label=Bookstack&message=Tutorial&color=blue&logo=bookstack)](https://bookstack.inmo.dev/books/telegrambotapi/chapter/introduction-tutorial) |
|:---:|:---:| |:---:|:---:|

View File

@@ -6,4 +6,4 @@ kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
library_group=dev.inmo library_group=dev.inmo
library_version=3.1.0 library_version=3.2.1

View File

@@ -1,19 +1,19 @@
[versions] [versions]
kotlin = "1.7.10" kotlin = "1.7.10"
kotlin-serialization = "1.4.0-RC" kotlin-serialization = "1.4.0"
kotlin-coroutines = "1.6.4" kotlin-coroutines = "1.6.4"
javax-activation = "1.1.1" javax-activation = "1.1.1"
korlibs = "3.0.0" korlibs = "3.1.0"
uuid = "0.5.0" uuid = "0.5.0"
ktor = "2.1.0" ktor = "2.1.1"
ksp = "1.7.10-1.0.6" ksp = "1.7.10-1.0.6"
kotlin-poet = "1.12.0" kotlin-poet = "1.12.0"
microutils = "0.12.1" microutils = "0.12.10"
github-release-plugin = "2.4.1" github-release-plugin = "2.4.1"

View File

@@ -0,0 +1,37 @@
package dev.inmo.tgbotapi.extensions.api.get
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.get.GetCustomEmojiStickers
import dev.inmo.tgbotapi.requests.get.GetStickerSet
import dev.inmo.tgbotapi.types.CustomEmojiId
import dev.inmo.tgbotapi.types.files.Sticker
import kotlin.js.JsName
import kotlin.jvm.JvmName
suspend fun TelegramBot.getCustomEmojiStickers(
customEmojiIds: List<CustomEmojiId>
) = execute(
GetCustomEmojiStickers(customEmojiIds)
)
@JvmName("getCustomEmojiStickersWithStringsList")
@JsName("getCustomEmojiStickersWithStringsList")
suspend fun TelegramBot.getCustomEmojiStickers(
customEmojiIds: List<String>
) = getCustomEmojiStickers(customEmojiIds.map(::CustomEmojiId))
suspend fun TelegramBot.getCustomEmojiStickerOrNull(
customEmojiId: CustomEmojiId
) = getCustomEmojiStickers(listOf(customEmojiId)).firstOrNull()
suspend fun TelegramBot.getCustomEmojiStickerOrThrow(
customEmojiId: CustomEmojiId
) = getCustomEmojiStickers(listOf(customEmojiId)).first()
suspend fun TelegramBot.getCustomEmojiStickerOrNull(
customEmojiId: String
) = getCustomEmojiStickerOrNull(CustomEmojiId(customEmojiId))
suspend fun TelegramBot.getCustomEmojiStickerOrThrow(
customEmojiId: String
) = getCustomEmojiStickerOrThrow(CustomEmojiId(customEmojiId))

View File

@@ -0,0 +1,54 @@
package dev.inmo.tgbotapi.extensions.api.stickers
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import dev.inmo.tgbotapi.requests.stickers.*
import dev.inmo.tgbotapi.types.chat.CommonUser
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.stickers.MaskPosition
suspend fun TelegramBot.createNewMaskAnimatedStickerSet(
userId: UserId,
name: String,
title: String,
sticker: FileId,
emojis: String,
maskPosition: MaskPosition
) = execute(
CreateNewMaskAnimatedStickerSet(userId, name, title, sticker, emojis, maskPosition)
)
suspend fun TelegramBot.createNewMaskAnimatedStickerSet(
userId: UserId,
name: String,
title: String,
sticker: MultipartFile,
emojis: String,
maskPosition: MaskPosition
) = execute(
CreateNewMaskAnimatedStickerSet(userId, name, title, sticker, emojis, maskPosition)
)
suspend fun TelegramBot.createNewMaskAnimatedStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: FileId,
emojis: String,
maskPosition: MaskPosition
) = createNewMaskAnimatedStickerSet(
user.id, name, title, sticker, emojis, maskPosition
)
suspend fun TelegramBot.createNewMaskAnimatedStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: MultipartFile,
emojis: String,
maskPosition: MaskPosition
) = createNewMaskAnimatedStickerSet(
user.id, name, title, sticker, emojis, maskPosition
)

View File

@@ -0,0 +1,54 @@
package dev.inmo.tgbotapi.extensions.api.stickers
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import dev.inmo.tgbotapi.requests.stickers.*
import dev.inmo.tgbotapi.types.chat.CommonUser
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.stickers.MaskPosition
suspend fun TelegramBot.createNewMaskStickerSet(
userId: UserId,
name: String,
title: String,
sticker: FileId,
emojis: String,
maskPosition: MaskPosition
) = execute(
CreateNewMaskStickerSet(userId, name, title, sticker, emojis, maskPosition)
)
suspend fun TelegramBot.createNewMaskStickerSet(
userId: UserId,
name: String,
title: String,
sticker: MultipartFile,
emojis: String,
maskPosition: MaskPosition
) = execute(
CreateNewMaskStickerSet(userId, name, title, sticker, emojis, maskPosition)
)
suspend fun TelegramBot.createNewMaskStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: FileId,
emojis: String,
maskPosition: MaskPosition
) = createNewMaskStickerSet(
user.id, name, title, sticker, emojis, maskPosition
)
suspend fun TelegramBot.createNewMaskStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: MultipartFile,
emojis: String,
maskPosition: MaskPosition
) = createNewMaskStickerSet(
user.id, name, title, sticker, emojis, maskPosition
)

View File

@@ -0,0 +1,54 @@
package dev.inmo.tgbotapi.extensions.api.stickers
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import dev.inmo.tgbotapi.requests.stickers.*
import dev.inmo.tgbotapi.types.chat.CommonUser
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.stickers.MaskPosition
suspend fun TelegramBot.createNewMaskVideoStickerSet(
userId: UserId,
name: String,
title: String,
sticker: FileId,
emojis: String,
maskPosition: MaskPosition
) = execute(
CreateNewMaskVideoStickerSet(userId, name, title, sticker, emojis, maskPosition)
)
suspend fun TelegramBot.createNewMaskVideoStickerSet(
userId: UserId,
name: String,
title: String,
sticker: MultipartFile,
emojis: String,
maskPosition: MaskPosition
) = execute(
CreateNewMaskVideoStickerSet(userId, name, title, sticker, emojis, maskPosition)
)
suspend fun TelegramBot.createNewMaskVideoStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: FileId,
emojis: String,
maskPosition: MaskPosition
) = createNewMaskVideoStickerSet(
user.id, name, title, sticker, emojis, maskPosition
)
suspend fun TelegramBot.createNewMaskVideoStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: MultipartFile,
emojis: String,
maskPosition: MaskPosition
) = createNewMaskVideoStickerSet(
user.id, name, title, sticker, emojis, maskPosition
)

View File

@@ -0,0 +1,50 @@
package dev.inmo.tgbotapi.extensions.api.stickers
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import dev.inmo.tgbotapi.requests.stickers.*
import dev.inmo.tgbotapi.types.chat.CommonUser
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.stickers.MaskPosition
suspend fun TelegramBot.createNewRegularAnimatedStickerSet(
userId: UserId,
name: String,
title: String,
sticker: FileId,
emojis: String
) = execute(
CreateNewRegularAnimatedStickerSet(userId, name, title, sticker, emojis)
)
suspend fun TelegramBot.createNewRegularAnimatedStickerSet(
userId: UserId,
name: String,
title: String,
sticker: MultipartFile,
emojis: String
) = execute(
CreateNewRegularAnimatedStickerSet(userId, name, title, sticker, emojis)
)
suspend fun TelegramBot.createNewRegularAnimatedStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: FileId,
emojis: String
) = createNewRegularAnimatedStickerSet(
user.id, name, title, sticker, emojis
)
suspend fun TelegramBot.createNewRegularAnimatedStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: MultipartFile,
emojis: String
) = createNewRegularAnimatedStickerSet(
user.id, name, title, sticker, emojis
)

View File

@@ -0,0 +1,50 @@
package dev.inmo.tgbotapi.extensions.api.stickers
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import dev.inmo.tgbotapi.requests.stickers.*
import dev.inmo.tgbotapi.types.chat.CommonUser
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.stickers.MaskPosition
suspend fun TelegramBot.createNewRegularStickerSet(
userId: UserId,
name: String,
title: String,
sticker: FileId,
emojis: String
) = execute(
CreateNewRegularStickerSet(userId, name, title, sticker, emojis)
)
suspend fun TelegramBot.createNewRegularStickerSet(
userId: UserId,
name: String,
title: String,
sticker: MultipartFile,
emojis: String
) = execute(
CreateNewRegularStickerSet(userId, name, title, sticker, emojis)
)
suspend fun TelegramBot.createNewRegularStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: FileId,
emojis: String
) = createNewRegularStickerSet(
user.id, name, title, sticker, emojis
)
suspend fun TelegramBot.createNewRegularStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: MultipartFile,
emojis: String
) = createNewRegularStickerSet(
user.id, name, title, sticker, emojis
)

View File

@@ -0,0 +1,50 @@
package dev.inmo.tgbotapi.extensions.api.stickers
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import dev.inmo.tgbotapi.requests.stickers.*
import dev.inmo.tgbotapi.types.chat.CommonUser
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.stickers.MaskPosition
suspend fun TelegramBot.createNewRegularVideoStickerSet(
userId: UserId,
name: String,
title: String,
sticker: FileId,
emojis: String
) = execute(
CreateNewRegularVideoStickerSet(userId, name, title, sticker, emojis)
)
suspend fun TelegramBot.createNewRegularVideoStickerSet(
userId: UserId,
name: String,
title: String,
sticker: MultipartFile,
emojis: String
) = execute(
CreateNewRegularVideoStickerSet(userId, name, title, sticker, emojis)
)
suspend fun TelegramBot.createNewRegularVideoStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: FileId,
emojis: String
) = createNewRegularVideoStickerSet(
user.id, name, title, sticker, emojis
)
suspend fun TelegramBot.createNewRegularVideoStickerSet(
user: CommonUser,
name: String,
title: String,
sticker: MultipartFile,
emojis: String
) = createNewRegularVideoStickerSet(
user.id, name, title, sticker, emojis
)

View File

@@ -4,6 +4,7 @@ import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.abstracts.FileId import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import dev.inmo.tgbotapi.requests.webhook.SetWebhook import dev.inmo.tgbotapi.requests.webhook.SetWebhook
import dev.inmo.tgbotapi.types.ALL_UPDATES_LIST
/** /**
* Use this method to send information about webhook (like [url]) * Use this method to send information about webhook (like [url])
@@ -12,7 +13,7 @@ suspend fun TelegramBot.setWebhookInfo(
url: String, url: String,
ipAddress: String? = null, ipAddress: String? = null,
maxAllowedConnections: Int? = null, maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
dropPendingUpdates: Boolean? = null, dropPendingUpdates: Boolean? = null,
secretToken: String? = null secretToken: String? = null
) = execute( ) = execute(
@@ -29,7 +30,7 @@ suspend fun TelegramBot.setWebhookInfo(
certificate: FileId, certificate: FileId,
ipAddress: String? = null, ipAddress: String? = null,
maxAllowedConnections: Int? = null, maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
dropPendingUpdates: Boolean? = null, dropPendingUpdates: Boolean? = null,
secretToken: String? = null secretToken: String? = null
) = execute( ) = execute(
@@ -46,7 +47,7 @@ suspend fun TelegramBot.setWebhookInfo(
certificate: MultipartFile, certificate: MultipartFile,
ipAddress: String? = null, ipAddress: String? = null,
maxAllowedConnections: Int? = null, maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
dropPendingUpdates: Boolean? = null, dropPendingUpdates: Boolean? = null,
secretToken: String? = null secretToken: String? = null
) = execute( ) = execute(

View File

@@ -0,0 +1,119 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.doWithRegistration
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource
import dev.inmo.tgbotapi.types.message.textsources.TextSource
import kotlinx.coroutines.flow.*
/**
* Will filter all the messages and include required commands with [commandRegex].
*
* * In case you wish to get only the commands at the start of message, use [requireCommandAtStart]
* * In case you wish to exclude messages with more than one command, you may use [requireSingleCommand]
* * In case you wish to exclude messages with commands params, you may use [requireCommandsWithoutParams]
*/
suspend fun BehaviourContext.waitCommandMessage(
commandRegex: Regex,
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null }
) = channelFlow {
triggersHolder.handleableCommandsHolder.doWithRegistration(
commandRegex
) {
waitTextMessage(initRequest, errorFactory).filter {
it.content.textSources.any { it.botCommandTextSourceOrNull() ?.command ?.matches(commandRegex) == true }
}.collect {
send(it)
}
}
}
suspend fun BehaviourContext.waitCommandMessage(
command: String,
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null }
) = waitCommandMessage(Regex(command), initRequest, errorFactory)
fun Flow<CommonMessage<TextContent>>.requireCommandAtStart() = filter {
it.content.textSources.firstOrNull() is BotCommandTextSource
}
/**
* Subsequent [Flow] will retrieve only messages with ONE [BotCommandTextSource]. It does not guarantee that this
* [BotCommandTextSource] will be at the start of the message
*
* @see requireCommandAtStart
*/
fun Flow<CommonMessage<TextContent>>.requireSingleCommand() = filter {
var count = 0
it.content.textSources.forEach {
if (it is BotCommandTextSource) {
count++
if (count > 1) {
return@filter false
}
}
}
count == 1
}
/**
* Subsequent [Flow] will retrieve only messages without [TextContent.textSources] which are not [BotCommandTextSource]
*/
fun Flow<CommonMessage<TextContent>>.requireCommandsWithoutParams() = filter {
it.content.textSources.none { it !is BotCommandTextSource }
}
/**
* Map the commands with their arguments and source messages
*/
fun Flow<CommonMessage<TextContent>>.commandsWithParams(): Flow<Pair<CommonMessage<TextContent>, List<Pair<BotCommandTextSource, Array<TextSource>>>>> = mapNotNull {
var currentCommandTextSource: BotCommandTextSource? = null
val currentArgs = mutableListOf<TextSource>()
val result = mutableListOf<Pair<BotCommandTextSource, Array<TextSource>>>()
fun addCurrentCommandToResult() {
currentCommandTextSource ?.let {
result.add(it to currentArgs.toTypedArray())
currentArgs.clear()
}
}
it.content.textSources.forEach {
it.ifBotCommandTextSource {
addCurrentCommandToResult()
currentCommandTextSource = it
return@forEach
}
currentArgs.add(it)
}
addCurrentCommandToResult()
result.toList().takeIf { it.isNotEmpty() } ?.let { result ->
it to result
}
}
/**
* Flat [commandsWithParams]. Each [Pair] of [BotCommandTextSource] and its [Array] of arg text sources will
* be associated with its source message
*/
fun Flow<CommonMessage<TextContent>>.flattenCommandsWithParams() = commandsWithParams().flatMapConcat { (message, commandsWithParams) ->
commandsWithParams.map {
message to it
}.asFlow()
}
/**
* Use [flattenCommandsWithParams] and filter out the commands which do not [matches] to [commandRegex]
*/
fun Flow<CommonMessage<TextContent>>.commandParams(commandRegex: Regex) = flattenCommandsWithParams().filter { (_, commandWithParams) ->
commandWithParams.first.command.matches(commandRegex)
}

View File

@@ -0,0 +1,23 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.utils.regularTextSourceOrNull
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.textsources.RegularTextSource
import kotlinx.coroutines.flow.*
suspend fun BehaviourContext.waitDeepLinks(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
): Flow<Pair<CommonMessage<TextContent>, String>> = waitCommandMessage(
"start",
initRequest,
errorFactory
)
.requireSingleCommand()
.requireCommandAtStart()
.flattenCommandsWithParams().mapNotNull {
it.first to (it.second.second.singleOrNull() ?.regularTextSourceOrNull() ?.source ?.removePrefix(" ") ?: return@mapNotNull null)
}

View File

@@ -0,0 +1,48 @@
@file:Suppress("unused")
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.micro_utils.coroutines.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitDeepLinks
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByChatMessageMarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.content.TextMessage
import dev.inmo.tgbotapi.types.message.textsources.RegularTextSource
import dev.inmo.tgbotapi.types.update.abstracts.Update
import io.ktor.http.decodeURLQueryComponent
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.filter
private val startRegex = Regex("start")
suspend fun <BC : BehaviourContext> BC.onDeepLink(
initialFilter: SimpleFilter<Pair<TextMessage, String>>? = null,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, Pair<TextMessage, String>, Update> = { (message, _), update -> MessageFilterByChat(this, message, update) },
markerFactory: MarkerFactory<Pair<TextMessage, String>, Any> = MarkerFactory { (message, _) -> ByChatMessageMarkerFactory(message) },
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, Pair<TextMessage, String>>
): Job = on(
markerFactory,
SimpleFilter<Pair<TextMessage, String>> { (message, _) ->
message.content.textSources.size == 2
&& message.content.textSources.firstOrNull() ?.asBotCommandTextSource() ?.command == "start"
&& message.content.textSources.getOrNull(1) is RegularTextSource
} * initialFilter,
subcontextUpdatesFilter,
scenarioReceiver,
) {
(it.messageUpdateOrNull()) ?.data ?.commonMessageOrNull() ?.withContentOrNull<TextContent>() ?.let { message ->
message to message.content.textSources[1].source.removePrefix(" ").decodeURLQueryComponent()
} ?.let(::listOfNotNull)
}.also {
triggersHolder.handleableCommandsHolder.registerHandleable(startRegex)
it.invokeOnCompletion {
this@onDeepLink.launchSafelyWithoutExceptions { triggersHolder.handleableCommandsHolder.unregisterHandleable(startRegex) }
}
}

View File

@@ -4,6 +4,7 @@ fun interface SimpleFilter<in T> {
suspend operator fun invoke(o: T): Boolean suspend operator fun invoke(o: T): Boolean
} }
val TrueSimpleFilter = SimpleFilter<Any?> { true } val TrueSimpleFilter = SimpleFilter<Any?> { true }
val FalseSimpleFilter = SimpleFilter<Any?> { false }
/** /**
* @return [SimpleFilter] which will return true in case when all the items in incoming data passed [this] filter * @return [SimpleFilter] which will return true in case when all the items in incoming data passed [this] filter
@@ -28,21 +29,29 @@ fun <T> SimpleFilter<T>.listNone() = SimpleFilter<Iterable<T>> {
/** /**
* Makes an AND (&&) operation between [this] and [other] * Makes an AND (&&) operation between [this] and [other]
*
* * When both arguments are null, [TrueSimpleFilter] will be returned
*/ */
operator fun <T> SimpleFilter<T>?.times(other: SimpleFilter<T>): SimpleFilter<T> = this ?.let { infix operator fun <T> SimpleFilter<T>?.times(other: SimpleFilter<T>?): SimpleFilter<T> = this ?.let {
SimpleFilter { other ?.let {
this(it) && other(it) SimpleFilter {
} this(it) && other(it)
} ?: other }
} ?: it
} ?: other ?: TrueSimpleFilter
/** /**
* Makes an OR (||) operation between [this] and [other] * Makes an OR (||) operation between [this] and [other]
*
* * When both arguments are null, [TrueSimpleFilter] will be returned
*/ */
operator fun <T> SimpleFilter<T>?.plus(other: SimpleFilter<T>): SimpleFilter<T> = this ?.let { infix operator fun <T> SimpleFilter<T>?.plus(other: SimpleFilter<T>?): SimpleFilter<T> = this ?.let {
SimpleFilter { other ?.let {
this(it) || other(it) SimpleFilter {
} this(it) || other(it)
} ?: other }
} ?: it
} ?: other ?: TrueSimpleFilter
/** /**
* Reverse results of [this] * Reverse results of [this]

View File

@@ -1,5 +1,6 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@@ -7,6 +8,7 @@ open class HandleableTriggersHolder<T>(
preset: List<T> = emptyList() preset: List<T> = emptyList()
) { ) {
protected val commandsMutex = Mutex() protected val commandsMutex = Mutex()
protected val handleableCounts = mutableMapOf<T, Int>()
protected val _handleable = mutableListOf<T>().also { protected val _handleable = mutableListOf<T>().also {
it.addAll(preset) it.addAll(preset)
} }
@@ -16,12 +18,31 @@ open class HandleableTriggersHolder<T>(
suspend fun registerHandleable(data: T) { suspend fun registerHandleable(data: T) {
commandsMutex.withLock { commandsMutex.withLock {
_handleable.add(data) _handleable.add(data)
handleableCounts[data] = (handleableCounts[data] ?: 0) + 1
} }
} }
suspend fun unregisterHandleable(data: T) { suspend fun unregisterHandleable(data: T) {
commandsMutex.withLock { commandsMutex.withLock {
_handleable.remove(data) val newHandleableCount = (handleableCounts[data] ?: 0) - 1
if (newHandleableCount > 0) {
handleableCounts[data] = newHandleableCount
} else {
handleableCounts.remove(data)
_handleable.remove(data)
}
} }
} }
} }
suspend fun <T, R> HandleableTriggersHolder<T>.doWithRegistration(
data: T,
block: suspend () -> R
): R {
registerHandleable(data)
val result = runCatchingSafely {
block()
}
unregisterHandleable(data)
return result.getOrThrow()
}

View File

@@ -35,13 +35,18 @@ fun newRequestException(
} }
} ?: CommonRequestException(response, plainAnswer, message, cause) } ?: CommonRequestException(response, plainAnswer, message, cause)
sealed class BotException(message: String = "Something went wrong", cause: Throwable? = null) : IOException(message, cause)
class CommonBotException(message: String = "Something went wrong", cause: Throwable? = null) : BotException(message, cause)
sealed class RequestException constructor( sealed class RequestException constructor(
val response: Response, val response: Response,
val plainAnswer: String, val plainAnswer: String,
message: String? = null, message: String? = null,
override val cause: Throwable? = null cause: Throwable? = null
) : IOException( ) : BotException(
message ?: "Something went wrong" message ?: "Something went wrong",
cause
) )
class CommonRequestException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) : class CommonRequestException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) :

View File

@@ -1,9 +1,10 @@
package dev.inmo.tgbotapi.bot.ktor package dev.inmo.tgbotapi.bot.ktor
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.safely import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.bot.exceptions.newRequestException import dev.inmo.tgbotapi.bot.exceptions.*
import dev.inmo.tgbotapi.bot.ktor.base.* import dev.inmo.tgbotapi.bot.ktor.base.*
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
@@ -48,12 +49,36 @@ class KtorRequestsExecutor(
} }
override suspend fun <T : Any> execute(request: Request<T>): T { override suspend fun <T : Any> execute(request: Request<T>): T {
return runCatching { return runCatchingSafely {
safely( pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
{ e -> requestsLimiter.limit {
pipelineStepsHolder.onRequestException(request, e) ?.let { return@safely it } var result: T? = null
lateinit var factoryHandledRequest: KtorCallFactory
for (potentialFactory in callsFactories) {
pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory)
result = potentialFactory.makeCall(
client,
telegramAPIUrlsKeeper,
request,
jsonFormatter
)
result = pipelineStepsHolder.onAfterCallFactoryMakeCall(result, request, potentialFactory)
if (result != null) {
factoryHandledRequest = potentialFactory
break
}
}
throw if (e is ClientRequestException) { result ?.let {
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
}
}.let {
val result = it.exceptionOrNull() ?.let { e ->
pipelineStepsHolder.onRequestException(request, e) ?.let { return@let it }
if (e is ClientRequestException) {
val exceptionResult = runCatchingSafely {
val content = e.response.bodyAsText() val content = e.response.bodyAsText()
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content) val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
newRequestException( newRequestException(
@@ -61,38 +86,15 @@ class KtorRequestsExecutor(
content, content,
"Can't get result object from $content" "Can't get result object from $content"
) )
} else {
e
} }
exceptionResult.exceptionOrNull() ?.let {
CommonBotException(cause = e)
} ?: exceptionResult.getOrThrow()
} else {
CommonBotException(cause = e)
} }
) { } ?.let { Result.failure(it) } ?: it
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories) pipelineStepsHolder.onRequestReturnResult(result, request, callsFactories)
requestsLimiter.limit {
var result: T? = null
lateinit var factoryHandledRequest: KtorCallFactory
for (potentialFactory in callsFactories) {
pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory)
result = potentialFactory.makeCall(
client,
telegramAPIUrlsKeeper,
request,
jsonFormatter
)
result = pipelineStepsHolder.onAfterCallFactoryMakeCall(result, request, potentialFactory)
if (result != null) {
factoryHandledRequest = potentialFactory
break
}
}
result ?.let {
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
}
}
}.let {
pipelineStepsHolder.onRequestReturnResult(it, request, callsFactories)
} }
} }

View File

@@ -4,17 +4,19 @@ import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.files.CustomEmojiSticker import dev.inmo.tgbotapi.types.files.CustomEmojiSticker
import dev.inmo.tgbotapi.types.files.StickerSerializer import dev.inmo.tgbotapi.types.files.StickerSerializer
import dev.inmo.tgbotapi.types.stickers.StickerSet
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
internal val getCustomEmojiStickersResultSerializer = ListSerializer(StickerSerializer) as DeserializationStrategy<List<CustomEmojiSticker>>
@Serializable @Serializable
data class GetCustomEmojiStickers( data class GetCustomEmojiStickers(
@SerialName(customEmojiIdsField) @SerialName(customEmojiIdsField)
val customEmojiIds: List<CustomEmojiId> val customEmojiIds: List<CustomEmojiId>
): SimpleRequest<CustomEmojiSticker> { ): SimpleRequest<List<CustomEmojiSticker>> {
override fun method(): String = "getCustomEmojiStickers" override fun method(): String = "getCustomEmojiStickers"
override val resultDeserializer: DeserializationStrategy<CustomEmojiSticker> override val resultDeserializer: DeserializationStrategy<List<CustomEmojiSticker>>
get() = StickerSerializer as DeserializationStrategy<CustomEmojiSticker> get() = getCustomEmojiStickersResultSerializer
override val requestSerializer: SerializationStrategy<*> override val requestSerializer: SerializationStrategy<*>
get() = serializer() get() = serializer()
} }

View File

@@ -1,13 +1,14 @@
package dev.inmo.tgbotapi.requests.get package dev.inmo.tgbotapi.requests.get
import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest
import dev.inmo.tgbotapi.types.nameField
import dev.inmo.tgbotapi.types.stickerSetNameField import dev.inmo.tgbotapi.types.stickerSetNameField
import dev.inmo.tgbotapi.types.stickers.StickerSet import dev.inmo.tgbotapi.types.stickers.StickerSet
import kotlinx.serialization.* import kotlinx.serialization.*
@Serializable @Serializable
data class GetStickerSet( data class GetStickerSet(
@SerialName(stickerSetNameField) @SerialName(nameField)
val name: String val name: String
): SimpleRequest<StickerSet> { ): SimpleRequest<StickerSet> {
override fun method(): String = "getStickerSet" override fun method(): String = "getStickerSet"

View File

@@ -8,26 +8,8 @@ import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.stickers.MaskPosition import dev.inmo.tgbotapi.types.stickers.MaskPosition
import kotlinx.serialization.* import kotlinx.serialization.*
fun CreateNewAnimatedStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String,
containsMasks: Boolean? = null,
maskPosition: MaskPosition? = null
): Request<Boolean> {
val data = CreateNewAnimatedStickerSet(userId, name, title, emojis, sticker as? FileId, containsMasks, maskPosition)
return when (sticker) {
is MultipartFile -> CommonMultipartFileRequest(
data,
mapOf(tgsStickerField to sticker)
)
is FileId -> data
}
}
@Serializable @Serializable
@Deprecated("Use CreateNewStickerSet class instead")
data class CreateNewAnimatedStickerSet internal constructor( data class CreateNewAnimatedStickerSet internal constructor(
@SerialName(userIdField) @SerialName(userIdField)
override val userId: UserId, override val userId: UserId,

View File

@@ -8,36 +8,8 @@ import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.stickers.MaskPosition import dev.inmo.tgbotapi.types.stickers.MaskPosition
import kotlinx.serialization.* import kotlinx.serialization.*
fun CreateNewStaticStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String,
containsMasks: Boolean? = null,
maskPosition: MaskPosition? = null
): Request<Boolean> {
val data = CreateNewStaticStickerSet(userId, name, title, emojis, sticker as? FileId, containsMasks, maskPosition)
return when (sticker) {
is MultipartFile -> CommonMultipartFileRequest(
data,
mapOf(pngStickerField to sticker)
)
is FileId -> data
}
}
fun CreateNewStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String,
containsMasks: Boolean? = null,
maskPosition: MaskPosition? = null
): Request<Boolean> = CreateNewStaticStickerSet(userId, name, title, sticker, emojis, containsMasks, maskPosition)
@Serializable @Serializable
@Deprecated("Use CreateNewStickerSet class instead")
data class CreateNewStaticStickerSet internal constructor( data class CreateNewStaticStickerSet internal constructor(
@SerialName(userIdField) @SerialName(userIdField)
override val userId: UserId, override val userId: UserId,

View File

@@ -0,0 +1,77 @@
package dev.inmo.tgbotapi.requests.stickers
import dev.inmo.tgbotapi.requests.abstracts.*
import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest
import dev.inmo.tgbotapi.requests.stickers.abstracts.CreateStickerSetAction
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.stickers.MaskPosition
import kotlinx.serialization.*
internal fun CreateNewStickerSet(
userId: UserId,
name: String,
title: String,
emojis: String,
stickerType: StickerType = StickerType.Regular,
pngSticker: InputFile? = null,
tgsSticker: InputFile? = null,
webmSticker: InputFile? = null,
maskPosition: MaskPosition? = null
): Request<Boolean> {
val data = CreateNewStickerSet(
userId,
name,
title,
emojis,
stickerType,
pngSticker as? FileId,
tgsSticker as? FileId,
webmSticker as? FileId,
maskPosition
)
return if (pngSticker is MultipartFile || tgsSticker is MultipartFile || webmSticker is MultipartFile) {
CommonMultipartFileRequest(
data,
listOfNotNull(
(pngSticker as? MultipartFile) ?.let { pngStickerField to it },
(tgsSticker as? MultipartFile) ?.let { tgsStickerField to it },
(webmSticker as? MultipartFile) ?.let { webmStickerField to it },
).toMap()
)
} else {
data
}
}
@Serializable
data class CreateNewStickerSet internal constructor(
@SerialName(userIdField)
override val userId: UserId,
@SerialName(nameField)
override val name: String,
@SerialName(titleField)
override val title: String,
@SerialName(emojisField)
override val emojis: String,
@SerialName(stickerTypeField)
val stickerType: StickerType = StickerType.Regular,
@SerialName(pngStickerField)
val pngSticker: FileId? = null,
@SerialName(tgsStickerField)
val tgsSticker: FileId? = null,
@SerialName(webmStickerField)
val webmSticker: FileId? = null,
@SerialName(maskPositionField)
override val maskPosition: MaskPosition? = null
) : CreateStickerSetAction {
init {
if(emojis.isEmpty()) {
throw IllegalArgumentException("Emojis must not be empty")
}
}
override val requestSerializer: SerializationStrategy<*>
get() = serializer()
override fun method(): String = "createNewStickerSet"
}

View File

@@ -8,26 +8,8 @@ import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.stickers.MaskPosition import dev.inmo.tgbotapi.types.stickers.MaskPosition
import kotlinx.serialization.* import kotlinx.serialization.*
fun CreateNewVideoStickerSet(
userId: UserId,
linkName: String,
title: String,
sticker: InputFile,
emojis: String,
containsMasks: Boolean? = null,
maskPosition: MaskPosition? = null
): Request<Boolean> {
val data = CreateNewVideoStickerSet(userId, linkName, title, emojis, sticker as? FileId, containsMasks, maskPosition)
return when (sticker) {
is MultipartFile -> CommonMultipartFileRequest(
data,
mapOf(webmStickerField to sticker)
)
is FileId -> data
}
}
@Serializable @Serializable
@Deprecated("Use CreateNewStickerSet class instead")
data class CreateNewVideoStickerSet internal constructor( data class CreateNewVideoStickerSet internal constructor(
@SerialName(userIdField) @SerialName(userIdField)
override val userId: UserId, override val userId: UserId,

View File

@@ -0,0 +1,105 @@
package dev.inmo.tgbotapi.requests.stickers
import dev.inmo.tgbotapi.requests.abstracts.InputFile
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.StickerType
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.stickers.MaskPosition
fun CreateNewRegularStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String
): Request<Boolean> = CreateNewStickerSet(
userId,
name,
title,
emojis,
StickerType.Regular,
pngSticker = sticker
)
fun CreateNewRegularVideoStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String
): Request<Boolean> = CreateNewStickerSet(
userId,
name,
title,
emojis,
StickerType.Regular,
webmSticker = sticker
)
fun CreateNewRegularAnimatedStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String
): Request<Boolean> = CreateNewStickerSet(
userId,
name,
title,
emojis,
StickerType.Regular,
tgsSticker = sticker
)
fun CreateNewMaskStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String,
maskPosition: MaskPosition
): Request<Boolean> = CreateNewStickerSet(
userId,
name,
title,
emojis,
StickerType.Mask,
pngSticker = sticker,
maskPosition = maskPosition
)
fun CreateNewMaskVideoStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String,
maskPosition: MaskPosition
): Request<Boolean> = CreateNewStickerSet(
userId,
name,
title,
emojis,
StickerType.Mask,
webmSticker = sticker,
maskPosition = maskPosition
)
fun CreateNewMaskAnimatedStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String,
maskPosition: MaskPosition
): Request<Boolean> = CreateNewStickerSet(
userId,
name,
title,
emojis,
StickerType.Mask,
tgsSticker = sticker,
maskPosition = maskPosition
)

View File

@@ -0,0 +1,80 @@
package dev.inmo.tgbotapi.requests.stickers
import dev.inmo.tgbotapi.requests.abstracts.InputFile
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.StickerType
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.stickers.MaskPosition
fun CreateNewVideoStickerSet(
userId: UserId,
linkName: String,
title: String,
sticker: InputFile,
emojis: String,
containsMasks: Boolean? = null,
maskPosition: MaskPosition? = null
): Request<Boolean> = CreateNewStickerSet(
userId,
linkName,
title,
emojis,
if (containsMasks == true) StickerType.Mask else StickerType.Regular,
webmSticker = sticker,
maskPosition = maskPosition
)
fun CreateNewStaticStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String,
containsMasks: Boolean? = null,
maskPosition: MaskPosition? = null
): Request<Boolean> = CreateNewStickerSet(
userId,
name,
title,
emojis,
if (containsMasks == true) StickerType.Mask else StickerType.Regular,
pngSticker = sticker,
maskPosition = maskPosition
)
fun CreateNewStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String,
containsMasks: Boolean? = null,
maskPosition: MaskPosition? = null
): Request<Boolean> = CreateNewStickerSet(
userId,
name,
title,
emojis,
if (containsMasks == true) StickerType.Mask else StickerType.Regular,
pngSticker = sticker,
maskPosition = maskPosition
)
fun CreateNewAnimatedStickerSet(
userId: UserId,
name: String,
title: String,
sticker: InputFile,
emojis: String,
containsMasks: Boolean? = null,
maskPosition: MaskPosition? = null
): Request<Boolean> = CreateNewStickerSet(
userId,
name,
title,
emojis,
if (containsMasks == true) StickerType.Mask else StickerType.Regular,
tgsSticker = sticker,
maskPosition = maskPosition
)

View File

@@ -19,7 +19,7 @@ class MultipartSetWebhookRequest(
certificate: MultipartFile, certificate: MultipartFile,
ipAddress: String? = null, ipAddress: String? = null,
maxAllowedConnections: Int? = null, maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
dropPendingUpdates: Boolean? = null, dropPendingUpdates: Boolean? = null,
secretToken: String? = null secretToken: String? = null
) : SetWebhookRequest(), MultipartRequest<Boolean> by MultipartRequestImpl( ) : SetWebhookRequest(), MultipartRequest<Boolean> by MultipartRequestImpl(
@@ -40,7 +40,7 @@ fun SetWebhook(
certificate: MultipartFile, certificate: MultipartFile,
ipAddress: String? = null, ipAddress: String? = null,
maxAllowedConnections: Int? = null, maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
dropPendingUpdates: Boolean? = null, dropPendingUpdates: Boolean? = null,
secretToken: String? = null secretToken: String? = null
): MultipartSetWebhookRequest = MultipartSetWebhookRequest( ): MultipartSetWebhookRequest = MultipartSetWebhookRequest(
@@ -58,7 +58,7 @@ fun SetWebhook(
certificate: FileId, certificate: FileId,
ipAddress: String? = null, ipAddress: String? = null,
maxAllowedConnections: Int? = null, maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
dropPendingUpdates: Boolean? = null, dropPendingUpdates: Boolean? = null,
secretToken: String? = null secretToken: String? = null
): SetWebhook = SetWebhook( ): SetWebhook = SetWebhook(
@@ -84,7 +84,7 @@ fun SetWebhook(
certificate: InputFile, certificate: InputFile,
ipAddress: String? = null, ipAddress: String? = null,
maxAllowedConnections: Int? = null, maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
dropPendingUpdates: Boolean? = null, dropPendingUpdates: Boolean? = null,
secretToken: String? = null secretToken: String? = null
) = when (certificate) { ) = when (certificate) {
@@ -104,7 +104,7 @@ fun SetWebhook(
url: String, url: String,
ipAddress: String? = null, ipAddress: String? = null,
maxAllowedConnections: Int? = null, maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
dropPendingUpdates: Boolean? = null, dropPendingUpdates: Boolean? = null,
secretToken: String? = null secretToken: String? = null
) = SetWebhook( ) = SetWebhook(
@@ -135,7 +135,7 @@ data class SetWebhook internal constructor(
@SerialName(maxAllowedConnectionsField) @SerialName(maxAllowedConnectionsField)
val maxAllowedConnections: Int? = null, val maxAllowedConnections: Int? = null,
@SerialName(allowedUpdatesField) @SerialName(allowedUpdatesField)
val allowedUpdates: List<String>? = null, val allowedUpdates: List<String>? = ALL_UPDATES_LIST,
@SerialName(dropPendingUpdatesField) @SerialName(dropPendingUpdatesField)
val dropPendingUpdates: Boolean? = null, val dropPendingUpdates: Boolean? = null,
@SerialName(secretTokenField) @SerialName(secretTokenField)

View File

@@ -59,6 +59,8 @@ sealed interface StickerType {
object Mask : StickerType { override val type: String = "mask" } object Mask : StickerType { override val type: String = "mask" }
@Serializable @Serializable
object CustomEmoji : StickerType { override val type: String = "custom_emoji" } object CustomEmoji : StickerType { override val type: String = "custom_emoji" }
@Serializable
data class Unknown(override val type: String = "custom_emoji") : StickerType
object Serializer : KSerializer<StickerType> { object Serializer : KSerializer<StickerType> {
override val descriptor: SerialDescriptor = String.serializer().descriptor override val descriptor: SerialDescriptor = String.serializer().descriptor
@@ -66,9 +68,9 @@ sealed interface StickerType {
override fun deserialize(decoder: Decoder): StickerType { override fun deserialize(decoder: Decoder): StickerType {
return when (val type = decoder.decodeString()) { return when (val type = decoder.decodeString()) {
Regular.type -> Regular Regular.type -> Regular
Mask.type -> Regular Mask.type -> Mask
CustomEmoji.type -> Regular CustomEmoji.type -> CustomEmoji
else -> error("Unknown type of emoji $type") else -> Unknown(type)
} }
} }
@@ -79,6 +81,8 @@ sealed interface StickerType {
} }
} }
val usernameRegex = Regex("@[\\w\\d_]+")
val degreesLimit = 1 .. 360 val degreesLimit = 1 .. 360
val horizontalAccuracyLimit = 0F .. 1500F val horizontalAccuracyLimit = 0F .. 1500F
@@ -162,6 +166,7 @@ const val languageCodeField = "language_code"
const val addedToAttachmentMenuField = "added_to_attachment_menu" const val addedToAttachmentMenuField = "added_to_attachment_menu"
const val isPremiumField = "is_premium" const val isPremiumField = "is_premium"
const val hasPrivateForwardsField = "has_private_forwards" const val hasPrivateForwardsField = "has_private_forwards"
const val hasRestrictedVoiceAndVideoMessagesField = "has_restricted_voice_and_video_messages"
const val canJoinGroupsField = "can_join_groups" const val canJoinGroupsField = "can_join_groups"
const val canReadAllGroupMessagesField = "can_read_all_group_messages" const val canReadAllGroupMessagesField = "can_read_all_group_messages"
const val supportInlineQueriesField = "supports_inline_queries" const val supportInlineQueriesField = "supports_inline_queries"

View File

@@ -62,7 +62,9 @@ data class ExtendedPrivateChatImpl(
@SerialName(bioField) @SerialName(bioField)
override val bio: String = "", override val bio: String = "",
@SerialName(hasPrivateForwardsField) @SerialName(hasPrivateForwardsField)
override val hasPrivateForwards: Boolean = false override val hasPrivateForwards: Boolean = false,
@SerialName(hasRestrictedVoiceAndVideoMessagesField)
override val hasRestrictedVoiceAndVideoMessages: Boolean = false
) : ExtendedPrivateChat ) : ExtendedPrivateChat
typealias ExtendedUser = ExtendedPrivateChatImpl typealias ExtendedUser = ExtendedPrivateChatImpl

View File

@@ -19,6 +19,7 @@ sealed interface ExtendedGroupChat : GroupChat, ExtendedPublicChat {
sealed interface ExtendedPrivateChat : PrivateChat, ExtendedChat { sealed interface ExtendedPrivateChat : PrivateChat, ExtendedChat {
val bio: String val bio: String
val hasPrivateForwards: Boolean val hasPrivateForwards: Boolean
val hasRestrictedVoiceAndVideoMessages: Boolean
val allowCreateUserIdLink: Boolean val allowCreateUserIdLink: Boolean
get() = hasPrivateForwards get() = hasPrivateForwards

View File

@@ -4,10 +4,12 @@ import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.stickers.MaskPosition import dev.inmo.tgbotapi.types.stickers.MaskPosition
import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonElement
@Serializable @Serializable
@RiskFeature("This class is used for serialization/deserialization of Sticker interface") @RiskFeature("This class is used for serialization/deserialization of Sticker interface")
@@ -44,7 +46,8 @@ object StickerSerializer : KSerializer<Sticker> {
override val descriptor: SerialDescriptor = StickerSurrogate.serializer().descriptor override val descriptor: SerialDescriptor = StickerSurrogate.serializer().descriptor
override fun deserialize(decoder: Decoder): Sticker { override fun deserialize(decoder: Decoder): Sticker {
val surrogate = StickerSurrogate.serializer().deserialize(decoder) val json = JsonElement.serializer().deserialize(decoder)
val surrogate = nonstrictJsonFormat.decodeFromJsonElement(StickerSurrogate.serializer(), json)
return when (surrogate.type) { return when (surrogate.type) {
StickerType.Regular -> when { StickerType.Regular -> when {
@@ -82,19 +85,41 @@ object StickerSerializer : KSerializer<Sticker> {
surrogate.file_size surrogate.file_size
) )
} }
StickerType.Mask -> MaskSticker( StickerType.Mask -> when {
surrogate.file_id, surrogate.is_animated == true -> MaskAnimatedSticker(
surrogate.file_unique_id, surrogate.file_id,
surrogate.width, surrogate.file_unique_id,
surrogate.height, surrogate.width,
surrogate.mask_position ?: error("For mask stickers field mask_position should be presented"), surrogate.height,
surrogate.is_animated == true, surrogate.mask_position ?: error("For mask stickers field mask_position should be presented"),
surrogate.is_video == true, surrogate.thumb,
surrogate.thumb, surrogate.emoji,
surrogate.emoji, surrogate.set_name,
surrogate.set_name, surrogate.file_size
surrogate.file_size )
) surrogate.is_video == true -> MaskVideoSticker(
surrogate.file_id,
surrogate.file_unique_id,
surrogate.width,
surrogate.height,
surrogate.mask_position ?: error("For mask stickers field mask_position should be presented"),
surrogate.thumb,
surrogate.emoji,
surrogate.set_name,
surrogate.file_size
)
else -> MaskSimpleSticker(
surrogate.file_id,
surrogate.file_unique_id,
surrogate.width,
surrogate.height,
surrogate.mask_position ?: error("For mask stickers field mask_position should be presented"),
surrogate.thumb,
surrogate.emoji,
surrogate.set_name,
surrogate.file_size
)
}
StickerType.CustomEmoji -> when { StickerType.CustomEmoji -> when {
surrogate.is_animated == true -> CustomEmojiAnimatedSticker( surrogate.is_animated == true -> CustomEmojiAnimatedSticker(
surrogate.file_id, surrogate.file_id,
@@ -130,6 +155,17 @@ object StickerSerializer : KSerializer<Sticker> {
surrogate.file_size surrogate.file_size
) )
} }
is StickerType.Unknown -> UnknownSticker(
surrogate.file_id,
surrogate.file_unique_id,
surrogate.width,
surrogate.height,
surrogate.thumb,
surrogate.emoji,
surrogate.set_name,
surrogate.file_size,
json
)
} }
} }
@@ -139,48 +175,22 @@ object StickerSerializer : KSerializer<Sticker> {
} }
@Serializable(StickerSerializer::class) @Serializable
sealed interface VideoSticker : Sticker { sealed interface VideoSticker : Sticker {
override val isVideo: Boolean override val isVideo: Boolean
get() = true get() = true
} }
@Serializable(StickerSerializer::class) @Serializable
sealed interface AnimatedSticker : Sticker { sealed interface AnimatedSticker : Sticker {
override val isAnimated: Boolean override val isAnimated: Boolean
get() = true get() = true
} }
@Serializable(StickerSerializer::class) @Serializable
sealed interface RegularSticker : Sticker { sealed interface RegularSticker : Sticker {
val premiumAnimationFile: File? val premiumAnimationFile: File?
} }
@Serializable
data class MaskSticker(
@SerialName(fileIdField)
override val fileId: FileId,
@SerialName(fileUniqueIdField)
override val fileUniqueId: FileUniqueId,
@SerialName(widthField)
override val width: Int,
@SerialName(heightField)
override val height: Int,
@SerialName(maskPositionField)
val maskPosition: MaskPosition,
@SerialName(isAnimatedField)
override val isAnimated: Boolean,
@SerialName(isVideoField)
override val isVideo: Boolean,
@SerialName(thumbField)
override val thumb: PhotoSize? = null,
@SerialName(emojiField)
override val emoji: String? = null,
@SerialName(stickerSetNameField)
override val stickerSetName: StickerSetName? = null,
@SerialName(fileSizeField)
override val fileSize: Long? = null,
) : Sticker
@Serializable @Serializable
data class RegularSimpleSticker( data class RegularSimpleSticker(
@SerialName(fileIdField) @SerialName(fileIdField)
@@ -247,7 +257,76 @@ data class RegularVideoSticker(
override val fileSize: Long? = null, override val fileSize: Long? = null,
) : RegularSticker, VideoSticker ) : RegularSticker, VideoSticker
@Serializable(StickerSerializer::class)
@Serializable
sealed interface MaskSticker : Sticker {
val maskPosition: MaskPosition
}
@Serializable
data class MaskSimpleSticker(
@SerialName(fileIdField)
override val fileId: FileId,
@SerialName(fileUniqueIdField)
override val fileUniqueId: FileUniqueId,
@SerialName(widthField)
override val width: Int,
@SerialName(heightField)
override val height: Int,
@SerialName(maskPositionField)
override val maskPosition: MaskPosition,
@SerialName(thumbField)
override val thumb: PhotoSize? = null,
@SerialName(emojiField)
override val emoji: String? = null,
@SerialName(stickerSetNameField)
override val stickerSetName: StickerSetName? = null,
@SerialName(fileSizeField)
override val fileSize: Long? = null,
) : MaskSticker
@Serializable
data class MaskAnimatedSticker(
@SerialName(fileIdField)
override val fileId: FileId,
@SerialName(fileUniqueIdField)
override val fileUniqueId: FileUniqueId,
@SerialName(widthField)
override val width: Int,
@SerialName(heightField)
override val height: Int,
@SerialName(maskPositionField)
override val maskPosition: MaskPosition,
@SerialName(thumbField)
override val thumb: PhotoSize? = null,
@SerialName(emojiField)
override val emoji: String? = null,
@SerialName(stickerSetNameField)
override val stickerSetName: StickerSetName? = null,
@SerialName(fileSizeField)
override val fileSize: Long? = null,
) : MaskSticker, AnimatedSticker
@Serializable
data class MaskVideoSticker(
@SerialName(fileIdField)
override val fileId: FileId,
@SerialName(fileUniqueIdField)
override val fileUniqueId: FileUniqueId,
@SerialName(widthField)
override val width: Int,
@SerialName(heightField)
override val height: Int,
@SerialName(maskPositionField)
override val maskPosition: MaskPosition,
@SerialName(thumbField)
override val thumb: PhotoSize? = null,
@SerialName(emojiField)
override val emoji: String? = null,
@SerialName(stickerSetNameField)
override val stickerSetName: StickerSetName? = null,
@SerialName(fileSizeField)
override val fileSize: Long? = null,
) : MaskSticker, VideoSticker
@Serializable
sealed interface CustomEmojiSticker : Sticker { sealed interface CustomEmojiSticker : Sticker {
val customEmojiId: CustomEmojiId val customEmojiId: CustomEmojiId
} }
@@ -315,3 +394,24 @@ data class CustomEmojiVideoSticker(
@SerialName(fileSizeField) @SerialName(fileSizeField)
override val fileSize: Long? = null, override val fileSize: Long? = null,
) : CustomEmojiSticker, VideoSticker ) : CustomEmojiSticker, VideoSticker
@Serializable
data class UnknownSticker(
@SerialName(fileIdField)
override val fileId: FileId,
@SerialName(fileUniqueIdField)
override val fileUniqueId: FileUniqueId,
@SerialName(widthField)
override val width: Int,
@SerialName(heightField)
override val height: Int,
@SerialName(thumbField)
override val thumb: PhotoSize? = null,
@SerialName(emojiField)
override val emoji: String? = null,
@SerialName(stickerSetNameField)
override val stickerSetName: StickerSetName? = null,
@SerialName(fileSizeField)
override val fileSize: Long? = null,
val raw: JsonElement
) : Sticker

View File

@@ -161,7 +161,7 @@ internal fun TextSource.toRawMessageEntities(offset: Int = 0): List<RawMessageEn
is UnderlineTextSource -> RawMessageEntity("underline", offset, length) is UnderlineTextSource -> RawMessageEntity("underline", offset, length)
is StrikethroughTextSource -> RawMessageEntity("strikethrough", offset, length) is StrikethroughTextSource -> RawMessageEntity("strikethrough", offset, length)
is SpoilerTextSource -> RawMessageEntity("spoiler", offset, length) is SpoilerTextSource -> RawMessageEntity("spoiler", offset, length)
is CustomEmojiTextSource -> RawMessageEntity("custom_emoji", offset, length) is CustomEmojiTextSource -> RawMessageEntity("custom_emoji", offset, length, custom_emoji_id = customEmojiId)
is RegularTextSource -> null is RegularTextSource -> null
} }
) + if (this is MultilevelTextSource) { ) + if (this is MultilevelTextSource) {

View File

@@ -1,5 +1,7 @@
package dev.inmo.tgbotapi.types.message.textsources package dev.inmo.tgbotapi.types.message.textsources
import dev.inmo.tgbotapi.types.usernameRegex
import dev.inmo.tgbotapi.types.Username
import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.internal.* import dev.inmo.tgbotapi.utils.internal.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -16,6 +18,9 @@ data class BotCommandTextSource @RiskFeature(DirectInvocationOfTextSourceConstru
val command: String by lazy { val command: String by lazy {
commandRegex.find(source) ?.value ?.substring(1) ?: source.substring(1)// skip first symbol like "/" or "!" commandRegex.find(source) ?.value ?.substring(1) ?: source.substring(1)// skip first symbol like "/" or "!"
} }
val username: Username? by lazy {
Username(usernameRegex.find(source) ?.value ?: return@lazy null)
}
override val markdown: String by lazy { source.commandMarkdown() } override val markdown: String by lazy { source.commandMarkdown() }
override val markdownV2: String by lazy { source.commandMarkdownV2() } override val markdownV2: String by lazy { source.commandMarkdownV2() }

View File

@@ -36,7 +36,7 @@ sealed interface MultilevelTextSource : TextSource {
val subsources: List<TextSource> val subsources: List<TextSource>
} }
fun List<TextSource>.separateForMessage(limit: IntRange, numberOfParts: Int? = null): List<List<TextSource>> { fun List<TextSource>.splitForMessage(limit: IntRange, numberOfParts: Int? = null): List<List<TextSource>> {
if (isEmpty()) { if (isEmpty()) {
return emptyList() return emptyList()
} }
@@ -70,13 +70,27 @@ fun List<TextSource>.separateForMessage(limit: IntRange, numberOfParts: Int? = n
* This method will prepare [TextSource]s list for messages. Remember, that first part will be separated with * This method will prepare [TextSource]s list for messages. Remember, that first part will be separated with
* [captionLength] and all others with * [captionLength] and all others with
*/ */
fun List<TextSource>.separateForCaption(): List<List<TextSource>> { fun List<TextSource>.splitForCaption(): List<List<TextSource>> {
val captionPart = separateForMessage(captionLength, 1).first() val captionPart = splitForMessage(captionLength, 1).first()
return listOf(captionPart) + minus(captionPart).separateForMessage(textLength) return listOf(captionPart) + minus(captionPart).splitForMessage(textLength)
} }
/** /**
* This method will prepare [TextSource]s list for messages with [textLength] * This method will prepare [TextSource]s list for messages with [textLength]
*/ */
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun List<TextSource>.separateForText(): List<List<TextSource>> = separateForMessage(textLength) inline fun List<TextSource>.splitForText(): List<List<TextSource>> = splitForMessage(textLength)
fun List<TextSource>.separateForMessage(limit: IntRange, numberOfParts: Int? = null): List<List<TextSource>> = splitForMessage(limit, numberOfParts)
/**
* This method will prepare [TextSource]s list for messages. Remember, that first part will be separated with
* [captionLength] and all others with
*/
fun List<TextSource>.separateForCaption(): List<List<TextSource>> = splitForCaption()
/**
* This method will prepare [TextSource]s list for messages with [textLength]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun List<TextSource>.separateForText(): List<List<TextSource>> = splitForText()

View File

@@ -2,86 +2,297 @@ package dev.inmo.tgbotapi.types.stickers
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.files.* import dev.inmo.tgbotapi.types.files.*
import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
@Serializable @Serializable
data class SurrogateStickerSet(
val name: String,
val title: String,
val sticker_type: StickerType,
val is_animated: Boolean? = false,
val is_video: Boolean? = false,
val stickers: List<@Serializable(StickerSerializer::class) Sticker> = emptyList(),
val thumb: PhotoSize? = null
)
@Serializable(StickerSet.Serializer::class)
sealed interface StickerSet { sealed interface StickerSet {
@SerialName(nameField)
val name: String val name: String
@SerialName(titleField)
val title: String val title: String
@SerialName(stickerTypeField)
val stickerType: StickerType val stickerType: StickerType
@SerialName(stickersField)
val stickers: List<Sticker> val stickers: List<Sticker>
@SerialName(isAnimatedField)
val isAnimated: Boolean val isAnimated: Boolean
@SerialName(isVideoField) get() = false
val isVideo: Boolean val isVideo: Boolean
@SerialName(containsMasksField) get() = false
val thumb: PhotoSize?
@Deprecated("Will be removed soon due to its redundancy") @Deprecated("Will be removed soon due to its redundancy")
val containsMasks: Boolean val containsMasks: Boolean
get() = this is MaskStickerSet get() = this is MaskStickerSet
@SerialName(thumbField)
val thumb: PhotoSize? object Serializer : KSerializer<StickerSet> {
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
override fun deserialize(decoder: Decoder): StickerSet {
val json = JsonElement.serializer().deserialize(decoder)
val surrogate = nonstrictJsonFormat.decodeFromJsonElement(SurrogateStickerSet.serializer(), json)
return when (surrogate.sticker_type) {
StickerType.CustomEmoji -> when {
surrogate.is_animated == true -> CustomEmojiAnimatedStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<CustomEmojiAnimatedSticker>(),
surrogate.thumb
)
surrogate.is_video == true -> CustomEmojiVideoStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<CustomEmojiVideoSticker>(),
surrogate.thumb
)
else -> CustomEmojiSimpleStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<CustomEmojiSimpleSticker>(),
surrogate.thumb
)
}
StickerType.Mask -> when {
surrogate.is_animated == true -> MaskAnimatedStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<MaskAnimatedSticker>(),
surrogate.thumb
)
surrogate.is_video == true -> MaskVideoStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<MaskVideoSticker>(),
surrogate.thumb
)
else -> MaskSimpleStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<MaskSimpleSticker>(),
surrogate.thumb
)
}
StickerType.Regular -> when {
surrogate.is_animated == true -> RegularAnimatedStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<RegularAnimatedSticker>(),
surrogate.thumb
)
surrogate.is_video == true -> RegularVideoStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<RegularVideoSticker>(),
surrogate.thumb
)
else -> RegularSimpleStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<RegularSimpleSticker>(),
surrogate.thumb
)
}
is StickerType.Unknown -> UnknownStickerSet(
surrogate.name,
surrogate.title,
surrogate.stickers.filterIsInstance<RegularSimpleSticker>(),
surrogate.sticker_type,
surrogate.thumb,
json
)
}
}
override fun serialize(encoder: Encoder, value: StickerSet) {
TODO("Not yet implemented")
}
}
} }
@Serializable @Serializable
data class RegularStickerSet( sealed interface AnimatedStickerSet : StickerSet {
override val isAnimated: Boolean
get() = true
}
@Serializable
sealed interface VideoStickerSet : StickerSet {
override val isVideo: Boolean
get() = true
}
@Serializable
sealed interface RegularStickerSet : StickerSet
@Serializable
sealed interface MaskStickerSet : StickerSet
@Serializable
sealed interface CustomEmojiStickerSet : StickerSet
@Serializable
data class RegularSimpleStickerSet(
@SerialName(nameField) @SerialName(nameField)
override val name: String, override val name: String,
@SerialName(titleField) @SerialName(titleField)
override val title: String, override val title: String,
@SerialName(stickersField) @SerialName(stickersField)
override val stickers: List<RegularSticker>, override val stickers: List<RegularSimpleSticker>,
@SerialName(isAnimatedField)
override val isAnimated: Boolean = false,
@SerialName(isVideoField)
override val isVideo: Boolean = false,
@SerialName(thumbField) @SerialName(thumbField)
override val thumb: PhotoSize? = null override val thumb: PhotoSize? = null
) : StickerSet { ) : RegularStickerSet {
@SerialName(stickerTypeField) @SerialName(stickerTypeField)
@EncodeDefault @EncodeDefault
override val stickerType: StickerType = StickerType.Regular override val stickerType: StickerType = StickerType.Regular
} }
@Serializable @Serializable
data class MaskStickerSet( data class RegularAnimatedStickerSet(
@SerialName(nameField) @SerialName(nameField)
override val name: String, override val name: String,
@SerialName(titleField) @SerialName(titleField)
override val title: String, override val title: String,
@SerialName(stickersField) @SerialName(stickersField)
override val stickers: List<MaskSticker>, override val stickers: List<RegularAnimatedSticker>,
@SerialName(isAnimatedField)
override val isAnimated: Boolean = false,
@SerialName(isVideoField)
override val isVideo: Boolean = false,
@SerialName(thumbField) @SerialName(thumbField)
override val thumb: PhotoSize? = null override val thumb: PhotoSize? = null
) : StickerSet { ) : RegularStickerSet, AnimatedStickerSet {
@SerialName(stickerTypeField)
@EncodeDefault
override val stickerType: StickerType = StickerType.Regular
}
@Serializable
data class RegularVideoStickerSet(
@SerialName(nameField)
override val name: String,
@SerialName(titleField)
override val title: String,
@SerialName(stickersField)
override val stickers: List<RegularVideoSticker>,
@SerialName(thumbField)
override val thumb: PhotoSize? = null
) : RegularStickerSet, VideoStickerSet {
@SerialName(stickerTypeField)
@EncodeDefault
override val stickerType: StickerType = StickerType.Regular
}
@Serializable
data class MaskSimpleStickerSet(
@SerialName(nameField)
override val name: String,
@SerialName(titleField)
override val title: String,
@SerialName(stickersField)
override val stickers: List<MaskSimpleSticker>,
@SerialName(thumbField)
override val thumb: PhotoSize? = null
) : MaskStickerSet {
@SerialName(stickerTypeField) @SerialName(stickerTypeField)
@EncodeDefault @EncodeDefault
override val stickerType: StickerType = StickerType.Mask override val stickerType: StickerType = StickerType.Mask
} }
@Serializable @Serializable
data class CustomEmojiStickerSet( data class MaskAnimatedStickerSet(
@SerialName(nameField) @SerialName(nameField)
override val name: String, override val name: String,
@SerialName(titleField) @SerialName(titleField)
override val title: String, override val title: String,
@SerialName(stickersField) @SerialName(stickersField)
override val stickers: List<CustomEmojiSticker>, override val stickers: List<MaskAnimatedSticker>,
@SerialName(isAnimatedField)
override val isAnimated: Boolean = false,
@SerialName(isVideoField)
override val isVideo: Boolean = false,
@SerialName(thumbField) @SerialName(thumbField)
override val thumb: PhotoSize? = null override val thumb: PhotoSize? = null
) : StickerSet { ) : MaskStickerSet, AnimatedStickerSet {
@SerialName(stickerTypeField)
@EncodeDefault
override val stickerType: StickerType = StickerType.Mask
}
@Serializable
data class MaskVideoStickerSet(
@SerialName(nameField)
override val name: String,
@SerialName(titleField)
override val title: String,
@SerialName(stickersField)
override val stickers: List<MaskVideoSticker>,
@SerialName(thumbField)
override val thumb: PhotoSize? = null
) : MaskStickerSet, VideoStickerSet {
@SerialName(stickerTypeField)
@EncodeDefault
override val stickerType: StickerType = StickerType.Mask
}
@Serializable
data class CustomEmojiSimpleStickerSet(
@SerialName(nameField)
override val name: String,
@SerialName(titleField)
override val title: String,
@SerialName(stickersField)
override val stickers: List<CustomEmojiSimpleSticker>,
@SerialName(thumbField)
override val thumb: PhotoSize? = null
) : CustomEmojiStickerSet {
@SerialName(stickerTypeField) @SerialName(stickerTypeField)
@EncodeDefault @EncodeDefault
override val stickerType: StickerType = StickerType.CustomEmoji override val stickerType: StickerType = StickerType.CustomEmoji
} }
@Serializable
data class CustomEmojiAnimatedStickerSet(
@SerialName(nameField)
override val name: String,
@SerialName(titleField)
override val title: String,
@SerialName(stickersField)
override val stickers: List<CustomEmojiAnimatedSticker>,
@SerialName(thumbField)
override val thumb: PhotoSize? = null
) : CustomEmojiStickerSet, AnimatedStickerSet {
@SerialName(stickerTypeField)
@EncodeDefault
override val stickerType: StickerType = StickerType.CustomEmoji
}
@Serializable
data class CustomEmojiVideoStickerSet(
@SerialName(nameField)
override val name: String,
@SerialName(titleField)
override val title: String,
@SerialName(stickersField)
override val stickers: List<CustomEmojiVideoSticker>,
@SerialName(thumbField)
override val thumb: PhotoSize? = null
) : CustomEmojiStickerSet, VideoStickerSet {
@SerialName(stickerTypeField)
@EncodeDefault
override val stickerType: StickerType = StickerType.CustomEmoji
}
@Serializable
data class UnknownStickerSet(
@SerialName(nameField)
override val name: String,
@SerialName(titleField)
override val title: String,
@SerialName(stickersField)
override val stickers: List<Sticker>,
@SerialName(stickerTypeField)
override val stickerType: StickerType,
@SerialName(thumbField)
override val thumb: PhotoSize? = null,
val raw: JsonElement
) : CustomEmojiStickerSet, VideoStickerSet

View File

@@ -167,18 +167,30 @@ import dev.inmo.tgbotapi.types.dice.SlotMachineDiceAnimationType
import dev.inmo.tgbotapi.types.files.AnimatedSticker import dev.inmo.tgbotapi.types.files.AnimatedSticker
import dev.inmo.tgbotapi.types.files.AnimationFile import dev.inmo.tgbotapi.types.files.AnimationFile
import dev.inmo.tgbotapi.types.files.AudioFile import dev.inmo.tgbotapi.types.files.AudioFile
import dev.inmo.tgbotapi.types.files.CustomEmojiAnimatedSticker
import dev.inmo.tgbotapi.types.files.CustomEmojiSimpleSticker
import dev.inmo.tgbotapi.types.files.CustomEmojiSticker
import dev.inmo.tgbotapi.types.files.CustomEmojiVideoSticker
import dev.inmo.tgbotapi.types.files.DocumentFile import dev.inmo.tgbotapi.types.files.DocumentFile
import dev.inmo.tgbotapi.types.files.File import dev.inmo.tgbotapi.types.files.File
import dev.inmo.tgbotapi.types.files.MaskAnimatedSticker
import dev.inmo.tgbotapi.types.files.MaskSimpleSticker
import dev.inmo.tgbotapi.types.files.MaskSticker
import dev.inmo.tgbotapi.types.files.MaskVideoSticker
import dev.inmo.tgbotapi.types.files.MimedMediaFile import dev.inmo.tgbotapi.types.files.MimedMediaFile
import dev.inmo.tgbotapi.types.files.PassportFile import dev.inmo.tgbotapi.types.files.PassportFile
import dev.inmo.tgbotapi.types.files.PathedFile import dev.inmo.tgbotapi.types.files.PathedFile
import dev.inmo.tgbotapi.types.files.PhotoSize import dev.inmo.tgbotapi.types.files.PhotoSize
import dev.inmo.tgbotapi.types.files.PlayableMediaFile import dev.inmo.tgbotapi.types.files.PlayableMediaFile
import dev.inmo.tgbotapi.types.files.SimpleSticker import dev.inmo.tgbotapi.types.files.RegularAnimatedSticker
import dev.inmo.tgbotapi.types.files.RegularSimpleSticker
import dev.inmo.tgbotapi.types.files.RegularSticker
import dev.inmo.tgbotapi.types.files.RegularVideoSticker
import dev.inmo.tgbotapi.types.files.SizedMediaFile import dev.inmo.tgbotapi.types.files.SizedMediaFile
import dev.inmo.tgbotapi.types.files.Sticker import dev.inmo.tgbotapi.types.files.Sticker
import dev.inmo.tgbotapi.types.files.TelegramMediaFile import dev.inmo.tgbotapi.types.files.TelegramMediaFile
import dev.inmo.tgbotapi.types.files.ThumbedMediaFile import dev.inmo.tgbotapi.types.files.ThumbedMediaFile
import dev.inmo.tgbotapi.types.files.UnknownSticker
import dev.inmo.tgbotapi.types.files.VideoFile import dev.inmo.tgbotapi.types.files.VideoFile
import dev.inmo.tgbotapi.types.files.VideoNoteFile import dev.inmo.tgbotapi.types.files.VideoNoteFile
import dev.inmo.tgbotapi.types.files.VideoSticker import dev.inmo.tgbotapi.types.files.VideoSticker
@@ -2267,14 +2279,14 @@ public inline fun TelegramMediaFile.stickerOrThrow(): Sticker = this as
public inline fun <T> TelegramMediaFile.ifSticker(block: (Sticker) -> T): T? = stickerOrNull() public inline fun <T> TelegramMediaFile.ifSticker(block: (Sticker) -> T): T? = stickerOrNull()
?.let(block) ?.let(block)
public inline fun TelegramMediaFile.simpleStickerOrNull(): SimpleSticker? = this as? public inline fun TelegramMediaFile.videoStickerOrNull(): VideoSticker? = this as?
dev.inmo.tgbotapi.types.files.SimpleSticker dev.inmo.tgbotapi.types.files.VideoSticker
public inline fun TelegramMediaFile.simpleStickerOrThrow(): SimpleSticker = this as public inline fun TelegramMediaFile.videoStickerOrThrow(): VideoSticker = this as
dev.inmo.tgbotapi.types.files.SimpleSticker dev.inmo.tgbotapi.types.files.VideoSticker
public inline fun <T> TelegramMediaFile.ifSimpleSticker(block: (SimpleSticker) -> T): T? = public inline fun <T> TelegramMediaFile.ifVideoSticker(block: (VideoSticker) -> T): T? =
simpleStickerOrNull() ?.let(block) videoStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.animatedStickerOrNull(): AnimatedSticker? = this as? public inline fun TelegramMediaFile.animatedStickerOrNull(): AnimatedSticker? = this as?
dev.inmo.tgbotapi.types.files.AnimatedSticker dev.inmo.tgbotapi.types.files.AnimatedSticker
@@ -2285,14 +2297,126 @@ public inline fun TelegramMediaFile.animatedStickerOrThrow(): AnimatedSticker =
public inline fun <T> TelegramMediaFile.ifAnimatedSticker(block: (AnimatedSticker) -> T): T? = public inline fun <T> TelegramMediaFile.ifAnimatedSticker(block: (AnimatedSticker) -> T): T? =
animatedStickerOrNull() ?.let(block) animatedStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.videoStickerOrNull(): VideoSticker? = this as? public inline fun TelegramMediaFile.regularStickerOrNull(): RegularSticker? = this as?
dev.inmo.tgbotapi.types.files.VideoSticker dev.inmo.tgbotapi.types.files.RegularSticker
public inline fun TelegramMediaFile.videoStickerOrThrow(): VideoSticker = this as public inline fun TelegramMediaFile.regularStickerOrThrow(): RegularSticker = this as
dev.inmo.tgbotapi.types.files.VideoSticker dev.inmo.tgbotapi.types.files.RegularSticker
public inline fun <T> TelegramMediaFile.ifVideoSticker(block: (VideoSticker) -> T): T? = public inline fun <T> TelegramMediaFile.ifRegularSticker(block: (RegularSticker) -> T): T? =
videoStickerOrNull() ?.let(block) regularStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.regularSimpleStickerOrNull(): RegularSimpleSticker? = this as?
dev.inmo.tgbotapi.types.files.RegularSimpleSticker
public inline fun TelegramMediaFile.regularSimpleStickerOrThrow(): RegularSimpleSticker = this as
dev.inmo.tgbotapi.types.files.RegularSimpleSticker
public inline fun <T> TelegramMediaFile.ifRegularSimpleSticker(block: (RegularSimpleSticker) -> T):
T? = regularSimpleStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.regularAnimatedStickerOrNull(): RegularAnimatedSticker? = this
as? dev.inmo.tgbotapi.types.files.RegularAnimatedSticker
public inline fun TelegramMediaFile.regularAnimatedStickerOrThrow(): RegularAnimatedSticker = this
as dev.inmo.tgbotapi.types.files.RegularAnimatedSticker
public inline fun <T>
TelegramMediaFile.ifRegularAnimatedSticker(block: (RegularAnimatedSticker) -> T): T? =
regularAnimatedStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.regularVideoStickerOrNull(): RegularVideoSticker? = this as?
dev.inmo.tgbotapi.types.files.RegularVideoSticker
public inline fun TelegramMediaFile.regularVideoStickerOrThrow(): RegularVideoSticker = this as
dev.inmo.tgbotapi.types.files.RegularVideoSticker
public inline fun <T> TelegramMediaFile.ifRegularVideoSticker(block: (RegularVideoSticker) -> T): T?
= regularVideoStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.maskStickerOrNull(): MaskSticker? = this as?
dev.inmo.tgbotapi.types.files.MaskSticker
public inline fun TelegramMediaFile.maskStickerOrThrow(): MaskSticker = this as
dev.inmo.tgbotapi.types.files.MaskSticker
public inline fun <T> TelegramMediaFile.ifMaskSticker(block: (MaskSticker) -> T): T? =
maskStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.maskSimpleStickerOrNull(): MaskSimpleSticker? = this as?
dev.inmo.tgbotapi.types.files.MaskSimpleSticker
public inline fun TelegramMediaFile.maskSimpleStickerOrThrow(): MaskSimpleSticker = this as
dev.inmo.tgbotapi.types.files.MaskSimpleSticker
public inline fun <T> TelegramMediaFile.ifMaskSimpleSticker(block: (MaskSimpleSticker) -> T): T? =
maskSimpleStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.maskAnimatedStickerOrNull(): MaskAnimatedSticker? = this as?
dev.inmo.tgbotapi.types.files.MaskAnimatedSticker
public inline fun TelegramMediaFile.maskAnimatedStickerOrThrow(): MaskAnimatedSticker = this as
dev.inmo.tgbotapi.types.files.MaskAnimatedSticker
public inline fun <T> TelegramMediaFile.ifMaskAnimatedSticker(block: (MaskAnimatedSticker) -> T): T?
= maskAnimatedStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.maskVideoStickerOrNull(): MaskVideoSticker? = this as?
dev.inmo.tgbotapi.types.files.MaskVideoSticker
public inline fun TelegramMediaFile.maskVideoStickerOrThrow(): MaskVideoSticker = this as
dev.inmo.tgbotapi.types.files.MaskVideoSticker
public inline fun <T> TelegramMediaFile.ifMaskVideoSticker(block: (MaskVideoSticker) -> T): T? =
maskVideoStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.customEmojiStickerOrNull(): CustomEmojiSticker? = this as?
dev.inmo.tgbotapi.types.files.CustomEmojiSticker
public inline fun TelegramMediaFile.customEmojiStickerOrThrow(): CustomEmojiSticker = this as
dev.inmo.tgbotapi.types.files.CustomEmojiSticker
public inline fun <T> TelegramMediaFile.ifCustomEmojiSticker(block: (CustomEmojiSticker) -> T): T? =
customEmojiStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.customEmojiSimpleStickerOrNull(): CustomEmojiSimpleSticker? =
this as? dev.inmo.tgbotapi.types.files.CustomEmojiSimpleSticker
public inline fun TelegramMediaFile.customEmojiSimpleStickerOrThrow(): CustomEmojiSimpleSticker =
this as dev.inmo.tgbotapi.types.files.CustomEmojiSimpleSticker
public inline fun <T>
TelegramMediaFile.ifCustomEmojiSimpleSticker(block: (CustomEmojiSimpleSticker) -> T): T? =
customEmojiSimpleStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.customEmojiAnimatedStickerOrNull(): CustomEmojiAnimatedSticker?
= this as? dev.inmo.tgbotapi.types.files.CustomEmojiAnimatedSticker
public inline fun TelegramMediaFile.customEmojiAnimatedStickerOrThrow(): CustomEmojiAnimatedSticker
= this as dev.inmo.tgbotapi.types.files.CustomEmojiAnimatedSticker
public inline fun <T>
TelegramMediaFile.ifCustomEmojiAnimatedSticker(block: (CustomEmojiAnimatedSticker) -> T): T? =
customEmojiAnimatedStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.customEmojiVideoStickerOrNull(): CustomEmojiVideoSticker? = this
as? dev.inmo.tgbotapi.types.files.CustomEmojiVideoSticker
public inline fun TelegramMediaFile.customEmojiVideoStickerOrThrow(): CustomEmojiVideoSticker = this
as dev.inmo.tgbotapi.types.files.CustomEmojiVideoSticker
public inline fun <T>
TelegramMediaFile.ifCustomEmojiVideoSticker(block: (CustomEmojiVideoSticker) -> T): T? =
customEmojiVideoStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.unknownStickerOrNull(): UnknownSticker? = this as?
dev.inmo.tgbotapi.types.files.UnknownSticker
public inline fun TelegramMediaFile.unknownStickerOrThrow(): UnknownSticker = this as
dev.inmo.tgbotapi.types.files.UnknownSticker
public inline fun <T> TelegramMediaFile.ifUnknownSticker(block: (UnknownSticker) -> T): T? =
unknownStickerOrNull() ?.let(block)
public inline fun TelegramMediaFile.thumbedMediaFileOrNull(): ThumbedMediaFile? = this as? public inline fun TelegramMediaFile.thumbedMediaFileOrNull(): ThumbedMediaFile? = this as?
dev.inmo.tgbotapi.types.files.ThumbedMediaFile dev.inmo.tgbotapi.types.files.ThumbedMediaFile

View File

@@ -30,7 +30,7 @@ fun <T> Flow<Iterable<T>>.flatten(): Flow<T> = flow {
} }
} }
fun <T, R> Flow<T>.flatMap(mapper: (T) -> Iterable<R>): Flow<R> = flow { fun <T, R> Flow<T>.flatMap(mapper: suspend (T) -> Iterable<R>): Flow<R> = flow {
collect { collect {
mapper(it).forEach { mapper(it).forEach {
emit(it) emit(it)

View File

@@ -1,8 +1,8 @@
package dev.inmo.tgbotapi.extensions.utils.extensions.raw package dev.inmo.tgbotapi.extensions.utils.extensions.raw
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.requests.abstracts.FileId import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.types.FileUniqueId import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.StickerSetName
import dev.inmo.tgbotapi.types.files.* import dev.inmo.tgbotapi.types.files.*
import dev.inmo.tgbotapi.types.stickers.MaskPosition import dev.inmo.tgbotapi.types.stickers.MaskPosition
@@ -17,8 +17,10 @@ inline val Sticker.is_video: Boolean
inline val Sticker.set_name: StickerSetName? inline val Sticker.set_name: StickerSetName?
get() = stickerSetName get() = stickerSetName
inline val Sticker.mask_position: MaskPosition? inline val Sticker.mask_position: MaskPosition?
get() = maskPosition get() = maskStickerOrNull() ?.maskPosition
inline val Sticker.file_size: Long? inline val Sticker.file_size: Long?
get() = fileSize get() = fileSize
inline val Sticker.premium_animation: File? inline val Sticker.premium_animation: File?
get() = premiumAnimationFile get() = regularStickerOrNull() ?.premiumAnimationFile
inline val Sticker.custom_emoji_id: CustomEmojiId?
get() = customEmojiStickerOrNull() ?.customEmojiId

View File

@@ -4,17 +4,25 @@ import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.chat.* import dev.inmo.tgbotapi.types.chat.*
import dev.inmo.tgbotapi.types.message.abstracts.Message import dev.inmo.tgbotapi.types.message.abstracts.Message
import dev.inmo.tgbotapi.types.message.textsources.link import dev.inmo.tgbotapi.types.message.textsources.link
import io.ktor.http.encodeURLQueryComponent
fun makeUsernameLink(username: String) = "$internalLinkBeginning/$username" fun makeUsernameLink(username: String) = "$internalLinkBeginning/$username"
fun makeUsernameDeepLinkPrefix(username: String) = "${makeUsernameLink(username)}?start=" fun makeUsernameDeepLinkPrefix(username: String) = "${makeUsernameLink(username)}?start="
fun makeUsernameStartattachPrefix(username: String) = "$internalLinkBeginning/$username?startattach"
fun makeUsernameStartattachLink(username: String, data: String? = null) = "${makeUsernameStartattachPrefix(username)}${data?.let { "=$it" } ?: ""}"
inline val Username.link inline val Username.link
get() = makeUsernameLink(usernameWithoutAt) get() = makeUsernameLink(usernameWithoutAt)
inline val Username.deepLinkPrefix inline val Username.deepLinkPrefix
get() = makeUsernameDeepLinkPrefix(usernameWithoutAt) get() = makeUsernameDeepLinkPrefix(usernameWithoutAt)
inline val Username.startattachPrefix
get() = makeUsernameStartattachPrefix(usernameWithoutAt)
inline fun makeLink(username: Username) = username.link inline fun makeLink(username: Username) = username.link
inline fun makeTelegramDeepLink(username: String, startParameter: String) = "${makeUsernameDeepLinkPrefix(username)}$startParameter" inline fun makeTelegramDeepLink(username: String, startParameter: String) = "${makeUsernameDeepLinkPrefix(username)}$startParameter".encodeURLQueryComponent()
inline fun makeDeepLink(username: Username, startParameter: String) = "${username.deepLinkPrefix}$startParameter" inline fun makeTelegramStartattach(username: String, data: String? = null) = makeUsernameStartattachLink(username, data)
inline fun makeDeepLink(username: Username, startParameter: String) = makeTelegramDeepLink(username.usernameWithoutAt, startParameter)
inline fun makeTelegramDeepLink(username: Username, startParameter: String) = makeDeepLink(username, startParameter) inline fun makeTelegramDeepLink(username: Username, startParameter: String) = makeDeepLink(username, startParameter)
inline fun makeTelegramStartattach(username: Username, data: String? = null) = makeTelegramStartattach(username.usernameWithoutAt, data)
fun makeLinkToMessage( fun makeLinkToMessage(
username: String, username: String,

View File

@@ -21,7 +21,7 @@ import kotlinx.coroutines.flow.*
fun TelegramBot.longPollingFlow( fun TelegramBot.longPollingFlow(
timeoutSeconds: Seconds = 30, timeoutSeconds: Seconds = 30,
exceptionsHandler: (ExceptionHandler<Unit>)? = null, exceptionsHandler: (ExceptionHandler<Unit>)? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
): Flow<Update> = channelFlow { ): Flow<Update> = channelFlow {
var lastUpdateIdentifier: UpdateIdentifier? = null var lastUpdateIdentifier: UpdateIdentifier? = null
@@ -81,7 +81,7 @@ fun TelegramBot.startGettingOfUpdatesByLongPolling(
timeoutSeconds: Seconds = 30, timeoutSeconds: Seconds = 30,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default), scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
exceptionsHandler: (ExceptionHandler<Unit>)? = null, exceptionsHandler: (ExceptionHandler<Unit>)? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
updatesReceiver: UpdateReceiver<Update> updatesReceiver: UpdateReceiver<Update>
): Job = longPollingFlow(timeoutSeconds, exceptionsHandler, allowedUpdates).subscribeSafely( ): Job = longPollingFlow(timeoutSeconds, exceptionsHandler, allowedUpdates).subscribeSafely(
scope, scope,
@@ -97,7 +97,7 @@ fun TelegramBot.createAccumulatedUpdatesRetrieverFlow(
avoidInlineQueries: Boolean = false, avoidInlineQueries: Boolean = false,
avoidCallbackQueries: Boolean = false, avoidCallbackQueries: Boolean = false,
exceptionsHandler: ExceptionHandler<Unit>? = null, exceptionsHandler: ExceptionHandler<Unit>? = null,
allowedUpdates: List<String>? = null allowedUpdates: List<String>? = ALL_UPDATES_LIST
): Flow<Update> = longPollingFlow( ): Flow<Update> = longPollingFlow(
timeoutSeconds = 0, timeoutSeconds = 0,
exceptionsHandler = { exceptionsHandler = {
@@ -117,7 +117,7 @@ fun TelegramBot.retrieveAccumulatedUpdates(
avoidCallbackQueries: Boolean = false, avoidCallbackQueries: Boolean = false,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default), scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
exceptionsHandler: (ExceptionHandler<Unit>)? = null, exceptionsHandler: (ExceptionHandler<Unit>)? = null,
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
updatesReceiver: UpdateReceiver<Update> updatesReceiver: UpdateReceiver<Update>
): Job = createAccumulatedUpdatesRetrieverFlow( ): Job = createAccumulatedUpdatesRetrieverFlow(
avoidInlineQueries, avoidInlineQueries,
@@ -149,7 +149,7 @@ suspend fun TelegramBot.flushAccumulatedUpdates(
avoidInlineQueries: Boolean = false, avoidInlineQueries: Boolean = false,
avoidCallbackQueries: Boolean = false, avoidCallbackQueries: Boolean = false,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default), scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
allowedUpdates: List<String>? = null, allowedUpdates: List<String>? = ALL_UPDATES_LIST,
exceptionsHandler: ExceptionHandler<Unit>? = null, exceptionsHandler: ExceptionHandler<Unit>? = null,
updatesReceiver: UpdateReceiver<Update> = {} updatesReceiver: UpdateReceiver<Update> = {}
) = retrieveAccumulatedUpdates( ) = retrieveAccumulatedUpdates(

View File

@@ -0,0 +1,3 @@
package dev.inmo.tgbotapi.webapps
typealias AlertCallback = () -> Unit

View File

@@ -0,0 +1,3 @@
package dev.inmo.tgbotapi.webapps
typealias ConfirmCallback = (confirmed: Boolean) -> Unit

View File

@@ -5,3 +5,4 @@ import dev.inmo.tgbotapi.webapps.invoice.InvoiceClosedInfo
typealias EventHandler = WebApp.() -> Unit typealias EventHandler = WebApp.() -> Unit
typealias ViewportChangedEventHandler = WebApp.(ViewportChangedData) -> Unit typealias ViewportChangedEventHandler = WebApp.(ViewportChangedData) -> Unit
typealias InvoiceClosedEventHandler = WebApp.(InvoiceClosedInfo) -> Unit typealias InvoiceClosedEventHandler = WebApp.(InvoiceClosedInfo) -> Unit
typealias PopupClosedEventHandler = WebApp.(String?) -> Unit

View File

@@ -7,4 +7,5 @@ sealed class EventType(val typeName: String) {
object BackButtonClicked : EventType("backButtonClicked") object BackButtonClicked : EventType("backButtonClicked")
object SettingsButtonClicked : EventType("settingsButtonClicked") object SettingsButtonClicked : EventType("settingsButtonClicked")
object InvoiceClosed : EventType("invoiceClosed") object InvoiceClosed : EventType("invoiceClosed")
object PopupClosed : EventType("popupClosed")
} }

View File

@@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.webapps
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
import dev.inmo.tgbotapi.webapps.haptic.HapticFeedback import dev.inmo.tgbotapi.webapps.haptic.HapticFeedback
import dev.inmo.tgbotapi.webapps.invoice.InvoiceClosedInfo import dev.inmo.tgbotapi.webapps.invoice.InvoiceClosedInfo
import dev.inmo.tgbotapi.webapps.popup.*
external class WebApp { external class WebApp {
val version: String val version: String
@@ -24,6 +25,15 @@ external class WebApp {
val viewportHeight: Float val viewportHeight: Float
val viewportStableHeight: Float val viewportStableHeight: Float
val isClosingConfirmationEnabled: Boolean
fun enableClosingConfirmation()
fun disableClosingConfirmation()
fun showPopup(params: PopupParams, callback: ClosePopupCallback? = definedExternally)
fun showAlert(message: String, callback: AlertCallback? = definedExternally)
fun showConfirm(message: String, callback: ConfirmCallback? = definedExternally)
@JsName("MainButton") @JsName("MainButton")
val mainButton: MainButton val mainButton: MainButton
@@ -38,6 +48,8 @@ external class WebApp {
internal fun onEventWithViewportChangedData(type: String, callback: (ViewportChangedData) -> Unit) internal fun onEventWithViewportChangedData(type: String, callback: (ViewportChangedData) -> Unit)
@JsName("onEvent") @JsName("onEvent")
internal fun onEventWithInvoiceClosedInfo(type: String, callback: (InvoiceClosedInfo) -> Unit) internal fun onEventWithInvoiceClosedInfo(type: String, callback: (InvoiceClosedInfo) -> Unit)
@JsName("onEvent")
internal fun onEventWithPopupClosedInfo(type: String, callback: (String?) -> Unit)
fun offEvent(type: String, callback: () -> Unit) fun offEvent(type: String, callback: () -> Unit)
@JsName("offEvent") @JsName("offEvent")
@@ -100,6 +112,18 @@ fun WebApp.onEvent(type: EventType.InvoiceClosed, eventHandler: InvoiceClosedEve
) )
} }
/**
* @return The callback which should be used in case you want to turn off events handling
*/
fun WebApp.onEvent(type: EventType.PopupClosed, eventHandler: PopupClosedEventHandler) = { it: String? ->
eventHandler(js("this").unsafeCast<WebApp>(), it)
}.also {
onEventWithPopupClosedInfo(
type.typeName,
callback = it
)
}
/** /**
* @return The callback which should be used in case you want to turn off events handling * @return The callback which should be used in case you want to turn off events handling
*/ */
@@ -124,8 +148,55 @@ fun WebApp.onSettingsButtonClicked(eventHandler: EventHandler) = onEvent(EventTy
* @return The callback which should be used in case you want to turn off events handling * @return The callback which should be used in case you want to turn off events handling
*/ */
fun WebApp.onInvoiceClosed(eventHandler: InvoiceClosedEventHandler) = onEvent(EventType.InvoiceClosed, eventHandler) fun WebApp.onInvoiceClosed(eventHandler: InvoiceClosedEventHandler) = onEvent(EventType.InvoiceClosed, eventHandler)
/**
* @return The callback which should be used in case you want to turn off events handling
*/
fun WebApp.onPopupClosed(eventHandler: PopupClosedEventHandler) = onEvent(EventType.PopupClosed, eventHandler)
fun WebApp.isInitDataSafe(botToken: String) = TelegramAPIUrlsKeeper(botToken).checkWebAppData( fun WebApp.isInitDataSafe(botToken: String) = TelegramAPIUrlsKeeper(botToken).checkWebAppData(
initData, initData,
initDataUnsafe.hash initDataUnsafe.hash
) )
fun WebApp.showPopup(
message: String,
title: String?,
buttons: Array<PopupButton>,
callback: ClosePopupCallback? = null
) = showPopup(
PopupParams(
message,
title,
buttons
),
callback
)
fun WebApp.showPopup(
message: String,
title: String?,
firstButton: PopupButton,
vararg otherButtons: PopupButton,
callback: ClosePopupCallback? = null
) = showPopup(
PopupParams(
message,
title,
arrayOf(firstButton, *otherButtons)
),
callback
)
var WebApp.requireClosingConfirmation
get() = isClosingConfirmationEnabled
set(value) {
if (value) {
enableClosingConfirmation()
} else {
disableClosingConfirmation()
}
}
fun WebApp.toggleClosingConfirmation() {
requireClosingConfirmation = !requireClosingConfirmation
}

View File

@@ -17,10 +17,14 @@ external interface WebAppUser {
val username: String? val username: String?
@JsName(languageCodeField) @JsName(languageCodeField)
val languageCode: String? val languageCode: String?
val is_premium: Boolean?
@JsName(photoUrlField) @JsName(photoUrlField)
val photoUrl: String? val photoUrl: String?
} }
val WebAppUser.isPremium
get() = is_premium == true
fun WebAppUser.asUser() = if (isBot == true) { fun WebAppUser.asUser() = if (isBot == true) {
CommonBot( CommonBot(
UserId(id), UserId(id),
@@ -34,6 +38,7 @@ fun WebAppUser.asUser() = if (isBot == true) {
firstName, firstName,
lastName ?: "", lastName ?: "",
username ?.let(::Username), username ?.let(::Username),
languageCode ?.let(::IetfLanguageCode) languageCode ?.let(::IetfLanguageCode),
isPremium = isPremium
) )
} }

View File

@@ -0,0 +1,3 @@
package dev.inmo.tgbotapi.webapps.popup
typealias ClosePopupCallback = (id: String) -> Unit

View File

@@ -0,0 +1,55 @@
package dev.inmo.tgbotapi.webapps.popup
import kotlin.js.json
external interface PopupButton {
val id: String
val type: PopupButtonType
val text: String?
}
fun PopupButton(
id: String,
type: PopupButtonType,
text: String? = null
) = json(
*listOfNotNull(
"id" to id,
"type" to type.typeName,
("text" to text).takeIf { text != null }
).toTypedArray()
).unsafeCast<PopupButton>()
value class PopupButtonType(
val typeName: String
) {
companion object {
val Default = PopupButtonType("default")
val Ok = PopupButtonType("ok")
val Close = PopupButtonType("close")
val Cancel = PopupButtonType("cancel")
val Destructive = PopupButtonType("destructive")
}
}
fun DefaultPopupButton(
id: String,
text: String
) = PopupButton(id, PopupButtonType.Default, text)
fun OkPopupButton(
id: String
) = PopupButton(id, PopupButtonType.Ok)
fun ClosePopupButton(
id: String
) = PopupButton(id, PopupButtonType.Close)
fun CancelPopupButton(
id: String
) = PopupButton(id, PopupButtonType.Cancel)
fun DestructivePopupButton(
id: String,
text: String
) = PopupButton(id, PopupButtonType.Destructive, text)

View File

@@ -0,0 +1,48 @@
package dev.inmo.tgbotapi.webapps.popup
import kotlin.js.json
external interface PopupParams {
val message: String
val title: String?
val buttons: Array<PopupButton>
}
fun PopupParams(
message: String,
title: String?,
buttons: Array<PopupButton>
) = json(
*listOfNotNull(
"message" to message,
"buttons" to buttons,
("title" to title).takeIf { title != null }
).toTypedArray()
).unsafeCast<PopupParams>()
fun PopupParams(
message: String,
firstButton: PopupButton,
vararg otherButtons: PopupButton
) = PopupParams(
message,
null,
arrayOf(
firstButton,
*otherButtons
)
)
fun PopupParams(
title: String,
message: String,
firstButton: PopupButton,
vararg otherButtons: PopupButton
) = PopupParams(
message,
title,
arrayOf(
firstButton,
*otherButtons
)
)