mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-12-22 16:47:13 +00:00
add support of expandable blockquote
This commit is contained in:
parent
93e06a5765
commit
e403bbca0b
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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))
|
@ -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 {
|
||||||
|
@ -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]
|
||||||
*/
|
*/
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user