1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2024-12-23 00:57:13 +00:00

add support of expandable blockquote

This commit is contained in:
InsanusMokrassar 2024-05-28 23:46:30 +06:00
parent 93e06a5765
commit e403bbca0b
11 changed files with 178 additions and 49 deletions

View File

@ -32,6 +32,7 @@ internal data class RawMessageEntity(
"phone_number" -> 1 "phone_number" -> 1
"bold" -> 1 "bold" -> 1
"blockquote" -> 0 "blockquote" -> 0
"expandable_blockquote" -> 0
"italic" -> 1 "italic" -> 1
"text_mention" -> 1 "text_mention" -> 1
"strikethrough" -> 1 "strikethrough" -> 1
@ -66,6 +67,7 @@ internal fun RawMessageEntity.asTextSource(
"phone_number" -> PhoneNumberTextSource(sourceSubstring, subPartsWithRegulars) "phone_number" -> PhoneNumberTextSource(sourceSubstring, subPartsWithRegulars)
"bold" -> BoldTextSource(sourceSubstring, subPartsWithRegulars) "bold" -> BoldTextSource(sourceSubstring, subPartsWithRegulars)
"blockquote" -> BlockquoteTextSource(sourceSubstring, subPartsWithRegulars) "blockquote" -> BlockquoteTextSource(sourceSubstring, subPartsWithRegulars)
"expandable_blockquote" -> ExpandableBlockquoteTextSource(sourceSubstring, subPartsWithRegulars)
"italic" -> ItalicTextSource(sourceSubstring, subPartsWithRegulars) "italic" -> ItalicTextSource(sourceSubstring, subPartsWithRegulars)
"code" -> CodeTextSource(sourceSubstring) "code" -> CodeTextSource(sourceSubstring)
"pre" -> PreTextSource(sourceSubstring, language) "pre" -> PreTextSource(sourceSubstring, language)
@ -186,6 +188,7 @@ internal fun TextSource.toRawMessageEntities(offset: Int = 0): List<RawMessageEn
is PhoneNumberTextSource -> RawMessageEntity("phone_number", offset, length) is PhoneNumberTextSource -> RawMessageEntity("phone_number", offset, length)
is BoldTextSource -> RawMessageEntity("bold", offset, length) is BoldTextSource -> RawMessageEntity("bold", offset, length)
is BlockquoteTextSource -> RawMessageEntity("blockquote", offset, length) is BlockquoteTextSource -> RawMessageEntity("blockquote", offset, length)
is ExpandableBlockquoteTextSource -> RawMessageEntity("expandable_blockquote", offset, length)
is ItalicTextSource -> RawMessageEntity("italic", offset, length) is ItalicTextSource -> RawMessageEntity("italic", offset, length)
is CodeTextSource -> RawMessageEntity("code", offset, length) is CodeTextSource -> RawMessageEntity("code", offset, length)
is PreTextSource -> RawMessageEntity("pre", offset, length, language = language) is PreTextSource -> RawMessageEntity("pre", offset, length, language = language)

View File

@ -31,20 +31,21 @@ data class AnimationContent(
replyParameters: ReplyParameters?, replyParameters: ReplyParameters?,
replyMarkup: KeyboardMarkup? replyMarkup: KeyboardMarkup?
): Request<ContentMessage<AnimationContent>> = SendAnimation( ): Request<ContentMessage<AnimationContent>> = SendAnimation(
chatId, chatId = chatId,
media.fileId, animation = media.fileId,
media.thumbnail ?.fileId, thumbnail = media.thumbnail ?.fileId,
textSources, entities = textSources,
spoilered, showCaptionAboveMedia = showCaptionAboveMedia,
media.duration, spoilered = spoilered,
media.width, duration = media.duration,
media.height, width = media.width,
messageThreadId, height = media.height,
businessConnectionId, threadId = messageThreadId,
disableNotification, businessConnectionId = businessConnectionId,
protectContent, disableNotification = disableNotification,
replyParameters, protectContent = protectContent,
replyMarkup replyParameters = replyParameters,
replyMarkup = replyMarkup
) )
override fun asTelegramMedia(): TelegramMediaAnimation = TelegramMediaAnimation( override fun asTelegramMedia(): TelegramMediaAnimation = TelegramMediaAnimation(

View File

@ -33,16 +33,17 @@ data class PhotoContent(
replyParameters: ReplyParameters?, replyParameters: ReplyParameters?,
replyMarkup: KeyboardMarkup? replyMarkup: KeyboardMarkup?
): Request<ContentMessage<PhotoContent>> = SendPhoto( ): Request<ContentMessage<PhotoContent>> = SendPhoto(
chatId, chatId = chatId,
media.fileId, photo = media.fileId,
textSources, entities = textSources,
spoilered, showCaptionAboveMedia = showCaptionAboveMedia,
messageThreadId, spoilered = spoilered,
businessConnectionId, threadId = messageThreadId,
disableNotification, businessConnectionId = businessConnectionId,
protectContent, disableNotification = disableNotification,
replyParameters, protectContent = protectContent,
replyMarkup replyParameters = replyParameters,
replyMarkup = replyMarkup
) )
override fun toMediaGroupMemberTelegramMedia(): TelegramMediaPhoto = asTelegramMedia() override fun toMediaGroupMemberTelegramMedia(): TelegramMediaPhoto = asTelegramMedia()

View File

@ -30,21 +30,22 @@ data class VideoContent(
replyParameters: ReplyParameters?, replyParameters: ReplyParameters?,
replyMarkup: KeyboardMarkup? replyMarkup: KeyboardMarkup?
): Request<ContentMessage<VideoContent>> = SendVideo( ): Request<ContentMessage<VideoContent>> = SendVideo(
chatId, chatId = chatId,
media.fileId, video = media.fileId,
media.thumbnail ?.fileId, thumbnail = media.thumbnail ?.fileId,
textSources, entities = textSources,
spoilered, showCaptionAboveMedia = showCaptionAboveMedia,
media.duration, spoilered = spoilered,
media.width, duration = media.duration,
media.height, width = media.width,
null, height = media.height,
messageThreadId, supportStreaming = null,
businessConnectionId, threadId = messageThreadId,
disableNotification, businessConnectionId = businessConnectionId,
protectContent, disableNotification = disableNotification,
replyParameters, protectContent = protectContent,
replyMarkup replyParameters = replyParameters,
replyMarkup = replyMarkup
) )
override fun toMediaGroupMemberTelegramMedia(): TelegramMediaVideo = asTelegramMedia() override fun toMediaGroupMemberTelegramMedia(): TelegramMediaVideo = asTelegramMedia()

View File

@ -0,0 +1,26 @@
package dev.inmo.tgbotapi.types.message.textsources
import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.extensions.makeString
import dev.inmo.tgbotapi.utils.internal.*
import kotlinx.serialization.Serializable
/**
* @see expandableBlockquote
*/
@Serializable
data class ExpandableBlockquoteTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String,
override val subsources: TextSourcesList
) : MultilevelTextSource {
override val markdown: String by lazy { source.expandableBlockquoteMarkdown() }
override val markdownV2: String by lazy { expandableBlockquoteMarkdownV2() }
override val html: String by lazy { expandableBlockquoteHTML() }
}
@Suppress("NOTHING_TO_INLINE")
inline fun expandableBlockquote(parts: TextSourcesList) = ExpandableBlockquoteTextSource(parts.makeString(), parts)
@Suppress("NOTHING_TO_INLINE")
inline fun expandableBlockquote(vararg parts: TextSource) = expandableBlockquote(parts.toList())
@Suppress("NOTHING_TO_INLINE")
inline fun expandableBlockquote(text: String) = expandableBlockquote(regular(text))

View File

@ -22,14 +22,37 @@ sealed interface TextSource {
get() = source get() = source
} }
@Suppress("NOTHING_TO_INLINE") operator fun TextSource.plus(other: TextSource) = when {
inline operator fun TextSource.plus(other: TextSource) = listOf(this, other) this is RegularTextSource && other is RegularTextSource -> listOf(RegularTextSource(source + other.source))
@Suppress("NOTHING_TO_INLINE") else -> listOf(this, other)
inline operator fun TextSource.plus(other: List<TextSource>) = listOf(this) + other }
@Suppress("NOTHING_TO_INLINE") operator fun TextSource.plus(text: String) = this + regular(text)
inline operator fun TextSource.plus(text: String) = listOf(this, regular(text)) operator fun List<TextSource>.plus(text: String): List<TextSource> {
@Suppress("NOTHING_TO_INLINE") val newList = mutableListOf<TextSource>()
inline operator fun List<TextSource>.plus(text: String) = this + regular(text)
for (i in 0 until size - 1) {
newList.add(get(i))
}
val sublist = lastOrNull() ?.let {
it + text
} ?: listOf(regular(text))
newList.addAll(sublist)
return newList
}
operator fun TextSource.plus(other: List<TextSource>) = other.fold(listOf(this)) { acc, textSource ->
val newList = mutableListOf<TextSource>()
for (i in 0 until acc.size - 1) {
newList.add(acc.get(i))
}
newList.addAll(acc.last() + textSource)
newList
}
@Serializable(TextSourceSerializer::class) @Serializable(TextSourceSerializer::class)
sealed interface MultilevelTextSource : TextSource { sealed interface MultilevelTextSource : TextSource {

View File

@ -140,6 +140,44 @@ inline fun EntitiesBuilder.blockquote(text: String) = add(dev.inmo.tgbotapi.type
*/ */
inline fun EntitiesBuilder.blockquoteln(text: String) = blockquote(text) + newLine inline fun EntitiesBuilder.blockquoteln(text: String) = blockquote(text) + newLine
/**
* Add blockquote using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.message.textsources.blockquote]
*/
inline fun EntitiesBuilder.expandableBlockquote(parts: TextSourcesList) = add(dev.inmo.tgbotapi.types.message.textsources.expandableBlockquote(parts))
/**
* Version of [EntitiesBuilder.expandableBlockquote] with new line at the end
*/
inline fun EntitiesBuilder.expandableBlockquoteln(parts: TextSourcesList) = expandableBlockquote(parts) + newLine
/**
* Add expandableBlockquote using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.message.textsources.expandableBlockquote].
* Will reuse separator config from [buildEntities]
*/
inline fun EntitiesBuilder.expandableBlockquote(noinline init: EntitiesBuilderBody) = add(dev.inmo.tgbotapi.types.message.textsources.expandableBlockquote(
buildEntities(separator, init)
))
/**
* Version of [EntitiesBuilder.expandableBlockquote] with new line at the end.
* Will reuse separator config from [buildEntities]
*/
inline fun EntitiesBuilder.expandableBlockquoteln(noinline init: EntitiesBuilderBody) = expandableBlockquote(init) + newLine
/**
* Add expandableBlockquote using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.message.textsources.expandableBlockquote]
*/
inline fun EntitiesBuilder.expandableBlockquote(vararg parts: TextSource) = add(dev.inmo.tgbotapi.types.message.textsources.expandableBlockquote(*parts))
/**
* Version of [EntitiesBuilder.expandableBlockquote] with new line at the end
*/
inline fun EntitiesBuilder.expandableBlockquoteln(vararg parts: TextSource) = expandableBlockquote(*parts) + newLine
/**
* Add expandableBlockquote using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.message.textsources.expandableBlockquote]
*/
inline fun EntitiesBuilder.expandableBlockquote(text: String) = add(dev.inmo.tgbotapi.types.message.textsources.expandableBlockquote(text))
/**
* Version of [EntitiesBuilder.expandableBlockquote] with new line at the end
*/
inline fun EntitiesBuilder.expandableBlockquoteln(text: String) = expandableBlockquote(text) + newLine
/** /**
* Add spoiler using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.message.textsources.spoiler] * Add spoiler using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.message.textsources.spoiler]
*/ */

View File

@ -45,6 +45,17 @@ internal fun MultilevelTextSource.blockquoteMarkdownV2(): String = markdownV2Def
internal fun MultilevelTextSource.blockquoteHTML(): String = htmlDefault(htmlBlockquoteControl) internal fun MultilevelTextSource.blockquoteHTML(): String = htmlDefault(htmlBlockquoteControl)
internal fun MultilevelTextSource.expandableBlockquoteMarkdownV2(): String = markdownV2Default(
openControlSymbol = markdownV2ExpandableBlockquoteOpenControl,
closeControlSymbol = markdownV2ExpandableBlockquoteCloseControl,
eachLineSeparator = markdownBlockquoteControl
)
internal fun MultilevelTextSource.expandableBlockquoteHTML(): String = htmlDefault(
openControlSymbol = htmlBlockquoteOpenControl,
closeControlSymbol = htmlBlockquoteControl
)
internal fun MultilevelTextSource.cashTagMarkdownV2(): String = subsources.makeMarkdownV2String() internal fun MultilevelTextSource.cashTagMarkdownV2(): String = subsources.makeMarkdownV2String()
internal fun MultilevelTextSource.cashTagHTML(): String = subsources.makeHtmlString() internal fun MultilevelTextSource.cashTagHTML(): String = subsources.makeHtmlString()

View File

@ -16,9 +16,12 @@ const val markdownV2StrikethroughControl = "~"
const val markdownV2UnderlineControl = "__" const val markdownV2UnderlineControl = "__"
const val markdownV2UnderlineEndControl = "$markdownV2UnderlineControl$markdownV2ItalicUnderlineDelimiter" const val markdownV2UnderlineEndControl = "$markdownV2UnderlineControl$markdownV2ItalicUnderlineDelimiter"
const val markdownV2ItalicEndControl = "$markdownItalicControl$markdownV2ItalicUnderlineDelimiter" const val markdownV2ItalicEndControl = "$markdownItalicControl$markdownV2ItalicUnderlineDelimiter"
const val markdownV2ExpandableBlockquoteOpenControl = "**"
const val markdownV2ExpandableBlockquoteCloseControl = "||"
const val htmlBoldControl = "b" const val htmlBoldControl = "b"
const val htmlBlockquoteControl = "blockquote" const val htmlBlockquoteControl = "blockquote"
const val htmlBlockquoteOpenControl = "blockquote expandable"
const val htmlItalicControl = "i" const val htmlItalicControl = "i"
const val htmlSpoilerControl = "span class=\"tg-spoiler\"" const val htmlSpoilerControl = "span class=\"tg-spoiler\""
const val htmlSpoilerClosingControl = "span" const val htmlSpoilerClosingControl = "span"
@ -51,6 +54,8 @@ internal fun String.boldMarkdown(): String = markdownDefault(markdownBoldControl
internal fun String.blockquoteMarkdown(): String = regularMarkdown() internal fun String.blockquoteMarkdown(): String = regularMarkdown()
internal fun String.expandableBlockquoteMarkdown(): String = regularMarkdown()
internal fun String.italicMarkdown(): String = markdownDefault(markdownItalicControl) internal fun String.italicMarkdown(): String = markdownDefault(markdownItalicControl)
internal fun String.spoilerMarkdown(): String = regularMarkdown() internal fun String.spoilerMarkdown(): String = regularMarkdown()

View File

@ -1,14 +1,21 @@
package dev.inmo.tgbotapi.types.MessageEntity package dev.inmo.tgbotapi.types.MessageEntity
import dev.inmo.tgbotapi.types.message.RawMessageEntity import dev.inmo.tgbotapi.types.message.RawMessageEntity
import dev.inmo.tgbotapi.types.message.asTextSources
import dev.inmo.tgbotapi.types.message.textsources.* import dev.inmo.tgbotapi.types.message.textsources.*
import dev.inmo.tgbotapi.types.message.toRawMessageEntities
import dev.inmo.tgbotapi.utils.extensions.makeSourceString
import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
const val testText = "It (is?) is simple hello world with #tag and @mention. Start of blockquote: Block quotation started\n" + const val testText = "It (is?) is simple hello world with #tag and @mention. Start of blockquote: Block quotation started\n" +
"Block quotation continued\n" +
"The last line of the block quotation\n" +
". Start of expandable blockquote: Block quotation started\n" +
"Block quotation continued\n" + "Block quotation continued\n" +
"The last line of the block quotation" "The last line of the block quotation"
const val formattedV2Text = "It \\(is?\\) *_is_ ~__simple__~* ||hello world|| with \\#tag and @mention\\. Start of blockquote: >Block quotation started\n>Block quotation continued\n>The last line of the block quotation" const val formattedV2Text = "It \\(is?\\) *_is_ ~__simple__~* ||hello world|| with \\#tag and @mention\\. Start of blockquote: >Block quotation started\n>Block quotation continued\n>The last line of the block quotation\n\\. Start of expandable blockquote: **>Block quotation started\n>Block quotation continued\n>The last line of the block quotation||"
const val formattedHtmlText = "It (is?) <b><i>is</i> <s><u>simple</u></s></b> <span class=\"tg-spoiler\">hello world</span> with #tag and @mention. Start of blockquote: <blockquote>Block quotation started\nBlock quotation continued\nThe last line of the block quotation</blockquote>" const val formattedHtmlText = "It (is?) <b><i>is</i> <s><u>simple</u></s></b> <span class=\"tg-spoiler\">hello world</span> with #tag and @mention. Start of blockquote: <blockquote>Block quotation started\nBlock quotation continued\nThe last line of the block quotation</blockquote>\n. Start of expandable blockquote: <blockquote expandable>Block quotation started\nBlock quotation continued\nThe last line of the block quotation</blockquote>"
internal val testTextEntities = listOf( internal val testTextEntities = listOf(
RawMessageEntity( RawMessageEntity(
"bold", "bold",
@ -49,6 +56,11 @@ internal val testTextEntities = listOf(
"blockquote", "blockquote",
76, 76,
86 86
),
RawMessageEntity(
"expandable_blockquote",
120,
204
) )
) )
@ -72,4 +84,6 @@ fun TextSourcesList.testTextSources() {
val blockquoteSource = get(9) as BlockquoteTextSource val blockquoteSource = get(9) as BlockquoteTextSource
assertTrue (blockquoteSource.subsources.first() is RegularTextSource) assertTrue (blockquoteSource.subsources.first() is RegularTextSource)
assertEquals(this, toRawMessageEntities().asTextSources(makeSourceString()))
} }

View File

@ -54,6 +54,12 @@ class StringFormattingTests {
"Block quotation started\n" + "Block quotation started\n" +
"Block quotation continued\n" + "Block quotation continued\n" +
"The last line of the block quotation" "The last line of the block quotation"
) +
"\n. Start of expandable blockquote: " +
expandableBlockquote(
"Block quotation started\n" +
"Block quotation continued\n" +
"The last line of the block quotation"
) )
sources.testTextSources() sources.testTextSources()