1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2025-11-19 05:45:40 +00:00

Compare commits

..

110 Commits

Author SHA1 Message Date
c0b4b523cf update micro_utils version 2021-01-08 16:41:08 +06:00
49e6e9cfe3 fix in new project publication scripts 2021-01-08 16:39:29 +06:00
ffadb6355b fixes in readme 2021-01-08 16:37:03 +06:00
9cc402b42d ContentMessage -> CommonMessage in behaviour_builder, fill readme of behaviour_builder and update projects schema 2021-01-08 16:28:00 +06:00
b9341f89ac add media groups triggers and waiters 2021-01-08 11:02:18 +06:00
ac07f44c81 change public signature of waiters 2021-01-08 10:29:29 +06:00
2e53247726 now content waiters and triggers may have set up manually parameter "includeMediaGroups" 2021-01-08 10:22:29 +06:00
e2dddf96a1 several small additions 2021-01-08 00:30:29 +06:00
ce0fceb240 include media groups 2021-01-08 00:22:19 +06:00
633239961a update FlowsUsh 2021-01-07 19:02:35 +06:00
8b79b15777 FlowsUpdatesFilter#allUpdatesWithoutMediaGroupsGroupingFlow 2021-01-07 19:01:02 +06:00
a28cf5ddff now BehaviourContext is UpdateFilterm TelegramBot and CoroutineScope 2021-01-07 18:20:23 +06:00
8c2cffc8e3 rename steps subproject to behaviour_builder 2021-01-07 18:17:50 +06:00
b933361258 optimize imports in steps 2021-01-07 18:12:37 +06:00
383e722d07 rename scenario to behaviour context 2021-01-07 18:11:24 +06:00
3125c2fc1b add callback query expectations and triggers 2021-01-07 17:57:08 +06:00
965b8c3c50 add chat event triggers and expectations in steps 2021-01-07 17:24:58 +06:00
94745ef373 add docs to base expectations 2021-01-07 16:01:25 +06:00
aee5ab564b optimize imports 2021-01-07 12:45:54 +06:00
c70f0b65dd add content triggers and update command 2021-01-07 12:45:30 +06:00
30e6f68228 small update of scenarios 2021-01-06 23:21:33 +06:00
00873a255c preview state of steps 2021-01-06 23:06:48 +06:00
6bd423dc11 class casts for ResendableContent and TextSource 2021-01-06 20:49:55 +06:00
c5ada8cea0 start 0.30.13 2021-01-06 15:41:27 +06:00
0cb3df4d1a Merge pull request #251 from InsanusMokrassar/0.3.12
0.3.12
2021-01-05 23:06:35 +06:00
b22118b400 add suppressing of unchecked casts 2021-01-05 23:04:09 +06:00
2006e45b57 last update of classcasts 2021-01-05 23:02:00 +06:00
c091098feb remove *Impl casts 2021-01-05 22:37:52 +06:00
6cf8d47cbf update classcasts 2021-01-05 22:27:42 +06:00
3dc4e9dda4 preview of solution for #145 2021-01-05 11:54:02 +06:00
6407ad1a93 Revert "one more update of klassindex"
This reverts commit f974e5787f.
2021-01-05 11:11:50 +06:00
f974e5787f one more update of klassindex 2021-01-05 11:11:37 +06:00
28a9bbd310 Revert "add klassindex"
This reverts commit 599d5a51e3.
2021-01-05 01:37:01 +06:00
599d5a51e3 add klassindex 2021-01-05 01:36:52 +06:00
25f8d15a4b start 0.3.12 2021-01-05 00:16:11 +06:00
66c2cb2d30 Merge pull request #243 from InsanusMokrassar/0.30.11
0.30.11
2021-01-04 14:35:56 +06:00
74fb448378 update klock 2021-01-04 14:29:06 +06:00
3417ec060f Update README.md 2021-01-03 15:22:19 +06:00
4f54a00003 Update README.md 2021-01-03 15:20:42 +06:00
4fb187da30 Update README.md 2021-01-03 15:19:35 +06:00
7637b6f69a Update README.md 2021-01-03 15:17:58 +06:00
9df9af193c bot actions got class-cast shortcuts 2021-01-02 16:06:00 +06:00
ba4b4c4b64 bot actions shortcut 2021-01-02 15:51:26 +06:00
fa0861b8bc update micro_utils 2020-12-23 13:42:00 +06:00
20494e1d4a Update CHANGELOG.md 2020-12-22 17:34:14 +06:00
a404008dee Update dependencies 2020-12-22 17:33:20 +06:00
881205dd80 update dependencies 2020-12-16 14:25:09 +06:00
2096c44811 start 0.30.11 2020-12-13 22:25:57 +06:00
1a3da33589 Merge pull request #237 from InsanusMokrassar/0.30.10
0.30.10
2020-12-08 14:25:00 +06:00
be28c9bd5d update dependencies 2020-12-08 12:03:45 +06:00
a193ef5fd5 buildBot 2020-12-04 16:29:50 +06:00
7c43d3aaa5 update dependencies 2020-12-04 16:13:02 +06:00
e034afc75c start 0.30.10 2020-12-04 16:06:14 +06:00
08e3326d1a Merge pull request #226 from InsanusMokrassar/0.30.9
0.30.9
2020-11-27 00:59:33 +06:00
6dd1825b98 update dependencies 2020-11-27 00:44:29 +06:00
bd4eb29fc1 update limits of dices 2020-11-26 19:37:35 +06:00
2cc66ef13c BowlingDiceAnimationType 2020-11-25 23:09:49 +06:00
9a8175d1c8 update dependencies 2020-11-25 23:02:54 +06:00
25285b2e8a start 0.30.9 2020-11-25 23:01:19 +06:00
bb09f05d7b Merge pull request #223 from InsanusMokrassar/0.30.8
0.30.8
2020-11-21 16:37:47 +06:00
da5e46e59f remove several PreviewFeature 2020-11-21 15:19:31 +06:00
a3f315a73a update micro_utils 2020-11-21 15:15:07 +06:00
43583a885a update versions 2020-11-21 14:40:34 +06:00
ec02257ecd start 0.30.8 2020-11-21 14:32:32 +06:00
29ee4a9396 update publishing urls 2020-11-18 17:51:53 +06:00
2717cb4fc6 exprimentally update publishing scripts 2020-11-18 17:18:51 +06:00
1ce2526401 Merge pull request #217 from InsanusMokrassar/0.30.7
0.30.7
2020-11-17 16:32:19 +06:00
74c480b07e one new telegramBot function 2020-11-17 16:18:58 +06:00
657e9aa770 add KtorRequestsExecutorBuilder 2020-11-17 16:06:25 +06:00
0d19952ba7 PowLimiter and CommonLimiter rewriting (#210) 2020-11-17 15:48:58 +06:00
f8cccc3e17 update gradle 2020-11-17 14:41:53 +06:00
bc1b7c3f25 fix ending of host url in TelegramAPIUrlsKeeper 2020-11-17 13:18:11 +06:00
ec74111a9d update micro_utils version 2020-11-17 12:51:55 +06:00
2dadeb7eb7 start 0.30.7 2020-11-17 12:50:54 +06:00
24bd65501b Update README.md 2020-11-16 21:59:25 +06:00
574ca803fa Update README.md 2020-11-16 21:56:31 +06:00
947e3bf34e add fast start 2020-11-16 21:55:22 +06:00
488158d8fb Merge pull request #214 from InsanusMokrassar/0.30.6
0.30.6
2020-11-16 16:29:46 +06:00
41bf3c7f0b filling of changelog and renaming of new function makeLink -> makeUsernameLink 2020-11-16 16:25:10 +06:00
c2032d21cd TextSource properties has been renamed 2020-11-16 13:32:20 +06:00
6650dd9cfe reworking of links extension 2020-11-16 13:22:08 +06:00
af1d6d6f1a start 0.30.6 2020-11-15 23:05:19 +06:00
a5861d659d Merge pull request #212 from InsanusMokrassar/0.30.5
0.30.5
2020-11-14 19:44:26 +06:00
b36f80a6d3 update micro_utils 2020-11-14 19:41:26 +06:00
7c4f034a6c fixes in imports 2020-11-14 14:41:16 +06:00
3b08bc6dd2 serialization of ChatMember and changing of id type in User 2020-11-14 14:38:54 +06:00
ed87c0ad95 start 0.30.5 2020-11-14 11:19:53 +06:00
c8c0fc2ce8 Merge pull request #211 from InsanusMokrassar/0.30.4
0.30.4
2020-11-12 23:05:49 +06:00
1f40ce1a81 add a lot of shortcuts for events 2020-11-12 22:35:27 +06:00
b0219389fc add several functions and changelog notes 2020-11-12 22:23:10 +06:00
f6ec82b449 deprecate old asChatEventsFlow 2020-11-12 22:04:46 +06:00
71dac70635 deprecations in old functions 2020-11-12 22:02:06 +06:00
98f68a9e1e filling of changelog 2020-11-12 21:54:14 +06:00
e7199e7451 fix use user in group messages 2020-11-12 21:46:33 +06:00
33b50c6c68 events filters 2020-11-12 21:17:11 +06:00
e4ce6f8fc7 fixes in events 2020-11-12 17:44:33 +06:00
8764f18ca8 TextContent now implements TextedInput 2020-11-12 16:57:32 +06:00
b2fa7fee9d rename MultilevelTextSource#textSources 2020-11-12 16:37:17 +06:00
0121e3a104 update micro_utils 2020-11-12 16:33:48 +06:00
f6e5664632 start 0.30.4 2020-11-12 16:31:02 +06:00
c5b7c4e1f5 Merge pull request #209 from InsanusMokrassar/0.30.3
0.30.3
2020-11-11 11:25:25 +06:00
53800d49bf small rework in ExceptionsOnlyLimiter 2020-11-11 11:23:24 +06:00
d83e3eb10a add handling of 429 status 2020-11-11 11:15:19 +06:00
66b4d06064 limiter rework 2020-11-11 10:23:20 +06:00
4fab01b2a2 update micro_utils 2020-11-11 10:15:06 +06:00
b81086c4bb start 0.30.3 2020-11-11 10:14:10 +06:00
d776071cac Update build.gradle 2020-11-11 01:32:25 +06:00
5f33d05deb Update extensions utils dependencies 2020-11-11 01:31:28 +06:00
912cc7217c Update extensions api dependencies 2020-11-11 01:30:31 +06:00
96e00f6e31 Merge pull request #207 from InsanusMokrassar/0.30.2
0.30.2
2020-11-10 21:52:37 +06:00
110 changed files with 4321 additions and 1340 deletions

View File

@@ -1,5 +1,149 @@
# TelegramBotAPI changelog # TelegramBotAPI changelog
## 0.30.13
* `Common`:
* `Version`:
* `MicroUtils`: `0.4.15` -> `0.4.16`
* `Core`:
* New variable `FlowsUpdatesFilter#allUpdatesWithoutMediaGroupsGroupingFlow` which will contains updates without
`SentMediaGroupUpdate`
* `Utils`:
* Extensions for `ResendableContent` has been added
* Extensions for `TextSource` has been added
* `Behaviour Builder`:
* Project has been created :)
## 0.30.12
* `Utils`:
* Class casts has been added. Now you can write something like `message.asGroupMessage() ?.let { ... }` instead of
`(message as? GroupMessage<*>) ?.let { ... }`
## 0.30.11
* `Common`:
* `Version`:
* `MicroUtils`: `0.4.11` -> `0.4.15`
* `Klock`: `2.0.1` -> `2.0.3`
* `Ktor`: `1.4.3` -> `1.5.0`
* `Core`:
* All bot actions got functions for short calling, like `recordVideo` for `RecordVideoNote`
* All bot actions got class-cast shortcuts
## 0.30.10
* `Common`:
* `Version`:
* `Kotlin`: `1.4.20` -> `1.4.21`
* `Klock`: `2.0.0` -> `2.0.1`
* `Ktor`: `1.4.2` -> `1.4.3`
* `MicroUtils`: `0.4.6` -> `0.4.11`
* `API Extensions`:
* New function `buildBot`
## 0.30.9
* `Common`:
* `Version`:
* `UUID`: `0.2.2` -> `0.2.3`
* `Coroutines`: `1.4.1` -> `1.4.2`
* `MicroUtils`: `0.4.3` -> `0.4.6`
* `Core`:
* Add `BowlingDiceAnimationType`
## 0.30.8
* `Common`:
* `Version`:
* `Kotlin`: `1.4.10` -> `1.4.20`
* `Klock`: `1.12.1` -> `2.0.0`
* `MicroUtils`: `0.4.1` -> `0.4.3`
## 0.30.7
* `Common`:
* `Version`:
* `MicroUtils`: `0.4.0` -> `0.4.1`
* `Core`:
* `TelegramAPIUrlsKeeper` will fix ending of host url since this version
* New mechanisms in`PowLimiter` and `CommonLimiter` has been added
* New builder `KtorRequestsExecutorBuilder`
* New function `telegramBot`
* `Utils`:
* Simple function `telegramBot(TelegramAPIUrlsKeeper)` has been deprecated with replacement by almost the same
function in `Core`
## 0.30.6
* `Core`
* `TextSource` properties has been renamed:
* `asMarkdownSource` -> `markdown`
* `asMarkdownV2Source` -> `markdownV2`
* `asHtmlSource` -> `html`
* `PrivateChat` override `id` property with type `UserId`
* Several new extensions and functions in links creation:
* New function `makeUsernameLink` with parameter `String`
* New extension `Username#link` and function `makeLink(Username)`
* Function `makeLinkToMessage` now able to get any type of chat
* New extension `Message#link`
* Old functions `makeLinkToAddStickerSet...` has been deprecated:
* `makeLinkToAddStickerSet`
* `makeLinkToAddStickerSetInMarkdownV2`
* `makeLinkToAddStickerSetInMarkdown`
* `makeLinkToAddStickerSetInHtml`
## 0.30.5
* `Common`:
* `Version`:
* `MicroUtils`: `0.3.3` -> `0.4.0`
* `Core`:
* Mechanism of `ChatMember` serialization has been changed
* Since this version any `ChatMember` can be serialized (even outside in case it marked by `@Serializable`)
* Since this version any `ChatMember` (included in this project) can be deserialized in common way
* `User` property `id` has changed its type: now it is `UserId` (under the hood it is the same as `ChatId`)
## 0.30.4
* `Common`:
* `Version`:
* `MicroUtils`: `0.3.1` -> `0.3.3`
* `Core`:
* `MultilevelTextSource#textSources` has been safely renamed to `subsources`
* `TextContent#fullEntitiesList` has been deprecated
* Now `TextContent` implements `TextedInput`
* `TextContent#entities` has been deprecated
* `GroupEventMessage` now overrides `chatEvent` with type `GroupEvent`
* `SupergroupEventMessage` now overrides `chatEvent` with type `SupergroupEvent`
* Any `ChatEventMessage` now have generic type of its `chatEvent` (just like messages)
* `Utils`:
* Old extensions related to chat events are deprecated:
* `Flow<ChatEventMessage<*>>#divideBySource`
* `Flow<ChatEventMessage<*>>#onlyChannelEvents`
* `Flow<ChatEventMessage<*>>#onlyGroupEvents`
* `Flow<ChatEventMessage<*>>#onlySupergroupEvents`
* A lot of extensions for `Flow<ChatEventMessage>` has been added:
* `FlowsUpdatesFilter#events`
* `FlowsUpdatesFilter#channelEvents`
* `FlowsUpdatesFilter#groupEvents`
* `FlowsUpdatesFilter#supergroupEvents`
* And a lot of other filters with specific types
## 0.30.3
* `Common`:
* `Version`:
* `MicroUtils`: `0.3.0` -> `0.3.1`
* `Core`:
* New type of requests exceptions `TooMuchRequestsException`. In fact it will be rare case when you will get this
exception
* `EmptyLimiter` has been renamed to `ExceptionsOnlyLimiter` and currently will stop requests after
`TooMuchRequestsException` happen until retry time is actual
* Now `ExceptionsOnlyLimiter` (previously `EmptyLimiter`) is a class
* `AbstractRequestCallFactory` currently will not look at the response and wait if it have `RetryAfter` error. New
behaviour aimed on delegating of this work to `RequestsLimiter`
## 0.30.2 ## 0.30.2
* `Common`: * `Common`:

View File

@@ -1,17 +1,30 @@
# TelegramBotAPI # TelegramBotAPI
<details>
<summary><b>I do not wanna read a lot, just give me my bot</b></summary>
You can simply use <a href="https://github.com/InsanusMokrassar/TelegramBotAPI-bot_template">this template</a> (and button
<a href="https://github.com/InsanusMokrassar/TelegramBotAPI-bot_template/generate">Use template</a>) to get your copy of bot and start to code.
<p></p>
<b>P.S. Do not forget to look into our <a href="https://bookstack.inmo.dev/books/telegrambotapi/">minidocs</a> and
<a href="https://tgbotapi.inmo.dev/docs/index.html">kdocs</a></b>
</details>
| Common info | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) [![Build Status](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI) [Small survey](https://forms.gle/2Hex2ynbHWHhi1KY7)| | Common info | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) [![Build Status](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI) [Small survey](https://forms.gle/2Hex2ynbHWHhi1KY7)|
| -------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | -------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Useful links | [![Chat in Telegram](badges/chat.svg)](https://t.me/InMoTelegramBotAPI) [![Create bot](badges/template.svg)](https://github.com/InsanusMokrassar/TelegramBotAPI-bot_template/generate) [![KDocs](badges/kdocs.svg)](https://tgbotapi.inmo.dev/docs/index.html) [Examples](https://github.com/InsanusMokrassar/TelegramBotAPI-examples/), [Mini tutorial](https://bookstack.inmo.dev/books/telegrambotapi/chapter/introduction-tutorial) | | Useful links | [![Chat in Telegram](badges/chat.svg)](https://t.me/InMoTelegramBotAPI) [![Create bot](badges/template.svg)](https://github.com/InsanusMokrassar/TelegramBotAPI-bot_template/generate) [![KDocs](badges/kdocs.svg)](https://tgbotapi.inmo.dev/docs/index.html) [Examples](https://github.com/InsanusMokrassar/TelegramBotAPI-examples/), [Mini tutorial](https://bookstack.inmo.dev/books/telegrambotapi/chapter/introduction-tutorial) |
| TelegramBotAPI Core status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi.core/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi.core/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.core) | | TelegramBotAPI Core status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi.core/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi.core/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.core) |
| TelegramBotAPI Extensions status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.api/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.api/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.api/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.api) | | TelegramBotAPI API Extensions status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.api/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.api/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.api/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.api) |
| TelegramBotAPI Util Extensions status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.utils/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.utils/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.utils/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.utils) | | TelegramBotAPI Util Extensions status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.utils/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.utils/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.utils/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.utils) |
| TelegramBotAPI Behaviour Builder Extensions status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.behaviour_builder/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi.extensions.behaviour_builder/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.behaviour_builder/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.extensions.behaviour_builder) |
| TelegramBotAPI All status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi) | | TelegramBotAPI All status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi) |
**At the time of publication of version `0.28.0` there are errors in serialization plugins like **At the time of publication of version `0.28.0` there are errors in serialization plugins like
[kotlinx.serialization#1004](https://github.com/Kotlin/kotlinx.serialization/issues/1004). It is possible, that both JVM [kotlinx.serialization#1004](https://github.com/Kotlin/kotlinx.serialization/issues/1004). It is possible, that both JVM
and JS version may work improperly in some cases with `kotlinx.serialization` version `1.0.0-RC`** and JS version may work improperly in some cases with `kotlinx.serialization` version `1.0.0-RC`**
## What is it?
It is a complex of libraries for working with `TelegramBotAPI` in type-safe and strict way as much as it possible. In It is a complex of libraries for working with `TelegramBotAPI` in type-safe and strict way as much as it possible. In
the list of this complex currently next projects: the list of this complex currently next projects:
@@ -22,6 +35,8 @@ the list of this complex currently next projects:
`RequestsExecutor`), which allows to use the core library in more pleasant way `RequestsExecutor`), which allows to use the core library in more pleasant way
* [TelegramBotAPI Util Extensions](tgbotapi.extensions.utils/README.md) - contains extensions for more comfortable * [TelegramBotAPI Util Extensions](tgbotapi.extensions.utils/README.md) - contains extensions for more comfortable
work with commands, updates and other different things work with commands, updates and other different things
* [TelegramBotAPI Behaviour Builder Extensions](tgbotapi.extensions.behaviour_builder/README.md) - builder for
step-by-step handling of bot behaviour in more comfortable manner
* [TelegramBotAPI](tgbotapi/README.md) - concentration of all previously mentioned libraries * [TelegramBotAPI](tgbotapi/README.md) - concentration of all previously mentioned libraries
Most part of some specific solves or unuseful Most part of some specific solves or unuseful
@@ -76,6 +91,7 @@ kotlin {
In most cases, the most simple way will be to implement [TelegramBotAPI](tgbotapi/README.md) - it contains In most cases, the most simple way will be to implement [TelegramBotAPI](tgbotapi/README.md) - it contains
all necessary tools for comfort usage of this library. If you want to exclude some libraries, you can implement just all necessary tools for comfort usage of this library. If you want to exclude some libraries, you can implement just
[TelegramBotAPI BehaviourBuilder Extensions](tgbotapi.extensions.behaviour_builder/README.md),
[TelegramBotAPI API Extensions](tgbotapi.extensions.api/README.md), [TelegramBotAPI API Extensions](tgbotapi.extensions.api/README.md),
[TelegramBotAPI Util Extensions](tgbotapi.extensions.utils/README.md) or even [TelegramBotAPI Util Extensions](tgbotapi.extensions.utils/README.md) or even
[TelegramBotAPI Core](tgbotapi.core/README.md). [TelegramBotAPI Core](tgbotapi.core/README.md).

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<minder version="1.11.1"> <minder version="1.11.3">
<theme name="default" label="Default" index="-1"/> <theme name="default" label="Default" index="-1"/>
<styles> <styles>
<style level="0" isset="true" branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="200" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/> <style level="0" isset="true" branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="200" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/>
@@ -14,45 +14,54 @@
<style level="9" isset="true" branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="200" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/> <style level="9" isset="true" branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="200" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/>
<style level="10" isset="true" branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="200" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/> <style level="10" isset="true" branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="200" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true" connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/>
</styles> </styles>
<drawarea x="-320.56697591145837" y="-10.028254191080691" scale="0.75"/> <drawarea x="-950.47548925255796" y="-49.650554065281653" scale="0.5"/>
<images/> <images/>
<nodes> <nodes>
<node id="0" posx="748.88964843749955" posy="119.42341613769531" width="472" height="168" side="top" fold="false" treesize="603" layout="Downwards" group="false"> <node id="0" posx="1378.798161778599" posy="159.04571601189673" width="472" height="168" side="top" fold="false" treesize="743" layout="Downwards" group="false">
<style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="439" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/> <style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="439" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/>
<nodename posx="764.88964843749955" posy="135.42341613769531" maxwidth="488.96484375"> <nodename posx="1394.798161778599" posy="175.04571601189673" maxwidth="488.96484375">
<text data="tgbotapi.core&#10;&#10;Root project with API. It is not recommended to use its requests directly and better to use at least tgbotapi.extensions.api"/> <text data="tgbotapi.core&#10;&#10;Root project with API. It is not recommended to use its requests directly and better to use at least tgbotapi.extensions.api"/>
</nodename> </nodename>
<nodenote></nodenote> <nodenote></nodenote>
<nodes> <nodes>
<node id="1" posx="781.88964843749955" posy="387.42341613769531" width="406" height="145" side="bottom" fold="false" treesize="603" color="#68b723" colorroot="true" layout="Downwards" group="false"> <node id="1" posx="1411.798161778599" posy="427.04571601189673" width="406" height="145" side="bottom" fold="false" treesize="743" color="#68b723" colorroot="true" layout="Downwards" group="false">
<style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="none" nodewidth="394" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/> <style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="none" nodewidth="394" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/>
<nodename posx="797.88964843749955" posy="403.42341613769531" maxwidth="419.451171875"> <nodename posx="1427.798161778599" posy="443.04571601189673" maxwidth="419.451171875">
<text data="TelegramBotAPI extensions&#10;&#10;Family of projects which are fully based on TelegramBotAPI and extend its functionality"/> <text data="TelegramBotAPI extensions&#10;&#10;Family of projects which are fully based on TelegramBotAPI and extend its functionality"/>
</nodename> </nodename>
<nodenote></nodenote> <nodenote></nodenote>
<nodes> <nodes>
<node id="2" posx="683.38964843749955" posy="632.42341613769531" width="296" height="191" side="bottom" fold="false" treesize="296" color="#68b723" colorroot="true" layout="Downwards" group="false"> <node id="2" posx="1247.298161778599" posy="672.04571601189673" width="296" height="191" side="bottom" fold="false" treesize="296" color="#68b723" colorroot="true" layout="Downwards" group="false">
<style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="203" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/> <style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="203" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/>
<nodename posx="699.38964843749955" posy="648.42341613769531" maxwidth="295.90315755208337"> <nodename posx="1263.298161778599" posy="688.04571601189673" maxwidth="295.90315755208337">
<text data="tgbotapi.extensions.api&#10;&#10;Extensions project for make requests more look like in the Telegram Bot API and give opportunity to use it's easier"/> <text data="tgbotapi.extensions.api&#10;&#10;Extensions project for make requests more look like in the Telegram Bot API and give opportunity to use it's easier"/>
</nodename> </nodename>
<nodenote></nodenote> <nodenote></nodenote>
</node> </node>
<node id="3" posx="979.38964843749955" posy="632.42341613769531" width="307" height="168" side="bottom" fold="false" treesize="307" color="#68b723" colorroot="true" layout="Downwards" group="false"> <node id="3" posx="1609.298161778599" posy="672.04571601189673" width="307" height="168" side="bottom" fold="false" treesize="439" color="#68b723" colorroot="true" layout="Downwards" group="false">
<style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="286" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/> <style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="286" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/>
<nodename posx="995.38964843749955" posy="648.42341613769531" maxwidth="299.252197265625"> <nodename posx="1625.298161778599" posy="688.04571601189673" maxwidth="299.252197265625">
<text data="tgbotapi.extensions.utils&#10;&#10;Extensions project with utils things which will make easier different operations"/> <text data="tgbotapi.extensions.utils&#10;&#10;Extensions project with utils things which will make easier different operations"/>
</nodename> </nodename>
<nodenote></nodenote> <nodenote></nodenote>
<nodes>
<node id="4" posx="1543.298161778599" posy="940.04571601189673" width="439" height="122" side="bottom" fold="false" treesize="439" color="#68b723" colorroot="false" layout="Downwards" group="false">
<style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="387" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/>
<nodename posx="1559.298161778599" posy="956.04571601189673" maxwidth="408.97932942708348">
<text data="tgbotapi.extensions.behaviour_builder&#10;&#10;Extension project for building bot behaviour via special dsl"/>
</nodename>
<nodenote></nodenote>
</node>
</nodes>
</node> </node>
</nodes> </nodes>
</node> </node>
</nodes> </nodes>
</node> </node>
<node id="4" posx="815.52319335937455" posy="948.04447937011719" width="329" height="213" side="top" fold="false" treesize="329" layout="Downwards" group="false"> <node id="5" posx="1391.8445078072455" posy="1155.6062730594231" width="461" height="236" side="bottom" fold="false" treesize="461" layout="Downwards" group="false">
<style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="388" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/> <style branchmargin="100" linktype="curved" linkwidth="5" linkarrow="true" linkdash="solid" nodeborder="bracket" nodewidth="430" nodeborderwidth="3" nodefill="false" nodemargin="11" nodepadding="5" nodefont="Roboto Mono 14" nodemarkup="true"/>
<nodename posx="831.52319335937455" posy="964.04447937011719" maxwidth="394.3671875"> <nodename posx="1407.8445078072455" posy="1171.6062730594231" maxwidth="453.885498046875">
<text data="tgbotapi&#10;&#10;Here included all available TelegramBotAPI libraries:&#13;&#10;&#13;&#10;* tgbotapi.core&#13;&#10;* tgbotapi.extensions.api&#13;&#10;* tgbotapi.extensions.utils"> <text data="tgbotapi&#10;&#10;Here included all available TelegramBotAPI libraries:&#13;&#10;&#13;&#10;* tgbotapi.core&#13;&#10;* tgbotapi.extensions.api&#13;&#10;* tgbotapi.extensions.utils&#10;* tgbotapi.extensions.behaviour_builder">
<color> <color>
<range start="67" end="68" extra="rgb(255,0,0)"/> <range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/> <range start="67" end="68" extra="rgb(255,0,0)"/>
@@ -70,6 +79,94 @@
<range start="67" end="68" extra="rgb(255,0,0)"/> <range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/> <range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/> <range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="67" end="68" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/> <range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/> <range start="84" end="85" extra="rgb(255,0,0)"/>
<range start="84" end="85" extra="rgb(255,0,0)"/> <range start="84" end="85" extra="rgb(255,0,0)"/>
@@ -102,6 +199,90 @@
<range start="111" end="112" extra="rgb(255,0,0)"/> <range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/> <range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/> <range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="111" end="112" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
<range start="139" end="140" extra="rgb(255,0,0)"/>
</color> </color>
</text> </text>
</nodename> </nodename>
@@ -110,12 +291,17 @@
</nodes> </nodes>
<groups/> <groups/>
<connections> <connections>
<connection from_id="2" to_id="4" drag_x="905.70642089843705" drag_y="891.23394775390625" color="#777777"> <connection from_id="2" to_id="5" drag_x="1475.8213347929195" drag_y="1014.8259945356604" color="#777777">
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/> <style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/>
<title></title> <title></title>
<note></note> <note></note>
</connection> </connection>
<connection from_id="3" to_id="4" drag_x="1056.456420898437" drag_y="885.48394775390625" color="#777777"> <connection from_id="4" to_id="5" drag_x="1691.5447998046875" drag_y="1107.00439453125" color="#777777">
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/>
<title></title>
<note></note>
</connection>
<connection from_id="3" to_id="5" drag_x="1483.48876953125" drag_y="896.18115234375">
<style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/> <style connectiondash="dotted" connectionlwidth="2" connectionarrow="fromto" connectionpadding="3" connectionfont="Sans 10" connectiontwidth="100"/>
<title></title> <title></title>
<note></note> <note></note>

View File

@@ -9,7 +9,6 @@ buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version" classpath "com.github.breadmoirai:github-release:$github_release_plugin_version"
} }
} }

View File

@@ -1,3 +1,3 @@
dokka_version=1.4.0 dokka_version=1.4.20
org.gradle.jvmargs=-Xmx1024m org.gradle.jvmargs=-Xmx1024m

View File

@@ -5,19 +5,18 @@ kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
kotlin_version=1.4.10 kotlin_version=1.4.21
kotlin_coroutines_version=1.4.1 kotlin_coroutines_version=1.4.2
kotlin_serialisation_runtime_version=1.0.1 kotlin_serialisation_runtime_version=1.0.1
klock_version=1.12.1 klock_version=2.0.3
uuid_version=0.2.2 uuid_version=0.2.3
ktor_version=1.4.2 ktor_version=1.5.0
micro_utils_version=0.3.0 micro_utils_version=0.4.16
javax_activation_version=1.1.1 javax_activation_version=1.1.1
library_group=dev.inmo library_group=dev.inmo
library_version=0.30.2 library_version=0.30.13
gradle_bintray_plugin_version=1.8.5
github_release_plugin_version=2.2.12 github_release_plugin_version=2.2.12

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -8,5 +8,6 @@ pluginManagement {
include ":tgbotapi.core" include ":tgbotapi.core"
include ":tgbotapi.extensions.api" include ":tgbotapi.extensions.api"
include ":tgbotapi.extensions.utils" include ":tgbotapi.extensions.utils"
include ":tgbotapi.extensions.behaviour_builder"
include ":tgbotapi" include ":tgbotapi"
include ":docs" include ":docs"

View File

@@ -8,7 +8,6 @@ buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
} }
} }

View File

@@ -1,59 +1,65 @@
apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish'
apply from: "maven.publish.gradle" task javadocsJar(type: Jar) {
classifier = 'javadoc'
bintray {
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
filesSpec {
from "${buildDir}/publications/"
eachFile {
String directorySubname = it.getFile().parentFile.name
if (it.getName() == "module.json") {
if (directorySubname == "kotlinMultiplatform") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
} else {
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
}
} else {
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
} else {
it.exclude()
}
}
}
into "${project.group}".replace(".", "/")
}
publish = true
override = true
pkg {
repo = "TelegramBotAPI"
name = "${project.name}"
vcsUrl = "https://github.com/InsanusMokrassar/TelegramBotAPI"
licenses = ["Apache-2.0"]
version {
name = "${project.version}"
released = new Date()
vcsTag = "${project.version}"
gpg {
sign = true
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
}
}
}
} }
bintrayUpload.doFirst { afterEvaluate {
publications = publishing.publications.collect { project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) { if (it.name.contains('kotlinMultiplatform')) {
null artifactId = "${project.name}"
} else { } else {
it.name artifactId = "${project.name}-$name"
} }
} - null }
} }
bintrayUpload.dependsOn publishToMavenLocal publishing {
publications.all {
artifact javadocsJar
pom {
description = "Library for Object-Oriented and type-safe work with Telegram Bot API"
name = "Telegram Bot API Core"
url = "https://insanusmokrassar.github.io/TelegramBotAPI"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/insanusmokrassar/TelegramBotAPI.git[push=]https://github.com/insanusmokrassar/TelegramBotAPI.git"
url = "https://github.com/insanusmokrassar/TelegramBotAPI.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Ovsiannikov Aleksei"
email = "ovsyannikov.alexey95@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"
}
}
}
repositories {
maven {
name = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/TelegramBotAPI/${project.name}/;publish=1;override=1")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
}
}
}
}
}

View File

@@ -14,13 +14,23 @@ typealias FullTextSourcesList = List<TextSource>
typealias FullTextPartsList = List<TextPart> typealias FullTextPartsList = List<TextPart>
interface TextSource { interface TextSource {
val asMarkdownSource: String val markdown: String
val asMarkdownV2Source: String val markdownV2: String
val asHtmlSource: String val html: String
val source: String val source: String
val asText: String val asText: String
get() = source get() = source
@Deprecated("Rename", ReplaceWith("markdown"))
val asMarkdownSource: String
get() = markdown
@Deprecated("Rename", ReplaceWith("markdownV2"))
val asMarkdownV2Source: String
get() = markdownV2
@Deprecated("Rename", ReplaceWith("html"))
val asHtmlSource: String
get() = html
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
@@ -36,7 +46,10 @@ interface MultilevelTextSource : TextSource {
@Deprecated("Will be removed in near major release") @Deprecated("Will be removed in near major release")
val textParts: List<TextPart> val textParts: List<TextPart>
get() = textParts(0) get() = textParts(0)
val subsources: List<TextSource>
@Deprecated("Will be removed in near major release", ReplaceWith("subsources"))
val textSources: List<TextSource> val textSources: List<TextSource>
get() = subsources
} }
data class TextPart( data class TextPart(
@@ -46,7 +59,7 @@ data class TextPart(
fun List<TextPart>.justTextSources() = map { it.source } fun List<TextPart>.justTextSources() = map { it.source }
fun List<TextSource>.makeString() = joinToString("") { it.source } fun List<TextSource>.makeString() = joinToString("") { it.source }
internal fun MultilevelTextSource.textParts(offset: Int): List<TextPart> = textSources.toTextParts(offset) internal fun MultilevelTextSource.textParts(offset: Int): List<TextPart> = subsources.toTextParts(offset)
fun List<TextSource>.separateForMessage(limit: IntRange, numberOfParts: Int? = null): List<List<TextSource>> { fun List<TextSource>.separateForMessage(limit: IntRange, numberOfParts: Int? = null): List<List<TextSource>> {
if (isEmpty()) { if (isEmpty()) {
return emptyList() return emptyList()

View File

@@ -19,13 +19,17 @@ interface TextedOutput : ParsableOutput, EntitiesOutput
interface TextedInput : Texted { interface TextedInput : Texted {
/** /**
* Not full list of entities. This list WILL NOT contain [TextPart]s with [dev.inmo.tgbotapi.types.MessageEntity.textsources.RegularTextSource] * Here must be full list of entities. This list must contains [TextPart]s with
* [dev.inmo.tgbotapi.types.MessageEntity.textsources.RegularTextSource] in case if source text contains parts of
* regular text
* @see [CaptionedInput.fullEntitiesList] * @see [CaptionedInput.fullEntitiesList]
*/ */
val textEntities: List<TextPart> val textEntities: List<TextPart>
} }
/** /**
* Full list of [TextSource] built from source[TextedInput.textEntities]
*
* @see TextedInput.textEntities * @see TextedInput.textEntities
* @see justTextSources * @see justTextSources
*/ */

View File

@@ -3,9 +3,9 @@ package dev.inmo.tgbotapi.bot.Ktor
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.Ktor.base.* import dev.inmo.tgbotapi.bot.Ktor.base.*
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.bot.exceptions.newRequestException import dev.inmo.tgbotapi.bot.exceptions.newRequestException
import dev.inmo.tgbotapi.bot.settings.limiters.EmptyLimiter import dev.inmo.tgbotapi.bot.settings.limiters.*
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.Response import dev.inmo.tgbotapi.types.Response
import dev.inmo.tgbotapi.utils.* import dev.inmo.tgbotapi.utils.*
@@ -14,12 +14,39 @@ import io.ktor.client.features.*
import io.ktor.client.statement.readText import io.ktor.client.statement.readText
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
class KtorRequestsExecutorBuilder(
var telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper
) {
var client: HttpClient = HttpClient()
var callsFactories: List<KtorCallFactory> = emptyList()
var excludeDefaultFactories: Boolean = false
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter()
var jsonFormatter: Json = nonstrictJsonFormat
fun build() = KtorRequestsExecutor(telegramAPIUrlsKeeper, client, callsFactories, excludeDefaultFactories, requestsLimiter, jsonFormatter)
}
inline fun telegramBot(
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
crossinline builder: KtorRequestsExecutorBuilder.() -> Unit = {}
): TelegramBot = KtorRequestsExecutorBuilder(telegramAPIUrlsKeeper).apply(builder).build()
/**
* Shortcut for [telegramBot]
*/
@Suppress("NOTHING_TO_INLINE")
inline fun telegramBot(
token: String,
apiUrl: String = telegramBotAPIDefaultUrl,
crossinline builder: KtorRequestsExecutorBuilder.() -> Unit = {}
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), builder)
class KtorRequestsExecutor( class KtorRequestsExecutor(
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper, telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
client: HttpClient = HttpClient(), client: HttpClient = HttpClient(),
callsFactories: List<KtorCallFactory> = emptyList(), callsFactories: List<KtorCallFactory> = emptyList(),
excludeDefaultFactories: Boolean = false, excludeDefaultFactories: Boolean = false,
private val requestsLimiter: RequestLimiter = EmptyLimiter, private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter(),
private val jsonFormatter: Json = nonstrictJsonFormat private val jsonFormatter: Json = nonstrictJsonFormat
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) { ) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
private val callsFactories: List<KtorCallFactory> = callsFactories.run { private val callsFactories: List<KtorCallFactory> = callsFactories.run {

View File

@@ -1,5 +1,6 @@
package dev.inmo.tgbotapi.bot.Ktor.base package dev.inmo.tgbotapi.bot.Ktor.base
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.tgbotapi.bot.Ktor.KtorCallFactory import dev.inmo.tgbotapi.bot.Ktor.KtorCallFactory
import dev.inmo.tgbotapi.bot.exceptions.newRequestException import dev.inmo.tgbotapi.bot.exceptions.newRequestException
import dev.inmo.tgbotapi.requests.GetUpdates import dev.inmo.tgbotapi.requests.GetUpdates
@@ -51,23 +52,17 @@ abstract class AbstractRequestCallFactory : KtorCallFactory {
val content = response.receive<String>() val content = response.receive<String>()
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content) val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
return (responseObject.result?.let { return safely {
jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it) (responseObject.result?.let {
} ?: responseObject.parameters?.let { jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it)
val error = it.error } ?: response.let {
if (error is RetryAfterError) { throw newRequestException(
delay(error.leftToRetry) responseObject,
makeCall(client, urlsKeeper, request, jsonFormatter) content,
} else { "Can't get result object from $content"
null )
} })
} ?: response.let { }
throw newRequestException(
responseObject,
content,
"Can't get result object from $content"
)
})
} }
} }

View File

@@ -1,6 +1,7 @@
package dev.inmo.tgbotapi.bot.exceptions package dev.inmo.tgbotapi.bot.exceptions
import dev.inmo.tgbotapi.types.Response import com.soywiz.klock.DateTime
import dev.inmo.tgbotapi.types.*
import io.ktor.utils.io.errors.IOException import io.ktor.utils.io.errors.IOException
fun newRequestException( fun newRequestException(
@@ -16,6 +17,13 @@ fun newRequestException(
description == "Unauthorized" -> UnauthorizedException(response, plainAnswer, message, cause) description == "Unauthorized" -> UnauthorizedException(response, plainAnswer, message, cause)
description.contains("PHOTO_INVALID_DIMENSIONS") -> InvalidPhotoDimensionsException(response, plainAnswer, message, cause) description.contains("PHOTO_INVALID_DIMENSIONS") -> InvalidPhotoDimensionsException(response, plainAnswer, message, cause)
description.contains("wrong file identifier") -> WrongFileIdentifierException(response, plainAnswer, message, cause) description.contains("wrong file identifier") -> WrongFileIdentifierException(response, plainAnswer, message, cause)
description.contains("Too Many Requests") -> TooMuchRequestsException(
(response.parameters ?.error as? RetryAfterError) ?: RetryAfterError(60, DateTime.now().unixMillisLong),
response,
plainAnswer,
message,
cause
)
else -> null else -> null
} }
} ?: CommonRequestException(response, plainAnswer, message, cause) } ?: CommonRequestException(response, plainAnswer, message, cause)
@@ -49,3 +57,6 @@ class InvalidPhotoDimensionsException(response: Response, plainAnswer: String, m
class WrongFileIdentifierException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) : class WrongFileIdentifierException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
RequestException(response, plainAnswer, message, cause) RequestException(response, plainAnswer, message, cause)
class TooMuchRequestsException(val retryAfter: RetryAfterError, response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
RequestException(response, plainAnswer, message, cause)

View File

@@ -1,67 +1,43 @@
package dev.inmo.tgbotapi.bot.settings.limiters package dev.inmo.tgbotapi.bot.settings.limiters
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.coroutines.*
import dev.inmo.tgbotapi.types.MilliSeconds
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.sync.Semaphore
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlin.coroutines.Continuation
import kotlin.math.roundToLong
private fun now(): Long = DateTime.nowUnixLong() private fun now(): Long = DateTime.nowUnixLong()
@Serializable
class CommonLimiter( class CommonLimiter(
private val lockCount: Int = 10, private val lockCount: Int = 10,
private val regenTime: Long = 20 * 1000L // 20 seconds for full regen of opportunity to send message private val regenTime: MilliSeconds = 15 * 1000, // 15 seconds for full regen of opportunity to send message
@Transient
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : RequestLimiter { ) : RequestLimiter {
private var doLimit: Boolean = false private val quotaSemaphore = Semaphore(lockCount)
private val counterRegeneratorJob = scope.launch {
private val counterChannel = Channel<Unit>(Channel.UNLIMITED) val regenDelay: MilliSeconds = (regenTime.toDouble() / lockCount).roundToLong()
private val scope = CoroutineScope(Dispatchers.Default) while (isActive) {
private val counterJob = scope.launch { delay(regenDelay)
var wasLastSecond = 0 if (quotaSemaphore.availablePermits < lockCount) {
var lastCountTime = now() try {
var limitManagementJob: Job? = null quotaSemaphore.release()
var removeLimitTime: Long = lastCountTime } catch (_: IllegalStateException) {
for (counter in counterChannel) { // Skip IllegalStateException due to the fact that this exception may happens in release method
val now = now()
if (now - lastCountTime > 1000) {
lastCountTime = now
wasLastSecond = 1
} else {
wasLastSecond++
}
if (wasLastSecond >= lockCount) {
removeLimitTime = now + regenTime
if (limitManagementJob == null) {
limitManagementJob = launch {
doLimit = true
var internalNow = now()
while (internalNow < removeLimitTime) {
delay(removeLimitTime - internalNow)
internalNow = now()
}
doLimit = false
}
} }
} }
if (now > removeLimitTime) {
limitManagementJob = null
}
}
}
private val quoterChannel = Channel<Unit>(Channel.CONFLATED)
private val tickerJob = scope.launch {
while (isActive) {
quoterChannel.send(Unit)
delay(1000L)
} }
} }
override suspend fun <T> limit(block: suspend () -> T): T { override suspend fun <T> limit(block: suspend () -> T): T {
counterChannel.send(Unit) quotaSemaphore.acquire()
return if (!doLimit) { return block()
block()
} else {
quoterChannel.receive()
block()
}
} }
} }

View File

@@ -1,5 +0,0 @@
package dev.inmo.tgbotapi.bot.settings.limiters
object EmptyLimiter : RequestLimiter {
override suspend fun <T> limit(block: suspend () -> T): T = block()
}

View File

@@ -0,0 +1,65 @@
package dev.inmo.tgbotapi.bot.settings.limiters
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.tgbotapi.bot.exceptions.TooMuchRequestsException
import dev.inmo.tgbotapi.types.*
import io.ktor.client.features.ClientRequestException
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
/**
* This limiter will limit requests only after getting a [RetryAfterError] or [ClientRequestException] with
* [HttpStatusCode.TooManyRequests] status code. Important thing is that in case if some of block has been blocked, all
* the others will wait until it will be possible to be called
*
* @param defaultTooManyRequestsDelay This parameter will be used in case of getting [ClientRequestException] with
* [HttpStatusCode.TooManyRequests] as a parameter for delay like it would be [TooMuchRequestsException]. The reason of
* it is that in [ClientRequestException] there is no information about required delay between requests
*/
class ExceptionsOnlyLimiter(
private val defaultTooManyRequestsDelay: MilliSeconds = 1000L
) : RequestLimiter {
private val lockState = MutableStateFlow(false)
private suspend fun lock(timeMillis: MilliSeconds) {
try {
safely {
lockState.emit(true)
delay(timeMillis)
}
} finally {
lockState.emit(false)
}
}
override suspend fun <T> limit(block: suspend () -> T): T {
while (true) {
lockState.first { !it }
val result = safely({
when (it) {
is TooMuchRequestsException -> {
lock(it.retryAfter.leftToRetry)
Result.failure(it)
}
is ClientRequestException -> {
if (it.response.status == HttpStatusCode.TooManyRequests) {
lock(defaultTooManyRequestsDelay)
} else {
throw it
}
Result.failure(it)
}
else -> throw it
}
}) {
Result.success(block())
}
if (result.isSuccess) {
return result.getOrNull()!!
}
}
}
}
@Deprecated("Renamed", ReplaceWith("ExceptionsOnlyLimiter", "dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter"))
typealias EmptyLimiter = ExceptionsOnlyLimiter

View File

@@ -1,5 +1,7 @@
package dev.inmo.tgbotapi.bot.settings.limiters package dev.inmo.tgbotapi.bot.settings.limiters
import dev.inmo.micro_utils.coroutines.*
import dev.inmo.tgbotapi.types.MilliSeconds
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -9,62 +11,60 @@ import kotlin.math.pow
private sealed class RequestEvent private sealed class RequestEvent
private class AddRequest( private class AddRequest(
val continuation: Continuation<Long> val continuation: Continuation<MilliSeconds>
) : RequestEvent() ) : RequestEvent()
private object CompleteRequest : RequestEvent() private object CompleteRequest : RequestEvent()
@Serializable @Serializable
data class PowLimiter( data class PowLimiter(
private val minAwaitTime: Long = 0L, private val minAwaitTime: MilliSeconds = 0L,
private val maxAwaitTime: Long = 10000L, private val maxAwaitTime: MilliSeconds = 10000L,
private val powValue: Double = 4.0, private val powValue: Double = 4.0,
private val powK: Double = 0.0016 private val powK: Double = 1.6,
@Transient
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : RequestLimiter { ) : RequestLimiter {
@Transient
private val scope = CoroutineScope(Dispatchers.Default)
@Transient
private val eventsChannel = Channel<RequestEvent>(Channel.UNLIMITED)
@Transient @Transient
private val awaitTimeRange = minAwaitTime .. maxAwaitTime private val awaitTimeRange = minAwaitTime .. maxAwaitTime
@Transient
private val eventsChannel = let {
var requestsInWork = 0.0
scope.actor<RequestEvent> {
when (it) {
is AddRequest -> {
val awaitTime = (requestsInWork.pow(powValue) * powK).toLong()
requestsInWork++
init { it.continuation.resume(
scope.launch { when {
var requestsInWork: Double = 0.0 awaitTime in awaitTimeRange -> awaitTime
for (event in eventsChannel) { awaitTime < awaitTimeRange.first -> awaitTimeRange.first
when (event) { else -> awaitTimeRange.last
is AddRequest -> { }
val awaitTime = (((requestsInWork.pow(powValue) * powK) * 1000L).toLong()) )
requestsInWork++
event.continuation.resume(
if (awaitTime in awaitTimeRange) {
awaitTime
} else {
if (awaitTime < minAwaitTime) {
minAwaitTime
} else {
maxAwaitTime
}
}
)
}
is CompleteRequest -> requestsInWork--
} }
is CompleteRequest -> requestsInWork--
} }
} }
} }
override suspend fun <T> limit( private suspend inline fun <T> withDelay(
block: suspend () -> T crossinline block: suspend () -> T
): T { ): T {
val delayMillis = suspendCoroutine<Long> { val delayMillis = suspendCoroutine<Long> {
scope.launch { eventsChannel.send(AddRequest(it)) } scope.launch { eventsChannel.send(AddRequest(it)) }
} }
delay(delayMillis) delay(delayMillis)
return try { return try {
block() safely { block() }
} finally { } finally {
eventsChannel.send(CompleteRequest) eventsChannel.send(CompleteRequest)
} }
} }
override suspend fun <T> limit(
block: suspend () -> T
): T {
return withDelay(block)
}
} }

View File

@@ -4,13 +4,13 @@ import dev.inmo.tgbotapi.CommonAbstracts.types.ChatRequest
import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest
import dev.inmo.tgbotapi.types.ChatIdentifier import dev.inmo.tgbotapi.types.ChatIdentifier
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMemberSerializerWithoutDeserialization import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMemberSerializer
import dev.inmo.tgbotapi.types.chatIdField import dev.inmo.tgbotapi.types.chatIdField
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.ListSerializer
private val chatMembersListSerializer = ListSerializer( private val chatMembersListSerializer = ListSerializer(
AdministratorChatMemberSerializerWithoutDeserialization AdministratorChatMemberSerializer
) )
@Serializable @Serializable

View File

@@ -3,7 +3,7 @@ package dev.inmo.tgbotapi.requests.chat.members
import dev.inmo.tgbotapi.requests.chat.abstracts.ChatMemberRequest import dev.inmo.tgbotapi.requests.chat.abstracts.ChatMemberRequest
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatMember import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatMember
import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatMemberDeserializationStrategy import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatMemberSerializer
import kotlinx.serialization.* import kotlinx.serialization.*
@Serializable @Serializable
@@ -15,7 +15,7 @@ data class GetChatMember(
) : ChatMemberRequest<ChatMember> { ) : ChatMemberRequest<ChatMember> {
override fun method(): String = "getChatMember" override fun method(): String = "getChatMember"
override val resultDeserializer: DeserializationStrategy<ChatMember> override val resultDeserializer: DeserializationStrategy<ChatMember>
get() = ChatMemberDeserializationStrategy get() = ChatMemberSerializer
override val requestSerializer: SerializationStrategy<*> override val requestSerializer: SerializationStrategy<*>
get() = serializer() get() = serializer()
} }

View File

@@ -1,19 +1,37 @@
package dev.inmo.tgbotapi.types.ChatMember package dev.inmo.tgbotapi.types.ChatMember
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
import dev.inmo.tgbotapi.types.User import kotlinx.serialization.*
@Serializable
data class AdministratorChatMemberImpl( data class AdministratorChatMemberImpl(
@SerialName(userField)
override val user: User, override val user: User,
override val canBeEdited: Boolean, @SerialName(canBeEditedField)
override val canChangeInfo: Boolean, override val canBeEdited: Boolean = false,
override val canPostMessages: Boolean, @SerialName(canChangeInfoField)
override val canEditMessages: Boolean, override val canChangeInfo: Boolean = false,
override val canRemoveMessages: Boolean, @SerialName(canPostMessagesField)
override val canInviteUsers: Boolean, override val canPostMessages: Boolean = false,
override val canRestrictMembers: Boolean, @SerialName(canEditMessagesField)
override val canPinMessages: Boolean, override val canEditMessages: Boolean = false,
override val canPromoteMembers: Boolean, @SerialName(canDeleteMessagesField)
override val isAnonymous: Boolean, override val canRemoveMessages: Boolean = false,
override val customTitle: String? @SerialName(canInviteUsersField)
) : AdministratorChatMember override val canInviteUsers: Boolean = false,
@SerialName(canRestrictMembersField)
override val canRestrictMembers: Boolean = false,
@SerialName(canPinMessagesField)
override val canPinMessages: Boolean = false,
@SerialName(canPromoteMembersField)
override val canPromoteMembers: Boolean = false,
@SerialName(isAnonymousField)
override val isAnonymous: Boolean = false,
@SerialName(customTitleField)
override val customTitle: String? = null
) : AdministratorChatMember {
@SerialName(statusField)
@Required
private val type: String = "administrator"
}

View File

@@ -1,20 +1,36 @@
package dev.inmo.tgbotapi.types.ChatMember package dev.inmo.tgbotapi.types.ChatMember
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
import dev.inmo.tgbotapi.types.User import kotlinx.serialization.*
@Serializable
data class CreatorChatMember( data class CreatorChatMember(
override val user: User, override val user: User,
override val isAnonymous: Boolean, @SerialName(isAnonymousField)
override val customTitle: String? override val isAnonymous: Boolean = false,
@SerialName(customTitleField)
override val customTitle: String? = null
) : AdministratorChatMember { ) : AdministratorChatMember {
@Transient
override val canBeEdited: Boolean = true override val canBeEdited: Boolean = true
@Transient
override val canChangeInfo: Boolean = true override val canChangeInfo: Boolean = true
@Transient
override val canPostMessages: Boolean = true override val canPostMessages: Boolean = true
@Transient
override val canEditMessages: Boolean = true override val canEditMessages: Boolean = true
@Transient
override val canRemoveMessages: Boolean = true override val canRemoveMessages: Boolean = true
@Transient
override val canInviteUsers: Boolean = true override val canInviteUsers: Boolean = true
@Transient
override val canRestrictMembers: Boolean = true override val canRestrictMembers: Boolean = true
@Transient
override val canPinMessages: Boolean = true override val canPinMessages: Boolean = true
@Transient
override val canPromoteMembers: Boolean = true override val canPromoteMembers: Boolean = true
@SerialName(statusField)
@Required
private val type: String = "creator"
} }

View File

@@ -1,10 +1,17 @@
package dev.inmo.tgbotapi.types.ChatMember package dev.inmo.tgbotapi.types.ChatMember
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.BannedChatMember import dev.inmo.tgbotapi.types.ChatMember.abstracts.BannedChatMember
import dev.inmo.tgbotapi.types.TelegramDate import kotlinx.serialization.*
import dev.inmo.tgbotapi.types.User
@Serializable
data class KickedChatMember( data class KickedChatMember(
@SerialName(userField)
override val user: User, override val user: User,
override val untilDate: TelegramDate? @SerialName(untilDateField)
) : BannedChatMember override val untilDate: TelegramDate? = null
) : BannedChatMember {
@SerialName(statusField)
@Required
private val type: String = "kicked"
}

View File

@@ -1,7 +1,12 @@
package dev.inmo.tgbotapi.types.ChatMember package dev.inmo.tgbotapi.types.ChatMember
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatMember import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatMember
import dev.inmo.tgbotapi.types.User import kotlinx.serialization.*
data class LeftChatMember(override val user: User) : @Serializable
ChatMember data class LeftChatMember(@SerialName(userField) override val user: User) : ChatMember {
@SerialName(statusField)
@Required
private val type: String = "left"
}

View File

@@ -1,7 +1,12 @@
package dev.inmo.tgbotapi.types.ChatMember package dev.inmo.tgbotapi.types.ChatMember
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatMember import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatMember
import dev.inmo.tgbotapi.types.User import kotlinx.serialization.*
data class MemberChatMember(override val user: User) : @Serializable
ChatMember data class MemberChatMember(@SerialName(userField) override val user: User) : ChatMember {
@SerialName(statusField)
@Required
private val type: String = "member"
}

View File

@@ -1,87 +0,0 @@
package dev.inmo.tgbotapi.types.ChatMember
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.ChatMember
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
internal data class RawChatMember(
val user: User,
private val status: String,
private val until_date: TelegramDate? = null,
@SerialName(canBeEditedField)
private val canBeEdited: Boolean = false,
@SerialName(canChangeInfoField)
private val canChangeInfo: Boolean = false,
@SerialName(canPostMessagesField)
private val canPostMessages: Boolean = false,
@SerialName(canEditMessagesField)
private val canEditMessages: Boolean = false,
@SerialName(canDeleteMessagesField)
private val canDeleteMessages: Boolean = false,
@SerialName(canInviteUsersField)
private val canInviteUsers: Boolean = false,
@SerialName(canRestrictMembersField)
private val canRestrictMembers: Boolean = false,
@SerialName(canPinMessagesField)
private val canPinMessages: Boolean = false,
@SerialName(canPromoteMembersField)
private val canPromoteMembers: Boolean = false,
@SerialName(isMemberField)
private val isMember: Boolean = false,
@SerialName(canSendMessagesField)
private val canSendMessages: Boolean = false,
@SerialName(canSendMediaMessagesField)
private val canSendMediaMessages: Boolean = false,
@SerialName(canSendPollsField)
private val canSendPolls: Boolean = false,
@SerialName(canSendOtherMessagesField)
private val canSendOtherMessages: Boolean = false,
@SerialName(canAddWebPagePreviewsField)
private val canAddWebPagePreviews: Boolean = false,
@SerialName(isAnonymousField)
private val isAnonymous: Boolean = false,
@SerialName(customTitleField)
private val customTitle: String? = null
) {
val asChatMember: ChatMember by lazy {
when (status) {
"creator" -> CreatorChatMember(user, isAnonymous, customTitle)
"administrator" -> AdministratorChatMemberImpl(
user,
canBeEdited,
canChangeInfo,
canPostMessages,
canEditMessages,
canDeleteMessages,
canInviteUsers,
canRestrictMembers,
canPinMessages,
canPromoteMembers,
isAnonymous,
customTitle
)
"member" -> MemberChatMember(user)
"restricted" -> RestrictedChatMember(
user,
until_date,
isMember,
canSendMessages,
canSendMediaMessages,
canSendPolls,
canSendOtherMessages,
canAddWebPagePreviews,
canChangeInfo,
canInviteUsers,
canPinMessages
)
"left" -> LeftChatMember(user)
"kicked" -> KickedChatMember(
user,
until_date
)
else -> throw IllegalStateException("Can't understand type of user: $status")
}
}
}

View File

@@ -1,20 +1,36 @@
package dev.inmo.tgbotapi.types.ChatMember package dev.inmo.tgbotapi.types.ChatMember
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.BannedChatMember import dev.inmo.tgbotapi.types.ChatMember.abstracts.BannedChatMember
import dev.inmo.tgbotapi.types.ChatMember.abstracts.SpecialRightsChatMember import dev.inmo.tgbotapi.types.ChatMember.abstracts.SpecialRightsChatMember
import dev.inmo.tgbotapi.types.TelegramDate import kotlinx.serialization.*
import dev.inmo.tgbotapi.types.User
@Serializable
data class RestrictedChatMember( data class RestrictedChatMember(
@SerialName(userField)
override val user: User, override val user: User,
override val untilDate: TelegramDate?, @SerialName(untilDateField)
val isMember: Boolean, override val untilDate: TelegramDate? = null,
val canSendMessages: Boolean, @SerialName(isMemberField)
val canSendMediaMessages: Boolean, val isMember: Boolean = false,
val canSendPolls: Boolean, @SerialName(canSendMessagesField)
val canSendOtherMessages: Boolean, val canSendMessages: Boolean = false,
val canAddWebpagePreviews: Boolean, @SerialName(canSendMediaMessagesField)
override val canChangeInfo: Boolean, val canSendMediaMessages: Boolean = false,
override val canInviteUsers: Boolean, @SerialName(canSendPollsField)
override val canPinMessages: Boolean val canSendPolls: Boolean = false,
) : BannedChatMember, SpecialRightsChatMember @SerialName(canSendOtherMessagesField)
val canSendOtherMessages: Boolean = false,
@SerialName(canAddWebPagePreviewsField)
val canAddWebpagePreviews: Boolean = false,
@SerialName(canChangeInfoField)
override val canChangeInfo: Boolean = false,
@SerialName(canInviteUsersField)
override val canInviteUsers: Boolean = false,
@SerialName(canPinMessagesField)
override val canPinMessages: Boolean = false
) : BannedChatMember, SpecialRightsChatMember {
@SerialName(statusField)
@Required
private val type: String = "restricted"
}

View File

@@ -1,5 +1,11 @@
package dev.inmo.tgbotapi.types.ChatMember.abstracts package dev.inmo.tgbotapi.types.ChatMember.abstracts
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
@Serializable(AdministratorChatMemberSerializer::class)
interface AdministratorChatMember : SpecialRightsChatMember { interface AdministratorChatMember : SpecialRightsChatMember {
val canBeEdited: Boolean val canBeEdited: Boolean
val canPostMessages: Boolean val canPostMessages: Boolean
@@ -9,4 +15,12 @@ interface AdministratorChatMember : SpecialRightsChatMember {
val canPromoteMembers: Boolean val canPromoteMembers: Boolean
val isAnonymous: Boolean val isAnonymous: Boolean
val customTitle: String? val customTitle: String?
} }
@Serializer(AdministratorChatMember::class)
internal object AdministratorChatMemberSerializer : KSerializer<AdministratorChatMember> {
override val descriptor: SerialDescriptor = ChatMemberSerializer.descriptor
override fun deserialize(decoder: Decoder): AdministratorChatMember = ChatMemberSerializer.deserialize(decoder) as AdministratorChatMember
override fun serialize(encoder: Encoder, value: AdministratorChatMember) = ChatMemberSerializer.serialize(encoder, value)
}

View File

@@ -1,5 +1,7 @@
package dev.inmo.tgbotapi.types.ChatMember.abstracts package dev.inmo.tgbotapi.types.ChatMember.abstracts
import dev.inmo.tgbotapi.CommonAbstracts.types.UntilDate import dev.inmo.tgbotapi.CommonAbstracts.types.UntilDate
import kotlinx.serialization.Serializable
@Serializable(ChatMemberSerializer::class)
interface BannedChatMember : ChatMember, UntilDate interface BannedChatMember : ChatMember, UntilDate

View File

@@ -1,27 +1,46 @@
package dev.inmo.tgbotapi.types.ChatMember.abstracts package dev.inmo.tgbotapi.types.ChatMember.abstracts
import dev.inmo.tgbotapi.types.ChatMember.RawChatMember import dev.inmo.tgbotapi.types.ChatMember.*
import dev.inmo.tgbotapi.types.User import dev.inmo.tgbotapi.types.User
import kotlinx.serialization.DeserializationStrategy import dev.inmo.tgbotapi.types.statusField
import kotlinx.serialization.KSerializer import dev.inmo.tgbotapi.utils.nonstrictJsonFormat
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.JsonObject
import kotlinx.serialization.json.jsonPrimitive
@Serializable(ChatMemberSerializer::class)
interface ChatMember { interface ChatMember {
val user: User val user: User
} }
internal object AdministratorChatMemberSerializerWithoutDeserialization : KSerializer<AdministratorChatMember> { @Serializer(ChatMember::class)
override val descriptor: SerialDescriptor = ChatMemberDeserializationStrategy.descriptor internal object ChatMemberSerializer : KSerializer<ChatMember> {
override val descriptor: SerialDescriptor = JsonObject.serializer().descriptor
override fun deserialize(decoder: Decoder): AdministratorChatMember override fun deserialize(decoder: Decoder): ChatMember {
= ChatMemberDeserializationStrategy.deserialize(decoder) as AdministratorChatMember val json = JsonObject.serializer().deserialize(decoder)
override fun serialize(encoder: Encoder, value: AdministratorChatMember) = throw UnsupportedOperationException() return when (json[statusField] ?.jsonPrimitive ?.content ?: error("Status field of chat member must be specified, but incoming json contains next: $json")) {
} "creator" -> nonstrictJsonFormat.decodeFromJsonElement(CreatorChatMember.serializer(), json)
"administrator" -> nonstrictJsonFormat.decodeFromJsonElement(AdministratorChatMemberImpl.serializer(), json)
internal object ChatMemberDeserializationStrategy : DeserializationStrategy<ChatMember> { "member" -> nonstrictJsonFormat.decodeFromJsonElement(MemberChatMember.serializer(), json)
override val descriptor: SerialDescriptor = RawChatMember.serializer().descriptor "restricted" -> nonstrictJsonFormat.decodeFromJsonElement(RestrictedChatMember.serializer(), json)
"left" -> nonstrictJsonFormat.decodeFromJsonElement(LeftChatMember.serializer(), json)
override fun deserialize(decoder: Decoder): ChatMember = RawChatMember.serializer().deserialize(decoder).asChatMember "kicked" -> nonstrictJsonFormat.decodeFromJsonElement(KickedChatMember.serializer(), json)
else -> error("Unknown type of chat member in json: $json")
}
}
override fun serialize(encoder: Encoder, value: ChatMember) {
when (value) {
is CreatorChatMember -> CreatorChatMember.serializer()
is AdministratorChatMemberImpl -> AdministratorChatMemberImpl.serializer()
is MemberChatMember -> MemberChatMember.serializer()
is RestrictedChatMember -> RestrictedChatMember.serializer()
is LeftChatMember -> LeftChatMember.serializer()
is KickedChatMember -> KickedChatMember.serializer()
}
}
} }

View File

@@ -1,5 +1,8 @@
package dev.inmo.tgbotapi.types.ChatMember.abstracts package dev.inmo.tgbotapi.types.ChatMember.abstracts
import kotlinx.serialization.Serializable
@Serializable(ChatMemberSerializer::class)
interface SpecialRightsChatMember : ChatMember { interface SpecialRightsChatMember : ChatMember {
val canChangeInfo: Boolean val canChangeInfo: Boolean
val canInviteUsers: Boolean val canInviteUsers: Boolean

View File

@@ -28,6 +28,7 @@ typealias GooglePlaceId = String
typealias GooglePlaceType = String typealias GooglePlaceType = String
typealias Seconds = Int typealias Seconds = Int
typealias MilliSeconds = Long
typealias LongSeconds = Long typealias LongSeconds = Long
typealias Meters = Float typealias Meters = Float
@@ -60,10 +61,13 @@ val inlineQueryAnswerResultsLimit = 0 .. 50
val customTitleLength = 0 .. 16 val customTitleLength = 0 .. 16
val dartsAndCubeDiceResultLimit = 1 .. 6 val dartsCubeAndBowlingDiceResultLimit = 1 .. 6
@Deprecated("Renamed", ReplaceWith("dartsAndCubeDiceResultLimit", "dev.inmo.tgbotapi.types.dartsAndCubeDiceResultLimit")) @Deprecated("Renamed", ReplaceWith("dartsCubeAndBowlingDiceResultLimit", "dev.inmo.tgbotapi.types.dartsCubeAndBowlingDiceResultLimit"))
val dartsAndCubeDiceResultLimit
get() = dartsCubeAndBowlingDiceResultLimit
@Deprecated("Renamed", ReplaceWith("dartsCubeAndBowlingDiceResultLimit", "dev.inmo.tgbotapi.types.dartsCubeAndBowlingDiceResultLimit"))
val diceResultLimit val diceResultLimit
get() = dartsAndCubeDiceResultLimit get() = dartsCubeAndBowlingDiceResultLimit
val basketballAndFootballDiceResultLimit = 1 .. 5 val basketballAndFootballDiceResultLimit = 1 .. 5
val slotMachineDiceResultLimit = 1 .. 64 val slotMachineDiceResultLimit = 1 .. 64
@@ -231,6 +235,7 @@ const val canSendPollsField = "can_send_polls"
const val canAddWebPagePreviewsField = "can_add_web_page_previews" const val canAddWebPagePreviewsField = "can_add_web_page_previews"
const val canSetStickerSetField = "can_set_sticker_set" const val canSetStickerSetField = "can_set_sticker_set"
const val statusField = "status"
const val canBeEditedField = "can_be_edited" const val canBeEditedField = "can_be_edited"
const val canChangeInfoField = "can_change_info" const val canChangeInfoField = "can_change_info"
const val canPostMessagesField = "can_post_messages" const val canPostMessagesField = "can_post_messages"

View File

@@ -11,11 +11,11 @@ import dev.inmo.tgbotapi.utils.internal.boldMarkdownV2
*/ */
data class BoldTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class BoldTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asMarkdownSource: String by lazy { source.boldMarkdown() } override val markdown: String by lazy { source.boldMarkdown() }
override val asMarkdownV2Source: String by lazy { boldMarkdownV2() } override val markdownV2: String by lazy { boldMarkdownV2() }
override val asHtmlSource: String by lazy { boldHTML() } override val html: String by lazy { boldHTML() }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -18,9 +18,9 @@ data class BotCommandTextSource @RiskFeature(DirectInvocationOfTextSourceConstru
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 "!"
} }
override val asMarkdownSource: String by lazy { source.commandMarkdown() } override val markdown: String by lazy { source.commandMarkdown() }
override val asMarkdownV2Source: String by lazy { source.commandMarkdownV2() } override val markdownV2: String by lazy { source.commandMarkdownV2() }
override val asHtmlSource: String by lazy { source.commandHTML() } override val html: String by lazy { source.commandHTML() }
} }
/** /**

View File

@@ -11,11 +11,11 @@ import dev.inmo.tgbotapi.utils.internal.cashTagMarkdownV2
*/ */
data class CashTagTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class CashTagTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asMarkdownSource: String by lazy { source.cashTagMarkdown() } override val markdown: String by lazy { source.cashTagMarkdown() }
override val asMarkdownV2Source: String by lazy { cashTagMarkdownV2() } override val markdownV2: String by lazy { cashTagMarkdownV2() }
override val asHtmlSource: String by lazy { cashTagHTML() } override val html: String by lazy { cashTagHTML() }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -13,9 +13,9 @@ import dev.inmo.tgbotapi.utils.internal.codeMarkdownV2
data class CodeTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class CodeTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String override val source: String
) : TextSource { ) : TextSource {
override val asMarkdownSource: String by lazy { source.codeMarkdown() } override val markdown: String by lazy { source.codeMarkdown() }
override val asMarkdownV2Source: String by lazy { source.codeMarkdownV2() } override val markdownV2: String by lazy { source.codeMarkdownV2() }
override val asHtmlSource: String by lazy { source.codeHTML() } override val html: String by lazy { source.codeHTML() }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -11,11 +11,11 @@ import dev.inmo.tgbotapi.utils.internal.emailMarkdownV2
*/ */
data class EMailTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class EMailTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asMarkdownSource: String by lazy { source.emailMarkdown() } override val markdown: String by lazy { source.emailMarkdown() }
override val asMarkdownV2Source: String by lazy { emailMarkdownV2(source) } override val markdownV2: String by lazy { emailMarkdownV2(source) }
override val asHtmlSource: String by lazy { emailHTML(source) } override val html: String by lazy { emailHTML(source) }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -11,11 +11,11 @@ import dev.inmo.tgbotapi.utils.internal.hashTagMarkdownV2
*/ */
data class HashTagTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class HashTagTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asMarkdownSource: String by lazy { source.hashTagMarkdown() } override val markdown: String by lazy { source.hashTagMarkdown() }
override val asMarkdownV2Source: String by lazy { hashTagMarkdownV2() } override val markdownV2: String by lazy { hashTagMarkdownV2() }
override val asHtmlSource: String by lazy { hashTagHTML() } override val html: String by lazy { hashTagHTML() }
init { init {
if (!source.startsWith("#")) { if (!source.startsWith("#")) {

View File

@@ -11,11 +11,11 @@ import dev.inmo.tgbotapi.utils.internal.italicMarkdownV2
*/ */
data class ItalicTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class ItalicTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asMarkdownSource: String by lazy { source.italicMarkdown() } override val markdown: String by lazy { source.italicMarkdown() }
override val asMarkdownV2Source: String by lazy { italicMarkdownV2() } override val markdownV2: String by lazy { italicMarkdownV2() }
override val asHtmlSource: String by lazy { italicHTML() } override val html: String by lazy { italicHTML() }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -18,11 +18,11 @@ private val String.withoutCommercialAt
*/ */
data class MentionTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class MentionTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asMarkdownSource: String by lazy { source.mentionMarkdown() } override val markdown: String by lazy { source.mentionMarkdown() }
override val asMarkdownV2Source: String by lazy { mentionMarkdownV2() } override val markdownV2: String by lazy { mentionMarkdownV2() }
override val asHtmlSource: String by lazy { mentionHTML() } override val html: String by lazy { mentionHTML() }
init { init {
if (!source.startsWith("@")) { if (!source.startsWith("@")) {

View File

@@ -11,11 +11,11 @@ import dev.inmo.tgbotapi.utils.internal.phoneMarkdownV2
*/ */
data class PhoneNumberTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class PhoneNumberTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asMarkdownSource: String by lazy { source.phoneMarkdown() } override val markdown: String by lazy { source.phoneMarkdown() }
override val asMarkdownV2Source: String by lazy { phoneMarkdownV2() } override val markdownV2: String by lazy { phoneMarkdownV2() }
override val asHtmlSource: String by lazy { phoneHTML() } override val html: String by lazy { phoneHTML() }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -13,9 +13,9 @@ data class PreTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) c
override val source: String, override val source: String,
val language: String? = null val language: String? = null
) : TextSource { ) : TextSource {
override val asMarkdownSource: String by lazy { source.preMarkdown(language) } override val markdown: String by lazy { source.preMarkdown(language) }
override val asMarkdownV2Source: String by lazy { source.preMarkdownV2(language) } override val markdownV2: String by lazy { source.preMarkdownV2(language) }
override val asHtmlSource: String by lazy { source.preHTML(language) } override val html: String by lazy { source.preHTML(language) }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -12,9 +12,9 @@ import dev.inmo.tgbotapi.utils.internal.regularMarkdownV2
data class RegularTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class RegularTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String override val source: String
) : TextSource { ) : TextSource {
override val asMarkdownSource: String by lazy { source.regularMarkdown() } override val markdown: String by lazy { source.regularMarkdown() }
override val asMarkdownV2Source: String by lazy { source.regularMarkdownV2() } override val markdownV2: String by lazy { source.regularMarkdownV2() }
override val asHtmlSource: String by lazy { source.regularHtml() } override val html: String by lazy { source.regularHtml() }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -11,11 +11,11 @@ import dev.inmo.tgbotapi.utils.internal.strikethroughMarkdownV2
*/ */
data class StrikethroughTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class StrikethroughTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asHtmlSource: String by lazy { strikethroughHTML() } override val html: String by lazy { strikethroughHTML() }
override val asMarkdownV2Source: String by lazy { strikethroughMarkdownV2() } override val markdownV2: String by lazy { strikethroughMarkdownV2() }
override val asMarkdownSource: String by lazy { source.strikethroughMarkdown() } override val markdown: String by lazy { source.strikethroughMarkdown() }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -13,9 +13,9 @@ data class TextLinkTextSource @RiskFeature(DirectInvocationOfTextSourceConstruct
override val source: String, override val source: String,
val url: String val url: String
) : TextSource { ) : TextSource {
override val asMarkdownSource: String by lazy { source.linkMarkdown(url) } override val markdown: String by lazy { source.linkMarkdown(url) }
override val asMarkdownV2Source: String by lazy { source.linkMarkdownV2(url) } override val markdownV2: String by lazy { source.linkMarkdownV2(url) }
override val asHtmlSource: String by lazy { source.linkHTML(url) } override val html: String by lazy { source.linkHTML(url) }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -13,11 +13,11 @@ import dev.inmo.tgbotapi.utils.internal.textMentionMarkdownV2
data class TextMentionTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class TextMentionTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
val user: User, val user: User,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asMarkdownSource: String by lazy { source.textMentionMarkdown(user.id) } override val markdown: String by lazy { source.textMentionMarkdown(user.id) }
override val asMarkdownV2Source: String by lazy { textMentionMarkdownV2(user.id) } override val markdownV2: String by lazy { textMentionMarkdownV2(user.id) }
override val asHtmlSource: String by lazy { textMentionHTML(user.id) } override val html: String by lazy { textMentionHTML(user.id) }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -12,9 +12,9 @@ import dev.inmo.tgbotapi.utils.internal.linkMarkdownV2
data class URLTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class URLTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String override val source: String
) : TextSource { ) : TextSource {
override val asMarkdownSource: String by lazy { source.linkMarkdown(source) } override val markdown: String by lazy { source.linkMarkdown(source) }
override val asMarkdownV2Source: String by lazy { source.linkMarkdownV2(source) } override val markdownV2: String by lazy { source.linkMarkdownV2(source) }
override val asHtmlSource: String by lazy { source.linkHTML(source) } override val html: String by lazy { source.linkHTML(source) }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -11,11 +11,11 @@ import dev.inmo.tgbotapi.utils.internal.underlineMarkdownV2
*/ */
data class UnderlineTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( data class UnderlineTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String, override val source: String,
override val textSources: List<TextSource> override val subsources: List<TextSource>
) : MultilevelTextSource { ) : MultilevelTextSource {
override val asMarkdownSource: String by lazy { source.underlineMarkdown() } override val markdown: String by lazy { source.underlineMarkdown() }
override val asMarkdownV2Source: String by lazy { underlineMarkdownV2() } override val markdownV2: String by lazy { underlineMarkdownV2() }
override val asHtmlSource: String by lazy { underlineHTML() } override val html: String by lazy { underlineHTML() }
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@@ -5,7 +5,7 @@ import com.soywiz.klock.DateTime
sealed class RequestError sealed class RequestError
data class RetryAfterError( data class RetryAfterError(
val seconds: Long, val seconds: Seconds,
val startCountingMillis: Long val startCountingMillis: Long
) : RequestError() { ) : RequestError() {
val canContinue = (seconds * 1000L) + startCountingMillis val canContinue = (seconds * 1000L) + startCountingMillis

View File

@@ -8,7 +8,7 @@ data class ResponseParametersRaw(
@SerialName("migrate_to_chat_id") @SerialName("migrate_to_chat_id")
private val migrateToChatId: ChatId? = null, private val migrateToChatId: ChatId? = null,
@SerialName("retry_after") @SerialName("retry_after")
private val retryAfter: Long? = null private val retryAfter: Seconds? = null
) { ) {
@Transient @Transient
private val createTime: Long = DateTime.now().unixMillisLong private val createTime: Long = DateTime.now().unixMillisLong

View File

@@ -14,7 +14,7 @@ sealed class User : PrivateChat
@Serializable @Serializable
data class CommonUser( data class CommonUser(
override val id: ChatId, override val id: UserId,
@SerialName(firstNameField) @SerialName(firstNameField)
override val firstName: String, override val firstName: String,
@SerialName(lastNameField) @SerialName(lastNameField)
@@ -35,7 +35,7 @@ sealed class Bot : User() {
@Serializable @Serializable
data class CommonBot( data class CommonBot(
override val id: ChatId, override val id: UserId,
@SerialName(usernameField) @SerialName(usernameField)
override val username: Username, override val username: Username,
@SerialName(firstNameField) @SerialName(firstNameField)
@@ -49,7 +49,7 @@ data class CommonBot(
@Serializable @Serializable
data class ExtendedBot( data class ExtendedBot(
override val id: ChatId, override val id: UserId,
@SerialName(usernameField) @SerialName(usernameField)
override val username: Username, override val username: Username,
@SerialName(firstNameField) @SerialName(firstNameField)

View File

@@ -42,6 +42,9 @@ internal object BotActionSerializer: KSerializer<BotAction> {
object TypingAction : BotAction() { object TypingAction : BotAction() {
override val actionName: String = "typing" override val actionName: String = "typing"
} }
inline val typing
get() = TypingAction
inline fun BotAction.asTyping() = this as? TypingAction
/** /**
* Will notify user that bot is uploading some photo * Will notify user that bot is uploading some photo
@@ -50,6 +53,9 @@ object TypingAction : BotAction() {
object UploadPhotoAction : BotAction() { object UploadPhotoAction : BotAction() {
override val actionName: String = "upload_photo" override val actionName: String = "upload_photo"
} }
inline val uploadPhoto
get() = UploadPhotoAction
inline fun BotAction.asUploadPhoto() = this as? UploadPhotoAction
/** /**
* Will notify user that bot is recording some video * Will notify user that bot is recording some video
@@ -58,6 +64,9 @@ object UploadPhotoAction : BotAction() {
object RecordVideoAction : BotAction() { object RecordVideoAction : BotAction() {
override val actionName: String = "record_video" override val actionName: String = "record_video"
} }
inline val recordVideo
get() = RecordVideoAction
inline fun BotAction.asRecordVideo() = this as? RecordVideoAction
/** /**
* Will notify user that bot is uploading some photo * Will notify user that bot is uploading some photo
@@ -66,6 +75,9 @@ object RecordVideoAction : BotAction() {
object UploadVideoAction : BotAction() { object UploadVideoAction : BotAction() {
override val actionName: String = "upload_video" override val actionName: String = "upload_video"
} }
inline val uploadVideo
get() = UploadVideoAction
inline fun BotAction.asUploadVideo() = this as? UploadVideoAction
/** /**
* Will notify user that bot is recording some audio * Will notify user that bot is recording some audio
@@ -74,6 +86,9 @@ object UploadVideoAction : BotAction() {
object RecordAudioAction : BotAction() { object RecordAudioAction : BotAction() {
override val actionName: String = "record_audio" override val actionName: String = "record_audio"
} }
inline val recordAudio
get() = RecordAudioAction
inline fun BotAction.asRecordAudio() = this as? RecordAudioAction
/** /**
* Will notify user that bot is uploading some audio * Will notify user that bot is uploading some audio
@@ -82,6 +97,9 @@ object RecordAudioAction : BotAction() {
object UploadAudioAction : BotAction() { object UploadAudioAction : BotAction() {
override val actionName: String = "upload_audio" override val actionName: String = "upload_audio"
} }
inline val uploadAudio
get() = UploadAudioAction
inline fun BotAction.asUploadAudio() = this as? UploadAudioAction
/** /**
* Will notify user that bot is uploading some document * Will notify user that bot is uploading some document
@@ -90,6 +108,9 @@ object UploadAudioAction : BotAction() {
object UploadDocumentAction : BotAction() { object UploadDocumentAction : BotAction() {
override val actionName: String = "upload_document" override val actionName: String = "upload_document"
} }
inline val uploadDocument
get() = UploadDocumentAction
inline fun BotAction.asUploadDocument() = this as? UploadDocumentAction
/** /**
* Will notify user that bot is trying to find location * Will notify user that bot is trying to find location
@@ -98,6 +119,9 @@ object UploadDocumentAction : BotAction() {
object FindLocationAction : BotAction() { object FindLocationAction : BotAction() {
override val actionName: String = "find_location" override val actionName: String = "find_location"
} }
inline val findLocation
get() = FindLocationAction
inline fun BotAction.asFindLocation() = this as? FindLocationAction
/** /**
* Will notify user that bot is recording video note * Will notify user that bot is recording video note
@@ -106,6 +130,9 @@ object FindLocationAction : BotAction() {
object RecordVideoNoteAction : BotAction() { object RecordVideoNoteAction : BotAction() {
override val actionName: String = "record_video_note" override val actionName: String = "record_video_note"
} }
inline val recordVideoNote
get() = RecordVideoNoteAction
inline fun BotAction.asRecordVideoNote() = this as? RecordVideoNoteAction
/** /**
* Will notify user that bot is uploading video note * Will notify user that bot is uploading video note
@@ -114,3 +141,6 @@ object RecordVideoNoteAction : BotAction() {
object UploadVideoNoteAction : BotAction() { object UploadVideoNoteAction : BotAction() {
override val actionName: String = "upload_video_note" override val actionName: String = "upload_video_note"
} }
inline val uploadVideoNote
get() = UploadVideoNoteAction
inline fun BotAction.asUploadVideoNote() = this as? UploadVideoNoteAction

View File

@@ -1,10 +1,13 @@
package dev.inmo.tgbotapi.types.chat.abstracts package dev.inmo.tgbotapi.types.chat.abstracts
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.chat.PreviewChatSerializer import dev.inmo.tgbotapi.types.chat.PreviewChatSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable(PreviewChatSerializer::class) @Serializable(PreviewChatSerializer::class)
interface PrivateChat : Chat, UsernameChat { interface PrivateChat : Chat, UsernameChat {
override val id: UserId
val firstName: String val firstName: String
val lastName: String val lastName: String
} }

View File

@@ -15,13 +15,13 @@ sealed class DiceAnimationType {
object CubeDiceAnimationType : DiceAnimationType() { object CubeDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFB2" override val emoji: String = "\uD83C\uDFB2"
override val valueLimits: IntRange override val valueLimits: IntRange
get() = dartsAndCubeDiceResultLimit get() = dartsCubeAndBowlingDiceResultLimit
} }
@Serializable(DiceAnimationTypeSerializer::class) @Serializable(DiceAnimationTypeSerializer::class)
object DartsDiceAnimationType : DiceAnimationType() { object DartsDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFAF" override val emoji: String = "\uD83C\uDFAF"
override val valueLimits: IntRange override val valueLimits: IntRange
get() = dartsAndCubeDiceResultLimit get() = dartsCubeAndBowlingDiceResultLimit
} }
@Serializable(DiceAnimationTypeSerializer::class) @Serializable(DiceAnimationTypeSerializer::class)
object BasketballDiceAnimationType : DiceAnimationType() { object BasketballDiceAnimationType : DiceAnimationType() {
@@ -36,6 +36,12 @@ object FootballDiceAnimationType : DiceAnimationType() {
get() = basketballAndFootballDiceResultLimit get() = basketballAndFootballDiceResultLimit
} }
@Serializable(DiceAnimationTypeSerializer::class) @Serializable(DiceAnimationTypeSerializer::class)
object BowlingDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFB3"
override val valueLimits: IntRange
get() = dartsCubeAndBowlingDiceResultLimit
}
@Serializable(DiceAnimationTypeSerializer::class)
object SlotMachineDiceAnimationType : DiceAnimationType() { object SlotMachineDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFB0" override val emoji: String = "\uD83C\uDFB0"
override val valueLimits: IntRange override val valueLimits: IntRange
@@ -59,6 +65,7 @@ internal object DiceAnimationTypeSerializer : KSerializer<DiceAnimationType> {
BasketballDiceAnimationType.emoji -> BasketballDiceAnimationType BasketballDiceAnimationType.emoji -> BasketballDiceAnimationType
SlotMachineDiceAnimationType.emoji -> SlotMachineDiceAnimationType SlotMachineDiceAnimationType.emoji -> SlotMachineDiceAnimationType
FootballDiceAnimationType.emoji -> FootballDiceAnimationType FootballDiceAnimationType.emoji -> FootballDiceAnimationType
BowlingDiceAnimationType.emoji -> BowlingDiceAnimationType
else -> CustomDiceAnimationType(type) else -> CustomDiceAnimationType(type)
} }
} }

View File

@@ -4,11 +4,12 @@ import com.soywiz.klock.DateTime
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.chat.abstracts.ChannelChat import dev.inmo.tgbotapi.types.chat.abstracts.ChannelChat
import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.ChannelEvent import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.ChannelEvent
import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.GroupEvent
import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage
data class ChannelEventMessage( data class ChannelEventMessage<T : ChannelEvent>(
override val messageId: MessageIdentifier, override val messageId: MessageIdentifier,
override val chat: ChannelChat, override val chat: ChannelChat,
override val chatEvent: ChannelEvent, override val chatEvent: T,
override val date: DateTime override val date: DateTime
) : ChatEventMessage ) : ChatEventMessage<T>

View File

@@ -1,3 +1,3 @@
package dev.inmo.tgbotapi.types.message.ChatEvents.abstracts package dev.inmo.tgbotapi.types.message.ChatEvents.abstracts
interface SupergroupEvent: ChatEvent interface SupergroupEvent: GroupEvent

View File

@@ -8,12 +8,12 @@ import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.GroupEvent
import dev.inmo.tgbotapi.types.message.abstracts.GroupEventMessage import dev.inmo.tgbotapi.types.message.abstracts.GroupEventMessage
@Deprecated("Renamed", ReplaceWith("CommonGroupEventMessage")) @Deprecated("Renamed", ReplaceWith("CommonGroupEventMessage"))
typealias GroupEventMessage = CommonGroupEventMessage typealias GroupEventMessage = CommonGroupEventMessage<*>
data class CommonGroupEventMessage( data class CommonGroupEventMessage<T : GroupEvent>(
override val messageId: MessageIdentifier, override val messageId: MessageIdentifier,
override val user: User, override val user: User,
override val chat: GroupChat, override val chat: GroupChat,
override val chatEvent: GroupEvent, override val chatEvent: T,
override val date: DateTime override val date: DateTime
) : GroupEventMessage ) : GroupEventMessage<T>

View File

@@ -4,16 +4,17 @@ import com.soywiz.klock.DateTime
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.User import dev.inmo.tgbotapi.types.User
import dev.inmo.tgbotapi.types.chat.abstracts.SupergroupChat import dev.inmo.tgbotapi.types.chat.abstracts.SupergroupChat
import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.GroupEvent
import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.SupergroupEvent import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.SupergroupEvent
import dev.inmo.tgbotapi.types.message.abstracts.SupergroupEventMessage import dev.inmo.tgbotapi.types.message.abstracts.SupergroupEventMessage
@Deprecated("Renamed", ReplaceWith("CommonSupergroupEventMessage")) @Deprecated("Renamed", ReplaceWith("CommonSupergroupEventMessage"))
typealias SupergroupEventMessage = CommonSupergroupEventMessage typealias SupergroupEventMessage = CommonSupergroupEventMessage<*>
data class CommonSupergroupEventMessage( data class CommonSupergroupEventMessage<T : SupergroupEvent>(
override val messageId: MessageIdentifier, override val messageId: MessageIdentifier,
override val user: User, override val user: User,
override val chat: SupergroupChat, override val chat: SupergroupChat,
override val chatEvent: SupergroupEvent, override val chatEvent: T,
override val date: DateTime override val date: DateTime
) : SupergroupEventMessage ) : SupergroupEventMessage<T>

View File

@@ -38,6 +38,7 @@ data class AnonymousGroupMessageImpl<T : MessageContent>(
data class CommonGroupMessageImpl<T : MessageContent>( data class CommonGroupMessageImpl<T : MessageContent>(
override val chat: GroupChat, override val chat: GroupChat,
override val messageId: MessageIdentifier, override val messageId: MessageIdentifier,
override val user: User,
override val date: DateTime, override val date: DateTime,
override val forwardInfo: ForwardInfo?, override val forwardInfo: ForwardInfo?,
override val editDate: DateTime?, override val editDate: DateTime?,

View File

@@ -296,6 +296,7 @@ internal data class RawMessage(
null -> CommonGroupMessageImpl( null -> CommonGroupMessageImpl(
chat, chat,
messageId, messageId,
from ?: error("It is expected that in messages from non anonymous users and channels user must be specified"),
date.asDate, date.asDate,
forwarded, forwarded,
edit_date ?.asDate, edit_date ?.asDate,

View File

@@ -2,6 +2,6 @@ package dev.inmo.tgbotapi.types.message.abstracts
import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.ChatEvent import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.ChatEvent
interface ChatEventMessage : Message { interface ChatEventMessage<T : ChatEvent> : Message {
val chatEvent: ChatEvent val chatEvent: T
} }

View File

@@ -1,3 +1,5 @@
package dev.inmo.tgbotapi.types.message.abstracts package dev.inmo.tgbotapi.types.message.abstracts
interface GroupEventMessage : ChatEventMessage, FromUserMessage import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.GroupEvent
interface GroupEventMessage<T : GroupEvent> : ChatEventMessage<T>, FromUserMessage

View File

@@ -17,4 +17,4 @@ interface AnonymousGroupMessage<T : MessageContent> : GroupMessage<T>, SignedMes
override val senderChat: GroupChat override val senderChat: GroupChat
get() = chat get() = chat
} }
interface CommonGroupMessage<T : MessageContent> : GroupMessage<T> interface CommonGroupMessage<T : MessageContent> : GroupMessage<T>, FromUserMessage

View File

@@ -1,3 +1,5 @@
package dev.inmo.tgbotapi.types.message.abstracts package dev.inmo.tgbotapi.types.message.abstracts
interface SupergroupEventMessage : GroupEventMessage import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.SupergroupEvent
interface SupergroupEventMessage<T : SupergroupEvent> : GroupEventMessage<T>

View File

@@ -1,7 +1,6 @@
package dev.inmo.tgbotapi.types.message.content package dev.inmo.tgbotapi.types.message.content
import dev.inmo.tgbotapi.CommonAbstracts.TextSourcesList import dev.inmo.tgbotapi.CommonAbstracts.*
import dev.inmo.tgbotapi.CommonAbstracts.TextPart
import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.requests.send.SendTextMessage import dev.inmo.tgbotapi.requests.send.SendTextMessage
import dev.inmo.tgbotapi.types.ChatIdentifier import dev.inmo.tgbotapi.types.ChatIdentifier
@@ -15,13 +14,13 @@ import dev.inmo.tgbotapi.utils.internal.fullListOfSubSource
import dev.inmo.tgbotapi.utils.internal.toMarkdownTexts import dev.inmo.tgbotapi.utils.internal.toMarkdownTexts
data class TextContent( data class TextContent(
val text: String, override val text: String,
/** override val textEntities: List<TextPart> = emptyList()
* Not full list of entities. This list WILL NOT contain [TextPart]s with [dev.inmo.tgbotapi.types.MessageEntity.textsources.RegularTextSource] ) : MessageContent, TextedInput {
* @see [TextContent.fullEntitiesList] @Deprecated("Has been renamed", ReplaceWith("textEntities"))
*/ val entities: List<TextPart>
val entities: List<TextPart> = emptyList() get() = textEntities
) : MessageContent {
override fun createResend( override fun createResend(
chatId: ChatIdentifier, chatId: ChatIdentifier,
disableNotification: Boolean, disableNotification: Boolean,
@@ -83,4 +82,5 @@ data class TextContent(
* Convert its [TextContent.entities] to list of [dev.inmo.tgbotapi.CommonAbstracts.TextSource] * Convert its [TextContent.entities] to list of [dev.inmo.tgbotapi.CommonAbstracts.TextSource]
* with [dev.inmo.tgbotapi.types.MessageEntity.textsources.RegularTextSource] * with [dev.inmo.tgbotapi.types.MessageEntity.textsources.RegularTextSource]
*/ */
@Deprecated("Useless due to the fact that currently every message contains full list of sources")
fun TextContent.fullEntitiesList(): TextSourcesList = text.fullListOfSubSource(entities).map { it.source } fun TextContent.fullEntitiesList(): TextSourcesList = text.fullListOfSubSource(entities).map { it.source }

View File

@@ -14,6 +14,14 @@ class FlowsUpdatesFilter(
private val updatesSharedFlow = MutableSharedFlow<Update>(extraBufferCapacity = broadcastChannelsSize) private val updatesSharedFlow = MutableSharedFlow<Update>(extraBufferCapacity = broadcastChannelsSize)
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
val allUpdatesFlow: Flow<Update> = updatesSharedFlow.asSharedFlow() val allUpdatesFlow: Flow<Update> = updatesSharedFlow.asSharedFlow()
@Suppress("MemberVisibilityCanBePrivate")
val allUpdatesWithoutMediaGroupsGroupingFlow: Flow<Update> = updatesSharedFlow.flatMapConcat {
when (it) {
is SentMediaGroupUpdate -> it.origins.asFlow()
is EditMediaGroupUpdate -> flowOf(it.origin)
else -> flowOf(it)
}
}
override val allowedUpdates: List<String> override val allowedUpdates: List<String>
get() = ALL_UPDATES_LIST get() = ALL_UPDATES_LIST

View File

@@ -2,10 +2,28 @@ package dev.inmo.tgbotapi.utils
const val telegramBotAPIDefaultUrl = "https://api.telegram.org" const val telegramBotAPIDefaultUrl = "https://api.telegram.org"
private inline val String.withoutLastSlash: String
get() {
var correctedUrl = this
while (true) {
val withoutSuffix = correctedUrl.removeSuffix("/")
if (withoutSuffix == correctedUrl) {
return correctedUrl
}
correctedUrl = withoutSuffix
}
}
class TelegramAPIUrlsKeeper( class TelegramAPIUrlsKeeper(
token: String, token: String,
hostUrl: String = telegramBotAPIDefaultUrl hostUrl: String = telegramBotAPIDefaultUrl
) { ) {
val commonAPIUrl = "$hostUrl/bot$token" val commonAPIUrl: String
val fileBaseUrl = "$hostUrl/file/bot$token" val fileBaseUrl: String
init {
val correctedHost = hostUrl.withoutLastSlash
commonAPIUrl = "$correctedHost/bot$token"
fileBaseUrl = "$correctedHost/file/bot$token"
}
} }

View File

@@ -15,9 +15,9 @@ internal fun createFormattedText(
val textBuilder = StringBuilder(partLength) val textBuilder = StringBuilder(partLength)
for (entity in entities) { for (entity in entities) {
val string = when (mode) { val string = when (mode) {
is MarkdownParseMode -> entity.asMarkdownSource is MarkdownParseMode -> entity.markdown
is MarkdownV2ParseMode -> entity.asMarkdownV2Source is MarkdownV2ParseMode -> entity.markdownV2
is HTMLParseMode -> entity.asHtmlSource is HTMLParseMode -> entity.html
} }
if (textBuilder.length + string.length > partLength) { if (textBuilder.length + string.length > partLength) {
if (textBuilder.isNotEmpty()) { if (textBuilder.isNotEmpty()) {

View File

@@ -73,29 +73,29 @@ internal fun List<TextPart>.shiftSourcesToTheLeft(shiftCount: Int = 1): List<Tex
} }
private fun List<TextSource>.joinSubSourcesMarkdownV2() = joinToString("") { private fun List<TextSource>.joinSubSourcesMarkdownV2() = joinToString("") {
it.asMarkdownV2Source it.markdownV2
} }
private fun List<TextSource>.joinSubSourcesHtml() = joinToString("") { private fun List<TextSource>.joinSubSourcesHtml() = joinToString("") {
it.asHtmlSource it.html
} }
internal fun MultilevelTextSource.markdownV2Default( internal fun MultilevelTextSource.markdownV2Default(
openControlSymbol: String, openControlSymbol: String,
closeControlSymbol: String = openControlSymbol closeControlSymbol: String = openControlSymbol
) = "$openControlSymbol${textSources.joinSubSourcesMarkdownV2()}$closeControlSymbol" ) = "$openControlSymbol${subsources.joinSubSourcesMarkdownV2()}$closeControlSymbol"
internal fun MultilevelTextSource.htmlDefault( internal fun MultilevelTextSource.htmlDefault(
openControlSymbol: String, openControlSymbol: String,
closeControlSymbol: String = openControlSymbol closeControlSymbol: String = openControlSymbol
) = "<$openControlSymbol>${textSources.joinSubSourcesHtml()}</$closeControlSymbol>" ) = "<$openControlSymbol>${subsources.joinSubSourcesHtml()}</$closeControlSymbol>"
internal fun MultilevelTextSource.linkMarkdownV2( internal fun MultilevelTextSource.linkMarkdownV2(
link: String link: String
) = "[${textSources.joinSubSourcesMarkdownV2()}](${link.escapeMarkdownV2Link()})" ) = "[${subsources.joinSubSourcesMarkdownV2()}](${link.escapeMarkdownV2Link()})"
internal fun MultilevelTextSource.linkHTML( internal fun MultilevelTextSource.linkHTML(
link: String link: String
) = "<a href=\"${link.toHtml()}\">${textSources.joinSubSourcesHtml()}</a>" ) = "<a href=\"${link.toHtml()}\">${subsources.joinSubSourcesHtml()}</a>"
internal fun MultilevelTextSource.optionalPrefix( internal fun MultilevelTextSource.optionalPrefix(
@@ -116,8 +116,8 @@ internal fun MultilevelTextSource.boldMarkdownV2(): String = markdownV2Default(m
internal fun MultilevelTextSource.boldHTML(): String = htmlDefault(htmlBoldControl) internal fun MultilevelTextSource.boldHTML(): String = htmlDefault(htmlBoldControl)
internal fun MultilevelTextSource.cashTagMarkdownV2(): String = textSources.joinSubSourcesMarkdownV2() internal fun MultilevelTextSource.cashTagMarkdownV2(): String = subsources.joinSubSourcesMarkdownV2()
internal fun MultilevelTextSource.cashTagHTML(): String = textSources.joinSubSourcesHtml() internal fun MultilevelTextSource.cashTagHTML(): String = subsources.joinSubSourcesHtml()
internal fun MultilevelTextSource.italicMarkdownV2(): String = markdownV2Default(markdownItalicControl) internal fun MultilevelTextSource.italicMarkdownV2(): String = markdownV2Default(markdownItalicControl)
@@ -135,21 +135,21 @@ internal fun MultilevelTextSource.underlineHTML(): String = htmlDefault(htmlUnde
internal fun MultilevelTextSource.textMentionMarkdownV2(userId: UserId): String = linkMarkdownV2(userId.link) internal fun MultilevelTextSource.textMentionMarkdownV2(userId: UserId): String = linkMarkdownV2(userId.link)
internal fun MultilevelTextSource.textMentionHTML(userId: UserId): String = linkHTML(userId.link) internal fun MultilevelTextSource.textMentionHTML(userId: UserId): String = linkHTML(userId.link)
internal fun MultilevelTextSource.mentionMarkdownV2(): String = optionalPrefix("@") + textSources.joinSubSourcesMarkdownV2() internal fun MultilevelTextSource.mentionMarkdownV2(): String = optionalPrefix("@") + subsources.joinSubSourcesMarkdownV2()
internal fun MultilevelTextSource.mentionHTML(): String = optionalPrefix("@") + textSources.joinSubSourcesHtml() internal fun MultilevelTextSource.mentionHTML(): String = optionalPrefix("@") + subsources.joinSubSourcesHtml()
internal fun MultilevelTextSource.hashTagMarkdownV2(): String = when { internal fun MultilevelTextSource.hashTagMarkdownV2(): String = when {
source.startsWith("\\#") || source.startsWith("#") -> "" source.startsWith("\\#") || source.startsWith("#") -> ""
else -> "\\#" else -> "\\#"
} + textSources.joinSubSourcesMarkdownV2() } + subsources.joinSubSourcesMarkdownV2()
internal fun MultilevelTextSource.hashTagHTML(): String = optionalPrefix("#") + textSources.joinSubSourcesHtml() internal fun MultilevelTextSource.hashTagHTML(): String = optionalPrefix("#") + subsources.joinSubSourcesHtml()
internal fun MultilevelTextSource.phoneMarkdownV2(): String = textSources.joinSubSourcesMarkdownV2() internal fun MultilevelTextSource.phoneMarkdownV2(): String = subsources.joinSubSourcesMarkdownV2()
internal fun MultilevelTextSource.phoneHTML(): String = textSources.joinSubSourcesHtml() internal fun MultilevelTextSource.phoneHTML(): String = subsources.joinSubSourcesHtml()
internal fun MultilevelTextSource.commandMarkdownV2(): String = optionalPrefix("/") + textSources.joinSubSourcesMarkdownV2() internal fun MultilevelTextSource.commandMarkdownV2(): String = optionalPrefix("/") + subsources.joinSubSourcesMarkdownV2()
internal fun MultilevelTextSource.commandHTML(): String = optionalPrefix("/") + textSources.joinSubSourcesHtml() internal fun MultilevelTextSource.commandHTML(): String = optionalPrefix("/") + subsources.joinSubSourcesHtml()

View File

@@ -49,8 +49,8 @@ fun List<TextPart>.testTextParts() {
assertTrue (get(5).source is MentionTextSource) assertTrue (get(5).source is MentionTextSource)
val boldSource = get(1).source as BoldTextSource val boldSource = get(1).source as BoldTextSource
assertTrue (boldSource.textSources.first() is ItalicTextSource) assertTrue (boldSource.subsources.first() is ItalicTextSource)
assertTrue (boldSource.textSources[1] is RegularTextSource) assertTrue (boldSource.subsources[1] is RegularTextSource)
assertTrue (boldSource.textSources[2] is StrikethroughTextSource) assertTrue (boldSource.subsources[2] is StrikethroughTextSource)
assertTrue ((boldSource.textSources[2] as StrikethroughTextSource).textSources.first() is UnderlineTextSource) assertTrue ((boldSource.subsources[2] as StrikethroughTextSource).subsources.first() is UnderlineTextSource)
} }

View File

@@ -8,7 +8,6 @@ buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
} }
} }
@@ -40,11 +39,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
implementation kotlin('stdlib') implementation kotlin('stdlib')
if ((project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true") { api project(":tgbotapi.core")
api "${project.group}:tgbotapi.core:$library_version"
} else {
api project(":tgbotapi.core")
}
} }
} }
} }

View File

@@ -1,53 +0,0 @@
apply plugin: 'maven-publish'
task javadocsJar(type: Jar) {
classifier = 'javadoc'
}
afterEvaluate {
project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) {
artifactId = "${project.name}"
} else {
artifactId = "${project.name}-$name"
}
}
}
publishing {
publications.all {
artifact javadocsJar
pom {
description = "API extensions which provide work with RequestsExecutor of TelegramBotAPI almost like it is described in original Telegram Bot API reference"
name = "Telegram Bot API Extensions for API"
url = "https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI-extensions-api"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/insanusmokrassar/TelegramBotAPI.git[push=]https://github.com/insanusmokrassar/TelegramBotAPI.git"
url = "https://github.com/insanusmokrassar/TelegramBotAPI.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Ovsiannikov Aleksei"
email = "ovsyannikov.alexey95@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"
}
}
}
}
}

View File

@@ -1,59 +1,65 @@
apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish'
apply from: "maven.publish.gradle" task javadocsJar(type: Jar) {
classifier = 'javadoc'
bintray {
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
filesSpec {
from "${buildDir}/publications/"
eachFile {
String directorySubname = it.getFile().parentFile.name
if (it.getName() == "module.json") {
if (directorySubname == "kotlinMultiplatform") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
} else {
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
}
} else {
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
} else {
it.exclude()
}
}
}
into "${project.group}".replace(".", "/")
}
publish = true
override = true
pkg {
repo = "TelegramBotAPI"
name = "${project.name}"
vcsUrl = "https://github.com/InsanusMokrassar/TelegramBotAPI"
licenses = ["Apache-2.0"]
version {
name = "${project.version}"
released = new Date()
vcsTag = "${project.version}"
gpg {
sign = true
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
}
}
}
} }
bintrayUpload.doFirst { afterEvaluate {
publications = publishing.publications.collect { project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) { if (it.name.contains('kotlinMultiplatform')) {
null artifactId = "${project.name}"
} else { } else {
it.name artifactId = "${project.name}-$name"
} }
} - null }
} }
bintrayUpload.dependsOn publishToMavenLocal publishing {
publications.all {
artifact javadocsJar
pom {
description = "API extensions which provide work with RequestsExecutor of TelegramBotAPI almost like it is described in original Telegram Bot API reference"
name = "Telegram Bot API Extensions for API"
url = "https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI-extensions-api"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/insanusmokrassar/TelegramBotAPI.git[push=]https://github.com/insanusmokrassar/TelegramBotAPI.git"
url = "https://github.com/insanusmokrassar/TelegramBotAPI.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Ovsiannikov Aleksei"
email = "ovsyannikov.alexey95@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"
}
}
}
repositories {
maven {
name = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/TelegramBotAPI/${project.name}/;publish=1;override=1")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
}
}
}
}
}

View File

@@ -2,6 +2,7 @@ package dev.inmo.tgbotapi.extensions.api
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.* import io.ktor.client.engine.*
@@ -36,10 +37,25 @@ data class BotBuilder internal constructor(
* @return Created by [telegramBotWithCustomClientConfig] function [TelegramBot]. This executor will be preconfigured using [token] and * @return Created by [telegramBotWithCustomClientConfig] function [TelegramBot]. This executor will be preconfigured using [token] and
* [block] * [block]
*/ */
fun buildBot(
token: String,
apiUrl: String = telegramBotAPIDefaultUrl,
block: BotBuilder.() -> Unit
) = telegramBot(
TelegramAPIUrlsKeeper(token, apiUrl),
BotBuilder().apply(block).createHttpClient()
)
/**
* @return Created by [telegramBotWithCustomClientConfig] function [TelegramBot]. This executor will be preconfigured using [token] and
* [block]
*/
@Deprecated("Renamed", ReplaceWith("buildBot", "dev.inmo.tgbotapi.extensions.api.buildBot"))
fun telegramBot( fun telegramBot(
token: String, token: String,
block: BotBuilder.() -> Unit block: BotBuilder.() -> Unit
): TelegramBot = telegramBot( ): TelegramBot = buildBot(
TelegramAPIUrlsKeeper(token), token,
BotBuilder().apply(block).createHttpClient() telegramBotAPIDefaultUrl,
block
) )

View File

@@ -1,6 +1,6 @@
package dev.inmo.tgbotapi.extensions.api package dev.inmo.tgbotapi.extensions.api
import dev.inmo.tgbotapi.bot.Ktor.KtorRequestsExecutor import dev.inmo.tgbotapi.bot.Ktor.KtorRequestsExecutorBuilder
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl
@@ -11,11 +11,11 @@ import io.ktor.client.engine.*
/** /**
* Allows to create bot using bot [urlsKeeper] * Allows to create bot using bot [urlsKeeper]
*/ */
@Deprecated("Replaced in core", ReplaceWith("telegramBot", "dev.inmo.tgbotapi.bot.Ktor.telegramBot"))
fun telegramBot( fun telegramBot(
urlsKeeper: TelegramAPIUrlsKeeper urlsKeeper: TelegramAPIUrlsKeeper
): TelegramBot = KtorRequestsExecutor( ): TelegramBot = dev.inmo.tgbotapi.bot.Ktor.telegramBot(
urlsKeeper, urlsKeeper
HttpClient()
) )
/** /**
@@ -24,10 +24,9 @@ fun telegramBot(
fun telegramBot( fun telegramBot(
urlsKeeper: TelegramAPIUrlsKeeper, urlsKeeper: TelegramAPIUrlsKeeper,
client: HttpClient client: HttpClient
): TelegramBot = KtorRequestsExecutor( ): TelegramBot = dev.inmo.tgbotapi.bot.Ktor.telegramBot(urlsKeeper) {
urlsKeeper, this.client = client
client }
)
/** /**
* Allows to create bot using bot [urlsKeeper] and specify [HttpClientEngineFactory] by passing [clientFactory] param and optionally * Allows to create bot using bot [urlsKeeper] and specify [HttpClientEngineFactory] by passing [clientFactory] param and optionally
@@ -73,11 +72,12 @@ inline fun telegramBot(
/** /**
* Allows to create bot using bot [token], [apiUrl] (for custom api servers) and already prepared [client] * Allows to create bot using bot [token], [apiUrl] (for custom api servers) and already prepared [client]
*/ */
@Deprecated("Replaced in core", ReplaceWith("telegramBot", "dev.inmo.tgbotapi.bot.Ktor.telegramBot"))
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun telegramBot( inline fun telegramBot(
token: String, token: String,
apiUrl: String = telegramBotAPIDefaultUrl apiUrl: String = telegramBotAPIDefaultUrl
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl)) ): TelegramBot = dev.inmo.tgbotapi.bot.Ktor.telegramBot(token, apiUrl)
/** /**
* Allows to create bot using bot [token], [apiUrl] (for custom api servers) and already prepared [client] * Allows to create bot using bot [token], [apiUrl] (for custom api servers) and already prepared [client]

View File

@@ -0,0 +1,62 @@
# TelegramBotAPI Behaviour Builder Extensions
This extension was created to make it more simple to build bot steps handling. Usually, you must use something like:
```kotlin
val bot = telegramBot(TOKEN)
bot.startGettingFlowsUpdatesByLongPolling {
messagesFlow.subscribeSafelyWithoutExceptions {
// ...
}
// here I already tired to write this example 😫
}
```
This library offer other way to do a lot of routine in more simple way:
```kotlin
telegramBot(token) {
onCommand("start".regex) {
execute(SendTextMessage(it.chat.id, "This bot can ...")) // replaceable with reply(it, "This bot can ...") when you are using `tgbotapi.extensions.api`
}
}
```
## Triggers
In terminology of this project the `Triggers` are things which have no initial message, may have own filter for incoming
messages and filter messages for context which will be used in subcontext. Full syntax with `onText` as an example:
```kotlin
telegramBot(TOKEN) {
onText(
includeFilterByChatInBehaviourSubContext = true, // if false - last lambda will receive all messages instead of filtered by chat messages
additionalFilter = { message: CommonMessage<TextContent> ->
// here you may check incoming message for any requirements before it will be passed to the main lambda
}
) { message: CommonMessage<TextContent> -> // this here is `BehaviourContext`
// here put your actions and additional waiters
}
}
```
## Waiters
Waiters targeted to get some content "here and now", they must be used inside some trigger main lambda:
```kotlin
telegramBot(TOKEN) {
onCommand("start") { message: CommonMessage<TextContent> ->
val userPhotos = waitPhoto(
SendTextMessage(it.chat.id, "Ok, send me some photo, please"), // init request, can be any `Request` object
{ update: Update -> // That is update which is NOT passed requirements. In current context we expect some photo, but received something else
SendTextMessage(it.chat.id, "Excuse me, but I can accept only photos") // it could be null
},
2, // some count of photos
includeMediaGroups = true, // if false, messages related to some media group will be skipped and recognized as incorrect
) { message: CommonMessate<PhotoContent> -> // this method is optional and you can use it in case you want to add some additional requirements checks
message.content // return null if message didn't passed requirements
}
}
}
```

View File

@@ -0,0 +1,46 @@
buildscript {
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
}
project.version = "$library_version"
project.group = "$library_group"
apply from: "publish.gradle"
repositories {
mavenLocal()
jcenter()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
kotlin {
jvm()
js(BOTH) {
browser()
nodejs()
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
api project(":tgbotapi.extensions.utils")
}
}
}
}

View File

@@ -0,0 +1 @@
{"bintrayConfig":{"repo":"TelegramBotAPI","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/TelegramBotAPI","autoPublish":true,"overridePublish":true},"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"}],"mavenConfig":{"name":"Telegram Bot API Steps Extensions","description":"This extensions project contains tools for simple interaction with chats","url":"https://insanusmokrassar.github.io/TelegramBotAPI/tgbotapi.extensions.steps","vcsUrl":"https://github.com/insanusmokrassar/TelegramBotAPI.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}]}}

View File

@@ -3,6 +3,9 @@ apply plugin: 'maven-publish'
task javadocsJar(type: Jar) { task javadocsJar(type: Jar) {
classifier = 'javadoc' classifier = 'javadoc'
} }
task sourceJar (type : Jar) {
classifier = 'sources'
}
afterEvaluate { afterEvaluate {
project.publishing.publications.all { project.publishing.publications.all {
@@ -10,6 +13,7 @@ afterEvaluate {
groupId "${project.group}" groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) { if (it.name.contains('kotlinMultiplatform')) {
artifactId = "${project.name}" artifactId = "${project.name}"
artifact sourceJar
} else { } else {
artifactId = "${project.name}-$name" artifactId = "${project.name}-$name"
} }
@@ -21,9 +25,9 @@ publishing {
artifact javadocsJar artifact javadocsJar
pom { pom {
description = "Library for Object-Oriented and type-safe work with Telegram Bot API" description = "This extensions project contains tools for simple interaction with chats"
name = "Telegram Bot API Core" name = "Telegram Bot API Steps Extensions"
url = "https://insanusmokrassar.github.io/TelegramBotAPI" url = "https://insanusmokrassar.github.io/TelegramBotAPI/tgbotapi.extensions.steps"
scm { scm {
developerConnection = "scm:git:[fetch=]https://github.com/insanusmokrassar/TelegramBotAPI.git[push=]https://github.com/insanusmokrassar/TelegramBotAPI.git" developerConnection = "scm:git:[fetch=]https://github.com/insanusmokrassar/TelegramBotAPI.git[push=]https://github.com/insanusmokrassar/TelegramBotAPI.git"
@@ -49,5 +53,17 @@ publishing {
} }
} }
repositories {
maven {
name = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/TelegramBotAPI/${project.name}/;publish=1;override=1")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
}
}
}
} }
} }

View File

@@ -0,0 +1,33 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import kotlinx.coroutines.CoroutineScope
suspend fun TelegramBot.buildBehaviour(
scope: CoroutineScope,
flowUpdatesFilter: FlowsUpdatesFilter,
block: BehaviourContextReceiver<Unit>
) {
BehaviourContext(
this,
scope,
flowUpdatesFilter
).block()
}
suspend fun TelegramBot.buildBehaviour(
scope: CoroutineScope,
block: BehaviourContextReceiver<Unit>
) = FlowsUpdatesFilter().also {
buildBehaviour(
scope,
it,
block
)
startGettingOfUpdatesByLongPolling(
updatesFilter = it,
scope = scope
)
}

View File

@@ -0,0 +1,15 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import dev.inmo.tgbotapi.updateshandlers.UpdatesFilter
import kotlinx.coroutines.CoroutineScope
typealias BehaviourContextReceiver<T> = suspend BehaviourContext.() -> T
typealias BehaviourContextAndTypeReceiver<T, I> = suspend BehaviourContext.(I) -> T
data class BehaviourContext(
val bot: TelegramBot,
val scope: CoroutineScope,
val flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter()
) : UpdatesFilter by flowsUpdatesFilter, TelegramBot by bot, CoroutineScope by scope

View File

@@ -0,0 +1,123 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import dev.inmo.tgbotapi.utils.RiskFeature
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.*
private val cancelledByFilterException = CancellationException("Cancelled by filter precreatedException")
typealias RequestBuilder<T> = suspend (Update) -> Request<T>
typealias NullableRequestBuilder<T> = suspend (Update) -> Request<T>?
/**
* @param initRequest If not null, this request will be sent by [bot] before returning value
* @param count If set, result [Flow] will return [count] elements on each [Flow.collect]
* @param errorFactory If set, this factory will be used to produce requests in case when user have sent incorrect data
* @param cancelRequestFactory If set, this factory will be used to produce requests in case when it is required to say
* user that chain of scenario has been cancelled
* @param cancelTrigger When this trigger returns true, chain is cancelled
* @param filter It is main param, which will be called on each update. When it return not null, result will be returned
* as is, but when it returns null, then will be called [cancelTrigger] (if it will return true - [cancelRequestFactory]
* will be called too), [errorFactory] and then will be returned null
*/
@RiskFeature("This method is not very comfortable to use and too low-level. It is recommended to use methods which already included into library")
suspend fun <T> FlowsUpdatesFilter.expectFlow(
bot: TelegramBot,
initRequest: Request<*>? = null,
count: Int? = null,
errorFactory: NullableRequestBuilder<*> = { null },
cancelRequestFactory: NullableRequestBuilder<*> = { null },
cancelTrigger: suspend (Update) -> Boolean = { cancelRequestFactory(it) != null },
filter: suspend (Update) -> List<T>
): Flow<T> {
val flow = allUpdatesFlow.flatMapConcat {
val result = safelyWithoutExceptions { filter(it) }
(if (result == null || result.isEmpty()) {
if (cancelTrigger(it)) {
cancelRequestFactory(it) ?.also {
safelyWithoutExceptions { bot.execute(it) }
throw cancelledByFilterException
}
}
errorFactory(it) ?.also { errorRequest ->
safelyWithoutExceptions { bot.execute(errorRequest) }
}
emptyList()
} else {
result
}).asFlow()
}
val result = if (count == null) {
flow
} else {
flow.take(count)
}
initRequest ?.also { safelyWithoutExceptions { bot.execute(initRequest) } }
return result
}
/**
* @param initRequest If not null, this request will be sent by [bot] before returning value
* @param count If set, result [Flow] will return [count] elements on each [Flow.collect]
* @param errorFactory If set, this factory will be used to produce requests in case when user have sent incorrect data
* @param cancelRequestFactory If set, this factory will be used to produce requests in case when it is required to say
* user that chain of scenario has been cancelled
* @param cancelTrigger When this trigger returns true, chain is cancelled
* @param filter It is main param, which will be called on each update. When it return not null, result will be returned
* as is, but when it returns null, then will be called [cancelTrigger] (if it will return true - [cancelRequestFactory]
* will be called too), [errorFactory] and then will be returned null
*/
suspend fun <T> BehaviourContext.expectFlow(
initRequest: Request<*>? = null,
count: Int? = null,
errorFactory: NullableRequestBuilder<*> = { null },
cancelRequestFactory: NullableRequestBuilder<*> = { null },
cancelTrigger: suspend (Update) -> Boolean = { cancelRequestFactory(it) != null },
filter: suspend (Update) -> List<T>
) = flowsUpdatesFilter.expectFlow(bot, initRequest, count, errorFactory, cancelRequestFactory, cancelTrigger, filter)
/**
* @param initRequest If not null, this request will be sent by [bot] before returning value
* @param errorFactory If set, this factory will be used to produce requests in case when user have sent incorrect data
* @param cancelRequestFactory If set, this factory will be used to produce requests in case when it is required to say
* user that chain of scenario has been cancelled
* @param cancelTrigger When this trigger returns true, chain is cancelled
* @param filter It is main param, which will be called on each update. When it return not null, result will be returned
* as is, but when it returns null, then will be called [cancelTrigger] (if it will return true - [cancelRequestFactory]
* will be called too), [errorFactory] and then will be returned null
*/
@RiskFeature("This method is not very comfortable to use and too low-level. It is recommended to use methods which already included into library")
suspend fun <T> FlowsUpdatesFilter.expectOne(
bot: TelegramBot,
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
cancelRequestFactory: NullableRequestBuilder<*> = { null },
cancelTrigger: suspend (Update) -> Boolean = { cancelRequestFactory(it) != null },
filter: suspend (Update) -> T?
): T = expectFlow(bot, initRequest, 1, errorFactory, cancelRequestFactory, cancelTrigger) {
listOfNotNull(filter.invoke(it))
}.first()
/**
* @param initRequest If not null, this request will be sent by [bot] before returning value
* @param errorFactory If set, this factory will be used to produce requests in case when user have sent incorrect data
* @param cancelRequestFactory If set, this factory will be used to produce requests in case when it is required to say
* user that chain of scenario has been cancelled
* @param cancelTrigger When this trigger returns true, chain is cancelled
* @param filter It is main param, which will be called on each update. When it return not null, result will be returned
* as is, but when it returns null, then will be called [cancelTrigger] (if it will return true - [cancelRequestFactory]
* will be called too), [errorFactory] and then will be returned null
*/
suspend fun <T> BehaviourContext.expectOne(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
cancelRequestFactory: NullableRequestBuilder<*> = { null },
cancelTrigger: suspend (Update) -> Boolean = { cancelRequestFactory(it) != null },
filter: suspend (Update) -> T?
) = flowsUpdatesFilter.expectOne(bot, initRequest, errorFactory, cancelRequestFactory, cancelTrigger, filter)

View File

@@ -0,0 +1,102 @@
@file:Suppress("unused")
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.utils.asCallbackQueryUpdate
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.CallbackQuery.*
import kotlinx.coroutines.flow.toList
typealias CallbackQueryMapper<T> = T.() -> T?
private suspend fun <O> BehaviourContext.waitCallbackQueries(
count: Int = 1,
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
mapper: suspend CallbackQuery.() -> O?
): List<O> = expectFlow(
initRequest,
count,
errorFactory
) {
it.asCallbackQueryUpdate() ?.data ?.mapper().let(::listOfNotNull)
}.toList().toList()
private suspend inline fun <reified T : CallbackQuery> BehaviourContext.waitEvents(
count: Int = 1,
initRequest: Request<*>? = null,
noinline errorFactory: NullableRequestBuilder<*> = { null },
noinline filter: CallbackQueryMapper<T>? = null
) : List<T> = waitCallbackQueries<T>(
count,
initRequest,
errorFactory
) {
if (this is T) {
if (filter == null) {
this
} else {
filter(this)
}
} else {
null
}
}
suspend fun BehaviourContext.waitDataCallbackQuery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CallbackQueryMapper<DataCallbackQuery>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitGameShortNameCallbackQuery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CallbackQueryMapper<GameShortNameCallbackQuery>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitInlineMessageIdCallbackQuery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CallbackQueryMapper<InlineMessageIdCallbackQuery>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitInlineMessageIdDataCallbackQuery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CallbackQueryMapper<InlineMessageIdDataCallbackQuery>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitInlineMessageIdGameShortNameCallbackQuery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CallbackQueryMapper<InlineMessageIdGameShortNameCallbackQuery>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitMessageCallbackQuery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CallbackQueryMapper<MessageCallbackQuery>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitMessageDataCallbackQuery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CallbackQueryMapper<MessageDataCallbackQuery>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitMessageGameShortNameCallbackQuery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CallbackQueryMapper<MessageGameShortNameCallbackQuery>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitUnknownCallbackQuery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CallbackQueryMapper<UnknownCallbackQueryType>? = null
) = waitEvents(count, initRequest, errorFactory, filter)

View File

@@ -0,0 +1,197 @@
@file:Suppress("unused")
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
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.abstracts.MediaGroupMessage
import dev.inmo.tgbotapi.types.message.content.*
import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.types.message.content.media.*
import dev.inmo.tgbotapi.types.message.payments.InvoiceContent
import kotlinx.coroutines.flow.toList
typealias CommonMessageToContentMapper<T> = suspend CommonMessage<T>.() -> T?
private suspend fun <O> BehaviourContext.waitCommonMessage(
count: Int = 1,
initRequest: Request<*>? = null,
includeMediaGroups: Boolean = true,
errorFactory: NullableRequestBuilder<*> = { null },
mapper: suspend CommonMessage<MessageContent>.() -> O?
): List<O> = expectFlow(
initRequest,
count,
errorFactory
) {
if (includeMediaGroups) {
it.asSentMediaGroupUpdate() ?.data ?.mapNotNull {
(it as CommonMessage<MessageContent>).mapper()
} ?.let { return@expectFlow it }
}
it.asMessageUpdate() ?.data ?.asCommonMessage() ?.mapper().let(::listOfNotNull)
}.toList().toList()
private suspend inline fun <reified T : MessageContent> BehaviourContext.waitContent(
count: Int = 1,
initRequest: Request<*>? = null,
includeMediaGroups: Boolean = true,
noinline errorFactory: NullableRequestBuilder<*> = { null },
noinline filter: CommonMessageToContentMapper<T>? = null
) : List<T> = waitCommonMessage<T>(
count,
initRequest,
includeMediaGroups,
errorFactory
) {
if (content is T) {
@Suppress("UNCHECKED_CAST")
val message = (this as CommonMessage<T>)
if (filter == null) {
message.content
} else {
safelyWithoutExceptions { filter(message) }
}
} else {
null
}
}
suspend fun BehaviourContext.waitContact(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<ContactContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitDice(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<DiceContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitGame(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<GameContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitLocation(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<LocationContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitPoll(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<PollContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitText(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<TextContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitVenue(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<VenueContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitAudioMediaGroup(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: CommonMessageToContentMapper<AudioMediaGroupContent>? = null
) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter)
suspend fun BehaviourContext.waitDocumentMediaGroup(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: CommonMessageToContentMapper<DocumentMediaGroupContent>? = null
) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter)
suspend fun BehaviourContext.waitMedia(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: CommonMessageToContentMapper<MediaContent>? = null
) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter)
suspend fun BehaviourContext.waitMediaGroup(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: CommonMessageToContentMapper<MediaGroupContent>? = null
) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter)
suspend fun BehaviourContext.waitVisualMediaGroup(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: CommonMessageToContentMapper<VisualMediaGroupContent>? = null
) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter)
suspend fun BehaviourContext.waitAnimation(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<AnimationContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitAudio(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: CommonMessageToContentMapper<AudioContent>? = null
) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter)
suspend fun BehaviourContext.waitDocument(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: CommonMessageToContentMapper<DocumentContent>? = null
) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter)
suspend fun BehaviourContext.waitPhoto(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: CommonMessageToContentMapper<PhotoContent>? = null
) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter)
suspend fun BehaviourContext.waitSticker(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<StickerContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitVideo(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
includeMediaGroups: Boolean = true,
filter: CommonMessageToContentMapper<VideoContent>? = null
) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter)
suspend fun BehaviourContext.waitVideoNote(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<VideoNoteContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitVoice(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<VoiceContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)
suspend fun BehaviourContext.waitInvoice(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: CommonMessageToContentMapper<InvoiceContent>? = null
) = waitContent(count, initRequest, false, errorFactory, filter)

View File

@@ -0,0 +1,144 @@
@file:Suppress("unused")
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.utils.asChatEventMessage
import dev.inmo.tgbotapi.extensions.utils.asMessageUpdate
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.message.ChatEvents.*
import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.*
import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage
import kotlinx.coroutines.flow.toList
typealias EventMessageToEventMapper<T> = suspend ChatEventMessage<T>.() -> T?
private suspend fun <O> BehaviourContext.waitEventMessages(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
mapper: suspend ChatEventMessage<ChatEvent>.() -> O?
): List<O> = expectFlow(
initRequest,
count,
errorFactory
) {
it.asMessageUpdate() ?.data ?.asChatEventMessage() ?.mapper().let(::listOfNotNull)
}.toList().toList()
private suspend inline fun <reified T : ChatEvent> BehaviourContext.waitEvents(
count: Int = 1,
initRequest: Request<*>? = null,
noinline errorFactory: NullableRequestBuilder<*> = { null },
noinline filter: EventMessageToEventMapper<T>? = null
) : List<T> = waitEventMessages<T>(
initRequest,
errorFactory,
count
) {
if (chatEvent is T) {
@Suppress("UNCHECKED_CAST")
val message = (this as ChatEventMessage<T>)
if (filter == null) {
message.chatEvent
} else {
filter(message)
}
} else {
null
}
}
suspend fun BehaviourContext.waitChannelEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<ChannelEvent>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitChatEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<ChatEvent>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitCommonEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<CommonEvent>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitGroupEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<GroupEvent>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitSupergroupEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<SupergroupEvent>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitChannelChatCreatedEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<ChannelChatCreated>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitDeleteChatPhotoEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<DeleteChatPhoto>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitGroupChatCreatedEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<GroupChatCreated>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitLeftChatMemberEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<LeftChatMember>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitNewChatPhotoEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<NewChatPhoto>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitNewChatMembersEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<NewChatMembers>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitNewChatTitleEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<NewChatTitle>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitPinnedMessageEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<PinnedMessage>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitProximityAlertTriggeredEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<ProximityAlertTriggered>? = null
) = waitEvents(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitSupergroupChatCreatedEvents(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: EventMessageToEventMapper<SupergroupChatCreated>? = null
) = waitEvents(count, initRequest, errorFactory, filter)

View File

@@ -0,0 +1,61 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.message.abstracts.MediaGroupMessage
import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.types.message.content.media.PhotoContent
import dev.inmo.tgbotapi.types.message.content.media.VideoContent
import dev.inmo.tgbotapi.utils.PreviewFeature
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
@PreviewFeature
internal suspend inline fun <reified T : MediaGroupContent> BehaviourContext.onMediaGroup(
count: Int = 1,
initRequest: Request<*>? = null,
noinline errorFactory: NullableRequestBuilder<*> = { null },
noinline filter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null
) = flowsUpdatesFilter.expectFlow(bot, initRequest, count, errorFactory) { update ->
update.asSentMediaGroupUpdate() ?.data ?.let { mediaGroup ->
if (mediaGroup.all { message -> message.content is T } && (filter == null || filter(mediaGroup))) {
listOf(
mediaGroup.map { it.content as T }
)
} else {
null
}
} ?: emptyList()
}.take(count).toList()
suspend fun BehaviourContext.waitPlaylist(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null
) = onMediaGroup<AudioMediaGroupContent>(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitDocumentsGroup(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null
) = onMediaGroup<DocumentMediaGroupContent>(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitVisualGallery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null
) = onMediaGroup<VisualMediaGroupContent>(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitPhotoGallery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null
) = onMediaGroup<PhotoContent>(count, initRequest, errorFactory, filter)
suspend fun BehaviourContext.waitVideoGallery(
initRequest: Request<*>? = null,
errorFactory: NullableRequestBuilder<*> = { null },
count: Int = 1,
filter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null
) = onMediaGroup<VideoContent>(count, initRequest, errorFactory, filter)

View File

@@ -0,0 +1,91 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTypeReceiver
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.expectFlow
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat
import dev.inmo.tgbotapi.types.CallbackQuery.*
import dev.inmo.tgbotapi.types.message.ChatEvents.*
import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.*
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import kotlinx.coroutines.flow.filter
internal suspend inline fun <reified T : CallbackQuery> BehaviourContext.onCallbackQuery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
noinline additionalFilter: (suspend (T) -> Boolean)? = null,
noinline scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, T>
) = flowsUpdatesFilter.expectFlow(bot) {
it.asCallbackQueryUpdate() ?.data ?.let { query ->
if (query is T) {
if (additionalFilter == null || additionalFilter(query)) query else null
} else {
null
}
}.let(::listOfNotNull)
}.subscribeSafelyWithoutExceptions(scope) { triggerQuery ->
val (jobToCancel, scenario) = if (includeFilterByChatInBehaviourSubContext) {
val subFilter = FlowsUpdatesFilter()
val subBehaviourContext = copy(flowsUpdatesFilter = subFilter)
flowsUpdatesFilter.allUpdatesFlow.filter {
val chat = it.sourceChat() ?: return@filter false
chat.id.chatId == triggerQuery.user.id.chatId
}.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subBehaviourContext
} else {
null to this
}
safelyWithoutExceptions { scenario.scenarioReceiver(triggerQuery) }
jobToCancel ?.cancel()
}
suspend fun BehaviourContext.onDataCallbackQuery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (DataCallbackQuery) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, DataCallbackQuery>
) = onCallbackQuery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onGameShortNameCallbackQuery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (GameShortNameCallbackQuery) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, GameShortNameCallbackQuery>
) = onCallbackQuery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onInlineMessageIdCallbackQuery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (InlineMessageIdCallbackQuery) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, InlineMessageIdCallbackQuery>
) = onCallbackQuery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onInlineMessageIdDataCallbackQuery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (InlineMessageIdDataCallbackQuery) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, InlineMessageIdDataCallbackQuery>
) = onCallbackQuery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onInlineMessageIdGameShortNameCallbackQuery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (InlineMessageIdGameShortNameCallbackQuery) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, InlineMessageIdGameShortNameCallbackQuery>
) = onCallbackQuery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onMessageCallbackQuery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (MessageCallbackQuery) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, MessageCallbackQuery>
) = onCallbackQuery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onMessageDataCallbackQuery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (MessageDataCallbackQuery) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, MessageDataCallbackQuery>
) = onCallbackQuery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onMessageGameShortNameCallbackQuery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (MessageGameShortNameCallbackQuery) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, MessageGameShortNameCallbackQuery>
) = onCallbackQuery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onUnknownCallbackQueryType(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (UnknownCallbackQueryType) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, UnknownCallbackQueryType>
) = onCallbackQuery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)

View File

@@ -0,0 +1,35 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.tgbotapi.CommonAbstracts.textSources
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import kotlinx.coroutines.Job
suspend fun BehaviourContext.command(
commandRegex: Regex,
requireOnlyCommandInMessage: Boolean = true,
includeFilterByChatInBehaviourSubContext: Boolean = true,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<TextContent>>
): Job = onText(
includeFilterByChatInBehaviourSubContext,
{ message ->
val content = message.content
val textSources = content.textSources
val sizeRequirement = if (requireOnlyCommandInMessage) {
textSources.size == 1
} else {
true
}
sizeRequirement && textSources.any { commandRegex.matches(it.asBotCommandTextSource() ?.command ?: return@any false) }
},
scenarioReceiver
)
suspend inline fun BehaviourContext.onCommand(
commandRegex: Regex,
requireOnlyCommandInMessage: Boolean = true,
includeFilterByChatInBehaviourSubContext: Boolean = true,
noinline scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<TextContent>>
): Job = command(commandRegex, requireOnlyCommandInMessage, includeFilterByChatInBehaviourSubContext, scenarioReceiver)

View File

@@ -0,0 +1,186 @@
@file:Suppress("unused", "UNCHECKED_CAST")
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTypeReceiver
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.expectFlow
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat
import dev.inmo.tgbotapi.types.files.abstracts.TelegramMediaFile
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.*
import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.types.message.content.media.*
import dev.inmo.tgbotapi.types.message.payments.InvoiceContent
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import dev.inmo.tgbotapi.utils.PreviewFeature
import kotlinx.coroutines.flow.filter
typealias CommonMessageFilter<T> = (suspend (CommonMessage<T>) -> Boolean)
@PreviewFeature
internal suspend inline fun <reified T : MessageContent> BehaviourContext.onContent(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
noinline additionalFilter: CommonMessageFilter<T>? = null,
noinline scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<T>>
) = flowsUpdatesFilter.expectFlow(bot) {
if (includeMediaGroups) {
it.asSentMediaGroupUpdate() ?.data ?.mapNotNull {
if (it.content is T) {
val adaptedMessage = it as CommonMessage<T>
if (additionalFilter == null || additionalFilter(adaptedMessage)) adaptedMessage else null
} else {
null
}
} ?.let {
return@expectFlow it
}
}
it.asMessageUpdate() ?.data ?.asCommonMessage() ?.let { message ->
if (message.content is T) {
val adaptedMessage = message as CommonMessage<T>
if (additionalFilter == null || additionalFilter(adaptedMessage)) adaptedMessage else null
} else {
null
}
}.let(::listOfNotNull)
}.subscribeSafelyWithoutExceptions(scope) { triggerMessage ->
val (jobToCancel, scenario) = if (includeFilterByChatInBehaviourSubContext) {
val subFilter = FlowsUpdatesFilter()
val subBehaviourContext = copy(flowsUpdatesFilter = subFilter)
flowsUpdatesFilter.allUpdatesFlow.filter {
val chat = it.sourceChat() ?: return@filter false
chat.id.chatId == triggerMessage.chat.id.chatId
}.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subBehaviourContext
} else {
null to this
}
safelyWithoutExceptions { scenario.scenarioReceiver(triggerMessage) }
jobToCancel ?.cancel()
}
suspend fun BehaviourContext.onContact(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<ContactContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<ContactContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onDice(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<DiceContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<DiceContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onGame(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<GameContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<GameContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onLocation(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<LocationContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<LocationContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onPoll(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<PollContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<PollContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onText(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<TextContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<TextContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onVenue(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<VenueContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<VenueContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onAudioMediaGroup(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<AudioMediaGroupContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<AudioMediaGroupContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, true, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onDocumentMediaGroup(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
additionalFilter: CommonMessageFilter<DocumentMediaGroupContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<DocumentMediaGroupContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onMediaCollection(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
additionalFilter: (suspend (CommonMessage<MediaCollectionContent<TelegramMediaFile>>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<MediaCollectionContent<TelegramMediaFile>>>
) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onMedia(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
additionalFilter: CommonMessageFilter<MediaContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<MediaContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onMediaGroup(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
additionalFilter: CommonMessageFilter<MediaGroupContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<MediaGroupContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onVisualMediaGroup(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
additionalFilter: CommonMessageFilter<VisualMediaGroupContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<VisualMediaGroupContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onAnimation(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<AnimationContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<AnimationContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onAudio(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
additionalFilter: CommonMessageFilter<AudioContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<AudioContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onDocument(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
additionalFilter: CommonMessageFilter<DocumentContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<DocumentContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onPhoto(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
additionalFilter: CommonMessageFilter<PhotoContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<PhotoContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onSticker(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<StickerContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<StickerContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onVideo(
includeFilterByChatInBehaviourSubContext: Boolean = true,
includeMediaGroups: Boolean = true,
additionalFilter: CommonMessageFilter<VideoContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<VideoContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onVideoNote(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<VideoNoteContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<VideoNoteContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onVoice(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<VoiceContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<VoiceContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onInvoice(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: CommonMessageFilter<InvoiceContent>? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, CommonMessage<InvoiceContent>>
) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver)

View File

@@ -0,0 +1,124 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTypeReceiver
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.expectFlow
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat
import dev.inmo.tgbotapi.types.message.ChatEvents.*
import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.*
import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage
import dev.inmo.tgbotapi.types.message.content.*
import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.types.message.content.media.*
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import kotlinx.coroutines.flow.filter
internal suspend inline fun <reified T : ChatEvent> BehaviourContext.onEvent(
includeFilterByChatInBehaviourSubContext: Boolean = true,
noinline additionalFilter: (suspend (ChatEventMessage<T>) -> Boolean)? = null,
noinline scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<T>>
) = flowsUpdatesFilter.expectFlow(bot) {
it.asMessageUpdate() ?.data ?.asChatEventMessage() ?.let { message ->
if (message.chatEvent is T) {
val adaptedMessage = message as ChatEventMessage<T>
if (additionalFilter == null || additionalFilter(adaptedMessage)) adaptedMessage else null
} else {
null
}
}.let(::listOfNotNull)
}.subscribeSafelyWithoutExceptions(scope) { triggerMessage ->
val (jobToCancel, scenario) = if (includeFilterByChatInBehaviourSubContext) {
val subFilter = FlowsUpdatesFilter()
val subBehaviourContext = copy(flowsUpdatesFilter = subFilter)
flowsUpdatesFilter.allUpdatesFlow.filter {
val chat = it.sourceChat() ?: return@filter false
chat.id.chatId == triggerMessage.chat.id.chatId
}.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subBehaviourContext
} else {
null to this
}
safelyWithoutExceptions { scenario.scenarioReceiver(triggerMessage) }
jobToCancel ?.cancel()
}
suspend fun BehaviourContext.onChannelEvent(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<ChannelEvent>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<ChannelEvent>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onChatEvent(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<ChatEvent>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<ChatEvent>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onCommonEvent(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<CommonEvent>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<CommonEvent>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onGroupEvent(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<GroupEvent>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<GroupEvent>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onSupergroupEvent(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<SupergroupEvent>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<SupergroupEvent>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onChannelChatCreated(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<ChannelChatCreated>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<ChannelChatCreated>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onDeleteChatPhoto(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<DeleteChatPhoto>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<DeleteChatPhoto>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onGroupChatCreated(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<GroupChatCreated>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<GroupChatCreated>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onLeftChatMember(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<LeftChatMember>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<LeftChatMember>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onNewChatMembers(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<NewChatMembers>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<NewChatMembers>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onNewChatPhoto(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<NewChatPhoto>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<NewChatPhoto>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onNewChatTitle(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<NewChatTitle>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<NewChatTitle>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onPinnedMessage(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<PinnedMessage>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<PinnedMessage>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onProximityAlertTriggered(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<ProximityAlertTriggered>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<ProximityAlertTriggered>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onSupergroupChatCreated(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (ChatEventMessage<SupergroupChatCreated>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, ChatEventMessage<SupergroupChatCreated>>
) = onEvent(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)

View File

@@ -0,0 +1,77 @@
@file:Suppress("unused")
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTypeReceiver
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.expectFlow
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat
import dev.inmo.tgbotapi.extensions.utils.shortcuts.chat
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.abstracts.MediaGroupMessage
import dev.inmo.tgbotapi.types.message.content.ContactContent
import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.types.message.content.media.PhotoContent
import dev.inmo.tgbotapi.types.message.content.media.VideoContent
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import dev.inmo.tgbotapi.utils.PreviewFeature
import kotlinx.coroutines.flow.filter
@PreviewFeature
internal suspend inline fun <reified T : MediaGroupContent> BehaviourContext.onMediaGroup(
includeFilterByChatInBehaviourSubContext: Boolean = true,
noinline additionalFilter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null,
noinline scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, List<MediaGroupMessage>>
) = flowsUpdatesFilter.expectFlow(bot) { update ->
update.asSentMediaGroupUpdate() ?.data ?.let { mediaGroup ->
if (mediaGroup.all { message -> message.content is T } && (additionalFilter == null || additionalFilter(mediaGroup))) {
listOf(mediaGroup)
} else {
null
}
} ?: emptyList()
}.subscribeSafelyWithoutExceptions(scope) { mediaGroup ->
val (jobToCancel, scenario) = if (includeFilterByChatInBehaviourSubContext) {
val subFilter = FlowsUpdatesFilter()
val subBehaviourContext = copy(flowsUpdatesFilter = subFilter)
flowsUpdatesFilter.allUpdatesFlow.filter {
val chat = it.sourceChat() ?: return@filter false
chat.id.chatId == mediaGroup.chat!!.id.chatId
}.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subBehaviourContext
} else {
null to this
}
safelyWithoutExceptions { scenario.scenarioReceiver(mediaGroup) }
jobToCancel ?.cancel()
}
suspend fun BehaviourContext.onPlaylist(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, List<MediaGroupMessage>>
) = onMediaGroup<AudioMediaGroupContent>(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onDocumentsGroup(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, List<MediaGroupMessage>>
) = onMediaGroup<DocumentMediaGroupContent>(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onVisualGallery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, List<MediaGroupMessage>>
) = onMediaGroup<VisualMediaGroupContent>(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onPhotoGallery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, List<MediaGroupMessage>>
) = onMediaGroup<PhotoContent>(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)
suspend fun BehaviourContext.onVideoGallery(
includeFilterByChatInBehaviourSubContext: Boolean = true,
additionalFilter: (suspend (List<MediaGroupMessage>) -> Boolean)? = null,
scenarioReceiver: BehaviourContextAndTypeReceiver<Unit, List<MediaGroupMessage>>
) = onMediaGroup<VideoContent>(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)

View File

@@ -8,7 +8,6 @@ buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
} }
} }
@@ -40,11 +39,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
implementation kotlin('stdlib') implementation kotlin('stdlib')
if ((project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true") { api project(":tgbotapi.core")
api "${project.group}:tgbotapi.core:$library_version"
} else {
api project(":tgbotapi.core")
}
} }
} }
} }

View File

@@ -1,53 +0,0 @@
apply plugin: 'maven-publish'
task javadocsJar(type: Jar) {
classifier = 'javadoc'
}
afterEvaluate {
project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) {
artifactId = "${project.name}"
} else {
artifactId = "${project.name}-$name"
}
}
}
publishing {
publications.all {
artifact javadocsJar
pom {
description = "Util extensions for more useful work with updates and other things"
name = "Telegram Bot API Utility Extensions"
url = "https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI-extensions-utils"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/insanusmokrassar/TelegramBotAPI.git[push=]https://github.com/insanusmokrassar/TelegramBotAPI.git"
url = "https://github.com/insanusmokrassar/TelegramBotAPI.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Ovsiannikov Aleksei"
email = "ovsyannikov.alexey95@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"
}
}
}
}
}

View File

@@ -1,59 +1,65 @@
apply plugin: 'com.jfrog.bintray' apply plugin: 'maven-publish'
apply from: "maven.publish.gradle" task javadocsJar(type: Jar) {
classifier = 'javadoc'
bintray {
user = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
key = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
filesSpec {
from "${buildDir}/publications/"
eachFile {
String directorySubname = it.getFile().parentFile.name
if (it.getName() == "module.json") {
if (directorySubname == "kotlinMultiplatform") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.module")
} else {
it.setPath("${project.name}-${directorySubname}/${project.version}/${project.name}-${directorySubname}-${project.version}.module")
}
} else {
if (directorySubname == "kotlinMultiplatform" && it.getName() == "pom-default.xml") {
it.setPath("${project.name}/${project.version}/${project.name}-${project.version}.pom")
} else {
it.exclude()
}
}
}
into "${project.group}".replace(".", "/")
}
publish = true
override = true
pkg {
repo = "TelegramBotAPI"
name = "${project.name}"
vcsUrl = "https://github.com/InsanusMokrassar/TelegramBotAPI"
licenses = ["Apache-2.0"]
version {
name = "${project.version}"
released = new Date()
vcsTag = "${project.version}"
gpg {
sign = true
passphrase = project.hasProperty('signing.gnupg.passphrase') ? project.property('signing.gnupg.passphrase') : System.getenv('signing.gnupg.passphrase')
}
}
}
} }
bintrayUpload.doFirst { afterEvaluate {
publications = publishing.publications.collect { project.publishing.publications.all {
// rename artifacts
groupId "${project.group}"
if (it.name.contains('kotlinMultiplatform')) { if (it.name.contains('kotlinMultiplatform')) {
null artifactId = "${project.name}"
} else { } else {
it.name artifactId = "${project.name}-$name"
} }
} - null }
} }
bintrayUpload.dependsOn publishToMavenLocal publishing {
publications.all {
artifact javadocsJar
pom {
description = "Util extensions for more useful work with updates and other things"
name = "Telegram Bot API Utility Extensions"
url = "https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI-extensions-utils"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/insanusmokrassar/TelegramBotAPI.git[push=]https://github.com/insanusmokrassar/TelegramBotAPI.git"
url = "https://github.com/insanusmokrassar/TelegramBotAPI.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Ovsiannikov Aleksei"
email = "ovsyannikov.alexey95@gmail.com"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"
}
}
}
repositories {
maven {
name = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/TelegramBotAPI/${project.name}/;publish=1;override=1")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
}
}
}
}
}

View File

@@ -0,0 +1,957 @@
@file:Suppress("NOTHING_TO_INLINE", "unused", "UNCHECKED_CAST")
package dev.inmo.tgbotapi.extensions.utils
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.CallbackQuery.*
import dev.inmo.tgbotapi.types.ChatMember.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.results.audio.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.results.document.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.results.gif.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.results.mpeg4gif.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.results.photo.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.results.video.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.results.voice.*
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.*
import dev.inmo.tgbotapi.types.InlineQueries.abstracts.InlineQuery
import dev.inmo.tgbotapi.types.InlineQueries.abstracts.InputMessageContent
import dev.inmo.tgbotapi.types.InlineQueries.query.BaseInlineQuery
import dev.inmo.tgbotapi.types.InlineQueries.query.LocationInlineQuery
import dev.inmo.tgbotapi.types.InputMedia.*
import dev.inmo.tgbotapi.types.actions.*
import dev.inmo.tgbotapi.types.buttons.*
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.*
import dev.inmo.tgbotapi.types.chat.abstracts.*
import dev.inmo.tgbotapi.types.chat.abstracts.extended.*
import dev.inmo.tgbotapi.types.files.*
import dev.inmo.tgbotapi.types.files.abstracts.*
import dev.inmo.tgbotapi.types.message.*
import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.*
import dev.inmo.tgbotapi.types.message.abstracts.*
import dev.inmo.tgbotapi.types.message.abstracts.ChannelMessage
import dev.inmo.tgbotapi.types.message.abstracts.GroupEventMessage
import dev.inmo.tgbotapi.types.message.abstracts.SupergroupEventMessage
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent
import dev.inmo.tgbotapi.types.message.content.abstracts.PossiblySentViaBotCommonMessage
import dev.inmo.tgbotapi.types.polls.*
import dev.inmo.tgbotapi.types.update.*
import dev.inmo.tgbotapi.types.update.MediaGroupUpdates.*
import dev.inmo.tgbotapi.types.update.abstracts.*
import dev.inmo.tgbotapi.utils.PreviewFeature
import dev.inmo.tgbotapi.types.message.content.abstracts.ResendableContent
import dev.inmo.tgbotapi.types.message.content.ContactContent
import dev.inmo.tgbotapi.types.message.content.DiceContent
import dev.inmo.tgbotapi.types.message.content.GameContent
import dev.inmo.tgbotapi.types.message.content.LocationContent
import dev.inmo.tgbotapi.types.message.content.PollContent
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.content.VenueContent
import dev.inmo.tgbotapi.types.message.content.abstracts.AudioMediaGroupContent
import dev.inmo.tgbotapi.types.message.content.abstracts.DocumentMediaGroupContent
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaCollectionContent
import dev.inmo.tgbotapi.types.files.abstracts.TelegramMediaFile
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaGroupContent
import dev.inmo.tgbotapi.types.message.content.abstracts.VisualMediaGroupContent
import dev.inmo.tgbotapi.types.message.content.media.AnimationContent
import dev.inmo.tgbotapi.types.message.content.media.AudioContent
import dev.inmo.tgbotapi.types.message.content.media.DocumentContent
import dev.inmo.tgbotapi.types.message.content.media.PhotoContent
import dev.inmo.tgbotapi.types.message.content.media.StickerContent
import dev.inmo.tgbotapi.types.message.content.media.VideoContent
import dev.inmo.tgbotapi.types.message.content.media.VideoNoteContent
import dev.inmo.tgbotapi.types.message.content.media.VoiceContent
import dev.inmo.tgbotapi.types.message.payments.InvoiceContent
import dev.inmo.tgbotapi.CommonAbstracts.TextSource
import dev.inmo.tgbotapi.CommonAbstracts.MultilevelTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.BoldTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.BotCommandTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.CashTagTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.CodeTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.EMailTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.HashTagTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.ItalicTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.MentionTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.PhoneNumberTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.PreTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.RegularTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.StrikethroughTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.TextLinkTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.TextMentionTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.URLTextSource
import dev.inmo.tgbotapi.types.MessageEntity.textsources.UnderlineTextSource
@PreviewFeature
inline fun Chat.asBot(): Bot? = this as? Bot
@PreviewFeature
inline fun Chat.requireBot(): Bot = this as Bot
@PreviewFeature
inline fun Chat.asCommonBot(): CommonBot? = this as? CommonBot
@PreviewFeature
inline fun Chat.requireCommonBot(): CommonBot = this as CommonBot
@PreviewFeature
inline fun Chat.asCommonUser(): CommonUser? = this as? CommonUser
@PreviewFeature
inline fun Chat.requireCommonUser(): CommonUser = this as CommonUser
@PreviewFeature
inline fun Chat.asExtendedBot(): ExtendedBot? = this as? ExtendedBot
@PreviewFeature
inline fun Chat.requireExtendedBot(): ExtendedBot = this as ExtendedBot
@PreviewFeature
inline fun Chat.asUser(): User? = this as? User
@PreviewFeature
inline fun Chat.requireUser(): User = this as User
@PreviewFeature
inline fun Chat.asChannelChat(): ChannelChat? = this as? ChannelChat
@PreviewFeature
inline fun Chat.requireChannelChat(): ChannelChat = this as ChannelChat
@PreviewFeature
inline fun Chat.asGroupChat(): GroupChat? = this as? GroupChat
@PreviewFeature
inline fun Chat.requireGroupChat(): GroupChat = this as GroupChat
@PreviewFeature
inline fun Chat.asPrivateChat(): PrivateChat? = this as? PrivateChat
@PreviewFeature
inline fun Chat.requirePrivateChat(): PrivateChat = this as PrivateChat
@PreviewFeature
inline fun Chat.asPublicChat(): PublicChat? = this as? PublicChat
@PreviewFeature
inline fun Chat.requirePublicChat(): PublicChat = this as PublicChat
@PreviewFeature
inline fun Chat.asSuperPublicChat(): SuperPublicChat? = this as? SuperPublicChat
@PreviewFeature
inline fun Chat.requireSuperPublicChat(): SuperPublicChat = this as SuperPublicChat
@PreviewFeature
inline fun Chat.asSupergroupChat(): SupergroupChat? = this as? SupergroupChat
@PreviewFeature
inline fun Chat.requireSupergroupChat(): SupergroupChat = this as SupergroupChat
@PreviewFeature
inline fun Chat.asUnknownChatType(): UnknownChatType? = this as? UnknownChatType
@PreviewFeature
inline fun Chat.requireUnknownChatType(): UnknownChatType = this as UnknownChatType
@PreviewFeature
inline fun Chat.asUsernameChat(): UsernameChat? = this as? UsernameChat
@PreviewFeature
inline fun Chat.requireUsernameChat(): UsernameChat = this as UsernameChat
@PreviewFeature
inline fun Chat.asExtendedChannelChat(): ExtendedChannelChat? = this as? ExtendedChannelChat
@PreviewFeature
inline fun Chat.requireExtendedChannelChat(): ExtendedChannelChat = this as ExtendedChannelChat
@PreviewFeature
inline fun Chat.asExtendedChat(): ExtendedChat? = this as? ExtendedChat
@PreviewFeature
inline fun Chat.requireExtendedChat(): ExtendedChat = this as ExtendedChat
@PreviewFeature
inline fun Chat.asExtendedGroupChat(): ExtendedGroupChat? = this as? ExtendedGroupChat
@PreviewFeature
inline fun Chat.requireExtendedGroupChat(): ExtendedGroupChat = this as ExtendedGroupChat
@PreviewFeature
inline fun Chat.asExtendedPrivateChat(): ExtendedPrivateChat? = this as? ExtendedPrivateChat
@PreviewFeature
inline fun Chat.requireExtendedPrivateChat(): ExtendedPrivateChat = this as ExtendedPrivateChat
@PreviewFeature
inline fun Chat.asExtendedPublicChat(): ExtendedPublicChat? = this as? ExtendedPublicChat
@PreviewFeature
inline fun Chat.requireExtendedPublicChat(): ExtendedPublicChat = this as ExtendedPublicChat
@PreviewFeature
inline fun Chat.asExtendedSupergroupChat(): ExtendedSupergroupChat? = this as? ExtendedSupergroupChat
@PreviewFeature
inline fun Chat.requireExtendedSupergroupChat(): ExtendedSupergroupChat = this as ExtendedSupergroupChat
@PreviewFeature
inline fun CallbackQuery.asDataCallbackQuery(): DataCallbackQuery? = this as? DataCallbackQuery
@PreviewFeature
inline fun CallbackQuery.requireDataCallbackQuery(): DataCallbackQuery = this as DataCallbackQuery
@PreviewFeature
inline fun CallbackQuery.asGameShortNameCallbackQuery(): GameShortNameCallbackQuery? = this as? GameShortNameCallbackQuery
@PreviewFeature
inline fun CallbackQuery.requireGameShortNameCallbackQuery(): GameShortNameCallbackQuery = this as GameShortNameCallbackQuery
@PreviewFeature
inline fun CallbackQuery.asInlineMessageIdCallbackQuery(): InlineMessageIdCallbackQuery? = this as? InlineMessageIdCallbackQuery
@PreviewFeature
inline fun CallbackQuery.requireInlineMessageIdCallbackQuery(): InlineMessageIdCallbackQuery = this as InlineMessageIdCallbackQuery
@PreviewFeature
inline fun CallbackQuery.asInlineMessageIdDataCallbackQuery(): InlineMessageIdDataCallbackQuery? = this as? InlineMessageIdDataCallbackQuery
@PreviewFeature
inline fun CallbackQuery.requireInlineMessageIdDataCallbackQuery(): InlineMessageIdDataCallbackQuery = this as InlineMessageIdDataCallbackQuery
@PreviewFeature
inline fun CallbackQuery.asInlineMessageIdGameShortNameCallbackQuery(): InlineMessageIdGameShortNameCallbackQuery? = this as? InlineMessageIdGameShortNameCallbackQuery
@PreviewFeature
inline fun CallbackQuery.requireInlineMessageIdGameShortNameCallbackQuery(): InlineMessageIdGameShortNameCallbackQuery = this as InlineMessageIdGameShortNameCallbackQuery
@PreviewFeature
inline fun CallbackQuery.asMessageCallbackQuery(): MessageCallbackQuery? = this as? MessageCallbackQuery
@PreviewFeature
inline fun CallbackQuery.requireMessageCallbackQuery(): MessageCallbackQuery = this as MessageCallbackQuery
@PreviewFeature
inline fun CallbackQuery.asMessageDataCallbackQuery(): MessageDataCallbackQuery? = this as? MessageDataCallbackQuery
@PreviewFeature
inline fun CallbackQuery.requireMessageDataCallbackQuery(): MessageDataCallbackQuery = this as MessageDataCallbackQuery
@PreviewFeature
inline fun CallbackQuery.asMessageGameShortNameCallbackQuery(): MessageGameShortNameCallbackQuery? = this as? MessageGameShortNameCallbackQuery
@PreviewFeature
inline fun CallbackQuery.requireMessageGameShortNameCallbackQuery(): MessageGameShortNameCallbackQuery = this as MessageGameShortNameCallbackQuery
@PreviewFeature
inline fun CallbackQuery.asUnknownCallbackQueryType(): UnknownCallbackQueryType? = this as? UnknownCallbackQueryType
@PreviewFeature
inline fun CallbackQuery.requireUnknownCallbackQueryType(): UnknownCallbackQueryType = this as UnknownCallbackQueryType
@PreviewFeature
inline fun Message.asChannelEventMessage(): ChannelEventMessage<ChannelEvent>? = this as? ChannelEventMessage<ChannelEvent>
@PreviewFeature
inline fun Message.requireChannelEventMessage(): ChannelEventMessage<ChannelEvent> = this as ChannelEventMessage<ChannelEvent>
@PreviewFeature
inline fun Message.asChannelMediaGroupMessage(): ChannelMediaGroupMessage? = this as? ChannelMediaGroupMessage
@PreviewFeature
inline fun Message.requireChannelMediaGroupMessage(): ChannelMediaGroupMessage = this as ChannelMediaGroupMessage
@PreviewFeature
inline fun Message.asCommonGroupEventMessage(): CommonGroupEventMessage<GroupEvent>? = this as? CommonGroupEventMessage<GroupEvent>
@PreviewFeature
inline fun Message.requireCommonGroupEventMessage(): CommonGroupEventMessage<GroupEvent> = this as CommonGroupEventMessage<GroupEvent>
@PreviewFeature
inline fun Message.asCommonMediaGroupMessage(): CommonMediaGroupMessage? = this as? CommonMediaGroupMessage
@PreviewFeature
inline fun Message.requireCommonMediaGroupMessage(): CommonMediaGroupMessage = this as CommonMediaGroupMessage
@PreviewFeature
inline fun Message.asCommonSupergroupEventMessage(): CommonSupergroupEventMessage<SupergroupEvent>? = this as? CommonSupergroupEventMessage<SupergroupEvent>
@PreviewFeature
inline fun Message.requireCommonSupergroupEventMessage(): CommonSupergroupEventMessage<SupergroupEvent> = this as CommonSupergroupEventMessage<SupergroupEvent>
@PreviewFeature
inline fun Message.asAnonymousGroupMessage(): AnonymousGroupMessage<MessageContent>? = this as? AnonymousGroupMessage<MessageContent>
@PreviewFeature
inline fun Message.requireAnonymousGroupMessage(): AnonymousGroupMessage<MessageContent> = this as AnonymousGroupMessage<MessageContent>
@PreviewFeature
inline fun Message.asChannelMessage(): ChannelMessage<MessageContent>? = this as? ChannelMessage<MessageContent>
@PreviewFeature
inline fun Message.requireChannelMessage(): ChannelMessage<MessageContent> = this as ChannelMessage<MessageContent>
@PreviewFeature
inline fun Message.asChatEventMessage(): ChatEventMessage<ChatEvent>? = this as? ChatEventMessage<ChatEvent>
@PreviewFeature
inline fun Message.requireChatEventMessage(): ChatEventMessage<ChatEvent> = this as ChatEventMessage<ChatEvent>
@PreviewFeature
inline fun Message.asCommonGroupMessage(): CommonGroupMessage<MessageContent>? = this as? CommonGroupMessage<MessageContent>
@PreviewFeature
inline fun Message.requireCommonGroupMessage(): CommonGroupMessage<MessageContent> = this as CommonGroupMessage<MessageContent>
@PreviewFeature
inline fun Message.asCommonMessage(): CommonMessage<MessageContent>? = this as? CommonMessage<MessageContent>
@PreviewFeature
inline fun Message.requireCommonMessage(): CommonMessage<MessageContent> = this as CommonMessage<MessageContent>
@PreviewFeature
inline fun Message.asContentMessage(): ContentMessage<MessageContent>? = this as? ContentMessage<MessageContent>
@PreviewFeature
inline fun Message.requireContentMessage(): ContentMessage<MessageContent> = this as ContentMessage<MessageContent>
@PreviewFeature
inline fun Message.asFromChannelGroupMessage(): FromChannelGroupMessage<MessageContent>? = this as? FromChannelGroupMessage<MessageContent>
@PreviewFeature
inline fun Message.requireFromChannelGroupMessage(): FromChannelGroupMessage<MessageContent> = this as FromChannelGroupMessage<MessageContent>
@PreviewFeature
inline fun Message.asGroupEventMessage(): GroupEventMessage<GroupEvent>? = this as? GroupEventMessage<GroupEvent>
@PreviewFeature
inline fun Message.requireGroupEventMessage(): GroupEventMessage<GroupEvent> = this as GroupEventMessage<GroupEvent>
@PreviewFeature
inline fun Message.asGroupMessage(): GroupMessage<MessageContent>? = this as? GroupMessage<MessageContent>
@PreviewFeature
inline fun Message.requireGroupMessage(): GroupMessage<MessageContent> = this as GroupMessage<MessageContent>
@PreviewFeature
inline fun Message.asMediaGroupMessage(): MediaGroupMessage? = this as? MediaGroupMessage
@PreviewFeature
inline fun Message.requireMediaGroupMessage(): MediaGroupMessage = this as MediaGroupMessage
@PreviewFeature
inline fun Message.asPossiblyEditedMessage(): PossiblyEditedMessage? = this as? PossiblyEditedMessage
@PreviewFeature
inline fun Message.requirePossiblyEditedMessage(): PossiblyEditedMessage = this as PossiblyEditedMessage
@PreviewFeature
inline fun Message.asPossiblyForwardedMessage(): PossiblyForwardedMessage? = this as? PossiblyForwardedMessage
@PreviewFeature
inline fun Message.requirePossiblyForwardedMessage(): PossiblyForwardedMessage = this as PossiblyForwardedMessage
@PreviewFeature
inline fun Message.asPossiblyPaymentMessage(): PossiblyPaymentMessage? = this as? PossiblyPaymentMessage
@PreviewFeature
inline fun Message.requirePossiblyPaymentMessage(): PossiblyPaymentMessage = this as PossiblyPaymentMessage
@PreviewFeature
inline fun Message.asPrivateMessage(): PrivateMessage<MessageContent>? = this as? PrivateMessage<MessageContent>
@PreviewFeature
inline fun Message.requirePrivateMessage(): PrivateMessage<MessageContent> = this as PrivateMessage<MessageContent>
@PreviewFeature
inline fun Message.asPublicMessage(): PublicMessage<MessageContent>? = this as? PublicMessage<MessageContent>
@PreviewFeature
inline fun Message.requirePublicMessage(): PublicMessage<MessageContent> = this as PublicMessage<MessageContent>
@PreviewFeature
inline fun Message.asSignedMessage(): SignedMessage? = this as? SignedMessage
@PreviewFeature
inline fun Message.requireSignedMessage(): SignedMessage = this as SignedMessage
@PreviewFeature
inline fun Message.asSupergroupEventMessage(): SupergroupEventMessage<SupergroupEvent>? = this as? SupergroupEventMessage<SupergroupEvent>
@PreviewFeature
inline fun Message.requireSupergroupEventMessage(): SupergroupEventMessage<SupergroupEvent> = this as SupergroupEventMessage<SupergroupEvent>
@PreviewFeature
inline fun Message.asUnknownMessageType(): UnknownMessageType? = this as? UnknownMessageType
@PreviewFeature
inline fun Message.requireUnknownMessageType(): UnknownMessageType = this as UnknownMessageType
@PreviewFeature
inline fun Message.asPossiblySentViaBotCommonMessage(): PossiblySentViaBotCommonMessage<MessageContent>? = this as? PossiblySentViaBotCommonMessage<MessageContent>
@PreviewFeature
inline fun Message.requirePossiblySentViaBotCommonMessage(): PossiblySentViaBotCommonMessage<MessageContent> = this as PossiblySentViaBotCommonMessage<MessageContent>
@PreviewFeature
inline fun BotAction.asFindLocationAction(): FindLocationAction? = this as? FindLocationAction
@PreviewFeature
inline fun BotAction.requireFindLocationAction(): FindLocationAction = this as FindLocationAction
@PreviewFeature
inline fun BotAction.asRecordAudioAction(): RecordAudioAction? = this as? RecordAudioAction
@PreviewFeature
inline fun BotAction.requireRecordAudioAction(): RecordAudioAction = this as RecordAudioAction
@PreviewFeature
inline fun BotAction.asRecordVideoAction(): RecordVideoAction? = this as? RecordVideoAction
@PreviewFeature
inline fun BotAction.requireRecordVideoAction(): RecordVideoAction = this as RecordVideoAction
@PreviewFeature
inline fun BotAction.asRecordVideoNoteAction(): RecordVideoNoteAction? = this as? RecordVideoNoteAction
@PreviewFeature
inline fun BotAction.requireRecordVideoNoteAction(): RecordVideoNoteAction = this as RecordVideoNoteAction
@PreviewFeature
inline fun BotAction.asTypingAction(): TypingAction? = this as? TypingAction
@PreviewFeature
inline fun BotAction.requireTypingAction(): TypingAction = this as TypingAction
@PreviewFeature
inline fun BotAction.asUploadAudioAction(): UploadAudioAction? = this as? UploadAudioAction
@PreviewFeature
inline fun BotAction.requireUploadAudioAction(): UploadAudioAction = this as UploadAudioAction
@PreviewFeature
inline fun BotAction.asUploadDocumentAction(): UploadDocumentAction? = this as? UploadDocumentAction
@PreviewFeature
inline fun BotAction.requireUploadDocumentAction(): UploadDocumentAction = this as UploadDocumentAction
@PreviewFeature
inline fun BotAction.asUploadPhotoAction(): UploadPhotoAction? = this as? UploadPhotoAction
@PreviewFeature
inline fun BotAction.requireUploadPhotoAction(): UploadPhotoAction = this as UploadPhotoAction
@PreviewFeature
inline fun BotAction.asUploadVideoAction(): UploadVideoAction? = this as? UploadVideoAction
@PreviewFeature
inline fun BotAction.requireUploadVideoAction(): UploadVideoAction = this as UploadVideoAction
@PreviewFeature
inline fun BotAction.asUploadVideoNoteAction(): UploadVideoNoteAction? = this as? UploadVideoNoteAction
@PreviewFeature
inline fun BotAction.requireUploadVideoNoteAction(): UploadVideoNoteAction = this as UploadVideoNoteAction
@PreviewFeature
inline fun InlineQuery.asBaseInlineQuery(): BaseInlineQuery? = this as? BaseInlineQuery
@PreviewFeature
inline fun InlineQuery.requireBaseInlineQuery(): BaseInlineQuery = this as BaseInlineQuery
@PreviewFeature
inline fun InlineQuery.asLocationInlineQuery(): LocationInlineQuery? = this as? LocationInlineQuery
@PreviewFeature
inline fun InlineQuery.requireLocationInlineQuery(): LocationInlineQuery = this as LocationInlineQuery
@PreviewFeature
inline fun InputMessageContent.asInputContactMessageContent(): InputContactMessageContent? = this as? InputContactMessageContent
@PreviewFeature
inline fun InputMessageContent.requireInputContactMessageContent(): InputContactMessageContent = this as InputContactMessageContent
@PreviewFeature
inline fun InputMessageContent.asInputLocationMessageContent(): InputLocationMessageContent? = this as? InputLocationMessageContent
@PreviewFeature
inline fun InputMessageContent.requireInputLocationMessageContent(): InputLocationMessageContent = this as InputLocationMessageContent
@PreviewFeature
inline fun InputMessageContent.asInputTextMessageContent(): InputTextMessageContent? = this as? InputTextMessageContent
@PreviewFeature
inline fun InputMessageContent.requireInputTextMessageContent(): InputTextMessageContent = this as InputTextMessageContent
@PreviewFeature
inline fun InputMessageContent.asInputVenueMessageContent(): InputVenueMessageContent? = this as? InputVenueMessageContent
@PreviewFeature
inline fun InputMessageContent.requireInputVenueMessageContent(): InputVenueMessageContent = this as InputVenueMessageContent
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultArticle(): InlineQueryResultArticle? = this as? InlineQueryResultArticle
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultArticle(): InlineQueryResultArticle = this as InlineQueryResultArticle
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultContact(): InlineQueryResultContact? = this as? InlineQueryResultContact
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultContact(): InlineQueryResultContact = this as InlineQueryResultContact
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultGame(): InlineQueryResultGame? = this as? InlineQueryResultGame
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultGame(): InlineQueryResultGame = this as InlineQueryResultGame
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultLocation(): InlineQueryResultLocation? = this as? InlineQueryResultLocation
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultLocation(): InlineQueryResultLocation = this as InlineQueryResultLocation
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultStickerCached(): InlineQueryResultStickerCached? = this as? InlineQueryResultStickerCached
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultStickerCached(): InlineQueryResultStickerCached = this as InlineQueryResultStickerCached
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultVenue(): InlineQueryResultVenue? = this as? InlineQueryResultVenue
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultVenue(): InlineQueryResultVenue = this as InlineQueryResultVenue
@PreviewFeature
inline fun InlineQueryResult.asDescribedInlineQueryResult(): DescribedInlineQueryResult? = this as? DescribedInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireDescribedInlineQueryResult(): DescribedInlineQueryResult = this as DescribedInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asFileInlineQueryResult(): FileInlineQueryResult? = this as? FileInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireFileInlineQueryResult(): FileInlineQueryResult = this as FileInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asOptionallyTitledInlineQueryResult(): OptionallyTitledInlineQueryResult? = this as? OptionallyTitledInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireOptionallyTitledInlineQueryResult(): OptionallyTitledInlineQueryResult = this as OptionallyTitledInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asSizedInlineQueryResult(): SizedInlineQueryResult? = this as? SizedInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireSizedInlineQueryResult(): SizedInlineQueryResult = this as SizedInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asThumbSizedInlineQueryResult(): ThumbSizedInlineQueryResult? = this as? ThumbSizedInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireThumbSizedInlineQueryResult(): ThumbSizedInlineQueryResult = this as ThumbSizedInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asThumbedInlineQueryResult(): ThumbedInlineQueryResult? = this as? ThumbedInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireThumbedInlineQueryResult(): ThumbedInlineQueryResult = this as ThumbedInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asThumbedWithMimeTypeInlineQueryResult(): ThumbedWithMimeTypeInlineQueryResult? = this as? ThumbedWithMimeTypeInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireThumbedWithMimeTypeInlineQueryResult(): ThumbedWithMimeTypeInlineQueryResult = this as ThumbedWithMimeTypeInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asTitledInlineQueryResult(): TitledInlineQueryResult? = this as? TitledInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireTitledInlineQueryResult(): TitledInlineQueryResult = this as TitledInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asUrlInlineQueryResult(): UrlInlineQueryResult? = this as? UrlInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireUrlInlineQueryResult(): UrlInlineQueryResult = this as UrlInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asWithInputMessageContentInlineQueryResult(): WithInputMessageContentInlineQueryResult? = this as? WithInputMessageContentInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.requireWithInputMessageContentInlineQueryResult(): WithInputMessageContentInlineQueryResult = this as WithInputMessageContentInlineQueryResult
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultAudio(): InlineQueryResultAudio? = this as? InlineQueryResultAudio
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultAudio(): InlineQueryResultAudio = this as InlineQueryResultAudio
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultAudioCached(): InlineQueryResultAudioCached? = this as? InlineQueryResultAudioCached
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultAudioCached(): InlineQueryResultAudioCached = this as InlineQueryResultAudioCached
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultAudioCommon(): InlineQueryResultAudioCommon? = this as? InlineQueryResultAudioCommon
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultAudioCommon(): InlineQueryResultAudioCommon = this as InlineQueryResultAudioCommon
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultDocument(): InlineQueryResultDocument? = this as? InlineQueryResultDocument
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultDocument(): InlineQueryResultDocument = this as InlineQueryResultDocument
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultDocumentCached(): InlineQueryResultDocumentCached? = this as? InlineQueryResultDocumentCached
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultDocumentCached(): InlineQueryResultDocumentCached = this as InlineQueryResultDocumentCached
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultDocumentCommon(): InlineQueryResultDocumentCommon? = this as? InlineQueryResultDocumentCommon
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultDocumentCommon(): InlineQueryResultDocumentCommon = this as InlineQueryResultDocumentCommon
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultGif(): InlineQueryResultGif? = this as? InlineQueryResultGif
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultGif(): InlineQueryResultGif = this as InlineQueryResultGif
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultGifCached(): InlineQueryResultGifCached? = this as? InlineQueryResultGifCached
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultGifCached(): InlineQueryResultGifCached = this as InlineQueryResultGifCached
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultGifCommon(): InlineQueryResultGifCommon? = this as? InlineQueryResultGifCommon
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultGifCommon(): InlineQueryResultGifCommon = this as InlineQueryResultGifCommon
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultMpeg4Gif(): InlineQueryResultMpeg4Gif? = this as? InlineQueryResultMpeg4Gif
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultMpeg4Gif(): InlineQueryResultMpeg4Gif = this as InlineQueryResultMpeg4Gif
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultMpeg4GifCached(): InlineQueryResultMpeg4GifCached? = this as? InlineQueryResultMpeg4GifCached
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultMpeg4GifCached(): InlineQueryResultMpeg4GifCached = this as InlineQueryResultMpeg4GifCached
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultMpeg4GifCommon(): InlineQueryResultMpeg4GifCommon? = this as? InlineQueryResultMpeg4GifCommon
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultMpeg4GifCommon(): InlineQueryResultMpeg4GifCommon = this as InlineQueryResultMpeg4GifCommon
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultPhoto(): InlineQueryResultPhoto? = this as? InlineQueryResultPhoto
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultPhoto(): InlineQueryResultPhoto = this as InlineQueryResultPhoto
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultPhotoCached(): InlineQueryResultPhotoCached? = this as? InlineQueryResultPhotoCached
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultPhotoCached(): InlineQueryResultPhotoCached = this as InlineQueryResultPhotoCached
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultPhotoCommon(): InlineQueryResultPhotoCommon? = this as? InlineQueryResultPhotoCommon
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultPhotoCommon(): InlineQueryResultPhotoCommon = this as InlineQueryResultPhotoCommon
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultVideo(): InlineQueryResultVideo? = this as? InlineQueryResultVideo
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultVideo(): InlineQueryResultVideo = this as InlineQueryResultVideo
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultVideoCached(): InlineQueryResultVideoCached? = this as? InlineQueryResultVideoCached
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultVideoCached(): InlineQueryResultVideoCached = this as InlineQueryResultVideoCached
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultVideoCommon(): InlineQueryResultVideoCommon? = this as? InlineQueryResultVideoCommon
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultVideoCommon(): InlineQueryResultVideoCommon = this as InlineQueryResultVideoCommon
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultVoice(): InlineQueryResultVoice? = this as? InlineQueryResultVoice
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultVoice(): InlineQueryResultVoice = this as InlineQueryResultVoice
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultVoiceCached(): InlineQueryResultVoiceCached? = this as? InlineQueryResultVoiceCached
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultVoiceCached(): InlineQueryResultVoiceCached = this as InlineQueryResultVoiceCached
@PreviewFeature
inline fun InlineQueryResult.asInlineQueryResultVoiceCommon(): InlineQueryResultVoiceCommon? = this as? InlineQueryResultVoiceCommon
@PreviewFeature
inline fun InlineQueryResult.requireInlineQueryResultVoiceCommon(): InlineQueryResultVoiceCommon = this as InlineQueryResultVoiceCommon
@PreviewFeature
inline fun ChatMember.asCreatorChatMember(): CreatorChatMember? = this as? CreatorChatMember
@PreviewFeature
inline fun ChatMember.requireCreatorChatMember(): CreatorChatMember = this as CreatorChatMember
@PreviewFeature
inline fun ChatMember.asKickedChatMember(): KickedChatMember? = this as? KickedChatMember
@PreviewFeature
inline fun ChatMember.requireKickedChatMember(): KickedChatMember = this as KickedChatMember
@PreviewFeature
inline fun ChatMember.asLeftChatMember(): LeftChatMember? = this as? LeftChatMember
@PreviewFeature
inline fun ChatMember.requireLeftChatMember(): LeftChatMember = this as LeftChatMember
@PreviewFeature
inline fun ChatMember.asMemberChatMember(): MemberChatMember? = this as? MemberChatMember
@PreviewFeature
inline fun ChatMember.requireMemberChatMember(): MemberChatMember = this as MemberChatMember
@PreviewFeature
inline fun ChatMember.asRestrictedChatMember(): RestrictedChatMember? = this as? RestrictedChatMember
@PreviewFeature
inline fun ChatMember.requireRestrictedChatMember(): RestrictedChatMember = this as RestrictedChatMember
@PreviewFeature
inline fun ChatMember.asAdministratorChatMember(): AdministratorChatMember? = this as? AdministratorChatMember
@PreviewFeature
inline fun ChatMember.requireAdministratorChatMember(): AdministratorChatMember = this as AdministratorChatMember
@PreviewFeature
inline fun ChatMember.asBannedChatMember(): BannedChatMember? = this as? BannedChatMember
@PreviewFeature
inline fun ChatMember.requireBannedChatMember(): BannedChatMember = this as BannedChatMember
@PreviewFeature
inline fun ChatMember.asSpecialRightsChatMember(): SpecialRightsChatMember? = this as? SpecialRightsChatMember
@PreviewFeature
inline fun ChatMember.requireSpecialRightsChatMember(): SpecialRightsChatMember = this as SpecialRightsChatMember
@PreviewFeature
inline fun InputMedia.asAudioMediaGroupMemberInputMedia(): AudioMediaGroupMemberInputMedia? = this as? AudioMediaGroupMemberInputMedia
@PreviewFeature
inline fun InputMedia.requireAudioMediaGroupMemberInputMedia(): AudioMediaGroupMemberInputMedia = this as AudioMediaGroupMemberInputMedia
@PreviewFeature
inline fun InputMedia.asDocumentMediaGroupMemberInputMedia(): DocumentMediaGroupMemberInputMedia? = this as? DocumentMediaGroupMemberInputMedia
@PreviewFeature
inline fun InputMedia.requireDocumentMediaGroupMemberInputMedia(): DocumentMediaGroupMemberInputMedia = this as DocumentMediaGroupMemberInputMedia
@PreviewFeature
inline fun InputMedia.asDuratedInputMedia(): DuratedInputMedia? = this as? DuratedInputMedia
@PreviewFeature
inline fun InputMedia.requireDuratedInputMedia(): DuratedInputMedia = this as DuratedInputMedia
@PreviewFeature
inline fun InputMedia.asInputMediaAnimation(): InputMediaAnimation? = this as? InputMediaAnimation
@PreviewFeature
inline fun InputMedia.requireInputMediaAnimation(): InputMediaAnimation = this as InputMediaAnimation
@PreviewFeature
inline fun InputMedia.asInputMediaAudio(): InputMediaAudio? = this as? InputMediaAudio
@PreviewFeature
inline fun InputMedia.requireInputMediaAudio(): InputMediaAudio = this as InputMediaAudio
@PreviewFeature
inline fun InputMedia.asInputMediaDocument(): InputMediaDocument? = this as? InputMediaDocument
@PreviewFeature
inline fun InputMedia.requireInputMediaDocument(): InputMediaDocument = this as InputMediaDocument
@PreviewFeature
inline fun InputMedia.asInputMediaPhoto(): InputMediaPhoto? = this as? InputMediaPhoto
@PreviewFeature
inline fun InputMedia.requireInputMediaPhoto(): InputMediaPhoto = this as InputMediaPhoto
@PreviewFeature
inline fun InputMedia.asInputMediaVideo(): InputMediaVideo? = this as? InputMediaVideo
@PreviewFeature
inline fun InputMedia.requireInputMediaVideo(): InputMediaVideo = this as InputMediaVideo
@PreviewFeature
inline fun InputMedia.asMediaGroupMemberInputMedia(): MediaGroupMemberInputMedia? = this as? MediaGroupMemberInputMedia
@PreviewFeature
inline fun InputMedia.requireMediaGroupMemberInputMedia(): MediaGroupMemberInputMedia = this as MediaGroupMemberInputMedia
@PreviewFeature
inline fun InputMedia.asSizedInputMedia(): SizedInputMedia? = this as? SizedInputMedia
@PreviewFeature
inline fun InputMedia.requireSizedInputMedia(): SizedInputMedia = this as SizedInputMedia
@PreviewFeature
inline fun InputMedia.asThumbedInputMedia(): ThumbedInputMedia? = this as? ThumbedInputMedia
@PreviewFeature
inline fun InputMedia.requireThumbedInputMedia(): ThumbedInputMedia = this as ThumbedInputMedia
@PreviewFeature
inline fun InputMedia.asTitledInputMedia(): TitledInputMedia? = this as? TitledInputMedia
@PreviewFeature
inline fun InputMedia.requireTitledInputMedia(): TitledInputMedia = this as TitledInputMedia
@PreviewFeature
inline fun InputMedia.asVisualMediaGroupMemberInputMedia(): VisualMediaGroupMemberInputMedia? = this as? VisualMediaGroupMemberInputMedia
@PreviewFeature
inline fun InputMedia.requireVisualMediaGroupMemberInputMedia(): VisualMediaGroupMemberInputMedia = this as VisualMediaGroupMemberInputMedia
@PreviewFeature
inline fun Update.asCallbackQueryUpdate(): CallbackQueryUpdate? = this as? CallbackQueryUpdate
@PreviewFeature
inline fun Update.requireCallbackQueryUpdate(): CallbackQueryUpdate = this as CallbackQueryUpdate
@PreviewFeature
inline fun Update.asChannelPostUpdate(): ChannelPostUpdate? = this as? ChannelPostUpdate
@PreviewFeature
inline fun Update.requireChannelPostUpdate(): ChannelPostUpdate = this as ChannelPostUpdate
@PreviewFeature
inline fun Update.asChosenInlineResultUpdate(): ChosenInlineResultUpdate? = this as? ChosenInlineResultUpdate
@PreviewFeature
inline fun Update.requireChosenInlineResultUpdate(): ChosenInlineResultUpdate = this as ChosenInlineResultUpdate
@PreviewFeature
inline fun Update.asEditChannelPostUpdate(): EditChannelPostUpdate? = this as? EditChannelPostUpdate
@PreviewFeature
inline fun Update.requireEditChannelPostUpdate(): EditChannelPostUpdate = this as EditChannelPostUpdate
@PreviewFeature
inline fun Update.asEditMessageUpdate(): EditMessageUpdate? = this as? EditMessageUpdate
@PreviewFeature
inline fun Update.requireEditMessageUpdate(): EditMessageUpdate = this as EditMessageUpdate
@PreviewFeature
inline fun Update.asInlineQueryUpdate(): InlineQueryUpdate? = this as? InlineQueryUpdate
@PreviewFeature
inline fun Update.requireInlineQueryUpdate(): InlineQueryUpdate = this as InlineQueryUpdate
@PreviewFeature
inline fun Update.asChannelPostMediaGroupUpdate(): ChannelPostMediaGroupUpdate? = this as? ChannelPostMediaGroupUpdate
@PreviewFeature
inline fun Update.requireChannelPostMediaGroupUpdate(): ChannelPostMediaGroupUpdate = this as ChannelPostMediaGroupUpdate
@PreviewFeature
inline fun Update.asEditChannelPostMediaGroupUpdate(): EditChannelPostMediaGroupUpdate? = this as? EditChannelPostMediaGroupUpdate
@PreviewFeature
inline fun Update.requireEditChannelPostMediaGroupUpdate(): EditChannelPostMediaGroupUpdate = this as EditChannelPostMediaGroupUpdate
@PreviewFeature
inline fun Update.asEditMediaGroupUpdate(): EditMediaGroupUpdate? = this as? EditMediaGroupUpdate
@PreviewFeature
inline fun Update.requireEditMediaGroupUpdate(): EditMediaGroupUpdate = this as EditMediaGroupUpdate
@PreviewFeature
inline fun Update.asEditMessageMediaGroupUpdate(): EditMessageMediaGroupUpdate? = this as? EditMessageMediaGroupUpdate
@PreviewFeature
inline fun Update.requireEditMessageMediaGroupUpdate(): EditMessageMediaGroupUpdate = this as EditMessageMediaGroupUpdate
@PreviewFeature
inline fun Update.asMediaGroupUpdate(): MediaGroupUpdate? = this as? MediaGroupUpdate
@PreviewFeature
inline fun Update.requireMediaGroupUpdate(): MediaGroupUpdate = this as MediaGroupUpdate
@PreviewFeature
inline fun Update.asMessageMediaGroupUpdate(): MessageMediaGroupUpdate? = this as? MessageMediaGroupUpdate
@PreviewFeature
inline fun Update.requireMessageMediaGroupUpdate(): MessageMediaGroupUpdate = this as MessageMediaGroupUpdate
@PreviewFeature
inline fun Update.asSentMediaGroupUpdate(): SentMediaGroupUpdate? = this as? SentMediaGroupUpdate
@PreviewFeature
inline fun Update.requireSentMediaGroupUpdate(): SentMediaGroupUpdate = this as SentMediaGroupUpdate
@PreviewFeature
inline fun Update.asMessageUpdate(): MessageUpdate? = this as? MessageUpdate
@PreviewFeature
inline fun Update.requireMessageUpdate(): MessageUpdate = this as MessageUpdate
@PreviewFeature
inline fun Update.asPollAnswerUpdate(): PollAnswerUpdate? = this as? PollAnswerUpdate
@PreviewFeature
inline fun Update.requirePollAnswerUpdate(): PollAnswerUpdate = this as PollAnswerUpdate
@PreviewFeature
inline fun Update.asPollUpdate(): PollUpdate? = this as? PollUpdate
@PreviewFeature
inline fun Update.requirePollUpdate(): PollUpdate = this as PollUpdate
@PreviewFeature
inline fun Update.asPreCheckoutQueryUpdate(): PreCheckoutQueryUpdate? = this as? PreCheckoutQueryUpdate
@PreviewFeature
inline fun Update.requirePreCheckoutQueryUpdate(): PreCheckoutQueryUpdate = this as PreCheckoutQueryUpdate
@PreviewFeature
inline fun Update.asShippingQueryUpdate(): ShippingQueryUpdate? = this as? ShippingQueryUpdate
@PreviewFeature
inline fun Update.requireShippingQueryUpdate(): ShippingQueryUpdate = this as ShippingQueryUpdate
@PreviewFeature
inline fun Update.asBaseEditMessageUpdate(): BaseEditMessageUpdate? = this as? BaseEditMessageUpdate
@PreviewFeature
inline fun Update.requireBaseEditMessageUpdate(): BaseEditMessageUpdate = this as BaseEditMessageUpdate
@PreviewFeature
inline fun Update.asBaseMessageUpdate(): BaseMessageUpdate? = this as? BaseMessageUpdate
@PreviewFeature
inline fun Update.requireBaseMessageUpdate(): BaseMessageUpdate = this as BaseMessageUpdate
@PreviewFeature
inline fun Update.asBaseSentMessageUpdate(): BaseSentMessageUpdate? = this as? BaseSentMessageUpdate
@PreviewFeature
inline fun Update.requireBaseSentMessageUpdate(): BaseSentMessageUpdate = this as BaseSentMessageUpdate
@PreviewFeature
inline fun Update.asUnknownUpdate(): UnknownUpdate? = this as? UnknownUpdate
@PreviewFeature
inline fun Update.requireUnknownUpdate(): UnknownUpdate = this as UnknownUpdate
@PreviewFeature
inline fun TelegramMediaFile.asAnimationFile(): AnimationFile? = this as? AnimationFile
@PreviewFeature
inline fun TelegramMediaFile.requireAnimationFile(): AnimationFile = this as AnimationFile
@PreviewFeature
inline fun TelegramMediaFile.asAudioFile(): AudioFile? = this as? AudioFile
@PreviewFeature
inline fun TelegramMediaFile.requireAudioFile(): AudioFile = this as AudioFile
@PreviewFeature
inline fun TelegramMediaFile.asDocumentFile(): DocumentFile? = this as? DocumentFile
@PreviewFeature
inline fun TelegramMediaFile.requireDocumentFile(): DocumentFile = this as DocumentFile
@PreviewFeature
inline fun TelegramMediaFile.asFile(): File? = this as? File
@PreviewFeature
inline fun TelegramMediaFile.requireFile(): File = this as File
@PreviewFeature
inline fun TelegramMediaFile.asPathedFile(): PathedFile? = this as? PathedFile
@PreviewFeature
inline fun TelegramMediaFile.requirePathedFile(): PathedFile = this as PathedFile
@PreviewFeature
inline fun TelegramMediaFile.asPhotoSize(): PhotoSize? = this as? PhotoSize
@PreviewFeature
inline fun TelegramMediaFile.requirePhotoSize(): PhotoSize = this as PhotoSize
@PreviewFeature
inline fun TelegramMediaFile.asSticker(): Sticker? = this as? Sticker
@PreviewFeature
inline fun TelegramMediaFile.requireSticker(): Sticker = this as Sticker
@PreviewFeature
inline fun TelegramMediaFile.asVideoFile(): VideoFile? = this as? VideoFile
@PreviewFeature
inline fun TelegramMediaFile.requireVideoFile(): VideoFile = this as VideoFile
@PreviewFeature
inline fun TelegramMediaFile.asVideoNoteFile(): VideoNoteFile? = this as? VideoNoteFile
@PreviewFeature
inline fun TelegramMediaFile.requireVideoNoteFile(): VideoNoteFile = this as VideoNoteFile
@PreviewFeature
inline fun TelegramMediaFile.asVoiceFile(): VoiceFile? = this as? VoiceFile
@PreviewFeature
inline fun TelegramMediaFile.requireVoiceFile(): VoiceFile = this as VoiceFile
@PreviewFeature
inline fun TelegramMediaFile.asMimedMediaFile(): MimedMediaFile? = this as? MimedMediaFile
@PreviewFeature
inline fun TelegramMediaFile.requireMimedMediaFile(): MimedMediaFile = this as MimedMediaFile
@PreviewFeature
inline fun TelegramMediaFile.asPlayableMediaFile(): PlayableMediaFile? = this as? PlayableMediaFile
@PreviewFeature
inline fun TelegramMediaFile.requirePlayableMediaFile(): PlayableMediaFile = this as PlayableMediaFile
@PreviewFeature
inline fun TelegramMediaFile.asSizedMediaFile(): SizedMediaFile? = this as? SizedMediaFile
@PreviewFeature
inline fun TelegramMediaFile.requireSizedMediaFile(): SizedMediaFile = this as SizedMediaFile
@PreviewFeature
inline fun TelegramMediaFile.asThumbedMediaFile(): ThumbedMediaFile? = this as? ThumbedMediaFile
@PreviewFeature
inline fun TelegramMediaFile.requireThumbedMediaFile(): ThumbedMediaFile = this as ThumbedMediaFile
@PreviewFeature
inline fun KeyboardMarkup.asForceReply(): ForceReply? = this as? ForceReply
@PreviewFeature
inline fun KeyboardMarkup.requireForceReply(): ForceReply = this as ForceReply
@PreviewFeature
inline fun KeyboardMarkup.asInlineKeyboardMarkup(): InlineKeyboardMarkup? = this as? InlineKeyboardMarkup
@PreviewFeature
inline fun KeyboardMarkup.requireInlineKeyboardMarkup(): InlineKeyboardMarkup = this as InlineKeyboardMarkup
@PreviewFeature
inline fun KeyboardMarkup.asReplyKeyboardMarkup(): ReplyKeyboardMarkup? = this as? ReplyKeyboardMarkup
@PreviewFeature
inline fun KeyboardMarkup.requireReplyKeyboardMarkup(): ReplyKeyboardMarkup = this as ReplyKeyboardMarkup
@PreviewFeature
inline fun KeyboardMarkup.asReplyKeyboardRemove(): ReplyKeyboardRemove? = this as? ReplyKeyboardRemove
@PreviewFeature
inline fun KeyboardMarkup.requireReplyKeyboardRemove(): ReplyKeyboardRemove = this as ReplyKeyboardRemove
@PreviewFeature
inline fun InlineKeyboardButton.asCallbackDataInlineKeyboardButton(): CallbackDataInlineKeyboardButton? = this as? CallbackDataInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.requireCallbackDataInlineKeyboardButton(): CallbackDataInlineKeyboardButton = this as CallbackDataInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.asCallbackGameInlineKeyboardButton(): CallbackGameInlineKeyboardButton? = this as? CallbackGameInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.requireCallbackGameInlineKeyboardButton(): CallbackGameInlineKeyboardButton = this as CallbackGameInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.asLoginURLInlineKeyboardButton(): LoginURLInlineKeyboardButton? = this as? LoginURLInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.requireLoginURLInlineKeyboardButton(): LoginURLInlineKeyboardButton = this as LoginURLInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.asPayInlineKeyboardButton(): PayInlineKeyboardButton? = this as? PayInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.requirePayInlineKeyboardButton(): PayInlineKeyboardButton = this as PayInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.asSwitchInlineQueryCurrentChatInlineKeyboardButton(): SwitchInlineQueryCurrentChatInlineKeyboardButton? = this as? SwitchInlineQueryCurrentChatInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.requireSwitchInlineQueryCurrentChatInlineKeyboardButton(): SwitchInlineQueryCurrentChatInlineKeyboardButton = this as SwitchInlineQueryCurrentChatInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.asSwitchInlineQueryInlineKeyboardButton(): SwitchInlineQueryInlineKeyboardButton? = this as? SwitchInlineQueryInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.requireSwitchInlineQueryInlineKeyboardButton(): SwitchInlineQueryInlineKeyboardButton = this as SwitchInlineQueryInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.asURLInlineKeyboardButton(): URLInlineKeyboardButton? = this as? URLInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.requireURLInlineKeyboardButton(): URLInlineKeyboardButton = this as URLInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.asUnknownInlineKeyboardButton(): UnknownInlineKeyboardButton? = this as? UnknownInlineKeyboardButton
@PreviewFeature
inline fun InlineKeyboardButton.requireUnknownInlineKeyboardButton(): UnknownInlineKeyboardButton = this as UnknownInlineKeyboardButton
@PreviewFeature
inline fun Poll.asMultipleAnswersPoll(): MultipleAnswersPoll? = this as? MultipleAnswersPoll
@PreviewFeature
inline fun Poll.requireMultipleAnswersPoll(): MultipleAnswersPoll = this as MultipleAnswersPoll
@PreviewFeature
inline fun Poll.asQuizPoll(): QuizPoll? = this as? QuizPoll
@PreviewFeature
inline fun Poll.requireQuizPoll(): QuizPoll = this as QuizPoll
@PreviewFeature
inline fun Poll.asRegularPoll(): RegularPoll? = this as? RegularPoll
@PreviewFeature
inline fun Poll.requireRegularPoll(): RegularPoll = this as RegularPoll
@PreviewFeature
inline fun Poll.asUnknownPollType(): UnknownPollType? = this as? UnknownPollType
@PreviewFeature
inline fun Poll.requireUnknownPollType(): UnknownPollType = this as UnknownPollType
@PreviewFeature
inline fun ResendableContent.asContactContent(): ContactContent? = this as? ContactContent
@PreviewFeature
inline fun ResendableContent.requireContactContent(): ContactContent = this as ContactContent
@PreviewFeature
inline fun ResendableContent.asDiceContent(): DiceContent? = this as? DiceContent
@PreviewFeature
inline fun ResendableContent.requireDiceContent(): DiceContent = this as DiceContent
@PreviewFeature
inline fun ResendableContent.asGameContent(): GameContent? = this as? GameContent
@PreviewFeature
inline fun ResendableContent.requireGameContent(): GameContent = this as GameContent
@PreviewFeature
inline fun ResendableContent.asLocationContent(): LocationContent? = this as? LocationContent
@PreviewFeature
inline fun ResendableContent.requireLocationContent(): LocationContent = this as LocationContent
@PreviewFeature
inline fun ResendableContent.asPollContent(): PollContent? = this as? PollContent
@PreviewFeature
inline fun ResendableContent.requirePollContent(): PollContent = this as PollContent
@PreviewFeature
inline fun ResendableContent.asTextContent(): TextContent? = this as? TextContent
@PreviewFeature
inline fun ResendableContent.requireTextContent(): TextContent = this as TextContent
@PreviewFeature
inline fun ResendableContent.asVenueContent(): VenueContent? = this as? VenueContent
@PreviewFeature
inline fun ResendableContent.requireVenueContent(): VenueContent = this as VenueContent
@PreviewFeature
inline fun ResendableContent.asAudioMediaGroupContent(): AudioMediaGroupContent? = this as? AudioMediaGroupContent
@PreviewFeature
inline fun ResendableContent.requireAudioMediaGroupContent(): AudioMediaGroupContent = this as AudioMediaGroupContent
@PreviewFeature
inline fun ResendableContent.asDocumentMediaGroupContent(): DocumentMediaGroupContent? = this as? DocumentMediaGroupContent
@PreviewFeature
inline fun ResendableContent.requireDocumentMediaGroupContent(): DocumentMediaGroupContent = this as DocumentMediaGroupContent
@PreviewFeature
inline fun ResendableContent.asMediaCollectionContent(): MediaCollectionContent<TelegramMediaFile>? = this as? MediaCollectionContent<TelegramMediaFile>
@PreviewFeature
inline fun ResendableContent.requireMediaCollectionContent(): MediaCollectionContent<TelegramMediaFile> = this as MediaCollectionContent<TelegramMediaFile>
@PreviewFeature
inline fun ResendableContent.asMediaContent(): MediaContent? = this as? MediaContent
@PreviewFeature
inline fun ResendableContent.requireMediaContent(): MediaContent = this as MediaContent
@PreviewFeature
inline fun ResendableContent.asMediaGroupContent(): MediaGroupContent? = this as? MediaGroupContent
@PreviewFeature
inline fun ResendableContent.requireMediaGroupContent(): MediaGroupContent = this as MediaGroupContent
@PreviewFeature
inline fun ResendableContent.asMessageContent(): MessageContent? = this as? MessageContent
@PreviewFeature
inline fun ResendableContent.requireMessageContent(): MessageContent = this as MessageContent
@PreviewFeature
inline fun ResendableContent.asVisualMediaGroupContent(): VisualMediaGroupContent? = this as? VisualMediaGroupContent
@PreviewFeature
inline fun ResendableContent.requireVisualMediaGroupContent(): VisualMediaGroupContent = this as VisualMediaGroupContent
@PreviewFeature
inline fun ResendableContent.asAnimationContent(): AnimationContent? = this as? AnimationContent
@PreviewFeature
inline fun ResendableContent.requireAnimationContent(): AnimationContent = this as AnimationContent
@PreviewFeature
inline fun ResendableContent.asAudioContent(): AudioContent? = this as? AudioContent
@PreviewFeature
inline fun ResendableContent.requireAudioContent(): AudioContent = this as AudioContent
@PreviewFeature
inline fun ResendableContent.asDocumentContent(): DocumentContent? = this as? DocumentContent
@PreviewFeature
inline fun ResendableContent.requireDocumentContent(): DocumentContent = this as DocumentContent
@PreviewFeature
inline fun ResendableContent.asPhotoContent(): PhotoContent? = this as? PhotoContent
@PreviewFeature
inline fun ResendableContent.requirePhotoContent(): PhotoContent = this as PhotoContent
@PreviewFeature
inline fun ResendableContent.asStickerContent(): StickerContent? = this as? StickerContent
@PreviewFeature
inline fun ResendableContent.requireStickerContent(): StickerContent = this as StickerContent
@PreviewFeature
inline fun ResendableContent.asVideoContent(): VideoContent? = this as? VideoContent
@PreviewFeature
inline fun ResendableContent.requireVideoContent(): VideoContent = this as VideoContent
@PreviewFeature
inline fun ResendableContent.asVideoNoteContent(): VideoNoteContent? = this as? VideoNoteContent
@PreviewFeature
inline fun ResendableContent.requireVideoNoteContent(): VideoNoteContent = this as VideoNoteContent
@PreviewFeature
inline fun ResendableContent.asVoiceContent(): VoiceContent? = this as? VoiceContent
@PreviewFeature
inline fun ResendableContent.requireVoiceContent(): VoiceContent = this as VoiceContent
@PreviewFeature
inline fun ResendableContent.asInvoiceContent(): InvoiceContent? = this as? InvoiceContent
@PreviewFeature
inline fun ResendableContent.requireInvoiceContent(): InvoiceContent = this as InvoiceContent
@PreviewFeature
inline fun TextSource.asMultilevelTextSource(): MultilevelTextSource? = this as? MultilevelTextSource
@PreviewFeature
inline fun TextSource.requireMultilevelTextSource(): MultilevelTextSource = this as MultilevelTextSource
@PreviewFeature
inline fun TextSource.asBoldTextSource(): BoldTextSource? = this as? BoldTextSource
@PreviewFeature
inline fun TextSource.requireBoldTextSource(): BoldTextSource = this as BoldTextSource
@PreviewFeature
inline fun TextSource.asBotCommandTextSource(): BotCommandTextSource? = this as? BotCommandTextSource
@PreviewFeature
inline fun TextSource.requireBotCommandTextSource(): BotCommandTextSource = this as BotCommandTextSource
@PreviewFeature
inline fun TextSource.asCashTagTextSource(): CashTagTextSource? = this as? CashTagTextSource
@PreviewFeature
inline fun TextSource.requireCashTagTextSource(): CashTagTextSource = this as CashTagTextSource
@PreviewFeature
inline fun TextSource.asCodeTextSource(): CodeTextSource? = this as? CodeTextSource
@PreviewFeature
inline fun TextSource.requireCodeTextSource(): CodeTextSource = this as CodeTextSource
@PreviewFeature
inline fun TextSource.asEMailTextSource(): EMailTextSource? = this as? EMailTextSource
@PreviewFeature
inline fun TextSource.requireEMailTextSource(): EMailTextSource = this as EMailTextSource
@PreviewFeature
inline fun TextSource.asHashTagTextSource(): HashTagTextSource? = this as? HashTagTextSource
@PreviewFeature
inline fun TextSource.requireHashTagTextSource(): HashTagTextSource = this as HashTagTextSource
@PreviewFeature
inline fun TextSource.asItalicTextSource(): ItalicTextSource? = this as? ItalicTextSource
@PreviewFeature
inline fun TextSource.requireItalicTextSource(): ItalicTextSource = this as ItalicTextSource
@PreviewFeature
inline fun TextSource.asMentionTextSource(): MentionTextSource? = this as? MentionTextSource
@PreviewFeature
inline fun TextSource.requireMentionTextSource(): MentionTextSource = this as MentionTextSource
@PreviewFeature
inline fun TextSource.asPhoneNumberTextSource(): PhoneNumberTextSource? = this as? PhoneNumberTextSource
@PreviewFeature
inline fun TextSource.requirePhoneNumberTextSource(): PhoneNumberTextSource = this as PhoneNumberTextSource
@PreviewFeature
inline fun TextSource.asPreTextSource(): PreTextSource? = this as? PreTextSource
@PreviewFeature
inline fun TextSource.requirePreTextSource(): PreTextSource = this as PreTextSource
@PreviewFeature
inline fun TextSource.asRegularTextSource(): RegularTextSource? = this as? RegularTextSource
@PreviewFeature
inline fun TextSource.requireRegularTextSource(): RegularTextSource = this as RegularTextSource
@PreviewFeature
inline fun TextSource.asStrikethroughTextSource(): StrikethroughTextSource? = this as? StrikethroughTextSource
@PreviewFeature
inline fun TextSource.requireStrikethroughTextSource(): StrikethroughTextSource = this as StrikethroughTextSource
@PreviewFeature
inline fun TextSource.asTextLinkTextSource(): TextLinkTextSource? = this as? TextLinkTextSource
@PreviewFeature
inline fun TextSource.requireTextLinkTextSource(): TextLinkTextSource = this as TextLinkTextSource
@PreviewFeature
inline fun TextSource.asTextMentionTextSource(): TextMentionTextSource? = this as? TextMentionTextSource
@PreviewFeature
inline fun TextSource.requireTextMentionTextSource(): TextMentionTextSource = this as TextMentionTextSource
@PreviewFeature
inline fun TextSource.asURLTextSource(): URLTextSource? = this as? URLTextSource
@PreviewFeature
inline fun TextSource.requireURLTextSource(): URLTextSource = this as URLTextSource
@PreviewFeature
inline fun TextSource.asUnderlineTextSource(): UnderlineTextSource? = this as? UnderlineTextSource
@PreviewFeature
inline fun TextSource.requireUnderlineTextSource(): UnderlineTextSource = this as UnderlineTextSource

View File

@@ -7,7 +7,6 @@ import kotlinx.coroutines.CoroutineScope
/** /**
* Shortcut for [dev.inmo.micro_utils.coroutines.safely]. It was created for more comfortable way of handling different things * Shortcut for [dev.inmo.micro_utils.coroutines.safely]. It was created for more comfortable way of handling different things
*/ */
@PreviewFeature
@Deprecated("In future will be used typealias from micro_utils", ReplaceWith("safely", "dev.inmo.micro_utils.coroutines.safely")) @Deprecated("In future will be used typealias from micro_utils", ReplaceWith("safely", "dev.inmo.micro_utils.coroutines.safely"))
suspend inline fun <T> safely( suspend inline fun <T> safely(
noinline onException: ExceptionHandler<T> = { throw it }, noinline onException: ExceptionHandler<T> = { throw it },

View File

@@ -1,13 +1,13 @@
package dev.inmo.tgbotapi.extensions.utils.chat_events package dev.inmo.tgbotapi.extensions.utils.chat_events
import dev.inmo.tgbotapi.types.message.* import dev.inmo.tgbotapi.extensions.utils.shortcuts.*
import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlin.reflect.KClass import kotlin.reflect.KClass
@Deprecated("Refactored, replaced and renamed", ReplaceWith("filterByChatEvent", "dev.inmo.tgbotapi.extensions.utils.shortcuts.filterByChatEvent"))
fun <T : ChatEventMessage> Flow<ChatEventMessage>.divideBySource(contentType: KClass<T>) = mapNotNull { fun <T : ChatEventMessage<*>> Flow<ChatEventMessage<*>>.divideBySource(contentType: KClass<T>) = mapNotNull {
if (contentType.isInstance(it)) { if (contentType.isInstance(it)) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
it as T it as T
@@ -16,6 +16,9 @@ fun <T : ChatEventMessage> Flow<ChatEventMessage>.divideBySource(contentType: KC
} }
} }
fun Flow<ChatEventMessage>.onlyChannelEvents() = divideBySource(ChannelEventMessage::class) @Deprecated("Replaced and renamed", ReplaceWith("channelEvents", "dev.inmo.tgbotapi.extensions.utils.shortcuts.channelEvents"))
fun Flow<ChatEventMessage>.onlyGroupEvents() = divideBySource(CommonGroupEventMessage::class) fun Flow<ChatEventMessage<*>>.onlyChannelEvents() = channelEvents()
fun Flow<ChatEventMessage>.onlySupergroupEvents() = divideBySource(CommonSupergroupEventMessage::class) @Deprecated("Replaced and renamed", ReplaceWith("groupEvents", "dev.inmo.tgbotapi.extensions.utils.shortcuts.groupEvents"))
fun Flow<ChatEventMessage<*>>.onlyGroupEvents() = groupEvents()
@Deprecated("Replaced and renamed", ReplaceWith("supergroupEvents", "dev.inmo.tgbotapi.extensions.utils.shortcuts.supergroupEvents"))
fun Flow<ChatEventMessage<*>>.onlySupergroupEvents() = supergroupEvents()

View File

@@ -0,0 +1,26 @@
package dev.inmo.tgbotapi.extensions.utils.extensions
import dev.inmo.tgbotapi.extensions.utils.shortcuts.chat
import dev.inmo.tgbotapi.types.chat.abstracts.Chat
import dev.inmo.tgbotapi.types.update.*
import dev.inmo.tgbotapi.types.update.MediaGroupUpdates.*
import dev.inmo.tgbotapi.types.update.abstracts.BaseMessageUpdate
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.utils.PreviewFeature
@PreviewFeature
fun Update.sourceChat(): Chat? = when (this) {
is MediaGroupUpdate -> when (this) {
is SentMediaGroupUpdate -> data.chat
is EditMediaGroupUpdate -> data.chat
else -> null
}
is BaseMessageUpdate -> data.chat
is InlineQueryUpdate -> data.from
is ChosenInlineResultUpdate -> data.user
is CallbackQueryUpdate -> data.user
is PreCheckoutQueryUpdate -> data.user
is PollAnswerUpdate -> data.user
is ShippingQueryUpdate -> data.user
else -> null
}

Some files were not shown because too many files have changed in this diff Show More