diff --git a/WebApp/src/commonMain/kotlin/PreparedSampleData.kt b/WebApp/src/commonMain/kotlin/PreparedSampleData.kt new file mode 100644 index 0000000..16d53d2 --- /dev/null +++ b/WebApp/src/commonMain/kotlin/PreparedSampleData.kt @@ -0,0 +1,6 @@ +import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButtonId +import dev.inmo.tgbotapi.types.request.RequestId +import kotlin.random.Random +import kotlin.random.nextUInt + +val preparedSampleKeyboardRequestId = RequestId(Random.nextUInt().toUShort()) diff --git a/WebApp/src/jsMain/kotlin/main.kt b/WebApp/src/jsMain/kotlin/main.kt index c312cb3..533dd5d 100644 --- a/WebApp/src/jsMain/kotlin/main.kt +++ b/WebApp/src/jsMain/kotlin/main.kt @@ -1,6 +1,7 @@ import androidx.compose.runtime.* import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions import dev.inmo.tgbotapi.types.CustomEmojiId +import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButtonId import dev.inmo.tgbotapi.types.userIdField import dev.inmo.tgbotapi.types.webAppQueryIdField import dev.inmo.tgbotapi.webapps.* @@ -17,6 +18,7 @@ import io.ktor.client.request.* import io.ktor.client.statement.bodyAsText import io.ktor.http.* import io.ktor.http.content.TextContent +import kotlinx.browser.document import kotlinx.browser.window import kotlinx.coroutines.* import kotlinx.dom.appendElement @@ -65,7 +67,12 @@ fun main() { } val scope = rememberCoroutineScope() val isSafeState = remember { mutableStateOf(null) } - val logsState = remember { mutableStateListOf() } + val logsState = remember { + mutableStateListOf( + window.location.href, + ) + } + val buttonIdState = remember { mutableStateOf(null) } // Text(window.location.href) // P() @@ -94,6 +101,30 @@ fun main() { ) } + LaunchedEffect(baseUrl) { + val response = client.post("$baseUrl/getPreparedKeyboardButtonId") { + setBody( + Json.encodeToString( + WebAppDataWrapper.serializer(), + WebAppDataWrapper(webApp.initData, webApp.initDataUnsafe.hash) + ) + ) + parameter(userIdField, webApp.initDataUnsafe.user ?.id ?.long ?: return@LaunchedEffect) + } + when (response.status) { + HttpStatusCode.OK -> { + val buttonId = response.bodyAsText() + buttonIdState.value = PreparedKeyboardButtonId(buttonId) + } + HttpStatusCode.NoContent -> { + buttonIdState.value = null + } + else -> { + logsState.add("Error while getting prepared keyboard button id: ${response.status}") + } + } + } + Text( when (isSafeState.value) { null -> "Checking safe state..." @@ -249,6 +280,23 @@ fun main() { Text("Confirm") } + P() + H3 { Text("Prepared keyboard button") } + val buttonIdValue = buttonIdState.value + if (buttonIdValue == null) { + Text("Ensure that you have called /prepareKeyboard in bot. If you did it, check logs of server") + } else { + Button({ + onClick { + webApp.requestChat(buttonIdValue) { + logsState.add("Chat have been received: $it") + } + } + }) { + Text("Prepared keyboard button") + } + } + P() H3 { Text("Write access callbacks") } Button({ @@ -396,11 +444,15 @@ fun main() { } mainButton.apply { setText("Main button") - setParams( - BottomButtonParams( - iconCustomEmojiId = CustomEmojiId("5370976574969486150") // 😏 + runCatching { + setParams( + BottomButtonParams( + iconCustomEmojiId = CustomEmojiId("5370976574969486150") // 😏 + ) ) - ) + }.onFailure { + logsState.add("Can't set params for main button: $it") + } onClick { logsState.add("Main button clicked") hapticFeedback.notificationOccurred( @@ -411,11 +463,15 @@ fun main() { } secondaryButton.apply { setText("Secondary button") - setParams( - BottomButtonParams( - iconCustomEmojiId = CustomEmojiId("5370763368497944736") // 😒 + runCatching { + setParams( + BottomButtonParams( + iconCustomEmojiId = CustomEmojiId("5370763368497944736") // 😒 + ) ) - ) + }.onFailure { + logsState.add("Can't set params for secondary button: $it") + } onClick { logsState.add("Secondary button clicked") hapticFeedback.notificationOccurred( diff --git a/WebApp/src/jvmMain/kotlin/WebAppServer.kt b/WebApp/src/jvmMain/kotlin/WebAppServer.kt index 11b1f8a..e89b6eb 100644 --- a/WebApp/src/jvmMain/kotlin/WebAppServer.kt +++ b/WebApp/src/jvmMain/kotlin/WebAppServer.kt @@ -5,6 +5,7 @@ import dev.inmo.micro_utils.ktor.server.createKtorServer import dev.inmo.tgbotapi.extensions.api.answers.answerInlineQuery import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands +import dev.inmo.tgbotapi.extensions.api.savePreparedKeyboardButton import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.api.set.setUserEmojiStatus @@ -21,6 +22,10 @@ import dev.inmo.tgbotapi.requests.answers.InlineQueryResultsButton import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent +import dev.inmo.tgbotapi.types.buttons.KeyboardButtonRequestManagedBot +import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButton +import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButtonId +import dev.inmo.tgbotapi.types.buttons.reply.requestManagedBotReplyButton import dev.inmo.tgbotapi.types.webapps.WebAppInfo import dev.inmo.tgbotapi.utils.* import io.ktor.http.* @@ -59,6 +64,7 @@ suspend fun main(vararg args: String) { val initiationLogger = KSLog("Initialization") val bot = telegramBot(telegramBotAPIUrlsKeeper) + val usersToButtonsMap = mutableMapOf() createKtorServer( "0.0.0.0", args.getOrNull(2) ?.toIntOrNull() ?: 8080 @@ -123,6 +129,24 @@ suspend fun main(vararg args: String) { call.respond(HttpStatusCode.OK, set.toString()) } + post("getPreparedKeyboardButtonId") { + val requestBody = call.receiveText() + val webAppCheckData = Json.decodeFromString(WebAppDataWrapper.serializer(), requestBody) + + val isSafe = telegramBotAPIUrlsKeeper.checkWebAppData(webAppCheckData.data, webAppCheckData.hash) + val rawUserId = call.parameters[userIdField] ?.toLongOrNull() ?.let(::RawChatId) ?: error("$userIdField should be presented as long value") + + if (isSafe) { + val buttonId = usersToButtonsMap[UserId(rawUserId)] + if (buttonId == null) { + call.respond(HttpStatusCode.NoContent) + } else { + call.respond(HttpStatusCode.OK, buttonId.string) + } + } else { + call.respond(HttpStatusCode.Forbidden) + } + } } }.start(false) @@ -171,6 +195,20 @@ suspend fun main(vararg args: String) { ) ) } + onCommand("prepareKeyboard") { + val preparedKeyboardButton = savePreparedKeyboardButton( + userId = it.chat.id.toChatId(), + button = requestManagedBotReplyButton( + text = "Saved sample button", + requestManagedBot = KeyboardButtonRequestManagedBot( + requestId = preparedSampleKeyboardRequestId, + suggestedName = "Saved sample button bot", + suggestedUsername = Username.prepare("saved_sample_button_bot") + ) + ) + ) + usersToButtonsMap[it.chat.id.toChatId()] = preparedKeyboardButton.id + } onBaseInlineQuery { answerInlineQuery( it,