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

add support of users tags

This commit is contained in:
2026-03-02 16:51:10 +06:00
parent 4f97327d29
commit b533bab95f
26 changed files with 368 additions and 79 deletions

View File

@@ -0,0 +1,7 @@
package dev.inmo.tgbotapi.abstracts
import dev.inmo.tgbotapi.types.UserTag
interface OptionallyTagged {
val tag: UserTag?
}

View File

@@ -16,6 +16,7 @@ import io.ktor.client.request.*
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.content.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import kotlin.collections.set

View File

@@ -49,6 +49,8 @@ data class PromoteChatMember(
private val canDeleteStories: Boolean? = null,
@SerialName(canManageDirectMessagesField)
private val canManageDirectMessages: Boolean? = null,
@SerialName(canManageTagsField)
private val canManageTags: Boolean? = null,
) : ChatMemberRequest<Boolean>, UntilDate {
override fun method(): String = "promoteChatMember"
override val resultDeserializer: DeserializationStrategy<Boolean>
@@ -69,6 +71,7 @@ fun PromoteChatMember(
canPromoteMembers: Boolean? = null,
canManageVideoChats: Boolean? = null,
canManageChat: Boolean? = null,
canManageTags: Boolean? = null,
) = PromoteChatMember(
chatId = chatId,
userId = userId,
@@ -84,6 +87,7 @@ fun PromoteChatMember(
canPromoteMembers = canPromoteMembers,
canManageVideoChats = canManageVideoChats,
canManageChat = canManageChat,
canManageTags = canManageTags,
canManageTopics = null,
canPostStories = null,
canEditStories = null,
@@ -144,6 +148,7 @@ fun PromoteSupergroupAdministrator(
canManageVideoChats: Boolean? = null,
canManageChat: Boolean? = null,
canManageTopics: Boolean? = null,
canManageTags: Boolean? = null,
) = PromoteChatMember(
chatId = chatId,
userId = userId,
@@ -160,6 +165,7 @@ fun PromoteSupergroupAdministrator(
canManageVideoChats = canManageVideoChats,
canManageChat = canManageChat,
canManageTopics = canManageTopics,
canManageTags = canManageTags,
canPostStories = null,
canEditStories = null,
canDeleteStories = null

View File

@@ -0,0 +1,23 @@
package dev.inmo.tgbotapi.requests.chat.members
import dev.inmo.tgbotapi.requests.chat.abstracts.ChatMemberRequest
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.utils.serializers.UnitFromBooleanSerializer
import kotlinx.serialization.*
@Serializable
data class SetChatMemberTag(
@SerialName(chatIdField)
override val chatId: ChatIdentifier,
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@SerialName(userIdField)
override val userId: UserId,
@SerialName(tagField)
val tag: UserTag? = null
) : ChatMemberRequest<Unit> {
override fun method(): String = "setChatMemberTag"
override val resultDeserializer: DeserializationStrategy<Unit>
get() = UnitFromBooleanSerializer
override val requestSerializer: SerializationStrategy<*>
get() = serializer()
}

View File

@@ -80,6 +80,7 @@ val livePeriodLimit = 60 .. LiveLocation.INDEFINITE_LIVE_PERIOD
val inlineQueryAnswerResultsLimit = 0 .. 50
val customTitleLength = 0 .. 16
val memberTagLength = 0 .. 16
val dartsCubeAndBowlingDiceResultLimit = 1 .. 6
val basketballAndFootballDiceResultLimit = 1 .. 5
@@ -272,10 +273,12 @@ const val correctOptionIdField = "correct_option_id"
const val allowsMultipleAnswersField = "allows_multiple_answers"
const val isAnonymousField = "is_anonymous"
const val canManageTopicsField = "can_manage_topics"
const val canEditTagField = "can_edit_tag"
const val canPostStoriesField = "can_post_stories"
const val canEditStoriesField = "can_edit_stories"
const val canDeleteStoriesField = "can_delete_stories"
const val canManageDirectMessagesField = "can_manage_direct_messages"
const val canManageTagsField = "can_manage_tags"
const val captionEntitiesField = "caption_entities"
const val hasSpoilerField = "has_spoiler"
const val showCaptionAboveMediaField = "show_caption_above_media"
@@ -488,6 +491,7 @@ const val headingField = "heading"
const val fromField = "from"
const val userChatIdField = "user_chat_id"
const val userField = "user"
const val tagField = "tag"
const val newOwnerField = "new_owner"
const val dateField = "date"
const val reactionsField = "reactions"
@@ -666,6 +670,7 @@ const val mainFrameTimestampField = "main_frame_timestamp"
const val firstProfileAudioField = "first_profile_audio"
const val paidMessageStarCountField = "paid_message_star_count"
const val senderTagField = "sender_tag"
const val countField = "count"
const val ratingField = "rating"
const val uniqueGiftColorsField = "unique_gift_colors"

View File

@@ -0,0 +1,16 @@
package dev.inmo.tgbotapi.types
import dev.inmo.kslog.common.w
import dev.inmo.tgbotapi.utils.DefaultKTgBotAPIKSLog
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
@Serializable
@JvmInline
value class UserTag(val string: String) {
init {
if (string.length !in memberTagLength) {
DefaultKTgBotAPIKSLog.w("UserTag", "Tag length must be in range $memberTagLength, but was ${string.length} ($string)")
}
}
}

View File

@@ -33,6 +33,7 @@ interface ChatPermissions {
val canChangeInfo: Boolean?
val canInviteUsers: Boolean?
val canPinMessages: Boolean?
val canEditTag: Boolean?
@Transient
val isGranular
get() = canSendAudios != null ||
@@ -73,7 +74,9 @@ interface ChatPermissions {
@SerialName(canInviteUsersField)
override val canInviteUsers: Boolean? = null,
@SerialName(canPinMessagesField)
override val canPinMessages: Boolean? = null
override val canPinMessages: Boolean? = null,
@SerialName(canEditTagField)
override val canEditTag: Boolean = false,
) : ChatPermissions {
@Transient
override val isGranular: Boolean
@@ -93,7 +96,9 @@ interface ChatPermissions {
@SerialName(canInviteUsersField)
override val canInviteUsers: Boolean? = null,
@SerialName(canPinMessagesField)
override val canPinMessages: Boolean? = null
override val canPinMessages: Boolean? = null,
@SerialName(canEditTagField)
override val canEditTag: Boolean = false,
) : ChatPermissions {
@Transient
override val isGranular: Boolean

View File

@@ -48,6 +48,8 @@ data class AdministratorChatMemberImpl(
override val canDeleteStories: Boolean = false,
@SerialName(canManageDirectMessagesField)
override val canManageDirectMessages: Boolean = false,
@SerialName(canManageTagsField)
override val canManageTags: Boolean = false,
) : AdministratorChatMember {
@SerialName(statusField)
@Required

View File

@@ -20,6 +20,7 @@ sealed interface ChatAdministratorRights : SpecialChatAdministratorRights {
val canEditStories: Boolean
val canDeleteStories: Boolean
val canManageDirectMessages: Boolean
val canManageTags: Boolean
companion object {
operator fun invoke(

View File

@@ -38,4 +38,6 @@ data class ChatCommonAdministratorRights(
override val canDeleteStories: Boolean = false,
@SerialName(canManageDirectMessagesField)
override val canManageDirectMessages: Boolean = false,
@SerialName(canManageTagsField)
override val canManageTags: Boolean = false,
) : ChatAdministratorRights

View File

@@ -13,7 +13,9 @@ data class KickedChatMember(
@SerialName(userField)
override val user: PreviewUser,
@SerialName(untilDateField)
override val untilDate: TelegramDate? = null
override val untilDate: TelegramDate? = null,
@SerialName(tagField)
override val tag: UserTag? = null,
) : RestrictedChatMember {
@SerialName(statusField)
@Required

View File

@@ -1,7 +1,8 @@
package dev.inmo.tgbotapi.types.chat.member
import dev.inmo.tgbotapi.abstracts.OptionallyTagged
import kotlinx.serialization.Serializable
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(ChatMemberSerializer::class)
sealed interface MemberChatMember : ChatMember
sealed interface MemberChatMember : ChatMember, OptionallyTagged

View File

@@ -10,7 +10,9 @@ import kotlinx.serialization.*
data class MemberChatMemberImpl(
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@SerialName(userField)
override val user: PreviewUser
override val user: PreviewUser,
@SerialName(tagField)
override val tag: UserTag? = null
) : MemberChatMember {
@SerialName(statusField)
@Required

View File

@@ -48,6 +48,8 @@ data class OwnerChatMember(
override val canDeleteStories: Boolean = true
@Transient
override val canManageDirectMessages: Boolean = true
@Transient
override val canManageTags: Boolean = true
@SerialName(statusField)
@Required

View File

@@ -1,5 +1,6 @@
package dev.inmo.tgbotapi.types.chat.member
import dev.inmo.tgbotapi.abstracts.OptionallyTagged
import dev.inmo.tgbotapi.abstracts.types.UntilDate
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.chat.PreviewUser
@@ -8,7 +9,7 @@ import kotlinx.serialization.Serializable
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Serializable(ChatMemberSerializer::class)
sealed interface RestrictedChatMember : ChatMember, UntilDate {
sealed interface RestrictedChatMember : ChatMember, UntilDate, OptionallyTagged {
companion object {
// backward compatibility fun
@Deprecated(

View File

@@ -5,7 +5,6 @@ package dev.inmo.tgbotapi.types.chat.member
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.chat.ChatPermissions
import dev.inmo.tgbotapi.types.chat.PreviewUser
import dev.inmo.tgbotapi.types.chat.User
import kotlinx.serialization.*
/**
@@ -48,7 +47,11 @@ data class RestrictedMemberChatMember(
@SerialName(canPinMessagesField)
override val canPinMessages: Boolean = false,
@SerialName(canManageTopicsField)
override val canManageTopics: Boolean = false
override val canManageTopics: Boolean = false,
@SerialName(canEditTagField)
override val canEditTag: Boolean = false,
@SerialName(tagField)
override val tag: UserTag? = null,
) : RestrictedChatMember, SpecialRightsChatMember, MemberChatMember, ChatPermissions {
@SerialName(statusField)
@Required

View File

@@ -13,7 +13,9 @@ data class SubscriptionMemberChatMemberImpl(
override val user: PreviewUser,
@SerialName(untilDateField)
@Serializable(TelegramDateSerializer::class)
override val untilDate: TelegramDate
override val untilDate: TelegramDate,
@SerialName(tagField)
override val tag: UserTag? = null
) : SubscriptionMemberChatMember {
@SerialName(statusField)
@Required

View File

@@ -185,6 +185,8 @@ data class CommonGroupContentMessageImpl<T : MessageContent>(
override val fromOffline: Boolean,
@SerialName(paidMessageStarCountField)
override val cost: Int? = null,
@SerialName(senderTagField)
override val senderTag: UserTag? = null,
) : CommonGroupContentMessage<T> {
constructor(
chat: PreviewGroupChat,
@@ -463,6 +465,8 @@ data class CommonForumContentMessageImpl<T : MessageContent>(
override val fromOffline: Boolean,
@SerialName(paidMessageStarCountField)
override val cost: Int? = null,
@SerialName(senderTagField)
override val senderTag: UserTag? = null,
) : CommonForumContentMessage<T> {
constructor(
chat: PreviewForumChat,
@@ -520,6 +524,8 @@ data class CommonChannelDirectMessagesContentMessageImpl<T : MessageContent>(
override val fromOffline: Boolean,
@SerialName(paidMessageStarCountField)
override val cost: Int? = null,
@SerialName(senderTagField)
override val senderTag: UserTag? = null
) : CommonChannelDirectMessagesContentMessage<T> {
constructor(
chat: PreviewChannelDirectMessagesChat,
@@ -577,7 +583,9 @@ data class CommonSuggestedChannelDirectMessagesContentMessageImpl<T : MessageCon
override val fromOffline: Boolean,
override val suggestedPostInfo: SuggestedPostInfo,
@SerialName(paidMessageStarCountField)
override val cost: Int? = null
override val cost: Int? = null,
@SerialName(senderTagField)
override val senderTag: UserTag? = null
) : CommonSuggestedChannelDirectMessagesContentMessage<T> {
constructor(
chat: PreviewChannelDirectMessagesChat,

View File

@@ -65,6 +65,7 @@ internal data class RawMessage(
private val direct_messages_topic: DirectMessagesTopic? = null,
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
private val from: PreviewUser? = null,
private val sender_tag: UserTag? = null,
@Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
private val sender_chat: PreviewPublicChat? = null,
private val forward_origin: MessageOrigin? = null,
@@ -476,6 +477,7 @@ internal data class RawMessage(
senderBoostsCount = sender_boost_count,
fromOffline = is_from_offline,
cost = paid_star_count,
senderTag = sender_tag,
)
}
}
@@ -519,6 +521,7 @@ internal data class RawMessage(
fromOffline = is_from_offline,
suggestedPostInfo = suggested_post_info,
cost = paid_star_count,
senderTag = sender_tag,
)
}
}
@@ -625,6 +628,7 @@ internal data class RawMessage(
senderBoostsCount = sender_boost_count,
fromOffline = is_from_offline,
cost = paid_star_count,
senderTag = sender_tag,
)
}
} else {
@@ -698,6 +702,7 @@ internal data class RawMessage(
senderBoostsCount = sender_boost_count,
fromOffline = is_from_offline,
cost = paid_star_count,
senderTag = sender_tag
)
}
}
@@ -773,6 +778,7 @@ internal data class RawMessage(
senderBoostsCount = sender_boost_count,
fromOffline = is_from_offline,
cost = paid_star_count,
senderTag = sender_tag,
)
}
}

View File

@@ -1,8 +1,8 @@
package dev.inmo.tgbotapi.types.message.abstracts
import dev.inmo.tgbotapi.requests.chat.forum.CreateForumTopic
import dev.inmo.tgbotapi.types.DirectMessageThreadId
import dev.inmo.tgbotapi.types.MessageThreadId
import dev.inmo.tgbotapi.types.UserTag
import dev.inmo.tgbotapi.types.chat.*
import dev.inmo.tgbotapi.types.message.ChatEvents.forum.ForumTopicCreated
import dev.inmo.tgbotapi.types.message.ChatEvents.suggested.SuggestedPostInfo
@@ -14,6 +14,7 @@ sealed interface GroupContentMessage<T : MessageContent> : PublicContentMessage<
sealed interface PotentiallyFromUserGroupContentMessage<T : MessageContent> : GroupContentMessage<T> {
val senderBoostsCount: Int?
val senderTag: UserTag?
}
sealed interface ForumContentMessage<T : MessageContent> : GroupContentMessage<T>, PossiblyTopicMessage {

View File

@@ -99,6 +99,7 @@ fun <T : MediaGroupPartContent> List<PossiblySentViaBotCommonMessage<T>>.asMedia
senderBoostsCount = sourceMessage.senderBoostsCount,
fromOffline = sourceMessage.fromOffline,
cost = sourceMessage.cost,
senderTag = sourceMessage.senderTag,
)
is ConnectedFromChannelGroupContentMessage -> ConnectedFromChannelGroupContentMessageImpl(
chat = sourceMessage.chat,
@@ -168,6 +169,7 @@ fun <T : MediaGroupPartContent> List<PossiblySentViaBotCommonMessage<T>>.asMedia
senderBoostsCount = sourceMessage.senderBoostsCount,
fromOffline = sourceMessage.fromOffline,
cost = sourceMessage.cost,
senderTag = sourceMessage.senderTag,
)
is CommonChannelDirectMessagesContentMessage -> CommonChannelDirectMessagesContentMessageImpl(
chat = sourceMessage.chat,
@@ -186,6 +188,7 @@ fun <T : MediaGroupPartContent> List<PossiblySentViaBotCommonMessage<T>>.asMedia
senderBoostsCount = sourceMessage.senderBoostsCount,
fromOffline = sourceMessage.fromOffline,
cost = sourceMessage.cost,
senderTag = sourceMessage.senderTag,
)
is FromChannelForumContentMessage -> FromChannelForumContentMessageImpl(
chat = sourceMessage.chat,
@@ -243,6 +246,7 @@ fun <T : MediaGroupPartContent> List<PossiblySentViaBotCommonMessage<T>>.asMedia
fromOffline = sourceMessage.fromOffline,
suggestedPostInfo = sourceMessage.suggestedPostInfo,
cost = sourceMessage.cost,
senderTag = sourceMessage.senderTag,
)
is FromChannelSuggestedChannelDirectMessagesContentMessage<*> -> FromChannelSuggestedChannelDirectMessagesContentMessageImpl(
chat = sourceMessage.chat,

View File

@@ -0,0 +1,24 @@
package dev.inmo.tgbotapi.utils.serializers
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object UnitFromBooleanSerializer : KSerializer<Unit> {
override val descriptor: SerialDescriptor
get() = Boolean.serializer().descriptor
override fun serialize(encoder: Encoder, value: Unit) {
encoder.encodeBoolean(true)
}
override fun deserialize(decoder: Decoder) {
return if (decoder.decodeBoolean()) {
Unit
} else {
throw IllegalStateException("Can't deserialize Unit from false")
}
}
}