From 1708cad65477ba146adc43d9b8673af6cf95e31e Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 11 May 2025 21:13:58 +0600 Subject: [PATCH 01/16] start migration onto 25.0.0 --- InlineQueriesBot/build.gradle | 17 ++++++++--------- InlineQueriesBot/src/commonMain/kotlin/Bot.kt | 4 ++-- .../src/commonMain/kotlin/KeyboardsBot.kt | 9 +++++---- RandomFileSenderBot/build.gradle | 12 +++++++----- .../src/commonMain/kotlin/ResenderBot.kt | 14 +++++++------- .../src/commonMain/kotlin/StickerInfoBot.kt | 6 +++--- WebApp/build.gradle | 14 +++++++------- WebApp/src/jsMain/kotlin/main.kt | 5 +++-- WebApp/src/jvmMain/kotlin/WebAppServer.kt | 3 ++- gradle.properties | 8 ++++---- 10 files changed, 48 insertions(+), 44 deletions(-) diff --git a/InlineQueriesBot/build.gradle b/InlineQueriesBot/build.gradle index 7131a21..49b7e81 100644 --- a/InlineQueriesBot/build.gradle +++ b/InlineQueriesBot/build.gradle @@ -12,14 +12,16 @@ plugins { id "org.jetbrains.kotlin.multiplatform" } -apply plugin: 'application' - -mainClassName="InlineQueriesBotKt" - apply from: "$nativePartTemplate" kotlin { - jvm() + jvm { + binaries { + executable { + mainClass.set("InlineQueriesBotKt") + } + } + } sourceSets { commonMain { @@ -27,12 +29,9 @@ kotlin { implementation kotlin('stdlib') api "dev.inmo:tgbotapi:$telegram_bot_api_version" + api "io.ktor:ktor-client-logging:$ktor_version" } } } } -dependencies { - implementation 'io.ktor:ktor-client-logging-jvm:3.1.0' -} - diff --git a/InlineQueriesBot/src/commonMain/kotlin/Bot.kt b/InlineQueriesBot/src/commonMain/kotlin/Bot.kt index 18019d8..c89df0f 100644 --- a/InlineQueriesBot/src/commonMain/kotlin/Bot.kt +++ b/InlineQueriesBot/src/commonMain/kotlin/Bot.kt @@ -1,4 +1,4 @@ -import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.send.reply @@ -59,7 +59,7 @@ suspend fun doInlineQueriesBot(token: String) { reply(message, deepLink) } - allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { + allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) { println(it) } diff --git a/KeyboardsBot/KeyboardsBotLib/src/commonMain/kotlin/KeyboardsBot.kt b/KeyboardsBot/KeyboardsBotLib/src/commonMain/kotlin/KeyboardsBot.kt index 9781465..1be39cf 100644 --- a/KeyboardsBot/KeyboardsBotLib/src/commonMain/kotlin/KeyboardsBot.kt +++ b/KeyboardsBot/KeyboardsBotLib/src/commonMain/kotlin/KeyboardsBot.kt @@ -1,4 +1,4 @@ -import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.bot.getMe @@ -89,7 +89,7 @@ suspend fun activateKeyboardsBot( onCommandWithArgs("inline") { message, args -> val numberArgs = args.mapNotNull { it.toIntOrNull() } val numberOfPages = numberArgs.getOrNull(1) ?: numberArgs.firstOrNull() ?: 10 - val page = numberArgs.firstOrNull() ?.takeIf { numberArgs.size > 1 } ?.coerceAtLeast(1) ?: 1 + val page = numberArgs.firstOrNull()?.takeIf { numberArgs.size > 1 }?.coerceAtLeast(1) ?: 1 reply( message, replyMarkup = inlineKeyboard { @@ -138,7 +138,8 @@ suspend fun activateKeyboardsBot( onBaseInlineQuery { val page = it.query.takeWhile { it.isDigit() }.toIntOrNull() ?: return@onBaseInlineQuery - val count = it.query.removePrefix(page.toString()).dropWhile { !it.isDigit() }.takeWhile { it.isDigit() }.toIntOrNull() ?: return@onBaseInlineQuery + val count = it.query.removePrefix(page.toString()).dropWhile { !it.isDigit() }.takeWhile { it.isDigit() } + .toIntOrNull() ?: return@onBaseInlineQuery answer( it, @@ -170,7 +171,7 @@ suspend fun activateKeyboardsBot( setMyCommands(BotCommand("inline", "Creates message with pagination inline keyboard")) - allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { + allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) { println(it) } }.join() diff --git a/RandomFileSenderBot/build.gradle b/RandomFileSenderBot/build.gradle index d4e2f24..8626fe1 100644 --- a/RandomFileSenderBot/build.gradle +++ b/RandomFileSenderBot/build.gradle @@ -12,12 +12,14 @@ plugins { id "org.jetbrains.kotlin.multiplatform" } -apply plugin: 'application' - -mainClassName="RandomFileSenderBotKt" - kotlin { - jvm() + jvm { + binaries { + executable { + mainClass.set("RandomFileSenderBotKt") + } + } + } sourceSets { commonMain { diff --git a/ResenderBot/ResenderBotLib/src/commonMain/kotlin/ResenderBot.kt b/ResenderBot/ResenderBotLib/src/commonMain/kotlin/ResenderBot.kt index 69fb356..094ff67 100644 --- a/ResenderBot/ResenderBotLib/src/commonMain/kotlin/ResenderBot.kt +++ b/ResenderBot/ResenderBotLib/src/commonMain/kotlin/ResenderBot.kt @@ -1,4 +1,4 @@ -import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.send.withTypingAction import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat @@ -31,15 +31,15 @@ suspend fun activateResenderBot( it.content.createResend( chat.id, messageThreadId = it.threadIdOrNull, - replyParameters = it.replyInfo ?.messageMeta ?.let { meta -> - val quote = it.withContentOrNull<TextContent>() ?.content ?.quote + replyParameters = it.replyInfo?.messageMeta?.let { meta -> + val quote = it.withContentOrNull<TextContent>()?.content?.quote ReplyParameters( meta, - entities = quote ?.textSources ?: emptyList(), - quotePosition = quote ?.position + entities = quote?.textSources ?: emptyList(), + quotePosition = quote?.position ) }, - effectId = it.possiblyWithEffectMessageOrNull() ?.effectId + effectId = it.possiblyWithEffectMessageOrNull()?.effectId ) ) { it.forEach(print) @@ -49,7 +49,7 @@ suspend fun activateResenderBot( println("Answer info: $answer") } - allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { + allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) { println(it) } print(bot.getMe()) diff --git a/StickerInfoBot/StickerInfoBotLib/src/commonMain/kotlin/StickerInfoBot.kt b/StickerInfoBot/StickerInfoBotLib/src/commonMain/kotlin/StickerInfoBot.kt index da0db26..19aefe1 100644 --- a/StickerInfoBot/StickerInfoBotLib/src/commonMain/kotlin/StickerInfoBot.kt +++ b/StickerInfoBot/StickerInfoBotLib/src/commonMain/kotlin/StickerInfoBot.kt @@ -1,5 +1,5 @@ import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandler -import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.get.getCustomEmojiStickerOrNull @@ -55,7 +55,7 @@ suspend fun activateStickerInfoBot( withTypingAction(it.chat) { it.content.textSources.mapNotNull { if (it is CustomEmojiTextSource) { - getCustomEmojiStickerOrNull(it.customEmojiId) ?.stickerSetName + getCustomEmojiStickerOrNull(it.customEmojiId)?.stickerSetName } else { null } @@ -76,7 +76,7 @@ suspend fun activateStickerInfoBot( ) } - allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { + allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) { println(it) } }.join() diff --git a/WebApp/build.gradle b/WebApp/build.gradle index 8e04927..22cad8d 100644 --- a/WebApp/build.gradle +++ b/WebApp/build.gradle @@ -16,10 +16,14 @@ plugins { id "org.jetbrains.compose" version "$compose_version" } -apply plugin: 'application' - kotlin { - jvm() + jvm { + binaries { + executable { + mainClass.set("WebAppServerKt") + } + } + } js(IR) { browser() binaries.executable() @@ -53,10 +57,6 @@ kotlin { } } -application { - mainClassName = "WebAppServerKt" -} - tasks.getByName("compileKotlinJvm") .dependsOn(jsBrowserDistribution) tasks.getByName("compileKotlinJvm").configure { diff --git a/WebApp/src/jsMain/kotlin/main.kt b/WebApp/src/jsMain/kotlin/main.kt index fa1c3d0..60a10f8 100644 --- a/WebApp/src/jsMain/kotlin/main.kt +++ b/WebApp/src/jsMain/kotlin/main.kt @@ -1,4 +1,5 @@ import androidx.compose.runtime.* +import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions import dev.inmo.tgbotapi.types.CustomEmojiId import dev.inmo.tgbotapi.types.userIdField @@ -110,7 +111,7 @@ fun main() { userId ?.let { userId -> Button({ onClick { - scope.launchSafelyWithoutExceptions { + scope.launchLoggingDropExceptions { client.post("$baseUrl/setCustomEmoji") { parameter(userIdField, userId.long) setBody( @@ -130,7 +131,7 @@ fun main() { Button({ onClick { - scope.launchSafelyWithoutExceptions { + scope.launchLoggingDropExceptions { handleResult({ "Clicked" }) { client.post("${window.location.origin.removeSuffix("/")}/inline") { parameter(webAppQueryIdField, it) diff --git a/WebApp/src/jvmMain/kotlin/WebAppServer.kt b/WebApp/src/jvmMain/kotlin/WebAppServer.kt index d6ed35f..11b1f8a 100644 --- a/WebApp/src/jvmMain/kotlin/WebAppServer.kt +++ b/WebApp/src/jvmMain/kotlin/WebAppServer.kt @@ -1,4 +1,5 @@ import dev.inmo.kslog.common.* +import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.ktor.server.createKtorServer import dev.inmo.tgbotapi.extensions.api.answers.answerInlineQuery @@ -195,7 +196,7 @@ suspend fun main(vararg args: String) { BotCommand("reply_markup", "Use to get reply markup keyboard with web app trigger"), BotCommand("inline", "Use to get inline keyboard with web app trigger"), ) - allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { + allUpdatesFlow.subscribeLoggingDropExceptions(this) { println(it) } println(getMe()) diff --git a/gradle.properties b/gradle.properties index dc5e890..cfda679 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,9 +5,9 @@ org.gradle.jvmargs=-Xmx3148m kotlin.daemon.jvmargs=-Xmx3g -Xms500m -kotlin_version=2.1.10 -telegram_bot_api_version=23.2.0 -micro_utils_version=0.24.6 +kotlin_version=2.1.20 +telegram_bot_api_version=25.0.0-rc +micro_utils_version=0.25.3 serialization_version=1.8.0 -ktor_version=3.1.0 +ktor_version=3.1.1 compose_version=1.7.3 From 349517462e505319e7517bc0425fba3590736754 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 11 May 2025 21:28:32 +0600 Subject: [PATCH 02/16] start add tests for new business account features --- .../src/main/kotlin/BusinessConnectionsBot.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt index 2623cfc..c342678 100644 --- a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt +++ b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt @@ -15,11 +15,15 @@ import dev.inmo.tgbotapi.extensions.utils.ifAccessibleMessage import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage import dev.inmo.tgbotapi.extensions.utils.textContentOrNull import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.business_connection.BusinessConnection import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId +import dev.inmo.tgbotapi.types.chat.PrivateChat +import dev.inmo.tgbotapi.utils.toJson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.serialization.json.Json suspend fun main(args: Array<String>) { val botToken = args.first() @@ -34,6 +38,7 @@ suspend fun main(args: Array<String>) { } val businessConnectionsChats = mutableMapOf<BusinessConnectionId, ChatId>() + val chatsBusinessConnections = mutableMapOf<ChatId, BusinessConnectionId>() val businessConnectionsChatsMutex = Mutex() telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) { @@ -43,12 +48,14 @@ suspend fun main(args: Array<String>) { onBusinessConnectionEnabled { businessConnectionsChatsMutex.withLock { businessConnectionsChats[it.id] = it.userChatId + chatsBusinessConnections[it.userChatId] = it.id } send(it.userChatId, "Business connection ${it.businessConnectionId.string} has been enabled") } onBusinessConnectionDisabled { businessConnectionsChatsMutex.withLock { businessConnectionsChats.remove(it.id) + chatsBusinessConnections.remove(it.userChatId) } send(it.userChatId, "Business connection ${it.businessConnectionId.string} has been disabled") } @@ -98,5 +105,16 @@ suspend fun main(args: Array<String>) { } send(businessConnectionOwnerChat, "There are several removed messages in chat ${it.chat.id}: ${it.messageIds}") } + onCommand("get_business_account_info", initialFilter = { it.chat is PrivateChat }) { + val businessConnectionId = chatsBusinessConnections[it.chat.id] + val businessConnectionInfo = businessConnectionId ?.let { getBusinessConnection(it) } + reply(it) { + if (businessConnectionInfo == null) { + +"There is no business connection for current chat" + } else { + +(Json { prettyPrint = true; encodeDefaults = true }.encodeToString(businessConnectionInfo)) + } + } + } }.second.join() } \ No newline at end of file From b1bb11d826d2fee2e257df4aa91f52178f1760f2 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 18 May 2025 21:13:43 +0600 Subject: [PATCH 03/16] update nexus.inmo.dev maven repo --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ddbaec4..e9b413c 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ allprojects { } } - maven { url "https://nexus.inmo.dev/repository/maven-releases/" } + maven { url "https://proxy.nexus.inmo.dev/repository/maven-releases/" } } } From 9352bb00906ffa90f48a5321a6bd4ee86112f3ff Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 18 May 2025 22:04:03 +0600 Subject: [PATCH 04/16] add tests for reading and removing business messages --- .../src/main/kotlin/BusinessConnectionsBot.kt | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt index c342678..eb111ad 100644 --- a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt +++ b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt @@ -2,7 +2,10 @@ import dev.inmo.kslog.common.KSLog import dev.inmo.kslog.common.LogLevel import dev.inmo.kslog.common.defaultMessageFormatter import dev.inmo.kslog.common.setDefaultKSLog +import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.bot.getMe +import dev.inmo.tgbotapi.extensions.api.business.deleteBusinessMessages +import dev.inmo.tgbotapi.extensions.api.business.readBusinessMessage import dev.inmo.tgbotapi.extensions.api.chat.modify.pinChatMessage import dev.inmo.tgbotapi.extensions.api.chat.modify.unpinChatMessage import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection @@ -14,10 +17,15 @@ import dev.inmo.tgbotapi.extensions.utils.accessibleMessageOrNull import dev.inmo.tgbotapi.extensions.utils.ifAccessibleMessage import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage import dev.inmo.tgbotapi.extensions.utils.textContentOrNull +import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton +import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.MessageId +import dev.inmo.tgbotapi.types.RawChatId import dev.inmo.tgbotapi.types.business_connection.BusinessConnection import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId import dev.inmo.tgbotapi.types.chat.PrivateChat +import dev.inmo.tgbotapi.utils.row import dev.inmo.tgbotapi.utils.toJson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -78,7 +86,20 @@ suspend fun main(args: Array<String>) { if (businessContentMessage.sentByBusinessConnectionOwner) { reply(sent, "You have sent this message to the ${businessContentMessage.businessConnectionId.string} related chat") } else { - reply(sent, "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat") + reply( + to = sent, + text = "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat", + ) + send( + chatId = businessConnectionsChats[it.businessConnectionId] ?: return@ifBusinessContentMessage, + text = "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat", + replyMarkup = inlineKeyboard { + row { + dataButton("Read message", "read ${it.chat.id.chatId.long} ${it.messageId.long}") + dataButton("Delete message", "delete ${it.chat.id.chatId.long} ${it.messageId.long}") + } + } + ) } } } @@ -116,5 +137,37 @@ suspend fun main(args: Array<String>) { } } } + onMessageDataCallbackQuery(Regex("read \\d+ \\d+")) { + val (_, chatIdString, messageIdString) = it.data.split(" ") + val chatId = chatIdString.toLongOrNull() ?.let(::RawChatId) ?.let(::ChatId) ?: return@onMessageDataCallbackQuery + val messageId = messageIdString.toLongOrNull() ?.let(::MessageId) ?: return@onMessageDataCallbackQuery + val businessConnectionId = chatsBusinessConnections[it.message.chat.id] + + val readResponse = businessConnectionId ?.let { readBusinessMessage(it, chatId, messageId) } + answer( + it, + if (readResponse == null) { + "There is no business connection for current chat" + } else { + "Message has been read" + } + ) + } + onMessageDataCallbackQuery(Regex("delete \\d+ \\d+")) { + val (_, chatIdString, messageIdString) = it.data.split(" ") + val chatId = chatIdString.toLongOrNull() ?.let(::RawChatId) ?.let(::ChatId) ?: return@onMessageDataCallbackQuery + val messageId = messageIdString.toLongOrNull() ?.let(::MessageId) ?: return@onMessageDataCallbackQuery + val businessConnectionId = chatsBusinessConnections[it.message.chat.id] + + val readResponse = businessConnectionId ?.let { deleteBusinessMessages(it, listOf(messageId)) } + answer( + it, + if (readResponse == null) { + "There is no business connection for current chat" + } else { + "Message has been deleted" + } + ) + } }.second.join() } \ No newline at end of file From d203d48391a33f8b887ba01272e782193df3097d Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sat, 24 May 2025 22:32:10 +0600 Subject: [PATCH 05/16] add support of setBusinessAccountName --- .../src/main/kotlin/BusinessConnectionsBot.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt index eb111ad..5e90c11 100644 --- a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt +++ b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt @@ -6,6 +6,7 @@ import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.business.deleteBusinessMessages import dev.inmo.tgbotapi.extensions.api.business.readBusinessMessage +import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountName import dev.inmo.tgbotapi.extensions.api.chat.modify.pinChatMessage import dev.inmo.tgbotapi.extensions.api.chat.modify.unpinChatMessage import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection @@ -169,5 +170,24 @@ suspend fun main(args: Array<String>) { } ) } + onCommandWithArgs("set_business_account_name", initialFilter = { it.chat is PrivateChat }) { it, args -> + val firstName = args[0] + val secondName = args.getOrNull(1) + val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommandWithArgs + val set = runCatching { + setBusinessAccountName( + businessConnectionId, + firstName, + secondName + ) + }.getOrElse { false } + reply(it) { + if (set) { + +"Account name has been set" + } else { + +"Account name has not been set" + } + } + } }.second.join() } \ No newline at end of file From 7ce784d0a29c55ce8b98d08e67621e79e684a519 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 25 May 2025 13:01:22 +0600 Subject: [PATCH 06/16] add device storage sample and beckground/text color config in webapps --- WebApp/src/jsMain/kotlin/main.kt | 66 ++++++++++++++++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/WebApp/src/jsMain/kotlin/main.kt b/WebApp/src/jsMain/kotlin/main.kt index 60a10f8..b5206e7 100644 --- a/WebApp/src/jsMain/kotlin/main.kt +++ b/WebApp/src/jsMain/kotlin/main.kt @@ -13,6 +13,7 @@ import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackStyle import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackType import dev.inmo.tgbotapi.webapps.orientation.DeviceOrientationStartParams import dev.inmo.tgbotapi.webapps.popup.* +import dev.inmo.tgbotapi.webapps.storage.getWithResult import io.ktor.client.HttpClient import io.ktor.client.request.* import io.ktor.client.statement.bodyAsText @@ -24,9 +25,13 @@ import kotlinx.dom.appendElement import kotlinx.dom.appendText import kotlinx.serialization.json.Json import org.jetbrains.compose.web.attributes.InputType +import org.jetbrains.compose.web.attributes.placeholder import org.jetbrains.compose.web.css.DisplayStyle +import org.jetbrains.compose.web.css.Style +import org.jetbrains.compose.web.css.StyleSheet import org.jetbrains.compose.web.css.Color as ComposeColor import org.jetbrains.compose.web.css.backgroundColor +import org.jetbrains.compose.web.css.color import org.jetbrains.compose.web.css.display import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.Text @@ -40,6 +45,13 @@ fun HTMLElement.log(text: String) { appendElement("p", {}) } +private object RootStyleSheet : StyleSheet() { + val rootClass by style { + color(ComposeColor("var(--tg-theme-text-color)")) + backgroundColor(ComposeColor("var(--tg-theme-bg-color)")) + } +} + @OptIn(ExperimentalUnsignedTypes::class) fun main() { console.log("Web app started") @@ -47,6 +59,14 @@ fun main() { val baseUrl = window.location.origin.removeSuffix("/") renderComposable("root") { + Style(RootStyleSheet) + DisposableEffect(null) { + scopeElement.classList.add(RootStyleSheet.rootClass) + + onDispose { + scopeElement.classList.remove(RootStyleSheet.rootClass) + } + } val scope = rememberCoroutineScope() val isSafeState = remember { mutableStateOf<Boolean?>(null) } val logsState = remember { mutableStateListOf<Any?>() } @@ -558,6 +578,52 @@ fun main() { } P() + let { // DeviceStorage + val fieldKey = remember { mutableStateOf("") } + val fieldValue = remember { mutableStateOf("") } + val message = remember { mutableStateOf("") } + Div { + Text("Start type title of key. If value will be found in device storage, it will be shown in value input") + } + + Input(InputType.Text) { + placeholder("Key") + value(fieldKey.value) + onInput { + fieldKey.value = it.value + webApp.deviceStorage.getItem(it.value) { e, v -> + fieldValue.value = v ?: "" + if (v == null) { + message.value = "Value for key \"${it.value}\" has not been found" + } else { + message.value = "Value for key \"${it.value}\" has been found: \"$v\"" + } + } + } + } + Div { + Text("If you want to change value if typed key - just put it here") + } + Input(InputType.Text) { + placeholder("Value") + value(fieldValue.value) + onInput { + fieldValue.value = it.value + webApp.deviceStorage.setItem(fieldKey.value, it.value) { e, v -> + if (v == true) { + fieldValue.value = it.value + message.value = "Value \"${it.value}\" has been saved" + } + } + } + } + + if (message.value.isNotEmpty()) { + Div { Text(message.value) } + } + } + P() + EventType.values().forEach { eventType -> when (eventType) { EventType.AccelerometerChanged -> webApp.onAccelerometerChanged { /*logsState.add("AccelerometerChanged") /* see accelerometer block */ */ } diff --git a/gradle.properties b/gradle.properties index cfda679..32a639e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.daemon.jvmargs=-Xmx3g -Xms500m kotlin_version=2.1.20 -telegram_bot_api_version=25.0.0-rc +telegram_bot_api_version=25.0.0-rc2 micro_utils_version=0.25.3 serialization_version=1.8.0 ktor_version=3.1.1 From 92d1c7a402586d092518b6af37227e0ba08d1b1f Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 25 May 2025 13:49:02 +0600 Subject: [PATCH 07/16] add sections and update version of library --- WebApp/src/jsMain/kotlin/main.kt | 243 ++++++++++++++++++++----------- gradle.properties | 2 +- 2 files changed, 159 insertions(+), 86 deletions(-) diff --git a/WebApp/src/jsMain/kotlin/main.kt b/WebApp/src/jsMain/kotlin/main.kt index b5206e7..609850c 100644 --- a/WebApp/src/jsMain/kotlin/main.kt +++ b/WebApp/src/jsMain/kotlin/main.kt @@ -1,7 +1,5 @@ import androidx.compose.runtime.* import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions -import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions -import dev.inmo.tgbotapi.types.CustomEmojiId import dev.inmo.tgbotapi.types.userIdField import dev.inmo.tgbotapi.types.webAppQueryIdField import dev.inmo.tgbotapi.webapps.* @@ -13,7 +11,6 @@ import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackStyle import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackType import dev.inmo.tgbotapi.webapps.orientation.DeviceOrientationStartParams import dev.inmo.tgbotapi.webapps.popup.* -import dev.inmo.tgbotapi.webapps.storage.getWithResult import io.ktor.client.HttpClient import io.ktor.client.request.* import io.ktor.client.statement.bodyAsText @@ -26,13 +23,11 @@ import kotlinx.dom.appendText import kotlinx.serialization.json.Json import org.jetbrains.compose.web.attributes.InputType import org.jetbrains.compose.web.attributes.placeholder -import org.jetbrains.compose.web.css.DisplayStyle import org.jetbrains.compose.web.css.Style import org.jetbrains.compose.web.css.StyleSheet import org.jetbrains.compose.web.css.Color as ComposeColor import org.jetbrains.compose.web.css.backgroundColor import org.jetbrains.compose.web.css.color -import org.jetbrains.compose.web.css.display import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.Text import org.jetbrains.compose.web.renderComposable @@ -108,6 +103,7 @@ fun main() { P() Text("Chat from WebAppInitData: ${webApp.initDataUnsafe.chat}") + H3 { Text("Emoji status management") } val emojiStatusAccessState = remember { mutableStateOf(false) } webApp.onEmojiStatusAccessRequested { emojiStatusAccessState.value = it.isAllowed @@ -148,7 +144,9 @@ fun main() { } } } + P() + H3 { Text("Call server method with webAppQueryIdField") } Button({ onClick { scope.launchLoggingDropExceptions { @@ -166,10 +164,11 @@ fun main() { } P() + H3 { Text("User info") } Text("Allow to write in private messages: ${webApp.initDataUnsafe.user ?.allowsWriteToPM ?: "User unavailable"}") P() - Text("Alerts:") + H3 { Text("Alerts") } Button({ onClick { webApp.showPopup( @@ -207,8 +206,22 @@ fun main() { }) { Text("Alert") } + Button({ + onClick { + webApp.showConfirm( + "This is confirm message" + ) { + logsState.add( + "You have pressed \"${if (it) "Ok" else "Cancel"}\" in confirm" + ) + } + } + }) { + Text("Confirm") + } P() + H3 { Text("Write access callbacks") } Button({ onClick { webApp.requestWriteAccess() @@ -227,6 +240,7 @@ fun main() { } P() + H3 { Text("Request contact") } Button({ onClick { webApp.requestContact() @@ -241,24 +255,9 @@ fun main() { }) { Text("Request contact with callback") } - P() - - Button({ - onClick { - webApp.showConfirm( - "This is confirm message" - ) { - logsState.add( - "You have pressed \"${if (it) "Ok" else "Cancel"}\" in confirm" - ) - } - } - }) { - Text("Confirm") - } P() - + H3 { Text("Closing confirmation") } val isClosingConfirmationEnabledState = remember { mutableStateOf(webApp.isClosingConfirmationEnabled) } Button({ onClick { @@ -276,7 +275,7 @@ fun main() { } P() - + H3 { Text("Colors") } val headerColor = remember { mutableStateOf<Color.Hex>(Color.Hex("#000000")) } fun updateHeaderColor() { val (r, g, b) = Random.nextUBytes(3) @@ -301,7 +300,6 @@ fun main() { } P() - val backgroundColor = remember { mutableStateOf<Color.Hex>(Color.Hex("#000000")) } fun updateBackgroundColor() { val (r, g, b) = Random.nextUBytes(3) @@ -326,7 +324,6 @@ fun main() { } P() - val bottomBarColor = remember { mutableStateOf<Color.Hex>(Color.Hex("#000000")) } fun updateBottomBarColor() { val (r, g, b) = Random.nextUBytes(3) @@ -350,60 +347,6 @@ fun main() { } } - P() - - val storageTrigger = remember { mutableStateOf<List<Pair<CloudStorageKey, CloudStorageValue>>>(emptyList()) } - fun updateCloudStorage() { - webApp.cloudStorage.getAll { - it.onSuccess { - storageTrigger.value = it.toList().sortedBy { it.first.key } - } - } - } - key(storageTrigger.value) { - storageTrigger.value.forEach { (key, value) -> - val keyState = remember { mutableStateOf(key.key) } - val valueState = remember { mutableStateOf(value.value) } - Input(InputType.Text) { - value(key.key) - onInput { keyState.value = it.value } - } - Input(InputType.Text) { - value(value.value) - onInput { valueState.value = it.value } - } - Button({ - onClick { - if (key.key != keyState.value) { - webApp.cloudStorage.remove(key) - } - webApp.cloudStorage.set(keyState.value, valueState.value) - updateCloudStorage() - } - }) { - Text("Save") - } - } - let { // new element adding - val keyState = remember { mutableStateOf("") } - val valueState = remember { mutableStateOf("") } - Input(InputType.Text) { - onInput { keyState.value = it.value } - } - Input(InputType.Text) { - onInput { valueState.value = it.value } - } - Button({ - onClick { - webApp.cloudStorage.set(keyState.value, valueState.value) - updateCloudStorage() - } - }) { - Text("Save") - } - } - } - remember { webApp.apply { @@ -453,9 +396,10 @@ fun main() { } } } - P() - let { // Accelerometer + P() + let { + H3 { Text("Accelerometer") } val enabledState = remember { mutableStateOf(webApp.accelerometer.isStarted) } webApp.onAccelerometerStarted { enabledState.value = true } webApp.onAccelerometerStopped { enabledState.value = false } @@ -496,7 +440,8 @@ fun main() { } P() - let { // Gyroscope + let { + H3 { Text("Gyroscope") } val enabledState = remember { mutableStateOf(webApp.gyroscope.isStarted) } webApp.onGyroscopeStarted { enabledState.value = true } webApp.onGyroscopeStopped { enabledState.value = false } @@ -535,9 +480,10 @@ fun main() { Text("z: ${zState.value}") } } - P() - let { // DeviceOrientation + P() + let { + H3 { Text("Device Orientation") } val enabledState = remember { mutableStateOf(webApp.deviceOrientation.isStarted) } webApp.onDeviceOrientationStarted { enabledState.value = true } webApp.onDeviceOrientationStopped { enabledState.value = false } @@ -576,9 +522,64 @@ fun main() { Text("gamma: ${gammaState.value}") } } - P() + P() + H3 { Text("Cloud storage") } + val storageTrigger = remember { mutableStateOf<List<Pair<CloudStorageKey, CloudStorageValue>>>(emptyList()) } + fun updateCloudStorage() { + webApp.cloudStorage.getAll { + it.onSuccess { + storageTrigger.value = it.toList().sortedBy { it.first.key } + } + } + } + key(storageTrigger.value) { + storageTrigger.value.forEach { (key, value) -> + val keyState = remember { mutableStateOf(key.key) } + val valueState = remember { mutableStateOf(value.value) } + Input(InputType.Text) { + value(key.key) + onInput { keyState.value = it.value } + } + Input(InputType.Text) { + value(value.value) + onInput { valueState.value = it.value } + } + Button({ + onClick { + if (key.key != keyState.value) { + webApp.cloudStorage.remove(key) + } + webApp.cloudStorage.set(keyState.value, valueState.value) + updateCloudStorage() + } + }) { + Text("Save") + } + } + let { // new element adding + val keyState = remember { mutableStateOf("") } + val valueState = remember { mutableStateOf("") } + Input(InputType.Text) { + onInput { keyState.value = it.value } + } + Input(InputType.Text) { + onInput { valueState.value = it.value } + } + Button({ + onClick { + webApp.cloudStorage.set(keyState.value, valueState.value) + updateCloudStorage() + } + }) { + Text("Save") + } + } + } + + P() let { // DeviceStorage + H3 { Text("Device storage") } val fieldKey = remember { mutableStateOf("") } val fieldValue = remember { mutableStateOf("") } val message = remember { mutableStateOf("") } @@ -624,6 +625,78 @@ fun main() { } P() + let { // DeviceStorage + H3 { Text("Secure storage") } + val fieldKey = remember { mutableStateOf("") } + val fieldValue = remember { mutableStateOf("") } + val message = remember { mutableStateOf("") } + val restorableState = remember { mutableStateOf(false) } + Div { + Text("Start type title of key. If value will be found in device storage, it will be shown in value input") + } + + Input(InputType.Text) { + placeholder("Key") + value(fieldKey.value) + onInput { + fieldKey.value = it.value + webApp.secureStorage.getItem(it.value) { e, v, restorable -> + fieldValue.value = v ?: "" + restorableState.value = restorable == true + if (v == null) { + if (restorable == true) { + message.value = "Value for key \"${it.value}\" has not been found, but can be restored" + } else { + message.value = "Value for key \"${it.value}\" has not been found. Error: $e" + } + } else { + message.value = "Value for key \"${it.value}\" has been found: \"$v\"" + } + } + } + } + if (restorableState.value) { + Button({ + onClick { + webApp.secureStorage.restoreItem(fieldKey.value) { e, v -> + fieldValue.value = v ?: "" + if (v == null) { + message.value = "Value for key \"${fieldKey.value}\" has not been restored. Error: $e" + } else { + message.value = "Value for key \"${fieldKey.value}\" has been restored: \"$v\"" + } + } + } + }) { + Text("Restore") + } + } + Div { + Text("If you want to change value if typed key - just put it here") + } + Input(InputType.Text) { + placeholder("Value") + value(fieldValue.value) + onInput { + fieldValue.value = it.value + webApp.secureStorage.setItem(fieldKey.value, it.value) { e, v -> + if (v) { + fieldValue.value = it.value + message.value = "Value \"${it.value}\" has been saved" + } else { + message.value = "Value \"${it.value}\" has not been saved. Error: $e" + } + } + } + } + + if (message.value.isNotEmpty()) { + Div { Text(message.value) } + } + } + P() + + H3 { Text("Events") } EventType.values().forEach { eventType -> when (eventType) { EventType.AccelerometerChanged -> webApp.onAccelerometerChanged { /*logsState.add("AccelerometerChanged") /* see accelerometer block */ */ } diff --git a/gradle.properties b/gradle.properties index 32a639e..44428a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.daemon.jvmargs=-Xmx3g -Xms500m kotlin_version=2.1.20 -telegram_bot_api_version=25.0.0-rc2 +telegram_bot_api_version=25.0.0-rc3 micro_utils_version=0.25.3 serialization_version=1.8.0 ktor_version=3.1.1 From 36163d5619e2f368e798431354b8582082fba8ff Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 25 May 2025 19:30:51 +0600 Subject: [PATCH 08/16] add sample for set business account username --- .../src/main/kotlin/BusinessConnectionsBot.kt | 22 +++++++++++++++++++ gradle.properties | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt index 5e90c11..88135ea 100644 --- a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt +++ b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt @@ -7,6 +7,7 @@ import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.business.deleteBusinessMessages import dev.inmo.tgbotapi.extensions.api.business.readBusinessMessage import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountName +import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountUsername import dev.inmo.tgbotapi.extensions.api.chat.modify.pinChatMessage import dev.inmo.tgbotapi.extensions.api.chat.modify.unpinChatMessage import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection @@ -23,6 +24,7 @@ import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.MessageId import dev.inmo.tgbotapi.types.RawChatId +import dev.inmo.tgbotapi.types.Username import dev.inmo.tgbotapi.types.business_connection.BusinessConnection import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId import dev.inmo.tgbotapi.types.chat.PrivateChat @@ -189,5 +191,25 @@ suspend fun main(args: Array<String>) { } } } + onCommandWithArgs("set_business_account_username", initialFilter = { it.chat is PrivateChat }) { it, args -> + val username = args[0] + val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommandWithArgs + val set = runCatching { + setBusinessAccountUsername( + businessConnectionId, + username + ) + }.getOrElse { + it.printStackTrace() + false + } + reply(it) { + if (set) { + +"Account username has been set" + } else { + +"Account username has not been set" + } + } + } }.second.join() } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 44428a1..b35b66d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.daemon.jvmargs=-Xmx3g -Xms500m kotlin_version=2.1.20 -telegram_bot_api_version=25.0.0-rc3 +telegram_bot_api_version=25.0.0-rc5 micro_utils_version=0.25.3 serialization_version=1.8.0 ktor_version=3.1.1 From aee070c6c6b51a824e4080caf50b3cd9c631bbc1 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 25 May 2025 19:42:47 +0600 Subject: [PATCH 09/16] add check of set business account bio --- .../src/main/kotlin/BusinessConnectionsBot.kt | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt index 88135ea..a80807e 100644 --- a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt +++ b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt @@ -6,8 +6,10 @@ import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.business.deleteBusinessMessages import dev.inmo.tgbotapi.extensions.api.business.readBusinessMessage +import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountBio import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountName import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountUsername +import dev.inmo.tgbotapi.extensions.api.chat.get.getChat import dev.inmo.tgbotapi.extensions.api.chat.modify.pinChatMessage import dev.inmo.tgbotapi.extensions.api.chat.modify.unpinChatMessage import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection @@ -15,7 +17,7 @@ import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.* -import dev.inmo.tgbotapi.extensions.utils.accessibleMessageOrNull +import dev.inmo.tgbotapi.extensions.utils.extendedPrivateChatOrThrow import dev.inmo.tgbotapi.extensions.utils.ifAccessibleMessage import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage import dev.inmo.tgbotapi.extensions.utils.textContentOrNull @@ -24,14 +26,14 @@ import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.MessageId import dev.inmo.tgbotapi.types.RawChatId -import dev.inmo.tgbotapi.types.Username -import dev.inmo.tgbotapi.types.business_connection.BusinessConnection import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId import dev.inmo.tgbotapi.types.chat.PrivateChat +import dev.inmo.tgbotapi.utils.code import dev.inmo.tgbotapi.utils.row -import dev.inmo.tgbotapi.utils.toJson +import korlibs.time.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.serialization.json.Json @@ -211,5 +213,43 @@ suspend fun main(args: Array<String>) { } } } + onCommand("set_business_account_bio", requireOnlyCommandInMessage = false, initialFilter = { it.chat is PrivateChat }) { + val initialBio = getChat(it.chat).extendedPrivateChatOrThrow().bio + val bio = it.content.text.removePrefix("/set_business_account_bio").trim() + val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand + val set = runCatching { + setBusinessAccountBio( + businessConnectionId, + bio + ) + }.getOrElse { + it.printStackTrace() + false + } + reply(it) { + if (set) { + +"Account bio has been set. It will be reset within 15 seconds.\n\nInitial bio: " + code(initialBio) + } else { + +"Account bio has not been set" + } + } + delay(15.seconds) + val reset = runCatching { + setBusinessAccountBio( + businessConnectionId, + initialBio + ) + }.getOrElse { + it.printStackTrace() + false + } + reply(it) { + if (set) { + +"Account bio has been reset" + } else { + +"Account bio has not been set. Set it manually: " + code(initialBio) + } + } + } }.second.join() } \ No newline at end of file From 69eda92bc79feaacc9a7c2c784dfc2abf09ad09b Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 1 Jun 2025 19:24:01 +0600 Subject: [PATCH 10/16] add set business account profile photo tests --- .../src/main/kotlin/BusinessConnectionsBot.kt | 67 ++++++++++++++++++- gradle.properties | 2 +- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt index a80807e..787c71f 100644 --- a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt +++ b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt @@ -6,28 +6,38 @@ import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.business.deleteBusinessMessages import dev.inmo.tgbotapi.extensions.api.business.readBusinessMessage +import dev.inmo.tgbotapi.extensions.api.business.removeBusinessAccountProfilePhoto import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountBio import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountName +import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountProfilePhoto import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountUsername import dev.inmo.tgbotapi.extensions.api.chat.get.getChat import dev.inmo.tgbotapi.extensions.api.chat.modify.pinChatMessage import dev.inmo.tgbotapi.extensions.api.chat.modify.unpinChatMessage +import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.* +import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull import dev.inmo.tgbotapi.extensions.utils.extendedPrivateChatOrThrow import dev.inmo.tgbotapi.extensions.utils.ifAccessibleMessage import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage import dev.inmo.tgbotapi.extensions.utils.textContentOrNull import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard +import dev.inmo.tgbotapi.extensions.utils.withContentOrNull +import dev.inmo.tgbotapi.requests.abstracts.multipartFile +import dev.inmo.tgbotapi.requests.business_connection.InputProfilePhoto import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.MessageId import dev.inmo.tgbotapi.types.RawChatId import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId import dev.inmo.tgbotapi.types.chat.PrivateChat +import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage +import dev.inmo.tgbotapi.types.message.content.PhotoContent +import dev.inmo.tgbotapi.types.message.content.TextContent import dev.inmo.tgbotapi.utils.code import dev.inmo.tgbotapi.utils.row import korlibs.time.seconds @@ -244,12 +254,67 @@ suspend fun main(args: Array<String>) { false } reply(it) { - if (set) { + if (reset) { +"Account bio has been reset" } else { +"Account bio has not been set. Set it manually: " + code(initialBio) } } } + suspend fun handleSetProfilePhoto(it: CommonMessage<TextContent>, isPublic: Boolean) { + val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@handleSetProfilePhoto + val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<PhotoContent>() + if (replyTo == null) { + reply(it) { + +"Reply to photo for using of this command" + } + return@handleSetProfilePhoto + } + + val set = runCatching { + val file = downloadFileToTemp(replyTo.content) + setBusinessAccountProfilePhoto( + businessConnectionId, + InputProfilePhoto.Static( + file.multipartFile() + ), + isPublic = isPublic + ) + }.getOrElse { + it.printStackTrace() + false + } + reply(it) { + if (set) { + +"Account profile photo has been set. It will be reset within 15 seconds" + } else { + +"Account profile photo has not been set" + } + } + if (set == false) { return@handleSetProfilePhoto } + delay(15.seconds) + val reset = runCatching { + removeBusinessAccountProfilePhoto( + businessConnectionId, + isPublic = isPublic + ) + }.getOrElse { + it.printStackTrace() + false + } + reply(it) { + if (reset) { + +"Account profile photo has been reset" + } else { + +"Account profile photo has not been set. Set it manually" + } + } + } + onCommand("set_business_account_profile_photo", initialFilter = { it.chat is PrivateChat }) { + handleSetProfilePhoto(it, false) + } + onCommand("set_business_account_profile_photo_public", initialFilter = { it.chat is PrivateChat }) { + handleSetProfilePhoto(it, true) + } }.second.join() } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index b35b66d..e06dde8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.daemon.jvmargs=-Xmx3g -Xms500m kotlin_version=2.1.20 -telegram_bot_api_version=25.0.0-rc5 +telegram_bot_api_version=25.0.0-rc7 micro_utils_version=0.25.3 serialization_version=1.8.0 ktor_version=3.1.1 From ad90180defd9620eed02c84dd11682f8ba5eaf2a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 1 Jun 2025 20:23:19 +0600 Subject: [PATCH 11/16] fixes --- .../src/main/kotlin/BusinessConnectionsBot.kt | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt index 787c71f..41e67e7 100644 --- a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt +++ b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt @@ -2,6 +2,7 @@ import dev.inmo.kslog.common.KSLog import dev.inmo.kslog.common.LogLevel import dev.inmo.kslog.common.defaultMessageFormatter import dev.inmo.kslog.common.setDefaultKSLog +import dev.inmo.micro_utils.common.Percentage import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.business.deleteBusinessMessages @@ -18,6 +19,7 @@ import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.send +import dev.inmo.tgbotapi.extensions.api.stories.postStory import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.* import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull @@ -30,6 +32,7 @@ import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard import dev.inmo.tgbotapi.extensions.utils.withContentOrNull import dev.inmo.tgbotapi.requests.abstracts.multipartFile import dev.inmo.tgbotapi.requests.business_connection.InputProfilePhoto +import dev.inmo.tgbotapi.requests.stories.PostStory import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.MessageId import dev.inmo.tgbotapi.types.RawChatId @@ -38,6 +41,11 @@ import dev.inmo.tgbotapi.types.chat.PrivateChat import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage import dev.inmo.tgbotapi.types.message.content.PhotoContent import dev.inmo.tgbotapi.types.message.content.TextContent +import dev.inmo.tgbotapi.types.stories.InputStoryContent +import dev.inmo.tgbotapi.types.stories.StoryArea +import dev.inmo.tgbotapi.types.stories.StoryAreaPosition +import dev.inmo.tgbotapi.types.stories.StoryAreaType +import dev.inmo.tgbotapi.utils.botCommand import dev.inmo.tgbotapi.utils.code import dev.inmo.tgbotapi.utils.row import korlibs.time.seconds @@ -316,5 +324,54 @@ suspend fun main(args: Array<String>) { onCommand("set_business_account_profile_photo_public", initialFilter = { it.chat is PrivateChat }) { handleSetProfilePhoto(it, true) } + + onCommand("postStory", initialFilter = { it.chat is PrivateChat }) { + val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand + val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<PhotoContent>() + if (replyTo == null) { + reply(it) { + +"Reply to photo for using of this command" + } + return@onCommand + } + + val set = runCatching { + val file = downloadFileToTemp(replyTo.content) + postStory( + businessConnectionId, + InputStoryContent.Photo( + file.multipartFile() + ), + activePeriod = PostStory.ACTIVE_PERIOD_6_HOURS, + areas = listOf( + StoryArea( + StoryAreaPosition( + x = Percentage.of100(50.0), + y = Percentage.of100(50.0), + width = Percentage.of100(8.0), + height = Percentage.of100(8.0), + rotationAngle = 45.0, + cornerRadius = Percentage.of100(4.0), + ), + StoryAreaType.Link( + "https://github.com/InsanusMokrassar/TelegramBotAPI-examples/blob/master/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt" + ) + ) + ) + ) { + +"It is test of postStory :)" + } + }.getOrElse { + it.printStackTrace() + null + } + reply(it) { + if (set != null) { + +"Story has been posted. You may unpost it with " + botCommand("remove_story") + } else { + +"Story has not been posted" + } + } + } }.second.join() } \ No newline at end of file From b0554adb7fa3cd76ad9f7f9e7d574918680ad624 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 1 Jun 2025 21:19:37 +0600 Subject: [PATCH 12/16] add delete_story test --- .../src/main/kotlin/BusinessConnectionsBot.kt | 50 ++++++++++++++++--- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt index 41e67e7..a314495 100644 --- a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt +++ b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt @@ -19,6 +19,7 @@ import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.send +import dev.inmo.tgbotapi.extensions.api.stories.deleteStory import dev.inmo.tgbotapi.extensions.api.stories.postStory import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.* @@ -40,7 +41,10 @@ import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId import dev.inmo.tgbotapi.types.chat.PrivateChat import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage import dev.inmo.tgbotapi.types.message.content.PhotoContent +import dev.inmo.tgbotapi.types.message.content.StoryContent import dev.inmo.tgbotapi.types.message.content.TextContent +import dev.inmo.tgbotapi.types.message.content.VideoContent +import dev.inmo.tgbotapi.types.message.content.VisualMediaGroupPartContent import dev.inmo.tgbotapi.types.stories.InputStoryContent import dev.inmo.tgbotapi.types.stories.StoryArea import dev.inmo.tgbotapi.types.stories.StoryAreaPosition @@ -325,23 +329,28 @@ suspend fun main(args: Array<String>) { handleSetProfilePhoto(it, true) } - onCommand("postStory", initialFilter = { it.chat is PrivateChat }) { + onCommand("post_story", initialFilter = { it.chat is PrivateChat }) { val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand - val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<PhotoContent>() + val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<VisualMediaGroupPartContent>() if (replyTo == null) { reply(it) { - +"Reply to photo for using of this command" + +"Reply to photo or video for using of this command" } return@onCommand } - val set = runCatching { + val posted = runCatching { val file = downloadFileToTemp(replyTo.content) postStory( businessConnectionId, - InputStoryContent.Photo( - file.multipartFile() - ), + when (replyTo.content) { + is PhotoContent -> InputStoryContent.Photo( + file.multipartFile() + ) + is VideoContent -> InputStoryContent.Video( + file.multipartFile() + ) + }, activePeriod = PostStory.ACTIVE_PERIOD_6_HOURS, areas = listOf( StoryArea( @@ -366,12 +375,37 @@ suspend fun main(args: Array<String>) { null } reply(it) { - if (set != null) { + if (posted != null) { +"Story has been posted. You may unpost it with " + botCommand("remove_story") } else { +"Story has not been posted" } } } + + onCommand("delete_story", initialFilter = { it.chat is PrivateChat }) { + val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand + val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<StoryContent>() + if (replyTo == null) { + reply(it) { + +"Reply to photo or video for using of this command" + } + return@onCommand + } + + val deleted = runCatching { + deleteStory(businessConnectionId, replyTo.content.story.id) + }.getOrElse { + it.printStackTrace() + false + } + reply(it) { + if (deleted) { + +"Story has been deleted" + } else { + +"Story has not been deleted" + } + } + } }.second.join() } \ No newline at end of file From ad8fa92e87ec30840f8dd92857c9149089069e5a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 1 Jun 2025 22:15:57 +0600 Subject: [PATCH 13/16] protess on tests --- .../src/main/kotlin/BusinessConnectionsBot.kt | 64 +++++++++++++++++++ CustomBot/src/main/kotlin/CustomBot.kt | 2 + 2 files changed, 66 insertions(+) diff --git a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt index a314495..9b9e1f6 100644 --- a/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt +++ b/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt @@ -5,13 +5,17 @@ import dev.inmo.kslog.common.setDefaultKSLog import dev.inmo.micro_utils.common.Percentage import dev.inmo.tgbotapi.extensions.api.answers.answer import dev.inmo.tgbotapi.extensions.api.bot.getMe +import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountStarBalance import dev.inmo.tgbotapi.extensions.api.business.deleteBusinessMessages +import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGifts +import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow import dev.inmo.tgbotapi.extensions.api.business.readBusinessMessage import dev.inmo.tgbotapi.extensions.api.business.removeBusinessAccountProfilePhoto import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountBio import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountName import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountProfilePhoto import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountUsername +import dev.inmo.tgbotapi.extensions.api.business.transferBusinessAccountStars import dev.inmo.tgbotapi.extensions.api.chat.get.getChat import dev.inmo.tgbotapi.extensions.api.chat.modify.pinChatMessage import dev.inmo.tgbotapi.extensions.api.chat.modify.unpinChatMessage @@ -51,6 +55,7 @@ import dev.inmo.tgbotapi.types.stories.StoryAreaPosition import dev.inmo.tgbotapi.types.stories.StoryAreaType import dev.inmo.tgbotapi.utils.botCommand import dev.inmo.tgbotapi.utils.code +import dev.inmo.tgbotapi.utils.extensions.splitForText import dev.inmo.tgbotapi.utils.row import korlibs.time.seconds import kotlinx.coroutines.CoroutineScope @@ -235,6 +240,65 @@ suspend fun main(args: Array<String>) { } } } + onCommand("get_business_account_star_balance", initialFilter = { it.chat is PrivateChat }) { + val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand + val starAmount = runCatching { + getBusinessAccountStarBalance(businessConnectionId) + }.getOrElse { + it.printStackTrace() + null + } + reply(it) { + if (starAmount != null) { + +"Account stars amount: $starAmount" + } else { + +"Account stars amount has not been got" + } + } + } + onCommandWithArgs("transfer_business_account_stars", initialFilter = { it.chat is PrivateChat }) { it, args -> + val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommandWithArgs + val count = args.firstOrNull() ?.toIntOrNull() ?: reply(it) { + "Pass amount of stars to transfer to bot with command" + }.let { + return@onCommandWithArgs + } + val transferred = runCatching { + transferBusinessAccountStars(businessConnectionId, count) + }.getOrElse { + it.printStackTrace() + false + } + reply(it) { + if (transferred) { + +"Stars have been transferred" + } else { + +"Stars have not been transferred" + } + } + } + onCommand("get_business_account_gifts", initialFilter = { it.chat is PrivateChat }) { + val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand + val giftsFlow = runCatching { + getBusinessAccountGiftsFlow(businessConnectionId) + }.getOrElse { + it.printStackTrace() + null + } + if (giftsFlow == null) { + reply(it) { + +"Error in receiving of gifts" + } + } else { + giftsFlow.collect { giftsPage -> + giftsPage.gifts.joinToString { + it.toString() + }.splitForText().forEach { message -> + reply(it, message) + } + } + } + } onCommand("set_business_account_bio", requireOnlyCommandInMessage = false, initialFilter = { it.chat is PrivateChat }) { val initialBio = getChat(it.chat).extendedPrivateChatOrThrow().bio val bio = it.content.text.removePrefix("/set_business_account_bio").trim() diff --git a/CustomBot/src/main/kotlin/CustomBot.kt b/CustomBot/src/main/kotlin/CustomBot.kt index 6cf9334..4bf65a2 100644 --- a/CustomBot/src/main/kotlin/CustomBot.kt +++ b/CustomBot/src/main/kotlin/CustomBot.kt @@ -4,6 +4,7 @@ import dev.inmo.kslog.common.defaultMessageFormatter import dev.inmo.kslog.common.setDefaultKSLog import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.tgbotapi.extensions.api.bot.getMe +import dev.inmo.tgbotapi.extensions.api.chat.get.getChat import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextData import dev.inmo.tgbotapi.extensions.behaviour_builder.buildSubcontextInitialAction import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling @@ -65,6 +66,7 @@ suspend fun main(vararg args: String) { onCommand("start") { println(data.update) println(data.commonMessage) + println(getChat(it.chat)) } onCommand( From bc39279c6ce24c9f6b219484b599dd55f1919c3b Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sun, 1 Jun 2025 22:18:08 +0600 Subject: [PATCH 14/16] update telegram bot api version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e06dde8..f2a00ce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.daemon.jvmargs=-Xmx3g -Xms500m kotlin_version=2.1.20 -telegram_bot_api_version=25.0.0-rc7 +telegram_bot_api_version=25.0.0 micro_utils_version=0.25.3 serialization_version=1.8.0 ktor_version=3.1.1 From f05301336026e5f7fa895bd41310ff40c181453e Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sat, 14 Jun 2025 22:28:14 +0600 Subject: [PATCH 15/16] fix of build with adding google repository in webapp sample? --- WebApp/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/WebApp/build.gradle b/WebApp/build.gradle index 22cad8d..b87c70d 100644 --- a/WebApp/build.gradle +++ b/WebApp/build.gradle @@ -1,6 +1,7 @@ buildscript { repositories { mavenCentral() + google() } dependencies { From 73f05bbcd7aa433d92fa1fa647f66f79cd732e44 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com> Date: Sat, 14 Jun 2025 23:21:00 +0600 Subject: [PATCH 16/16] fix in repositories --- WebApp/build.gradle | 1 - build.gradle | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/WebApp/build.gradle b/WebApp/build.gradle index b87c70d..22cad8d 100644 --- a/WebApp/build.gradle +++ b/WebApp/build.gradle @@ -1,7 +1,6 @@ buildscript { repositories { mavenCentral() - google() } dependencies { diff --git a/build.gradle b/build.gradle index e9b413c..491d1cc 100644 --- a/build.gradle +++ b/build.gradle @@ -14,8 +14,8 @@ allprojects { nativePartTemplate = "${rootProject.projectDir.absolutePath}/native_template.gradle" } repositories { - mavenLocal() mavenCentral() + google() if (project.hasProperty("GITHUB_USER") && project.hasProperty("GITHUB_TOKEN")) { maven { url "https://maven.pkg.github.com/InsanusMokrassar/TelegramBotAPI" @@ -27,6 +27,7 @@ allprojects { } maven { url "https://proxy.nexus.inmo.dev/repository/maven-releases/" } + mavenLocal() } }