diff --git a/scalabot-app/build.gradle.kts b/scalabot-app/build.gradle.kts index f954c43..922ca96 100644 --- a/scalabot-app/build.gradle.kts +++ b/scalabot-app/build.gradle.kts @@ -8,6 +8,7 @@ plugins { } dependencies { + implementation(project(":scalabot-meta")) implementation(project(":scalabot-extension")) implementation("org.slf4j:slf4j-api:1.7.36") diff --git a/scalabot-app/src/main/kotlin/AppConfigs.kt b/scalabot-app/src/main/kotlin/AppConfigs.kt index 09b38c6..27700cb 100644 --- a/scalabot-app/src/main/kotlin/AppConfigs.kt +++ b/scalabot-app/src/main/kotlin/AppConfigs.kt @@ -5,14 +5,14 @@ import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.reflect.TypeToken import mu.KotlinLogging -import net.lamgc.scalabot.util.* +import net.lamgc.scalabot.config.* +import net.lamgc.scalabot.config.serializer.* import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Proxy import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.repository.RepositoryPolicy import org.telegram.telegrambots.bots.DefaultBotOptions -import org.telegram.telegrambots.meta.ApiConstants import java.io.File import java.net.URL import java.nio.charset.StandardCharsets @@ -22,154 +22,63 @@ import kotlin.system.exitProcess private val log = KotlinLogging.logger { } -/** - * 机器人帐号信息. - * @property name 机器人名称, 建议与实际设定的名称相同. - * @property token 机器人 API Token. - * @property creatorId 机器人创建者, 管理机器人需要使用该信息. - */ -internal data class BotAccount( - val name: String, - val token: String, - val creatorId: Long = -1 -) { - - val id - // 不要想着每次获取都要从 token 里取出有性能损耗. - // 由于 Gson 解析方式, 如果不这么做, 会出现 token 设置前 id 初始化完成, 就只有"0"了, - // 虽然能过单元测试, 但实际使用过程是不能正常用的. - get() = token.substringBefore(":").toLong() +internal fun ProxyType.toTelegramBotsType(): DefaultBotOptions.ProxyType { + return when (this) { + ProxyType.NO_PROXY -> DefaultBotOptions.ProxyType.NO_PROXY + ProxyType.HTTP -> DefaultBotOptions.ProxyType.HTTP + ProxyType.SOCKS4 -> DefaultBotOptions.ProxyType.SOCKS4 + ProxyType.SOCKS5 -> DefaultBotOptions.ProxyType.SOCKS5 + } } -/** - * 机器人配置. - * @property account 机器人帐号信息, 用于访问 API. - * @property disableBuiltInAbility 是否禁用 AbilityBot 自带命令. - * @property extensions 该机器人启用的扩展. - * @property proxy 为该机器人单独设置的代理配置, 如无设置, 则使用 AppConfig 中的代理配置. - */ -internal data class BotConfig( - val enabled: Boolean = true, - val account: BotAccount, - val disableBuiltInAbility: Boolean = false, - val autoUpdateCommandList: Boolean = false, - /* - * 使用构件坐标来选择机器人所使用的扩展包. - * 这么做的原因是我暂时没找到一个合适的方法来让开发者方便地设定自己的扩展 Id, - * 而构件坐标(POM Reference 或者叫 GAV 坐标)是开发者创建 Maven/Gradle 项目时一定会设置的, - * 所以就直接用了. :P - */ - val extensions: Set, - val proxy: ProxyConfig? = ProxyConfig(), - val baseApiUrl: String? = ApiConstants.BASE_URL -) +internal fun ProxyConfig.toAetherProxy(): Proxy? { + return if (type == ProxyType.HTTP) { + Proxy(Proxy.TYPE_HTTP, host, port) + } else { + null + } +} -/** - * 代理配置. - * @property type 代理类型. - * @property host 代理服务端地址. - * @property port 代理服务端端口. - */ -internal data class ProxyConfig( - val type: DefaultBotOptions.ProxyType = DefaultBotOptions.ProxyType.NO_PROXY, - val host: String = "127.0.0.1", - val port: Int = 1080 -) { - - fun toAetherProxy(): Proxy? { - return if (type == DefaultBotOptions.ProxyType.HTTP) { - Proxy(Proxy.TYPE_HTTP, host, port) - } else { - null - } +internal fun MavenRepositoryConfig.toRemoteRepository(proxyConfig: ProxyConfig): RemoteRepository { + val builder = + RemoteRepository.Builder(id ?: createDefaultRepositoryId(), checkRepositoryLayout(layout), url.toString()) + if (proxy != null) { + builder.setProxy(proxy) + } else if (proxyConfig.type == ProxyType.HTTP) { + builder.setProxy(proxyConfig.toAetherProxy()) } -} - -internal data class MetricsConfig( - val enable: Boolean = false, - val port: Int = 9386, - val bindAddress: String? = "0.0.0.0", - val authenticator: UsernameAuthenticator? = null -) - -/** - * Maven 远端仓库配置. - * @property url 仓库地址. - * @property proxy 访问仓库所使用的代理, 仅支持 http/https 代理. - * @property layout 仓库布局版本, Maven 2 及以上使用 `default`, Maven 1 使用 `legacy`. - */ -internal data class MavenRepositoryConfig( - val id: String? = null, - val url: URL, - val proxy: Proxy? = null, - val layout: String = "default", - val enableReleases: Boolean = true, - val enableSnapshots: Boolean = true, - // 可能要设计个 type 来判断解析成什么类型的 Authentication. - val authentication: Authentication? = null -) { - - fun toRemoteRepository(proxyConfig: ProxyConfig): RemoteRepository { - val builder = - RemoteRepository.Builder(id ?: createDefaultRepositoryId(), checkRepositoryLayout(layout), url.toString()) - if (proxy != null) { - builder.setProxy(proxy) - } else if (proxyConfig.type == DefaultBotOptions.ProxyType.HTTP) { - builder.setProxy(proxyConfig.toAetherProxy()) - } - - builder.setReleasePolicy( - RepositoryPolicy( - enableReleases, - RepositoryPolicy.UPDATE_POLICY_NEVER, - RepositoryPolicy.CHECKSUM_POLICY_FAIL - ) + builder.setReleasePolicy( + RepositoryPolicy( + enableReleases, + RepositoryPolicy.UPDATE_POLICY_NEVER, + RepositoryPolicy.CHECKSUM_POLICY_FAIL ) - builder.setSnapshotPolicy( - RepositoryPolicy( - enableSnapshots, - RepositoryPolicy.UPDATE_POLICY_ALWAYS, - RepositoryPolicy.CHECKSUM_POLICY_WARN - ) + ) + builder.setSnapshotPolicy( + RepositoryPolicy( + enableSnapshots, + RepositoryPolicy.UPDATE_POLICY_ALWAYS, + RepositoryPolicy.CHECKSUM_POLICY_WARN ) + ) - return builder.build() - } - - private companion object { - fun checkRepositoryLayout(layoutType: String): String { - val type = layoutType.trim().lowercase() - if (type != "default" && type != "legacy") { - throw IllegalArgumentException("Invalid layout type (expecting 'default' or 'legacy')") - } - return type - } - - private val repoNumber = AtomicInteger(1) - - fun createDefaultRepositoryId(): String { - return "Repository-${repoNumber.getAndIncrement()}" - } - - } + return builder.build() } -/** - * ScalaBot App 配置. - * - * App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中. - * @property proxy Telegram API 代理配置. - * @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据. - * @property mavenRepositories Maven 远端仓库配置. - * @property mavenLocalRepository Maven 本地仓库路径. 相对于运行目录 (而不是 DATA_ROOT 目录) - */ -internal data class AppConfig( - val proxy: ProxyConfig = ProxyConfig(), - val metrics: MetricsConfig = MetricsConfig(), - val mavenRepositories: List = emptyList(), - val mavenLocalRepository: String? = null -) +internal fun checkRepositoryLayout(layoutType: String): String { + val type = layoutType.trim().lowercase() + if (type != "default" && type != "legacy") { + throw IllegalArgumentException("Invalid layout type (expecting 'default' or 'legacy')") + } + return type +} + +private val repoNumberGenerator = AtomicInteger(1) + +internal fun createDefaultRepositoryId(): String { + return "Repository-${repoNumberGenerator.getAndIncrement()}" +} /** * 需要用到的路径. diff --git a/scalabot-app/src/main/kotlin/AppMain.kt b/scalabot-app/src/main/kotlin/AppMain.kt index 6b1b623..e5ba519 100644 --- a/scalabot-app/src/main/kotlin/AppMain.kt +++ b/scalabot-app/src/main/kotlin/AppMain.kt @@ -3,6 +3,10 @@ package net.lamgc.scalabot import io.prometheus.client.exporter.HTTPServer import kotlinx.coroutines.runBlocking import mu.KotlinLogging +import net.lamgc.scalabot.config.AppConfig +import net.lamgc.scalabot.config.BotConfig +import net.lamgc.scalabot.config.MetricsConfig +import net.lamgc.scalabot.config.ProxyType import net.lamgc.scalabot.util.registerShutdownHook import org.eclipse.aether.repository.LocalRepository import org.telegram.telegrambots.bots.DefaultBotOptions @@ -71,9 +75,9 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos private fun getMavenLocalRepository(): LocalRepository { val localPath = - if (config.mavenLocalRepository != null && config.mavenLocalRepository.isNotEmpty()) { + if (config.mavenLocalRepository != null && config.mavenLocalRepository!!.isNotEmpty()) { val repoPath = AppPaths.DATA_ROOT.file.toPath() - .resolve(config.mavenLocalRepository) + .resolve(config.mavenLocalRepository!!) .apply { if (!exists()) { if (!parent.isWritable() || !parent.isReadable()) { @@ -136,15 +140,15 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos log.info { "正在启动机器人 `${botConfig.account.name}`..." } val botOption = DefaultBotOptions().apply { val proxyConfig = - if (botConfig.proxy != null && botConfig.proxy.type != DefaultBotOptions.ProxyType.NO_PROXY) { + if (botConfig.proxy != null && botConfig.proxy!!.type != ProxyType.NO_PROXY) { botConfig.proxy - } else if (config.proxy.type != DefaultBotOptions.ProxyType.NO_PROXY) { + } else if (config.proxy.type != ProxyType.NO_PROXY) { config.proxy } else { null } if (proxyConfig != null) { - proxyType = proxyConfig.type + proxyType = proxyConfig.type.toTelegramBotsType() proxyHost = config.proxy.host proxyPort = config.proxy.port log.debug { "机器人 `${botConfig.account.name}` 已启用代理配置: $proxyConfig" } diff --git a/scalabot-app/src/main/kotlin/BotDBMaker.kt b/scalabot-app/src/main/kotlin/BotDBMaker.kt index bdea555..24737e9 100644 --- a/scalabot-app/src/main/kotlin/BotDBMaker.kt +++ b/scalabot-app/src/main/kotlin/BotDBMaker.kt @@ -2,6 +2,7 @@ package net.lamgc.scalabot import com.google.common.io.Files import mu.KotlinLogging +import net.lamgc.scalabot.config.BotAccount import net.lamgc.scalabot.util.toHexString import org.mapdb.DB import org.mapdb.DBException diff --git a/scalabot-app/src/main/kotlin/ScalaBot.kt b/scalabot-app/src/main/kotlin/ScalaBot.kt index 06fbd4f..6723698 100644 --- a/scalabot-app/src/main/kotlin/ScalaBot.kt +++ b/scalabot-app/src/main/kotlin/ScalaBot.kt @@ -4,6 +4,7 @@ import io.prometheus.client.Counter import io.prometheus.client.Gauge import io.prometheus.client.Summary import mu.KotlinLogging +import net.lamgc.scalabot.config.BotConfig import org.eclipse.aether.artifact.Artifact import org.telegram.abilitybots.api.bot.AbilityBot import org.telegram.abilitybots.api.db.DBContext diff --git a/scalabot-app/src/main/kotlin/util/UsernameAuthenticator.kt b/scalabot-app/src/main/kotlin/util/UsernameAuthenticator.kt deleted file mode 100644 index b5b8770..0000000 --- a/scalabot-app/src/main/kotlin/util/UsernameAuthenticator.kt +++ /dev/null @@ -1,65 +0,0 @@ -package net.lamgc.scalabot.util - -import com.google.gson.* -import com.sun.net.httpserver.BasicAuthenticator -import java.lang.reflect.Type - -class UsernameAuthenticator(private val username: String, private val password: String) : - BasicAuthenticator("metrics") { - override fun checkCredentials(username: String?, password: String?): Boolean = - this.username == username && this.password == password - - fun toJsonObject(): JsonObject = JsonObject().apply { - addProperty("username", username) - addProperty("password", password) - } - - override fun equals(other: Any?): Boolean { - return other is UsernameAuthenticator && this.username == other.username && this.password == other.password - } - - override fun hashCode(): Int { - var result = username.hashCode() - result = 31 * result + password.hashCode() - return result - } - -} - -object UsernameAuthenticatorSerializer : JsonSerializer, - JsonDeserializer { - - override fun serialize( - src: UsernameAuthenticator, - typeOfSrc: Type?, - context: JsonSerializationContext? - ): JsonElement { - return src.toJsonObject() - } - - override fun deserialize( - json: JsonElement, - typeOfT: Type?, - context: JsonDeserializationContext? - ): UsernameAuthenticator? { - if (json.isJsonNull) { - return null - } else if (!json.isJsonObject) { - throw JsonParseException("Invalid attribute value type.") - } - - val jsonObj = json.asJsonObject - - if (jsonObj["username"]?.isJsonPrimitive != true) { - throw JsonParseException("Invalid attribute value: username") - } else if (jsonObj["password"]?.isJsonPrimitive != true) { - throw JsonParseException("Invalid attribute value: password") - } - - if (jsonObj["username"].asString.isEmpty() || jsonObj["password"].asString.isEmpty()) { - throw JsonParseException("`username` or `password` is empty.") - } - return UsernameAuthenticator(jsonObj["username"].asString, jsonObj["password"].asString) - } - -} diff --git a/scalabot-app/src/test/kotlin/AppConfigTest.kt b/scalabot-app/src/test/kotlin/AppConfigTest.kt index ad39f47..a3a6f92 100644 --- a/scalabot-app/src/test/kotlin/AppConfigTest.kt +++ b/scalabot-app/src/test/kotlin/AppConfigTest.kt @@ -1,50 +1,16 @@ package net.lamgc.scalabot import com.github.stefanbirkner.systemlambda.SystemLambda -import com.google.gson.Gson -import com.google.gson.JsonObject import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.junit.jupiter.api.io.TempDir import java.io.File -import java.util.* -import kotlin.math.abs import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue -internal class BotAccountTest { - - @Test - fun `id getter`() { - val accountId = abs(Random().nextInt()).toLong() - assertEquals(accountId, BotAccount("Test", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", 0).id) - } - - @Test - fun deserializerTest() { - val accountId = abs(Random().nextInt()).toLong() - val creatorId = abs(Random().nextInt()).toLong() - val botAccountJsonObject = Gson().fromJson( - """ - { - "name": "TestBot", - "token": "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", - "creatorId": $creatorId - } - """.trimIndent(), JsonObject::class.java - ) - val botAccount = Gson().fromJson(botAccountJsonObject, BotAccount::class.java) - assertEquals(botAccountJsonObject["name"].asString, botAccount.name) - assertEquals(botAccountJsonObject["token"].asString, botAccount.token) - assertEquals(accountId, botAccount.id, "BotAccount ID does not match expectations.") - assertEquals(creatorId, botAccount.creatorId) - } - -} - internal class AppPathsTest { @Test diff --git a/scalabot-meta/build.gradle.kts b/scalabot-meta/build.gradle.kts new file mode 100644 index 0000000..f0ef77e --- /dev/null +++ b/scalabot-meta/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + kotlin("jvm") version "1.6.10" + id("org.jetbrains.kotlinx.kover") version "0.5.1" +} + +group = "net.lamgc" +version = "0.3.1" + +repositories { + mavenCentral() +} + +dependencies { + val aetherVersion = "1.1.0" + implementation("org.eclipse.aether:aether-api:$aetherVersion") + implementation("org.eclipse.aether:aether-util:$aetherVersion") + + implementation("org.telegram:telegrambots-meta:6.0.1") + + implementation("com.google.code.gson:gson:2.9.0") + + testImplementation(kotlin("test")) + testImplementation("io.mockk:mockk:1.12.4") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") +} + +tasks.getByName("test") { + useJUnitPlatform() +} \ No newline at end of file diff --git a/scalabot-meta/src/main/kotlin/Configs.kt b/scalabot-meta/src/main/kotlin/Configs.kt new file mode 100644 index 0000000..f8299f0 --- /dev/null +++ b/scalabot-meta/src/main/kotlin/Configs.kt @@ -0,0 +1,108 @@ +package net.lamgc.scalabot.config + +import org.eclipse.aether.artifact.Artifact +import org.eclipse.aether.repository.Authentication +import org.eclipse.aether.repository.Proxy +import org.telegram.telegrambots.meta.ApiConstants +import java.net.URL + +/** + * 机器人帐号信息. + * @property name 机器人名称, 建议与实际设定的名称相同. + * @property token 机器人 API Token. + * @property creatorId 机器人创建者, 管理机器人需要使用该信息. + */ +data class BotAccount( + val name: String, + val token: String, + val creatorId: Long = -1 +) { + + val id + // 不要想着每次获取都要从 token 里取出有性能损耗. + // 由于 Gson 解析方式, 如果不这么做, 会出现 token 设置前 id 初始化完成, 就只有"0"了, + // 虽然能过单元测试, 但实际使用过程是不能正常用的. + get() = token.substringBefore(":").toLong() +} + +/** + * 机器人配置. + * @property account 机器人帐号信息, 用于访问 API. + * @property disableBuiltInAbility 是否禁用 AbilityBot 自带命令. + * @property extensions 该机器人启用的扩展. + * @property proxy 为该机器人单独设置的代理配置, 如无设置, 则使用 AppConfig 中的代理配置. + */ +data class BotConfig( + val enabled: Boolean = true, + val account: BotAccount, + val disableBuiltInAbility: Boolean = false, + val autoUpdateCommandList: Boolean = false, + /* + * 使用构件坐标来选择机器人所使用的扩展包. + * 这么做的原因是我暂时没找到一个合适的方法来让开发者方便地设定自己的扩展 Id, + * 而构件坐标(POM Reference 或者叫 GAV 坐标)是开发者创建 Maven/Gradle 项目时一定会设置的, + * 所以就直接用了. :P + */ + val extensions: Set, + val proxy: ProxyConfig? = ProxyConfig(), + val baseApiUrl: String? = ApiConstants.BASE_URL +) + +enum class ProxyType { + NO_PROXY, + HTTP, + SOCKS4, + SOCKS5 +} + +/** + * 代理配置. + * @property type 代理类型. + * @property host 代理服务端地址. + * @property port 代理服务端端口. + */ +data class ProxyConfig( + val type: ProxyType = ProxyType.NO_PROXY, + val host: String = "127.0.0.1", + val port: Int = 1080 +) + +data class MetricsConfig( + val enable: Boolean = false, + val port: Int = 9386, + val bindAddress: String? = "0.0.0.0", + val authenticator: UsernameAuthenticator? = null +) + +/** + * Maven 远端仓库配置. + * @property url 仓库地址. + * @property proxy 访问仓库所使用的代理, 仅支持 http/https 代理. + * @property layout 仓库布局版本, Maven 2 及以上使用 `default`, Maven 1 使用 `legacy`. + */ +data class MavenRepositoryConfig( + val id: String? = null, + val url: URL, + val proxy: Proxy? = null, + val layout: String = "default", + val enableReleases: Boolean = true, + val enableSnapshots: Boolean = true, + // 可能要设计个 type 来判断解析成什么类型的 Authentication. + val authentication: Authentication? = null +) + +/** + * ScalaBot App 配置. + * + * App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中. + * @property proxy Telegram API 代理配置. + * @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据. + * @property mavenRepositories Maven 远端仓库配置. + * @property mavenLocalRepository Maven 本地仓库路径. 相对于运行目录 (而不是 DATA_ROOT 目录) + */ +data class AppConfig( + val proxy: ProxyConfig = ProxyConfig(), + val metrics: MetricsConfig = MetricsConfig(), + val mavenRepositories: List = emptyList(), + val mavenLocalRepository: String? = null +) diff --git a/scalabot-meta/src/main/kotlin/UsernameAuthenticator.kt b/scalabot-meta/src/main/kotlin/UsernameAuthenticator.kt new file mode 100644 index 0000000..f81a6f4 --- /dev/null +++ b/scalabot-meta/src/main/kotlin/UsernameAuthenticator.kt @@ -0,0 +1,26 @@ +package net.lamgc.scalabot.config + +import com.google.gson.JsonObject +import com.sun.net.httpserver.BasicAuthenticator + +class UsernameAuthenticator(private val username: String, private val password: String) : + BasicAuthenticator("metrics") { + override fun checkCredentials(username: String?, password: String?): Boolean = + this.username == username && this.password == password + + fun toJsonObject(): JsonObject = JsonObject().apply { + addProperty("username", username) + addProperty("password", password) + } + + override fun equals(other: Any?): Boolean { + return other is UsernameAuthenticator && this.username == other.username && this.password == other.password + } + + override fun hashCode(): Int { + var result = username.hashCode() + result = 31 * result + password.hashCode() + return result + } + +} diff --git a/scalabot-app/src/main/kotlin/util/Serializers.kt b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt similarity index 69% rename from scalabot-app/src/main/kotlin/util/Serializers.kt rename to scalabot-meta/src/main/kotlin/serializer/Serializer.kt index b574154..ab6de86 100644 --- a/scalabot-app/src/main/kotlin/util/Serializers.kt +++ b/scalabot-meta/src/main/kotlin/serializer/Serializer.kt @@ -1,40 +1,41 @@ -package net.lamgc.scalabot.util +package net.lamgc.scalabot.config.serializer import com.google.gson.* -import net.lamgc.scalabot.MavenRepositoryConfig +import net.lamgc.scalabot.config.MavenRepositoryConfig +import net.lamgc.scalabot.config.ProxyType +import net.lamgc.scalabot.config.UsernameAuthenticator import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Proxy import org.eclipse.aether.util.repository.AuthenticationBuilder -import org.telegram.telegrambots.bots.DefaultBotOptions import java.lang.reflect.Type import java.net.URL -internal object ProxyTypeSerializer : JsonDeserializer, - JsonSerializer { +object ProxyTypeSerializer : JsonDeserializer, + JsonSerializer { override fun deserialize( json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext? - ): DefaultBotOptions.ProxyType { + ): ProxyType { if (json.isJsonNull) { - return DefaultBotOptions.ProxyType.NO_PROXY + return ProxyType.NO_PROXY } if (!json.isJsonPrimitive) { throw JsonParseException("Wrong configuration value type.") } val value = json.asString.trim() try { - return DefaultBotOptions.ProxyType.valueOf(value.uppercase()) + return ProxyType.valueOf(value.uppercase()) } catch (e: IllegalArgumentException) { throw JsonParseException("Invalid value: $value") } } override fun serialize( - src: DefaultBotOptions.ProxyType, + src: ProxyType, typeOfSrc: Type?, context: JsonSerializationContext? ): JsonElement { @@ -42,7 +43,7 @@ internal object ProxyTypeSerializer : JsonDeserializer, JsonDeserializer { +object ArtifactSerializer : JsonSerializer, JsonDeserializer { override fun serialize(src: Artifact, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { val gavBuilder = StringBuilder("${src.groupId}:${src.artifactId}") if (!src.extension.equals("jar")) { @@ -63,7 +64,7 @@ internal object ArtifactSerializer : JsonSerializer, JsonDeserializer< } -internal object AuthenticationSerializer : JsonDeserializer { +object AuthenticationSerializer : JsonDeserializer { override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication { if (json !is JsonObject) { @@ -90,7 +91,7 @@ private object SerializerUtils { } } -internal object MavenRepositoryConfigSerializer +object MavenRepositoryConfigSerializer : JsonDeserializer { override fun deserialize( @@ -125,3 +126,41 @@ internal object MavenRepositoryConfigSerializer } } } + +object UsernameAuthenticatorSerializer : JsonSerializer, + JsonDeserializer { + + override fun serialize( + src: UsernameAuthenticator, + typeOfSrc: Type?, + context: JsonSerializationContext? + ): JsonElement { + return src.toJsonObject() + } + + override fun deserialize( + json: JsonElement, + typeOfT: Type?, + context: JsonDeserializationContext? + ): UsernameAuthenticator? { + if (json.isJsonNull) { + return null + } else if (!json.isJsonObject) { + throw JsonParseException("Invalid attribute value type.") + } + + val jsonObj = json.asJsonObject + + if (jsonObj["username"]?.isJsonPrimitive != true) { + throw JsonParseException("Invalid attribute value: username") + } else if (jsonObj["password"]?.isJsonPrimitive != true) { + throw JsonParseException("Invalid attribute value: password") + } + + if (jsonObj["username"].asString.isEmpty() || jsonObj["password"].asString.isEmpty()) { + throw JsonParseException("`username` or `password` is empty.") + } + return UsernameAuthenticator(jsonObj["username"].asString, jsonObj["password"].asString) + } + +} diff --git a/scalabot-meta/src/test/kotlin/BotAccountTest.kt b/scalabot-meta/src/test/kotlin/BotAccountTest.kt new file mode 100644 index 0000000..db11830 --- /dev/null +++ b/scalabot-meta/src/test/kotlin/BotAccountTest.kt @@ -0,0 +1,38 @@ +package net.lamgc.scalabot.config + +import com.google.gson.Gson +import com.google.gson.JsonObject +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import java.util.* +import kotlin.math.abs + +internal class BotAccountTest { + + @Test + fun `id getter`() { + val accountId = abs(Random().nextInt()).toLong() + assertEquals(accountId, BotAccount("Test", "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", 0).id) + } + + @Test + fun deserializerTest() { + val accountId = abs(Random().nextInt()).toLong() + val creatorId = abs(Random().nextInt()).toLong() + val botAccountJsonObject = Gson().fromJson( + """ + { + "name": "TestBot", + "token": "${accountId}:AAHErDroUTznQsOd_oZPJ6cQEj4Z5mGHO10", + "creatorId": $creatorId + } + """.trimIndent(), JsonObject::class.java + ) + val botAccount = Gson().fromJson(botAccountJsonObject, BotAccount::class.java) + assertEquals(botAccountJsonObject["name"].asString, botAccount.name) + assertEquals(botAccountJsonObject["token"].asString, botAccount.token) + assertEquals(accountId, botAccount.id, "BotAccount ID does not match expectations.") + assertEquals(creatorId, botAccount.creatorId) + } + +} \ No newline at end of file diff --git a/scalabot-app/src/test/kotlin/util/ArtifactSerializerTest.kt b/scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt similarity index 97% rename from scalabot-app/src/test/kotlin/util/ArtifactSerializerTest.kt rename to scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt index 100c5b4..6c12ea4 100644 --- a/scalabot-app/src/test/kotlin/util/ArtifactSerializerTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/ArtifactSerializerTest.kt @@ -1,6 +1,6 @@ @file:Suppress("PackageDirectoryMismatch") -package net.lamgc.scalabot.util +package net.lamgc.scalabot.config.serializer import com.google.gson.JsonObject import com.google.gson.JsonParseException diff --git a/scalabot-app/src/test/kotlin/util/SerializersKtTest.kt b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt similarity index 95% rename from scalabot-app/src/test/kotlin/util/SerializersKtTest.kt rename to scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt index 591c8e8..0a116ed 100644 --- a/scalabot-app/src/test/kotlin/util/SerializersKtTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/SerializersKtTest.kt @@ -1,19 +1,16 @@ -package util +package net.lamgc.scalabot.config.serializer import com.google.gson.* import io.mockk.every import io.mockk.mockk import io.mockk.verify -import net.lamgc.scalabot.MavenRepositoryConfig -import net.lamgc.scalabot.util.AuthenticationSerializer -import net.lamgc.scalabot.util.MavenRepositoryConfigSerializer -import net.lamgc.scalabot.util.ProxyTypeSerializer +import net.lamgc.scalabot.config.MavenRepositoryConfig +import net.lamgc.scalabot.config.ProxyType import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.AuthenticationContext import org.eclipse.aether.repository.Proxy import org.intellij.lang.annotations.Language import org.junit.jupiter.api.Assertions.assertThrows -import org.telegram.telegrambots.bots.DefaultBotOptions import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.lang.reflect.Type @@ -26,7 +23,7 @@ internal class SerializersKtTest { private val method: Method init { - val clazz = Class.forName("net.lamgc.scalabot.util.SerializerUtils") + val clazz = Class.forName("net.lamgc.scalabot.config.serializer.SerializerUtils") method = clazz.getDeclaredMethod("checkJsonKey", JsonObject::class.java, String::class.java) method.isAccessible = true instance = clazz.getDeclaredField("INSTANCE").apply { @@ -69,7 +66,7 @@ internal class ProxyTypeSerializerTest { @Test fun `serialize test`() { - for (type in DefaultBotOptions.ProxyType.values()) { + for (type in ProxyType.values()) { assertEquals( JsonPrimitive(type.name), ProxyTypeSerializer.serialize(type, null, null), "ProxyType 序列化结果与预期不符." @@ -91,11 +88,11 @@ internal class ProxyTypeSerializerTest { } assertEquals( - DefaultBotOptions.ProxyType.NO_PROXY, + ProxyType.NO_PROXY, ProxyTypeSerializer.deserialize(JsonNull.INSTANCE, null, null) ) - for (type in DefaultBotOptions.ProxyType.values()) { + for (type in ProxyType.values()) { assertEquals( type, ProxyTypeSerializer.deserialize(JsonPrimitive(type.name), null, null), "ProxyType 反序列化结果与预期不符." diff --git a/scalabot-app/src/test/kotlin/util/UsernameAuthenticatorTest.kt b/scalabot-meta/src/test/kotlin/serializer/UsernameAuthenticatorTest.kt similarity index 97% rename from scalabot-app/src/test/kotlin/util/UsernameAuthenticatorTest.kt rename to scalabot-meta/src/test/kotlin/serializer/UsernameAuthenticatorTest.kt index 92629db..8f71a37 100644 --- a/scalabot-app/src/test/kotlin/util/UsernameAuthenticatorTest.kt +++ b/scalabot-meta/src/test/kotlin/serializer/UsernameAuthenticatorTest.kt @@ -1,6 +1,7 @@ -package net.lamgc.scalabot.util +package net.lamgc.scalabot.config.serializer import com.google.gson.* +import net.lamgc.scalabot.config.UsernameAuthenticator import org.junit.jupiter.api.assertThrows import kotlin.test.* diff --git a/settings.gradle.kts b/settings.gradle.kts index 7db5f57..3b0db76 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,3 +4,4 @@ rootProject.name = "scalabot" include(":scalabot-app") include(":scalabot-extension") include("scalabot-ext-example") +include("scalabot-meta")