refactor(config): 将与配置相关的内容迁移到 scalabot-meta 模块.

通过将配置迁移到单独的模块, 可以方便使用其他程序扩展 ScaleBot, 而不仅仅是让 ScaleBot 成为扩展的平台.

BREAKING CHANGE: 与配置有关的 Class 移动到了 scalabot-meta 模块.

目前仅所有配置类(以 `Config` 结尾的 Class)和相应的序列化类(以 `Serializer` 结尾的)都迁移到了 meta 模块, 但其工具方法则作为扩展函数保留在 app 模块中.
这么做的好处是为了方便其他应用(例如 ScalaBot 外部管理程序)根据需要生成配置文件.
scalabot-meta 将会作为依赖项发布, 可根据需要获取 ScalaBot-meta 生成 ScalaBot 的配置.
此次改动普通用户无需迁移.
This commit is contained in:
LamGC 2022-06-20 20:55:04 +08:00
parent dbc4232dd6
commit 289b9678f2
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D
16 changed files with 325 additions and 268 deletions

View File

@ -8,6 +8,7 @@ plugins {
} }
dependencies { dependencies {
implementation(project(":scalabot-meta"))
implementation(project(":scalabot-extension")) implementation(project(":scalabot-extension"))
implementation("org.slf4j:slf4j-api:1.7.36") implementation("org.slf4j:slf4j-api:1.7.36")

View File

@ -5,14 +5,14 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import mu.KotlinLogging 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.artifact.Artifact
import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.Proxy import org.eclipse.aether.repository.Proxy
import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.repository.RepositoryPolicy import org.eclipse.aether.repository.RepositoryPolicy
import org.telegram.telegrambots.bots.DefaultBotOptions import org.telegram.telegrambots.bots.DefaultBotOptions
import org.telegram.telegrambots.meta.ApiConstants
import java.io.File import java.io.File
import java.net.URL import java.net.URL
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@ -22,154 +22,63 @@ import kotlin.system.exitProcess
private val log = KotlinLogging.logger { } private val log = KotlinLogging.logger { }
/** internal fun ProxyType.toTelegramBotsType(): DefaultBotOptions.ProxyType {
* 机器人帐号信息. return when (this) {
* @property name 机器人名称, 建议与实际设定的名称相同. ProxyType.NO_PROXY -> DefaultBotOptions.ProxyType.NO_PROXY
* @property token 机器人 API Token. ProxyType.HTTP -> DefaultBotOptions.ProxyType.HTTP
* @property creatorId 机器人创建者, 管理机器人需要使用该信息. ProxyType.SOCKS4 -> DefaultBotOptions.ProxyType.SOCKS4
*/ ProxyType.SOCKS5 -> DefaultBotOptions.ProxyType.SOCKS5
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 ProxyConfig.toAetherProxy(): Proxy? {
* 机器人配置. return if (type == ProxyType.HTTP) {
* @property account 机器人帐号信息, 用于访问 API. Proxy(Proxy.TYPE_HTTP, host, port)
* @property disableBuiltInAbility 是否禁用 AbilityBot 自带命令. } else {
* @property extensions 该机器人启用的扩展. null
* @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<Artifact>,
val proxy: ProxyConfig? = ProxyConfig(),
val baseApiUrl: String? = ApiConstants.BASE_URL
)
/** internal fun MavenRepositoryConfig.toRemoteRepository(proxyConfig: ProxyConfig): RemoteRepository {
* 代理配置. val builder =
* @property type 代理类型. RemoteRepository.Builder(id ?: createDefaultRepositoryId(), checkRepositoryLayout(layout), url.toString())
* @property host 代理服务端地址. if (proxy != null) {
* @property port 代理服务端端口. builder.setProxy(proxy)
*/ } else if (proxyConfig.type == ProxyType.HTTP) {
internal data class ProxyConfig( builder.setProxy(proxyConfig.toAetherProxy())
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
}
} }
} builder.setReleasePolicy(
RepositoryPolicy(
internal data class MetricsConfig( enableReleases,
val enable: Boolean = false, RepositoryPolicy.UPDATE_POLICY_NEVER,
val port: Int = 9386, RepositoryPolicy.CHECKSUM_POLICY_FAIL
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.setSnapshotPolicy( )
RepositoryPolicy( builder.setSnapshotPolicy(
enableSnapshots, RepositoryPolicy(
RepositoryPolicy.UPDATE_POLICY_ALWAYS, enableSnapshots,
RepositoryPolicy.CHECKSUM_POLICY_WARN RepositoryPolicy.UPDATE_POLICY_ALWAYS,
) RepositoryPolicy.CHECKSUM_POLICY_WARN
) )
)
return builder.build() 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()}"
}
}
} }
/** internal fun checkRepositoryLayout(layoutType: String): String {
* ScalaBot App 配置. val type = layoutType.trim().lowercase()
* if (type != "default" && type != "legacy") {
* App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中. throw IllegalArgumentException("Invalid layout type (expecting 'default' or 'legacy')")
* @property proxy Telegram API 代理配置. }
* @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据. return type
* @property mavenRepositories Maven 远端仓库配置. }
* @property mavenLocalRepository Maven 本地仓库路径. 相对于运行目录 (而不是 DATA_ROOT 目录)
*/ private val repoNumberGenerator = AtomicInteger(1)
internal data class AppConfig(
val proxy: ProxyConfig = ProxyConfig(), internal fun createDefaultRepositoryId(): String {
val metrics: MetricsConfig = MetricsConfig(), return "Repository-${repoNumberGenerator.getAndIncrement()}"
val mavenRepositories: List<MavenRepositoryConfig> = emptyList(), }
val mavenLocalRepository: String? = null
)
/** /**
* 需要用到的路径. * 需要用到的路径.

View File

@ -3,6 +3,10 @@ package net.lamgc.scalabot
import io.prometheus.client.exporter.HTTPServer import io.prometheus.client.exporter.HTTPServer
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import mu.KotlinLogging 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 net.lamgc.scalabot.util.registerShutdownHook
import org.eclipse.aether.repository.LocalRepository import org.eclipse.aether.repository.LocalRepository
import org.telegram.telegrambots.bots.DefaultBotOptions import org.telegram.telegrambots.bots.DefaultBotOptions
@ -71,9 +75,9 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos
private fun getMavenLocalRepository(): LocalRepository { private fun getMavenLocalRepository(): LocalRepository {
val localPath = val localPath =
if (config.mavenLocalRepository != null && config.mavenLocalRepository.isNotEmpty()) { if (config.mavenLocalRepository != null && config.mavenLocalRepository!!.isNotEmpty()) {
val repoPath = AppPaths.DATA_ROOT.file.toPath() val repoPath = AppPaths.DATA_ROOT.file.toPath()
.resolve(config.mavenLocalRepository) .resolve(config.mavenLocalRepository!!)
.apply { .apply {
if (!exists()) { if (!exists()) {
if (!parent.isWritable() || !parent.isReadable()) { if (!parent.isWritable() || !parent.isReadable()) {
@ -136,15 +140,15 @@ internal class Launcher(private val config: AppConfig = Const.config) : AutoClos
log.info { "正在启动机器人 `${botConfig.account.name}`..." } log.info { "正在启动机器人 `${botConfig.account.name}`..." }
val botOption = DefaultBotOptions().apply { val botOption = DefaultBotOptions().apply {
val proxyConfig = 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 botConfig.proxy
} else if (config.proxy.type != DefaultBotOptions.ProxyType.NO_PROXY) { } else if (config.proxy.type != ProxyType.NO_PROXY) {
config.proxy config.proxy
} else { } else {
null null
} }
if (proxyConfig != null) { if (proxyConfig != null) {
proxyType = proxyConfig.type proxyType = proxyConfig.type.toTelegramBotsType()
proxyHost = config.proxy.host proxyHost = config.proxy.host
proxyPort = config.proxy.port proxyPort = config.proxy.port
log.debug { "机器人 `${botConfig.account.name}` 已启用代理配置: $proxyConfig" } log.debug { "机器人 `${botConfig.account.name}` 已启用代理配置: $proxyConfig" }

View File

@ -2,6 +2,7 @@ package net.lamgc.scalabot
import com.google.common.io.Files import com.google.common.io.Files
import mu.KotlinLogging import mu.KotlinLogging
import net.lamgc.scalabot.config.BotAccount
import net.lamgc.scalabot.util.toHexString import net.lamgc.scalabot.util.toHexString
import org.mapdb.DB import org.mapdb.DB
import org.mapdb.DBException import org.mapdb.DBException

View File

@ -4,6 +4,7 @@ import io.prometheus.client.Counter
import io.prometheus.client.Gauge import io.prometheus.client.Gauge
import io.prometheus.client.Summary import io.prometheus.client.Summary
import mu.KotlinLogging import mu.KotlinLogging
import net.lamgc.scalabot.config.BotConfig
import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.Artifact
import org.telegram.abilitybots.api.bot.AbilityBot import org.telegram.abilitybots.api.bot.AbilityBot
import org.telegram.abilitybots.api.db.DBContext import org.telegram.abilitybots.api.db.DBContext

View File

@ -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<UsernameAuthenticator>,
JsonDeserializer<UsernameAuthenticator> {
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)
}
}

View File

@ -1,50 +1,16 @@
package net.lamgc.scalabot package net.lamgc.scalabot
import com.github.stefanbirkner.systemlambda.SystemLambda import com.github.stefanbirkner.systemlambda.SystemLambda
import com.google.gson.Gson
import com.google.gson.JsonObject
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import org.junit.jupiter.api.io.TempDir import org.junit.jupiter.api.io.TempDir
import java.io.File import java.io.File
import java.util.*
import kotlin.math.abs
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import kotlin.test.assertTrue 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 { internal class AppPathsTest {
@Test @Test

View File

@ -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>("test") {
useJUnitPlatform()
}

View File

@ -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<Artifact>,
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<MavenRepositoryConfig> = emptyList(),
val mavenLocalRepository: String? = null
)

View File

@ -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
}
}

View File

@ -1,40 +1,41 @@
package net.lamgc.scalabot.util package net.lamgc.scalabot.config.serializer
import com.google.gson.* 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.Artifact
import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.Proxy import org.eclipse.aether.repository.Proxy
import org.eclipse.aether.util.repository.AuthenticationBuilder import org.eclipse.aether.util.repository.AuthenticationBuilder
import org.telegram.telegrambots.bots.DefaultBotOptions
import java.lang.reflect.Type import java.lang.reflect.Type
import java.net.URL import java.net.URL
internal object ProxyTypeSerializer : JsonDeserializer<DefaultBotOptions.ProxyType>, object ProxyTypeSerializer : JsonDeserializer<ProxyType>,
JsonSerializer<DefaultBotOptions.ProxyType> { JsonSerializer<ProxyType> {
override fun deserialize( override fun deserialize(
json: JsonElement, json: JsonElement,
typeOfT: Type?, typeOfT: Type?,
context: JsonDeserializationContext? context: JsonDeserializationContext?
): DefaultBotOptions.ProxyType { ): ProxyType {
if (json.isJsonNull) { if (json.isJsonNull) {
return DefaultBotOptions.ProxyType.NO_PROXY return ProxyType.NO_PROXY
} }
if (!json.isJsonPrimitive) { if (!json.isJsonPrimitive) {
throw JsonParseException("Wrong configuration value type.") throw JsonParseException("Wrong configuration value type.")
} }
val value = json.asString.trim() val value = json.asString.trim()
try { try {
return DefaultBotOptions.ProxyType.valueOf(value.uppercase()) return ProxyType.valueOf(value.uppercase())
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
throw JsonParseException("Invalid value: $value") throw JsonParseException("Invalid value: $value")
} }
} }
override fun serialize( override fun serialize(
src: DefaultBotOptions.ProxyType, src: ProxyType,
typeOfSrc: Type?, typeOfSrc: Type?,
context: JsonSerializationContext? context: JsonSerializationContext?
): JsonElement { ): JsonElement {
@ -42,7 +43,7 @@ internal object ProxyTypeSerializer : JsonDeserializer<DefaultBotOptions.ProxyTy
} }
} }
internal object ArtifactSerializer : JsonSerializer<Artifact>, JsonDeserializer<Artifact> { object ArtifactSerializer : JsonSerializer<Artifact>, JsonDeserializer<Artifact> {
override fun serialize(src: Artifact, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement { override fun serialize(src: Artifact, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
val gavBuilder = StringBuilder("${src.groupId}:${src.artifactId}") val gavBuilder = StringBuilder("${src.groupId}:${src.artifactId}")
if (!src.extension.equals("jar")) { if (!src.extension.equals("jar")) {
@ -63,7 +64,7 @@ internal object ArtifactSerializer : JsonSerializer<Artifact>, JsonDeserializer<
} }
internal object AuthenticationSerializer : JsonDeserializer<Authentication> { object AuthenticationSerializer : JsonDeserializer<Authentication> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication { override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication {
if (json !is JsonObject) { if (json !is JsonObject) {
@ -90,7 +91,7 @@ private object SerializerUtils {
} }
} }
internal object MavenRepositoryConfigSerializer object MavenRepositoryConfigSerializer
: JsonDeserializer<MavenRepositoryConfig> { : JsonDeserializer<MavenRepositoryConfig> {
override fun deserialize( override fun deserialize(
@ -125,3 +126,41 @@ internal object MavenRepositoryConfigSerializer
} }
} }
} }
object UsernameAuthenticatorSerializer : JsonSerializer<UsernameAuthenticator>,
JsonDeserializer<UsernameAuthenticator> {
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)
}
}

View File

@ -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)
}
}

View File

@ -1,6 +1,6 @@
@file:Suppress("PackageDirectoryMismatch") @file:Suppress("PackageDirectoryMismatch")
package net.lamgc.scalabot.util package net.lamgc.scalabot.config.serializer
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParseException import com.google.gson.JsonParseException

View File

@ -1,19 +1,16 @@
package util package net.lamgc.scalabot.config.serializer
import com.google.gson.* import com.google.gson.*
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import net.lamgc.scalabot.MavenRepositoryConfig import net.lamgc.scalabot.config.MavenRepositoryConfig
import net.lamgc.scalabot.util.AuthenticationSerializer import net.lamgc.scalabot.config.ProxyType
import net.lamgc.scalabot.util.MavenRepositoryConfigSerializer
import net.lamgc.scalabot.util.ProxyTypeSerializer
import org.eclipse.aether.repository.Authentication import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.AuthenticationContext import org.eclipse.aether.repository.AuthenticationContext
import org.eclipse.aether.repository.Proxy import org.eclipse.aether.repository.Proxy
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertThrows
import org.telegram.telegrambots.bots.DefaultBotOptions
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method import java.lang.reflect.Method
import java.lang.reflect.Type import java.lang.reflect.Type
@ -26,7 +23,7 @@ internal class SerializersKtTest {
private val method: Method private val method: Method
init { 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 = clazz.getDeclaredMethod("checkJsonKey", JsonObject::class.java, String::class.java)
method.isAccessible = true method.isAccessible = true
instance = clazz.getDeclaredField("INSTANCE").apply { instance = clazz.getDeclaredField("INSTANCE").apply {
@ -69,7 +66,7 @@ internal class ProxyTypeSerializerTest {
@Test @Test
fun `serialize test`() { fun `serialize test`() {
for (type in DefaultBotOptions.ProxyType.values()) { for (type in ProxyType.values()) {
assertEquals( assertEquals(
JsonPrimitive(type.name), ProxyTypeSerializer.serialize(type, null, null), JsonPrimitive(type.name), ProxyTypeSerializer.serialize(type, null, null),
"ProxyType 序列化结果与预期不符." "ProxyType 序列化结果与预期不符."
@ -91,11 +88,11 @@ internal class ProxyTypeSerializerTest {
} }
assertEquals( assertEquals(
DefaultBotOptions.ProxyType.NO_PROXY, ProxyType.NO_PROXY,
ProxyTypeSerializer.deserialize(JsonNull.INSTANCE, null, null) ProxyTypeSerializer.deserialize(JsonNull.INSTANCE, null, null)
) )
for (type in DefaultBotOptions.ProxyType.values()) { for (type in ProxyType.values()) {
assertEquals( assertEquals(
type, ProxyTypeSerializer.deserialize(JsonPrimitive(type.name), null, null), type, ProxyTypeSerializer.deserialize(JsonPrimitive(type.name), null, null),
"ProxyType 反序列化结果与预期不符." "ProxyType 反序列化结果与预期不符."

View File

@ -1,6 +1,7 @@
package net.lamgc.scalabot.util package net.lamgc.scalabot.config.serializer
import com.google.gson.* import com.google.gson.*
import net.lamgc.scalabot.config.UsernameAuthenticator
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import kotlin.test.* import kotlin.test.*

View File

@ -4,3 +4,4 @@ rootProject.name = "scalabot"
include(":scalabot-app") include(":scalabot-app")
include(":scalabot-extension") include(":scalabot-extension")
include("scalabot-ext-example") include("scalabot-ext-example")
include("scalabot-meta")