mirror of
				https://github.com/InsanusMokrassar/TelegramBotAPI.git
				synced 2025-10-30 19:50:12 +00:00 
			
		
		
		
	add spoiler support
This commit is contained in:
		| @@ -11,6 +11,8 @@ | |||||||
|         * `Klock`: `2.4.8` -> `2.4.10` |         * `Klock`: `2.4.8` -> `2.4.10` | ||||||
|         * `Ktor`: `1.6.5` -> `1.6.7` |         * `Ktor`: `1.6.5` -> `1.6.7` | ||||||
|         * `MicroUtils`: `0.8.7` -> `0.9.0` |         * `MicroUtils`: `0.8.7` -> `0.9.0` | ||||||
|  | * `Core`: | ||||||
|  |     * Add `SpoilerTextSource` (as part of `Telegram Bot API 5.6` update) | ||||||
|  |  | ||||||
| ## 0.37.4 | ## 0.37.4 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -49,6 +49,7 @@ internal fun RawMessageEntity.asTextSource( | |||||||
|         ) |         ) | ||||||
|         "underline" -> UnderlineTextSource(sourceSubstring, subPartsWithRegulars) |         "underline" -> UnderlineTextSource(sourceSubstring, subPartsWithRegulars) | ||||||
|         "strikethrough" -> StrikethroughTextSource(sourceSubstring, subPartsWithRegulars) |         "strikethrough" -> StrikethroughTextSource(sourceSubstring, subPartsWithRegulars) | ||||||
|  |         "spoiler" -> SpoilerTextSource(sourceSubstring, subPartsWithRegulars) | ||||||
|         else -> RegularTextSource(sourceSubstring) |         else -> RegularTextSource(sourceSubstring) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -158,6 +159,7 @@ internal fun TextSource.toRawMessageEntities(offset: Int = 0): List<RawMessageEn | |||||||
|             is TextMentionTextSource -> RawMessageEntity("text_mention", offset, length, user = user) |             is TextMentionTextSource -> RawMessageEntity("text_mention", offset, length, user = user) | ||||||
|             is UnderlineTextSource -> RawMessageEntity("underline", offset, length) |             is UnderlineTextSource -> RawMessageEntity("underline", offset, length) | ||||||
|             is StrikethroughTextSource -> RawMessageEntity("strikethrough", offset, length) |             is StrikethroughTextSource -> RawMessageEntity("strikethrough", offset, length) | ||||||
|  |             is SpoilerTextSource -> RawMessageEntity("spoiler", offset, length) | ||||||
|             else -> null |             else -> null | ||||||
|         } |         } | ||||||
|     ) + if (this is MultilevelTextSource) { |     ) + if (this is MultilevelTextSource) { | ||||||
|   | |||||||
| @@ -0,0 +1,27 @@ | |||||||
|  | package dev.inmo.tgbotapi.types.MessageEntity.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 italic | ||||||
|  |  */ | ||||||
|  | @Serializable | ||||||
|  | data class SpoilerTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor ( | ||||||
|  |     override val source: String, | ||||||
|  |     override val subsources: TextSourcesList | ||||||
|  | ) : MultilevelTextSource { | ||||||
|  |     override val markdown: String by lazy { source.spoilerMarkdown() } | ||||||
|  |     override val markdownV2: String by lazy { spoilerMarkdownV2() } | ||||||
|  |     override val html: String by lazy { spoilerHTML() } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @Suppress("NOTHING_TO_INLINE") | ||||||
|  | inline fun spoiler(parts: TextSourcesList) = SpoilerTextSource(parts.makeString(), parts) | ||||||
|  | @Suppress("NOTHING_TO_INLINE") | ||||||
|  | inline fun spoiler(vararg parts: TextSource) = spoiler(parts.toList()) | ||||||
|  | @Suppress("NOTHING_TO_INLINE") | ||||||
|  | inline fun spoiler(text: String) = spoiler(regular(text)) | ||||||
|  |  | ||||||
| @@ -20,6 +20,7 @@ private val baseSerializers: Map<String, KSerializer<out TextSource>> = mapOf( | |||||||
|     "text_mention" to TextMentionTextSource.serializer(), |     "text_mention" to TextMentionTextSource.serializer(), | ||||||
|     "hashtag" to HashTagTextSource.serializer(), |     "hashtag" to HashTagTextSource.serializer(), | ||||||
|     "cashtag" to CashTagTextSource.serializer(), |     "cashtag" to CashTagTextSource.serializer(), | ||||||
|  |     "spoiler" to SpoilerTextSource.serializer(), | ||||||
| ) | ) | ||||||
|  |  | ||||||
| object TextSourceSerializer : TypedSerializer<TextSource>(TextSource::class, baseSerializers) { | object TextSourceSerializer : TypedSerializer<TextSource>(TextSource::class, baseSerializers) { | ||||||
|   | |||||||
| @@ -49,6 +49,10 @@ internal fun MultilevelTextSource.italicMarkdownV2(): String = markdownV2Default | |||||||
| internal fun MultilevelTextSource.italicHTML(): String = htmlDefault(htmlItalicControl) | internal fun MultilevelTextSource.italicHTML(): String = htmlDefault(htmlItalicControl) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | internal fun MultilevelTextSource.spoilerMarkdownV2(): String = markdownV2Default(markdownSpoilerControl) | ||||||
|  | internal fun MultilevelTextSource.spoilerHTML(): String = htmlDefault(htmlSpoilerControl, htmlSpoilerClosingControl) | ||||||
|  |  | ||||||
|  |  | ||||||
| internal fun MultilevelTextSource.strikethroughMarkdownV2(): String = markdownV2Default(markdownV2StrikethroughControl) | internal fun MultilevelTextSource.strikethroughMarkdownV2(): String = markdownV2Default(markdownV2StrikethroughControl) | ||||||
| internal fun MultilevelTextSource.strikethroughHTML(): String = htmlDefault(htmlStrikethroughControl) | internal fun MultilevelTextSource.strikethroughHTML(): String = htmlDefault(htmlStrikethroughControl) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ import dev.inmo.tgbotapi.utils.extensions.* | |||||||
|  |  | ||||||
| const val markdownBoldControl = "*" | const val markdownBoldControl = "*" | ||||||
| const val markdownItalicControl = "_" | const val markdownItalicControl = "_" | ||||||
|  | const val markdownSpoilerControl = "||" | ||||||
| const val markdownCodeControl = "`" | const val markdownCodeControl = "`" | ||||||
| const val markdownPreControl = "```" | const val markdownPreControl = "```" | ||||||
|  |  | ||||||
| @@ -18,6 +19,8 @@ const val markdownV2ItalicEndControl = "$markdownItalicControl$markdownV2ItalicU | |||||||
|  |  | ||||||
| const val htmlBoldControl = "b" | const val htmlBoldControl = "b" | ||||||
| const val htmlItalicControl = "i" | const val htmlItalicControl = "i" | ||||||
|  | const val htmlSpoilerControl = "span class=\"tg-spoiler\"" | ||||||
|  | const val htmlSpoilerClosingControl = "span" | ||||||
| const val htmlCodeControl = "code" | const val htmlCodeControl = "code" | ||||||
| const val htmlPreControl = "pre" | const val htmlPreControl = "pre" | ||||||
| const val htmlUnderlineControl = "u" | const val htmlUnderlineControl = "u" | ||||||
| @@ -46,12 +49,13 @@ internal fun String.boldMarkdown(): String = markdownDefault(markdownBoldControl | |||||||
|  |  | ||||||
| internal fun String.italicMarkdown(): String = markdownDefault(markdownItalicControl) | internal fun String.italicMarkdown(): String = markdownDefault(markdownItalicControl) | ||||||
|  |  | ||||||
|  | internal fun String.spoilerMarkdown(): String = markdownDefault(markdownSpoilerControl) | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Crutch for support of strikethrough in default markdown. Simply add modifier, but it will not look like correct |  * Crutch for support of strikethrough in default markdown. Simply add modifier, but it will not look like correct | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| internal fun String.strikethroughMarkdown(): String = map { it + "\u0336" }.joinToString("") | internal fun String.strikethroughMarkdown(): String = map { it + "\u0336" }.joinToString("") | ||||||
| internal fun String.strikethroughMarkdownV2(): String = markdownV2Default(markdownV2StrikethroughControl) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|   | |||||||
| @@ -4,8 +4,8 @@ import dev.inmo.tgbotapi.types.MessageEntity.textsources.* | |||||||
| import kotlin.test.assertTrue | import kotlin.test.assertTrue | ||||||
|  |  | ||||||
| const val testText = "It is simple hello world with #tag and @mention" | const val testText = "It is simple hello world with #tag and @mention" | ||||||
| const val formattedV2Text = "It *_is_ ~__simple__~* hello world with \\#tag and @mention" | const val formattedV2Text = "It *_is_ ~__simple__~* ||hello world|| with \\#tag and @mention" | ||||||
| const val formattedHtmlText = "It <b><i>is</i> <s><u>simple</u></s></b> hello world with #tag and @mention" | const val formattedHtmlText = "It <b><i>is</i> <s><u>simple</u></s></b> <span class=\"tg-spoiler\">hello world</span> with #tag and @mention" | ||||||
| internal val testTextEntities = listOf( | internal val testTextEntities = listOf( | ||||||
|     RawMessageEntity( |     RawMessageEntity( | ||||||
|         "bold", |         "bold", | ||||||
| @@ -27,6 +27,11 @@ internal val testTextEntities = listOf( | |||||||
|         6, |         6, | ||||||
|         6 |         6 | ||||||
|     ), |     ), | ||||||
|  |     RawMessageEntity( | ||||||
|  |         "spoiler", | ||||||
|  |         13, | ||||||
|  |         11 | ||||||
|  |     ), | ||||||
|     RawMessageEntity( |     RawMessageEntity( | ||||||
|         "hashtag", |         "hashtag", | ||||||
|         30, |         30, | ||||||
| @@ -43,9 +48,11 @@ fun TextSourcesList.testTextSources() { | |||||||
|     assertTrue (first() is RegularTextSource) |     assertTrue (first() is RegularTextSource) | ||||||
|     assertTrue (get(1) is BoldTextSource) |     assertTrue (get(1) is BoldTextSource) | ||||||
|     assertTrue (get(2) is RegularTextSource) |     assertTrue (get(2) is RegularTextSource) | ||||||
|     assertTrue (get(3) is HashTagTextSource) |     assertTrue (get(3) is SpoilerTextSource) | ||||||
|     assertTrue (get(4) is RegularTextSource) |     assertTrue (get(4) is RegularTextSource) | ||||||
|     assertTrue (get(5) is MentionTextSource) |     assertTrue (get(5) is HashTagTextSource) | ||||||
|  |     assertTrue (get(6) is RegularTextSource) | ||||||
|  |     assertTrue (get(7) is MentionTextSource) | ||||||
|  |  | ||||||
|     val boldSource = get(1) as BoldTextSource |     val boldSource = get(1) as BoldTextSource | ||||||
|     assertTrue (boldSource.subsources.first() is ItalicTextSource) |     assertTrue (boldSource.subsources.first() is ItalicTextSource) | ||||||
|   | |||||||
| @@ -42,7 +42,9 @@ class StringFormattingTests { | |||||||
|             bold(italic("is") + |             bold(italic("is") + | ||||||
|                 " " + |                 " " + | ||||||
|                 strikethrough(underline("simple"))) + |                 strikethrough(underline("simple"))) + | ||||||
|                 " hello world with " + |                 " " + | ||||||
|  |                 spoiler("hello world") + | ||||||
|  |                 " with " + | ||||||
|                 hashtag("tag") + |                 hashtag("tag") + | ||||||
|                 " and " + |                 " and " + | ||||||
|                 mention("mention") |                 mention("mention") | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.types | |||||||
| import dev.inmo.tgbotapi.TestsJsonFormat | import dev.inmo.tgbotapi.TestsJsonFormat | ||||||
| import dev.inmo.tgbotapi.extensions.utils.formatting.* | import dev.inmo.tgbotapi.extensions.utils.formatting.* | ||||||
| import dev.inmo.tgbotapi.types.MessageEntity.textsources.TextSourceSerializer | import dev.inmo.tgbotapi.types.MessageEntity.textsources.TextSourceSerializer | ||||||
|  | import dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler | ||||||
| import dev.inmo.tgbotapi.utils.extensions.makeString | import dev.inmo.tgbotapi.utils.extensions.makeString | ||||||
| import kotlinx.serialization.builtins.ListSerializer | import kotlinx.serialization.builtins.ListSerializer | ||||||
| import kotlin.test.Test | import kotlin.test.Test | ||||||
| @@ -16,6 +17,11 @@ class TextSourcesTests { | |||||||
|                 italic("It") |                 italic("It") | ||||||
|                 link("is example", "https://is.example") |                 link("is example", "https://is.example") | ||||||
|             } |             } | ||||||
|  |             spoiler { | ||||||
|  |                 regular("and") | ||||||
|  |                 italic("that") | ||||||
|  |                 link("is spoiler", "https://is.example") | ||||||
|  |             } | ||||||
|             underline("of") |             underline("of") | ||||||
|             italic( |             italic( | ||||||
|                 buildEntities { |                 buildEntities { | ||||||
| @@ -32,6 +38,6 @@ class TextSourcesTests { | |||||||
|         ) |         ) | ||||||
|         assertEquals(testList, deserialized) |         assertEquals(testList, deserialized) | ||||||
|         assertEquals(testList.makeString(), deserialized.makeString()) |         assertEquals(testList.makeString(), deserialized.makeString()) | ||||||
|         assertEquals("It is example of complex text", testList.makeString()) |         assertEquals("It is example and that is spoiler of complex text", testList.makeString()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -99,6 +99,41 @@ inline fun EntitiesBuilder.bold(text: String) = add(dev.inmo.tgbotapi.types.Mess | |||||||
|  */ |  */ | ||||||
| inline fun EntitiesBuilder.boldln(text: String) = bold(text) + newLine | inline fun EntitiesBuilder.boldln(text: String) = bold(text) + newLine | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Add spoiler using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler] | ||||||
|  |  */ | ||||||
|  | inline fun EntitiesBuilder.spoiler(parts: TextSourcesList) = add(dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler(parts)) | ||||||
|  | /** | ||||||
|  |  * Version of [EntitiesBuilder.spoiler] with new line at the end | ||||||
|  |  */ | ||||||
|  | inline fun EntitiesBuilder.spoilerln(parts: TextSourcesList) = spoiler(parts) + newLine | ||||||
|  | /** | ||||||
|  |  * Add spoiler using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler]. | ||||||
|  |  * Will reuse separator config from [buildEntities] | ||||||
|  |  */ | ||||||
|  | inline fun EntitiesBuilder.spoiler(noinline init: EntitiesBuilderBody) = add(dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler(buildEntities(separator, init))) | ||||||
|  | /** | ||||||
|  |  * Version of [EntitiesBuilder.spoiler] with new line at the end. | ||||||
|  |  * Will reuse separator config from [buildEntities] | ||||||
|  |  */ | ||||||
|  | inline fun EntitiesBuilder.spoilerln(noinline init: EntitiesBuilderBody) = spoiler(init) + newLine | ||||||
|  | /** | ||||||
|  |  * Add spoiler using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler] | ||||||
|  |  */ | ||||||
|  | inline fun EntitiesBuilder.spoiler(vararg parts: TextSource) = add(dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler(*parts)) | ||||||
|  | /** | ||||||
|  |  * Version of [EntitiesBuilder.spoiler] with new line at the end | ||||||
|  |  */ | ||||||
|  | inline fun EntitiesBuilder.spoilerln(vararg parts: TextSource) = spoiler(*parts) + newLine | ||||||
|  | /** | ||||||
|  |  * Add spoiler using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler] | ||||||
|  |  */ | ||||||
|  | inline fun EntitiesBuilder.spoiler(text: String) = add(dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler(text)) | ||||||
|  | /** | ||||||
|  |  * Version of [EntitiesBuilder.spoiler] with new line at the end | ||||||
|  |  */ | ||||||
|  | inline fun EntitiesBuilder.spoilerln(text: String) = spoiler(text) + newLine | ||||||
|  |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Add botCommand using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.MessageEntity.textsources.botCommand] |  * Add botCommand using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.MessageEntity.textsources.botCommand] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user