ScalaBot/scalabot-app/src/main/kotlin/AppConfigs.kt
LamGC 581eeba20b
feat(config): 新增 HTTPS 代理类型, 增加 Maven 对 HTTPS 代理的支持.
为 ProxyType 增加 HTTPS 类型, 同时为 Aether 增加 Https 代理支持, 方便用户使用现有的公开代理下载依赖包.
2022-06-25 23:00:51 +08:00

265 lines
8.2 KiB
Kotlin

package net.lamgc.scalabot
import ch.qos.logback.core.PropertyDefinerBase
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import mu.KotlinLogging
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 java.io.File
import java.net.URL
import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.system.exitProcess
private val log = KotlinLogging.logger { }
internal fun ProxyType.toTelegramBotsType(): DefaultBotOptions.ProxyType {
return when (this) {
ProxyType.NO_PROXY -> DefaultBotOptions.ProxyType.NO_PROXY
ProxyType.HTTP -> DefaultBotOptions.ProxyType.HTTP
ProxyType.HTTPS -> DefaultBotOptions.ProxyType.HTTP
ProxyType.SOCKS4 -> DefaultBotOptions.ProxyType.SOCKS4
ProxyType.SOCKS5 -> DefaultBotOptions.ProxyType.SOCKS5
}
}
internal fun ProxyConfig.toAetherProxy(): Proxy? {
val typeStr = when (type) {
ProxyType.HTTP -> Proxy.TYPE_HTTP
ProxyType.HTTPS -> Proxy.TYPE_HTTPS
else -> return null
}
return Proxy(typeStr, host, port)
}
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())
}
builder.setReleasePolicy(
RepositoryPolicy(
enableReleases,
RepositoryPolicy.UPDATE_POLICY_NEVER,
RepositoryPolicy.CHECKSUM_POLICY_FAIL
)
)
builder.setSnapshotPolicy(
RepositoryPolicy(
enableSnapshots,
RepositoryPolicy.UPDATE_POLICY_ALWAYS,
RepositoryPolicy.CHECKSUM_POLICY_WARN
)
)
return builder.build()
}
private 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)
private fun createDefaultRepositoryId(): String {
return "Repository-${repoNumberGenerator.getAndIncrement()}"
}
/**
* 需要用到的路径.
*
* 必须提供 `pathSupplier` 或 `fileSupplier` 其中一个, 才能正常提供路径.
*/
internal enum class AppPaths(
private val pathSupplier: () -> String = { fileSupplier.invoke().canonicalPath },
private val initializer: AppPaths.() -> Unit = AppPaths::defaultInitializer,
private val fileSupplier: () -> File = { File(pathSupplier()) }
) {
/**
* 数据根目录.
*
* 所有运行数据的存放位置.
*
* 提示: 结尾不带 `/`.
*/
DATA_ROOT(fileSupplier = {
File(
System.getProperty(PathConst.PROP_DATA_PATH) ?: System.getenv(PathConst.ENV_DATA_PATH)
?: System.getProperty("user.dir") ?: "."
)
}, initializer = {
val f = file
if (!f.exists()) {
f.mkdirs()
}
}),
DEFAULT_CONFIG_APPLICATION({ "$DATA_ROOT/config.json" }, {
if (!file.exists()) {
file.bufferedWriter(StandardCharsets.UTF_8).use {
GsonConst.botConfigGson.toJson(
AppConfig(
mavenRepositories = listOf(
MavenRepositoryConfig(
id = "central",
url = URL(MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL)
)
)
), it
)
}
}
}),
DEFAULT_CONFIG_BOT({ "$DATA_ROOT/bot.json" }, {
if (!file.exists()) {
file.bufferedWriter(StandardCharsets.UTF_8).use {
GsonConst.botConfigGson.toJson(
setOf(
BotConfig(
enabled = true,
proxy = ProxyConfig(),
account = BotAccount(
"Bot Username",
"Bot API Token",
-1
), extensions = emptySet()
)
), it
)
}
}
}),
DATA_DB({ "$DATA_ROOT/data/db/" }),
DATA_LOGS({ "$DATA_ROOT/data/logs/" }),
EXTENSIONS({ "$DATA_ROOT/extensions/" }),
DATA_EXTENSIONS({ "$DATA_ROOT/data/extensions/" }),
TEMP({ "$DATA_ROOT/tmp/" })
;
val file: File
get() = fileSupplier.invoke()
val path: String
get() = pathSupplier.invoke()
private val initialized = AtomicBoolean(false)
@Synchronized
fun initial() {
if (!initialized.get()) {
initializer()
initialized.set(true)
}
}
override fun toString(): String {
return path
}
private object PathConst {
const val PROP_DATA_PATH = "bot.path.data"
const val ENV_DATA_PATH = "BOT_DATA_PATH"
}
}
/**
* 为 LogBack 提供日志目录路径.
*/
internal class LogDirectorySupplier : PropertyDefinerBase() {
override fun getPropertyValue(): String {
return AppPaths.DATA_LOGS.path
}
}
internal object Const {
val config = loadAppConfig()
}
private fun AppPaths.defaultInitializer() {
val f = file
val p = path
if (!f.exists()) {
val result = if (p.endsWith("/")) {
f.mkdirs()
} else {
f.createNewFile()
}
if (!result) {
log.warn { "初始化文件(夹)失败: $p" }
}
}
}
internal fun initialFiles() {
val configFilesNotInitialized = !AppPaths.DEFAULT_CONFIG_APPLICATION.file.exists()
&& !AppPaths.DEFAULT_CONFIG_BOT.file.exists()
for (path in AppPaths.values()) {
path.initial()
}
if (configFilesNotInitialized) {
log.warn { "配置文件已初始化, 请根据需要修改配置文件后重新启动本程序." }
exitProcess(1)
}
}
private object GsonConst {
val baseGson: Gson = GsonBuilder()
.setPrettyPrinting()
.serializeNulls()
.create()
val appConfigGson: Gson = baseGson.newBuilder()
.registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer)
.registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer)
.registerTypeAdapter(Authentication::class.java, AuthenticationSerializer)
.registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer)
.create()
val botConfigGson: Gson = baseGson.newBuilder()
.registerTypeAdapter(ProxyType::class.java, ProxyTypeSerializer)
.registerTypeAdapter(BotConfig::class.java, BotConfigSerializer)
.registerTypeAdapter(Artifact::class.java, ArtifactSerializer)
.create()
}
internal fun loadAppConfig(configFile: File = AppPaths.DEFAULT_CONFIG_APPLICATION.file): AppConfig {
try {
configFile.bufferedReader(StandardCharsets.UTF_8).use {
return GsonConst.appConfigGson.fromJson(it, AppConfig::class.java)!!
}
} catch (e: Exception) {
log.error { "读取 config.json 时发生错误, 请检查配置格式是否正确." }
throw e
}
}
internal fun loadBotConfig(botConfigFile: File = AppPaths.DEFAULT_CONFIG_BOT.file): Set<BotConfig>? {
try {
botConfigFile.bufferedReader(StandardCharsets.UTF_8).use {
return GsonConst.botConfigGson.fromJson(it, object : TypeToken<Set<BotConfig>>() {}.type)!!
}
} catch (e: Exception) {
log.error(e) { "读取 Bot 配置文件 (bot.json) 时发生错误, 请检查配置格式是否正确." }
return null
}
}