From a5446b5adbb3d70d3e330138b8f55b959ded4808 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 6 Apr 2026 16:32:26 +0600 Subject: [PATCH] add support of managed bots creation link --- tgbotapi.core/api/tgbotapi.core.api | 25 ++-- .../dev/inmo/tgbotapi/types/ChatIdentifier.kt | 1 + tgbotapi.utils/api/tgbotapi.utils.api | 8 ++ .../utils/formatting/LinksFormatting.kt | 16 +++ .../kotlin/LinksFormattingKtTest.kt | 115 ++++++++++++++++++ 5 files changed, 153 insertions(+), 12 deletions(-) create mode 100644 tgbotapi.utils/src/commonTest/kotlin/LinksFormattingKtTest.kt diff --git a/tgbotapi.core/api/tgbotapi.core.api b/tgbotapi.core/api/tgbotapi.core.api index 9e557f47ac..e5a2da649f 100644 --- a/tgbotapi.core/api/tgbotapi.core.api +++ b/tgbotapi.core/api/tgbotapi.core.api @@ -10230,6 +10230,7 @@ public final class dev/inmo/tgbotapi/types/ChatIdentifierKt { public static final field internalLinkBeginning Ljava/lang/String; public static final field internalTgAppLinksBeginning Ljava/lang/String; public static final field internalUserLinkBeginning Ljava/lang/String; + public static final field managedBotNewBotUsername Ljava/lang/String; public static final fun getBusinessConnectionId (Ldev/inmo/tgbotapi/types/ChatIdentifier;)Ljava/lang/String; public static final fun getDirectMessageThreadId (Ldev/inmo/tgbotapi/types/ChatIdentifier;)Ldev/inmo/tgbotapi/types/DirectMessageThreadId; public static final fun getThreadId (Ldev/inmo/tgbotapi/types/ChatIdentifier;)Ldev/inmo/tgbotapi/types/MessageThreadId; @@ -16545,11 +16546,11 @@ public final class dev/inmo/tgbotapi/types/buttons/KeyboardMarkupSerializer : ko public final class dev/inmo/tgbotapi/types/buttons/PreparedKeyboardButton { public static final field Companion Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButton$Companion; public synthetic fun (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1-WMoZTbI ()Ljava/lang/String; - public final fun copy-zz-h5ko (Ljava/lang/String;)Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButton; - public static synthetic fun copy-zz-h5ko$default (Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButton;Ljava/lang/String;ILjava/lang/Object;)Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButton; + public final fun component1-pE_R3EE ()Ljava/lang/String; + public final fun copy-ZZTyg0o (Ljava/lang/String;)Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButton; + public static synthetic fun copy-ZZTyg0o$default (Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButton;Ljava/lang/String;ILjava/lang/Object;)Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButton; public fun equals (Ljava/lang/Object;)Z - public final fun getId-WMoZTbI ()Ljava/lang/String; + public final fun getId-pE_R3EE ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -16569,9 +16570,9 @@ public final class dev/inmo/tgbotapi/types/buttons/PreparedKeyboardButton$Compan public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public final class dev/inmo/tgbotapi/types/buttons/PreparedKeyboardId { - public static final field Companion Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardId$Companion; - public static final synthetic fun box-impl (Ljava/lang/String;)Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardId; +public final class dev/inmo/tgbotapi/types/buttons/PreparedKeyboardButtonId { + public static final field Companion Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButtonId$Companion; + public static final synthetic fun box-impl (Ljava/lang/String;)Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButtonId; public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String; public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z @@ -16584,18 +16585,18 @@ public final class dev/inmo/tgbotapi/types/buttons/PreparedKeyboardId { public final synthetic fun unbox-impl ()Ljava/lang/String; } -public final synthetic class dev/inmo/tgbotapi/types/buttons/PreparedKeyboardId$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardId$$serializer; +public final synthetic class dev/inmo/tgbotapi/types/buttons/PreparedKeyboardButtonId$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Ldev/inmo/tgbotapi/types/buttons/PreparedKeyboardButtonId$$serializer; public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun deserialize-KQHJBJs (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/String; + public final fun deserialize-w0ivoVs (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/String; public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public final fun serialize-JKX4Qt8 (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V + public final fun serialize-nK4aqxo (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/String;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } -public final class dev/inmo/tgbotapi/types/buttons/PreparedKeyboardId$Companion { +public final class dev/inmo/tgbotapi/types/buttons/PreparedKeyboardButtonId$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatIdentifier.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatIdentifier.kt index 2b76f630f7..580af1e004 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatIdentifier.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/ChatIdentifier.kt @@ -20,6 +20,7 @@ import kotlin.jvm.JvmInline const val internalTgAppLinksBeginning = "tg://" const val internalLinkBeginning = "https://t.me" const val internalUserLinkBeginning = "${internalTgAppLinksBeginning}user?id=" +const val managedBotNewBotUsername = "newbot" @Serializable(ChatIdentifierSerializer::class) @ClassCastsIncluded diff --git a/tgbotapi.utils/api/tgbotapi.utils.api b/tgbotapi.utils/api/tgbotapi.utils.api index e270a636d4..9f045dd0e8 100644 --- a/tgbotapi.utils/api/tgbotapi.utils.api +++ b/tgbotapi.utils/api/tgbotapi.utils.api @@ -3346,6 +3346,14 @@ public final class dev/inmo/tgbotapi/extensions/utils/formatting/LinksFormatting public static final fun makeUsernameStartattachLink (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; public static synthetic fun makeUsernameStartattachLink$default (Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; public static final fun makeUsernameStartattachPrefix (Ljava/lang/String;)Ljava/lang/String; + public static final fun managedBotCreationLink (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public static synthetic fun managedBotCreationLink$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; + public static final fun managedBotCreationLink-B8si7ts (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public static synthetic fun managedBotCreationLink-B8si7ts$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; + public static final fun managedBotCreationLink-hj--HSE (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public static synthetic fun managedBotCreationLink-hj--HSE$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; + public static final fun managedBotCreationLink-rmazcNg (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + public static synthetic fun managedBotCreationLink-rmazcNg$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/String; } public final class dev/inmo/tgbotapi/extensions/utils/formatting/ResendingTextFormattingKt { diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/formatting/LinksFormatting.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/formatting/LinksFormatting.kt index d1b7bd8a01..13003b0d92 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/formatting/LinksFormatting.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/formatting/LinksFormatting.kt @@ -114,6 +114,22 @@ val Chat.chatLink: String? return null } +fun managedBotCreationLink(managerBotUsername: String, suggestedUsername: String, suggestedName: String? = null): String { + return "${makeUsernameLink(managedBotNewBotUsername)}/$managerBotUsername/$suggestedUsername${suggestedName ?.let { "?$nameField=${it.encodeURLQueryComponent()}" } ?: ""}" +} + +fun managedBotCreationLink(managerBotUsername: String, suggestedUsername: Username, suggestedName: String? = null): String { + return managedBotCreationLink(managerBotUsername, suggestedUsername.withoutAt, suggestedName) +} + +fun managedBotCreationLink(managerBotUsername: Username, suggestedUsername: String, suggestedName: String? = null): String { + return managedBotCreationLink(managerBotUsername.withoutAt, suggestedUsername, suggestedName) +} + +fun managedBotCreationLink(managerBotUsername: Username, suggestedUsername: Username, suggestedName: String? = null): String { + return managedBotCreationLink(managerBotUsername.withoutAt, suggestedUsername.withoutAt, suggestedName) +} + private const val stickerSetAddingLinkPrefix = "$internalLinkBeginning/addstickers" val StickerSetName.stickerSetLink diff --git a/tgbotapi.utils/src/commonTest/kotlin/LinksFormattingKtTest.kt b/tgbotapi.utils/src/commonTest/kotlin/LinksFormattingKtTest.kt new file mode 100644 index 0000000000..d09aca0dcf --- /dev/null +++ b/tgbotapi.utils/src/commonTest/kotlin/LinksFormattingKtTest.kt @@ -0,0 +1,115 @@ +import dev.inmo.tgbotapi.extensions.utils.formatting.managedBotCreationLink +import dev.inmo.tgbotapi.types.Username +import io.ktor.http.encodeURLQueryComponent +import kotlin.test.Test +import kotlin.test.assertEquals + +class LinksFormattingKtTest { + + /** + * Tests for the `managedBotCreationLink` function. + * These tests verify that the function generates Telegram bot creation links correctly for various inputs. + */ + + @Test + fun testManagedBotCreationLinkWithStringsWithoutSuggestedName() { + // Arrange + val managerBotUsername = "managerBot" + val suggestedUsername = "testBot" + + // Act + val result = managedBotCreationLink(managerBotUsername, suggestedUsername) + + // Assert + val expected = "https://t.me/newbot/managerBot/testBot" + assertEquals(expected, result) + } + + @Test + fun testManagedBotCreationLinkWithStringsWithSuggestedName() { + // Arrange + val managerBotUsername = "managerBot" + val suggestedUsername = "testBot" + val suggestedName = "Test Bot".encodeURLQueryComponent() + + // Act + val result = managedBotCreationLink(managerBotUsername, suggestedUsername, suggestedName) + + // Assert + val expected = "https://t.me/newbot/managerBot/testBot?name=${suggestedName.encodeURLQueryComponent()}" + assertEquals(expected, result) + } + + @Test + fun testManagedBotCreationLinkWithUsernamesWithoutSuggestedName() { + // Arrange + val managerBotUsername = Username("@managerBot") + val suggestedUsername = Username("@testBot") + + // Act + val result = managedBotCreationLink(managerBotUsername, suggestedUsername) + + // Assert + val expected = "https://t.me/newbot/managerBot/testBot" + assertEquals(expected, result) + } + + @Test + fun testManagedBotCreationLinkWithUsernamesWithSuggestedName() { + // Arrange + val managerBotUsername = Username("@managerBot") + val suggestedUsername = Username("@testBot") + val suggestedName = "Test Bot" + + // Act + val result = managedBotCreationLink(managerBotUsername, suggestedUsername, suggestedName) + + // Assert + val expected = "https://t.me/newbot/managerBot/testBot?name=${suggestedName.encodeURLQueryComponent()}" + assertEquals(expected, result) + } + + @Test + fun testManagedBotCreationLinkMixedStringAndUsernameWithoutSuggestedName() { + // Arrange + val managerBotUsername = "managerBot" + val suggestedUsername = Username("@testBot") + + // Act + val result = managedBotCreationLink(managerBotUsername, suggestedUsername) + + // Assert + val expected = "https://t.me/newbot/managerBot/testBot" + assertEquals(expected, result) + } + + @Test + fun testManagedBotCreationLinkMixedUsernameAndStringWithSuggestedName() { + // Arrange + val managerBotUsername = Username("@managerBot") + val suggestedUsername = "testBot" + val suggestedName = "Test Bot" + + // Act + val result = managedBotCreationLink(managerBotUsername, suggestedUsername, suggestedName) + + // Assert + val expected = "https://t.me/newbot/managerBot/testBot?name=${suggestedName.encodeURLQueryComponent()}" + assertEquals(expected, result) + } + + @Test + fun testManagedBotCreationLinkWithSpecialCharactersInSuggestedName() { + // Arrange + val managerBotUsername = "managerBot" + val suggestedUsername = "testBot" + val suggestedName = "Test Bot & Co." + + // Act + val result = managedBotCreationLink(managerBotUsername, suggestedUsername, suggestedName) + + // Assert + val expected = "https://t.me/newbot/managerBot/testBot?name=${suggestedName.encodeURLQueryComponent()}" + assertEquals(expected, result) + } +} \ No newline at end of file