mirror of
https://github.com/InsanusMokrassar/BooruGrabberTelegramBot.git
synced 2024-11-21 23:53:46 +00:00
temporal progress
This commit is contained in:
parent
4a6e7e472b
commit
0b6f3aeb17
@ -28,6 +28,8 @@ dependencies {
|
|||||||
implementation libs.microutils.repos.cache
|
implementation libs.microutils.repos.cache
|
||||||
implementation libs.kslog
|
implementation libs.kslog
|
||||||
implementation libs.exposed
|
implementation libs.exposed
|
||||||
|
implementation libs.clikt
|
||||||
|
implementation libs.krontab
|
||||||
implementation libs.psql
|
implementation libs.psql
|
||||||
implementation libs.imageboard
|
implementation libs.imageboard
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
version: "3.4"
|
version: "3.4"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
server:
|
booru_grabber_postgres:
|
||||||
build: .
|
image: postgres
|
||||||
restart: unless-stopped
|
container_name: "booru_grabber_postgres"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: "test"
|
||||||
|
POSTGRES_PASSWORD: "test"
|
||||||
|
POSTGRES_DB: "test"
|
||||||
|
ports:
|
||||||
|
- "8092:5432"
|
||||||
|
@ -8,6 +8,7 @@ krontab = "0.8.0"
|
|||||||
kslog = "0.5.1"
|
kslog = "0.5.1"
|
||||||
exposed = "0.39.2"
|
exposed = "0.39.2"
|
||||||
psql = "42.5.0"
|
psql = "42.5.0"
|
||||||
|
clikt = "3.5.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
|
||||||
@ -24,6 +25,8 @@ psql = { module = "org.postgresql:postgresql", version.ref = "psql" }
|
|||||||
|
|
||||||
imageboard = { module = "com.github.Kodehawa:imageboard-api", version.ref = "imageboard" }
|
imageboard = { module = "com.github.Kodehawa:imageboard-api", version.ref = "imageboard" }
|
||||||
|
|
||||||
|
clikt = { module = "com.github.ajalt.clikt:clikt", version.ref = "clikt" }
|
||||||
|
|
||||||
# Libs for classpath
|
# Libs for classpath
|
||||||
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
|
kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
|
||||||
|
@ -1,16 +1,30 @@
|
|||||||
|
import dev.inmo.krontab.utils.asFlow
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
|
||||||
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
|
||||||
import dev.inmo.micro_utils.repos.cache.cached
|
import dev.inmo.micro_utils.repos.cache.cached
|
||||||
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
|
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
|
||||||
import dev.inmo.micro_utils.repos.mappers.withMapper
|
import dev.inmo.micro_utils.repos.mappers.withMapper
|
||||||
|
import dev.inmo.micro_utils.repos.unset
|
||||||
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
import dev.inmo.tgbotapi.bot.ktor.telegramBot
|
||||||
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
import dev.inmo.tgbotapi.extensions.api.bot.getMe
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.delete
|
||||||
|
import dev.inmo.tgbotapi.extensions.api.send.media.*
|
||||||
import dev.inmo.tgbotapi.extensions.api.send.reply
|
import dev.inmo.tgbotapi.extensions.api.send.reply
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviourWithLongPolling
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommandWithArgs
|
||||||
|
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
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import models.Config
|
import models.Config
|
||||||
|
|
||||||
@ -35,28 +49,104 @@ suspend fun main(args: Array<String>) {
|
|||||||
"configs"
|
"configs"
|
||||||
).withMapper(
|
).withMapper(
|
||||||
{ chatId },
|
{ chatId },
|
||||||
{ json.encodeToString(ChatConfig.serializer(), this) },
|
{ json.encodeToString(ChatSettings.serializer(), this) },
|
||||||
{ ChatId(this) },
|
{ ChatId(this) },
|
||||||
{ json.decodeFromString(ChatConfig.serializer(), this) },
|
{ json.decodeFromString(ChatSettings.serializer(), this) },
|
||||||
).cached(FullKVCache(), scope = scope)
|
).cached(FullKVCache(), scope = scope)
|
||||||
|
|
||||||
val chatsChangingMutex = Mutex()
|
val chatsChangingMutex = Mutex()
|
||||||
val chatsSendingJobs = mutableMapOf<ChatId, Job>()
|
val chatsSendingJobs = mutableMapOf<ChatId, Job>()
|
||||||
|
|
||||||
|
|
||||||
// here should be main logic of your bot
|
// here should be main logic of your bot
|
||||||
bot.buildBehaviourWithLongPolling(scope) {
|
bot.buildBehaviourWithLongPolling(scope) {
|
||||||
// in this lambda you will be able to call methods without "bot." prefix
|
// in this lambda you will be able to call methods without "bot." prefix
|
||||||
val me = getMe()
|
val me = getMe()
|
||||||
|
|
||||||
// this method will create point to react on each /start command
|
suspend fun refreshChatJob(chatId: ChatId, settings: ChatSettings?) {
|
||||||
onCommand("start", requireOnlyCommandInMessage = true) {
|
val settings = settings ?: repo.get(chatId)
|
||||||
// simply reply :)
|
chatsChangingMutex.withLock {
|
||||||
reply(it, "Hello, I am ${me.firstName}")
|
chatsSendingJobs[chatId] ?.cancel()
|
||||||
|
settings ?.let {
|
||||||
|
chatsSendingJobs[chatId] = settings.scheduler.asFlow().subscribeSafelyWithoutExceptions(scope) {
|
||||||
|
val result = settings.makeRequest()
|
||||||
|
when {
|
||||||
|
result.isEmpty() -> return@subscribeSafelyWithoutExceptions
|
||||||
|
result.size == 1 -> sendPhoto(
|
||||||
|
chatId,
|
||||||
|
FileUrl(result.first().url)
|
||||||
|
)
|
||||||
|
settings.gallery -> result.chunked(mediaCountInMediaGroup.last + 1).forEach {
|
||||||
|
sendVisualMediaGroup(
|
||||||
|
chatId,
|
||||||
|
it.map {
|
||||||
|
TelegramMediaPhoto(FileUrl(it.url))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> result.forEach {
|
||||||
|
sendPhoto(
|
||||||
|
chatId,
|
||||||
|
FileUrl(it.url)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// That will be called on the end of bot initiation. After that println will be started long polling and bot will
|
doForAllWithNextPaging {
|
||||||
// react on your commands
|
repo.keys(it).also {
|
||||||
|
it.results.forEach {
|
||||||
|
refreshChatJob(it, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.onNewValue.subscribeSafelyWithoutExceptions(this) {
|
||||||
|
refreshChatJob(it.first, it.second)
|
||||||
|
}
|
||||||
|
repo.onValueRemoved.subscribeSafelyWithoutExceptions(this) {
|
||||||
|
refreshChatJob(it, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
onCommand(Regex("(help|start)"), requireOnlyCommandInMessage = true) {
|
||||||
|
reply(it, EnableArgsParser(it.chat.id, repo, scope).getFormattedHelp().takeIf { it.isNotBlank() } ?: return@onCommand)
|
||||||
|
}
|
||||||
|
onCommandWithArgs("enable") { message, strings ->
|
||||||
|
val parser = EnableArgsParser(message.chat.id, repo, this)
|
||||||
|
runCatchingSafely {
|
||||||
|
parser.parse(strings)
|
||||||
|
}.onFailure { e ->
|
||||||
|
e.printStackTrace()
|
||||||
|
if (message.chat is PrivateChat) {
|
||||||
|
reply(message, parser.getFormattedHelp())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runCatchingSafely {
|
||||||
|
if (message.chat is ChannelChat) {
|
||||||
|
delete(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
println(me)
|
println(me)
|
||||||
}.join()
|
}.join()
|
||||||
}
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
import dev.inmo.krontab.*
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ChatConfig(
|
|
||||||
val krontab: KrontabTemplate
|
|
||||||
) {
|
|
||||||
val scheduler by lazy {
|
|
||||||
krontab.toSchedule()
|
|
||||||
}
|
|
||||||
}
|
|
58
src/main/kotlin/ChatSettings.kt
Normal file
58
src/main/kotlin/ChatSettings.kt
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import dev.inmo.krontab.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.serialization.*
|
||||||
|
import kotlinx.serialization.builtins.serializer
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import net.kodehawa.lib.imageboards.DefaultImageBoards
|
||||||
|
import net.kodehawa.lib.imageboards.ImageBoard
|
||||||
|
import net.kodehawa.lib.imageboards.boards.DefaultBoards
|
||||||
|
import net.kodehawa.lib.imageboards.entities.BoardImage
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChatSettings(
|
||||||
|
val query: String,
|
||||||
|
val krontabTemplate: KrontabTemplate,
|
||||||
|
@Serializable(BoardSerializer::class)
|
||||||
|
private val boardBase: DefaultBoards,
|
||||||
|
val count: Int = 1,
|
||||||
|
val gallery: Boolean = false
|
||||||
|
) {
|
||||||
|
val scheduler by lazy {
|
||||||
|
krontabTemplate.toSchedule()
|
||||||
|
}
|
||||||
|
|
||||||
|
val board: ImageBoard<*>
|
||||||
|
get() = when (boardBase) {
|
||||||
|
DefaultBoards.R34 -> DefaultImageBoards.RULE34
|
||||||
|
DefaultBoards.E621 -> DefaultImageBoards.E621
|
||||||
|
DefaultBoards.KONACHAN -> DefaultImageBoards.KONACHAN
|
||||||
|
DefaultBoards.YANDERE -> DefaultImageBoards.YANDERE
|
||||||
|
DefaultBoards.DANBOORU -> DefaultImageBoards.DANBOORU
|
||||||
|
DefaultBoards.SAFEBOORU -> DefaultImageBoards.SAFEBOORU
|
||||||
|
DefaultBoards.GELBOORU -> DefaultImageBoards.GELBOORU
|
||||||
|
DefaultBoards.E926 -> DefaultImageBoards.E926
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun makeRequest(): List<BoardImage> {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
board.search(count, query).blocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializer(DefaultBoards::class)
|
||||||
|
object BoardSerializer : KSerializer<DefaultBoards> {
|
||||||
|
override val descriptor: SerialDescriptor = String.serializer().descriptor
|
||||||
|
val types = DefaultBoards.values().associateBy { it.name.lowercase() }
|
||||||
|
override fun deserialize(decoder: Decoder): DefaultBoards {
|
||||||
|
val type = decoder.decodeString()
|
||||||
|
return types.getValue(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(encoder: Encoder, value: DefaultBoards) {
|
||||||
|
encoder.encodeString(value.name.lowercase())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/main/kotlin/EnableArgsParser.kt
Normal file
38
src/main/kotlin/EnableArgsParser.kt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
|
import com.github.ajalt.clikt.parameters.arguments.*
|
||||||
|
import com.github.ajalt.clikt.parameters.options.*
|
||||||
|
import com.github.ajalt.clikt.parameters.types.int
|
||||||
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
|
import dev.inmo.micro_utils.repos.KeyValueRepo
|
||||||
|
import dev.inmo.micro_utils.repos.set
|
||||||
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
class EnableArgsParser(
|
||||||
|
private val chatId: ChatId,
|
||||||
|
private val repo: KeyValueRepo<ChatId, ChatSettings>,
|
||||||
|
private val scope: CoroutineScope
|
||||||
|
) : CliktCommand(name = "enable") {
|
||||||
|
val count by option("-n").int().help("Amount of pictures to grab each trigger time").default(1).check("Count should be in range 1-10") {
|
||||||
|
it in 1 .. 10
|
||||||
|
}
|
||||||
|
val query by argument().multiple(required = true).help("Your query to booru. Use syntax \"-- -sometag\" to add excluding of some tag in query")
|
||||||
|
val krontab by option("-k", "--krontab").required().help("Krontab in format * * * * *. See https://bookstack.inmo.dev/books/krontab/page/string-format")
|
||||||
|
val board by option("-b", "--board").convert {
|
||||||
|
ChatSettings.BoardSerializer.types.getValue(it)
|
||||||
|
}.required().help("Board type. Possible values: ${ChatSettings.BoardSerializer.types.keys.joinToString { it }}")
|
||||||
|
val gallery by option("-g", "--gallery").flag(default = false).help("Effective only when count passed > 1. Will send chosen images as gallery instead of separated images")
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val chatSettings = ChatSettings(
|
||||||
|
query.joinToString(" "),
|
||||||
|
krontab,
|
||||||
|
board,
|
||||||
|
count,
|
||||||
|
gallery
|
||||||
|
)
|
||||||
|
scope.launchSafelyWithoutExceptions {
|
||||||
|
repo.set(chatId, chatSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user