BooruGrabberTelegramBot/src/main/kotlin/App.kt

225 lines
8.9 KiB
Kotlin
Raw Normal View History

2022-09-07 09:01:03 +00:00
import dev.inmo.krontab.utils.asFlow
2022-09-07 11:34:07 +00:00
import dev.inmo.micro_utils.coroutines.*
2022-09-07 09:01:03 +00:00
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
2022-09-09 10:19:14 +00:00
import dev.inmo.micro_utils.repos.*
2022-09-06 18:52:45 +00:00
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
2022-09-07 14:13:39 +00:00
import dev.inmo.micro_utils.repos.cache.cache.KVCache
2022-09-06 18:52:45 +00:00
import dev.inmo.micro_utils.repos.cache.cached
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
2022-09-07 11:34:07 +00:00
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedKeyValuesRepo
2022-09-06 18:52:45 +00:00
import dev.inmo.micro_utils.repos.mappers.withMapper
2022-09-06 18:24:01 +00:00
import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.bot.getMe
2022-09-07 09:01:03 +00:00
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
import dev.inmo.tgbotapi.extensions.api.delete
import dev.inmo.tgbotapi.extensions.api.send.media.*
2022-09-06 18:24:01 +00:00
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.onCommand
2022-09-07 09:01:03 +00:00
import dev.inmo.tgbotapi.requests.abstracts.FileUrl
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.chat.ChannelChat
import dev.inmo.tgbotapi.types.chat.PrivateChat
import dev.inmo.tgbotapi.types.media.TelegramMediaPhoto
2022-09-06 18:24:01 +00:00
import java.io.File
import kotlinx.coroutines.*
2022-09-06 18:52:45 +00:00
import kotlinx.coroutines.sync.Mutex
2022-09-07 09:01:03 +00:00
import kotlinx.coroutines.sync.withLock
2022-09-06 18:24:01 +00:00
import kotlinx.serialization.json.Json
2022-09-06 18:52:45 +00:00
import models.Config
2022-09-07 11:34:07 +00:00
import net.kodehawa.lib.imageboards.ImageBoard
import net.kodehawa.lib.imageboards.entities.BoardImage
2022-09-06 18:24:01 +00:00
/**
* This method by default expects one argument in [args] field: telegram bot configuration
*/
suspend fun main(args: Array<String>) {
// create json to decode config
val json = Json { ignoreUnknownKeys = true }
// decode config
val config: Config = json.decodeFromString(Config.serializer(), File(args.first()).readText())
// that is your bot
val bot = telegramBot(config.token)
2022-09-07 11:34:07 +00:00
ImageBoard.setUserAgent("WhoAmI?")
2022-09-06 18:24:01 +00:00
// that is kotlin coroutine scope which will be used in requests and parallel works under the hood
2022-09-07 11:34:07 +00:00
val scope = CoroutineScope(Dispatchers.Default + ContextSafelyExceptionHandler { it.printStackTrace() })
2022-09-06 18:24:01 +00:00
2022-09-06 18:52:45 +00:00
val repo = ExposedKeyValueRepo(
config.database.database,
{ long("chat_id") },
{ text("config") },
"configs"
).withMapper(
{ chatId },
2022-09-07 09:01:03 +00:00
{ json.encodeToString(ChatSettings.serializer(), this) },
2022-09-06 18:52:45 +00:00
{ ChatId(this) },
2022-09-07 09:01:03 +00:00
{ json.decodeFromString(ChatSettings.serializer(), this) },
2022-09-06 18:52:45 +00:00
).cached(FullKVCache(), scope = scope)
2022-09-07 11:34:07 +00:00
val chatsUrlsSeen = ExposedKeyValuesRepo(
config.database.database,
{ long("chat_id") },
{ text("url") },
"chatsUrlsSeen"
).withMapper(
{ chatId },
{ this },
{ ChatId(this) },
{ this },
2022-09-07 14:13:39 +00:00
).cached(KVCache(128), scope = scope)
2022-09-07 11:34:07 +00:00
2022-09-06 18:52:45 +00:00
val chatsChangingMutex = Mutex()
val chatsSendingJobs = mutableMapOf<ChatId, Job>()
2022-09-06 18:24:01 +00:00
// here should be main logic of your bot
bot.buildBehaviourWithLongPolling(scope) {
// in this lambda you will be able to call methods without "bot." prefix
val me = getMe()
2022-09-07 14:13:39 +00:00
suspend fun triggerSendForChat(chatId: ChatId, settings: ChatSettings) {
val result = let {
val result = mutableListOf<BoardImage>()
var i = 0
while (result.size < settings.count) {
val images = settings.makeRequest(i).takeIf { it.isNotEmpty() } ?: break
result.addAll(
images.filterNot {
chatsUrlsSeen.contains(chatId, it.url)
}
)
i++
}
val toDrop = (result.size - settings.count).takeIf { it > 0 } ?: return@let result
result.dropLast(toDrop)
}.takeIf { it.isNotEmpty() } ?: return
runCatchingSafely {
2022-09-09 10:19:14 +00:00
val urls = result.map { it.url }
2022-09-07 14:13:39 +00:00
chatsUrlsSeen.add(chatId, urls)
when {
urls.isEmpty() -> return@runCatchingSafely
urls.size == 1 -> sendPhoto(
chatId,
2022-09-09 10:42:59 +00:00
FileUrl(urls.first()),
if (settings.attachUrls) urls.first() else null
2022-09-07 14:13:39 +00:00
)
settings.gallery -> urls.chunked(mediaCountInMediaGroup.last + 1).forEach {
sendVisualMediaGroup(
chatId,
it.map {
2022-09-09 10:42:59 +00:00
TelegramMediaPhoto(FileUrl(it), if (settings.attachUrls) it else null)
2022-09-07 14:13:39 +00:00
}
)
}
else -> urls.forEach {
sendPhoto(
chatId,
2022-09-09 10:42:59 +00:00
FileUrl(it),
if (settings.attachUrls) it else null
2022-09-07 14:13:39 +00:00
)
}
}
}.onFailure {
triggerSendForChat(chatId, settings)
}
}
2022-09-07 09:01:03 +00:00
suspend fun refreshChatJob(chatId: ChatId, settings: ChatSettings?) {
val settings = settings ?: repo.get(chatId)
chatsChangingMutex.withLock {
chatsSendingJobs[chatId] ?.cancel()
2022-09-09 10:28:14 +00:00
settings ?.scheduler ?.let {
chatsSendingJobs[chatId] = it.asFlow().subscribeSafelyWithoutExceptions(scope) {
2022-09-07 14:13:39 +00:00
triggerSendForChat(chatId, settings)
2022-09-07 09:01:03 +00:00
}
}
}
}
doForAllWithNextPaging {
repo.keys(it).also {
it.results.forEach {
2022-09-07 11:34:07 +00:00
runCatchingSafely {
refreshChatJob(it, null)
}
2022-09-07 09:01:03 +00:00
}
}
}
repo.onNewValue.subscribeSafelyWithoutExceptions(this) {
refreshChatJob(it.first, it.second)
}
repo.onValueRemoved.subscribeSafelyWithoutExceptions(this) {
refreshChatJob(it, null)
2022-09-06 18:24:01 +00:00
}
2022-09-07 09:01:03 +00:00
onCommand(Regex("(help|start)"), requireOnlyCommandInMessage = true) {
2022-10-07 16:15:06 +00:00
reply(it, EnableArgsParser(onlyQueryIsRequired = false).getFormattedHelp().takeIf { it.isNotBlank() } ?: return@onCommand)
2022-09-07 09:01:03 +00:00
}
2022-09-07 11:34:07 +00:00
onCommand("enable", requireOnlyCommandInMessage = false) {
val args = it.content.textSources.drop(1).joinToString("") { it.source }.split(" ")
2022-10-07 16:15:06 +00:00
val parser = EnableArgsParser(onlyQueryIsRequired = false)
2022-09-07 09:01:03 +00:00
runCatchingSafely {
2022-09-07 11:34:07 +00:00
parser.parse(args)
2022-12-11 06:02:36 +00:00
repo.set(ChatId(it.chat.id.chatId), parser.resultSettings ?: return@runCatchingSafely)
2022-09-07 09:01:03 +00:00
}.onFailure { e ->
e.printStackTrace()
2022-09-07 11:34:07 +00:00
if (it.chat is PrivateChat) {
reply(it, parser.getFormattedHelp())
2022-09-07 09:01:03 +00:00
}
}
runCatchingSafely {
2022-09-07 11:34:07 +00:00
if (it.chat is ChannelChat) {
delete(it)
2022-09-07 09:01:03 +00:00
}
}
}
2022-09-09 10:19:14 +00:00
onCommand("request", requireOnlyCommandInMessage = false) {
2022-09-09 10:28:14 +00:00
val args = it.content.textSources.drop(1).joinToString("") { it.source }.trim().takeIf { it.isNotBlank() } ?.split(" ")
2022-09-09 10:19:14 +00:00
2022-09-09 10:28:14 +00:00
val chatSettings = if (args.isNullOrEmpty()) {
2022-12-11 06:02:36 +00:00
repo.get(ChatId(it.chat.id.chatId)) ?: run {
2022-09-09 10:19:14 +00:00
if (it.chat is PrivateChat) {
reply(it, "Unable to find default config")
}
return@onCommand
}
} else {
2022-12-11 06:02:36 +00:00
val parser = EnableArgsParser(onlyQueryIsRequired = true, repo.get(ChatId(it.chat.id.chatId)) ?: ChatSettings.DEFAULT)
2022-09-09 10:19:14 +00:00
runCatchingSafely {
parser.parse(args)
parser.resultSettings
}.onFailure { e ->
e.printStackTrace()
if (it.chat is PrivateChat) {
reply(it, parser.getFormattedHelp())
}
}.getOrNull()
}
2022-12-11 06:02:36 +00:00
triggerSendForChat(ChatId(it.chat.id.chatId), chatSettings ?: return@onCommand)
2022-09-09 10:19:14 +00:00
}
2022-09-07 09:01:03 +00:00
onCommand("disable", requireOnlyCommandInMessage = true) {
runCatchingSafely {
2022-12-11 06:02:36 +00:00
repo.unset(ChatId(it.chat.id.chatId))
2022-09-07 09:01:03 +00:00
}
runCatchingSafely {
delete(it)
}
}
setMyCommands(
listOf(
BotCommand("start", "Will return the help for the enable command"),
BotCommand("help", "Will return the help for the enable command"),
2022-09-09 10:19:14 +00:00
BotCommand("request", "Will trigger image immediately with custom settings from arguments or default settings of chat if any"),
2022-09-07 09:01:03 +00:00
BotCommand("enable", "Will enable images grabbing for current chat or update exists settings"),
BotCommand("disable", "Will disable bot for current chat"),
)
)
2022-09-06 18:24:01 +00:00
println(me)
}.join()
}