Compare commits

..

10 Commits

60 changed files with 959 additions and 1436 deletions

View File

@@ -1,39 +1,6 @@
# BoostsInfoBot
# UserChatShared
A bot that retrieves and displays the boost information for a chat.
## Functionality
On `/start`, the bot sends a reply keyboard with a *Request Channel* button. When the user selects
a channel, the bot calls `getUserChatBoosts` and replies with a formatted list of all active boosts
for that user in the selected chat, including the start and expiration dates of each boost.
## Arguments
| Position | Value | Sample | Description |
|----------|-------|--------|-------------|
| 1 | `BOT_TOKEN` | `1234567890:AABBccDDeeFF` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Sends the channel-request keyboard |
## Capabilities
- Reply keyboard with a `RequestChat` button configured for channels
- Retrieves user boost list via `getUserChatBoosts`
- Formats each boost with its add date and expiration date
- Handles `ChatShared` service messages to extract the target chat ID
- Runs via long polling
Showing info about boosts
## Launch

View File

@@ -1,55 +1,6 @@
# BusinessConnectionsBot
# BusinessConnectionBotBot
A comprehensive bot that demonstrates the Telegram Business Account API, including message
management, profile editing, star transfers, story posting, and gift listing.
## Functionality
The bot connects to a business account. When a business connection is established it maps the
business chat ID to the owner's personal chat so that management commands can be used in the
personal chat. Messages received via the business connection are forwarded to the owner.
Typing `PIN` or `UNPIN` in a business message pins or unpins it. A wide set of management commands
is available in the owner's PM.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/get_business_account_info` | Print account name, username, bio, and other details |
| `/set_business_account_name` | Set the account's first and last name (prompts for input) |
| `/set_business_account_username` | Set the account's username (prompts for input) |
| `/set_business_account_bio` | Set the account bio (auto-resets to the old value after 15 seconds) |
| `/set_business_account_profile_photo` | Set a private profile photo (send a photo in reply) |
| `/set_business_account_profile_photo_public` | Set a public profile photo (send a photo in reply) |
| `/get_business_account_star_balance` | Show the current star balance of the business account |
| `/transfer_business_account_stars` | Transfer stars from the business account to the bot |
| `/get_business_account_gifts` | List all gifts received by the business account |
| `/post_story` | Post a story with a link area (send a photo in reply) |
| `/delete_story` | Delete the most recently posted story |
## Capabilities
- `BusinessConnection` event handling: maps business chat IDs to personal owner chats
- Forwards business messages to the owner's PM
- PIN / UNPIN keyword detection to pin or unpin messages in the business chat
- Business message deletion tracking
- Mutex-protected concurrent access to the chat mapping
- Story creation with `InputStoryContentPhoto` and `StoryAreaTypeLink`
- Checklist content support
- Runs via long polling
When bot connected or disconnected to the business chat, it will notify this chat
## Launch

View File

@@ -3,6 +3,7 @@ import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.micro_utils.common.Percentage
import dev.inmo.tgbotapi.types.chat.PreviewBot
import dev.inmo.tgbotapi.extensions.api.answers.answer
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountStarBalance
@@ -27,7 +28,8 @@ import dev.inmo.tgbotapi.extensions.api.stories.deleteStory
import dev.inmo.tgbotapi.extensions.api.stories.postStory
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.chatContentMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.chatMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.extendedPrivateChatOrThrow
import dev.inmo.tgbotapi.extensions.utils.ifAccessibleMessage
import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage
@@ -44,13 +46,15 @@ import dev.inmo.tgbotapi.types.MessageId
import dev.inmo.tgbotapi.types.RawChatId
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
import dev.inmo.tgbotapi.types.chat.PrivateChat
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
import dev.inmo.tgbotapi.types.message.content.LivePhotoContent
import dev.inmo.tgbotapi.types.message.content.PhotoContent
import dev.inmo.tgbotapi.types.message.content.StoryContent
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.content.VideoContent
import dev.inmo.tgbotapi.types.message.content.VisualMediaGroupPartContent
import dev.inmo.tgbotapi.types.stories.InputStoryContent
import dev.inmo.tgbotapi.types.stories.InputStoryContent.*
import dev.inmo.tgbotapi.types.stories.StoryArea
import dev.inmo.tgbotapi.types.stories.StoryAreaPosition
import dev.inmo.tgbotapi.types.stories.StoryAreaType
@@ -120,6 +124,15 @@ suspend fun main(args: Array<String>) {
if (businessContentMessage.sentByBusinessConnectionOwner) {
reply(sent, "You have sent this message to the ${businessContentMessage.businessConnectionId.string} related chat")
} else {
// Since TG Bot API 9.0: business bots can reply to other bots in business context
// when bot-to-bot communication is enabled for both bots
if (businessContentMessage.from is PreviewBot) {
reply(
to = sent,
text = "Replying to bot ${businessContentMessage.from.firstName} in business context (bot-to-bot reply)",
)
return@ifBusinessContentMessage
}
reply(
to = sent,
text = "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat",
@@ -203,6 +216,8 @@ suspend fun main(args: Array<String>) {
}
)
}
// Since TG Bot API 9.0: the following account management commands no longer require
// the connected user to have a Telegram Premium subscription.
onCommandWithArgs("set_business_account_name", initialFilter = { it.chat is PrivateChat }) { it, args ->
val firstName = args[0]
val secondName = args.getOrNull(1)
@@ -349,9 +364,9 @@ suspend fun main(args: Array<String>) {
}
}
}
suspend fun handleSetProfilePhoto(it: CommonMessage<TextContent>, isPublic: Boolean) {
suspend fun handleSetProfilePhoto(it: ChatContentMessage<TextContent>, isPublic: Boolean) {
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@handleSetProfilePhoto
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<PhotoContent>()
val replyTo = it.replyTo ?.chatContentMessageOrNull() ?.withContentOrNull<PhotoContent>()
if (replyTo == null) {
reply(it) {
+"Reply to photo for using of this command"
@@ -411,7 +426,7 @@ suspend fun main(args: Array<String>) {
onCommand("post_story", initialFilter = { it.chat is PrivateChat }) {
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<VisualMediaGroupPartContent>()
val replyTo = it.replyTo ?.chatContentMessageOrNull() ?.withContentOrNull<VisualMediaGroupPartContent>()
if (replyTo == null) {
reply(it) {
+"Reply to photo or video for using of this command"
@@ -424,12 +439,16 @@ suspend fun main(args: Array<String>) {
postStory(
businessConnectionId,
when (replyTo.content) {
is PhotoContent -> InputStoryContent.Photo(
is PhotoContent -> Photo(
file.multipartFile()
)
is VideoContent -> InputStoryContent.Video(
is VideoContent -> Video(
file.multipartFile()
)
is LivePhotoContent -> Video(
file.multipartFile(),
isAnimation = true
)
},
activePeriod = PostStory.ACTIVE_PERIOD_6_HOURS,
areas = listOf(
@@ -465,7 +484,7 @@ suspend fun main(args: Array<String>) {
onCommand("delete_story", initialFilter = { it.chat is PrivateChat }) {
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<StoryContent>()
val replyTo = it.replyTo ?.chatContentMessageOrNull() ?.withContentOrNull<StoryContent>()
if (replyTo == null) {
reply(it) {
+"Reply to photo or video for using of this command"

View File

@@ -1,41 +1,9 @@
# ChatAvatarSetter
A bot that updates a group or channel's avatar using a photo sent to the bot.
## Functionality
When the bot receives a photo message, it downloads the highest-resolution version of the photo
and sets it as the chat photo for the chat the message was sent from. If the operation fails (e.g.,
due to missing admin rights), the bot sends an error message back to the user.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Downloads the largest available photo size from the incoming message
- Calls `setChatPhoto` to apply the downloaded image as the chat's avatar
- Returns a user-facing error message if the update fails
- Runs via long polling
This bot will set the chat avatar based on the image sent to bot
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```
> **Note:** The bot must be an administrator with *Change group info* permission in the target chat.

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="ChatManagementBotKt"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
}

View File

@@ -0,0 +1,164 @@
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.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.chat.get.getChatAdministrators
import dev.inmo.tgbotapi.extensions.api.chat.members.getChatMember
import dev.inmo.tgbotapi.extensions.api.send.deleteAllUserMessageReactions
import dev.inmo.tgbotapi.extensions.api.send.deleteUserMessageReaction
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.chatMemberGotRestrictedFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.chatMemberGotRestrictionsChangedFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatMemberUpdated
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.utils.plus
import dev.inmo.tgbotapi.extensions.utils.fromUserChatMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.fromUserMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.publicChatOrNull
import dev.inmo.tgbotapi.extensions.utils.requireRestrictedChatMember
import dev.inmo.tgbotapi.extensions.utils.restrictedMemberChatMemberOrNull
import dev.inmo.tgbotapi.extensions.utils.specialRightsChatMemberOrNull
import dev.inmo.tgbotapi.types.chat.CommonBot
import dev.inmo.tgbotapi.types.chat.ChatPermissions
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
/**
* This bot demonstrates Chat Management API features added in Bot API 9.x:
*
* 1. `can_react_to_messages` field in `ChatMemberRestricted` — printed when a member's
* restrictions are changed (requires the bot to be an admin in the group).
* `RestrictedMemberChatMember` also implements `ChatPermissions`, so the same field
* covers both `ChatMemberRestricted` and `ChatPermissions` from the spec.
*
* 2. `return_bots` in `getChatAdministrators` — `/admins` command lists all admins
* including other bots (retrieveOtherBots = true).
*
* 3. `deleteAllMessageReactions` — `/deleteallreactions` in reply to a message removes
* all reactions that the replied message's author has left across the entire chat.
*
* 4. `deleteMessageReaction` — `/deletereaction` in reply to a message removes the
* reaction the replied message's author placed on that specific message.
*
* 5. Seeing messages from other bots in groups — demonstrated via `canReadAllGroupMessages`
* from `getMe()`. When true (privacy mode off), the bot receives messages from other bots.
* All such messages are logged.
*
* Usage: pass the bot token as the first argument. Optional: `debug`, `testServer`.
*/
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: ${me.firstName} (@${me.username?.username})")
// Feature 5: canReadAllGroupMessages (can_read_all_group_messages) from getMe()
// When true, the bot receives messages from other bots in groups (privacy mode off)
println("canReadAllGroupMessages: ${me.canReadAllGroupMessages}")
// Feature 1: can_react_to_messages in ChatMemberRestricted and ChatPermissions
// RestrictedMemberChatMember implements ChatPermissions, so canReactToMessages
// appears in both types as required by the Telegram Bot API spec
onChatMemberUpdated(
initialFilter = chatMemberGotRestrictedFilter + chatMemberGotRestrictionsChangedFilter
) { update ->
val restricted = update.newChatMemberState.restrictedMemberChatMemberOrNull()
?: return@onChatMemberUpdated
println("Restriction update for ${update.member.firstName}:")
// canReactToMessages as ChatMemberRestricted field
println(" canReactToMessages (ChatMemberRestricted): ${restricted.canReactToMessages}")
// same field via ChatPermissions — RestrictedMemberChatMember : ChatPermissions
val permissions: ChatPermissions = restricted
println(" canReactToMessages (ChatPermissions): ${permissions.canReactToMessages}")
}
// Feature 1: can_react_to_messages in ChatMemberRestricted and ChatPermissions
// RestrictedMemberChatMember implements ChatPermissions, so canReactToMessages
// appears in both types as required by the Telegram Bot API spec
onCommand(
"retrieveRights"
) { message ->
val replyMessage = message.replyTo ?.fromUserChatMessageOrNull() ?: run {
reply(message) { +"This command works only in groups/supergroups/channels" }
return@onCommand
}
val chatMember = getChatMember(message.chat.id, replyMessage.user.id)
val chatPermissions = chatMember.restrictedMemberChatMemberOrNull()
val canReactToMessages = chatPermissions ?.canReactToMessages
reply(message) { +"Can react to messages: $canReactToMessages" }
}
// Feature 2: return_bots parameter in getChatAdministrators
// retrieveOtherBots = true corresponds to return_bots = true in the Telegram API
onCommand("admins") { message ->
val chat = message.chat.publicChatOrNull() ?: run {
reply(message) { +"This command works only in groups/supergroups/channels" }
return@onCommand
}
val admins = getChatAdministrators(chat, retrieveOtherBots = true)
reply(message) {
+"Administrators (retrieveOtherBots=true, includes bots):\n"
admins.forEach { admin ->
val kind = if (admin.user is CommonBot) "bot" else "user"
+"${admin.user.firstName} [$kind]\n"
}
}
}
// Feature 4: deleteMessageReaction
// Deletes a specific reaction by the replied message's author on that message
onCommand("deleteReaction") { message ->
val replied = message.replyTo ?.fromUserChatMessageOrNull() ?: run {
reply(message) { +"Reply to a message to remove that user's reaction from it" }
return@onCommand
}
deleteUserMessageReaction(replied, replied.user.id)
reply(message) { +"Deleted reaction by ${replied.user.firstName} on the replied message" }
}
// Feature 3: deleteAllMessageReactions
// Deletes all reactions that the replied message's author has left in this chat
onCommand("deleteAllReactions") { message ->
val replied = message.replyTo?.fromUserMessageOrNull() ?: run {
reply(message) { +"Reply to a message to clear all reactions of that user in this chat" }
return@onCommand
}
deleteAllUserMessageReactions(message.chat, replied.user.id)
reply(message) { +"Deleted all reactions by ${replied.user.firstName} in this chat" }
}
// Feature 5: messages from other bots in groups
// Bots with canReadAllGroupMessages=true (privacy mode off) receive messages from other bots.
// This handler logs all such messages to demonstrate the feature.
onContentMessage(
initialFilter = { msg ->
val user = msg.fromUserMessageOrNull()?.user
user is CommonBot && user.id != me.id
}
) { message ->
val sender = message.fromUserMessageOrNull()?.user
println("Message from other bot received (canReadAllGroupMessages=${me.canReadAllGroupMessages}):")
println(" sender: ${sender?.firstName} (@${(sender as? CommonBot)?.username?.username})")
println(" content: ${message.content}")
}
}.second.join()
}

View File

@@ -1,42 +0,0 @@
# ChecklistsBot
A bot that handles Telegram premium checklist messages and tracks task completion events.
## Functionality
Listens for messages containing a checklist. When a checklist message is received, the bot sends
a formatted reply showing all tasks with their completion status. It also reacts to task-level
events: when a task is marked as done or a new task is added to an existing checklist, the bot
sends an update reply referencing the affected task.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Detects `ChecklistContent` messages (Telegram Premium feature)
- Formats checklist tasks with ✅ (completed) and ⬜ (pending) indicators
- Handles `ChecklistTasksDone` events — replies when a task is marked complete
- Handles `ChecklistTasksAdded` events — replies when new tasks are appended
- Uses rich text message building for formatted output
- Runs via long polling
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

View File

@@ -4,7 +4,6 @@ import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.micro_utils.coroutines.runCatchingLogging
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
@@ -33,7 +32,7 @@ import dev.inmo.tgbotapi.extensions.utils.previewChannelDirectMessagesChatOrNull
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
import dev.inmo.tgbotapi.types.checklists.ChecklistTaskId
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
import dev.inmo.tgbotapi.types.message.content.ChecklistContent
import dev.inmo.tgbotapi.types.message.textsources.TextSourcesList
import dev.inmo.tgbotapi.types.update.abstracts.Update

View File

@@ -1,44 +1,6 @@
# CustomBot
A bot that demonstrates custom middleware, custom subcontext data, and several utility features
of the TelegramBotAPI library.
## Functionality
Shows how to attach a logging middleware to every API request and how to store arbitrary data in
a per-update subcontext. Additionally demonstrates retrieving and sending a user's profile audio
playlist and querying the bot's own star balance.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Retrieves the sender's profile audio files and sends them as an audio media group |
| `/additional_command` | Demo command that accesses and prints custom subcontext data |
| `/getMyStarBalance` | Queries and replies with the bot's current Telegram Star balance |
## Capabilities
- Custom request middleware that logs every outgoing API call
- Custom `BehaviourContext` subcontext with arbitrary stored data
- Profile audio retrieval via `getUserProfilePhotos`-style API for audio
- Audio media group sending (batched uploads)
- Star balance query via `getStarTransactions`
- Channel direct-message configuration tracking via `ChatBoostUpdated` events
- Runs via long polling
This bot basically have no any useful behaviour, but you may customize it as a playground
## Launch

View File

@@ -2,7 +2,7 @@ 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.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
@@ -22,7 +22,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPhoto
import dev.inmo.tgbotapi.types.media.AudioMediaGroupMemberTelegramMedia
import dev.inmo.tgbotapi.types.media.toTelegramMediaAudio
import dev.inmo.tgbotapi.types.media.toTelegramPaidMediaPhoto
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
import dev.inmo.tgbotapi.types.update.abstracts.Update
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -31,8 +31,8 @@ private var BehaviourContextData.update: Update?
get() = get("update") as? Update
set(value) = set("update", value)
private var BehaviourContextData.commonMessage: CommonMessage<*>?
get() = get("commonMessage") as? CommonMessage<*>
private var BehaviourContextData.commonMessage: ChatContentMessage<*>?
get() = get("commonMessage") as? ChatContentMessage<*>
set(value) = set("commonMessage", value)
/**
@@ -129,7 +129,7 @@ suspend fun main(vararg args: String) {
println(it.chatEvent)
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
println(it)
}
}.second.join()

View File

@@ -1,37 +1,6 @@
# DeepLinksBot
A bot that generates and handles Telegram deep links.
## Functionality
Generates a deep link to the bot when the user sends any text message. When a deep link is followed
(i.e., the `/start` command is received with a payload), the bot confirms what payload was received.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Displays a help/welcome message; also handles deep-link payloads |
## Capabilities
- Requires a registered bot username (validates that `getMe` returns a username)
- Generates a `t.me/<username>?start=<payload>` deep link from any incoming text message
- Subscribes to deep-link follow events with `waitDeepLinks()` and confirms the received payload
- Runs via long polling
This bot will send you deeplink to this bot when you send some text message and react on the `start` button
## Launch

View File

@@ -1,4 +1,4 @@
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
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.expectations.waitDeepLinks
@@ -36,7 +36,7 @@ suspend fun main(vararg args: String) {
onDeepLink { (it, deepLink) ->
reply(it, "Ok, I got deep link \"${deepLink}\" in trigger")
}
waitDeepLinks().subscribeSafelyWithoutExceptions(this) { (it, deepLink) ->
waitDeepLinks().subscribeLoggingDropExceptions(this) { (it, deepLink) ->
reply(it, "Ok, I got deep link \"${deepLink}\" in waiter")
println(triggersHolder.handleableCommandsHolder.handleable)
}

View File

@@ -1,37 +1,6 @@
# DraftsBot
# Drafts bot
A bot that demonstrates the message-draft flow API by progressively revealing text to the user.
## Functionality
On `/test_draft_flow`, the bot sends a series of draft text updates to the user, each building
on the previous one, before committing the final message. This illustrates how to use the draft
message API to stream partial content before finalising it.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/test_draft_flow` | Starts a draft-message flow that progressively reveals text |
## Capabilities
- Uses the `draftFlow` / `sendDraftMessage` API to emit incremental text updates
- Demonstrates the difference between draft (editable intermediate state) and final message
- Runs via long polling
The main purpose of this bot is just to answer "Oh, hi, " and add user mention here
## Launch

View File

@@ -3,7 +3,6 @@ import dev.inmo.kslog.common.w
import dev.inmo.micro_utils.coroutines.runCatchingLogging
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
@@ -75,8 +74,29 @@ suspend fun main(vararg args: String) {
send(it.chat, testText)
}
// sendMessageDraft now accepts empty text (length 0 is valid since TG Bot API 9.0)
// Useful to show a typing indicator without any text yet
onCommand("test_empty_draft") {
sendMessageDraftFlowWithTexts(
it.chat.id,
flow<String> {
emit("") // empty draft — clears / initializes typing indicator with no content
delay(1500L)
val step = 50
var currentLength = step
while (isActive && testText.length > currentLength) {
delay(500L)
emit(testText.take(currentLength))
currentLength += step
}
},
)
send(it.chat, testText)
}
setMyCommands(
BotCommand("test_draft_flow", "Start draft testing with flow"),
BotCommand("test_empty_draft", "Draft starting from empty text (TG Bot API 9.0)"),
scope = BotCommandScope.AllGroupChats
)
allUpdatesFlow.subscribeLoggingDropExceptions(this) {

View File

@@ -1,41 +1,7 @@
# FSMBot
# FSM
A demonstration of the Finite State Machine (FSM) pattern provided by the
[MicroUtils](https://github.com/InsanusMokrassar/MicroUtils) library.
## Functionality
Implements a simple two-state FSM. After `/start` is sent, the bot enters
`ExpectContentOrStopState` and re-sends every message it receives back to the user.
This continues until the user sends `/stop`, at which point the FSM transitions to
`StopState` and content forwarding ends.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Starts the FSM loop — bot begins echoing content back to the user |
| `/stop` | Ends the FSM loop — bot stops echoing |
## Capabilities
- Two-state FSM: `ExpectContentOrStopState``StopState`
- `ExpectContentOrStopState` uses `expectContentOrCommands()` to filter messages
- Erroneous FSM states are caught and handled gracefully
- Runs via long polling
This bot contains an example of working with FSM included in project
[MicroUtils](https://github.com/InsanusMokrassar/MicroUtils)
## Launch

View File

@@ -1,5 +1,5 @@
import dev.inmo.micro_utils.coroutines.awaitFirst
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitAnyContentMessage
@@ -13,7 +13,7 @@ import dev.inmo.tgbotapi.extensions.utils.extensions.sameThread
import dev.inmo.tgbotapi.extensions.utils.textContentOrNull
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.utils.botCommand
import dev.inmo.tgbotapi.utils.firstOf
@@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
sealed interface BotState : State
data class ExpectContentOrStopState(override val context: IdChatIdentifier, val sourceMessage: CommonMessage<TextContent>) : BotState
data class ExpectContentOrStopState(override val context: IdChatIdentifier, val sourceMessage: ChatContentMessage<TextContent>) : BotState
data class StopState(override val context: IdChatIdentifier) : BotState
suspend fun main(args: Array<String>) {
@@ -97,7 +97,7 @@ suspend fun main(args: Array<String>) {
startChain(ExpectContentOrStopState(it.chat.id, it.withContentOrNull() ?: return@onContentMessage))
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
println(it)
}
}.second.join()

View File

@@ -1,48 +1,9 @@
# FilesLoaderBot
A bot that downloads any media file sent to it and then re-uploads it back to the chat.
## Functionality
For every message containing a file (photo, video, audio, document, sticker, animation, voice,
video note, etc.), the bot downloads the file to a local directory, then sends the file back to
the chat. Media groups are expanded and each file is re-sent individually. While processing, the
bot sends an appropriate "upload" chat action (e.g., *uploading video*, *uploading photo*).
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
| 2 *(optional)* | `/path/to/dir` | Directory where files are saved (defaults to `/tmp/`) |
Optional flags (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Sends a usage instruction message |
## Capabilities
- Supports all Telegram media types: photos, videos, audio, documents, stickers, animations, voice messages, video notes
- Handles media groups by iterating over each item and re-uploading it individually
- Sends contextually appropriate chat actions during upload (typing, upload_video, upload_audio, etc.)
- Logs the local file path after each download
- Runs via long polling
This bot will download incoming files
## Launch
```bash
# Default directory (/tmp/)
../gradlew run --args="BOT_TOKEN"
# Custom directory
../gradlew run --args="BOT_TOKEN /path/to/save/dir"
../gradlew run --args="BOT_TOKEN[ optional/folder/path]"
```

View File

@@ -1,4 +1,4 @@
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.tgbotapi.extensions.api.files.downloadFile
import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp
import dev.inmo.tgbotapi.extensions.api.get.getFileAdditionalInfo
@@ -10,6 +10,7 @@ import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile
import dev.inmo.tgbotapi.types.actions.*
import dev.inmo.tgbotapi.types.media.TelegramMediaAudio
import dev.inmo.tgbotapi.types.media.TelegramMediaDocument
import dev.inmo.tgbotapi.types.media.TelegramMediaLivePhoto
import dev.inmo.tgbotapi.types.media.TelegramMediaPhoto
import dev.inmo.tgbotapi.types.media.TelegramMediaVideo
import dev.inmo.tgbotapi.types.message.content.*
@@ -46,6 +47,7 @@ suspend fun main(args: Array<String>) {
val action = when (content) {
is PhotoContent -> UploadPhotoAction
is AnimationContent,
is LivePhotoContent,
is VideoContent -> UploadVideoAction
is StickerContent -> ChooseStickerAction
is MediaGroupContent<*> -> UploadPhotoAction
@@ -74,7 +76,7 @@ suspend fun main(args: Array<String>) {
)
is MediaGroupContent<*> -> replyWithMediaGroup(
it,
content.group.map {
content.group.mapNotNull {
when (val innerContent = it.content) {
is AudioContent -> TelegramMediaAudio(
downloadFileToTemp(innerContent.media).asMultipartFile()
@@ -88,6 +90,10 @@ suspend fun main(args: Array<String>) {
is VideoContent -> TelegramMediaVideo(
downloadFileToTemp(innerContent.media).asMultipartFile()
)
is LivePhotoContent -> TelegramMediaLivePhoto(
downloadFileToTemp(innerContent.media).asMultipartFile(),
innerContent.media.photo ?.fileId ?: return@mapNotNull null
)
}
}
)
@@ -107,10 +113,16 @@ suspend fun main(args: Array<String>) {
it,
outFile.asMultipartFile()
)
is LivePhotoContent -> replyWithLivePhoto(
it,
outFile.asMultipartFile(),
content.media.photo ?.fileId ?: error("Unable to resend live photo files without their photos")
)
}
}
}
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
allUpdatesFlow.subscribeLoggingDropExceptions(this) { println(it) }
}.second.join()
}

View File

@@ -1,36 +1,6 @@
# ForwardInfoSenderBot
# ForwarderBot
A bot that analyses the origin of forwarded messages and prints detailed information about the forwarder.
## Functionality
For every message that was forwarded to the bot, it inspects the forward metadata and sends back a
formatted reply describing who or what originally sent the message: a regular user, a bot, a channel,
or an anonymous/hidden sender. Premium status, user IDs, and usernames are included where available.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Identifies forwarder type: regular user, bot, public channel, anonymous group admin, or hidden user
- Displays premium user status, numeric IDs, and usernames using `code` and hyperlink entities
- Re-sends the original message content alongside the metadata reply
- Runs via long polling
The main purpose of this bot is just to send info about forwarder when bot receive any update
## Launch

View File

@@ -1,44 +0,0 @@
# GiftsBot
A bot that retrieves and displays all gifts received by a user, chat, or business account.
## Functionality
On `/start`, the bot fetches gifts from multiple sources (user gifts, chat gifts, business account
gifts) and sends a formatted summary to the user. Each gift is described with its type (regular or
unique, standard or business-owned) and relevant metadata.
## Arguments
| Position | Value | Sample | Description |
|----------|-------|--------|-------------|
| 1 | `BOT_TOKEN` | `1234567890:AABBccDDeeFF` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Fetch and display all gifts for the requesting user |
## Capabilities
- Retrieves user gifts via `getUserGifts`
- Retrieves chat gifts via `getChatGifts`
- Retrieves business account gifts via `getBusinessAccountGifts`
- Distinguishes between regular gifts and unique gifts
- Distinguishes between standard (user-owned) and business-owned gifts
- Paginates through the full gift list
- Runs via long polling
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

View File

@@ -2,7 +2,6 @@ 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.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow
import dev.inmo.tgbotapi.extensions.api.gifts.getChatGiftsFlow
@@ -105,7 +104,7 @@ suspend fun main(vararg args: String) {
}
}
// allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
// allUpdatesFlow.subscribeLoggingDropExceptions(this) {
// println(it)
// }
}.second.join()

View File

@@ -1,37 +1,6 @@
# GiveawaysBot
# CustomBot
A bot that monitors and logs all giveaway lifecycle events in chats.
## Functionality
Listens for Telegram giveaway service messages and logs each event to standard output. No
interactive commands are provided; the bot is purely an observer/logger for giveaway activity.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Detects and logs giveaway creation events (`GiveawayCreated`)
- Detects and logs giveaway completion events with results (`Giveaway` with results)
- Detects and logs winner announcement messages (`GiveawayWinners`)
- Detects content messages that contain giveaway information (`GiveawayPublicResults`)
- All events are printed to stdout for inspection
- Runs via long polling
Printing giveaways
## Launch

View File

@@ -2,7 +2,7 @@ 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.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayCompleted
@@ -50,7 +50,7 @@ suspend fun main(vararg args: String) {
println(it)
}
// allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
// allUpdatesFlow.subscribeLoggingDropExceptions(this) {
// println(it)
// }
}.second.join()

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

@@ -1,36 +1,6 @@
# HelloBot
A minimal bot that responds whenever someone mentions the bot's username in a chat.
## Functionality
Listens for any message that contains the bot's username mention. When triggered, replies with
`Oh, hi, ` followed by a mention of the sender (or the group/channel name for non-private chats).
Uses MarkdownV2 formatting and adapts the reply text based on the chat type.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None. The bot is triggered by username mentions, not commands.
## Capabilities
- Detects mentions of the bot username in all chat types (private, group, supergroup, channel, business)
- Builds a MarkdownV2-formatted reply that links back to the sender
- For public chats the reply contains a clickable mention link; for private chats it uses a text mention
- Runs via long polling
The main purpose of this bot is just to answer "Oh, hi, " and add user mention here
## Launch

View File

@@ -1,4 +1,4 @@
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
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.send.reply
@@ -77,6 +77,6 @@ suspend fun main(vararg args: String) {
MarkdownV2
)
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
allUpdatesFlow.subscribeLoggingDropExceptions(this) { println(it) }
}.second.join()
}

View File

@@ -1,38 +1,6 @@
# InlineQueriesBot
A multiplatform bot that answers inline queries with paginated article results.
## Functionality
Responds to inline queries by returning a page of article results. Each result includes a
description and a deep-link button. Navigation between pages is handled via the query offset
(next/previous buttons encoded in the result set).
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None. The bot is driven by inline queries (type `@BotUsername` in any chat).
## Capabilities
- Answers inline queries with `InlineQueryResultArticle` items
- Offset-based pagination: each result page encodes the next-page offset in the answer
- Each result includes a deep-link `InlineKeyboardButton` back to the bot
- Multiplatform module with a shared `commonMain` implementation and a JVM launcher entry point
- Requires *Inline Mode* to be enabled in BotFather settings
- Runs via long polling
This bot will form the inline queries for you. For that feature you should explicitly enable inline queries in bot settings
## Launch

View File

@@ -1,47 +0,0 @@
# KeyboardsBot
A multiplatform bot (JVM + JS) that demonstrates inline keyboard pagination and various button types.
## Functionality
On `/inline <page> <count>`, the bot sends an inline keyboard built from `count` items starting
at `page`. The keyboard includes previous/next navigation buttons, copy-text buttons, styled
action buttons, and an inline-query chosen-chat button. Callback queries from the buttons navigate
between pages.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional flags (any order):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Arguments | Description |
|---------|-----------|-------------|
| `/inline` | `<page> <count>` | Send a paginated inline keyboard starting at `page` with `count` items per page |
## Capabilities
- Multi-page inline keyboard navigation (previous / next buttons encoded as callback data)
- Copy-text buttons (`CopyTextButton`)
- Styled action buttons: Primary, Success, Danger colour variants
- Inline query chosen-chat button (`SwitchInlineQueryChosenChat`)
- Answers inline queries that originate from the keyboard buttons
- Shared `commonMain` library with JVM and JS launchers
- Runs via long polling
## Launch
### JVM
```bash
./gradlew :KeyboardsBot:jvm_launcher:run --args="BOT_TOKEN"
```

View File

@@ -1,43 +1,6 @@
# LinkPreviewsBot
# ReactionsInfoBot
A bot that demonstrates all `LinkPreviewOptions` variants by replying with multiple messages, each
using a different link preview style.
## Functionality
When the user sends a message containing a URL, the bot extracts the URL and sends several reply
messages, each with a different `LinkPreviewOptions` configuration: disabled, small preview above
text, large preview above text, small preview below text, large preview below text, and the default
(no explicit options).
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Extracts URLs from the text entities of incoming messages
- Sends one reply per `LinkPreviewOptions` variant:
- Preview disabled
- Small image, positioned above text
- Large image, positioned above text
- Small image, positioned below text
- Large image, positioned below text
- Default (Telegram-chosen behaviour)
- Runs via long polling
This bot will resend messages with links with all variants of `LinkPreviewOptions`
## Launch

View File

@@ -1,39 +1,6 @@
# LiveLocationsBot
A bot that sends a live location and updates it periodically until the user cancels.
## Functionality
On `/start`, the bot sends a live location message with an inline *Cancel* button. A coroutine then
updates the location every 3 seconds with a slightly changing coordinate. When the user presses
*Cancel* the update loop is stopped and the live location is closed.
## Arguments
| Position | Value | Sample | Description |
|----------|-------|--------|-------------|
| 1 | `BOT_TOKEN` | `1234567890:AABBccDDeeFF` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Sample | Description |
|-------|--------|-------------|
| `debug` | `debug` | Enable verbose debug logging |
| `testServer` | `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Sends a live location message and starts the position update loop |
## Capabilities
- Sends an initial live location using `sendLiveLocation`
- Updates the location every 3 seconds via `editLiveLocation` in a background coroutine
- Inline keyboard with a *Cancel* callback button
- Handles the cancel callback to stop the update loop and close the live location
- Runs via long polling
This bot will send you live location and update it from time to time
## Launch

View File

@@ -1,4 +1,4 @@
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.tgbotapi.extensions.api.EditLiveLocationInfo
import dev.inmo.tgbotapi.extensions.api.edit.location.live.stopLiveLocation
import dev.inmo.tgbotapi.extensions.api.handleLiveLocation
@@ -61,7 +61,7 @@ suspend fun main(vararg args: String) {
stopLiveLocation(it, replyMarkup = null)
}
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
allUpdatesFlow.subscribeLoggingDropExceptions(this) { println(it) }
}.second.join()
}

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="LivePhotosBotKt"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
}

View File

@@ -0,0 +1,192 @@
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.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.edit.media.editMessageMedia
import dev.inmo.tgbotapi.extensions.api.send.media.sendLivePhoto
import dev.inmo.tgbotapi.extensions.api.send.media.sendMediaGroup
import dev.inmo.tgbotapi.extensions.api.send.media.sendPaidMedia
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.replyWithLivePhoto
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.onEditedLivePhoto
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onLivePhoto
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onLivePhotoGallery
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMediaGroupMessages
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPaidMediaInfoContent
import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.photoContentOrNull
import dev.inmo.tgbotapi.extensions.utils.photoFileOrNull
import dev.inmo.tgbotapi.extensions.utils.videoContentOrNull
import dev.inmo.tgbotapi.extensions.utils.videoFileOrNull
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
import dev.inmo.tgbotapi.types.message.content.LivePhotoContent
import dev.inmo.tgbotapi.types.message.payments.PaidMedia
import dev.inmo.tgbotapi.types.media.TelegramMediaLivePhoto
import dev.inmo.tgbotapi.types.media.TelegramPaidMediaLivePhoto
import dev.inmo.tgbotapi.types.media.toTelegramPaidMediaLivePhoto
import dev.inmo.tgbotapi.types.message.content.MediaContent
import dev.inmo.tgbotapi.types.message.content.MediaGroupContent
import dev.inmo.tgbotapi.types.message.content.MediaGroupPartContent
import dev.inmo.tgbotapi.types.message.content.VideoContent
import dev.inmo.tgbotapi.utils.RiskFeature
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
/**
* This bot demonstrates Live Photos support introduced in Telegram Bot API.
*
* Key concepts demonstrated:
* - [dev.inmo.tgbotapi.types.files.LivePhotoFile] — the LivePhoto class: a photo with an attached short video
* - [TelegramMediaLivePhoto] — InputMediaLivePhoto: used in sendMediaGroup and editMessageMedia
* - [LivePhotoContent] — the content type carried in Message.live_photo / ExternalReplyInfo.live_photo
* - [sendLivePhoto] — method to send a live photo
* - [PaidMedia.LivePhoto] — PaidMediaLivePhoto: a live photo inside paid media content
* - [TelegramPaidMediaLivePhoto] — InputPaidMediaLivePhoto: used in sendPaidMedia
* - sendMediaGroup and editMessageMedia with live photos
*/
@OptIn(RiskFeature::class)
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
) {
// Demonstrates: LivePhoto class (LivePhotoFile), live_photo field in Message, sendLivePhoto,
// InputMediaLivePhoto (TelegramMediaLivePhoto), InputPaidMediaLivePhoto (TelegramPaidMediaLivePhoto),
// editMessageMedia with live photo
onLivePhoto { message ->
// message.content is LivePhotoContent — this is the live_photo field of Message
val content: LivePhotoContent = message.content
// content.media is LivePhotoFile — the LivePhoto class (photo + short video in one file)
val livePhotoFile = content.media
println("=== Live photo received ===")
println(" fileId: ${livePhotoFile.fileId}")
println(" fileUniqueId: ${livePhotoFile.fileUniqueId}")
println(" width: ${livePhotoFile.width}")
println(" height: ${livePhotoFile.height}")
println(" duration: ${livePhotoFile.duration}s")
println(" photo (thumb): ${livePhotoFile.photo?.fileId}")
println(" mimeType: ${livePhotoFile.mimeType}")
println(" fileSize: ${livePhotoFile.fileSize}")
println(" caption: ${content.text}")
// sendLivePhoto: resend the received live photo back using LivePhotoFile overload
val sent = sendLivePhoto(
chatId = message.chat.id,
livePhoto = livePhotoFile,
text = "Resent via sendLivePhoto"
)
println(" sent message id: ${sent.messageId}")
// InputPaidMediaLivePhoto (TelegramPaidMediaLivePhoto): send the live photo as paid media (1 star)
sendPaidMedia(
chatId = message.chat.id,
starCount = 1,
media = listOf(
// TelegramPaidMediaLivePhoto is InputPaidMediaLivePhoto
TelegramPaidMediaLivePhoto(
file = livePhotoFile.fileId,
photo = livePhotoFile.photo?.fileId ?: livePhotoFile.fileId
)
),
text = "Paid live photo (1 star)"
)
// editMessageMedia with InputMediaLivePhoto (TelegramMediaLivePhoto):
// edit the previously sent message to replace it with itself via TelegramMediaLivePhoto
val sentAsMedia = sent.withContentOrNull<LivePhotoContent>()
if (sentAsMedia != null) {
editMessageMedia(
message = sentAsMedia,
// TelegramMediaLivePhoto is InputMediaLivePhoto
media = TelegramMediaLivePhoto(
file = livePhotoFile.fileId,
photo = livePhotoFile.photo?.fileId ?: livePhotoFile.fileId,
text = "Edited via editMessageMedia with TelegramMediaLivePhoto"
)
)
}
}
// Demonstrates: sendMediaGroup with live photos, InputMediaLivePhoto (TelegramMediaLivePhoto)
onLivePhotoGallery { mediaGroupContent ->
println("=== Live photo gallery received (${mediaGroupContent.group.size} items) ===")
mediaGroupContent.group.forEach { groupMember ->
val livePhotoFile = groupMember.content.media
println(" - fileId: ${livePhotoFile.fileId}, ${livePhotoFile.width}x${livePhotoFile.height}")
}
// sendMediaGroup with TelegramMediaLivePhoto (InputMediaLivePhoto)
sendMediaGroup(
chatId = mediaGroupContent.group.first().sourceMessage.chat.id,
media = mediaGroupContent.group.map { groupMember ->
val livePhotoFile = groupMember.content.media
// TelegramMediaLivePhoto is InputMediaLivePhoto — used here in sendMediaGroup
TelegramMediaLivePhoto(
file = livePhotoFile.fileId,
photo = livePhotoFile.photo?.fileId ?: livePhotoFile.fileId
)
}
)
}
// Demonstrates: PaidMediaLivePhoto (PaidMedia.LivePhoto) in received paid media content
onPaidMediaInfoContent { message ->
val paidMedia = message.content.paidMediaInfo.media
val livePhotos = paidMedia.filterIsInstance<PaidMedia.LivePhoto>()
if (livePhotos.isNotEmpty()) {
println("=== Paid media with live photos received ===")
livePhotos.forEach { paidLivePhoto ->
// paidLivePhoto is PaidMedia.LivePhoto — PaidMediaLivePhoto class
val livePhotoFile = paidLivePhoto.livePhoto
println(" - fileId: ${livePhotoFile.fileId}, ${livePhotoFile.width}x${livePhotoFile.height}")
println(" duration: ${livePhotoFile.duration}s")
}
reply(message, "Received ${livePhotos.size} paid live photo(s)")
}
}
// Demonstrates: live_photo field in edited messages (EditedMessage with LivePhotoContent)
onEditedLivePhoto { message ->
println("=== Edited live photo received ===")
println(" fileId: ${message.content.media.fileId}")
println(" caption: ${message.content.text}")
}
onMediaGroupMessages {
val photo = it.content.group.firstNotNullOfOrNull {
it.content.photoContentOrNull()
} ?: return@onMediaGroupMessages
val video = it.content.group.firstNotNullOfOrNull {
it.content.videoContentOrNull()
} ?: return@onMediaGroupMessages
replyWithLivePhoto(
it,
video.media.fileId,
photo.media.fileId
)
}
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
println(it)
}
}.second.join()
}

View File

@@ -1,47 +0,0 @@
# ManagedBotsBot
A bot that demonstrates the Managed Bots API: creating child bots and replacing their tokens.
## Functionality
Allows the operator to check whether the bot supports managed bots, create new managed bots via a
keyboard button, and replace an existing managed bot's token. When a managed bot is created its
token is sent back to the operator. The bot also demonstrates custom middleware and subcontext usage.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/canManageBots` | Check whether this bot has the ability to create managed bots |
| `/keyboard` | Send a reply keyboard with a *Create managed bot* button |
| `/replaceToken` | Replace the token of a managed bot (send as reply to the bot's token message) |
## Capabilities
- Queries bot capabilities via `getMe` extended fields
- Creates a managed child bot via the `BotKeyboardButton` with `RequestBot` type
- Receives the new bot's info in a `BotShared` service message
- Replaces a managed bot's token via `replaceStickerInSet` (token replacement API)
- Handles `ManagedBotUpdated` events for tracking child bot status changes
- Custom request middleware for logging
- Custom `BehaviourContext` subcontext
- Runs via long polling
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

View File

@@ -5,15 +5,20 @@ import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.getUserPersonalChatMessages
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.managed_bots.getManagedBotAccessSettings
import dev.inmo.tgbotapi.extensions.api.managed_bots.getManagedBotToken
import dev.inmo.tgbotapi.extensions.api.managed_bots.replaceManagedBotToken
import dev.inmo.tgbotapi.extensions.api.managed_bots.setManagedBotAccessSettings
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.api.send.sendMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextData
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildSubcontextInitialAction
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.onCommandWithArgs
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onManagedBotCreated
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onManagedBotUpdated
import dev.inmo.tgbotapi.extensions.utils.chatEventMessageOrNull
@@ -22,10 +27,12 @@ import dev.inmo.tgbotapi.extensions.utils.managedBotCreatedOrNull
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatReplyKeyboard
import dev.inmo.tgbotapi.extensions.utils.types.buttons.replyKeyboard
import dev.inmo.tgbotapi.extensions.utils.types.buttons.requestManagedBotButton
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.RawChatId
import dev.inmo.tgbotapi.types.Username
import dev.inmo.tgbotapi.types.buttons.KeyboardButtonRequestManagedBot
import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButtonId
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
import dev.inmo.tgbotapi.types.request.RequestId
import dev.inmo.tgbotapi.types.toChatId
import dev.inmo.tgbotapi.types.update.abstracts.Update
@@ -36,8 +43,8 @@ private var BehaviourContextData.update: Update?
get() = get("update") as? Update
set(value) = set("update", value)
private var BehaviourContextData.commonMessage: CommonMessage<*>?
get() = get("commonMessage") as? CommonMessage<*>
private var BehaviourContextData.commonMessage: ChatContentMessage<*>?
get() = get("commonMessage") as? ChatContentMessage<*>
set(value) = set("commonMessage", value)
/**
@@ -114,19 +121,19 @@ suspend fun main(vararg args: String) {
}
onManagedBotCreated {
reply(it, "Managed bot created successfully: ${it.chatEvent.bot}")
val token = getManagedBotToken(
it.chatEvent.bot.id.toChatId()
)
reply(it, "Token: $token")
val botChatId = it.chatEvent.bot.id.toChatId()
reply(it, "Managed bot created successfully: ${it.chatEvent.bot}\nBot ID: ${botChatId.chatId.long}")
val token = getManagedBotToken(botChatId)
val accessSettings = getManagedBotAccessSettings(botChatId)
reply(it, "Token: $token; Access settings: $accessSettings")
}
onManagedBotUpdated {
send(it.user, "Managed bot has been updated: ${it.bot}")
val token = getManagedBotToken(
it.bot.id.toChatId()
)
send(it.user, "Token: $token")
val botChatId = it.bot.id.toChatId()
send(it.user, "Managed bot has been updated: ${it.bot}\nBot ID: ${botChatId.chatId.long}")
val token = getManagedBotToken(botChatId)
val accessSettings = getManagedBotAccessSettings(botChatId)
send(it.user, "Token: $token; Access settings: $accessSettings")
}
onCommand("replaceToken") {
@@ -136,6 +143,71 @@ suspend fun main(vararg args: String) {
reply(it, "Token in replace update: ${replaceManagedBotToken(managedBotCreated.bot.id.toChatId())}")
}
// getManagedBotAccessSettings — show BotAccessSettings: who can access the given managed bot
// Usage: /get_bot_access_settings <botId>
onCommandWithArgs("get_bot_access_settings") { message, args ->
val botId = args.firstOrNull()?.toLongOrNull()?.let(::RawChatId)?.toChatId()
?: run { reply(message, "Usage: /get_bot_access_settings <botId>\n(Bot ID shown after /keyboard → create bot)"); return@onCommandWithArgs }
val settings = runCatching { getManagedBotAccessSettings(botId) }.getOrElse {
reply(message, "Error: ${it.message}"); return@onCommandWithArgs
}
reply(message, buildString {
append("Access settings for managed bot $botId:\n")
append(" isAccessRestricted: ${settings.isAccessRestricted}\n")
if (settings.addedUsers != null) {
append(" allowedUsers: ${settings.addedUsers!!.joinToString { "${it.firstName} (${it.id})" }}")
} else {
append(" allowedUsers: all (unrestricted)")
}
})
}
// setManagedBotAccessSettings — restrict access to a list of user IDs, or open to all
// Usage: /set_bot_access_settings <botId> [userId1 userId2 ...]
// Omit userIds to open access to all users (addedUserIds = null)
onCommandWithArgs("set_bot_access_settings") { message, args ->
val botId = args.firstOrNull()?.toLongOrNull()?.let(::RawChatId)?.toChatId()
?: run { reply(message, "Usage: /set_bot_access_settings <botId> [userId1 userId2 ...]"); return@onCommandWithArgs }
val allowedIds = args.drop(1).mapNotNull { it.toLongOrNull()?.let(::RawChatId)?.toChatId() }
val addedUserIds: List<ChatId>? = allowedIds.ifEmpty { null }
runCatching {
setManagedBotAccessSettings(botId, addedUserIds)
}.onSuccess {
reply(message, if (addedUserIds == null) "Access opened to all users." else "Access restricted to ${addedUserIds.size} user(s).")
}.onFailure {
reply(message, "Error: ${it.message}")
}
}
// getUserPersonalChatMessages — get recent messages from the user's personal channel
// Works only if the user has a personal channel linked to their account
onCommand("get_personal_messages") {
val msg = it
val userId = msg.chat.id.toChatId()
val messages = runCatching { getUserPersonalChatMessages(userId, limit = 10) }.getOrElse { e ->
reply(msg, "Error: ${e.message}"); return@onCommand
}
reply(msg, "Personal channel messages (${messages.size}):\n" +
messages.joinToString("\n") { m -> " [${m.messageId}] ${m.content::class.simpleName}" }
.ifEmpty { " (none)" }
)
}
// Bot-to-bot communication: send a message to another bot by @username
// Since TG Bot API 9.0: works if both bots have bot-to-bot communication enabled in BotFather
onCommandWithArgs("send_to_bot") { message, args ->
val usernameArg = args.firstOrNull() ?: run { reply(message, "Usage: /send_to_bot @username [text]"); return@onCommandWithArgs }
val targetUsername = Username.prepare(usernameArg)
val text = args.drop(1).joinToString(" ").ifEmpty { "Hello from bot-to-bot communication!" }
runCatching {
sendMessage(targetUsername, text)
}.onSuccess {
reply(message, "Message sent to $targetUsername")
}.onFailure {
reply(message, "Failed to send to $targetUsername: ${it.message}")
}
}
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
println(it)
}

View File

@@ -1,39 +1,7 @@
# MemberUpdatedWatcherBot
A bot that monitors all `ChatMemberUpdated` events and sends descriptive notifications to the chat.
This bot will watch for some ChatMemberUpdated events using new extensions from 18.0.0
## Functionality
Watches for every member status change in all chats the bot is a member of: bot additions,
admin promotions and demotions, user joins and leaves, and permission restriction changes.
For each event the bot sends a human-readable message describing what changed.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Detects when the bot itself is added to or removed from a chat and sends a greeting/farewell
- Detects when the bot is promoted to or demoted from administrator
- Detects when any user joins or leaves the chat
- Detects when any user is promoted to or demoted from administrator
- Detects granular permission changes (e.g., restrictions added or lifted)
- Uses the `ChatMemberUpdated` extension functions introduced in TelegramBotAPI 18.0.0
- Runs via long polling
## Launch

View File

@@ -1,36 +1,6 @@
# MyBot
# GetMeBot
A bot that monitors messages containing its username and responds with a contextual link or mention.
## Functionality
Watches every incoming message for a mention of the bot's username. When found, it replies with a
MarkdownV2-formatted message that includes a link or text mention pointing back to the originating
chat or user, adapting the reply to the type of chat the message came from.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None. The bot reacts to username mentions, not to commands.
## Capabilities
- Handles all chat types: private, group, supergroup, channel, business connection chats, channel groups
- For public chats builds a `t.me/<username>` hyperlink; for private chats uses an inline text mention
- Prints information about the originating chat using `getChat`
- Runs via long polling
This is one of the most easiest bot - it will just print information about itself
## Launch

View File

@@ -1,45 +1,8 @@
# PollsBot
A bot that demonstrates creation and management of Telegram polls (anonymous, public, and quiz).
This bot will send test poll in the chat where commands will be received. Commands:
## Functionality
Creates polls on demand and tracks live answer updates. Users can reply to an existing poll message
to add new options or remove the last option. Quiz polls are created with a random correct answer.
Custom emoji stickers in poll options are supported.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/anonymous` | Create an anonymous poll |
| `/public` | Create a public poll; users can add options by replying |
| `/quiz` | Create a quiz poll with a randomly chosen correct answer |
All three commands accept an optional custom emoji ID as an extra argument to use in poll option text.
## Capabilities
- Mutex-protected in-memory poll registry to safely track concurrent updates
- Live poll answer updates via `onPollUpdated` handler
- Reply-based option management: reply to a poll message with text to add an option, or reply with `/remove` to delete the last option
- Quiz polls: random correct answer selection, answer explanation included
- Custom emoji in poll option text
- Registers all three commands with Telegram (`setMyCommands`)
- Runs via long polling
## Launch

View File

@@ -2,7 +2,7 @@ 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.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
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.sendRegularPoll
@@ -16,14 +16,22 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollOp
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.utils.accessibleMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.chatContentMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.customEmojiTextSourceOrNull
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgsSources
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
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.TelegramMediaLocation
import dev.inmo.tgbotapi.types.media.TelegramMediaSticker
import dev.inmo.tgbotapi.types.media.TelegramMediaVenue
import dev.inmo.tgbotapi.types.message.content.StickerContent
import dev.inmo.tgbotapi.types.polls.InputPollOption
import dev.inmo.tgbotapi.types.polls.PollAnswer
import dev.inmo.tgbotapi.types.polls.QuizPoll
import dev.inmo.tgbotapi.utils.buildEntities
import dev.inmo.tgbotapi.utils.customEmoji
import dev.inmo.tgbotapi.utils.regular
@@ -35,11 +43,22 @@ import kotlinx.coroutines.sync.withLock
import kotlin.random.Random
/**
* This bot will answer with anonymous or public poll and send message on
* any update.
* This bot demonstrates poll features including the new API additions:
*
* * Use `/anonymous` to take anonymous regular poll
* * Use `/public` to take public regular poll
* * `/anonymous` anonymous regular poll
* * `/public` public regular poll with option adding
* * `/quiz` — quiz poll with random correct answer
* * `/media_poll` — poll with [TelegramMediaLocation] as poll media (InputMediaLocation),
* and [TelegramMediaVenue] as option media (InputMediaVenue / InputPollOptionMedia)
* * `/quiz_media` — quiz poll with [TelegramMediaLocation] as `media` and [TelegramMediaVenue]
* as `explanationMedia` (new [QuizPoll.explanationMedia] field)
* * `/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)
*
* [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
* [dev.inmo.tgbotapi.types.polls.PollOption.media] for each option.
*/
suspend fun main(vararg args: String) {
val botToken = args.first()
@@ -173,6 +192,126 @@ suspend fun main(vararg args: String) {
}
}
// TelegramMediaLocation implements InputPollMedia and InputPollOptionMedia (InputMediaLocation)
// TelegramMediaVenue implements InputPollMedia and InputPollOptionMedia (InputMediaVenue)
// Both can be used as poll question media or as option media
onCommand("media_poll") {
val replySticker = it.replyTo ?.contentMessageOrNull() ?.withContentOrNull<StickerContent>() ?.content ?.media
val sentPoll = sendRegularPoll(
it.chat.id,
buildEntities { regular("Which venue would you visit?") },
listOfNotNull(
// InputPollOptionMedia via TelegramMediaVenue (InputMediaVenue)
InputPollOption(
media = TelegramMediaVenue(
latitude = 48.8566,
longitude = 2.3522,
title = "Eiffel Tower",
address = "Champ de Mars, Paris"
)
) { regular("Eiffel Tower") },
// InputPollOptionMedia via TelegramMediaLocation (InputMediaLocation)
InputPollOption(
media = TelegramMediaLocation(latitude = 51.5007, longitude = -0.1246)
) { regular("Big Ben") },
InputPollOption { regular("Neither") },
replySticker ?.let {
InputPollOption(media = TelegramMediaSticker(replySticker.fileId)) {
regular("Your sticker")
}
}
),
isAnonymous = false,
// InputMediaLocation as InputPollMedia — poll question media
media = TelegramMediaLocation(latitude = 48.8566, longitude = 2.3522),
replyParameters = ReplyParameters(it)
)
pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
}
}
// Demonstrates InputPollMedia on quiz + new QuizPoll.explanationMedia field
onCommand("quiz_media") {
val sentPoll = sendQuizPoll(
it.chat.id,
questionEntities = buildEntities { regular("Where is the Eiffel Tower?") },
options = listOf(
InputPollOption { regular("Paris") },
InputPollOption { regular("London") },
InputPollOption { regular("Berlin") },
),
correctOptionIds = listOf(0),
explanation = "The Eiffel Tower is in Paris, France.",
isAnonymous = false,
// InputMediaLocation as InputPollMedia — poll question media (new Poll.media field)
media = TelegramMediaLocation(latitude = 48.8566, longitude = 2.3522),
// explanationMedia is new on QuizPoll — media shown with quiz explanation
explanationMedia = TelegramMediaVenue(
latitude = 48.8566,
longitude = 2.3522,
title = "Eiffel Tower",
address = "Champ de Mars, 5 Av. Anatole France, Paris"
),
replyParameters = ReplyParameters(it)
)
pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
}
}
// Demonstrates Poll.membersOnly and the membersOnly sendPoll parameter
onCommand("members_only") {
val sentPoll = sendRegularPoll(
it.chat.id,
buildEntities { regular("Members-only poll") },
listOf(
InputPollOption { regular("Yes") },
InputPollOption { regular("No") },
),
isAnonymous = true,
membersOnly = true,
replyParameters = ReplyParameters(it)
)
pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
}
}
// Demonstrates Poll.countryCodes and the countryCodes sendPoll parameter
onCommand("country_codes") {
val sentPoll = sendRegularPoll(
it.chat.id,
buildEntities { regular("Country-targeted poll (US, DE, JP)") },
listOf(
InputPollOption { regular("Option A") },
InputPollOption { regular("Option B") },
),
isAnonymous = true,
countryCodes = listOf("US", "DE", "JP"),
replyParameters = ReplyParameters(it)
)
pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
}
}
// Demonstrates that minimum poll options count is now 1 (was 2 before)
onCommand("single_option") {
val sentPoll = sendRegularPoll(
it.chat.id,
buildEntities { regular("Acknowledge this notice") },
listOf(
InputPollOption { regular("Got it") },
),
isAnonymous = false,
replyParameters = ReplyParameters(it)
)
pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
}
}
onPollAnswer {
val chatId = pollToChat[it.pollId] ?: return@onPollAnswer
@@ -185,14 +324,29 @@ suspend fun main(vararg args: String) {
onPollUpdates {
val chatId = pollToChat[it.id] ?: return@onPollUpdates
when(it.isAnonymous) {
false -> send(chatId, "[onPollUpdates] Public poll updated: ${it.options.joinToString()}")
true -> send(chatId, "[onPollUpdates] Anonymous poll updated: ${it.options.joinToString()}")
// Poll.media — PollMedia attached to the poll question (new field)
// Poll.membersOnly — whether poll is restricted to channel members (new field)
// Poll.countryCodes — country restriction list (new field)
// QuizPoll.explanationMedia — PollMedia attached to quiz explanation (new field)
// PollOption.media — PollMedia attached to each option (new field)
val pollInfo = buildString {
append("[onPollUpdates] anonymous=${it.isAnonymous}")
append(" | media=${it.media}")
append(" | membersOnly=${it.membersOnly}")
append(" | countryCodes=${it.countryCodes}")
if (it is QuizPoll) {
append(" | explanationMedia=${it.explanationMedia}")
}
append("\n options:")
it.options.forEach { option ->
append("\n ${option.text}: votes=${option.votes}, media=${option.media}")
}
}
send(chatId, pollInfo)
}
onPollOptionAdded {
it.chatEvent.pollMessage ?.accessibleMessageOrNull() ?.let { pollMessage ->
it.chatEvent.pollMessage ?.accessibleMessageOrNull() ?.chatContentMessageOrNull() ?.let { pollMessage ->
reply(pollMessage) {
+"Poll option added: \n"
+it.chatEvent.optionTextSources
@@ -200,7 +354,7 @@ suspend fun main(vararg args: String) {
}
}
onPollOptionDeleted {
it.chatEvent.pollMessage ?.accessibleMessageOrNull() ?.let { pollMessage ->
it.chatEvent.pollMessage ?.accessibleMessageOrNull() ?.chatContentMessageOrNull() ?.let { pollMessage ->
reply(pollMessage) {
+"Poll option deleted: \n"
+it.chatEvent.optionTextSources
@@ -210,7 +364,7 @@ suspend fun main(vararg args: String) {
onContentMessage {
val replyPollOptionId = it.replyInfo ?.pollOptionId ?: return@onContentMessage
it.replyTo ?.accessibleMessageOrNull() ?.let { replied ->
it.replyTo ?.accessibleMessageOrNull() ?.chatContentMessageOrNull() ?.let { replied ->
reply(replied, pollOptionId = replyPollOptionId) {
+"Reply to poll option"
}
@@ -221,8 +375,15 @@ suspend fun main(vararg args: String) {
BotCommand("anonymous", "Create anonymous regular poll"),
BotCommand("public", "Create non anonymous regular poll"),
BotCommand("quiz", "Create quiz poll with random right answer"),
BotCommand("media_poll", "Poll with location/venue media on question and options"),
BotCommand("quiz_media", "Quiz with media and explanationMedia on question/explanation"),
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)"),
)
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
println(it)
}
}.second.join()
}

View File

@@ -1,53 +1,9 @@
# RandomFileSenderBot
A multiplatform bot (JVM + Native) that picks random files from a directory and sends them to the
requester.
## Functionality
Picks one or more files at random from a specified directory and sends them to the user. Multiple
files are batched into a media group. Files are sent as protected content. The bot is implemented
as a shared library with separate JVM and Native launcher entry points.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
| 2 *(optional)* | `/path/to/dir` | Directory to pick files from (defaults to the current working directory) |
Optional flags (any order):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/send_file` | Send 1 random file from the configured directory |
| `/send_file N` | Send *N* random files from the configured directory |
## Capabilities
- Platform-specific random file selection (JVM uses `java.io.File`, Native uses POSIX directory API)
- Groups multiple files into a single media group message when N > 1
- Files are sent as protected content (forwarding disabled)
- Multiplatform: shared logic in `commonMain`, launchers in `jvm_launcher` and `native_launcher`
- Runs via long polling
This bot will send random file from input folder OR from bot working folder
## Launch
### JVM
```bash
./gradlew :RandomFileSenderBot:jvm_launcher:run --args="BOT_TOKEN /optional/path"
```
### Native (after build)
```bash
./RandomFileSenderBot/native_launcher/build/bin/native/releaseExecutable/native_launcher.kexe BOT_TOKEN /optional/path
../gradlew run --args="BOT_TOKEN[ optional/folder/path]"
```

View File

@@ -1,38 +1,6 @@
# ReactionsInfoBot
A bot that tracks message reactions and reports them back to the user.
## Functionality
Monitors reaction updates in the bot's private chat. When a user adds or removes a reaction on a
message, the bot replies to that message with a formatted summary of the current reactions.
The bot also sets a reaction emoji on incoming messages to acknowledge them.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Handles `MessageReactionUpdated` events (per-user reaction changes)
- Handles `MessageReactionCountUpdated` events (aggregate reaction counts)
- Identifies reaction types: standard emoji, custom emoji, paid reactions
- Replies to the reacted-to message with a formatted list of current reactions
- Sets a reaction on received messages using `setMessageReaction`
- Runs via long polling
This bot will send info about user reactions in his PM with reply to message user reacted to
## Launch

View File

@@ -1,48 +0,0 @@
# ResenderBot
A multiplatform bot (JVM + Native + JS) that echoes every content message back to the sender.
## Functionality
For every content message the bot receives, it immediately re-sends the same content back to the
originating chat. Reply quotes and message effects are preserved. The bot is implemented as a
shared library (`ResenderBotLib`) with separate launcher modules for JVM and Native targets.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional flags (any order):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Re-sends any content message (text, photo, video, audio, document, sticker, etc.)
- Preserves `reply_to_message` quote when the original message was a reply
- Preserves `effect_id` (message effects / animations)
- Shared `commonMain` implementation across JVM, Native, and JS targets
- Runs via long polling
## Launch
### JVM
```bash
./gradlew :ResenderBot:jvm_launcher:run --args="BOT_TOKEN"
```
### Native (after build)
```bash
./ResenderBot/native_launcher/build/bin/native/releaseExecutable/native_launcher.kexe BOT_TOKEN
```

View File

@@ -1,50 +1,12 @@
# RightsChangerBot
# RightsChanger
A bot for managing user permissions and administrator rights in Telegram groups and channels.
All the commands should be called with reply to some common user.
## Functionality
Provides two modes of permission editing (simple / granular) for regular member restrictions, and a
full FSM-based flow for editing channel administrator rights. Changes are presented as inline
keyboards with visual ✅/❌ toggles that persist until the user is done.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
| 2 | `ADMIN_USER_ID` | Numeric Telegram user ID allowed to use the bot |
Optional flags (any order after the required arguments):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/simple` | Show a common-permissions keyboard (send messages, polls, web previews, etc.) for the replied-to user |
| `/granular` | Show a granular-permissions keyboard (individual media types) for the replied-to user |
| `/rights_in_channel` | Start the FSM flow to pick a channel and a user, then edit that user's administrator rights in the channel |
All commands must be sent as a **reply** to a target user's message.
## Capabilities
- **Simple mode** — toggles grouped permissions: send messages, send media, send polls, send other content, add web page previews, change info, invite users, pin messages
- **Granular mode** — toggles individual media-type permissions: audios, documents, photos, videos, video notes, voice notes, stickers, animations, games, gift premiums, forward channels, forward non-channels
- **Channel admin rights** — FSM with three states:
1. `RetrievingChannelChatState` — user picks the channel
2. `RetrievingUserIdChatState` — user picks the member
3. `RetrievingChatInfoDoneState` — inline keyboard for toggling admin rights (post messages, edit messages, delete messages, ban users, invite users, pin messages, manage topics, manage video chats, post stories, edit stories, delete stories, remain anonymous)
- Inline keyboard callbacks update permission state in real time
- Runs via long polling
* Use `/simple` with bot to get request buttons for non-independent permissions change
* Use `/granular` with bot to get request buttons for independent permissions change
## Launch
```bash
../gradlew run --args="BOT_TOKEN ADMIN_USER_ID"
../gradlew run --args="BOT_TOKEN allowed_user_id_long"
```

View File

@@ -33,6 +33,7 @@ import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
import dev.inmo.tgbotapi.types.chat.member.ChatCommonAdministratorRights
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.request.RequestId
import dev.inmo.tgbotapi.utils.*
import kotlinx.coroutines.flow.filter
@@ -208,7 +209,7 @@ suspend fun main(args: Array<String>) {
) {
val replyMessage = it.replyTo
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
if (replyMessage is AccessibleMessage) {
if (replyMessage is ChatMessage) {
reply(
replyMessage,
"Manage keyboard:",
@@ -229,7 +230,7 @@ suspend fun main(args: Array<String>) {
val replyMessage = it.replyTo
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
if (replyMessage is AccessibleMessage) {
if (replyMessage is ChatMessage) {
reply(
replyMessage,
"Manage keyboard:",
@@ -247,7 +248,7 @@ suspend fun main(args: Array<String>) {
initialFilter = { it.user.id == allowedAdmin }
) {
val messageReply =
it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
it.message.chatContentMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
val userId = messageReply.user.id
val permissions =
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
@@ -334,7 +335,7 @@ suspend fun main(args: Array<String>) {
initialFilter = { it.user.id == allowedAdmin }
) {
val messageReply =
it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
it.message.chatContentMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
val userId = messageReply.user.id
val permissions =
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery

View File

@@ -1,35 +1,6 @@
# SlotMachineDetectorBot
A bot that detects slot-machine dice rolls and reports the result.
## Functionality
Listens for dice messages of the *SlotMachine* type. When one is received, it calculates the
combination shown on the three reels and replies with the formatted result.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Filters incoming dice messages specifically for the slot-machine emoji type
- Decodes the numeric dice value into the three reel symbols
- Replies with a human-readable description of the result
- Runs via long polling
This bot must reply with information about slot machine answer
## Launch

View File

@@ -1,46 +1,9 @@
# StarTransactionsBot
A bot that demonstrates Telegram Stars payments: sending invoices, handling transactions, and
delivering paid media.
## Functionality
Sends a 1-star invoice on `/start`. After successful payment the bot sends paid media (a photo
and a video). The admin can browse the full transaction history with pagination. Refunds received
from Telegram are logged. Checkout queries are validated before approval.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
| 2 | `ADMIN_USER_ID` | Numeric Telegram user ID that is allowed to view transaction history |
Optional flags (any order after the required arguments):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Send a 1-star invoice to the user |
| `/transactions` | Browse paginated star transaction history *(admin only)* |
## Capabilities
- Creates and sends a Stars invoice via `sendInvoice`
- Handles `PreCheckoutQuery` events to approve or reject checkout
- Delivers paid media (photo + video) after a successful payment
- Paginates transaction history using inline keyboard next/previous buttons
- Tracks and logs refund notifications
- Runs via long polling
This bot basically have no any useful behaviour, but you may customize it as a playground
## Launch
```bash
../gradlew run --args="BOT_TOKEN ADMIN_USER_ID"
../gradlew run --args="BOT_TOKEN"
```

View File

@@ -1,44 +0,0 @@
# StickerInfoBot
A multiplatform bot (JVM + JS) that displays detailed information about stickers and custom emoji.
## Functionality
When the user sends a sticker, the bot replies with the sticker set name, title, and sticker type.
When the user sends a text message containing custom emoji entities, the bot fetches the
corresponding sticker objects and sends back their information.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional flags (any order):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
None.
## Capabilities
- Detects incoming sticker messages and calls `getStickerSet` to retrieve set metadata
- Reports sticker set name, title, and type (regular / mask / custom emoji)
- Scans text message entities for `CustomEmoji` types
- Fetches the corresponding sticker objects via `getCustomEmojiStickers`
- Sends sticker information back as a formatted reply
- Shared `commonMain` library with JVM and JS launchers
- Runs via long polling
## Launch
### JVM
```bash
./gradlew :StickerInfoBot:jvm_launcher:run --args="BOT_TOKEN"
```

View File

@@ -1,46 +1,9 @@
# StickerSetHandler
A bot that builds and manages a personal sticker set for each user from stickers they send.
Send sticker to this bot to form your own stickers set. Send /delete to delete this sticker set
## Functionality
When a user sends a sticker, the bot extracts its emoji and adds it to a per-user sticker set
named `<user_id>_by_<bot_username>`. If the set does not yet exist, it is created first. The bot
supports regular, mask, and custom emoji sticker sets, determined by the type of the first sticker
added. Sending `/delete` removes the user's entire sticker set.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Sends a welcome message explaining how to use the bot |
| `/delete` | Deletes the user's personal sticker set created by this bot |
## Capabilities
- Per-user sticker set with a deterministic name based on user ID and bot username
- Automatic sticker set creation on first sticker received
- Supports all sticker set types: regular, mask, custom emoji
- Emoji extraction from incoming stickers
- Sticker added to an existing set via `addStickerToSet`
- Set deletion via `deleteStickerSet`
- Runs via long polling
## Launch
## How to run
```bash
../gradlew run --args="BOT_TOKEN"
./gradlew run --args="TOKEN"
```

View File

@@ -1,43 +1,9 @@
# SuggestedPosts
# StickerSetHandler
A bot that handles the channel Direct Messages (suggested post) approval flow.
Send sticker to this bot to form your own stickers set. Send /delete to delete this sticker set
## Functionality
Monitors suggested post events in a channel connected via Direct Messages. When a post is
suggested, the bot automatically schedules a decline after a short delay (demonstrating the
decline flow). Paid post events and approval/decline confirmations are also tracked and logged.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Initialises the bot and confirms it is running |
## Capabilities
- Handles `SuggestedPostApproved` events
- Handles `SuggestedPostDeclined` events
- Handles `SuggestedPostPaid` and `SuggestedPostRefunded` events
- Handles `SuggestedPostApprovalFailed` errors
- Automatically declines new suggestions after a configurable delay to demonstrate the decline API
- Runs via long polling
## Launch
## How to run
```bash
../gradlew run --args="BOT_TOKEN"
./gradlew run --args="TOKEN"
```

View File

@@ -31,7 +31,7 @@ import dev.inmo.tgbotapi.extensions.utils.previewChannelDirectMessagesChatOrNull
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
import dev.inmo.tgbotapi.types.message.abstracts.ChannelPaidPost
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.utils.firstOf
import kotlinx.coroutines.CoroutineScope

View File

@@ -1,46 +0,0 @@
# TagsBot
A bot that manages custom member tags in Telegram groups.
## Functionality
Allows administrators to assign custom text tags to group members, remove tags, and grant or
revoke the *manage tags* permission. All tag-related commands require the command to be sent as a
reply to the target member's message.
## Arguments
| Position | Value | Sample | Description |
|----------|-------|--------|-------------|
| 1 | `BOT_TOKEN` | `1234567890:AABBccDDeeFF` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Sample | Description |
|-------|--------|-------------|
| `debug` | `debug` | Enable verbose debug logging |
| `testServer` | `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/setChatMemberTag <tag>` | Set a custom tag on the replied-to member |
| `/removeChatMemberTag` | Remove the custom tag from the replied-to member |
| `/setCanManageTags <true\|false>` | Grant (`true`) or revoke (`false`) the *manage tags* admin right for the replied-to member |
All commands must be sent as a **reply** to the target user's message.
## Capabilities
- Sets custom tags on group members via `setChatMemberTag`
- Removes tags via `removeChatMemberTag`
- Promotes members with tag management permission via `promoteChatMember`
- Reads existing tag information through the Risk API (`getChatMember`)
- Runs via long polling
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

View File

@@ -3,7 +3,6 @@ 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.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.abstracts.FromUser
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow

View File

@@ -1,50 +1,9 @@
# TopicsHandling
# HelloBot
A bot that demonstrates full forum-topic management for Telegram supergroups with forum mode enabled.
## Functionality
Provides commands to create, edit, close, reopen, and delete forum topics. Also handles the general
topic (hide/unhide, close/reopen, pin/unpin messages) and listens for all topic lifecycle events,
logging each one.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start_test_topics` | Runs a full test sequence: creates a topic, edits its name and icon, pins a message, closes and reopens it, then deletes it |
| `/delete_topic` | Deletes the forum topic in which the command was sent |
| `/unpin_all_forum_topic_messages` | Unpins all messages in the current forum topic |
## Capabilities
- Creates colour-coded forum topics with a custom emoji icon
- Edits topic name and icon
- Closes and reopens topics
- Deletes topics
- Manages the general (default) topic: hide, unhide, close, reopen
- Pins and unpins messages within a topic
- Detects and logs topic creation, editing, closure, reopening, and general-topic visibility changes
- Detects private forum support and enables private topics when available
- Runs via long polling
The main purpose of this bot is just to answer "Oh, hi, " and add user mention here
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```
> **Note:** The bot must be an administrator with *Manage Topics* permission in the target supergroup.

View File

@@ -1,41 +1,6 @@
# UserChatShared
A bot that demonstrates the `RequestUsers` and `RequestChat` keyboard button types, letting users
share user or chat contacts with the bot.
## Functionality
On `/start`, the bot sends a reply keyboard containing various request buttons. When the user taps
one of these buttons, Telegram opens a picker and the selected user(s) or chat is shared back with
the bot. The bot then logs and replies with the received share information.
## Arguments
| Position | Value | Sample | Description |
|----------|-------|--------|-------------|
| 1 | `BOT_TOKEN` | `1234567890:AABBccDDeeFF` | Telegram bot token |
Optional arguments (any order after the token):
| Value | Sample | Description |
|-------|--------|-------------|
| `debug` | `debug` | Enable verbose debug logging |
| `testServer` | `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Sends the reply keyboard with all request buttons |
## Capabilities
- `RequestUsers` buttons for: any single user, premium user, non-premium user, multiple users
- `RequestChat` buttons for: any channel, any group, a forum group, a group with specific privacy/creator requirements
- Buttons can optionally request the user's/chat's photo, name, and username
- Unique request IDs are assigned to each button to distinguish responses
- Handles `UserShared` and `ChatShared` service messages and replies with the received data
- Runs via long polling
Use `/start` with bot to get request buttons. Bot will ask you to choose user/chat from your list and send it to him.
## Launch

View File

@@ -1,59 +1,17 @@
# WebApp
A multiplatform bot (JVM server + JS WebApp) that demonstrates Telegram WebApp integration.
Here you may find simple example of `WebApp`. For work of this example you will need one of two things:
## Functionality
* Your own domain with SSL (letsencrypt is okay)
* Test account in telegram
The JVM part hosts a Ktor HTTP server that serves a static WebApp frontend and exposes REST
endpoints for inline query submission, WebApp data validation, custom emoji status setting, and
prepared keyboard button management. The JS part is the WebApp itself — a single-page app with a
button that communicates back to the bot and adapts to the user's Telegram theme and viewport.
What is there in this module:
## Arguments
* JVM part of this example is a server with simple static webapp sharing and bot which just gives the webapp button to open webapp
* JS part is the WebApp with one button and reacting to chaged user theme and app viewport
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
| 2 | `WEB_APP_URL` | Public HTTPS URL where the WebApp is hosted |
| 3 *(optional)* | `PORT` | Port for the Ktor server (default: `8080`) |
Optional flags:
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
## Bot Commands
| Command | Description |
|---------|-------------|
| `/reply_markup` | Send a reply keyboard containing a WebApp button |
| `/inline` | Send an inline keyboard containing a WebApp button |
| `/attachment_menu` | Send an attachment-menu WebApp button |
| `/prepareKeyboard` | Retrieve and display the saved prepared inline keyboard button |
## Server Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/inline` | Accept an inline query result submitted from the WebApp |
| `POST` | `/check` | Validate the `initData` signature sent by the WebApp |
| `POST` | `/setCustomEmoji` | Set a custom emoji status for the user based on WebApp data |
| `POST` | `/getPreparedKeyboardButtonId` | Return the ID of a previously saved prepared inline keyboard button |
## Capabilities
- Serves the compiled JS WebApp as static files
- HMAC-SHA256 validation of Telegram WebApp `initData`
- Custom emoji status setting via `setUserEmojiStatus`
- Prepared inline keyboard button saved with `savePreparedInlineMessage`
- Supports all three WebApp button surfaces: reply keyboard, inline keyboard, attachment menu
- Requires a domain with valid SSL (or a Telegram test account)
- JVM server + Kotlin/JS frontend in a single Gradle multiplatform project
## Launch
## How to run
```bash
./gradlew :WebApp:run --args="BOT_TOKEN https://your.domain.com 8080"
./gradlew run --args="TOKEN WEB_APP_ADDRESS"
```

View File

@@ -1,53 +1,28 @@
# WebHooks
A bot that uses a Telegram webhook instead of long polling, served via an embedded Ktor HTTP server.
## Functionality
Registers a webhook URL with Telegram, starts a Ktor server on the configured port, and processes
incoming updates through that server. Responds to `/start` with information about the active webhook
configuration.
## Arguments
| Position | Value | Description |
|----------|-------|-------------|
| 1 | `BOT_TOKEN` | Telegram bot token |
| 2+ | `https://...` | One or more HTTPS URLs to register as the webhook URL |
Additional optional arguments (any order, after token and URL):
| Value | Description |
|-------|-------------|
| `debug` | Enable verbose debug logging |
| `testServer` | Connect to the Telegram test server instead of production |
| *any number* | Port to listen on (e.g. `8080`); defaults to `8080` |
| *any other string* | Sub-path to mount the webhook route on (e.g. `it/is/subpath`) |
### Example
```
BOT_TOKEN https://sample.com it/is/subpath 8080
```
- Webhook registered as `https://sample.com/it/is/subpath`
- Ktor listens on `0.0.0.0:8080` at path `/it/is/subpath`
## Bot Commands
| Command | Description |
|---------|-------------|
| `/start` | Replies with current webhook URL and configuration details |
## Capabilities
- Full webhook integration via `setWebhook` + Ktor route
- Configurable listening port and sub-path
- Optional debug mode
- Runs via Ktor embedded server (not long polling)
Launches webhook-based simple bot. Use `/start` with bot to get simple info about webhooks
## Launch
```bash
../gradlew run --args="BOT_TOKEN https://sample.com it/is/subpath 8080"
../gradlew run --args="BOT_TOKEN https://sample.com it/is/subpath 8080 debug"
```
Required arguments:
1. Token
2. Arguments starting with `https://`
Optional arguments:
* Any argument == `debug` to enable debug mode
* Any argument **not** starting with `https://` and **not** equal to `debug` as **subpath** (will be used as
subroute to place listening of webhooks)
* Any argument as number of port
Sample: `TOKEN https://sample.com it/is/subpath 8080` will result to:
* `TOKEN` used as token
* Bot will set up its webhook info as `https://sample.com/it/is/subpath`
* Bot will set up to listen webhooks on route `it/is/subpath`
* Bot will start to listen any incoming request on port `8080` and url `0.0.0.0`

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

View File

@@ -5,8 +5,8 @@ org.gradle.jvmargs=-Xmx3148m
kotlin.daemon.jvmargs=-Xmx3g -Xms500m
kotlin_version=2.3.20
telegram_bot_api_version=33.0.0
kotlin_version=2.4.0
telegram_bot_api_version=34.0.0
micro_utils_version=0.29.1
serialization_version=1.10.0
ktor_version=3.4.1

View File

@@ -71,3 +71,9 @@ include ":GiftsBot"
include ":TagsBot"
include ":ManagedBotsBot"
include ":GuestQueryBot"
include ":LivePhotosBot"
include ":ChatManagementBot"