Compare commits

...

104 Commits

Author SHA1 Message Date
79700f24e5 small updates 2024-05-10 19:53:21 +06:00
73c1af15b3 add CustomBot 2024-05-10 18:46:47 +06:00
aa9ca976f0 update polls to include showing of polls question and explanation text sources passing 2024-05-10 16:45:47 +06:00
238533a350 update polls sample 2024-05-10 16:35:07 +06:00
6f2a8bb0be Update gradle.properties 2024-04-26 10:45:55 +06:00
99232b53d7 Merge pull request #260 from InsanusMokrassar/12.0.0
12.0.0
2024-04-21 00:25:25 +06:00
30358f7d2f Merge pull request #258 from InsanusMokrassar/11.0.0
11.0.0
2024-04-21 00:24:07 +06:00
11a97c520a update 2024-04-19 19:34:48 +06:00
d6a6ad8d37 improvements in samples 2024-04-19 18:10:46 +06:00
c04a367375 fixes and improvements 2024-04-18 20:06:59 +06:00
b660bf5f42 upgrade to support new ktgbotapi 2024-04-18 17:03:53 +06:00
57dd2380cd fix up to 11.0.0 2024-03-18 13:46:15 +06:00
fbb41c7714 start update up to tgbotapi 11.0.0 2024-03-18 12:32:15 +06:00
9170d30b2f Update gradle-wrapper.properties 2024-03-02 00:08:22 +06:00
2f3fd2e53b Update gradle.properties 2024-03-02 00:05:34 +06:00
88697fb5a6 Merge pull request #253 from InsanusMokrassar/10.1.0
10.1.0
2024-02-17 13:38:13 +06:00
578d00cac6 Update gradle.properties 2024-02-17 13:37:50 +06:00
13ecb3f0df update dependencies 2024-02-17 01:51:32 +06:00
a008d861da Merge pull request #248 from InsanusMokrassar/10.0.0
10.0.0
2024-01-12 14:57:51 +06:00
6f3766dff6 fixes in samples and update up to 10.0.0 2024-01-12 14:23:27 +06:00
fda366d820 add boosts sample 2024-01-12 00:49:59 +06:00
578887ac63 update userChatShared bot 2024-01-12 00:29:48 +06:00
6a04b3980c improvements in users requests 2024-01-10 23:10:28 +06:00
984ffb8bae update dependencies 2024-01-10 16:47:31 +06:00
2bcec6487d fixes 2024-01-09 18:30:54 +06:00
a5e3bfc3fe update dependency of tgbotapi 2024-01-09 18:05:30 +06:00
941afd0902 update LinkPreviewsBot 2024-01-09 17:57:49 +06:00
94c014b308 add LinkPreviewsBot 2024-01-09 17:56:33 +06:00
538cc9d44f improvement of ReactionsInfoBot 2024-01-09 13:55:35 +06:00
cb29726487 add showing of reactions count in println 2024-01-09 13:06:18 +06:00
262ef26239 updates and fixes 2024-01-08 19:23:06 +06:00
41efe5e141 update tgbotapi version 2024-01-08 15:55:05 +06:00
05e289975a add reactions info bot 2024-01-08 15:46:23 +06:00
753d686fab build fixes 2024-01-08 13:21:41 +06:00
281243c7e5 update dependencies 2024-01-08 10:14:08 +06:00
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
0cfe60fd77 Update gradle.properties 2023-04-22 20:31:41 +06:00
6719b9e17c Update Bot.kt 2023-04-22 16:39:59 +06:00
8d33dc0ab2 Update README.md 2023-04-22 16:36:14 +06:00
3e2ccf9cf1 add answerInlineQuery with WebAppInfo 2023-04-22 11:06:03 +06:00
eccbe71e68 finish checking update 2023-04-22 00:17:39 +06:00
24c74f3b1a in keyboards bot add sample with sending of inline query 2023-04-22 00:11:56 +06:00
d7a7e7153e add inline queries sample 2023-04-21 23:21:15 +06:00
0b37acb7a9 Merge pull request #202 from InsanusMokrassar/7.0.2
7.0.2
2023-04-20 03:55:59 +06:00
47 changed files with 1743 additions and 245 deletions

View File

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

3
.gitignore vendored
View File

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

9
BoostsInfoBot/README.md Normal file
View File

@@ -0,0 +1,9 @@
# UserChatShared
Showing info about boosts
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

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

View File

@@ -0,0 +1,65 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.get.getUserChatBoosts
import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatBoostUpdated
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatShared
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.types.chat.member.ChatCommonAdministratorRights
import dev.inmo.tgbotapi.types.request.RequestId
import dev.inmo.tgbotapi.utils.regular
import korlibs.time.DateFormat
import korlibs.time.format
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))
}
)
}
val requestChatId = RequestId(1)
val bot = telegramBot(args.first())
bot.buildBehaviourWithLongPolling (defaultExceptionsHandler = { it.printStackTrace() }) {
onChatBoostUpdated {
println(it)
}
onCommand("start") {
reply(
it,
replyMarkup = flatReplyKeyboard {
requestChannelButton(
"Click me :)",
requestChatId,
botIsMember = true
)
}
) {
regular("Select chat to get know about your boosts")
}
}
onChatShared(initialFilter = { it.chatEvent.requestId == requestChatId }) {
val boosts = getUserChatBoosts(it.chatEvent.chatId, it.chat.id)
reply(
it
) {
boosts.boosts.forEach {
regular("Boost added: ${DateFormat.FORMAT1.format(it.addDate.asDate)}; Boost expire: ${DateFormat.FORMAT1.format(it.expirationDate.asDate)}; Unformatted: $it") + "\n"
}
}
}
}.join()
}

View File

@@ -0,0 +1,9 @@
# BusinessConnectionBotBot
When bot connected or disconnected to the business chat, it will notify this chat
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

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

View File

@@ -0,0 +1,85 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
suspend fun main(args: Array<String>) {
val botToken = args.first()
val isDebug = args.getOrNull(1) == "debug"
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
val businessConnectionsChats = mutableMapOf<BusinessConnectionId, ChatId>()
val businessConnectionsChatsMutex = Mutex()
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
val me = getMe()
println(me)
onBusinessConnectionEnabled {
businessConnectionsChatsMutex.withLock {
businessConnectionsChats[it.id] = it.userChatId
}
send(it.userChatId, "Business connection ${it.businessConnectionId.string} has been enabled")
}
onBusinessConnectionDisabled {
businessConnectionsChatsMutex.withLock {
businessConnectionsChats.remove(it.id)
}
send(it.userChatId, "Business connection ${it.businessConnectionId.string} has been disabled")
}
onContentMessage {
it.ifBusinessContentMessage {
val sent = execute(it.content.createResend(it.from.id))
if (it.sentByBusinessConnectionOwner) {
reply(sent, "You have sent this message to the ${it.businessConnectionId.string} related chat")
} else {
reply(sent, "User have sent this message to you in the ${it.businessConnectionId.string} related chat")
}
}
}
onEditedContentMessage {
it.ifBusinessContentMessage {
val sent = execute(it.content.createResend(it.from.id))
if (it.sentByBusinessConnectionOwner) {
reply(sent, "You have edited this message in the ${it.businessConnectionId.string} related chat")
} else {
reply(sent, "User have edited this message to you in the ${it.businessConnectionId.string} related chat")
}
}
}
onBusinessMessagesDeleted {
var businessConnectionOwnerChat = businessConnectionsChatsMutex.withLock {
businessConnectionsChats[it.businessConnectionId]
}
if (businessConnectionOwnerChat == null) {
val businessConnection = getBusinessConnection(it.businessConnectionId)
businessConnectionsChatsMutex.withLock {
businessConnectionsChats[businessConnection.businessConnectionId] = businessConnection.userChatId
}
businessConnectionOwnerChat = businessConnection.userChatId
}
send(businessConnectionOwnerChat, "There are several removed messages in chat ${it.chat.id}: ${it.messageIds}")
}
}.second.join()
}

9
CustomBot/README.md Normal file
View File

@@ -0,0 +1,9 @@
# CustomBot
This bot basically have no any useful behaviour, but you may customize it as a playground
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

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

View File

@@ -0,0 +1,33 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.utils.PreviewFeature
import kotlinx.coroutines.*
/**
* The main purpose of this bot is just to answer "Oh, hi, " and add user mention here
*/
@OptIn(PreviewFeature::class)
suspend fun main(vararg args: String) {
val botToken = args.first()
val isDebug = args.any { it == "debug" }
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
val me = getMe()
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
}.second.join()
}

View File

@@ -17,12 +17,17 @@ suspend fun main(vararg args: String) {
telegramBotWithBehaviourAndLongPolling(botToken) {
val me = bot.getMe()
val username = me.username
println(me)
if (username == null) {
error("Unable to start bot work: it have no username")
}
onText(
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

View File

@@ -1,13 +1,16 @@
import dev.inmo.micro_utils.coroutines.AccumulatorFlow
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.api.send.sendMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgs
import dev.inmo.tgbotapi.extensions.utils.extensions.sameThread
import dev.inmo.tgbotapi.extensions.utils.formatting.*
import dev.inmo.tgbotapi.extensions.utils.textContentOrNull
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.MessageThreadId
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
@@ -54,7 +57,8 @@ suspend fun main(args: Array<String>) {
val content = contentMessage.content
when {
content is TextContent && content.parseCommandsWithParams().keys.contains("stop") -> StopState(it.context)
content is TextContent && content.text == "/stop"
|| content is TextContent && content.parseCommandsWithArgs().keys.contains("stop") -> StopState(it.context)
else -> {
execute(content.createResend(it.context))
it
@@ -72,5 +76,17 @@ suspend fun main(args: Array<String>) {
) {
startChain(ExpectContentOrStopState(it.chat.id, it))
}
onContentMessage(
{
it.content.textContentOrNull() ?.text == "/start"
}
) {
startChain(ExpectContentOrStopState(it.chat.id, it.withContentOrNull() ?: return@onContentMessage))
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
println(it)
}
}.second.join()
}

View File

@@ -1,24 +1,31 @@
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.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.chat.forum.closeForumTopic
import dev.inmo.tgbotapi.extensions.api.chat.forum.createForumTopic
import dev.inmo.tgbotapi.extensions.api.chat.forum.deleteForumTopic
import dev.inmo.tgbotapi.extensions.api.chat.forum.reopenForumTopic
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviour
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicClosed
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.CustomEmojiId
import dev.inmo.tgbotapi.types.ForumTopic
import kotlinx.coroutines.delay
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.utils.DefaultKTgBotAPIKSLog
/**
* This is one of the most easiest bot - it will just print information about itself
*/
suspend fun main(vararg args: String) {
val botToken = args.first()
val isDebug = args.getOrNull(1) == "debug"
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
val bot = telegramBot(botToken)
println(bot.getMe())
val me = bot.getMe()
println(me)
println(bot.getChat(me))
}

View File

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

View File

@@ -0,0 +1,9 @@
# InlineQueriesBot
This bot will form the inline queries for you. For that feature you should explicitly enable inline queries in bot settings
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

View File

@@ -0,0 +1,38 @@
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
plugins {
id "org.jetbrains.kotlin.multiplatform"
}
apply plugin: 'application'
mainClassName="InlineQueriesBotKt"
apply from: "$nativePartTemplate"
kotlin {
jvm()
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
api "dev.inmo:tgbotapi:$telegram_bot_api_version"
}
}
}
}
dependencies {
implementation 'io.ktor:ktor-client-logging-jvm:2.3.7'
}

View File

@@ -0,0 +1,68 @@
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
import dev.inmo.tgbotapi.extensions.api.telegramBot
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onBaseInlineQuery
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onDeepLink
import dev.inmo.tgbotapi.requests.answers.InlineQueryResultsButton
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent
import dev.inmo.tgbotapi.types.InlineQueryId
import dev.inmo.tgbotapi.types.inlineQueryAnswerResultsLimit
import dev.inmo.tgbotapi.utils.buildEntities
/**
* Thi bot will create inline query answers. You
* should enable inline queries in bot settings
*/
suspend fun doInlineQueriesBot(token: String) {
val bot = telegramBot(token)
bot.buildBehaviourWithLongPolling(
defaultExceptionsHandler = { it.printStackTrace() },
) {
onBaseInlineQuery {
val page = it.offset.toIntOrNull() ?: 0
val results = (0 until inlineQueryAnswerResultsLimit.last).map {
(page * inlineQueryAnswerResultsLimit.last) + it
}
answer(
it,
results = results.map { resultNumber ->
val inlineQueryId = InlineQueryId(resultNumber.toString())
InlineQueryResultArticle(
inlineQueryId,
"Title $resultNumber",
InputTextMessageContent(
buildEntities {
+"Result text of " + resultNumber.toString() + " result:\n"
+it.query
}
),
description = "Description of $resultNumber result"
)
},
cachedTime = 0,
isPersonal = true,
button = InlineQueryResultsButton.Start(
"Text of button with page $page",
"deep_link_for_page_$page"
),
nextOffset = (page + 1).toString()
)
}
onDeepLink { (message, deepLink) ->
reply(message, deepLink)
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
println(it)
}
println(getMe())
}.join()
}

View File

@@ -0,0 +1,5 @@
import dev.inmo.micro_utils.common.MPPFile
suspend fun main(args: Array<String>) {
doInlineQueriesBot(args.first())
}

View File

@@ -0,0 +1,7 @@
import kotlinx.coroutines.runBlocking
fun main(args: Array<String>) {
runBlocking {
doInlineQueriesBot(args.first())
}
}

View File

@@ -4,12 +4,18 @@ import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.answers.answer
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.api.edit.editMessageText
import dev.inmo.tgbotapi.extensions.api.edit.reply_markup.editMessageReplyMarkup
import dev.inmo.tgbotapi.extensions.api.edit.text.editMessageText
import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.extensions.utils.withContent
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent
import dev.inmo.tgbotapi.types.InlineQueryId
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.utils.*
import kotlinx.coroutines.*
@@ -55,6 +61,16 @@ fun InlineKeyboardBuilder.includePageButtons(page: Int, count: Int) {
dataButton(">>", "$count $count")
}
}
row {
inlineQueryInChosenChatButton(
"Send somebody page",
query = "$page $count",
allowUsers = true,
allowBots = true,
allowGroups = true,
allowChannels = true,
)
}
}
suspend fun activateKeyboardsBot(
@@ -71,9 +87,7 @@ suspend fun activateKeyboardsBot(
reply(
message,
replyMarkup = inlineKeyboard {
row {
includePageButtons(1, numberOfPages)
}
includePageButtons(1, numberOfPages)
}
) {
regular("Your inline keyboard with $numberOfPages pages")
@@ -92,15 +106,48 @@ suspend fun activateKeyboardsBot(
return@onMessageDataCallbackQuery
},
replyMarkup = inlineKeyboard {
row {
includePageButtons(page, count)
}
includePageButtons(page, count)
}
) {
regular("This is $page of $count")
}
answer(it)
}
onInlineMessageIdDataCallbackQuery {
val (page, count) = it.data.parsePageAndCount() ?: it.let {
answer(it, "Unsupported data :(")
return@onInlineMessageIdDataCallbackQuery
}
editMessageText(
it.inlineMessageId,
replyMarkup = inlineKeyboard {
includePageButtons(page, count)
}
) {
regular("This is $page of $count")
}
answer(it)
}
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
answer(
it,
results = listOf(
InlineQueryResultArticle(
InlineQueryId(it.query),
"Send buttons",
InputTextMessageContent("It is sent via inline mode inline buttons"),
replyMarkup = inlineKeyboard {
includePageButtons(page, count)
}
)
)
)
}
onUnhandledCommand {
reply(

View File

@@ -0,0 +1,9 @@
# ReactionsInfoBot
This bot will resend messages with links with all variants of `LinkPreviewOptions`
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

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

View File

@@ -0,0 +1,93 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.send.copyMessage
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.send
import dev.inmo.tgbotapi.extensions.api.send.setMessageReaction
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatMessageReactionUpdatedByUser
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatMessageReactionsCountUpdated
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
import dev.inmo.tgbotapi.extensions.utils.textLinkTextSourceOrNull
import dev.inmo.tgbotapi.extensions.utils.uRLTextSourceOrNull
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
import dev.inmo.tgbotapi.types.LinkPreviewOptions
import dev.inmo.tgbotapi.types.chat.ExtendedChat
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.content.TextedContent
import dev.inmo.tgbotapi.types.reactions.Reaction
import dev.inmo.tgbotapi.utils.customEmoji
import dev.inmo.tgbotapi.utils.regular
/**
* This bot will reply with the same
*/
suspend fun main(vararg args: String) {
val botToken = args.first()
val isDebug = args.getOrNull(1) == "debug"
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
val bot = telegramBot(botToken)
bot.buildBehaviourWithLongPolling {
onContentMessage {
val url = it.withContentOrNull<TextedContent>() ?.let {
it.content.textSources.firstNotNullOfOrNull {
it.textLinkTextSourceOrNull() ?.url ?: it.uRLTextSourceOrNull() ?.source
}
} ?: null.apply {
reply(it) {
regular("I am support only content with text contains url only")
}
} ?: return@onContentMessage
it.withContentOrNull<TextedContent>() ?.let {
send(
it.chat,
it.content.textSources,
linkPreviewOptions = LinkPreviewOptions.Disabled
)
send(
it.chat,
it.content.textSources,
linkPreviewOptions = LinkPreviewOptions.Large(url, showAboveText = true)
)
send(
it.chat,
it.content.textSources,
linkPreviewOptions = LinkPreviewOptions.Large(url, showAboveText = false)
)
send(
it.chat,
it.content.textSources,
linkPreviewOptions = LinkPreviewOptions.Small(url, showAboveText = true)
)
send(
it.chat,
it.content.textSources,
linkPreviewOptions = LinkPreviewOptions.Small(url, showAboveText = false)
)
send(
it.chat,
it.content.textSources,
linkPreviewOptions = LinkPreviewOptions.Default(url, showAboveText = true)
)
send(
it.chat,
it.content.textSources,
linkPreviewOptions = LinkPreviewOptions.Default(url, showAboveText = false)
)
}
}
}.join()
}

View File

@@ -1,32 +1,16 @@
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.EditLiveLocationInfo
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
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.send.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.behaviour_builder.oneOf
import dev.inmo.tgbotapi.extensions.behaviour_builder.parallel
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
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.extensions.utils.types.buttons.dataButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
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.location.LiveLocation
import dev.inmo.tgbotapi.types.message.MarkdownV2
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.LiveLocationContent
import dev.inmo.tgbotapi.types.message.content.LocationContent
import dev.inmo.tgbotapi.utils.PreviewFeature
import dev.inmo.tgbotapi.utils.extensions.escapeMarkdownV2Common
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
@@ -63,7 +47,7 @@ suspend fun main(vararg args: String) {
handleLiveLocation(
it.chat.id,
locationsFlow,
sentMessageFlow = FlowCollector { currentMessageState.emit(it) }
sentMessageFlow = FlowCollector { currentMessageState.emit(it) },
)
}
@@ -75,7 +59,7 @@ suspend fun main(vararg args: String) {
sendingJob.cancel() // ends live location
currentMessageState.value ?.let {
edit(it, replyMarkup = null) // removing reply keyboard
stopLiveLocation(it, replyMarkup = null)
}
}
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,186 @@
import com.benasher44.uuid.uuid4
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.api.send.polls.sendQuizPoll
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.customEmojiTextSourceOrNull
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgsSources
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.*
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.*
import dev.inmo.tgbotapi.utils.*
import dev.inmo.tgbotapi.utils.extensions.escapeMarkdownV2Common
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.random.Random
/**
* 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()
val isDebug = args.any { it == "debug" }
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
val me = getMe()
val pollToChat = mutableMapOf<PollId, IdChatIdentifier>()
val pollToChatMutex = Mutex()
onCommand("anonymous", requireOnlyCommandInMessage = false) {
val customEmoji = it.content.parseCommandsWithArgsSources()
.toList()
.firstOrNull { it.first.command == "anonymous" }
?.second
?.firstNotNullOfOrNull { it.customEmojiTextSourceOrNull() }
val sentPoll = sendRegularPoll(
it.chat.id,
buildEntities {
regular("Test regular anonymous poll")
if (customEmoji != null) {
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
}
},
(1 .. 10).map {
InputPollOption {
regular(it.toString()) + " "
if (customEmoji != null) {
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
}
}
},
isAnonymous = true,
replyParameters = ReplyParameters(it)
)
pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
}
}
onCommand("public", requireOnlyCommandInMessage = false) {
val customEmoji = it.content.parseCommandsWithArgsSources()
.toList()
.firstOrNull { it.first.command == "public" }
?.second
?.firstNotNullOfOrNull { it.customEmojiTextSourceOrNull() }
val sentPoll = sendRegularPoll(
it.chat.id,
buildEntities {
regular("Test regular non anonymous poll")
if (customEmoji != null) {
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
}
},
(1 .. 10).map {
InputPollOption {
regular(it.toString()) + " "
if (customEmoji != null) {
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
}
}
},
isAnonymous = false,
replyParameters = ReplyParameters(it)
)
pollToChatMutex.withLock {
pollToChat[sentPoll.content.poll.id] = sentPoll.chat.id
}
}
onCommand("quiz", requireOnlyCommandInMessage = false) {
val customEmoji = it.content.parseCommandsWithArgsSources()
.toList()
.firstOrNull { it.first.command == "quiz" }
?.second
?.firstNotNullOfOrNull { it.customEmojiTextSourceOrNull() }
val correctAnswer = Random.nextInt(10)
val sentPoll = sendQuizPoll(
it.chat.id,
questionEntities = buildEntities {
regular("Test quiz poll")
if (customEmoji != null) {
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
}
},
(1 .. 10).map {
InputPollOption {
regular(it.toString()) + " "
if (customEmoji != null) {
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
}
}
},
isAnonymous = false,
replyParameters = ReplyParameters(it),
correctOptionId = correctAnswer,
explanationTextSources = buildEntities {
regular("Random solved it to be ") + underline((correctAnswer + 1).toString()) + " "
if (customEmoji != null) {
customEmoji(customEmoji.customEmojiId, customEmoji.subsources)
}
}
)
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()}")
}
}
setMyCommands(
BotCommand("anonymous", "Create anonymous regular poll"),
BotCommand("public", "Create non anonymous regular poll"),
BotCommand("quiz", "Create quiz poll with random right answer"),
)
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
}.second.join()
}

View File

@@ -16,15 +16,7 @@ apply plugin: 'application'
mainClassName="RandomFileSenderBotKt"
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()
sourceSets {
@@ -35,18 +27,8 @@ kotlin {
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

@@ -0,0 +1,9 @@
# ReactionsInfoBot
This bot will send info about user reactions in his PM with reply to message user reacted to
## Launch
```bash
../gradlew run --args="BOT_TOKEN"
```

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

View File

@@ -0,0 +1,68 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.setMessageReaction
import dev.inmo.tgbotapi.extensions.api.send.setMessageReactions
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatMessageReactionUpdatedByUser
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatMessageReactionsCountUpdated
import dev.inmo.tgbotapi.types.chat.ExtendedChat
import dev.inmo.tgbotapi.types.reactions.Reaction
import dev.inmo.tgbotapi.utils.customEmoji
import dev.inmo.tgbotapi.utils.regular
/**
* This bot will send info about user reactions in his PM with reply to message user reacted to
*/
suspend fun main(vararg args: String) {
val botToken = args.first()
val isDebug = args.getOrNull(1) == "debug"
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
val bot = telegramBot(botToken)
bot.buildBehaviourWithLongPolling {
onChatMessageReactionUpdatedByUser {
setMessageReaction(
it.chat.id,
it.messageId,
""
)
val replyResult = reply(
it.chat.id,
it.messageId,
replyInChatId = it.reactedUser.id
) {
regular("Current reactions for message in reply:\n")
it.new.forEach {
when (it) {
is Reaction.CustomEmoji -> regular("") + customEmoji(it.customEmojiId) + regular("(customEmojiId: ${it.customEmojiId})")
is Reaction.Emoji -> regular("${it.emoji}")
is Reaction.Unknown -> regular("• Unknown emoji ($it)")
}
regular("\n")
}
}
setMessageReaction(
it.chat.id,
it.messageId,
)
}
onChatMessageReactionsCountUpdated {
val extendedChat: ExtendedChat = getChat(it.chat)
println(extendedChat)
println(it)
}
}.join()
}

View File

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

View File

@@ -8,6 +8,11 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilte
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.extensions.utils.shortcuts.*
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.types.quoteEntitiesField
import dev.inmo.tgbotapi.utils.extensions.threadIdOrNull
import kotlinx.coroutines.*
@@ -18,6 +23,7 @@ suspend fun activateResenderBot(
telegramBotWithBehaviourAndLongPolling(token, scope = CoroutineScope(currentCoroutineContext() + SupervisorJob())) {
onContentMessage(
subcontextUpdatesFilter = MessageFilterByChat,
initialFilter = { it !is BusinessContentMessage<*> || !it.sentByBusinessConnectionOwner }
) {
val chat = it.chat
@@ -26,7 +32,14 @@ suspend fun activateResenderBot(
it.content.createResend(
chat.id,
messageThreadId = it.threadIdOrNull,
replyToMessageId = it.messageId
replyParameters = it.replyInfo ?.messageMeta ?.let { meta ->
val quote = it.withContentOrNull<TextContent>() ?.content ?.quote
ReplyParameters(
meta,
entities = quote ?.textSources ?: emptyList(),
quotePosition = quote ?.position
)
}
)
) {
it.forEach(print)

View File

@@ -12,15 +12,9 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
}
apply from: "$nativePartTemplate"
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 {
commonMain {
dependencies {
@@ -29,18 +23,6 @@ kotlin {
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 "dev.inmo:tgbotapi:$telegram_bot_api_version"
implementation 'io.ktor:ktor-client-logging-jvm:2.3.7'
}

View File

@@ -1,40 +1,73 @@
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.firstOf
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
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.promoteChannelAdministrator
import dev.inmo.tgbotapi.extensions.api.chat.members.restrictChatMember
import dev.inmo.tgbotapi.extensions.api.edit.edit
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.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.onMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.utils.asContentMessage
import dev.inmo.tgbotapi.extensions.utils.asPossiblyReplyMessage
import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull
import dev.inmo.tgbotapi.extensions.utils.contentMessageOrNull
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.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.types.*
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.PublicChat
import dev.inmo.tgbotapi.types.chat.member.*
import dev.inmo.tgbotapi.types.commands.BotCommandScope
import dev.inmo.tgbotapi.types.toChatId
import dev.inmo.tgbotapi.utils.row
import dev.inmo.tgbotapi.types.message.abstracts.AccessibleMessage
import dev.inmo.tgbotapi.types.request.RequestId
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>) {
val botToken = args.first()
val isDebug = args.getOrNull(2) == "debug"
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
val bot = telegramBot(botToken)
val allowedAdmin = ChatId(args[1].toLong())
val allowedAdmin = ChatId(RawChatId(args[1].toLong()))
fun Boolean?.allowedSymbol() = when (this) {
true -> ""
@@ -59,20 +92,31 @@ suspend fun main(args: Array<String>) {
val otherMessagesToggleCommonData = "$commonDataPrefix other messages"
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? {
val chatMember = getChatMember(chatId, userId)
return chatMember.restrictedChatMemberOrNull() ?: chatMember.whenMemberChatMember {
getChat(chatId).extendedGroupChatOrNull() ?.permissions
}
}
suspend fun BehaviourContext.buildGranularKeyboard(chatId: ChatId, userId: UserId): InlineKeyboardMarkup? {
val permissions = getUserChatPermissions(chatId, userId) ?: return null
fun buildGranularKeyboard(
permissions: ChatPermissions
): InlineKeyboardMarkup {
return inlineKeyboard {
row {
dataButton("Send messages${permissions.canSendMessages.allowedSymbol()}", messagesToggleGranularData)
dataButton("Send other messages${permissions.canSendOtherMessages.allowedSymbol()}", otherMessagesToggleGranularData)
dataButton(
"Send other messages${permissions.canSendOtherMessages.allowedSymbol()}",
otherMessagesToggleGranularData
)
}
row {
dataButton("Send audios${permissions.canSendAudios.allowedSymbol()}", audiosToggleGranularData)
@@ -80,11 +124,17 @@ suspend fun main(args: Array<String>) {
}
row {
dataButton("Send videos${permissions.canSendVideos.allowedSymbol()}", videosToggleGranularData)
dataButton("Send video notes${permissions.canSendVideoNotes.allowedSymbol()}", videoNotesToggleGranularData)
dataButton(
"Send video notes${permissions.canSendVideoNotes.allowedSymbol()}",
videoNotesToggleGranularData
)
}
row {
dataButton("Send photos${permissions.canSendPhotos.allowedSymbol()}", photosToggleGranularData)
dataButton("Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}", webPagePreviewToggleGranularData)
dataButton(
"Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}",
webPagePreviewToggleGranularData
)
}
row {
dataButton("Send polls${permissions.canSendPolls.allowedSymbol()}", pollsToggleGranularData)
@@ -92,6 +142,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? {
val permissions = getUserChatPermissions(chatId, userId) ?: return null
@@ -109,88 +194,122 @@ suspend fun main(args: Array<String>) {
}
}
bot.buildBehaviourWithLongPolling(
bot.buildBehaviourWithFSMAndStartLongPolling<UserRetrievingStep>(
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 userInReply = replyMessage ?.fromUserMessageOrNull() ?.user ?.id ?: return@onCommand
reply(
replyMessage,
"Manage keyboard:",
replyMarkup = buildCommonKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand
)
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
if (replyMessage is AccessibleMessage) {
reply(
replyMessage,
"Manage keyboard:",
replyMarkup = buildCommonKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand
)
} else {
reply(it) {
regular("Reply to somebody's message to get hist/her rights keyboard")
}
}
}
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 userInReply = replyMessage ?.fromUserMessageOrNull() ?.user ?.id ?: return@onCommand
reply(
replyMessage,
"Manage keyboard:",
replyMarkup = buildGranularKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand
)
val usernameInText = it.content.textSources.firstNotNullOfOrNull { it.mentionTextSourceOrNull() } ?.username
val userInReply = replyMessage?.fromUserMessageOrNull()?.user?.id ?: return@onCommand
if (replyMessage is AccessibleMessage) {
reply(
replyMessage,
"Manage keyboard:",
replyMarkup = buildGranularKeyboard(it.chat.id.toChatId(), userInReply) ?: return@onCommand
)
} else {
reply(it) {
regular("Reply to somebody's message to get hist/her rights keyboard")
}
}
}
onMessageDataCallbackQuery(
Regex("^${granularDataPrefix}.*"),
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 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) {
messagesToggleGranularData -> {
permissions.copyGranular(
canSendMessages = permissions.canSendMessages ?.let { !it } ?: false
canSendMessages = permissions.canSendMessages?.let { !it } ?: false
)
}
otherMessagesToggleGranularData -> {
permissions.copyGranular(
canSendOtherMessages = permissions.canSendOtherMessages ?.let { !it } ?: false
canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false
)
}
audiosToggleGranularData -> {
permissions.copyGranular(
canSendAudios = permissions.canSendAudios ?.let { !it } ?: false
canSendAudios = permissions.canSendAudios?.let { !it } ?: false
)
}
voicesToggleGranularData -> {
permissions.copyGranular(
canSendVoiceNotes = permissions.canSendVoiceNotes ?.let { !it } ?: false
canSendVoiceNotes = permissions.canSendVoiceNotes?.let { !it } ?: false
)
}
videosToggleGranularData -> {
permissions.copyGranular(
canSendVideos = permissions.canSendVideos ?.let { !it } ?: false
canSendVideos = permissions.canSendVideos?.let { !it } ?: false
)
}
videoNotesToggleGranularData -> {
permissions.copyGranular(
canSendVideoNotes = permissions.canSendVideoNotes ?.let { !it } ?: false
canSendVideoNotes = permissions.canSendVideoNotes?.let { !it } ?: false
)
}
photosToggleGranularData -> {
permissions.copyGranular(
canSendPhotos = permissions.canSendPhotos ?.let { !it } ?: false
canSendPhotos = permissions.canSendPhotos?.let { !it } ?: false
)
}
webPagePreviewToggleGranularData -> {
permissions.copyGranular(
canAddWebPagePreviews = permissions.canAddWebPagePreviews ?.let { !it } ?: false
canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false
)
}
pollsToggleGranularData -> {
permissions.copyGranular(
canSendPolls = permissions.canSendPolls ?.let { !it } ?: false
canSendPolls = permissions.canSendPolls?.let { !it } ?: false
)
}
documentsToggleGranularData -> {
permissions.copyGranular(
canSendDocuments = permissions.canSendDocuments ?.let { !it } ?: false
canSendDocuments = permissions.canSendDocuments?.let { !it } ?: false
)
}
else -> permissions.copyGranular()
}
@@ -203,7 +322,8 @@ suspend fun main(args: Array<String>) {
edit(
it.message,
replyMarkup = buildGranularKeyboard(it.message.chat.id.toChatId(), userId) ?: return@onMessageDataCallbackQuery
replyMarkup = buildGranularKeyboard(it.message.chat.id.toChatId(), userId)
?: return@onMessageDataCallbackQuery
)
}
@@ -211,25 +331,30 @@ suspend fun main(args: Array<String>) {
Regex("^${commonDataPrefix}.*"),
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 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) {
pollsToggleCommonData -> {
permissions.copyCommon(
canSendPolls = permissions.canSendPolls ?.let { !it } ?: false
canSendPolls = permissions.canSendPolls?.let { !it } ?: false
)
}
otherMessagesToggleCommonData -> {
permissions.copyCommon(
canSendOtherMessages = permissions.canSendOtherMessages ?.let { !it } ?: false
canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false
)
}
webPagePreviewToggleCommonData -> {
permissions.copyCommon(
canAddWebPagePreviews = permissions.canAddWebPagePreviews ?.let { !it } ?: false
canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false
)
}
else -> permissions.copyCommon()
}
@@ -242,14 +367,174 @@ suspend fun main(args: Array<String>) {
edit(
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(RawChatId(channelIdString.toLong()))
val userId = ChatId(RawChatId(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(
BotCommand("simple", "Trigger simple 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
)
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
println(it)
}
}.join()
}

View File

@@ -9,16 +9,9 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.types.StickerFormat
import dev.inmo.tgbotapi.types.StickerType
import dev.inmo.tgbotapi.types.message.textsources.*
import dev.inmo.tgbotapi.types.stickers.AnimatedStickerSet
import dev.inmo.tgbotapi.types.stickers.CustomEmojiSimpleStickerSet
import dev.inmo.tgbotapi.types.stickers.CustomEmojiStickerSet
import dev.inmo.tgbotapi.types.stickers.CustomEmojiVideoStickerSet
import dev.inmo.tgbotapi.types.stickers.MaskSimpleStickerSet
import dev.inmo.tgbotapi.types.stickers.MaskStickerSet
import dev.inmo.tgbotapi.types.stickers.MaskVideoStickerSet
import dev.inmo.tgbotapi.types.stickers.RegularSimpleStickerSet
import dev.inmo.tgbotapi.types.stickers.RegularStickerSet
import dev.inmo.tgbotapi.types.stickers.RegularVideoStickerSet
import dev.inmo.tgbotapi.types.stickers.StickerSet
import dev.inmo.tgbotapi.types.stickers.UnknownStickerSet
import dev.inmo.tgbotapi.utils.bold
@@ -31,12 +24,6 @@ fun StickerSet?.buildInfo() = buildEntities {
} else {
bold("StickerSet name: ") + "${name}\n"
bold("StickerSet title: ") + "${title}\n"
bold("Sticker format: ") + when (stickerFormat) {
StickerFormat.Animated -> "Animated"
StickerFormat.Static -> "Static"
is StickerFormat.Unknown -> stickerFormat.type
StickerFormat.Video -> "Video"
} + "\n"
bold(
when (stickerType) {
StickerType.CustomEmoji -> "Custom emoji"
@@ -44,7 +31,7 @@ fun StickerSet?.buildInfo() = buildEntities {
StickerType.Regular -> "Regular"
is StickerType.Unknown -> "Unknown type \"${stickerType.type}\""
}
) + " sticker set with title " + bold(title) + " and name " + bold(name)
) + " sticker set with title " + bold(title) + " and name " + bold(name.string)
}
}

View File

@@ -13,6 +13,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSticke
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.sticker
import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile
import dev.inmo.tgbotapi.requests.stickers.InputSticker
import dev.inmo.tgbotapi.types.StickerSetName
import dev.inmo.tgbotapi.types.chat.Chat
import dev.inmo.tgbotapi.types.files.*
import dev.inmo.tgbotapi.types.toChatId
@@ -32,7 +33,7 @@ suspend fun main(args: Array<String>) {
}
) {
val me = getMe()
fun Chat.stickerSetName() = "s${id.chatId}_by_${me.username.usernameWithoutAt}"
fun Chat.stickerSetName() = StickerSetName("s${id.chatId}_by_${me.username ?.withoutAt}")
onCommand("start") {
reply(it) {
botCommand("delete") + " - to clear stickers"
@@ -55,16 +56,19 @@ suspend fun main(args: Array<String>) {
val newSticker = when (sticker) {
is CustomEmojiSticker -> InputSticker.WithKeywords.CustomEmoji(
downloadFileToTemp(sticker.fileId).asMultipartFile(),
sticker.stickerFormat,
listOf(sticker.emoji ?: "\uD83D\uDE0A"),
emptyList()
)
is MaskSticker -> InputSticker.Mask(
downloadFileToTemp(sticker.fileId).asMultipartFile(),
sticker.stickerFormat,
listOf(sticker.emoji ?: "\uD83D\uDE0A"),
sticker.maskPosition
)
is RegularSticker -> InputSticker.WithKeywords.Regular(
downloadFileToTemp(sticker.fileId).asMultipartFile(),
sticker.stickerFormat,
listOf(sticker.emoji ?: "\uD83D\uDE0A"),
emptyList()
)
@@ -79,12 +83,11 @@ suspend fun main(args: Array<String>) {
getStickerSet(stickerSetName).stickers.last()
)
}
}.onFailure { _ ->
}.onFailure { exception ->
createNewStickerSet(
it.chat.id.toChatId(),
stickerSetName,
stickerSetName.string,
"Sticker set by ${me.firstName}",
it.content.media.stickerFormat,
listOf(
newSticker
),

View File

@@ -1,3 +1,7 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
@@ -7,20 +11,26 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPoll
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatShared
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onUserShared
import dev.inmo.tgbotapi.extensions.utils.types.buttons.replyKeyboard
import dev.inmo.tgbotapi.extensions.utils.types.buttons.requestBotButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.requestChatButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.requestGroupButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.requestUserButton
import dev.inmo.tgbotapi.extensions.utils.types.buttons.requestUserOrBotButton
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onUsersShared
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.chat.PrivateChat
import dev.inmo.tgbotapi.types.keyboardButtonRequestUserLimit
import dev.inmo.tgbotapi.types.message.textsources.mention
import dev.inmo.tgbotapi.types.request.RequestId
import dev.inmo.tgbotapi.utils.row
suspend fun main(args: Array<String>) {
val botToken = args.first()
val isDebug = args.getOrNull(1) == "debug"
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
val bot = telegramBot(botToken)
@@ -30,127 +40,234 @@ suspend fun main(args: Array<String>) {
val requestIdUserPremium = RequestId(3)
val requestIdBot = RequestId(4)
val requestIdAnyChat = RequestId(5)
val requestIdChannel = RequestId(6)
val requestIdPublicChannel = RequestId(7)
val requestIdPrivateChannel = RequestId(8)
val requestIdChannelUserOwner = RequestId(9)
val requestIdUsersOrBots = RequestId(5)
val requestIdUsersNonPremium = RequestId(6)
val requestIdUsersAny = RequestId(7)
val requestIdUsersPremium = RequestId(8)
val requestIdBots = RequestId(9)
val requestIdGroup = RequestId(10)
val requestIdPublicGroup = RequestId(11)
val requestIdPrivateGroup = RequestId(12)
val requestIdGroupUserOwner = RequestId(13)
val requestIdAnyChat = RequestId(10)
val requestIdChannel = RequestId(11)
val requestIdPublicChannel = RequestId(12)
val requestIdPrivateChannel = RequestId(13)
val requestIdChannelUserOwner = RequestId(14)
val requestIdForum = RequestId(14)
val requestIdPublicForum = RequestId(15)
val requestIdPrivateForum = RequestId(16)
val requestIdForumUserOwner = RequestId(17)
val requestIdGroup = RequestId(15)
val requestIdPublicGroup = RequestId(16)
val requestIdPrivateGroup = RequestId(17)
val requestIdGroupUserOwner = RequestId(18)
val requestIdForum = RequestId(19)
val requestIdPublicForum = RequestId(20)
val requestIdPrivateForum = RequestId(21)
val requestIdForumUserOwner = RequestId(22)
val keyboard = replyKeyboard(
resizeKeyboard = true,
) {
row {
requestUserOrBotButton(
"\uD83D\uDC64/\uD83E\uDD16",
requestIdUserOrBot
"\uD83D\uDC64/\uD83E\uDD16 (1)",
requestIdUserOrBot,
requestName = true,
requestUsername = true,
requestPhoto = true
)
}
row {
requestUserButton(
"\uD83D\uDC64",
"\uD83D\uDC64 (1)",
requestIdUserNonPremium,
premiumUser = false
premiumUser = false,
requestName = true,
requestUsername = true,
requestPhoto = true
)
requestUserButton(
"\uD83D\uDC64",
"\uD83D\uDC64 (1)",
requestIdUserAny,
premiumUser = null
premiumUser = null,
requestName = true,
requestUsername = true,
requestPhoto = true
)
requestUserButton(
"\uD83D\uDC64",
"\uD83D\uDC64 (1)",
requestIdUserPremium,
premiumUser = true
premiumUser = true,
requestName = true,
requestUsername = true,
requestPhoto = true
)
requestBotButton(
"\uD83E\uDD16 (1)",
requestIdBot,
requestName = true,
requestUsername = true,
requestPhoto = true
)
}
row {
requestUsersOrBotsButton(
"\uD83D\uDC64/\uD83E\uDD16",
requestIdUsersOrBots,
maxCount = keyboardButtonRequestUserLimit.last,
requestName = true,
requestUsername = true,
requestPhoto = true
)
}
row {
requestUsersButton(
"\uD83D\uDC64",
requestIdUsersNonPremium,
premiumUser = false,
maxCount = keyboardButtonRequestUserLimit.last,
requestName = true,
requestUsername = true,
requestPhoto = true
)
requestUsersButton(
"\uD83D\uDC64",
requestIdUsersAny,
premiumUser = null,
maxCount = keyboardButtonRequestUserLimit.last,
requestName = true,
requestUsername = true,
requestPhoto = true
)
requestUsersButton(
"\uD83D\uDC64",
requestIdUsersPremium,
premiumUser = true,
maxCount = keyboardButtonRequestUserLimit.last,
requestName = true,
requestUsername = true,
requestPhoto = true
)
requestBotsButton(
"\uD83E\uDD16",
requestIdBot
requestIdBots,
maxCount = keyboardButtonRequestUserLimit.last,
requestName = true,
requestUsername = true,
requestPhoto = true
)
}
row {
requestChatButton(
"\uD83D\uDDE3/\uD83D\uDC65",
requestIdAnyChat
requestIdAnyChat,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
}
row {
requestChatButton(
"\uD83D\uDDE3",
requestIdChannel,
isChannel = true
isChannel = true,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
requestChatButton(
"\uD83D\uDDE3\uD83D\uDD17",
requestIdPublicChannel,
isChannel = true,
isPublic = true
isPublic = true,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
requestChatButton(
"\uD83D\uDDE3\uD83D\uDD17",
requestIdPrivateChannel,
isChannel = true,
isPublic = false
isPublic = false,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
requestChatButton(
"\uD83D\uDDE3\uD83D\uDC6E",
requestIdChannelUserOwner,
isChannel = true,
isOwnedBy = true
isOwnedBy = true,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
}
row {
requestGroupButton(
"👥",
requestIdGroup
requestIdGroup,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
requestGroupButton(
"👥\uD83D\uDD17",
requestIdPublicGroup,
isPublic = true
isPublic = true,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
requestGroupButton(
"👥❌\uD83D\uDD17",
requestIdPrivateGroup,
isPublic = false
isPublic = false,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
requestGroupButton(
"👥\uD83D\uDC6E",
requestIdGroupUserOwner,
isOwnedBy = true
isOwnedBy = true,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
}
row {
requestGroupButton(
"🏛",
requestIdForum,
isForum = true
isForum = true,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
requestGroupButton(
"🏛\uD83D\uDD17",
requestIdPublicForum,
isPublic = true,
isForum = true
isForum = true,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
requestGroupButton(
"🏛❌\uD83D\uDD17",
requestIdPrivateForum,
isPublic = false,
isForum = true
isForum = true,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
requestGroupButton(
"🏛\uD83D\uDC6E",
requestIdForumUserOwner,
isOwnedBy = true,
isForum = true
isForum = true,
requestTitle = true,
requestUsername = true,
requestPhoto = true
)
}
}
@@ -164,25 +281,26 @@ suspend fun main(args: Array<String>) {
)
}
onUserShared {
val userId = it.chatEvent.userId
val userInfo = runCatchingSafely { getChat(userId) }.getOrNull()
reply(
it,
) {
+"You have shared "
+mention(
when (it.chatEvent.requestId) {
requestIdUserOrBot -> "user or bot"
requestIdUserNonPremium -> "non premium user"
requestIdUserAny -> "any user"
requestIdUserPremium -> "premium user"
requestIdBot -> "bot"
else -> "somebody O.o"
},
userId
)
+" (user info: $userInfo; user id: $userId)"
onUsersShared {
it.chatEvent.userIds.forEach { userId ->
val userInfo = runCatchingSafely { getChat(userId) }.getOrNull()
reply(
it,
) {
+"You have shared "
+mention(
when (it.chatEvent.requestId) {
requestIdUserOrBot -> "user or bot"
requestIdUserNonPremium -> "non premium user"
requestIdUserAny -> "any user"
requestIdUserPremium -> "premium user"
requestIdBot -> "bot"
else -> "somebody O.o"
},
userId
)
+" (user info: $userInfo; user id: $userId)"
}
}
}

View File

@@ -1,6 +1,7 @@
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.tgbotapi.types.webAppQueryIdField
import dev.inmo.tgbotapi.webapps.*
import dev.inmo.tgbotapi.webapps.cloud.*
import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackStyle
import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackType
import dev.inmo.tgbotapi.webapps.popup.*
@@ -14,8 +15,11 @@ import kotlinx.browser.window
import kotlinx.coroutines.*
import kotlinx.dom.appendElement
import kotlinx.dom.appendText
import kotlinx.dom.clear
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) {
appendText(text)
@@ -67,9 +71,12 @@ fun main() {
}
}
})
appendText("Example button")
appendText("Answer in chat button")
} ?: 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 ?.appendText("Alerts:")
@@ -110,6 +117,32 @@ fun main() {
appendText("Alert")
} ?: 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") {
addEventListener("click", {
webApp.showConfirm(
@@ -142,6 +175,91 @@ fun main() {
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 {
onThemeChanged {
document.body ?.log("Theme changed: ${webApp.themeParams}")
@@ -171,8 +289,18 @@ fun main() {
onSettingsButtonClicked {
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()
document.body ?.appendElement("input", {
(this as HTMLInputElement).value = window.location.href
})
cloudStorageContentDiv.updateCloudStorageContent()
}.onFailure {
window.alert(it.stackTraceToString())
}

View File

@@ -1,16 +1,25 @@
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.LogLevel
import dev.inmo.kslog.common.defaultMessageFormatter
import dev.inmo.kslog.common.setDefaultKSLog
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.ktor.server.createKtorServer
import dev.inmo.tgbotapi.extensions.api.answers.answer
import dev.inmo.tgbotapi.extensions.api.answers.answerInlineQuery
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.api.telegramBot
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.extensions.utils.formatting.makeTelegramStartattach
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.requests.answers.InlineQueryResultsButton
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent
import dev.inmo.tgbotapi.types.InlineQueryId
import dev.inmo.tgbotapi.types.LinkPreviewOptions
import dev.inmo.tgbotapi.types.webAppQueryIdField
import dev.inmo.tgbotapi.types.webapps.WebAppInfo
import dev.inmo.tgbotapi.utils.*
@@ -40,6 +49,16 @@ suspend fun main(vararg args: String) {
args.first(),
testServer = args.any { it == "testServer" }
)
val isDebug = args.any { it == "debug" }
if (isDebug) {
setDefaultKSLog(
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
println(defaultMessageFormatter(level, tag, message, throwable))
}
)
}
val bot = telegramBot(telegramBotAPIUrlsKeeper)
createKtorServer(
"0.0.0.0",
@@ -49,14 +68,19 @@ suspend fun main(vararg args: String) {
}
) {
routing {
staticFiles("", File("WebApp/build/distributions")) {
default("WebApp/build/distributions/index.html")
val baseJsFolder = File("WebApp/build/dist/js/")
baseJsFolder.list() ?.forEach {
if (it == "productionExecutable" || it == "developmentExecutable") {
staticFiles("", File(baseJsFolder, it)) {
default("WebApp/build/dist/js/$it/index.html")
}
}
}
post("inline") {
val requestBody = call.receiveText()
val queryId = call.parameters[webAppQueryIdField] ?: error("$webAppQueryIdField should be presented")
val queryId = call.parameters[webAppQueryIdField] ?.let(::InlineQueryId) ?: error("$webAppQueryIdField should be presented")
bot.answer(queryId, InlineQueryResultArticle(queryId, "Result", InputTextMessageContent(requestBody)))
bot.answerInlineQuery(queryId, listOf(InlineQueryResultArticle(queryId, "Result", InputTextMessageContent(requestBody))))
call.respond(HttpStatusCode.OK)
}
post("check") {
@@ -73,6 +97,7 @@ suspend fun main(vararg args: String) {
bot.buildBehaviourWithLongPolling(
defaultExceptionsHandler = { it.printStackTrace() }
) {
val me = getMe()
onCommand("reply_markup") {
reply(
it,
@@ -93,8 +118,35 @@ suspend fun main(vararg args: String) {
row {
webAppButton("Open WebApp", WebAppInfo(args[1]))
}
}
},
linkPreviewOptions = LinkPreviewOptions.Small(
args[1],
showAboveText = false
)
)
}
onCommand("attachment_menu") {
reply(
it,
"Button",
replyMarkup = inlineKeyboard {
row {
webAppButton("Open WebApp", WebAppInfo(args[1]))
}
},
linkPreviewOptions = LinkPreviewOptions.Large(
args[1],
showAboveText = true
)
)
}
onBaseInlineQuery {
answerInlineQuery(
it,
button = InlineQueryResultsButton.invoke(
"Open webApp",
WebAppInfo(args[1])
)
)
}
onUnhandledCommand {
@@ -106,6 +158,9 @@ suspend fun main(vararg args: String) {
}
)
}
onWriteAccessAllowed(initialFilter = { it.chatEvent.webAppName != null }) {
send(it.chat, "Thanks for adding ${it.chatEvent.webAppName} to the attachment menu")
}
setMyCommands(
BotCommand("reply_markup", "Use to get reply markup keyboard with web app trigger"),
BotCommand("inline", "Use to get inline keyboard with web app trigger"),

View File

@@ -10,6 +10,9 @@ buildscript {
}
allprojects {
ext {
nativePartTemplate = "${rootProject.projectDir.absolutePath}/native_template.gradle"
}
repositories {
mavenLocal()
mavenCentral()
@@ -23,6 +26,6 @@ allprojects {
}
}
maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" }
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
}
}

View File

@@ -1,11 +1,11 @@
kotlin.code.style=official
org.gradle.parallel=true
# Due to parallel compilation project require next amount of memory on full build
org.gradle.jvmargs=-Xmx2g
org.gradle.jvmargs=-Xmx2344m
kotlin_version=1.8.20
telegram_bot_api_version=7.0.2
micro_utils_version=0.17.8
serialization_version=1.5.0
ktor_version=2.3.0
kotlin_version=1.9.23
telegram_bot_api_version=13.0.0
micro_utils_version=0.20.45
serialization_version=1.6.3
ktor_version=2.3.10

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-7.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-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 ":PollsBot"
include ":GetMeBot"
include ":DeepLinksBot"
@@ -37,3 +39,15 @@ include ":RightsChangerBot"
include ":LiveLocationsBot"
include ":StickerSetHandler"
include ":InlineQueriesBot"
include ":ReactionsInfoBot"
include ":LinkPreviewsBot"
include ":BoostsInfoBot"
include ":BusinessConnectionsBot"
include ":CustomBot"