diff --git a/README.md b/README.md index 7ecc629..e3ee059 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,99 @@ Bot for booru-boards grabbing. It uses [imageboard-api](https://github.com/Kodeh Sample bot presented here: [@booru_grabber_bot](https://t.me/booru_grabber_bot) +## Fast how to start + +1. Create a `config.json`. Minimal required config: + +```json +{ + "token": "your bot token", + "database": { + "url": "jdbc:postgresql://booru_grabber_postgres:5432/test", + "username": "test", + "password": "test" + } +} +``` + +All available properties: + +```json +{ + "token": "your bot token", + "database": { + "url": "jdbc:postgresql://booru_grabber_postgres:5432/test", + "driver": "org.postgresql.Driver", + "username": "test", + "password": "test", + "reconnectOptions": { + "attempts": 3, + "delay": 1000 + } + }, + "client": { + "connectionTimeoutMillis": null, + "requestTimeoutMillis": null, + "responseTimeoutMillis": null, + "proxy": { + "hostname": "proxy.example.com", + "type": "socks", + "port": 1080, + "username": null, + "password": null + } + } +} +``` + +Property reference: + +**`token`** *(required)* — Telegram bot token from [@BotFather](https://t.me/BotFather). + +**`database`** *(required)*: + +- `url` — JDBC connection URL (default: `jdbc:pgsql://localhost:12346/test`) +- `driver` — JDBC driver class (default: `org.postgresql.Driver`) +- `username` — database user (default: empty) +- `password` — database password (default: empty) +- `reconnectOptions.attempts` — how many times to retry connecting on startup (default: `3`) +- `reconnectOptions.delay` — delay in milliseconds between retries (default: `1000`) + +**`client`** *(optional)* — HTTP client settings for Telegram API requests: + +- `connectionTimeoutMillis` — connection timeout in ms +- `requestTimeoutMillis` — write/request timeout in ms +- `responseTimeoutMillis` — read/response timeout in ms +- `proxy.hostname` *(required if proxy set)* — proxy host +- `proxy.type` — `socks` (default) or `http` +- `proxy.port` — proxy port (default: `1080` for socks, `3128` for http) +- `proxy.username` — proxy username (optional; for socks, password is required when username is set) +- `proxy.password` — proxy password (optional) + +2. In `docker-compose.yml`, uncomment the `booru_grabber_bot` service and set the path to your config file: + +```yaml +services: + booru_grabber_postgres: + image: postgres + container_name: "booru_grabber_postgres" + environment: + POSTGRES_USER: "test" + POSTGRES_PASSWORD: "test" + POSTGRES_DB: "test" + booru_grabber_bot: + image: insanusmokrassar/booru_grabber_bot + container_name: "booru_grabber_bot" + volumes: + - "/absolute/path/to/config.json:/booru_grabber_bot/config.json:ro" +``` + +3. Start the services: + +```bash +docker-compose up -d +``` + ## Available commands Bot have two helping commands: `/start` and `/help`. These commands will return help for bot `/request`/`/enable` commands: diff --git a/gradle.properties b/gradle.properties index 6155c48..ace3e0f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,4 @@ org.gradle.parallel=true kotlin.js.generate.externals=true kotlin.incremental=true -docker_version=0.0.5 +docker_version=0.0.6 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index be25136..04365ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,15 @@ [versions] -kotlin = "2.2.10" -tgbotapi = "28.0.2" -microutils = "0.26.3" -imageboard = "2.7.0" -krontab = "2.7.2" -kslog = "1.5.0" -ktor = "3.2.3" -exposed = "0.61.0" -psql = "42.7.4" -clikt = "5.0.1" +kotlin = "2.3.21" +tgbotapi = "33.1.0" +microutils = "0.29.2" +imageboard = "2.6.1.1" +krontab = "2.9.0" +kslog = "1.6.1" +ktor = "3.4.3" +exposed = "1.2.0" +psql = "42.7.11" +clikt = "5.1.0" [libraries] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2733ed5..03b32a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/kotlin/App.kt b/src/main/kotlin/App.kt index 2c9bd49..084528b 100644 --- a/src/main/kotlin/App.kt +++ b/src/main/kotlin/App.kt @@ -106,12 +106,12 @@ suspend fun main(args: Array) { } result.take(settings.count) }.takeIf { it.isNotEmpty() } ?: return - runCatchingSafely { + runCatchingLogging { val urls = result.map { it.url } chatsUrlsSeenRepo.add(chatId, urls) seenUrls.addAll(urls) when { - urls.isEmpty() -> return@runCatchingSafely + urls.isEmpty() -> return@runCatchingLogging urls.size == 1 -> sendPhoto( chatId, FileUrl(urls.first()), @@ -143,24 +143,24 @@ suspend fun main(args: Array) { chatsChangingMutex.withLock { chatsSendingJobs[chatId] ?.cancel() settings ?.scheduler ?.let { - chatsSendingJobs[chatId] = it.asFlowWithDelays().subscribeSafelyWithoutExceptions(scope) { + chatsSendingJobs[chatId] = it.asFlowWithDelays().subscribeLoggingDropExceptions(scope) { triggerSendForChat(chatId, settings) } } } } - repo.onNewValue.subscribeSafelyWithoutExceptions(this) { + repo.onNewValue.subscribeLoggingDropExceptions(this) { refreshChatJob(it.first, it.second) } - repo.onValueRemoved.subscribeSafelyWithoutExceptions(this) { + repo.onValueRemoved.subscribeLoggingDropExceptions(this) { refreshChatJob(it, null) } doForAllWithNextPaging { repo.keys(it).also { it.results.forEach { - runCatchingSafely { + runCatchingLogging { refreshChatJob(it, null) } } @@ -173,16 +173,16 @@ suspend fun main(args: Array) { onCommand("enable", requireOnlyCommandInMessage = false) { val args = it.content.textSources.drop(1).joinToString("") { it.source }.split(" ") val parser = EnableArgsParser() - runCatchingSafely { + runCatchingLogging { parser.parse(args) - repo.set(ChatId(it.chat.id.chatId), parser.resultSettings ?: return@runCatchingSafely) + repo.set(ChatId(it.chat.id.chatId), parser.resultSettings ?: return@runCatchingLogging) }.onFailure { e -> e.printStackTrace() if (it.chat is PrivateChat) { reply(it, parser.getFormattedHelp()!!) } } - runCatchingSafely { + runCatchingLogging { if (it.chat is ChannelChat) { delete(it) } @@ -200,7 +200,7 @@ suspend fun main(args: Array) { } } else { val parser = EnableArgsParser(repo.get(ChatId(it.chat.id.chatId)) ?: ChatSettings.DEFAULT) - runCatchingSafely { + runCatchingLogging { parser.parse(args) parser.resultSettings }.onFailure { e -> @@ -214,18 +214,18 @@ suspend fun main(args: Array) { triggerSendForChat(ChatId(it.chat.id.chatId), chatSettings ?: return@onCommand) } onCommand("disable", requireOnlyCommandInMessage = true) { - runCatchingSafely { + runCatchingLogging { repo.unset(ChatId(it.chat.id.chatId)) } - runCatchingSafely { + runCatchingLogging { delete(it) } } onCommand("take_settings", requireOnlyCommandInMessage = true) { - val settings = runCatchingSafely { + val settings = runCatchingLogging { repo.get(ChatId(it.chat.id.chatId)) }.getOrNull() - runCatchingSafely { + runCatchingLogging { if (settings == null) { reply(it, "You didn't enable requesting") } else { diff --git a/src/main/kotlin/ChatSettings.kt b/src/main/kotlin/ChatSettings.kt index a862ef1..50692cb 100644 --- a/src/main/kotlin/ChatSettings.kt +++ b/src/main/kotlin/ChatSettings.kt @@ -1,3 +1,5 @@ +@file:OptIn(ExperimentalSerializationApi::class) + import dev.inmo.krontab.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -44,6 +46,8 @@ data class ChatSettings( } } + @ExperimentalSerializationApi + @Suppress("EXTERNAL_SERIALIZER_USELESS") @Serializer(DefaultBoards::class) object BoardSerializer : KSerializer { override val descriptor: SerialDescriptor = String.serializer().descriptor diff --git a/src/main/kotlin/models/DatabaseConfig.kt b/src/main/kotlin/models/DatabaseConfig.kt index 7ce9542..2fb5938 100644 --- a/src/main/kotlin/models/DatabaseConfig.kt +++ b/src/main/kotlin/models/DatabaseConfig.kt @@ -4,8 +4,8 @@ import dev.inmo.kslog.common.e import dev.inmo.kslog.common.logger import kotlinx.serialization.Serializable import kotlinx.serialization.Transient -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.transactions.transactionManager +import org.jetbrains.exposed.v1.jdbc.Database +import org.jetbrains.exposed.v1.jdbc.transactions.transactionManager import org.postgresql.Driver import java.sql.Connection