fix(examples): add bot selection and ephemeral photo captions

Limit channel-rights selection to private chats, preserve full chat
identifiers, offer bot selection, and use the replied-to sender ID.

Supply a caption when replacing ephemeral photo media so the next
caption edit starts from a captioned message.
This commit is contained in:
2026-10-08 19:30:19 +06:00
parent c0c5805705
commit 48b0e068d5
2 changed files with 22 additions and 11 deletions

View File

@@ -152,7 +152,10 @@ suspend fun main(vararg args: String) {
photoMessage.chat.id, photoMessage.chat.id,
receiverUserId, receiverUserId,
ephemeralMessageId, ephemeralMessageId,
TelegramMediaPhoto(photoBytes.asMultipartFile("ephemeral-photo.jpg")), TelegramMediaPhoto(
photoBytes.asMultipartFile("ephemeral-photo.jpg"),
text = "sample"
),
) )
editEphemeralMessageCaption( editEphemeralMessageCaption(
photoMessage.chat.id, photoMessage.chat.id,

View File

@@ -3,6 +3,7 @@ import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.micro_utils.coroutines.firstOf import dev.inmo.micro_utils.coroutines.firstOf
import dev.inmo.micro_utils.coroutines.runCatchingLogging
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.bot.ktor.telegramBot
@@ -22,6 +23,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitUserShare
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.onMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.from
import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat
import dev.inmo.tgbotapi.extensions.utils.types.buttons.* import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
@@ -32,30 +34,30 @@ import dev.inmo.tgbotapi.types.chat.PublicChat
import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
import dev.inmo.tgbotapi.types.chat.member.ChatCommonAdministratorRights import dev.inmo.tgbotapi.types.chat.member.ChatCommonAdministratorRights
import dev.inmo.tgbotapi.types.commands.BotCommandScope import dev.inmo.tgbotapi.types.commands.BotCommandScope
import dev.inmo.tgbotapi.types.message.abstracts.AccessibleMessage
import dev.inmo.tgbotapi.types.message.abstracts.ChatMessage import dev.inmo.tgbotapi.types.message.abstracts.ChatMessage
import dev.inmo.tgbotapi.types.request.RequestId import dev.inmo.tgbotapi.types.request.RequestId
import dev.inmo.tgbotapi.utils.* import dev.inmo.tgbotapi.utils.*
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.merge
/** States used by the private-chat flow that selects a channel and one of its users. */ /** States used by the private-chat flow that selects a channel and one of its users. */
sealed interface UserRetrievingStep : State { sealed interface UserRetrievingStep : State {
/** Waits for the user to share a channel in the private chat identified by [context]. */ /** Waits for the user to share a channel in the private chat identified by [context]. */
data class RetrievingChannelChatState( data class RetrievingChannelChatState(
override val context: ChatId override val context: IdChatIdentifier
) : UserRetrievingStep ) : UserRetrievingStep
/** Waits for a user selection after [channelId] has been shared. */ /** Waits for a user selection after [channelId] has been shared. */
data class RetrievingUserIdChatState( data class RetrievingUserIdChatState(
override val context: ChatId, override val context: IdChatIdentifier,
val channelId: ChatId val channelId: ChatId
) : UserRetrievingStep ) : UserRetrievingStep
/** Carries the selected [channelId] and [userId] to the administrator-rights keyboard step. */ /** Carries the selected [channelId] and [userId] to the administrator-rights keyboard step. */
data class RetrievingChatInfoDoneState( data class RetrievingChatInfoDoneState(
override val context: ChatId, override val context: IdChatIdentifier,
val channelId: ChatId, val channelId: ChatId,
val userId: UserId val userId: UserId
) : UserRetrievingStep ) : UserRetrievingStep
@@ -247,13 +249,14 @@ suspend fun main(args: Array<String>) {
} }
) { ) {
val replyMessage = it.replyTo val replyMessage = it.replyTo
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand val userInReply = replyMessage ?.from ?.id ?: return@onCommand
if (replyMessage is ChatMessage) { if (replyMessage is ChatMessage) {
val keyboard = buildGranularKeyboard(it.chat.id.toChatId(), userInReply)
reply( reply(
replyMessage, replyMessage,
"Manage keyboard:", "Manage keyboard:",
replyMarkup = buildGranularKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand replyMarkup = keyboard ?: return@onCommand
) )
} else { } else {
reply(it) { reply(it) {
@@ -485,7 +488,8 @@ suspend fun main(args: Array<String>) {
} }
} }
strictlyOn<UserRetrievingStep.RetrievingUserIdChatState> { state -> strictlyOn<UserRetrievingStep.RetrievingUserIdChatState> { state ->
val requestId = RequestId.random() val userRequestId = RequestId.random()
val botRequestId = RequestId.random()
send( send(
state.context, state.context,
replyMarkup = replyKeyboard( replyMarkup = replyKeyboard(
@@ -495,7 +499,11 @@ suspend fun main(args: Array<String>) {
row { row {
requestUserButton( requestUserButton(
"Choose user", "Choose user",
requestId = requestId requestId = userRequestId
)
requestBotButton(
"Choose bot",
requestId = botRequestId
) )
} }
} }
@@ -543,8 +551,8 @@ suspend fun main(args: Array<String>) {
null null
} }
onCommand("rights_in_channel") { onCommand("rights_in_channel", initialFilter = { it.chat.privateChatOrNull() != null }) {
startChain(UserRetrievingStep.RetrievingChannelChatState(it.chat.id.toChatId())) startChain(UserRetrievingStep.RetrievingChannelChatState(it.chat.id))
} }
setMyCommands( setMyCommands(