BooruGrabberTelegramBot/src/main/kotlin/App.kt

198 lines
7.5 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-07 11:34:07 +00:00
import dev.inmo.micro_utils.repos.add
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-07 09:01:03 +00:00
import dev.inmo.micro_utils.repos.unset
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-07 11:34:07 +00:00
import dev.inmo.tgbotapi.types.message.content.PhotoContent
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 {
val urls = result.map { it.url.also(::println) }
chatsUrlsSeen.add(chatId, urls)
when {
urls.isEmpty() -> return@runCatchingSafely
urls.size == 1 -> sendPhoto(
chatId,
FileUrl(urls.first())
)
settings.gallery -> urls.chunked(mediaCountInMediaGroup.last + 1).forEach {
sendVisualMediaGroup(
chatId,
it.map {
TelegramMediaPhoto(FileUrl(it))
}
)
}
else -> urls.forEach {
sendPhoto(
chatId,
FileUrl(it)
)
}
}
}.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()
settings ?.let {
chatsSendingJobs[chatId] = settings.scheduler.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) {
reply(it, EnableArgsParser(it.chat.id, repo, scope).getFormattedHelp().takeIf { it.isNotBlank() } ?: return@onCommand)
}
2022-09-07 11:34:07 +00:00
onCommand("enable", requireOnlyCommandInMessage = false) {
val args = it.content.textSources.drop(1).joinToString("") { it.source }.split(" ")
val parser = EnableArgsParser(it.chat.id, repo, this)
2022-09-07 09:01:03 +00:00
runCatchingSafely {
2022-09-07 11:34:07 +00:00
parser.parse(args)
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
}
}
}
onCommand("disable", requireOnlyCommandInMessage = true) {
runCatchingSafely {
repo.unset(it.chat.id)
}
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"),
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()
}