1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2026-06-20 02:06:11 +00:00

Add Bot API 10.1 Rich Messages support

Adds the full Rich Messages type system and methods:
- RichText hierarchy: RichTextPlain, RichTextGroup, RichTextEntity and all
  24 RichText* entity types, with a recursive serializer handling plain
  strings, arrays and typed objects
- RichBlock hierarchy: all 21 RichBlock* types plus RichBlockCaption,
  RichBlockTableCell and RichBlockListItem (JsonContentPolymorphic by type)
- RichMessage type and RichMessageContent message content; rich_message
  parsed in RawMessage; RichMessageContentMessage typealias
- InputRichMessage (internal constructor + InputRichMessageHTML /
  InputRichMessageMarkdown factories) and InputRichMessageContent usable as
  InputMessageContent
- SendRichMessage and SendRichMessageDraft requests with API bindings
- richMessage parameter on EditChatMessageText
- Serialization round-trip test for RichMessage/RichText/RichBlock

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 17:32:56 +06:00
parent f6d5b3ea71
commit 6bdd217530
25 changed files with 4130 additions and 2 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,7 @@ import dev.inmo.tgbotapi.types.message.RawMessageEntity
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.toRawMessageEntities
import dev.inmo.tgbotapi.types.rich.InputRichMessage
import dev.inmo.tgbotapi.utils.extensions.makeString
import kotlinx.serialization.*
@@ -73,7 +74,9 @@ data class EditChatMessageText internal constructor(
@SerialName(linkPreviewOptionsField)
override val linkPreviewOptions: LinkPreviewOptions? = null,
@SerialName(replyMarkupField)
override val replyMarkup: InlineKeyboardMarkup? = null
override val replyMarkup: InlineKeyboardMarkup? = null,
@SerialName(richMessageField)
val richMessage: InputRichMessage? = null
) : EditChatMessage<TextContent>, EditTextChatMessage, EditReplyMessage, EditLinkPreviewOptionsContainer {
override val textSources: TextSourcesList? by lazy {
rawEntities ?.asTextSources(text)

View File

@@ -0,0 +1,65 @@
package dev.inmo.tgbotapi.requests.send
import dev.inmo.tgbotapi.requests.send.abstracts.ReplyingMarkupSendMessageRequest
import dev.inmo.tgbotapi.requests.send.abstracts.SendContentMessageRequest
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
import dev.inmo.tgbotapi.types.message.*
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
import dev.inmo.tgbotapi.types.message.abstracts.TelegramBotAPIMessageDeserializationStrategyClass
import dev.inmo.tgbotapi.types.message.content.RichMessageContent
import dev.inmo.tgbotapi.types.rich.InputRichMessage
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationStrategy
internal val RichMessageContentMessageResultDeserializer: DeserializationStrategy<ChatContentMessage<RichMessageContent>>
= TelegramBotAPIMessageDeserializationStrategyClass()
/**
* Use this method to send rich messages.
*
* @see <a href="https://core.telegram.org/bots/api#sendrichmessage">sendRichMessage</a>
*/
@Serializable
data class SendRichMessage(
@SerialName(chatIdField)
override val chatId: ChatIdentifier,
@SerialName(richMessageField)
val richMessage: InputRichMessage,
@OptIn(ExperimentalSerializationApi::class)
@SerialName(messageThreadIdField)
@EncodeDefault
override val threadId: MessageThreadId? = chatId.threadId,
@OptIn(ExperimentalSerializationApi::class)
@EncodeDefault
@SerialName(directMessagesTopicIdField)
override val directMessageThreadId: DirectMessageThreadId? = chatId.directMessageThreadId,
@SerialName(businessConnectionIdField)
override val businessConnectionId: BusinessConnectionId? = chatId.businessConnectionId,
@SerialName(disableNotificationField)
override val disableNotification: Boolean = false,
@SerialName(protectContentField)
override val protectContent: Boolean = false,
@SerialName(allowPaidBroadcastField)
override val allowPaidBroadcast: Boolean = false,
@SerialName(messageEffectIdField)
override val effectId: EffectId? = null,
@SerialName(suggestedPostParametersField)
override val suggestedPostParameters: SuggestedPostParameters? = null,
@SerialName(replyParametersField)
override val replyParameters: ReplyParameters? = null,
@SerialName(replyMarkupField)
override val replyMarkup: KeyboardMarkup? = null
) : SendContentMessageRequest<ChatContentMessage<RichMessageContent>>,
ReplyingMarkupSendMessageRequest<ChatContentMessage<RichMessageContent>> {
override fun method(): String = "sendRichMessage"
override val resultDeserializer: DeserializationStrategy<ChatContentMessage<RichMessageContent>>
get() = RichMessageContentMessageResultDeserializer
override val requestSerializer: SerializationStrategy<*>
get() = serializer()
}

View File

@@ -0,0 +1,51 @@
package dev.inmo.tgbotapi.requests.send
import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.MessageThreadId
import dev.inmo.tgbotapi.types.chatIdField
import dev.inmo.tgbotapi.types.draftIdField
import dev.inmo.tgbotapi.types.messageThreadIdField
import dev.inmo.tgbotapi.types.richMessageField
import dev.inmo.tgbotapi.types.rich.InputRichMessage
import dev.inmo.tgbotapi.utils.serializers.UnitFromBooleanSerializer
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationStrategy
/**
* Use this method to stream a partial rich message to a user while the message is being generated. The streamed draft is
* ephemeral and acts as a temporary 30-second preview - once the output is finalized, [SendRichMessage] must be called
* with the complete message to persist it in the user's chat.
*
* @see <a href="https://core.telegram.org/bots/api#sendrichmessagedraft">sendRichMessageDraft</a>
*/
@Serializable
data class SendRichMessageDraft(
@SerialName(chatIdField)
val chatId: ChatId,
/**
* Unique identifier of the message draft; must be non-zero. Changes to drafts with the same identifier are animated.
*/
@SerialName(draftIdField)
val draftId: Long,
@SerialName(richMessageField)
val richMessage: InputRichMessage,
@SerialName(messageThreadIdField)
val threadId: MessageThreadId? = null
) : SimpleRequest<Unit> {
init {
require(draftId != 0L) {
"draftId of SendRichMessageDraft must be non-zero"
}
}
override val requestSerializer: SerializationStrategy<*>
get() = serializer()
override val resultDeserializer: DeserializationStrategy<Unit>
get() = UnitFromBooleanSerializer
override fun method(): String = "sendRichMessageDraft"
}

View File

@@ -573,6 +573,40 @@ const val supportsJoinRequestQueriesField = "supports_join_request_queries"
const val queryIdField = "query_id"
const val chatJoinRequestQueryIdField = "chat_join_request_query_id"
const val webAppUrlField = "web_app_url"
const val richMessageField = "rich_message"
const val isRtlField = "is_rtl"
const val skipEntityDetectionField = "skip_entity_detection"
const val markdownField = "markdown"
const val htmlField = "html"
const val unixTimeField = "unix_time"
const val dateTimeFormatField = "date_time_format"
const val alternativeTextField = "alternative_text"
const val expressionField = "expression"
const val emailAddressField = "email_address"
const val hashtagField = "hashtag"
const val cashtagField = "cashtag"
const val bankCardNumberField = "bank_card_number"
const val anchorNameField = "anchor_name"
const val referenceNameField = "reference_name"
const val blocksField = "blocks"
const val itemsField = "items"
const val summaryField = "summary"
const val sizeField = "size"
const val languageField = "language"
const val creditField = "credit"
const val cellsField = "cells"
const val isHeaderField = "is_header"
const val colspanField = "colspan"
const val rowspanField = "rowspan"
const val alignField = "align"
const val valignField = "valign"
const val zoomField = "zoom"
const val voiceNoteField = "voice_note"
const val hasCheckboxField = "has_checkbox"
const val isCheckedField = "is_checked"
const val isOpenField = "is_open"
const val isBorderedField = "is_bordered"
const val isStripedField = "is_striped"
const val certificateField = "certificate"
const val questionField = "question"
const val questionEntitiesField = "question_entities"

View File

@@ -0,0 +1,17 @@
package dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent
import dev.inmo.tgbotapi.types.richMessageField
import dev.inmo.tgbotapi.types.rich.InputRichMessage
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Represents the content of a rich message to be sent as the result of an inline query.
*
* @see <a href="https://core.telegram.org/bots/api#inputrichmessagecontent">InputRichMessageContent</a>
*/
@Serializable
data class InputRichMessageContent(
@SerialName(richMessageField)
val richMessage: InputRichMessage
) : InputMessageContent

View File

@@ -22,6 +22,7 @@ object InputMessageContentSerializer : KSerializer<InputMessageContent> {
is InputTextMessageContent -> InputTextMessageContent.serializer().serialize(encoder, value)
is InputVenueMessageContent -> InputVenueMessageContent.serializer().serialize(encoder, value)
is InputInvoiceMessageContent -> InputInvoiceMessageContent.serializer().serialize(encoder, value)
is InputRichMessageContent -> InputRichMessageContent.serializer().serialize(encoder, value)
}
}

View File

@@ -50,6 +50,7 @@ import dev.inmo.tgbotapi.types.polls.Poll
import dev.inmo.tgbotapi.types.polls.PollOptionPersistentId
import dev.inmo.tgbotapi.types.request.ChatShared
import dev.inmo.tgbotapi.types.request.UsersShared
import dev.inmo.tgbotapi.types.rich.RichMessage
import dev.inmo.tgbotapi.types.stories.Story
import dev.inmo.tgbotapi.types.venue.Venue
import dev.inmo.tgbotapi.utils.isFakeTelegramUser
@@ -92,6 +93,7 @@ internal data class RawMessage(
private val caption_entities: RawMessageEntities? = null,
private val has_media_spoiler: Boolean? = null,
private val story: Story? = null,
private val rich_message: RichMessage? = null,
private val audio: AudioFile? = null,
private val document: DocumentFile? = null,
private val paid_media: PaidMediaInfo? = null,
@@ -230,6 +232,11 @@ internal data class RawMessage(
} ?: emptyList()
when {
rich_message != null -> RichMessageContent(
chat,
messageId,
rich_message
)
story != null -> StoryContent(
chat,
messageId,

View File

@@ -55,6 +55,7 @@ sealed interface MessageContent: ResendableContent {
subclass(StoryContent::class)
subclass(GiveawayPublicResultsContent::class)
subclass(GiveawayContent::class)
subclass(RichMessageContent::class)
additionalBuilder()
}

View File

@@ -0,0 +1,44 @@
package dev.inmo.tgbotapi.types.message.content
import dev.inmo.tgbotapi.requests.ForwardMessage
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup
import dev.inmo.tgbotapi.types.chat.Chat
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
import dev.inmo.tgbotapi.types.rich.RichMessage
import kotlinx.serialization.Serializable
@Serializable
data class RichMessageContent(
private val chat: Chat,
private val messageId: MessageId,
val richMessage: RichMessage
) : MessageContent {
override fun createResend(
chatId: ChatIdentifier,
messageThreadId: MessageThreadId?,
directMessageThreadId: DirectMessageThreadId?,
businessConnectionId: BusinessConnectionId?,
disableNotification: Boolean,
protectContent: Boolean,
allowPaidBroadcast: Boolean,
effectId: EffectId?,
suggestedPostParameters: SuggestedPostParameters?,
replyParameters: ReplyParameters?,
replyMarkup: KeyboardMarkup?
): Request<ChatContentMessage<RichMessageContent>> {
@Suppress("UNCHECKED_CAST")
return ForwardMessage(
chat.id,
toChatId = chatId,
messageId = messageId,
threadId = messageThreadId,
directMessageThreadId = directMessageThreadId,
disableNotification = disableNotification,
protectContent = protectContent
) as Request<ChatContentMessage<RichMessageContent>>
}
}

View File

@@ -13,6 +13,8 @@ typealias PollMessage = ChatContentMessage<PollContent>
typealias TextMessage = ChatContentMessage<TextContent>
typealias StoryMessage = ChatContentMessage<StoryContent>
typealias RichMessageContentMessage = ChatContentMessage<RichMessageContent>
typealias LocationMessage = ChatContentMessage<LocationContent>
typealias LiveLocationMessage = ChatContentMessage<LiveLocationContent>
typealias StaticLocationMessage = ChatContentMessage<StaticLocationContent>

View File

@@ -0,0 +1,65 @@
package dev.inmo.tgbotapi.types.rich
import dev.inmo.tgbotapi.types.htmlField
import dev.inmo.tgbotapi.types.isRtlField
import dev.inmo.tgbotapi.types.markdownField
import dev.inmo.tgbotapi.types.skipEntityDetectionField
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Describes a rich message to be sent. Exactly one of the fields [html] or [markdown] must be used. Use the
* [InputRichMessageHTML] and [InputRichMessageMarkdown] factories to build an instance.
*
* @see <a href="https://core.telegram.org/bots/api#inputrichmessage">InputRichMessage</a>
*/
@ConsistentCopyVisibility
@Serializable
data class InputRichMessage internal constructor(
@SerialName(htmlField)
val html: String? = null,
@SerialName(markdownField)
val markdown: String? = null,
@SerialName(isRtlField)
val isRtl: Boolean? = null,
@SerialName(skipEntityDetectionField)
val skipEntityDetection: Boolean? = null
) {
init {
require((html == null) != (markdown == null)) {
"Exactly one of the fields html or markdown must be used in InputRichMessage"
}
}
}
/**
* Creates an [InputRichMessage] with the content described using HTML formatting.
*
* @see <a href="https://core.telegram.org/bots/api#inputrichmessage">InputRichMessage</a>
*/
fun InputRichMessageHTML(
html: String,
isRtl: Boolean? = null,
skipEntityDetection: Boolean? = null
): InputRichMessage = InputRichMessage(
html = html,
markdown = null,
isRtl = isRtl,
skipEntityDetection = skipEntityDetection
)
/**
* Creates an [InputRichMessage] with the content described using Markdown formatting.
*
* @see <a href="https://core.telegram.org/bots/api#inputrichmessage">InputRichMessage</a>
*/
fun InputRichMessageMarkdown(
markdown: String,
isRtl: Boolean? = null,
skipEntityDetection: Boolean? = null
): InputRichMessage = InputRichMessage(
html = null,
markdown = markdown,
isRtl = isRtl,
skipEntityDetection = skipEntityDetection
)

View File

@@ -0,0 +1,50 @@
package dev.inmo.tgbotapi.types.rich
import dev.inmo.tgbotapi.types.typeField
import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
/**
* Represents a block in a rich formatted message.
*
* @see <a href="https://core.telegram.org/bots/api#richblock">RichBlock</a>
*/
@Serializable(RichBlockSerializer::class)
@ClassCastsIncluded
sealed interface RichBlock {
val type: String
}
object RichBlockSerializer : JsonContentPolymorphicSerializer<RichBlock>(RichBlock::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RichBlock> {
return when (val type = element.jsonObject[typeField]?.jsonPrimitive?.content) {
RichBlockParagraph.TYPE -> RichBlockParagraph.serializer()
RichBlockSectionHeading.TYPE -> RichBlockSectionHeading.serializer()
RichBlockPreformatted.TYPE -> RichBlockPreformatted.serializer()
RichBlockFooter.TYPE -> RichBlockFooter.serializer()
RichBlockDivider.TYPE -> RichBlockDivider.serializer()
RichBlockMathematicalExpression.TYPE -> RichBlockMathematicalExpression.serializer()
RichBlockAnchor.TYPE -> RichBlockAnchor.serializer()
RichBlockList.TYPE -> RichBlockList.serializer()
RichBlockBlockQuotation.TYPE -> RichBlockBlockQuotation.serializer()
RichBlockPullQuotation.TYPE -> RichBlockPullQuotation.serializer()
RichBlockCollage.TYPE -> RichBlockCollage.serializer()
RichBlockSlideshow.TYPE -> RichBlockSlideshow.serializer()
RichBlockTable.TYPE -> RichBlockTable.serializer()
RichBlockDetails.TYPE -> RichBlockDetails.serializer()
RichBlockMap.TYPE -> RichBlockMap.serializer()
RichBlockAnimation.TYPE -> RichBlockAnimation.serializer()
RichBlockAudio.TYPE -> RichBlockAudio.serializer()
RichBlockPhoto.TYPE -> RichBlockPhoto.serializer()
RichBlockVideo.TYPE -> RichBlockVideo.serializer()
RichBlockVoiceNote.TYPE -> RichBlockVoiceNote.serializer()
RichBlockThinking.TYPE -> RichBlockThinking.serializer()
else -> error("Unknown RichBlock type: $type")
}
}
}

View File

@@ -0,0 +1,75 @@
package dev.inmo.tgbotapi.types.rich
import dev.inmo.tgbotapi.types.alignField
import dev.inmo.tgbotapi.types.blocksField
import dev.inmo.tgbotapi.types.colspanField
import dev.inmo.tgbotapi.types.creditField
import dev.inmo.tgbotapi.types.hasCheckboxField
import dev.inmo.tgbotapi.types.isCheckedField
import dev.inmo.tgbotapi.types.isHeaderField
import dev.inmo.tgbotapi.types.labelField
import dev.inmo.tgbotapi.types.rowspanField
import dev.inmo.tgbotapi.types.textField
import dev.inmo.tgbotapi.types.typeField
import dev.inmo.tgbotapi.types.valignField
import dev.inmo.tgbotapi.types.valueField
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Caption of a rich formatted block.
*
* @see <a href="https://core.telegram.org/bots/api#richblockcaption">RichBlockCaption</a>
*/
@Serializable
data class RichBlockCaption(
@SerialName(textField)
val text: RichText,
@SerialName(creditField)
val credit: RichText? = null
)
/**
* A cell in a [RichBlockTable].
*
* @see <a href="https://core.telegram.org/bots/api#richblocktablecell">RichBlockTableCell</a>
*/
@Serializable
data class RichBlockTableCell(
@SerialName(textField)
val text: RichText? = null,
@SerialName(isHeaderField)
val isHeader: Boolean? = null,
@SerialName(colspanField)
val colspan: Int? = null,
@SerialName(rowspanField)
val rowspan: Int? = null,
@SerialName(alignField)
val align: String,
@SerialName(valignField)
val valign: String
)
/**
* An item of a [RichBlockList].
*
* @see <a href="https://core.telegram.org/bots/api#richblocklistitem">RichBlockListItem</a>
*/
@Serializable
data class RichBlockListItem(
@SerialName(labelField)
val label: String,
@SerialName(blocksField)
val blocks: List<RichBlock>,
@SerialName(hasCheckboxField)
val hasCheckbox: Boolean? = null,
@SerialName(isCheckedField)
val isChecked: Boolean? = null,
@SerialName(valueField)
val value: Int? = null,
/**
* For ordered lists, the type of the item label; must be one of "a", "A", "i", "I" or "1".
*/
@SerialName(typeField)
val labelType: String? = null
)

View File

@@ -0,0 +1,490 @@
package dev.inmo.tgbotapi.types.rich
import dev.inmo.tgbotapi.types.animationField
import dev.inmo.tgbotapi.types.audioField
import dev.inmo.tgbotapi.types.blocksField
import dev.inmo.tgbotapi.types.captionField
import dev.inmo.tgbotapi.types.cellsField
import dev.inmo.tgbotapi.types.creditField
import dev.inmo.tgbotapi.types.expressionField
import dev.inmo.tgbotapi.types.files.AnimationFile
import dev.inmo.tgbotapi.types.files.AudioFile
import dev.inmo.tgbotapi.types.files.PhotoFile
import dev.inmo.tgbotapi.types.files.VideoFile
import dev.inmo.tgbotapi.types.files.VoiceFile
import dev.inmo.tgbotapi.types.hasSpoilerField
import dev.inmo.tgbotapi.types.heightField
import dev.inmo.tgbotapi.types.isBorderedField
import dev.inmo.tgbotapi.types.isOpenField
import dev.inmo.tgbotapi.types.isStripedField
import dev.inmo.tgbotapi.types.itemsField
import dev.inmo.tgbotapi.types.languageField
import dev.inmo.tgbotapi.types.location.StaticLocation
import dev.inmo.tgbotapi.types.locationField
import dev.inmo.tgbotapi.types.nameField
import dev.inmo.tgbotapi.types.photoField
import dev.inmo.tgbotapi.types.sizeField
import dev.inmo.tgbotapi.types.summaryField
import dev.inmo.tgbotapi.types.textField
import dev.inmo.tgbotapi.types.typeField
import dev.inmo.tgbotapi.types.videoField
import dev.inmo.tgbotapi.types.voiceNoteField
import dev.inmo.tgbotapi.types.widthField
import dev.inmo.tgbotapi.types.zoomField
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* A text paragraph.
*
* @see <a href="https://core.telegram.org/bots/api#richblockparagraph">RichBlockParagraph</a>
*/
@Serializable
data class RichBlockParagraph(
@SerialName(textField)
val text: RichText
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "paragraph"
}
}
/**
* A section heading.
*
* @see <a href="https://core.telegram.org/bots/api#richblocksectionheading">RichBlockSectionHeading</a>
*/
@Serializable
data class RichBlockSectionHeading(
@SerialName(textField)
val text: RichText,
/**
* Relative size of the text font; 1-6, 1 is the largest, 6 is the smallest.
*/
@SerialName(sizeField)
val size: Int
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "heading"
}
}
/**
* A preformatted text block.
*
* @see <a href="https://core.telegram.org/bots/api#richblockpreformatted">RichBlockPreformatted</a>
*/
@Serializable
data class RichBlockPreformatted(
@SerialName(textField)
val text: RichText,
@SerialName(languageField)
val language: String? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "pre"
}
}
/**
* A footer.
*
* @see <a href="https://core.telegram.org/bots/api#richblockfooter">RichBlockFooter</a>
*/
@Serializable
data class RichBlockFooter(
@SerialName(textField)
val text: RichText
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "footer"
}
}
/**
* A divider.
*
* @see <a href="https://core.telegram.org/bots/api#richblockdivider">RichBlockDivider</a>
*/
@Serializable
class RichBlockDivider : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
override fun equals(other: Any?): Boolean = other is RichBlockDivider
override fun hashCode(): Int = TYPE.hashCode()
override fun toString(): String = "RichBlockDivider"
companion object {
const val TYPE = "divider"
}
}
/**
* A block with a mathematical expression in LaTeX format.
*
* @see <a href="https://core.telegram.org/bots/api#richblockmathematicalexpression">RichBlockMathematicalExpression</a>
*/
@Serializable
data class RichBlockMathematicalExpression(
@SerialName(expressionField)
val expression: String
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "mathematical_expression"
}
}
/**
* A block with an anchor.
*
* @see <a href="https://core.telegram.org/bots/api#richblockanchor">RichBlockAnchor</a>
*/
@Serializable
data class RichBlockAnchor(
@SerialName(nameField)
val name: String
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "anchor"
}
}
/**
* A list of blocks.
*
* @see <a href="https://core.telegram.org/bots/api#richblocklist">RichBlockList</a>
*/
@Serializable
data class RichBlockList(
@SerialName(itemsField)
val items: List<RichBlockListItem>
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "list"
}
}
/**
* A block quotation.
*
* @see <a href="https://core.telegram.org/bots/api#richblockblockquotation">RichBlockBlockQuotation</a>
*/
@Serializable
data class RichBlockBlockQuotation(
@SerialName(blocksField)
val blocks: List<RichBlock>,
@SerialName(creditField)
val credit: RichText? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "blockquote"
}
}
/**
* A quotation with centered text.
*
* @see <a href="https://core.telegram.org/bots/api#richblockpullquotation">RichBlockPullQuotation</a>
*/
@Serializable
data class RichBlockPullQuotation(
@SerialName(textField)
val text: RichText,
@SerialName(creditField)
val credit: RichText? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "pullquote"
}
}
/**
* A collage.
*
* @see <a href="https://core.telegram.org/bots/api#richblockcollage">RichBlockCollage</a>
*/
@Serializable
data class RichBlockCollage(
@SerialName(blocksField)
val blocks: List<RichBlock>,
@SerialName(captionField)
val caption: RichBlockCaption? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "collage"
}
}
/**
* A slideshow.
*
* @see <a href="https://core.telegram.org/bots/api#richblockslideshow">RichBlockSlideshow</a>
*/
@Serializable
data class RichBlockSlideshow(
@SerialName(blocksField)
val blocks: List<RichBlock>,
@SerialName(captionField)
val caption: RichBlockCaption? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "slideshow"
}
}
/**
* A table.
*
* @see <a href="https://core.telegram.org/bots/api#richblocktable">RichBlockTable</a>
*/
@Serializable
data class RichBlockTable(
@SerialName(cellsField)
val cells: List<List<RichBlockTableCell>>,
@SerialName(isBorderedField)
val isBordered: Boolean? = null,
@SerialName(isStripedField)
val isStriped: Boolean? = null,
@SerialName(captionField)
val caption: RichText? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "table"
}
}
/**
* An expandable block for details disclosure.
*
* @see <a href="https://core.telegram.org/bots/api#richblockdetails">RichBlockDetails</a>
*/
@Serializable
data class RichBlockDetails(
@SerialName(summaryField)
val summary: RichText,
@SerialName(blocksField)
val blocks: List<RichBlock>,
@SerialName(isOpenField)
val isOpen: Boolean? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "details"
}
}
/**
* A block with a map.
*
* @see <a href="https://core.telegram.org/bots/api#richblockmap">RichBlockMap</a>
*/
@Serializable
data class RichBlockMap(
@SerialName(locationField)
val location: StaticLocation,
/**
* Map zoom level; 13-20.
*/
@SerialName(zoomField)
val zoom: Int,
@SerialName(widthField)
val width: Int,
@SerialName(heightField)
val height: Int,
@SerialName(captionField)
val caption: RichBlockCaption? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "map"
}
}
/**
* A block with an animation.
*
* @see <a href="https://core.telegram.org/bots/api#richblockanimation">RichBlockAnimation</a>
*/
@Serializable
data class RichBlockAnimation(
@SerialName(animationField)
val animation: AnimationFile,
@SerialName(hasSpoilerField)
val hasSpoiler: Boolean? = null,
@SerialName(captionField)
val caption: RichBlockCaption? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "animation"
}
}
/**
* A block with a music file.
*
* @see <a href="https://core.telegram.org/bots/api#richblockaudio">RichBlockAudio</a>
*/
@Serializable
data class RichBlockAudio(
@SerialName(audioField)
val audio: AudioFile,
@SerialName(captionField)
val caption: RichBlockCaption? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "audio"
}
}
/**
* A block with a photo.
*
* @see <a href="https://core.telegram.org/bots/api#richblockphoto">RichBlockPhoto</a>
*/
@Serializable
data class RichBlockPhoto(
@SerialName(photoField)
val photo: PhotoFile,
@SerialName(hasSpoilerField)
val hasSpoiler: Boolean? = null,
@SerialName(captionField)
val caption: RichBlockCaption? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "photo"
}
}
/**
* A block with a video.
*
* @see <a href="https://core.telegram.org/bots/api#richblockvideo">RichBlockVideo</a>
*/
@Serializable
data class RichBlockVideo(
@SerialName(videoField)
val video: VideoFile,
@SerialName(hasSpoilerField)
val hasSpoiler: Boolean? = null,
@SerialName(captionField)
val caption: RichBlockCaption? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "video"
}
}
/**
* A block with a voice note.
*
* @see <a href="https://core.telegram.org/bots/api#richblockvoicenote">RichBlockVoiceNote</a>
*/
@Serializable
data class RichBlockVoiceNote(
@SerialName(voiceNoteField)
val voiceNote: VoiceFile,
@SerialName(captionField)
val caption: RichBlockCaption? = null
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "voice_note"
}
}
/**
* A block with a "Thinking…" placeholder. May be used only in sendRichMessageDraft.
*
* @see <a href="https://core.telegram.org/bots/api#richblockthinking">RichBlockThinking</a>
*/
@Serializable
data class RichBlockThinking(
@SerialName(textField)
val text: RichText
) : RichBlock {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "thinking"
}
}

View File

@@ -0,0 +1,19 @@
package dev.inmo.tgbotapi.types.rich
import dev.inmo.tgbotapi.types.blocksField
import dev.inmo.tgbotapi.types.isRtlField
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Rich formatted message.
*
* @see <a href="https://core.telegram.org/bots/api#richmessage">RichMessage</a>
*/
@Serializable
data class RichMessage(
@SerialName(blocksField)
val blocks: List<RichBlock>,
@SerialName(isRtlField)
val isRtl: Boolean? = null
)

View File

@@ -0,0 +1,104 @@
package dev.inmo.tgbotapi.types.rich
import dev.inmo.tgbotapi.types.typeField
import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
/**
* Represents a rich formatted text. It can be either a plain text ([RichTextPlain]), a group of rich texts
* ([RichTextGroup]) or any of [RichTextEntity] subtypes.
*
* @see <a href="https://core.telegram.org/bots/api#richtext">RichText</a>
*/
@Serializable(RichTextSerializer::class)
@ClassCastsIncluded
sealed interface RichText
/**
* A plain (non-formatted) part of a [RichText]. Serialized as a bare JSON string.
*/
@Serializable
data class RichTextPlain(
val text: String
) : RichText
/**
* A group of [RichText]s. Serialized as a JSON array.
*/
@Serializable
data class RichTextGroup(
val parts: List<RichText>
) : RichText
/**
* Any typed (formatted) part of a [RichText]. Serialized as a JSON object with the [type] discriminator.
*/
@Serializable(RichTextEntitySerializer::class)
sealed interface RichTextEntity : RichText {
val type: String
}
object RichTextSerializer : KSerializer<RichText> {
override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor
private fun fromJson(json: Json, element: JsonElement): RichText = when (element) {
is JsonArray -> RichTextGroup(element.map { fromJson(json, it) })
is JsonObject -> json.decodeFromJsonElement(RichTextEntitySerializer, element)
is JsonPrimitive -> RichTextPlain(element.content)
}
private fun toJson(json: Json, value: RichText): JsonElement = when (value) {
is RichTextPlain -> JsonPrimitive(value.text)
is RichTextGroup -> JsonArray(value.parts.map { toJson(json, it) })
is RichTextEntity -> json.encodeToJsonElement(RichTextEntitySerializer, value)
}
override fun deserialize(decoder: Decoder): RichText {
val input = decoder as JsonDecoder
return fromJson(input.json, input.decodeJsonElement())
}
override fun serialize(encoder: Encoder, value: RichText) {
val output = encoder as JsonEncoder
output.encodeJsonElement(toJson(output.json, value))
}
}
object RichTextEntitySerializer : JsonContentPolymorphicSerializer<RichTextEntity>(RichTextEntity::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<RichTextEntity> {
return when (val type = element.jsonObject[typeField]?.jsonPrimitive?.content) {
RichTextBold.TYPE -> RichTextBold.serializer()
RichTextItalic.TYPE -> RichTextItalic.serializer()
RichTextUnderline.TYPE -> RichTextUnderline.serializer()
RichTextStrikethrough.TYPE -> RichTextStrikethrough.serializer()
RichTextSpoiler.TYPE -> RichTextSpoiler.serializer()
RichTextDateTime.TYPE -> RichTextDateTime.serializer()
RichTextTextMention.TYPE -> RichTextTextMention.serializer()
RichTextSubscript.TYPE -> RichTextSubscript.serializer()
RichTextSuperscript.TYPE -> RichTextSuperscript.serializer()
RichTextMarked.TYPE -> RichTextMarked.serializer()
RichTextCode.TYPE -> RichTextCode.serializer()
RichTextCustomEmoji.TYPE -> RichTextCustomEmoji.serializer()
RichTextMathematicalExpression.TYPE -> RichTextMathematicalExpression.serializer()
RichTextUrl.TYPE -> RichTextUrl.serializer()
RichTextEmailAddress.TYPE -> RichTextEmailAddress.serializer()
RichTextPhoneNumber.TYPE -> RichTextPhoneNumber.serializer()
RichTextBankCardNumber.TYPE -> RichTextBankCardNumber.serializer()
RichTextMention.TYPE -> RichTextMention.serializer()
RichTextHashtag.TYPE -> RichTextHashtag.serializer()
RichTextCashtag.TYPE -> RichTextCashtag.serializer()
RichTextBotCommand.TYPE -> RichTextBotCommand.serializer()
RichTextAnchor.TYPE -> RichTextAnchor.serializer()
RichTextAnchorLink.TYPE -> RichTextAnchorLink.serializer()
RichTextReference.TYPE -> RichTextReference.serializer()
RichTextReferenceLink.TYPE -> RichTextReferenceLink.serializer()
else -> error("Unknown RichTextEntity type: $type")
}
}
}

View File

@@ -0,0 +1,531 @@
package dev.inmo.tgbotapi.types.rich
import dev.inmo.tgbotapi.types.CustomEmojiId
import dev.inmo.tgbotapi.types.alternativeTextField
import dev.inmo.tgbotapi.types.anchorNameField
import dev.inmo.tgbotapi.types.bankCardNumberField
import dev.inmo.tgbotapi.types.botCommandField
import dev.inmo.tgbotapi.types.cashtagField
import dev.inmo.tgbotapi.types.chat.User
import dev.inmo.tgbotapi.types.customEmojiIdField
import dev.inmo.tgbotapi.types.dateTimeFormatField
import dev.inmo.tgbotapi.types.emailAddressField
import dev.inmo.tgbotapi.types.expressionField
import dev.inmo.tgbotapi.types.hashtagField
import dev.inmo.tgbotapi.types.nameField
import dev.inmo.tgbotapi.types.phoneNumberField
import dev.inmo.tgbotapi.types.referenceNameField
import dev.inmo.tgbotapi.types.textField
import dev.inmo.tgbotapi.types.typeField
import dev.inmo.tgbotapi.types.unixTimeField
import dev.inmo.tgbotapi.types.urlField
import dev.inmo.tgbotapi.types.userField
import dev.inmo.tgbotapi.types.usernameField
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* A bold [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextbold">RichTextBold</a>
*/
@Serializable
data class RichTextBold(
@SerialName(textField)
val text: RichText
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "bold"
}
}
/**
* An italicized [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextitalic">RichTextItalic</a>
*/
@Serializable
data class RichTextItalic(
@SerialName(textField)
val text: RichText
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "italic"
}
}
/**
* An underlined [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextunderline">RichTextUnderline</a>
*/
@Serializable
data class RichTextUnderline(
@SerialName(textField)
val text: RichText
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "underline"
}
}
/**
* A strikethrough [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextstrikethrough">RichTextStrikethrough</a>
*/
@Serializable
data class RichTextStrikethrough(
@SerialName(textField)
val text: RichText
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "strikethrough"
}
}
/**
* A [RichTextEntity] covered by a spoiler.
*
* @see <a href="https://core.telegram.org/bots/api#richtextspoiler">RichTextSpoiler</a>
*/
@Serializable
data class RichTextSpoiler(
@SerialName(textField)
val text: RichText
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "spoiler"
}
}
/**
* A subscript [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextsubscript">RichTextSubscript</a>
*/
@Serializable
data class RichTextSubscript(
@SerialName(textField)
val text: RichText
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "subscript"
}
}
/**
* A superscript [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextsuperscript">RichTextSuperscript</a>
*/
@Serializable
data class RichTextSuperscript(
@SerialName(textField)
val text: RichText
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "superscript"
}
}
/**
* A marked [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextmarked">RichTextMarked</a>
*/
@Serializable
data class RichTextMarked(
@SerialName(textField)
val text: RichText
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "marked"
}
}
/**
* A monowidth [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextcode">RichTextCode</a>
*/
@Serializable
data class RichTextCode(
@SerialName(textField)
val text: RichText
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "code"
}
}
/**
* A formatted date and time [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextdatetime">RichTextDateTime</a>
*/
@Serializable
data class RichTextDateTime(
@SerialName(textField)
val text: RichText,
@SerialName(unixTimeField)
val unixTime: Long,
@SerialName(dateTimeFormatField)
val dateTimeFormat: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "date_time"
}
}
/**
* A mention of a Telegram user by their identifier.
*
* @see <a href="https://core.telegram.org/bots/api#richtexttextmention">RichTextTextMention</a>
*/
@Serializable
data class RichTextTextMention(
@SerialName(textField)
val text: RichText,
@SerialName(userField)
val user: User
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "text_mention"
}
}
/**
* A custom emoji [RichTextEntity].
*
* @see <a href="https://core.telegram.org/bots/api#richtextcustomemoji">RichTextCustomEmoji</a>
*/
@Serializable
data class RichTextCustomEmoji(
@SerialName(customEmojiIdField)
val customEmojiId: CustomEmojiId,
@SerialName(alternativeTextField)
val alternativeText: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "custom_emoji"
}
}
/**
* A mathematical expression in LaTeX format.
*
* @see <a href="https://core.telegram.org/bots/api#richtextmathematicalexpression">RichTextMathematicalExpression</a>
*/
@Serializable
data class RichTextMathematicalExpression(
@SerialName(expressionField)
val expression: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "mathematical_expression"
}
}
/**
* A [RichTextEntity] with a link.
*
* @see <a href="https://core.telegram.org/bots/api#richtexturl">RichTextUrl</a>
*/
@Serializable
data class RichTextUrl(
@SerialName(textField)
val text: RichText,
@SerialName(urlField)
val url: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "url"
}
}
/**
* A [RichTextEntity] with an email address.
*
* @see <a href="https://core.telegram.org/bots/api#richtextemailaddress">RichTextEmailAddress</a>
*/
@Serializable
data class RichTextEmailAddress(
@SerialName(textField)
val text: RichText,
@SerialName(emailAddressField)
val emailAddress: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "email_address"
}
}
/**
* A [RichTextEntity] with a phone number.
*
* @see <a href="https://core.telegram.org/bots/api#richtextphonenumber">RichTextPhoneNumber</a>
*/
@Serializable
data class RichTextPhoneNumber(
@SerialName(textField)
val text: RichText,
@SerialName(phoneNumberField)
val phoneNumber: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "phone_number"
}
}
/**
* A [RichTextEntity] with a bank card number.
*
* @see <a href="https://core.telegram.org/bots/api#richtextbankcardnumber">RichTextBankCardNumber</a>
*/
@Serializable
data class RichTextBankCardNumber(
@SerialName(textField)
val text: RichText,
@SerialName(bankCardNumberField)
val bankCardNumber: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "bank_card_number"
}
}
/**
* A mention by a username.
*
* @see <a href="https://core.telegram.org/bots/api#richtextmention">RichTextMention</a>
*/
@Serializable
data class RichTextMention(
@SerialName(textField)
val text: RichText,
@SerialName(usernameField)
val username: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "mention"
}
}
/**
* A hashtag.
*
* @see <a href="https://core.telegram.org/bots/api#richtexthashtag">RichTextHashtag</a>
*/
@Serializable
data class RichTextHashtag(
@SerialName(textField)
val text: RichText,
@SerialName(hashtagField)
val hashtag: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "hashtag"
}
}
/**
* A cashtag.
*
* @see <a href="https://core.telegram.org/bots/api#richtextcashtag">RichTextCashtag</a>
*/
@Serializable
data class RichTextCashtag(
@SerialName(textField)
val text: RichText,
@SerialName(cashtagField)
val cashtag: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "cashtag"
}
}
/**
* A bot command.
*
* @see <a href="https://core.telegram.org/bots/api#richtextbotcommand">RichTextBotCommand</a>
*/
@Serializable
data class RichTextBotCommand(
@SerialName(textField)
val text: RichText,
@SerialName(botCommandField)
val botCommand: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "bot_command"
}
}
/**
* An anchor.
*
* @see <a href="https://core.telegram.org/bots/api#richtextanchor">RichTextAnchor</a>
*/
@Serializable
data class RichTextAnchor(
@SerialName(nameField)
val name: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "anchor"
}
}
/**
* A link to an anchor.
*
* @see <a href="https://core.telegram.org/bots/api#richtextanchorlink">RichTextAnchorLink</a>
*/
@Serializable
data class RichTextAnchorLink(
@SerialName(textField)
val text: RichText,
@SerialName(anchorNameField)
val anchorName: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "anchor_link"
}
}
/**
* A reference.
*
* @see <a href="https://core.telegram.org/bots/api#richtextreference">RichTextReference</a>
*/
@Serializable
data class RichTextReference(
@SerialName(textField)
val text: RichText,
@SerialName(nameField)
val name: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "reference"
}
}
/**
* A link to a reference.
*
* @see <a href="https://core.telegram.org/bots/api#richtextreferencelink">RichTextReferenceLink</a>
*/
@Serializable
data class RichTextReferenceLink(
@SerialName(textField)
val text: RichText,
@SerialName(referenceNameField)
val referenceName: String
) : RichTextEntity {
@EncodeDefault
@SerialName(typeField)
override val type: String = TYPE
companion object {
const val TYPE = "reference_link"
}
}

View File

@@ -0,0 +1,72 @@
package dev.inmo.tgbotapi.types
import dev.inmo.tgbotapi.types.rich.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class RichMessageSerializationTest {
private val json = Json { encodeDefaults = true }
@Test
fun decodesRichMessageWithMixedRichText() {
val source = """
{
"blocks": [
{
"type": "paragraph",
"text": ["Hello ", {"type": "bold", "text": "world"}, "!"]
},
{"type": "heading", "text": "Title", "size": 1},
{"type": "divider"},
{"type": "list", "items": [
{"label": "1", "blocks": [{"type": "paragraph", "text": "first"}]}
]}
],
"is_rtl": false
}
""".trimIndent()
val message = json.decodeFromString(RichMessage.serializer(), source)
assertEquals(4, message.blocks.size)
val paragraph = message.blocks[0] as RichBlockParagraph
val group = paragraph.text as RichTextGroup
assertEquals(RichTextPlain("Hello "), group.parts[0])
assertEquals(RichTextBold(RichTextPlain("world")), group.parts[1])
assertEquals(RichTextPlain("!"), group.parts[2])
val heading = message.blocks[1] as RichBlockSectionHeading
assertEquals(RichTextPlain("Title"), heading.text)
assertEquals(1, heading.size)
assertTrue(message.blocks[2] is RichBlockDivider)
val list = message.blocks[3] as RichBlockList
assertEquals("1", list.items[0].label)
}
@Test
fun roundTripsRichMessage() {
val message = RichMessage(
blocks = listOf(
RichBlockParagraph(
RichTextGroup(
listOf(
RichTextPlain("a "),
RichTextItalic(RichTextPlain("b")),
RichTextUrl(RichTextPlain("link"), "https://example.org")
)
)
),
RichBlockDivider()
)
)
val encoded = json.encodeToString(RichMessage.serializer(), message)
val decoded = json.decodeFromString(RichMessage.serializer(), encoded)
assertEquals(message, decoded)
}
}