Compare commits

..

1 Commits

25 changed files with 264 additions and 1179 deletions

View File

@@ -2,69 +2,24 @@ 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.common.Percentage
import dev.inmo.tgbotapi.extensions.api.answers.answer
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountStarBalance
import dev.inmo.tgbotapi.extensions.api.business.deleteBusinessMessages
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGifts
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow
import dev.inmo.tgbotapi.extensions.api.business.readBusinessMessage
import dev.inmo.tgbotapi.extensions.api.business.removeBusinessAccountProfilePhoto
import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountBio
import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountName
import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountProfilePhoto
import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountUsername
import dev.inmo.tgbotapi.extensions.api.business.transferBusinessAccountStars
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.chat.modify.pinChatMessage
import dev.inmo.tgbotapi.extensions.api.chat.modify.unpinChatMessage
import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp
import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.api.stories.deleteStory
import dev.inmo.tgbotapi.extensions.api.stories.postStory
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.extendedPrivateChatOrThrow
import dev.inmo.tgbotapi.extensions.utils.accessibleMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.ifAccessibleMessage
import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage
import dev.inmo.tgbotapi.extensions.utils.textContentOrNull
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.flushAccumulatedUpdates
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
import dev.inmo.tgbotapi.requests.abstracts.multipartFile
import dev.inmo.tgbotapi.requests.business_connection.InputProfilePhoto
import dev.inmo.tgbotapi.requests.stories.PostStory
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.MessageId
import dev.inmo.tgbotapi.types.RawChatId
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
import dev.inmo.tgbotapi.types.chat.PrivateChat
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.PhotoContent
import dev.inmo.tgbotapi.types.message.content.StoryContent
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.content.VideoContent
import dev.inmo.tgbotapi.types.message.content.VisualMediaGroupPartContent
import dev.inmo.tgbotapi.types.stories.InputStoryContent
import dev.inmo.tgbotapi.types.stories.StoryArea
import dev.inmo.tgbotapi.types.stories.StoryAreaPosition
import dev.inmo.tgbotapi.types.stories.StoryAreaType
import dev.inmo.tgbotapi.utils.botCommand
import dev.inmo.tgbotapi.utils.code
import dev.inmo.tgbotapi.utils.extensions.splitForText
import dev.inmo.tgbotapi.utils.row
import korlibs.time.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.json.Json
suspend fun main(args: Array<String>) {
val botToken = args.first()
@@ -79,25 +34,21 @@ suspend fun main(args: Array<String>) {
}
val businessConnectionsChats = mutableMapOf<BusinessConnectionId, ChatId>()
val chatsBusinessConnections = mutableMapOf<ChatId, BusinessConnectionId>()
val businessConnectionsChatsMutex = Mutex()
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
val me = getMe()
println(me)
flushAccumulatedUpdates()
onBusinessConnectionEnabled {
businessConnectionsChatsMutex.withLock {
businessConnectionsChats[it.id] = it.userChatId
chatsBusinessConnections[it.userChatId] = it.id
}
send(it.userChatId, "Business connection ${it.businessConnectionId.string} has been enabled")
}
onBusinessConnectionDisabled {
businessConnectionsChatsMutex.withLock {
businessConnectionsChats.remove(it.id)
chatsBusinessConnections.remove(it.userChatId)
}
send(it.userChatId, "Business connection ${it.businessConnectionId.string} has been disabled")
}
@@ -120,20 +71,7 @@ suspend fun main(args: Array<String>) {
if (businessContentMessage.sentByBusinessConnectionOwner) {
reply(sent, "You have sent this message to the ${businessContentMessage.businessConnectionId.string} related chat")
} else {
reply(
to = sent,
text = "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat",
)
send(
chatId = businessConnectionsChats[it.businessConnectionId] ?: return@ifBusinessContentMessage,
text = "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat",
replyMarkup = inlineKeyboard {
row {
dataButton("Read message", "read ${it.chat.id.chatId.long} ${it.messageId.long}")
dataButton("Delete message", "delete ${it.chat.id.chatId.long} ${it.messageId.long}")
}
}
)
reply(sent, "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat")
}
}
}
@@ -160,328 +98,5 @@ suspend fun main(args: Array<String>) {
}
send(businessConnectionOwnerChat, "There are several removed messages in chat ${it.chat.id}: ${it.messageIds}")
}
onCommand("get_business_account_info", initialFilter = { it.chat is PrivateChat }) {
val businessConnectionId = chatsBusinessConnections[it.chat.id]
val businessConnectionInfo = businessConnectionId ?.let { getBusinessConnection(it) }
reply(it) {
if (businessConnectionInfo == null) {
+"There is no business connection for current chat"
} else {
+(Json { prettyPrint = true; encodeDefaults = true }.encodeToString(businessConnectionInfo))
}
}
}
onMessageDataCallbackQuery(Regex("read \\d+ \\d+")) {
val (_, chatIdString, messageIdString) = it.data.split(" ")
val chatId = chatIdString.toLongOrNull() ?.let(::RawChatId) ?.let(::ChatId) ?: return@onMessageDataCallbackQuery
val messageId = messageIdString.toLongOrNull() ?.let(::MessageId) ?: return@onMessageDataCallbackQuery
val businessConnectionId = chatsBusinessConnections[it.message.chat.id]
val readResponse = businessConnectionId ?.let { readBusinessMessage(it, chatId, messageId) }
answer(
it,
if (readResponse == null) {
"There is no business connection for current chat"
} else {
"Message has been read"
}
)
}
onMessageDataCallbackQuery(Regex("delete \\d+ \\d+")) {
val (_, chatIdString, messageIdString) = it.data.split(" ")
val chatId = chatIdString.toLongOrNull() ?.let(::RawChatId) ?.let(::ChatId) ?: return@onMessageDataCallbackQuery
val messageId = messageIdString.toLongOrNull() ?.let(::MessageId) ?: return@onMessageDataCallbackQuery
val businessConnectionId = chatsBusinessConnections[it.message.chat.id]
val readResponse = businessConnectionId ?.let { deleteBusinessMessages(it, listOf(messageId)) }
answer(
it,
if (readResponse == null) {
"There is no business connection for current chat"
} else {
"Message has been deleted"
}
)
}
onCommandWithArgs("set_business_account_name", initialFilter = { it.chat is PrivateChat }) { it, args ->
val firstName = args[0]
val secondName = args.getOrNull(1)
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommandWithArgs
val set = runCatching {
setBusinessAccountName(
businessConnectionId,
firstName,
secondName
)
}.getOrElse { false }
reply(it) {
if (set) {
+"Account name has been set"
} else {
+"Account name has not been set"
}
}
}
onCommandWithArgs("set_business_account_username", initialFilter = { it.chat is PrivateChat }) { it, args ->
val username = args[0]
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommandWithArgs
val set = runCatching {
setBusinessAccountUsername(
businessConnectionId,
username
)
}.getOrElse {
it.printStackTrace()
false
}
reply(it) {
if (set) {
+"Account username has been set"
} else {
+"Account username has not been set"
}
}
}
onCommand("get_business_account_star_balance", initialFilter = { it.chat is PrivateChat }) {
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
val starAmount = runCatching {
getBusinessAccountStarBalance(businessConnectionId)
}.getOrElse {
it.printStackTrace()
null
}
reply(it) {
if (starAmount != null) {
+"Account stars amount: $starAmount"
} else {
+"Account stars amount has not been got"
}
}
}
onCommandWithArgs("transfer_business_account_stars", initialFilter = { it.chat is PrivateChat }) { it, args ->
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommandWithArgs
val count = args.firstOrNull() ?.toIntOrNull() ?: reply(it) {
"Pass amount of stars to transfer to bot with command"
}.let {
return@onCommandWithArgs
}
val transferred = runCatching {
transferBusinessAccountStars(businessConnectionId, count)
}.getOrElse {
it.printStackTrace()
false
}
reply(it) {
if (transferred) {
+"Stars have been transferred"
} else {
+"Stars have not been transferred"
}
}
}
onCommand("get_business_account_gifts", initialFilter = { it.chat is PrivateChat }) {
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
val giftsFlow = runCatching {
getBusinessAccountGiftsFlow(businessConnectionId)
}.getOrElse {
it.printStackTrace()
null
}
if (giftsFlow == null) {
reply(it) {
+"Error in receiving of gifts"
}
} else {
giftsFlow.collect { giftsPage ->
giftsPage.gifts.joinToString {
it.toString()
}.splitForText().forEach { message ->
reply(it, message)
}
}
}
}
onCommand("set_business_account_bio", requireOnlyCommandInMessage = false, initialFilter = { it.chat is PrivateChat }) {
val initialBio = getChat(it.chat).extendedPrivateChatOrThrow().bio
val bio = it.content.text.removePrefix("/set_business_account_bio").trim()
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
val set = runCatching {
setBusinessAccountBio(
businessConnectionId,
bio
)
}.getOrElse {
it.printStackTrace()
false
}
reply(it) {
if (set) {
+"Account bio has been set. It will be reset within 15 seconds.\n\nInitial bio: " + code(initialBio)
} else {
+"Account bio has not been set"
}
}
delay(15.seconds)
val reset = runCatching {
setBusinessAccountBio(
businessConnectionId,
initialBio
)
}.getOrElse {
it.printStackTrace()
false
}
reply(it) {
if (reset) {
+"Account bio has been reset"
} else {
+"Account bio has not been set. Set it manually: " + code(initialBio)
}
}
}
suspend fun handleSetProfilePhoto(it: CommonMessage<TextContent>, isPublic: Boolean) {
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@handleSetProfilePhoto
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<PhotoContent>()
if (replyTo == null) {
reply(it) {
+"Reply to photo for using of this command"
}
return@handleSetProfilePhoto
}
val set = runCatching {
val file = downloadFileToTemp(replyTo.content)
setBusinessAccountProfilePhoto(
businessConnectionId,
InputProfilePhoto.Static(
file.multipartFile()
),
isPublic = isPublic
)
}.getOrElse {
it.printStackTrace()
false
}
reply(it) {
if (set) {
+"Account profile photo has been set. It will be reset within 15 seconds"
} else {
+"Account profile photo has not been set"
}
}
if (set == false) { return@handleSetProfilePhoto }
delay(15.seconds)
val reset = runCatching {
removeBusinessAccountProfilePhoto(
businessConnectionId,
isPublic = isPublic
)
}.getOrElse {
it.printStackTrace()
false
}
reply(it) {
if (reset) {
+"Account profile photo has been reset"
} else {
+"Account profile photo has not been set. Set it manually"
}
}
}
onCommand("set_business_account_profile_photo", initialFilter = { it.chat is PrivateChat }) {
handleSetProfilePhoto(it, false)
}
onCommand("set_business_account_profile_photo_public", initialFilter = { it.chat is PrivateChat }) {
handleSetProfilePhoto(it, true)
}
onCommand("post_story", initialFilter = { it.chat is PrivateChat }) {
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<VisualMediaGroupPartContent>()
if (replyTo == null) {
reply(it) {
+"Reply to photo or video for using of this command"
}
return@onCommand
}
val posted = runCatching {
val file = downloadFileToTemp(replyTo.content)
postStory(
businessConnectionId,
when (replyTo.content) {
is PhotoContent -> InputStoryContent.Photo(
file.multipartFile()
)
is VideoContent -> InputStoryContent.Video(
file.multipartFile()
)
},
activePeriod = PostStory.ACTIVE_PERIOD_6_HOURS,
areas = listOf(
StoryArea(
StoryAreaPosition(
x = Percentage.of100(50.0),
y = Percentage.of100(50.0),
width = Percentage.of100(8.0),
height = Percentage.of100(8.0),
rotationAngle = 45.0,
cornerRadius = Percentage.of100(4.0),
),
StoryAreaType.Link(
"https://github.com/InsanusMokrassar/TelegramBotAPI-examples/blob/master/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt"
)
)
)
) {
+"It is test of postStory :)"
}
}.getOrElse {
it.printStackTrace()
null
}
reply(it) {
if (posted != null) {
+"Story has been posted. You may unpost it with " + botCommand("remove_story")
} else {
+"Story has not been posted"
}
}
}
onCommand("delete_story", initialFilter = { it.chat is PrivateChat }) {
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<StoryContent>()
if (replyTo == null) {
reply(it) {
+"Reply to photo or video for using of this command"
}
return@onCommand
}
val deleted = runCatching {
deleteStory(businessConnectionId, replyTo.content.story.id)
}.getOrElse {
it.printStackTrace()
false
}
reply(it) {
if (deleted) {
+"Story has been deleted"
} else {
+"Story has not been deleted"
}
}
}
// Will work when some premium user sending to some other user checklist
onChecklistContent {
execute(
it.content.createResend(
it.chat.id,
businessConnectionId = it.chat.id.businessConnectionId ?: chatsBusinessConnections[it.chat.id] ?: return@onChecklistContent
)
)
}
}.second.join()
}

View File

@@ -1,21 +0,0 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName="ChecklistsBotKt"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
}

View File

@@ -1,120 +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.runCatchingLogging
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.resend
import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.api.suggested.approveSuggestedPost
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextData
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildSubcontextInitialAction
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitSuggestedPostApproved
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitSuggestedPostDeclined
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChannelDirectMessagesConfigurationChanged
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChecklistContent
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChecklistTasksAdded
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChecklistTasksDone
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.onSuggestedPostApprovalFailed
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostApproved
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostDeclined
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostPaid
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostRefunded
import dev.inmo.tgbotapi.extensions.utils.channelDirectMessagesContentMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.previewChannelDirectMessagesChatOrNull
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
import dev.inmo.tgbotapi.types.checklists.ChecklistTaskId
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.ChecklistContent
import dev.inmo.tgbotapi.types.message.textsources.TextSourcesList
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.utils.bold
import dev.inmo.tgbotapi.utils.buildEntities
import dev.inmo.tgbotapi.utils.code
import dev.inmo.tgbotapi.utils.firstOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
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.Default),
testServer = isTestServer,
) {
// start here!!
val me = getMe()
println(me)
fun ChecklistContent.textBuilderTextSources(): TextSourcesList {
return buildEntities {
+checklist.textSources + "\n\n"
checklist.tasks.forEach { task ->
+""
code(
if (task.completionDate != null) {
"[x] "
} else {
"[ ] "
}
)
bold(task.textSources) + "\n"
}
}
}
onChecklistContent { messageWithContent ->
reply(messageWithContent) {
+messageWithContent.content.textBuilderTextSources()
}
}
onChecklistTasksDone { eventMessage ->
reply(
eventMessage,
checklistTaskId = eventMessage.chatEvent.markedAsDone ?.firstOrNull()
) {
eventMessage.chatEvent.checklistMessage.content.checklist
+eventMessage.chatEvent.checklistMessage.content.textBuilderTextSources()
}
}
onChecklistTasksAdded { messageWithContent ->
reply(
messageWithContent.chatEvent.checklistMessage,
checklistTaskId = messageWithContent.chatEvent.tasks.firstOrNull() ?.id
) {
+messageWithContent.chatEvent.checklistMessage.content.textBuilderTextSources()
}
}
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
println(it)
}
}.second.join()
}

View File

@@ -4,13 +4,9 @@ import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.send.reply
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.onChannelDirectMessagesConfigurationChanged
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.update.abstracts.Update
@@ -69,7 +65,6 @@ suspend fun main(vararg args: String) {
onCommand("start") {
println(data.update)
println(data.commonMessage)
println(getChat(it.chat))
}
onCommand(
@@ -82,17 +77,6 @@ suspend fun main(vararg args: String) {
println(data.commonMessage)
}
onCommand("getMyStarBalance") {
reply(
to = it,
text = getMyStarBalance().toString()
)
}
onChannelDirectMessagesConfigurationChanged {
println(it.chatEvent)
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
println(it)
}

View File

@@ -1,13 +1,10 @@
import dev.inmo.micro_utils.coroutines.awaitFirst
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitAnyContentMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitCommandMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndFSMAndStartLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.command
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.containsCommand
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgs
import dev.inmo.tgbotapi.extensions.utils.extensions.sameThread
import dev.inmo.tgbotapi.extensions.utils.textContentOrNull
@@ -16,12 +13,10 @@ import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.utils.botCommand
import dev.inmo.tgbotapi.utils.firstOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
sealed interface BotState : State
data class ExpectContentOrStopState(override val context: IdChatIdentifier, val sourceMessage: CommonMessage<TextContent>) : BotState
@@ -53,29 +48,19 @@ suspend fun main(args: Array<String>) {
+"Send me some content or " + botCommand("stop") + " if you want to stop sending"
}
val contentMessage = firstOf(
{
waitCommandMessage("stop").filter { message ->
message.sameThread(it.sourceMessage)
}.first()
null
},
{
waitAnyContentMessage().filter { message ->
message.sameThread(it.sourceMessage)
}.filter {
containsCommand(
"stop",
it.withContentOrNull<TextContent>() ?.content ?.textSources ?: return@filter false
) == false
}.first()
}
) ?: return@strictlyOn StopState(it.context)
val contentMessage = waitAnyContentMessage().filter { message ->
message.sameThread(it.sourceMessage)
}.first()
val content = contentMessage.content
execute(content.createResend(it.context))
it
when {
content is TextContent && content.text == "/stop"
|| content is TextContent && content.parseCommandsWithArgs().keys.contains("stop") -> StopState(it.context)
else -> {
execute(content.createResend(it.context))
it
}
}
}
strictlyOn<StopState> {
send(it.context) { +"You have stopped sending of content" }

View File

@@ -7,11 +7,13 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onConten
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMentionWithAnyContent
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.sender_chat
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.text
import dev.inmo.tgbotapi.extensions.utils.formatting.chatLink
import dev.inmo.tgbotapi.extensions.utils.formatting.linkMarkdownV2
import dev.inmo.tgbotapi.extensions.utils.formatting.textMentionMarkdownV2
import dev.inmo.tgbotapi.extensions.utils.ifFromChannelGroupContentMessage
import dev.inmo.tgbotapi.types.chat.*
import dev.inmo.tgbotapi.types.message.MarkdownV2
import dev.inmo.tgbotapi.types.message.abstracts.ForumContentMessage
import dev.inmo.tgbotapi.utils.PreviewFeature
import dev.inmo.tgbotapi.utils.extensions.escapeMarkdownV2Common
import kotlinx.coroutines.CoroutineScope
@@ -56,8 +58,17 @@ suspend fun main(vararg args: String) {
reply(message, answer, MarkdownV2)
return@onContentMessage
}
"Oh, hi, " + when (chat) {
is SupergroupChat -> (chat.username ?.username ?: getChat(chat).inviteLink) ?.let {
"Oh, hi, " + when {
chat is ForumChat && message is ForumContentMessage<*> -> {
val baseTitle = (chat.username ?.username ?: getChat(chat).inviteLink) ?.let {
chat.title.linkMarkdownV2(it)
} ?: chat.title
val additionalTitle = message.threadCreatingInfo ?.let {
it.name.linkMarkdownV2(message.chat.id.chatLink)
} ?: "Main topic"
"$baseTitle \\($additionalTitle\\)"
}
chat is SupergroupChat -> (chat.username ?.username ?: getChat(chat).inviteLink) ?.let {
chat.title.linkMarkdownV2(it)
} ?: chat.title
else -> bot.getChat(chat).inviteLink ?.let {

View File

@@ -12,16 +12,14 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
}
apply plugin: 'application'
mainClassName="InlineQueriesBotKt"
apply from: "$nativePartTemplate"
kotlin {
jvm {
binaries {
executable {
mainClass.set("InlineQueriesBotKt")
}
}
}
jvm()
sourceSets {
commonMain {
@@ -29,9 +27,12 @@ kotlin {
implementation kotlin('stdlib')
api "dev.inmo:tgbotapi:$telegram_bot_api_version"
api "io.ktor:ktor-client-logging:$ktor_version"
}
}
}
}
dependencies {
implementation 'io.ktor:ktor-client-logging-jvm:3.1.0'
}

View File

@@ -1,4 +1,4 @@
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.answers.answer
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.send.reply
@@ -59,7 +59,7 @@ suspend fun doInlineQueriesBot(token: String) {
reply(message, deepLink)
}
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
println(it)
}

View File

@@ -1,4 +1,4 @@
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.answers.answer
import dev.inmo.tgbotapi.extensions.api.bot.getMe
@@ -89,7 +89,7 @@ suspend fun activateKeyboardsBot(
onCommandWithArgs("inline") { message, args ->
val numberArgs = args.mapNotNull { it.toIntOrNull() }
val numberOfPages = numberArgs.getOrNull(1) ?: numberArgs.firstOrNull() ?: 10
val page = numberArgs.firstOrNull()?.takeIf { numberArgs.size > 1 }?.coerceAtLeast(1) ?: 1
val page = numberArgs.firstOrNull() ?.takeIf { numberArgs.size > 1 } ?.coerceAtLeast(1) ?: 1
reply(
message,
replyMarkup = inlineKeyboard {
@@ -138,8 +138,7 @@ suspend fun activateKeyboardsBot(
onBaseInlineQuery {
val page = it.query.takeWhile { it.isDigit() }.toIntOrNull() ?: return@onBaseInlineQuery
val count = it.query.removePrefix(page.toString()).dropWhile { !it.isDigit() }.takeWhile { it.isDigit() }
.toIntOrNull() ?: return@onBaseInlineQuery
val count = it.query.removePrefix(page.toString()).dropWhile { !it.isDigit() }.takeWhile { it.isDigit() }.toIntOrNull() ?: return@onBaseInlineQuery
answer(
it,
@@ -171,7 +170,7 @@ suspend fun activateKeyboardsBot(
setMyCommands(BotCommand("inline", "Creates message with pagination inline keyboard"))
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
println(it)
}
}.join()

View File

@@ -12,14 +12,12 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
}
apply plugin: 'application'
mainClassName="RandomFileSenderBotKt"
kotlin {
jvm {
binaries {
executable {
mainClass.set("RandomFileSenderBotKt")
}
}
}
jvm()
sourceSets {
commonMain {

View File

@@ -1,7 +1,4 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.filter.filtered
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.send.withTypingAction
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat
@@ -13,9 +10,7 @@ import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
import dev.inmo.tgbotapi.types.ReplyParameters
import dev.inmo.tgbotapi.types.message.abstracts.BusinessContentMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.utils.DefaultKTgBotAPIKSLog
import dev.inmo.tgbotapi.utils.extensions.threadIdOrNull
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.currentCoroutineContext
@@ -24,10 +19,7 @@ suspend fun activateResenderBot(
token: String,
print: (Any) -> Unit
) {
telegramBotWithBehaviourAndLongPolling(
token,
scope = CoroutineScope(currentCoroutineContext() + SupervisorJob()),
) {
telegramBotWithBehaviourAndLongPolling(token, scope = CoroutineScope(currentCoroutineContext() + SupervisorJob())) {
onContentMessage(
subcontextUpdatesFilter = MessageFilterByChat,
initialFilter = { it !is BusinessContentMessage<*> || !it.sentByBusinessConnectionOwner }
@@ -39,15 +31,15 @@ suspend fun activateResenderBot(
it.content.createResend(
chat.id,
messageThreadId = it.threadIdOrNull,
replyParameters = it.replyInfo?.messageMeta?.let { meta ->
val quote = it.withContentOrNull<TextContent>()?.content?.quote
replyParameters = it.replyInfo ?.messageMeta ?.let { meta ->
val quote = it.withContentOrNull<TextContent>() ?.content ?.quote
ReplyParameters(
meta,
entities = quote?.textSources ?: emptyList(),
quotePosition = quote?.position
entities = quote ?.textSources ?: emptyList(),
quotePosition = quote ?.position
)
},
effectId = it.possiblyWithEffectMessageOrNull()?.effectId
effectId = it.possiblyWithEffectMessageOrNull() ?.effectId
)
) {
it.forEach(print)
@@ -57,7 +49,7 @@ suspend fun activateResenderBot(
println("Answer info: $answer")
}
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
println(it)
}
print(bot.getMe())

View File

@@ -1,19 +1,4 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
suspend fun main(args: Array<String>) {
val isDebug = args.getOrNull(1) == "debug"
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
activateResenderBot(args.first()) {
println(it)
}

View File

@@ -18,5 +18,5 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
implementation 'io.ktor:ktor-client-logging-jvm:3.2.3'
implementation 'io.ktor:ktor-client-logging-jvm:3.1.0'
}

View File

@@ -1,4 +1,4 @@
# StarTransactionsBot
# CustomBot
This bot basically have no any useful behaviour, but you may customize it as a playground

View File

@@ -1,5 +1,5 @@
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandler
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.get.getCustomEmojiStickerOrNull
@@ -55,7 +55,7 @@ suspend fun activateStickerInfoBot(
withTypingAction(it.chat) {
it.content.textSources.mapNotNull {
if (it is CustomEmojiTextSource) {
getCustomEmojiStickerOrNull(it.customEmojiId)?.stickerSetName
getCustomEmojiStickerOrNull(it.customEmojiId) ?.stickerSetName
} else {
null
}
@@ -76,7 +76,7 @@ suspend fun activateStickerInfoBot(
)
}
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
println(it)
}
}.join()

View File

@@ -1,9 +0,0 @@
# StickerSetHandler
Send sticker to this bot to form your own stickers set. Send /delete to delete this sticker set
## How to run
```bash
./gradlew run --args="TOKEN"
```

View File

@@ -1,21 +0,0 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName="SuggestedPostsBotKt"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
}

View File

@@ -1,140 +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.runCatchingLogging
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.resend
import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.api.suggested.approveSuggestedPost
import dev.inmo.tgbotapi.extensions.api.suggested.declineSuggestedPost
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextData
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildSubcontextInitialAction
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitSuggestedPostApproved
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitSuggestedPostDeclined
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChannelDirectMessagesConfigurationChanged
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.onSuggestedPostApprovalFailed
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostApproved
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostDeclined
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostPaid
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostRefunded
import dev.inmo.tgbotapi.extensions.utils.channelDirectMessagesContentMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.previewChannelDirectMessagesChatOrNull
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
import dev.inmo.tgbotapi.types.message.abstracts.ChannelPaidPost
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.utils.firstOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
/**
* 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.Default),
testServer = isTestServer,
) {
// start here!!
val me = getMe()
println(me)
onCommand("start") {
println(getChat(it.chat))
}
onContentMessage {
val message = it.channelDirectMessagesContentMessageOrNull() ?: return@onContentMessage
val chat = getChat(it.chat)
println(chat)
resend(
message.chat.id,
message.content,
suggestedPostParameters = SuggestedPostParameters()
)
}
onContentMessage(
subcontextUpdatesFilter = { _, _ -> true } // important to not miss updates in channel for waitSuggestedPost events
) { message ->
val suggestedPost = message.suggestedChannelDirectMessagesContentMessageOrNull() ?: return@onContentMessage
firstOf(
{
waitSuggestedPostApproved().filter {
it.suggestedPostMessage ?.chat ?.id == message.chat.id
}.first()
},
{
waitSuggestedPostDeclined().filter {
it.suggestedPostMessage ?.chat ?.id == message.chat.id
}.first()
},
{
for (i in 0 until 3) {
delay(1000L)
send(suggestedPost.chat, "${3 - i}")
}
declineSuggestedPost(suggestedPost)
},
)
}
onContentMessage(initialFilter = { it is ChannelPaidPost<*> }) {
println(it)
}
onSuggestedPostPaid {
println(it)
reply(it, "Paid")
}
onSuggestedPostApproved {
println(it)
reply(it, "Approved")
}
onSuggestedPostDeclined {
println(it)
reply(it, "Declined")
}
onSuggestedPostRefunded {
println(it)
reply(it, "Refunded")
}
onSuggestedPostApprovalFailed {
println(it)
reply(it, "Approval failed")
}
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
println(it)
}
}.second.join()
}

View File

@@ -16,14 +16,10 @@ plugins {
id "org.jetbrains.compose" version "$compose_version"
}
apply plugin: 'application'
kotlin {
jvm {
binaries {
executable {
mainClass.set("WebAppServerKt")
}
}
}
jvm()
js(IR) {
browser()
binaries.executable()
@@ -57,6 +53,10 @@ kotlin {
}
}
application {
mainClassName = "WebAppServerKt"
}
tasks.getByName("compileKotlinJvm")
.dependsOn(jsBrowserDistribution)
tasks.getByName("compileKotlinJvm").configure {

View File

@@ -1,5 +1,6 @@
import androidx.compose.runtime.*
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.tgbotapi.types.CustomEmojiId
import dev.inmo.tgbotapi.types.userIdField
import dev.inmo.tgbotapi.types.webAppQueryIdField
import dev.inmo.tgbotapi.webapps.*
@@ -22,12 +23,10 @@ import kotlinx.dom.appendElement
import kotlinx.dom.appendText
import kotlinx.serialization.json.Json
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.attributes.placeholder
import org.jetbrains.compose.web.css.Style
import org.jetbrains.compose.web.css.StyleSheet
import org.jetbrains.compose.web.css.DisplayStyle
import org.jetbrains.compose.web.css.Color as ComposeColor
import org.jetbrains.compose.web.css.backgroundColor
import org.jetbrains.compose.web.css.color
import org.jetbrains.compose.web.css.display
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.dom.Text
import org.jetbrains.compose.web.renderComposable
@@ -40,13 +39,6 @@ fun HTMLElement.log(text: String) {
appendElement("p", {})
}
private object RootStyleSheet : StyleSheet() {
val rootClass by style {
color(ComposeColor("var(--tg-theme-text-color)"))
backgroundColor(ComposeColor("var(--tg-theme-bg-color)"))
}
}
@OptIn(ExperimentalUnsignedTypes::class)
fun main() {
console.log("Web app started")
@@ -54,14 +46,6 @@ fun main() {
val baseUrl = window.location.origin.removeSuffix("/")
renderComposable("root") {
Style(RootStyleSheet)
DisposableEffect(null) {
scopeElement.classList.add(RootStyleSheet.rootClass)
onDispose {
scopeElement.classList.remove(RootStyleSheet.rootClass)
}
}
val scope = rememberCoroutineScope()
val isSafeState = remember { mutableStateOf<Boolean?>(null) }
val logsState = remember { mutableStateListOf<Any?>() }
@@ -103,7 +87,6 @@ fun main() {
P()
Text("Chat from WebAppInitData: ${webApp.initDataUnsafe.chat}")
H3 { Text("Emoji status management") }
val emojiStatusAccessState = remember { mutableStateOf(false) }
webApp.onEmojiStatusAccessRequested {
emojiStatusAccessState.value = it.isAllowed
@@ -127,7 +110,7 @@ fun main() {
userId ?.let { userId ->
Button({
onClick {
scope.launchLoggingDropExceptions {
scope.launchSafelyWithoutExceptions {
client.post("$baseUrl/setCustomEmoji") {
parameter(userIdField, userId.long)
setBody(
@@ -144,12 +127,10 @@ fun main() {
}
}
}
P()
H3 { Text("Call server method with webAppQueryIdField") }
Button({
onClick {
scope.launchLoggingDropExceptions {
scope.launchSafelyWithoutExceptions {
handleResult({ "Clicked" }) {
client.post("${window.location.origin.removeSuffix("/")}/inline") {
parameter(webAppQueryIdField, it)
@@ -163,40 +144,11 @@ fun main() {
Text("Answer in chat button")
}
H3 { Text("Hide keyboard") }
val hideCountdown = remember { mutableStateOf<Int?>(null) }
Button({
onClick {
hideCountdown.value = 5
}
}) {
if (hideCountdown.value == null) {
Text("Hide")
} else {
Text("Hide in ${hideCountdown.value} seconds")
}
}
LaunchedEffect(hideCountdown.value) {
val value = hideCountdown.value
when {
value == null -> return@LaunchedEffect
value > 0 -> {
delay(1000)
hideCountdown.value = hideCountdown.value ?.minus(1)
}
else -> {
webApp.hideKeyboard()
hideCountdown.value = null
}
}
}
P()
H3 { Text("User info") }
Text("Allow to write in private messages: ${webApp.initDataUnsafe.user ?.allowsWriteToPM ?: "User unavailable"}")
P()
H3 { Text("Alerts") }
Text("Alerts:")
Button({
onClick {
webApp.showPopup(
@@ -234,22 +186,8 @@ fun main() {
}) {
Text("Alert")
}
Button({
onClick {
webApp.showConfirm(
"This is confirm message"
) {
logsState.add(
"You have pressed \"${if (it) "Ok" else "Cancel"}\" in confirm"
)
}
}
}) {
Text("Confirm")
}
P()
H3 { Text("Write access callbacks") }
Button({
onClick {
webApp.requestWriteAccess()
@@ -268,7 +206,6 @@ fun main() {
}
P()
H3 { Text("Request contact") }
Button({
onClick {
webApp.requestContact()
@@ -283,9 +220,24 @@ fun main() {
}) {
Text("Request contact with callback")
}
P()
Button({
onClick {
webApp.showConfirm(
"This is confirm message"
) {
logsState.add(
"You have pressed \"${if (it) "Ok" else "Cancel"}\" in confirm"
)
}
}
}) {
Text("Confirm")
}
P()
H3 { Text("Closing confirmation") }
val isClosingConfirmationEnabledState = remember { mutableStateOf(webApp.isClosingConfirmationEnabled) }
Button({
onClick {
@@ -303,7 +255,7 @@ fun main() {
}
P()
H3 { Text("Colors") }
val headerColor = remember { mutableStateOf<Color.Hex>(Color.Hex("#000000")) }
fun updateHeaderColor() {
val (r, g, b) = Random.nextUBytes(3)
@@ -328,6 +280,7 @@ fun main() {
}
P()
val backgroundColor = remember { mutableStateOf<Color.Hex>(Color.Hex("#000000")) }
fun updateBackgroundColor() {
val (r, g, b) = Random.nextUBytes(3)
@@ -352,6 +305,7 @@ fun main() {
}
P()
val bottomBarColor = remember { mutableStateOf<Color.Hex>(Color.Hex("#000000")) }
fun updateBottomBarColor() {
val (r, g, b) = Random.nextUBytes(3)
@@ -375,184 +329,8 @@ fun main() {
}
}
remember {
webApp.apply {
onThemeChanged {
logsState.add("Theme changed: ${webApp.themeParams}")
}
onViewportChanged {
logsState.add("Viewport changed: ${it}")
}
backButton.apply {
onClick {
logsState.add("Back button clicked")
hapticFeedback.impactOccurred(
HapticFeedbackStyle.Heavy
)
}
show()
}
mainButton.apply {
setText("Main button")
onClick {
logsState.add("Main button clicked")
hapticFeedback.notificationOccurred(
HapticFeedbackType.Success
)
}
show()
}
secondaryButton.apply {
setText("Secondary button")
onClick {
logsState.add("Secondary button clicked")
hapticFeedback.notificationOccurred(
HapticFeedbackType.Warning
)
}
show()
}
onSettingsButtonClicked {
logsState.add("Settings button clicked")
}
onWriteAccessRequested {
logsState.add("Write access request result: $it")
}
onContactRequested {
logsState.add("Contact request result: $it")
}
}
}
P()
let {
H3 { Text("Accelerometer") }
val enabledState = remember { mutableStateOf(webApp.accelerometer.isStarted) }
webApp.onAccelerometerStarted { enabledState.value = true }
webApp.onAccelerometerStopped { enabledState.value = false }
Button({
onClick {
if (enabledState.value) {
webApp.accelerometer.stop { }
} else {
webApp.accelerometer.start(AccelerometerStartParams(200))
}
}
}) {
Text("${if (enabledState.value) "Stop" else "Start"} accelerometer")
}
val xState = remember { mutableStateOf(webApp.accelerometer.x) }
val yState = remember { mutableStateOf(webApp.accelerometer.y) }
val zState = remember { mutableStateOf(webApp.accelerometer.z) }
fun updateValues() {
xState.value = webApp.accelerometer.x
yState.value = webApp.accelerometer.y
zState.value = webApp.accelerometer.z
}
remember {
updateValues()
}
webApp.onAccelerometerChanged {
updateValues()
}
if (enabledState.value) {
P()
Text("x: ${xState.value}")
P()
Text("y: ${yState.value}")
P()
Text("z: ${zState.value}")
}
}
P()
let {
H3 { Text("Gyroscope") }
val enabledState = remember { mutableStateOf(webApp.gyroscope.isStarted) }
webApp.onGyroscopeStarted { enabledState.value = true }
webApp.onGyroscopeStopped { enabledState.value = false }
Button({
onClick {
if (enabledState.value) {
webApp.gyroscope.stop { }
} else {
webApp.gyroscope.start(GyroscopeStartParams(200))
}
}
}) {
Text("${if (enabledState.value) "Stop" else "Start"} gyroscope")
}
val xState = remember { mutableStateOf(webApp.gyroscope.x) }
val yState = remember { mutableStateOf(webApp.gyroscope.y) }
val zState = remember { mutableStateOf(webApp.gyroscope.z) }
fun updateValues() {
xState.value = webApp.gyroscope.x
yState.value = webApp.gyroscope.y
zState.value = webApp.gyroscope.z
}
remember {
updateValues()
}
webApp.onGyroscopeChanged {
updateValues()
}
if (enabledState.value) {
P()
Text("x: ${xState.value}")
P()
Text("y: ${yState.value}")
P()
Text("z: ${zState.value}")
}
}
P()
let {
H3 { Text("Device Orientation") }
val enabledState = remember { mutableStateOf(webApp.deviceOrientation.isStarted) }
webApp.onDeviceOrientationStarted { enabledState.value = true }
webApp.onDeviceOrientationStopped { enabledState.value = false }
Button({
onClick {
if (enabledState.value) {
webApp.deviceOrientation.stop { }
} else {
webApp.deviceOrientation.start(DeviceOrientationStartParams(200))
}
}
}) {
Text("${if (enabledState.value) "Stop" else "Start"} deviceOrientation")
}
val alphaState = remember { mutableStateOf(webApp.deviceOrientation.alpha) }
val betaState = remember { mutableStateOf(webApp.deviceOrientation.beta) }
val gammaState = remember { mutableStateOf(webApp.deviceOrientation.gamma) }
fun updateValues() {
alphaState.value = webApp.deviceOrientation.alpha
betaState.value = webApp.deviceOrientation.beta
gammaState.value = webApp.deviceOrientation.gamma
}
remember {
updateValues()
}
webApp.onDeviceOrientationChanged {
updateValues()
}
if (enabledState.value) {
P()
Text("alpha: ${alphaState.value}")
P()
Text("beta: ${betaState.value}")
P()
Text("gamma: ${gammaState.value}")
}
}
P()
H3 { Text("Cloud storage") }
val storageTrigger = remember { mutableStateOf<List<Pair<CloudStorageKey, CloudStorageValue>>>(emptyList()) }
fun updateCloudStorage() {
webApp.cloudStorage.getAll {
@@ -605,126 +383,180 @@ fun main() {
}
}
P()
let { // DeviceStorage
H3 { Text("Device storage") }
val fieldKey = remember { mutableStateOf("") }
val fieldValue = remember { mutableStateOf("") }
val message = remember { mutableStateOf("") }
Div {
Text("Start type title of key. If value will be found in device storage, it will be shown in value input")
}
remember {
webApp.apply {
Input(InputType.Text) {
placeholder("Key")
value(fieldKey.value)
onInput {
fieldKey.value = it.value
webApp.deviceStorage.getItem(it.value) { e, v ->
fieldValue.value = v ?: ""
if (v == null) {
message.value = "Value for key \"${it.value}\" has not been found"
} else {
message.value = "Value for key \"${it.value}\" has been found: \"$v\""
}
}
onThemeChanged {
logsState.add("Theme changed: ${webApp.themeParams}")
}
}
Div {
Text("If you want to change value if typed key - just put it here")
}
Input(InputType.Text) {
placeholder("Value")
value(fieldValue.value)
onInput {
fieldValue.value = it.value
webApp.deviceStorage.setItem(fieldKey.value, it.value) { e, v ->
if (v == true) {
fieldValue.value = it.value
message.value = "Value \"${it.value}\" has been saved"
}
}
onViewportChanged {
logsState.add("Viewport changed: ${it}")
}
}
if (message.value.isNotEmpty()) {
Div { Text(message.value) }
}
}
P()
let { // DeviceStorage
H3 { Text("Secure storage") }
val fieldKey = remember { mutableStateOf("") }
val fieldValue = remember { mutableStateOf("") }
val message = remember { mutableStateOf("") }
val restorableState = remember { mutableStateOf(false) }
Div {
Text("Start type title of key. If value will be found in device storage, it will be shown in value input")
}
Input(InputType.Text) {
placeholder("Key")
value(fieldKey.value)
onInput {
fieldKey.value = it.value
webApp.secureStorage.getItem(it.value) { e, v, restorable ->
fieldValue.value = v ?: ""
restorableState.value = restorable == true
if (v == null) {
if (restorable == true) {
message.value = "Value for key \"${it.value}\" has not been found, but can be restored"
} else {
message.value = "Value for key \"${it.value}\" has not been found. Error: $e"
}
} else {
message.value = "Value for key \"${it.value}\" has been found: \"$v\""
}
}
}
}
if (restorableState.value) {
Button({
backButton.apply {
onClick {
webApp.secureStorage.restoreItem(fieldKey.value) { e, v ->
fieldValue.value = v ?: ""
if (v == null) {
message.value = "Value for key \"${fieldKey.value}\" has not been restored. Error: $e"
} else {
message.value = "Value for key \"${fieldKey.value}\" has been restored: \"$v\""
}
}
logsState.add("Back button clicked")
hapticFeedback.impactOccurred(
HapticFeedbackStyle.Heavy
)
}
}) {
Text("Restore")
show()
}
}
Div {
Text("If you want to change value if typed key - just put it here")
}
Input(InputType.Text) {
placeholder("Value")
value(fieldValue.value)
onInput {
fieldValue.value = it.value
webApp.secureStorage.setItem(fieldKey.value, it.value) { e, v ->
if (v) {
fieldValue.value = it.value
message.value = "Value \"${it.value}\" has been saved"
} else {
message.value = "Value \"${it.value}\" has not been saved. Error: $e"
}
mainButton.apply {
setText("Main button")
onClick {
logsState.add("Main button clicked")
hapticFeedback.notificationOccurred(
HapticFeedbackType.Success
)
}
show()
}
secondaryButton.apply {
setText("Secondary button")
onClick {
logsState.add("Secondary button clicked")
hapticFeedback.notificationOccurred(
HapticFeedbackType.Warning
)
}
show()
}
onSettingsButtonClicked {
logsState.add("Settings button clicked")
}
onWriteAccessRequested {
logsState.add("Write access request result: $it")
}
onContactRequested {
logsState.add("Contact request result: $it")
}
}
if (message.value.isNotEmpty()) {
Div { Text(message.value) }
}
}
P()
H3 { Text("Events") }
let { // Accelerometer
val enabledState = remember { mutableStateOf(webApp.accelerometer.isStarted) }
webApp.onAccelerometerStarted { enabledState.value = true }
webApp.onAccelerometerStopped { enabledState.value = false }
Button({
onClick {
if (enabledState.value) {
webApp.accelerometer.stop { }
} else {
webApp.accelerometer.start(AccelerometerStartParams(200))
}
}
}) {
Text("${if (enabledState.value) "Stop" else "Start"} accelerometer")
}
val xState = remember { mutableStateOf(webApp.accelerometer.x) }
val yState = remember { mutableStateOf(webApp.accelerometer.y) }
val zState = remember { mutableStateOf(webApp.accelerometer.z) }
fun updateValues() {
xState.value = webApp.accelerometer.x
yState.value = webApp.accelerometer.y
zState.value = webApp.accelerometer.z
}
remember {
updateValues()
}
webApp.onAccelerometerChanged {
updateValues()
}
if (enabledState.value) {
P()
Text("x: ${xState.value}")
P()
Text("y: ${yState.value}")
P()
Text("z: ${zState.value}")
}
}
P()
let { // Gyroscope
val enabledState = remember { mutableStateOf(webApp.gyroscope.isStarted) }
webApp.onGyroscopeStarted { enabledState.value = true }
webApp.onGyroscopeStopped { enabledState.value = false }
Button({
onClick {
if (enabledState.value) {
webApp.gyroscope.stop { }
} else {
webApp.gyroscope.start(GyroscopeStartParams(200))
}
}
}) {
Text("${if (enabledState.value) "Stop" else "Start"} gyroscope")
}
val xState = remember { mutableStateOf(webApp.gyroscope.x) }
val yState = remember { mutableStateOf(webApp.gyroscope.y) }
val zState = remember { mutableStateOf(webApp.gyroscope.z) }
fun updateValues() {
xState.value = webApp.gyroscope.x
yState.value = webApp.gyroscope.y
zState.value = webApp.gyroscope.z
}
remember {
updateValues()
}
webApp.onGyroscopeChanged {
updateValues()
}
if (enabledState.value) {
P()
Text("x: ${xState.value}")
P()
Text("y: ${yState.value}")
P()
Text("z: ${zState.value}")
}
}
P()
let { // DeviceOrientation
val enabledState = remember { mutableStateOf(webApp.deviceOrientation.isStarted) }
webApp.onDeviceOrientationStarted { enabledState.value = true }
webApp.onDeviceOrientationStopped { enabledState.value = false }
Button({
onClick {
if (enabledState.value) {
webApp.deviceOrientation.stop { }
} else {
webApp.deviceOrientation.start(DeviceOrientationStartParams(200))
}
}
}) {
Text("${if (enabledState.value) "Stop" else "Start"} deviceOrientation")
}
val alphaState = remember { mutableStateOf(webApp.deviceOrientation.alpha) }
val betaState = remember { mutableStateOf(webApp.deviceOrientation.beta) }
val gammaState = remember { mutableStateOf(webApp.deviceOrientation.gamma) }
fun updateValues() {
alphaState.value = webApp.deviceOrientation.alpha
betaState.value = webApp.deviceOrientation.beta
gammaState.value = webApp.deviceOrientation.gamma
}
remember {
updateValues()
}
webApp.onDeviceOrientationChanged {
updateValues()
}
if (enabledState.value) {
P()
Text("alpha: ${alphaState.value}")
P()
Text("beta: ${betaState.value}")
P()
Text("gamma: ${gammaState.value}")
}
}
P()
EventType.values().forEach { eventType ->
when (eventType) {
EventType.AccelerometerChanged -> webApp.onAccelerometerChanged { /*logsState.add("AccelerometerChanged") /* see accelerometer block */ */ }

View File

@@ -1,5 +1,4 @@
import dev.inmo.kslog.common.*
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.server.createKtorServer
import dev.inmo.tgbotapi.extensions.api.answers.answerInlineQuery
@@ -196,7 +195,7 @@ suspend fun main(vararg args: String) {
BotCommand("reply_markup", "Use to get reply markup keyboard with web app trigger"),
BotCommand("inline", "Use to get inline keyboard with web app trigger"),
)
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
println(it)
}
println(getMe())

View File

@@ -14,8 +14,8 @@ allprojects {
nativePartTemplate = "${rootProject.projectDir.absolutePath}/native_template.gradle"
}
repositories {
mavenLocal()
mavenCentral()
google()
if (project.hasProperty("GITHUB_USER") && project.hasProperty("GITHUB_TOKEN")) {
maven {
url "https://maven.pkg.github.com/InsanusMokrassar/TelegramBotAPI"
@@ -26,8 +26,7 @@ allprojects {
}
}
maven { url "https://proxy.nexus.inmo.dev/repository/maven-releases/" }
mavenLocal()
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
}
}

View File

@@ -5,9 +5,9 @@ org.gradle.jvmargs=-Xmx3148m
kotlin.daemon.jvmargs=-Xmx3g -Xms500m
kotlin_version=2.2.20
telegram_bot_api_version=30.0.0
micro_utils_version=0.26.6
serialization_version=1.9.0
ktor_version=3.3.1
compose_version=1.9.1
kotlin_version=2.1.10
telegram_bot_api_version=24.0.0
micro_utils_version=0.24.6
serialization_version=1.8.0
ktor_version=3.1.0
compose_version=1.7.3

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip

View File

@@ -59,7 +59,3 @@ include ":CustomBot"
include ":MemberUpdatedWatcherBot"
include ":WebHooks"
include ":SuggestedPosts"
include ":ChecklistsBot"