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

Compare commits

..

72 Commits

Author SHA1 Message Date
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
79 changed files with 1763 additions and 159 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

@@ -49,6 +49,49 @@
* `closePollExactAfter`
* `closePollAfter`
### 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`:

View File

@@ -1,6 +1,6 @@
# 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) |
| 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) |
| -------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 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) |
@@ -15,6 +15,7 @@ the list of this complex currently next projects:
`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).
@@ -62,15 +63,64 @@ kotlin {
## Ok, where should I start?
In most cases, the most simple way will be to implement
[TelegramBotAPI Extensions](TelegramBotAPI-extensions-api/README.md) and
[TelegramBotAPI Util Extensions](TelegramBotAPI-extensions-utils/README.md) for the reason that they contains more
simple tools. If you want to dive deeper in the core of library or develop something for it - welcome to
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).
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,57 @@
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.withXml {
asNode().children().last() + {
resolveStrategy = Closure.DELEGATE_FIRST
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

@@ -55,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() |
@@ -70,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

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

@@ -26,7 +26,7 @@ publishing {
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"
url "https://insanusmokrassar.github.io/TelegramBotAPI/TelegramBotAPI-extensions-api"
scm {
developerConnection "scm:git:[fetch=]https://github.com/insanusmokrassar/TelegramBotAPI.git[push=]https://github.com/insanusmokrassar/TelegramBotAPI.git"

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,45 @@
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.HttpClientEngine
import io.ktor.client.engine.ProxyConfig
/**
* @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,
var ktorClientEngine: HttpClientEngine? = null,
var ktorClientConfig: (HttpClientConfig<*>.() -> Unit) ? = null
) {
internal fun createHttpClient(): HttpClient = ktorClientEngine ?.let { engine ->
HttpClient(engine) {
ktorClientConfig ?.let { it() }
engine {
proxy = this@BotBuilder.proxy ?: proxy
}
}
} ?: HttpClient {
ktorClientConfig ?.let { it() }
engine {
proxy = this@BotBuilder.proxy ?: 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

@@ -10,14 +10,14 @@ 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.PreviewFeature
import com.github.insanusmokrassar.TelegramBotAPI.utils.handleSafely
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 {
@@ -68,7 +68,10 @@ fun RequestsExecutor.startGettingOfUpdates(
* [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),
@@ -80,6 +83,7 @@ fun RequestsExecutor.startGettingFlowsUpdates(
startGettingOfUpdates(timeoutSeconds, scope, exceptionsHandler, allowedUpdates, asUpdateReceiver)
}
@Deprecated("Replaced and renamed in TelegramBotAPI-extensions-utils")
fun RequestsExecutor.startGettingOfUpdates(
updatesFilter: UpdatesFilter,
timeoutSeconds: Seconds = 30,
@@ -93,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,
@@ -137,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

@@ -1,5 +1,23 @@
# 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)
@@ -60,31 +78,164 @@ val filter = FlowsUpdatesFilter(64)
Alternative way to use the things below:
```kotlin
val filter = bot.startGettingUpdates(
val filter = bot.startGettingFlowsUpdatesByLongPolling(
scope = CoroutineScope(Dispatchers.Default)
) {
// place code from examples here with replacing of `filter` by `this`
}
```
### Getting of only text incoming messages
### 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
filter.asContentMessagesFlow().onlyTextContentMessages().onEach {
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.
#### 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)
println(it.fullEntitiesList())
}.launchIn(
CoroutineScope(Dispatchers.Default)
)
```
As a result, each received message which will be just text message will be printed out with full list of its internal entities
##### Chat actions
## Shortcuts
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
#### ScheduledCloseInfo
In case if you are creating some poll, you able to use next shortcuts.

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

@@ -26,7 +26,7 @@ publishing {
description "Util extensions for more useful work with updates and other things"
name "Telegram Bot API Utility Extensions"
url "https://insanusmokrassar.github.io/TelegramBotAPI"
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"

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 Utility Extensions","description":"Util extensions for more useful work with updates and other things","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 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,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,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

@@ -5,12 +5,21 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.update.CallbackQueryUpda
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

@@ -8,12 +8,37 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.fullEnti
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 ->
@@ -23,10 +48,18 @@ fun <T : BaseSentMessageUpdate> Flow<T>.filterCommandsInsideTextMessages(
}
/**
* @return Result [Flow] will emit all [TextSource]s to the collector ONLY IN CASE if first [TextSource] is
* [BotCommandTextSource] and its [BotCommandTextSource.command] is [Regex.matches] to incoming [commandRegex]. Internal
* behaviour contains next rules: all incoming text sources will be passed as is, [RegularTextSource] will be divided
* by " " for several [RegularTextSource] which will contains not empty args without spaces
* 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

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

@@ -5,14 +5,23 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseSen
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 [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

@@ -7,13 +7,25 @@ import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.BaseMes
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
/**
* [Flow.filter] incoming [BaseMessageUpdate]s by their [ChatId]
*/
fun <T : BaseMessageUpdate> Flow<T>.filterBaseMessageUpdates(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>.filterBaseMessageUpdates(chat: Chat): Flow<T> = filterBaseMessageUpdates(chat.id)
/**
* [Flow.filter] incoming [SentMediaGroupUpdate]s by their [ChatId]
*/
fun <T : SentMediaGroupUpdate> Flow<T>.filterSentMediaGroupUpdates(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>.filterSentMediaGroupUpdates(chat: Chat): Flow<T> = filterSentMediaGroupUpdates(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,201 @@
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 {
lateinit var engine: 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
}
}
engine = embeddedServer(engineFactory, env)
engine.start(false)
return engine
}
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>
): Job {
return try {
execute(setWebhookRequest)
val engine = startListenWebhooks(listenPort, engineFactory, exceptionsHandler, listenHost, listenRoute, privateKeyConfig, scope, block)
scope.launch {
engine.environment.parentCoroutineContext[Job] ?.join()
engine.stop(1000, 5000)
}
} 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>
): Job = 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>
): Job = internalSetWebhookInfoAndStartListenWebhooks(
listenPort,
engineFactory,
setWebhookRequest as Request<Boolean>,
exceptionsHandler,
listenHost,
listenRoute,
privateKeyConfig,
scope,
block
)

View File

@@ -149,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 {
@@ -81,13 +83,4 @@ kotlin {
}
}
}
targets.all {
compilations.all {
kotlinOptions {
freeCompilerArgs += ["-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi", "-Xopt-in=kotlin.RequiresOptIn"]
}
}
}
}

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>
}
/**
* 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

@@ -12,7 +12,15 @@ interface ExplainedOutput : Explained {
}
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

@@ -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.utils.extensions.executeAsync] or
* [com.github.insanusmokrassar.TelegramBotAPI.utils.extensions.executeUnsafe]
*
* @throws Exception
*/
suspend fun <T : Any> execute(request: Request<T>): T
}

View File

@@ -11,6 +11,15 @@ private val updatesListSerializer = ListSerializer(
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

@@ -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

@@ -85,7 +85,7 @@ fun Poll.createRequest(
correctOptionId,
isAnonymous,
isClosed,
caption ?.fullListOfSubSource(captionEntities) ?.justTextSources() ?.toMarkdownV2Captions() ?.firstOrNull(),
explanation ?.fullListOfSubSource(explanationEntities) ?.justTextSources() ?.toMarkdownV2Captions() ?.firstOrNull(),
MarkdownV2,
scheduledCloseInfo,
disableNotification,

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

@@ -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

@@ -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

@@ -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

@@ -7,7 +7,7 @@ data class WebhookInfo(
@SerialName(urlField)
val url: String,
@SerialName(pendingUpdateCountField)
val awaitDeliery: Int,
val awaitDelivery: Int,
@SerialName(maxAllowedConnectionsField)
val maxConnections: Int = 40, // default count according to documentation
@SerialName(hasCustomCertificateField)

View File

@@ -2,6 +2,9 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.actions
import kotlinx.serialization.*
/**
* Use BotAction objects realisations to notify user about bot actions
*/
@Serializable(BotActionSerializer::class)
sealed class BotAction {
abstract val actionName: String
@@ -31,54 +34,80 @@ internal object BotActionSerializer: KSerializer<BotAction> {
}
/**
* Use BotAction objects realisations to notify user about bot actions
* Will notify user that bot is "typing" something
*/
@Serializable(BotActionSerializer::class)
object TypingAction : BotAction() {
override val actionName: String = "typing"
}
/**
* Will notify user that bot is uploading some photo
*/
@Serializable(BotActionSerializer::class)
object UploadPhotoAction : BotAction() {
override val actionName: String = "upload_photo"
}
/**
* Will notify user that bot is recording some video
*/
@Serializable(BotActionSerializer::class)
object RecordVideoAction : BotAction() {
override val actionName: String = "record_video"
}
/**
* Will notify user that bot is uploading some photo
*/
@Serializable(BotActionSerializer::class)
object UploadVideoAction : BotAction() {
override val actionName: String = "upload_video"
}
/**
* Will notify user that bot is recording some audio
*/
@Serializable(BotActionSerializer::class)
object RecordAudioAction : BotAction() {
override val actionName: String = "record_audio"
}
/**
* Will notify user that bot is uploading some audio
*/
@Serializable(BotActionSerializer::class)
object UploadAudioAction : BotAction() {
override val actionName: String = "upload_audio"
}
/**
* Will notify user that bot is uploading some document
*/
@Serializable(BotActionSerializer::class)
object UploadDocumentAction : BotAction() {
override val actionName: String = "upload_document"
}
/**
* Will notify user that bot is trying to find location
*/
@Serializable(BotActionSerializer::class)
object FindLocationAction : BotAction() {
override val actionName: String = "find_location"
}
/**
* Will notify user that bot is recording video note
*/
@Serializable(BotActionSerializer::class)
object RecordVideoNoteAction : BotAction() {
override val actionName: String = "record_video_note"
}
/**
* Will notify user that bot is uploading video note
*/
@Serializable(BotActionSerializer::class)
object UploadVideoNoteAction : BotAction() {
override val actionName: String = "upload_video_note"

View File

@@ -15,9 +15,15 @@ object DartsDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFAF"
}
@Serializable(DiceAnimationTypeSerializer::class)
class UnknownDiceAnimationType(
object BasketballDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFC0"
}
@Serializable(DiceAnimationTypeSerializer::class)
data class CustomDiceAnimationType(
override val emoji: String
) : DiceAnimationType()
@Deprecated("Renamed", ReplaceWith("CustomDiceAnimationType"))
typealias UnknownDiceAnimationType = CustomDiceAnimationType
@Serializer(DiceAnimationType::class)
internal object DiceAnimationTypeSerializer : KSerializer<DiceAnimationType> {
@@ -26,7 +32,8 @@ internal object DiceAnimationTypeSerializer : KSerializer<DiceAnimationType> {
return when (val type = decoder.decodeString()) {
CubeDiceAnimationType.emoji -> CubeDiceAnimationType
DartsDiceAnimationType.emoji -> DartsDiceAnimationType
else -> UnknownDiceAnimationType(type)
BasketballDiceAnimationType.emoji -> BasketballDiceAnimationType
else -> CustomDiceAnimationType(type)
}
}

View File

@@ -4,6 +4,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.FileId
import com.github.insanusmokrassar.TelegramBotAPI.types.FileUniqueId
import com.github.insanusmokrassar.TelegramBotAPI.types.fileUniqueIdField
import com.github.insanusmokrassar.TelegramBotAPI.types.files.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -20,7 +21,7 @@ data class AnimationFile(
@SerialName(fileNameField)
override val fileName: String? = null,
@SerialName(mimeTypeField)
override val mimeType: String? = null,
override val mimeType: MimeType? = null,
@SerialName(fileSizeField)
override val fileSize: Long? = null
) : TelegramMediaFile, MimedMediaFile, ThumbedMediaFile, PlayableMediaFile, CustomNamedMediaFile, SizedMediaFile

View File

@@ -5,6 +5,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.FileId
import com.github.insanusmokrassar.TelegramBotAPI.types.FileUniqueId
import com.github.insanusmokrassar.TelegramBotAPI.types.fileUniqueIdField
import com.github.insanusmokrassar.TelegramBotAPI.types.files.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -18,7 +19,7 @@ data class AudioFile(
override val performer: String? = null,
override val title: String? = null,
@SerialName(mimeTypeField)
override val mimeType: String? = null,
override val mimeType: MimeType? = null,
@SerialName(fileSizeField)
override val fileSize: Long? = null,
override val thumb: PhotoSize? = null

View File

@@ -4,6 +4,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.FileId
import com.github.insanusmokrassar.TelegramBotAPI.types.FileUniqueId
import com.github.insanusmokrassar.TelegramBotAPI.types.fileUniqueIdField
import com.github.insanusmokrassar.TelegramBotAPI.types.files.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -17,7 +18,7 @@ data class DocumentFile(
override val fileSize: Long? = null,
override val thumb: PhotoSize? = null,
@SerialName(mimeTypeField)
override val mimeType: String? = null,
override val mimeType: MimeType? = null,
@SerialName(fileNameField)
override val fileName: String? = null
) : TelegramMediaFile, MimedMediaFile, ThumbedMediaFile, CustomNamedMediaFile

View File

@@ -4,6 +4,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.FileId
import com.github.insanusmokrassar.TelegramBotAPI.types.FileUniqueId
import com.github.insanusmokrassar.TelegramBotAPI.types.fileUniqueIdField
import com.github.insanusmokrassar.TelegramBotAPI.types.files.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -18,7 +19,7 @@ data class VideoFile(
override val duration: Long? = null,
override val thumb: PhotoSize? = null,
@SerialName(mimeTypeField)
override val mimeType: String? = null,
override val mimeType: MimeType? = null,
@SerialName(fileSizeField)
override val fileSize: Long? = null
) : TelegramMediaFile, MimedMediaFile, ThumbedMediaFile, PlayableMediaFile, SizedMediaFile

View File

@@ -1,9 +1,9 @@
package com.github.insanusmokrassar.TelegramBotAPI.types.files
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.FileId
import com.github.insanusmokrassar.TelegramBotAPI.types.FileUniqueId
import com.github.insanusmokrassar.TelegramBotAPI.types.fileUniqueIdField
import com.github.insanusmokrassar.TelegramBotAPI.types.*
import com.github.insanusmokrassar.TelegramBotAPI.types.files.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.MimeType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -13,9 +13,10 @@ data class VoiceFile(
override val fileId: FileId,
@SerialName(fileUniqueIdField)
override val fileUniqueId: FileUniqueId,
@SerialName(durationField)
override val duration: Long? = null,
@SerialName(mimeTypeField)
override val mimeType: String? = null,
override val mimeType: MimeType? = null,
@SerialName(fileSizeField)
override val fileSize: Long? = null
) : TelegramMediaFile, MimedMediaFile, PlayableMediaFile

View File

@@ -8,7 +8,7 @@ internal const val fileSizeField = "file_size"
internal const val filePathField = "file_path"
/**
* Declare common part of media files in Telegram. Note: it is not representation of `File` type
* Declare common part of media files in Telegram. Note: it is not representation of JVM `File` type
*/
interface TelegramMediaFile {
val fileId: FileId

View File

@@ -14,6 +14,10 @@ import com.github.insanusmokrassar.TelegramBotAPI.utils.*
data class TextContent(
val text: String,
/**
* Not full list of entities. This list WILL NOT contain [TextPart]s with [com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsources.RegularTextSource]
* @see [TextContent.fullEntitiesList]
*/
val entities: List<TextPart> = emptyList()
) : MessageContent {
override fun createResend(
@@ -67,4 +71,8 @@ data class TextContent(
}
}
/**
* Convert its [TextContent.entities] to list of [com.github.insanusmokrassar.TelegramBotAPI.CommonAbstracts.TextSource]
* with [com.github.insanusmokrassar.TelegramBotAPI.types.MessageEntity.textsources.RegularTextSource]
*/
fun TextContent.fullEntitiesList(): FullTextSourcesList = text.fullListOfSubSource(entities).map { it.source }

View File

@@ -212,8 +212,8 @@ internal object PollSerializer : KSerializer<Poll> {
value.isAnonymous,
regularPollType,
correctOptionId = value.correctOptionId,
explanation = value.caption,
explanationEntities = value.captionEntities.asRawMessageEntities(),
explanation = value.explanation,
explanationEntities = value.explanationEntities.asRawMessageEntities(),
openPeriod = (closeInfo as? ApproximateScheduledCloseInfo) ?.openDuration ?.seconds ?.toLong(),
closeDate = (closeInfo as? ExactScheduledCloseInfo) ?.closeDateTime ?.unixMillisLong ?.div(1000L)
)

View File

@@ -3,6 +3,13 @@ package com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdate
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage
import com.github.insanusmokrassar.TelegramBotAPI.types.update.abstracts.*
/**
* By default there is no instances of objects which could be deserialized from raw updates. If you want to get objects
* with this type, you should use something like [com.github.insanusmokrassar.TelegramBotAPI.extensions.api.SetWebhookKt.includeWebhookInRoute]
*
* @see com.github.insanusmokrassar.TelegramBotAPI.extensions.api.SetWebhookKt.includeWebhookInRoute
* @see com.github.insanusmokrassar.TelegramBotAPI.extensions.api.updates.UpdatesPollingKt.startGettingOfUpdates
*/
interface MediaGroupUpdate : Update
interface SentMediaGroupUpdate: MediaGroupUpdate {

View File

@@ -26,7 +26,14 @@ internal object UpdateSerializerWithoutSerialization : KSerializer<Update> {
override fun serialize(encoder: Encoder, value: Update) = throw UnsupportedOperationException()
}
internal object UpdateDeserializationStrategy : DeserializationStrategy<Update> {
/**
* Use this object to deserialize objects with type [Update]. Currently it is restricted to use this
* [DeserializationStrategy] only with JSON
*
* @see StringFormat.parse
* @see kotlinx.serialization.json.Json.parse
*/
object UpdateDeserializationStrategy : DeserializationStrategy<Update> {
override val descriptor: SerialDescriptor = JsonElementSerializer.descriptor
override fun patch(decoder: Decoder, old: Update): Update = throw UpdateNotSupportedException("Update")

View File

@@ -3,17 +3,16 @@ package com.github.insanusmokrassar.TelegramBotAPI.updateshandlers
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 kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
@Suppress("EXPERIMENTAL_API_USAGE")
private fun <T> BroadcastChannel<T>.createUpdateReceiver(): UpdateReceiver<T> = ::send
@FlowPreview
@Suppress("EXPERIMENTAL_API_USAGE", "unused")
class FlowsUpdatesFilter(
broadcastChannelsSize: Int = Channel.CONFLATED
broadcastChannelsSize: Int = 64
): UpdatesFilter {
private val messageChannel: BroadcastChannel<MessageUpdate> = BroadcastChannel(broadcastChannelsSize)
private val messageMediaGroupChannel: BroadcastChannel<MessageMediaGroupUpdate> = BroadcastChannel(broadcastChannelsSize)

View File

@@ -3,6 +3,8 @@ package com.github.insanusmokrassar.TelegramBotAPI.utils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.supervisorScope
typealias ExceptionHandler<T> = suspend (Exception) -> T
/**
* It will run [block] inside of [supervisorScope] to avoid problems with catching of exceptions
*
@@ -10,7 +12,7 @@ import kotlinx.coroutines.supervisorScope
* exception will be available for catching
*/
suspend inline fun <T> handleSafely(
noinline onException: suspend (Exception) -> T = { throw it },
noinline onException: ExceptionHandler<T> = { throw it },
noinline block: suspend CoroutineScope.() -> T
): T {
return try {

View File

@@ -3,6 +3,7 @@ package com.github.insanusmokrassar.TelegramBotAPI.utils
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.json.*
@Suppress("EXPERIMENTAL_API_USAGE")
internal val nonstrictJsonFormat = Json {
isLenient = true
ignoreUnknownKeys = true

View File

@@ -0,0 +1,11 @@
package com.github.insanusmokrassar.TelegramBotAPI.utils
import kotlinx.serialization.*
@Serializable(MimeTypeSerializer::class)
expect class MimeType {
val raw: String
}
@Serializer(MimeType::class)
internal expect object MimeTypeSerializer : KSerializer<MimeType>

View File

@@ -4,6 +4,7 @@ import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor
import com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.RequestException
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request
import com.github.insanusmokrassar.TelegramBotAPI.types.Response
import com.github.insanusmokrassar.TelegramBotAPI.utils.handleSafely
import kotlinx.coroutines.*
@@ -33,16 +34,23 @@ fun <T: Any> RequestsExecutor.executeAsync(
suspend fun <T: Any> RequestsExecutor.executeUnsafe(
request: Request<T>,
retries: Int = 0,
retriesDelay: Long = 1000L
retriesDelay: Long = 1000L,
onAllFailed: (suspend (exceptions: Array<Exception>) -> Unit)? = null
): T? {
var leftRetries = retries
val exceptions = onAllFailed ?.let { mutableListOf<Exception>() }
do {
try {
return execute(request)
} catch (e: RequestException) {
leftRetries--
delay(retriesDelay)
}
handleSafely(
{
leftRetries--
delay(retriesDelay)
exceptions ?.add(it)
null
}
) {
execute(request)
} ?.let { return it }
} while(leftRetries >= 0)
onAllFailed ?.invoke(exceptions ?.toTypedArray() ?: emptyArray())
return null
}

View File

@@ -4,9 +4,3 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
val TestsJsonFormat = Json(JsonConfiguration.Stable)
val NonstrictTestsJsonFormat = Json {
isLenient = true
ignoreUnknownKeys = true
serializeSpecialFloatingPointValues = true
useArrayPolymorphism = true
}

View File

@@ -0,0 +1,33 @@
package com.github.insanusmokrassar.TelegramBotAPI.utils
import kotlinx.serialization.*
import org.w3c.dom.get
import kotlin.browser.window
private val mimesCache = mutableMapOf<String, MimeType>()
@Serializable(MimeTypeSerializer::class)
actual class MimeType(
actual val raw: String
) {
@Transient
val jsMimeType = window.navigator.mimeTypes[raw]
override fun toString(): String = raw
}
@Serializer(MimeType::class)
internal actual object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveDescriptor("mimeType", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MimeType {
val mimeType = decoder.decodeString()
return mimesCache.getOrPut(mimeType) {
MimeType(mimeType)
}
}
override fun serialize(encoder: Encoder, value: MimeType) {
encoder.encodeString(value.raw)
}
}

View File

@@ -0,0 +1,26 @@
package com.github.insanusmokrassar.TelegramBotAPI.utils
import kotlinx.serialization.*
private val mimesCache = mutableMapOf<String, MimeType>()
@Serializable(MimeTypeSerializer::class)
actual class MimeType(
actual val raw: String
) : javax.activation.MimeType(raw)
@Serializer(MimeType::class)
internal actual object MimeTypeSerializer : KSerializer<MimeType> {
override val descriptor: SerialDescriptor = PrimitiveDescriptor("mimeType", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MimeType {
val mimeType = decoder.decodeString()
return mimesCache.getOrPut(mimeType) {
MimeType(mimeType)
}
}
override fun serialize(encoder: Encoder, value: MimeType) {
encoder.encodeString(value.raw)
}
}

View File

@@ -30,6 +30,7 @@ import java.util.concurrent.TimeUnit
* which will be used by telegram to send encrypted messages
* @param scope Scope which will be used for
*/
@Deprecated("Replaced into project TelegramBotAPI-extensions-utils")
suspend fun RequestsExecutor.setWebhook(
url: String,
port: Int,
@@ -41,7 +42,7 @@ suspend fun RequestsExecutor.setWebhook(
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
allowedUpdates: List<String>? = null,
maxAllowedConnections: Int? = null,
exceptionsHandler: (suspend (Exception) -> Unit)? = null,
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
block: UpdateReceiver<Update>
): Job {
val executeDeferred = certificate ?.let {
@@ -137,6 +138,7 @@ suspend fun RequestsExecutor.setWebhook(
}
}
@Deprecated("Replaced into project TelegramBotAPI-extensions-utils")
suspend fun RequestsExecutor.setWebhook(
url: String,
port: Int,
@@ -164,6 +166,7 @@ suspend fun RequestsExecutor.setWebhook(
block
)
@Deprecated("Replaced into project TelegramBotAPI-extensions-utils")
suspend fun RequestsExecutor.setWebhook(
url: String,
port: Int,
@@ -188,6 +191,7 @@ suspend fun RequestsExecutor.setWebhook(
block
)
@Deprecated("Replaced into project TelegramBotAPI-extensions-utils")
suspend fun RequestsExecutor.setWebhook(
url: String,
port: Int,

87
docs/build.gradle Normal file
View File

@@ -0,0 +1,87 @@
buildscript {
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
}
}
plugins {
id "org.jetbrains.kotlin.multiplatform" version "$kotlin_version"
id "org.jetbrains.kotlin.plugin.serialization" version "$kotlin_version"
id "org.jetbrains.dokka" version "$dokka_version"
}
repositories {
mavenLocal()
jcenter()
mavenCentral()
}
kotlin {
jvm()
js()
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
project.parent.subprojects.forEach {
if (it != project) {
api it
}
}
}
}
}
}
private Closure includeSourcesInDokka(String... approximateNames) {
return {
parent.subprojects.forEach {
if (it != project) {
File srcDir = new File(it.projectDir.absolutePath, "src")
if (srcDir.exists() && srcDir.isDirectory()) {
srcDir.eachFile { file ->
if (approximateNames.any { file.name.contains(it) } && file.isDirectory()) {
String pathToSrc = file.absolutePath
sourceRoot {
path = pathToSrc
}
}
}
}
}
}
}
}
String dokkaFolder = "$buildDir/dokka"
dokka {
outputFormat = 'html'
outputDirectory = dokkaFolder
multiplatform {
global {
skipDeprecated = true
sourceLink {
path = "./"
url = "https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/"
lineSuffix = "#L"
}
}
common(includeSourcesInDokka("commonMain"))
js(includeSourcesInDokka("jsMain"/*, "commonMain"*/))
jvm(includeSourcesInDokka("jvmMain"/*, "commonMain"*/))
}
}

3
docs/gradle.properties Normal file
View File

@@ -0,0 +1,3 @@
dokka_version=0.10.1
org.gradle.jvmargs=-Xmx1024m

View File

@@ -1,12 +1,14 @@
kotlin.code.style=official
kotlin_version=1.3.72
kotlin_coroutines_version=1.3.5
kotlin_coroutines_version=1.3.6
kotlin_serialisation_runtime_version=0.20.0
klock_version=1.10.5
klock_version=1.11.1
uuid_version=0.1.0
ktor_version=1.3.2
javax_activation_version=1.1.1
library_group=com.github.insanusmokrassar
library_version=0.27.1
library_version=0.27.3
gradle_bintray_plugin_version=1.8.4

View File

@@ -1,3 +1,5 @@
include ":TelegramBotAPI"
include ":TelegramBotAPI-extensions-api"
include ":TelegramBotAPI-extensions-utils"
include ":TelegramBotAPI-all"
include ":docs"