1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2026-03-03 17:32:23 +00:00

add support of date_time messages entities

This commit is contained in:
2026-03-02 15:29:26 +06:00
parent b17d7a868a
commit 4f97327d29
11 changed files with 436 additions and 5 deletions

View File

@@ -0,0 +1,113 @@
package dev.inmo.tgbotapi.types
import kotlinx.serialization.Serializable
/**
* Common interface for parts of date time format. Used for [dev.inmo.tgbotapi.types.message.textsources.DateTimeTextSource]
*
* @see TgDateTimeFormatBuilder
* @see buildDateTimeFormat
*/
@Serializable
sealed interface DateTimeFormatPart {
/**
* Character that represents this part in the format string. Used by [TgDateTimeFormatBuilder.build]
*/
val controlCharacter: String
/**
* Represents relative time format (e.g. "2 hours ago"). Control character: "r"
*/
@Serializable
data object Relative : DateTimeFormatPart { override val controlCharacter: String get() = "r" }
/**
* Represents day of the week format (e.g. "Monday"). Control character: "w"
*/
@Serializable
data object WeekDay : DateTimeFormatPart { override val controlCharacter: String get() = "w" }
/**
* Group for date-related format parts
*/
@Serializable
sealed interface Date : DateTimeFormatPart {
/**
* Represents short date format (e.g. "01.01.2023"). Control character: "d"
*/
@Serializable
data object Short : Date { override val controlCharacter: String get() = "d" }
/**
* Represents long date format (e.g. "January 1, 2023"). Control character: "D"
*/
@Serializable
data object Long : Date { override val controlCharacter: String get() = "D" }
}
/**
* Group for time-related format parts
*/
@Serializable
sealed interface Time : DateTimeFormatPart {
/**
* Represents short time format (e.g. "12:00"). Control character: "t"
*/
@Serializable
data object Short : Time { override val controlCharacter: String get() = "t" }
/**
* Represents long time format (e.g. "12:00:00"). Control character: "T"
*/
@Serializable
data object Long : Time { override val controlCharacter: String get() = "T" }
}
}
/**
* Builder for date time format string. Use [buildDateTimeFormat] for convenience
*/
class TgDateTimeFormatBuilder {
private val parts = mutableSetOf<DateTimeFormatPart>()
/**
* Adds [DateTimeFormatPart.Relative] to the format
*/
fun relative() = apply { parts.add(DateTimeFormatPart.Relative) }
/**
* Adds [DateTimeFormatPart.WeekDay] to the format
*/
fun weekDay() = apply { parts.add(DateTimeFormatPart.WeekDay) }
/**
* Adds [DateTimeFormatPart.Date.Short] to the format. Removes any other [DateTimeFormatPart.Date] parts
*/
fun dateShort() = apply {
parts.removeAll { it is DateTimeFormatPart.Date }
parts.add(DateTimeFormatPart.Date.Short)
}
/**
* Adds [DateTimeFormatPart.Time.Short] to the format. Removes any other [DateTimeFormatPart.Time] parts
*/
fun timeShort() = apply {
parts.removeAll { it is DateTimeFormatPart.Time }
parts.add(DateTimeFormatPart.Time.Short)
}
/**
* Adds [DateTimeFormatPart.Date.Long] to the format. Removes any other [DateTimeFormatPart.Date] parts
*/
fun dateLong() = apply {
parts.removeAll { it is DateTimeFormatPart.Date }
parts.add(DateTimeFormatPart.Date.Long)
}
/**
* Adds [DateTimeFormatPart.Time.Long] to the format. Removes any other [DateTimeFormatPart.Time] parts
*/
fun timeLong() = apply {
parts.removeAll { it is DateTimeFormatPart.Time }
parts.add(DateTimeFormatPart.Time.Long)
}
/**
* Joins all added parts into a single format string
*/
fun build() = parts.joinToString("")
}
/**
* Convenient way to build date time format string
*/
fun buildDateTimeFormat(block: TgDateTimeFormatBuilder.() -> Unit): String = TgDateTimeFormatBuilder().apply(block).build()

View File

@@ -2,6 +2,7 @@ package dev.inmo.tgbotapi.types.message
import dev.inmo.micro_utils.common.Warning
import dev.inmo.tgbotapi.types.CustomEmojiId
import dev.inmo.tgbotapi.types.UnixTimeStamp
import dev.inmo.tgbotapi.types.chat.User
import dev.inmo.tgbotapi.types.message.textsources.*
import kotlinx.serialization.Serializable
@@ -15,7 +16,9 @@ data class RawMessageEntity(
val url: String? = null,
val user: User? = null,
val language: String? = null,
val custom_emoji_id: CustomEmojiId? = null
val custom_emoji_id: CustomEmojiId? = null,
val unix_time: UnixTimeStamp? = null,
val date_time_format: String? = null
) {
internal val range by lazy {
offset until (offset + length)
@@ -43,6 +46,7 @@ data class RawMessageEntity(
"code" -> 2
"pre" -> 2
"text_link" -> 2
"date_time" -> 2
else -> 2
}
}
@@ -75,6 +79,11 @@ fun RawMessageEntity.asTextSource(
sourceSubstring,
url ?: throw IllegalStateException("URL must not be null for text link")
)
"date_time" -> DateTimeTextSource(
sourceSubstring,
unix_time ?: throw IllegalStateException("Unix time must not be null for date_time"),
date_time_format
)
"text_mention" -> TextMentionTextSource(
sourceSubstring,
user ?: throw IllegalStateException("User must not be null for text mention"),
@@ -200,6 +209,7 @@ fun TextSource.toRawMessageEntities(offset: Int = 0): List<RawMessageEntity> {
is StrikethroughTextSource -> RawMessageEntity("strikethrough", offset, length)
is SpoilerTextSource -> RawMessageEntity("spoiler", offset, length)
is CustomEmojiTextSource -> RawMessageEntity("custom_emoji", offset, length, custom_emoji_id = customEmojiId)
is DateTimeTextSource -> RawMessageEntity("date_time", offset, length, unix_time = unixTimeStamp, date_time_format = dateTimeFormat)
is RegularTextSource -> null
}
) + if (this is MultilevelTextSource) {

View File

@@ -0,0 +1,26 @@
package dev.inmo.tgbotapi.types.message.textsources
import dev.inmo.tgbotapi.types.UnixTimeStamp
import dev.inmo.tgbotapi.utils.RiskFeature
import dev.inmo.tgbotapi.utils.internal.*
import kotlinx.serialization.Serializable
/**
* @see linkTextSource
*/
@Serializable
data class DateTimeTextSource @RiskFeature(DirectInvocationOfTextSourceConstructor) constructor (
override val source: String,
val unixTimeStamp: UnixTimeStamp,
val dateTimeFormat: String?
) : TextSource {
override val markdown: String by lazy { source.dateTimeMarkdown(unixTimeStamp, dateTimeFormat) }
override val markdownV2: String by lazy { source.dateTimeMarkdownV2(unixTimeStamp, dateTimeFormat) }
override val html: String by lazy { source.dateTimeHTML(unixTimeStamp, dateTimeFormat) }
}
fun dateTimeTextSource(
text: String,
unixTimeStamp: UnixTimeStamp,
dateTimeFormat: String?
) = DateTimeTextSource(text, unixTimeStamp, dateTimeFormat)

View File

@@ -28,6 +28,7 @@ object TextSourceSerializer : TypedSerializer<TextSource>(TextSource::class, emp
"cashtag" to CashTagTextSource.serializer(),
"spoiler" to SpoilerTextSource.serializer(),
"custom_emoji" to CustomEmojiTextSource.serializer(),
"date_time" to DateTimeTextSource.serializer(),
"blockquote" to BlockquoteTextSource.serializer(),
"expandable_blockquote" to ExpandableBlockquoteTextSource.serializer(),
).also {

View File

@@ -5,6 +5,7 @@ package dev.inmo.tgbotapi.utils
import dev.inmo.micro_utils.common.joinTo
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.CustomEmojiId
import dev.inmo.tgbotapi.types.UnixTimeStamp
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.chat.User
import dev.inmo.tgbotapi.types.message.textsources.*
@@ -654,6 +655,16 @@ inline fun EntitiesBuilder.link(url: String) = add(dev.inmo.tgbotapi.types.messa
inline fun EntitiesBuilder.linkln(url: String) = link(url) + newLine
/**
* Add [DateTimeTextSource] using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.message.textsources.dateTimeTextSource]
*/
inline fun EntitiesBuilder.dateTime(text: String, unixTimeStamp: UnixTimeStamp, dateTimeFormat: String?) = add(dev.inmo.tgbotapi.types.message.textsources.dateTimeTextSource(text, unixTimeStamp, dateTimeFormat))
/**
* Version of [EntitiesBuilder.dateTime] with new line at the end
*/
inline fun EntitiesBuilder.dateTimeln(text: String, unixTimeStamp: UnixTimeStamp, dateTimeFormat: String?) = dateTime(text, unixTimeStamp, dateTimeFormat) + newLine
/**
* Add underline using [EntitiesBuilder.add] with [dev.inmo.tgbotapi.types.message.textsources.underlineTextSource]
*/

View File

@@ -50,6 +50,10 @@ internal fun String.linkMarkdown(link: String): String = "[${toMarkdown()}](${li
internal fun String.linkMarkdownV2(link: String): String = "[${escapeMarkdownV2Common()}](${link.escapeMarkdownV2Link()})"
internal fun String.linkHTML(link: String): String = "<a href=\"$link\">${toHtml()}</a>"
internal fun String.dateTimeMarkdown(unixTimeStamp: UnixTimeStamp, dateTimeFormat: String?): String = "![${toMarkdown()}](tg://time?unix=$unixTimeStamp${dateTimeFormat ?.let { "&format=${dateTimeFormat.toMarkdown()}" } ?: ""})"
internal fun String.dateTimeMarkdownV2(unixTimeStamp: UnixTimeStamp, dateTimeFormat: String?): String = "![${escapeMarkdownV2Common()}](tg://time?unix=$unixTimeStamp${dateTimeFormat?.let { "&format=${dateTimeFormat.escapeMarkdownV2Link()}" } ?: ""})"
internal fun String.dateTimeHTML(unixTimeStamp: UnixTimeStamp, dateTimeFormat: String?): String = "<tg-time unix=\"$unixTimeStamp\"${dateTimeFormat?.let { " format=\"$dateTimeFormat\"" } ?: ""}>${toHtml()}</tg-time>"
internal fun String.boldMarkdown(): String = markdownDefault(markdownBoldControl)
internal fun String.blockquoteMarkdown(): String = regularMarkdown()

View File

@@ -0,0 +1,105 @@
package dev.inmo.tgbotapi.types.message.textsources
import dev.inmo.tgbotapi.types.message.RawMessageEntity
import dev.inmo.tgbotapi.types.message.asTextSources
import dev.inmo.tgbotapi.types.message.toRawMessageEntities
import dev.inmo.tgbotapi.utils.buildEntities
import dev.inmo.tgbotapi.utils.dateTime
import kotlin.test.*
class DateTimeTextSourceTests {
@Test
fun testDateTimeTextSourceFormatting() {
val unix = 1714560000L
val format = "r"
val text = "some date"
val source = DateTimeTextSource(text, unix, format)
assertEquals("![$text](tg://time?unix=$unix&format=$format)", source.markdown)
assertEquals("![$text](tg://time?unix=$unix&format=$format)", source.markdownV2)
assertEquals("<tg-time unix=\"$unix\" format=\"$format\">$text</tg-time>", source.html)
}
@Test
fun testDateTimeTextSourceFormattingWithoutFormat() {
val unix = 1714560000L
val format = null
val text = "some date"
val source = DateTimeTextSource(text, unix, format)
assertEquals("![$text](tg://time?unix=$unix)", source.markdown)
assertEquals("![$text](tg://time?unix=$unix)", source.markdownV2)
assertEquals("<tg-time unix=\"$unix\">$text</tg-time>", source.html)
}
@Test
fun testDateTimeTextSourceInRawMessageEntity() {
val sourceText = "date: 2024-05-01"
val unix = 1714560000L
val format = "wd"
val entities = listOf(
RawMessageEntity("date_time", 6, 10, unix_time = unix, date_time_format = format)
)
val textSources = entities.asTextSources(sourceText)
assertEquals(2, textSources.size)
assertTrue(textSources[0] is RegularTextSource)
assertTrue(textSources[1] is DateTimeTextSource)
val dateTimeSource = textSources[1] as DateTimeTextSource
assertEquals("2024-05-01", dateTimeSource.source)
assertEquals(unix, dateTimeSource.unixTimeStamp)
assertEquals(format, dateTimeSource.dateTimeFormat)
val backToEntities = dateTimeSource.toRawMessageEntities(6)
assertEquals(1, backToEntities.size)
val entity = backToEntities[0]
assertEquals("date_time", entity.type)
assertEquals(6, entity.offset)
assertEquals(10, entity.length)
assertEquals(unix, entity.unix_time)
assertEquals(format, entity.date_time_format)
}
@Test
fun testDateTimeTextSourceInRawMessageEntityWithNullFormat() {
val sourceText = "date: 2024-05-01"
val unix = 1714560000L
val format = null
val entities = listOf(
RawMessageEntity("date_time", 6, 10, unix_time = unix, date_time_format = format)
)
val textSources = entities.asTextSources(sourceText)
assertEquals(2, textSources.size)
assertTrue(textSources[0] is RegularTextSource)
assertTrue(textSources[1] is DateTimeTextSource)
val dateTimeSource = textSources[1] as DateTimeTextSource
assertEquals("2024-05-01", dateTimeSource.source)
assertEquals(unix, dateTimeSource.unixTimeStamp)
assertEquals(format, dateTimeSource.dateTimeFormat)
val backToEntities = dateTimeSource.toRawMessageEntities(6)
assertEquals(1, backToEntities.size)
val entity = backToEntities[0]
assertEquals("date_time", entity.type)
assertEquals(6, entity.offset)
assertEquals(10, entity.length)
assertEquals(unix, entity.unix_time)
assertEquals(format, entity.date_time_format)
}
@Test
fun testDateTimeInEntitiesBuilder() {
val unix = 1714560000L
val format = "D"
val sources = buildEntities {
dateTime("today", unix, format)
}
assertEquals(1, sources.size)
val source = sources[0] as DateTimeTextSource
assertEquals("today", source.source)
assertEquals(unix, source.unixTimeStamp)
assertEquals(format, source.dateTimeFormat)
}
}