From 4f97327d294f0c4fc47687174750f107d5e45153 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 2 Mar 2026 15:29:26 +0600 Subject: [PATCH] add support of date_time messages entities --- tgbotapi.core/api/tgbotapi.core.api | 147 +++++++++++++++++- .../inmo/tgbotapi/types/DateTimeFormatPart.kt | 113 ++++++++++++++ .../types/message/RawMessageEntity.kt | 12 +- .../message/textsources/DateTimeTextSource.kt | 26 ++++ .../textsources/TextSourceSerializer.kt | 1 + .../inmo/tgbotapi/utils/EntitiesBuilder.kt | 11 ++ .../utils/internal/StringFormatting.kt | 4 + .../textsources/DateTimeTextSourceTests.kt | 105 +++++++++++++ tgbotapi.utils/api/tgbotapi.utils.api | 6 + .../tgbotapi/extensions/utils/ClassCasts.kt | 9 ++ .../extensions/utils/ClassCastsNew.kt | 7 + 11 files changed, 436 insertions(+), 5 deletions(-) create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/DateTimeFormatPart.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource.kt create mode 100644 tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSourceTests.kt diff --git a/tgbotapi.core/api/tgbotapi.core.api b/tgbotapi.core/api/tgbotapi.core.api index 9a526572b9..78e5389fa0 100644 --- a/tgbotapi.core/api/tgbotapi.core.api +++ b/tgbotapi.core/api/tgbotapi.core.api @@ -11090,6 +11090,89 @@ public final class dev/inmo/tgbotapi/types/CustomEmojiId$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface class dev/inmo/tgbotapi/types/DateTimeFormatPart { + public static final field Companion Ldev/inmo/tgbotapi/types/DateTimeFormatPart$Companion; + public abstract fun getControlCharacter ()Ljava/lang/String; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPart$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public abstract interface class dev/inmo/tgbotapi/types/DateTimeFormatPart$Date : dev/inmo/tgbotapi/types/DateTimeFormatPart { + public static final field Companion Ldev/inmo/tgbotapi/types/DateTimeFormatPart$Date$Companion; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPart$Date$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPart$Date$Long : dev/inmo/tgbotapi/types/DateTimeFormatPart$Date { + public static final field INSTANCE Ldev/inmo/tgbotapi/types/DateTimeFormatPart$Date$Long; + public fun equals (Ljava/lang/Object;)Z + public fun getControlCharacter ()Ljava/lang/String; + public fun hashCode ()I + public final fun serializer ()Lkotlinx/serialization/KSerializer; + public fun toString ()Ljava/lang/String; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPart$Date$Short : dev/inmo/tgbotapi/types/DateTimeFormatPart$Date { + public static final field INSTANCE Ldev/inmo/tgbotapi/types/DateTimeFormatPart$Date$Short; + public fun equals (Ljava/lang/Object;)Z + public fun getControlCharacter ()Ljava/lang/String; + public fun hashCode ()I + public final fun serializer ()Lkotlinx/serialization/KSerializer; + public fun toString ()Ljava/lang/String; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPart$Relative : dev/inmo/tgbotapi/types/DateTimeFormatPart { + public static final field INSTANCE Ldev/inmo/tgbotapi/types/DateTimeFormatPart$Relative; + public fun equals (Ljava/lang/Object;)Z + public fun getControlCharacter ()Ljava/lang/String; + public fun hashCode ()I + public final fun serializer ()Lkotlinx/serialization/KSerializer; + public fun toString ()Ljava/lang/String; +} + +public abstract interface class dev/inmo/tgbotapi/types/DateTimeFormatPart$Time : dev/inmo/tgbotapi/types/DateTimeFormatPart { + public static final field Companion Ldev/inmo/tgbotapi/types/DateTimeFormatPart$Time$Companion; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPart$Time$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPart$Time$Long : dev/inmo/tgbotapi/types/DateTimeFormatPart$Time { + public static final field INSTANCE Ldev/inmo/tgbotapi/types/DateTimeFormatPart$Time$Long; + public fun equals (Ljava/lang/Object;)Z + public fun getControlCharacter ()Ljava/lang/String; + public fun hashCode ()I + public final fun serializer ()Lkotlinx/serialization/KSerializer; + public fun toString ()Ljava/lang/String; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPart$Time$Short : dev/inmo/tgbotapi/types/DateTimeFormatPart$Time { + public static final field INSTANCE Ldev/inmo/tgbotapi/types/DateTimeFormatPart$Time$Short; + public fun equals (Ljava/lang/Object;)Z + public fun getControlCharacter ()Ljava/lang/String; + public fun hashCode ()I + public final fun serializer ()Lkotlinx/serialization/KSerializer; + public fun toString ()Ljava/lang/String; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPart$WeekDay : dev/inmo/tgbotapi/types/DateTimeFormatPart { + public static final field INSTANCE Ldev/inmo/tgbotapi/types/DateTimeFormatPart$WeekDay; + public fun equals (Ljava/lang/Object;)Z + public fun getControlCharacter ()Ljava/lang/String; + public fun hashCode ()I + public final fun serializer ()Lkotlinx/serialization/KSerializer; + public fun toString ()Ljava/lang/String; +} + +public final class dev/inmo/tgbotapi/types/DateTimeFormatPartKt { + public static final fun buildDateTimeFormat (Lkotlin/jvm/functions/Function1;)Ljava/lang/String; +} + public final class dev/inmo/tgbotapi/types/DirectMessageThreadId { public static final field Companion Ldev/inmo/tgbotapi/types/DirectMessageThreadId$Companion; public static final synthetic fun box-impl (J)Ldev/inmo/tgbotapi/types/DirectMessageThreadId; @@ -14335,6 +14418,17 @@ public final class dev/inmo/tgbotapi/types/TextQuote$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class dev/inmo/tgbotapi/types/TgDateTimeFormatBuilder { + public fun ()V + public final fun build ()Ljava/lang/String; + public final fun dateLong ()Ldev/inmo/tgbotapi/types/TgDateTimeFormatBuilder; + public final fun dateShort ()Ldev/inmo/tgbotapi/types/TgDateTimeFormatBuilder; + public final fun relative ()Ldev/inmo/tgbotapi/types/TgDateTimeFormatBuilder; + public final fun timeLong ()Ldev/inmo/tgbotapi/types/TgDateTimeFormatBuilder; + public final fun timeShort ()Ldev/inmo/tgbotapi/types/TgDateTimeFormatBuilder; + public final fun weekDay ()Ldev/inmo/tgbotapi/types/TgDateTimeFormatBuilder; +} + public final class dev/inmo/tgbotapi/types/TgFileUniqueId { public static final field Companion Ldev/inmo/tgbotapi/types/TgFileUniqueId$Companion; public static final synthetic fun box-impl (Ljava/lang/String;)Ldev/inmo/tgbotapi/types/TgFileUniqueId; @@ -25808,8 +25902,8 @@ public final class dev/inmo/tgbotapi/types/message/PrivateForumEventMessage : de public final class dev/inmo/tgbotapi/types/message/RawMessageEntity { public static final field Companion Ldev/inmo/tgbotapi/types/message/RawMessageEntity$Companion; - public synthetic fun (Ljava/lang/String;IILjava/lang/String;Ldev/inmo/tgbotapi/types/chat/User;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/String;IILjava/lang/String;Ldev/inmo/tgbotapi/types/chat/User;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;IILjava/lang/String;Ldev/inmo/tgbotapi/types/chat/User;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/String;IILjava/lang/String;Ldev/inmo/tgbotapi/types/chat/User;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()I public final fun component3 ()I @@ -25817,15 +25911,19 @@ public final class dev/inmo/tgbotapi/types/message/RawMessageEntity { public final fun component5 ()Ldev/inmo/tgbotapi/types/chat/User; public final fun component6 ()Ljava/lang/String; public final fun component7-GbmMWyQ ()Ljava/lang/String; - public final fun copy-SbQeJ6M (Ljava/lang/String;IILjava/lang/String;Ldev/inmo/tgbotapi/types/chat/User;Ljava/lang/String;Ljava/lang/String;)Ldev/inmo/tgbotapi/types/message/RawMessageEntity; - public static synthetic fun copy-SbQeJ6M$default (Ldev/inmo/tgbotapi/types/message/RawMessageEntity;Ljava/lang/String;IILjava/lang/String;Ldev/inmo/tgbotapi/types/chat/User;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ldev/inmo/tgbotapi/types/message/RawMessageEntity; + public final fun component8 ()Ljava/lang/Long; + public final fun component9 ()Ljava/lang/String; + public final fun copy-w5b-Tf8 (Ljava/lang/String;IILjava/lang/String;Ldev/inmo/tgbotapi/types/chat/User;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;)Ldev/inmo/tgbotapi/types/message/RawMessageEntity; + public static synthetic fun copy-w5b-Tf8$default (Ldev/inmo/tgbotapi/types/message/RawMessageEntity;Ljava/lang/String;IILjava/lang/String;Ldev/inmo/tgbotapi/types/chat/User;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;ILjava/lang/Object;)Ldev/inmo/tgbotapi/types/message/RawMessageEntity; public fun equals (Ljava/lang/Object;)Z public final fun getCustom_emoji_id-GbmMWyQ ()Ljava/lang/String; + public final fun getDate_time_format ()Ljava/lang/String; public final fun getLanguage ()Ljava/lang/String; public final fun getLength ()I public final fun getOffset ()I public final fun getPriority ()I public final fun getType ()Ljava/lang/String; + public final fun getUnix_time ()Ljava/lang/Long; public final fun getUrl ()Ljava/lang/String; public final fun getUser ()Ldev/inmo/tgbotapi/types/chat/User; public fun hashCode ()I @@ -27947,6 +28045,45 @@ public final class dev/inmo/tgbotapi/types/message/textsources/CustomEmojiTextSo public static final fun customEmojiTextSource-R1fjqgo (Ljava/lang/String;[Ldev/inmo/tgbotapi/types/message/textsources/TextSource;)Ldev/inmo/tgbotapi/types/message/textsources/CustomEmojiTextSource; } +public final class dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource : dev/inmo/tgbotapi/types/message/textsources/TextSource { + public static final field Companion Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource$Companion; + public fun (Ljava/lang/String;JLjava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()J + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;JLjava/lang/String;)Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource; + public static synthetic fun copy$default (Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource;Ljava/lang/String;JLjava/lang/String;ILjava/lang/Object;)Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource; + public fun equals (Ljava/lang/Object;)Z + public fun getAsText ()Ljava/lang/String; + public final fun getDateTimeFormat ()Ljava/lang/String; + public fun getHtml ()Ljava/lang/String; + public fun getMarkdown ()Ljava/lang/String; + public fun getMarkdownV2 ()Ljava/lang/String; + public fun getSource ()Ljava/lang/String; + public final fun getUnixTimeStamp ()J + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSourceKt { + public static final fun dateTimeTextSource (Ljava/lang/String;JLjava/lang/String;)Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource; +} + public final class dev/inmo/tgbotapi/types/message/textsources/EMailTextSource : dev/inmo/tgbotapi/types/message/textsources/MultilevelTextSource { public static final field Companion Ldev/inmo/tgbotapi/types/message/textsources/EMailTextSource$Companion; public fun (Ljava/lang/String;Ljava/util/List;)V @@ -33756,6 +33893,8 @@ public final class dev/inmo/tgbotapi/utils/EntitiesBuilderKt { public static final fun customEmojiln-fRZnA7w (Ldev/inmo/tgbotapi/utils/EntitiesBuilder;Ljava/lang/String;Ljava/util/List;)Ldev/inmo/tgbotapi/utils/EntitiesBuilder; public static final fun customEmojiln-fRZnA7w (Ldev/inmo/tgbotapi/utils/EntitiesBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Ldev/inmo/tgbotapi/utils/EntitiesBuilder; public static final fun customEmojiln-fRZnA7w (Ldev/inmo/tgbotapi/utils/EntitiesBuilder;Ljava/lang/String;[Ldev/inmo/tgbotapi/types/message/textsources/TextSource;)Ldev/inmo/tgbotapi/utils/EntitiesBuilder; + public static final fun dateTime (Ldev/inmo/tgbotapi/utils/EntitiesBuilder;Ljava/lang/String;JLjava/lang/String;)Ldev/inmo/tgbotapi/utils/EntitiesBuilder; + public static final fun dateTimeln (Ldev/inmo/tgbotapi/utils/EntitiesBuilder;Ljava/lang/String;JLjava/lang/String;)Ldev/inmo/tgbotapi/utils/EntitiesBuilder; public static final fun email (Ldev/inmo/tgbotapi/utils/EntitiesBuilder;Ljava/lang/String;)Ldev/inmo/tgbotapi/utils/EntitiesBuilder; public static final fun email (Ldev/inmo/tgbotapi/utils/EntitiesBuilder;Ljava/util/List;)Ldev/inmo/tgbotapi/utils/EntitiesBuilder; public static final fun email (Ldev/inmo/tgbotapi/utils/EntitiesBuilder;Lkotlin/jvm/functions/Function1;)Ldev/inmo/tgbotapi/utils/EntitiesBuilder; diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/DateTimeFormatPart.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/DateTimeFormatPart.kt new file mode 100644 index 0000000000..a770d6a9b2 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/DateTimeFormatPart.kt @@ -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() + + /** + * 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() diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/RawMessageEntity.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/RawMessageEntity.kt index 90a580bd51..656346ea47 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/RawMessageEntity.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/RawMessageEntity.kt @@ -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 { 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) { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource.kt new file mode 100644 index 0000000000..c819fac960 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource.kt @@ -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) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/textsources/TextSourceSerializer.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/textsources/TextSourceSerializer.kt index 7034b9ae92..464271b93d 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/textsources/TextSourceSerializer.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/textsources/TextSourceSerializer.kt @@ -28,6 +28,7 @@ object TextSourceSerializer : TypedSerializer(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 { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/EntitiesBuilder.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/EntitiesBuilder.kt index 50c8075f82..7f548e6913 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/EntitiesBuilder.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/EntitiesBuilder.kt @@ -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] */ 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 73d2b7d1d5..bd6bea2a15 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 @@ -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 = "${toHtml()}" +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 = "${toHtml()}" + internal fun String.boldMarkdown(): String = markdownDefault(markdownBoldControl) internal fun String.blockquoteMarkdown(): String = regularMarkdown() diff --git a/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSourceTests.kt b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSourceTests.kt new file mode 100644 index 0000000000..b3d9cd97d9 --- /dev/null +++ b/tgbotapi.core/src/commonTest/kotlin/dev/inmo/tgbotapi/types/message/textsources/DateTimeTextSourceTests.kt @@ -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("$text", 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("$text", 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) + } +} diff --git a/tgbotapi.utils/api/tgbotapi.utils.api b/tgbotapi.utils/api/tgbotapi.utils.api index 32490811ee..66e0ab624d 100644 --- a/tgbotapi.utils/api/tgbotapi.utils.api +++ b/tgbotapi.utils/api/tgbotapi.utils.api @@ -71,6 +71,7 @@ public final class dev/inmo/tgbotapi/extensions/utils/ClassCastsKt { public static final fun asCustomDiceAnimationType (Ldev/inmo/tgbotapi/types/dice/DiceAnimationType;)Ldev/inmo/tgbotapi/types/dice/CustomDiceAnimationType; public static final fun asDartsDiceAnimationType (Ldev/inmo/tgbotapi/types/dice/DiceAnimationType;)Ldev/inmo/tgbotapi/types/dice/DartsDiceAnimationType; public static final fun asDataCallbackQuery (Ldev/inmo/tgbotapi/types/queries/callback/CallbackQuery;)Ldev/inmo/tgbotapi/types/queries/callback/DataCallbackQuery; + public static final fun asDateTimeTextSource (Ldev/inmo/tgbotapi/types/message/textsources/TextSource;)Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource; public static final fun asDeleteChatPhoto (Ldev/inmo/tgbotapi/types/message/ChatEvents/abstracts/ChatEvent;)Ldev/inmo/tgbotapi/types/message/ChatEvents/DeleteChatPhoto; public static final fun asDescribedInlineQueryResult (Ldev/inmo/tgbotapi/types/InlineQueries/InlineQueryResult/abstracts/InlineQueryResult;)Ldev/inmo/tgbotapi/types/InlineQueries/InlineQueryResult/abstracts/DescribedInlineQueryResult; public static final fun asDiceContent (Ldev/inmo/tgbotapi/types/message/content/ResendableContent;)Ldev/inmo/tgbotapi/types/message/content/DiceContent; @@ -416,6 +417,7 @@ public final class dev/inmo/tgbotapi/extensions/utils/ClassCastsKt { public static final fun requireCustomDiceAnimationType (Ldev/inmo/tgbotapi/types/dice/DiceAnimationType;)Ldev/inmo/tgbotapi/types/dice/CustomDiceAnimationType; public static final fun requireDartsDiceAnimationType (Ldev/inmo/tgbotapi/types/dice/DiceAnimationType;)Ldev/inmo/tgbotapi/types/dice/DartsDiceAnimationType; public static final fun requireDataCallbackQuery (Ldev/inmo/tgbotapi/types/queries/callback/CallbackQuery;)Ldev/inmo/tgbotapi/types/queries/callback/DataCallbackQuery; + public static final fun requireDateTimeTextSource (Ldev/inmo/tgbotapi/types/message/textsources/TextSource;)Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource; public static final fun requireDeleteChatPhoto (Ldev/inmo/tgbotapi/types/message/ChatEvents/abstracts/ChatEvent;)Ldev/inmo/tgbotapi/types/message/ChatEvents/DeleteChatPhoto; public static final fun requireDescribedInlineQueryResult (Ldev/inmo/tgbotapi/types/InlineQueries/InlineQueryResult/abstracts/InlineQueryResult;)Ldev/inmo/tgbotapi/types/InlineQueries/InlineQueryResult/abstracts/DescribedInlineQueryResult; public static final fun requireDiceContent (Ldev/inmo/tgbotapi/types/message/content/ResendableContent;)Ldev/inmo/tgbotapi/types/message/content/DiceContent; @@ -761,6 +763,7 @@ public final class dev/inmo/tgbotapi/extensions/utils/ClassCastsKt { public static final fun whenCustomDiceAnimationType (Ldev/inmo/tgbotapi/types/dice/DiceAnimationType;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun whenDartsDiceAnimationType (Ldev/inmo/tgbotapi/types/dice/DiceAnimationType;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun whenDataCallbackQuery (Ldev/inmo/tgbotapi/types/queries/callback/CallbackQuery;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun whenDateTimeTextSource (Ldev/inmo/tgbotapi/types/message/textsources/TextSource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun whenDeleteChatPhoto (Ldev/inmo/tgbotapi/types/message/ChatEvents/abstracts/ChatEvent;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun whenDescribedInlineQueryResult (Ldev/inmo/tgbotapi/types/InlineQueries/InlineQueryResult/abstracts/InlineQueryResult;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun whenDiceContent (Ldev/inmo/tgbotapi/types/message/content/ResendableContent;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; @@ -1337,6 +1340,8 @@ public final class dev/inmo/tgbotapi/extensions/utils/ClassCastsNewKt { public static final fun dartsDiceAnimationTypeOrThrow (Ldev/inmo/tgbotapi/types/dice/DiceAnimationType;)Ldev/inmo/tgbotapi/types/dice/DartsDiceAnimationType; public static final fun dataCallbackQueryOrNull (Ldev/inmo/tgbotapi/abstracts/OptionallyWithUser;)Ldev/inmo/tgbotapi/types/queries/callback/DataCallbackQuery; public static final fun dataCallbackQueryOrThrow (Ldev/inmo/tgbotapi/abstracts/OptionallyWithUser;)Ldev/inmo/tgbotapi/types/queries/callback/DataCallbackQuery; + public static final fun dateTimeTextSourceOrNull (Ldev/inmo/tgbotapi/types/message/textsources/TextSource;)Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource; + public static final fun dateTimeTextSourceOrThrow (Ldev/inmo/tgbotapi/types/message/textsources/TextSource;)Ldev/inmo/tgbotapi/types/message/textsources/DateTimeTextSource; public static final fun deleteChatPhotoOrNull (Ldev/inmo/tgbotapi/types/message/ChatEvents/abstracts/ChatEvent;)Ldev/inmo/tgbotapi/types/message/ChatEvents/DeleteChatPhoto; public static final fun deleteChatPhotoOrThrow (Ldev/inmo/tgbotapi/types/message/ChatEvents/abstracts/ChatEvent;)Ldev/inmo/tgbotapi/types/message/ChatEvents/DeleteChatPhoto; public static final fun deletedBusinessMessageUpdateOrNull (Ldev/inmo/tgbotapi/types/update/abstracts/Update;)Ldev/inmo/tgbotapi/types/update/DeletedBusinessMessageUpdate; @@ -1710,6 +1715,7 @@ public final class dev/inmo/tgbotapi/extensions/utils/ClassCastsNewKt { public static final fun ifCustomEmojiVideoSticker (Ldev/inmo/tgbotapi/types/files/TelegramMediaFile;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun ifDartsDiceAnimationType (Ldev/inmo/tgbotapi/types/dice/DiceAnimationType;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun ifDataCallbackQuery (Ldev/inmo/tgbotapi/abstracts/OptionallyWithUser;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; + public static final fun ifDateTimeTextSource (Ldev/inmo/tgbotapi/types/message/textsources/TextSource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun ifDeleteChatPhoto (Ldev/inmo/tgbotapi/types/message/ChatEvents/abstracts/ChatEvent;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun ifDeletedBusinessMessageUpdate (Ldev/inmo/tgbotapi/types/update/abstracts/Update;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun ifDescribedInlineQueryResult (Ldev/inmo/tgbotapi/types/InlineQueries/InlineQueryResult/abstracts/InlineQueryResult;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt index 38f11d836e..d52ea1f3d8 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCasts.kt @@ -3036,6 +3036,15 @@ inline fun TextSource.asTextMentionTextSource(): TextMentionTextSource? = this a @PreviewFeature inline fun TextSource.requireTextMentionTextSource(): TextMentionTextSource = this as TextMentionTextSource +@PreviewFeature +inline fun TextSource.whenDateTimeTextSource(block: (DateTimeTextSource) -> T) = asDateTimeTextSource()?.let(block) + +@PreviewFeature +inline fun TextSource.asDateTimeTextSource(): DateTimeTextSource? = this as? DateTimeTextSource + +@PreviewFeature +inline fun TextSource.requireDateTimeTextSource(): DateTimeTextSource = this as DateTimeTextSource + @PreviewFeature inline fun TextSource.whenURLTextSource(block: (URLTextSource) -> T) = asURLTextSource()?.let(block) diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCastsNew.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCastsNew.kt index 49b4af8b4b..16f2bf8132 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCastsNew.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/ClassCastsNew.kt @@ -402,6 +402,7 @@ import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource import dev.inmo.tgbotapi.types.message.textsources.CashTagTextSource import dev.inmo.tgbotapi.types.message.textsources.CodeTextSource import dev.inmo.tgbotapi.types.message.textsources.CustomEmojiTextSource +import dev.inmo.tgbotapi.types.message.textsources.DateTimeTextSource import dev.inmo.tgbotapi.types.message.textsources.EMailTextSource import dev.inmo.tgbotapi.types.message.textsources.ExpandableBlockquoteTextSource import dev.inmo.tgbotapi.types.message.textsources.HashTagTextSource @@ -2805,6 +2806,12 @@ public inline fun TextSource.italicTextSourceOrThrow(): ItalicTextSource = this public inline fun TextSource.ifItalicTextSource(block: (ItalicTextSource) -> T): T? = italicTextSourceOrNull() ?.let(block) +public inline fun TextSource.dateTimeTextSourceOrNull(): DateTimeTextSource? = this as? dev.inmo.tgbotapi.types.message.textsources.DateTimeTextSource + +public inline fun TextSource.dateTimeTextSourceOrThrow(): DateTimeTextSource = this as dev.inmo.tgbotapi.types.message.textsources.DateTimeTextSource + +public inline fun TextSource.ifDateTimeTextSource(block: (DateTimeTextSource) -> T): T? = dateTimeTextSourceOrNull() ?.let(block) + public inline fun TextSource.textMentionTextSourceOrNull(): TextMentionTextSource? = this as? dev.inmo.tgbotapi.types.message.textsources.TextMentionTextSource public inline fun TextSource.textMentionTextSourceOrThrow(): TextMentionTextSource = this as dev.inmo.tgbotapi.types.message.textsources.TextMentionTextSource