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
+
+
Paragraph text
+pre-formatted fixed-width code block+
print('pre-formatted fixed-width code block written in the Python programming language')
+
+ Block quotation started+ + +
Block quotation continued
The last line of the block quotationThe Author
+
+
+
+
+
+ 




| Header 1 | Header 2 |
|---|---|
| Value 1 | Value 2 |
| Value | Value2 | Value3 | |
| Value4 | Value5 | Value6 | |
| Value7 | |||
spoiler code