diff --git a/build.gradle b/build.gradle index 2e4e8d3..d0254d3 100644 --- a/build.gradle +++ b/build.gradle @@ -17,12 +17,19 @@ plugins { repositories { mavenCentral() + maven { url "https://jitpack.io" } } dependencies { implementation libs.kotlin implementation libs.tgbotapi + implementation libs.microutils.repos.exposed + implementation libs.microutils.repos.cache + implementation libs.kslog + implementation libs.exposed + implementation libs.psql + implementation libs.imageboard } application { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c8bacea..2cf8ce5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,11 +2,27 @@ kotlin = "1.7.10" tgbotapi = "3.2.0" +microutils = "0.12.7" +imageboard = "2.5.2" +krontab = "0.8.0" +kslog = "0.5.1" +exposed = "0.39.2" +psql = "42.5.0" [libraries] kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } + tgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "tgbotapi" } +microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" } +microutils-repos-cache = { module = "dev.inmo:micro_utils.repos.cache", version.ref = "microutils" } +krontab = { module = "dev.inmo:krontab", version.ref = "krontab" } +kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } + +exposed = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" } +psql = { module = "org.postgresql:postgresql", version.ref = "psql" } + +imageboard = { module = "com.github.Kodehawa:imageboard-api", version.ref = "imageboard" } # Libs for classpath kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } diff --git a/src/main/kotlin/telegram_bot/App.kt b/src/main/kotlin/App.kt similarity index 66% rename from src/main/kotlin/telegram_bot/App.kt rename to src/main/kotlin/App.kt index e81907c..9c56bbc 100644 --- a/src/main/kotlin/telegram_bot/App.kt +++ b/src/main/kotlin/App.kt @@ -1,13 +1,18 @@ -package telegram_bot - +import dev.inmo.micro_utils.repos.cache.cache.FullKVCache +import dev.inmo.micro_utils.repos.cache.cached +import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo +import dev.inmo.micro_utils.repos.mappers.withMapper 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.buildBehaviourWithLongPolling import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand +import dev.inmo.tgbotapi.types.ChatId import java.io.File import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex import kotlinx.serialization.json.Json +import models.Config /** * This method by default expects one argument in [args] field: telegram bot configuration @@ -23,6 +28,22 @@ suspend fun main(args: Array) { // that is kotlin coroutine scope which will be used in requests and parallel works under the hood val scope = CoroutineScope(Dispatchers.Default) + val repo = ExposedKeyValueRepo( + config.database.database, + { long("chat_id") }, + { text("config") }, + "configs" + ).withMapper( + { chatId }, + { json.encodeToString(ChatConfig.serializer(), this) }, + { ChatId(this) }, + { json.decodeFromString(ChatConfig.serializer(), this) }, + ).cached(FullKVCache(), scope = scope) + + val chatsChangingMutex = Mutex() + val chatsSendingJobs = mutableMapOf() + + // here should be main logic of your bot bot.buildBehaviourWithLongPolling(scope) { // in this lambda you will be able to call methods without "bot." prefix diff --git a/src/main/kotlin/ChatConfig.kt b/src/main/kotlin/ChatConfig.kt new file mode 100644 index 0000000..1750e1e --- /dev/null +++ b/src/main/kotlin/ChatConfig.kt @@ -0,0 +1,11 @@ +import dev.inmo.krontab.* +import kotlinx.serialization.Serializable + +@Serializable +data class ChatConfig( + val krontab: KrontabTemplate +) { + val scheduler by lazy { + krontab.toSchedule() + } +} diff --git a/src/main/kotlin/telegram_bot/Config.kt b/src/main/kotlin/models/Config.kt similarity index 52% rename from src/main/kotlin/telegram_bot/Config.kt rename to src/main/kotlin/models/Config.kt index 0f01b91..af967d2 100644 --- a/src/main/kotlin/telegram_bot/Config.kt +++ b/src/main/kotlin/models/Config.kt @@ -1,8 +1,9 @@ -package telegram_bot +package models import kotlinx.serialization.Serializable @Serializable data class Config( - val token: String + val token: String, + val database: DatabaseConfig ) diff --git a/src/main/kotlin/models/DBConnectOptions.kt b/src/main/kotlin/models/DBConnectOptions.kt new file mode 100644 index 0000000..ad1c502 --- /dev/null +++ b/src/main/kotlin/models/DBConnectOptions.kt @@ -0,0 +1,9 @@ +package models + +import kotlinx.serialization.Serializable + +@Serializable +data class DBConnectOptions( + val attempts: Int = 3, + val delay: Long = 1000L +) diff --git a/src/main/kotlin/models/DatabaseConfig.kt b/src/main/kotlin/models/DatabaseConfig.kt new file mode 100644 index 0000000..7ce9542 --- /dev/null +++ b/src/main/kotlin/models/DatabaseConfig.kt @@ -0,0 +1,37 @@ +package models + +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.postgresql.Driver +import java.sql.Connection + +@Serializable +data class DatabaseConfig( + val url: String = "jdbc:pgsql://localhost:12346/test", + val driver: String = Driver::class.qualifiedName!!, + val username: String = "", + val password: String = "", + val reconnectOptions: DBConnectOptions? = DBConnectOptions() +) { + @Transient + val database: Database = (0 until (reconnectOptions ?.attempts ?: 1)).firstNotNullOfOrNull { + runCatching { + Database.connect( + url, + driver, + username, + password + ).also { + it.transactionManager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE // Or Connection.TRANSACTION_READ_UNCOMMITTED + it.connector().close() + } + }.onFailure { + logger.e(it) + Thread.sleep(reconnectOptions ?.delay ?: return@onFailure) + }.getOrNull() + } ?: error("Unable to create database") +}