1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2025-11-16 12:00:18 +00:00

Compare commits

..

175 Commits

Author SHA1 Message Date
670cfcca13 update kotlin coroutines 2020-06-05 15:17:14 +06:00
130e00b62b extensions for CommonMessage 2020-06-05 14:57:58 +06:00
ca4beee95f add PossiblySentViaBotCommonMessage and fix error in build 2020-06-05 14:25:19 +06:00
ca784e67df add BuiltInMimeTypes, ThumbedWithMimeTypeInlineQueryResult and buildMimeType factory 2020-06-05 14:01:19 +06:00
835b8b34f9 add via_bot 2020-06-05 13:40:12 +06:00
e6430a729c work on version 0.27.6 has been started 2020-06-05 13:37:11 +06:00
6c4c9f2fc6 by default dokka task will not set up exact folder for docs 2020-06-03 15:42:00 +06:00
310a7e6e82 update docs build gradle to be able to put dokka where it is required 2020-06-03 15:39:54 +06:00
7375894645 Merge pull request #90 from InsanusMokrassar/0.27.5
0.27.5
2020-06-02 22:26:07 +06:00
69973c597b update BotCommandNameRegex 2020-06-02 22:10:33 +06:00
22e8b06fda BotBuilder fixes 2020-06-02 21:18:49 +06:00
7ede53fdbb BotCommandNameRegex and strict check of incoming command 2020-06-02 20:16:55 +06:00
ca9051920d new setMyCommands extension added 2020-06-02 19:54:13 +06:00
b477e8d585 extend getChat extensions 2020-06-02 13:54:30 +06:00
8ae2f57d55 update description of PreviewFeature annotation 2020-06-02 13:47:28 +06:00
1fb2ecf15f executes was replaced 2020-06-02 13:39:09 +06:00
6073d914d5 hotfixes in new media groups shortcuts 2020-06-02 13:22:36 +06:00
16f55d70af resend and other media group utils replaced into TelegramBotAPI_extensions-utils 2020-06-02 13:21:38 +06:00
b484a31a4a replacement of CaptionAndTextSourcesToText 2020-06-02 13:05:15 +06:00
0a162c4129 StringFormattiing replacement 2020-06-02 01:26:46 +06:00
648f1b488b add makeLinkToAddStickerSet 2020-06-02 01:15:08 +06:00
5fbde4bc06 new "row" 2020-06-02 01:02:38 +06:00
2a276d9272 safely function 2020-06-01 12:56:48 +06:00
9ae252717d versions update 2020-06-01 12:00:06 +06:00
456143a266 complete refresh of CaptinAndTextSourcesToText 2020-06-01 11:55:48 +06:00
0bcc98e126 update caption errors 2020-06-01 11:53:33 +06:00
ab9ceba41c updates in caption and text lengths 2020-06-01 11:44:58 +06:00
7cd5666e88 use maxTextLength inside of CaptionAndTextSourcesToText 2020-05-31 22:53:04 +06:00
35dcd6ada7 fix in SendMessage 2020-05-31 22:51:42 +06:00
ec37df82a9 start 0.27.5 2020-05-31 22:43:35 +06:00
220cb47615 update look like of kdocs badge 2020-05-23 15:46:49 +06:00
d79b8a337a add kdocs badge 2020-05-23 15:43:47 +06:00
cef6a6f741 update telegram badge 2020-05-23 15:27:45 +06:00
9471df1f2d Merge pull request #89 from InsanusMokrassar/0.27.4
0.27.4
2020-05-23 15:22:13 +06:00
f121e5f9c3 renames for JVM signature duplication avoiding 2020-05-23 12:53:42 +06:00
7f4fe318c5 update filters 2020-05-22 16:40:30 +06:00
dbf5c2dbb2 update publish scripts 2020-05-22 14:03:48 +06:00
105415873d updates extensions 2020-05-22 13:21:22 +06:00
ff32fd1dfc 0.27.4 started 2020-05-22 12:56:50 +06:00
006251ed07 fixes 2020-05-17 15:10:02 +06:00
9307582654 Add TelegramBotAPI-all status to readme 2020-05-17 01:30:05 +06:00
fe11a2119e Return opts in telegram bot api 2020-05-16 22:24:50 +06:00
c31403c1a2 Merge pull request #88 from InsanusMokrassar/0.27.3
0.27.3
2020-05-16 22:07:56 +06:00
0260e7bedc fixes of warnings 2020-05-16 22:02:57 +06:00
fa43a55f26 one more update of labeler 2020-05-16 21:54:54 +06:00
e9e1f4b9cf Merge branch 'master' into 0.27.3 2020-05-16 21:24:14 +06:00
e7b5b9184d update rules for labelers 2020-05-16 21:22:37 +06:00
81aa3f2307 update labeler task 2020-05-16 21:19:38 +06:00
a9fe584504 add TOC for TelegramBotAPI-extensions-utils 2020-05-16 21:14:10 +06:00
4c8861ba79 change visibility of internalSetWebhookInfoAndStartListenWebhooks 2020-05-16 20:57:57 +06:00
0ec18cbf06 dice changes 2020-05-16 20:46:49 +06:00
7008f312dc fixes 2020-05-16 20:43:09 +06:00
85317a510e update docs 2020-05-16 11:09:31 +06:00
d629aa206e fixes 2020-05-15 22:14:19 +06:00
6394e1a52b setWebhookInfo 2020-05-15 22:02:53 +06:00
23dca3d307 small grammar fix in WebhookInfo 2020-05-15 19:19:30 +06:00
3032aa8474 renaming of telegramBot functions 2020-05-15 19:14:09 +06:00
db19b69ca0 fails handler in executeUnsafe 2020-05-15 19:04:10 +06:00
f3827f81a7 change mechanism of executeUnsafe 2020-05-15 18:59:42 +06:00
0532dbb1ae fixes 2020-05-15 18:39:12 +06:00
efc2681da8 add additional signatures for setWebhookInfoAndStartListenWebhooks 2020-05-15 18:35:16 +06:00
735ed9fd86 change flowsUpdatesFilter signature 2020-05-15 18:31:49 +06:00
e856dc4754 add flowsUdatesFilter 2020-05-15 18:17:55 +06:00
0706ff1f95 Update labeler.yml 2020-05-15 18:06:29 +06:00
336b830b0b Merge branch 'master' into 0.27.3 2020-05-15 18:05:57 +06:00
1a638fe0a5 update labeler
one more update of labeler
2020-05-15 18:05:00 +06:00
45467e5bd7 Create labeler.yml 2020-05-15 18:05:00 +06:00
8419b0ab6a Create label.yml 2020-05-15 18:05:00 +06:00
49573607fb Create greetings.yml 2020-05-15 18:05:00 +06:00
35fe48db35 Create labeler.yml 2020-05-15 17:05:53 +06:00
590db3e672 Create label.yml 2020-05-15 17:02:33 +06:00
ea40474c47 add opportunity to set up media groups devounce time 2020-05-15 16:53:21 +06:00
7354389f2d Create greetings.yml 2020-05-15 15:49:10 +06:00
1f20ae16aa Update README.md 2020-05-15 00:54:00 +06:00
095c91bf39 update readmes with updates handling 2020-05-15 00:48:34 +06:00
dc173d752c optimize imports 2020-05-15 00:29:17 +06:00
a1788e35b2 clean up in webhooks 2020-05-15 00:28:16 +06:00
ea224fd765 add javax.activation dependency 2020-05-14 21:18:33 +06:00
7f51544bb9 Fix links in TelegramBotAPI-all readme 2020-05-14 16:25:39 +06:00
dfb22b0e89 update readme of all includer 2020-05-14 14:26:42 +06:00
e675e841da update urls 2020-05-14 14:23:26 +06:00
dea43aad8e update subprojects implementation types 2020-05-14 14:11:33 +06:00
52e25e934d add TelegramBotAPI-all 2020-05-14 14:07:23 +06:00
acc067585d add build instructions in README 2020-05-14 13:44:18 +06:00
47aa1a0795 fixes in CHANGELOG and docs 2020-05-14 13:26:56 +06:00
b40cc0c1ea fixes 2020-05-14 13:11:46 +06:00
b5632626ad small extention docs for RequestsExecutor 2020-05-14 12:21:38 +06:00
67fafdac00 add docs for RequestsExecutor 2020-05-14 12:14:38 +06:00
738e628a89 small change of deprecations message 2020-05-13 23:25:22 +06:00
420b846466 replace long polling and webhook tools 2020-05-13 23:22:35 +06:00
05e8c9c90d replace webhooks work into api subproject 2020-05-13 21:21:07 +06:00
e776c5182f additional setWebhook 2020-05-13 20:56:03 +06:00
be5b3745b9 extension was added 2020-05-13 20:01:58 +06:00
0de1d9cfda UpdateDeserializationStrategy currently is public 2020-05-13 19:53:27 +06:00
01da98d2fe start 0.27.3 2020-05-13 19:48:07 +06:00
e985100c21 Update README.md 2020-05-13 19:41:46 +06:00
671faabef9 add telegramBot function 2020-05-12 18:52:37 +06:00
bb9c9e22a2 remove dokka docs from github 2020-05-12 00:14:16 +06:00
42228f0eaa Merge pull request #86 from InsanusMokrassar/0.27.2
0.27.2
2020-05-11 23:42:17 +06:00
dafd0a8ece add docs 2020-05-11 23:37:38 +06:00
bee9d82372 update folder of dokka 2020-05-11 23:24:26 +06:00
ec6cf0f029 docs 2020-05-11 20:44:03 +06:00
9cee22165d start normal filling of docs 2020-05-11 20:32:14 +06:00
a58aad1198 update docs generation 2020-05-11 13:56:45 +06:00
aa78d99179 add docs project 2020-05-11 01:09:07 +06:00
603efe9259 small update of utils readme 2020-05-10 16:40:53 +06:00
21e3e10222 renames 2020-05-10 16:33:44 +06:00
34eb6eb4bf add ChatEventsConversations and refill README of telegramboapi-extensions-utils 2020-05-10 16:25:42 +06:00
565a724b9c remove todo from thumbed input media 2020-05-10 15:30:23 +06:00
e87c4a0126 MediaGroupMemberInputMedia deserialization 2020-05-10 15:26:07 +06:00
9b16d5d82b add MimeType 2020-05-10 14:37:55 +06:00
9747c8bff1 warnings fixes 2020-05-09 21:04:56 +06:00
3ee84700f4 update versions 2020-05-09 20:57:55 +06:00
04a463f42c start 0.27.2 2020-05-09 20:52:52 +06:00
668a201789 Merge pull request #85 from InsanusMokrassar/0.27.1
0.27.1
2020-04-25 10:12:53 +06:00
b336b17eef FullTextSourcesList 2020-04-25 09:57:59 +06:00
76b25d719a explanation small utils and explanationLimit 2020-04-25 09:48:23 +06:00
a4d077dd17 add Explained interface and use it in polls 2020-04-25 09:41:04 +06:00
469712150b started 0.27.1 2020-04-25 09:26:10 +06:00
fad27ede78 Merge pull request #84 from InsanusMokrassar/0.27.0
0.27.0
2020-04-24 21:25:11 +06:00
1de90412b3 addition in changelog 2020-04-24 21:24:24 +06:00
215c8793e1 optimize imports 2020-04-24 20:25:53 +06:00
54835f97d1 update TelegramBotAPI utils readme 2020-04-24 20:23:25 +06:00
923e929670 update TelegramBotAPI readme 2020-04-24 20:16:12 +06:00
37488e92e6 utils ScheduledCloseInfo shortcuts 2020-04-24 20:01:20 +06:00
830ca8122d add openPeriodPollSecondsLimit and checks 2020-04-24 19:46:47 +06:00
fbb2758bdb update changelog 2020-04-24 19:26:57 +06:00
a5c3e06f1c remove List<TextPart> extensions for texts due to JVM signature conflict and actualize TelegramBotAPI-extensions-api 2020-04-24 19:24:49 +06:00
c6fb50c4a6 SendPoll#closeInfo functionality 2020-04-24 19:08:54 +06:00
976c0b86dc send quiz poll functionality 2020-04-24 18:46:26 +06:00
fee5d8f925 texts and captions utils 2020-04-24 18:44:04 +06:00
808746e12d Dice#emoji support 2020-04-24 18:25:47 +06:00
3fb80dd475 add support of income explanation functionality in polls and polls auto close functionality 2020-04-24 17:51:09 +06:00
db8ea0da94 update versions 2020-04-24 16:18:07 +06:00
fbdfb714a3 started 0.27.0 2020-04-24 16:13:02 +06:00
1facfbc2b7 Merge pull request #83 from InsanusMokrassar/0.26.4
0.26.4
2020-04-22 15:08:35 +06:00
914a0662a9 CallbackGameInlineKeyboardButton now have only one income parameter 2020-04-22 14:11:46 +06:00
eda3003b7d change the way how we are deserializing updates in webhooks 2020-04-22 13:20:15 +06:00
459942de36 webhook update handling enhancement 2020-04-22 13:16:46 +06:00
17f64f9b48 CallbackGame update 2020-04-22 13:08:05 +06:00
3f896c2240 fix not implemented error thrown 2020-04-22 13:05:57 +06:00
94f8c971c5 started 0.26.4 2020-04-22 13:01:17 +06:00
c43109c063 Update README.md 2020-04-17 15:52:47 +06:00
f6058e29b4 Update README.md 2020-04-17 15:52:28 +06:00
3a37311331 Update README.md 2020-04-17 15:52:03 +06:00
9fe1472e64 update readme for help on JS platform 2020-04-17 15:43:43 +06:00
f1480c40a7 Merge pull request #81 from InsanusMokrassar/0.26.3
0.26.3
2020-04-13 12:55:48 +06:00
88eebdc448 small optimization of updates polling exception handling 2020-04-13 12:53:02 +06:00
8c76283db5 suspend inline function handleSafely was added 2020-04-13 12:40:14 +06:00
7668c48081 BaseEditMessageUpdate#data now is CommonMessage 2020-04-13 12:09:59 +06:00
35d2135f73 update serialization of InlineKeyboardButton 2020-04-13 11:31:36 +06:00
1cb0e096be fixes in InlineKeyboardButtonSerializer 2020-04-13 11:20:39 +06:00
31f7c7f31b UnknownUpdateType even if serialization exception 2020-04-13 11:17:15 +06:00
82d3b3bc48 add UnknownInlineKeyboardButton 2020-04-13 11:11:09 +06:00
b3734a5c2a fix of changelog 2020-04-13 01:46:58 +06:00
55b8736d50 Merge pull request #80 from Djaler/inline-keyboard-button-callback-game
Add support for inline keyboard buttons with callback_game field
2020-04-13 01:40:52 +06:00
3334fd3ca6 started 0.26.3 2020-04-13 01:39:31 +06:00
Kirill Romanov
e2416b405a add support for inline keyboard buttons with callback_game field 2020-04-12 22:30:37 +03:00
14f012fbfa Update README.md 2020-04-09 01:24:55 +06:00
1ff55057f2 Merge pull request #78 from InsanusMokrassar/0.26.2
0.26.2
2020-04-08 16:48:00 +06:00
71b5e33dbc update common README 2020-04-08 15:28:03 +06:00
08d9d183f4 add filterCommandsWithArgs 2020-04-08 15:23:12 +06:00
7183634fd6 experimentally make source string available inside of text sources 2020-04-08 15:17:13 +06:00
cf9f270651 rename new getting updates extension to avoid JVM signature collisions 2020-04-08 14:56:56 +06:00
bd87938e9c fixes in UpdatesChatFilters 2020-04-08 14:51:26 +06:00
ba76eaeb90 update readmes 2020-04-08 14:34:55 +06:00
d8492ae168 fixes, new readme 2020-04-08 14:02:55 +06:00
0db85232d3 fixes in changelog 2020-04-08 13:20:48 +06:00
bcf2325be8 make new startGettingOfUpdates more useful 2020-04-08 13:19:19 +06:00
51174a13de added some of extensions in TelegramBotAPI-extensions-utils 2020-04-08 11:50:47 +06:00
dfc1fa4d7e refill of changelog 2020-04-08 11:11:31 +06:00
10a1d1cb38 add TelegramBotAPI-extensions-utils 2020-04-08 11:05:48 +06:00
6c39dc4d06 new startGettingOfUpdates extension 2020-04-08 10:44:44 +06:00
4877b8958e start 0.26.2 2020-04-08 10:37:05 +06:00
db9c460e66 Merge pull request #75 from InsanusMokrassar/0.26.1
0.26.1
2020-04-06 12:15:27 +06:00
159 changed files with 4286 additions and 511 deletions

7
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
api: "TelegramBotAPI-extensions-api/**"
utils: "TelegramBotAPI-extensions-utils/**"
core: "TelegramBotAPI/**" # currently not work
code: "**/*.kt"
gradle: "**/*.gradle"
markdown: "**/*.md"

13
.github/workflows/greetings.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: Greetings
on: [pull_request, issues]
jobs:
greeting:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
issue-message: 'Welcome with your first issue'
pr-message: 'Welcome with your first PullRequest'

18
.github/workflows/label.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
# This workflow will triage pull requests and apply a label based on the
# paths that are modified in the pull request.
#
# To use this workflow, you will need to set up a .github/labeler.yml
# file with configuration. For more information, see:
# https://github.com/actions/labeler/blob/master/README.md
name: "Pull Request Labeler"
on:
- pull_request
jobs:
triage:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v2
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -1,5 +1,184 @@
# TelegramBotAPI changelog
## 0.27.0
* `Common`:
* Versions updates:
* `Kotlin`: `1.3.71` -> `1.3.72`
* `Klock`: `1.10.3` -> `1.10.5`
* `TelegramBotAPI`:
* Typealias `LongSeconds` was added for correct explanation of seconds in `Long` primitive type
* Several new fields was added:
* `explanationField`
* `explanationEntitiesField`
* `openPeriodField`
* `closeDateField`
* Extension `List<TextPart>#justTextSources` was added for mapping of `List<TextPart>` to `List<TextSource>`
* Field `SendPoll#closeInfo` was added
* Range `openPeriodPollSecondsLimit` was added and used in all `SendPoll` requests for checking income data
* `SendQuizPoll` now able to use fields `caption` and `parseMode` for `explanation` functionality
* `quizPollExplanationLimit` was added for checking `QuizPoll` explanation size
* Field `TextLinkTextSource#url` was added
* Field `TextMentionTextSource#user` was added
* Sealed class `ScheduledCloseInfo` was added
* Class `ExactScheduledCloseInfo` was added for cases with `close_date`
* Class `ApproximateScheduledCloseInfo` was added for cases with `open_period`
* Field `Poll#scheduledCloseInfo` was added
* Sealed class `MultipleAnswersPoll` was added
* Class `RegularPoll` now extends `MultipleAnswersPoll`
* `Dice` class was replaced into new package
* Sealed class `DiceAnimationType` was added
* Field `Dice#animationType` was added as `emoji` API representation
* `SendDice` now receive `animationType` as second parameter
* For `List<TextSource>` was added several extensions:
* `toMarkdownCaptions`
* `toMarkdownTexts`
* `toMarkdownV2Captions`
* `toMarkdownV2Texts`
* `toHtmlCaptions`
* `toHtmlTexts`
* `TelegramBotAPI-extensions-api`:
* All `RequestsExecutor#sendDice` extensions now accept `DiceAnimationType?` as second parameter
* All `RequestsExecutor#sendRegularPoll` extensions now accept `ScheduledCloseInfo` fourth parameter
* All `RequestsExecutor#sendQuizPoll` extensions now accept additional parameters `caption: String` and
`parseMode: ParseMode` for `explanation` functionality and `closeInfo: ScheduledCloseInfo?` for autoclose poll
functionality
* `TelegramBotAPI-extensions-utils`:
* Several shortcuts for `ScheduledCloseInfo` was added:
* `closePollExactAt`
* `closePollExactAfter`
* `closePollAfter`
### 0.27.6
* `Common`:
* Versions:
* `Kotlin Coroutines`: `1.3.6` -> `1.3.7`
* `TelegramBotAPI`:
* Interface `PossiblySentViaBot` has been added
* Additional interface `PossiblySentViaBotCommonMessage` was added for more explicit typing declaration for
compiler
* Currently, only `ChannelMessage` and `CommonMessageImpl` are implementing the interface
`PossiblySentViaBotCommonMessage`. It could be changed in future
* Factory `buildMimeType` was added
* `BuiltinMimeTypes` was added
* Abstraction `ThumbedWithMimeTypeInlineQueryResult` with `thumbMimeType` field was added
* `InlineQueryResultGif` and `InlineQueryResultMpeg4Gif` now extend `ThumbedWithMimeTypeInlineQueryResult`
instead of `ThumbedInlineQueryResult`
* `TelegramBotAPI-extensions-utils`:
* New extensions `onlyCommonMessages`, `onlySentViaBot` and `withoutSentViaBot` was added
### 0.27.5
* `Common`:
* Versions:
* `Klock`: `1.11.1` -> `1.11.3`
* `TelegramotAPI`:
* Fix: for sending requests caption and text lengths limits were updated
* New variant of `row` was added
* `makeLinkToMessage` extensions has been deprecated (replaced into `TelegramBotAPI-extensions-utils`)
* Next things was deprecated and replaced into `TelegramBotAPI-extensions-utils`:
* All `String` formatting public extensions and functions
* All extensions like `CaptionedInput#toHtmlCaptions`
* All helper extensions for `List<BaseMessageUpdate>`
* All `RequestsExecutor#executeAsync` and `RequestsExecutor#executeUnsafe`
* `BotCommand` now more strictly check commands which passed to it
* Regex `BotCommandNameRegex` was added
* `TelegramBotAPI-extensions-api`:
* A lot of `RequesstExecutor#getChat` extensions was added for more explicit types showing
* New `RequesstExecutor#setMyCommands` extension was added
* New field `BotBuilder#ktorClientEngineFactory` introduced
* Field `BotBuilder#ktorClientEngine` now is deprecated
* `TelegramBotAPI-extensions-utils`:
* `safely` function was introduced. It is in `PreviewFeature` state currently
* `makeLinkToMessage` extensions has been added
* `makeLinkToAddStickerSet` function and its variations were added
* Next tools was added from `TelegramBotAPI`:
* All `String` formatting extensions and functions
* All extensions like `CaptionedInput#toHtmlCaptions`
* All helper extensions for `List<BaseMessageUpdate>`
* Several new extensions for `SentMediaGroupUpdate` were added:
* `SentMediaGroupUpdate#forwardInfo`
* `SentMediaGroupUpdate#replyTo`
* `SentMediaGroupUpdate#chat`
* `SentMediaGroupUpdate#mediaGroupId`
* Several `List<MediaGroupMessage>.createResend` extensions were added
* `RequestsExecutor#executeAsync` and `RequestsExecutor#executeUnsafe`
### 0.27.4
* `TelegramBotAPI-extensions-utils`:
* Several extensions for updates was added:
* `onlyBaseMessageUpdates`
* `onlySentMessageUpdates`
* `onlyEditMessageUpdates`
* `onlyMediaGroupsUpdates`
* `onlySentMediaGroupUpdates`
* `onlyEditMediaGroupUpdates`
* Renames in chat filters extensions:
* `filterBaseMessageUpdates` -> `filterBaseMessageUpdatesByChatId` and `filterBaseMessageUpdatesByChat`
* `filterSentMediaGroupUpdates` -> `filterSentMediaGroupUpdatesByChatId` and `filterSentMediaGroupUpdatesByChat`
### 0.27.3
* `TelegramBotAPI`:
* `UpdateDeserializationStrategy` is publicly available now
* All `setWebhook` extensions was marked as deprecated, renamed and replaced into `TelegramBotAPI-extensions-utils`
* Typealias `ExceptionHandler` was added - it will be used for `handleSafely`
* `SetWebhook` factories signatures was changed (backward compatibility was not broken)
* `executeUnsafe` now working differently
* Now it is possible to pass exceptions handler into `executeUnsafe`
* `BasketballDiceAnimationType` was added
* `UnknownDiceAnimationType` now is deprecated due to renaming - currently it is typealias for `CustomDiceAnimationType`
* `CustomDiceAnimationType` now is `data` class instead of common class
* `FlowsUpdatesFilter` will use size 64 by default for internal broadcast channels
* `TelegramBotAPI-extensions-api`:
* Long Polling extensions now are deprecated in this project. It was replaced into `TelegramBotAPI-extensions-utils`
* Several `telegramBot` functions was renamed into `telegramBotWithCustomClientConfig`
* Add one more `setWebhookInfo` realisation
* `TelegramBotAPI-extensions-utils`:
* Extension `toTelegramUpdate` was added
* Long Polling extensions were added
* Updates utils were added
* New extensions `startListenWebhooks`, `setWebhookInfoAndStartListenWebhooks` and `includeWebhookHandlingInRoute` was added
* New extension `CoroutineScope#updateHandlerWithMediaGroupsAdaptation` was added
* New extension `flowsUpdatesFilter` was added
* `TelegramBotAPI-all`:
* Project was created
### 0.27.2
* `Common`:
* Versions:
* Coroutines: `1.3.5` -> `1.3.6`
* Klock: `1.10.5` -> `1.11.1`
* `TelegramBotAPI`:
* Expected class `MimeType` was added
* Field `MimeTyped#mimeType` now typed by `MimeType` instead of `String`
* `MediaGroupMemberInputMedia` children now can be deserialized (but only those ones who are declared inside library)
* `TelegramBotAPI-extensions-utils`:
* Chat events splitters added:
* Extension `Flow<ChatEventMessage>#onlyChannelEvents` was added
* Extension `Flow<ChatEventMessage>#onlyGroupEvents` was added
* Extension `Flow<ChatEventMessage>#onlySupergroupEvents` was added
### 0.27.1
* `TelegramBotAPI`:
* Interface `Explained` and subsinterfaces `ExplainedInput` and `ExplainedOutput` was added
* Class `QuizPoll` now implement `ExplainedInput`
* In `QuizPoll#caption` and `QuizPoll#captionEntities` are deprecated now
* Class `SendQuizPoll` now implement `ExplainedOutput`
* In `SendQuizPoll#caption` is deprecated now
* `explanationLimit` range was added as future replacement of `quizPollExplanationLimit`
* `quizPollExplanationLimit` now is deprecated
* Extensions `toMarkdownExplanations`, `toMarkdownV2Explanations` and `toHtmlExplanations` was added
* Typealias `FullTextSourcesList` was added
* All extensions `fullEntitiesList` now return `FullTextSourcesList`
* All extensions of `List<TextSource>` now are extensions for `FullTextSourcesList`
* `TelegramBotAPI-extensions-api`:
* `sendQuizPoll` now is using `explanation` parameter instead of `caption`
## 0.26.0
* `Common`:
@@ -38,6 +217,62 @@
and size of retrieved updates is equal to 100 (max count of retrieved updates)
* Extensions `getUpdates` now will receive only not nullable `limit` parameter
### 0.26.4
* `TelegramBotAPI`:
* Now any getting of updates will return `UnknownUpdateType` when inside of deserialization will be
`SerializationException` or `NotImplemented` error
* `CallbackGame` currently is an object
* It is possible to use `CallbackGame` for now
* `CallbackGameInlineKeyboardButton` now will not accept `callbackGame` as income object
* Now it is possible to pass exception handler in webhook
### 0.26.3
* `TelegramBotAPI`:
* `CallbackGameInlineKeyboardButton` was added
([Issue-79](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/79),
[PR-80](https://github.com/InsanusMokrassar/TelegramBotAPI/pull/80))
* `UnknownInlineKeyboardButton` was added. It is unavailable for creating, but you can receive it, for example, in
`InlineQueryResult`
* `Update` now will be created even if was `SerializationException` inside of creating the update instance - in this
case will be created `UnknownUpdateType`
* `UnknownUpdateType$rawJson` value now is included (`JsonElement`)
* **EXPERIMENTALLY** `BaseEditMessageUpdate#data` now is `CommonMessage<*>`
* Suspend inline function `handleSafely` was added
* `KtorRequestsExecutor` now use `handleSafely` instead of `try` with `supervisorScope`
* `UpdatesPolling` now use `handleSafely` instead of `try` with `supervisorScope`
### 0.26.2
* `TelegramBotAPI`:
* Now `EditMediaGroupUpdate` also extends `BaseEditMessageUpdate`
* **EXPERIMENTALLY** Now all `TextSource` realisations will contain `source` field as a property inside of them
* `TelegramBotAPI-extensions-api`:
* `startGettingFlowsUpdates` extension which do not require filter (but return a new one) was added
* `TelegramBotAPI-extensions-utils`:
* Subproject was added
* `filterBaseMessageUpdates`, `filterSentMediaGroupUpdates` and `filterEditMediaGroupUpdates` extensions was added
* `filterCommandsWithArgs`, `filterExactCommands` and `filterCommandsInsideTextMessages` extensions was added
* `asContentMessagesFlow`, `asChatEventsFlow` and `asUnknownMessagesFlow` extensions was added
* `withContentType` extension was added
* `onlyAnimationContentMessages` extension was added
* `onlyAudioContentMessages` extension was added
* `onlyContactContentMessages` extension was added
* `onlyDiceContentMessages` extension was added
* `onlyDocumentContentMessages` extension was added
* `onlyGameContentMessages` extension was added
* `onlyInvoiceContentMessages` extension was added
* `onlyLocationContentMessages` extension was added
* `onlyPhotoContentMessages` extension was added
* `onlyPollContentMessages` extension was added
* `onlyStickerContentMessages` extension was added
* `onlyTextContentMessages` extension was added
* `onlyVenueContentMessages` extension was added
* `onlyVideoContentMessages` extension was added
* `onlyVideoNoteContentMessages` extension was added
* `onlyVoiceContentMessages` extension was added
### 0.26.1
* `TelegramBotAPI`:

124
README.md
View File

@@ -1,27 +1,127 @@
# TelegramBotAPI
| Common info | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) [![Chat in Telegram](badges/chat.svg)](https://t.me/InMoTelegramBotAPI) [![Build Status](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI) |
| -----------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| TelegramBotAPI status | [![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI/images/download.svg)](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI) |
| TelegramBotAPI Extensions status | [![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-api/images/download.svg)](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-api/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-api/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-api) |
| Common info | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) [![Chat in Telegram](badges/chat.svg)](https://teleg.one/InMoTelegramBotAPI) [![Build Status](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI) [![KDocs](badges/kdocs.svg)](https://tgbotapi.inmo.dev/docs/index.html) |
| -------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| TelegramBotAPI status | [![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI/images/download.svg)](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI) |
| TelegramBotAPI Extensions status | [![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-api/images/download.svg)](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-api/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-api/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-api) |
| TelegramBotAPI Util Extensions status | [![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-utils/images/download.svg)](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-utils/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-utils/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-utils) |
| TelegramBotAPI All status | [![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI-all/images/download.svg)](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI-all/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-all/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-all) |
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:
* [TelegramBotAPI](TelegramBotAPI/README.md) - core of library. In fact it is independent library and can be used alone
without any additional library
without any additional library
* [TelegramBotAPI Extensions](TelegramBotAPI-extensions-api/README.md) - contains extensions (mostly for
`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](TelegramBotAPI-extensions-utils/README.md) - contains extensions for more comfortable
work with commands, updates and other different things
* [TelegramBotAPI All](TelegramBotAPI-all/README.md) - concentration of all previously mentioned libraries
Most part of some specific solves or unuseful
moments are describing by official [Telegram Bot API](https://core.telegram.org/bots/api).
## JavaScript notes
In case if you are want to use this library inside of browser, you will need additional settings (thanks for help to [Alexander Nozik](https://research.jetbrains.org/researchers/altavir)):
<details>
<summary>Gradle build script help</summary>
```groovy
dependencies {
/* ... */
implementation "com.github.insanusmokrassar:TelegramBotAPI:$tgbot_api_version"
implementation "com.github.insanusmokrassar:TelegramBotAPI-extensions-api:$tgbot_api_version" // optional
implementation "com.github.insanusmokrassar:TelegramBotAPI-extensions-utils:$tgbot_api_version" // optional
/* Block of dependencies for correct building in browser */
implementation(npm("fs"))
implementation(npm("bufferutil"))
implementation(npm("utf-8-validate"))
implementation(npm("abort-controller"))
implementation(npm("text-encoding"))
}
/* ... */
kotlin {
target {
browser {
/* Block for fix of exception in absence of some functionality, https://github.com/ktorio/ktor/issues/1339 */
dceTask {
dceOptions {
keep("ktor-ktor-io.\$\$importsForInline\$\$.ktor-ktor-io.io.ktor.utils.io")
}
}
}
}
}
```
</details>
## Ok, where should I start?
Firstly, look at the [TelegramBotAPI](TelegramBotAPI/README.md). Here you can find all information about currently
covered Telegram Bot API and other things. After this you can look at the
[TelegramBotAPI Extensions](TelegramBotAPI-extensions-api/README.md).
In most cases, the most simple way will be to implement [TelegramBotAPI All](TelegramBotAPI-all/README.md) - it contains
all necessary tools for comfort usage of this library. If you want to exclude some libraries, you can implement just
[TelegramBotAPI API Extensions](TelegramBotAPI-extensions-api/README.md),
[TelegramBotAPI Util Extensions](TelegramBotAPI-extensions-utils/README.md) or even
[TelegramBotAPI](TelegramBotAPI/README.md).
Anyway, all libraries are very typical inside of them. For example, any request in TelegramBotAPI look like
`requestsExecutor.execute(SomeRequest())`.
If you want to dive deeper in the core of library or develop something for it - welcome to learn more from
[TelegramBotAPI](TelegramBotAPI/README.md) and our [Telegram Chat](https://teleg.one/InMoTelegramBotAPIChat).
Anyway, all libraries are very typical inside of them. Examples:
* In `TelegramBotAPI` common request look like `requestsExecutor.execute(SomeRequest())`
* `TelegramBotAPI-extensions-api` typical syntax look like `requestsExecutor.someRequest()` (in most cases it would be
better to use `bot` name instead of `requestsExecutor`)
* `TelegramBotAPI-extensions-utils` will look like `filter.filterBaseMessageUpdates(chatId).filterExactCommands(Regex("^.*$"))...`
## Build instruction
If you want to build this project or to contribute, there are several recommendations:
### Build
In case if you want to just build project, run next command:
```bash
./gradlew clean build
```
On windows:
```
gradlew.bat clean build
```
### Publishing for work with your version locally
In case, if you want to work in your other projects using your modification (or some state) of this library,
you can use next code:
```bash
./gradlew clean build publishToMavenLocal
```
On windows:
```
gradlew.bat clean build publishToMavenLocal
```
But you must remember, that in this case your local maven repo must be the first one from
your project retrieving libraries:
```groovy
repositories {
mavenLocal() // that must be the first one
jcenter()
mavenCentral()
}
```
Besides, for your own version you can change variable `library_version` in the file [gradle.properties](./gradle.properties).

View File

@@ -0,0 +1,16 @@
# TelegramBotAPI-all
Concentration of all TelegramBotAPI libraries:
* [TelegramBotAPI](../TelegramBotAPI/README.md)
* [TelegramBotAPI Extensions](../TelegramBotAPI-extensions-api/README.md)
* [TelegramBotAPI Util Extensions](../TelegramBotAPI-extensions-utils/README.md)
## Implementation
```groovy
dependencies {
// ...
implementation "com.github.insanusmokrassar:TelegramBotAPI-all:tgBotAPIVersion"
}
```

View File

@@ -0,0 +1,52 @@
buildscript {
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
}
}
plugins {
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
}
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()
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
if ((project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true") {
api "${project.group}:TelegramBotAPI:$library_version"
api "${project.group}:TelegramBotAPI-extensions-api:$library_version"
api "${project.group}:TelegramBotAPI-extensions-utils:$library_version"
} else {
api project(":TelegramBotAPI")
api project(":TelegramBotAPI-extensions-api")
api project(":TelegramBotAPI-extensions-utils")
}
}
}
}
}

View File

@@ -0,0 +1,53 @@
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 = "This project just include all subproject of TelegramBotAPI"
name = "Telegram Bot API All"
url = "https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI-all"
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

@@ -0,0 +1 @@
{"bintrayConfig":{"repo":"StandardRepository","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/TelegramBotAPI"},"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 All","description":"This project just include all subproject of TelegramBotAPI","url":"https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI-all","vcsUrl":"https://github.com/insanusmokrassar/TelegramBotAPI.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}]},"type":"Multiplatform"}

View File

@@ -0,0 +1,55 @@
apply plugin: 'com.jfrog.bintray'
apply from: "maven.publish.gradle"
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(".", "/")
}
pkg {
repo = "StandardRepository"
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 {
publications = publishing.publications.collect {
if (it.name.contains('kotlinMultiplatform')) {
null
} else {
it.name
}
} - null
}
bintrayUpload.dependsOn publishToMavenLocal

View File

@@ -1,9 +1,7 @@
# TelegramBotAPI extensions
[![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin)
[![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-api/images/download.svg) ](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-api/_latestVersion)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-api/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-api)
[![Build Status](https://jenkins.insanusmokrassar.com/buildStatus/icon?job=TelegramBotAPI-extensions-api_master__publishing)](https://jenkins.insanusmokrassar.com/job/TelegramBotAPI-extensions-api_master__publishing/)
## What is it?
@@ -57,14 +55,22 @@ compile "com.github.insanusmokrassar:TelegramBotAPI-extensions-api:$telegrambota
## Example of usage and comparison with `TelegramBotAPI`
Here presented review table for comparison of api from original [TelegramBotAPI](../TelegramBotAPI/README.md#Requests)
and extensions-api library:
In all examples supposed that you have created bot with next approximate lines:
and extensions-api library. First of all, this library allow to create bot instance in a new way:
```kotlin
val bot: RequestsExecutor = ...
val bot = telegramBot("IT IS YOUR TOKEN")
```
There are a lot of signature for this. For example, you can create bot with next code:
```kotlin
val bot = telegramBot("IT IS YOUR TOKEN") {
proxy = ProxyBuilder.socks("127.0.0.1", 1080)
}
```
In all examples supposed that you have created bot.
| TelegramBotAPI | TelegramBotAPI-extensions-api |
|----------------|-------------------------------|
| bot.execute(GetMe) | bot.getMe() |
@@ -72,6 +78,10 @@ val bot: RequestsExecutor = ...
## Updates
**Currently, these paragraphs almost outdated due to the fact that extensions for listening of updates and webhooks were
replaced into `TelegramBotAPI-extensions-utils`. But, most part of information below is correct with small fixes and
adding of `TelegramBotAPI-extensions-utils` dependency.**
Usually, it is more comfortable to use filter object to get separated types of updates:
```kotlin
@@ -99,3 +109,21 @@ filter.messageFlow.mapNotNull {
CoroutineScope(Dispatchers.Default)
)
```
### Alternative way
There is an alternative way to get updates. In fact it is almost the same, but could be more useful for some cases:
```kotlin
val filter = bot.startGettingOfUpdates(
scope = CoroutineScope(Dispatchers.Default)
) { // Here as reveiver will be FlowsUpdatesFilter
messageFlow.mapNotNull {
it.data as? ContentMessage<*>
}.onEach {
println(it)
}.launchIn(
CoroutineScope(Dispatchers.Default)
)
}
```

View File

@@ -40,7 +40,7 @@ kotlin {
if ((project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true") {
api "${project.group}:TelegramBotAPI:$library_version"
} else {
implementation project(":TelegramBotAPI")
api project(":TelegramBotAPI")
}
}
}

View File

@@ -20,37 +20,33 @@ publishing {
publications.all {
artifact javadocsJar
pom.withXml {
asNode().children().last() + {
resolveStrategy = Closure.DELEGATE_FIRST
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"
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"
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"
}
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"
}
}
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"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"
}
}
}
}

View File

@@ -1 +1 @@
{"bintrayConfig":{"repo":"StandardRepository","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/TelegramBotAPI"},"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 Extensions for API","description":"API extensions which provide work with RequestsExecutor of TelegramBotAPI almost like it is described in original Telegram Bot API reference","url":"https://insanusmokrassar.github.io/TelegramBotAPI","vcsUrl":"https://github.com/insanusmokrassar/TelegramBotAPI.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}]},"type":"Multiplatform"}
{"bintrayConfig":{"repo":"StandardRepository","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/TelegramBotAPI"},"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 Extensions for API","description":"API extensions which provide work with RequestsExecutor of TelegramBotAPI almost like it is described in original Telegram Bot API reference","url":"https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI-extensions-api","vcsUrl":"https://github.com/insanusmokrassar/TelegramBotAPI.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}]},"type":"Multiplatform"}

View File

@@ -0,0 +1,54 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.api
import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor
import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.*
/**
* @param proxy Standard ktor [ProxyConfig]
* @param ktorClientEngine Engine like [io.ktor.client.engine.cio.CIO]
* @param ktorClientConfig Config block for preconfiguring of bot [HttpClient]
*/
data class BotBuilder internal constructor(
var proxy: ProxyConfig? = null,
@Deprecated("ktorClientEngineFactory parameter will be used preferable. In future this parameter will be removed")
var ktorClientEngine: HttpClientEngine? = null,
var ktorClientEngineFactory: HttpClientEngineFactory<out HttpClientEngineConfig>? = null,
var ktorClientConfig: (HttpClientConfig<*>.() -> Unit) ? = null
) {
internal fun createHttpClient(): HttpClient = ktorClientEngineFactory ?.let {
HttpClient(
it.create {
this@create.proxy = this@BotBuilder.proxy ?: this@create.proxy
}
) {
ktorClientConfig ?.let { it() }
}
} ?: ktorClientEngine ?.let { engine ->
HttpClient(engine) {
ktorClientConfig ?.let { it() }
engine {
this@engine.proxy = this@BotBuilder.proxy ?: this@engine.proxy
}
}
} ?: HttpClient {
ktorClientConfig ?.let { it() }
engine {
this@engine.proxy = this@BotBuilder.proxy ?: this@engine.proxy
}
}
}
/**
* @return Created by [telegramBotWithCustomClientConfig] function [RequestsExecutor]. This executor will be preconfigured using [token] and
* [block]
*/
fun telegramBot(
token: String,
block: BotBuilder.() -> Unit
): RequestsExecutor = telegramBot(
TelegramAPIUrlsKeeper(token),
BotBuilder().apply(block).createHttpClient()
)

View File

@@ -0,0 +1,76 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.api
import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.KtorRequestsExecutor
import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor
import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.HttpClientEngine
/**
* Allows to create bot using bot [urlsKeeper] and already prepared [client]
*/
fun telegramBot(
urlsKeeper: TelegramAPIUrlsKeeper,
client: HttpClient
): RequestsExecutor = KtorRequestsExecutor(
urlsKeeper,
client
)
/**
* Allows to create bot using bot [urlsKeeper] and specify [HttpClientEngine] by passing [clientEngine] param and optionally
* configure [HttpClient] using [clientConfig]
*/
fun telegramBotWithCustomClientConfig(
urlsKeeper: TelegramAPIUrlsKeeper,
clientEngine: HttpClientEngine,
clientConfig: HttpClientConfig<*>.() -> Unit = {}
): RequestsExecutor = telegramBot(
urlsKeeper,
HttpClient(clientEngine, clientConfig)
)
/**
* Allows to create bot using bot [urlsKeeper] and optionally configure [HttpClient] using [clientConfig]
*/
fun telegramBotWithCustomClientConfig(
urlsKeeper: TelegramAPIUrlsKeeper,
clientConfig: HttpClientConfig<*>.() -> Unit = {}
): RequestsExecutor = telegramBot(
urlsKeeper,
HttpClient(clientConfig)
)
/**
* Allows to create bot using bot [token]
*/
fun telegramBot(
token: String
): RequestsExecutor = telegramBotWithCustomClientConfig(TelegramAPIUrlsKeeper(token))
/**
* Allows to create bot using bot [token] and already prepared [client]
*/
fun telegramBot(
token: String,
client: HttpClient
): RequestsExecutor = telegramBot(TelegramAPIUrlsKeeper(token), client)
/**
* Allows to create bot using bot [token] and configure [HttpClient] using [clientConfig]
*/
fun telegramBotWithCustomClientConfig(
token: String,
clientConfig: HttpClientConfig<*>.() -> Unit
): RequestsExecutor = telegramBotWithCustomClientConfig(TelegramAPIUrlsKeeper(token), clientConfig)
/**
* Allows to create bot using bot [token] and specify [HttpClientEngine] by passing [clientEngine] param and optionally
* configure [HttpClient] using [clientConfig]
*/
fun telegramBot(
token: String,
clientEngine: HttpClientEngine,
clientConfig: HttpClientConfig<*>.() -> Unit = {}
): RequestsExecutor = telegramBotWithCustomClientConfig(TelegramAPIUrlsKeeper(token), clientEngine, clientConfig)

View File

@@ -0,0 +1,11 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.api
import kotlinx.serialization.json.Json
@Suppress("EXPERIMENTAL_API_USAGE")
internal val nonstrictJsonFormat = Json {
isLenient = true
ignoreUnknownKeys = true
serializeSpecialFloatingPointValues = true
useArrayPolymorphism = true
}

View File

@@ -7,3 +7,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.BotCommand
suspend fun RequestsExecutor.setMyCommands(
commands: List<BotCommand>
) = execute(SetMyCommands(commands))
suspend fun RequestsExecutor.setMyCommands(
vararg commands: BotCommand
) = setMyCommands(commands.toList())

View File

@@ -2,8 +2,12 @@ package com.github.insanusmokrassar.TelegramBotAPI.extensions.api.chat.get
import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor
import com.github.insanusmokrassar.TelegramBotAPI.requests.chat.get.GetChat
import com.github.insanusmokrassar.TelegramBotAPI.types.ChatIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.*
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.*
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.extended.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.PreviewFeature
suspend fun RequestsExecutor.getChat(
chatId: ChatIdentifier
@@ -12,3 +16,117 @@ suspend fun RequestsExecutor.getChat(
suspend fun RequestsExecutor.getChat(
chat: Chat
) = getChat(chat.id)
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedPublicChat] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: PublicChat
) = getChat(chat.id) as ExtendedPublicChat
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedChannelChat] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: ChannelChat
) = getChat(chat.id) as ExtendedChannelChat
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedChannelChatImpl] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: ChannelChatImpl
) = getChat(chat.id) as ExtendedChannelChatImpl
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedGroupChat] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: GroupChat
) = getChat(chat.id) as ExtendedGroupChat
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedGroupChatImpl] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: GroupChatImpl
) = getChat(chat.id) as ExtendedGroupChatImpl
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedSupergroupChat] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: SupergroupChat
) = getChat(chat.id) as ExtendedSupergroupChat
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedSupergroupChatImpl] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: SupergroupChatImpl
) = getChat(chat.id) as ExtendedSupergroupChatImpl
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedPrivateChat] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: PrivateChat
) = getChat(chat.id) as ExtendedPrivateChat
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedPrivateChatImpl] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: PrivateChatImpl
) = getChat(chat.id) as ExtendedPrivateChatImpl
/**
* Will cast incoming [com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat] to a
* [ExtendedUser] with unsafe operator "as"
*
* @throws ClassCastException
*/
@PreviewFeature
suspend fun RequestsExecutor.getChat(
chat: CommonUser
) = getChat(chat.id) as ExtendedUser

View File

@@ -6,19 +6,22 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.ChatIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat
import com.github.insanusmokrassar.TelegramBotAPI.types.dice.DiceAnimationType
suspend fun RequestsExecutor.sendDice(
chatId: ChatIdentifier,
animationType: DiceAnimationType? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendDice(chatId, disableNotification, replyToMessageId, replyMarkup)
SendDice(chatId, animationType, disableNotification, replyToMessageId, replyMarkup)
)
suspend fun RequestsExecutor.sendDice(
chat: Chat,
animationType: DiceAnimationType? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendDice(chat.id, disableNotification, replyToMessageId, replyMarkup)
) = sendDice(chat.id, animationType, disableNotification, replyToMessageId, replyMarkup)

View File

@@ -5,10 +5,10 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.send.polls.SendQuizPo
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.polls.SendRegularPoll
import com.github.insanusmokrassar.TelegramBotAPI.types.ChatIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.ParseMode
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.QuizPoll
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.RegularPoll
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.*
suspend fun RequestsExecutor.sendRegularPoll(
chatId: ChatIdentifier,
@@ -17,12 +17,13 @@ suspend fun RequestsExecutor.sendRegularPoll(
isAnonymous: Boolean = true,
isClosed: Boolean = false,
allowMultipleAnswers: Boolean = false,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendRegularPoll(
chatId, question, options, isAnonymous, isClosed, allowMultipleAnswers, disableNotification, replyToMessageId, replyMarkup
chatId, question, options, isAnonymous, isClosed, allowMultipleAnswers, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
)
suspend fun RequestsExecutor.sendRegularPoll(
@@ -33,12 +34,13 @@ suspend fun RequestsExecutor.sendRegularPoll(
options: List<String> = poll.options.map { it.text },
isAnonymous: Boolean = poll.isAnonymous,
allowMultipleAnswers: Boolean = poll.allowMultipleAnswers,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendRegularPoll(
chatId, question, options, isAnonymous, isClosed, allowMultipleAnswers, disableNotification, replyToMessageId, replyMarkup
chatId, question, options, isAnonymous, isClosed, allowMultipleAnswers, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
)
@@ -49,11 +51,12 @@ suspend fun RequestsExecutor.sendRegularPoll(
isAnonymous: Boolean = true,
isClosed: Boolean = false,
allowMultipleAnswers: Boolean = false,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendRegularPoll(
chat.id, question, options, isAnonymous, isClosed, allowMultipleAnswers, disableNotification, replyToMessageId, replyMarkup
chat.id, question, options, isAnonymous, isClosed, allowMultipleAnswers, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
suspend fun RequestsExecutor.sendRegularPoll(
@@ -64,11 +67,12 @@ suspend fun RequestsExecutor.sendRegularPoll(
options: List<String> = poll.options.map { it.text },
isAnonymous: Boolean = poll.isAnonymous,
allowMultipleAnswers: Boolean = poll.allowMultipleAnswers,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendRegularPoll(
chat.id, question, options, isAnonymous, isClosed, allowMultipleAnswers, disableNotification, replyToMessageId, replyMarkup
chat.id, question, options, isAnonymous, isClosed, allowMultipleAnswers, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
@@ -79,12 +83,15 @@ suspend fun RequestsExecutor.sendQuizPoll(
correctOptionId: Int,
isAnonymous: Boolean = true,
isClosed: Boolean = false,
explanation: String? = null,
parseMode: ParseMode? = null,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendQuizPoll(
chatId, question, options, correctOptionId, isAnonymous, isClosed, disableNotification, replyToMessageId, replyMarkup
chatId, question, options, correctOptionId, isAnonymous, isClosed, explanation, parseMode, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
)
@@ -95,11 +102,14 @@ suspend fun RequestsExecutor.sendQuizPoll(
correctOptionId: Int,
isAnonymous: Boolean = true,
isClosed: Boolean = false,
explanation: String? = null,
parseMode: ParseMode? = null,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendQuizPoll(
chat.id, question, options, correctOptionId, isAnonymous, isClosed, disableNotification, replyToMessageId, replyMarkup
chat.id, question, options, correctOptionId, isAnonymous, isClosed, explanation, parseMode, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
suspend fun RequestsExecutor.sendQuizPoll(
@@ -110,12 +120,15 @@ suspend fun RequestsExecutor.sendQuizPoll(
options: List<String> = quizPoll.options.map { it.text },
correctOptionId: Int = quizPoll.correctOptionId ?: error("Correct option ID must be provided by income QuizPoll or by developer"),
isAnonymous: Boolean = quizPoll.isAnonymous,
explanation: String? = null,
parseMode: ParseMode? = null,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = execute(
SendQuizPoll(
chatId, question, options, correctOptionId, isAnonymous, isClosed, disableNotification, replyToMessageId, replyMarkup
chatId, question, options, correctOptionId, isAnonymous, isClosed, explanation, parseMode, closeInfo, disableNotification, replyToMessageId, replyMarkup
)
)
@@ -127,9 +140,12 @@ suspend fun RequestsExecutor.sendQuizPoll(
options: List<String> = quizPoll.options.map { it.text },
correctOptionId: Int = quizPoll.correctOptionId ?: error("Correct option ID must be provided by income QuizPoll or by developer"),
isAnonymous: Boolean = quizPoll.isAnonymous,
explanation: String? = null,
parseMode: ParseMode? = null,
closeInfo: ScheduledCloseInfo? = null,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null,
replyMarkup: KeyboardMarkup? = null
) = sendQuizPoll(
chat.id, question, options, correctOptionId, isAnonymous, isClosed, disableNotification, replyToMessageId, replyMarkup
chat.id, question, options, correctOptionId, isAnonymous, isClosed, explanation, parseMode, closeInfo, disableNotification, replyToMessageId, replyMarkup
)

View File

@@ -10,59 +10,80 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.update.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update
import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.*
import io.ktor.client.features.HttpRequestTimeoutException
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
import kotlinx.coroutines.*
@Deprecated("Replaced and renamed in TelegramBotAPI-extensions-utils")
fun RequestsExecutor.startGettingOfUpdates(
timeoutSeconds: Seconds = 30,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
exceptionsHandler: (suspend (Exception) -> Unit)? = null,
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
allowedUpdates: List<String>? = null,
updatesReceiver: UpdateReceiver<Update>
): Job = scope.launch {
var lastUpdateIdentifier: UpdateIdentifier? = null
while (isActive) {
try {
supervisorScope {
val updates = getUpdates(
offset = lastUpdateIdentifier?.plus(1),
timeout = timeoutSeconds,
allowed_updates = allowedUpdates
).let { originalUpdates ->
val converted = originalUpdates.convertWithMediaGroupUpdates()
/**
* Dirty hack for cases when the media group was retrieved not fully:
*
* We are throw out the last media group and will reretrieve it again in the next get updates
* and it will guarantee that it is full
*/
if (originalUpdates.size == getUpdatesLimit.last && converted.last() is SentMediaGroupUpdate) {
converted - converted.last()
} else {
converted
}
}
supervisorScope {
for (update in updates) {
updatesReceiver(update)
lastUpdateIdentifier = update.lastUpdateIdentifier()
}
handleSafely(
{ e ->
exceptionsHandler ?.invoke(e)
if (e is RequestException) {
delay(1000L)
}
}
) {
val updates = getUpdates(
offset = lastUpdateIdentifier?.plus(1),
timeout = timeoutSeconds,
allowed_updates = allowedUpdates
).let { originalUpdates ->
val converted = originalUpdates.convertWithMediaGroupUpdates()
/**
* Dirty hack for cases when the media group was retrieved not fully:
*
* We are throw out the last media group and will reretrieve it again in the next get updates
* and it will guarantee that it is full
*/
if (originalUpdates.size == getUpdatesLimit.last && converted.last() is SentMediaGroupUpdate) {
converted - converted.last()
} else {
converted
}
}
handleSafely {
for (update in updates) {
updatesReceiver(update)
lastUpdateIdentifier = update.lastUpdateIdentifier()
}
}
} catch (e: HttpRequestTimeoutException) {
exceptionsHandler ?.invoke(e) // it is ok due to mechanism of long polling
} catch (e: RequestException) {
exceptionsHandler ?.invoke(e) // it is not ok, but in most cases it will mean that there is some limit for requests count
delay(1000L)
} catch (e: Exception) {
exceptionsHandler ?.invoke(e)
}
}
}
/**
* This method will create a new one [FlowsUpdatesFilter]. This method could be unsafe due to the fact that it will start
* getting updates IMMEDIATELY. That means that your bot will be able to skip some of them until you will call
* [kotlinx.coroutines.flow.Flow.collect] on one of [FlowsUpdatesFilter] flows. To avoid it, you can pass
* [flowUpdatesPreset] lambda - it will be called BEFORE starting updates getting
*/
@FlowPreview
@PreviewFeature
@Suppress("unused")
@Deprecated("Replaced and renamed in TelegramBotAPI-extensions-utils")
fun RequestsExecutor.startGettingFlowsUpdates(
timeoutSeconds: Seconds = 30,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
exceptionsHandler: (suspend (Exception) -> Unit)? = null,
flowsUpdatesFilterUpdatesKeeperCount: Int = 64,
flowUpdatesPreset: FlowsUpdatesFilter.() -> Unit = {}
): FlowsUpdatesFilter = FlowsUpdatesFilter(flowsUpdatesFilterUpdatesKeeperCount).apply {
flowUpdatesPreset()
startGettingOfUpdates(timeoutSeconds, scope, exceptionsHandler, allowedUpdates, asUpdateReceiver)
}
@Deprecated("Replaced and renamed in TelegramBotAPI-extensions-utils")
fun RequestsExecutor.startGettingOfUpdates(
updatesFilter: UpdatesFilter,
timeoutSeconds: Seconds = 30,
@@ -76,6 +97,7 @@ fun RequestsExecutor.startGettingOfUpdates(
updatesFilter.asUpdateReceiver
)
@Deprecated("Replaced and renamed in TelegramBotAPI-extensions-utils")
fun RequestsExecutor.startGettingOfUpdates(
messageCallback: UpdateReceiver<MessageUpdate>? = null,
messageMediaGroupCallback: UpdateReceiver<MessageMediaGroupUpdate>? = null,
@@ -120,6 +142,8 @@ fun RequestsExecutor.startGettingOfUpdates(
)
}
@Suppress("unused")
@Deprecated("Replaced and renamed in TelegramBotAPI-extensions-utils")
fun RequestsExecutor.startGettingOfUpdates(
messageCallback: UpdateReceiver<MessageUpdate>? = null,
mediaGroupCallback: UpdateReceiver<MediaGroupUpdate>? = null,

View File

@@ -0,0 +1,49 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.api.utils
import com.github.insanusmokrassar.TelegramBotAPI.extensions.api.InternalUtils.convertWithMediaGroupUpdates
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update
import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.UpdateReceiver
import com.github.insanusmokrassar.TelegramBotAPI.utils.extensions.accumulateByKey
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
/**
* Create [UpdateReceiver] object which will correctly accumulate updates and send into output updates which INCLUDE
* [com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.MediaGroupUpdate]s.
*
* @see UpdateReceiver
*/
fun CoroutineScope.updateHandlerWithMediaGroupsAdaptation(
output: UpdateReceiver<Update>,
mediaGroupsDebounceMillis: Long = 1000L
): UpdateReceiver<Update> {
val updatesChannel = Channel<Update>(Channel.UNLIMITED)
val mediaGroupChannel = Channel<Pair<String, BaseMessageUpdate>>(Channel.UNLIMITED)
val mediaGroupAccumulatedChannel = mediaGroupChannel.accumulateByKey(
mediaGroupsDebounceMillis,
scope = this
)
launch {
launch {
for (update in updatesChannel) {
when (val data = update.data) {
is MediaGroupMessage -> mediaGroupChannel.send("${data.mediaGroupId}${update::class.simpleName}" to update as BaseMessageUpdate)
else -> output(update)
}
}
}
launch {
for ((_, mediaGroup) in mediaGroupAccumulatedChannel) {
mediaGroup.convertWithMediaGroupUpdates().forEach {
output(it)
}
}
}
}
return { updatesChannel.send(it) }
}

View File

@@ -5,6 +5,22 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.FileId
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.MultipartFile
import com.github.insanusmokrassar.TelegramBotAPI.requests.webhook.SetWebhook
/**
* Use this method to send information about webhook (like [url])
*/
suspend fun RequestsExecutor.setWebhookInfo(
url: String,
maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null
) = execute(
SetWebhook(
url, maxAllowedConnections, allowedUpdates
)
)
/**
* Use this method to send information about webhook (like [url] and [certificate])
*/
suspend fun RequestsExecutor.setWebhookInfo(
url: String,
certificate: FileId,
@@ -16,6 +32,9 @@ suspend fun RequestsExecutor.setWebhookInfo(
)
)
/**
* Use this method to send information about webhook (like [url] and [certificate])
*/
suspend fun RequestsExecutor.setWebhookInfo(
url: String,
certificate: MultipartFile,

View File

@@ -0,0 +1,289 @@
# TelegramBotAPI Util Extensions
- [TelegramBotAPI Util Extensions](#telegrambotapi-util--extensions)
* [What is it?](#what-is-it-)
* [How to implement library?](#how-to-implement-library-)
+ [Maven](#maven)
+ [Gradle](#gradle)
* [How to use?](#how-to-use-)
+ [Updates](#updates)
- [Long polling](#long-polling)
- [WebHooks (currently JVM-only)](#webhooks--currently-jvm-only-)
+ [Filters](#filters)
- [Sent messages](#sent-messages)
* [Common messages](#common-messages)
* [Chat actions](#chat-actions)
+ [Shortcuts](#shortcuts)
- [ScheduledCloseInfo](#scheduledcloseinfo)
<small><i><a href='http://ecotrust-canada.github.io/markdown-toc/'>Table of contents generated with markdown-toc</a></i></small>
[![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-utils/images/download.svg) ](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-utils/_latestVersion)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-utils/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI-extensions-utils)
## What is it?
It is wrapper library for [TelegramBotAPI](../TelegramBotAPI/README.md). Currently, this library contains some usefull filters for commands, updates types and different others.
## How to implement library?
Common ways to implement this library are presented here. In some cases it will require additional steps
like inserting of additional libraries (like `kotlin stdlib`). In the examples will be used variable
`telegrambotapi-extensions-utils_version`, which must be set up by developer. Available versions are presented on
[bintray](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-utils), next version is last published:
[![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-utils/images/download.svg) ](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI-extensions-utils/_latestVersion)
### Maven
Dependency config presented here:
```xml
<dependency>
<groupId>com.github.insanusmokrassar</groupId>
<artifactId>TelegramBotAPI-extensions-utils</artifactId>
<version>${telegrambotapi-extensions-utils_version}</version>
</dependency>
```
### Gradle
To use last versions you will need to add one line in repositories block of your `build.gradle`:
`jcenter()` or `mavenCentral()`
And add next line to your dependencies block:
```groovy
implementation "com.github.insanusmokrassar:TelegramBotAPI-extensions-utils:$telegrambotapi-extensions-utils_version"
```
or for old gradle:
```groovy
compile "com.github.insanusmokrassar:TelegramBotAPI-extensions-utils:$telegrambotapi-extensions-utils_version"
```
## How to use?
Here will be presented several examples of usage. In all cases it is expected that you have created your bot and filter:
```kotlin
val bot: RequestsExecutor = KtorRequestsExecutor(
TelegramAPIUrlsKeeper(BOT_TOKEN)
)
val filter = FlowsUpdatesFilter(64)
```
Alternative way to use the things below:
```kotlin
val filter = bot.startGettingFlowsUpdatesByLongPolling(
scope = CoroutineScope(Dispatchers.Default)
) {
// place code from examples here with replacing of `filter` by `this`
}
```
### Updates
As mentioned in [Telegram Bot API reference](https://core.telegram.org/bots/api#getting-updates), there are two ways for
updates retrieving:
* Webhooks
* Long Polling
Both of them you could use in your project using [TelegramBotAPI](../TelegramBotAPI/README.md), but here there are
several useful extensions for both of them.
Anyway, in both of ways it will be useful to know that it is possible to create `UpdateReceiver` object using function
`flowsUpdatesFilter`:
```kotlin
val internalChannelsSizes = 128
flowsUpdatesFilter(internalChannelsSizes/* default is 64 */) {
/* ... */
}
```
#### Long polling
The most simple way is Long Polling and one of the usages was mentioned above:
```kotlin
val filter = bot.startGettingFlowsUpdatesByLongPolling(
scope = CoroutineScope(Dispatchers.Default)
) {
// place code from examples here with replacing of `filter` by `this`
}
```
Extension `startGettingFlowsUpdatesByLongPolling` was used in this example, but there are a lot of variations of
`startGettingOfUpdatesByLongPolling` and others for getting the same result. Usually, it is supposed that you already
have created `filter` object (or something like this) and will pass it into extension:
```kotlin
val filter = FlowsUpdatesFilter(64)
bot.startGettingOfUpdatesByLongPolling(
filter
)
```
But also there are extensions which allow to pass lambdas directly:
```kotlin
bot.startGettingOfUpdatesByLongPolling(
{
println("Received message update: $it")
}
)
```
Anyway, it is strictly recommended to pass your `CoroutineScope` object to this method at least for more comfortable
management of updates.
#### WebHooks (currently JVM-only)
For webhooks there are less number of functions and extensions than for Long Polling (but it is still fully automated):
```kotlin
startListenWebhooks(
8081,
CIO // require to implement this engine dependency
) {
// here will be all updates one by one in $it
}
```
Besides, there are two additional opportunities:
* Extension `Route#includeWebhookHandlingInRoute`, which allow you to include webhook processing inside your ktor
application without creating of new one server (as it is happening in `startListenWebhooks`)
* Extension `RequestsExecutor#setWebhookInfoAndStartListenWebhooks`. It is allow to set up full server (in fact, with
`startListenWebhooks`), but also send `SetWebhook` request before and check that it was successful
### Filters
There are several filters for flows.
#### Updates
In the next table it is supposed that you are using some `Flow` with type from `Base type of update` and apply
extension `Extension` and will get `Flow` with type from `Result type of update` column.
| Base type of update | Extension | Result type of update |
| ------------------- | --------- | --------------------- |
| `Update` | `onlyBaseMessageUpdates` | `BaseMessageUpdate` |
| | | |
| `BaseMessageUpdate` | `onlySentMessageUpdates` | `BaseSentMessageUpdate` |
| `BaseMessageUpdate` | `onlyEditMessageUpdates` | `BaseEditMessageUpdate` |
| `BaseMessageUpdate` | `onlyMediaGroupsUpdates` | `MediaGroupUpdate` |
| | | |
| `MediaGroupUpdate` | `onlySentMediaGroupUpdates` | `SentMediaGroupUpdate` |
| `MediaGroupUpdate` | `onlyEditMediaGroupUpdates` | `EditMediaGroupUpdate` |
All of these extensions was made for more simple work with the others:
```kotlin
val flow: Flow<BaseMessageUpdate> = ...; // here we are getting flow from somewhere,
// for example, FlowsUpdatesFilter#messageFlow
flow.onlySentMessageUpdates().filterExactCommands(Regex("start"))
```
Here we have used filter `filterExactCommands` which will pass only `ContentMessage` with only one command `start`
#### Sent messages
All sent messages can be filtered for three types:
| Type | Description | Flow extension |
|:---- |:----------- |:-------------- |
| Common messages | Simple messages with text, media, location, etc. | `asContentMessagesFlow` |
| Chat actions | New chat member, rename of chat, etc. | `asChatEventsFlow` |
| Unknown events | Any other messages, that contain unsupported data | `asUnknownMessagesFlow` |
##### Common messages
Unfortunately, due to the erasing of generic types, when you are using `asContentMessagesFlow` you will retrieve
data with type `ContentMessage<*>`. For correct filtering of content type for retrieved objects, was created special
filters:
| Content type | Result type | Flow extension |
|:---- |:----------- |:-------------- |
| Animation | `ContentMessage<AnimationContent>`| `onlyAnimationContentMessages` |
| Audio | `ContentMessage<AudioContent>` | `onlyAudioContentMessages` |
| Contact | `ContentMessage<ContactContent>` | `onlyContactContentMessages` |
| Dice | `ContentMessage<DiceContent>` | `onlyDiceContentMessages` |
| Document | `ContentMessage<DocumentContent>` | `onlyDocumentContentMessages` |
| Game | `ContentMessage<GameContent>` | `onlyGameContentMessages` |
| Invoice | `ContentMessage<InvoiceContent>` | `onlyInvoiceContentMessages` |
| Location | `ContentMessage<LocationContent>` | `onlyLocationContentMessages` |
| Photo | `ContentMessage<PhotoContent>` | `onlyPhotoContentMessages` |
| Poll | `ContentMessage<PollContent>` | `onlyPollContentMessages` |
| Sticker | `ContentMessage<StickerContent>` | `onlyStickerContentMessages` |
| Text | `ContentMessage<TextContent>` | `onlyTextContentMessages` |
| Venue | `ContentMessage<VenueContent>` | `onlyVenueContentMessages` |
| Video | `ContentMessage<VideoContent>` | `onlyVideoContentMessages` |
| VideoNote | `ContentMessage<VideoNoteContent>` | `onlyVideoNoteContentMessages` |
| Voice | `ContentMessage<VoiceContent>` | `onlyVoiceContentMessages` |
For example, if you wish to get only photo messages from private chats of groups, you should call next code:
```kotlin
filter.messageFlow.asContentMessagesFlow().onlyPhotoContentMessages().onEach {
println(it.content)
}.launchIn(
CoroutineScope(Dispatchers.Default)
)
```
##### Chat actions
Chat actions can be divided for three types of events source:
| Type | Flow extension |
|:---- |:-------------- |
| Channel events | `onlyChannelEvents` |
| Group events | `onlyGroupEvents` |
| Supergroup events | `onlySupergroupEvents` |
According to this table, if you want to add filtering by supergroup events, you will use code like this:
```kotlin
filter.messageFlow.asChatEventsFlow().onlySupergroupEvents().onEach {
println(it.chatEvent)
}.launchIn(
CoroutineScope(Dispatchers.Default)
)
```
### Shortcuts
With shortcuts you are able to use simple factories for several things.
#### ScheduledCloseInfo
In case if you are creating some poll, you able to use next shortcuts.
Next sample will use info with closing at the 10 seconds after now:
```kotlin
closePollExactAt(DateTime.now() + TimeSpan(10000.0))
```
In this example we will do the same, but in another way:
```kotlin
closePollExactAfter(10)
```
Here we have passed `10` seconds and will get the same result object.
In opposite to previous shortcuts, the next one will create `approximate` closing schedule:
```kotlin
closePollAfter(10)
```
The main difference here is that the last one will be closed after 10 seconds since the sending. With first samples
will be created **exact** time for closing of poll

View File

@@ -0,0 +1,48 @@
buildscript {
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$gradle_bintray_plugin_version"
}
}
plugins {
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
}
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()
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
if ((project.hasProperty('RELEASE_MODE') && project.property('RELEASE_MODE') == "true") || System.getenv('RELEASE_MODE') == "true") {
api "${project.group}:TelegramBotAPI:$library_version"
} else {
api project(":TelegramBotAPI")
}
}
}
}
}

View File

@@ -0,0 +1,53 @@
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

@@ -0,0 +1 @@
{"bintrayConfig":{"repo":"StandardRepository","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/TelegramBotAPI"},"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 Utility Extensions","description":"Util extensions for more useful work with updates and other things","url":"https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI-extensions-utils","vcsUrl":"https://github.com/insanusmokrassar/TelegramBotAPI.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}]},"type":"Multiplatform"}

View File

@@ -0,0 +1,55 @@
apply plugin: 'com.jfrog.bintray'
apply from: "maven.publish.gradle"
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(".", "/")
}
pkg {
repo = "StandardRepository"
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 {
publications = publishing.publications.collect {
if (it.name.contains('kotlinMultiplatform')) {
null
} else {
it.name
}
} - null
}
bintrayUpload.dependsOn publishToMavenLocal

View File

@@ -0,0 +1,12 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils
import com.github.insanusmokrassar.TelegramBotAPI.types.CallbackQuery.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
fun <T : CallbackQuery> Flow<T>.onlyMessageDataCallbackQueries() = mapNotNull {
it as? MessageDataCallbackQuery
}
fun <T : CallbackQuery> Flow<T>.onlyInlineMessageIdDataCallbackQueries() = mapNotNull {
it as? InlineMessageIdDataCallbackQuery
}

View File

@@ -0,0 +1,34 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.CommonMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.abstracts.MessageContent
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.abstracts.PossiblySentViaBotCommonMessage
import kotlinx.coroutines.flow.*
/**
* Simple factory to convert [ContentMessage] to a [CommonMessage]
*/
fun <C: MessageContent, T : ContentMessage<C>> Flow<T>.onlyCommonMessages() = filterIsInstance<CommonMessage<C>>()
/**
* Filter the messages and checking that incoming [CommonMessage] is [PossiblySentViaBotCommonMessage] and its
* [PossiblySentViaBotCommonMessage.senderBot] is not null
*/
fun <T : MessageContent> Flow<CommonMessage<T>>.onlySentViaBot() = mapNotNull {
(it as? PossiblySentViaBotCommonMessage) ?.let { possiblySentViaBot ->
if (possiblySentViaBot.senderBot != null) {
possiblySentViaBot
} else {
null
}
}
}
/**
* Filter the messages and checking that incoming [CommonMessage] not is [PossiblySentViaBotCommonMessage] or its
* [PossiblySentViaBotCommonMessage.senderBot] is null
*/
fun <T : MessageContent> Flow<CommonMessage<T>>.withoutSentViaBot() = filter {
it !is PossiblySentViaBotCommonMessage || it.senderBot == null
}

View File

@@ -0,0 +1,36 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.*
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.abstracts.MessageContent
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.media.*
import com.github.insanusmokrassar.TelegramBotAPI.types.message.payments.InvoiceContent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
import kotlin.reflect.KClass
fun <T : MessageContent> Flow<ContentMessage<*>>.withContentType(contentType: KClass<T>) = mapNotNull {
if (contentType.isInstance(it.content)) {
@Suppress("UNCHECKED_CAST")
it as ContentMessage<T>
} else {
null
}
}
fun Flow<ContentMessage<*>>.onlyAnimationContentMessages() = withContentType(AnimationContent::class)
fun Flow<ContentMessage<*>>.onlyAudioContentMessages() = withContentType(AudioContent::class)
fun Flow<ContentMessage<*>>.onlyContactContentMessages() = withContentType(ContactContent::class)
fun Flow<ContentMessage<*>>.onlyDiceContentMessages() = withContentType(DiceContent::class)
fun Flow<ContentMessage<*>>.onlyDocumentContentMessages() = withContentType(DocumentContent::class)
fun Flow<ContentMessage<*>>.onlyGameContentMessages() = withContentType(GameContent::class)
fun Flow<ContentMessage<*>>.onlyInvoiceContentMessages() = withContentType(InvoiceContent::class)
fun Flow<ContentMessage<*>>.onlyLocationContentMessages() = withContentType(LocationContent::class)
fun Flow<ContentMessage<*>>.onlyPhotoContentMessages() = withContentType(PhotoContent::class)
fun Flow<ContentMessage<*>>.onlyPollContentMessages() = withContentType(PollContent::class)
fun Flow<ContentMessage<*>>.onlyStickerContentMessages() = withContentType(StickerContent::class)
fun Flow<ContentMessage<*>>.onlyTextContentMessages() = withContentType(TextContent::class)
fun Flow<ContentMessage<*>>.onlyVenueContentMessages() = withContentType(VenueContent::class)
fun Flow<ContentMessage<*>>.onlyVideoContentMessages() = withContentType(VideoContent::class)
fun Flow<ContentMessage<*>>.onlyVideoNoteContentMessages() = withContentType(VideoNoteContent::class)
fun Flow<ContentMessage<*>>.onlyVoiceContentMessages() = withContentType(VoiceContent::class)

View File

@@ -0,0 +1,11 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils
import kotlinx.serialization.json.Json
@Suppress("EXPERIMENTAL_API_USAGE")
internal val nonstrictJsonFormat = Json {
isLenient = true
ignoreUnknownKeys = true
serializeSpecialFloatingPointValues = true
useArrayPolymorphism = true
}

View File

@@ -0,0 +1,16 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
import kotlinx.coroutines.CoroutineScope
/**
* Shortcut for [handleSafely]. It was created for more comfortable way of handling different things
*/
@PreviewFeature
suspend inline fun <T> safely(
noinline onException: ExceptionHandler<T> = { throw it },
noinline block: suspend CoroutineScope.() -> T
): T = handleSafely(
onException,
block
)

View File

@@ -0,0 +1,21 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.chat_events
import com.github.insanusmokrassar.TelegramBotAPI.types.message.*
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ChatEventMessage
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
import kotlin.reflect.KClass
fun <T : ChatEventMessage> Flow<ChatEventMessage>.divideBySource(contentType: KClass<T>) = mapNotNull {
if (contentType.isInstance(it)) {
@Suppress("UNCHECKED_CAST")
it as T
} else {
null
}
}
fun Flow<ChatEventMessage>.onlyChannelEvents() = divideBySource(ChannelEventMessage::class)
fun Flow<ChatEventMessage>.onlyGroupEvents() = divideBySource(GroupEventMessage::class)
fun Flow<ChatEventMessage>.onlySupergroupEvents() = divideBySource(SupergroupEventMessage::class)

View File

@@ -0,0 +1,80 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.formatting
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.*
import com.github.insanusmokrassar.TelegramBotAPI.types.StickerSetName
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.PrivateChat
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.UsernameChat
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.extended.ExtendedChat
import com.github.insanusmokrassar.TelegramBotAPI.utils.link
private const val internalLinkBeginning = "https://t.me"
fun makeLinkToMessage(
username: String,
messageId: MessageIdentifier
): String = "$internalLinkBeginning/$username/$messageId"
private val linkIdRedundantPartRegex = Regex("^-100")
private val usernameBeginSymbolRegex = Regex("^@")
fun makeLinkToMessage(
chat: ExtendedChat,
messageId: MessageIdentifier
): String? {
return when {
chat is UsernameChat && chat.username != null -> {
"$internalLinkBeginning/${chat.username ?.username ?.replace(
usernameBeginSymbolRegex, "")}/$messageId"
}
chat !is PrivateChat -> chat.id.chatId.toString().replace(
linkIdRedundantPartRegex,
""
).let { bareId ->
"$internalLinkBeginning/c/$bareId/$messageId"
}
else -> return null
}
}
private const val stickerSetAddingLinkPrefix = "$internalLinkBeginning/addstickers"
/**
* Create a link for adding of sticker set with name [stickerSetName]. Was added thanks to user Djaler and based on
* https://github.com/Djaler/evil-bot/blob/master/src/main/kotlin/com/github/djaler/evilbot/utils/StickerUtils.kt#L6-L8
*
* @see [makeLinkToAddStickerSetInMarkdownV2]
* @see [makeLinkToAddStickerSetInMarkdown]
* @see [makeLinkToAddStickerSetInHtml]
*/
fun makeLinkToAddStickerSet(
stickerSetName: StickerSetName,
parseMode: ParseMode
) = (stickerSetName to "$stickerSetAddingLinkPrefix/$stickerSetName").link(
parseMode
)
/**
* @return Link for adding of sticker set with name [stickerSetName] with formatting for [MarkdownV2]
*/
fun makeLinkToAddStickerSetInMarkdownV2(stickerSetName: StickerSetName) =
makeLinkToAddStickerSet(
stickerSetName,
MarkdownV2
)
/**
* @return Link for adding of sticker set with name [stickerSetName] with formatting for [Markdown]
*/
fun makeLinkToAddStickerSetInMarkdown(stickerSetName: StickerSetName) =
makeLinkToAddStickerSet(
stickerSetName,
Markdown
)
/**
* @return Link for adding of sticker set with name [stickerSetName] with formatting for [HTML]
*/
fun makeLinkToAddStickerSetInHtml(stickerSetName: StickerSetName) =
makeLinkToAddStickerSet(
stickerSetName,
HTML
)

View File

@@ -0,0 +1,121 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.formatting
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.*
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.TextContent
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.fullEntitiesList
fun createFormattedText(
entities: FullTextSourcesList,
partLength: Int = textLength.last,
mode: ParseMode = MarkdownParseMode
): List<String> {
val texts = mutableListOf<String>()
val textBuilder = StringBuilder(partLength)
for (entity in entities) {
val string = when (mode) {
is MarkdownParseMode -> entity.asMarkdownSource
is MarkdownV2ParseMode -> entity.asMarkdownV2Source
is HTMLParseMode -> entity.asHtmlSource
}
if (textBuilder.length + string.length > partLength) {
if (textBuilder.isNotEmpty()) {
texts.add(textBuilder.toString())
textBuilder.clear()
}
val chunked = string.chunked(partLength)
val last = chunked.last()
textBuilder.append(last)
val listToAdd = if (chunked.size > 1) {
chunked.subList(0, chunked.size - 1)
} else {
emptyList()
}
listToAdd.forEach {
texts.add(it)
}
} else {
textBuilder.append(string)
}
}
if (textBuilder.isNotEmpty()) {
texts.add(textBuilder.toString())
textBuilder.clear()
}
return texts
}
fun createMarkdownText(
entities: FullTextSourcesList,
partLength: Int = textLength.last
): List<String> = createFormattedText(entities, partLength, MarkdownParseMode)
fun FullTextSourcesList.toMarkdownCaptions(): List<String> = createMarkdownText(
this,
captionLength.last
)
fun CaptionedInput.toMarkdownCaptions(): List<String> = fullEntitiesList().toMarkdownCaptions()
fun FullTextSourcesList.toMarkdownTexts(): List<String> = createMarkdownText(
this,
textLength.last
)
fun TextContent.toMarkdownTexts(): List<String> = fullEntitiesList().toMarkdownTexts()
fun FullTextSourcesList.toMarkdownExplanations(): List<String> = createMarkdownText(
this,
explanationLimit.last
)
fun ExplainedInput.toMarkdownExplanations(): List<String> = fullEntitiesList().toMarkdownTexts()
fun createMarkdownV2Text(
entities: FullTextSourcesList,
partLength: Int = textLength.last
): List<String> = createFormattedText(entities, partLength, MarkdownV2ParseMode)
fun FullTextSourcesList.toMarkdownV2Captions(): List<String> = createMarkdownV2Text(
this,
captionLength.last
)
fun CaptionedInput.toMarkdownV2Captions(): List<String> = fullEntitiesList().toMarkdownV2Captions()
fun FullTextSourcesList.toMarkdownV2Texts(): List<String> = createMarkdownV2Text(
this,
textLength.last
)
fun TextContent.toMarkdownV2Texts(): List<String> = fullEntitiesList().toMarkdownV2Texts()
fun FullTextSourcesList.toMarkdownV2Explanations(): List<String> = createMarkdownV2Text(
this,
explanationLimit.last
)
fun ExplainedInput.toMarkdownV2Explanations(): List<String> = fullEntitiesList().toMarkdownV2Texts()
fun createHtmlText(
entities: FullTextSourcesList,
partLength: Int = textLength.last
): List<String> = createFormattedText(entities, partLength, HTMLParseMode)
fun FullTextSourcesList.toHtmlCaptions(): List<String> = createHtmlText(
this,
captionLength.last
)
fun CaptionedInput.toHtmlCaptions(): List<String> = fullEntitiesList().toHtmlCaptions()
fun FullTextSourcesList.toHtmlTexts(): List<String> = createHtmlText(
this,
textLength.last
)
fun TextContent.toHtmlTexts(): List<String> = fullEntitiesList().toHtmlTexts()
fun FullTextSourcesList.toHtmlExplanations(): List<String> = createHtmlText(
this,
explanationLimit.last
)
fun ExplainedInput.toHtmlExplanations(): List<String> = fullEntitiesList().toHtmlTexts()

View File

@@ -0,0 +1,243 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.formatting
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.extensions.*
const val markdownBoldControl = "*"
const val markdownItalicControl = "_"
const val markdownCodeControl = "`"
const val markdownPreControl = "```"
const val markdownV2ItalicUnderlineDelimiter = "\u0013"
const val markdownV2StrikethroughControl = "~"
const val markdownV2UnderlineControl = "__"
const val markdownV2UnderlineEndControl = "$markdownV2UnderlineControl$markdownV2ItalicUnderlineDelimiter"
const val markdownV2ItalicEndControl = "$markdownItalicControl$markdownV2ItalicUnderlineDelimiter"
const val htmlBoldControl = "b"
const val htmlItalicControl = "i"
const val htmlCodeControl = "code"
const val htmlPreControl = "pre"
const val htmlUnderlineControl = "u"
const val htmlStrikethroughControl = "s"
private fun String.markdownDefault(
openControlSymbol: String,
closeControlSymbol: String = openControlSymbol
) = "$openControlSymbol${toMarkdown()}$closeControlSymbol"
private fun String.markdownV2Default(
openControlSymbol: String,
closeControlSymbol: String = openControlSymbol,
escapeFun: String.() -> String = String::escapeMarkdownV2Common
) = "$openControlSymbol${escapeFun()}$closeControlSymbol"
private fun String.htmlDefault(
openControlSymbol: String,
closeControlSymbol: String = openControlSymbol
) = "<$openControlSymbol>${toHtml()}</$closeControlSymbol>"
fun String.linkMarkdown(link: String): String = "[${toMarkdown()}](${link.toMarkdown()})"
fun String.linkMarkdownV2(link: String): String = "[${escapeMarkdownV2Common()}](${link.escapeMarkdownV2Link()})"
fun String.linkHTML(link: String): String = "<a href=\"$link\">${toHtml()}</a>"
fun String.boldMarkdown(): String = markdownDefault(markdownBoldControl)
fun String.boldMarkdownV2(): String = markdownV2Default(markdownBoldControl)
fun String.boldHTML(): String = htmlDefault(htmlBoldControl)
fun String.italicMarkdown(): String = markdownDefault(markdownItalicControl)
fun String.italicMarkdownV2(): String = markdownV2Default(markdownItalicControl, markdownV2ItalicEndControl)
fun String.italicHTML(): String = htmlDefault(htmlItalicControl)
/**
* Crutch for support of strikethrough in default markdown. Simply add modifier, but it will not look like correct
*/
fun String.strikethroughMarkdown(): String = map { it + "\u0336" }.joinToString("")
fun String.strikethroughMarkdownV2(): String = markdownV2Default(markdownV2StrikethroughControl)
fun String.strikethroughHTML(): String = htmlDefault(htmlStrikethroughControl)
/**
* Crutch for support of underline in default markdown. Simply add modifier, but it will not look like correct
*/
fun String.underlineMarkdown(): String = map { it + "\u0347" }.joinToString("")
fun String.underlineMarkdownV2(): String = markdownV2Default(markdownV2UnderlineControl, markdownV2UnderlineEndControl)
fun String.underlineHTML(): String = htmlDefault(htmlUnderlineControl)
fun String.codeMarkdown(): String = markdownDefault(markdownCodeControl)
fun String.codeMarkdownV2(): String = markdownV2Default(markdownCodeControl, escapeFun = String::escapeMarkdownV2PreAndCode)
fun String.codeHTML(): String = htmlDefault(htmlCodeControl)
fun String.preMarkdown(language: String? = null): String = markdownDefault(
"$markdownPreControl${language ?: ""}\n",
"\n$markdownPreControl"
)
fun String.preMarkdownV2(language: String? = null): String = markdownV2Default(
"$markdownPreControl${language ?: ""}\n",
"\n$markdownPreControl",
String::escapeMarkdownV2PreAndCode
)
fun String.preHTML(language: String? = null): String = htmlDefault(
language ?.let {
"$htmlPreControl><$htmlCodeControl class=\"language-$language\""
} ?: htmlPreControl,
language ?.let {
"$htmlCodeControl></$htmlPreControl"
} ?: htmlPreControl
)
fun String.emailMarkdown(): String = linkMarkdown("mailto://$${toMarkdown()}")
fun String.emailMarkdownV2(): String = linkMarkdownV2("mailto://$${toMarkdown()}")
fun String.emailHTML(): String = linkHTML("mailto://$${toHtml()}")
private inline fun String.mention(adapt: String.() -> String): String = if (startsWith("@")) {
adapt()
} else {
"@${adapt()}"
}
private inline fun String.hashTag(adapt: String.() -> String): String = if (startsWith("#")) {
adapt()
} else {
"#${adapt()}"
}
fun String.textMentionMarkdown(userId: UserId): String = linkMarkdown(userId.link)
fun String.textMentionMarkdownV2(userId: UserId): String = linkMarkdownV2(userId.link)
fun String.textMentionHTML(userId: UserId): String = linkHTML(userId.link)
fun String.mentionMarkdown(): String = mention(String::toMarkdown)
fun String.mentionMarkdownV2(): String = mention(String::escapeMarkdownV2Common)
fun String.mentionHTML(): String = mention(String::toHtml)
fun String.hashTagMarkdown(): String = hashTag(String::toMarkdown)
fun String.hashTagMarkdownV2(): String = hashTag(String::escapeMarkdownV2Common).escapeMarkdownV2Common()
fun String.hashTagHTML(): String = hashTag(String::toHtml)
fun String.phoneMarkdown(): String = toMarkdown()
fun String.phoneMarkdownV2(): String = escapeMarkdownV2Common()
fun String.phoneHTML(): String = toHtml()
fun String.command(adapt: String.() -> String): String = if (startsWith("/")) {
adapt()
} else {
"/${adapt()}"
}
fun String.commandMarkdown(): String = command(String::toMarkdown)
fun String.commandMarkdownV2(): String = command(String::escapeMarkdownV2Common)
fun String.commandHTML(): String = command(String::toHtml)
fun String.regularMarkdown(): String = toMarkdown()
fun String.regularMarkdownV2(): String = escapeMarkdownV2Common()
fun String.regularHtml(): String = toHtml()
fun String.cashTagMarkdown(): String = toMarkdown()
fun String.cashTagMarkdownV2(): String = escapeMarkdownV2Common()
fun String.cashTagHtml(): String = toHtml()
infix fun String.bold(parseMode: ParseMode): String = when (parseMode) {
is HTML -> boldHTML()
is Markdown -> boldMarkdown()
is MarkdownV2 -> boldMarkdownV2()
}
infix fun String.italic(parseMode: ParseMode): String = when (parseMode) {
is HTML -> italicHTML()
is Markdown -> italicMarkdown()
is MarkdownV2 -> italicMarkdownV2()
}
infix fun String.hashTag(parseMode: ParseMode): String = when (parseMode) {
is HTML -> hashTagHTML()
is Markdown -> hashTagMarkdown()
is MarkdownV2 -> hashTagMarkdownV2()
}
infix fun String.code(parseMode: ParseMode): String = when (parseMode) {
is HTML -> codeHTML()
is Markdown -> codeMarkdown()
is MarkdownV2 -> codeMarkdownV2()
}
fun String.pre(parseMode: ParseMode, language: String? = null): String = when (parseMode) {
is HTML -> preHTML(language)
is Markdown -> preMarkdown(language)
is MarkdownV2 -> preMarkdownV2(language)
}
infix fun String.pre(parseMode: ParseMode): String = pre(parseMode, null)
infix fun String.email(parseMode: ParseMode): String = when (parseMode) {
is HTML -> emailHTML()
is Markdown -> emailMarkdown()
is MarkdownV2 -> emailMarkdownV2()
}
infix fun Pair<String, String>.link(parseMode: ParseMode): String = when (parseMode) {
is HTML -> first.linkHTML(second)
is Markdown -> first.linkMarkdown(second)
is MarkdownV2 -> first.linkMarkdownV2(second)
}
infix fun String.mention(parseMode: ParseMode): String = when (parseMode) {
is HTML -> mentionHTML()
is Markdown -> mentionMarkdown()
is MarkdownV2 -> mentionMarkdownV2()
}
infix fun Pair<String, ChatId>.mention(parseMode: ParseMode): String = when (parseMode) {
is HTML -> first.textMentionHTML(second)
is Markdown -> first.textMentionMarkdown(second)
is MarkdownV2 -> first.textMentionMarkdownV2(second)
}
infix fun String.phone(parseMode: ParseMode): String = when (parseMode) {
is HTML -> phoneHTML()
is Markdown -> phoneMarkdown()
is MarkdownV2 -> phoneMarkdownV2()
}
infix fun String.command(parseMode: ParseMode): String = when (parseMode) {
is HTML -> commandHTML()
is Markdown -> commandMarkdown()
is MarkdownV2 -> commandMarkdownV2()
}
infix fun String.underline(parseMode: ParseMode): String = when (parseMode) {
is HTML -> underlineHTML()
is Markdown -> underlineMarkdown()
is MarkdownV2 -> underlineMarkdownV2()
}
infix fun String.strikethrough(parseMode: ParseMode): String = when (parseMode) {
is HTML -> strikethroughHTML()
is Markdown -> strikethroughMarkdown()
is MarkdownV2 -> strikethroughMarkdownV2()
}
infix fun String.regular(parseMode: ParseMode): String = when (parseMode) {
is HTML -> regularHtml()
is Markdown -> regularMarkdown()
is MarkdownV2 -> regularMarkdownV2()
}
infix fun String.cashtag(parseMode: ParseMode): String = when (parseMode) {
is HTML -> cashTagHtml()
is Markdown -> cashTagMarkdown()
is MarkdownV2 -> cashTagMarkdownV2()
}

View File

@@ -0,0 +1,56 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.shortcuts
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.SendMediaGroup
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat
import com.github.insanusmokrassar.TelegramBotAPI.types.message.ForwardInfo
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.SentMediaGroupUpdate
val List<MediaGroupMessage>.forwardInfo: ForwardInfo?
get() = firstOrNull() ?.forwardInfo
val List<MediaGroupMessage>.replyTo: Message?
get() = firstOrNull() ?.replyTo
val List<MediaGroupMessage>.chat: Chat?
get() = firstOrNull() ?.chat
val List<MediaGroupMessage>.mediaGroupId: MediaGroupIdentifier?
get() = firstOrNull() ?.mediaGroupId
val SentMediaGroupUpdate.forwardInfo: ForwardInfo?
get() = data.first().forwardInfo
val SentMediaGroupUpdate.replyTo: Message?
get() = data.first().replyTo
val SentMediaGroupUpdate.chat: Chat
get() = data.chat!!
val SentMediaGroupUpdate.mediaGroupId: MediaGroupIdentifier
get() = data.mediaGroupId!!
fun List<MediaGroupMessage>.createResend(
chatId: ChatId,
disableNotification: Boolean = false,
replyTo: MessageIdentifier? = null
) = SendMediaGroup(
chatId,
map { it.content.toMediaGroupMemberInputMedia() },
disableNotification,
replyTo
)
fun List<MediaGroupMessage>.createResend(
chat: Chat,
disableNotification: Boolean = false,
replyTo: MessageIdentifier? = null
) = createResend(
chat.id,
disableNotification,
replyTo
)
fun SentMediaGroupUpdate.createResend(
disableNotification: Boolean = false,
replyTo: MessageIdentifier? = null
) = data.createResend(
chat,
disableNotification,
replyTo
)

View File

@@ -0,0 +1,35 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.shortcuts
import com.github.insanusmokrassar.TelegramBotAPI.types.LongSeconds
import com.github.insanusmokrassar.TelegramBotAPI.types.Seconds
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.ApproximateScheduledCloseInfo
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.ExactScheduledCloseInfo
import com.soywiz.klock.DateTime
import com.soywiz.klock.TimeSpan
fun closePollExactAt(
dateTime: DateTime
) = ExactScheduledCloseInfo(
dateTime
)
fun closePollExactAfter(
seconds: LongSeconds
) = closePollExactAt(
DateTime.now() + TimeSpan(seconds.toDouble() * 1000L)
)
fun closePollExactAfter(
seconds: Seconds
) = closePollExactAfter(
seconds.toLong()
)
fun closePollAfter(
seconds: LongSeconds
) = ApproximateScheduledCloseInfo(
TimeSpan(seconds.toDouble() * 1000L)
)
fun closePollAfter(
seconds: Seconds
) = closePollAfter(seconds.toLong())

View File

@@ -0,0 +1,45 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.shortcuts
import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request
import com.github.insanusmokrassar.TelegramBotAPI.utils.handleSafely
import kotlinx.coroutines.*
fun <T: Any> RequestsExecutor.executeAsync(
request: Request<T>,
scope: CoroutineScope
): Deferred<T> = scope.async {
handleSafely {
execute(request)
}
}
suspend fun <T: Any> RequestsExecutor.executeAsync(
request: Request<T>
): Deferred<T> = coroutineScope {
executeAsync(request, this)
}
suspend fun <T: Any> RequestsExecutor.executeUnsafe(
request: Request<T>,
retries: Int = 0,
retriesDelay: Long = 1000L,
onAllFailed: (suspend (exceptions: Array<Exception>) -> Unit)? = null
): T? {
var leftRetries = retries
val exceptions = onAllFailed ?.let { mutableListOf<Exception>() }
do {
return handleSafely(
{
leftRetries--
delay(retriesDelay)
exceptions ?.add(it)
null
}
) {
execute(request)
} ?: continue
} while(leftRetries >= 0)
onAllFailed ?.invoke(exceptions ?.toTypedArray() ?: emptyArray())
return null
}

View File

@@ -0,0 +1,49 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates
import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
fun Flow<Update>.onlyBaseMessageUpdates(): Flow<BaseMessageUpdate> = mapNotNull {
it as? BaseMessageUpdate
}
/**
* Converts flow to [Flow] of [BaseSentMessageUpdate]
*/
fun Flow<BaseMessageUpdate>.onlySentMessageUpdates(): Flow<BaseSentMessageUpdate> = mapNotNull {
it as? BaseSentMessageUpdate
}
/**
* Converts flow to [Flow] of [BaseSentMessageUpdate]
*/
fun Flow<BaseMessageUpdate>.onlyEditMessageUpdates(): Flow<BaseEditMessageUpdate> = mapNotNull {
it as? BaseEditMessageUpdate
}
/**
* Converts flow to [Flow] of [MediaGroupUpdate]. Please, remember that it could be either [EditMediaGroupUpdate]
* or [SentMediaGroupUpdate]
*
* @see onlySentMediaGroupUpdates
* @see onlyEditMediaGroupUpdates
*/
fun Flow<BaseMessageUpdate>.onlyMediaGroupsUpdates(): Flow<MediaGroupUpdate> = mapNotNull {
it as? MediaGroupUpdate
}
/**
* Converts flow to [Flow] of [SentMediaGroupUpdate]
*/
fun Flow<MediaGroupUpdate>.onlySentMediaGroupUpdates(): Flow<SentMediaGroupUpdate> = mapNotNull {
it as? SentMediaGroupUpdate
}
/**
* Converts flow to [Flow] of [EditMediaGroupUpdate]
*/
fun Flow<MediaGroupUpdate>.onlyEditMediaGroupUpdates(): Flow<EditMediaGroupUpdate> = mapNotNull {
it as? EditMediaGroupUpdate
}

View File

@@ -0,0 +1,25 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates
import com.github.insanusmokrassar.TelegramBotAPI.types.CallbackQuery.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.CallbackQueryUpdate
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
/**
* @return New [Flow] with [DataCallbackQuery] type, got from [CallbackQueryUpdate.data] field
*/
fun Flow<CallbackQueryUpdate>.asDataCallbackQueryFlow() = mapNotNull {
it.data as? DataCallbackQuery
}
/**
* @return New [Flow] with [GameShortNameCallbackQuery] type, got from [CallbackQueryUpdate.data] field
*/
fun Flow<CallbackQueryUpdate>.asGameShortNameCallbackQueryFlow() = mapNotNull {
it.data as? GameShortNameCallbackQuery
}
/**
* @return New [Flow] with [UnknownCallbackQueryType] type, got from [CallbackQueryUpdate.data] field
*/
fun Flow<CallbackQueryUpdate>.asUnknownCallbackQueryFlow() = mapNotNull {
it.data as? UnknownCallbackQueryType
}

View File

@@ -0,0 +1,86 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextSource
import com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.onlyTextContentMessages
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsources.BotCommandTextSource
import com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsources.RegularTextSource
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.fullEntitiesList
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseSentMessageUpdate
import kotlinx.coroutines.flow.*
/**
* Convert incoming [com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage.content] of
* messages with [fullEntitiesList] and check that incoming message contains ONLY ONE [TextSource] and that is
* [BotCommandTextSource]. Besides, it is checking that [BotCommandTextSource.command] [Regex.matches] with incoming
* [commandRegex]
*
* @return The same message in case if it contains only [BotCommandTextSource] with [Regex.matches]
* [BotCommandTextSource.command]
*
* @see fullEntitiesList
* @see asContentMessagesFlow
* @see onlyTextContentMessages
*/
fun <T : BaseSentMessageUpdate> Flow<T>.filterExactCommands(
commandRegex: Regex
) = asContentMessagesFlow().onlyTextContentMessages().filter { contentMessage ->
(contentMessage.content.fullEntitiesList().singleOrNull() as? BotCommandTextSource) ?.let { commandRegex.matches(it.command) } == true
}
/**
* Convert incoming [com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage.content] of
* messages with [fullEntitiesList] and check that incoming message contains [BotCommandTextSource]. Besides, it is
* checking that [BotCommandTextSource.command] [Regex.matches] with incoming [commandRegex]
*
* @return The same message in case if it contains somewhere in text [BotCommandTextSource] with [Regex.matches]
* [BotCommandTextSource.command]
*
* @see fullEntitiesList
* @see asContentMessagesFlow
* @see onlyTextContentMessages
*/
fun <T : BaseSentMessageUpdate> Flow<T>.filterCommandsInsideTextMessages(
commandRegex: Regex
) = asContentMessagesFlow().onlyTextContentMessages().filter { contentMessage ->
contentMessage.content.fullEntitiesList().any {
(it as? BotCommandTextSource) ?.let { commandRegex.matches(it.command) } == true
}
}
/**
* Convert incoming [com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage.content] of
* messages with [fullEntitiesList] and check that incoming message contains first [TextSource] as
* [BotCommandTextSource]. Besides, it is checking that [BotCommandTextSource.command] [Regex.matches] with incoming
* [commandRegex] and for other [TextSource] objects used next rules: all incoming text sources will be passed as is,
* [RegularTextSource] will be split by " " for several [RegularTextSource] which will contains not empty args without
* spaces.
*
* @return Converted list with first entity [BotCommandTextSource] and than all others according to rules in description
*
* @see fullEntitiesList
* @see asContentMessagesFlow
* @see onlyTextContentMessages
*/
fun <T : BaseSentMessageUpdate> Flow<T>.filterCommandsWithArgs(
commandRegex: Regex
): Flow<List<TextSource>> = asContentMessagesFlow().onlyTextContentMessages().mapNotNull { contentMessage ->
val allEntities = contentMessage.content.fullEntitiesList()
(allEntities.firstOrNull() as? BotCommandTextSource) ?.let {
if (commandRegex.matches(it.command)) {
allEntities.flatMap {
when (it) {
is RegularTextSource -> it.source.split(" ").mapNotNull { regularTextSourcePart ->
if (regularTextSourcePart.isNotBlank()) {
RegularTextSource(regularTextSourcePart)
} else {
null
}
}
else -> listOf(it)
}
}
} else {
null
}
}
}

View File

@@ -0,0 +1,17 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates
import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.FlowsUpdatesFilter
/**
* Non-suspendable function for easy-to-use creating of [FlowsUpdatesFilter] and applying the block to it
*
* @see flowsUpdatesFilter
*/
inline fun flowsUpdatesFilter(
internalChannelsSizes: Int = 64,
block: FlowsUpdatesFilter.() -> Unit
): FlowsUpdatesFilter {
val filter = FlowsUpdatesFilter(internalChannelsSizes)
filter.block()
return filter
}

View File

@@ -0,0 +1,34 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseSentMessageUpdate
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapNotNull
/**
* Will map incoming [BaseSentMessageUpdate]s to [ContentMessage] from [BaseSentMessageUpdate.data]
*/
fun <T : BaseSentMessageUpdate> Flow<T>.asContentMessagesFlow() = mapNotNull {
it.data as? ContentMessage<*>
}
/**
* Will map incoming [BaseSentMessageUpdate]s to [CommonMessage] from [BaseSentMessageUpdate.data]
*/
fun <T : BaseSentMessageUpdate> Flow<T>.asCommonMessagesFlow() = mapNotNull {
it.data as? CommonMessage<*>
}
/**
* Will map incoming [BaseSentMessageUpdate]s to [ChatEventMessage] from [BaseSentMessageUpdate.data]
*/
fun <T : BaseSentMessageUpdate> Flow<T>.asChatEventsFlow() = mapNotNull {
it.data as? ChatEventMessage
}
/**
* Will map incoming [BaseSentMessageUpdate]s to [UnknownMessageType] from [BaseSentMessageUpdate.data]
*/
fun <T : BaseSentMessageUpdate> Flow<T>.asUnknownMessagesFlow() = mapNotNull {
it.data as? UnknownMessageType
}

View File

@@ -0,0 +1,31 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates
import com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.nonstrictJsonFormat
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.UpdateDeserializationStrategy
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
/**
* @return Deserialize [source] as [com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update]
*/
fun Json.toTelegramUpdate(source: String) = parse(UpdateDeserializationStrategy, source)
/**
* @return Deserialize [source] as [com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update]
*/
fun Json.toTelegramUpdate(source: JsonElement) = fromJson(UpdateDeserializationStrategy, source)
/**
* @return Deserialize [this] as [com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update]. In fact,
* it is must be JSON
*
* @see Json.toTelegramUpdate
*/
fun String.toTelegramUpdate() = nonstrictJsonFormat.toTelegramUpdate(this)
/**
* @return Deserialize [this] as [com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update]
*
* @see Json.toTelegramUpdate
*/
fun JsonElement.toTelegramUpdate() = nonstrictJsonFormat.toTelegramUpdate(this)

View File

@@ -0,0 +1,47 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates
import com.github.insanusmokrassar.TelegramBotAPI.types.ChatId
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat
import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.SentMediaGroupUpdate
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
/**
* [Flow.filter] incoming [BaseMessageUpdate]s by their [ChatId]
*/
fun <T : BaseMessageUpdate> Flow<T>.filterBaseMessageUpdatesByChatId(chatId: ChatId): Flow<T> = filter { it.data.chat.id == chatId }
/**
* [Flow.filter] incoming [BaseMessageUpdate]s by their [ChatId] using [Chat.id] of [chat]
*/
fun <T : BaseMessageUpdate> Flow<T>.filterBaseMessageUpdatesByChat(chat: Chat): Flow<T> = filterBaseMessageUpdatesByChatId(chat.id)
/**
* [Flow.filter] incoming [BaseMessageUpdate]s by their [ChatId]
*/
@Deprecated("Renamed", ReplaceWith("filterBaseMessageUpdatesByChatId"))
fun <T : BaseMessageUpdate> Flow<T>.filterBaseMessageUpdates(chatId: ChatId): Flow<T> = filterBaseMessageUpdatesByChatId(chatId)
/**
* [Flow.filter] incoming [BaseMessageUpdate]s by their [ChatId] using [Chat.id] of [chat]
*/
@Deprecated("Renamed", ReplaceWith("filterBaseMessageUpdatesByChat"))
fun <T : BaseMessageUpdate> Flow<T>.filterBaseMessageUpdates(chat: Chat): Flow<T> = filterBaseMessageUpdatesByChatId(chat.id)
/**
* [Flow.filter] incoming [SentMediaGroupUpdate]s by their [ChatId]
*/
fun <T : SentMediaGroupUpdate> Flow<T>.filterSentMediaGroupUpdatesByChatId(chatId: ChatId): Flow<T> = filter { it.data.first().chat.id == chatId }
/**
* [Flow.filter] incoming [SentMediaGroupUpdate]s by their [ChatId] using [Chat.id] of [chat]
*/
fun <T : SentMediaGroupUpdate> Flow<T>.filterSentMediaGroupUpdatesByChat(chat: Chat): Flow<T> = filterSentMediaGroupUpdatesByChatId(chat.id)
/**
* [Flow.filter] incoming [SentMediaGroupUpdate]s by their [ChatId]
*/
@Deprecated("Renamed", ReplaceWith("filterSentMediaGroupUpdatesByChatId"))
fun <T : SentMediaGroupUpdate> Flow<T>.filterSentMediaGroupUpdates(chatId: ChatId): Flow<T> = filterSentMediaGroupUpdatesByChatId(chatId)
/**
* [Flow.filter] incoming [SentMediaGroupUpdate]s by their [ChatId] using [Chat.id] of [chat]
*/
@Deprecated("Renamed", ReplaceWith("filterSentMediaGroupUpdatesByChat"))
fun <T : SentMediaGroupUpdate> Flow<T>.filterSentMediaGroupUpdates(chat: Chat): Flow<T> = filterSentMediaGroupUpdatesByChatId(chat.id)

View File

@@ -0,0 +1,93 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates
import com.github.insanusmokrassar.TelegramBotAPI.types.MediaGroupIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.UpdateIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.update.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.*
/**
* @return If [this] is [SentMediaGroupUpdate] - [Update.updateId] of [last] element, or its own [Update.updateId]
*/
fun Update.lastUpdateIdentifier(): UpdateIdentifier {
return if (this is SentMediaGroupUpdate) {
origins.last().updateId
} else {
updateId
}
}
/**
* @return The biggest [UpdateIdentifier] OR null
*
* @see [Update.lastUpdateIdentifier]
*/
fun List<Update>.lastUpdateIdentifier(): UpdateIdentifier? {
return maxBy { it.updateId } ?.lastUpdateIdentifier()
}
/**
* Will convert incoming list of updates to list with [MediaGroupUpdate]s
*/
fun List<Update>.convertWithMediaGroupUpdates(): List<Update> {
val resultUpdates = mutableListOf<Update>()
val mediaGroups = mutableMapOf<MediaGroupIdentifier, MutableList<BaseSentMessageUpdate>>()
for (update in this) {
val data = (update.data as? MediaGroupMessage)
if (data == null) {
resultUpdates.add(update)
continue
}
when (update) {
is BaseEditMessageUpdate -> resultUpdates.add(
update.toEditMediaGroupUpdate()
)
is BaseSentMessageUpdate -> {
mediaGroups.getOrPut(data.mediaGroupId) {
mutableListOf()
}.add(update)
}
else -> resultUpdates.add(update)
}
}
mediaGroups.values.map {
it.toSentMediaGroupUpdate() ?.let { mediaGroupUpdate ->
resultUpdates.add(mediaGroupUpdate)
}
}
resultUpdates.sortBy { it.updateId }
return resultUpdates
}
/**
* @receiver List of [BaseSentMessageUpdate] where [BaseSentMessageUpdate.data] is [MediaGroupMessage] and all messages
* have the same [MediaGroupMessage.mediaGroupId]
* @return [MessageMediaGroupUpdate] in case if [first] object of [this] is [MessageUpdate]. When [first] object is
* [ChannelPostUpdate] instance - will return [ChannelPostMediaGroupUpdate]. Otherwise will be returned null
*/
fun List<BaseSentMessageUpdate>.toSentMediaGroupUpdate(): SentMediaGroupUpdate? = (this as? SentMediaGroupUpdate) ?: let {
if (isEmpty()) {
return@let null
}
val resultList = sortedBy { it.updateId }
when (first()) {
is MessageUpdate -> MessageMediaGroupUpdate(resultList)
is ChannelPostUpdate -> ChannelPostMediaGroupUpdate(resultList)
else -> null
}
}
/**
* @return [EditMessageMediaGroupUpdate] in case if [this] is [EditMessageUpdate]. When [this] object is
* [EditChannelPostUpdate] instance - will return [EditChannelPostMediaGroupUpdate]
*
* @throws IllegalStateException
*/
fun BaseEditMessageUpdate.toEditMediaGroupUpdate(): EditMediaGroupUpdate = (this as? EditMediaGroupUpdate) ?: let {
when (this) {
is EditMessageUpdate -> EditMessageMediaGroupUpdate(this)
is EditChannelPostUpdate -> EditChannelPostMediaGroupUpdate(this)
else -> error("Unsupported type of ${BaseEditMessageUpdate::class.simpleName}")
}
}

View File

@@ -0,0 +1,180 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates.retrieving
import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor
import com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.RequestException
import com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates.convertWithMediaGroupUpdates
import com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates.lastUpdateIdentifier
import com.github.insanusmokrassar.TelegramBotAPI.requests.GetUpdates
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update
import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
import kotlinx.coroutines.*
fun RequestsExecutor.startGettingOfUpdatesByLongPolling(
timeoutSeconds: Seconds = 30,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
allowedUpdates: List<String>? = null,
updatesReceiver: UpdateReceiver<Update>
): Job = scope.launch {
var lastUpdateIdentifier: UpdateIdentifier? = null
while (isActive) {
handleSafely(
{ e ->
exceptionsHandler ?.invoke(e)
if (e is RequestException) {
delay(1000L)
}
}
) {
val updates = execute(
GetUpdates(
offset = lastUpdateIdentifier?.plus(1),
timeout = timeoutSeconds,
allowed_updates = allowedUpdates
)
).let { originalUpdates ->
val converted = originalUpdates.convertWithMediaGroupUpdates()
/**
* Dirty hack for cases when the media group was retrieved not fully:
*
* We are throw out the last media group and will reretrieve it again in the next get updates
* and it will guarantee that it is full
*/
if (originalUpdates.size == getUpdatesLimit.last && converted.last() is SentMediaGroupUpdate) {
converted - converted.last()
} else {
converted
}
}
handleSafely {
for (update in updates) {
updatesReceiver(update)
lastUpdateIdentifier = update.lastUpdateIdentifier()
}
}
}
}
}
/**
* This method will create a new one [FlowsUpdatesFilter]. This method could be unsafe due to the fact that it will start
* getting updates IMMEDIATELY. That means that your bot will be able to skip some of them until you will call
* [kotlinx.coroutines.flow.Flow.collect] on one of [FlowsUpdatesFilter] flows. To avoid it, you can pass
* [flowUpdatesPreset] lambda - it will be called BEFORE starting updates getting
*/
@FlowPreview
@PreviewFeature
@Suppress("unused")
fun RequestsExecutor.startGettingFlowsUpdatesByLongPolling(
timeoutSeconds: Seconds = 30,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
exceptionsHandler: ExceptionHandler<Unit>? = null,
flowsUpdatesFilterUpdatesKeeperCount: Int = 64,
flowUpdatesPreset: FlowsUpdatesFilter.() -> Unit = {}
): FlowsUpdatesFilter = FlowsUpdatesFilter(flowsUpdatesFilterUpdatesKeeperCount).apply {
flowUpdatesPreset()
startGettingOfUpdatesByLongPolling(timeoutSeconds, scope, exceptionsHandler, allowedUpdates, asUpdateReceiver)
}
fun RequestsExecutor.startGettingOfUpdatesByLongPolling(
updatesFilter: UpdatesFilter,
timeoutSeconds: Seconds = 30,
exceptionsHandler: ExceptionHandler<Unit>? = null,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
): Job = startGettingOfUpdatesByLongPolling(
timeoutSeconds,
scope,
exceptionsHandler,
updatesFilter.allowedUpdates,
updatesFilter.asUpdateReceiver
)
fun RequestsExecutor.startGettingOfUpdatesByLongPolling(
messageCallback: UpdateReceiver<MessageUpdate>? = null,
messageMediaGroupCallback: UpdateReceiver<MessageMediaGroupUpdate>? = null,
editedMessageCallback: UpdateReceiver<EditMessageUpdate>? = null,
editedMessageMediaGroupCallback: UpdateReceiver<EditMessageMediaGroupUpdate>? = null,
channelPostCallback: UpdateReceiver<ChannelPostUpdate>? = null,
channelPostMediaGroupCallback: UpdateReceiver<ChannelPostMediaGroupUpdate>? = null,
editedChannelPostCallback: UpdateReceiver<EditChannelPostUpdate>? = null,
editedChannelPostMediaGroupCallback: UpdateReceiver<EditChannelPostMediaGroupUpdate>? = null,
chosenInlineResultCallback: UpdateReceiver<ChosenInlineResultUpdate>? = null,
inlineQueryCallback: UpdateReceiver<InlineQueryUpdate>? = null,
callbackQueryCallback: UpdateReceiver<CallbackQueryUpdate>? = null,
shippingQueryCallback: UpdateReceiver<ShippingQueryUpdate>? = null,
preCheckoutQueryCallback: UpdateReceiver<PreCheckoutQueryUpdate>? = null,
pollCallback: UpdateReceiver<PollUpdate>? = null,
pollAnswerCallback: UpdateReceiver<PollAnswerUpdate>? = null,
timeoutSeconds: Seconds = 30,
exceptionsHandler: ExceptionHandler<Unit>? = null,
scope: CoroutineScope = GlobalScope
): Job {
return startGettingOfUpdatesByLongPolling(
SimpleUpdatesFilter(
messageCallback,
messageMediaGroupCallback,
editedMessageCallback,
editedMessageMediaGroupCallback,
channelPostCallback,
channelPostMediaGroupCallback,
editedChannelPostCallback,
editedChannelPostMediaGroupCallback,
chosenInlineResultCallback,
inlineQueryCallback,
callbackQueryCallback,
shippingQueryCallback,
preCheckoutQueryCallback,
pollCallback,
pollAnswerCallback
),
timeoutSeconds,
exceptionsHandler,
scope
)
}
@Suppress("unused")
fun RequestsExecutor.startGettingOfUpdatesByLongPolling(
messageCallback: UpdateReceiver<MessageUpdate>? = null,
mediaGroupCallback: UpdateReceiver<MediaGroupUpdate>? = null,
editedMessageCallback: UpdateReceiver<EditMessageUpdate>? = null,
channelPostCallback: UpdateReceiver<ChannelPostUpdate>? = null,
editedChannelPostCallback: UpdateReceiver<EditChannelPostUpdate>? = null,
chosenInlineResultCallback: UpdateReceiver<ChosenInlineResultUpdate>? = null,
inlineQueryCallback: UpdateReceiver<InlineQueryUpdate>? = null,
callbackQueryCallback: UpdateReceiver<CallbackQueryUpdate>? = null,
shippingQueryCallback: UpdateReceiver<ShippingQueryUpdate>? = null,
preCheckoutQueryCallback: UpdateReceiver<PreCheckoutQueryUpdate>? = null,
pollCallback: UpdateReceiver<PollUpdate>? = null,
pollAnswerCallback: UpdateReceiver<PollAnswerUpdate>? = null,
timeoutSeconds: Seconds = 30,
exceptionsHandler: ExceptionHandler<Unit>? = null,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
): Job = startGettingOfUpdatesByLongPolling(
messageCallback = messageCallback,
messageMediaGroupCallback = mediaGroupCallback,
editedMessageCallback = editedMessageCallback,
editedMessageMediaGroupCallback = mediaGroupCallback,
channelPostCallback = channelPostCallback,
channelPostMediaGroupCallback = mediaGroupCallback,
editedChannelPostCallback = editedChannelPostCallback,
editedChannelPostMediaGroupCallback = mediaGroupCallback,
chosenInlineResultCallback = chosenInlineResultCallback,
inlineQueryCallback = inlineQueryCallback,
callbackQueryCallback = callbackQueryCallback,
shippingQueryCallback = shippingQueryCallback,
preCheckoutQueryCallback = preCheckoutQueryCallback,
pollCallback = pollCallback,
pollAnswerCallback = pollAnswerCallback,
timeoutSeconds = timeoutSeconds,
exceptionsHandler = exceptionsHandler,
scope = scope
)

View File

@@ -0,0 +1,60 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates.retrieving
import com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates.convertWithMediaGroupUpdates
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMessageUpdate
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update
import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.UpdateReceiver
import com.github.insanusmokrassar.TelegramBotAPI.utils.extensions.accumulateByKey
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
/**
* Create [UpdateReceiver] object which will correctly accumulate updates and send into output updates which INCLUDE
* [com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.MediaGroupUpdate]s.
*
* @see UpdateReceiver
*/
fun CoroutineScope.updateHandlerWithMediaGroupsAdaptation(
output: UpdateReceiver<Update>,
debounceTimeMillis: Long = 1000L
): UpdateReceiver<Update> {
val updatesChannel = Channel<Update>(Channel.UNLIMITED)
val mediaGroupChannel = Channel<Pair<String, BaseMessageUpdate>>(Channel.UNLIMITED)
val mediaGroupAccumulatedChannel = mediaGroupChannel.accumulateByKey(
debounceTimeMillis,
scope = this
)
launch {
launch {
for (update in updatesChannel) {
when (val data = update.data) {
is MediaGroupMessage -> mediaGroupChannel.send("${data.mediaGroupId}${update::class.simpleName}" to update as BaseMessageUpdate)
else -> output(update)
}
}
}
launch {
for ((_, mediaGroup) in mediaGroupAccumulatedChannel) {
mediaGroup.convertWithMediaGroupUpdates().forEach {
output(it)
}
}
}
}
return { updatesChannel.send(it) }
}
/**
* Create [UpdateReceiver] object which will correctly accumulate updates and send into output updates which INCLUDE
* [com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.MediaGroupUpdate]s.
*
* @see UpdateReceiver
*/
fun CoroutineScope.updateHandlerWithMediaGroupsAdaptation(
output: UpdateReceiver<Update>
) = updateHandlerWithMediaGroupsAdaptation(output, 1000L)

View File

@@ -0,0 +1,196 @@
package com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates.retrieving
import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor
import com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.nonstrictJsonFormat
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.MultipartFile
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.base.MultipartRequestImpl
import com.github.insanusmokrassar.TelegramBotAPI.requests.webhook.SetWebhook
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.UpdateDeserializationStrategy
import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.UpdateReceiver
import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.UpdatesFilter
import com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.webhook.WebhookPrivateKeyConfig
import com.github.insanusmokrassar.TelegramBotAPI.utils.ExceptionHandler
import com.github.insanusmokrassar.TelegramBotAPI.utils.handleSafely
import io.ktor.application.call
import io.ktor.request.receiveText
import io.ktor.response.respond
import io.ktor.routing.*
import io.ktor.server.engine.*
import kotlinx.coroutines.*
import java.util.concurrent.Executors
/**
* Allows to include webhook in custom route everywhere in your server
*
* @param [scope] Will be used for mapping of media groups
* @param [exceptionsHandler] Pass this parameter to set custom exception handler for getting updates
* @param [block] Some receiver block like [com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.FlowsUpdatesFilter]
*
* @see com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.FlowsUpdatesFilter
* @see UpdatesFilter
* @see UpdatesFilter.asUpdateReceiver
*/
fun Route.includeWebhookHandlingInRoute(
scope: CoroutineScope,
exceptionsHandler: ExceptionHandler<Unit>? = null,
block: UpdateReceiver<Update>
) {
val transformer = scope.updateHandlerWithMediaGroupsAdaptation(block)
post {
handleSafely(
exceptionsHandler ?: {}
) {
val asJson =
nonstrictJsonFormat.parseJson(call.receiveText())
val update = nonstrictJsonFormat.fromJson(
UpdateDeserializationStrategy,
asJson
)
transformer(update)
}
call.respond("Ok")
}
}
/**
* Setting up ktor server, set webhook info via [SetWebhook] request.
*
* @param listenPort port which will be listen by bot
* @param listenRoute address to listen by bot. If null - will be set up in root of host
* @param scope Scope which will be used for
* @param privateKeyConfig If configured - server will be created with [sslConnector]. [connector] will be used otherwise
*
* @see com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.FlowsUpdatesFilter
* @see UpdatesFilter
* @see UpdatesFilter.asUpdateReceiver
*/
fun startListenWebhooks(
listenPort: Int,
engineFactory: ApplicationEngineFactory<*, *>,
exceptionsHandler: ExceptionHandler<Unit>,
listenHost: String = "0.0.0.0",
listenRoute: String? = null,
privateKeyConfig: WebhookPrivateKeyConfig? = null,
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
block: UpdateReceiver<Update>
): ApplicationEngine {
val env = applicationEngineEnvironment {
module {
routing {
listenRoute ?.also {
createRouteFromPath(it).includeWebhookHandlingInRoute(scope, exceptionsHandler, block)
} ?: includeWebhookHandlingInRoute(scope, exceptionsHandler, block)
}
}
privateKeyConfig ?.let {
sslConnector(
privateKeyConfig.keyStore,
privateKeyConfig.aliasName,
privateKeyConfig::keyStorePassword,
privateKeyConfig::aliasPassword
) {
host = listenHost
port = listenPort
}
} ?: connector {
host = listenHost
port = listenPort
}
}
return embeddedServer(engineFactory, env).also {
it.start(false)
}
}
private suspend fun RequestsExecutor.internalSetWebhookInfoAndStartListenWebhooks(
listenPort: Int,
engineFactory: ApplicationEngineFactory<*, *>,
setWebhookRequest: Request<Boolean>,
exceptionsHandler: ExceptionHandler<Unit> = {},
listenHost: String = "0.0.0.0",
listenRoute: String? = null,
privateKeyConfig: WebhookPrivateKeyConfig? = null,
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
block: UpdateReceiver<Update>
): ApplicationEngine {
return try {
execute(setWebhookRequest)
startListenWebhooks(listenPort, engineFactory, exceptionsHandler, listenHost, listenRoute, privateKeyConfig, scope, block)
} catch (e: Exception) {
throw e
}
}
/**
* Setting up ktor server, set webhook info via [SetWebhook] request.
*
* @param listenPort port which will be listen by bot
* @param listenRoute address to listen by bot
* @param scope Scope which will be used for
*
* @see com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.FlowsUpdatesFilter
* @see UpdatesFilter
* @see UpdatesFilter.asUpdateReceiver
*/
@Suppress("unused")
suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks(
listenPort: Int,
engineFactory: ApplicationEngineFactory<*, *>,
setWebhookRequest: SetWebhook,
exceptionsHandler: ExceptionHandler<Unit> = {},
listenHost: String = "0.0.0.0",
listenRoute: String = "/",
privateKeyConfig: WebhookPrivateKeyConfig? = null,
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
block: UpdateReceiver<Update>
): ApplicationEngine = internalSetWebhookInfoAndStartListenWebhooks(
listenPort,
engineFactory,
setWebhookRequest as Request<Boolean>,
exceptionsHandler,
listenHost,
listenRoute,
privateKeyConfig,
scope,
block
)
/**
* Setting up ktor server, set webhook info via [SetWebhook] request.
*
* @param listenPort port which will be listen by bot
* @param listenRoute address to listen by bot
* @param scope Scope which will be used for
*
* @see com.github.insanusmokrassar.TelegramBotAPI.updateshandlers.FlowsUpdatesFilter
* @see UpdatesFilter
* @see UpdatesFilter.asUpdateReceiver
*/
@Suppress("unused")
suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks(
listenPort: Int,
engineFactory: ApplicationEngineFactory<*, *>,
setWebhookRequest: MultipartRequestImpl<SetWebhook, Map<String, MultipartFile>, Boolean>,
exceptionsHandler: ExceptionHandler<Unit> = {},
listenHost: String = "0.0.0.0",
listenRoute: String? = null,
privateKeyConfig: WebhookPrivateKeyConfig? = null,
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
block: UpdateReceiver<Update>
): ApplicationEngine = internalSetWebhookInfoAndStartListenWebhooks(
listenPort,
engineFactory,
setWebhookRequest as Request<Boolean>,
exceptionsHandler,
listenHost,
listenRoute,
privateKeyConfig,
scope,
block
)

View File

@@ -1,9 +1,7 @@
# TelegramBotAPI
[![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin)
[![Download](https://api.bintray.com/packages/insanusmokrassar/StandardRepository/TelegramBotAPI/images/download.svg) ](https://bintray.com/insanusmokrassar/StandardRepository/TelegramBotAPI/_latestVersion)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.insanusmokrassar/TelegramBotAPI)
[![Build Status](https://jenkins.insanusmokrassar.com/buildStatus/icon?job=TelegramBotAPI_master__publishing)](https://jenkins.insanusmokrassar.com/job/TelegramBotAPI_master__publishing/)
## What is it?
@@ -12,7 +10,7 @@ moments are describing by official [Telegram Bot API](https://core.telegram.org/
## Compatibility
This version compatible with [30th of March 2020 update of TelegramBotAPI (version 4.7)](https://core.telegram.org/bots/api#march-30-2020).
This version compatible with [24th of April 2020 update of TelegramBotAPI (version 4.8)](https://core.telegram.org/bots/api#april-24-2020).
There is only one exception of implemented functionality - Telegram Passport API, which was presented in
[August 2018 update of TelegramBotAPI](https://core.telegram.org/bots/api-changelog#august-27-2018) update. It will be implemented
as soon as possible.
@@ -151,53 +149,3 @@ Here was used `okhttp` realisation of client, but there are several others engin
available on ktor.io site for [client](https://ktor.io/clients/http-client/engines.html) and [server](https://ktor.io/quickstart/artifacts.html)
engines.
## Getting updates
In this library currently realised two ways to get updates from telegram:
* Polling - in this case bot will request updates from time to time (you can set up delay between requests)
* Webhook via reverse proxy or something like this
### Updates filters
Currently webhook method contains `UpdatesFilter` as necessary argument for getting updates.
`UpdatesFilter` will sort updates and throw their into different callbacks. Currently supporting
separate getting updates for media groups - they are accumulating with debounce in one second
(for being sure that all objects of media group was received).
Updates polling also support `UpdatesFilter` but it is not required to use it and you can get updates directly
in `UpdateReceiver`, which you will provide to `startGettingOfUpdates` method
### Webhook set up
If you wish to use webhook method, you will need:
* White IP - your IP address or host, which available for calling. [TelegramBotAPI](https://core.telegram.org/bots/api#setwebhook)
recommend to use some unique address for each bot which you are using
* SSL certificate. Usually you can obtain the certificate using your domain provider, [Let'sEncrypt](https://letsencrypt.org/) or [create it](https://core.telegram.org/bots/self-signed)
* Nginx or something like this
Template for Nginx server config you can find in [this gist](https://gist.github.com/InsanusMokrassar/fcc6e09cebd07e46e8f0fdec234750c4#file-nginxssl-conf).
For webhook you can provide `File` with public part of certificate, `URL` where bot will be available and inner `PORT` which
will be used to start receiving of updates. Actually, you can skip passing of `File` when you have something like
nginx for proxy forwarding.
In case of using `nginx` with reverse-proxy config, setting up of Webhook will look like:
```kotlin
requestsExecutor.setWebhook(
WEBHOOK_URL,
INTERNAL_PORT,
filter,
ENGINE_FACTORY
)
```
Here:
* `WEBHOOK_URL` - the url which will be used by Telegram system to send updates
* `INTERNAL_PORT` - the port which will be used in bot for listening of updates
* `filter` - instance of [UpdatesFilter](https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/TelegramBotAPI/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/updateshandlers/UpdatesFilter.kt),
which will be used to filter incoming updates
* `ENGINE_FACTORY` - used factory name, for example, `CIO` in case of usage `io.ktor:ktor-server-cio` as server engine

View File

@@ -64,6 +64,8 @@ kotlin {
api "io.ktor:ktor-server-host-common:$ktor_version"
api "io.ktor:ktor-client-cio:$ktor_version"
api "javax.activation:activation:$javax_activation_version"
}
}
jvmTest {
@@ -82,7 +84,6 @@ kotlin {
}
}
targets.all {
compilations.all {
kotlinOptions {

View File

@@ -20,37 +20,33 @@ publishing {
publications.all {
artifact javadocsJar
pom.withXml {
asNode().children().last() + {
resolveStrategy = Closure.DELEGATE_FIRST
pom {
description = "Library for Object-Oriented and type-safe work with Telegram Bot API"
name = "Telegram Bot API"
url = "https://insanusmokrassar.github.io/TelegramBotAPI"
description "Library for Object-Oriented and type-safe work with Telegram Bot API"
name "Telegram Bot API"
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"
}
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"
}
}
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"
}
}
licenses {
license {
name = "Apache Software License 2.0"
url = "https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/LICENSE"
}
}
}
}

View File

@@ -12,7 +12,15 @@ interface CaptionedOutput : Captioned {
}
interface CaptionedInput : Captioned {
/**
* Not full list of entities. This list WILL NOT contain [TextPart]s with [com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsources.RegularTextSource]
* @see [CaptionedInput.fullEntitiesList]
*/
val captionEntities: List<TextPart>
}
fun CaptionedInput.fullEntitiesList() = caption ?.fullListOfSubSource(captionEntities) ?.map { it.source } ?: emptyList()
/**
* Convert its [CaptionedInput.captionEntities] to list of [com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextSource]
* with [com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsources.RegularTextSource]
*/
fun CaptionedInput.fullEntitiesList(): FullTextSourcesList = caption ?.fullListOfSubSource(captionEntities) ?.map { it.source } ?: emptyList()

View File

@@ -0,0 +1,26 @@
package com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.ParseMode
import com.github.insanusmokrassar.TelegramBotAPI.utils.fullListOfSubSource
interface Explained {
val explanation: String?
}
interface ExplainedOutput : Explained {
val parseMode: ParseMode?
}
interface ExplainedInput : Explained {
/**
* Not full list of entities. This list WILL NOT contain [TextPart]s with [com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsources.RegularTextSource]
* @see [ExplainedInput.fullEntitiesList]
*/
val explanationEntities: List<TextPart>
}
/**
* Convert its [ExplainedInput.explanationEntities] to list of [com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextSource]
* with [com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsources.RegularTextSource]
*/
fun ExplainedInput.fullEntitiesList(): FullTextSourcesList = explanation ?.fullListOfSubSource(explanationEntities) ?.map { it.source } ?: emptyList()

View File

@@ -1,5 +1,7 @@
package com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
interface MimeTyped {
val mimeType: String? // TODO::replace by something like enum or interface
val mimeType: MimeType?
}

View File

@@ -1,9 +1,13 @@
package com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts
typealias FullTextSourcesList = List<TextSource>
typealias FullTextPartsList = List<TextPart>
interface TextSource {
val asMarkdownSource: String
val asMarkdownV2Source: String
val asHtmlSource: String
val source: String
}
@@ -15,3 +19,5 @@ data class TextPart(
val range: IntRange,
val source: TextSource
)
fun List<TextPart>.justTextSources() = map { it.source }

View File

@@ -9,15 +9,13 @@ import com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters.RequestL
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request
import com.github.insanusmokrassar.TelegramBotAPI.types.Response
import com.github.insanusmokrassar.TelegramBotAPI.types.RetryAfterError
import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper
import com.github.insanusmokrassar.TelegramBotAPI.utils.nonstrictJsonFormat
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
import io.ktor.client.HttpClient
import io.ktor.client.call.receive
import io.ktor.client.features.*
import io.ktor.client.statement.HttpStatement
import io.ktor.client.statement.readText
import kotlinx.coroutines.delay
import kotlinx.coroutines.supervisorScope
import kotlinx.serialization.json.Json
class KtorRequestsExecutor(
@@ -43,54 +41,56 @@ class KtorRequestsExecutor(
}
override suspend fun <T : Any> execute(request: Request<T>): T {
return try {
supervisorScope {
requestsLimiter.limit {
var statement: HttpStatement? = null
for (factory in callsFactories) {
statement = factory.prepareCall(
client,
telegramAPIUrlsKeeper.commonAPIUrl,
request
)
if (statement != null) {
break
}
}
val response = statement?.execute() ?: throw IllegalArgumentException("Can't execute request: $request")
val content = response.receive<String>()
return handleSafely(
{ e ->
throw if (e is ClientRequestException) {
val content = e.response.readText()
val responseObject = jsonFormatter.parse(Response.serializer(), content)
(responseObject.result?.let {
jsonFormatter.fromJson(request.resultDeserializer, it)
} ?: responseObject.parameters?.let {
val error = it.error
if (error is RetryAfterError) {
delay(error.leftToRetry)
execute(request)
} else {
null
}
} ?: response.let {
throw newRequestException(
responseObject,
content,
"Can't get result object from $content"
)
})
newRequestException(
responseObject,
content,
"Can't get result object from $content"
)
} else {
e
}
}
} catch (e: ClientRequestException) {
val content = e.response.readText()
val responseObject = jsonFormatter.parse(Response.serializer(), content)
throw newRequestException(
responseObject,
content,
"Can't get result object from $content"
)
} catch (e: Exception) {
throw e
) {
requestsLimiter.limit {
var statement: HttpStatement? = null
for (factory in callsFactories) {
statement = factory.prepareCall(
client,
telegramAPIUrlsKeeper.commonAPIUrl,
request
)
if (statement != null) {
break
}
}
val response = statement?.execute() ?: throw IllegalArgumentException("Can't execute request: $request")
val content = response.receive<String>()
val responseObject = jsonFormatter.parse(Response.serializer(), content)
(responseObject.result?.let {
jsonFormatter.fromJson(request.resultDeserializer, it)
} ?: responseObject.parameters?.let {
val error = it.error
if (error is RetryAfterError) {
delay(error.leftToRetry)
execute(request)
} else {
null
}
} ?: response.let {
throw newRequestException(
responseObject,
content,
"Can't get result object from $content"
)
})
}
}
}

View File

@@ -3,9 +3,20 @@ package com.github.insanusmokrassar.TelegramBotAPI.bot
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request
import io.ktor.utils.io.core.Closeable
/**
* Interface for making requests to Telegram Bot API. Currently, there is only one built-in implementation -
* [com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.KtorRequestsExecutor]
*
* @see Request
* @see com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.KtorRequestsExecutor
*/
interface RequestsExecutor : Closeable {
/**
* @throws com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.RequestException
* Unsafe execution of incoming [request]. Can throw almost any exception. So, it is better to use
* something like [com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.shortcuts.executeAsync] or
* [com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.shortcuts.executeUnsafe]
*
* @throws Exception
*/
suspend fun <T : Any> execute(request: Request<T>): T
}

View File

@@ -3,14 +3,23 @@ package com.github.insanusmokrassar.TelegramBotAPI.requests
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.Update
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.UpdateSerializerWithoutDeserialization
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.UpdateSerializerWithoutSerialization
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
private val updatesListSerializer = ListSerializer(
UpdateSerializerWithoutDeserialization
UpdateSerializerWithoutSerialization
)
/**
* Request updates from Telegram Bot API system. It is important, that the result updates WILL NOT include
* [com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.MediaGroupUpdate] objects due to the fact,
* that it is internal abstraction and in fact any [com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage]
* is just a common [com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Message]
*
* @see com.github.insanusmokrassar.TelegramBotAPI.extensions.utils.updates.retrieving.updateHandlerWithMediaGroupsAdaptation
* @see com.github.insanusmokrassar.TelegramBotAPI.utils.convertWithMediaGroupUpdates
*/
@Serializable
data class GetUpdates(
val offset: UpdateIdentifier? = null,// set `last update id + 1` to receive next part of updates

View File

@@ -5,11 +5,6 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.*
import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
/**
* Representation of https://core.telegram.org/bots/api#setchatadministratorcustomtitle
*
* Please, remember about restrictions for characters in custom title
*/
@Serializable
data class SetChatAdministratorCustomTitle(
@SerialName(chatIdField)

View File

@@ -5,6 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.types.ReplyMes
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.ReplyingMarkupSendMessageRequest
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.dice.DiceAnimationType
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.DiceContent
@@ -17,6 +18,8 @@ internal val DiceContentMessageResultDeserializer: DeserializationStrategy<Conte
data class SendDice(
@SerialName(chatIdField)
override val chatId: ChatIdentifier,
@SerialName(emojiField)
val animationType: DiceAnimationType? = null,
@SerialName(disableNotificationField)
override val disableNotification: Boolean = false,
@SerialName(replyToMessageIdField)

View File

@@ -9,6 +9,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.TextContent
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import kotlinx.serialization.*
internal val TextContentMessageResultDeserializer: DeserializationStrategy<ContentMessage<TextContent>>
@@ -37,7 +38,7 @@ data class SendTextMessage(
{
init {
if (text.length !in textLength) {
throw IllegalArgumentException("Text must be in $textLength range")
throwRangeError("Text length", textLength, text.length)
}
}

View File

@@ -11,6 +11,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Conten
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.media.AnimationContent
import com.github.insanusmokrassar.TelegramBotAPI.utils.mapOfNotNull
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import kotlinx.serialization.*
fun SendAnimation(
@@ -93,7 +94,7 @@ data class SendAnimationData internal constructor(
init {
text ?.let {
if (it.length !in captionLength) {
throw IllegalArgumentException("Caption must be in $captionLength range")
throwRangeError("Caption length", captionLength, it.length)
}
}
}

View File

@@ -12,6 +12,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Conten
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.media.AudioContent
import com.github.insanusmokrassar.TelegramBotAPI.utils.mapOfNotNull
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import kotlinx.serialization.*
fun SendAudio(
@@ -95,7 +96,7 @@ data class SendAudioData internal constructor(
init {
text ?.let {
if (it.length !in captionLength) {
throw IllegalArgumentException("Caption must be in $captionLength range")
throwRangeError("Caption length", captionLength, it.length)
}
}
}

View File

@@ -11,6 +11,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Conten
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.media.DocumentContent
import com.github.insanusmokrassar.TelegramBotAPI.utils.mapOfNotNull
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import kotlinx.serialization.*
fun SendDocument(
@@ -79,7 +80,7 @@ data class SendDocumentData internal constructor(
init {
text ?.let {
if (it.length !in captionLength) {
throw IllegalArgumentException("Caption must be in $captionLength range")
throwRangeError("Caption length", captionLength, it.length)
}
}
}

View File

@@ -8,12 +8,15 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.InputMedia.*
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializeOnlySerializerClass
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import com.github.insanusmokrassar.TelegramBotAPI.utils.toJsonWithoutNulls
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.jsonArray
val membersCountInMediaGroup: IntRange = 2 .. 10
@Deprecated("Replaced and renamed", ReplaceWith("mediaCountInMediaGroup", "com.github.insanusmokrassar.TelegramBotAPI.types.mediaCountInMediaGroup"))
val membersCountInMediaGroup
get() = mediaCountInMediaGroup
fun SendMediaGroup(
chatId: ChatIdentifier,
@@ -21,8 +24,8 @@ fun SendMediaGroup(
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
): Request<List<MediaGroupMessage>> {
if (media.size !in membersCountInMediaGroup) {
throw IllegalArgumentException("Count of members for media group must be in $membersCountInMediaGroup range")
if (media.size !in mediaCountInMediaGroup) {
throwRangeError("Count of members in media group", mediaCountInMediaGroup, media.size)
}
val files: List<MultipartFile> = media.flatMap {

View File

@@ -10,6 +10,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.media.PhotoContent
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import kotlinx.serialization.*
fun SendPhoto(
@@ -65,7 +66,7 @@ data class SendPhotoData internal constructor(
init {
text ?.let {
if (it.length !in captionLength) {
throw IllegalArgumentException("Caption must be in $captionLength range")
throwRangeError("Caption length", captionLength, it.length)
}
}
}

View File

@@ -11,6 +11,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Conten
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.media.VideoContent
import com.github.insanusmokrassar.TelegramBotAPI.utils.mapOfNotNull
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import kotlinx.serialization.*
fun SendVideo(
@@ -97,7 +98,7 @@ data class SendVideoData internal constructor(
init {
text ?.let {
if (it.length !in captionLength) {
throw IllegalArgumentException("Caption must be in $captionLength range")
throwRangeError("Caption length", captionLength, it.length)
}
}
}

View File

@@ -11,6 +11,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Conten
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.media.VideoNoteContent
import com.github.insanusmokrassar.TelegramBotAPI.utils.mapOfNotNull
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import kotlinx.serialization.*
fun SendVideoNote(
@@ -92,7 +93,7 @@ data class SendVideoNoteData internal constructor(
init {
text ?.let {
if (it.length !in captionLength) {
throw IllegalArgumentException("Caption must be in $captionLength range")
throwRangeError("Caption length", captionLength, it.length)
}
}
}

View File

@@ -11,6 +11,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Conten
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.media.VoiceContent
import com.github.insanusmokrassar.TelegramBotAPI.utils.mapOfNotNull
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import kotlinx.serialization.*
fun SendVoice(
@@ -84,7 +85,7 @@ data class SendVoiceData internal constructor(
init {
text ?.let {
if (it.length !in captionLength) {
throw IllegalArgumentException("Caption must be in $captionLength range")
throwRangeError("Caption length", captionLength, it.length)
}
}
}

View File

@@ -4,9 +4,6 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.*
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.json.JsonObject
/**
* Will be used as SimpleRequest if
*/
class MultipartRequestImpl<D: DataRequest<R>, F: Files, R: Any>(
val data: D,
val files: F

View File

@@ -1,13 +1,19 @@
package com.github.insanusmokrassar.TelegramBotAPI.requests.send.polls
import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.ReplyingMarkupSendMessageRequest
import com.github.insanusmokrassar.TelegramBotAPI.requests.send.abstracts.SendMessageRequest
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.MarkdownV2
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.ParseMode
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.KeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.ContentMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.PollContent
import com.github.insanusmokrassar.TelegramBotAPI.types.polls.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.fullListOfSubSource
import com.github.insanusmokrassar.TelegramBotAPI.utils.toMarkdownV2Captions
import com.soywiz.klock.DateTime
import kotlinx.serialization.*
private val commonResultDeserializer: DeserializationStrategy<ContentMessage<PollContent>> = TelegramBotAPIMessageDeserializationStrategyClass()
@@ -66,6 +72,7 @@ fun Poll.createRequest(
isAnonymous,
isClosed,
allowMultipleAnswers,
scheduledCloseInfo,
disableNotification,
replyToMessageId,
replyMarkup
@@ -78,6 +85,9 @@ fun Poll.createRequest(
correctOptionId,
isAnonymous,
isClosed,
explanation ?.fullListOfSubSource(explanationEntities) ?.justTextSources() ?.toMarkdownV2Captions() ?.firstOrNull(),
MarkdownV2,
scheduledCloseInfo,
disableNotification,
replyToMessageId,
replyMarkup
@@ -89,6 +99,7 @@ fun Poll.createRequest(
isAnonymous,
isClosed,
false,
scheduledCloseInfo,
disableNotification,
replyToMessageId,
replyMarkup
@@ -100,20 +111,35 @@ fun Poll.createRequest(
isAnonymous,
isClosed,
false,
scheduledCloseInfo,
disableNotification,
replyToMessageId,
replyMarkup
)
}
private fun ScheduledCloseInfo.checkSendData() {
val span = when (this) {
is ExactScheduledCloseInfo -> (closeDateTime - DateTime.now()).seconds
is ApproximateScheduledCloseInfo -> openDuration.seconds
}.toInt()
if (span !in openPeriodPollSecondsLimit) {
error("Duration of autoclose for polls must be in range $openPeriodPollSecondsLimit, but was $span")
}
}
sealed class SendPoll : SendMessageRequest<ContentMessage<PollContent>>,
ReplyingMarkupSendMessageRequest<ContentMessage<PollContent>> {
abstract val question: String
abstract val options: List<String>
abstract val isAnonymous: Boolean
abstract val isClosed: Boolean
abstract val closeInfo: ScheduledCloseInfo?
abstract val type: String
internal abstract val openPeriod: LongSeconds?
internal abstract val closeDate: LongSeconds?
override fun method(): String = "sendPoll"
override val resultDeserializer: DeserializationStrategy<ContentMessage<PollContent>>
get() = commonResultDeserializer
@@ -133,6 +159,8 @@ data class SendRegularPoll(
override val isClosed: Boolean = false,
@SerialName(allowsMultipleAnswersField)
val allowMultipleAnswers: Boolean = false,
@Transient
override val closeInfo: ScheduledCloseInfo? = null,
@SerialName(disableNotificationField)
override val disableNotification: Boolean = false,
@SerialName(replyToMessageIdField)
@@ -144,8 +172,17 @@ data class SendRegularPoll(
override val requestSerializer: SerializationStrategy<*>
get() = serializer()
@SerialName(openPeriodField)
override val openPeriod: LongSeconds?
= (closeInfo as? ApproximateScheduledCloseInfo) ?.openDuration ?.millisecondsLong ?.div(1000)
@SerialName(closeDateField)
override val closeDate: LongSeconds?
= (closeInfo as? ExactScheduledCloseInfo) ?.closeDateTime ?.unixMillisLong ?.div(1000)
init {
checkPollInfo(question, options)
closeInfo ?.checkSendData()
}
}
@@ -163,23 +200,46 @@ data class SendQuizPoll(
override val isAnonymous: Boolean = true,
@SerialName(isClosedField)
override val isClosed: Boolean = false,
@SerialName(explanationField)
override val explanation: String? = null,
@SerialName(explanationParseModeField)
override val parseMode: ParseMode? = null,
@Transient
override val closeInfo: ScheduledCloseInfo? = null,
@SerialName(disableNotificationField)
override val disableNotification: Boolean = false,
@SerialName(replyToMessageIdField)
override val replyToMessageId: MessageIdentifier? = null,
@SerialName(replyMarkupField)
override val replyMarkup: KeyboardMarkup? = null
) : SendPoll() {
) : SendPoll(), CaptionedOutput, ExplainedOutput {
override val type: String = quizPollType
override val requestSerializer: SerializationStrategy<*>
get() = serializer()
@Deprecated("Will be removed in near updates", ReplaceWith("explanation"))
override val caption: String?
get() = explanation
@SerialName(openPeriodField)
override val openPeriod: LongSeconds?
= (closeInfo as? ApproximateScheduledCloseInfo) ?.openDuration ?.millisecondsLong ?.div(1000)
@SerialName(closeDateField)
override val closeDate: LongSeconds?
= (closeInfo as? ExactScheduledCloseInfo) ?.closeDateTime ?.unixMillisLong ?.div(1000)
init {
checkPollInfo(question, options)
closeInfo ?.checkSendData()
val correctOptionIdRange = 0 .. options.size
if (correctOptionId !in correctOptionIdRange) {
throw IllegalArgumentException("Correct option id must be in range of $correctOptionIdRange, but actual " +
"value is $correctOptionId")
}
if (explanation != null && explanation.length !in explanationLimit) {
error("Quiz poll explanation size must be in range $explanationLimit," +
"but actual explanation contains ${explanation.length} symbols")
}
}
}

View File

@@ -7,33 +7,56 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.*
import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
private fun correctWebhookUrl(sourceUrl: String) = if (sourceUrl.contains("://")) {
sourceUrl
} else {
"https://$sourceUrl"
}
fun SetWebhook(
url: String,
certificate: MultipartFile,
maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null
): MultipartRequestImpl<SetWebhook, Map<String, MultipartFile>, Boolean> = MultipartRequestImpl(
SetWebhook(
correctWebhookUrl(url),
null,
maxAllowedConnections,
allowedUpdates
),
mapOf(certificateField to certificate)
)
fun SetWebhook(
url: String,
certificate: FileId,
maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null
): SetWebhook = SetWebhook(
correctWebhookUrl(url),
certificate.fileId,
maxAllowedConnections,
allowedUpdates
)
@Suppress("USELESS_CAST")
fun SetWebhook(
url: String,
certificate: InputFile,
maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null
) : Request<Boolean> {
val data = SetWebhook(
url,
(certificate as? FileId) ?.fileId,
maxAllowedConnections,
allowedUpdates
)
return when (certificate) {
is FileId -> data
is MultipartFile -> MultipartRequestImpl(
data,
mapOf(certificateField to certificate)
)
}
): Request<Boolean> = when (certificate) {
is MultipartFile -> SetWebhook(correctWebhookUrl(url), certificate as MultipartFile, maxAllowedConnections, allowedUpdates)
is FileId -> SetWebhook(correctWebhookUrl(url), certificate as FileId, maxAllowedConnections, allowedUpdates)
}
fun SetWebhook(
url: String,
maxAllowedConnections: Int? = null,
allowedUpdates: List<String>? = null
) : Request<Boolean> = SetWebhook(
url,
) = SetWebhook(
correctWebhookUrl(url),
null,
maxAllowedConnections,
allowedUpdates

View File

@@ -1,8 +1,11 @@
package com.github.insanusmokrassar.TelegramBotAPI.types
import com.github.insanusmokrassar.TelegramBotAPI.utils.throwRangeError
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
val BotCommandNameRegex = Regex("^[a-z_0-9]{${botCommandLengthLimit.first},${botCommandLengthLimit.last}}$")
@Serializable
data class BotCommand(
@SerialName(botCommandField)
@@ -12,10 +15,13 @@ data class BotCommand(
) {
init {
if (command.length !in botCommandLengthLimit) {
error("Command size must be in range $botCommandLengthLimit, but actually have length ${command.length}")
throwRangeError("Command name size", botCommandLengthLimit, command.length)
}
if (!command.matches(BotCommandNameRegex)) {
error("Bot command must contains only lowercase English letters, digits and underscores, but incoming command was $command")
}
if (description.length !in botCommandDescriptionLimit) {
error("Command description size must be in range $botCommandDescriptionLimit, but actually have length ${description.length}")
throwRangeError("Command description size", botCommandDescriptionLimit, description.length)
}
}
}

View File

@@ -1,5 +1,7 @@
package com.github.insanusmokrassar.TelegramBotAPI.types
import com.github.insanusmokrassar.TelegramBotAPI.utils.BuiltinMimeTypes
typealias Identifier = Long
typealias MessageIdentifier = Long
typealias InlineQueryIdentifier = String
@@ -22,11 +24,12 @@ typealias FileUniqueId = String
typealias DiceResult = Int
typealias Seconds = Int
typealias LongSeconds = Long
val getUpdatesLimit = 1 .. 100
val callbackQueryAnswerLength = 0 until 200
val captionLength = 0 until 1024
val textLength = 0 until 4096
val captionLength = 0 .. 1024
val textLength = 1 .. 4096
val userProfilePhotosRequestLimit = 0 .. 100
val chatTitleLength = 1 until 255
val chatDescriptionLength = 0 until 256
@@ -54,6 +57,20 @@ val botCommandLimit = botCommandLengthLimit
val botCommandDescriptionLimit = 3 .. 256
val botCommandsLimit = 0 .. 100
val mediaCountInMediaGroup: IntRange = 2 .. 10
val explanationLimit = 0 .. 200
@Deprecated("Will be removed in near updates", ReplaceWith("explanationLimit"))
val quizPollExplanationLimit = explanationLimit
val openPeriodPollSecondsLimit = 5 .. 600
val telegramInlineModeGifPermittedMimeTypes = listOf(
BuiltinMimeTypes.Image.Jpg,
BuiltinMimeTypes.Image.Gif,
BuiltinMimeTypes.Video.MP4
)
const val chatIdField = "chat_id"
const val messageIdField = "message_id"
const val updateIdField = "update_id"
@@ -82,6 +99,7 @@ const val containsMasksField = "contains_masks"
const val resultIdField = "result_id"
const val inlineMessageIdField = "inline_message_id"
const val callbackDataField = "callback_data"
const val callbackGameField = "callback_game"
const val callbackQueryIdField = "callback_query_id"
const val inlineQueryIdField = "inline_query_id"
const val inlineKeyboardField = "inline_keyboard"
@@ -167,6 +185,7 @@ const val stickerFileIdField = "sticker_file_id"
const val gameShortNameField = "game_short_name"
const val thumbUrlField = "thumb_url"
const val thumbMimeTypeField = "thumb_mime_type"
const val thumbWidthField = "thumb_width"
const val thumbHeightField = "thumb_height"
@@ -198,6 +217,7 @@ const val tgsStickerField = "tgs_sticker"
const val okField = "ok"
const val captionField = "caption"
const val explanationField = "explanation"
const val idField = "id"
const val pollIdField = "poll_id"
const val textField = "text"
@@ -249,6 +269,10 @@ const val xShiftField = "x_shift"
const val yShiftField = "y_shift"
const val scaleField = "scale"
const val explanationEntitiesField = "explanation_entities"
const val explanationParseModeField = "explanation_parse_mode"
const val openPeriodField = "open_period"
const val closeDateField = "close_date"
const val smallFileIdField = "small_file_id"
const val bigFileIdField = "big_file_id"

View File

@@ -1,10 +1,9 @@
package com.github.insanusmokrassar.TelegramBotAPI.types
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import com.github.insanusmokrassar.TelegramBotAPI.types.dice.Dice
@Serializable
data class Dice(
@SerialName(valueField)
val value: DiceResult
@Deprecated(
"Replaced",
ReplaceWith("Dice", "com.github.insanusmokrassar.TelegramBotAPI.types.dice.Dice")
)
typealias Dice = Dice

View File

@@ -8,6 +8,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.ParseMode
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.parseModeField
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.InlineKeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.files.abstracts.mimeTypeField
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -20,7 +21,7 @@ data class InlineQueryResultDocumentImpl(
@SerialName(titleField)
override val title: String,
@SerialName(mimeTypeField)
override val mimeType: String,
override val mimeType: MimeType,
@SerialName(thumbUrlField)
override val thumbUrl: String? = null,
@SerialName(thumbWidthField)

View File

@@ -7,6 +7,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.InlineQueries.abstracts.
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.ParseMode
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.parseModeField
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.InlineKeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -18,6 +19,8 @@ data class InlineQueryResultGifImpl(
override val url: String,
@SerialName(thumbUrlField)
override val thumbUrl: String,
@SerialName(thumbMimeTypeField)
override val thumbMimeType: MimeType? = null,
@SerialName(gifWidthField)
override val width: Int? = null,
@SerialName(gifHeightField)
@@ -36,4 +39,10 @@ data class InlineQueryResultGifImpl(
override val inputMessageContent: InputMessageContent? = null
) : InlineQueryResultGif {
override val type: String = inlineQueryResultGifType
init {
if (thumbMimeType != null && thumbMimeType !in telegramInlineModeGifPermittedMimeTypes) {
error("Passed thumb mime type is not permitted in Telegram Bot API. Passed $thumbMimeType, but permitted $telegramInlineModeGifPermittedMimeTypes")
}
}
}

View File

@@ -7,6 +7,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.InlineQueries.abstracts.
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.ParseMode
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.parseModeField
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.InlineKeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -18,6 +19,8 @@ data class InlineQueryResultMpeg4GifImpl(
override val url: String,
@SerialName(thumbUrlField)
override val thumbUrl: String,
@SerialName(thumbMimeTypeField)
override val thumbMimeType: MimeType? = null,
@SerialName(mpeg4GifWidthField)
override val width: Int? = null,
@SerialName(mpeg4GifHeightField)
@@ -36,4 +39,10 @@ data class InlineQueryResultMpeg4GifImpl(
override val inputMessageContent: InputMessageContent? = null
) : InlineQueryResultMpeg4Gif {
override val type: String = inlineQueryResultMpeg4GifType
init {
if (thumbMimeType != null && thumbMimeType !in telegramInlineModeGifPermittedMimeTypes) {
error("Passed thumb mime type is not permitted in Telegram Bot API. Passed $thumbMimeType, but permitted $telegramInlineModeGifPermittedMimeTypes")
}
}
}

View File

@@ -8,6 +8,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.ParseMode
import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.parseModeField
import com.github.insanusmokrassar.TelegramBotAPI.types.buttons.InlineKeyboardMarkup
import com.github.insanusmokrassar.TelegramBotAPI.types.files.abstracts.mimeTypeField
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -20,7 +21,7 @@ data class InlineQueryResultVideoImpl(
@SerialName(thumbUrlField)
override val thumbUrl: String,
@SerialName(mimeTypeField)
override val mimeType: String,
override val mimeType: MimeType,
@SerialName(titleField)
override val title: String,
@SerialName(videoWidthField)

View File

@@ -1,5 +1,11 @@
package com.github.insanusmokrassar.TelegramBotAPI.types.InlineQueries.InlineQueryResult.abstracts
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
interface ThumbedInlineQueryResult : InlineQueryResult {
val thumbUrl: String?
}
interface ThumbedWithMimeTypeInlineQueryResult : ThumbedInlineQueryResult {
val thumbMimeType: MimeType?
}

View File

@@ -2,4 +2,4 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.InlineQueries.InlineQue
import com.github.insanusmokrassar.TelegramBotAPI.types.InlineQueries.InlineQueryResult.abstracts.*
interface InlineQueryResultGif : InlineQueryResultGifCommon, UrlInlineQueryResult, ThumbedInlineQueryResult, SizedInlineQueryResult, DuratedInlineResultQuery
interface InlineQueryResultGif : InlineQueryResultGifCommon, UrlInlineQueryResult, ThumbedWithMimeTypeInlineQueryResult, SizedInlineQueryResult, DuratedInlineResultQuery

View File

@@ -2,4 +2,4 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.InlineQueries.InlineQue
import com.github.insanusmokrassar.TelegramBotAPI.types.InlineQueries.InlineQueryResult.abstracts.*
interface InlineQueryResultMpeg4Gif : InlineQueryResultMpeg4GifCommon, UrlInlineQueryResult, ThumbedInlineQueryResult, SizedInlineQueryResult, DuratedInlineResultQuery
interface InlineQueryResultMpeg4Gif : InlineQueryResultMpeg4GifCommon, UrlInlineQueryResult, ThumbedWithMimeTypeInlineQueryResult, SizedInlineQueryResult, DuratedInlineResultQuery

View File

@@ -7,6 +7,8 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.files.PhotoSize
import com.github.insanusmokrassar.TelegramBotAPI.types.mediaField
import kotlinx.serialization.*
internal const val photoInputMediaType = "photo"
@Serializable
data class InputMediaPhoto(
override val file: InputFile,
@@ -14,7 +16,7 @@ data class InputMediaPhoto(
@SerialName(parseModeField)
override val parseMode: ParseMode? = null
) : InputMedia, MediaGroupMemberInputMedia {
override val type: String = "photo"
override val type: String = photoInputMediaType
override fun serialize(format: StringFormat): String = format.stringify(serializer(), this)

View File

@@ -6,6 +6,8 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.ParseMode.parseModeField
import com.github.insanusmokrassar.TelegramBotAPI.types.mediaField
import kotlinx.serialization.*
internal const val videoInputMediaType = "video"
@Serializable
data class InputMediaVideo(
override val file: InputFile,
@@ -17,7 +19,7 @@ data class InputMediaVideo(
override val duration: Long? = null,
override val thumb: InputFile? = null
) : InputMedia, SizedInputMedia, DuratedInputMedia, ThumbedInputMedia, MediaGroupMemberInputMedia {
override val type: String = "video"
override val type: String = videoInputMediaType
override fun serialize(format: StringFormat): String = format.stringify(serializer(), this)

View File

@@ -1,6 +1,9 @@
package com.github.insanusmokrassar.TelegramBotAPI.types.InputMedia
import com.github.insanusmokrassar.TelegramBotAPI.types.typeField
import com.github.insanusmokrassar.TelegramBotAPI.utils.nonstrictJsonFormat
import kotlinx.serialization.*
import kotlinx.serialization.json.JsonObjectSerializer
@Serializer(MediaGroupMemberInputMedia::class)
internal object MediaGroupMemberInputMediaSerializer : KSerializer<MediaGroupMemberInputMedia> {
@@ -13,6 +16,12 @@ internal object MediaGroupMemberInputMediaSerializer : KSerializer<MediaGroupMem
}
override fun deserialize(decoder: Decoder): MediaGroupMemberInputMedia {
TODO("not implemented")
val json = JsonObjectSerializer.deserialize(decoder)
return when (json.getPrimitiveOrNull(typeField) ?.contentOrNull) {
photoInputMediaType -> nonstrictJsonFormat.fromJson(InputMediaPhoto.serializer(), json)
videoInputMediaType -> nonstrictJsonFormat.fromJson(InputMediaVideo.serializer(), json)
else -> error("Illegal type of incoming MediaGroupMemberInputMedia")
}
}
}

View File

@@ -5,7 +5,6 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.thumbField
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
// TODO:: fill thumbed
interface ThumbedInputMedia : InputMedia {
val thumb: InputFile?
@Serializable

View File

@@ -83,6 +83,28 @@ internal fun createTextPart(from: String, entities: RawMessageEntities): List<Te
return resultList
}
internal fun List<TextPart>.asRawMessageEntities() = mapNotNull {
val source = it.source
when (source) {
is MentionTextSource -> RawMessageEntity("mention", it.range.first, it.range.last - it.range.first)
is HashTagTextSource -> RawMessageEntity("hashtag", it.range.first, it.range.last - it.range.first)
is CashTagTextSource -> RawMessageEntity("cashtag", it.range.first, it.range.last - it.range.first)
is BotCommandTextSource -> RawMessageEntity("bot_command", it.range.first, it.range.last - it.range.first)
is URLTextSource -> RawMessageEntity("url", it.range.first, it.range.last - it.range.first)
is EMailTextSource -> RawMessageEntity("email", it.range.first, it.range.last - it.range.first)
is PhoneNumberTextSource -> RawMessageEntity("phone_number", it.range.first, it.range.last - it.range.first)
is BoldTextSource -> RawMessageEntity("bold", it.range.first, it.range.last - it.range.first)
is ItalicTextSource -> RawMessageEntity("italic", it.range.first, it.range.last - it.range.first)
is CodeTextSource -> RawMessageEntity("code", it.range.first, it.range.last - it.range.first)
is PreTextSource -> RawMessageEntity("pre", it.range.first, it.range.last - it.range.first, language = source.language)
is TextLinkTextSource -> RawMessageEntity("text_link", it.range.first, it.range.last - it.range.first, source.url)
is TextMentionTextSource -> RawMessageEntity("text_mention", it.range.first, it.range.last - it.range.first, user = source.user)
is UnderlineTextSource -> RawMessageEntity("underline", it.range.first, it.range.last - it.range.first)
is StrikethroughTextSource -> RawMessageEntity("strikethrough", it.range.first, it.range.last - it.range.first)
else -> null
}
}
internal fun RawMessageEntities.asTextParts(sourceString: String): List<TextPart> = createTextPart(sourceString, this)
internal typealias RawMessageEntities = List<RawMessageEntity>

View File

@@ -5,7 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextPart
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
class BoldTextSource(
source: String,
override val source: String,
textParts: List<TextPart>
) : MultilevelTextSource {
override val textParts: List<TextPart> by lazy { source.fullListOfSubSource(textParts) }

View File

@@ -7,7 +7,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.utils.*
private val commandRegex = Regex("[/!][^@\\s]*")
class BotCommandTextSource(
source: String,
override val source: String,
textParts: List<TextPart>
) : MultilevelTextSource {
val command: String by lazy {

View File

@@ -5,7 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextPart
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
class CashTagTextSource(
source: String,
override val source: String,
textParts: List<TextPart>
) : MultilevelTextSource {
override val textParts: List<TextPart> by lazy { source.fullListOfSubSource(textParts) }

View File

@@ -4,7 +4,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextSource
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
class CodeTextSource(
source: String
override val source: String
) : TextSource {
override val asMarkdownSource: String by lazy { source.codeMarkdown() }
override val asMarkdownV2Source: String by lazy { source.codeMarkdownV2() }

View File

@@ -5,7 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextPart
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
class EMailTextSource(
source: String,
override val source: String,
textParts: List<TextPart>
) : MultilevelTextSource {
override val textParts: List<TextPart> by lazy { source.fullListOfSubSource(textParts) }

View File

@@ -12,7 +12,7 @@ private val String.withoutSharp
}
class HashTagTextSource(
source: String,
override val source: String,
textParts: List<TextPart>
) : MultilevelTextSource {
override val textParts: List<TextPart> by lazy {

View File

@@ -5,7 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextPart
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
class ItalicTextSource(
source: String,
override val source: String,
textParts: List<TextPart>
) : MultilevelTextSource {
override val textParts: List<TextPart> by lazy { source.fullListOfSubSource(textParts) }

View File

@@ -12,7 +12,7 @@ private val String.withoutCommercialAt
}
class MentionTextSource(
source: String,
override val source: String,
textParts: List<TextPart>
) : MultilevelTextSource {
override val textParts: List<TextPart> by lazy {

View File

@@ -5,7 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextPart
import com.github.insanusmokrassar.TelegramBotAPI.utils.*
class PhoneNumberTextSource(
source: String,
override val source: String,
textParts: List<TextPart>
) : MultilevelTextSource {
override val textParts: List<TextPart> by lazy { source.fullListOfSubSource(textParts) }

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