mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI-examples.git
synced 2026-06-06 15:47:17 +00:00
Compare commits
1 Commits
34.0.0
...
791ebcbe56
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
791ebcbe56 |
@@ -3,7 +3,6 @@ import dev.inmo.kslog.common.LogLevel
|
|||||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||||
import dev.inmo.kslog.common.setDefaultKSLog
|
import dev.inmo.kslog.common.setDefaultKSLog
|
||||||
import dev.inmo.micro_utils.common.Percentage
|
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.answers.answer
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountStarBalance
|
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountStarBalance
|
||||||
@@ -28,8 +27,7 @@ import dev.inmo.tgbotapi.extensions.api.stories.deleteStory
|
|||||||
import dev.inmo.tgbotapi.extensions.api.stories.postStory
|
import dev.inmo.tgbotapi.extensions.api.stories.postStory
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
||||||
import dev.inmo.tgbotapi.extensions.utils.chatContentMessageOrNull
|
import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull
|
||||||
import dev.inmo.tgbotapi.extensions.utils.chatMessageOrNull
|
|
||||||
import dev.inmo.tgbotapi.extensions.utils.extendedPrivateChatOrThrow
|
import dev.inmo.tgbotapi.extensions.utils.extendedPrivateChatOrThrow
|
||||||
import dev.inmo.tgbotapi.extensions.utils.ifAccessibleMessage
|
import dev.inmo.tgbotapi.extensions.utils.ifAccessibleMessage
|
||||||
import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage
|
import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage
|
||||||
@@ -46,15 +44,13 @@ import dev.inmo.tgbotapi.types.MessageId
|
|||||||
import dev.inmo.tgbotapi.types.RawChatId
|
import dev.inmo.tgbotapi.types.RawChatId
|
||||||
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
|
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
|
||||||
import dev.inmo.tgbotapi.types.chat.PrivateChat
|
import dev.inmo.tgbotapi.types.chat.PrivateChat
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
|
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||||
import dev.inmo.tgbotapi.types.message.content.LivePhotoContent
|
|
||||||
import dev.inmo.tgbotapi.types.message.content.PhotoContent
|
import dev.inmo.tgbotapi.types.message.content.PhotoContent
|
||||||
import dev.inmo.tgbotapi.types.message.content.StoryContent
|
import dev.inmo.tgbotapi.types.message.content.StoryContent
|
||||||
import dev.inmo.tgbotapi.types.message.content.TextContent
|
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||||
import dev.inmo.tgbotapi.types.message.content.VideoContent
|
import dev.inmo.tgbotapi.types.message.content.VideoContent
|
||||||
import dev.inmo.tgbotapi.types.message.content.VisualMediaGroupPartContent
|
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.InputStoryContent.*
|
|
||||||
import dev.inmo.tgbotapi.types.stories.StoryArea
|
import dev.inmo.tgbotapi.types.stories.StoryArea
|
||||||
import dev.inmo.tgbotapi.types.stories.StoryAreaPosition
|
import dev.inmo.tgbotapi.types.stories.StoryAreaPosition
|
||||||
import dev.inmo.tgbotapi.types.stories.StoryAreaType
|
import dev.inmo.tgbotapi.types.stories.StoryAreaType
|
||||||
@@ -124,15 +120,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
if (businessContentMessage.sentByBusinessConnectionOwner) {
|
if (businessContentMessage.sentByBusinessConnectionOwner) {
|
||||||
reply(sent, "You have sent this message to the ${businessContentMessage.businessConnectionId.string} related chat")
|
reply(sent, "You have sent this message to the ${businessContentMessage.businessConnectionId.string} related chat")
|
||||||
} else {
|
} 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(
|
reply(
|
||||||
to = sent,
|
to = sent,
|
||||||
text = "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat",
|
text = "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat",
|
||||||
@@ -216,8 +203,6 @@ 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 ->
|
onCommandWithArgs("set_business_account_name", initialFilter = { it.chat is PrivateChat }) { it, args ->
|
||||||
val firstName = args[0]
|
val firstName = args[0]
|
||||||
val secondName = args.getOrNull(1)
|
val secondName = args.getOrNull(1)
|
||||||
@@ -228,8 +213,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
firstName,
|
firstName,
|
||||||
secondName
|
secondName
|
||||||
)
|
)
|
||||||
}.map {
|
|
||||||
true
|
|
||||||
}.getOrElse { false }
|
}.getOrElse { false }
|
||||||
reply(it) {
|
reply(it) {
|
||||||
if (set) {
|
if (set) {
|
||||||
@@ -247,8 +230,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
businessConnectionId,
|
businessConnectionId,
|
||||||
username
|
username
|
||||||
)
|
)
|
||||||
}.map {
|
|
||||||
true
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
false
|
false
|
||||||
@@ -286,8 +267,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
val transferred = runCatching {
|
val transferred = runCatching {
|
||||||
transferBusinessAccountStars(businessConnectionId, count)
|
transferBusinessAccountStars(businessConnectionId, count)
|
||||||
}.map {
|
|
||||||
true
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
false
|
false
|
||||||
@@ -331,8 +310,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
businessConnectionId,
|
businessConnectionId,
|
||||||
bio
|
bio
|
||||||
)
|
)
|
||||||
}.map {
|
|
||||||
true
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
false
|
false
|
||||||
@@ -350,8 +327,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
businessConnectionId,
|
businessConnectionId,
|
||||||
initialBio
|
initialBio
|
||||||
)
|
)
|
||||||
}.map {
|
|
||||||
true
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
false
|
false
|
||||||
@@ -364,9 +339,9 @@ suspend fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
suspend fun handleSetProfilePhoto(it: ChatContentMessage<TextContent>, isPublic: Boolean) {
|
suspend fun handleSetProfilePhoto(it: CommonMessage<TextContent>, isPublic: Boolean) {
|
||||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@handleSetProfilePhoto
|
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@handleSetProfilePhoto
|
||||||
val replyTo = it.replyTo ?.chatContentMessageOrNull() ?.withContentOrNull<PhotoContent>()
|
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<PhotoContent>()
|
||||||
if (replyTo == null) {
|
if (replyTo == null) {
|
||||||
reply(it) {
|
reply(it) {
|
||||||
+"Reply to photo for using of this command"
|
+"Reply to photo for using of this command"
|
||||||
@@ -383,8 +358,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
),
|
),
|
||||||
isPublic = isPublic
|
isPublic = isPublic
|
||||||
)
|
)
|
||||||
}.map {
|
|
||||||
true
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
false
|
false
|
||||||
@@ -403,8 +376,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
businessConnectionId,
|
businessConnectionId,
|
||||||
isPublic = isPublic
|
isPublic = isPublic
|
||||||
)
|
)
|
||||||
}.map {
|
|
||||||
true
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
false
|
false
|
||||||
@@ -426,7 +397,7 @@ suspend fun main(args: Array<String>) {
|
|||||||
|
|
||||||
onCommand("post_story", initialFilter = { it.chat is PrivateChat }) {
|
onCommand("post_story", initialFilter = { it.chat is PrivateChat }) {
|
||||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
|
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
|
||||||
val replyTo = it.replyTo ?.chatContentMessageOrNull() ?.withContentOrNull<VisualMediaGroupPartContent>()
|
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<VisualMediaGroupPartContent>()
|
||||||
if (replyTo == null) {
|
if (replyTo == null) {
|
||||||
reply(it) {
|
reply(it) {
|
||||||
+"Reply to photo or video for using of this command"
|
+"Reply to photo or video for using of this command"
|
||||||
@@ -439,16 +410,12 @@ suspend fun main(args: Array<String>) {
|
|||||||
postStory(
|
postStory(
|
||||||
businessConnectionId,
|
businessConnectionId,
|
||||||
when (replyTo.content) {
|
when (replyTo.content) {
|
||||||
is PhotoContent -> Photo(
|
is PhotoContent -> InputStoryContent.Photo(
|
||||||
file.multipartFile()
|
file.multipartFile()
|
||||||
)
|
)
|
||||||
is VideoContent -> Video(
|
is VideoContent -> InputStoryContent.Video(
|
||||||
file.multipartFile()
|
file.multipartFile()
|
||||||
)
|
)
|
||||||
is LivePhotoContent -> Video(
|
|
||||||
file.multipartFile(),
|
|
||||||
isAnimation = true
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
activePeriod = PostStory.ACTIVE_PERIOD_6_HOURS,
|
activePeriod = PostStory.ACTIVE_PERIOD_6_HOURS,
|
||||||
areas = listOf(
|
areas = listOf(
|
||||||
@@ -484,7 +451,7 @@ suspend fun main(args: Array<String>) {
|
|||||||
|
|
||||||
onCommand("delete_story", initialFilter = { it.chat is PrivateChat }) {
|
onCommand("delete_story", initialFilter = { it.chat is PrivateChat }) {
|
||||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
|
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
|
||||||
val replyTo = it.replyTo ?.chatContentMessageOrNull() ?.withContentOrNull<StoryContent>()
|
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<StoryContent>()
|
||||||
if (replyTo == null) {
|
if (replyTo == null) {
|
||||||
reply(it) {
|
reply(it) {
|
||||||
+"Reply to photo or video for using of this command"
|
+"Reply to photo or video for using of this command"
|
||||||
@@ -494,8 +461,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
|
|
||||||
val deleted = runCatching {
|
val deleted = runCatching {
|
||||||
deleteStory(businessConnectionId, replyTo.content.story.id)
|
deleteStory(businessConnectionId, replyTo.content.story.id)
|
||||||
}.map {
|
|
||||||
true
|
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
it.printStackTrace()
|
it.printStackTrace()
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
|
||||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
||||||
import dev.inmo.tgbotapi.extensions.api.chat.modify.setChatPhoto
|
import dev.inmo.tgbotapi.extensions.api.chat.modify.setChatPhoto
|
||||||
@@ -16,13 +15,17 @@ suspend fun main(args: Array<String>) {
|
|||||||
bot.buildBehaviourWithLongPolling(scope = CoroutineScope(Dispatchers.IO)) {
|
bot.buildBehaviourWithLongPolling(scope = CoroutineScope(Dispatchers.IO)) {
|
||||||
onPhoto {
|
onPhoto {
|
||||||
val bytes = downloadFile(it.content)
|
val bytes = downloadFile(it.content)
|
||||||
runCatchingLogging {
|
runCatchingSafely {
|
||||||
setChatPhoto(
|
setChatPhoto(
|
||||||
it.chat.id,
|
it.chat.id,
|
||||||
bytes.asMultipartFile("sample.jpg")
|
bytes.asMultipartFile("sample.jpg")
|
||||||
)
|
)
|
||||||
}.onSuccess { _ ->
|
}.onSuccess { b ->
|
||||||
reply(it, "Done")
|
if (b) {
|
||||||
|
reply(it, "Done")
|
||||||
|
} else {
|
||||||
|
reply(it, "Something went wrong")
|
||||||
|
}
|
||||||
}.onFailure { e ->
|
}.onFailure { e ->
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
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.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.chatContentMessageOrNull
|
|
||||||
import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull
|
|
||||||
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.restrictedMemberChatMemberOrNull
|
|
||||||
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 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()
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import dev.inmo.kslog.common.defaultMessageFormatter
|
|||||||
import dev.inmo.kslog.common.setDefaultKSLog
|
import dev.inmo.kslog.common.setDefaultKSLog
|
||||||
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
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.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
|
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
|
||||||
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
||||||
@@ -32,7 +33,7 @@ import dev.inmo.tgbotapi.extensions.utils.previewChannelDirectMessagesChatOrNull
|
|||||||
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
|
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
|
||||||
import dev.inmo.tgbotapi.types.checklists.ChecklistTaskId
|
import dev.inmo.tgbotapi.types.checklists.ChecklistTaskId
|
||||||
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
|
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
|
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||||
import dev.inmo.tgbotapi.types.message.content.ChecklistContent
|
import dev.inmo.tgbotapi.types.message.content.ChecklistContent
|
||||||
import dev.inmo.tgbotapi.types.message.textsources.TextSourcesList
|
import dev.inmo.tgbotapi.types.message.textsources.TextSourcesList
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import dev.inmo.kslog.common.KSLog
|
|||||||
import dev.inmo.kslog.common.LogLevel
|
import dev.inmo.kslog.common.LogLevel
|
||||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||||
import dev.inmo.kslog.common.setDefaultKSLog
|
import dev.inmo.kslog.common.setDefaultKSLog
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
|
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
|
||||||
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
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.AudioMediaGroupMemberTelegramMedia
|
||||||
import dev.inmo.tgbotapi.types.media.toTelegramMediaAudio
|
import dev.inmo.tgbotapi.types.media.toTelegramMediaAudio
|
||||||
import dev.inmo.tgbotapi.types.media.toTelegramPaidMediaPhoto
|
import dev.inmo.tgbotapi.types.media.toTelegramPaidMediaPhoto
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
|
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -31,8 +31,8 @@ private var BehaviourContextData.update: Update?
|
|||||||
get() = get("update") as? Update
|
get() = get("update") as? Update
|
||||||
set(value) = set("update", value)
|
set(value) = set("update", value)
|
||||||
|
|
||||||
private var BehaviourContextData.commonMessage: ChatContentMessage<*>?
|
private var BehaviourContextData.commonMessage: CommonMessage<*>?
|
||||||
get() = get("commonMessage") as? ChatContentMessage<*>
|
get() = get("commonMessage") as? CommonMessage<*>
|
||||||
set(value) = set("commonMessage", value)
|
set(value) = set("commonMessage", value)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,7 +129,7 @@ suspend fun main(vararg args: String) {
|
|||||||
println(it.chatEvent)
|
println(it.chatEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||||
println(it)
|
println(it)
|
||||||
}
|
}
|
||||||
}.second.join()
|
}.second.join()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
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.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitDeepLinks
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitDeepLinks
|
||||||
@@ -36,7 +36,7 @@ suspend fun main(vararg args: String) {
|
|||||||
onDeepLink { (it, deepLink) ->
|
onDeepLink { (it, deepLink) ->
|
||||||
reply(it, "Ok, I got deep link \"${deepLink}\" in trigger")
|
reply(it, "Ok, I got deep link \"${deepLink}\" in trigger")
|
||||||
}
|
}
|
||||||
waitDeepLinks().subscribeLoggingDropExceptions(this) { (it, deepLink) ->
|
waitDeepLinks().subscribeSafelyWithoutExceptions(this) { (it, deepLink) ->
|
||||||
reply(it, "Ok, I got deep link \"${deepLink}\" in waiter")
|
reply(it, "Ok, I got deep link \"${deepLink}\" in waiter")
|
||||||
println(triggersHolder.handleableCommandsHolder.handleable)
|
println(triggersHolder.handleableCommandsHolder.handleable)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import dev.inmo.kslog.common.w
|
|||||||
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
||||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
||||||
@@ -74,29 +75,8 @@ suspend fun main(vararg args: String) {
|
|||||||
send(it.chat, testText)
|
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(
|
setMyCommands(
|
||||||
BotCommand("test_draft_flow", "Start draft testing with flow"),
|
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
|
scope = BotCommandScope.AllGroupChats
|
||||||
)
|
)
|
||||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import dev.inmo.micro_utils.coroutines.awaitFirst
|
import dev.inmo.micro_utils.coroutines.awaitFirst
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.fsm.common.State
|
import dev.inmo.micro_utils.fsm.common.State
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitAnyContentMessage
|
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.textContentOrNull
|
||||||
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
||||||
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
|
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||||
import dev.inmo.tgbotapi.types.message.content.TextContent
|
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||||
import dev.inmo.tgbotapi.utils.botCommand
|
import dev.inmo.tgbotapi.utils.botCommand
|
||||||
import dev.inmo.tgbotapi.utils.firstOf
|
import dev.inmo.tgbotapi.utils.firstOf
|
||||||
@@ -24,7 +24,7 @@ import kotlinx.coroutines.flow.first
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
sealed interface BotState : State
|
sealed interface BotState : State
|
||||||
data class ExpectContentOrStopState(override val context: IdChatIdentifier, val sourceMessage: ChatContentMessage<TextContent>) : BotState
|
data class ExpectContentOrStopState(override val context: IdChatIdentifier, val sourceMessage: CommonMessage<TextContent>) : BotState
|
||||||
data class StopState(override val context: IdChatIdentifier) : BotState
|
data class StopState(override val context: IdChatIdentifier) : BotState
|
||||||
|
|
||||||
suspend fun main(args: Array<String>) {
|
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))
|
startChain(ExpectContentOrStopState(it.chat.id, it.withContentOrNull() ?: return@onContentMessage))
|
||||||
}
|
}
|
||||||
|
|
||||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||||
println(it)
|
println(it)
|
||||||
}
|
}
|
||||||
}.second.join()
|
}.second.join()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.tgbotapi.extensions.api.files.downloadFile
|
import dev.inmo.tgbotapi.extensions.api.files.downloadFile
|
||||||
import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp
|
import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp
|
||||||
import dev.inmo.tgbotapi.extensions.api.get.getFileAdditionalInfo
|
import dev.inmo.tgbotapi.extensions.api.get.getFileAdditionalInfo
|
||||||
@@ -10,7 +10,6 @@ import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile
|
|||||||
import dev.inmo.tgbotapi.types.actions.*
|
import dev.inmo.tgbotapi.types.actions.*
|
||||||
import dev.inmo.tgbotapi.types.media.TelegramMediaAudio
|
import dev.inmo.tgbotapi.types.media.TelegramMediaAudio
|
||||||
import dev.inmo.tgbotapi.types.media.TelegramMediaDocument
|
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.TelegramMediaPhoto
|
||||||
import dev.inmo.tgbotapi.types.media.TelegramMediaVideo
|
import dev.inmo.tgbotapi.types.media.TelegramMediaVideo
|
||||||
import dev.inmo.tgbotapi.types.message.content.*
|
import dev.inmo.tgbotapi.types.message.content.*
|
||||||
@@ -47,7 +46,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
val action = when (content) {
|
val action = when (content) {
|
||||||
is PhotoContent -> UploadPhotoAction
|
is PhotoContent -> UploadPhotoAction
|
||||||
is AnimationContent,
|
is AnimationContent,
|
||||||
is LivePhotoContent,
|
|
||||||
is VideoContent -> UploadVideoAction
|
is VideoContent -> UploadVideoAction
|
||||||
is StickerContent -> ChooseStickerAction
|
is StickerContent -> ChooseStickerAction
|
||||||
is MediaGroupContent<*> -> UploadPhotoAction
|
is MediaGroupContent<*> -> UploadPhotoAction
|
||||||
@@ -76,7 +74,7 @@ suspend fun main(args: Array<String>) {
|
|||||||
)
|
)
|
||||||
is MediaGroupContent<*> -> replyWithMediaGroup(
|
is MediaGroupContent<*> -> replyWithMediaGroup(
|
||||||
it,
|
it,
|
||||||
content.group.mapNotNull {
|
content.group.map {
|
||||||
when (val innerContent = it.content) {
|
when (val innerContent = it.content) {
|
||||||
is AudioContent -> TelegramMediaAudio(
|
is AudioContent -> TelegramMediaAudio(
|
||||||
downloadFileToTemp(innerContent.media).asMultipartFile()
|
downloadFileToTemp(innerContent.media).asMultipartFile()
|
||||||
@@ -90,10 +88,6 @@ suspend fun main(args: Array<String>) {
|
|||||||
is VideoContent -> TelegramMediaVideo(
|
is VideoContent -> TelegramMediaVideo(
|
||||||
downloadFileToTemp(innerContent.media).asMultipartFile()
|
downloadFileToTemp(innerContent.media).asMultipartFile()
|
||||||
)
|
)
|
||||||
is LivePhotoContent -> TelegramMediaLivePhoto(
|
|
||||||
downloadFileToTemp(innerContent.media).asMultipartFile(),
|
|
||||||
innerContent.media.photo ?.fileId ?: return@mapNotNull null
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -113,16 +107,10 @@ suspend fun main(args: Array<String>) {
|
|||||||
it,
|
it,
|
||||||
outFile.asMultipartFile()
|
outFile.asMultipartFile()
|
||||||
)
|
)
|
||||||
|
|
||||||
is LivePhotoContent -> replyWithLivePhoto(
|
|
||||||
it,
|
|
||||||
outFile.asMultipartFile(),
|
|
||||||
content.media.photo ?.fileId ?: error("Unable to resend live photo files without their photos")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) { println(it) }
|
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
|
||||||
}.second.join()
|
}.second.join()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import dev.inmo.kslog.common.KSLog
|
|||||||
import dev.inmo.kslog.common.LogLevel
|
import dev.inmo.kslog.common.LogLevel
|
||||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||||
import dev.inmo.kslog.common.setDefaultKSLog
|
import dev.inmo.kslog.common.setDefaultKSLog
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow
|
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow
|
||||||
import dev.inmo.tgbotapi.extensions.api.gifts.getChatGiftsFlow
|
import dev.inmo.tgbotapi.extensions.api.gifts.getChatGiftsFlow
|
||||||
@@ -104,7 +105,7 @@ suspend fun main(vararg args: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
// allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||||
// println(it)
|
// println(it)
|
||||||
// }
|
// }
|
||||||
}.second.join()
|
}.second.join()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import dev.inmo.kslog.common.KSLog
|
|||||||
import dev.inmo.kslog.common.LogLevel
|
import dev.inmo.kslog.common.LogLevel
|
||||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||||
import dev.inmo.kslog.common.setDefaultKSLog
|
import dev.inmo.kslog.common.setDefaultKSLog
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayCompleted
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayCompleted
|
||||||
@@ -50,7 +50,7 @@ suspend fun main(vararg args: String) {
|
|||||||
println(it)
|
println(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
// allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||||
// println(it)
|
// println(it)
|
||||||
// }
|
// }
|
||||||
}.second.join()
|
}.second.join()
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
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.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||||
@@ -77,6 +77,6 @@ suspend fun main(vararg args: String) {
|
|||||||
MarkdownV2
|
MarkdownV2
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) { println(it) }
|
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
|
||||||
}.second.join()
|
}.second.join()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.tgbotapi.extensions.api.EditLiveLocationInfo
|
import dev.inmo.tgbotapi.extensions.api.EditLiveLocationInfo
|
||||||
import dev.inmo.tgbotapi.extensions.api.edit.location.live.stopLiveLocation
|
import dev.inmo.tgbotapi.extensions.api.edit.location.live.stopLiveLocation
|
||||||
import dev.inmo.tgbotapi.extensions.api.handleLiveLocation
|
import dev.inmo.tgbotapi.extensions.api.handleLiveLocation
|
||||||
@@ -61,7 +61,7 @@ suspend fun main(vararg args: String) {
|
|||||||
stopLiveLocation(it, replyMarkup = null)
|
stopLiveLocation(it, replyMarkup = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) { println(it) }
|
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
|
||||||
}.second.join()
|
}.second.join()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
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.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
|
||||||
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.onPaidMediaInfoContent
|
|
||||||
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.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}")
|
|
||||||
}
|
|
||||||
|
|
||||||
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
|
|
||||||
println(it)
|
|
||||||
}
|
|
||||||
}.second.join()
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: 'kotlin'
|
|
||||||
apply plugin: 'application'
|
|
||||||
|
|
||||||
mainClassName="CustomBotKt"
|
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
||||||
|
|
||||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
|
||||||
}
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
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.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
|
|
||||||
import dev.inmo.tgbotapi.extensions.utils.groupContentMessageOrNull
|
|
||||||
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.ChatContentMessage
|
|
||||||
import dev.inmo.tgbotapi.types.request.RequestId
|
|
||||||
import dev.inmo.tgbotapi.types.toChatId
|
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
|
|
||||||
private var BehaviourContextData.update: Update?
|
|
||||||
get() = get("update") as? Update
|
|
||||||
set(value) = set("update", value)
|
|
||||||
|
|
||||||
private var BehaviourContextData.commonMessage: ChatContentMessage<*>?
|
|
||||||
get() = get("commonMessage") as? ChatContentMessage<*>
|
|
||||||
set(value) = set("commonMessage", value)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This place can be the playground for your code.
|
|
||||||
*/
|
|
||||||
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,
|
|
||||||
builder = {
|
|
||||||
includeMiddlewares {
|
|
||||||
addMiddleware {
|
|
||||||
doOnRequestReturnResult { result, request, _ ->
|
|
||||||
println("Result of $request:\n\n$result")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
subcontextInitialAction = buildSubcontextInitialAction {
|
|
||||||
add {
|
|
||||||
data.update = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
// start here!!
|
|
||||||
val me = getMe()
|
|
||||||
println(me)
|
|
||||||
|
|
||||||
onCommand("start") {
|
|
||||||
println(data.update)
|
|
||||||
println(data.commonMessage)
|
|
||||||
println(getChat(it.chat))
|
|
||||||
}
|
|
||||||
|
|
||||||
onCommand("canManageBots") {
|
|
||||||
val me = getMe()
|
|
||||||
reply(it, if (me.canManageBots) "Yes" else "No")
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestId = RequestId(0)
|
|
||||||
onCommand("keyboard") {
|
|
||||||
reply(
|
|
||||||
it,
|
|
||||||
"Keyboard",
|
|
||||||
replyMarkup = flatReplyKeyboard(
|
|
||||||
resizeKeyboard = true,
|
|
||||||
oneTimeKeyboard = true,
|
|
||||||
) {
|
|
||||||
requestManagedBotButton(
|
|
||||||
"Add managed bot",
|
|
||||||
KeyboardButtonRequestManagedBot(
|
|
||||||
requestId = requestId,
|
|
||||||
suggestedName = "SampleName",
|
|
||||||
suggestedUsername = Username("@some_sample_bot")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
onManagedBotCreated {
|
|
||||||
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)
|
|
||||||
reply(it, "Token: $token")
|
|
||||||
}
|
|
||||||
|
|
||||||
onManagedBotUpdated {
|
|
||||||
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)
|
|
||||||
send(it.user, "Token: $token")
|
|
||||||
}
|
|
||||||
|
|
||||||
onCommand("replaceToken") {
|
|
||||||
val reply = it.replyTo ?.chatEventMessageOrNull() ?: return@onCommand
|
|
||||||
val managedBotCreated = reply.chatEvent.managedBotCreatedOrNull() ?: return@onCommand
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}.second.join()
|
|
||||||
}
|
|
||||||
@@ -70,18 +70,21 @@ suspend fun main(vararg args: String) {
|
|||||||
|
|
||||||
draftMessagesChannel.send("Photo file have been downloaded. Start set my profile photo")
|
draftMessagesChannel.send("Photo file have been downloaded. Start set my profile photo")
|
||||||
|
|
||||||
setMyProfilePhoto(
|
val setResult = setMyProfilePhoto(
|
||||||
InputProfilePhoto.Static(
|
InputProfilePhoto.Static(
|
||||||
photoFile.asMultipartFile()
|
photoFile.asMultipartFile()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
reply(commandMessage, "New photo have been set")
|
if (setResult) {
|
||||||
|
reply(commandMessage, "New photo have been set")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onCommand("removeMyProfilePhoto") {
|
onCommand("removeMyProfilePhoto") {
|
||||||
runCatchingLogging {
|
runCatchingLogging {
|
||||||
removeMyProfilePhoto()
|
if (removeMyProfilePhoto()) {
|
||||||
reply(it, "Photo have been removed")
|
reply(it, "Photo have been removed")
|
||||||
|
}
|
||||||
}.onFailure { e ->
|
}.onFailure { e ->
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
reply(it, "Something web wrong. See logs for details.")
|
reply(it, "Something web wrong. See logs for details.")
|
||||||
|
|||||||
@@ -2,32 +2,23 @@ import dev.inmo.kslog.common.KSLog
|
|||||||
import dev.inmo.kslog.common.LogLevel
|
import dev.inmo.kslog.common.LogLevel
|
||||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||||
import dev.inmo.kslog.common.setDefaultKSLog
|
import dev.inmo.kslog.common.setDefaultKSLog
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.polls.sendQuizPoll
|
import dev.inmo.tgbotapi.extensions.api.send.polls.sendQuizPoll
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.polls.sendRegularPoll
|
import dev.inmo.tgbotapi.extensions.api.send.polls.sendRegularPoll
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollAnswer
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollAnswer
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollOptionAdded
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollOptionDeleted
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollUpdates
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollUpdates
|
||||||
import dev.inmo.tgbotapi.extensions.utils.accessibleMessageOrNull
|
|
||||||
import dev.inmo.tgbotapi.extensions.utils.chatContentMessageOrNull
|
|
||||||
import dev.inmo.tgbotapi.extensions.utils.customEmojiTextSourceOrNull
|
import dev.inmo.tgbotapi.extensions.utils.customEmojiTextSourceOrNull
|
||||||
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgsSources
|
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgsSources
|
||||||
import dev.inmo.tgbotapi.types.BotCommand
|
import dev.inmo.tgbotapi.types.BotCommand
|
||||||
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||||
import dev.inmo.tgbotapi.types.PollId
|
import dev.inmo.tgbotapi.types.PollId
|
||||||
import dev.inmo.tgbotapi.types.ReplyParameters
|
import dev.inmo.tgbotapi.types.ReplyParameters
|
||||||
import dev.inmo.tgbotapi.types.media.TelegramMediaLocation
|
|
||||||
import dev.inmo.tgbotapi.types.media.TelegramMediaVenue
|
|
||||||
import dev.inmo.tgbotapi.types.polls.InputPollOption
|
import dev.inmo.tgbotapi.types.polls.InputPollOption
|
||||||
import dev.inmo.tgbotapi.types.polls.PollAnswer
|
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.buildEntities
|
||||||
import dev.inmo.tgbotapi.utils.customEmoji
|
import dev.inmo.tgbotapi.utils.customEmoji
|
||||||
import dev.inmo.tgbotapi.utils.regular
|
import dev.inmo.tgbotapi.utils.regular
|
||||||
@@ -39,22 +30,11 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This bot demonstrates poll features including the new API additions:
|
* This bot will answer with anonymous or public poll and send message on
|
||||||
|
* any update.
|
||||||
*
|
*
|
||||||
* * `/anonymous` — anonymous regular poll
|
* * Use `/anonymous` to take anonymous regular poll
|
||||||
* * `/public` — public regular poll with option adding
|
* * Use `/public` to take public regular poll
|
||||||
* * `/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) {
|
suspend fun main(vararg args: String) {
|
||||||
val botToken = args.first()
|
val botToken = args.first()
|
||||||
@@ -125,9 +105,7 @@ suspend fun main(vararg args: String) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
isAnonymous = false,
|
isAnonymous = false,
|
||||||
replyParameters = ReplyParameters(it),
|
replyParameters = ReplyParameters(it)
|
||||||
allowAddingOptions = true,
|
|
||||||
hideResultsUntilCloses = true,
|
|
||||||
)
|
)
|
||||||
pollToChatMutex.withLock {
|
pollToChatMutex.withLock {
|
||||||
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
|
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
|
||||||
@@ -140,12 +118,7 @@ suspend fun main(vararg args: String) {
|
|||||||
.firstOrNull { it.first.command == "quiz" }
|
.firstOrNull { it.first.command == "quiz" }
|
||||||
?.second
|
?.second
|
||||||
?.firstNotNullOfOrNull { it.customEmojiTextSourceOrNull() }
|
?.firstNotNullOfOrNull { it.customEmojiTextSourceOrNull() }
|
||||||
val correctAnswer = mutableListOf<Int>()
|
val correctAnswer = Random.nextInt(10)
|
||||||
(1 until Random.nextInt(9)).forEach {
|
|
||||||
val option = Random.nextInt(10)
|
|
||||||
if (correctAnswer.contains(option)) return@forEach
|
|
||||||
correctAnswer.add(option)
|
|
||||||
}
|
|
||||||
val sentPoll = sendQuizPoll(
|
val sentPoll = sendQuizPoll(
|
||||||
it.chat.id,
|
it.chat.id,
|
||||||
questionEntities = buildEntities {
|
questionEntities = buildEntities {
|
||||||
@@ -154,13 +127,7 @@ suspend fun main(vararg args: String) {
|
|||||||
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
|
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
descriptionTextSources = buildEntities {
|
(1 .. 10).map {
|
||||||
regular("Test quiz poll description:")
|
|
||||||
if (customEmoji != null) {
|
|
||||||
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options = (1 .. 10).map {
|
|
||||||
InputPollOption {
|
InputPollOption {
|
||||||
regular(it.toString()) + " "
|
regular(it.toString()) + " "
|
||||||
if (customEmoji != null) {
|
if (customEmoji != null) {
|
||||||
@@ -170,11 +137,7 @@ suspend fun main(vararg args: String) {
|
|||||||
},
|
},
|
||||||
isAnonymous = false,
|
isAnonymous = false,
|
||||||
replyParameters = ReplyParameters(it),
|
replyParameters = ReplyParameters(it),
|
||||||
correctOptionIds = correctAnswer.sorted(),
|
correctOptionId = correctAnswer,
|
||||||
allowsMultipleAnswers = correctAnswer.size > 1,
|
|
||||||
allowsRevoting = true,
|
|
||||||
shuffleOptions = true,
|
|
||||||
hideResultsUntilCloses = true,
|
|
||||||
explanationTextSources = buildEntities {
|
explanationTextSources = buildEntities {
|
||||||
regular("Random solved it to be ") + underline((correctAnswer + 1).toString()) + " "
|
regular("Random solved it to be ") + underline((correctAnswer + 1).toString()) + " "
|
||||||
if (customEmoji != null) {
|
if (customEmoji != null) {
|
||||||
@@ -182,121 +145,6 @@ suspend fun main(vararg args: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
println("Sent poll data: $sentPoll")
|
|
||||||
pollToChatMutex.withLock {
|
|
||||||
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 sentPoll = sendRegularPoll(
|
|
||||||
it.chat.id,
|
|
||||||
buildEntities { regular("Which venue would you visit?") },
|
|
||||||
listOf(
|
|
||||||
// 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") },
|
|
||||||
),
|
|
||||||
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 = false,
|
|
||||||
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 = false,
|
|
||||||
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 {
|
pollToChatMutex.withLock {
|
||||||
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
|
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
|
||||||
}
|
}
|
||||||
@@ -314,50 +162,9 @@ suspend fun main(vararg args: String) {
|
|||||||
onPollUpdates {
|
onPollUpdates {
|
||||||
val chatId = pollToChat[it.id] ?: return@onPollUpdates
|
val chatId = pollToChat[it.id] ?: return@onPollUpdates
|
||||||
|
|
||||||
// Poll.media — PollMedia attached to the poll question (new field)
|
when(it.isAnonymous) {
|
||||||
// Poll.membersOnly — whether poll is restricted to channel members (new field)
|
false -> send(chatId, "[onPollUpdates] Public poll updated: ${it.options.joinToString()}")
|
||||||
// Poll.countryCodes — country restriction list (new field)
|
true -> send(chatId, "[onPollUpdates] Anonymous poll updated: ${it.options.joinToString()}")
|
||||||
// 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() ?.chatContentMessageOrNull() ?.let { pollMessage ->
|
|
||||||
reply(pollMessage) {
|
|
||||||
+"Poll option added: \n"
|
|
||||||
+it.chatEvent.optionTextSources
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPollOptionDeleted {
|
|
||||||
it.chatEvent.pollMessage ?.accessibleMessageOrNull() ?.chatContentMessageOrNull() ?.let { pollMessage ->
|
|
||||||
reply(pollMessage) {
|
|
||||||
+"Poll option deleted: \n"
|
|
||||||
+it.chatEvent.optionTextSources
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onContentMessage {
|
|
||||||
val replyPollOptionId = it.replyInfo ?.pollOptionId ?: return@onContentMessage
|
|
||||||
it.replyTo ?.accessibleMessageOrNull() ?.chatContentMessageOrNull() ?.let { replied ->
|
|
||||||
reply(replied, pollOptionId = replyPollOptionId) {
|
|
||||||
+"Reply to poll option"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,13 +172,8 @@ suspend fun main(vararg args: String) {
|
|||||||
BotCommand("anonymous", "Create anonymous regular poll"),
|
BotCommand("anonymous", "Create anonymous regular poll"),
|
||||||
BotCommand("public", "Create non anonymous regular poll"),
|
BotCommand("public", "Create non anonymous regular poll"),
|
||||||
BotCommand("quiz", "Create quiz poll with random right answer"),
|
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.subscribeLoggingDropExceptions(scope = this) { println(it) }
|
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
|
||||||
}.second.join()
|
}.second.join()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
|
|||||||
import dev.inmo.tgbotapi.types.chat.member.ChatCommonAdministratorRights
|
import dev.inmo.tgbotapi.types.chat.member.ChatCommonAdministratorRights
|
||||||
import dev.inmo.tgbotapi.types.commands.BotCommandScope
|
import dev.inmo.tgbotapi.types.commands.BotCommandScope
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.AccessibleMessage
|
import dev.inmo.tgbotapi.types.message.abstracts.AccessibleMessage
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.ChatMessage
|
|
||||||
import dev.inmo.tgbotapi.types.request.RequestId
|
import dev.inmo.tgbotapi.types.request.RequestId
|
||||||
import dev.inmo.tgbotapi.utils.*
|
import dev.inmo.tgbotapi.utils.*
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
@@ -209,7 +208,7 @@ suspend fun main(args: Array<String>) {
|
|||||||
) {
|
) {
|
||||||
val replyMessage = it.replyTo
|
val replyMessage = it.replyTo
|
||||||
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
|
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
|
||||||
if (replyMessage is ChatMessage) {
|
if (replyMessage is AccessibleMessage) {
|
||||||
reply(
|
reply(
|
||||||
replyMessage,
|
replyMessage,
|
||||||
"Manage keyboard:",
|
"Manage keyboard:",
|
||||||
@@ -230,7 +229,7 @@ suspend fun main(args: Array<String>) {
|
|||||||
val replyMessage = it.replyTo
|
val replyMessage = it.replyTo
|
||||||
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
|
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
|
||||||
|
|
||||||
if (replyMessage is ChatMessage) {
|
if (replyMessage is AccessibleMessage) {
|
||||||
reply(
|
reply(
|
||||||
replyMessage,
|
replyMessage,
|
||||||
"Manage keyboard:",
|
"Manage keyboard:",
|
||||||
@@ -248,7 +247,7 @@ suspend fun main(args: Array<String>) {
|
|||||||
initialFilter = { it.user.id == allowedAdmin }
|
initialFilter = { it.user.id == allowedAdmin }
|
||||||
) {
|
) {
|
||||||
val messageReply =
|
val messageReply =
|
||||||
it.message.chatContentMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
|
it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
|
||||||
val userId = messageReply.user.id
|
val userId = messageReply.user.id
|
||||||
val permissions =
|
val permissions =
|
||||||
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
|
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
|
||||||
@@ -335,7 +334,7 @@ suspend fun main(args: Array<String>) {
|
|||||||
initialFilter = { it.user.id == allowedAdmin }
|
initialFilter = { it.user.id == allowedAdmin }
|
||||||
) {
|
) {
|
||||||
val messageReply =
|
val messageReply =
|
||||||
it.message.chatContentMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
|
it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
|
||||||
val userId = messageReply.user.id
|
val userId = messageReply.user.id
|
||||||
val permissions =
|
val permissions =
|
||||||
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
|
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
|
||||||
|
|||||||
@@ -44,11 +44,7 @@ suspend fun main(args: Array<String>) {
|
|||||||
onCommand("delete") {
|
onCommand("delete") {
|
||||||
val deleted = runCatchingSafely {
|
val deleted = runCatchingSafely {
|
||||||
deleteStickerSet(it.chat.stickerSetName())
|
deleteStickerSet(it.chat.stickerSetName())
|
||||||
}.map {
|
}.getOrElse { false }
|
||||||
true
|
|
||||||
}.getOrElse {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
reply(it, "Deleted")
|
reply(it, "Deleted")
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import dev.inmo.tgbotapi.extensions.utils.previewChannelDirectMessagesChatOrNull
|
|||||||
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
|
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
|
||||||
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
|
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.ChannelPaidPost
|
import dev.inmo.tgbotapi.types.message.abstracts.ChannelPaidPost
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.ChatContentMessage
|
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
import dev.inmo.tgbotapi.utils.firstOf
|
import dev.inmo.tgbotapi.utils.firstOf
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import dev.inmo.kslog.common.LogLevel
|
|||||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||||
import dev.inmo.kslog.common.setDefaultKSLog
|
import dev.inmo.kslog.common.setDefaultKSLog
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.tgbotapi.abstracts.FromUser
|
import dev.inmo.tgbotapi.abstracts.FromUser
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow
|
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButtonId
|
|
||||||
import dev.inmo.tgbotapi.types.request.RequestId
|
|
||||||
import kotlin.random.Random
|
|
||||||
import kotlin.random.nextUInt
|
|
||||||
|
|
||||||
val preparedSampleKeyboardRequestId = RequestId(Random.nextUInt().toUShort())
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
|
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
|
||||||
import dev.inmo.tgbotapi.types.CustomEmojiId
|
import dev.inmo.tgbotapi.types.CustomEmojiId
|
||||||
import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButtonId
|
|
||||||
import dev.inmo.tgbotapi.types.userIdField
|
import dev.inmo.tgbotapi.types.userIdField
|
||||||
import dev.inmo.tgbotapi.types.webAppQueryIdField
|
import dev.inmo.tgbotapi.types.webAppQueryIdField
|
||||||
import dev.inmo.tgbotapi.webapps.*
|
import dev.inmo.tgbotapi.webapps.*
|
||||||
@@ -18,7 +17,6 @@ import io.ktor.client.request.*
|
|||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
import io.ktor.http.content.TextContent
|
import io.ktor.http.content.TextContent
|
||||||
import kotlinx.browser.document
|
|
||||||
import kotlinx.browser.window
|
import kotlinx.browser.window
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.dom.appendElement
|
import kotlinx.dom.appendElement
|
||||||
@@ -67,12 +65,7 @@ fun main() {
|
|||||||
}
|
}
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val isSafeState = remember { mutableStateOf<Boolean?>(null) }
|
val isSafeState = remember { mutableStateOf<Boolean?>(null) }
|
||||||
val logsState = remember {
|
val logsState = remember { mutableStateListOf<Any?>() }
|
||||||
mutableStateListOf<Any?>(
|
|
||||||
window.location.href,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val buttonIdState = remember { mutableStateOf<PreparedKeyboardButtonId?>(null) }
|
|
||||||
|
|
||||||
// Text(window.location.href)
|
// Text(window.location.href)
|
||||||
// P()
|
// P()
|
||||||
@@ -101,30 +94,6 @@ fun main() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(baseUrl) {
|
|
||||||
val response = client.post("$baseUrl/getPreparedKeyboardButtonId") {
|
|
||||||
setBody(
|
|
||||||
Json.encodeToString(
|
|
||||||
WebAppDataWrapper.serializer(),
|
|
||||||
WebAppDataWrapper(webApp.initData, webApp.initDataUnsafe.hash)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
parameter(userIdField, webApp.initDataUnsafe.user ?.id ?.long ?: return@LaunchedEffect)
|
|
||||||
}
|
|
||||||
when (response.status) {
|
|
||||||
HttpStatusCode.OK -> {
|
|
||||||
val buttonId = response.bodyAsText()
|
|
||||||
buttonIdState.value = PreparedKeyboardButtonId(buttonId)
|
|
||||||
}
|
|
||||||
HttpStatusCode.NoContent -> {
|
|
||||||
buttonIdState.value = null
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
logsState.add("Error while getting prepared keyboard button id: ${response.status}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
when (isSafeState.value) {
|
when (isSafeState.value) {
|
||||||
null -> "Checking safe state..."
|
null -> "Checking safe state..."
|
||||||
@@ -280,23 +249,6 @@ fun main() {
|
|||||||
Text("Confirm")
|
Text("Confirm")
|
||||||
}
|
}
|
||||||
|
|
||||||
P()
|
|
||||||
H3 { Text("Prepared keyboard button") }
|
|
||||||
val buttonIdValue = buttonIdState.value
|
|
||||||
if (buttonIdValue == null) {
|
|
||||||
Text("Ensure that you have called /prepareKeyboard in bot. If you did it, check logs of server")
|
|
||||||
} else {
|
|
||||||
Button({
|
|
||||||
onClick {
|
|
||||||
webApp.requestChat(buttonIdValue) {
|
|
||||||
logsState.add("Chat have been received: $it")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Text("Prepared keyboard button")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
P()
|
P()
|
||||||
H3 { Text("Write access callbacks") }
|
H3 { Text("Write access callbacks") }
|
||||||
Button({
|
Button({
|
||||||
@@ -444,15 +396,11 @@ fun main() {
|
|||||||
}
|
}
|
||||||
mainButton.apply {
|
mainButton.apply {
|
||||||
setText("Main button")
|
setText("Main button")
|
||||||
runCatching {
|
setParams(
|
||||||
setParams(
|
BottomButtonParams(
|
||||||
BottomButtonParams(
|
iconCustomEmojiId = CustomEmojiId("5370976574969486150") // 😏
|
||||||
iconCustomEmojiId = CustomEmojiId("5370976574969486150") // 😏
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}.onFailure {
|
)
|
||||||
logsState.add("Can't set params for main button: $it")
|
|
||||||
}
|
|
||||||
onClick {
|
onClick {
|
||||||
logsState.add("Main button clicked")
|
logsState.add("Main button clicked")
|
||||||
hapticFeedback.notificationOccurred(
|
hapticFeedback.notificationOccurred(
|
||||||
@@ -463,15 +411,11 @@ fun main() {
|
|||||||
}
|
}
|
||||||
secondaryButton.apply {
|
secondaryButton.apply {
|
||||||
setText("Secondary button")
|
setText("Secondary button")
|
||||||
runCatching {
|
setParams(
|
||||||
setParams(
|
BottomButtonParams(
|
||||||
BottomButtonParams(
|
iconCustomEmojiId = CustomEmojiId("5370763368497944736") // 😒
|
||||||
iconCustomEmojiId = CustomEmojiId("5370763368497944736") // 😒
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}.onFailure {
|
)
|
||||||
logsState.add("Can't set params for secondary button: $it")
|
|
||||||
}
|
|
||||||
onClick {
|
onClick {
|
||||||
logsState.add("Secondary button clicked")
|
logsState.add("Secondary button clicked")
|
||||||
hapticFeedback.notificationOccurred(
|
hapticFeedback.notificationOccurred(
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import dev.inmo.micro_utils.ktor.server.createKtorServer
|
|||||||
import dev.inmo.tgbotapi.extensions.api.answers.answerInlineQuery
|
import dev.inmo.tgbotapi.extensions.api.answers.answerInlineQuery
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
||||||
import dev.inmo.tgbotapi.extensions.api.savePreparedKeyboardButton
|
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||||
import dev.inmo.tgbotapi.extensions.api.set.setUserEmojiStatus
|
import dev.inmo.tgbotapi.extensions.api.set.setUserEmojiStatus
|
||||||
@@ -22,10 +21,6 @@ import dev.inmo.tgbotapi.requests.answers.InlineQueryResultsButton
|
|||||||
import dev.inmo.tgbotapi.types.*
|
import dev.inmo.tgbotapi.types.*
|
||||||
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
|
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
|
||||||
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent
|
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent
|
||||||
import dev.inmo.tgbotapi.types.buttons.KeyboardButtonRequestManagedBot
|
|
||||||
import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButton
|
|
||||||
import dev.inmo.tgbotapi.types.buttons.PreparedKeyboardButtonId
|
|
||||||
import dev.inmo.tgbotapi.types.buttons.reply.requestManagedBotReplyButton
|
|
||||||
import dev.inmo.tgbotapi.types.webapps.WebAppInfo
|
import dev.inmo.tgbotapi.types.webapps.WebAppInfo
|
||||||
import dev.inmo.tgbotapi.utils.*
|
import dev.inmo.tgbotapi.utils.*
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
@@ -64,7 +59,6 @@ suspend fun main(vararg args: String) {
|
|||||||
val initiationLogger = KSLog("Initialization")
|
val initiationLogger = KSLog("Initialization")
|
||||||
|
|
||||||
val bot = telegramBot(telegramBotAPIUrlsKeeper)
|
val bot = telegramBot(telegramBotAPIUrlsKeeper)
|
||||||
val usersToButtonsMap = mutableMapOf<UserId, PreparedKeyboardButtonId>()
|
|
||||||
createKtorServer(
|
createKtorServer(
|
||||||
"0.0.0.0",
|
"0.0.0.0",
|
||||||
args.getOrNull(2) ?.toIntOrNull() ?: 8080
|
args.getOrNull(2) ?.toIntOrNull() ?: 8080
|
||||||
@@ -129,24 +123,6 @@ suspend fun main(vararg args: String) {
|
|||||||
|
|
||||||
call.respond(HttpStatusCode.OK, set.toString())
|
call.respond(HttpStatusCode.OK, set.toString())
|
||||||
}
|
}
|
||||||
post("getPreparedKeyboardButtonId") {
|
|
||||||
val requestBody = call.receiveText()
|
|
||||||
val webAppCheckData = Json.decodeFromString(WebAppDataWrapper.serializer(), requestBody)
|
|
||||||
|
|
||||||
val isSafe = telegramBotAPIUrlsKeeper.checkWebAppData(webAppCheckData.data, webAppCheckData.hash)
|
|
||||||
val rawUserId = call.parameters[userIdField] ?.toLongOrNull() ?.let(::RawChatId) ?: error("$userIdField should be presented as long value")
|
|
||||||
|
|
||||||
if (isSafe) {
|
|
||||||
val buttonId = usersToButtonsMap[UserId(rawUserId)]
|
|
||||||
if (buttonId == null) {
|
|
||||||
call.respond(HttpStatusCode.NoContent)
|
|
||||||
} else {
|
|
||||||
call.respond(HttpStatusCode.OK, buttonId.string)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
call.respond(HttpStatusCode.Forbidden)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.start(false)
|
}.start(false)
|
||||||
|
|
||||||
@@ -195,20 +171,6 @@ suspend fun main(vararg args: String) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onCommand("prepareKeyboard") {
|
|
||||||
val preparedKeyboardButton = savePreparedKeyboardButton(
|
|
||||||
userId = it.chat.id.toChatId(),
|
|
||||||
button = requestManagedBotReplyButton(
|
|
||||||
text = "Saved sample button",
|
|
||||||
requestManagedBot = KeyboardButtonRequestManagedBot(
|
|
||||||
requestId = preparedSampleKeyboardRequestId,
|
|
||||||
suggestedName = "Saved sample button bot",
|
|
||||||
suggestedUsername = Username.prepare("saved_sample_button_bot")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
usersToButtonsMap[it.chat.id.toChatId()] = preparedKeyboardButton.id
|
|
||||||
}
|
|
||||||
onBaseInlineQuery {
|
onBaseInlineQuery {
|
||||||
answerInlineQuery(
|
answerInlineQuery(
|
||||||
it,
|
it,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// maven { url "https://proxy.nexus.inmo.dev/repository/maven-releases/" }
|
maven { url "https://proxy.nexus.inmo.dev/repository/maven-releases/" }
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ org.gradle.jvmargs=-Xmx3148m
|
|||||||
kotlin.daemon.jvmargs=-Xmx3g -Xms500m
|
kotlin.daemon.jvmargs=-Xmx3g -Xms500m
|
||||||
|
|
||||||
|
|
||||||
kotlin_version=2.3.20
|
kotlin_version=2.2.21
|
||||||
telegram_bot_api_version=34.0.0-t7
|
telegram_bot_api_version=31.2.0
|
||||||
micro_utils_version=0.29.1
|
micro_utils_version=0.26.9
|
||||||
serialization_version=1.10.0
|
serialization_version=1.9.0
|
||||||
ktor_version=3.4.1
|
ktor_version=3.3.2
|
||||||
compose_version=1.10.2
|
compose_version=1.8.2
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
|
||||||
|
|||||||
310
gradlew
vendored
310
gradlew
vendored
@@ -1,78 +1,128 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
app_path=$0
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
# Need this for daisy-chained symlinks.
|
||||||
ls=`ls -ld "$PRG"`
|
while
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
[ -h "$app_path" ]
|
||||||
PRG="$link"
|
do
|
||||||
else
|
ls=$( ls -ld "$app_path" )
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
link=${ls#*' -> '}
|
||||||
fi
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
# This is normally unused
|
||||||
APP_BASE_NAME=`basename "$0"`
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
DEFAULT_JVM_OPTS=""
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@@ -81,92 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
# shellcheck disable=SC2039,SC3045
|
||||||
fi
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
ulimit -n $MAX_FD
|
warn "Could not query maximum file descriptor limit"
|
||||||
if [ $? -ne 0 ] ; then
|
esac
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
case $MAX_FD in #(
|
||||||
fi
|
'' | soft) :;; #(
|
||||||
else
|
*)
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
fi
|
# shellcheck disable=SC2039,SC3045
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
# For Darwin, add options to specify how the application appears in the dock
|
|
||||||
if $darwin; then
|
|
||||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
|
||||||
if $cygwin ; then
|
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
|
||||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
|
||||||
SEP=""
|
|
||||||
for dir in $ROOTDIRSRAW ; do
|
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
|
||||||
SEP="|"
|
|
||||||
done
|
|
||||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
|
||||||
# Add a user-defined pattern to the cygpath arguments
|
|
||||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
|
||||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
|
||||||
fi
|
|
||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
|
||||||
i=0
|
|
||||||
for arg in "$@" ; do
|
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
|
||||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
|
||||||
else
|
|
||||||
eval `echo args$i`="\"$arg\""
|
|
||||||
fi
|
|
||||||
i=$((i+1))
|
|
||||||
done
|
|
||||||
case $i in
|
|
||||||
(0) set -- ;;
|
|
||||||
(1) set -- "$args0" ;;
|
|
||||||
(2) set -- "$args0" "$args1" ;;
|
|
||||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
|
||||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
|
||||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
|
||||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
|
||||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
|
||||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
|
||||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=$(save "$@")
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
|
||||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
|
||||||
cd "$(dirname "$0")"
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
|||||||
79
gradlew.bat
vendored
79
gradlew.bat
vendored
@@ -1,4 +1,22 @@
|
|||||||
@if "%DEBUG%" == "" @echo off
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@@ -9,25 +27,29 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
set DEFAULT_JVM_OPTS=
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
@rem Find java.exe
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto init
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -35,48 +57,35 @@ goto fail
|
|||||||
set JAVA_HOME=%JAVA_HOME:"=%
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto init
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
:init
|
|
||||||
@rem Get command-line arguments, handling Windows variants
|
|
||||||
|
|
||||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
|
||||||
|
|
||||||
:win9xME_args
|
|
||||||
@rem Slurp the command line arguments.
|
|
||||||
set CMD_LINE_ARGS=
|
|
||||||
set _SKIP=2
|
|
||||||
|
|
||||||
:win9xME_args_slurp
|
|
||||||
if "x%~1" == "x" goto execute
|
|
||||||
|
|
||||||
set CMD_LINE_ARGS=%*
|
|
||||||
|
|
||||||
:execute
|
:execute
|
||||||
@rem Setup the command line
|
@rem Setup the command line
|
||||||
|
|
||||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|
||||||
|
|
||||||
@rem Execute Gradle
|
@rem Execute Gradle
|
||||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|||||||
@@ -69,11 +69,3 @@ include ":DraftsBot"
|
|||||||
include ":GiftsBot"
|
include ":GiftsBot"
|
||||||
|
|
||||||
include ":TagsBot"
|
include ":TagsBot"
|
||||||
|
|
||||||
include ":ManagedBotsBot"
|
|
||||||
|
|
||||||
include ":GuestQueryBot"
|
|
||||||
|
|
||||||
include ":LivePhotosBot"
|
|
||||||
|
|
||||||
include ":ChatManagementBot"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user