1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2025-11-29 10:55:42 +00:00

Compare commits

..

56 Commits

Author SHA1 Message Date
a10d766295 slotmachine utils 2020-11-02 20:32:46 +06:00
ea6ab8f024 start 0.29.4 2020-11-02 20:10:38 +06:00
f377d61bf0 Merge pull request #158 from InsanusMokrassar/0.29.3
0.29.3
2020-11-02 14:21:54 +06:00
18c9d4e468 fill changelog 2020-11-02 14:14:08 +06:00
20b931138c update FlowsUpdatesFilter 2020-11-02 14:11:26 +06:00
d7bca15693 deprecations 2020-11-02 13:57:44 +06:00
957649603b fix for media fields in input media 2020-11-02 13:51:20 +06:00
bbc56fe94c hotfix in changelog 2020-11-02 13:26:06 +06:00
d048958423 WrongFileIdentifierException 2020-11-02 13:25:40 +06:00
f0e9267664 fixes in files and filling of changelog 2020-11-02 13:16:46 +06:00
5dd7207e09 update serialization version 2020-11-02 12:56:29 +06:00
d96d47e32c make different interfaces for audio and documents media content 2020-11-02 12:51:09 +06:00
1ee73dd406 sending media groups api updates 2020-11-02 12:39:12 +06:00
e9a05c4930 include audio playlists, documents and slot machine dice 2020-11-02 12:02:12 +06:00
9e771fa04a fixes 2020-11-02 11:13:16 +06:00
06ba21fc1f add changelog for audio and document media items 2020-10-30 20:40:44 +06:00
7d72d72f2c Revert "Revert "Revert "Revert "add slot machine, but currently it is not working""""
This reverts commit b457d11067.
2020-10-30 20:38:04 +06:00
80d4bdfe17 make audio and documents able to be media group items 2020-10-30 20:38:02 +06:00
b457d11067 Revert "Revert "Revert "add slot machine, but currently it is not working"""
This reverts commit 46fe16fd73.
2020-10-30 19:45:55 +06:00
46fe16fd73 Revert "Revert "add slot machine, but currently it is not working""
This reverts commit f317e144e6.
2020-10-30 19:37:26 +06:00
8e1cafb1b6 start 0.29.3 2020-10-30 19:36:48 +06:00
68d971f874 Merge pull request #155 from InsanusMokrassar/0.29.2
0.29.2
2020-10-27 15:55:17 +06:00
37a23c7e79 fixes in aggregation of flows 2020-10-27 15:51:48 +06:00
b053c29ea3 update aggregateFlows 2020-10-27 15:26:32 +06:00
23a1fed7dd Revert "update greetings config"
This reverts commit 87d2537bda.
2020-10-27 15:16:04 +06:00
87d2537bda update greetings config 2020-10-27 15:14:45 +06:00
6d782f28c3 updates after coroutines version change 2020-10-27 15:11:57 +06:00
d8dbbdf549 update coroutines 2020-10-27 15:01:30 +06:00
f317e144e6 Revert "add slot machine, but currently it is not working"
This reverts commit 4f9cb531c0.
2020-10-27 14:53:22 +06:00
05c27804ce update travis config 2020-10-22 19:19:55 +06:00
eb97a8e151 Merge pull request #154 from Megamiun/gm/update-dokka
Gm/update dokka
2020-10-22 19:18:51 +06:00
Gabryel Monteiro
06639e5f3c Update gradle.properties 2020-10-22 09:41:06 -03:00
82b7bf676f Update .travis.yml 2020-10-20 02:05:34 +06:00
4f9cb531c0 add slot machine, but currently it is not working 2020-10-19 20:26:36 +06:00
09bbb1945c start 0.29.2 2020-10-19 20:12:18 +06:00
7bedc4caf0 Merge remote-tracking branch 'original/master' into gm/update-dokka 2020-10-19 19:51:03 +06:00
6a73aa1525 add travis dokka step 2020-10-19 19:50:41 +06:00
e1ee541005 extend max heap size for gradle 2020-10-19 19:45:46 +06:00
Gabryel Monteiro
2d2fe01227 Changing Dokka version 2020-10-19 01:51:09 -03:00
Gabryel Monteiro
af53682b1f Removing kts notation 2020-10-19 00:45:29 -03:00
Gabryel Monteiro
c8a5552c9e Adding projects 2020-10-19 00:40:24 -03:00
Gabryel Monteiro
ffa78ebfe1 Update to 1.4.0 2020-10-18 22:38:16 -03:00
e85e7d02f7 Merge pull request #149 from InsanusMokrassar/0.29.1
0.29.1
2020-10-13 16:08:19 +06:00
37ad279ab4 Merge pull request #148 from leobia/master
Reformat CHANGELOG.md
2020-10-12 15:27:09 +06:00
3b41dc1fbf Merge remote-tracking branch 'original/0.29.1' 2020-10-12 15:26:11 +06:00
5747383ed1 Update README.md 2020-10-12 01:08:56 +06:00
18a6efabb8 post fixes for serialization update 2020-10-09 11:49:24 +06:00
dcb837b155 update serialization version 2020-10-09 11:41:08 +06:00
03755e9eef start 0.29.1 2020-10-09 11:35:33 +06:00
lbia
b9db2d544d Reformat CHANGELOG.md 2020-10-08 22:11:39 +02:00
800d14561e add conversations between AudioFile and VoiceFile 2020-10-04 20:00:43 +06:00
2378237fc5 Merge pull request #146 from InsanusMokrassar/0.29.0
0.29.0
2020-10-04 19:46:22 +06:00
48c6f103b5 Update README.md 2020-10-02 14:27:07 +06:00
e326d289cb Create pull_request_template.md 2020-10-02 13:26:32 +06:00
7fd9c4c897 Create CONTRIBUTING.md 2020-10-02 13:10:01 +06:00
347a6212d4 Merge pull request #141 from InsanusMokrassar/0.28.4
0.28.4
2020-09-28 13:29:22 +06:00
43 changed files with 1117 additions and 697 deletions

2
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,2 @@
* Please, be sure that you have read [Contributing](https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/CONTRIBUTING.md) file. Of course, this line must be removed during PR preparation :)
* Describe your changes, shortly. As example (or format) you can look at the body of any [Project releases](https://github.com/InsanusMokrassar/TelegramBotAPI/releases)

View File

@@ -11,3 +11,5 @@ jobs:
script: ./gradlew build -s -x jvmTest -x jsIrTest -x jsIrBrowserTest -x jsIrNodeTest -x jsLegacyTest -x jsLegacyBrowserTest -x jsLegacyNodeTest script: ./gradlew build -s -x jvmTest -x jsIrTest -x jsIrBrowserTest -x jsIrNodeTest -x jsLegacyTest -x jsLegacyBrowserTest -x jsLegacyNodeTest
- state: test - state: test
script: ./gradlew allTests script: ./gradlew allTests
- state: dokka
script: ./gradlew dokkaHtml

File diff suppressed because it is too large Load Diff

10
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,10 @@
# Contributing
In case you wish to contribute this project, there are several small things you must remember:
* Give maintainers opportunity to manage your Pull request. It is required for two reasons
* For more efficient Pull Requests handling (it is much easier for me to fix something small directly in your pull request than ask to fix some small things after each review)
* Usually, you will set as a target `master` branch, but I prefer to include code into separated version branch firstly. So, if you will give me opporunity to change Pull Request, I will be available to change base branch
* Currently in Pull Requests there are several bots. The most important is Travis bot and it must always successfuly build code from your Pull Request
This project was built on the idea of strongly-typed declaration of TelegramBotAPI. So, do not worry if maintaners will change your pull requests: you are already cool because you have contributed this project:)

View File

@@ -1,6 +1,6 @@
# TelegramBotAPI # TelegramBotAPI
| Common info | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) [![Build Status](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI) [Small survey](https://forms.gle/tnjuExdSKEr32ygKA)| | Common info | [![Awesome Kotlin Badge](https://kotlin.link/awesome-kotlin.svg)](https://github.com/KotlinBy/awesome-kotlin) [![Build Status](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI.svg?branch=master)](https://travis-ci.com/InsanusMokrassar/TelegramBotAPI) [Small survey](https://forms.gle/2Hex2ynbHWHhi1KY7)|
| -------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | -------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Useful links | [![Chat in Telegram](badges/chat.svg)](https://t.me/InMoTelegramBotAPI) [![KDocs](badges/kdocs.svg)](https://tgbotapi.inmo.dev/docs/index.html) [Examples](https://github.com/InsanusMokrassar/TelegramBotAPI-examples/), [Mini tutorial](https://bookstack.inmo.dev/books/telegrambotapi/chapter/introduction-tutorial) | | Useful links | [![Chat in Telegram](badges/chat.svg)](https://t.me/InMoTelegramBotAPI) [![KDocs](badges/kdocs.svg)](https://tgbotapi.inmo.dev/docs/index.html) [Examples](https://github.com/InsanusMokrassar/TelegramBotAPI-examples/), [Mini tutorial](https://bookstack.inmo.dev/books/telegrambotapi/chapter/introduction-tutorial) |
| TelegramBotAPI Core status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi.core/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi.core/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.core) | | TelegramBotAPI Core status | [![Download](https://api.bintray.com/packages/insanusmokrassar/TelegramBotAPI/tgbotapi.core/images/download.svg)](https://bintray.com/insanusmokrassar/TelegramBotAPI/tgbotapi.core/_latestVersion) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.core/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/tgbotapi.core) |
@@ -74,7 +74,7 @@ kotlin {
![Libraries hierarchy](resources/TelegramBotAPI-libraries-hierarchy.svg) ![Libraries hierarchy](resources/TelegramBotAPI-libraries-hierarchy.svg)
In most cases, the most simple way will be to implement [TelegramBotAPI](TelegramBotAPI/README.md) - it contains In most cases, the most simple way will be to implement [TelegramBotAPI](tgbotapi/README.md) - it contains
all necessary tools for comfort usage of this library. If you want to exclude some libraries, you can implement just all necessary tools for comfort usage of this library. If you want to exclude some libraries, you can implement just
[TelegramBotAPI API Extensions](tgbotapi.extensions.api/README.md), [TelegramBotAPI API Extensions](tgbotapi.extensions.api/README.md),
[TelegramBotAPI Util Extensions](tgbotapi.extensions.utils/README.md) or even [TelegramBotAPI Util Extensions](tgbotapi.extensions.utils/README.md) or even

View File

@@ -46,29 +46,17 @@ kotlin {
} }
} }
private Closure includeSourcesInDokka(String... approximateNames) { private List<SourceDirectorySet> findSourcesWithName(String... approximateNames) {
return { return parent.subprojects
parent.subprojects.forEach { .findAll { it != project }
if (it != project) { .collectMany { it.kotlin.sourceSets }
File srcDir = new File(it.projectDir.absolutePath, "src") .findAll { sourceSet -> approximateNames.any {
if (srcDir.exists() && srcDir.isDirectory()) { nameToFilter -> sourceSet.name.contains(nameToFilter)
srcDir.eachFile { file ->
if (approximateNames.any { file.name.contains(it) } && file.isDirectory()) {
String pathToSrc = file.absolutePath
sourceRoot {
path = pathToSrc
}
}
}
}
} }
} }.collect { it.kotlin }
}
} }
dokka { tasks.dokkaHtml {
outputFormat = 'html'
switch (true) { switch (true) {
case project.hasProperty("DOKKA_PATH"): case project.hasProperty("DOKKA_PATH"):
outputDirectory = project.property("DOKKA_PATH").toString() outputDirectory = project.property("DOKKA_PATH").toString()
@@ -78,19 +66,27 @@ dokka {
break break
} }
multiplatform { dokkaSourceSets {
global { configureEach {
skipDeprecated = true skipDeprecated.set(true)
sourceLink { sourceLink {
path = "./" localDirectory.set(file("./"))
url = "https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/" remoteUrl.set(new URL("https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/"))
lineSuffix = "#L" remoteLineSuffix.set("#L")
} }
} }
common(includeSourcesInDokka("commonMain")) named("commonMain") {
js(includeSourcesInDokka("jsMain"/*, "commonMain"*/)) sourceRoots.setFrom(findSourcesWithName("commonMain"))
jvm(includeSourcesInDokka("jvmMain"/*, "commonMain"*/)) }
named("jsMain") {
sourceRoots.setFrom(findSourcesWithName("jsMain", "commonMain"))
}
named("jvmMain") {
sourceRoots.setFrom(findSourcesWithName("jvmMain", "commonMain"))
}
} }
} }

View File

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

View File

@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx1024m org.gradle.jvmargs=-Xmx2048m
kotlin.code.style=official kotlin.code.style=official
org.gradle.parallel=true org.gradle.parallel=true
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
@@ -6,8 +6,8 @@ kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
kotlin_version=1.4.10 kotlin_version=1.4.10
kotlin_coroutines_version=1.3.9 kotlin_coroutines_version=1.4.0
kotlin_serialisation_runtime_version=1.0.0-RC2 kotlin_serialisation_runtime_version=1.0.1
klock_version=1.12.1 klock_version=1.12.1
uuid_version=0.2.2 uuid_version=0.2.2
ktor_version=1.4.1 ktor_version=1.4.1
@@ -15,7 +15,7 @@ ktor_version=1.4.1
javax_activation_version=1.1.1 javax_activation_version=1.1.1
library_group=dev.inmo library_group=dev.inmo
library_version=0.29.0 library_version=0.29.4
gradle_bintray_plugin_version=1.8.5 gradle_bintray_plugin_version=1.8.5
github_release_plugin_version=2.2.12 github_release_plugin_version=2.2.12

View File

@@ -1,3 +1,10 @@
pluginManagement {
repositories {
gradlePluginPortal()
jcenter()
}
}
include ":tgbotapi.core" include ":tgbotapi.core"
include ":tgbotapi.extensions.api" include ":tgbotapi.extensions.api"
include ":tgbotapi.extensions.utils" include ":tgbotapi.extensions.utils"

View File

@@ -15,6 +15,7 @@ fun newRequestException(
description.contains("Bad Request: message is not modified") -> MessageIsNotModifiedException(response, plainAnswer, message, cause) description.contains("Bad Request: message is not modified") -> MessageIsNotModifiedException(response, plainAnswer, message, cause)
description == "Unauthorized" -> UnauthorizedException(response, plainAnswer, message, cause) description == "Unauthorized" -> UnauthorizedException(response, plainAnswer, message, cause)
description.contains("PHOTO_INVALID_DIMENSIONS") -> InvalidPhotoDimensionsException(response, plainAnswer, message, cause) description.contains("PHOTO_INVALID_DIMENSIONS") -> InvalidPhotoDimensionsException(response, plainAnswer, message, cause)
description.contains("wrong file identifier") -> WrongFileIdentifierException(response, plainAnswer, message, cause)
else -> null else -> null
} }
} ?: CommonRequestException(response, plainAnswer, message, cause) } ?: CommonRequestException(response, plainAnswer, message, cause)
@@ -45,3 +46,6 @@ class MessageToEditNotFoundException(response: Response, plainAnswer: String, me
class InvalidPhotoDimensionsException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) : class InvalidPhotoDimensionsException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
RequestException(response, plainAnswer, message, cause) RequestException(response, plainAnswer, message, cause)
class WrongFileIdentifierException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
RequestException(response, plainAnswer, message, cause)

View File

@@ -1,6 +1,5 @@
package dev.inmo.tgbotapi.requests.abstracts package dev.inmo.tgbotapi.requests.abstracts
import dev.inmo.tgbotapi.types.InputMedia.toInputMediaFileAttachmentName
import dev.inmo.tgbotapi.utils.StorageFile import dev.inmo.tgbotapi.utils.StorageFile
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.descriptors.* import kotlinx.serialization.descriptors.*
@@ -12,6 +11,14 @@ sealed class InputFile {
abstract val fileId: String abstract val fileId: String
} }
internal inline val InputFile.attachFileId
get() = "attach://$fileId"
internal inline val InputFile.fileIdToSend
get() = when (this) {
is FileId -> fileId
is MultipartFile -> attachFileId
}
// TODO:: add checks for file url/file id regex // TODO:: add checks for file url/file id regex
/** /**
* Contains file id or file url * Contains file id or file url
@@ -30,12 +37,6 @@ internal object InputFileSerializer : KSerializer<InputFile> {
override fun deserialize(decoder: Decoder): FileId = FileId(decoder.decodeString()) override fun deserialize(decoder: Decoder): FileId = FileId(decoder.decodeString())
} }
internal val InputFile.asMediaData: String
get() = when (this) {
is FileId -> fileId
is MultipartFile -> fileId.toInputMediaFileAttachmentName()
}
// TODO:: add checks for files size // TODO:: add checks for files size
/** /**
* Contains info about file for sending * Contains info about file for sending

View File

@@ -8,12 +8,16 @@ import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.InputMedia.* import dev.inmo.tgbotapi.types.InputMedia.*
import dev.inmo.tgbotapi.types.message.abstracts.MediaGroupMessage import dev.inmo.tgbotapi.types.message.abstracts.MediaGroupMessage
import dev.inmo.tgbotapi.types.message.abstracts.TelegramBotAPIMessageDeserializeOnlySerializerClass import dev.inmo.tgbotapi.types.message.abstracts.TelegramBotAPIMessageDeserializeOnlySerializerClass
import dev.inmo.tgbotapi.utils.*
import dev.inmo.tgbotapi.utils.throwRangeError import dev.inmo.tgbotapi.utils.throwRangeError
import dev.inmo.tgbotapi.utils.toJsonWithoutNulls
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonArray
const val rawSendingMediaGroupsWarning = "Media groups contains restrictions related to combinations of media" +
" types. Currently it is possible to combine photo + video OR audio OR documents"
@RiskFeature(rawSendingMediaGroupsWarning)
fun SendMediaGroup( fun SendMediaGroup(
chatId: ChatIdentifier, chatId: ChatIdentifier,
media: List<MediaGroupMemberInputMedia>, media: List<MediaGroupMemberInputMedia>,
@@ -52,6 +56,46 @@ fun SendMediaGroup(
} }
} }
/**
* Use this method to be sure that you are correctly sending playlist with audios
*
* @see InputMediaAudio
*/
@Suppress("NOTHING_TO_INLINE")
inline fun SendPlaylist(
chatId: ChatIdentifier,
media: List<AudioMediaGroupMemberInputMedia>,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
) = SendMediaGroup(chatId, media, disableNotification, replyToMessageId)
/**
* Use this method to be sure that you are correctly sending documents media group
*
* @see InputMediaDocument
*/
@Suppress("NOTHING_TO_INLINE")
inline fun SendDocumentsGroup(
chatId: ChatIdentifier,
media: List<DocumentMediaGroupMemberInputMedia>,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
) = SendMediaGroup(chatId, media, disableNotification, replyToMessageId)
/**
* Use this method to be sure that you are correctly sending visual media group
*
* @see InputMediaPhoto
* @see InputMediaVideo
*/
@Suppress("NOTHING_TO_INLINE")
inline fun SendVisualMediaGroup(
chatId: ChatIdentifier,
media: List<VisualMediaGroupMemberInputMedia>,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
) = SendMediaGroup(chatId, media, disableNotification, replyToMessageId)
private val messagesListSerializer: KSerializer<List<MediaGroupMessage>> private val messagesListSerializer: KSerializer<List<MediaGroupMessage>>
= ListSerializer(TelegramBotAPIMessageDeserializeOnlySerializerClass()) = ListSerializer(TelegramBotAPIMessageDeserializeOnlySerializerClass())

View File

@@ -23,5 +23,4 @@ internal object ChatMemberDeserializationStrategy : DeserializationStrategy<Chat
override val descriptor: SerialDescriptor = RawChatMember.serializer().descriptor override val descriptor: SerialDescriptor = RawChatMember.serializer().descriptor
override fun deserialize(decoder: Decoder): ChatMember = RawChatMember.serializer().deserialize(decoder).asChatMember override fun deserialize(decoder: Decoder): ChatMember = RawChatMember.serializer().deserialize(decoder).asChatMember
override fun patch(decoder: Decoder, old: ChatMember): ChatMember = error("ChatMember can't be patched")
} }

View File

@@ -52,7 +52,11 @@ val inlineQueryAnswerResultsLimit = 0 .. 50
val customTitleLength = 0 .. 16 val customTitleLength = 0 .. 16
val diceResultLimit = 1 .. 6 val commonDiceResultLimit = 1 .. 6
@Deprecated("Renamed", ReplaceWith("commonDiceResultLimit", "dev.inmo.tgbotapi.types.commonDiceResultLimit"))
val diceResultLimit
get() = commonDiceResultLimit
val slotMachineDiceResultLimit = 1 .. 64
val botCommandLengthLimit = 1 .. 32 val botCommandLengthLimit = 1 .. 32
val botCommandLimit = botCommandLengthLimit val botCommandLimit = botCommandLengthLimit

View File

@@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.types.InputMedia
import dev.inmo.tgbotapi.requests.abstracts.InputFile import dev.inmo.tgbotapi.requests.abstracts.InputFile
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Deprecated("Will be removed due to redundancy for end-side users")
fun String.toInputMediaFileAttachmentName() = "attach://$this" fun String.toInputMediaFileAttachmentName() = "attach://$this"
@Serializable(InputMediaSerializer::class) @Serializable(InputMediaSerializer::class)

View File

@@ -23,5 +23,5 @@ data class InputMediaAnimation(
@SerialName(mediaField) @SerialName(mediaField)
override val media: String override val media: String
init { media = file.fileId } // crutch until js compiling will be fixed init { media = file.fileIdToSend } // crutch until js compiling will be fixed
} }

View File

@@ -5,9 +5,13 @@ import dev.inmo.tgbotapi.CommonAbstracts.Performerable
import dev.inmo.tgbotapi.requests.abstracts.* import dev.inmo.tgbotapi.requests.abstracts.*
import dev.inmo.tgbotapi.types.ParseMode.ParseMode import dev.inmo.tgbotapi.types.ParseMode.ParseMode
import dev.inmo.tgbotapi.types.ParseMode.parseModeField import dev.inmo.tgbotapi.types.ParseMode.parseModeField
import dev.inmo.tgbotapi.types.files.AudioFile
import dev.inmo.tgbotapi.types.files.PhotoSize
import dev.inmo.tgbotapi.types.mediaField import dev.inmo.tgbotapi.types.mediaField
import kotlinx.serialization.SerialName import dev.inmo.tgbotapi.types.message.content.media.AudioContent
import kotlinx.serialization.Serializable import kotlinx.serialization.*
internal const val audioInputMediaType = "audio"
@Serializable @Serializable
data class InputMediaAudio( data class InputMediaAudio(
@@ -19,10 +23,26 @@ data class InputMediaAudio(
override val performer: String? = null, override val performer: String? = null,
override val title: String? = null, override val title: String? = null,
override val thumb: InputFile? = null override val thumb: InputFile? = null
) : InputMedia, DuratedInputMedia, ThumbedInputMedia, TitledInputMedia, CaptionedOutput, Performerable { ) : InputMedia, AudioMediaGroupMemberInputMedia, DuratedInputMedia, ThumbedInputMedia, TitledInputMedia, CaptionedOutput, Performerable {
override val type: String = "audio" override val type: String = audioInputMediaType
override fun serialize(format: StringFormat): String = format.encodeToString(serializer(), this)
@SerialName(mediaField) @SerialName(mediaField)
override val media: String override val media: String
init { media = file.fileId } // crutch until js compiling will be fixed init { media = file.fileIdToSend } // crutch until js compiling will be fixed
} }
fun AudioFile.toInputMediaAudio(
caption: String? = null,
parseMode: ParseMode? = null,
title: String? = this.title
): InputMediaAudio = InputMediaAudio(
fileId,
caption,
parseMode,
duration,
performer,
title,
thumb ?.fileId
)

View File

@@ -4,9 +4,11 @@ import dev.inmo.tgbotapi.CommonAbstracts.CaptionedOutput
import dev.inmo.tgbotapi.requests.abstracts.* import dev.inmo.tgbotapi.requests.abstracts.*
import dev.inmo.tgbotapi.types.ParseMode.ParseMode import dev.inmo.tgbotapi.types.ParseMode.ParseMode
import dev.inmo.tgbotapi.types.ParseMode.parseModeField import dev.inmo.tgbotapi.types.ParseMode.parseModeField
import dev.inmo.tgbotapi.types.files.DocumentFile
import dev.inmo.tgbotapi.types.mediaField import dev.inmo.tgbotapi.types.mediaField
import kotlinx.serialization.SerialName import kotlinx.serialization.*
import kotlinx.serialization.Serializable
internal const val documentInputMediaType = "document"
@Serializable @Serializable
data class InputMediaDocument( data class InputMediaDocument(
@@ -15,10 +17,22 @@ data class InputMediaDocument(
@SerialName(parseModeField) @SerialName(parseModeField)
override val parseMode: ParseMode? = null, override val parseMode: ParseMode? = null,
override val thumb: InputFile? = null override val thumb: InputFile? = null
) : InputMedia, ThumbedInputMedia, CaptionedOutput { ) : InputMedia, DocumentMediaGroupMemberInputMedia, ThumbedInputMedia, CaptionedOutput {
override val type: String = "document" override val type: String = documentInputMediaType
override fun serialize(format: StringFormat): String = format.encodeToString(serializer(), this)
@SerialName(mediaField) @SerialName(mediaField)
override val media: String override val media: String
init { media = file.fileId } // crutch until js compiling will be fixed init { media = file.fileIdToSend } // crutch until js compiling will be fixed
} }
fun DocumentFile.toInputMediaDocument(
caption: String? = null,
parseMode: ParseMode? = null
) = InputMediaDocument(
fileId,
caption,
parseMode,
thumb ?.fileId
)

View File

@@ -17,14 +17,14 @@ data class InputMediaPhoto(
override val caption: String? = null, override val caption: String? = null,
@SerialName(parseModeField) @SerialName(parseModeField)
override val parseMode: ParseMode? = null override val parseMode: ParseMode? = null
) : InputMedia, MediaGroupMemberInputMedia { ) : InputMedia, VisualMediaGroupMemberInputMedia {
override val type: String = photoInputMediaType override val type: String = photoInputMediaType
override fun serialize(format: StringFormat): String = format.encodeToString(serializer(), this) override fun serialize(format: StringFormat): String = format.encodeToString(serializer(), this)
@SerialName(mediaField) @SerialName(mediaField)
override val media: String override val media: String
init { media = file.fileId } // crutch until js compiling will be fixed init { media = file.fileIdToSend } // crutch until js compiling will be fixed
} }
fun PhotoSize.toInputMediaPhoto( fun PhotoSize.toInputMediaPhoto(

View File

@@ -19,12 +19,12 @@ data class InputMediaVideo(
override val height: Int? = null, override val height: Int? = null,
override val duration: Long? = null, override val duration: Long? = null,
override val thumb: InputFile? = null override val thumb: InputFile? = null
) : InputMedia, SizedInputMedia, DuratedInputMedia, ThumbedInputMedia, MediaGroupMemberInputMedia { ) : InputMedia, SizedInputMedia, DuratedInputMedia, ThumbedInputMedia, VisualMediaGroupMemberInputMedia {
override val type: String = videoInputMediaType override val type: String = videoInputMediaType
override fun serialize(format: StringFormat): String = format.encodeToString(serializer(), this) override fun serialize(format: StringFormat): String = format.encodeToString(serializer(), this)
@SerialName(mediaField) @SerialName(mediaField)
override val media: String override val media: String
init { media = file.fileId } // crutch until js compiling will be fixed init { media = file.fileIdToSend } // crutch until js compiling will be fixed
} }

View File

@@ -18,3 +18,9 @@ internal fun <T> T.buildArguments(withSerializer: SerializationStrategy<T>) = ar
interface MediaGroupMemberInputMedia : InputMedia, CaptionedOutput { interface MediaGroupMemberInputMedia : InputMedia, CaptionedOutput {
fun serialize(format: StringFormat): String fun serialize(format: StringFormat): String
} }
interface AudioMediaGroupMemberInputMedia: MediaGroupMemberInputMedia
interface DocumentMediaGroupMemberInputMedia: MediaGroupMemberInputMedia
@Serializable(MediaGroupMemberInputMediaSerializer::class)
interface VisualMediaGroupMemberInputMedia : MediaGroupMemberInputMedia

View File

@@ -16,6 +16,8 @@ internal object MediaGroupMemberInputMediaSerializer : KSerializer<MediaGroupMem
when (value) { when (value) {
is InputMediaPhoto -> InputMediaPhoto.serializer().serialize(encoder, value) is InputMediaPhoto -> InputMediaPhoto.serializer().serialize(encoder, value)
is InputMediaVideo -> InputMediaVideo.serializer().serialize(encoder, value) is InputMediaVideo -> InputMediaVideo.serializer().serialize(encoder, value)
is InputMediaAudio -> InputMediaAudio.serializer().serialize(encoder, value)
is InputMediaDocument -> InputMediaDocument.serializer().serialize(encoder, value)
} }
} }
@@ -25,6 +27,8 @@ internal object MediaGroupMemberInputMediaSerializer : KSerializer<MediaGroupMem
return when (json[typeField] ?.jsonPrimitive ?.contentOrNull) { return when (json[typeField] ?.jsonPrimitive ?.contentOrNull) {
photoInputMediaType -> nonstrictJsonFormat.decodeFromJsonElement(InputMediaPhoto.serializer(), json) photoInputMediaType -> nonstrictJsonFormat.decodeFromJsonElement(InputMediaPhoto.serializer(), json)
videoInputMediaType -> nonstrictJsonFormat.decodeFromJsonElement(InputMediaVideo.serializer(), json) videoInputMediaType -> nonstrictJsonFormat.decodeFromJsonElement(InputMediaVideo.serializer(), json)
audioInputMediaType -> nonstrictJsonFormat.decodeFromJsonElement(InputMediaAudio.serializer(), json)
documentInputMediaType -> nonstrictJsonFormat.decodeFromJsonElement(InputMediaDocument.serializer(), json)
else -> error("Illegal type of incoming MediaGroupMemberInputMedia") else -> error("Illegal type of incoming MediaGroupMemberInputMedia")
} }
} }

View File

@@ -9,6 +9,7 @@ interface ThumbedInputMedia : InputMedia {
val thumb: InputFile? val thumb: InputFile?
@Serializable @Serializable
@SerialName(thumbField) @SerialName(thumbField)
@Deprecated("Will be removed due to useless state")
val thumbMedia: String? val thumbMedia: String?
get() = thumb ?.let { get() = thumb ?.let {
when (it) { when (it) {

View File

@@ -22,6 +22,10 @@ object BasketballDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFC0" override val emoji: String = "\uD83C\uDFC0"
} }
@Serializable(DiceAnimationTypeSerializer::class) @Serializable(DiceAnimationTypeSerializer::class)
object SlotMachineDiceAnimationType : DiceAnimationType() {
override val emoji: String = "\uD83C\uDFB0"
}
@Serializable(DiceAnimationTypeSerializer::class)
data class CustomDiceAnimationType( data class CustomDiceAnimationType(
override val emoji: String override val emoji: String
) : DiceAnimationType() ) : DiceAnimationType()
@@ -34,6 +38,7 @@ internal object DiceAnimationTypeSerializer : KSerializer<DiceAnimationType> {
CubeDiceAnimationType.emoji -> CubeDiceAnimationType CubeDiceAnimationType.emoji -> CubeDiceAnimationType
DartsDiceAnimationType.emoji -> DartsDiceAnimationType DartsDiceAnimationType.emoji -> DartsDiceAnimationType
BasketballDiceAnimationType.emoji -> BasketballDiceAnimationType BasketballDiceAnimationType.emoji -> BasketballDiceAnimationType
SlotMachineDiceAnimationType.emoji -> SlotMachineDiceAnimationType
else -> CustomDiceAnimationType(type) else -> CustomDiceAnimationType(type)
} }
} }

View File

@@ -24,3 +24,5 @@ data class AudioFile(
override val fileSize: Long? = null, override val fileSize: Long? = null,
override val thumb: PhotoSize? = null override val thumb: PhotoSize? = null
) : TelegramMediaFile, MimedMediaFile, ThumbedMediaFile, PlayableMediaFile, TitledMediaFile, Performerable ) : TelegramMediaFile, MimedMediaFile, ThumbedMediaFile, PlayableMediaFile, TitledMediaFile, Performerable
fun AudioFile.asVoiceFile() = VoiceFile(fileId, fileUniqueId, duration, mimeType, fileSize)

View File

@@ -22,3 +22,13 @@ data class DocumentFile(
@SerialName(fileNameField) @SerialName(fileNameField)
override val fileName: String? = null override val fileName: String? = null
) : TelegramMediaFile, MimedMediaFile, ThumbedMediaFile, CustomNamedMediaFile ) : TelegramMediaFile, MimedMediaFile, ThumbedMediaFile, CustomNamedMediaFile
@Suppress("NOTHING_TO_INLINE")
inline fun TelegramMediaFile.asDocumentFile() = DocumentFile(
fileId,
fileUniqueId,
fileSize,
(this as? ThumbedMediaFile) ?.thumb,
(this as? MimedMediaFile) ?.mimeType,
(this as? CustomNamedMediaFile) ?.fileName
)

View File

@@ -2,9 +2,13 @@ package dev.inmo.tgbotapi.types.files
import dev.inmo.tgbotapi.requests.abstracts.FileId import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.types.FileUniqueId import dev.inmo.tgbotapi.types.FileUniqueId
import dev.inmo.tgbotapi.types.InputMedia.InputMediaVideo
import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode
import dev.inmo.tgbotapi.types.ParseMode.ParseMode
import dev.inmo.tgbotapi.types.fileUniqueIdField import dev.inmo.tgbotapi.types.fileUniqueIdField
import dev.inmo.tgbotapi.types.files.abstracts.* import dev.inmo.tgbotapi.types.files.abstracts.*
import dev.inmo.tgbotapi.utils.MimeType import dev.inmo.tgbotapi.utils.MimeType
import dev.inmo.tgbotapi.utils.toHtmlCaptions
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -23,3 +27,17 @@ data class VideoFile(
@SerialName(fileSizeField) @SerialName(fileSizeField)
override val fileSize: Long? = null override val fileSize: Long? = null
) : TelegramMediaFile, MimedMediaFile, ThumbedMediaFile, PlayableMediaFile, SizedMediaFile ) : TelegramMediaFile, MimedMediaFile, ThumbedMediaFile, PlayableMediaFile, SizedMediaFile
@Suppress("NOTHING_TO_INLINE")
inline fun VideoFile.toInputMediaVideo(
caption: String? = null,
parseMode: ParseMode? = null
) = InputMediaVideo(
fileId,
caption,
parseMode,
width,
height,
duration,
thumb ?.fileId
)

View File

@@ -20,3 +20,8 @@ data class VoiceFile(
@SerialName(fileSizeField) @SerialName(fileSizeField)
override val fileSize: Long? = null override val fileSize: Long? = null
) : TelegramMediaFile, MimedMediaFile, PlayableMediaFile ) : TelegramMediaFile, MimedMediaFile, PlayableMediaFile
fun VoiceFile.asAudioFile(
performer: String? = null,
title: String? = null
) = AudioFile(fileId, fileUniqueId, duration, performer, title, mimeType, fileSize)

View File

@@ -220,17 +220,20 @@ internal data class RawMessage(
} }
} ?: content?.let { content -> } ?: content?.let { content ->
media_group_id?.let { media_group_id?.let {
val checkedContent = when (content) {
is PhotoContent -> content
is VideoContent -> content
is AudioContent -> content
is DocumentContent -> content
else -> error("Unsupported content for media group")
}
when (from) { when (from) {
null -> ChannelMediaGroupMessage( null -> ChannelMediaGroupMessage(
messageId, messageId,
chat, chat,
date.asDate, date.asDate,
it, it,
when (content) { checkedContent,
is PhotoContent -> content
is VideoContent -> content
else -> error("Unsupported content for media group")
},
edit_date?.asDate, edit_date?.asDate,
forwarded, forwarded,
reply_to_message?.asMessage, reply_to_message?.asMessage,
@@ -242,11 +245,7 @@ internal data class RawMessage(
chat, chat,
date.asDate, date.asDate,
it, it,
when (content) { checkedContent,
is PhotoContent -> content
is VideoContent -> content
else -> error("Unsupported content for media group")
},
edit_date?.asDate, edit_date?.asDate,
forwarded, forwarded,
reply_to_message?.asMessage, reply_to_message?.asMessage,

View File

@@ -26,8 +26,6 @@ internal class TelegramBotAPIMessageDeserializationStrategyClass<T> : Deserializ
@InternalSerializationApi @InternalSerializationApi
override val descriptor: SerialDescriptor = buildSerialDescriptor("TelegramBotAPIMessageSerializer", PolymorphicKind.OPEN) override val descriptor: SerialDescriptor = buildSerialDescriptor("TelegramBotAPIMessageSerializer", PolymorphicKind.OPEN)
override fun patch(decoder: Decoder, old: T): T = error("TelegramBotAPIMessageSerializer")
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun deserialize(decoder: Decoder): T { override fun deserialize(decoder: Decoder): T {
return RawMessage.serializer().deserialize(decoder).asMessage as T return RawMessage.serializer().deserialize(decoder).asMessage as T

View File

@@ -5,4 +5,8 @@ import dev.inmo.tgbotapi.types.InputMedia.MediaGroupMemberInputMedia
interface MediaGroupContent : MediaContent, CaptionedInput { interface MediaGroupContent : MediaContent, CaptionedInput {
fun toMediaGroupMemberInputMedia(): MediaGroupMemberInputMedia fun toMediaGroupMemberInputMedia(): MediaGroupMemberInputMedia
} }
interface VisualMediaGroupContent : MediaGroupContent
interface AudioMediaGroupContent : MediaGroupContent
interface DocumentMediaGroupContent : MediaGroupContent

View File

@@ -1,18 +1,17 @@
package dev.inmo.tgbotapi.types.message.content.media package dev.inmo.tgbotapi.types.message.content.media
import dev.inmo.tgbotapi.CommonAbstracts.CaptionedInput
import dev.inmo.tgbotapi.CommonAbstracts.TextPart import dev.inmo.tgbotapi.CommonAbstracts.TextPart
import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.requests.send.media.SendAudio import dev.inmo.tgbotapi.requests.send.media.SendAudio
import dev.inmo.tgbotapi.types.ChatIdentifier import dev.inmo.tgbotapi.types.ChatIdentifier
import dev.inmo.tgbotapi.types.InputMedia.InputMediaAudio import dev.inmo.tgbotapi.types.InputMedia.*
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode
import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2 import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
import dev.inmo.tgbotapi.types.files.AudioFile import dev.inmo.tgbotapi.types.files.AudioFile
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.utils.toHtmlCaptions import dev.inmo.tgbotapi.utils.toHtmlCaptions
import dev.inmo.tgbotapi.utils.toMarkdownV2Captions import dev.inmo.tgbotapi.utils.toMarkdownV2Captions
@@ -20,7 +19,7 @@ data class AudioContent(
override val media: AudioFile, override val media: AudioFile,
override val caption: String? = null, override val caption: String? = null,
override val captionEntities: List<TextPart> = emptyList() override val captionEntities: List<TextPart> = emptyList()
) : MediaContent, CaptionedInput { ) : AudioMediaGroupContent {
override fun createResend( override fun createResend(
chatId: ChatIdentifier, chatId: ChatIdentifier,
disableNotification: Boolean, disableNotification: Boolean,
@@ -40,13 +39,10 @@ data class AudioContent(
replyMarkup replyMarkup
) )
override fun asInputMedia(): InputMediaAudio = InputMediaAudio( override fun toMediaGroupMemberInputMedia(): InputMediaAudio = asInputMedia()
media.fileId,
toMarkdownV2Captions().firstOrNull(), override fun asInputMedia(): InputMediaAudio = media.toInputMediaAudio(
MarkdownV2, toHtmlCaptions().firstOrNull(),
media.duration, HTMLParseMode
media.performer,
media.title,
media.thumb ?.fileId
) )
} }

View File

@@ -5,14 +5,15 @@ import dev.inmo.tgbotapi.CommonAbstracts.TextPart
import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.requests.send.media.SendDocument import dev.inmo.tgbotapi.requests.send.media.SendDocument
import dev.inmo.tgbotapi.types.ChatIdentifier import dev.inmo.tgbotapi.types.ChatIdentifier
import dev.inmo.tgbotapi.types.InputMedia.InputMediaDocument import dev.inmo.tgbotapi.types.InputMedia.*
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode
import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2 import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
import dev.inmo.tgbotapi.types.files.DocumentFile import dev.inmo.tgbotapi.types.files.DocumentFile
import dev.inmo.tgbotapi.types.files.asDocumentFile
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.utils.toHtmlCaptions import dev.inmo.tgbotapi.utils.toHtmlCaptions
import dev.inmo.tgbotapi.utils.toMarkdownV2Captions import dev.inmo.tgbotapi.utils.toMarkdownV2Captions
@@ -20,7 +21,7 @@ data class DocumentContent(
override val media: DocumentFile, override val media: DocumentFile,
override val caption: String? = null, override val caption: String? = null,
override val captionEntities: List<TextPart> = emptyList() override val captionEntities: List<TextPart> = emptyList()
) : MediaContent, CaptionedInput { ) : DocumentMediaGroupContent {
override fun createResend( override fun createResend(
chatId: ChatIdentifier, chatId: ChatIdentifier,
disableNotification: Boolean, disableNotification: Boolean,
@@ -37,10 +38,22 @@ data class DocumentContent(
replyMarkup replyMarkup
) )
override fun asInputMedia(): InputMediaDocument = InputMediaDocument( override fun toMediaGroupMemberInputMedia(): InputMediaDocument = asInputMedia()
media.fileId,
toMarkdownV2Captions().firstOrNull(), override fun asInputMedia(): InputMediaDocument = media.toInputMediaDocument(
MarkdownV2, toHtmlCaptions().firstOrNull(),
media.thumb ?.fileId HTMLParseMode
)
}
@Suppress("NOTHING_TO_INLINE")
inline fun MediaContent.asDocumentContent() = when (this) {
is CaptionedInput -> DocumentContent(
media.asDocumentFile(),
caption,
captionEntities
)
else -> DocumentContent(
media.asDocumentFile()
) )
} }

View File

@@ -4,16 +4,14 @@ import dev.inmo.tgbotapi.CommonAbstracts.TextPart
import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.requests.send.media.SendPhoto import dev.inmo.tgbotapi.requests.send.media.SendPhoto
import dev.inmo.tgbotapi.types.ChatIdentifier import dev.inmo.tgbotapi.types.ChatIdentifier
import dev.inmo.tgbotapi.types.InputMedia.InputMediaPhoto import dev.inmo.tgbotapi.types.InputMedia.*
import dev.inmo.tgbotapi.types.InputMedia.MediaGroupMemberInputMedia
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode
import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2 import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
import dev.inmo.tgbotapi.types.files.* import dev.inmo.tgbotapi.types.files.*
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaCollectionContent import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaGroupContent
import dev.inmo.tgbotapi.utils.toHtmlCaptions import dev.inmo.tgbotapi.utils.toHtmlCaptions
import dev.inmo.tgbotapi.utils.toMarkdownV2Captions import dev.inmo.tgbotapi.utils.toMarkdownV2Captions
@@ -21,7 +19,7 @@ data class PhotoContent(
override val mediaCollection: Photo, override val mediaCollection: Photo,
override val caption: String? = null, override val caption: String? = null,
override val captionEntities: List<TextPart> = emptyList() override val captionEntities: List<TextPart> = emptyList()
) : MediaCollectionContent<PhotoSize>, MediaGroupContent { ) : MediaCollectionContent<PhotoSize>, VisualMediaGroupContent {
override val media: PhotoSize = mediaCollection.biggest() ?: throw IllegalStateException("Can't locate any photo size for this content") override val media: PhotoSize = mediaCollection.biggest() ?: throw IllegalStateException("Can't locate any photo size for this content")
override fun createResend( override fun createResend(
@@ -39,15 +37,10 @@ data class PhotoContent(
replyMarkup replyMarkup
) )
override fun toMediaGroupMemberInputMedia(): MediaGroupMemberInputMedia = InputMediaPhoto( override fun toMediaGroupMemberInputMedia(): InputMediaPhoto = asInputMedia()
media.fileId,
override fun asInputMedia(): InputMediaPhoto = media.toInputMediaPhoto(
toHtmlCaptions().firstOrNull(), toHtmlCaptions().firstOrNull(),
HTMLParseMode HTMLParseMode
) )
override fun asInputMedia(): InputMediaPhoto = InputMediaPhoto(
media.fileId,
toMarkdownV2Captions().firstOrNull(),
MarkdownV2
)
} }

View File

@@ -11,8 +11,10 @@ import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode
import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2 import dev.inmo.tgbotapi.types.ParseMode.MarkdownV2
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
import dev.inmo.tgbotapi.types.files.VideoFile import dev.inmo.tgbotapi.types.files.VideoFile
import dev.inmo.tgbotapi.types.files.toInputMediaVideo
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaGroupContent import dev.inmo.tgbotapi.types.message.content.abstracts.MediaGroupContent
import dev.inmo.tgbotapi.types.message.content.abstracts.VisualMediaGroupContent
import dev.inmo.tgbotapi.utils.toHtmlCaptions import dev.inmo.tgbotapi.utils.toHtmlCaptions
import dev.inmo.tgbotapi.utils.toMarkdownV2Captions import dev.inmo.tgbotapi.utils.toMarkdownV2Captions
@@ -20,7 +22,7 @@ data class VideoContent(
override val media: VideoFile, override val media: VideoFile,
override val caption: String? = null, override val caption: String? = null,
override val captionEntities: List<TextPart> = emptyList() override val captionEntities: List<TextPart> = emptyList()
) : MediaGroupContent { ) : VisualMediaGroupContent {
override fun createResend( override fun createResend(
chatId: ChatIdentifier, chatId: ChatIdentifier,
disableNotification: Boolean, disableNotification: Boolean,
@@ -41,23 +43,10 @@ data class VideoContent(
replyMarkup replyMarkup
) )
override fun toMediaGroupMemberInputMedia(): MediaGroupMemberInputMedia = InputMediaVideo( override fun toMediaGroupMemberInputMedia(): InputMediaVideo = asInputMedia()
media.fileId,
toHtmlCaptions().firstOrNull(),
HTMLParseMode,
media.width,
media.height,
media.duration,
media.thumb ?.fileId
)
override fun asInputMedia(): InputMediaVideo = InputMediaVideo( override fun asInputMedia(): InputMediaVideo = media.toInputMediaVideo(
media.fileId, toHtmlCaptions().firstOrNull(),
toMarkdownV2Captions().firstOrNull(), HTMLParseMode
MarkdownV2,
media.width,
media.height,
media.duration,
media.thumb ?.fileId
) )
} }

View File

@@ -47,6 +47,4 @@ object UpdateDeserializationStrategy : DeserializationStrategy<Update> {
asJson asJson
) )
} }
override fun patch(decoder: Decoder, old: Update): Update = error("Unsupported operation")
} }

View File

@@ -5,21 +5,20 @@ import dev.inmo.tgbotapi.types.update.*
import dev.inmo.tgbotapi.types.update.MediaGroupUpdates.* import dev.inmo.tgbotapi.types.update.MediaGroupUpdates.*
import dev.inmo.tgbotapi.types.update.abstracts.UnknownUpdate import dev.inmo.tgbotapi.types.update.abstracts.UnknownUpdate
import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.types.update.abstracts.Update
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
@Suppress("EXPERIMENTAL_API_USAGE", "unused") @Suppress("EXPERIMENTAL_API_USAGE", "unused")
class FlowsUpdatesFilter( class FlowsUpdatesFilter(
broadcastChannelsSize: Int = 100 broadcastChannelsSize: Int = 100
): UpdatesFilter { ): UpdatesFilter {
private val updatesReceivingChannel = BroadcastChannel<Update>(broadcastChannelsSize) private val updatesSharedFlow = MutableSharedFlow<Update>(extraBufferCapacity = broadcastChannelsSize)
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
val allUpdatesFlow: Flow<Update> = updatesReceivingChannel.asFlow() val allUpdatesFlow: Flow<Update> = updatesSharedFlow.asSharedFlow()
override val allowedUpdates: List<String> override val allowedUpdates: List<String>
get() = ALL_UPDATES_LIST get() = ALL_UPDATES_LIST
override val asUpdateReceiver: UpdateReceiver<Update> = { override val asUpdateReceiver: UpdateReceiver<Update> = {
updatesReceivingChannel.send(it) updatesSharedFlow.emit(it)
} }
val messageFlow: Flow<MessageUpdate> = allUpdatesFlow.filterIsInstance() val messageFlow: Flow<MessageUpdate> = allUpdatesFlow.filterIsInstance()

View File

@@ -17,3 +17,21 @@ package dev.inmo.tgbotapi.utils
AnnotationTarget.TYPE_PARAMETER AnnotationTarget.TYPE_PARAMETER
) )
annotation class PreviewFeature annotation class PreviewFeature
@RequiresOptIn(
"This feature can work unstable and may have some restrictions in Telegram System",
RequiresOptIn.Level.WARNING
)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPE,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.TYPE_PARAMETER
)
annotation class RiskFeature(val message: String)

View File

@@ -11,6 +11,7 @@ private sealed class DebounceAction<T> {
private data class AddValue<T>(override val value: T) : DebounceAction<T>() private data class AddValue<T>(override val value: T) : DebounceAction<T>()
private data class RemoveJob<T>(override val value: T, val job: Job) : DebounceAction<T>() private data class RemoveJob<T>(override val value: T, val job: Job) : DebounceAction<T>()
@Deprecated("Unused and will be removed in next major release")
fun <T> ReceiveChannel<T>.debounceByValue( fun <T> ReceiveChannel<T>.debounceByValue(
delayMillis: Long, delayMillis: Long,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default), scope: CoroutineScope = CoroutineScope(Dispatchers.Default),

View File

@@ -1,13 +1,18 @@
package dev.inmo.tgbotapi.extensions.api.send.media package dev.inmo.tgbotapi.extensions.api.send.media
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.send.media.SendMediaGroup import dev.inmo.tgbotapi.requests.send.media.*
import dev.inmo.tgbotapi.types.ChatIdentifier import dev.inmo.tgbotapi.types.ChatIdentifier
import dev.inmo.tgbotapi.types.InputMedia.MediaGroupMemberInputMedia import dev.inmo.tgbotapi.types.InputMedia.*
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.chat.abstracts.Chat import dev.inmo.tgbotapi.types.chat.abstracts.Chat
import dev.inmo.tgbotapi.types.message.abstracts.Message import dev.inmo.tgbotapi.types.message.abstracts.Message
import dev.inmo.tgbotapi.utils.RiskFeature
/**
* @see SendMediaGroup
*/
@RiskFeature(rawSendingMediaGroupsWarning)
suspend fun TelegramBot.sendMediaGroup( suspend fun TelegramBot.sendMediaGroup(
chatId: ChatIdentifier, chatId: ChatIdentifier,
media: List<MediaGroupMemberInputMedia>, media: List<MediaGroupMemberInputMedia>,
@@ -19,6 +24,10 @@ suspend fun TelegramBot.sendMediaGroup(
) )
) )
/**
* @see SendMediaGroup
*/
@RiskFeature(rawSendingMediaGroupsWarning)
suspend fun TelegramBot.sendMediaGroup( suspend fun TelegramBot.sendMediaGroup(
chat: Chat, chat: Chat,
media: List<MediaGroupMemberInputMedia>, media: List<MediaGroupMemberInputMedia>,
@@ -28,12 +37,108 @@ suspend fun TelegramBot.sendMediaGroup(
chat.id, media, disableNotification, replyToMessageId chat.id, media, disableNotification, replyToMessageId
) )
/**
* @see SendPlaylist
*/
suspend fun TelegramBot.sendPlaylist(
chatId: ChatIdentifier,
media: List<AudioMediaGroupMemberInputMedia>,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
) = execute(
SendPlaylist(
chatId, media, disableNotification, replyToMessageId
)
)
/**
* @see SendPlaylist
*/
suspend fun TelegramBot.sendPlaylist(
chat: Chat,
media: List<AudioMediaGroupMemberInputMedia>,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
) = sendPlaylist(
chat.id, media, disableNotification, replyToMessageId
)
/**
* @see SendDocumentsGroup
*/
suspend fun TelegramBot.sendDocumentsGroup(
chatId: ChatIdentifier,
media: List<DocumentMediaGroupMemberInputMedia>,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
) = execute(
SendDocumentsGroup(
chatId, media, disableNotification, replyToMessageId
)
)
/**
* @see SendDocumentsGroup
*/
suspend fun TelegramBot.sendDocumentsGroup(
chat: Chat,
media: List<DocumentMediaGroupMemberInputMedia>,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
) = sendDocumentsGroup(
chat.id, media, disableNotification, replyToMessageId
)
/**
* @see SendVisualMediaGroup
*/
suspend fun TelegramBot.sendVisualMediaGroup(
chatId: ChatIdentifier,
media: List<VisualMediaGroupMemberInputMedia>,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
) = execute(
SendVisualMediaGroup(
chatId, media, disableNotification, replyToMessageId
)
)
/**
* @see SendVisualMediaGroup
*/
suspend fun TelegramBot.sendVisualMediaGroup(
chat: Chat,
media: List<VisualMediaGroupMemberInputMedia>,
disableNotification: Boolean = false,
replyToMessageId: MessageIdentifier? = null
) = sendVisualMediaGroup(
chat.id, media, disableNotification, replyToMessageId
)
suspend inline fun TelegramBot.replyWithMediaGroup( suspend inline fun TelegramBot.replyWithMediaGroup(
to: Message, to: Message,
media: List<MediaGroupMemberInputMedia>, media: List<MediaGroupMemberInputMedia>,
disableNotification: Boolean = false disableNotification: Boolean = false
) = sendMediaGroup(to.chat, media, disableNotification, to.messageId) ) = sendMediaGroup(to.chat, media, disableNotification, to.messageId)
suspend inline fun TelegramBot.replyWithPlaylist(
to: Message,
media: List<AudioMediaGroupMemberInputMedia>,
disableNotification: Boolean = false
) = sendPlaylist(to.chat, media, disableNotification, to.messageId)
suspend inline fun TelegramBot.replyWithDocumentsGroup(
to: Message,
media: List<DocumentMediaGroupMemberInputMedia>,
disableNotification: Boolean = false
) = sendDocumentsGroup(to.chat, media, disableNotification, to.messageId)
suspend inline fun TelegramBot.replyWithVisualMediaGroup(
to: Message,
media: List<VisualMediaGroupMemberInputMedia>,
disableNotification: Boolean = false
) = sendVisualMediaGroup(to.chat, media, disableNotification, to.messageId)
suspend inline fun TelegramBot.reply( suspend inline fun TelegramBot.reply(
to: Message, to: Message,
media: List<MediaGroupMemberInputMedia>, media: List<MediaGroupMemberInputMedia>,

View File

@@ -2,7 +2,6 @@ package dev.inmo.tgbotapi.extensions.utils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.BroadcastChannel import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
/** /**
@@ -11,15 +10,15 @@ import kotlinx.coroutines.flow.*
fun <T> aggregateFlows( fun <T> aggregateFlows(
withScope: CoroutineScope, withScope: CoroutineScope,
vararg flows: Flow<T>, vararg flows: Flow<T>,
internalBufferSize: Int = Channel.BUFFERED internalBufferSize: Int = 64
): Flow<T> { ): Flow<T> {
val bc = BroadcastChannel<T>(internalBufferSize) val sharedFlow = MutableSharedFlow<T>(extraBufferCapacity = internalBufferSize)
flows.forEach { flows.forEach {
it.onEach { it.onEach {
safely { bc.send(it) } safely { sharedFlow.emit(it) }
}.launchIn(withScope) }.launchIn(withScope)
} }
return bc.asFlow() return sharedFlow
} }
fun <T> Flow<Iterable<T>>.flatMap(): Flow<T> = flow { fun <T> Flow<Iterable<T>>.flatMap(): Flow<T> = flow {

View File

@@ -0,0 +1,43 @@
package dev.inmo.tgbotapi.extensions.utils
import dev.inmo.tgbotapi.types.DiceResult
import dev.inmo.tgbotapi.types.dice.Dice
import dev.inmo.tgbotapi.types.dice.SlotMachineDiceAnimationType
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
enum class SlotMachineReelImages {
BAR, BERRIES, LEMON, SEVEN
}
val Int.asSlotMachineReelImage
get() = when (this) {
0 -> SlotMachineReelImages.BAR
1 -> SlotMachineReelImages.BERRIES
2 -> SlotMachineReelImages.LEMON
else -> SlotMachineReelImages.SEVEN
}
@Serializable
data class SlotMachineResult(
val rawValue: DiceResult
) {
@Transient
val left = rawValue and 3
@Transient
val center = rawValue shr 2 and 3
@Transient
val right = rawValue shr 4
@Transient
val leftReel = left.asSlotMachineReelImage
@Transient
val centerReel = center.asSlotMachineReelImage
@Transient
val rightReel = right.asSlotMachineReelImage
}
fun Dice.calculateSlotMachineResult() = if (animationType == SlotMachineDiceAnimationType) {
SlotMachineResult(value - 1)
} else {
null
}

View File

@@ -6,8 +6,7 @@ import dev.inmo.tgbotapi.extensions.utils.updates.asContentMessagesFlow
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.* import dev.inmo.tgbotapi.types.message.content.*
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaGroupContent import dev.inmo.tgbotapi.types.message.content.abstracts.*
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent
import dev.inmo.tgbotapi.types.message.content.media.* import dev.inmo.tgbotapi.types.message.content.media.*
import dev.inmo.tgbotapi.types.message.payments.InvoiceContent import dev.inmo.tgbotapi.types.message.payments.InvoiceContent
import dev.inmo.tgbotapi.types.update.MediaGroupUpdates.SentMediaGroupUpdate import dev.inmo.tgbotapi.types.update.MediaGroupUpdates.SentMediaGroupUpdate
@@ -101,6 +100,12 @@ fun Flow<BaseSentMessageUpdate>.audioMessages() = filterContentMessages<AudioCon
fun FlowsUpdatesFilter.audioMessages( fun FlowsUpdatesFilter.audioMessages(
scopeToIncludeChannels: CoroutineScope? = null scopeToIncludeChannels: CoroutineScope? = null
) = filterContentMessages<AudioContent>(scopeToIncludeChannels) ) = filterContentMessages<AudioContent>(scopeToIncludeChannels)
fun FlowsUpdatesFilter.audioMessagesWithMediaGroups(
scopeToIncludeChannels: CoroutineScope? = null
) = merge(
filterContentMessages<AudioContent>(scopeToIncludeChannels),
mediaGroupAudioMessages(scopeToIncludeChannels).flatMap()
)
fun Flow<BaseSentMessageUpdate>.contactMessages() = filterContentMessages<ContactContent>() fun Flow<BaseSentMessageUpdate>.contactMessages() = filterContentMessages<ContactContent>()
fun FlowsUpdatesFilter.contactMessages( fun FlowsUpdatesFilter.contactMessages(
@@ -116,6 +121,12 @@ fun Flow<BaseSentMessageUpdate>.documentMessages() = filterContentMessages<Docum
fun FlowsUpdatesFilter.documentMessages( fun FlowsUpdatesFilter.documentMessages(
scopeToIncludeChannels: CoroutineScope? = null scopeToIncludeChannels: CoroutineScope? = null
) = filterContentMessages<DocumentContent>(scopeToIncludeChannels) ) = filterContentMessages<DocumentContent>(scopeToIncludeChannels)
fun FlowsUpdatesFilter.documentMessagesWithMediaGroups(
scopeToIncludeChannels: CoroutineScope? = null
) = merge(
filterContentMessages<DocumentContent>(scopeToIncludeChannels),
mediaGroupDocumentMessages(scopeToIncludeChannels).flatMap()
)
fun Flow<BaseSentMessageUpdate>.gameMessages() = filterContentMessages<GameContent>() fun Flow<BaseSentMessageUpdate>.gameMessages() = filterContentMessages<GameContent>()
fun FlowsUpdatesFilter.gameMessages( fun FlowsUpdatesFilter.gameMessages(
@@ -210,3 +221,18 @@ fun Flow<SentMediaGroupUpdate>.mediaGroupVideosMessages() = filterMediaGroupMess
fun FlowsUpdatesFilter.mediaGroupVideosMessages( fun FlowsUpdatesFilter.mediaGroupVideosMessages(
scopeToIncludeChannels: CoroutineScope? = null scopeToIncludeChannels: CoroutineScope? = null
) = filterMediaGroupMessages<VideoContent>(scopeToIncludeChannels) ) = filterMediaGroupMessages<VideoContent>(scopeToIncludeChannels)
fun Flow<SentMediaGroupUpdate>.mediaGroupVisualMessages() = filterMediaGroupMessages<VisualMediaGroupContent>()
fun FlowsUpdatesFilter.mediaGroupVisualMessages(
scopeToIncludeChannels: CoroutineScope? = null
) = filterMediaGroupMessages<VisualMediaGroupContent>(scopeToIncludeChannels)
fun Flow<SentMediaGroupUpdate>.mediaGroupAudioMessages() = filterMediaGroupMessages<AudioContent>()
fun FlowsUpdatesFilter.mediaGroupAudioMessages(
scopeToIncludeChannels: CoroutineScope? = null
) = filterMediaGroupMessages<AudioContent>(scopeToIncludeChannels)
fun Flow<SentMediaGroupUpdate>.mediaGroupDocumentMessages() = filterMediaGroupMessages<DocumentContent>()
fun FlowsUpdatesFilter.mediaGroupDocumentMessages(
scopeToIncludeChannels: CoroutineScope? = null
) = filterMediaGroupMessages<DocumentContent>(scopeToIncludeChannels)