mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI-examples.git
synced 2026-03-15 15:32:28 +00:00
Compare commits
341 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 828ab43317 | |||
| 1a4533221c | |||
| e304a5ecab | |||
| 600ac8ebbf | |||
| 07403546f4 | |||
| 9d4b7b5a50 | |||
| e1f5e40143 | |||
| 186a0f7abf | |||
| e660f06edf | |||
| fb6ed8b7ae | |||
| 9981e82a10 | |||
| bef86042f9 | |||
| d1791b3058 | |||
| 0432611f85 | |||
| 6b27aa01fb | |||
| 523e428bcb | |||
| 0e8714cf2b | |||
| ea74f884bf | |||
| 69cbc257b5 | |||
| d3e6014e06 | |||
| 1d260f82e9 | |||
| 5d0b48c4b7 | |||
| 34be1a25b2 | |||
| 990614e257 | |||
| 0d1bcf05fd | |||
| 7d5cb58a3f | |||
| 261df14412 | |||
| 81ba5831c3 | |||
| 0b9c715e25 | |||
| 0216919145 | |||
| e2d56a4d80 | |||
| 70aca52960 | |||
| 6c0d961339 | |||
|
|
a3cdf693f2 | ||
|
|
e378c6630c | ||
| 707ad9a160 | |||
| 68e9830a8f | |||
| 55ebdeadbc | |||
|
|
d4f3d4bc68 | ||
| b3d06c9773 | |||
| e6e3eabf97 | |||
| 47efedf311 | |||
| 8423b1377b | |||
| d0029603ce | |||
| 8d8fa74779 | |||
| 459a70c47b | |||
| 88102f3afa | |||
| a621058fdd | |||
| 56e072aabe | |||
| 73f05bbcd7 | |||
| f053013360 | |||
| bc39279c6c | |||
| ad8fa92e87 | |||
| b0554adb7f | |||
| ad90180def | |||
| 69eda92bc7 | |||
| aee070c6c6 | |||
| 36163d5619 | |||
| 92d1c7a402 | |||
| 7ce784d0a2 | |||
| d203d48391 | |||
| 9352bb0090 | |||
| b1bb11d826 | |||
| 349517462e | |||
| 1708cad654 | |||
| f87a9c5c66 | |||
| a7b54e4b63 | |||
|
|
436213492d | ||
|
|
0c2110a71d | ||
| 949fa1a429 | |||
| 97cdd5a95f | |||
|
|
0cb116acef | ||
|
|
a0332c4efd | ||
| f6bce640da | |||
| d22a99da19 | |||
| 467a3a1710 | |||
| 5810bc5930 | |||
| 2cf2c4264e | |||
|
|
3d5c2ee4b8 | ||
| 360c6b4364 | |||
|
|
bb6a0a125a | ||
| 6a61da2eb7 | |||
| 8cd75673f5 | |||
| d294d0ef59 | |||
| 2ab8ccbfdf | |||
| d12e9aa032 | |||
| 76f151586e | |||
| 1c437690e4 | |||
| 222c7ec8ee | |||
| 59778a3add | |||
| 3e20835bc6 | |||
| c3ad2d4319 | |||
| 59fca968d7 | |||
| f03ba5f177 | |||
|
|
855d2c1296 | ||
| 280f5abce0 | |||
|
|
ed81e76ef8 | ||
| 541b76b292 | |||
| 5b580b5a15 | |||
| 86790ee414 | |||
| 0bbe430374 | |||
| b7d53a7410 | |||
| 73064db226 | |||
| a50eda366d | |||
| e34f0ec9d8 | |||
| c2237f7e87 | |||
| 0bbc6a9555 | |||
| d4d8508abf | |||
| 9acb64fda9 | |||
|
|
760ae36207 | ||
| 5c6b1b7171 | |||
| 6e06357541 | |||
| 38f46dfa3b | |||
|
|
e7f7ef16ac | ||
|
|
d100a5a336 | ||
| 5f0f2ce76d | |||
| 14235e7bd4 | |||
| 6eafd89542 | |||
| ed2922045c | |||
| 21ec50c773 | |||
| ab362e8c3b | |||
| 346755b41c | |||
|
|
a601674d71 | ||
|
|
cea610a0f8 | ||
| b6c92f754f | |||
| 023b810d07 | |||
| 0ec543d5c5 | |||
| 777604e5a0 | |||
| 999c33b2f5 | |||
| ca0427bfdd | |||
| a62a14a599 | |||
|
|
3efd3463a3 | ||
| 590f9ec6d8 | |||
| acdbd4d2ea | |||
| d2d913fca8 | |||
| 75726cac89 | |||
| 71b64689d0 | |||
| 5ba2fc5bab | |||
| 51a5bfb81a | |||
| 35e330c016 | |||
| 90d447fbcf | |||
| 2c5da5da9f | |||
| f79e43364a | |||
| f5a9efa3e7 | |||
| b70b6d1e2b | |||
|
|
3f36a04ac2 | ||
| 62b830d31b | |||
| 06459ebc0a | |||
| 673424b234 | |||
|
|
5d156f6708 | ||
|
|
529f4156fd | ||
|
|
7842ac0dac | ||
|
|
358f2d27d3 | ||
|
|
7964dc4eea | ||
| 9fb6570d21 | |||
| f750589fd3 | |||
| 481533bee2 | |||
| a1a4338869 | |||
| d8e5825ccf | |||
| 3a4c0c4226 | |||
| b85d7a697c | |||
| ad57e4142c | |||
| c7068182e3 | |||
| a5740e6315 | |||
| 3a35995bc7 | |||
| 41fc5a9a4c | |||
| 79700f24e5 | |||
| 73c1af15b3 | |||
| aa9ca976f0 | |||
| 238533a350 | |||
| 6f2a8bb0be | |||
| 99232b53d7 | |||
| 30358f7d2f | |||
| 11a97c520a | |||
| d6a6ad8d37 | |||
| c04a367375 | |||
| b660bf5f42 | |||
| 57dd2380cd | |||
| fbb41c7714 | |||
| 9170d30b2f | |||
| 2f3fd2e53b | |||
| 88697fb5a6 | |||
| 578d00cac6 | |||
| 13ecb3f0df | |||
| a008d861da | |||
| 6f3766dff6 | |||
| fda366d820 | |||
| 578887ac63 | |||
| 6a04b3980c | |||
| 984ffb8bae | |||
| 2bcec6487d | |||
| a5e3bfc3fe | |||
| 941afd0902 | |||
| 94c014b308 | |||
| 538cc9d44f | |||
| cb29726487 | |||
| 262ef26239 | |||
| 41efe5e141 | |||
| 05e289975a | |||
| 753d686fab | |||
| 281243c7e5 | |||
| 3609ae6bc2 | |||
| 4f128f3421 | |||
| ada6cd61d7 | |||
|
|
051d647004 | ||
|
|
d21606860a | ||
|
|
93c0fcb5bd | ||
| b1b8d0eb75 | |||
| 2ac23f70ab | |||
| e155373655 | |||
| d842dab5b8 | |||
| 7186d5e624 | |||
|
|
8fefb17599 | ||
| bcf4ae5888 | |||
| 7090db148e | |||
| 7d786f0e06 | |||
| c88f84011f | |||
| b8cc8854ea | |||
| 13470999e8 | |||
| af04a854ef | |||
| 44e86c9349 | |||
| 65c32d97d5 | |||
| 9b7605591e | |||
| 89d5a4f911 | |||
| 53cf212175 | |||
| 28301a92c9 | |||
| f814b11777 | |||
| 9773a74890 | |||
| a81cfaaba9 | |||
|
|
ee599611f3 | ||
| d3d6cd16c6 | |||
| 02c3d3da1a | |||
| 0ad8e61c0c | |||
| 8f80b7e066 | |||
| 48d1077ce4 | |||
| 6922a6d667 | |||
| 676ce0df80 | |||
| d97c2a0562 | |||
| 35e0cb4a46 | |||
| 30f5513f54 | |||
| fff8edde5f | |||
| e28a795796 | |||
| d289c2101d | |||
| 2ce47074d8 | |||
| 281f0840eb | |||
| 34ed962104 | |||
| aa3337bf3a | |||
| 31d29712be | |||
| 88b348376f | |||
| 0d9e295baa | |||
| ea08bac6e8 | |||
| a85fdc227e | |||
| 43482ee94e | |||
| 4addb6c755 | |||
| 7d958b6edb | |||
| 323c21f415 | |||
| 6350581739 | |||
| ea1d40fd05 | |||
| 8cee63a0fb | |||
| d42ef2c6cb | |||
| c7fe90ddd7 | |||
| acb382d3f7 | |||
| 0cfe60fd77 | |||
| 6719b9e17c | |||
| 8d33dc0ab2 | |||
| 3e2ccf9cf1 | |||
| eccbe71e68 | |||
| 24c74f3b1a | |||
| d7a7e7153e | |||
| 0b37acb7a9 | |||
| 3925ef9423 | |||
| c6019b1862 | |||
| 7b996fe1de | |||
| 4e3c186952 | |||
| 8fdf715419 | |||
| fca8704cec | |||
| bf499ee780 | |||
| 9b10749411 | |||
| d3cb8a32ef | |||
| 0f0ad5a1af | |||
| c3bc55a15c | |||
| 253328f49a | |||
| 8ef50537ae | |||
| 7e7bbfaa93 | |||
| f152ede9b5 | |||
| fcedbf30da | |||
|
|
fd030a92e3 | ||
| d54abf0b32 | |||
|
|
877a20188f | ||
| d0151ff048 | |||
| f8f517cfbb | |||
|
|
b3cbbac917 | ||
| 3e85bb4b22 | |||
| 4e0fb1c137 | |||
| d8c90ef377 | |||
| cb84fd0884 | |||
| 4379862c78 | |||
|
|
ac1d812db0 | ||
| f52590868c | |||
| 51c300c734 | |||
| f6082cff30 | |||
| a40c16fe05 | |||
| a7fe62f4af | |||
| b9c745a21e | |||
| 1c2b068a94 | |||
| 51c2cb1b0e | |||
| cfd4e2fcd5 | |||
|
|
76ceeac757 | ||
| 340de11b0a | |||
| 68a59ca5c8 | |||
|
|
cdb8581318 | ||
|
|
8b3a2ac1ed | ||
| a5b925fc59 | |||
| 9ec9f7a68c | |||
| 0f2829945f | |||
| 4eb80ea53c | |||
| 17cff21847 | |||
| 431069d190 | |||
|
|
fdbac78603 | ||
| da73acd379 | |||
|
|
6e3880f152 | ||
| 1ede6e58e6 | |||
| 0e46f176fb | |||
|
|
2bd449b8b8 | ||
| 82f9da0529 | |||
| 78b7d468f2 | |||
|
|
08059f8174 | ||
|
|
16766046d7 | ||
| 91ea20a269 | |||
| 11e280d177 | |||
| a8d4a307ef | |||
| 2bd2328a38 | |||
| 139de35db9 | |||
|
|
5dd22e1da2 | ||
| 4186ab8270 | |||
| 5aa69d7990 | |||
|
|
df952c69b2 | ||
| 9a03a02bac | |||
| 0e7c050e9e | |||
|
|
bdec902b58 | ||
|
|
cc3c87590d |
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -8,9 +8,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y libcurl4-openssl-dev
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
java-version: 17
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
run: ./gradlew build --no-daemon
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.idea
|
||||
.kotlin
|
||||
out/*
|
||||
*.iml
|
||||
target
|
||||
@@ -10,3 +11,6 @@ build/
|
||||
out/
|
||||
|
||||
kotlin-js-store/
|
||||
|
||||
local.*
|
||||
local.*/
|
||||
|
||||
2
.template/bot/.env
Normal file
2
.template/bot/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
title=$prompt
|
||||
subtitle=Subtitle of {{$title}}
|
||||
9
.template/bot/{{$title}}/README.md
Normal file
9
.template/bot/{{$title}}/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# {{$title}}
|
||||
|
||||
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN"
|
||||
```
|
||||
22
.template/bot/{{$title}}/build.gradle
Normal file
22
.template/bot/{{$title}}/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName="{{$title}}Kt"
|
||||
{{$subtitle}}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
34
.template/bot/{{$title}}/src/main/kotlin/{{$title}}.kt
Normal file
34
.template/bot/{{$title}}/src/main/kotlin/{{$title}}.kt
Normal file
@@ -0,0 +1,34 @@
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
/**
|
||||
* This place can be the playground for your code.
|
||||
*/
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
|
||||
val isDebug = args.any { it == "debug" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
|
||||
// start here!!
|
||||
val me = getMe()
|
||||
println(me)
|
||||
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
|
||||
}.second.join()
|
||||
}
|
||||
208
.template/module_generator.main.kts
Executable file
208
.template/module_generator.main.kts
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env kotlin
|
||||
/**
|
||||
* Generates files and folders as they have been put in the folder. Envs uses common syntax, but
|
||||
* values may contains {{${'$'}sampleVariable}} parts, where {{${'$'}sampleVariable}} will be replaced with variable value.
|
||||
* Example:
|
||||
*
|
||||
* .env:
|
||||
* sampleVariable=${'$'}prompt # require request from command line
|
||||
* sampleVariable2=just some value
|
||||
* sampleVariable3=${'$'}{sampleVariable}.${'$'}{sampleVariable2}
|
||||
*
|
||||
* Result variables:
|
||||
* sampleVariable=your input in console # lets imagine you typed it
|
||||
* sampleVariable2=just some value
|
||||
* sampleVariable3=your input in console.just some value
|
||||
*
|
||||
* To use these variables in template, you will need to write {{${'$'}sampleVariable}}.
|
||||
* You may use it in text of files as well as in files/folders names.
|
||||
*
|
||||
* Usage: kotlin generator.kts [args] folders...
|
||||
* Args:
|
||||
* -e, --env: Path to file with args for generation; Use "${'$'}prompt" as values to read variable value from console
|
||||
* -o, --outputFolder: Folder where templates should be used. Folder of calling by default
|
||||
* folders: Folders-templates
|
||||
*/
|
||||
import java.io.File
|
||||
|
||||
val console = System.console()
|
||||
|
||||
fun String.replaceWithVariables(envs: Map<String, String>): String {
|
||||
var currentString = this
|
||||
var changed = false
|
||||
|
||||
do {
|
||||
changed = false
|
||||
envs.forEach { (k, v) ->
|
||||
val previousString = currentString
|
||||
currentString = currentString.replace("{{$${k}}}", v)
|
||||
changed = changed || currentString != previousString
|
||||
}
|
||||
} while (changed)
|
||||
|
||||
return currentString
|
||||
}
|
||||
|
||||
fun requestVariable(variableName: String, defaultValue: String?): String {
|
||||
console.printf("Enter value for variable $variableName${defaultValue ?.let { " [$it]" } ?: ""}: ")
|
||||
return console.readLine().ifEmpty { defaultValue } ?: ""
|
||||
}
|
||||
|
||||
fun readEnvs(content: String, presets: Map<String, String>): Map<String, String> {
|
||||
val initialEnvs = mutableMapOf<String, String>()
|
||||
content.split("\n").forEach {
|
||||
val withoutComment = it.replace(Regex("\\#.*"), "")
|
||||
|
||||
runCatching {
|
||||
val (key, value) = withoutComment.split("=")
|
||||
val existsValue = presets[key]
|
||||
if (value == "\$prompt") {
|
||||
initialEnvs[key] = requestVariable(key, existsValue)
|
||||
} else {
|
||||
initialEnvs[key] = requestVariable(key, value.replaceWithVariables(initialEnvs))
|
||||
}
|
||||
}
|
||||
}
|
||||
var i = 0
|
||||
val readEnvs = initialEnvs.toMutableMap()
|
||||
while (i < readEnvs.size) {
|
||||
val key = readEnvs.keys.elementAt(i)
|
||||
val currentValue = readEnvs.getValue(key)
|
||||
val withReplaced = currentValue.replaceWithVariables(readEnvs)
|
||||
var changed = false
|
||||
if (withReplaced != currentValue) {
|
||||
i = 0
|
||||
readEnvs[key] = withReplaced
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return presets + readEnvs
|
||||
}
|
||||
|
||||
var envFile: File? = null
|
||||
var outputFolder: File = File("./") // current folder by default
|
||||
val templatesFolders = mutableListOf<File>()
|
||||
var extensions: List<String>? = null
|
||||
|
||||
fun readParameters() {
|
||||
var i = 0
|
||||
while (i < args.size) {
|
||||
val arg = args[i]
|
||||
when (arg) {
|
||||
"--env",
|
||||
"-e" -> {
|
||||
i++
|
||||
envFile = File(args[i])
|
||||
}
|
||||
"--extensions",
|
||||
"-ex" -> {
|
||||
i++
|
||||
extensions = args[i].split(",")
|
||||
}
|
||||
"--outputFolder",
|
||||
"-o" -> {
|
||||
i++
|
||||
outputFolder = File(args[i])
|
||||
}
|
||||
"--help",
|
||||
"-h" -> {
|
||||
println("""
|
||||
Generates files and folders as the have been put in the folder. Envs uses common syntax, but
|
||||
values may contains {{${'$'}sampleVariable}} parts, where {{${'$'}sampleVariable}} will be replaced with variable value.
|
||||
Example:
|
||||
|
||||
.env:
|
||||
sampleVariable=${'$'}prompt # require request from command line
|
||||
sampleVariable2=just some value
|
||||
sampleVariable3=${'$'}{sampleVariable}.${'$'}{sampleVariable2}
|
||||
|
||||
Result variables:
|
||||
sampleVariable=your input in console # lets imagine you typed it
|
||||
sampleVariable2=just some value
|
||||
sampleVariable3=your input in console.just some value
|
||||
|
||||
To use these variables in template, you will need to write {{${'$'}sampleVariable}}.
|
||||
You may use it in text of files as well as in files/folders names.
|
||||
|
||||
Usage: kotlin generator.kts [args] folders...
|
||||
Args:
|
||||
-e, --env: Path to file with args for generation; Use "${'$'}prompt" as values to read variable value from console
|
||||
-o, --outputFolder: Folder where templates should be used. Folder of calling by default
|
||||
folders: Folders-templates
|
||||
""".trimIndent())
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
else -> {
|
||||
val potentialFile = File(arg)
|
||||
println("Potential file/folder as template: ${potentialFile.absolutePath}")
|
||||
runCatching {
|
||||
if (potentialFile.exists()) {
|
||||
println("Adding file/folder as template: ${potentialFile.absolutePath}")
|
||||
templatesFolders.add(potentialFile)
|
||||
}
|
||||
}.onFailure { e ->
|
||||
println("Unable to use folder $arg as template folder")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
readParameters()
|
||||
|
||||
val envs: MutableMap<String, String> = envFile ?.let { readEnvs(it.readText(), emptyMap()) } ?.toMutableMap() ?: mutableMapOf()
|
||||
|
||||
println(
|
||||
"""
|
||||
Result environments:
|
||||
${envs.toList().joinToString("\n ") { (k, v) -> "$k=$v" }}
|
||||
Result extensions:
|
||||
${extensions ?.joinToString()}
|
||||
Input folders:
|
||||
${templatesFolders.joinToString("\n ") { it.absolutePath }}
|
||||
Output folder:
|
||||
${outputFolder.absolutePath}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
fun File.handleTemplate(targetFolder: File, envs: Map<String, String>) {
|
||||
println("Handling $absolutePath")
|
||||
val localEnvs = File(absolutePath, ".env").takeIf { it.exists() } ?.let {
|
||||
println("Reading .env in ${absolutePath}")
|
||||
readEnvs(it.readText(), envs)
|
||||
} ?: envs
|
||||
println(
|
||||
"""
|
||||
Local environments:
|
||||
${localEnvs.toList().joinToString("\n ") { (k, v) -> "$k=$v" }}
|
||||
""".trimIndent()
|
||||
)
|
||||
val newName = name.replaceWithVariables(localEnvs)
|
||||
println("New name $newName")
|
||||
when {
|
||||
!exists() -> return
|
||||
isFile -> {
|
||||
val content = useLines {
|
||||
it.map { it.replaceWithVariables(localEnvs) }.toList()
|
||||
}.joinToString("\n")
|
||||
val targetFile = File(targetFolder, newName)
|
||||
targetFile.writeText(content)
|
||||
println("Target file: ${targetFile.absolutePath}")
|
||||
}
|
||||
else -> {
|
||||
val folder = File(targetFolder, newName)
|
||||
println("Target folder: ${folder.absolutePath}")
|
||||
folder.mkdirs()
|
||||
listFiles() ?.forEach { fileOrFolder ->
|
||||
fileOrFolder.handleTemplate(folder, localEnvs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templatesFolders.forEach { folderOrFile ->
|
||||
folderOrFile.handleTemplate(outputFolder, envs)
|
||||
}
|
||||
9
BoostsInfoBot/README.md
Normal file
9
BoostsInfoBot/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# UserChatShared
|
||||
|
||||
Showing info about boosts
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN"
|
||||
```
|
||||
21
BoostsInfoBot/build.gradle
Normal file
21
BoostsInfoBot/build.gradle
Normal 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"
|
||||
}
|
||||
72
BoostsInfoBot/src/main/kotlin/BoostsInfo.kt
Normal file
72
BoostsInfoBot/src/main/kotlin/BoostsInfo.kt
Normal file
@@ -0,0 +1,72 @@
|
||||
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.reply
|
||||
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.flatReplyKeyboard
|
||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.requestChannelButton
|
||||
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 boostsInfoContrainer = runCatching {
|
||||
getUserChatBoosts(it.chatEvent.chatId, it.chat.id)
|
||||
}.getOrNull()
|
||||
|
||||
reply(it) {
|
||||
when {
|
||||
boostsInfoContrainer == null -> +"Unable to take info about boosts in shared chat"
|
||||
boostsInfoContrainer.boosts.isEmpty() -> +"There is no any boosts in passed chat"
|
||||
else -> {
|
||||
boostsInfoContrainer.boosts.forEach {
|
||||
regular("Boost added: ${DateFormat.FORMAT1.format(it.addDate.asDate)}; Boost expire: ${DateFormat.FORMAT1.format(it.expirationDate.asDate)}; Unformatted: $it") + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.join()
|
||||
}
|
||||
9
BusinessConnectionsBot/README.md
Normal file
9
BusinessConnectionsBot/README.md
Normal 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"
|
||||
```
|
||||
21
BusinessConnectionsBot/build.gradle
Normal file
21
BusinessConnectionsBot/build.gradle
Normal 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"
|
||||
}
|
||||
487
BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt
Normal file
487
BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt
Normal file
@@ -0,0 +1,487 @@
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.LogLevel
|
||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||
import dev.inmo.kslog.common.setDefaultKSLog
|
||||
import dev.inmo.micro_utils.common.Percentage
|
||||
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountStarBalance
|
||||
import dev.inmo.tgbotapi.extensions.api.business.deleteBusinessMessages
|
||||
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGifts
|
||||
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow
|
||||
import dev.inmo.tgbotapi.extensions.api.business.readBusinessMessage
|
||||
import dev.inmo.tgbotapi.extensions.api.business.removeBusinessAccountProfilePhoto
|
||||
import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountBio
|
||||
import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountName
|
||||
import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountProfilePhoto
|
||||
import dev.inmo.tgbotapi.extensions.api.business.setBusinessAccountUsername
|
||||
import dev.inmo.tgbotapi.extensions.api.business.transferBusinessAccountStars
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.modify.pinChatMessage
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.modify.unpinChatMessage
|
||||
import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp
|
||||
import dev.inmo.tgbotapi.extensions.api.get.getBusinessConnection
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
import dev.inmo.tgbotapi.extensions.api.stories.deleteStory
|
||||
import dev.inmo.tgbotapi.extensions.api.stories.postStory
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
||||
import dev.inmo.tgbotapi.extensions.utils.commonMessageOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.extendedPrivateChatOrThrow
|
||||
import dev.inmo.tgbotapi.extensions.utils.ifAccessibleMessage
|
||||
import dev.inmo.tgbotapi.extensions.utils.ifBusinessContentMessage
|
||||
import dev.inmo.tgbotapi.extensions.utils.textContentOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
|
||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard
|
||||
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.flushAccumulatedUpdates
|
||||
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
||||
import dev.inmo.tgbotapi.requests.abstracts.multipartFile
|
||||
import dev.inmo.tgbotapi.requests.business_connection.InputProfilePhoto
|
||||
import dev.inmo.tgbotapi.requests.stories.PostStory
|
||||
import dev.inmo.tgbotapi.types.ChatId
|
||||
import dev.inmo.tgbotapi.types.MessageId
|
||||
import dev.inmo.tgbotapi.types.RawChatId
|
||||
import dev.inmo.tgbotapi.types.business_connection.BusinessConnectionId
|
||||
import dev.inmo.tgbotapi.types.chat.PrivateChat
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||
import dev.inmo.tgbotapi.types.message.content.PhotoContent
|
||||
import dev.inmo.tgbotapi.types.message.content.StoryContent
|
||||
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||
import dev.inmo.tgbotapi.types.message.content.VideoContent
|
||||
import dev.inmo.tgbotapi.types.message.content.VisualMediaGroupPartContent
|
||||
import dev.inmo.tgbotapi.types.stories.InputStoryContent
|
||||
import dev.inmo.tgbotapi.types.stories.StoryArea
|
||||
import dev.inmo.tgbotapi.types.stories.StoryAreaPosition
|
||||
import dev.inmo.tgbotapi.types.stories.StoryAreaType
|
||||
import dev.inmo.tgbotapi.utils.botCommand
|
||||
import dev.inmo.tgbotapi.utils.code
|
||||
import dev.inmo.tgbotapi.utils.extensions.splitForText
|
||||
import dev.inmo.tgbotapi.utils.row
|
||||
import korlibs.time.seconds
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
suspend fun main(args: Array<String>) {
|
||||
val botToken = args.first()
|
||||
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 chatsBusinessConnections = mutableMapOf<ChatId, BusinessConnectionId>()
|
||||
val businessConnectionsChatsMutex = Mutex()
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
|
||||
val me = getMe()
|
||||
println(me)
|
||||
flushAccumulatedUpdates()
|
||||
|
||||
onBusinessConnectionEnabled {
|
||||
businessConnectionsChatsMutex.withLock {
|
||||
businessConnectionsChats[it.id] = it.userChatId
|
||||
chatsBusinessConnections[it.userChatId] = it.id
|
||||
}
|
||||
send(it.userChatId, "Business connection ${it.businessConnectionId.string} has been enabled")
|
||||
}
|
||||
onBusinessConnectionDisabled {
|
||||
businessConnectionsChatsMutex.withLock {
|
||||
businessConnectionsChats.remove(it.id)
|
||||
chatsBusinessConnections.remove(it.userChatId)
|
||||
}
|
||||
send(it.userChatId, "Business connection ${it.businessConnectionId.string} has been disabled")
|
||||
}
|
||||
|
||||
onContentMessage {
|
||||
it.ifBusinessContentMessage { businessContentMessage ->
|
||||
if (businessContentMessage.content.textContentOrNull() ?.text ?.startsWith("/pin") == true) {
|
||||
businessContentMessage.replyTo ?.ifAccessibleMessage {
|
||||
pinChatMessage(it)
|
||||
return@ifBusinessContentMessage
|
||||
}
|
||||
}
|
||||
if (businessContentMessage.content.textContentOrNull() ?.text ?.startsWith("/unpin") == true) {
|
||||
businessContentMessage.replyTo ?.ifAccessibleMessage {
|
||||
unpinChatMessage(it)
|
||||
return@ifBusinessContentMessage
|
||||
}
|
||||
}
|
||||
val sent = execute(it.content.createResend(businessContentMessage.from.id))
|
||||
if (businessContentMessage.sentByBusinessConnectionOwner) {
|
||||
reply(sent, "You have sent this message to the ${businessContentMessage.businessConnectionId.string} related chat")
|
||||
} else {
|
||||
reply(
|
||||
to = sent,
|
||||
text = "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat",
|
||||
)
|
||||
send(
|
||||
chatId = businessConnectionsChats[it.businessConnectionId] ?: return@ifBusinessContentMessage,
|
||||
text = "User have sent this message to you in the ${businessContentMessage.businessConnectionId.string} related chat",
|
||||
replyMarkup = inlineKeyboard {
|
||||
row {
|
||||
dataButton("Read message", "read ${it.chat.id.chatId.long} ${it.messageId.long}")
|
||||
dataButton("Delete message", "delete ${it.chat.id.chatId.long} ${it.messageId.long}")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
onEditedContentMessage {
|
||||
it.ifBusinessContentMessage { businessContentMessage ->
|
||||
val sent = execute(businessContentMessage.content.createResend(businessContentMessage.from.id))
|
||||
if (businessContentMessage.sentByBusinessConnectionOwner) {
|
||||
reply(sent, "You have edited this message in the ${businessContentMessage.businessConnectionId.string} related chat")
|
||||
} else {
|
||||
reply(sent, "User have edited this message to you in the ${businessContentMessage.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}")
|
||||
}
|
||||
onCommand("get_business_account_info", initialFilter = { it.chat is PrivateChat }) {
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id]
|
||||
val businessConnectionInfo = businessConnectionId ?.let { getBusinessConnection(it) }
|
||||
reply(it) {
|
||||
if (businessConnectionInfo == null) {
|
||||
+"There is no business connection for current chat"
|
||||
} else {
|
||||
+(Json { prettyPrint = true; encodeDefaults = true }.encodeToString(businessConnectionInfo))
|
||||
}
|
||||
}
|
||||
}
|
||||
onMessageDataCallbackQuery(Regex("read \\d+ \\d+")) {
|
||||
val (_, chatIdString, messageIdString) = it.data.split(" ")
|
||||
val chatId = chatIdString.toLongOrNull() ?.let(::RawChatId) ?.let(::ChatId) ?: return@onMessageDataCallbackQuery
|
||||
val messageId = messageIdString.toLongOrNull() ?.let(::MessageId) ?: return@onMessageDataCallbackQuery
|
||||
val businessConnectionId = chatsBusinessConnections[it.message.chat.id]
|
||||
|
||||
val readResponse = businessConnectionId ?.let { readBusinessMessage(it, chatId, messageId) }
|
||||
answer(
|
||||
it,
|
||||
if (readResponse == null) {
|
||||
"There is no business connection for current chat"
|
||||
} else {
|
||||
"Message has been read"
|
||||
}
|
||||
)
|
||||
}
|
||||
onMessageDataCallbackQuery(Regex("delete \\d+ \\d+")) {
|
||||
val (_, chatIdString, messageIdString) = it.data.split(" ")
|
||||
val chatId = chatIdString.toLongOrNull() ?.let(::RawChatId) ?.let(::ChatId) ?: return@onMessageDataCallbackQuery
|
||||
val messageId = messageIdString.toLongOrNull() ?.let(::MessageId) ?: return@onMessageDataCallbackQuery
|
||||
val businessConnectionId = chatsBusinessConnections[it.message.chat.id]
|
||||
|
||||
val readResponse = businessConnectionId ?.let { deleteBusinessMessages(it, listOf(messageId)) }
|
||||
answer(
|
||||
it,
|
||||
if (readResponse == null) {
|
||||
"There is no business connection for current chat"
|
||||
} else {
|
||||
"Message has been deleted"
|
||||
}
|
||||
)
|
||||
}
|
||||
onCommandWithArgs("set_business_account_name", initialFilter = { it.chat is PrivateChat }) { it, args ->
|
||||
val firstName = args[0]
|
||||
val secondName = args.getOrNull(1)
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommandWithArgs
|
||||
val set = runCatching {
|
||||
setBusinessAccountName(
|
||||
businessConnectionId,
|
||||
firstName,
|
||||
secondName
|
||||
)
|
||||
}.getOrElse { false }
|
||||
reply(it) {
|
||||
if (set) {
|
||||
+"Account name has been set"
|
||||
} else {
|
||||
+"Account name has not been set"
|
||||
}
|
||||
}
|
||||
}
|
||||
onCommandWithArgs("set_business_account_username", initialFilter = { it.chat is PrivateChat }) { it, args ->
|
||||
val username = args[0]
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommandWithArgs
|
||||
val set = runCatching {
|
||||
setBusinessAccountUsername(
|
||||
businessConnectionId,
|
||||
username
|
||||
)
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
false
|
||||
}
|
||||
reply(it) {
|
||||
if (set) {
|
||||
+"Account username has been set"
|
||||
} else {
|
||||
+"Account username has not been set"
|
||||
}
|
||||
}
|
||||
}
|
||||
onCommand("get_business_account_star_balance", initialFilter = { it.chat is PrivateChat }) {
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
|
||||
val starAmount = runCatching {
|
||||
getBusinessAccountStarBalance(businessConnectionId)
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
null
|
||||
}
|
||||
reply(it) {
|
||||
if (starAmount != null) {
|
||||
+"Account stars amount: $starAmount"
|
||||
} else {
|
||||
+"Account stars amount has not been got"
|
||||
}
|
||||
}
|
||||
}
|
||||
onCommandWithArgs("transfer_business_account_stars", initialFilter = { it.chat is PrivateChat }) { it, args ->
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommandWithArgs
|
||||
val count = args.firstOrNull() ?.toIntOrNull() ?: reply(it) {
|
||||
"Pass amount of stars to transfer to bot with command"
|
||||
}.let {
|
||||
return@onCommandWithArgs
|
||||
}
|
||||
val transferred = runCatching {
|
||||
transferBusinessAccountStars(businessConnectionId, count)
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
false
|
||||
}
|
||||
reply(it) {
|
||||
if (transferred) {
|
||||
+"Stars have been transferred"
|
||||
} else {
|
||||
+"Stars have not been transferred"
|
||||
}
|
||||
}
|
||||
}
|
||||
onCommand("get_business_account_gifts", initialFilter = { it.chat is PrivateChat }) {
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
|
||||
val giftsFlow = runCatching {
|
||||
getBusinessAccountGiftsFlow(businessConnectionId)
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
null
|
||||
}
|
||||
if (giftsFlow == null) {
|
||||
reply(it) {
|
||||
+"Error in receiving of gifts"
|
||||
}
|
||||
} else {
|
||||
giftsFlow.collect { giftsPage ->
|
||||
giftsPage.gifts.joinToString {
|
||||
it.toString()
|
||||
}.splitForText().forEach { message ->
|
||||
reply(it, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
onCommand("set_business_account_bio", requireOnlyCommandInMessage = false, initialFilter = { it.chat is PrivateChat }) {
|
||||
val initialBio = getChat(it.chat).extendedPrivateChatOrThrow().bio
|
||||
val bio = it.content.text.removePrefix("/set_business_account_bio").trim()
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
|
||||
val set = runCatching {
|
||||
setBusinessAccountBio(
|
||||
businessConnectionId,
|
||||
bio
|
||||
)
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
false
|
||||
}
|
||||
reply(it) {
|
||||
if (set) {
|
||||
+"Account bio has been set. It will be reset within 15 seconds.\n\nInitial bio: " + code(initialBio)
|
||||
} else {
|
||||
+"Account bio has not been set"
|
||||
}
|
||||
}
|
||||
delay(15.seconds)
|
||||
val reset = runCatching {
|
||||
setBusinessAccountBio(
|
||||
businessConnectionId,
|
||||
initialBio
|
||||
)
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
false
|
||||
}
|
||||
reply(it) {
|
||||
if (reset) {
|
||||
+"Account bio has been reset"
|
||||
} else {
|
||||
+"Account bio has not been set. Set it manually: " + code(initialBio)
|
||||
}
|
||||
}
|
||||
}
|
||||
suspend fun handleSetProfilePhoto(it: CommonMessage<TextContent>, isPublic: Boolean) {
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@handleSetProfilePhoto
|
||||
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<PhotoContent>()
|
||||
if (replyTo == null) {
|
||||
reply(it) {
|
||||
+"Reply to photo for using of this command"
|
||||
}
|
||||
return@handleSetProfilePhoto
|
||||
}
|
||||
|
||||
val set = runCatching {
|
||||
val file = downloadFileToTemp(replyTo.content)
|
||||
setBusinessAccountProfilePhoto(
|
||||
businessConnectionId,
|
||||
InputProfilePhoto.Static(
|
||||
file.multipartFile()
|
||||
),
|
||||
isPublic = isPublic
|
||||
)
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
false
|
||||
}
|
||||
reply(it) {
|
||||
if (set) {
|
||||
+"Account profile photo has been set. It will be reset within 15 seconds"
|
||||
} else {
|
||||
+"Account profile photo has not been set"
|
||||
}
|
||||
}
|
||||
if (set == false) { return@handleSetProfilePhoto }
|
||||
delay(15.seconds)
|
||||
val reset = runCatching {
|
||||
removeBusinessAccountProfilePhoto(
|
||||
businessConnectionId,
|
||||
isPublic = isPublic
|
||||
)
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
false
|
||||
}
|
||||
reply(it) {
|
||||
if (reset) {
|
||||
+"Account profile photo has been reset"
|
||||
} else {
|
||||
+"Account profile photo has not been set. Set it manually"
|
||||
}
|
||||
}
|
||||
}
|
||||
onCommand("set_business_account_profile_photo", initialFilter = { it.chat is PrivateChat }) {
|
||||
handleSetProfilePhoto(it, false)
|
||||
}
|
||||
onCommand("set_business_account_profile_photo_public", initialFilter = { it.chat is PrivateChat }) {
|
||||
handleSetProfilePhoto(it, true)
|
||||
}
|
||||
|
||||
onCommand("post_story", initialFilter = { it.chat is PrivateChat }) {
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
|
||||
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<VisualMediaGroupPartContent>()
|
||||
if (replyTo == null) {
|
||||
reply(it) {
|
||||
+"Reply to photo or video for using of this command"
|
||||
}
|
||||
return@onCommand
|
||||
}
|
||||
|
||||
val posted = runCatching {
|
||||
val file = downloadFileToTemp(replyTo.content)
|
||||
postStory(
|
||||
businessConnectionId,
|
||||
when (replyTo.content) {
|
||||
is PhotoContent -> InputStoryContent.Photo(
|
||||
file.multipartFile()
|
||||
)
|
||||
is VideoContent -> InputStoryContent.Video(
|
||||
file.multipartFile()
|
||||
)
|
||||
},
|
||||
activePeriod = PostStory.ACTIVE_PERIOD_6_HOURS,
|
||||
areas = listOf(
|
||||
StoryArea(
|
||||
StoryAreaPosition(
|
||||
x = Percentage.of100(50.0),
|
||||
y = Percentage.of100(50.0),
|
||||
width = Percentage.of100(8.0),
|
||||
height = Percentage.of100(8.0),
|
||||
rotationAngle = 45.0,
|
||||
cornerRadius = Percentage.of100(4.0),
|
||||
),
|
||||
StoryAreaType.Link(
|
||||
"https://github.com/InsanusMokrassar/TelegramBotAPI-examples/blob/master/BusinessConnectionsBot/src/main/kotlin/BusinessConnectionsBot.kt"
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
+"It is test of postStory :)"
|
||||
}
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
null
|
||||
}
|
||||
reply(it) {
|
||||
if (posted != null) {
|
||||
+"Story has been posted. You may unpost it with " + botCommand("remove_story")
|
||||
} else {
|
||||
+"Story has not been posted"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCommand("delete_story", initialFilter = { it.chat is PrivateChat }) {
|
||||
val businessConnectionId = chatsBusinessConnections[it.chat.id] ?: return@onCommand
|
||||
val replyTo = it.replyTo ?.commonMessageOrNull() ?.withContentOrNull<StoryContent>()
|
||||
if (replyTo == null) {
|
||||
reply(it) {
|
||||
+"Reply to photo or video for using of this command"
|
||||
}
|
||||
return@onCommand
|
||||
}
|
||||
|
||||
val deleted = runCatching {
|
||||
deleteStory(businessConnectionId, replyTo.content.story.id)
|
||||
}.getOrElse {
|
||||
it.printStackTrace()
|
||||
false
|
||||
}
|
||||
reply(it) {
|
||||
if (deleted) {
|
||||
+"Story has been deleted"
|
||||
} else {
|
||||
+"Story has not been deleted"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Will work when some premium user sending to some other user checklist
|
||||
onChecklistContent {
|
||||
execute(
|
||||
it.content.createResend(
|
||||
it.chat.id,
|
||||
businessConnectionId = it.chat.id.businessConnectionId ?: chatsBusinessConnections[it.chat.id] ?: return@onChecklistContent
|
||||
)
|
||||
)
|
||||
}
|
||||
}.second.join()
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import dev.inmo.tgbotapi.extensions.api.files.downloadFile
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPhoto
|
||||
import dev.inmo.tgbotapi.extensions.utils.*
|
||||
import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
suspend fun main(args: Array<String>) {
|
||||
val bot = telegramBot(args.first())
|
||||
|
||||
21
ChecklistsBot/build.gradle
Normal file
21
ChecklistsBot/build.gradle
Normal 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="ChecklistsBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
120
ChecklistsBot/src/main/kotlin/ChecklistsBot.kt
Normal file
120
ChecklistsBot/src/main/kotlin/ChecklistsBot.kt
Normal file
@@ -0,0 +1,120 @@
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.LogLevel
|
||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||
import dev.inmo.kslog.common.setDefaultKSLog
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.resend
|
||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
import dev.inmo.tgbotapi.extensions.api.suggested.approveSuggestedPost
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextData
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildSubcontextInitialAction
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitSuggestedPostApproved
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitSuggestedPostDeclined
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChannelDirectMessagesConfigurationChanged
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChecklistContent
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChecklistTasksAdded
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChecklistTasksDone
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostApprovalFailed
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostApproved
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostDeclined
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostPaid
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostRefunded
|
||||
import dev.inmo.tgbotapi.extensions.utils.channelDirectMessagesContentMessageOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.previewChannelDirectMessagesChatOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
|
||||
import dev.inmo.tgbotapi.types.checklists.ChecklistTaskId
|
||||
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||
import dev.inmo.tgbotapi.types.message.content.ChecklistContent
|
||||
import dev.inmo.tgbotapi.types.message.textsources.TextSourcesList
|
||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||
import dev.inmo.tgbotapi.utils.bold
|
||||
import dev.inmo.tgbotapi.utils.buildEntities
|
||||
import dev.inmo.tgbotapi.utils.code
|
||||
import dev.inmo.tgbotapi.utils.firstOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
|
||||
val isDebug = args.any { it == "debug" }
|
||||
val isTestServer = args.any { it == "testServer" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(
|
||||
botToken,
|
||||
CoroutineScope(Dispatchers.Default),
|
||||
testServer = isTestServer,
|
||||
) {
|
||||
// start here!!
|
||||
val me = getMe()
|
||||
println(me)
|
||||
|
||||
fun ChecklistContent.textBuilderTextSources(): TextSourcesList {
|
||||
return buildEntities {
|
||||
+checklist.textSources + "\n\n"
|
||||
checklist.tasks.forEach { task ->
|
||||
+"• "
|
||||
code(
|
||||
if (task.completionDate != null) {
|
||||
"[x] "
|
||||
} else {
|
||||
"[ ] "
|
||||
}
|
||||
)
|
||||
|
||||
bold(task.textSources) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onChecklistContent { messageWithContent ->
|
||||
reply(messageWithContent) {
|
||||
+messageWithContent.content.textBuilderTextSources()
|
||||
}
|
||||
}
|
||||
|
||||
onChecklistTasksDone { eventMessage ->
|
||||
reply(
|
||||
eventMessage,
|
||||
checklistTaskId = eventMessage.chatEvent.markedAsDone ?.firstOrNull()
|
||||
) {
|
||||
eventMessage.chatEvent.checklistMessage.content.checklist
|
||||
+eventMessage.chatEvent.checklistMessage.content.textBuilderTextSources()
|
||||
}
|
||||
}
|
||||
|
||||
onChecklistTasksAdded { messageWithContent ->
|
||||
reply(
|
||||
messageWithContent.chatEvent.checklistMessage,
|
||||
checklistTaskId = messageWithContent.chatEvent.tasks.firstOrNull() ?.id
|
||||
) {
|
||||
+messageWithContent.chatEvent.checklistMessage.content.textBuilderTextSources()
|
||||
}
|
||||
}
|
||||
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
||||
println(it)
|
||||
}
|
||||
}.second.join()
|
||||
}
|
||||
9
CustomBot/README.md
Normal file
9
CustomBot/README.md
Normal 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
21
CustomBot/build.gradle
Normal 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"
|
||||
}
|
||||
136
CustomBot/src/main/kotlin/CustomBot.kt
Normal file
136
CustomBot/src/main/kotlin/CustomBot.kt
Normal file
@@ -0,0 +1,136 @@
|
||||
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.getMyStarBalance
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
||||
import dev.inmo.tgbotapi.extensions.api.get.getUserProfileAudios
|
||||
import dev.inmo.tgbotapi.extensions.api.send.media.sendPaidMedia
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.replyWithAudio
|
||||
import dev.inmo.tgbotapi.extensions.api.send.replyWithPlaylist
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextData
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildSubcontextInitialAction
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChannelDirectMessagesConfigurationChanged
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatOwnerChanged
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatOwnerLeft
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPhoto
|
||||
import dev.inmo.tgbotapi.types.media.AudioMediaGroupMemberTelegramMedia
|
||||
import dev.inmo.tgbotapi.types.media.toTelegramMediaAudio
|
||||
import dev.inmo.tgbotapi.types.media.toTelegramPaidMediaPhoto
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
private var BehaviourContextData.update: Update?
|
||||
get() = get("update") as? Update
|
||||
set(value) = set("update", value)
|
||||
|
||||
private var BehaviourContextData.commonMessage: CommonMessage<*>?
|
||||
get() = get("commonMessage") as? CommonMessage<*>
|
||||
set(value) = set("commonMessage", value)
|
||||
|
||||
/**
|
||||
* This place can be the playground for your code.
|
||||
*/
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
|
||||
val isDebug = args.any { it == "debug" }
|
||||
val isTestServer = args.any { it == "testServer" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(
|
||||
botToken,
|
||||
CoroutineScope(Dispatchers.IO),
|
||||
testServer = isTestServer,
|
||||
builder = {
|
||||
includeMiddlewares {
|
||||
addMiddleware {
|
||||
doOnRequestReturnResult { result, request, _ ->
|
||||
println("Result of $request:\n\n$result")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
subcontextInitialAction = buildSubcontextInitialAction {
|
||||
add {
|
||||
data.update = it
|
||||
}
|
||||
}
|
||||
) {
|
||||
// start here!!
|
||||
val me = getMe()
|
||||
println(me)
|
||||
|
||||
onCommand("start") {
|
||||
println(data.update)
|
||||
println(data.commonMessage)
|
||||
println(getChat(it.chat))
|
||||
var currentOffset = 0
|
||||
val pageSize = 2
|
||||
do {
|
||||
val userAudios = getUserProfileAudios(userId = it.chat.id, offset = currentOffset, limit = pageSize)
|
||||
currentOffset += pageSize
|
||||
println(userAudios)
|
||||
when (userAudios.audios.size) {
|
||||
1 -> {
|
||||
replyWithAudio(
|
||||
it,
|
||||
userAudios.audios.first().fileId
|
||||
)
|
||||
}
|
||||
0 -> {
|
||||
// do nothing
|
||||
}
|
||||
else -> {
|
||||
replyWithPlaylist(
|
||||
it,
|
||||
userAudios.audios.map {
|
||||
it.toTelegramMediaAudio()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} while (currentOffset < userAudios.totalCount && userAudios.audios.isNotEmpty())
|
||||
}
|
||||
|
||||
onCommand(
|
||||
"additional_command",
|
||||
additionalSubcontextInitialAction = { update, commonMessage ->
|
||||
data.commonMessage = commonMessage
|
||||
}
|
||||
) {
|
||||
println(data.update)
|
||||
println(data.commonMessage)
|
||||
}
|
||||
|
||||
onCommand("getMyStarBalance") {
|
||||
reply(
|
||||
to = it,
|
||||
text = getMyStarBalance().toString()
|
||||
)
|
||||
}
|
||||
|
||||
onChannelDirectMessagesConfigurationChanged {
|
||||
println(it.chatEvent)
|
||||
}
|
||||
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||
println(it)
|
||||
}
|
||||
}.second.join()
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelySkippingExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitDeepLinks
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onDeepLink
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onText
|
||||
import dev.inmo.tgbotapi.extensions.utils.formatting.makeTelegramDeepLink
|
||||
import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource
|
||||
|
||||
@@ -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
|
||||
|
||||
9
DraftsBot/README.md
Normal file
9
DraftsBot/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Drafts bot
|
||||
|
||||
The main purpose of this bot is just to answer "Oh, hi, " and add user mention here
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN"
|
||||
```
|
||||
21
DraftsBot/build.gradle
Normal file
21
DraftsBot/build.gradle
Normal 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="TopicsHandlingKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
86
DraftsBot/src/main/kotlin/DraftsBot.kt
Normal file
86
DraftsBot/src/main/kotlin/DraftsBot.kt
Normal file
@@ -0,0 +1,86 @@
|
||||
import com.benasher44.uuid.uuid4
|
||||
import dev.inmo.kslog.common.w
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.forum.*
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
import dev.inmo.tgbotapi.extensions.api.send.sendMessageDraftFlow
|
||||
import dev.inmo.tgbotapi.extensions.api.send.sendMessageDraftFlowWithTexts
|
||||
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.onForumTopicClosed
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicCreated
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicEdited
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicReopened
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGeneralForumTopicHidden
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGeneralForumTopicUnhidden
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPrivateForumTopicCreated
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPrivateForumTopicEdited
|
||||
import dev.inmo.tgbotapi.extensions.utils.forumChatOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.forumContentMessageOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.privateChatOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.privateForumChatOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.flushAccumulatedUpdates
|
||||
import dev.inmo.tgbotapi.types.BotCommand
|
||||
import dev.inmo.tgbotapi.types.ForumTopic
|
||||
import dev.inmo.tgbotapi.types.chat.PrivateChat
|
||||
import dev.inmo.tgbotapi.types.commands.BotCommandScope
|
||||
import io.ktor.client.plugins.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
|
||||
const val testText = """
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
"""
|
||||
|
||||
suspend fun main(vararg args: String) {
|
||||
telegramBotWithBehaviourAndLongPolling(
|
||||
args.first(),
|
||||
CoroutineScope(Dispatchers.Default),
|
||||
defaultExceptionsHandler = {
|
||||
it.printStackTrace()
|
||||
},
|
||||
builder = {
|
||||
client = client.config {
|
||||
install(HttpTimeout) {
|
||||
requestTimeoutMillis = 30000
|
||||
socketTimeoutMillis = 30000
|
||||
connectTimeoutMillis = 30000
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
onCommand("test_draft_flow") {
|
||||
sendMessageDraftFlowWithTexts(
|
||||
it.chat.id,
|
||||
flow<String> {
|
||||
val step = 50
|
||||
var currentLength = step
|
||||
while (isActive && testText.length > currentLength) {
|
||||
delay(500L)
|
||||
emit(testText.take(currentLength))
|
||||
currentLength += step
|
||||
}
|
||||
},
|
||||
)
|
||||
send(it.chat, testText)
|
||||
}
|
||||
|
||||
setMyCommands(
|
||||
BotCommand("test_draft_flow", "Start draft testing with flow"),
|
||||
scope = BotCommandScope.AllGroupChats
|
||||
)
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
||||
println(it)
|
||||
}
|
||||
}.second.join()
|
||||
}
|
||||
@@ -1,26 +1,31 @@
|
||||
import dev.inmo.micro_utils.coroutines.AccumulatorFlow
|
||||
import dev.inmo.micro_utils.coroutines.awaitFirst
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.fsm.common.State
|
||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
import dev.inmo.tgbotapi.extensions.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.behaviour_builder.expectations.waitAnyContentMessage
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitCommandMessage
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndFSMAndStartLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.command
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.containsCommand
|
||||
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgs
|
||||
import dev.inmo.tgbotapi.extensions.utils.extensions.sameThread
|
||||
import dev.inmo.tgbotapi.extensions.utils.formatting.*
|
||||
import dev.inmo.tgbotapi.types.ChatId
|
||||
import dev.inmo.tgbotapi.types.MessageThreadId
|
||||
import dev.inmo.tgbotapi.extensions.utils.textContentOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
||||
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||
import dev.inmo.tgbotapi.utils.botCommand
|
||||
import dev.inmo.tgbotapi.utils.extensions.threadIdOrNull
|
||||
import kotlinx.coroutines.*
|
||||
import dev.inmo.tgbotapi.utils.firstOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
sealed interface BotState : State
|
||||
data class ExpectContentOrStopState(override val context: Pair<ChatId, MessageThreadId?>, val sourceMessage: CommonMessage<TextContent>) : BotState
|
||||
data class StopState(override val context: Pair<ChatId, MessageThreadId?>) : BotState
|
||||
data class ExpectContentOrStopState(override val context: IdChatIdentifier, val sourceMessage: CommonMessage<TextContent>) : BotState
|
||||
data class StopState(override val context: IdChatIdentifier) : BotState
|
||||
|
||||
suspend fun main(args: Array<String>) {
|
||||
val botToken = args.first()
|
||||
@@ -43,27 +48,37 @@ suspend fun main(args: Array<String>) {
|
||||
) {
|
||||
strictlyOn<ExpectContentOrStopState> {
|
||||
send(
|
||||
it.context.first,
|
||||
threadId = it.context.second
|
||||
it.context,
|
||||
) {
|
||||
+"Send me some content or " + botCommand("stop") + " if you want to stop sending"
|
||||
}
|
||||
|
||||
val contentMessage = waitContentMessage().filter { message ->
|
||||
message.sameThread(it.sourceMessage)
|
||||
}.first()
|
||||
val contentMessage = firstOf(
|
||||
{
|
||||
waitCommandMessage("stop").filter { message ->
|
||||
message.sameThread(it.sourceMessage)
|
||||
}.first()
|
||||
null
|
||||
},
|
||||
{
|
||||
waitAnyContentMessage().filter { message ->
|
||||
message.sameThread(it.sourceMessage)
|
||||
}.filter {
|
||||
containsCommand(
|
||||
"stop",
|
||||
it.withContentOrNull<TextContent>() ?.content ?.textSources ?: return@filter false
|
||||
) == false
|
||||
}.first()
|
||||
}
|
||||
) ?: return@strictlyOn StopState(it.context)
|
||||
|
||||
val content = contentMessage.content
|
||||
|
||||
when {
|
||||
content is TextContent && content.parseCommandsWithParams().keys.contains("stop") -> StopState(it.context)
|
||||
else -> {
|
||||
execute(content.createResend(it.context.first, messageThreadId = it.context.second))
|
||||
it
|
||||
}
|
||||
}
|
||||
execute(content.createResend(it.context))
|
||||
it
|
||||
}
|
||||
strictlyOn<StopState> {
|
||||
send(it.context.first, threadId = it.context.second) { +"You have stopped sending of content" }
|
||||
send(it.context) { +"You have stopped sending of content" }
|
||||
|
||||
null
|
||||
}
|
||||
@@ -71,7 +86,19 @@ suspend fun main(args: Array<String>) {
|
||||
command(
|
||||
"start"
|
||||
) {
|
||||
startChain(ExpectContentOrStopState(it.chat.id to it.threadIdOrNull, it))
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.extensions.api.files.downloadFile
|
||||
import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp
|
||||
import dev.inmo.tgbotapi.extensions.api.get.getFileAdditionalInfo
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
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.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMedia
|
||||
import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile
|
||||
import dev.inmo.tgbotapi.types.actions.*
|
||||
import dev.inmo.tgbotapi.types.media.TelegramMediaAudio
|
||||
import dev.inmo.tgbotapi.types.media.TelegramMediaDocument
|
||||
import dev.inmo.tgbotapi.types.media.TelegramMediaPhoto
|
||||
import dev.inmo.tgbotapi.types.media.TelegramMediaVideo
|
||||
import dev.inmo.tgbotapi.types.message.content.*
|
||||
import dev.inmo.tgbotapi.utils.filenameFromUrl
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -18,16 +27,90 @@ suspend fun main(args: Array<String>) {
|
||||
directoryOrFile.mkdirs()
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
|
||||
onMedia(initialFilter = null) {
|
||||
val pathedFile = bot.getFileAdditionalInfo(it.content.media)
|
||||
val outFile = File(directoryOrFile, pathedFile.filePath.filenameFromUrl)
|
||||
runCatching {
|
||||
bot.downloadFile(it.content.media, outFile)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
reply(it, "Saved to ${outFile.absolutePath}")
|
||||
onCommand("start") {
|
||||
reply(it, "Send me any media (like photo or video) to download it")
|
||||
}
|
||||
onContentMessage { println(it) }
|
||||
onMedia(initialFilter = null) {
|
||||
val content = it.content
|
||||
val pathedFile = bot.getFileAdditionalInfo(content.media)
|
||||
val outFile = File(directoryOrFile, pathedFile.filePath.filenameFromUrl)
|
||||
withTypingAction(it.chat.id) {
|
||||
runCatching {
|
||||
bot.downloadFile(content.media, outFile)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}.onSuccess { _ ->
|
||||
reply(it, "Saved to ${outFile.absolutePath}")
|
||||
}
|
||||
}.onSuccess { _ ->
|
||||
val action = when (content) {
|
||||
is PhotoContent -> UploadPhotoAction
|
||||
is AnimationContent,
|
||||
is VideoContent -> UploadVideoAction
|
||||
is StickerContent -> ChooseStickerAction
|
||||
is MediaGroupContent<*> -> UploadPhotoAction
|
||||
is DocumentContent -> UploadDocumentAction
|
||||
is VoiceContent,
|
||||
is AudioContent -> RecordVoiceAction
|
||||
is VideoNoteContent -> UploadVideoNoteAction
|
||||
}
|
||||
withAction(it.chat.id, action) {
|
||||
when (content) {
|
||||
is PhotoContent -> replyWithPhoto(
|
||||
it,
|
||||
outFile.asMultipartFile()
|
||||
)
|
||||
is AnimationContent -> replyWithAnimation(
|
||||
it,
|
||||
outFile.asMultipartFile()
|
||||
)
|
||||
is VideoContent -> replyWithVideo(
|
||||
it,
|
||||
outFile.asMultipartFile()
|
||||
)
|
||||
is StickerContent -> replyWithSticker(
|
||||
it,
|
||||
outFile.asMultipartFile()
|
||||
)
|
||||
is MediaGroupContent<*> -> replyWithMediaGroup(
|
||||
it,
|
||||
content.group.map {
|
||||
when (val innerContent = it.content) {
|
||||
is AudioContent -> TelegramMediaAudio(
|
||||
downloadFileToTemp(innerContent.media).asMultipartFile()
|
||||
)
|
||||
is DocumentContent -> TelegramMediaDocument(
|
||||
downloadFileToTemp(innerContent.media).asMultipartFile()
|
||||
)
|
||||
is PhotoContent -> TelegramMediaPhoto(
|
||||
downloadFileToTemp(innerContent.media).asMultipartFile()
|
||||
)
|
||||
is VideoContent -> TelegramMediaVideo(
|
||||
downloadFileToTemp(innerContent.media).asMultipartFile()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
is AudioContent -> replyWithAudio(
|
||||
it,
|
||||
outFile.asMultipartFile()
|
||||
)
|
||||
is DocumentContent -> replyWithDocument(
|
||||
it,
|
||||
outFile.asMultipartFile()
|
||||
)
|
||||
is VoiceContent -> replyWithVoice(
|
||||
it,
|
||||
outFile.asMultipartFile()
|
||||
)
|
||||
is VideoNoteContent -> replyWithVideoNote(
|
||||
it,
|
||||
outFile.asMultipartFile()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
|
||||
}.second.join()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
|
||||
import dev.inmo.tgbotapi.extensions.utils.formatting.*
|
||||
import dev.inmo.tgbotapi.extensions.utils.formatting.makeLink
|
||||
import dev.inmo.tgbotapi.types.chat.CommonBot
|
||||
import dev.inmo.tgbotapi.types.chat.CommonUser
|
||||
import dev.inmo.tgbotapi.types.chat.ExtendedBot
|
||||
import dev.inmo.tgbotapi.types.message.*
|
||||
import dev.inmo.tgbotapi.utils.buildEntities
|
||||
import dev.inmo.tgbotapi.utils.code
|
||||
import dev.inmo.tgbotapi.utils.link
|
||||
import dev.inmo.tgbotapi.utils.regular
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
@@ -40,7 +42,14 @@ suspend fun main(vararg args: String) {
|
||||
is ExtendedBot -> regular("Bot ")
|
||||
} + code(user.id.chatId.toString()) + " (${user.firstName} ${user.lastName}: ${user.username?.username ?: "Without username"})"
|
||||
}
|
||||
is ForwardInfo.PublicChat.FromChannel -> regular("Channel (") + code(forwardInfo.channelChat.title) + ")"
|
||||
is ForwardInfo.PublicChat.FromChannel -> {
|
||||
regular("Channel (") + (forwardInfo.channelChat.username ?.let {
|
||||
link(
|
||||
forwardInfo.channelChat.title,
|
||||
makeLink(it)
|
||||
)
|
||||
} ?: code(forwardInfo.channelChat.title)) + ")"
|
||||
}
|
||||
is ForwardInfo.PublicChat.FromSupergroup -> regular("Supergroup (") + code(forwardInfo.group.title) + ")"
|
||||
is ForwardInfo.PublicChat.SentByChannel -> regular("Sent by channel (") + code(forwardInfo.channelChat.title) + ")"
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
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
|
||||
|
||||
/**
|
||||
* 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 bot = telegramBot(botToken)
|
||||
|
||||
println(bot.getMe())
|
||||
}
|
||||
21
GiftsBot/build.gradle
Normal file
21
GiftsBot/build.gradle
Normal 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="GiftsBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
112
GiftsBot/src/main/kotlin/GiftsBot.kt
Normal file
112
GiftsBot/src/main/kotlin/GiftsBot.kt
Normal file
@@ -0,0 +1,112 @@
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.LogLevel
|
||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||
import dev.inmo.kslog.common.setDefaultKSLog
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow
|
||||
import dev.inmo.tgbotapi.extensions.api.gifts.getChatGiftsFlow
|
||||
import dev.inmo.tgbotapi.extensions.api.gifts.getUserGiftsFlow
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.withTypingAction
|
||||
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.onGiveawayCompleted
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayContent
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayCreated
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayWinners
|
||||
import dev.inmo.tgbotapi.types.chat.BusinessChat
|
||||
import dev.inmo.tgbotapi.types.chat.PrivateChat
|
||||
import dev.inmo.tgbotapi.types.chat.PublicChat
|
||||
import dev.inmo.tgbotapi.types.chat.UnknownChatType
|
||||
import dev.inmo.tgbotapi.types.gifts.OwnedGift
|
||||
import dev.inmo.tgbotapi.types.message.textsources.splitForText
|
||||
import dev.inmo.tgbotapi.utils.bold
|
||||
import dev.inmo.tgbotapi.utils.buildEntities
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
|
||||
val isDebug = args.any { it == "debug" }
|
||||
val isTestServer = args.any { it == "testServer" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(botToken, testServer = isTestServer) {
|
||||
// start here!!
|
||||
val me = getMe()
|
||||
println(me)
|
||||
|
||||
onCommand("start") {
|
||||
val giftsFlow = when (val chat = it.chat) {
|
||||
is BusinessChat -> {
|
||||
getBusinessAccountGiftsFlow(
|
||||
chat.id.businessConnectionId
|
||||
)
|
||||
}
|
||||
is PrivateChat -> {
|
||||
getUserGiftsFlow(it.chat.id)
|
||||
}
|
||||
is UnknownChatType,
|
||||
is PublicChat -> {
|
||||
getChatGiftsFlow(it.chat.id)
|
||||
}
|
||||
}
|
||||
|
||||
withTypingAction(it.chat) {
|
||||
val texts = buildEntities {
|
||||
giftsFlow.collect { ownedGifts ->
|
||||
ownedGifts.gifts.forEach {
|
||||
when (it) {
|
||||
is OwnedGift.Regular.Common -> {
|
||||
bold("Type") + ": Regular common\n"
|
||||
bold("Id") + ": ${it.gift.id.string}\n"
|
||||
bold("Text") + ": ${it.text ?: "(None)"}\n"
|
||||
bold("Stars cost") + ": ${it.gift.starCount}\n"
|
||||
}
|
||||
is OwnedGift.Unique.Common -> {
|
||||
bold("Type") + ": Unique common\n"
|
||||
bold("Id") + ": ${it.gift.id ?.string ?: "(None)"}\n"
|
||||
bold("Name") + ": ${it.gift.name.value}\n"
|
||||
bold("Model") + ": ${it.gift.model.name}\n"
|
||||
bold("Number") + ": ${it.gift.number}\n"
|
||||
}
|
||||
is OwnedGift.Regular.OwnedByBusinessAccount -> {
|
||||
bold("Type") + ": Regular owned by business\n"
|
||||
bold("Id") + ": ${it.gift.id.string}\n"
|
||||
bold("Text") + ": ${it.text ?: "(None)"}\n"
|
||||
bold("Stars cost") + ": ${it.gift.starCount}\n"
|
||||
}
|
||||
is OwnedGift.Unique.OwnedByBusinessAccount -> {
|
||||
bold("Type") + ": Unique owned by business\n"
|
||||
bold("Id") + ": ${it.gift.id ?.string ?: "(None)"}\n"
|
||||
bold("Name") + ": ${it.gift.name.value}\n"
|
||||
bold("Model") + ": ${it.gift.model.name}\n"
|
||||
bold("Number") + ": ${it.gift.number}\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val preparedTexts = texts.splitForText()
|
||||
if (preparedTexts.isEmpty()) {
|
||||
reply(it, "This chat have no any gifts")
|
||||
} else {
|
||||
preparedTexts.forEach { preparedText -> reply(it, preparedText) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||
// println(it)
|
||||
// }
|
||||
}.second.join()
|
||||
}
|
||||
9
GiveawaysBot/README.md
Normal file
9
GiveawaysBot/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# CustomBot
|
||||
|
||||
Printing giveaways
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN"
|
||||
```
|
||||
21
GiveawaysBot/build.gradle
Normal file
21
GiveawaysBot/build.gradle
Normal 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="GiveawaysBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
57
GiveawaysBot/src/main/kotlin/GiveawaysBot.kt
Normal file
57
GiveawaysBot/src/main/kotlin/GiveawaysBot.kt
Normal file
@@ -0,0 +1,57 @@
|
||||
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.extensions.behaviour_builder.triggers_handling.onGiveawayCompleted
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayContent
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayCreated
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayWinners
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
/**
|
||||
* This place can be the playground for your code.
|
||||
*/
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
|
||||
val isDebug = args.any { it == "debug" }
|
||||
val isTestServer = args.any { it == "testServer" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(botToken, testServer = isTestServer) {
|
||||
// start here!!
|
||||
val me = getMe()
|
||||
println(me)
|
||||
|
||||
onGiveawayCreated {
|
||||
println(it)
|
||||
}
|
||||
|
||||
onGiveawayCompleted {
|
||||
println(it)
|
||||
}
|
||||
|
||||
onGiveawayWinners {
|
||||
println(it)
|
||||
}
|
||||
|
||||
onGiveawayContent {
|
||||
println(it)
|
||||
}
|
||||
|
||||
// allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||
// println(it)
|
||||
// }
|
||||
}.second.join()
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
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.reply
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMentionWithAnyContent
|
||||
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.sender_chat
|
||||
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.text
|
||||
import dev.inmo.tgbotapi.extensions.utils.formatting.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.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.utils.PreviewFeature
|
||||
import dev.inmo.tgbotapi.utils.extensions.escapeMarkdownV2Common
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
/**
|
||||
* The main purpose of this bot is just to answer "Oh, hi, " and add user mention here
|
||||
@@ -25,21 +25,33 @@ suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
|
||||
onContentMessage { message ->
|
||||
val chat = message.chat
|
||||
|
||||
val me = getMe()
|
||||
onContentMessage(
|
||||
initialFilter = initialFilter@{ it.text ?.contains(me.username ?.full ?: return@initialFilter false) == true }
|
||||
) { message ->
|
||||
val answerText = when (val chat = message.chat) {
|
||||
is ChannelChat -> {
|
||||
val answer = "Hi everybody in this channel \"${chat.title}\""
|
||||
reply(message, answer, MarkdownV2)
|
||||
is PreviewChannelChat -> {
|
||||
val sender = message.sender_chat
|
||||
val answer = "Hi everybody in this channel \"${chat.title}\"" + if (sender != null) {
|
||||
" and you, " + when (sender) {
|
||||
is BusinessChat -> "business chat (wat) ${sender.original}"
|
||||
is PrivateChat -> "${sender.lastName} ${sender.firstName}"
|
||||
is GroupChat -> "group ${sender.title}"
|
||||
is ChannelChat -> "channel ${sender.title}"
|
||||
is UnknownChatType -> "wat chat (${sender})"
|
||||
}
|
||||
} else {
|
||||
""
|
||||
}
|
||||
reply(message, answer.escapeMarkdownV2Common(), MarkdownV2)
|
||||
return@onContentMessage
|
||||
}
|
||||
is PrivateChat -> {
|
||||
is PreviewPrivateChat -> {
|
||||
reply(message, "Hi, " + "${chat.firstName} ${chat.lastName}".textMentionMarkdownV2(chat.id), MarkdownV2)
|
||||
return@onContentMessage
|
||||
}
|
||||
is GroupChat -> {
|
||||
message.ifFromChannelGroupContentMessage {
|
||||
is PreviewGroupChat -> {
|
||||
message.ifFromChannelGroupContentMessage<Unit> {
|
||||
val answer = "Hi, ${it.senderChat.title}"
|
||||
reply(message, answer, MarkdownV2)
|
||||
return@onContentMessage
|
||||
@@ -53,9 +65,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@onContentMessage
|
||||
}
|
||||
is UnknownChatType -> "Unknown :(".escapeMarkdownV2Common()
|
||||
else -> error("Something went wrong: unknown type of chat $chat")
|
||||
}
|
||||
reply(
|
||||
message,
|
||||
|
||||
9
InlineQueriesBot/README.md
Normal file
9
InlineQueriesBot/README.md
Normal 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"
|
||||
```
|
||||
37
InlineQueriesBot/build.gradle
Normal file
37
InlineQueriesBot/build.gradle
Normal file
@@ -0,0 +1,37 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
}
|
||||
|
||||
apply from: "$nativePartTemplate"
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
binaries {
|
||||
executable {
|
||||
mainClass.set("InlineQueriesBotKt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
api "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
api "io.ktor:ktor-client-logging:$ktor_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
68
InlineQueriesBot/src/commonMain/kotlin/Bot.kt
Normal file
68
InlineQueriesBot/src/commonMain/kotlin/Bot.kt
Normal file
@@ -0,0 +1,68 @@
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
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.subscribeLoggingDropExceptions(scope = this) {
|
||||
println(it)
|
||||
}
|
||||
|
||||
println(getMe())
|
||||
}.join()
|
||||
}
|
||||
3
InlineQueriesBot/src/jvmMain/kotlin/InlineQueriesBot.kt
Normal file
3
InlineQueriesBot/src/jvmMain/kotlin/InlineQueriesBot.kt
Normal file
@@ -0,0 +1,3 @@
|
||||
suspend fun main(args: Array<String>) {
|
||||
doInlineQueriesBot(args.first())
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runBlocking {
|
||||
doInlineQueriesBot(args.first())
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,29 @@
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
||||
import dev.inmo.tgbotapi.extensions.api.answers.answer
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
||||
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
||||
import dev.inmo.tgbotapi.extensions.api.send.*
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
|
||||
import dev.inmo.tgbotapi.extensions.api.edit.text.editMessageText
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
|
||||
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.CustomEmojiId
|
||||
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.buttons.KeyboardButtonStyle
|
||||
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||
import dev.inmo.tgbotapi.utils.*
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
private const val nextPageData = "next"
|
||||
private const val previousPageData = "previous"
|
||||
import dev.inmo.tgbotapi.utils.PreviewFeature
|
||||
import dev.inmo.tgbotapi.utils.botCommand
|
||||
import dev.inmo.tgbotapi.utils.regular
|
||||
import dev.inmo.tgbotapi.utils.row
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
|
||||
fun String.parsePageAndCount(): Pair<Int, Int>? {
|
||||
val (pageString, countString) = split(" ").takeIf { it.count() > 1 } ?: return null
|
||||
@@ -39,24 +47,38 @@ fun InlineKeyboardBuilder.includePageButtons(page: Int, count: Int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
row {
|
||||
copyTextButton("Command copy button", "/inline $page $count")
|
||||
}
|
||||
|
||||
row {
|
||||
if (page - 1 > 2) {
|
||||
dataButton("<<", "1 $count")
|
||||
dataButton("<<", "1 $count", style = KeyboardButtonStyle.Danger)
|
||||
}
|
||||
if (page - 1 > 1) {
|
||||
dataButton("<", "${page - 2} $count")
|
||||
dataButton("<", "${page - 2} $count", style = KeyboardButtonStyle.Primary)
|
||||
}
|
||||
|
||||
if (page + 1 < count) {
|
||||
dataButton(">", "${page + 2} $count")
|
||||
dataButton(">", "${page + 2} $count", style = KeyboardButtonStyle.Success)
|
||||
}
|
||||
if (page + 2 < count) {
|
||||
dataButton(">>", "$count $count")
|
||||
dataButton(">>", "$count $count", style = KeyboardButtonStyle.Danger)
|
||||
}
|
||||
}
|
||||
row {
|
||||
inlineQueryInChosenChatButton(
|
||||
"Send somebody page",
|
||||
query = "$page $count",
|
||||
allowUsers = true,
|
||||
allowBots = true,
|
||||
allowGroups = true,
|
||||
allowChannels = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(PreviewFeature::class)
|
||||
suspend fun activateKeyboardsBot(
|
||||
token: String,
|
||||
print: (Any) -> Unit
|
||||
@@ -67,13 +89,13 @@ suspend fun activateKeyboardsBot(
|
||||
|
||||
bot.buildBehaviourWithLongPolling(CoroutineScope(currentCoroutineContext() + SupervisorJob())) {
|
||||
onCommandWithArgs("inline") { message, args ->
|
||||
val numberOfPages = args.firstOrNull() ?.toIntOrNull() ?: 10
|
||||
val numberArgs = args.mapNotNull { it.toIntOrNull() }
|
||||
val numberOfPages = numberArgs.getOrNull(1) ?: numberArgs.firstOrNull() ?: 10
|
||||
val page = numberArgs.firstOrNull()?.takeIf { numberArgs.size > 1 }?.coerceAtLeast(1) ?: 1
|
||||
reply(
|
||||
message,
|
||||
replyMarkup = inlineKeyboard {
|
||||
row {
|
||||
includePageButtons(1, numberOfPages)
|
||||
}
|
||||
includePageButtons(page, numberOfPages)
|
||||
}
|
||||
) {
|
||||
regular("Your inline keyboard with $numberOfPages pages")
|
||||
@@ -92,9 +114,23 @@ 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")
|
||||
@@ -102,12 +138,32 @@ suspend fun activateKeyboardsBot(
|
||||
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(
|
||||
it,
|
||||
replyMarkup = replyKeyboard(resizeKeyboard = true, oneTimeKeyboard = true) {
|
||||
row {
|
||||
simpleButton("/inline")
|
||||
simpleButton("/inline", style = KeyboardButtonStyle.Primary)
|
||||
}
|
||||
}
|
||||
) {
|
||||
@@ -117,7 +173,7 @@ suspend fun activateKeyboardsBot(
|
||||
|
||||
setMyCommands(BotCommand("inline", "Creates message with pagination inline keyboard"))
|
||||
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
|
||||
println(it)
|
||||
}
|
||||
}.join()
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
suspend fun main(args: Array<String>) {
|
||||
val isDebug = args.any { it == "debug" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) { // IO for inheriting of it in side of activateKeyboardsBot
|
||||
activateKeyboardsBot(args.first()) {
|
||||
println(it)
|
||||
|
||||
9
LinkPreviewsBot/README.md
Normal file
9
LinkPreviewsBot/README.md
Normal 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"
|
||||
```
|
||||
21
LinkPreviewsBot/build.gradle
Normal file
21
LinkPreviewsBot/build.gradle
Normal 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"
|
||||
}
|
||||
84
LinkPreviewsBot/src/main/kotlin/LinkPreviewsBot.kt
Normal file
84
LinkPreviewsBot/src/main/kotlin/LinkPreviewsBot.kt
Normal file
@@ -0,0 +1,84 @@
|
||||
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.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
|
||||
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.message.content.TextedContent
|
||||
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 { contentMessage ->
|
||||
val url = contentMessage.withContentOrNull<TextedContent>() ?.let { message ->
|
||||
message.content.textSources.firstNotNullOfOrNull {
|
||||
it.textLinkTextSourceOrNull() ?.url ?: it.uRLTextSourceOrNull() ?.source
|
||||
}
|
||||
} ?: null.apply {
|
||||
reply(contentMessage) {
|
||||
regular("I am support only content with text contains url only")
|
||||
}
|
||||
} ?: return@onContentMessage
|
||||
contentMessage.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()
|
||||
}
|
||||
9
LiveLocationsBot/README.md
Normal file
9
LiveLocationsBot/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# LiveLocationsBot
|
||||
|
||||
This bot will send you live location and update it from time to time
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN"
|
||||
```
|
||||
21
LiveLocationsBot/build.gradle
Normal file
21
LiveLocationsBot/build.gradle
Normal 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="LiveLocationsBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
67
LiveLocationsBot/src/main/kotlin/LiveLocationsBot.kt
Normal file
67
LiveLocationsBot/src/main/kotlin/LiveLocationsBot.kt
Normal file
@@ -0,0 +1,67 @@
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.extensions.api.EditLiveLocationInfo
|
||||
import dev.inmo.tgbotapi.extensions.api.edit.location.live.stopLiveLocation
|
||||
import dev.inmo.tgbotapi.extensions.api.handleLiveLocation
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
|
||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.dataButton
|
||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.flatInlineKeyboard
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
|
||||
import dev.inmo.tgbotapi.types.message.content.LocationContent
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
/**
|
||||
* This bot will send you live location and update it from time to time
|
||||
*/
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO)) {
|
||||
val locationsFlow = flow {
|
||||
var i = 0
|
||||
while (isActive) {
|
||||
val newInfo = EditLiveLocationInfo(
|
||||
latitude = i.toDouble(),
|
||||
longitude = i.toDouble(),
|
||||
replyMarkup = flatInlineKeyboard {
|
||||
dataButton("Cancel", "cancel")
|
||||
}
|
||||
)
|
||||
emit(newInfo)
|
||||
i++
|
||||
delay(3000L) // 3 seconds
|
||||
}
|
||||
}
|
||||
onCommand("start") {
|
||||
// in this flow will be actual message with live location
|
||||
val currentMessageState = MutableStateFlow<ContentMessage<LocationContent>?>(null)
|
||||
val sendingJob = launch {
|
||||
handleLiveLocation(
|
||||
it.chat.id,
|
||||
locationsFlow,
|
||||
sentMessageFlow = { currentMessageState.emit(it) },
|
||||
)
|
||||
}
|
||||
|
||||
waitMessageDataCallbackQuery().filter {
|
||||
it.message.sameMessage(
|
||||
currentMessageState.value ?: return@filter false
|
||||
) && it.data == "cancel"
|
||||
}.first()
|
||||
|
||||
sendingJob.cancel() // ends live location
|
||||
currentMessageState.value ?.let {
|
||||
stopLiveLocation(it, replyMarkup = null)
|
||||
}
|
||||
}
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
|
||||
}.second.join()
|
||||
}
|
||||
|
||||
10
MemberUpdatedWatcherBot/README.md
Normal file
10
MemberUpdatedWatcherBot/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# MemberUpdatedWatcherBot
|
||||
|
||||
This bot will watch for some ChatMemberUpdated events using new extensions from 18.0.0
|
||||
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN"
|
||||
```
|
||||
21
MemberUpdatedWatcherBot/build.gradle
Normal file
21
MemberUpdatedWatcherBot/build.gradle
Normal 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="MemberUpdatedWatcherKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import dev.inmo.kslog.common.*
|
||||
import dev.inmo.tgbotapi.extensions.api.*
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.*
|
||||
import dev.inmo.tgbotapi.extensions.api.send.*
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.chatMemberGotRestrictedFilter
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.chatMemberGotRestrictionsChangedFilter
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.*
|
||||
import dev.inmo.tgbotapi.extensions.utils.*
|
||||
import dev.inmo.tgbotapi.types.chat.member.*
|
||||
import dev.inmo.tgbotapi.utils.*
|
||||
|
||||
|
||||
@OptIn(PreviewFeature::class)
|
||||
suspend fun main(args: Array<String>) {
|
||||
val token = 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))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val internalLogger = KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag ?: "ChatMemberUpdates", message, throwable))
|
||||
}
|
||||
|
||||
val bot = telegramBot(token)
|
||||
|
||||
bot.buildBehaviourWithLongPolling {
|
||||
val me = getMe()
|
||||
val filterSelfUpdates = SimpleFilter<ChatMemberUpdated> {
|
||||
it.member.id == me.id
|
||||
}
|
||||
|
||||
// This bot updates
|
||||
onChatMemberJoined(initialFilter = filterSelfUpdates) {
|
||||
internalLogger.i("Bot was added to chat")
|
||||
send(it.chat.id, "I was added to chat. Please grant me admin permissions to make me able to watch other users' events")
|
||||
}
|
||||
|
||||
onChatMemberGotPromoted(initialFilter = filterSelfUpdates) {
|
||||
internalLogger.i("Bot was granted admin permissions")
|
||||
send(it.chat.id, "I was promoted to admin. I now can watch other users' events")
|
||||
}
|
||||
|
||||
onChatMemberGotDemoted(initialFilter = filterSelfUpdates) {
|
||||
internalLogger.i("Admin permissions were revoked")
|
||||
send(it.chat.id, "I'm no longer an admin. Admin permissions are required to watch other users' events")
|
||||
}
|
||||
|
||||
// All users updates
|
||||
onChatMemberJoined {
|
||||
val member = it.member
|
||||
internalLogger.i("${member.firstName} joined the chat: ${it.oldChatMemberState::class.simpleName} => ${it.newChatMemberState::class.simpleName}")
|
||||
send(it.chat.id, "Welcome ${member.firstName}")
|
||||
}
|
||||
|
||||
onChatMemberLeft {
|
||||
val member = it.member
|
||||
internalLogger.i("${member.firstName} left the chat: ${it.oldChatMemberState::class.simpleName} => ${it.newChatMemberState::class.simpleName}")
|
||||
send(it.chat.id, "Goodbye ${member.firstName}")
|
||||
}
|
||||
|
||||
onChatMemberGotPromoted {
|
||||
val newState = it.newChatMemberState.administratorChatMemberOrThrow()
|
||||
internalLogger.i("${newState.user.firstName} got promoted to ${newState.customTitle ?: "Admin"}: ${it.oldChatMemberState::class.simpleName} => ${it.newChatMemberState::class.simpleName}")
|
||||
send(it.chat.id, "${newState.user.firstName} is now an ${newState.customTitle ?: "Admin"}")
|
||||
}
|
||||
|
||||
onChatMemberGotDemoted {
|
||||
val member = it.member
|
||||
internalLogger.i("${member.firstName} got demoted: ${it.oldChatMemberState::class.simpleName} => ${it.newChatMemberState::class.simpleName}")
|
||||
send(it.chat.id, "${member.firstName} is now got demoted back to member")
|
||||
}
|
||||
|
||||
onChatMemberGotPromotionChanged {
|
||||
val member = it.member
|
||||
val message = "${member.firstName} has the permissions changed: ${it.oldChatMemberState::class.simpleName} => ${it.newChatMemberState::class.simpleName}"
|
||||
internalLogger.i(message)
|
||||
send(it.chat.id, message)
|
||||
}
|
||||
|
||||
onChatMemberUpdated(
|
||||
initialFilter = chatMemberGotRestrictedFilter + chatMemberGotRestrictionsChangedFilter,
|
||||
) {
|
||||
val member = it.member
|
||||
val message = "${member.firstName} has the permissions changed: ${it.oldChatMemberState::class.simpleName} => ${it.newChatMemberState::class.simpleName}"
|
||||
internalLogger.i(message)
|
||||
send(it.chat.id, message)
|
||||
}
|
||||
}.join()
|
||||
}
|
||||
94
MyBot/src/main/kotlin/MyBot.kt
Normal file
94
MyBot/src/main/kotlin/MyBot.kt
Normal file
@@ -0,0 +1,94 @@
|
||||
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.launchLoggingDropExceptions
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
||||
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.removeMyProfilePhoto
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.setMyProfilePhoto
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
||||
import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.sendMessageDraftFlowWithTexts
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitPhotoMessage
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.utils.extensions.sameChat
|
||||
import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile
|
||||
import dev.inmo.tgbotapi.requests.business_connection.InputProfilePhoto
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
/**
|
||||
* This is one of the easiest bots - it will just print information about itself
|
||||
*/
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
val isDebug = args.any { it == "debug" }
|
||||
val isTestServer = args.any { it == "testServer" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val bot = telegramBot(botToken)
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(
|
||||
botToken,
|
||||
CoroutineScope(Dispatchers.Default),
|
||||
testServer = isTestServer,
|
||||
) {
|
||||
val me = bot.getMe()
|
||||
println(me)
|
||||
println(bot.getChat(me))
|
||||
|
||||
onCommand("setMyProfilePhoto") { commandMessage ->
|
||||
reply(commandMessage, "ok, send me new photo")
|
||||
val newPhotoMessage = waitPhotoMessage().filter { potentialPhotoMessage ->
|
||||
potentialPhotoMessage.sameChat(commandMessage)
|
||||
}.first()
|
||||
val draftMessagesChannel = Channel<String>(capacity = 1)
|
||||
|
||||
launchLoggingDropExceptions {
|
||||
sendMessageDraftFlowWithTexts(commandMessage.chat.id, draftMessagesChannel.consumeAsFlow())
|
||||
}.invokeOnCompletion {
|
||||
draftMessagesChannel.close(it)
|
||||
}
|
||||
|
||||
draftMessagesChannel.send("Start downloading photo")
|
||||
val photoFile = downloadFileToTemp(newPhotoMessage.content)
|
||||
|
||||
draftMessagesChannel.send("Photo file have been downloaded. Start set my profile photo")
|
||||
|
||||
val setResult = setMyProfilePhoto(
|
||||
InputProfilePhoto.Static(
|
||||
photoFile.asMultipartFile()
|
||||
)
|
||||
)
|
||||
if (setResult) {
|
||||
reply(commandMessage, "New photo have been set")
|
||||
}
|
||||
}
|
||||
|
||||
onCommand("removeMyProfilePhoto") {
|
||||
runCatchingLogging {
|
||||
if (removeMyProfilePhoto()) {
|
||||
reply(it, "Photo have been removed")
|
||||
}
|
||||
}.onFailure { e ->
|
||||
e.printStackTrace()
|
||||
reply(it, "Something web wrong. See logs for details.")
|
||||
}
|
||||
}
|
||||
}.second.join()
|
||||
}
|
||||
11
PollsBot/README.md
Normal file
11
PollsBot/README.md
Normal 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
21
PollsBot/build.gradle
Normal 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"
|
||||
}
|
||||
179
PollsBot/src/main/kotlin/PollsBot.kt
Normal file
179
PollsBot/src/main/kotlin/PollsBot.kt
Normal file
@@ -0,0 +1,179 @@
|
||||
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.setMyCommands
|
||||
import dev.inmo.tgbotapi.extensions.api.send.polls.sendQuizPoll
|
||||
import dev.inmo.tgbotapi.extensions.api.send.polls.sendRegularPoll
|
||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
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.onPollAnswer
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPollUpdates
|
||||
import dev.inmo.tgbotapi.extensions.utils.customEmojiTextSourceOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgsSources
|
||||
import dev.inmo.tgbotapi.types.BotCommand
|
||||
import dev.inmo.tgbotapi.types.IdChatIdentifier
|
||||
import dev.inmo.tgbotapi.types.PollId
|
||||
import dev.inmo.tgbotapi.types.ReplyParameters
|
||||
import dev.inmo.tgbotapi.types.polls.InputPollOption
|
||||
import dev.inmo.tgbotapi.types.polls.PollAnswer
|
||||
import dev.inmo.tgbotapi.utils.buildEntities
|
||||
import dev.inmo.tgbotapi.utils.customEmoji
|
||||
import dev.inmo.tgbotapi.utils.regular
|
||||
import dev.inmo.tgbotapi.utils.underline
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
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
|
||||
* any update.
|
||||
*
|
||||
* * Use `/anonymous` to take anonymous regular poll
|
||||
* * Use `/public` to take public regular poll
|
||||
*/
|
||||
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 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()
|
||||
}
|
||||
@@ -4,6 +4,8 @@ This repository contains several examples of simple bots which are using Telegra
|
||||
|
||||
## How to use this repository
|
||||
|
||||
***TO RUN NATIVE TARGETS ON LINUX YOU SHOULD INSTALL CURL LIBRARY. FOR EXAMPLE: `sudo apt install libcurl4-gnutls-dev`***
|
||||
|
||||
This repository contains several important things:
|
||||
|
||||
* Example subprojects
|
||||
|
||||
@@ -8,14 +8,29 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName="RandomFileSenderBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
binaries {
|
||||
executable {
|
||||
mainClass.set("RandomFileSenderBotKt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
api "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "$nativePartTemplate"
|
||||
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import dev.inmo.micro_utils.common.filesize
|
||||
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||
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.send.media.sendDocument
|
||||
import dev.inmo.tgbotapi.extensions.api.send.media.sendDocumentsGroup
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.withUploadDocumentAction
|
||||
import dev.inmo.tgbotapi.extensions.api.telegramBot
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommandWithArgs
|
||||
import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile
|
||||
import dev.inmo.tgbotapi.types.BotCommand
|
||||
import dev.inmo.tgbotapi.types.chat.Chat
|
||||
import dev.inmo.tgbotapi.types.files.DocumentFile
|
||||
import dev.inmo.tgbotapi.types.media.TelegramMediaDocument
|
||||
import dev.inmo.tgbotapi.types.mediaCountInMediaGroup
|
||||
import java.io.File
|
||||
|
||||
private const val command = "send_file"
|
||||
|
||||
expect fun pickFile(currentRoot: MPPFile): MPPFile?
|
||||
|
||||
/**
|
||||
* This bot will send files inside of working directory OR from directory in the second argument.
|
||||
* You may send /send_file command to this bot to get random file from the directory OR
|
||||
@@ -25,19 +27,10 @@ private const val command = "send_file"
|
||||
* /send_file and `/send_file 1` will have the same effect - bot will send one random file.
|
||||
* But if you will send `/send_file 5` it will choose 5 random files and send them as group
|
||||
*/
|
||||
suspend fun main(args: Array<String>) {
|
||||
val botToken = args.first()
|
||||
val directoryOrFile = args.getOrNull(1) ?.let { File(it) } ?: File("")
|
||||
suspend fun doRandomFileSenderBot(token: String, folder: MPPFile) {
|
||||
val bot = telegramBot(token)
|
||||
|
||||
fun pickFile(currentRoot: File = directoryOrFile): File? {
|
||||
if (currentRoot.isFile) {
|
||||
return currentRoot
|
||||
} else {
|
||||
return pickFile(currentRoot.listFiles() ?.takeIf { it.isNotEmpty() } ?.random() ?: return null)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun TelegramBot.sendFiles(chat: Chat, files: List<File>) {
|
||||
suspend fun TelegramBot.sendFiles(chat: Chat, files: List<MPPFile>) {
|
||||
when (files.size) {
|
||||
1 -> sendDocument(
|
||||
chat.id,
|
||||
@@ -52,8 +45,6 @@ suspend fun main(args: Array<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
val bot = telegramBot(botToken)
|
||||
|
||||
bot.buildBehaviourWithLongPolling (defaultExceptionsHandler = { it.printStackTrace() }) {
|
||||
onCommandWithArgs(command) { message, args ->
|
||||
|
||||
@@ -62,10 +53,10 @@ suspend fun main(args: Array<String>) {
|
||||
var sent = false
|
||||
|
||||
var left = count
|
||||
val chosen = mutableListOf<File>()
|
||||
val chosen = mutableListOf<MPPFile>()
|
||||
|
||||
while (left > 0) {
|
||||
val picked = pickFile() ?.takeIf { it.filesize > 0 } ?: continue
|
||||
val picked = pickFile(folder) ?.takeIf { it.filesize > 0 } ?: continue
|
||||
chosen.add(picked)
|
||||
left--
|
||||
if (chosen.size >= mediaCountInMediaGroup.last) {
|
||||
10
RandomFileSenderBot/src/jvmMain/kotlin/ActualPickFile.kt
Normal file
10
RandomFileSenderBot/src/jvmMain/kotlin/ActualPickFile.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import java.io.File
|
||||
|
||||
actual fun pickFile(currentRoot: MPPFile): File? {
|
||||
if (currentRoot.isFile) {
|
||||
return currentRoot
|
||||
} else {
|
||||
return pickFile(currentRoot.listFiles() ?.takeIf { it.isNotEmpty() } ?.random() ?: return null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
|
||||
suspend fun main(args: Array<String>) {
|
||||
doRandomFileSenderBot(args.first(), MPPFile(args.getOrNull(1) ?: ""))
|
||||
}
|
||||
10
RandomFileSenderBot/src/nativeMain/kotlin/ActualPickFile.kt
Normal file
10
RandomFileSenderBot/src/nativeMain/kotlin/ActualPickFile.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
import dev.inmo.micro_utils.common.MPPFile
|
||||
import okio.FileSystem
|
||||
|
||||
actual fun pickFile(currentRoot: MPPFile): MPPFile? {
|
||||
if (FileSystem.SYSTEM.exists(currentRoot) && FileSystem.SYSTEM.listOrNull(currentRoot) == null) {
|
||||
return currentRoot
|
||||
} else {
|
||||
return pickFile(FileSystem.SYSTEM.list(currentRoot).takeIf { it.isNotEmpty() } ?.random() ?: return null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okio.Path.Companion.toPath
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runBlocking {
|
||||
doRandomFileSenderBot(args.first(), args.getOrNull(1) ?.toPath() ?: "".toPath())
|
||||
}
|
||||
}
|
||||
9
ReactionsInfoBot/README.md
Normal file
9
ReactionsInfoBot/README.md
Normal 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"
|
||||
```
|
||||
21
ReactionsInfoBot/build.gradle
Normal file
21
ReactionsInfoBot/build.gradle
Normal 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"
|
||||
}
|
||||
68
ReactionsInfoBot/src/main/kotlin/ReactionsInfoBot.kt
Normal file
68
ReactionsInfoBot/src/main/kotlin/ReactionsInfoBot.kt
Normal 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.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.Paid -> regular("• Some paid reaction")
|
||||
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()
|
||||
}
|
||||
@@ -20,6 +20,9 @@ kotlin {
|
||||
browser()
|
||||
binaries.executable()
|
||||
}
|
||||
linuxX64()
|
||||
mingwX64()
|
||||
linuxArm64()
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
|
||||
@@ -1,27 +1,36 @@
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||
import dev.inmo.kslog.common.filter.filtered
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
||||
import dev.inmo.tgbotapi.extensions.api.send.*
|
||||
import dev.inmo.tgbotapi.extensions.api.send.media.*
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups
|
||||
import dev.inmo.tgbotapi.extensions.api.send.withTypingAction
|
||||
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.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
|
||||
import dev.inmo.tgbotapi.extensions.utils.possiblyWithEffectMessageOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.shortcuts.executeUnsafe
|
||||
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
||||
import dev.inmo.tgbotapi.types.ReplyParameters
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.BusinessContentMessage
|
||||
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||
import dev.inmo.tgbotapi.utils.DefaultKTgBotAPIKSLog
|
||||
import dev.inmo.tgbotapi.utils.extensions.threadIdOrNull
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
|
||||
suspend fun activateResenderBot(
|
||||
token: String,
|
||||
print: (Any) -> Unit
|
||||
) {
|
||||
val bot = telegramBot(token)
|
||||
|
||||
print(bot.getMe())
|
||||
|
||||
bot.buildBehaviourWithLongPolling(CoroutineScope(currentCoroutineContext() + SupervisorJob())) {
|
||||
telegramBotWithBehaviourAndLongPolling(
|
||||
token,
|
||||
scope = CoroutineScope(currentCoroutineContext() + SupervisorJob()),
|
||||
) {
|
||||
onContentMessage(
|
||||
subcontextUpdatesFilter = MessageFilterByChat,
|
||||
initialFilter = { it !is BusinessContentMessage<*> || !it.sentByBusinessConnectionOwner }
|
||||
) {
|
||||
val chat = it.chat
|
||||
|
||||
@@ -29,8 +38,15 @@ suspend fun activateResenderBot(
|
||||
executeUnsafe(
|
||||
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
|
||||
)
|
||||
},
|
||||
effectId = it.possiblyWithEffectMessageOrNull()?.effectId
|
||||
)
|
||||
) {
|
||||
it.forEach(print)
|
||||
@@ -40,8 +56,9 @@ suspend fun activateResenderBot(
|
||||
println("Answer info: $answer")
|
||||
}
|
||||
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
|
||||
println(it)
|
||||
}
|
||||
}.join()
|
||||
print(bot.getMe())
|
||||
}.second.join()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.LogLevel
|
||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||
import dev.inmo.kslog.common.setDefaultKSLog
|
||||
|
||||
suspend fun main(args: Array<String>) {
|
||||
val isDebug = args.getOrNull(1) == "debug"
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
activateResenderBot(args.first()) {
|
||||
println(it)
|
||||
}
|
||||
|
||||
28
ResenderBot/native_launcher/build.gradle
Normal file
28
ResenderBot/native_launcher/build.gradle
Normal file
@@ -0,0 +1,28 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
}
|
||||
|
||||
apply from: "$nativePartTemplate"
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
|
||||
api project(":ResenderBot:ResenderBotLib")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
fun main(vararg args: String) {
|
||||
runBlocking {
|
||||
activateResenderBot(args.first()) {
|
||||
println(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
12
RightsChangerBot/README.md
Normal file
12
RightsChangerBot/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# RightsChanger
|
||||
|
||||
All the commands should be called with reply to some common user.
|
||||
|
||||
* Use `/simple` with bot to get request buttons for non-independent permissions change
|
||||
* Use `/granular` with bot to get request buttons for independent permissions change
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN allowed_user_id_long"
|
||||
```
|
||||
22
RightsChangerBot/build.gradle
Normal file
22
RightsChangerBot/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName="RightsChangerKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
implementation 'io.ktor:ktor-client-logging-jvm:3.2.3'
|
||||
}
|
||||
540
RightsChangerBot/src/main/kotlin/RightsChanger.kt
Normal file
540
RightsChangerBot/src/main/kotlin/RightsChanger.kt
Normal file
@@ -0,0 +1,540 @@
|
||||
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.buildBehaviourWithFSMAndStartLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitChatSharedEventsMessages
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitCommandMessage
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitUserSharedEventsMessages
|
||||
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.*
|
||||
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.AdministratorChatMember
|
||||
import dev.inmo.tgbotapi.types.chat.member.ChatCommonAdministratorRights
|
||||
import dev.inmo.tgbotapi.types.commands.BotCommandScope
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.AccessibleMessage
|
||||
import dev.inmo.tgbotapi.types.request.RequestId
|
||||
import dev.inmo.tgbotapi.utils.*
|
||||
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
|
||||
}
|
||||
|
||||
@OptIn(PreviewFeature::class)
|
||||
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(RawChatId(args[1].toLong()))
|
||||
|
||||
fun Boolean?.allowedSymbol() = when (this) {
|
||||
true -> "✅"
|
||||
false -> "❌"
|
||||
null -> ""
|
||||
}
|
||||
|
||||
val granularDataPrefix = "granular"
|
||||
val messagesToggleGranularData = "$granularDataPrefix messages"
|
||||
val otherMessagesToggleGranularData = "$granularDataPrefix other messages"
|
||||
val audiosToggleGranularData = "$granularDataPrefix audios"
|
||||
val voicesToggleGranularData = "$granularDataPrefix voices"
|
||||
val videosToggleGranularData = "$granularDataPrefix videos"
|
||||
val videoNotesToggleGranularData = "$granularDataPrefix video notes"
|
||||
val photosToggleGranularData = "$granularDataPrefix photos"
|
||||
val webPagePreviewToggleGranularData = "$granularDataPrefix web page preview"
|
||||
val pollsToggleGranularData = "$granularDataPrefix polls"
|
||||
val documentsToggleGranularData = "$granularDataPrefix documents"
|
||||
|
||||
val commonDataPrefix = "common"
|
||||
val pollsToggleCommonData = "$commonDataPrefix polls"
|
||||
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.restrictedMemberChatMemberOrNull() ?: chatMember.whenMemberChatMember {
|
||||
getChat(chatId).extendedGroupChatOrNull() ?.permissions
|
||||
}
|
||||
}
|
||||
fun buildGranularKeyboard(
|
||||
permissions: ChatPermissions
|
||||
): InlineKeyboardMarkup {
|
||||
return inlineKeyboard {
|
||||
row {
|
||||
dataButton("Send messages${permissions.canSendMessages.allowedSymbol()}", messagesToggleGranularData)
|
||||
dataButton(
|
||||
"Send other messages${permissions.canSendOtherMessages.allowedSymbol()}",
|
||||
otherMessagesToggleGranularData
|
||||
)
|
||||
}
|
||||
row {
|
||||
dataButton("Send audios${permissions.canSendAudios.allowedSymbol()}", audiosToggleGranularData)
|
||||
dataButton("Send voices${permissions.canSendVoiceNotes.allowedSymbol()}", voicesToggleGranularData)
|
||||
}
|
||||
row {
|
||||
dataButton("Send videos${permissions.canSendVideos.allowedSymbol()}", videosToggleGranularData)
|
||||
dataButton(
|
||||
"Send video notes${permissions.canSendVideoNotes.allowedSymbol()}",
|
||||
videoNotesToggleGranularData
|
||||
)
|
||||
}
|
||||
row {
|
||||
dataButton("Send photos${permissions.canSendPhotos.allowedSymbol()}", photosToggleGranularData)
|
||||
dataButton(
|
||||
"Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}",
|
||||
webPagePreviewToggleGranularData
|
||||
)
|
||||
}
|
||||
row {
|
||||
dataButton("Send polls${permissions.canSendPolls.allowedSymbol()}", pollsToggleGranularData)
|
||||
dataButton("Send documents${permissions.canSendDocuments.allowedSymbol()}", documentsToggleGranularData)
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
return inlineKeyboard {
|
||||
row {
|
||||
dataButton("Send polls${permissions.canSendPolls.allowedSymbol()}", pollsToggleCommonData)
|
||||
}
|
||||
row {
|
||||
dataButton("Send other messages${permissions.canSendOtherMessages.allowedSymbol()}", otherMessagesToggleCommonData)
|
||||
}
|
||||
row {
|
||||
dataButton("Add web preview${permissions.canAddWebPagePreviews.allowedSymbol()}", webPagePreviewToggleCommonData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bot.buildBehaviourWithFSMAndStartLongPolling<UserRetrievingStep>(
|
||||
defaultExceptionsHandler = {
|
||||
it.printStackTrace()
|
||||
},
|
||||
) {
|
||||
onCommand(
|
||||
"simple",
|
||||
initialFilter = { it.chat is PublicChat && it.fromUserMessageOrNull()?.user?.id == allowedAdmin }
|
||||
) {
|
||||
val replyMessage = it.replyTo
|
||||
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 ChannelChat || (it.chat is PublicChat && it.fromUserMessageOrNull()?.user?.id == allowedAdmin)
|
||||
}
|
||||
) {
|
||||
val replyMessage = it.replyTo
|
||||
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 userId = messageReply.user.id
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
otherMessagesToggleGranularData -> {
|
||||
permissions.copyGranular(
|
||||
canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
audiosToggleGranularData -> {
|
||||
permissions.copyGranular(
|
||||
canSendAudios = permissions.canSendAudios?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
voicesToggleGranularData -> {
|
||||
permissions.copyGranular(
|
||||
canSendVoiceNotes = permissions.canSendVoiceNotes?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
videosToggleGranularData -> {
|
||||
permissions.copyGranular(
|
||||
canSendVideos = permissions.canSendVideos?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
videoNotesToggleGranularData -> {
|
||||
permissions.copyGranular(
|
||||
canSendVideoNotes = permissions.canSendVideoNotes?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
photosToggleGranularData -> {
|
||||
permissions.copyGranular(
|
||||
canSendPhotos = permissions.canSendPhotos?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
webPagePreviewToggleGranularData -> {
|
||||
permissions.copyGranular(
|
||||
canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
pollsToggleGranularData -> {
|
||||
permissions.copyGranular(
|
||||
canSendPolls = permissions.canSendPolls?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
documentsToggleGranularData -> {
|
||||
permissions.copyGranular(
|
||||
canSendDocuments = permissions.canSendDocuments?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
else -> permissions.copyGranular()
|
||||
}
|
||||
|
||||
restrictChatMember(
|
||||
it.message.chat.id,
|
||||
userId,
|
||||
permissions = newPermission,
|
||||
useIndependentChatPermissions = true
|
||||
)
|
||||
|
||||
edit(
|
||||
it.message,
|
||||
replyMarkup = buildGranularKeyboard(it.message.chat.id.toChatId(), userId)
|
||||
?: return@onMessageDataCallbackQuery
|
||||
)
|
||||
}
|
||||
|
||||
onMessageDataCallbackQuery(
|
||||
Regex("^${commonDataPrefix}.*"),
|
||||
initialFilter = { it.user.id == allowedAdmin }
|
||||
) {
|
||||
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 newPermission = when (it.data) {
|
||||
pollsToggleCommonData -> {
|
||||
permissions.copyCommon(
|
||||
canSendPolls = permissions.canSendPolls?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
otherMessagesToggleCommonData -> {
|
||||
permissions.copyCommon(
|
||||
canSendOtherMessages = permissions.canSendOtherMessages?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
webPagePreviewToggleCommonData -> {
|
||||
permissions.copyCommon(
|
||||
canAddWebPagePreviews = permissions.canAddWebPagePreviews?.let { !it } ?: false
|
||||
)
|
||||
}
|
||||
|
||||
else -> permissions.copyCommon()
|
||||
}
|
||||
|
||||
restrictChatMember(
|
||||
it.message.chat.id,
|
||||
userId,
|
||||
permissions = newPermission,
|
||||
useIndependentChatPermissions = false
|
||||
)
|
||||
|
||||
edit(
|
||||
it.message,
|
||||
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 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()
|
||||
}
|
||||
9
StarTransactionsBot/README.md
Normal file
9
StarTransactionsBot/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# StarTransactionsBot
|
||||
|
||||
This bot basically have no any useful behaviour, but you may customize it as a playground
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN"
|
||||
```
|
||||
21
StarTransactionsBot/build.gradle
Normal file
21
StarTransactionsBot/build.gradle
Normal 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="StarTransactionsBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
185
StarTransactionsBot/src/main/kotlin/StarTransactionsBot.kt
Normal file
185
StarTransactionsBot/src/main/kotlin/StarTransactionsBot.kt
Normal file
@@ -0,0 +1,185 @@
|
||||
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.answers.payments.answerPreCheckoutQueryOk
|
||||
import dev.inmo.tgbotapi.extensions.api.edit.edit
|
||||
import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp
|
||||
import dev.inmo.tgbotapi.extensions.api.get.getStarTransactions
|
||||
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.extensions.sameChat
|
||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
|
||||
import dev.inmo.tgbotapi.extensions.utils.withContentOrNull
|
||||
import dev.inmo.tgbotapi.requests.abstracts.asMultipartFile
|
||||
import dev.inmo.tgbotapi.types.ChatId
|
||||
import dev.inmo.tgbotapi.types.RawChatId
|
||||
import dev.inmo.tgbotapi.types.UserId
|
||||
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup
|
||||
import dev.inmo.tgbotapi.types.files.*
|
||||
import dev.inmo.tgbotapi.types.media.TelegramPaidMediaPhoto
|
||||
import dev.inmo.tgbotapi.types.media.TelegramPaidMediaVideo
|
||||
import dev.inmo.tgbotapi.types.media.toTelegramPaidMediaPhoto
|
||||
import dev.inmo.tgbotapi.types.media.toTelegramPaidMediaVideo
|
||||
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||
import dev.inmo.tgbotapi.types.message.textsources.TextSourcesList
|
||||
import dev.inmo.tgbotapi.types.payments.LabeledPrice
|
||||
import dev.inmo.tgbotapi.types.payments.stars.StarTransaction
|
||||
import dev.inmo.tgbotapi.types.request.RequestId
|
||||
import dev.inmo.tgbotapi.utils.bold
|
||||
import dev.inmo.tgbotapi.utils.buildEntities
|
||||
import dev.inmo.tgbotapi.utils.regular
|
||||
import dev.inmo.tgbotapi.utils.row
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
/**
|
||||
* An example bot that interacts with Telegram Stars API (used for payments)
|
||||
*/
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
val adminUserId = args.getOrNull(1) ?.toLongOrNull() ?.let(::RawChatId) ?.let(::ChatId) ?: error("Pass user-admin for full access to the bot")
|
||||
|
||||
val isDebug = args.any { it == "debug" }
|
||||
val isTestServer = args.any { it == "testServer" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(botToken, CoroutineScope(Dispatchers.IO), testServer = isTestServer) {
|
||||
val payload = "sample payload"
|
||||
command("start") {
|
||||
reply(
|
||||
it,
|
||||
price = LabeledPrice("1", 1L),
|
||||
title = "Sample",
|
||||
description = "Sample description",
|
||||
payload = payload,
|
||||
replyMarkup = flatInlineKeyboard {
|
||||
payButton("Pay")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
onPreCheckoutQuery(initialFilter = { it.invoicePayload == payload }) {
|
||||
answerPreCheckoutQueryOk(it)
|
||||
}
|
||||
|
||||
val transactionsDataPrefix = "getStarTransactions"
|
||||
fun buildTransactionsData(offset: Int, limit: Int = 10) = "$transactionsDataPrefix $offset $limit"
|
||||
fun parseTransactionsData(data: String): Pair<Int, Int> = data.split(" ").drop(1).let {
|
||||
it.first().toInt() to it.last().toInt()
|
||||
}
|
||||
suspend fun buildStarTransactionsPage(offset: Int, limit: Int = 10): Pair<TextSourcesList, InlineKeyboardMarkup> {
|
||||
val transactions = getStarTransactions(offset, limit)
|
||||
return buildEntities {
|
||||
transactions.transactions.forEach {
|
||||
regular("Transaction Id: ") + bold(it.id.string) + "\n"
|
||||
regular("Date: ") + bold(it.date.asDate.toStringDefault()) + "\n"
|
||||
regular("Amount: ") + bold(it.amount.toString()) + "\n"
|
||||
when (it) {
|
||||
is StarTransaction.Incoming -> {
|
||||
regular("Type: ") + bold("incoming") + "\n"
|
||||
regular("Partner: ") + bold(it.partner.type) + "\n"
|
||||
}
|
||||
is StarTransaction.Outgoing -> {
|
||||
regular("Type: ") + bold("outgoing") + "\n"
|
||||
regular("Partner: ") + bold(it.partner.type) + "\n"
|
||||
}
|
||||
is StarTransaction.Unknown -> {
|
||||
regular("Type: ") + bold("unknown") + "\n"
|
||||
regular("Partner: ") + bold(it.partner.type) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
} to inlineKeyboard {
|
||||
row {
|
||||
val prevOffset = (offset - limit).coerceAtLeast(0)
|
||||
if (prevOffset < offset) {
|
||||
dataButton("<", buildTransactionsData(prevOffset, limit))
|
||||
}
|
||||
val nextOffset = (offset + limit)
|
||||
dataButton(">", buildTransactionsData(nextOffset, limit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCommand("transactions", initialFilter = { it.sameChat(adminUserId) }) {
|
||||
val (text, keyboard) = buildStarTransactionsPage(0)
|
||||
|
||||
reply(it, text, replyMarkup = keyboard)
|
||||
}
|
||||
|
||||
onMessageDataCallbackQuery(Regex("$transactionsDataPrefix \\d+ \\d+")) {
|
||||
val (offset, limit) = parseTransactionsData(it.data)
|
||||
val (text, keyboard) = buildStarTransactionsPage(offset, limit)
|
||||
edit(
|
||||
it.message.withContentOrNull<TextContent>() ?: return@onMessageDataCallbackQuery,
|
||||
text,
|
||||
replyMarkup = keyboard,
|
||||
)
|
||||
}
|
||||
|
||||
onVisualGalleryMessages {
|
||||
send(
|
||||
it.chat,
|
||||
1,
|
||||
it.content.group.mapNotNull {
|
||||
val file = downloadFileToTemp(it.content.media)
|
||||
when (it.content.media) {
|
||||
is VideoFile -> {
|
||||
TelegramPaidMediaVideo(
|
||||
file.asMultipartFile()
|
||||
)
|
||||
}
|
||||
is PhotoSize -> {
|
||||
TelegramPaidMediaPhoto(
|
||||
file.asMultipartFile()
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
},
|
||||
it.content.textSources,
|
||||
showCaptionAboveMedia = true
|
||||
)
|
||||
}
|
||||
|
||||
onPhoto {
|
||||
send(
|
||||
it.chat,
|
||||
1,
|
||||
listOf(it.content.media.toTelegramPaidMediaPhoto())
|
||||
)
|
||||
}
|
||||
|
||||
onVideo {
|
||||
send(
|
||||
it.chat,
|
||||
1,
|
||||
listOf(it.content.media.toTelegramPaidMediaVideo())
|
||||
)
|
||||
}
|
||||
|
||||
onPaidMediaInfoContent {
|
||||
println(it)
|
||||
}
|
||||
|
||||
onRefundedPayment {
|
||||
reply(
|
||||
it,
|
||||
"Received your refund: ${it.chatEvent.payment}"
|
||||
)
|
||||
}
|
||||
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) { println(it) }
|
||||
}.second.join()
|
||||
}
|
||||
@@ -1,17 +1,25 @@
|
||||
import dev.inmo.micro_utils.coroutines.defaultSafelyWithoutExceptionHandler
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
||||
import dev.inmo.tgbotapi.extensions.api.get.*
|
||||
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.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.get.getCustomEmojiStickerOrNull
|
||||
import dev.inmo.tgbotapi.extensions.api.get.getStickerSet
|
||||
import dev.inmo.tgbotapi.extensions.api.get.getStickerSetOrNull
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.withTypingAction
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSticker
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onText
|
||||
import dev.inmo.tgbotapi.types.StickerType
|
||||
import dev.inmo.tgbotapi.types.message.textsources.*
|
||||
import dev.inmo.tgbotapi.types.message.textsources.CustomEmojiTextSource
|
||||
import dev.inmo.tgbotapi.types.message.textsources.regularTextSource
|
||||
import dev.inmo.tgbotapi.types.message.textsources.separateForText
|
||||
import dev.inmo.tgbotapi.types.stickers.StickerSet
|
||||
import dev.inmo.tgbotapi.utils.bold
|
||||
import dev.inmo.tgbotapi.utils.buildEntities
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
|
||||
fun StickerSet?.buildInfo() = buildEntities {
|
||||
if (this@buildInfo == null) {
|
||||
@@ -26,7 +34,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,14 +55,14 @@ suspend fun activateStickerInfoBot(
|
||||
withTypingAction(it.chat) {
|
||||
it.content.textSources.mapNotNull {
|
||||
if (it is CustomEmojiTextSource) {
|
||||
getCustomEmojiStickerOrNull(it.customEmojiId) ?.stickerSetName
|
||||
getCustomEmojiStickerOrNull(it.customEmojiId)?.stickerSetName
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.distinct().map {
|
||||
getStickerSet(it)
|
||||
}.distinct().flatMap {
|
||||
it.buildInfo() + regular("\n")
|
||||
it.buildInfo() + regularTextSource("\n")
|
||||
}.separateForText().map { entities ->
|
||||
reply(it, entities)
|
||||
}
|
||||
@@ -68,7 +76,7 @@ suspend fun activateStickerInfoBot(
|
||||
)
|
||||
}
|
||||
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(scope = this) {
|
||||
println(it)
|
||||
}
|
||||
}.join()
|
||||
|
||||
9
StickerSetHandler/README.md
Normal file
9
StickerSetHandler/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# StickerSetHandler
|
||||
|
||||
Send sticker to this bot to form your own stickers set. Send /delete to delete this sticker set
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
./gradlew run --args="TOKEN"
|
||||
```
|
||||
21
StickerSetHandler/build.gradle
Normal file
21
StickerSetHandler/build.gradle
Normal 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="StickerSetHandlerBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
118
StickerSetHandler/src/main/kotlin/StickerSetHandlerBot.kt
Normal file
118
StickerSetHandler/src/main/kotlin/StickerSetHandlerBot.kt
Normal file
@@ -0,0 +1,118 @@
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.files.downloadFileToTemp
|
||||
import dev.inmo.tgbotapi.extensions.api.get.getStickerSet
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.stickers.addStickerToSet
|
||||
import dev.inmo.tgbotapi.extensions.api.stickers.createNewStickerSet
|
||||
import dev.inmo.tgbotapi.extensions.api.stickers.deleteStickerSet
|
||||
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.onSticker
|
||||
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.CustomEmojiSticker
|
||||
import dev.inmo.tgbotapi.types.files.MaskSticker
|
||||
import dev.inmo.tgbotapi.types.files.RegularSticker
|
||||
import dev.inmo.tgbotapi.types.files.UnknownSticker
|
||||
import dev.inmo.tgbotapi.types.toChatId
|
||||
import dev.inmo.tgbotapi.utils.botCommand
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
/**
|
||||
* Send sticker to this bot to form your own stickers set. Send /delete to delete this sticker set
|
||||
*/
|
||||
suspend fun main(args: Array<String>) {
|
||||
telegramBotWithBehaviourAndLongPolling(
|
||||
args.first(),
|
||||
scope = CoroutineScope(Dispatchers.IO),
|
||||
defaultExceptionsHandler = {
|
||||
it.printStackTrace()
|
||||
}
|
||||
) {
|
||||
val me = getMe()
|
||||
fun Chat.stickerSetName() = StickerSetName("s${id.chatId}_by_${me.username ?.withoutAt}")
|
||||
onCommand("start") {
|
||||
reply(it) {
|
||||
botCommand("delete") + " - to clear stickers"
|
||||
}
|
||||
}
|
||||
onCommand("delete") {
|
||||
val deleted = runCatchingSafely {
|
||||
deleteStickerSet(it.chat.stickerSetName())
|
||||
}.getOrElse { false }
|
||||
|
||||
if (deleted) {
|
||||
reply(it, "Deleted")
|
||||
} else {
|
||||
reply(it, "Can't delete for some of reason")
|
||||
}
|
||||
}
|
||||
onSticker {
|
||||
val stickerSetName = it.chat.stickerSetName()
|
||||
val sticker = it.content.media
|
||||
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()
|
||||
)
|
||||
is UnknownSticker -> return@onSticker
|
||||
}
|
||||
runCatchingSafely {
|
||||
getStickerSet(stickerSetName)
|
||||
}.onSuccess { stickerSet ->
|
||||
runCatching {
|
||||
addStickerToSet(it.chat.id.toChatId(), stickerSet.name, newSticker).also { _ ->
|
||||
reply(
|
||||
it,
|
||||
getStickerSet(stickerSetName).stickers.last()
|
||||
)
|
||||
}
|
||||
}.onFailure { exception ->
|
||||
exception.printStackTrace()
|
||||
reply(
|
||||
it,
|
||||
"Unable to add sticker in stickerset"
|
||||
)
|
||||
}
|
||||
}.onFailure { exception ->
|
||||
createNewStickerSet(
|
||||
it.chat.id.toChatId(),
|
||||
stickerSetName.string,
|
||||
"Sticker set by ${me.firstName}",
|
||||
listOf(
|
||||
newSticker
|
||||
),
|
||||
(sticker as? CustomEmojiSticker) ?.needsRepainting ?: false
|
||||
).also { _ ->
|
||||
reply(
|
||||
it,
|
||||
getStickerSet(stickerSetName).stickers.first()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||
println(it)
|
||||
}
|
||||
}.second.join()
|
||||
}
|
||||
9
SuggestedPosts/README.md
Normal file
9
SuggestedPosts/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# StickerSetHandler
|
||||
|
||||
Send sticker to this bot to form your own stickers set. Send /delete to delete this sticker set
|
||||
|
||||
## How to run
|
||||
|
||||
```bash
|
||||
./gradlew run --args="TOKEN"
|
||||
```
|
||||
21
SuggestedPosts/build.gradle
Normal file
21
SuggestedPosts/build.gradle
Normal 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="SuggestedPostsBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
140
SuggestedPosts/src/main/kotlin/SuggestedPostsBot.kt
Normal file
140
SuggestedPosts/src/main/kotlin/SuggestedPostsBot.kt
Normal file
@@ -0,0 +1,140 @@
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.LogLevel
|
||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||
import dev.inmo.kslog.common.setDefaultKSLog
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMyStarBalance
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.resend
|
||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
import dev.inmo.tgbotapi.extensions.api.suggested.approveSuggestedPost
|
||||
import dev.inmo.tgbotapi.extensions.api.suggested.declineSuggestedPost
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextData
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildSubcontextInitialAction
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitSuggestedPostApproved
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitSuggestedPostDeclined
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChannelDirectMessagesConfigurationChanged
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onContentMessage
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostApprovalFailed
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostApproved
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostDeclined
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostPaid
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onSuggestedPostRefunded
|
||||
import dev.inmo.tgbotapi.extensions.utils.channelDirectMessagesContentMessageOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.previewChannelDirectMessagesChatOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.suggestedChannelDirectMessagesContentMessageOrNull
|
||||
import dev.inmo.tgbotapi.types.message.SuggestedPostParameters
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.ChannelPaidPost
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||
import dev.inmo.tgbotapi.utils.firstOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
/**
|
||||
* This place can be the playground for your code.
|
||||
*/
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
|
||||
val isDebug = args.any { it == "debug" }
|
||||
val isTestServer = args.any { it == "testServer" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(
|
||||
botToken,
|
||||
CoroutineScope(Dispatchers.Default),
|
||||
testServer = isTestServer,
|
||||
) {
|
||||
// start here!!
|
||||
val me = getMe()
|
||||
println(me)
|
||||
|
||||
onCommand("start") {
|
||||
println(getChat(it.chat))
|
||||
}
|
||||
|
||||
onContentMessage {
|
||||
val message = it.channelDirectMessagesContentMessageOrNull() ?: return@onContentMessage
|
||||
val chat = getChat(it.chat)
|
||||
println(chat)
|
||||
|
||||
resend(
|
||||
message.chat.id,
|
||||
message.content,
|
||||
suggestedPostParameters = SuggestedPostParameters()
|
||||
)
|
||||
}
|
||||
|
||||
onContentMessage(
|
||||
subcontextUpdatesFilter = { _, _ -> true } // important to not miss updates in channel for waitSuggestedPost events
|
||||
) { message ->
|
||||
val suggestedPost = message.suggestedChannelDirectMessagesContentMessageOrNull() ?: return@onContentMessage
|
||||
|
||||
firstOf(
|
||||
{
|
||||
waitSuggestedPostApproved().filter {
|
||||
it.suggestedPostMessage ?.chat ?.id == message.chat.id
|
||||
}.first()
|
||||
},
|
||||
{
|
||||
waitSuggestedPostDeclined().filter {
|
||||
it.suggestedPostMessage ?.chat ?.id == message.chat.id
|
||||
}.first()
|
||||
},
|
||||
{
|
||||
for (i in 0 until 3) {
|
||||
delay(1000L)
|
||||
send(suggestedPost.chat, "${3 - i}")
|
||||
}
|
||||
declineSuggestedPost(suggestedPost)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
onContentMessage(initialFilter = { it is ChannelPaidPost<*> }) {
|
||||
println(it)
|
||||
}
|
||||
|
||||
onSuggestedPostPaid {
|
||||
println(it)
|
||||
reply(it, "Paid")
|
||||
}
|
||||
onSuggestedPostApproved {
|
||||
println(it)
|
||||
reply(it, "Approved")
|
||||
}
|
||||
onSuggestedPostDeclined {
|
||||
println(it)
|
||||
reply(it, "Declined")
|
||||
}
|
||||
onSuggestedPostRefunded {
|
||||
println(it)
|
||||
reply(it, "Refunded")
|
||||
}
|
||||
onSuggestedPostApprovalFailed {
|
||||
println(it)
|
||||
reply(it, "Approval failed")
|
||||
}
|
||||
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
||||
println(it)
|
||||
}
|
||||
}.second.join()
|
||||
}
|
||||
21
TagsBot/build.gradle
Normal file
21
TagsBot/build.gradle
Normal 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="TagsBotKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
101
TagsBot/src/main/kotlin/TagsBot.kt
Normal file
101
TagsBot/src/main/kotlin/TagsBot.kt
Normal file
@@ -0,0 +1,101 @@
|
||||
import dev.inmo.kslog.common.KSLog
|
||||
import dev.inmo.kslog.common.LogLevel
|
||||
import dev.inmo.kslog.common.defaultMessageFormatter
|
||||
import dev.inmo.kslog.common.setDefaultKSLog
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.abstracts.FromUser
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.business.getBusinessAccountGiftsFlow
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.members.promoteChatAdministrator
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.members.promoteChatMember
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.members.setChatMemberTag
|
||||
import dev.inmo.tgbotapi.extensions.api.gifts.getChatGiftsFlow
|
||||
import dev.inmo.tgbotapi.extensions.api.gifts.getUserGiftsFlow
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.withTypingAction
|
||||
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.behaviour_builder.triggers_handling.onGiveawayCompleted
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayContent
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayCreated
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGiveawayWinners
|
||||
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.sender_chat
|
||||
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.sender_tag
|
||||
import dev.inmo.tgbotapi.extensions.utils.fromUserOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.groupContentMessageOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.idChatIdentifierOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.potentiallyFromUserGroupContentMessageOrNull
|
||||
import dev.inmo.tgbotapi.types.UserTag
|
||||
import dev.inmo.tgbotapi.types.chat.BusinessChat
|
||||
import dev.inmo.tgbotapi.types.chat.PrivateChat
|
||||
import dev.inmo.tgbotapi.types.chat.PublicChat
|
||||
import dev.inmo.tgbotapi.types.chat.UnknownChatType
|
||||
import dev.inmo.tgbotapi.types.gifts.OwnedGift
|
||||
import dev.inmo.tgbotapi.types.message.abstracts.OptionallyFromUserMessage
|
||||
import dev.inmo.tgbotapi.types.message.textsources.splitForText
|
||||
import dev.inmo.tgbotapi.utils.bold
|
||||
import dev.inmo.tgbotapi.utils.buildEntities
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
suspend fun main(vararg args: String) {
|
||||
val botToken = args.first()
|
||||
|
||||
val isDebug = args.any { it == "debug" }
|
||||
val isTestServer = args.any { it == "testServer" }
|
||||
|
||||
if (isDebug) {
|
||||
setDefaultKSLog(
|
||||
KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
|
||||
println(defaultMessageFormatter(level, tag, message, throwable))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
telegramBotWithBehaviourAndLongPolling(botToken, testServer = isTestServer) {
|
||||
// start here!!
|
||||
val me = getMe()
|
||||
println(me)
|
||||
|
||||
onCommand("setChatMemberTag", requireOnlyCommandInMessage = false) {
|
||||
val reply = it.replyTo ?.groupContentMessageOrNull() ?: return@onCommand
|
||||
val title = it.content.text.removePrefix("/setChatMemberTag").removePrefix(" ")
|
||||
setChatMemberTag(
|
||||
chatId = reply.chat.id,
|
||||
userId = reply.fromUserOrNull() ?.user ?.id ?: return@onCommand,
|
||||
tag = UserTag(title)
|
||||
)
|
||||
}
|
||||
|
||||
onCommand("setCanManageTags", requireOnlyCommandInMessage = false) {
|
||||
val reply = it.replyTo ?.groupContentMessageOrNull() ?: return@onCommand
|
||||
val setOrUnset = it.content.text.removePrefix("/setCanManageTags").removePrefix(" ") == "true"
|
||||
promoteChatAdministrator(
|
||||
it.chat.id,
|
||||
reply.fromUserOrNull() ?.user ?.id ?: return@onCommand,
|
||||
canManageTags = setOrUnset
|
||||
)
|
||||
}
|
||||
|
||||
onCommand("removeChatMemberTag") {
|
||||
val reply = it.replyTo ?.groupContentMessageOrNull() ?: return@onCommand
|
||||
setChatMemberTag(
|
||||
chatId = reply.chat.id,
|
||||
userId = reply.fromUserOrNull() ?.user ?.id ?: return@onCommand,
|
||||
tag = null
|
||||
)
|
||||
}
|
||||
|
||||
onContentMessage {
|
||||
val groupContentMessage = it.potentiallyFromUserGroupContentMessageOrNull() ?: return@onContentMessage
|
||||
reply(it, "Tag after casting: ${groupContentMessage.senderTag}")
|
||||
reply(it, "Tag by getting via risk API: ${it.sender_tag}")
|
||||
}
|
||||
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
||||
println(it)
|
||||
}
|
||||
}.second.join()
|
||||
}
|
||||
9
TopicsHandling/README.md
Normal file
9
TopicsHandling/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# HelloBot
|
||||
|
||||
The main purpose of this bot is just to answer "Oh, hi, " and add user mention here
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN"
|
||||
```
|
||||
21
TopicsHandling/build.gradle
Normal file
21
TopicsHandling/build.gradle
Normal 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="TopicsHandlingKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
219
TopicsHandling/src/main/kotlin/TopicsHandling.kt
Normal file
219
TopicsHandling/src/main/kotlin/TopicsHandling.kt
Normal file
@@ -0,0 +1,219 @@
|
||||
import com.benasher44.uuid.uuid4
|
||||
import dev.inmo.kslog.common.w
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingLogging
|
||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.forum.*
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.telegramBotWithBehaviourAndLongPolling
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicClosed
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicCreated
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicEdited
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onForumTopicReopened
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGeneralForumTopicHidden
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onGeneralForumTopicUnhidden
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPrivateForumTopicCreated
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPrivateForumTopicEdited
|
||||
import dev.inmo.tgbotapi.extensions.utils.forumChatOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.forumContentMessageOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.privateChatOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.privateForumChatOrNull
|
||||
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.flushAccumulatedUpdates
|
||||
import dev.inmo.tgbotapi.types.BotCommand
|
||||
import dev.inmo.tgbotapi.types.ForumTopic
|
||||
import dev.inmo.tgbotapi.types.chat.PrivateChat
|
||||
import dev.inmo.tgbotapi.types.commands.BotCommandScope
|
||||
import io.ktor.client.plugins.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
suspend fun main(vararg args: String) {
|
||||
telegramBotWithBehaviourAndLongPolling(
|
||||
args.first(),
|
||||
CoroutineScope(Dispatchers.Default),
|
||||
defaultExceptionsHandler = {
|
||||
it.printStackTrace()
|
||||
},
|
||||
builder = {
|
||||
client = client.config {
|
||||
install(HttpTimeout) {
|
||||
requestTimeoutMillis = 30000
|
||||
socketTimeoutMillis = 30000
|
||||
connectTimeoutMillis = 30000
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
suspend fun TelegramBot.isPrivateForumsEnabled(): Boolean {
|
||||
val me = getMe()
|
||||
if (me.hasTopicsEnabled == false) {
|
||||
Log.w("private forums are disabled. That means that they will not work in private chats")
|
||||
}
|
||||
return me.hasTopicsEnabled
|
||||
}
|
||||
println()
|
||||
flushAccumulatedUpdates()
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
||||
println(it)
|
||||
}
|
||||
onCommand("start_test_topics") {
|
||||
if (it.chat is PrivateChat && isPrivateForumsEnabled() == false) {
|
||||
return@onCommand
|
||||
}
|
||||
val forumTopic = createForumTopic(
|
||||
it.chat,
|
||||
"Test",
|
||||
ForumTopic.GREEN
|
||||
)
|
||||
|
||||
reply(it, "Test topic has been created")
|
||||
|
||||
delay(1000L)
|
||||
editForumTopic(
|
||||
it.chat.id,
|
||||
forumTopic.messageThreadId,
|
||||
"Test 01"
|
||||
)
|
||||
|
||||
reply(it, "Test topic has changed its name to Test 01")
|
||||
|
||||
if (it.chat.privateChatOrNull() == null) { // For private forums it is prohibited to close or reopen topics
|
||||
delay(1000L)
|
||||
closeForumTopic(
|
||||
it.chat.id,
|
||||
forumTopic.messageThreadId,
|
||||
)
|
||||
|
||||
reply(it, "Test topic has been closed")
|
||||
|
||||
delay(1000L)
|
||||
reopenForumTopic(
|
||||
it.chat.id,
|
||||
forumTopic.messageThreadId,
|
||||
)
|
||||
|
||||
reply(it, "Test topic has been reopened")
|
||||
}
|
||||
|
||||
delay(1000L)
|
||||
deleteForumTopic(
|
||||
it.chat.id,
|
||||
forumTopic.messageThreadId,
|
||||
)
|
||||
|
||||
reply(it, "Test topic has been deleted")
|
||||
|
||||
if (it.chat.privateChatOrNull() == null) { // For private forums it is prohibited to close or reopen topics
|
||||
delay(1000L)
|
||||
hideGeneralForumTopic(
|
||||
it.chat.id,
|
||||
)
|
||||
|
||||
reply(it, "General topic has been hidden")
|
||||
|
||||
delay(1000L)
|
||||
unhideGeneralForumTopic(
|
||||
it.chat.id
|
||||
)
|
||||
|
||||
reply(it, "General topic has been shown")
|
||||
|
||||
delay(1000L)
|
||||
runCatchingSafely(
|
||||
{ _ ->
|
||||
reopenGeneralForumTopic(
|
||||
it.chat.id
|
||||
)
|
||||
|
||||
closeGeneralForumTopic(
|
||||
it.chat.id
|
||||
)
|
||||
}
|
||||
) {
|
||||
closeGeneralForumTopic(
|
||||
it.chat.id
|
||||
)
|
||||
}
|
||||
|
||||
reply(it, "General topic has been closed")
|
||||
|
||||
delay(1000L)
|
||||
reopenGeneralForumTopic(
|
||||
it.chat.id
|
||||
)
|
||||
|
||||
reply(it, "General topic has been opened")
|
||||
|
||||
delay(1000L)
|
||||
editGeneralForumTopic(
|
||||
it.chat.id,
|
||||
uuid4().toString().take(10)
|
||||
)
|
||||
|
||||
reply(it, "General topic has been renamed")
|
||||
|
||||
delay(1000L)
|
||||
editGeneralForumTopic(
|
||||
it.chat.id,
|
||||
"Main topic"
|
||||
)
|
||||
|
||||
reply(it, "General topic has been renamed")
|
||||
}
|
||||
|
||||
delay(1000L)
|
||||
}
|
||||
|
||||
onCommand("delete_topic") {
|
||||
val chat = it.chat.forumChatOrNull() ?: return@onCommand
|
||||
|
||||
deleteForumTopic(chat, chat.id.threadId ?: return@onCommand)
|
||||
}
|
||||
|
||||
onCommand("unpin_all_forum_topic_messages") {
|
||||
val chat = it.chat.forumChatOrNull() ?: return@onCommand
|
||||
|
||||
unpinAllForumTopicMessages(chat, chat.id.threadId ?: return@onCommand)
|
||||
}
|
||||
|
||||
onForumTopicCreated {
|
||||
reply(it, "Topic has been created")
|
||||
}
|
||||
onPrivateForumTopicCreated {
|
||||
reply(it, "Private topic has been created")
|
||||
}
|
||||
|
||||
onForumTopicEdited {
|
||||
reply(it, "Topic has been edited")
|
||||
}
|
||||
onPrivateForumTopicEdited {
|
||||
reply(it, "Private topic has been edited")
|
||||
}
|
||||
|
||||
onForumTopicReopened {
|
||||
reply(it, "Topic has been reopened")
|
||||
}
|
||||
onGeneralForumTopicHidden {
|
||||
reply(it, "General topic has been hidden")
|
||||
}
|
||||
onGeneralForumTopicUnhidden {
|
||||
reply(it, "General topic has been unhidden")
|
||||
}
|
||||
|
||||
setMyCommands(
|
||||
BotCommand("start_test_topics", "start test topics"),
|
||||
BotCommand("delete_topic", "delete topic where message have been sent"),
|
||||
BotCommand("unpin_all_forum_topic_messages", "delete topic where message have been sent"),
|
||||
scope = BotCommandScope.AllGroupChats
|
||||
)
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
||||
println(it)
|
||||
}
|
||||
}.second.join()
|
||||
}
|
||||
9
UserChatShared/README.md
Normal file
9
UserChatShared/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# UserChatShared
|
||||
|
||||
Use `/start` with bot to get request buttons. Bot will ask you to choose user/chat from your list and send it to him.
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN"
|
||||
```
|
||||
21
UserChatShared/build.gradle
Normal file
21
UserChatShared/build.gradle
Normal 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="UserChatSharedKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
}
|
||||
335
UserChatShared/src/main/kotlin/UserChatShared.kt
Normal file
335
UserChatShared/src/main/kotlin/UserChatShared.kt
Normal file
@@ -0,0 +1,335 @@
|
||||
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
|
||||
import dev.inmo.tgbotapi.extensions.api.chat.get.getChat
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
|
||||
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.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.request.RequestId
|
||||
import dev.inmo.tgbotapi.utils.mention
|
||||
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)
|
||||
|
||||
val requestIdUserOrBot = RequestId(0)
|
||||
val requestIdUserNonPremium = RequestId(1)
|
||||
val requestIdUserAny = RequestId(2)
|
||||
val requestIdUserPremium = RequestId(3)
|
||||
val requestIdBot = RequestId(4)
|
||||
|
||||
val requestIdUsersOrBots = RequestId(5)
|
||||
val requestIdUsersNonPremium = RequestId(6)
|
||||
val requestIdUsersAny = RequestId(7)
|
||||
val requestIdUsersPremium = RequestId(8)
|
||||
val requestIdBots = RequestId(9)
|
||||
|
||||
val requestIdAnyChat = RequestId(10)
|
||||
val requestIdChannel = RequestId(11)
|
||||
val requestIdPublicChannel = RequestId(12)
|
||||
val requestIdPrivateChannel = RequestId(13)
|
||||
val requestIdChannelUserOwner = RequestId(14)
|
||||
|
||||
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 (1)",
|
||||
requestIdUserOrBot,
|
||||
requestName = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
}
|
||||
row {
|
||||
requestUserButton(
|
||||
"\uD83D\uDC64☆ (1)",
|
||||
requestIdUserNonPremium,
|
||||
premiumUser = false,
|
||||
requestName = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestUserButton(
|
||||
"\uD83D\uDC64 (1)",
|
||||
requestIdUserAny,
|
||||
premiumUser = null,
|
||||
requestName = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestUserButton(
|
||||
"\uD83D\uDC64★ (1)",
|
||||
requestIdUserPremium,
|
||||
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",
|
||||
requestIdBots,
|
||||
maxCount = keyboardButtonRequestUserLimit.last,
|
||||
requestName = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
}
|
||||
row {
|
||||
requestChatButton(
|
||||
"\uD83D\uDDE3/\uD83D\uDC65",
|
||||
requestIdAnyChat,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
}
|
||||
row {
|
||||
requestChatButton(
|
||||
"\uD83D\uDDE3",
|
||||
requestIdChannel,
|
||||
isChannel = true,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestChatButton(
|
||||
"\uD83D\uDDE3\uD83D\uDD17",
|
||||
requestIdPublicChannel,
|
||||
isChannel = true,
|
||||
isPublic = true,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestChatButton(
|
||||
"\uD83D\uDDE3❌\uD83D\uDD17",
|
||||
requestIdPrivateChannel,
|
||||
isChannel = true,
|
||||
isPublic = false,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestChatButton(
|
||||
"\uD83D\uDDE3\uD83D\uDC6E",
|
||||
requestIdChannelUserOwner,
|
||||
isChannel = true,
|
||||
isOwnedBy = true,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
}
|
||||
row {
|
||||
requestGroupButton(
|
||||
"👥",
|
||||
requestIdGroup,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestGroupButton(
|
||||
"👥\uD83D\uDD17",
|
||||
requestIdPublicGroup,
|
||||
isPublic = true,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestGroupButton(
|
||||
"👥❌\uD83D\uDD17",
|
||||
requestIdPrivateGroup,
|
||||
isPublic = false,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestGroupButton(
|
||||
"👥\uD83D\uDC6E",
|
||||
requestIdGroupUserOwner,
|
||||
isOwnedBy = true,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
}
|
||||
row {
|
||||
requestGroupButton(
|
||||
"🏛",
|
||||
requestIdForum,
|
||||
isForum = true,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestGroupButton(
|
||||
"🏛\uD83D\uDD17",
|
||||
requestIdPublicForum,
|
||||
isPublic = true,
|
||||
isForum = true,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestGroupButton(
|
||||
"🏛❌\uD83D\uDD17",
|
||||
requestIdPrivateForum,
|
||||
isPublic = false,
|
||||
isForum = true,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
requestGroupButton(
|
||||
"🏛\uD83D\uDC6E",
|
||||
requestIdForumUserOwner,
|
||||
isOwnedBy = true,
|
||||
isForum = true,
|
||||
requestTitle = true,
|
||||
requestUsername = true,
|
||||
requestPhoto = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
bot.buildBehaviourWithLongPolling (defaultExceptionsHandler = { it.printStackTrace() }) {
|
||||
onCommand("start", initialFilter = { it.chat is PrivateChat }) {
|
||||
reply(
|
||||
it,
|
||||
"Here possible requests buttons:",
|
||||
replyMarkup = keyboard
|
||||
)
|
||||
}
|
||||
|
||||
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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onChatShared {
|
||||
val chatId = it.chatEvent.chatId
|
||||
val chatInfo = runCatchingSafely { getChat(chatId) }.getOrNull()
|
||||
reply(
|
||||
it,
|
||||
) {
|
||||
+"You have shared "
|
||||
+when (it.chatEvent.requestId) {
|
||||
requestIdAnyChat -> "some chat"
|
||||
requestIdChannel -> "any channel"
|
||||
requestIdPublicChannel -> "public channel"
|
||||
requestIdPrivateChannel -> "private channel"
|
||||
requestIdChannelUserOwner -> "channel owned by you"
|
||||
requestIdGroup -> "any group"
|
||||
requestIdPublicGroup -> "public group"
|
||||
requestIdPrivateGroup -> "private group"
|
||||
requestIdGroupUserOwner -> "group owned by you"
|
||||
requestIdForum -> "any forum"
|
||||
requestIdPublicForum -> "public forum"
|
||||
requestIdPrivateForum -> "private forum"
|
||||
requestIdForumUserOwner -> "forum owned by you"
|
||||
else -> "some chat O.o"
|
||||
}
|
||||
+" (chat info: $chatInfo; chat id: $chatId)"
|
||||
}
|
||||
}
|
||||
|
||||
setMyCommands(BotCommand("start", "Trigger buttons"))
|
||||
}.join()
|
||||
}
|
||||
@@ -12,6 +12,6 @@ What is there in this module:
|
||||
|
||||
## How to run
|
||||
|
||||
```kotlin
|
||||
```bash
|
||||
./gradlew run --args="TOKEN WEB_APP_ADDRESS"
|
||||
```
|
||||
|
||||
@@ -11,12 +11,19 @@ buildscript {
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.multiplatform"
|
||||
id "org.jetbrains.kotlin.plugin.serialization"
|
||||
|
||||
id "org.jetbrains.kotlin.plugin.compose" version "$kotlin_version"
|
||||
id "org.jetbrains.compose" version "$compose_version"
|
||||
}
|
||||
|
||||
apply plugin: 'application'
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
jvm {
|
||||
binaries {
|
||||
executable {
|
||||
mainClass.set("WebAppServerKt")
|
||||
}
|
||||
}
|
||||
}
|
||||
js(IR) {
|
||||
browser()
|
||||
binaries.executable()
|
||||
@@ -27,12 +34,15 @@ kotlin {
|
||||
dependencies {
|
||||
implementation kotlin('stdlib')
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version"
|
||||
implementation "dev.inmo:tgbotapi.core:$telegram_bot_api_version"
|
||||
implementation compose.runtime
|
||||
}
|
||||
}
|
||||
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation "dev.inmo:tgbotapi.webapps:$telegram_bot_api_version"
|
||||
implementation compose.web.core
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,15 +51,12 @@ kotlin {
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
implementation "dev.inmo:micro_utils.ktor.server:$micro_utils_version"
|
||||
implementation "io.ktor:ktor-server-cio:$ktor_version"
|
||||
implementation compose.desktop.currentOs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = "WebAppServerKt"
|
||||
}
|
||||
|
||||
tasks.getByName("compileKotlinJvm")
|
||||
.dependsOn(jsBrowserDistribution)
|
||||
tasks.getByName("compileKotlinJvm").configure {
|
||||
|
||||
3
WebApp/src/commonMain/kotlin/CustomEmojiIdToSet.kt
Normal file
3
WebApp/src/commonMain/kotlin/CustomEmojiIdToSet.kt
Normal file
@@ -0,0 +1,3 @@
|
||||
import dev.inmo.tgbotapi.types.CustomEmojiId
|
||||
|
||||
val CustomEmojiIdToSet = CustomEmojiId("5424939566278649034")
|
||||
@@ -1,157 +1,393 @@
|
||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||
import androidx.compose.runtime.*
|
||||
import dev.inmo.micro_utils.coroutines.launchLoggingDropExceptions
|
||||
import dev.inmo.tgbotapi.types.CustomEmojiId
|
||||
import dev.inmo.tgbotapi.types.userIdField
|
||||
import dev.inmo.tgbotapi.types.webAppQueryIdField
|
||||
import dev.inmo.tgbotapi.webapps.*
|
||||
import dev.inmo.tgbotapi.webapps.accelerometer.AccelerometerStartParams
|
||||
import dev.inmo.tgbotapi.webapps.cloud.*
|
||||
import dev.inmo.tgbotapi.webapps.events.*
|
||||
import dev.inmo.tgbotapi.webapps.gyroscope.GyroscopeStartParams
|
||||
import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackStyle
|
||||
import dev.inmo.tgbotapi.webapps.haptic.HapticFeedbackType
|
||||
import dev.inmo.tgbotapi.webapps.orientation.DeviceOrientationStartParams
|
||||
import dev.inmo.tgbotapi.webapps.popup.*
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.content.TextContent
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.dom.appendElement
|
||||
import kotlinx.dom.appendText
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.jetbrains.compose.web.attributes.InputType
|
||||
import org.jetbrains.compose.web.attributes.placeholder
|
||||
import org.jetbrains.compose.web.css.Style
|
||||
import org.jetbrains.compose.web.css.StyleSheet
|
||||
import org.jetbrains.compose.web.css.Color as ComposeColor
|
||||
import org.jetbrains.compose.web.css.backgroundColor
|
||||
import org.jetbrains.compose.web.css.color
|
||||
import org.jetbrains.compose.web.dom.*
|
||||
import org.jetbrains.compose.web.dom.Text
|
||||
import org.jetbrains.compose.web.renderComposable
|
||||
import org.w3c.dom.*
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUBytes
|
||||
|
||||
fun HTMLElement.log(text: String) {
|
||||
appendText(text)
|
||||
appendElement("p", {})
|
||||
}
|
||||
|
||||
private object RootStyleSheet : StyleSheet() {
|
||||
val rootClass by style {
|
||||
color(ComposeColor("var(--tg-theme-text-color)"))
|
||||
backgroundColor(ComposeColor("var(--tg-theme-bg-color)"))
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
fun main() {
|
||||
console.log("Web app started")
|
||||
val client = HttpClient()
|
||||
val baseUrl = window.location.origin.removeSuffix("/")
|
||||
|
||||
window.onload = {
|
||||
val scope = CoroutineScope(Dispatchers.Default)
|
||||
runCatching {
|
||||
renderComposable("root") {
|
||||
Style(RootStyleSheet)
|
||||
DisposableEffect(null) {
|
||||
scopeElement.classList.add(RootStyleSheet.rootClass)
|
||||
|
||||
scope.launchSafelyWithoutExceptions {
|
||||
val response = client.post("$baseUrl/check") {
|
||||
setBody(
|
||||
Json { }.encodeToString(
|
||||
WebAppDataWrapper.serializer(),
|
||||
WebAppDataWrapper(webApp.initData, webApp.initDataUnsafe.hash)
|
||||
)
|
||||
onDispose {
|
||||
scopeElement.classList.remove(RootStyleSheet.rootClass)
|
||||
}
|
||||
}
|
||||
val scope = rememberCoroutineScope()
|
||||
val isSafeState = remember { mutableStateOf<Boolean?>(null) }
|
||||
val logsState = remember { mutableStateListOf<Any?>() }
|
||||
|
||||
// Text(window.location.href)
|
||||
// P()
|
||||
|
||||
LaunchedEffect(baseUrl) {
|
||||
val response = client.post("$baseUrl/check") {
|
||||
setBody(
|
||||
Json.encodeToString(
|
||||
WebAppDataWrapper.serializer(),
|
||||
WebAppDataWrapper(webApp.initData, webApp.initDataUnsafe.hash)
|
||||
)
|
||||
}
|
||||
val dataIsSafe = response.bodyAsText().toBoolean()
|
||||
|
||||
document.body ?.log(
|
||||
if (dataIsSafe) {
|
||||
"Data is safe"
|
||||
} else {
|
||||
"Data is unsafe"
|
||||
}
|
||||
)
|
||||
|
||||
document.body ?.log(
|
||||
webApp.initDataUnsafe.chat.toString()
|
||||
)
|
||||
}
|
||||
val dataIsSafe = response.bodyAsText().toBoolean()
|
||||
|
||||
document.body ?.appendElement("button") {
|
||||
addEventListener("click", {
|
||||
scope.launchSafelyWithoutExceptions {
|
||||
handleResult({ "Clicked" }) {
|
||||
client.post("${window.location.origin.removeSuffix("/")}/inline") {
|
||||
parameter(webAppQueryIdField, it)
|
||||
setBody(TextContent("Clicked", ContentType.Text.Plain))
|
||||
document.body ?.log(url.build().toString())
|
||||
}.coroutineContext.job.join()
|
||||
if (dataIsSafe) {
|
||||
isSafeState.value = true
|
||||
logsState.add("Data is safe")
|
||||
} else {
|
||||
isSafeState.value = false
|
||||
logsState.add("Data is unsafe")
|
||||
}
|
||||
|
||||
logsState.add(
|
||||
webApp.initDataUnsafe.chat.toString()
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
when (isSafeState.value) {
|
||||
null -> "Checking safe state..."
|
||||
true -> "Data is safe"
|
||||
false -> "Data is unsafe"
|
||||
}
|
||||
)
|
||||
P()
|
||||
Text("Chat from WebAppInitData: ${webApp.initDataUnsafe.chat}")
|
||||
|
||||
H3 { Text("Emoji status management") }
|
||||
val emojiStatusAccessState = remember { mutableStateOf(false) }
|
||||
webApp.onEmojiStatusAccessRequested {
|
||||
emojiStatusAccessState.value = it.isAllowed
|
||||
}
|
||||
Button({
|
||||
onClick {
|
||||
webApp.requestEmojiStatusAccess()
|
||||
}
|
||||
}) {
|
||||
Text("Request custom emoji status access")
|
||||
}
|
||||
if (emojiStatusAccessState.value) {
|
||||
Button({
|
||||
onClick {
|
||||
webApp.setEmojiStatus(CustomEmojiIdToSet/* android custom emoji id */)
|
||||
}
|
||||
}) {
|
||||
Text("Set custom emoji status")
|
||||
}
|
||||
val userId = webApp.initDataUnsafe.user ?.id
|
||||
userId ?.let { userId ->
|
||||
Button({
|
||||
onClick {
|
||||
scope.launchLoggingDropExceptions {
|
||||
client.post("$baseUrl/setCustomEmoji") {
|
||||
parameter(userIdField, userId.long)
|
||||
setBody(
|
||||
Json.encodeToString(
|
||||
WebAppDataWrapper.serializer(),
|
||||
WebAppDataWrapper(webApp.initData, webApp.initDataUnsafe.hash)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
appendText("Example button")
|
||||
} ?: window.alert("Unable to load body")
|
||||
}) {
|
||||
Text("Set custom emoji status via bot")
|
||||
}
|
||||
}
|
||||
}
|
||||
P()
|
||||
|
||||
document.body ?.appendElement("p", {})
|
||||
document.body ?.appendText("Alerts:")
|
||||
|
||||
document.body ?.appendElement("button") {
|
||||
addEventListener("click", {
|
||||
webApp.showPopup(
|
||||
PopupParams(
|
||||
"It is sample title of default button",
|
||||
"It is sample message of default button",
|
||||
DefaultPopupButton("default", "Default button"),
|
||||
OkPopupButton("ok"),
|
||||
DestructivePopupButton("destructive", "Destructive button")
|
||||
)
|
||||
) {
|
||||
document.body ?.log(
|
||||
when (it) {
|
||||
"default" -> "You have clicked default button in popup"
|
||||
"ok" -> "You have clicked ok button in popup"
|
||||
"destructive" -> "You have clicked destructive button in popup"
|
||||
else -> "I can't imagine where you take button with id $it"
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
appendText("Popup")
|
||||
} ?: window.alert("Unable to load body")
|
||||
|
||||
document.body ?.appendElement("button") {
|
||||
addEventListener("click", {
|
||||
webApp.showAlert(
|
||||
"This is alert message"
|
||||
) {
|
||||
document.body ?.log(
|
||||
"You have closed alert"
|
||||
)
|
||||
}
|
||||
})
|
||||
appendText("Alert")
|
||||
} ?: window.alert("Unable to load body")
|
||||
|
||||
document.body ?.appendElement("button") {
|
||||
addEventListener("click", {
|
||||
webApp.showConfirm(
|
||||
"This is confirm message"
|
||||
) {
|
||||
document.body ?.log(
|
||||
"You have pressed \"${if (it) "Ok" else "Cancel"}\" in confirm"
|
||||
)
|
||||
}
|
||||
})
|
||||
appendText("Confirm")
|
||||
} ?: window.alert("Unable to load body")
|
||||
|
||||
document.body ?.appendElement("p", {})
|
||||
|
||||
document.body ?.appendElement("button") {
|
||||
fun updateText() {
|
||||
textContent = if (webApp.isClosingConfirmationEnabled) {
|
||||
"Disable closing confirmation"
|
||||
} else {
|
||||
"Enable closing confirmation"
|
||||
H3 { Text("Call server method with webAppQueryIdField") }
|
||||
Button({
|
||||
onClick {
|
||||
scope.launchLoggingDropExceptions {
|
||||
handleResult({ "Clicked" }) {
|
||||
client.post("${window.location.origin.removeSuffix("/")}/inline") {
|
||||
parameter(webAppQueryIdField, it)
|
||||
setBody(TextContent("Clicked", ContentType.Text.Plain))
|
||||
logsState.add(url.build().toString())
|
||||
}.coroutineContext.job.join()
|
||||
}
|
||||
}
|
||||
addEventListener("click", {
|
||||
webApp.toggleClosingConfirmation()
|
||||
updateText()
|
||||
})
|
||||
updateText()
|
||||
} ?: window.alert("Unable to load body")
|
||||
}
|
||||
}) {
|
||||
Text("Answer in chat button")
|
||||
}
|
||||
|
||||
document.body ?.appendElement("p", {})
|
||||
H3 { Text("Hide keyboard") }
|
||||
val hideCountdown = remember { mutableStateOf<Int?>(null) }
|
||||
Button({
|
||||
onClick {
|
||||
hideCountdown.value = 5
|
||||
}
|
||||
}) {
|
||||
if (hideCountdown.value == null) {
|
||||
Text("Hide")
|
||||
} else {
|
||||
Text("Hide in ${hideCountdown.value} seconds")
|
||||
}
|
||||
}
|
||||
LaunchedEffect(hideCountdown.value) {
|
||||
val value = hideCountdown.value
|
||||
when {
|
||||
value == null -> return@LaunchedEffect
|
||||
value > 0 -> {
|
||||
delay(1000)
|
||||
hideCountdown.value = hideCountdown.value ?.minus(1)
|
||||
}
|
||||
else -> {
|
||||
webApp.hideKeyboard()
|
||||
hideCountdown.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
P()
|
||||
H3 { Text("User info") }
|
||||
Text("Allow to write in private messages: ${webApp.initDataUnsafe.user ?.allowsWriteToPM ?: "User unavailable"}")
|
||||
|
||||
P()
|
||||
H3 { Text("Alerts") }
|
||||
Button({
|
||||
onClick {
|
||||
webApp.showPopup(
|
||||
PopupParams(
|
||||
"It is sample title of default button",
|
||||
"It is sample message of default button",
|
||||
DefaultPopupButton("default", "Default button"),
|
||||
OkPopupButton("ok"),
|
||||
DestructivePopupButton("destructive", "Destructive button")
|
||||
)
|
||||
) {
|
||||
logsState.add(
|
||||
when (it) {
|
||||
"default" -> "You have clicked default button in popup"
|
||||
"ok" -> "You have clicked ok button in popup"
|
||||
"destructive" -> "You have clicked destructive button in popup"
|
||||
else -> "I can't imagine where you take button with id $it"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text("Popup")
|
||||
}
|
||||
Button({
|
||||
onClick {
|
||||
webApp.showAlert(
|
||||
"This is alert message"
|
||||
) {
|
||||
logsState.add(
|
||||
"You have closed alert"
|
||||
)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text("Alert")
|
||||
}
|
||||
Button({
|
||||
onClick {
|
||||
webApp.showConfirm(
|
||||
"This is confirm message"
|
||||
) {
|
||||
logsState.add(
|
||||
"You have pressed \"${if (it) "Ok" else "Cancel"}\" in confirm"
|
||||
)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text("Confirm")
|
||||
}
|
||||
|
||||
P()
|
||||
H3 { Text("Write access callbacks") }
|
||||
Button({
|
||||
onClick {
|
||||
webApp.requestWriteAccess()
|
||||
}
|
||||
}) {
|
||||
Text("Request write access without callback")
|
||||
}
|
||||
Button({
|
||||
onClick {
|
||||
webApp.requestWriteAccess {
|
||||
logsState.add("Write access request result: $it")
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text("Request write access with callback")
|
||||
}
|
||||
|
||||
P()
|
||||
H3 { Text("Request contact") }
|
||||
Button({
|
||||
onClick {
|
||||
webApp.requestContact()
|
||||
}
|
||||
}) {
|
||||
Text("Request contact without callback")
|
||||
}
|
||||
Button({
|
||||
onClick {
|
||||
webApp.requestContact { logsState.add("Contact request result: $it") }
|
||||
}
|
||||
}) {
|
||||
Text("Request contact with callback")
|
||||
}
|
||||
|
||||
P()
|
||||
H3 { Text("Closing confirmation") }
|
||||
val isClosingConfirmationEnabledState = remember { mutableStateOf(webApp.isClosingConfirmationEnabled) }
|
||||
Button({
|
||||
onClick {
|
||||
webApp.toggleClosingConfirmation()
|
||||
isClosingConfirmationEnabledState.value = webApp.isClosingConfirmationEnabled
|
||||
}
|
||||
}) {
|
||||
Text(
|
||||
if (isClosingConfirmationEnabledState.value) {
|
||||
"Disable closing confirmation"
|
||||
} else {
|
||||
"Enable closing confirmation"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
P()
|
||||
H3 { Text("Colors") }
|
||||
val headerColor = remember { mutableStateOf<Color.Hex>(Color.Hex("#000000")) }
|
||||
fun updateHeaderColor() {
|
||||
val (r, g, b) = Random.nextUBytes(3)
|
||||
headerColor.value = Color.Hex(r, g, b)
|
||||
webApp.setHeaderColor(headerColor.value)
|
||||
}
|
||||
DisposableEffect(0) {
|
||||
updateHeaderColor()
|
||||
onDispose { }
|
||||
}
|
||||
Button({
|
||||
style {
|
||||
backgroundColor(ComposeColor(headerColor.value.value))
|
||||
}
|
||||
onClick {
|
||||
updateHeaderColor()
|
||||
}
|
||||
}) {
|
||||
key(headerColor.value) {
|
||||
Text("Header color: ${webApp.headerColor ?.uppercase()} (click to change)")
|
||||
}
|
||||
}
|
||||
|
||||
P()
|
||||
val backgroundColor = remember { mutableStateOf<Color.Hex>(Color.Hex("#000000")) }
|
||||
fun updateBackgroundColor() {
|
||||
val (r, g, b) = Random.nextUBytes(3)
|
||||
backgroundColor.value = Color.Hex(r, g, b)
|
||||
webApp.setBackgroundColor(backgroundColor.value)
|
||||
}
|
||||
DisposableEffect(0) {
|
||||
updateBackgroundColor()
|
||||
onDispose { }
|
||||
}
|
||||
Button({
|
||||
style {
|
||||
backgroundColor(ComposeColor(backgroundColor.value.value))
|
||||
}
|
||||
onClick {
|
||||
updateBackgroundColor()
|
||||
}
|
||||
}) {
|
||||
key(backgroundColor.value) {
|
||||
Text("Background color: ${webApp.backgroundColor ?.uppercase()} (click to change)")
|
||||
}
|
||||
}
|
||||
|
||||
P()
|
||||
val bottomBarColor = remember { mutableStateOf<Color.Hex>(Color.Hex("#000000")) }
|
||||
fun updateBottomBarColor() {
|
||||
val (r, g, b) = Random.nextUBytes(3)
|
||||
bottomBarColor.value = Color.Hex(r, g, b)
|
||||
webApp.setBottomBarColor(bottomBarColor.value)
|
||||
}
|
||||
DisposableEffect(0) {
|
||||
updateBottomBarColor()
|
||||
onDispose { }
|
||||
}
|
||||
Button({
|
||||
style {
|
||||
backgroundColor(ComposeColor(bottomBarColor.value.value))
|
||||
}
|
||||
onClick {
|
||||
updateBottomBarColor()
|
||||
}
|
||||
}) {
|
||||
key(bottomBarColor.value) {
|
||||
Text("Bottom bar color: ${webApp.bottomBarColor ?.uppercase()} (click to change)")
|
||||
}
|
||||
}
|
||||
|
||||
remember {
|
||||
webApp.apply {
|
||||
|
||||
onThemeChanged {
|
||||
document.body ?.log("Theme changed: ${webApp.themeParams}")
|
||||
logsState.add("Theme changed: ${webApp.themeParams}")
|
||||
}
|
||||
onViewportChanged {
|
||||
document.body ?.log("Viewport changed: ${it.isStateStable}")
|
||||
logsState.add("Viewport changed: ${it}")
|
||||
}
|
||||
backButton.apply {
|
||||
onClick {
|
||||
document.body ?.log("Back button clicked")
|
||||
logsState.add("Back button clicked")
|
||||
hapticFeedback.impactOccurred(
|
||||
HapticFeedbackStyle.Heavy
|
||||
)
|
||||
@@ -160,21 +396,436 @@ fun main() {
|
||||
}
|
||||
mainButton.apply {
|
||||
setText("Main button")
|
||||
setParams(
|
||||
BottomButtonParams(
|
||||
iconCustomEmojiId = CustomEmojiId("5370976574969486150") // 😏
|
||||
)
|
||||
)
|
||||
onClick {
|
||||
document.body ?.log("Main button clicked")
|
||||
logsState.add("Main button clicked")
|
||||
hapticFeedback.notificationOccurred(
|
||||
HapticFeedbackType.Success
|
||||
)
|
||||
}
|
||||
show()
|
||||
}
|
||||
secondaryButton.apply {
|
||||
setText("Secondary button")
|
||||
setParams(
|
||||
BottomButtonParams(
|
||||
iconCustomEmojiId = CustomEmojiId("5370763368497944736") // 😒
|
||||
)
|
||||
)
|
||||
onClick {
|
||||
logsState.add("Secondary button clicked")
|
||||
hapticFeedback.notificationOccurred(
|
||||
HapticFeedbackType.Warning
|
||||
)
|
||||
}
|
||||
show()
|
||||
}
|
||||
onSettingsButtonClicked {
|
||||
document.body ?.log("Settings button clicked")
|
||||
logsState.add("Settings button clicked")
|
||||
}
|
||||
onWriteAccessRequested {
|
||||
logsState.add("Write access request result: $it")
|
||||
}
|
||||
onContactRequested {
|
||||
logsState.add("Contact request result: $it")
|
||||
}
|
||||
}
|
||||
webApp.ready()
|
||||
}.onFailure {
|
||||
window.alert(it.stackTraceToString())
|
||||
}
|
||||
|
||||
P()
|
||||
let {
|
||||
H3 { Text("Accelerometer") }
|
||||
val enabledState = remember { mutableStateOf(webApp.accelerometer.isStarted) }
|
||||
webApp.onAccelerometerStarted { enabledState.value = true }
|
||||
webApp.onAccelerometerStopped { enabledState.value = false }
|
||||
Button({
|
||||
onClick {
|
||||
if (enabledState.value) {
|
||||
webApp.accelerometer.stop { }
|
||||
} else {
|
||||
webApp.accelerometer.start(AccelerometerStartParams(200))
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text("${if (enabledState.value) "Stop" else "Start"} accelerometer")
|
||||
}
|
||||
val xState = remember { mutableStateOf(webApp.accelerometer.x) }
|
||||
val yState = remember { mutableStateOf(webApp.accelerometer.y) }
|
||||
val zState = remember { mutableStateOf(webApp.accelerometer.z) }
|
||||
fun updateValues() {
|
||||
xState.value = webApp.accelerometer.x
|
||||
yState.value = webApp.accelerometer.y
|
||||
zState.value = webApp.accelerometer.z
|
||||
}
|
||||
remember {
|
||||
updateValues()
|
||||
}
|
||||
|
||||
webApp.onAccelerometerChanged {
|
||||
updateValues()
|
||||
}
|
||||
if (enabledState.value) {
|
||||
P()
|
||||
Text("x: ${xState.value}")
|
||||
P()
|
||||
Text("y: ${yState.value}")
|
||||
P()
|
||||
Text("z: ${zState.value}")
|
||||
}
|
||||
}
|
||||
P()
|
||||
|
||||
let {
|
||||
H3 { Text("Gyroscope") }
|
||||
val enabledState = remember { mutableStateOf(webApp.gyroscope.isStarted) }
|
||||
webApp.onGyroscopeStarted { enabledState.value = true }
|
||||
webApp.onGyroscopeStopped { enabledState.value = false }
|
||||
Button({
|
||||
onClick {
|
||||
if (enabledState.value) {
|
||||
webApp.gyroscope.stop { }
|
||||
} else {
|
||||
webApp.gyroscope.start(GyroscopeStartParams(200))
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text("${if (enabledState.value) "Stop" else "Start"} gyroscope")
|
||||
}
|
||||
val xState = remember { mutableStateOf(webApp.gyroscope.x) }
|
||||
val yState = remember { mutableStateOf(webApp.gyroscope.y) }
|
||||
val zState = remember { mutableStateOf(webApp.gyroscope.z) }
|
||||
fun updateValues() {
|
||||
xState.value = webApp.gyroscope.x
|
||||
yState.value = webApp.gyroscope.y
|
||||
zState.value = webApp.gyroscope.z
|
||||
}
|
||||
remember {
|
||||
updateValues()
|
||||
}
|
||||
|
||||
webApp.onGyroscopeChanged {
|
||||
updateValues()
|
||||
}
|
||||
if (enabledState.value) {
|
||||
P()
|
||||
Text("x: ${xState.value}")
|
||||
P()
|
||||
Text("y: ${yState.value}")
|
||||
P()
|
||||
Text("z: ${zState.value}")
|
||||
}
|
||||
}
|
||||
|
||||
P()
|
||||
let {
|
||||
H3 { Text("Device Orientation") }
|
||||
val enabledState = remember { mutableStateOf(webApp.deviceOrientation.isStarted) }
|
||||
webApp.onDeviceOrientationStarted { enabledState.value = true }
|
||||
webApp.onDeviceOrientationStopped { enabledState.value = false }
|
||||
Button({
|
||||
onClick {
|
||||
if (enabledState.value) {
|
||||
webApp.deviceOrientation.stop { }
|
||||
} else {
|
||||
webApp.deviceOrientation.start(DeviceOrientationStartParams(200))
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text("${if (enabledState.value) "Stop" else "Start"} deviceOrientation")
|
||||
}
|
||||
val alphaState = remember { mutableStateOf(webApp.deviceOrientation.alpha) }
|
||||
val betaState = remember { mutableStateOf(webApp.deviceOrientation.beta) }
|
||||
val gammaState = remember { mutableStateOf(webApp.deviceOrientation.gamma) }
|
||||
fun updateValues() {
|
||||
alphaState.value = webApp.deviceOrientation.alpha
|
||||
betaState.value = webApp.deviceOrientation.beta
|
||||
gammaState.value = webApp.deviceOrientation.gamma
|
||||
}
|
||||
remember {
|
||||
updateValues()
|
||||
}
|
||||
|
||||
webApp.onDeviceOrientationChanged {
|
||||
updateValues()
|
||||
}
|
||||
if (enabledState.value) {
|
||||
P()
|
||||
Text("alpha: ${alphaState.value}")
|
||||
P()
|
||||
Text("beta: ${betaState.value}")
|
||||
P()
|
||||
Text("gamma: ${gammaState.value}")
|
||||
}
|
||||
}
|
||||
|
||||
P()
|
||||
H3 { Text("Cloud storage") }
|
||||
val storageTrigger = remember { mutableStateOf<List<Pair<CloudStorageKey, CloudStorageValue>>>(emptyList()) }
|
||||
fun updateCloudStorage() {
|
||||
webApp.cloudStorage.getAll {
|
||||
it.onSuccess {
|
||||
storageTrigger.value = it.toList().sortedBy { it.first.key }
|
||||
}
|
||||
}
|
||||
}
|
||||
key(storageTrigger.value) {
|
||||
storageTrigger.value.forEach { (key, value) ->
|
||||
val keyState = remember { mutableStateOf(key.key) }
|
||||
val valueState = remember { mutableStateOf(value.value) }
|
||||
Input(InputType.Text) {
|
||||
value(key.key)
|
||||
onInput { keyState.value = it.value }
|
||||
}
|
||||
Input(InputType.Text) {
|
||||
value(value.value)
|
||||
onInput { valueState.value = it.value }
|
||||
}
|
||||
Button({
|
||||
onClick {
|
||||
if (key.key != keyState.value) {
|
||||
webApp.cloudStorage.remove(key)
|
||||
}
|
||||
webApp.cloudStorage.set(keyState.value, valueState.value)
|
||||
updateCloudStorage()
|
||||
}
|
||||
}) {
|
||||
Text("Save")
|
||||
}
|
||||
}
|
||||
let { // new element adding
|
||||
val keyState = remember { mutableStateOf("") }
|
||||
val valueState = remember { mutableStateOf("") }
|
||||
Input(InputType.Text) {
|
||||
onInput { keyState.value = it.value }
|
||||
}
|
||||
Input(InputType.Text) {
|
||||
onInput { valueState.value = it.value }
|
||||
}
|
||||
Button({
|
||||
onClick {
|
||||
webApp.cloudStorage.set(keyState.value, valueState.value)
|
||||
updateCloudStorage()
|
||||
}
|
||||
}) {
|
||||
Text("Save")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
P()
|
||||
let { // DeviceStorage
|
||||
H3 { Text("Device storage") }
|
||||
val fieldKey = remember { mutableStateOf("") }
|
||||
val fieldValue = remember { mutableStateOf("") }
|
||||
val message = remember { mutableStateOf("") }
|
||||
Div {
|
||||
Text("Start type title of key. If value will be found in device storage, it will be shown in value input")
|
||||
}
|
||||
|
||||
Input(InputType.Text) {
|
||||
placeholder("Key")
|
||||
value(fieldKey.value)
|
||||
onInput {
|
||||
fieldKey.value = it.value
|
||||
webApp.deviceStorage.getItem(it.value) { e, v ->
|
||||
fieldValue.value = v ?: ""
|
||||
if (v == null) {
|
||||
message.value = "Value for key \"${it.value}\" has not been found"
|
||||
} else {
|
||||
message.value = "Value for key \"${it.value}\" has been found: \"$v\""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Div {
|
||||
Text("If you want to change value if typed key - just put it here")
|
||||
}
|
||||
Input(InputType.Text) {
|
||||
placeholder("Value")
|
||||
value(fieldValue.value)
|
||||
onInput {
|
||||
fieldValue.value = it.value
|
||||
webApp.deviceStorage.setItem(fieldKey.value, it.value) { e, v ->
|
||||
if (v == true) {
|
||||
fieldValue.value = it.value
|
||||
message.value = "Value \"${it.value}\" has been saved"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.value.isNotEmpty()) {
|
||||
Div { Text(message.value) }
|
||||
}
|
||||
}
|
||||
P()
|
||||
|
||||
let { // DeviceStorage
|
||||
H3 { Text("Secure storage") }
|
||||
val fieldKey = remember { mutableStateOf("") }
|
||||
val fieldValue = remember { mutableStateOf("") }
|
||||
val message = remember { mutableStateOf("") }
|
||||
val restorableState = remember { mutableStateOf(false) }
|
||||
Div {
|
||||
Text("Start type title of key. If value will be found in device storage, it will be shown in value input")
|
||||
}
|
||||
|
||||
Input(InputType.Text) {
|
||||
placeholder("Key")
|
||||
value(fieldKey.value)
|
||||
onInput {
|
||||
fieldKey.value = it.value
|
||||
webApp.secureStorage.getItem(it.value) { e, v, restorable ->
|
||||
fieldValue.value = v ?: ""
|
||||
restorableState.value = restorable == true
|
||||
if (v == null) {
|
||||
if (restorable == true) {
|
||||
message.value = "Value for key \"${it.value}\" has not been found, but can be restored"
|
||||
} else {
|
||||
message.value = "Value for key \"${it.value}\" has not been found. Error: $e"
|
||||
}
|
||||
} else {
|
||||
message.value = "Value for key \"${it.value}\" has been found: \"$v\""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (restorableState.value) {
|
||||
Button({
|
||||
onClick {
|
||||
webApp.secureStorage.restoreItem(fieldKey.value) { e, v ->
|
||||
fieldValue.value = v ?: ""
|
||||
if (v == null) {
|
||||
message.value = "Value for key \"${fieldKey.value}\" has not been restored. Error: $e"
|
||||
} else {
|
||||
message.value = "Value for key \"${fieldKey.value}\" has been restored: \"$v\""
|
||||
}
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Text("Restore")
|
||||
}
|
||||
}
|
||||
Div {
|
||||
Text("If you want to change value if typed key - just put it here")
|
||||
}
|
||||
Input(InputType.Text) {
|
||||
placeholder("Value")
|
||||
value(fieldValue.value)
|
||||
onInput {
|
||||
fieldValue.value = it.value
|
||||
webApp.secureStorage.setItem(fieldKey.value, it.value) { e, v ->
|
||||
if (v) {
|
||||
fieldValue.value = it.value
|
||||
message.value = "Value \"${it.value}\" has been saved"
|
||||
} else {
|
||||
message.value = "Value \"${it.value}\" has not been saved. Error: $e"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.value.isNotEmpty()) {
|
||||
Div { Text(message.value) }
|
||||
}
|
||||
}
|
||||
P()
|
||||
|
||||
H3 { Text("Events") }
|
||||
EventType.values().forEach { eventType ->
|
||||
when (eventType) {
|
||||
EventType.AccelerometerChanged -> webApp.onAccelerometerChanged { /*logsState.add("AccelerometerChanged") /* see accelerometer block */ */ }
|
||||
EventType.AccelerometerFailed -> webApp.onAccelerometerFailed {
|
||||
logsState.add(it.error)
|
||||
}
|
||||
EventType.AccelerometerStarted -> webApp.onAccelerometerStarted { logsState.add("AccelerometerStarted") }
|
||||
EventType.AccelerometerStopped -> webApp.onAccelerometerStopped { logsState.add("AccelerometerStopped") }
|
||||
EventType.Activated -> webApp.onActivated { logsState.add("Activated") }
|
||||
EventType.BackButtonClicked -> webApp.onBackButtonClicked { logsState.add("BackButtonClicked") }
|
||||
EventType.BiometricAuthRequested -> webApp.onBiometricAuthRequested {
|
||||
logsState.add(it.isAuthenticated)
|
||||
}
|
||||
EventType.BiometricManagerUpdated -> webApp.onBiometricManagerUpdated { logsState.add("BiometricManagerUpdated") }
|
||||
EventType.BiometricTokenUpdated -> webApp.onBiometricTokenUpdated {
|
||||
logsState.add(it.isUpdated)
|
||||
}
|
||||
EventType.ClipboardTextReceived -> webApp.onClipboardTextReceived {
|
||||
logsState.add(it.data)
|
||||
}
|
||||
EventType.ContactRequested -> webApp.onContactRequested {
|
||||
logsState.add(it.status)
|
||||
}
|
||||
EventType.ContentSafeAreaChanged -> webApp.onContentSafeAreaChanged { logsState.add("ContentSafeAreaChanged") }
|
||||
EventType.Deactivated -> webApp.onDeactivated { logsState.add("Deactivated") }
|
||||
EventType.DeviceOrientationChanged -> webApp.onDeviceOrientationChanged { /*logsState.add("DeviceOrientationChanged")*//* see accelerometer block */ }
|
||||
EventType.DeviceOrientationFailed -> webApp.onDeviceOrientationFailed {
|
||||
logsState.add(it.error)
|
||||
}
|
||||
EventType.DeviceOrientationStarted -> webApp.onDeviceOrientationStarted { logsState.add("DeviceOrientationStarted") }
|
||||
EventType.DeviceOrientationStopped -> webApp.onDeviceOrientationStopped { logsState.add("DeviceOrientationStopped") }
|
||||
EventType.EmojiStatusAccessRequested -> webApp.onEmojiStatusAccessRequested {
|
||||
logsState.add(it.status)
|
||||
}
|
||||
EventType.EmojiStatusFailed -> webApp.onEmojiStatusFailed {
|
||||
logsState.add(it.error)
|
||||
}
|
||||
EventType.EmojiStatusSet -> webApp.onEmojiStatusSet { logsState.add("EmojiStatusSet") }
|
||||
EventType.FileDownloadRequested -> webApp.onFileDownloadRequested {
|
||||
logsState.add(it.status)
|
||||
}
|
||||
EventType.FullscreenChanged -> webApp.onFullscreenChanged { logsState.add("FullscreenChanged") }
|
||||
EventType.FullscreenFailed -> webApp.onFullscreenFailed {
|
||||
logsState.add(it.error)
|
||||
}
|
||||
EventType.GyroscopeChanged -> webApp.onGyroscopeChanged { /*logsState.add("GyroscopeChanged")*//* see gyroscope block */ }
|
||||
EventType.GyroscopeFailed -> webApp.onGyroscopeFailed {
|
||||
logsState.add(it.error)
|
||||
}
|
||||
EventType.GyroscopeStarted -> webApp.onGyroscopeStarted { logsState.add("GyroscopeStarted")/* see accelerometer block */ }
|
||||
EventType.GyroscopeStopped -> webApp.onGyroscopeStopped { logsState.add("GyroscopeStopped") }
|
||||
EventType.HomeScreenAdded -> webApp.onHomeScreenAdded { logsState.add("HomeScreenAdded") }
|
||||
EventType.HomeScreenChecked -> webApp.onHomeScreenChecked {
|
||||
logsState.add(it.status)
|
||||
}
|
||||
EventType.InvoiceClosed -> webApp.onInvoiceClosed { url, status ->
|
||||
logsState.add(url)
|
||||
logsState.add(status)
|
||||
}
|
||||
EventType.LocationManagerUpdated -> webApp.onLocationManagerUpdated { logsState.add("LocationManagerUpdated") }
|
||||
EventType.LocationRequested -> webApp.onLocationRequested {
|
||||
logsState.add(it.locationData)
|
||||
}
|
||||
EventType.MainButtonClicked -> webApp.onMainButtonClicked { logsState.add("MainButtonClicked") }
|
||||
EventType.PopupClosed -> webApp.onPopupClosed {
|
||||
logsState.add(it.buttonId)
|
||||
}
|
||||
EventType.QrTextReceived -> webApp.onQrTextReceived {
|
||||
logsState.add(it.data)
|
||||
}
|
||||
EventType.SafeAreaChanged -> webApp.onSafeAreaChanged { logsState.add("SafeAreaChanged") }
|
||||
EventType.ScanQrPopupClosed -> webApp.onScanQrPopupClosed { logsState.add("ScanQrPopupClosed") }
|
||||
EventType.SecondaryButtonClicked -> webApp.onSecondaryButtonClicked { logsState.add("SecondaryButtonClicked") }
|
||||
EventType.SettingsButtonClicked -> webApp.onSettingsButtonClicked { logsState.add("SettingsButtonClicked") }
|
||||
EventType.ShareMessageFailed -> webApp.onShareMessageFailed {
|
||||
logsState.add(it.error)
|
||||
}
|
||||
EventType.ShareMessageSent -> webApp.onShareMessageSent { logsState.add("ShareMessageSent") }
|
||||
EventType.ThemeChanged -> webApp.onThemeChanged { logsState.add("ThemeChanged") }
|
||||
EventType.ViewportChanged -> webApp.onViewportChanged {
|
||||
logsState.add(it)
|
||||
}
|
||||
EventType.WriteAccessRequested -> webApp.onWriteAccessRequested {
|
||||
logsState.add(it.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logsState.forEach {
|
||||
P { Text(it.toString()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<title>Web App Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="application/javascript" src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||
<script type="application/javascript" src="WebApp.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -1,36 +1,43 @@
|
||||
import dev.inmo.kslog.common.*
|
||||
import dev.inmo.micro_utils.coroutines.subscribeLoggingDropExceptions
|
||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||
import dev.inmo.micro_utils.ktor.server.createKtorServer
|
||||
import dev.inmo.tgbotapi.extensions.api.answers.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.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.send.send
|
||||
import dev.inmo.tgbotapi.extensions.api.set.setUserEmojiStatus
|
||||
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.types.buttons.*
|
||||
import dev.inmo.tgbotapi.types.BotCommand
|
||||
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.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onUnhandledCommand
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onWriteAccessAllowed
|
||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.inlineKeyboard
|
||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.replyKeyboard
|
||||
import dev.inmo.tgbotapi.extensions.utils.types.buttons.webAppButton
|
||||
import dev.inmo.tgbotapi.requests.answers.InlineQueryResultsButton
|
||||
import dev.inmo.tgbotapi.types.*
|
||||
import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.InlineQueryResultArticle
|
||||
import dev.inmo.tgbotapi.types.InlineQueries.InputMessageContent.InputTextMessageContent
|
||||
import dev.inmo.tgbotapi.types.webAppQueryIdField
|
||||
import dev.inmo.tgbotapi.types.webapps.WebAppInfo
|
||||
import dev.inmo.tgbotapi.utils.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.application.call
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.http.content.*
|
||||
import io.ktor.server.request.receiveText
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.routing.post
|
||||
import io.ktor.server.routing.routing
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import io.ktor.server.request.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* Accepts two parameters:
|
||||
*
|
||||
* * Telegram Token
|
||||
* * URL where will be placed
|
||||
* * Port (default 8080)
|
||||
*
|
||||
* Will start the server to share the static (index.html and WebApp.js) on 0.0.0.0:8080
|
||||
*/
|
||||
@@ -40,34 +47,82 @@ 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 initiationLogger = KSLog("Initialization")
|
||||
|
||||
val bot = telegramBot(telegramBotAPIUrlsKeeper)
|
||||
createKtorServer(
|
||||
"0.0.0.0",
|
||||
8080,
|
||||
additionalEngineEnvironmentConfigurator = {
|
||||
parentCoroutineContext += Dispatchers.IO
|
||||
}
|
||||
args.getOrNull(2) ?.toIntOrNull() ?: 8080
|
||||
) {
|
||||
routing {
|
||||
static {
|
||||
files(File("WebApp/build/distributions"))
|
||||
default("WebApp/build/distributions/index.html")
|
||||
val baseJsFolder = File("WebApp/build/dist/js/")
|
||||
val prodSubFolder = File(baseJsFolder, "productionExecutable")
|
||||
val devSubFolder = File(baseJsFolder, "developmentExecutable")
|
||||
|
||||
val staticFolder = when {
|
||||
prodSubFolder.exists() -> {
|
||||
initiationLogger.i("Folder for static is ${prodSubFolder.absolutePath}")
|
||||
prodSubFolder
|
||||
}
|
||||
devSubFolder.exists() -> {
|
||||
initiationLogger.i("Folder for static is ${devSubFolder.absolutePath}")
|
||||
devSubFolder
|
||||
}
|
||||
else -> error("""
|
||||
Unable to detect any folder with static. Current working directory: ${File("").absolutePath}.
|
||||
Searched paths:
|
||||
* ${prodSubFolder.absolutePath}
|
||||
* ${devSubFolder.absolutePath}
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
staticFiles("", staticFolder) {
|
||||
default("${staticFolder.absolutePath}${File.separator}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") {
|
||||
val requestBody = call.receiveText()
|
||||
val webAppCheckData = Json { }.decodeFromString(WebAppDataWrapper.serializer(), requestBody)
|
||||
val webAppCheckData = Json.decodeFromString(WebAppDataWrapper.serializer(), requestBody)
|
||||
|
||||
val isSafe = telegramBotAPIUrlsKeeper.checkWebAppData(webAppCheckData.data, webAppCheckData.hash)
|
||||
|
||||
call.respond(HttpStatusCode.OK, isSafe.toString())
|
||||
}
|
||||
post("setCustomEmoji") {
|
||||
val requestBody = call.receiveText()
|
||||
val webAppCheckData = Json.decodeFromString(WebAppDataWrapper.serializer(), requestBody)
|
||||
|
||||
val isSafe = telegramBotAPIUrlsKeeper.checkWebAppData(webAppCheckData.data, webAppCheckData.hash)
|
||||
val rawUserId = call.parameters[userIdField] ?.toLongOrNull() ?.let(::RawChatId) ?: error("$userIdField should be presented as long value")
|
||||
|
||||
val set = if (isSafe) {
|
||||
runCatching {
|
||||
bot.setUserEmojiStatus(
|
||||
UserId(rawUserId),
|
||||
CustomEmojiIdToSet
|
||||
)
|
||||
}.getOrElse { false }
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
call.respond(HttpStatusCode.OK, set.toString())
|
||||
}
|
||||
}
|
||||
}.start(false)
|
||||
|
||||
@@ -94,8 +149,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 {
|
||||
@@ -107,11 +189,14 @@ 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"),
|
||||
)
|
||||
allUpdatesFlow.subscribeSafelyWithoutExceptions(this) {
|
||||
allUpdatesFlow.subscribeLoggingDropExceptions(this) {
|
||||
println(it)
|
||||
}
|
||||
println(getMe())
|
||||
|
||||
28
WebHooks/README.md
Normal file
28
WebHooks/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# WebHooks
|
||||
|
||||
Launches webhook-based simple bot. Use `/start` with bot to get simple info about webhooks
|
||||
|
||||
## Launch
|
||||
|
||||
```bash
|
||||
../gradlew run --args="BOT_TOKEN https://sample.com it/is/subpath 8080 debug"
|
||||
```
|
||||
|
||||
Required arguments:
|
||||
|
||||
1. Token
|
||||
2. Arguments starting with `https://`
|
||||
|
||||
Optional arguments:
|
||||
|
||||
* Any argument == `debug` to enable debug mode
|
||||
* Any argument **not** starting with `https://` and **not** equal to `debug` as **subpath** (will be used as
|
||||
subroute to place listening of webhooks)
|
||||
* Any argument as number of port
|
||||
|
||||
Sample: `TOKEN https://sample.com it/is/subpath 8080` will result to:
|
||||
|
||||
* `TOKEN` used as token
|
||||
* Bot will set up its webhook info as `https://sample.com/it/is/subpath`
|
||||
* Bot will set up to listen webhooks on route `it/is/subpath`
|
||||
* Bot will start to listen any incoming request on port `8080` and url `0.0.0.0`
|
||||
23
WebHooks/build.gradle
Normal file
23
WebHooks/build.gradle
Normal file
@@ -0,0 +1,23 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName="WebHooksKt"
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
implementation "dev.inmo:tgbotapi:$telegram_bot_api_version"
|
||||
implementation "dev.inmo:micro_utils.ktor.server:$micro_utils_version"
|
||||
implementation "io.ktor:ktor-server-cio:$ktor_version"
|
||||
}
|
||||
87
WebHooks/src/main/kotlin/WebHooks.kt
Normal file
87
WebHooks/src/main/kotlin/WebHooks.kt
Normal file
@@ -0,0 +1,87 @@
|
||||
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.ktor.server.createKtorServer
|
||||
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
||||
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||
import dev.inmo.tgbotapi.extensions.api.webhook.setWebhookInfo
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviour
|
||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.includeWebhookHandlingInRoute
|
||||
import dev.inmo.tgbotapi.types.BotCommand
|
||||
import dev.inmo.tgbotapi.types.chat.PrivateChat
|
||||
import dev.inmo.tgbotapi.utils.buildEntities
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
/**
|
||||
* Launches webhook-based simple bot. Required arguments:
|
||||
*
|
||||
* 1. Token
|
||||
* *. Arguments starting with `https://`
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* *. Any argument == `debug` to enable debug mode
|
||||
* *. Any argument **not** starting with `https://` and **not** equal to `debug` as **subpath** (will be used as
|
||||
* subroute to place listening of webhooks)
|
||||
* *. Any argument as number of port
|
||||
*
|
||||
* Sample: `TOKEN https://sample.com it/is/subpath 8080` will result to:
|
||||
*
|
||||
* * `TOKEN` used as token
|
||||
* * Bot will set up its webhook info as `https://sample.com/it/is/subpath`
|
||||
* * Bot will set up to listen webhooks on route `it/is/subpath`
|
||||
* * Bot will start to listen any incoming request on port `8080` and url `0.0.0.0`
|
||||
*/
|
||||
suspend fun main(args: Array<String>) {
|
||||
val botToken = args.first()
|
||||
val address = args.first { it.startsWith("https://") }
|
||||
val subpath = args.drop(1).firstOrNull { it != address && it != "debug" }
|
||||
val port = args.firstNotNullOfOrNull { it.toIntOrNull() } ?: 8080
|
||||
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(botToken)
|
||||
|
||||
val behaviourContext = bot.buildBehaviour (defaultExceptionsHandler = { it.printStackTrace() }) {
|
||||
onCommand("start", initialFilter = { it.chat is PrivateChat }) {
|
||||
reply(
|
||||
it,
|
||||
buildEntities {
|
||||
+"Url: $address" + "\n"
|
||||
+"Listening server: 0.0.0.0" + "\n"
|
||||
+"Listening port: $port"
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
setMyCommands(BotCommand("start", "Get webhook info"))
|
||||
}
|
||||
|
||||
val webhookInfoSubpath = subpath ?.let { "/" + it.removePrefix("/") } ?: "" // drop leading `/` to add it in the beginning for correct construction of subpath
|
||||
bot.setWebhookInfo(address + webhookInfoSubpath)
|
||||
|
||||
createKtorServer(
|
||||
"0.0.0.0",
|
||||
port,
|
||||
) {
|
||||
routing {
|
||||
if (subpath == null) {
|
||||
includeWebhookHandlingInRoute(behaviourContext, block = behaviourContext.asUpdateReceiver)
|
||||
} else {
|
||||
route(subpath) {
|
||||
includeWebhookHandlingInRoute(behaviourContext, block = behaviourContext.asUpdateReceiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start(true)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user