From e70650d213cf09426ddae79e7ffb37872fdfd744 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 29 Jun 2026 18:02:05 +0600 Subject: [PATCH] add two bots for testing of new functionality --- JoinRequestQueriesBot/build.gradle | 21 + .../src/main/kotlin/JoinRequestQueriesBot.kt | 106 +++++ PollsBot/src/main/kotlin/PollsBot.kt | 28 ++ RichMessagesBot/build.gradle | 21 + .../src/main/kotlin/RichMessagesBot.kt | 408 ++++++++++++++++++ build.gradle | 2 +- gradle.properties | 2 +- settings.gradle | 4 + 8 files changed, 590 insertions(+), 2 deletions(-) create mode 100644 JoinRequestQueriesBot/build.gradle create mode 100644 JoinRequestQueriesBot/src/main/kotlin/JoinRequestQueriesBot.kt create mode 100644 RichMessagesBot/build.gradle create mode 100644 RichMessagesBot/src/main/kotlin/RichMessagesBot.kt diff --git a/JoinRequestQueriesBot/build.gradle b/JoinRequestQueriesBot/build.gradle new file mode 100644 index 0000000..e1ea06c --- /dev/null +++ b/JoinRequestQueriesBot/build.gradle @@ -0,0 +1,21 @@ +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'kotlin' +apply plugin: 'application' + +mainClassName="JoinRequestQueriesBotKt" + + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + + implementation "dev.inmo:tgbotapi:$telegram_bot_api_version" +} diff --git a/JoinRequestQueriesBot/src/main/kotlin/JoinRequestQueriesBot.kt b/JoinRequestQueriesBot/src/main/kotlin/JoinRequestQueriesBot.kt new file mode 100644 index 0000000..d9a8821 --- /dev/null +++ b/JoinRequestQueriesBot/src/main/kotlin/JoinRequestQueriesBot.kt @@ -0,0 +1,106 @@ +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.chat.get.getChat +import dev.inmo.tgbotapi.extensions.api.chat.invite_links.answerChatJoinRequestQuery +import dev.inmo.tgbotapi.extensions.api.chat.invite_links.sendChatJoinRequestWebApp +import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatJoinRequest +import dev.inmo.tgbotapi.requests.chat.invite_links.ChatJoinRequestQueryResult +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers + +/** + * This bot demonstrates Join Request Queries support introduced in Telegram Bot API 10.1. + * + * A "guard bot" of a chat receives chat join requests as queries and must process them with + * [answerChatJoinRequestQuery] or hand the user a Web App via [sendChatJoinRequestWebApp] + * (for example, to run a captcha / verification flow before deciding). + * + * Your bot must be set as the guard bot of the chat and must have `can_invite_users` rights to + * receive these requests. + * + * Key concepts demonstrated: + * - [dev.inmo.tgbotapi.types.chat.ExtendedBot.supportsJoinRequestQueries] — whether the bot itself + * supports join request queries (from getMe(), maps `User.supports_join_request_queries`) + * - [dev.inmo.tgbotapi.types.chat.ExtendedChat.guardBot] — the bot that processes join request + * queries in a chat (from getChat(), maps `ChatFullInfo.guard_bot`) + * - [dev.inmo.tgbotapi.types.chat.ChatJoinRequest.queryId] — the [dev.inmo.tgbotapi.types.ChatJoinRequestQueryId] + * present when the request arrives as a query to the guard bot + * - [answerChatJoinRequestQuery] with [ChatJoinRequestQueryResult] (Approve / Decline / Queue / Unknown) + * - [sendChatJoinRequestWebApp] — open a Web App to process the request + */ +suspend fun main(vararg args: String) { + val botToken = args.first() + val isDebug = args.any { it == "debug" } + val isTestServer = args.any { it == "testServer" } + // pass a https url as the second argument to demonstrate sendChatJoinRequestWebApp + val webAppUrl = args.getOrNull(1) ?.takeIf { it.startsWith("https://") } + + 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") + // supportsJoinRequestQueries reflects the supports_join_request_queries field from the Telegram API + println("Supports join request queries: ${me.supportsJoinRequestQueries}") + + onChatJoinRequest { request -> + println("=== Chat join request received ===") + println(" from: ${request.from}") + println(" chat: ${request.chat}") + println(" bio: ${request.bio}") + // queryId is non-null only when the request arrives as a query to this bot as the guard bot + println(" queryId: ${request.queryId}") + + // guardBot is the bot processing join request queries in this chat (admins-only field) + val guardBot = runCatching { getChat(request.chat).guardBot }.getOrNull() + println(" guardBot: $guardBot") + + val queryId = request.queryId + if (queryId == null) { + println(" -> request has no queryId, this bot is not the guard bot here") + return@onChatJoinRequest + } + + if (webAppUrl != null) { + // sendChatJoinRequestWebApp: hand the user a Web App (e.g. captcha) instead of deciding now + sendChatJoinRequestWebApp(request, webAppUrl) + println(" -> sent join request Web App: $webAppUrl") + return@onChatJoinRequest + } + + // answerChatJoinRequestQuery with one of the ChatJoinRequestQueryResult variants: + // Approve — allow the user to join + // Decline — disallow the user to join + // Queue — leave the decision to other administrators + // Unknown — any future result not yet known to the library + val result = if (request.bio.isNullOrBlank()) { + // no bio -> let other admins decide + ChatJoinRequestQueryResult.Queue + } else { + // has a bio -> approve + ChatJoinRequestQueryResult.Approve + } + answerChatJoinRequestQuery(request, result) + println(" -> answered with: ${result.name}") + } + + allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) { + println(it) + } + }.second.join() +} diff --git a/PollsBot/src/main/kotlin/PollsBot.kt b/PollsBot/src/main/kotlin/PollsBot.kt index c1fa8f9..e81bf20 100644 --- a/PollsBot/src/main/kotlin/PollsBot.kt +++ b/PollsBot/src/main/kotlin/PollsBot.kt @@ -25,6 +25,7 @@ import dev.inmo.tgbotapi.types.BotCommand import dev.inmo.tgbotapi.types.IdChatIdentifier import dev.inmo.tgbotapi.types.PollId import dev.inmo.tgbotapi.types.ReplyParameters +import dev.inmo.tgbotapi.types.media.TelegramMediaLink import dev.inmo.tgbotapi.types.media.TelegramMediaLocation import dev.inmo.tgbotapi.types.media.TelegramMediaSticker import dev.inmo.tgbotapi.types.media.TelegramMediaVenue @@ -55,6 +56,8 @@ import kotlin.random.Random * * `/members_only` — poll with `membersOnly = true` (new [dev.inmo.tgbotapi.types.polls.Poll.membersOnly] field) * * `/country_codes` — poll with `countryCodes` (new [dev.inmo.tgbotapi.types.polls.Poll.countryCodes] field) * * `/single_option` — poll with just 1 option (minimum options count decreased from 2 to 1) + * * `/link_poll` — poll whose options carry a [TelegramMediaLink] (InputMediaLink / Bot API 10.1 + * [dev.inmo.tgbotapi.types.Link]) as [dev.inmo.tgbotapi.types.media.InputPollOptionMedia] * * [onPollUpdates] prints [dev.inmo.tgbotapi.types.polls.Poll.media], [dev.inmo.tgbotapi.types.polls.Poll.membersOnly], * [dev.inmo.tgbotapi.types.polls.Poll.countryCodes], [QuizPoll.explanationMedia], and @@ -312,6 +315,30 @@ suspend fun main(vararg args: String) { } } + // Demonstrates TelegramMediaLink (InputMediaLink, Bot API 10.1) as poll option media. + // Link is the only new poll media type in 10.1 and is allowed only as InputPollOptionMedia. + onCommand("link_poll") { + val sentPoll = sendRegularPoll( + it.chat.id, + buildEntities { regular("Pick your favourite resource") }, + listOf( + // InputPollOptionMedia via TelegramMediaLink (InputMediaLink) + InputPollOption( + media = TelegramMediaLink("https://core.telegram.org/bots/api") + ) { regular("Bot API docs") }, + InputPollOption( + media = TelegramMediaLink("https://github.com/InsanusMokrassar/ktgbotapi") + ) { regular("ktgbotapi") }, + InputPollOption { regular("None of these") }, + ), + isAnonymous = false, + replyParameters = ReplyParameters(it) + ) + pollToChatMutex.withLock { + pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id + } + } + onPollAnswer { val chatId = pollToChat[it.pollId] ?: return@onPollAnswer @@ -380,6 +407,7 @@ suspend fun main(vararg args: String) { BotCommand("members_only", "Poll restricted to channel members only (membersOnly)"), BotCommand("country_codes", "Poll targeted to US, DE, JP users (countryCodes)"), BotCommand("single_option", "Poll with 1 option (minimum is now 1, not 2)"), + BotCommand("link_poll", "Poll with link media (TelegramMediaLink) on options"), ) allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) { diff --git a/RichMessagesBot/build.gradle b/RichMessagesBot/build.gradle new file mode 100644 index 0000000..24ddd63 --- /dev/null +++ b/RichMessagesBot/build.gradle @@ -0,0 +1,21 @@ +buildscript { + repositories { + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'kotlin' +apply plugin: 'application' + +mainClassName="RichMessagesBotKt" + + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + + implementation "dev.inmo:tgbotapi:$telegram_bot_api_version" +} diff --git a/RichMessagesBot/src/main/kotlin/RichMessagesBot.kt b/RichMessagesBot/src/main/kotlin/RichMessagesBot.kt new file mode 100644 index 0000000..4205ddd --- /dev/null +++ b/RichMessagesBot/src/main/kotlin/RichMessagesBot.kt @@ -0,0 +1,408 @@ +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.answers.answer +import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands +import dev.inmo.tgbotapi.extensions.api.send.reply +import dev.inmo.tgbotapi.extensions.api.send.send +import dev.inmo.tgbotapi.extensions.api.send.sendRichMessage +import dev.inmo.tgbotapi.extensions.api.send.sendRichMessageDraft +import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitRichMessage +import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onBaseInlineQuery +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onRichMessage +import dev.inmo.tgbotapi.extensions.utils.baseSentMessageUpdateOrNull +import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull +import dev.inmo.tgbotapi.extensions.utils.onlyRichMessageContentMessages +import dev.inmo.tgbotapi.requests.edit.text.EditChatMessageRichText +import dev.inmo.tgbotapi.types.BotCommand +import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle +import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputRichMessageContent +import dev.inmo.tgbotapi.types.InlineQueryId +import dev.inmo.tgbotapi.types.rich.InputRichMessageHTML +import dev.inmo.tgbotapi.types.rich.InputRichMessageMarkdown +import dev.inmo.tgbotapi.types.toChatId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.mapNotNull + +/** + * This bot demonstrates Rich Messages support introduced in Telegram Bot API 10.1. + * + * Rich messages allow bots to send highly structured text (and to stream AI-generated replies + * with seamless rich formatting). Telegram parses the provided HTML/Markdown into a structured + * [dev.inmo.tgbotapi.types.rich.RichMessage] made of [dev.inmo.tgbotapi.types.rich.RichBlock]s. + * + * Key concepts demonstrated: + * - [dev.inmo.tgbotapi.types.rich.InputRichMessage] — describes a rich message to send. Built only via + * the [InputRichMessageHTML] / [InputRichMessageMarkdown] factories (exactly one format must be used) + * - [sendRichMessage] — sendRichMessage method + * - [sendRichMessageDraft] — sendRichMessageDraft method: stream partial rich messages by draftId + * - [EditChatMessageRichText] — editMessageText with the new `rich_message` parameter + * - [onRichMessage] — trigger for incoming [dev.inmo.tgbotapi.types.message.content.RichMessageContent] + * (the new `rich_message` field of Message) + * - [waitRichMessage] — expectation for a rich message + * - [onlyRichMessageContentMessages] — flow filter keeping only rich message content + * - [InputRichMessageContent] — usable as InputMessageContent in inline query results + */ +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 + ) { + // sendRichMessage with HTML-formatted content + onCommand("rich_html") { + sendRichMessage( + it.chat.id, + // InputRichMessageHTML factory — content described using HTML formatting + InputRichMessageHTML( + """ + + bold text, bold text + italic text, italic text + underlined text, underlined text + strikethrough text, strikethrough text, strikethrough text + inline fixed-width code + marked text + subscript text + superscript text + spoiler + + Reference + inline URL + inline e-mail + inline phone number + inline mention of a user + in-document link + + + Referenced text + 👍 + 👍 + 22:45 tomorrow + x^2 + y^2 + + #hashtag ${'$'}USD +12345678901, card: 4242 4242 4242 4242, https://t.me t.me a@t.me /command @username + + all the text above was on the same line + +

Heading 1

+

Heading 2

+

Heading 3

+

Heading 4

+
Heading 5
+
Heading 6
+ + + +

Paragraph text

+
pre-formatted fixed-width code block
+
  print('pre-formatted fixed-width code block written in the Python programming language')
+ +
+ +
  1. ordered list item
+
  1. ordered list item
+
  1. ordered list item with explicit number
+ + +
Block quotation started
Block quotation continued
The last line of the block quotationThe Author
+ + + + + + + + +
Photo captionPhoto credit
+
Video caption
+
Audio caption
+
Voice note caption
+
Animation caption
+ + +
Map caption
+ + + + + + +
Header 1Header 2
Value 1Value 2
+ + + +
Table caption
ValueValue2Value3
Value4Value5Value6
Value7
+ +
TitleContent
+
TitleContent
+ E = mc^2 + """.trimIndent() + ) + ) + } + + // sendRichMessage with Markdown-formatted content + onCommand("rich_markdown") { + val sent = sendRichMessage( + it.chat.id, + // InputRichMessageMarkdown factory — content described using Markdown formatting + InputRichMessageMarkdown( + """ + **bold text** + __bold text__ + *italic text* + _italic text_ + ~~strikethrough text~~ + `inline fixed-width code` + ==marked text== + ||spoiler|| + + [inline URL](https://t.me/) + [inline e-mail](mailto:user@example.com) + [inline phone number](tel:+123456789) + [inline mention of a user](tg://user?id=123456789) + ![👍](tg://emoji?id=5368324170671202286) + ![22:45 tomorrow](tg://time?unix=1647531900&format=wDT) + ${'$'}x^2 + y^2$ + \#hashtag ${'$'}USD +12345678901, card: 4242 4242 4242 4242, https://t.me t.me a@t.me /command @username + all the text above was on the same line + + # Heading 1 + ## Heading 2 + ### Heading 3 + #### Heading 4 + ##### Heading 5 + ###### Heading 6 + + Paragraph text + + ```python + print('pre-formatted fixed-width code block written in the Python programming language') + ``` + + --- + + - unordered list item + * unordered list item + + unordered list item + + 1. ordered list item + 2. ordered list item + + - [ ] task list item + - [x] completed task list item + + >Block quotation started + > + >Block quotation continued on the next line + >Block quotation continued on the same line + > + >The last line of the block quotation + + ![](https://telegram.org/example/photo.jpg) + ![](https://telegram.org/example/video.mp4) + ![](https://telegram.org/example/audio.mp3) + ![](https://telegram.org/example/audio.ogg) + ![](https://telegram.org/example/animation.gif) + + ![](https://telegram.org/example/photo.jpg "Photo caption") + ![](https://telegram.org/example/video.mp4 "Video caption") + ![](https://telegram.org/example/audio.mp3 "Audio caption") + ![](https://telegram.org/example/audio.ogg "Voice note caption") + ![](https://telegram.org/example/animation.gif "Animation caption") + + | Header 1 | Header 2 | + |:---------|:--------:| + | left | center | + + Text with a reference[^id1] and another one[^id2]. + + [^id1]: Definition of the first footnote. + [^id2]: Definition of the second footnote. + + $${'$'}E = mc^2$$ + + ```math + E = mc^2 + ``` + + ## Example Nested Syntax Report for _Q1_ + Intro with underlined text, ==marked text==, and ${'$'}x^2 + y^2$. + **Bold _italic underlined italic bold italic_ bold** + In inline tags, nested **markdown** is parsed + >Quote with **bold text, ~~strikethrough, and spoiler~~**, plus [a link](https://t.me/). + + - List item with `code`, superscript, subscript, and a footnote[^note] + - Another item with **bold spoiler code** + - Another item with ~~strikethrough and inserted text~~ + + | Metric | Value | + |:-------|------:| + | Speed | **42** ms | + | Status | ready | + + [^note]: Footnote with _italic text_ and HTML underline. + + --- + + # Details blocks can contain Markdown content: + +
Summary with **bold text** + + ### Details heading + - List item with _italic text_ + - List item with spoiler + +
+ + # Collages and slideshows can contain Markdown media blocks: + + + + ![](https://telegram.org/example/photo.jpg) + ![](https://telegram.org/example/video.mp4) + + + + + + ![](https://telegram.org/example/photo.jpg) + ![](https://telegram.org/example/video.mp4) + + + """.trimIndent() + ) + ) + println(sent) + } + + // sendRichMessageDraft: stream partial rich messages sharing one draftId, then finalize + // with a full sendRichMessage. Emulates streaming of an AI-generated reply. + onCommand("rich_draft") { + val chatId = it.chat.id.toChatId() + val draftId = 1L + val parts = listOf( + "Thinking", + "Thinking about *rich* messages", + "Thinking about *rich* messages and how to _stream_ them" + ) + parts.forEach { part -> + sendRichMessageDraft(chatId, draftId, InputRichMessageMarkdown(part)) + delay(1000) + } + // finalize the streamed draft with the real message + sendRichMessage(chatId, InputRichMessageMarkdown("Done! Here is the *final* rich message.")) + } + + // EditChatMessageRichText: send a rich message, then edit it with new rich content + onCommand("rich_edit") { + val sent = sendRichMessage(it.chat.id, InputRichMessageMarkdown("*Original* rich message")) + delay(2000) + execute( + EditChatMessageRichText( + chatId = sent.chat.id, + messageId = sent.messageId, + // the new rich_message parameter of editMessageText + richMessage = InputRichMessageMarkdown("*Edited* rich message — now _updated_") + ) + ) + } + + // waitRichMessage expectation: wait for the user to send a rich message + onCommand("wait_rich") { + reply(it, "Send me a rich message now") + val richMessageContent = waitRichMessage().first() + reply( + it, + "Got rich message with ${richMessageContent.richMessage.blocks.size} block(s)" + ) + } + + // onRichMessage trigger: incoming messages carrying the new rich_message field + onRichMessage { message -> + val richMessage = message.content.richMessage + println("=== Rich message received ===") + println(" isRtl: ${richMessage.isRtl}") + println(" blocks: ${richMessage.blocks.size}") + richMessage.blocks.forEachIndexed { index, block -> + println(" [$index] $block") + } + reply(message, "Received a rich message with ${richMessage.blocks.size} block(s)") + execute( + message.content.createResend( + message.chat.id, + ) + ) + } + + // InputRichMessageContent as InputMessageContent of inline query results + onBaseInlineQuery { query -> + answer( + query, + results = listOf( + InlineQueryResultArticle( + InlineQueryId("rich_html"), + "Rich message (HTML)", + // InputRichMessageContent wraps an InputRichMessage and is a valid InputMessageContent + InputRichMessageContent( + InputRichMessageHTML("Bold rich message sent via inline query") + ), + description = "InputRichMessageContent built from HTML" + ), + InlineQueryResultArticle( + InlineQueryId("rich_markdown"), + "Rich message (Markdown)", + InputRichMessageContent( + InputRichMessageMarkdown("*Bold* rich message sent via inline query") + ), + description = "InputRichMessageContent built from Markdown" + ) + ), + cachedTime = 0 + ) + } + + // onlyRichMessageContentMessages: filter a Flow> down to rich message content + allUpdatesFlow + .mapNotNull { it.baseSentMessageUpdateOrNull() ?.data ?.contentMessageOrNull() } + .onlyRichMessageContentMessages() + .subscribeLoggingDropExceptions(scope = this) { richMessageContentMessage -> + println("[onlyRichMessageContentMessages] ${richMessageContentMessage.content.richMessage.blocks.size} blocks") + } + + setMyCommands( + BotCommand("rich_html", "Send a rich message described with HTML"), + BotCommand("rich_markdown", "Send a rich message described with Markdown"), + BotCommand("rich_draft", "Stream a rich message draft, then finalize it"), + BotCommand("rich_edit", "Send a rich message and edit it with new rich content"), + BotCommand("wait_rich", "Wait for you to send a rich message"), + ) + + allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) { + println(it) + } + }.second.join() +} diff --git a/build.gradle b/build.gradle index 2ec382f..1253035 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ allprojects { } } -// maven { url "https://proxy.nexus.inmo.dev/repository/maven-releases/" } + maven { url "https://nexus.inmo.dev/repository/maven-releases/" } mavenLocal() } } diff --git a/gradle.properties b/gradle.properties index 146e866..99950c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.daemon.jvmargs=-Xmx3g -Xms500m kotlin_version=2.3.20 -telegram_bot_api_version=34.0.0 +telegram_bot_api_version=35.0.0 micro_utils_version=0.29.1 serialization_version=1.10.0 ktor_version=3.4.1 diff --git a/settings.gradle b/settings.gradle index 828a821..caaa9a5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -77,3 +77,7 @@ include ":GuestQueryBot" include ":LivePhotosBot" include ":ChatManagementBot" + +include ":RichMessagesBot" + +include ":JoinRequestQueriesBot"