Compare commits

...

7 Commits

Author SHA1 Message Date
95a619431c fix of guest bot sample 2026-05-30 15:34:18 +06:00
3b61976734 add guest bot 2026-05-23 00:00:36 +06:00
b7f50b514f update tgbotapi version 2026-05-20 18:06:50 +06:00
89e1eec53e Merge pull request #358 from InsanusMokrassar/33.0.0
33.0.0
2026-04-18 19:34:46 +06:00
00ab078891 build fix 2026-04-18 18:40:16 +06:00
bd71e642b2 upfill polls bot 2026-04-15 16:52:54 +06:00
514d9d68b8 add checks of save button and other fixes 2026-04-15 15:37:50 +06:00
13 changed files with 323 additions and 30 deletions

View File

@@ -213,6 +213,8 @@ suspend fun main(args: Array<String>) {
firstName, firstName,
secondName secondName
) )
}.map {
true
}.getOrElse { false } }.getOrElse { false }
reply(it) { reply(it) {
if (set) { if (set) {
@@ -230,6 +232,8 @@ suspend fun main(args: Array<String>) {
businessConnectionId, businessConnectionId,
username username
) )
}.map {
true
}.getOrElse { }.getOrElse {
it.printStackTrace() it.printStackTrace()
false false
@@ -267,6 +271,8 @@ suspend fun main(args: Array<String>) {
} }
val transferred = runCatching { val transferred = runCatching {
transferBusinessAccountStars(businessConnectionId, count) transferBusinessAccountStars(businessConnectionId, count)
}.map {
true
}.getOrElse { }.getOrElse {
it.printStackTrace() it.printStackTrace()
false false
@@ -310,6 +316,8 @@ suspend fun main(args: Array<String>) {
businessConnectionId, businessConnectionId,
bio bio
) )
}.map {
true
}.getOrElse { }.getOrElse {
it.printStackTrace() it.printStackTrace()
false false
@@ -327,6 +335,8 @@ suspend fun main(args: Array<String>) {
businessConnectionId, businessConnectionId,
initialBio initialBio
) )
}.map {
true
}.getOrElse { }.getOrElse {
it.printStackTrace() it.printStackTrace()
false false
@@ -358,6 +368,8 @@ suspend fun main(args: Array<String>) {
), ),
isPublic = isPublic isPublic = isPublic
) )
}.map {
true
}.getOrElse { }.getOrElse {
it.printStackTrace() it.printStackTrace()
false false
@@ -376,6 +388,8 @@ suspend fun main(args: Array<String>) {
businessConnectionId, businessConnectionId,
isPublic = isPublic isPublic = isPublic
) )
}.map {
true
}.getOrElse { }.getOrElse {
it.printStackTrace() it.printStackTrace()
false false
@@ -461,6 +475,8 @@ suspend fun main(args: Array<String>) {
val deleted = runCatching { val deleted = runCatching {
deleteStory(businessConnectionId, replyTo.content.story.id) deleteStory(businessConnectionId, replyTo.content.story.id)
}.map {
true
}.getOrElse { }.getOrElse {
it.printStackTrace() it.printStackTrace()
false false

View File

@@ -1,3 +1,4 @@
import dev.inmo.micro_utils.coroutines.runCatchingLogging
import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.chat.modify.setChatPhoto import dev.inmo.tgbotapi.extensions.api.chat.modify.setChatPhoto
@@ -15,17 +16,13 @@ suspend fun main(args: Array<String>) {
bot.buildBehaviourWithLongPolling(scope = CoroutineScope(Dispatchers.IO)) { bot.buildBehaviourWithLongPolling(scope = CoroutineScope(Dispatchers.IO)) {
onPhoto { onPhoto {
val bytes = downloadFile(it.content) val bytes = downloadFile(it.content)
runCatchingSafely { runCatchingLogging {
setChatPhoto( setChatPhoto(
it.chat.id, it.chat.id,
bytes.asMultipartFile("sample.jpg") bytes.asMultipartFile("sample.jpg")
) )
}.onSuccess { b -> }.onSuccess { _ ->
if (b) {
reply(it, "Done") reply(it, "Done")
} else {
reply(it, "Something went wrong")
}
}.onFailure { e -> }.onFailure { e ->
e.printStackTrace() e.printStackTrace()

View File

@@ -0,0 +1,21 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName="GuestQueryBotKt"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
}

View File

@@ -0,0 +1,107 @@
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.coroutines.subscribeLoggingDropExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGuestRequestMessage
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.guest_bot_caller_chat
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.guest_bot_caller_user
import dev.inmo.tgbotapi.extensions.utils.publicChatOrNull
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent
import dev.inmo.tgbotapi.types.InlineQueryId
import dev.inmo.tgbotapi.utils.buildEntities
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
/**
* This bot demonstrates guest mode support introduced in Telegram Bot API.
*
* Guest mode allows bots to receive messages and reply within chats they are not a member of.
* To enable guest queries for your bot, set `supports_guest_queries` in BotFather settings.
*
* Key concepts demonstrated:
* - `supportsGuestQueries` field on the bot itself (via getMe())
* - `GuestMessageUpdate` — a new update type for messages sent in guest mode
* - `guestQueryId` — unique ID used to answer the guest query
* - `guestBotCallerUser` — the user who initiated the guest query
* - `guestBotCallerChat` — the chat from which the guest query was sent
* - `answerGuestQuery` / `reply(GuestMessage, InlineQueryResult)` — how to respond
* - `SentGuestMessage` — the result returned after answering, containing the inline_message_id
*/
suspend fun main(vararg args: String) {
val botToken = args.first()
val isDebug = args.any { it == "debug" }
val isTestServer = args.any { it == "testServer" }
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
telegramBotWithBehaviourAndLongPolling(
botToken,
CoroutineScope(Dispatchers.IO),
testServer = isTestServer
) {
val me = getMe()
println("Bot info: $me")
// supportsGuestQueries reflects the supports_guest_queries field from the Telegram API
println("Supports guest queries: ${me.supportsGuestQueries}")
onGuestRequestMessage { message ->
println("=== Guest message received ===")
// guestQueryId is the unique ID required to answer this guest query
println(" guestQueryId: ${message.guestQueryId}")
println(" from: ${message.from}")
println(" chat: ${message.chat}")
println(" content: ${message.content}")
// reply() on GuestMessage calls answerGuestQuery internally and returns SentGuestMessage
val sentGuestMessage = reply(
message,
InlineQueryResultArticle(
id = InlineQueryId(message.guestQueryId.string),
title = "Guest reply",
inputMessageContent = InputTextMessageContent(
buildEntities {
+"Guest mode reply"
+"\nQuery ID: "
+message.guestQueryId.string
}
),
description = "Reply to guest query from ${message.from.firstName}"
)
)
// SentGuestMessage contains the inline_message_id of the sent reply
println(" SentGuestMessage: $sentGuestMessage")
}
onContentMessage {
println(it)
val userCalledGuestMessage = it.guest_bot_caller_user
val chatCalledGuestMessage = it.guest_bot_caller_chat ?.publicChatOrNull()
if (userCalledGuestMessage != null) {
reply(it) {
+"User called guest bot: ${userCalledGuestMessage.lastName + " " + userCalledGuestMessage.firstName}"
}
}
if (chatCalledGuestMessage != null) {
reply(it) {
+"Chat called guest bot: ${chatCalledGuestMessage.title}"
}
}
}
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
println(it)
}
}.second.join()
}

View File

@@ -70,21 +70,18 @@ suspend fun main(vararg args: String) {
draftMessagesChannel.send("Photo file have been downloaded. Start set my profile photo") draftMessagesChannel.send("Photo file have been downloaded. Start set my profile photo")
val setResult = setMyProfilePhoto( setMyProfilePhoto(
InputProfilePhoto.Static( InputProfilePhoto.Static(
photoFile.asMultipartFile() photoFile.asMultipartFile()
) )
) )
if (setResult) {
reply(commandMessage, "New photo have been set") reply(commandMessage, "New photo have been set")
} }
}
onCommand("removeMyProfilePhoto") { onCommand("removeMyProfilePhoto") {
runCatchingLogging { runCatchingLogging {
if (removeMyProfilePhoto()) { removeMyProfilePhoto()
reply(it, "Photo have been removed") reply(it, "Photo have been removed")
}
}.onFailure { e -> }.onFailure { e ->
e.printStackTrace() e.printStackTrace()
reply(it, "Something web wrong. See logs for details.") reply(it, "Something web wrong. See logs for details.")

View File

@@ -6,11 +6,16 @@ import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
import dev.inmo.tgbotapi.extensions.api.send.polls.sendQuizPoll import dev.inmo.tgbotapi.extensions.api.send.polls.sendQuizPoll
import dev.inmo.tgbotapi.extensions.api.send.polls.sendRegularPoll import dev.inmo.tgbotapi.extensions.api.send.polls.sendRegularPoll
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollAnswer import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollAnswer
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollOptionAdded
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollOptionDeleted
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollUpdates import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollUpdates
import dev.inmo.tgbotapi.extensions.utils.accessibleMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.customEmojiTextSourceOrNull import dev.inmo.tgbotapi.extensions.utils.customEmojiTextSourceOrNull
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgsSources import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgsSources
import dev.inmo.tgbotapi.types.BotCommand import dev.inmo.tgbotapi.types.BotCommand
@@ -105,7 +110,9 @@ suspend fun main(vararg args: String) {
} }
}, },
isAnonymous = false, isAnonymous = false,
replyParameters = ReplyParameters(it) replyParameters = ReplyParameters(it),
allowAddingOptions = true,
hideResultsUntilCloses = true,
) )
pollToChatMutex.withLock { pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
@@ -118,7 +125,12 @@ suspend fun main(vararg args: String) {
.firstOrNull { it.first.command == "quiz" } .firstOrNull { it.first.command == "quiz" }
?.second ?.second
?.firstNotNullOfOrNull { it.customEmojiTextSourceOrNull() } ?.firstNotNullOfOrNull { it.customEmojiTextSourceOrNull() }
val correctAnswer = Random.nextInt(10) val correctAnswer = mutableListOf<Int>()
(1 until Random.nextInt(9)).forEach {
val option = Random.nextInt(10)
if (correctAnswer.contains(option)) return@forEach
correctAnswer.add(option)
}
val sentPoll = sendQuizPoll( val sentPoll = sendQuizPoll(
it.chat.id, it.chat.id,
questionEntities = buildEntities { questionEntities = buildEntities {
@@ -127,7 +139,13 @@ suspend fun main(vararg args: String) {
customEmoji(customEmoji.customEmojiId, customEmoji.subsources) customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
} }
}, },
(1 .. 10).map { descriptionTextSources = buildEntities {
regular("Test quiz poll description:")
if (customEmoji != null) {
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
}
},
options = (1 .. 10).map {
InputPollOption { InputPollOption {
regular(it.toString()) + " " regular(it.toString()) + " "
if (customEmoji != null) { if (customEmoji != null) {
@@ -137,7 +155,11 @@ suspend fun main(vararg args: String) {
}, },
isAnonymous = false, isAnonymous = false,
replyParameters = ReplyParameters(it), replyParameters = ReplyParameters(it),
correctOptionId = correctAnswer, correctOptionIds = correctAnswer.sorted(),
allowsMultipleAnswers = correctAnswer.size > 1,
allowsRevoting = true,
shuffleOptions = true,
hideResultsUntilCloses = true,
explanationTextSources = buildEntities { explanationTextSources = buildEntities {
regular("Random solved it to be ") + underline((correctAnswer + 1).toString()) + " " regular("Random solved it to be ") + underline((correctAnswer + 1).toString()) + " "
if (customEmoji != null) { if (customEmoji != null) {
@@ -145,6 +167,7 @@ suspend fun main(vararg args: String) {
} }
} }
) )
println("Sent poll data: $sentPoll")
pollToChatMutex.withLock { pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
} }
@@ -168,6 +191,32 @@ suspend fun main(vararg args: String) {
} }
} }
onPollOptionAdded {
it.chatEvent.pollMessage ?.accessibleMessageOrNull() ?.let { pollMessage ->
reply(pollMessage) {
+"Poll option added: \n"
+it.chatEvent.optionTextSources
}
}
}
onPollOptionDeleted {
it.chatEvent.pollMessage ?.accessibleMessageOrNull() ?.let { pollMessage ->
reply(pollMessage) {
+"Poll option deleted: \n"
+it.chatEvent.optionTextSources
}
}
}
onContentMessage {
val replyPollOptionId = it.replyInfo ?.pollOptionId ?: return@onContentMessage
it.replyTo ?.accessibleMessageOrNull() ?.let { replied ->
reply(replied, pollOptionId = replyPollOptionId) {
+"Reply to poll option"
}
}
}
setMyCommands( setMyCommands(
BotCommand("anonymous", "Create anonymous regular poll"), BotCommand("anonymous", "Create anonymous regular poll"),
BotCommand("public", "Create non anonymous regular poll"), BotCommand("public", "Create non anonymous regular poll"),

View File

@@ -44,7 +44,11 @@ suspend fun main(args: Array<String>) {
onCommand("delete") { onCommand("delete") {
val deleted = runCatchingSafely { val deleted = runCatchingSafely {
deleteStickerSet(it.chat.stickerSetName()) deleteStickerSet(it.chat.stickerSetName())
}.getOrElse { false } }.map {
true
}.getOrElse {
false
}
if (deleted) { if (deleted) {
reply(it, "Deleted") reply(it, "Deleted")

View File

@@ -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())

View File

@@ -1,6 +1,7 @@
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
import dev.inmo.tgbotapi.types.CustomEmojiId import dev.inmo.tgbotapi.types.CustomEmojiId
import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButtonId
import dev.inmo.tgbotapi.types.userIdField import dev.inmo.tgbotapi.types.userIdField
import dev.inmo.tgbotapi.types.webAppQueryIdField import dev.inmo.tgbotapi.types.webAppQueryIdField
import dev.inmo.tgbotapi.webapps.* import dev.inmo.tgbotapi.webapps.*
@@ -17,6 +18,7 @@ import io.ktor.client.request.*
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import io.ktor.http.* import io.ktor.http.*
import io.ktor.http.content.TextContent import io.ktor.http.content.TextContent
import kotlinx.browser.document
import kotlinx.browser.window import kotlinx.browser.window
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.dom.appendElement import kotlinx.dom.appendElement
@@ -65,7 +67,12 @@ fun main() {
} }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val isSafeState = remember { mutableStateOf<Boolean?>(null) } val isSafeState = remember { mutableStateOf<Boolean?>(null) }
val logsState = remember { mutableStateListOf<Any?>() } val logsState = remember {
mutableStateListOf<Any?>(
window.location.href,
)
}
val buttonIdState = remember { mutableStateOf<PreparedKeyboardButtonId?>(null) }
// Text(window.location.href) // Text(window.location.href)
// P() // 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( Text(
when (isSafeState.value) { when (isSafeState.value) {
null -> "Checking safe state..." null -> "Checking safe state..."
@@ -249,6 +280,23 @@ fun main() {
Text("Confirm") 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() P()
H3 { Text("Write access callbacks") } H3 { Text("Write access callbacks") }
Button({ Button({
@@ -396,11 +444,15 @@ fun main() {
} }
mainButton.apply { mainButton.apply {
setText("Main button") setText("Main button")
runCatching {
setParams( setParams(
BottomButtonParams( BottomButtonParams(
iconCustomEmojiId = CustomEmojiId("5370976574969486150") // 😏 iconCustomEmojiId = CustomEmojiId("5370976574969486150") // 😏
) )
) )
}.onFailure {
logsState.add("Can't set params for main button: $it")
}
onClick { onClick {
logsState.add("Main button clicked") logsState.add("Main button clicked")
hapticFeedback.notificationOccurred( hapticFeedback.notificationOccurred(
@@ -411,11 +463,15 @@ fun main() {
} }
secondaryButton.apply { secondaryButton.apply {
setText("Secondary button") setText("Secondary button")
runCatching {
setParams( setParams(
BottomButtonParams( BottomButtonParams(
iconCustomEmojiId = CustomEmojiId("5370763368497944736") // 😒 iconCustomEmojiId = CustomEmojiId("5370763368497944736") // 😒
) )
) )
}.onFailure {
logsState.add("Can't set params for secondary button: $it")
}
onClick { onClick {
logsState.add("Secondary button clicked") logsState.add("Secondary button clicked")
hapticFeedback.notificationOccurred( hapticFeedback.notificationOccurred(

View File

@@ -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.answers.answerInlineQuery
import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands 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.reply
import dev.inmo.tgbotapi.extensions.api.send.send import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.api.set.setUserEmojiStatus 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.*
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent 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.types.webapps.WebAppInfo
import dev.inmo.tgbotapi.utils.* import dev.inmo.tgbotapi.utils.*
import io.ktor.http.* import io.ktor.http.*
@@ -59,6 +64,7 @@ suspend fun main(vararg args: String) {
val initiationLogger = KSLog("Initialization") val initiationLogger = KSLog("Initialization")
val bot = telegramBot(telegramBotAPIUrlsKeeper) val bot = telegramBot(telegramBotAPIUrlsKeeper)
val usersToButtonsMap = mutableMapOf<UserId, PreparedKeyboardButtonId>()
createKtorServer( createKtorServer(
"0.0.0.0", "0.0.0.0",
args.getOrNull(2) ?.toIntOrNull() ?: 8080 args.getOrNull(2) ?.toIntOrNull() ?: 8080
@@ -123,6 +129,24 @@ suspend fun main(vararg args: String) {
call.respond(HttpStatusCode.OK, set.toString()) 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) }.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 { onBaseInlineQuery {
answerInlineQuery( answerInlineQuery(
it, it,

View File

@@ -26,7 +26,7 @@ allprojects {
} }
} }
maven { url "https://proxy.nexus.inmo.dev/repository/maven-releases/" } // maven { url "https://proxy.nexus.inmo.dev/repository/maven-releases/" }
mavenLocal() mavenLocal()
} }
} }

View File

@@ -6,7 +6,7 @@ kotlin.daemon.jvmargs=-Xmx3g -Xms500m
kotlin_version=2.3.20 kotlin_version=2.3.20
telegram_bot_api_version=33.0.0 telegram_bot_api_version=34.0.0-t6
micro_utils_version=0.29.1 micro_utils_version=0.29.1
serialization_version=1.10.0 serialization_version=1.10.0
ktor_version=3.4.1 ktor_version=3.4.1

View File

@@ -71,3 +71,5 @@ include ":GiftsBot"
include ":TagsBot" include ":TagsBot"
include ":ManagedBotsBot" include ":ManagedBotsBot"
include ":GuestQueryBot"