1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2024-11-22 16:23:48 +00:00

Merge pull request #295 from InsanusMokrassar/0.32.5

0.32.5
This commit is contained in:
InsanusMokrassar 2021-02-09 19:13:42 +06:00 committed by GitHub
commit 2e27b8b64d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 243 additions and 199 deletions

View File

@ -1,5 +1,16 @@
# TelegramBotAPI changelog # TelegramBotAPI changelog
## 0.32.5
* `Core`:
* Add `mention` variants for user ids and receiver variants ([#294](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/294))
* Now `AbstractRequestCallFactory` will set up one-second delay for zero timeouts in `GetUpdate` requests
* Several extensions for `TelegramBotAPI` like `retrieveAccumulatedUpdates` have been added as a solution for
[#293](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/293)
* Links for `tg://user?id=<user_id>` have been updated ([#292](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/292))
* All usages of captions or texts in resends and same things have been replaced with `textSources`
* Global `defaultParseMode` has been added ([#291](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/291))
## 0.32.4 ## 0.32.4
* `Common`: * `Common`:

View File

@ -17,6 +17,6 @@ micro_utils_version=0.4.24
javax_activation_version=1.1.1 javax_activation_version=1.1.1
library_group=dev.inmo library_group=dev.inmo
library_version=0.32.4 library_version=0.32.5
github_release_plugin_version=2.2.12 github_release_plugin_version=2.2.12

View File

@ -60,6 +60,7 @@ kotlin {
dependencies { dependencies {
implementation kotlin('test-common') implementation kotlin('test-common')
implementation kotlin('test-annotations-common') implementation kotlin('test-annotations-common')
implementation project(":tgbotapi.extensions.utils")
} }
} }

View File

@ -16,6 +16,8 @@ import io.ktor.http.ContentType
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlin.collections.set import kotlin.collections.set
var defaultUpdateTimeoutForZeroDelay = 1000L
abstract class AbstractRequestCallFactory : KtorCallFactory { abstract class AbstractRequestCallFactory : KtorCallFactory {
private val methodsCache: MutableMap<String, String> = mutableMapOf() private val methodsCache: MutableMap<String, String> = mutableMapOf()
override suspend fun <T : Any> makeCall( override suspend fun <T : Any> makeCall(
@ -41,6 +43,11 @@ abstract class AbstractRequestCallFactory : KtorCallFactory {
requestTimeoutMillis = customTimeoutMillis requestTimeoutMillis = customTimeoutMillis
socketTimeoutMillis = customTimeoutMillis socketTimeoutMillis = customTimeoutMillis
} }
} else {
timeout {
requestTimeoutMillis = defaultUpdateTimeoutForZeroDelay
socketTimeoutMillis = defaultUpdateTimeoutForZeroDelay
}
} }
} }
} }

View File

@ -1,5 +1,7 @@
package dev.inmo.tgbotapi.types package dev.inmo.tgbotapi.types
import dev.inmo.micro_utils.common.Warning
import dev.inmo.tgbotapi.types.chat.abstracts.Chat
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
@ -17,9 +19,20 @@ data class ChatId(
val chatId: Identifier val chatId: Identifier
) : ChatIdentifier() ) : ChatIdentifier()
/**
val ChatId.link: String * https://core.telegram.org/bots/api#formatting-options
get() = "tg://user?id=$chatId" */
@Warning("This API have restrictions in Telegram System")
val Identifier.link: String
get() = "tg://user?id=$this"
/**
* https://core.telegram.org/bots/api#formatting-options
*/
@Warning("This API have restrictions in Telegram System")
val UserId.link: String
get() = chatId.link
val User.link: String
get() = id.link
typealias UserId = ChatId typealias UserId = ChatId

View File

@ -74,3 +74,15 @@ fun AudioFile.toInputMediaAudio(
title, title,
thumb ?.fileId thumb ?.fileId
) )
fun AudioFile.toInputMediaAudio(
textSources: TextSourcesList = emptyList(),
title: String? = this.title
): InputMediaAudio = InputMediaAudio(
fileId,
textSources,
duration,
performer,
title,
thumb ?.fileId
)

View File

@ -70,3 +70,11 @@ fun DocumentFile.toInputMediaDocument(
parseMode, parseMode,
thumb ?.fileId thumb ?.fileId
) )
fun DocumentFile.toInputMediaDocument(
textSources: TextSourcesList = emptyList()
) = InputMediaDocument(
fileId,
textSources,
thumb ?.fileId
)

View File

@ -53,3 +53,10 @@ fun PhotoSize.toInputMediaPhoto(
caption, caption,
parseMode parseMode
) )
fun PhotoSize.toInputMediaPhoto(
textSources: TextSourcesList = emptyList()
): InputMediaPhoto = InputMediaPhoto(
fileId,
textSources
)

View File

@ -1,7 +1,7 @@
package dev.inmo.tgbotapi.types.MessageEntity.textsources package dev.inmo.tgbotapi.types.MessageEntity.textsources
import dev.inmo.tgbotapi.CommonAbstracts.* import dev.inmo.tgbotapi.CommonAbstracts.*
import dev.inmo.tgbotapi.types.User import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.internal.* import dev.inmo.tgbotapi.utils.internal.*
@ -21,6 +21,26 @@ data class TextMentionTextSource @RiskFeature(DirectInvocationOfTextSourceConstr
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun mention(parts: List<TextSource>, user: User) = TextMentionTextSource(parts.makeString(), user, parts) inline fun mention(parts: List<TextSource>, user: User) = TextMentionTextSource(parts.makeString(), user, parts)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun User.mention(parts: List<TextSource>) = mention(parts, this)
@Suppress("NOTHING_TO_INLINE")
inline fun mention(parts: List<TextSource>, userId: UserId) = mention(parts, CommonUser(userId, ""))
@Suppress("NOTHING_TO_INLINE")
inline fun UserId.mention(parts: List<TextSource>) = mention(parts, this)
@Suppress("NOTHING_TO_INLINE")
inline fun mention(parts: List<TextSource>, id: Identifier) = mention(parts, UserId(id))
@Suppress("NOTHING_TO_INLINE")
inline fun Identifier.mention(parts: List<TextSource>) = mention(parts, this)
@Suppress("NOTHING_TO_INLINE")
inline fun mention(user: User, vararg parts: TextSource) = mention(parts.toList(), user) inline fun mention(user: User, vararg parts: TextSource) = mention(parts.toList(), user)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun mention(text: String, user: User) = mention(user, regular(text)) inline fun mention(text: String, user: User) = mention(user, regular(text))
@Suppress("NOTHING_TO_INLINE")
inline fun User.mention(text: String) = mention(this, regular(text))
@Suppress("NOTHING_TO_INLINE")
inline fun mention(text: String, userId: UserId) = mention(text, CommonUser(userId, ""))
@Suppress("NOTHING_TO_INLINE")
inline fun UserId.mention(text: String) = mention(text, this)
@Suppress("NOTHING_TO_INLINE")
inline fun mention(text: String, id: Identifier) = mention(text, UserId(id))
@Suppress("NOTHING_TO_INLINE")
inline fun Identifier.mention(text: String) = mention(text, this)

View File

@ -35,12 +35,21 @@ typealias Markdown = MarkdownParseMode
typealias MarkdownV2 = MarkdownV2ParseMode typealias MarkdownV2 = MarkdownV2ParseMode
typealias HTML = HTMLParseMode typealias HTML = HTMLParseMode
/**
* This variable respects to default parse mode used in places like next:
*
* * [dev.inmo.tgbotapi.types.message.content.TextContent.createResends]
* *
*/
var defaultParseMode: ParseMode = HTML
@Serializer(ParseMode::class) @Serializer(ParseMode::class)
internal object ParseModeSerializerObject : KSerializer<ParseMode> { internal object ParseModeSerializerObject : KSerializer<ParseMode> {
override fun deserialize(decoder: Decoder): ParseMode { override fun deserialize(decoder: Decoder): ParseMode {
return when (decoder.decodeString()) { return when (decoder.decodeString()) {
MarkdownParseMode.parseModeName -> MarkdownParseMode Markdown.parseModeName -> Markdown
HTMLParseMode.parseModeName -> HTMLParseMode MarkdownV2.parseModeName -> MarkdownV2
HTML.parseModeName -> HTML
else -> throw IllegalArgumentException("Unknown parse mode") else -> throw IllegalArgumentException("Unknown parse mode")
} }
} }

View File

@ -1,5 +1,6 @@
package dev.inmo.tgbotapi.types.files package dev.inmo.tgbotapi.types.files
import dev.inmo.tgbotapi.CommonAbstracts.TextSourcesList
import dev.inmo.tgbotapi.requests.abstracts.FileId import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.InputMedia.InputMediaVideo import dev.inmo.tgbotapi.types.InputMedia.InputMediaVideo
@ -44,3 +45,15 @@ inline fun VideoFile.toInputMediaVideo(
duration, duration,
thumb ?.fileId thumb ?.fileId
) )
@Suppress("NOTHING_TO_INLINE")
inline fun VideoFile.toInputMediaVideo(
textSources: TextSourcesList
) = InputMediaVideo(
fileId,
textSources,
width,
height,
duration,
thumb ?.fileId
)

View File

@ -1,7 +1,6 @@
package dev.inmo.tgbotapi.types.message.content package dev.inmo.tgbotapi.types.message.content
import dev.inmo.tgbotapi.CommonAbstracts.TextPart import dev.inmo.tgbotapi.CommonAbstracts.*
import dev.inmo.tgbotapi.CommonAbstracts.TextedInput
import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.requests.send.SendTextMessage import dev.inmo.tgbotapi.requests.send.SendTextMessage
import dev.inmo.tgbotapi.types.ChatIdentifier import dev.inmo.tgbotapi.types.ChatIdentifier
@ -10,7 +9,6 @@ import dev.inmo.tgbotapi.types.ParseMode.*
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent
import dev.inmo.tgbotapi.utils.internal.*
data class TextContent( data class TextContent(
override val text: String, override val text: String,
@ -24,8 +22,7 @@ data class TextContent(
replyMarkup: KeyboardMarkup? replyMarkup: KeyboardMarkup?
): Request<ContentMessage<TextContent>> = SendTextMessage( ): Request<ContentMessage<TextContent>> = SendTextMessage(
chatId, chatId,
toHtmlTexts().first(), textSources,
HTMLParseMode,
false, false,
disableNotification, disableNotification,
replyToMessageId, replyToMessageId,
@ -33,42 +30,36 @@ data class TextContent(
replyMarkup replyMarkup
) )
@Deprecated(
"Useless due to fact that createResend currently use textSource and that will guarantee correct sending of message",
ReplaceWith("createResend")
)
override fun createResends( override fun createResends(
chatId: ChatIdentifier, chatId: ChatIdentifier,
disableNotification: Boolean, disableNotification: Boolean,
replyToMessageId: MessageIdentifier?, replyToMessageId: MessageIdentifier?,
allowSendingWithoutReply: Boolean?, allowSendingWithoutReply: Boolean?,
replyMarkup: KeyboardMarkup? replyMarkup: KeyboardMarkup?
): List<Request<ContentMessage<TextContent>>> = createResends( ): List<Request<ContentMessage<TextContent>>> = listOf(
createResend(
chatId, chatId,
disableNotification, disableNotification,
replyToMessageId, replyToMessageId,
allowSendingWithoutReply, allowSendingWithoutReply,
replyMarkup, replyMarkup
HTMLParseMode )
) )
@Deprecated(
"Useless due to fact that createResend currently use textSource and that will guarantee correct sending of message",
ReplaceWith("createResend")
)
fun createResends( fun createResends(
chatId: ChatIdentifier, chatId: ChatIdentifier,
disableNotification: Boolean, disableNotification: Boolean,
replyToMessageId: MessageIdentifier?, replyToMessageId: MessageIdentifier?,
allowSendingWithoutReply: Boolean?, allowSendingWithoutReply: Boolean?,
replyMarkup: KeyboardMarkup?, replyMarkup: KeyboardMarkup?,
parseMode: ParseMode = HTMLParseMode parseMode: ParseMode = defaultParseMode
): List<Request<ContentMessage<TextContent>>> = when (parseMode) { ): List<Request<ContentMessage<TextContent>>> = createResends(chatId, disableNotification, replyToMessageId, allowSendingWithoutReply, replyMarkup)
is MarkdownParseMode -> toMarkdownTexts()
is MarkdownV2ParseMode -> toMarkdownV2Texts()
is HTMLParseMode -> toHtmlTexts()
}.map {
SendTextMessage(
chatId,
it,
parseMode,
false,
disableNotification,
replyToMessageId,
allowSendingWithoutReply,
replyMarkup
)
}
} }

View File

@ -1,21 +1,16 @@
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.*
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.SendAnimation import dev.inmo.tgbotapi.requests.send.media.SendAnimation
import dev.inmo.tgbotapi.types.ChatIdentifier import dev.inmo.tgbotapi.types.ChatIdentifier
import dev.inmo.tgbotapi.types.InputMedia.InputMediaAnimation import dev.inmo.tgbotapi.types.InputMedia.InputMediaAnimation
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.MarkdownV2
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
import dev.inmo.tgbotapi.types.files.AnimationFile import dev.inmo.tgbotapi.types.files.AnimationFile
import dev.inmo.tgbotapi.types.files.DocumentFile import dev.inmo.tgbotapi.types.files.DocumentFile
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.MediaContent
import dev.inmo.tgbotapi.utils.internal.toHtmlCaptions
import dev.inmo.tgbotapi.utils.internal.toMarkdownV2Captions
data class AnimationContent( data class AnimationContent(
override val media: AnimationFile, override val media: AnimationFile,
@ -33,8 +28,7 @@ data class AnimationContent(
chatId, chatId,
media.fileId, media.fileId,
media.thumb ?.fileId, media.thumb ?.fileId,
toHtmlCaptions().firstOrNull(), textSources,
HTMLParseMode,
media.duration, media.duration,
media.width, media.width,
media.height, media.height,
@ -46,8 +40,7 @@ data class AnimationContent(
override fun asInputMedia(): InputMediaAnimation = InputMediaAnimation( override fun asInputMedia(): InputMediaAnimation = InputMediaAnimation(
media.fileId, media.fileId,
toMarkdownV2Captions().firstOrNull(), textSources,
MarkdownV2,
media.width, media.width,
media.height, media.height,
media.duration, media.duration,

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.TextPart import dev.inmo.tgbotapi.CommonAbstracts.TextPart
import dev.inmo.tgbotapi.CommonAbstracts.textSources
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.InputMediaAudio
import dev.inmo.tgbotapi.types.InputMedia.toInputMediaAudio import dev.inmo.tgbotapi.types.InputMedia.toInputMediaAudio
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode
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.AudioMediaGroupContent import dev.inmo.tgbotapi.types.message.content.abstracts.AudioMediaGroupContent
import dev.inmo.tgbotapi.utils.internal.toHtmlCaptions
data class AudioContent( data class AudioContent(
override val media: AudioFile, override val media: AudioFile,
@ -29,8 +28,7 @@ data class AudioContent(
chatId, chatId,
media.fileId, media.fileId,
media.thumb ?.fileId, media.thumb ?.fileId,
toHtmlCaptions().firstOrNull(), textSources,
HTMLParseMode,
media.duration, media.duration,
media.performer, media.performer,
media.title, media.title,
@ -42,8 +40,5 @@ data class AudioContent(
override fun toMediaGroupMemberInputMedia(): InputMediaAudio = asInputMedia() override fun toMediaGroupMemberInputMedia(): InputMediaAudio = asInputMedia()
override fun asInputMedia(): InputMediaAudio = media.toInputMediaAudio( override fun asInputMedia(): InputMediaAudio = media.toInputMediaAudio(textSources)
toHtmlCaptions().firstOrNull(),
HTMLParseMode
)
} }

View File

@ -1,21 +1,18 @@
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.*
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.InputMediaDocument
import dev.inmo.tgbotapi.types.InputMedia.toInputMediaDocument import dev.inmo.tgbotapi.types.InputMedia.toInputMediaDocument
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode
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.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.DocumentMediaGroupContent import dev.inmo.tgbotapi.types.message.content.abstracts.DocumentMediaGroupContent
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent
import dev.inmo.tgbotapi.utils.internal.toHtmlCaptions
data class DocumentContent( data class DocumentContent(
override val media: DocumentFile, override val media: DocumentFile,
@ -32,8 +29,7 @@ data class DocumentContent(
chatId, chatId,
media.fileId, media.fileId,
media.thumb ?.fileId, media.thumb ?.fileId,
toHtmlCaptions().firstOrNull(), textSources,
HTMLParseMode,
disableNotification, disableNotification,
replyToMessageId, replyToMessageId,
allowSendingWithoutReply, allowSendingWithoutReply,
@ -42,10 +38,7 @@ data class DocumentContent(
override fun toMediaGroupMemberInputMedia(): InputMediaDocument = asInputMedia() override fun toMediaGroupMemberInputMedia(): InputMediaDocument = asInputMedia()
override fun asInputMedia(): InputMediaDocument = media.toInputMediaDocument( override fun asInputMedia(): InputMediaDocument = media.toInputMediaDocument(textSources)
toHtmlCaptions().firstOrNull(),
HTMLParseMode
)
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")

View File

@ -1,19 +1,18 @@
package dev.inmo.tgbotapi.types.message.content.media package dev.inmo.tgbotapi.types.message.content.media
import dev.inmo.tgbotapi.CommonAbstracts.TextPart import dev.inmo.tgbotapi.CommonAbstracts.TextPart
import dev.inmo.tgbotapi.CommonAbstracts.textSources
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.InputMediaPhoto
import dev.inmo.tgbotapi.types.InputMedia.toInputMediaPhoto import dev.inmo.tgbotapi.types.InputMedia.toInputMediaPhoto
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode
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.MediaCollectionContent
import dev.inmo.tgbotapi.types.message.content.abstracts.VisualMediaGroupContent import dev.inmo.tgbotapi.types.message.content.abstracts.VisualMediaGroupContent
import dev.inmo.tgbotapi.utils.internal.toHtmlCaptions
data class PhotoContent( data class PhotoContent(
override val mediaCollection: Photo, override val mediaCollection: Photo,
@ -31,8 +30,7 @@ data class PhotoContent(
): Request<ContentMessage<PhotoContent>> = SendPhoto( ): Request<ContentMessage<PhotoContent>> = SendPhoto(
chatId, chatId,
media.fileId, media.fileId,
toHtmlCaptions().firstOrNull(), textSources,
HTMLParseMode,
disableNotification, disableNotification,
replyToMessageId, replyToMessageId,
allowSendingWithoutReply, allowSendingWithoutReply,
@ -41,8 +39,5 @@ data class PhotoContent(
override fun toMediaGroupMemberInputMedia(): InputMediaPhoto = asInputMedia() override fun toMediaGroupMemberInputMedia(): InputMediaPhoto = asInputMedia()
override fun asInputMedia(): InputMediaPhoto = media.toInputMediaPhoto( override fun asInputMedia(): InputMediaPhoto = media.toInputMediaPhoto(textSources)
toHtmlCaptions().firstOrNull(),
HTMLParseMode
)
} }

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.TextPart import dev.inmo.tgbotapi.CommonAbstracts.TextPart
import dev.inmo.tgbotapi.CommonAbstracts.textSources
import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.requests.send.media.SendVideo import dev.inmo.tgbotapi.requests.send.media.SendVideo
import dev.inmo.tgbotapi.types.ChatIdentifier import dev.inmo.tgbotapi.types.ChatIdentifier
import dev.inmo.tgbotapi.types.InputMedia.InputMediaVideo import dev.inmo.tgbotapi.types.InputMedia.InputMediaVideo
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.ParseMode.HTMLParseMode
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.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.VisualMediaGroupContent import dev.inmo.tgbotapi.types.message.content.abstracts.VisualMediaGroupContent
import dev.inmo.tgbotapi.utils.internal.toHtmlCaptions
data class VideoContent( data class VideoContent(
override val media: VideoFile, override val media: VideoFile,
@ -29,8 +28,7 @@ data class VideoContent(
chatId, chatId,
media.fileId, media.fileId,
media.thumb ?.fileId, media.thumb ?.fileId,
toHtmlCaptions().firstOrNull(), textSources,
HTMLParseMode,
media.duration, media.duration,
media.width, media.width,
media.height, media.height,
@ -43,8 +41,5 @@ data class VideoContent(
override fun toMediaGroupMemberInputMedia(): InputMediaVideo = asInputMedia() override fun toMediaGroupMemberInputMedia(): InputMediaVideo = asInputMedia()
override fun asInputMedia(): InputMediaVideo = media.toInputMediaVideo( override fun asInputMedia(): InputMediaVideo = media.toInputMediaVideo(textSources)
toHtmlCaptions().firstOrNull(),
HTMLParseMode
)
} }

View File

@ -1,20 +1,15 @@
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.*
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.SendVoice import dev.inmo.tgbotapi.requests.send.media.SendVoice
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.InputMediaAudio
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.MarkdownV2
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
import dev.inmo.tgbotapi.types.files.VoiceFile import dev.inmo.tgbotapi.types.files.VoiceFile
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.MediaContent
import dev.inmo.tgbotapi.utils.internal.toHtmlCaptions
import dev.inmo.tgbotapi.utils.internal.toMarkdownV2Captions
data class VoiceContent( data class VoiceContent(
override val media: VoiceFile, override val media: VoiceFile,
@ -30,8 +25,7 @@ data class VoiceContent(
): Request<ContentMessage<VoiceContent>> = SendVoice( ): Request<ContentMessage<VoiceContent>> = SendVoice(
chatId, chatId,
media.fileId, media.fileId,
toHtmlCaptions().firstOrNull(), textSources,
HTMLParseMode,
media.duration, media.duration,
disableNotification, disableNotification,
replyToMessageId, replyToMessageId,
@ -41,8 +35,7 @@ data class VoiceContent(
override fun asInputMedia(): InputMediaAudio = InputMediaAudio( override fun asInputMedia(): InputMediaAudio = InputMediaAudio(
media.fileId, media.fileId,
toMarkdownV2Captions().firstOrNull(), textSources,
MarkdownV2,
media.duration media.duration
) )
} }

View File

@ -1,97 +0,0 @@
package dev.inmo.tgbotapi.utils.internal
import dev.inmo.tgbotapi.CommonAbstracts.*
import dev.inmo.tgbotapi.types.ParseMode.*
import dev.inmo.tgbotapi.types.captionLength
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.textLength
internal fun createFormattedText(
entities: TextSourcesList,
partLength: Int = textLength.last,
mode: ParseMode = MarkdownParseMode
): List<String> {
val texts = mutableListOf<String>()
val textBuilder = StringBuilder(partLength)
for (entity in entities) {
val string = when (mode) {
is MarkdownParseMode -> entity.markdown
is MarkdownV2ParseMode -> entity.markdownV2
is HTMLParseMode -> entity.html
}
if (textBuilder.length + string.length > partLength) {
if (textBuilder.isNotEmpty()) {
texts.add(textBuilder.toString())
textBuilder.clear()
}
val chunked = string.chunked(partLength)
val last = chunked.last()
textBuilder.append(last)
val listToAdd = if (chunked.size > 1) {
chunked.subList(0, chunked.size - 1)
} else {
emptyList()
}
listToAdd.forEach {
texts.add(it)
}
} else {
textBuilder.append(string)
}
}
if (textBuilder.isNotEmpty()) {
texts.add(textBuilder.toString())
textBuilder.clear()
}
return texts
}
internal fun createMarkdownText(
entities: TextSourcesList,
partLength: Int = textLength.last
): List<String> = createFormattedText(entities, partLength, MarkdownParseMode)
internal fun TextSourcesList.toMarkdownTexts(): List<String> = createMarkdownText(
this,
textLength.last
)
internal fun TextContent.toMarkdownTexts(): List<String> = textSources.toMarkdownTexts()
internal fun createMarkdownV2Text(
entities: TextSourcesList,
partLength: Int = textLength.last
): List<String> = createFormattedText(entities, partLength, MarkdownV2ParseMode)
internal fun TextSourcesList.toMarkdownV2Captions(): List<String> = createMarkdownV2Text(
this,
captionLength.last
)
internal fun CaptionedInput.toMarkdownV2Captions(): List<String> = textSources.toMarkdownV2Captions()
internal fun TextSourcesList.toMarkdownV2Texts(): List<String> = createMarkdownV2Text(
this,
textLength.last
)
internal fun TextContent.toMarkdownV2Texts(): List<String> = textSources.toMarkdownV2Texts()
internal fun createHtmlText(
entities: TextSourcesList,
partLength: Int = textLength.last
): List<String> = createFormattedText(entities, partLength, HTMLParseMode)
internal fun TextSourcesList.toHtmlCaptions(): List<String> = createHtmlText(
this,
captionLength.last
)
internal fun CaptionedInput.toHtmlCaptions(): List<String> = textSources.toHtmlCaptions()
internal fun TextSourcesList.toHtmlTexts(): List<String> = createHtmlText(
this,
textLength.last
)
internal fun TextContent.toHtmlTexts(): List<String> = textSources.toHtmlTexts()

View File

@ -2,8 +2,8 @@ package dev.inmo.tgbotapi.types.MessageEntity
import dev.inmo.tgbotapi.CommonAbstracts.TextSource import dev.inmo.tgbotapi.CommonAbstracts.TextSource
import dev.inmo.tgbotapi.CommonAbstracts.plus import dev.inmo.tgbotapi.CommonAbstracts.plus
import dev.inmo.tgbotapi.extensions.utils.formatting.*
import dev.inmo.tgbotapi.types.MessageEntity.textsources.* import dev.inmo.tgbotapi.types.MessageEntity.textsources.*
import dev.inmo.tgbotapi.utils.internal.*
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -1,8 +1,8 @@
package dev.inmo.tgbotapi.types.MessageEntity package dev.inmo.tgbotapi.types.MessageEntity
import dev.inmo.tgbotapi.CommonAbstracts.justTextSources import dev.inmo.tgbotapi.CommonAbstracts.justTextSources
import dev.inmo.tgbotapi.utils.internal.toHtmlTexts import dev.inmo.tgbotapi.extensions.utils.formatting.toHtmlTexts
import dev.inmo.tgbotapi.utils.internal.toMarkdownV2Texts import dev.inmo.tgbotapi.extensions.utils.formatting.toMarkdownV2Texts
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals

View File

@ -44,5 +44,23 @@ kotlin {
api project(":tgbotapi.core") api project(":tgbotapi.core")
} }
} }
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
jsTest {
dependencies {
implementation kotlin('test-junit')
implementation kotlin('test-js')
}
}
} }
} }

View File

@ -8,15 +8,15 @@ import dev.inmo.tgbotapi.types.message.content.TextContent
fun createFormattedText( fun createFormattedText(
entities: TextSourcesList, entities: TextSourcesList,
partLength: Int = textLength.last, partLength: Int = textLength.last,
mode: ParseMode = MarkdownParseMode mode: ParseMode = defaultParseMode
): List<String> { ): List<String> {
val texts = mutableListOf<String>() val texts = mutableListOf<String>()
val textBuilder = StringBuilder(partLength) val textBuilder = StringBuilder(partLength)
for (entity in entities) { for (entity in entities) {
val string = when (mode) { val string = when (mode) {
is MarkdownParseMode -> entity.markdown is Markdown -> entity.markdown
is MarkdownV2ParseMode -> entity.markdownV2 is MarkdownV2 -> entity.markdownV2
is HTMLParseMode -> entity.html is HTML -> entity.html
} }
if (textBuilder.length + string.length > partLength) { if (textBuilder.length + string.length > partLength) {
if (textBuilder.isNotEmpty()) { if (textBuilder.isNotEmpty()) {
@ -49,7 +49,7 @@ fun createFormattedText(
fun createMarkdownText( fun createMarkdownText(
entities: TextSourcesList, entities: TextSourcesList,
partLength: Int = textLength.last partLength: Int = textLength.last
): List<String> = createFormattedText(entities, partLength, MarkdownParseMode) ): List<String> = createFormattedText(entities, partLength, Markdown)
fun TextSourcesList.toMarkdownCaptions(): List<String> = createMarkdownText( fun TextSourcesList.toMarkdownCaptions(): List<String> = createMarkdownText(
this, this,
@ -73,7 +73,7 @@ fun ExplainedInput.toMarkdownExplanations(): List<String> = textSources.toMarkdo
fun createMarkdownV2Text( fun createMarkdownV2Text(
entities: TextSourcesList, entities: TextSourcesList,
partLength: Int = textLength.last partLength: Int = textLength.last
): List<String> = createFormattedText(entities, partLength, MarkdownV2ParseMode) ): List<String> = createFormattedText(entities, partLength, MarkdownV2)
fun TextSourcesList.toMarkdownV2Captions(): List<String> = createMarkdownV2Text( fun TextSourcesList.toMarkdownV2Captions(): List<String> = createMarkdownV2Text(
this, this,
@ -97,7 +97,7 @@ fun ExplainedInput.toMarkdownV2Explanations(): List<String> = textSources.toMark
fun createHtmlText( fun createHtmlText(
entities: TextSourcesList, entities: TextSourcesList,
partLength: Int = textLength.last partLength: Int = textLength.last
): List<String> = createFormattedText(entities, partLength, HTMLParseMode) ): List<String> = createFormattedText(entities, partLength, HTML)
fun TextSourcesList.toHtmlCaptions(): List<String> = createHtmlText( fun TextSourcesList.toHtmlCaptions(): List<String> = createHtmlText(
this, this,

View File

@ -1,7 +1,6 @@
package dev.inmo.tgbotapi.extensions.utils.updates.retrieving package dev.inmo.tgbotapi.extensions.utils.updates.retrieving
import dev.inmo.micro_utils.coroutines.ExceptionHandler import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.tgbotapi.bot.RequestsExecutor import dev.inmo.tgbotapi.bot.RequestsExecutor
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.bot.exceptions.RequestException import dev.inmo.tgbotapi.bot.exceptions.RequestException
@ -14,7 +13,11 @@ import dev.inmo.tgbotapi.types.update.MediaGroupUpdates.*
import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.updateshandlers.* import dev.inmo.tgbotapi.updateshandlers.*
import dev.inmo.tgbotapi.utils.* import dev.inmo.tgbotapi.utils.*
import io.ktor.client.features.HttpRequestTimeoutException
import io.ktor.utils.io.core.use
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.coroutines.coroutineContext
fun TelegramBot.startGettingOfUpdatesByLongPolling( fun TelegramBot.startGettingOfUpdatesByLongPolling(
timeoutSeconds: Seconds = 30, timeoutSeconds: Seconds = 30,
@ -66,6 +69,70 @@ fun TelegramBot.startGettingOfUpdatesByLongPolling(
} }
} }
fun TelegramBot.retrieveAccumulatedUpdates(
avoidInlineQueries: Boolean = false,
avoidCallbackQueries: Boolean = false,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
allowedUpdates: List<String>? = null,
updatesReceiver: UpdateReceiver<Update>
): Job = scope.launch {
safelyWithoutExceptions {
startGettingOfUpdatesByLongPolling(
0,
CoroutineScope(coroutineContext + SupervisorJob()),
{
if (it is HttpRequestTimeoutException) {
throw CancellationException("Cancel due to absence of new updates")
} else {
exceptionsHandler ?.invoke(it)
}
},
allowedUpdates
) {
when {
it is InlineQueryUpdate && avoidInlineQueries ||
it is CallbackQueryUpdate && avoidCallbackQueries -> return@startGettingOfUpdatesByLongPolling
else -> updatesReceiver(it)
}
}.join()
}
}
/**
* @return [kotlinx.coroutines.flow.Flow] which will emit updates to the collector while they will be accumulated. Works
* the same as [retrieveAccumulatedUpdates], but pass [kotlinx.coroutines.flow.FlowCollector.emit] as a callback
*/
fun TelegramBot.createAccumulatedUpdatesRetrieverFlow(
avoidInlineQueries: Boolean = false,
avoidCallbackQueries: Boolean = false,
exceptionsHandler: ExceptionHandler<Unit>? = null,
allowedUpdates: List<String>? = null
): Flow<Update> = channelFlow {
val parentContext = kotlin.coroutines.coroutineContext
channel.apply {
retrieveAccumulatedUpdates(
avoidInlineQueries,
avoidCallbackQueries,
CoroutineScope(parentContext),
exceptionsHandler,
allowedUpdates,
::send
).join()
close()
}
}
fun TelegramBot.retrieveAccumulatedUpdates(
flowsUpdatesFilter: FlowsUpdatesFilter,
avoidInlineQueries: Boolean = false,
avoidCallbackQueries: Boolean = false,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
exceptionsHandler: ExceptionHandler<Unit>? = null
) = flowsUpdatesFilter.run {
retrieveAccumulatedUpdates(avoidInlineQueries, avoidCallbackQueries, scope, exceptionsHandler, allowedUpdates, asUpdateReceiver)
}
/** /**
* Will [startGettingOfUpdatesByLongPolling] using incoming [flowsUpdatesFilter]. It is assumed that you ALREADY CONFIGURE * Will [startGettingOfUpdatesByLongPolling] using incoming [flowsUpdatesFilter]. It is assumed that you ALREADY CONFIGURE
* all updates receivers, because this method will trigger getting of updates and. * all updates receivers, because this method will trigger getting of updates and.