From 5c5a19c91a1cd01c799dbf043bc23e2e3940057d Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 31 Dec 2021 13:48:51 +0600 Subject: [PATCH] add spoiler support --- CHANGELOG.md | 2 ++ .../types/MessageEntity/RawMessageEntity.kt | 2 ++ .../textsources/SpoilerTextSource.kt | 27 ++++++++++++++ .../textsources/TextSourceSerializer.kt | 1 + .../MultilevelTextSourceFormatting.kt | 4 +++ .../utils/internal/StringFormatting.kt | 6 +++- .../types/MessageEntity/EntitiesTestText.kt | 15 +++++--- .../MessageEntity/StringFormattingTests.kt | 4 ++- .../inmo/tgbotapi/types/TextSourcesTests.kt | 8 ++++- .../utils/formatting/EntitiesBuilder.kt | 35 +++++++++++++++++++ 10 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/textsources/SpoilerTextSource.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 176a8c50c7..bf8e867041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ * `Klock`: `2.4.8` -> `2.4.10` * `Ktor`: `1.6.5` -> `1.6.7` * `MicroUtils`: `0.8.7` -> `0.9.0` +* `Core`: + * Add `SpoilerTextSource` (as part of `Telegram Bot API 5.6` update) ## 0.37.4 diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/RawMessageEntity.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/RawMessageEntity.kt index 577e378c72..32289a6806 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/RawMessageEntity.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/RawMessageEntity.kt @@ -49,6 +49,7 @@ internal fun RawMessageEntity.asTextSource( ) "underline" -> UnderlineTextSource(sourceSubstring, subPartsWithRegulars) "strikethrough" -> StrikethroughTextSource(sourceSubstring, subPartsWithRegulars) + "spoiler" -> SpoilerTextSource(sourceSubstring, subPartsWithRegulars) else -> RegularTextSource(sourceSubstring) } } @@ -158,6 +159,7 @@ internal fun TextSource.toRawMessageEntities(offset: Int = 0): List RawMessageEntity("text_mention", offset, length, user = user) is UnderlineTextSource -> RawMessageEntity("underline", offset, length) is StrikethroughTextSource -> RawMessageEntity("strikethrough", offset, length) + is SpoilerTextSource -> RawMessageEntity("spoiler", offset, length) else -> null } ) + if (this is MultilevelTextSource) { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/textsources/SpoilerTextSource.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/textsources/SpoilerTextSource.kt new file mode 100644 index 0000000000..7b0a3e9c99 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/textsources/SpoilerTextSource.kt @@ -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)) + diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/textsources/TextSourceSerializer.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/textsources/TextSourceSerializer.kt index 4f081a9b5b..d6fd6ef438 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/textsources/TextSourceSerializer.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/MessageEntity/textsources/TextSourceSerializer.kt @@ -20,6 +20,7 @@ private val baseSerializers: Map> = mapOf( "text_mention" to TextMentionTextSource.serializer(), "hashtag" to HashTagTextSource.serializer(), "cashtag" to CashTagTextSource.serializer(), + "spoiler" to SpoilerTextSource.serializer(), ) object TextSourceSerializer : TypedSerializer(TextSource::class, baseSerializers) { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/internal/MultilevelTextSourceFormatting.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/internal/MultilevelTextSourceFormatting.kt index 275e00c00f..01b7d8744b 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/internal/MultilevelTextSourceFormatting.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/internal/MultilevelTextSourceFormatting.kt @@ -49,6 +49,10 @@ internal fun MultilevelTextSource.italicMarkdownV2(): String = markdownV2Default 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.strikethroughHTML(): String = htmlDefault(htmlStrikethroughControl) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/internal/StringFormatting.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/internal/StringFormatting.kt index dce43d2d63..109caa2ea7 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/internal/StringFormatting.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/internal/StringFormatting.kt @@ -7,6 +7,7 @@ import dev.inmo.tgbotapi.utils.extensions.* const val markdownBoldControl = "*" const val markdownItalicControl = "_" +const val markdownSpoilerControl = "||" const val markdownCodeControl = "`" const val markdownPreControl = "```" @@ -18,6 +19,8 @@ const val markdownV2ItalicEndControl = "$markdownItalicControl$markdownV2ItalicU const val htmlBoldControl = "b" const val htmlItalicControl = "i" +const val htmlSpoilerControl = "span class=\"tg-spoiler\"" +const val htmlSpoilerClosingControl = "span" const val htmlCodeControl = "code" const val htmlPreControl = "pre" 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.spoilerMarkdown(): String = markdownDefault(markdownSpoilerControl) + /** * 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.strikethroughMarkdownV2(): String = markdownV2Default(markdownV2StrikethroughControl) /** diff --git a/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/MessageEntity/EntitiesTestText.kt b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/MessageEntity/EntitiesTestText.kt index e0e1e32550..7048fa4b6c 100644 --- a/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/MessageEntity/EntitiesTestText.kt +++ b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/MessageEntity/EntitiesTestText.kt @@ -4,8 +4,8 @@ import dev.inmo.tgbotapi.types.MessageEntity.textsources.* import kotlin.test.assertTrue 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 formattedHtmlText = "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 is simple hello world with #tag and @mention" internal val testTextEntities = listOf( RawMessageEntity( "bold", @@ -27,6 +27,11 @@ internal val testTextEntities = listOf( 6, 6 ), + RawMessageEntity( + "spoiler", + 13, + 11 + ), RawMessageEntity( "hashtag", 30, @@ -43,9 +48,11 @@ fun TextSourcesList.testTextSources() { assertTrue (first() is RegularTextSource) assertTrue (get(1) is BoldTextSource) assertTrue (get(2) is RegularTextSource) - assertTrue (get(3) is HashTagTextSource) + assertTrue (get(3) is SpoilerTextSource) 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 assertTrue (boldSource.subsources.first() is ItalicTextSource) diff --git a/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/MessageEntity/StringFormattingTests.kt b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/MessageEntity/StringFormattingTests.kt index 747dcc8588..ba11fc9d3a 100644 --- a/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/MessageEntity/StringFormattingTests.kt +++ b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/MessageEntity/StringFormattingTests.kt @@ -42,7 +42,9 @@ class StringFormattingTests { bold(italic("is") + " " + strikethrough(underline("simple"))) + - " hello world with " + + " " + + spoiler("hello world") + + " with " + hashtag("tag") + " and " + mention("mention") diff --git a/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/TextSourcesTests.kt b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/TextSourcesTests.kt index 776497a3c6..b3336962ee 100644 --- a/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/TextSourcesTests.kt +++ b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/TextSourcesTests.kt @@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.types import dev.inmo.tgbotapi.TestsJsonFormat import dev.inmo.tgbotapi.extensions.utils.formatting.* import dev.inmo.tgbotapi.types.MessageEntity.textsources.TextSourceSerializer +import dev.inmo.tgbotapi.types.MessageEntity.textsources.spoiler import dev.inmo.tgbotapi.utils.extensions.makeString import kotlinx.serialization.builtins.ListSerializer import kotlin.test.Test @@ -16,6 +17,11 @@ class TextSourcesTests { italic("It") link("is example", "https://is.example") } + spoiler { + regular("and") + italic("that") + link("is spoiler", "https://is.example") + } underline("of") italic( buildEntities { @@ -32,6 +38,6 @@ class TextSourcesTests { ) assertEquals(testList, deserialized) 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()) } } diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/formatting/EntitiesBuilder.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/formatting/EntitiesBuilder.kt index 9ebc816630..eda972aaeb 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/formatting/EntitiesBuilder.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/formatting/EntitiesBuilder.kt @@ -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 +/** + * 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]