mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-25 07:30:14 +08:00
[console/logging] slf4j binding support
This commit is contained in:
parent
7537e29b0e
commit
6c295723db
@ -60,7 +60,7 @@ object Versions {
|
||||
const val shadow = "7.1.3-mirai-modified-SNAPSHOT"
|
||||
|
||||
const val logback = "1.2.5"
|
||||
const val slf4j = "1.7.32"
|
||||
const val slf4j = "2.0.3"
|
||||
const val log4j = "2.17.2"
|
||||
const val asm = "9.4"
|
||||
const val difflib = "1.3.0"
|
||||
|
@ -61,6 +61,7 @@ dependencies {
|
||||
smartImplementation(`maven-resolver-impl`)
|
||||
smartImplementation(`maven-resolver-connector-basic`)
|
||||
smartImplementation(`maven-resolver-transport-http`)
|
||||
smartImplementation(`slf4j-api`)
|
||||
smartApi(`kotlinx-coroutines-jdk8`)
|
||||
|
||||
testApi(project(":mirai-core"))
|
||||
|
@ -0,0 +1 @@
|
||||
net.mamoe.mirai.console.internal.logging.externalbind.slf4j.MiraiConsoleSLF4JService
|
@ -42,6 +42,7 @@ import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorageImpl
|
||||
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
|
||||
import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger
|
||||
import net.mamoe.mirai.console.internal.logging.externalbind.slf4j.MiraiConsoleSLF4JAdapter
|
||||
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
|
||||
import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl
|
||||
import net.mamoe.mirai.console.internal.shutdown.ShutdownDaemon
|
||||
@ -278,6 +279,10 @@ ___ ____ _ _____ _
|
||||
}
|
||||
}
|
||||
|
||||
phase("initialize logging bridges") {
|
||||
MiraiConsoleSLF4JAdapter.doSlf4JInit()
|
||||
}
|
||||
|
||||
phase("initialize all plugins") {
|
||||
pluginManager // init
|
||||
|
||||
|
@ -38,11 +38,14 @@ public class LoggerConfig : ReadOnlyPluginConfig("Logger") {
|
||||
"example.logger" to AbstractLoggerController.LogPriority.NONE,
|
||||
"console.debug" to AbstractLoggerController.LogPriority.NONE,
|
||||
"Bot" to AbstractLoggerController.LogPriority.ALL,
|
||||
"org.eclipse.aether.internal" to AbstractLoggerController.LogPriority.INFO,
|
||||
"org.apache.http.wire" to AbstractLoggerController.LogPriority.INFO,
|
||||
)
|
||||
)
|
||||
|
||||
@Serializable
|
||||
public class Binding @MiraiExperimentalApi public constructor(
|
||||
public val slf4j: Boolean = true,
|
||||
)
|
||||
|
||||
@ValueDescription(
|
||||
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.internal.logging.externalbind.slf4j
|
||||
|
||||
import net.mamoe.mirai.console.MiraiConsoleImplementation.ConsoleDataScope.Companion.get
|
||||
import net.mamoe.mirai.console.internal.data.builtins.DataScope
|
||||
import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.safeCast
|
||||
import org.slf4j.ILoggerFactory
|
||||
import org.slf4j.IMarkerFactory
|
||||
import org.slf4j.event.SubstituteLoggingEvent
|
||||
import org.slf4j.helpers.BasicMarkerFactory
|
||||
import org.slf4j.helpers.NOPLoggerFactory
|
||||
import org.slf4j.helpers.NOPMDCAdapter
|
||||
import org.slf4j.helpers.SubstituteLoggerFactory
|
||||
import org.slf4j.spi.MDCAdapter
|
||||
import org.slf4j.spi.SLF4JServiceProvider
|
||||
|
||||
@PublishedApi
|
||||
internal class MiraiConsoleSLF4JService : SLF4JServiceProvider {
|
||||
private val basicMarkerFactory = BasicMarkerFactory()
|
||||
private val nopMDCAdapter = NOPMDCAdapter()
|
||||
private val dfactory = ILoggerFactory { MiraiConsoleSLF4JAdapter.getCurrentLogFactory().getLogger(it) }
|
||||
|
||||
override fun getMarkerFactory(): IMarkerFactory = basicMarkerFactory
|
||||
override fun getMDCAdapter(): MDCAdapter = nopMDCAdapter
|
||||
override fun getRequestedApiVersion(): String = "2.0"
|
||||
override fun getLoggerFactory(): ILoggerFactory = dfactory
|
||||
override fun initialize() {}
|
||||
}
|
||||
|
||||
internal object MiraiConsoleSLF4JAdapter {
|
||||
/**
|
||||
* Used before mirai-console start
|
||||
*/
|
||||
private val substituteServiceFactory = SubstituteLoggerFactory()
|
||||
|
||||
@Volatile
|
||||
private var initialized: Boolean = false
|
||||
|
||||
@Volatile
|
||||
private var currentLoggerFactory: ILoggerFactory = substituteServiceFactory
|
||||
|
||||
internal fun getCurrentLogFactory(): ILoggerFactory {
|
||||
if (initialized) return currentLoggerFactory
|
||||
|
||||
synchronized(MiraiConsoleSLF4JAdapter::class.java) {
|
||||
return currentLoggerFactory
|
||||
}
|
||||
}
|
||||
|
||||
internal fun doSlf4JInit() {
|
||||
synchronized(MiraiConsoleSLF4JAdapter::class.java) {
|
||||
val logConfig = DataScope.get<LoggerConfig>()
|
||||
|
||||
currentLoggerFactory = if (logConfig.binding.slf4j) {
|
||||
ILoggerFactory { ident ->
|
||||
SLF4JAdapterLogger(MiraiLogger.Factory.create(MiraiConsoleSLF4JAdapter::class.java, ident))
|
||||
}
|
||||
} else {
|
||||
NOPLoggerFactory()
|
||||
}
|
||||
initialized = true
|
||||
|
||||
// region relay events
|
||||
|
||||
substituteServiceFactory.postInitialization()
|
||||
substituteServiceFactory.loggers.forEach { slog ->
|
||||
slog.setDelegate(currentLoggerFactory.getLogger(slog.name))
|
||||
}
|
||||
|
||||
substituteServiceFactory.eventQueue.let { queue ->
|
||||
for (event in queue) {
|
||||
replaySingleEvent(event)
|
||||
}
|
||||
}
|
||||
substituteServiceFactory.clear()
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun replaySingleEvent(event: SubstituteLoggingEvent?) {
|
||||
if (event == null) return
|
||||
val substLogger = event.logger
|
||||
|
||||
substLogger.delegate().safeCast<SLF4JAdapterLogger>()?.process(event)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,304 @@
|
||||
/*
|
||||
* Copyright 2019-2022 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.internal.logging.externalbind.slf4j
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.Marker
|
||||
import org.slf4j.event.SubstituteLoggingEvent
|
||||
import java.lang.invoke.MethodHandle
|
||||
import java.lang.invoke.MethodHandles
|
||||
import java.lang.invoke.MethodType
|
||||
import java.nio.CharBuffer
|
||||
import java.text.MessageFormat
|
||||
import java.util.regex.Pattern
|
||||
import org.slf4j.event.Level as SLF4JEventLevel
|
||||
|
||||
@Suppress("RegExpRedundantEscape")
|
||||
internal class SLF4JAdapterLogger(
|
||||
private val logger: MiraiLogger
|
||||
) : Logger {
|
||||
// Copied from Log4J
|
||||
internal companion object {
|
||||
private const val FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"
|
||||
|
||||
private val MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER)
|
||||
private const val DELIM_START = '{'
|
||||
private const val DELIM_STOP = '}'
|
||||
private const val ESCAPE_CHAR = '\\'
|
||||
|
||||
@JvmStatic
|
||||
internal fun String.simpleFormat(args: Array<out Any?>): String {
|
||||
val buffer = StringBuilder()
|
||||
val reader = CharBuffer.wrap(this)
|
||||
var isEscape = false
|
||||
var index = 0
|
||||
while (reader.hasRemaining()) {
|
||||
when (val next = reader.get()) {
|
||||
ESCAPE_CHAR -> {
|
||||
if (isEscape) {
|
||||
buffer.append(ESCAPE_CHAR)
|
||||
}
|
||||
isEscape = !isEscape
|
||||
}
|
||||
DELIM_START -> {
|
||||
if (isEscape) {
|
||||
buffer.append(next)
|
||||
} else {
|
||||
if (reader.hasRemaining()) {
|
||||
if (reader.get(reader.position()) == DELIM_STOP) {
|
||||
reader.get()
|
||||
buffer.append(args.getOrNull(index))
|
||||
index++
|
||||
} else {
|
||||
buffer.append(DELIM_START)
|
||||
}
|
||||
} else {
|
||||
buffer.append(DELIM_START)
|
||||
}
|
||||
}
|
||||
isEscape = false
|
||||
}
|
||||
else -> buffer.append(next).also { isEscape = false }
|
||||
}
|
||||
}
|
||||
return buffer.toString()
|
||||
}
|
||||
|
||||
internal fun String.format1(vararg arguments: Any?): String = format2(arguments)
|
||||
|
||||
// (java.lang.String, java.lang.Object[]): java.lang.String
|
||||
@Suppress("LocalVariableName")
|
||||
private val formatWithLog4JMH: MethodHandle? = kotlin.runCatching {
|
||||
val c_ParameterizedMessage = Class.forName("org.apache.logging.log4j.message.ParameterizedMessage")
|
||||
|
||||
val mhLookup = MethodHandles.lookup()
|
||||
val mh_newParameterizedMessage = mhLookup.findConstructor(
|
||||
c_ParameterizedMessage, MethodType.methodType(Void.TYPE, String::class.java, Array<Any>::class.java)
|
||||
).asFixedArity()
|
||||
|
||||
val mh_getFormattedMessage = mhLookup.findVirtual(
|
||||
c_ParameterizedMessage, "getFormattedMessage", MethodType.methodType(String::class.java)
|
||||
)
|
||||
|
||||
MethodHandles.filterReturnValue(mh_newParameterizedMessage, mh_getFormattedMessage)
|
||||
|
||||
}.getOrNull()
|
||||
|
||||
@JvmStatic
|
||||
internal fun String.format2(args: Array<out Any?>): String {
|
||||
kotlin.runCatching {
|
||||
val formatter = MessageFormat(this)
|
||||
val formats = formatter.formats
|
||||
if (formats.isNotEmpty()) {
|
||||
return formatter.format(args)
|
||||
}
|
||||
}
|
||||
kotlin.runCatching {
|
||||
if (MSG_PATTERN.matcher(this).find()) {
|
||||
return String.format(this, *args)
|
||||
}
|
||||
}
|
||||
kotlin.runCatching {
|
||||
// Try format with Log4J
|
||||
formatWithLog4JMH?.let { formatWithLog4JMH ->
|
||||
return formatWithLog4JMH.invoke(this@format2, args) as String
|
||||
}
|
||||
}
|
||||
return simpleFormat(args)
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
override fun isTraceEnabled(): Boolean = logger.isVerboseEnabled
|
||||
override fun isTraceEnabled(marker: Marker?): Boolean = logger.isVerboseEnabled
|
||||
override fun isDebugEnabled(): Boolean = logger.isDebugEnabled
|
||||
override fun isDebugEnabled(marker: Marker?): Boolean = logger.isDebugEnabled
|
||||
override fun isInfoEnabled(): Boolean = logger.isInfoEnabled
|
||||
override fun isInfoEnabled(marker: Marker?): Boolean = logger.isInfoEnabled
|
||||
override fun isWarnEnabled(): Boolean = logger.isWarningEnabled
|
||||
override fun isWarnEnabled(marker: Marker?): Boolean = logger.isWarningEnabled
|
||||
override fun isErrorEnabled(): Boolean = logger.isErrorEnabled
|
||||
override fun isErrorEnabled(marker: Marker?): Boolean = logger.isErrorEnabled
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun getName(): String = logger.identity ?: "<unknown>"
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
internal fun process(event: SubstituteLoggingEvent) {
|
||||
val msg = event.message
|
||||
val argx = event.argumentArray
|
||||
val throwx = event.throwable
|
||||
|
||||
val evtlv = event.level ?: return
|
||||
|
||||
val isEnabled = when (evtlv) {
|
||||
SLF4JEventLevel.ERROR -> isErrorEnabled
|
||||
SLF4JEventLevel.WARN -> isWarnEnabled
|
||||
SLF4JEventLevel.INFO -> isInfoEnabled
|
||||
SLF4JEventLevel.DEBUG -> isDebugEnabled
|
||||
SLF4JEventLevel.TRACE -> isTraceEnabled
|
||||
}
|
||||
if (!isEnabled) return
|
||||
|
||||
if (argx == null) {
|
||||
when (evtlv) {
|
||||
SLF4JEventLevel.ERROR -> error(msg, t = throwx)
|
||||
SLF4JEventLevel.WARN -> warn(msg, t = throwx)
|
||||
SLF4JEventLevel.INFO -> info(msg, t = throwx)
|
||||
SLF4JEventLevel.DEBUG -> debug(msg, t = throwx)
|
||||
SLF4JEventLevel.TRACE -> trace(msg, t = throwx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (throwx == null) {
|
||||
when (evtlv) {
|
||||
SLF4JEventLevel.ERROR -> error(msg, arguments = argx)
|
||||
SLF4JEventLevel.WARN -> warn(msg, arguments = argx)
|
||||
SLF4JEventLevel.INFO -> info(msg, arguments = argx)
|
||||
SLF4JEventLevel.DEBUG -> debug(msg, arguments = argx)
|
||||
SLF4JEventLevel.TRACE -> trace(msg, arguments = argx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val msg2 = msg.format2(argx)
|
||||
when (evtlv) {
|
||||
SLF4JEventLevel.ERROR -> error(msg2, t = throwx)
|
||||
SLF4JEventLevel.WARN -> warn(msg2, t = throwx)
|
||||
SLF4JEventLevel.INFO -> info(msg2, t = throwx)
|
||||
SLF4JEventLevel.DEBUG -> debug(msg2, t = throwx)
|
||||
SLF4JEventLevel.TRACE -> trace(msg2, t = throwx)
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
private inline fun iT(a: () -> Unit) {
|
||||
if (isTraceEnabled) a()
|
||||
}
|
||||
|
||||
private inline fun iD(a: () -> Unit) {
|
||||
if (isDebugEnabled) a()
|
||||
}
|
||||
|
||||
private inline fun iI(a: () -> Unit) {
|
||||
if (isInfoEnabled) a()
|
||||
}
|
||||
|
||||
private inline fun iW(a: () -> Unit) {
|
||||
if (isWarnEnabled) a()
|
||||
}
|
||||
|
||||
private inline fun iE(a: () -> Unit) {
|
||||
if (isErrorEnabled) a()
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun trace(msg: String?) {
|
||||
logger.verbose(msg)
|
||||
}
|
||||
|
||||
override fun trace(msg: String?, t: Throwable?) {
|
||||
logger.verbose(msg, t)
|
||||
}
|
||||
|
||||
override fun trace(format: String, arg: Any?) = iT { trace(format.format1(arg)) }
|
||||
override fun trace(format: String, arg1: Any?, arg2: Any?) = iT { trace(format.format1(arg1, arg2)) }
|
||||
override fun trace(format: String, arguments: Array<out Any?>) = iT { trace(format.format2(arguments)) }
|
||||
|
||||
override fun trace(marker: Marker?, msg: String?) = trace(msg)
|
||||
override fun trace(marker: Marker?, format: String, arg: Any?) = trace(format, arg)
|
||||
override fun trace(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = trace(format, arg1, arg2)
|
||||
override fun trace(marker: Marker?, format: String, argArray: Array<out Any?>) = trace(format, argArray)
|
||||
override fun trace(marker: Marker?, msg: String?, t: Throwable?) = trace(msg, t)
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun debug(msg: String?) {
|
||||
logger.debug(msg)
|
||||
}
|
||||
|
||||
override fun debug(msg: String?, t: Throwable?) {
|
||||
logger.debug(msg, t)
|
||||
}
|
||||
|
||||
override fun debug(format: String, arg: Any?) = iD { debug(format.format1(arg)) }
|
||||
override fun debug(format: String, arg1: Any?, arg2: Any?) = iD { debug(format.format1(arg1, arg2)) }
|
||||
override fun debug(format: String, arguments: Array<out Any?>) = iD { debug(format.format2(arguments)) }
|
||||
|
||||
override fun debug(marker: Marker?, msg: String?) = debug(msg)
|
||||
override fun debug(marker: Marker?, format: String, arg: Any?) = debug(format, arg)
|
||||
override fun debug(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = debug(format, arg1, arg2)
|
||||
override fun debug(marker: Marker?, format: String, arguments: Array<out Any?>) = debug(format, arguments)
|
||||
override fun debug(marker: Marker?, msg: String?, t: Throwable?) = debug(msg, t)
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun info(msg: String?) {
|
||||
logger.info(msg)
|
||||
}
|
||||
|
||||
override fun info(msg: String?, t: Throwable?) {
|
||||
logger.info(msg, t)
|
||||
}
|
||||
|
||||
override fun info(format: String, arg: Any?) = iI { info(format.format1(arg)) }
|
||||
override fun info(format: String, arg1: Any?, arg2: Any?) = iI { info(format.format1(arg1, arg2)) }
|
||||
override fun info(format: String, arguments: Array<out Any?>) = iI { info(format.format2(arguments)) }
|
||||
|
||||
override fun info(marker: Marker?, msg: String?) = info(msg)
|
||||
override fun info(marker: Marker?, format: String, arg: Any?) = info(format, arg)
|
||||
override fun info(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = info(format, arg1, arg2)
|
||||
override fun info(marker: Marker?, format: String, arguments: Array<out Any?>) = info(format, arguments)
|
||||
override fun info(marker: Marker?, msg: String?, t: Throwable?) = info(msg, t)
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun warn(msg: String?) {
|
||||
logger.warning(msg)
|
||||
}
|
||||
|
||||
override fun warn(msg: String?, t: Throwable?) {
|
||||
logger.warning(msg, t)
|
||||
}
|
||||
|
||||
override fun warn(format: String, arg: Any?) = iW { warn(format.format1(arg)) }
|
||||
override fun warn(format: String, arguments: Array<out Any?>) = iW { warn(format.format2(arguments)) }
|
||||
override fun warn(format: String, arg1: Any?, arg2: Any?) = iW { warn(format.format1(arg1, arg2)) }
|
||||
|
||||
override fun warn(marker: Marker?, msg: String?) = warn(msg)
|
||||
override fun warn(marker: Marker?, format: String, arg: Any?) = warn(format, arg)
|
||||
override fun warn(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = warn(format, arg1, arg2)
|
||||
override fun warn(marker: Marker?, format: String, arguments: Array<out Any?>) = warn(format, arguments)
|
||||
override fun warn(marker: Marker?, msg: String?, t: Throwable?) = warn(msg, t)
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
override fun error(msg: String?) {
|
||||
logger.error(msg)
|
||||
}
|
||||
|
||||
override fun error(msg: String?, t: Throwable?) {
|
||||
logger.error(msg, t)
|
||||
}
|
||||
|
||||
override fun error(format: String, arg: Any?) = iE { error(format.format1(arg)) }
|
||||
override fun error(format: String, arg1: Any?, arg2: Any?) = iE { error(format.format1(arg1, arg2)) }
|
||||
override fun error(format: String, arguments: Array<out Any?>) = iE { error(format.format2(arguments)) }
|
||||
|
||||
override fun error(marker: Marker?, msg: String?) = error(msg)
|
||||
override fun error(marker: Marker?, format: String, arg: Any?) = error(format, arg)
|
||||
override fun error(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = error(format, arg1, arg2)
|
||||
override fun error(marker: Marker?, format: String, arguments: Array<out Any?>) = error(format, arguments)
|
||||
override fun error(marker: Marker?, msg: String?, t: Throwable?) = error(msg, t)
|
||||
}
|
@ -129,7 +129,12 @@ internal class DynLibClassLoader : DynamicClasspathClassLoader {
|
||||
if (name in AllDependenciesClassesHolder.allclasses) {
|
||||
return AllDependenciesClassesHolder.appClassLoader.loadClass(name)
|
||||
}
|
||||
if (name.startsWith("net.mamoe.mirai.") || name.startsWith("kotlin.") || name.startsWith("kotlinx.")) { // Avoid plugin classing cheating
|
||||
if (
|
||||
name.startsWith("net.mamoe.mirai.")
|
||||
|| name.startsWith("kotlin.")
|
||||
|| name.startsWith("kotlinx.")
|
||||
|| name.startsWith("org.slf4j.")
|
||||
) { // Avoid plugin classing cheating
|
||||
try {
|
||||
return AllDependenciesClassesHolder.appClassLoader.loadClass(name)
|
||||
} catch (ignored: ClassNotFoundException) {
|
||||
|
@ -80,6 +80,11 @@ internal class JvmPluginDependencyDownloader(
|
||||
) return@DependencyFilter false
|
||||
}
|
||||
|
||||
// Re-download slf4j-api is unnecessary since slf4j-api was bound by console
|
||||
if (artGroup == "org.slf4j" && artId == "slf4j-api") {
|
||||
return@DependencyFilter false
|
||||
}
|
||||
|
||||
// Loaded by console system
|
||||
if ("$artGroup:$artId" in MiraiConsoleBuildDependencies.dependencies)
|
||||
return@DependencyFilter false
|
||||
|
Loading…
Reference in New Issue
Block a user