Compare commits

...

61 Commits

Author SHA1 Message Date
3609ae6bc2 Merge pull request #240 from InsanusMokrassar/renovate/serialization_version
Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.6.2
2023-12-25 09:00:04 +06:00
4f128f3421 Merge pull request #239 from InsanusMokrassar/renovate/io.ktor-ktor-client-logging-jvm-2.x
Update dependency io.ktor:ktor-client-logging-jvm to v2.3.7
2023-12-25 08:59:47 +06:00
ada6cd61d7 Merge pull request #247 from InsanusMokrassar/renovate/kotlin-monorepo
Update kotlin monorepo to v1.9.22
2023-12-25 08:59:28 +06:00
renovate[bot]
051d647004 Update kotlin monorepo to v1.9.22 2023-12-21 14:11:32 +00:00
renovate[bot]
d21606860a Update dependency io.ktor:ktor-client-logging-jvm to v2.3.7 2023-12-07 12:54:06 +00:00
renovate[bot]
93c0fcb5bd Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.6.2 2023-11-30 16:56:11 +00:00
b1b8d0eb75 Merge pull request #243 from InsanusMokrassar/9.4.0
9.4.0
2023-11-26 18:17:08 +06:00
2ac23f70ab Update gradle.properties 2023-11-26 15:28:56 +06:00
e155373655 small improvement in GetMe 2023-11-25 12:57:11 +06:00
d842dab5b8 update dependencies 2023-11-25 12:44:05 +06:00
7186d5e624 Merge pull request #215 from InsanusMokrassar/renovate/io.ktor-ktor-client-logging-jvm-2.x
Update dependency io.ktor:ktor-client-logging-jvm to v2.3.5
2023-11-06 13:27:49 +06:00
renovate[bot]
8fefb17599 Update dependency io.ktor:ktor-client-logging-jvm to v2.3.5 2023-11-06 07:23:57 +00:00
bcf4ae5888 Merge pull request #237 from InsanusMokrassar/9.3.0
9.3.0
2023-11-06 13:23:44 +06:00
7090db148e Update gradle.properties 2023-11-05 13:53:10 +06:00
7d786f0e06 improvements 2023-11-05 12:33:45 +06:00
c88f84011f Update build.yml 2023-11-05 03:41:21 +06:00
b8cc8854ea Update gradle-wrapper.properties 2023-11-05 02:43:40 +06:00
13470999e8 Update gradle.properties 2023-11-05 02:43:13 +06:00
af04a854ef fixes 2023-10-25 15:33:05 +06:00
44e86c9349 small fixes in ResenderBot Lib 2023-10-21 00:50:03 +06:00
65c32d97d5 update native buildings configuration 2023-10-21 00:28:12 +06:00
9b7605591e update dependencies to work with linux arm 64 2023-10-20 22:50:36 +06:00
89d5a4f911 add support of arch target 2023-10-20 22:29:59 +06:00
53cf212175 fixes 2023-10-17 23:28:13 +06:00
28301a92c9 update webapp sample 2023-10-17 23:23:24 +06:00
f814b11777 update up to tgbotapi 9.3.0 2023-10-15 23:29:02 +06:00
9773a74890 update ktgbotapi version 9.2.2 2023-10-11 15:21:44 +06:00
a81cfaaba9 Merge pull request #234 from InsanusMokrassar/renovate/telegram_bot_api_version
Update telegram_bot_api_version to v9.2.1
2023-09-30 07:26:47 +06:00
renovate[bot]
ee599611f3 Update telegram_bot_api_version to v9.2.1 2023-09-29 19:24:07 +00:00
d3d6cd16c6 Merge pull request #235 from InsanusMokrassar/9.2.0
Migration onto 9.2.0
2023-09-28 19:37:41 +06:00
02c3d3da1a update webapp sample to use cloud storage 2023-09-25 23:20:48 +06:00
0ad8e61c0c one more improvement 2023-09-25 16:30:17 +06:00
8f80b7e066 small fixes and improvements 2023-09-25 16:28:49 +06:00
48d1077ce4 update webapp 2023-09-25 16:20:25 +06:00
6922a6d667 fixes in rights bot 2023-09-25 14:55:37 +06:00
676ce0df80 start adding channels rights changer 2023-09-25 00:19:34 +06:00
d97c2a0562 start migration onto 9.2.0 2023-09-23 01:51:04 +06:00
35e0cb4a46 update tgbotapi 2023-09-07 22:41:02 +06:00
30f5513f54 Merge pull request #230 from InsanusMokrassar/9.1.0
Update up to 9.1.0
2023-08-20 14:31:47 +06:00
fff8edde5f update PollsBot 2023-08-20 14:30:55 +06:00
e28a795796 Update PollsBot.kt 2023-08-20 11:38:56 +06:00
d289c2101d add polls sample bot 2023-08-20 02:31:23 +06:00
2ce47074d8 update dependencies 2023-08-19 18:32:43 +06:00
281f0840eb Merge pull request #224 from InsanusMokrassar/9.0.0
9.0.0
2023-07-01 16:26:53 +06:00
34ed962104 Update gradle-wrapper.properties 2023-07-01 16:26:45 +06:00
aa3337bf3a update tgbotapi version 2023-07-01 13:52:48 +06:00
31d29712be small improvements 2023-07-01 03:54:42 +06:00
88b348376f add local folders and files into gitignore 2023-06-30 22:53:12 +06:00
0d9e295baa start migration onto 9.0.0 2023-06-30 17:49:19 +06:00
ea08bac6e8 Merge pull request #214 from InsanusMokrassar/8.0.0
8.0.0
2023-06-09 01:54:39 +06:00
a85fdc227e Update gradle.properties 2023-06-08 22:47:48 +06:00
43482ee94e start 8.0.0 2023-05-28 21:22:40 +06:00
4addb6c755 Merge pull request #212 from InsanusMokrassar/7.1.3
7.1.3
2023-05-20 22:13:25 +06:00
7d958b6edb Update gradle.properties 2023-05-20 22:12:52 +06:00
323c21f415 upgrade of hello bot 2023-05-19 22:45:46 +06:00
6350581739 Merge pull request #209 from InsanusMokrassar/7.1.2
7.1.2
2023-05-06 15:38:14 +06:00
ea1d40fd05 downgrade microutils 2023-05-06 13:27:34 +06:00
8cee63a0fb update dependencies 2023-05-06 13:13:38 +06:00
d42ef2c6cb Merge pull request #208 from InsanusMokrassar/7.1.1
7.1.1
2023-05-04 08:42:52 +06:00
c7fe90ddd7 update dependencies 2023-05-01 02:16:20 +06:00
acb382d3f7 Merge pull request #206 from InsanusMokrassar/7.1.0
7.1.0
2023-04-22 20:31:55 +06:00
23 changed files with 642 additions and 132 deletions

View File

@@ -11,9 +11,9 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt install -y libcurl4-openssl-dev sudo apt install -y libcurl4-openssl-dev
- name: Set up JDK 11 - name: Set up JDK 17
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 11 java-version: 17
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew build run: ./gradlew build

3
.gitignore vendored
View File

@@ -10,3 +10,6 @@ build/
out/ out/
kotlin-js-store/ kotlin-js-store/
local.*
local.*/

View File

@@ -17,12 +17,17 @@ suspend fun main(vararg args: String) {
telegramBotWithBehaviourAndLongPolling(botToken) { telegramBotWithBehaviourAndLongPolling(botToken) {
val me = bot.getMe() val me = bot.getMe()
val username = me.username
println(me) println(me)
if (username == null) {
error("Unable to start bot work: it have no username")
}
onText( onText(
initialFilter = { it.content.textSources.none { it is BotCommandTextSource } } // excluding messages with commands initialFilter = { it.content.textSources.none { it is BotCommandTextSource } } // excluding messages with commands
) { ) {
reply(it, makeTelegramDeepLink(me.username, it.content.text)) reply(it, makeTelegramDeepLink(username, it.content.text))
} }
onCommand("start", requireOnlyCommandInMessage = true) { // handling of `start` without args onCommand("start", requireOnlyCommandInMessage = true) { // handling of `start` without args

View File

@@ -1,5 +1,11 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.filter.filtered
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.utils.DefaultKTgBotAPIKSLog
/** /**
* This is one of the most easiest bot - it will just print information about itself * This is one of the most easiest bot - it will just print information about itself
@@ -7,6 +13,11 @@ import dev.inmo.tgbotapi.extensions.api.bot.getMe
suspend fun main(vararg args: String) { suspend fun main(vararg args: String) {
val botToken = args.first() val botToken = args.first()
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
val bot = telegramBot(botToken) val bot = telegramBot(botToken)
println(bot.getMe()) println(bot.getMe())

View File

@@ -1,8 +1,10 @@
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
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.* import dev.inmo.tgbotapi.extensions.api.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.onContentMessage import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
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.sender_chat
import dev.inmo.tgbotapi.extensions.utils.formatting.linkMarkdownV2 import dev.inmo.tgbotapi.extensions.utils.formatting.linkMarkdownV2
import dev.inmo.tgbotapi.extensions.utils.formatting.textMentionMarkdownV2 import dev.inmo.tgbotapi.extensions.utils.formatting.textMentionMarkdownV2
@@ -25,24 +27,25 @@ suspend fun main(vararg args: String) {
val botToken = args.first() val botToken = args.first()
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) { telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
onContentMessage { message -> val me = getMe()
onMentionWithAnyContent(me) { message ->
val chat = message.chat val chat = message.chat
val answerText = when (val chat = message.chat) { val answerText = when (val chat = message.chat) {
is ChannelChat -> { is PreviewChannelChat -> {
val answer = "Hi everybody in this channel \"${chat.title}\"" val answer = "Hi everybody in this channel \"${chat.title}\""
reply(message, answer, MarkdownV2) reply(message, answer, MarkdownV2)
return@onContentMessage return@onMentionWithAnyContent
} }
is PrivateChat -> { is PreviewPrivateChat -> {
reply(message, "Hi, " + "${chat.firstName} ${chat.lastName}".textMentionMarkdownV2(chat.id), MarkdownV2) reply(message, "Hi, " + "${chat.firstName} ${chat.lastName}".textMentionMarkdownV2(chat.id), MarkdownV2)
return@onContentMessage return@onMentionWithAnyContent
} }
is GroupChat -> { is PreviewGroupChat -> {
message.ifFromChannelGroupContentMessage { message.ifFromChannelGroupContentMessage {
val answer = "Hi, ${it.senderChat.title}" val answer = "Hi, ${it.senderChat.title}"
reply(message, answer, MarkdownV2) reply(message, answer, MarkdownV2)
return@onContentMessage return@onMentionWithAnyContent
} }
"Oh, hi, " + when (chat) { "Oh, hi, " + when (chat) {
is SupergroupChat -> (chat.username ?.username ?: getChat(chat).inviteLink) ?.let { is SupergroupChat -> (chat.username ?.username ?: getChat(chat).inviteLink) ?.let {
@@ -53,9 +56,7 @@ suspend fun main(vararg args: String) {
} ?: chat.title } ?: chat.title
} }
} }
is UnknownExtendedChat,
is UnknownChatType -> "Unknown :(".escapeMarkdownV2Common() is UnknownChatType -> "Unknown :(".escapeMarkdownV2Common()
else -> error("Something went wrong: unknown type of chat $chat")
} }
reply( reply(
message, message,

View File

@@ -16,15 +16,9 @@ apply plugin: 'application'
mainClassName="InlineQueriesBotKt" mainClassName="InlineQueriesBotKt"
apply from: "$nativePartTemplate"
kotlin { kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
def nativeTarget
if (hostOs == "Linux") nativeTarget = linuxX64("native") { binaries { executable() } }
else if (isMingwX64) nativeTarget = mingwX64("native") { binaries { executable() } }
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
jvm() jvm()
sourceSets { sourceSets {
@@ -35,22 +29,10 @@ kotlin {
api "dev.inmo:tgbotapi:$telegram_bot_api_version" api "dev.inmo:tgbotapi:$telegram_bot_api_version"
} }
} }
nativeMain {
dependencies {
def engine
if (hostOs == "Linux") engine = "curl"
else if (isMingwX64) engine = "winhttp"
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
api "io.ktor:ktor-client-$engine:$ktor_version"
}
}
} }
} }
dependencies { dependencies {
implementation 'io.ktor:ktor-client-logging-jvm:2.3.0' implementation 'io.ktor:ktor-client-logging-jvm:2.3.7'
} }

View File

@@ -2,6 +2,7 @@ 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.chat.get.getChat import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.api.edit.location.live.stopLiveLocation
import dev.inmo.tgbotapi.extensions.api.handleLiveLocation import dev.inmo.tgbotapi.extensions.api.handleLiveLocation
import dev.inmo.tgbotapi.extensions.api.send.* import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
@@ -75,7 +76,7 @@ suspend fun main(vararg args: String) {
sendingJob.cancel() // ends live location sendingJob.cancel() // ends live location
currentMessageState.value ?.let { currentMessageState.value ?.let {
edit(it, replyMarkup = null) // removing reply keyboard stopLiveLocation(it, replyMarkup = null)
} }
} }
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) } allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }

11
PollsBot/README.md Normal file
View File

@@ -0,0 +1,11 @@
# PollsBot
This bot will send test poll in the chat where commands will be received. Commands:
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

21
PollsBot/build.gradle Normal file
View File

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

View File

@@ -0,0 +1,99 @@
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.api.send.polls.sendRegularPoll
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.sender_chat
import dev.inmo.tgbotapi.extensions.utils.formatting.linkMarkdownV2
import dev.inmo.tgbotapi.extensions.utils.formatting.textMentionMarkdownV2
import dev.inmo.tgbotapi.extensions.utils.ifChannelChat
import dev.inmo.tgbotapi.extensions.utils.ifFromChannelGroupContentMessage
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.PollIdentifier
import dev.inmo.tgbotapi.types.chat.*
import dev.inmo.tgbotapi.types.chat.GroupChat
import dev.inmo.tgbotapi.types.chat.PrivateChat
import dev.inmo.tgbotapi.types.chat.SupergroupChat
import dev.inmo.tgbotapi.types.message.MarkdownV2
import dev.inmo.tgbotapi.types.polls.Poll
import dev.inmo.tgbotapi.types.polls.PollAnswer
import dev.inmo.tgbotapi.types.polls.PollOption
import dev.inmo.tgbotapi.types.polls.RegularPoll
import dev.inmo.tgbotapi.utils.PreviewFeature
import dev.inmo.tgbotapi.utils.extensions.escapeMarkdownV2Common
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* This bot will answer with anonymous or public poll and send message on
* updates of any of it.
*
* * Use `/anonymous` to take anonymous regular poll
* * Use `/public` to take public regular poll
*/
@OptIn(PreviewFeature::class)
suspend fun main(vararg args: String) {
val botToken = args.first()
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
val me = getMe()
val pollToChat = mutableMapOf<PollIdentifier, IdChatIdentifier>()
val pollToChatMutex = Mutex()
onCommand("anonymous") {
val sentPoll = sendRegularPoll(
it.chat,
"Test regular anonymous poll",
(1 .. 10).map {
it.toString()
},
isAnonymous = true,
replyToMessageId = it.messageId
)
pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
}
}
onCommand("public") {
val sentPoll = sendRegularPoll(
it.chat,
"Test regular anonymous poll",
(1 .. 10).map {
it.toString()
},
isAnonymous = false,
replyToMessageId = it.messageId
)
pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
}
}
onPollAnswer {
val chatId = pollToChat[it.pollId] ?: return@onPollAnswer
when(it) {
is PollAnswer.Public -> send(chatId, "[onPollAnswer] User ${it.user} have answered")
is PollAnswer.Anonymous -> send(chatId, "[onPollAnswer] Chat ${it.voterChat} have answered")
}
}
onPollUpdates {
val chatId = pollToChat[it.id] ?: return@onPollUpdates
when(it.isAnonymous) {
false -> send(chatId, "[onPollUpdates] Public poll updated: ${it.options.joinToString()}")
true -> send(chatId, "[onPollUpdates] Anonymous poll updated: ${it.options.joinToString()}")
}
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
}.second.join()
}

View File

@@ -16,15 +16,7 @@ apply plugin: 'application'
mainClassName="RandomFileSenderBotKt" mainClassName="RandomFileSenderBotKt"
kotlin { kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
def nativeTarget
if (hostOs == "Linux") nativeTarget = linuxX64("native") { binaries { executable() } }
else if (isMingwX64) nativeTarget = mingwX64("native") { binaries { executable() } }
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
jvm() jvm()
sourceSets { sourceSets {
@@ -35,18 +27,8 @@ kotlin {
api "dev.inmo:tgbotapi:$telegram_bot_api_version" api "dev.inmo:tgbotapi:$telegram_bot_api_version"
} }
} }
nativeMain {
dependencies {
def engine
if (hostOs == "Linux") engine = "curl"
else if (isMingwX64) engine = "winhttp"
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
api "io.ktor:ktor-client-$engine:$ktor_version"
}
}
} }
} }
apply from: "$nativePartTemplate"

View File

@@ -22,6 +22,7 @@ kotlin {
} }
linuxX64() linuxX64()
mingwX64() mingwX64()
linuxArm64()
sourceSets { sourceSets {
commonMain { commonMain {

View File

@@ -12,15 +12,9 @@ plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
} }
apply from: "$nativePartTemplate"
kotlin { kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
def nativeTarget
if (hostOs == "Linux") nativeTarget = linuxX64("native") { binaries { executable() } }
else if (isMingwX64) nativeTarget = mingwX64("native") { binaries { executable() } }
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
@@ -29,18 +23,6 @@ kotlin {
api project(":ResenderBot:ResenderBotLib") api project(":ResenderBot:ResenderBotLib")
} }
} }
nativeMain {
dependencies {
def engine
if (hostOs == "Linux") engine = "curl"
else if (isMingwX64) engine = "winhttp"
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
api "io.ktor:ktor-client-$engine:$ktor_version"
}
}
} }
} }

View File

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

View File

@@ -1,33 +1,50 @@
import dev.inmo.micro_utils.coroutines.firstOf
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.chat.members.getChatMember import dev.inmo.tgbotapi.extensions.api.chat.members.getChatMember
import dev.inmo.tgbotapi.extensions.api.chat.members.promoteChannelAdministrator
import dev.inmo.tgbotapi.extensions.api.chat.members.restrictChatMember import dev.inmo.tgbotapi.extensions.api.chat.members.restrictChatMember
import dev.inmo.tgbotapi.extensions.api.edit.edit import dev.inmo.tgbotapi.extensions.api.edit.edit
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.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithFSMAndStartLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.utils.asContentMessage import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.asPossiblyReplyMessage import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat
import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.extensions.utils.extendedGroupChatOrNull
import dev.inmo.tgbotapi.extensions.utils.fromUserMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.restrictedChatMemberOrNull
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard
import dev.inmo.tgbotapi.extensions.utils.whenMemberChatMember
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
import dev.inmo.tgbotapi.types.chat.ChannelChat
import dev.inmo.tgbotapi.types.chat.ChatPermissions import dev.inmo.tgbotapi.types.chat.ChatPermissions
import dev.inmo.tgbotapi.types.chat.PublicChat import dev.inmo.tgbotapi.types.chat.PublicChat
import dev.inmo.tgbotapi.types.chat.member.*
import dev.inmo.tgbotapi.types.commands.BotCommandScope import dev.inmo.tgbotapi.types.commands.BotCommandScope
import dev.inmo.tgbotapi.types.toChatId import dev.inmo.tgbotapi.types.request.RequestId
import dev.inmo.tgbotapi.utils.row import dev.inmo.tgbotapi.utils.*
import dev.inmo.tgbotapi.utils.mention
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapNotNull
sealed interface UserRetrievingStep : State {
data class RetrievingChannelChatState(
override val context: ChatId
) : UserRetrievingStep
data class RetrievingUserIdChatState(
override val context: ChatId,
val channelId: ChatId
) : UserRetrievingStep
data class RetrievingChatInfoDoneState(
override val context: ChatId,
val channelId: ChatId,
val userId: UserId
) : UserRetrievingStep
}
suspend fun main(args: Array<String>) { suspend fun main(args: Array<String>) {
val botToken = args.first() val botToken = args.first()
@@ -59,20 +76,31 @@ suspend fun main(args: Array<String>) {
val otherMessagesToggleCommonData = "$commonDataPrefix other messages" val otherMessagesToggleCommonData = "$commonDataPrefix other messages"
val webPagePreviewToggleCommonData = "$commonDataPrefix web page preview" val webPagePreviewToggleCommonData = "$commonDataPrefix web page preview"
val adminRightsDataPrefix = "admin"
val refreshAdminRightsData = "${adminRightsDataPrefix}_refresh"
val postMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_post_messages"
val editMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_edit_messages"
val deleteMessagesToggleAdminRightsData = "${adminRightsDataPrefix}_delete_messages"
val editStoriesToggleAdminRightsData = "${adminRightsDataPrefix}_edit_stories"
val deleteStoriesToggleAdminRightsData = "${adminRightsDataPrefix}_delete_stories"
val postStoriesToggleAdminRightsData = "${adminRightsDataPrefix}_post_stories"
suspend fun BehaviourContext.getUserChatPermissions(chatId: ChatId, userId: UserId): ChatPermissions? { suspend fun BehaviourContext.getUserChatPermissions(chatId: ChatId, userId: UserId): ChatPermissions? {
val chatMember = getChatMember(chatId, userId) val chatMember = getChatMember(chatId, userId)
return chatMember.restrictedChatMemberOrNull() ?: chatMember.whenMemberChatMember { return chatMember.restrictedChatMemberOrNull() ?: chatMember.whenMemberChatMember {
getChat(chatId).extendedGroupChatOrNull() ?.permissions getChat(chatId).extendedGroupChatOrNull() ?.permissions
} }
} }
fun buildGranularKeyboard(
suspend fun BehaviourContext.buildGranularKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? { permissions: ChatPermissions
val permissions = getUserChatPermissions(chatId, userId) ?: return null ): InlineKeyboardMarkup {
return inlineKeyboard { return inlineKeyboard {
row { row {
dataButton("Send messages${permissions.canSendMessages.allowedSymbol()}", messagesToggleGranularData) dataButton("Send messages${permissions.canSendMessages.allowedSymbol()}", messagesToggleGranularData)
dataButton("Send other messages${permissions.canSendOtherMessages.allowedSymbol()}", otherMessagesToggleGranularData) dataButton(
"Send other messages${permissions.canSendOtherMessages.allowedSymbol()}",
otherMessagesToggleGranularData
)
} }
row { row {
dataButton("Send audios${permissions.canSendAudios.allowedSymbol()}", audiosToggleGranularData) dataButton("Send audios${permissions.canSendAudios.allowedSymbol()}", audiosToggleGranularData)
@@ -80,11 +108,17 @@ suspend fun main(args: Array<String>) {
} }
row { row {
dataButton("Send videos${permissions.canSendVideos.allowedSymbol()}", videosToggleGranularData) dataButton("Send videos${permissions.canSendVideos.allowedSymbol()}", videosToggleGranularData)
dataButton("Send video notes${permissions.canSendVideoNotes.allowedSymbol()}", videoNotesToggleGranularData) dataButton(
"Send video notes${permissions.canSendVideoNotes.allowedSymbol()}",
videoNotesToggleGranularData
)
} }
row { row {
dataButton("Send photos${permissions.canSendPhotos.allowedSymbol()}", photosToggleGranularData) dataButton("Send photos${permissions.canSendPhotos.allowedSymbol()}", photosToggleGranularData)
dataButton("Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}", webPagePreviewToggleGranularData) dataButton(
"Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}",
webPagePreviewToggleGranularData
)
} }
row { row {
dataButton("Send polls${permissions.canSendPolls.allowedSymbol()}", pollsToggleGranularData) dataButton("Send polls${permissions.canSendPolls.allowedSymbol()}", pollsToggleGranularData)
@@ -92,6 +126,41 @@ suspend fun main(args: Array<String>) {
} }
} }
} }
fun buildAdminRightsKeyboard(
permissions: AdministratorChatMember?,
channelId: ChatId,
userId: UserId
): InlineKeyboardMarkup {
return inlineKeyboard {
permissions ?.also {
row {
dataButton("Refresh", "$refreshAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
row {
dataButton("Edit messages${permissions.canEditMessages.allowedSymbol()}", "$editMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
dataButton("Delete messages${permissions.canRemoveMessages.allowedSymbol()}", "$deleteMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
row {
dataButton("Post messages${permissions.canPostMessages.allowedSymbol()}", "$postMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
row {
dataButton("Edit stories${permissions.canEditStories.allowedSymbol()}", "$editStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
dataButton("Delete stories${permissions.canDeleteStories.allowedSymbol()}", "$deleteStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
row {
dataButton("Post stories${permissions.canPostStories.allowedSymbol()}", "$postStoriesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
} ?: row {
dataButton("Promote to admin", "$postMessagesToggleAdminRightsData ${channelId.chatId} ${userId.chatId}")
}
}
}
suspend fun BehaviourContext.buildGranularKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? {
return buildGranularKeyboard(
getUserChatPermissions(chatId, userId) ?: return null
)
}
suspend fun BehaviourContext.buildCommonKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? { suspend fun BehaviourContext.buildCommonKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? {
val permissions = getUserChatPermissions(chatId, userId) ?: return null val permissions = getUserChatPermissions(chatId, userId) ?: return null
@@ -109,12 +178,14 @@ suspend fun main(args: Array<String>) {
} }
} }
bot.buildBehaviourWithLongPolling( bot.buildBehaviourWithFSMAndStartLongPolling<UserRetrievingStep>(
defaultExceptionsHandler = { defaultExceptionsHandler = {
println(it) it.printStackTrace()
} },
) { ) {
onCommand("simple", initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull() ?.user ?.id == allowedAdmin }) { onCommand(
"simple",
initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull()?.user?.id == allowedAdmin }) {
val replyMessage = it.replyTo val replyMessage = it.replyTo
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
reply( reply(
@@ -123,9 +194,16 @@ suspend fun main(args: Array<String>) {
replyMarkup = buildCommonKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand replyMarkup = buildCommonKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand
) )
} }
onCommand("granular", initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull() ?.user ?.id == allowedAdmin }) { onCommand(
"granular",
initialFilter = {
it.chat is ChannelChat || (it.chat is PublicChat && it.fromUserMessageOrNull()?.user?.id == allowedAdmin)
}
) {
val replyMessage = it.replyTo val replyMessage = it.replyTo
val usernameInText = it.content.textSources.firstNotNullOfOrNull { it.mentionTextSourceOrNull() } ?.username
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
reply( reply(
replyMessage, replyMessage,
"Manage keyboard:", "Manage keyboard:",
@@ -137,60 +215,72 @@ suspend fun main(args: Array<String>) {
Regex("^${granularDataPrefix}.*"), Regex("^${granularDataPrefix}.*"),
initialFilter = { it.user.id == allowedAdmin } initialFilter = { it.user.id == allowedAdmin }
) { ) {
val messageReply = it.message.commonMessageOrNull() ?.replyTo ?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery val messageReply =
it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
val userId = messageReply.user.id val userId = messageReply.user.id
val permissions = getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery val permissions =
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
val newPermission = when (it.data) { val newPermission = when (it.data) {
messagesToggleGranularData -> { messagesToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendMessages = permissions.canSendMessages?.let { !it } ?: false canSendMessages = permissions.canSendMessages?.let { !it } ?: false
) )
} }
otherMessagesToggleGranularData -> { otherMessagesToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false
) )
} }
audiosToggleGranularData -> { audiosToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendAudios = permissions.canSendAudios?.let { !it } ?: false canSendAudios = permissions.canSendAudios?.let { !it } ?: false
) )
} }
voicesToggleGranularData -> { voicesToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendVoiceNotes = permissions.canSendVoiceNotes?.let { !it } ?: false canSendVoiceNotes = permissions.canSendVoiceNotes?.let { !it } ?: false
) )
} }
videosToggleGranularData -> { videosToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendVideos = permissions.canSendVideos?.let { !it } ?: false canSendVideos = permissions.canSendVideos?.let { !it } ?: false
) )
} }
videoNotesToggleGranularData -> { videoNotesToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendVideoNotes = permissions.canSendVideoNotes?.let { !it } ?: false canSendVideoNotes = permissions.canSendVideoNotes?.let { !it } ?: false
) )
} }
photosToggleGranularData -> { photosToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendPhotos = permissions.canSendPhotos?.let { !it } ?: false canSendPhotos = permissions.canSendPhotos?.let { !it } ?: false
) )
} }
webPagePreviewToggleGranularData -> { webPagePreviewToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false
) )
} }
pollsToggleGranularData -> { pollsToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendPolls = permissions.canSendPolls?.let { !it } ?: false canSendPolls = permissions.canSendPolls?.let { !it } ?: false
) )
} }
documentsToggleGranularData -> { documentsToggleGranularData -> {
permissions.copyGranular( permissions.copyGranular(
canSendDocuments = permissions.canSendDocuments?.let { !it } ?: false canSendDocuments = permissions.canSendDocuments?.let { !it } ?: false
) )
} }
else -> permissions.copyGranular() else -> permissions.copyGranular()
} }
@@ -203,7 +293,8 @@ suspend fun main(args: Array<String>) {
edit( edit(
it.message, it.message,
replyMarkup = buildGranularKeyboard(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery replyMarkup = buildGranularKeyboard(it.message.chat.id.toChatId(), userId)
?: return@onMessageDataCallbackQuery
) )
} }
@@ -211,25 +302,30 @@ suspend fun main(args: Array<String>) {
Regex("^${commonDataPrefix}.*"), Regex("^${commonDataPrefix}.*"),
initialFilter = { it.user.id == allowedAdmin } initialFilter = { it.user.id == allowedAdmin }
) { ) {
val messageReply = it.message.commonMessageOrNull() ?.replyTo ?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery val messageReply =
it.message.commonMessageOrNull()?.replyTo?.fromUserMessageOrNull() ?: return@onMessageDataCallbackQuery
val userId = messageReply.user.id val userId = messageReply.user.id
val permissions = getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery val permissions =
getUserChatPermissions(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
val newPermission = when (it.data) { val newPermission = when (it.data) {
pollsToggleCommonData -> { pollsToggleCommonData -> {
permissions.copyCommon( permissions.copyCommon(
canSendPolls = permissions.canSendPolls?.let { !it } ?: false canSendPolls = permissions.canSendPolls?.let { !it } ?: false
) )
} }
otherMessagesToggleCommonData -> { otherMessagesToggleCommonData -> {
permissions.copyCommon( permissions.copyCommon(
canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false
) )
} }
webPagePreviewToggleCommonData -> { webPagePreviewToggleCommonData -> {
permissions.copyCommon( permissions.copyCommon(
canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false
) )
} }
else -> permissions.copyCommon() else -> permissions.copyCommon()
} }
@@ -242,13 +338,169 @@ suspend fun main(args: Array<String>) {
edit( edit(
it.message, it.message,
replyMarkup = buildCommonKeyboard(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery replyMarkup = buildCommonKeyboard(it.message.chat.id.toChatId(), userId)
?: return@onMessageDataCallbackQuery
) )
} }
onMessageDataCallbackQuery(
Regex("^${adminRightsDataPrefix}.*"),
initialFilter = { it.user.id == allowedAdmin }
) {
val (channelIdString, userIdString) = it.data.split(" ").drop(1)
val channelId = ChatId(channelIdString.toLong())
val userId = ChatId(userIdString.toLong())
val chatMember = getChatMember(channelId, userId)
val asAdmin = chatMember.administratorChatMemberOrNull()
val asMember = chatMember.memberChatMemberOrNull()
val realData = it.data.takeWhile { it != ' ' }
fun Boolean?.toggleIfData(data: String) = if (realData == data) {
!(this ?: false)
} else {
null
}
if (realData != refreshAdminRightsData) {
promoteChannelAdministrator(
channelId,
userId,
canPostMessages = asAdmin ?.canPostMessages.toggleIfData(postMessagesToggleAdminRightsData),
canEditMessages = asAdmin ?.canEditMessages.toggleIfData(editMessagesToggleAdminRightsData),
canDeleteMessages = asAdmin ?.canRemoveMessages.toggleIfData(deleteMessagesToggleAdminRightsData),
canEditStories = asAdmin ?.canEditStories.toggleIfData(editStoriesToggleAdminRightsData),
canDeleteStories = asAdmin ?.canDeleteStories.toggleIfData(deleteStoriesToggleAdminRightsData),
canPostStories = asAdmin ?.canPostStories.toggleIfData(postStoriesToggleAdminRightsData),
)
}
edit(
it.message,
replyMarkup = buildAdminRightsKeyboard(
getChatMember(
channelId,
userId
).administratorChatMemberOrNull(),
channelId,
userId
)
)
}
strictlyOn<UserRetrievingStep.RetrievingChannelChatState> { state ->
val requestId = RequestId.random()
send(
state.context,
replyMarkup = replyKeyboard(
oneTimeKeyboard = true,
resizeKeyboard = true
) {
row {
requestChatButton(
"Choose channel",
requestId = requestId,
isChannel = true,
botIsMember = true,
botRightsInChat = ChatCommonAdministratorRights(
canPromoteMembers = true,
canRestrictMembers = true
),
userRightsInChat = ChatCommonAdministratorRights(
canPromoteMembers = true,
canRestrictMembers = true
)
)
}
}
) {
regular("Ok, send me the channel in which you wish to manage user, or use ")
botCommand("cancel")
regular(" to cancel the request")
}
firstOf {
include {
val chatId = waitChatSharedEventsMessages().mapNotNull {
it.chatEvent.chatId.takeIf { _ ->
it.chatEvent.requestId == requestId && it.sameChat(state.context)
}
}.first()
UserRetrievingStep.RetrievingUserIdChatState(state.context, chatId)
}
include {
waitCommandMessage("cancel").filter { it.sameChat(state.context) }.first()
null
}
}
}
strictlyOn<UserRetrievingStep.RetrievingUserIdChatState> { state ->
val requestId = RequestId.random()
send(
state.context,
replyMarkup = replyKeyboard(
oneTimeKeyboard = true,
resizeKeyboard = true
) {
row {
requestUserButton(
"Choose user",
requestId = requestId
)
}
}
) {
regular("Ok, send me the user for which you wish to change rights, or use ")
botCommand("cancel")
regular(" to cancel the request")
}
firstOf {
include {
val userContactChatId = waitUserSharedEventsMessages().filter {
it.sameChat(state.context)
}.first().chatEvent.chatId
UserRetrievingStep.RetrievingChatInfoDoneState(
state.context,
state.channelId,
userContactChatId
)
}
include {
waitCommandMessage("cancel").filter { it.sameChat(state.context) }.first()
null
}
}
}
strictlyOn<UserRetrievingStep.RetrievingChatInfoDoneState> { state ->
val chatMember = getChatMember(state.channelId, state.userId).administratorChatMemberOrNull()
if (chatMember == null) {
return@strictlyOn null
}
send(
state.context,
replyMarkup = buildAdminRightsKeyboard(
chatMember,
state.channelId,
state.userId
)
) {
regular("Rights of ")
mentionln(chatMember.user)
regular("Please, remember, that to be able to change user rights bot must promote user by itself to admin")
}
null
}
onCommand("rights_in_channel") {
startChain(UserRetrievingStep.RetrievingChannelChatState(it.chat.id.toChatId()))
}
setMyCommands( setMyCommands(
BotCommand("simple", "Trigger simple keyboard. Use with reply to user"), BotCommand("simple", "Trigger simple keyboard. Use with reply to user"),
BotCommand("granular", "Trigger granular keyboard. Use with reply to user"), BotCommand("granular", "Trigger granular keyboard. Use with reply to user"),
BotCommand("rights_in_channel", "Trigger granular keyboard. Use with reply to user"),
scope = BotCommandScope.AllGroupChats scope = BotCommandScope.AllGroupChats
) )
}.join() }.join()

View File

@@ -32,7 +32,7 @@ suspend fun main(args: Array<String>) {
} }
) { ) {
val me = getMe() val me = getMe()
fun Chat.stickerSetName() = "s${id.chatId}_by_${me.username.usernameWithoutAt}" fun Chat.stickerSetName() = "s${id.chatId}_by_${me.username ?.usernameWithoutAt}"
onCommand("start") { onCommand("start") {
reply(it) { reply(it) {
botCommand("delete") + " - to clear stickers" botCommand("delete") + " - to clear stickers"

View File

@@ -1,6 +1,7 @@
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.tgbotapi.types.webAppQueryIdField import dev.inmo.tgbotapi.types.webAppQueryIdField
import dev.inmo.tgbotapi.webapps.* import dev.inmo.tgbotapi.webapps.*
import dev.inmo.tgbotapi.webapps.cloud.*
import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackStyle import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackStyle
import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackType import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackType
import dev.inmo.tgbotapi.webapps.popup.* import dev.inmo.tgbotapi.webapps.popup.*
@@ -14,8 +15,11 @@ import kotlinx.browser.window
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.dom.appendElement import kotlinx.dom.appendElement
import kotlinx.dom.appendText import kotlinx.dom.appendText
import kotlinx.dom.clear
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import org.w3c.dom.HTMLElement import org.w3c.dom.*
import kotlin.random.Random
import kotlin.random.nextUBytes
fun HTMLElement.log(text: String) { fun HTMLElement.log(text: String) {
appendText(text) appendText(text)
@@ -67,9 +71,12 @@ fun main() {
} }
} }
}) })
appendText("Example button") appendText("Answer in chat button")
} ?: window.alert("Unable to load body") } ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
document.body ?.appendText("Allow to write in private messages: ${webApp.initDataUnsafe.user ?.allowsWriteToPM ?: "User unavailable"}")
document.body ?.appendElement("p", {}) document.body ?.appendElement("p", {})
document.body ?.appendText("Alerts:") document.body ?.appendText("Alerts:")
@@ -110,6 +117,32 @@ fun main() {
appendText("Alert") appendText("Alert")
} ?: window.alert("Unable to load body") } ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestWriteAccess() })
appendText("Request write access without callback")
} ?: window.alert("Unable to load body")
document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestWriteAccess { document.body ?.log("Write access request result: $it") } })
appendText("Request write access with callback")
} ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestContact() })
appendText("Request contact without callback")
} ?: window.alert("Unable to load body")
document.body ?.appendElement("button") {
addEventListener("click", { webApp.requestContact { document.body ?.log("Contact request result: $it") } })
appendText("Request contact with callback")
} ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
document.body ?.appendElement("button") { document.body ?.appendElement("button") {
addEventListener("click", { addEventListener("click", {
webApp.showConfirm( webApp.showConfirm(
@@ -142,6 +175,91 @@ fun main() {
document.body ?.appendElement("p", {}) document.body ?.appendElement("p", {})
document.body ?.appendElement("button") {
fun updateHeaderColor() {
val (r, g, b) = Random.nextUBytes(3)
val hex = Color.Hex(r, g, b)
webApp.setHeaderColor(hex)
(this as? HTMLButtonElement) ?.style ?.backgroundColor = hex.value
textContent = "Header color: ${hex.value.uppercase()} (click to change)"
}
addEventListener("click", {
updateHeaderColor()
})
updateHeaderColor()
} ?: window.alert("Unable to load body")
document.body ?.appendElement("p", {})
fun Element.updateCloudStorageContent() {
clear()
webApp.cloudStorage.getAll {
it.onSuccess {
document.body ?.log(it.toString())
appendElement("label") { textContent = "Cloud storage" }
appendElement("p", {})
it.forEach { (k, v) ->
appendElement("div") {
val kInput = appendElement("input", {}) as HTMLInputElement
val vInput = appendElement("input", {}) as HTMLInputElement
kInput.value = k.key
vInput.value = v.value
appendElement("button") {
addEventListener("click", {
if (k.key == kInput.value) {
webApp.cloudStorage.set(k.key, vInput.value) {
document.body ?.log(it.toString())
this@updateCloudStorageContent.updateCloudStorageContent()
}
} else {
webApp.cloudStorage.remove(k.key) {
it.onSuccess {
webApp.cloudStorage.set(kInput.value, vInput.value) {
document.body ?.log(it.toString())
this@updateCloudStorageContent.updateCloudStorageContent()
}
}
}
}
})
this.textContent = "Save"
}
}
appendElement("p", {})
}
appendElement("label") { textContent = "Cloud storage: add new" }
appendElement("p", {})
appendElement("div") {
val kInput = appendElement("input", {}) as HTMLInputElement
appendElement("button") {
textContent = "Add key"
addEventListener("click", {
webApp.cloudStorage.set(kInput.value, kInput.value) {
document.body ?.log(it.toString())
this@updateCloudStorageContent.updateCloudStorageContent()
}
})
}
}
appendElement("p", {})
}.onFailure {
document.body ?.log(it.stackTraceToString())
}
}
}
val cloudStorageContentDiv = document.body ?.appendElement("div") {} as HTMLDivElement
document.body ?.appendElement("p", {})
webApp.apply { webApp.apply {
onThemeChanged { onThemeChanged {
document.body ?.log("Theme changed: ${webApp.themeParams}") document.body ?.log("Theme changed: ${webApp.themeParams}")
@@ -171,8 +289,18 @@ fun main() {
onSettingsButtonClicked { onSettingsButtonClicked {
document.body ?.log("Settings button clicked") document.body ?.log("Settings button clicked")
} }
onWriteAccessRequested {
document.body ?.log("Write access request result: $it")
}
onContactRequested {
document.body ?.log("Contact request result: $it")
}
} }
webApp.ready() webApp.ready()
document.body ?.appendElement("input", {
(this as HTMLInputElement).value = window.location.href
})
cloudStorageContentDiv.updateCloudStorageContent()
}.onFailure { }.onFailure {
window.alert(it.stackTraceToString()) window.alert(it.stackTraceToString())
} }

View File

@@ -52,8 +52,13 @@ suspend fun main(vararg args: String) {
} }
) { ) {
routing { routing {
staticFiles("", File("WebApp/build/distributions")) { val baseJsFolder = File("WebApp/build/dist/js/")
default("WebApp/build/distributions/index.html") baseJsFolder.list() ?.forEach {
if (it == "productionExecutable" || it == "developmentExecutable") {
staticFiles("", File(baseJsFolder, it)) {
default("WebApp/build/dist/js/$it/index.html")
}
}
} }
post("inline") { post("inline") {
val requestBody = call.receiveText() val requestBody = call.receiveText()

View File

@@ -10,6 +10,9 @@ buildscript {
} }
allprojects { allprojects {
ext {
nativePartTemplate = "${rootProject.projectDir.absolutePath}/native_template.gradle"
}
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()

View File

@@ -4,8 +4,8 @@ org.gradle.parallel=true
org.gradle.jvmargs=-Xmx2g org.gradle.jvmargs=-Xmx2g
kotlin_version=1.8.20 kotlin_version=1.9.22
telegram_bot_api_version=7.1.0 telegram_bot_api_version=9.4.1
micro_utils_version=0.17.8 micro_utils_version=0.20.15
serialization_version=1.5.0 serialization_version=1.6.2
ktor_version=2.3.0 ktor_version=2.3.6

View File

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

20
native_template.gradle Normal file
View File

@@ -0,0 +1,20 @@
kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
def isArch64 = System.getProperty("os.arch") == "aarch64"
def nativeTarget
if (hostOs == "Linux") {
if (isArch64) {
nativeTarget = linuxArm64("native") { binaries { executable() } }
} else {
nativeTarget = linuxX64("native") { binaries { executable() } }
}
} else {
if (isMingwX64) {
nativeTarget = mingwX64("native") { binaries { executable() } }
} else {
throw new GradleException("Host OS is not supported in Kotlin/Native.")
}
}
}

View File

@@ -4,6 +4,8 @@ include ":RandomFileSenderBot"
include ":HelloBot" include ":HelloBot"
include ":PollsBot"
include ":GetMeBot" include ":GetMeBot"
include ":DeepLinksBot" include ":DeepLinksBot"