feat: 可通过配置文件设置用于查找扩展包的 Maven 仓库.

使用 Maven 扩展包搜素器将不再限制仓库, 可通过配置文件添加其他仓库.
This commit is contained in:
LamGC 2022-02-26 17:30:31 +08:00
parent 6df9f1b3c7
commit a642948f45
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D
6 changed files with 187 additions and 10 deletions

View File

@ -6,12 +6,17 @@ 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.ArtifactSerializer import net.lamgc.scalabot.util.ArtifactSerializer
import net.lamgc.scalabot.util.AuthenticationSerializer
import net.lamgc.scalabot.util.MavenRepositoryConfigSerializer
import net.lamgc.scalabot.util.ProxyTypeSerializer import net.lamgc.scalabot.util.ProxyTypeSerializer
import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.Artifact
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.telegram.telegrambots.bots.DefaultBotOptions import org.telegram.telegrambots.bots.DefaultBotOptions
import org.telegram.telegrambots.meta.ApiConstants import org.telegram.telegrambots.meta.ApiConstants
import java.io.File import java.io.File
import java.net.URL
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -76,18 +81,56 @@ internal data class ProxyConfig(
internal data class MetricsConfig( internal data class MetricsConfig(
val enable: Boolean = false, val enable: Boolean = false,
val port: Int = 9386, val port: Int = 9386,
val bindAddress: String? = null val bindAddress: String? = "0.0.0.0"
) )
/**
* Maven 远端仓库配置.
* @property url 仓库地址.
* @property proxy 访问仓库所使用的代理, 仅支持 http/https 代理.
* @property layout 仓库布局版本, Maven 2 及以上使用 `default`, Maven 1 使用 `legacy`.
*/
internal data class MavenRepositoryConfig(
val url: URL,
val proxy: Proxy? = Proxy("http", "127.0.0.1", 1080),
val layout: String = "default",
// 可能要设计个 type 来判断解析成什么类型的 Authentication.
val authentication: Authentication? = null
) {
fun toRemoteRepository(): RemoteRepository {
val builder = RemoteRepository.Builder(null, checkRepositoryLayout(layout), url.toString())
if (proxy != null) {
builder.setProxy(proxy)
} else if (Const.config.proxy.type == DefaultBotOptions.ProxyType.HTTP) {
builder.setProxy(Const.config.proxy.toAetherProxy())
}
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
}
}
}
/** /**
* ScalaBot App 配置. * ScalaBot App 配置.
* *
* App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中. * App 配置信息与 BotConfig 分开, 分别存储在各自单独的文件中.
* @property proxy Telegram API 代理配置. * @property proxy Telegram API 代理配置.
* @property metrics 运行指标数据配置. 可通过时序数据库记录运行数据.
* @property mavenRepositories Maven 远端仓库配置.
*/ */
internal data class AppConfig( internal data class AppConfig(
val proxy: ProxyConfig = ProxyConfig(), val proxy: ProxyConfig = ProxyConfig(),
val metrics: MetricsConfig = MetricsConfig() val metrics: MetricsConfig = MetricsConfig(),
val mavenRepositories: List<MavenRepositoryConfig> = emptyList()
) )
/** /**
@ -119,7 +162,15 @@ internal enum class AppPaths(
DEFAULT_CONFIG_APPLICATION({ "$DATA_ROOT/config.json" }, { DEFAULT_CONFIG_APPLICATION({ "$DATA_ROOT/config.json" }, {
if (!file.exists()) { if (!file.exists()) {
file.bufferedWriter(StandardCharsets.UTF_8).use { file.bufferedWriter(StandardCharsets.UTF_8).use {
GsonConst.botConfigGson.toJson(AppConfig(), it) GsonConst.botConfigGson.toJson(
AppConfig(
mavenRepositories = listOf(
MavenRepositoryConfig(
URL(MavenRepositoryExtensionFinder.MAVEN_CENTRAL_URL)
)
)
), it
)
} }
} }
}), }),
@ -217,6 +268,8 @@ private object GsonConst {
val appConfigGson: Gson = baseGson.newBuilder() val appConfigGson: Gson = baseGson.newBuilder()
.registerTypeAdapter(DefaultBotOptions.ProxyType::class.java, ProxyTypeSerializer) .registerTypeAdapter(DefaultBotOptions.ProxyType::class.java, ProxyTypeSerializer)
.registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer)
.registerTypeAdapter(Authentication::class.java, AuthenticationSerializer)
.create() .create()
val botConfigGson: Gson = baseGson.newBuilder() val botConfigGson: Gson = baseGson.newBuilder()

View File

@ -53,6 +53,11 @@ internal class Launcher : AutoCloseable {
private val botApi = TelegramBotsApi(DefaultBotSession::class.java) private val botApi = TelegramBotsApi(DefaultBotSession::class.java)
private val botSessionMap = mutableMapOf<ScalaBot, BotSession>() private val botSessionMap = mutableMapOf<ScalaBot, BotSession>()
private val remoteRepositories = Const.config.mavenRepositories
.map(MavenRepositoryConfig::toRemoteRepository)
.toMutableList().apply {
add(MavenRepositoryExtensionFinder.getMavenCentralRepository(proxy = Const.config.proxy.toAetherProxy()))
}.toList()
@Synchronized @Synchronized
fun launch(): Boolean { fun launch(): Boolean {
@ -94,6 +99,13 @@ internal class Launcher : AutoCloseable {
} }
} }
val account = botConfig.account val account = botConfig.account
val extensionPackageFinders = setOf(
MavenRepositoryExtensionFinder(
remoteRepositories = remoteRepositories,
proxy = Const.config.proxy.toAetherProxy()
)
)
val bot = ScalaBot( val bot = ScalaBot(
account.name, account.name,
account.token, account.token,
@ -101,6 +113,7 @@ internal class Launcher : AutoCloseable {
BotDBMaker.getBotMaker(account), BotDBMaker.getBotMaker(account),
botOption, botOption,
botConfig.extensions, botConfig.extensions,
extensionPackageFinders,
botConfig.disableBuiltInAbility botConfig.disableBuiltInAbility
) )
botSessionMap[bot] = botApi.registerBot(bot) botSessionMap[bot] = botApi.registerBot(bot)

View File

@ -4,7 +4,6 @@ import mu.KotlinLogging
import net.lamgc.scalabot.extension.BotExtensionFactory import net.lamgc.scalabot.extension.BotExtensionFactory
import net.lamgc.scalabot.util.getPriority import net.lamgc.scalabot.util.getPriority
import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.repository.LocalRepository
import org.telegram.abilitybots.api.util.AbilityExtension import org.telegram.abilitybots.api.util.AbilityExtension
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
@ -18,18 +17,18 @@ import java.util.concurrent.atomic.AtomicInteger
internal class ExtensionLoader( internal class ExtensionLoader(
private val bot: ScalaBot, private val bot: ScalaBot,
private val extensionsDataFolder: File = AppPaths.DATA_EXTENSIONS.file, private val extensionsDataFolder: File = AppPaths.DATA_EXTENSIONS.file,
private val extensionsPath: File = AppPaths.EXTENSIONS.file private val extensionsPath: File = AppPaths.EXTENSIONS.file,
private val extensionFinders: Set<ExtensionPackageFinder> = setOf()
) { ) {
private val log = KotlinLogging.logger { } private val log = KotlinLogging.logger { }
private val finders: Set<ExtensionPackageFinder> = setOf( private val finders: Set<ExtensionPackageFinder> = mutableSetOf(
FileNameFinder, FileNameFinder,
MavenMetaInformationFinder, MavenMetaInformationFinder,
MavenRepositoryExtensionFinder( MavenRepositoryExtensionFinder(
LocalRepository("${System.getProperty("user.home")}/.m2/repository"),
proxy = Const.config.proxy.toAetherProxy() proxy = Const.config.proxy.toAetherProxy()
) )
) ).apply { addAll(extensionFinders) }.toSet()
fun getExtensions(): Set<LoadedExtensionEntry> { fun getExtensions(): Set<LoadedExtensionEntry> {
val extensionEntries = mutableSetOf<LoadedExtensionEntry>() val extensionEntries = mutableSetOf<LoadedExtensionEntry>()

View File

@ -234,7 +234,7 @@ internal object MavenMetaInformationFinder : ExtensionPackageFinder {
*/ */
@FinderRules(priority = FinderPriority.REMOTE) @FinderRules(priority = FinderPriority.REMOTE)
internal class MavenRepositoryExtensionFinder( internal class MavenRepositoryExtensionFinder(
private val localRepository: LocalRepository, private val localRepository: LocalRepository = LocalRepository("${System.getProperty("user.home")}/.m2/repository"),
private val proxy: Proxy? = null, private val proxy: Proxy? = null,
private val remoteRepositories: List<RemoteRepository> = listOf(getMavenCentralRepository(proxy)), private val remoteRepositories: List<RemoteRepository> = listOf(getMavenCentralRepository(proxy)),
) : ExtensionPackageFinder { ) : ExtensionPackageFinder {

View File

@ -30,11 +30,15 @@ internal class ScalaBot(
db: DBContext, db: DBContext,
options: DefaultBotOptions, options: DefaultBotOptions,
val extensions: Set<Artifact>, val extensions: Set<Artifact>,
extensionFinders: Set<ExtensionPackageFinder>,
disableBuiltInAbility: Boolean disableBuiltInAbility: Boolean
) : ) :
AbilityBot(token, name, db, if (disableBuiltInAbility) BareboneToggle() else DefaultToggle(), options) { AbilityBot(token, name, db, if (disableBuiltInAbility) BareboneToggle() else DefaultToggle(), options) {
private val extensionLoader = ExtensionLoader(this) private val extensionLoader = ExtensionLoader(
bot = this,
extensionFinders = extensionFinders
)
init { init {
val extensionEntries = extensionLoader.getExtensions() val extensionEntries = extensionLoader.getExtensions()

View File

@ -1,10 +1,16 @@
package net.lamgc.scalabot.util package net.lamgc.scalabot.util
import com.google.gson.* import com.google.gson.*
import mu.KotlinLogging
import net.lamgc.scalabot.MavenRepositoryConfig
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.Proxy
import org.eclipse.aether.util.repository.AuthenticationBuilder
import org.telegram.telegrambots.bots.DefaultBotOptions import org.telegram.telegrambots.bots.DefaultBotOptions
import java.lang.reflect.Type import java.lang.reflect.Type
import java.net.URL
internal object ProxyTypeSerializer : JsonDeserializer<DefaultBotOptions.ProxyType>, internal object ProxyTypeSerializer : JsonDeserializer<DefaultBotOptions.ProxyType>,
JsonSerializer<DefaultBotOptions.ProxyType> { JsonSerializer<DefaultBotOptions.ProxyType> {
@ -55,3 +61,105 @@ internal object ArtifactSerializer : JsonSerializer<Artifact>, JsonDeserializer<
} }
internal object AuthenticationSerializer : JsonDeserializer<Authentication> {
private val log = KotlinLogging.logger { }
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Authentication? {
val builder = AuthenticationBuilder()
when (json) {
is JsonArray -> {
for (element in json) {
if (element is JsonArray) {
builder.addCustom(jsonArrayToAuthentication(element))
} else if (element is JsonObject) {
jsonToAuthentication(element, builder)
}
}
}
is JsonObject -> {
jsonToAuthentication(json, builder)
}
else -> {
throw JsonParseException("Unsupported JSON data type: ${json::class.java}")
}
}
return builder.build()
}
private fun jsonArrayToAuthentication(jsonArray: JsonArray): Authentication {
val builder = AuthenticationBuilder()
for (element in jsonArray) {
when (element) {
is JsonObject -> jsonToAuthentication(element, builder)
is JsonArray -> builder.addCustom(jsonArrayToAuthentication(element))
else -> log.warn { "不支持的 Json 类型: ${element::class.java}" }
}
}
return builder.build()
}
private const val KEY_TYPE = "type"
private fun jsonToAuthentication(json: JsonObject, builder: AuthenticationBuilder) {
if (!json.has(KEY_TYPE)) {
log.warn { "缺少 type 字段, 无法判断 Maven 认证信息类型." }
return
} else if (!json.get(KEY_TYPE).isJsonPrimitive) {
log.warn { "type 字段类型错误(应为 Primitive 类型), 无法判断 Maven 认证信息类型.(实际类型: `${json::class.java}`)" }
return
}
when (json.get(KEY_TYPE).asString.trim().lowercase()) {
"string" -> {
builder.addString(checkJsonKey(json, "key"), checkJsonKey(json, "value"))
}
"secret" -> {
builder.addSecret(checkJsonKey(json, "key"), checkJsonKey(json, "value"))
}
}
}
}
private fun checkJsonKey(json: JsonObject, key: String): String {
if (!json.has(key)) {
throw JsonParseException("Required field does not exist: $key")
} else if (!json.get(key).isJsonPrimitive) {
throw JsonParseException("Wrong field `$key` type: ${json.get(key)::class.java}")
}
return json.get(key).asString
}
internal object MavenRepositoryConfigSerializer
: JsonDeserializer<MavenRepositoryConfig> {
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): MavenRepositoryConfig {
return when (json) {
is JsonObject -> {
MavenRepositoryConfig(
url = URL(checkJsonKey(json, "url")),
proxy = if (json.has("proxy") && json.get("proxy").isJsonObject)
context.deserialize<Proxy>(
json.getAsJsonObject("proxy"), Proxy::class.java
) else null,
layout = json.get("layout").asString ?: "default",
authentication = if (json.has("authentication") && json.get("authentication").isJsonObject)
context.deserialize<Authentication>(
json.getAsJsonObject("authentication"), Authentication::class.java
) else null
)
}
is JsonPrimitive -> {
MavenRepositoryConfig(URL(json.asString))
}
else -> {
throw JsonParseException("Unsupported Maven warehouse configuration type.")
}
}
}
}