add spoiler support

This commit is contained in:
InsanusMokrassar 2021-12-31 13:48:51 +06:00
parent bd60c4f411
commit 5c5a19c91a
10 changed files with 97 additions and 7 deletions

View File

@ -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

View File

@ -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<RawMessageEn
is TextMentionTextSource -> 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) {

View File

@ -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))

View File

@ -20,6 +20,7 @@ private val baseSerializers: Map<String, KSerializer<out TextSource>> = mapOf(
"text_mention" to TextMentionTextSource.serializer(),
"hashtag" to HashTagTextSource.serializer(),
"cashtag" to CashTagTextSource.serializer(),
"spoiler" to SpoilerTextSource.serializer(),
)
object TextSourceSerializer : TypedSerializer<TextSource>(TextSource::class, baseSerializers) {

View File

@ -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)

View File

@ -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)
/**

View File

@ -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 <b><i>is</i> <s><u>simple</u></s></b> 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> <span class=\"tg-spoiler\">hello world</span> 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)

View File

@ -42,7 +42,9 @@ class StringFormattingTests {
bold(italic("is") +
" " +
strikethrough(underline("simple"))) +
" hello world with " +
" " +
spoiler("hello world") +
" with " +
hashtag("tag") +
" and " +
mention("mention")

View File

@ -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())
}
}

View File

@ -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]