Merge remote-tracking branch 'origin/reborn' into reborn

# Conflicts:
#	backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt
#	backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt
This commit is contained in:
jiahua.liu 2020-05-25 00:48:33 +08:00
commit 4190058b6d
55 changed files with 1596 additions and 1638 deletions

View File

@ -28,7 +28,7 @@ jobs:
run: ./gradlew build # if test's failed, don't publish
- name: Gradle :mirai-console:cuiCloudUpload
run: ./gradlew :mirai-console:cuiCloudUpload -Dcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Pcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Dcui_cloud_url=${{ secrets.CUI_CLOUD_URL }} -Pcui_cloud_url=${{ secrets.CUI_CLOUD_URL }}
- name: Gradle :mirai-console-qqandroid:cuiCloudUpload
- name: Gradle :mirai-console-graphical:cuiCloudUpload
run: ./gradlew :mirai-console-graphical:cuiCloudUpload -Dcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Pcui_cloud_key=${{ secrets.CUI_CLOUD_KEY }} -Dcui_cloud_url=${{ secrets.CUI_CLOUD_URL }} -Pcui_cloud_url=${{ secrets.CUI_CLOUD_URL }}

View File

@ -28,7 +28,7 @@ jobs:
run: ./gradlew build # if test's failed, don't publish
- name: Gradle :mirai-console:githubUpload
run: ./gradlew :mirai-console:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }}
- name: Gradle :mirai-console-qqandroid:githubUpload
- name: Gradle :mirai-console-graphical:githubUpload
run: ./gradlew :mirai-console-graphical:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }}

View File

@ -21,7 +21,8 @@ Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持
console 由后端和前端一起工作. 使用时必须选择一个前端.
**注意:`mirai-console` 后端和 pure 前端正在进行完全的重构master 分支将不再维护。可以在 [reborn](https://github.com/mamoe/mirai-console/tree/reborn) 查看进度**
**注意:`mirai-console` 后端和 pure 前端正在进行完全的重构master 分支将不再维护。**
**`mirai-console` 将在短时间内不可用。`
- `mirai-console`: console 的后端, 包含插件管理, 指令系统, 配置系统. 还包含一个轻量命令行的前端 (因此可以独立启动 `mirai-console`).
- `mirai-console-graphical`: console 的 JavaFX 图形化界面前端.

View File

@ -21,11 +21,13 @@ fun main() {
}.writeText(buildString {
appendln(COPYRIGHT)
appendln()
appendln(FILE_SUPPRESS)
appendln()
appendln(PACKAGE)
appendln()
// appendln(IMPORTS)
// appendln()
// appendln()
appendln(IMPORTS)
appendln()
appendln()
appendln(DO_NOT_MODIFY)
appendln()
appendln()
@ -44,8 +46,12 @@ private val PACKAGE = """
package net.mamoe.mirai.console.setting
""".trimIndent()
private val FILE_SUPPRESS = """
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused")
""".trimIndent()
private val IMPORTS = """
import kotlinx.serialization.builtins.*
import net.mamoe.mirai.console.setting.internal.valueImpl
import kotlin.internal.LowPriorityInOverloadResolution
""".trimIndent()
fun genAllValueUseSite(): String = buildString {
@ -111,6 +117,26 @@ fun genAllValueUseSite(): String = buildString {
@JvmName("valueMutable")
inline fun <reified T : Setting> Setting.value(default: MutableSet<T>): MutableSettingSetValue<T> = valueImpl(default)
/**
* 创建一个只引用对象而不跟踪其属性的值.
*
* @param T 类型. 必须拥有 [kotlinx.serialization.Serializable] 注解 (因此编译器会自动生成序列化器)
*/
@DangerousReferenceOnlyValue
@JvmName("valueDynamic")
@LowPriorityInOverloadResolution
inline fun <reified T : Any> Setting.value(default: T): Value<T> = valueImpl(default)
@RequiresOptIn(
""${'"'}
这种只保存引用的 Value 可能会导致意料之外的结果, 在使用时须保持谨慎.
对值的改变不会触发自动保存, 也不会同步到 UI . UI 中只能编辑序列化之后的值.
""${'"'}, level = RequiresOptIn.Level.WARNING
)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION)
annotation class DangerousReferenceOnlyValue
"""
)
}

View File

@ -16,7 +16,7 @@ import java.io.File
fun main() {
println(File("").absolutePath) // default project base dir
File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/_ValueImpl.kt").apply {
File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/internal/_ValueImpl.kt").apply {
createNewFile()
}.writeText(buildString {
appendln(COPYRIGHT)
@ -41,12 +41,13 @@ private val DO_NOT_MODIFY = """
""".trimIndent()
private val PACKAGE = """
package net.mamoe.mirai.console.setting
package net.mamoe.mirai.console.setting.internal
""".trimIndent()
private val IMPORTS = """
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import net.mamoe.mirai.console.setting.*
""".trimIndent()
fun genAllValueImpl(): String = buildString {
@ -56,20 +57,20 @@ fun genAllValueImpl(): String = buildString {
// PRIMITIVE
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendln(genValueImpl(number, number, "$number.serializer()", false))
appendln(genPrimitiveValueImpl(number, number, "$number.serializer()", false))
appendln()
}
// PRIMITIVE ARRAYS
for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) {
appendln(genValueImpl("${number}Array", "${number}Array", "${number}ArraySerializer()", true))
appendln(genPrimitiveValueImpl("${number}Array", "${number}Array", "${number}ArraySerializer()", true))
appendln()
}
// TYPED ARRAYS
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendln(
genValueImpl(
genPrimitiveValueImpl(
"Array<${number}>",
"Typed${number}Array",
"ArraySerializer(${number}.serializer())",
@ -83,7 +84,8 @@ fun genAllValueImpl(): String = buildString {
for (collectionName in listOf("List", "Set")) {
for (number in NUMBERS + OTHER_PRIMITIVES) {
appendln(
genValueImpl(
genCollectionValueImpl(
collectionName,
"${collectionName}<${number}>",
"${number}${collectionName}",
"${collectionName}Serializer(${number}.serializer())",
@ -108,7 +110,8 @@ fun genAllValueImpl(): String = buildString {
): Mutable${number}${collectionName}Value {
var internalValue: Mutable${collectionName}<${number}> = default
return object : Mutable${number}${collectionName}Value(), Mutable${collectionName}<${number}> by dynamicMutable${collectionName}({ internalValue }) {
val delegt = dynamicMutable${collectionName}{ internalValue }
return object : Mutable${number}${collectionName}Value(), Mutable${collectionName}<${number}> by delegt {
override var value: Mutable${collectionName}<${number}>
get() = internalValue
set(new) {
@ -118,7 +121,7 @@ fun genAllValueImpl(): String = buildString {
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<Mutable${collectionName}<${number}>> = object : KSerializer<Mutable${collectionName}<${number}>> {
private val delegate = ${collectionName}Serializer(${number}.serializer())
@ -126,7 +129,7 @@ fun genAllValueImpl(): String = buildString {
override fun deserialize(decoder: Decoder): Mutable${collectionName}<${number}> {
return delegate.deserialize(decoder).toMutable${collectionName}().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -177,7 +180,12 @@ fun genAllValueImpl(): String = buildString {
)
}
fun genValueImpl(kotlinTypeName: String, miraiValueName: String, serializer: String, isArray: Boolean): String =
fun genPrimitiveValueImpl(
kotlinTypeName: String,
miraiValueName: String,
serializer: String,
isArray: Boolean
): String =
"""
internal fun Setting.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value {
return object : ${miraiValueName}Value() {
@ -205,3 +213,39 @@ fun genValueImpl(kotlinTypeName: String, miraiValueName: String, serializer: Str
}
""".trimIndent() + "\n"
fun genCollectionValueImpl(
collectionName: String,
kotlinTypeName: String,
miraiValueName: String,
serializer: String,
isArray: Boolean
): String =
"""
internal fun Setting.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value {
var internalValue: $kotlinTypeName = default
val delegt = dynamic$collectionName { internalValue }
return object : ${miraiValueName}Value(), $kotlinTypeName by delegt {
override var value: $kotlinTypeName
get() = internalValue
set(new) {
${
if (isArray) """
if (!new.contentEquals(internalValue)) {
internalValue = new
onElementChanged(this)
}
""".trim()
else """
if (new != internalValue) {
internalValue = new
onElementChanged(this)
}
""".trim()
}
}
override val serializer = $serializer
}
}
""".trimIndent() + "\n"

View File

@ -74,7 +74,7 @@ fun genPublicApi() = buildString {
appendln(
"""
/**
* !!! These primitive types are auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt
* !!! This file is auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt
* !!! for better performance
* !!! DO NOT MODIFY THIS FILE MANUALLY
*/
@ -157,13 +157,14 @@ sealed class Value<T : Any> : ReadWriteProperty<Setting, T> {
appendln()
for (number in (NUMBERS + OTHER_PRIMITIVES).filterNot { it == "String" }) {
val template = """
appendln(
"""
abstract class ${number}ArrayValue internal constructor() : PrimitiveArrayValue<${number}Array>(), Iterable<${number}> {
override fun iterator(): Iterator<${number}> = this.value.iterator()
}
"""
appendln(template)
)
appendln()
}
appendln()
@ -180,11 +181,11 @@ sealed class Value<T : Any> : ReadWriteProperty<Setting, T> {
appendln()
for (number in (NUMBERS + OTHER_PRIMITIVES)) {
val template = """
appendln(
"""
abstract class Typed${number}ArrayValue internal constructor() : TypedPrimitiveArrayValue<${number}>()
"""
appendln(template)
)
}
appendln()
@ -194,9 +195,7 @@ sealed class Value<T : Any> : ReadWriteProperty<Setting, T> {
appendln(
"""
sealed class ${collectionName}Value<E> : Value<${collectionName}<E>>(), Iterable<E>{
override fun iterator() = this.value.iterator()
}
sealed class ${collectionName}Value<E> : Value<${collectionName}<E>>(), ${collectionName}<E>
"""
)
@ -265,7 +264,7 @@ sealed class Value<T : Any> : ReadWriteProperty<Setting, T> {
* 只引用这个对象, 而不跟踪其成员.
* 仅适用于基础类型, 用于 mutable list/map 等情况; 或标注了 [Serializable] 的类.
*/
abstract class DynamicReferenceValue<T : Any> internal constructor() : Value<T>()
abstract class DynamicReferenceValue<T : Any> : Value<T>()
"""
)
}

View File

@ -1,4 +1,3 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import upload.Bintray
import java.util.*
@ -12,18 +11,46 @@ plugins {
apply(plugin = "com.github.johnrengelman.shadow")
kotlin {
sourceSets {
all {
languageSettings.enableLanguageFeature("InlineClasses")
version = Versions.Mirai.console
description = "Console backend for mirai"
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
languageSettings.useExperimentalAnnotation("kotlin.OptIn")
languageSettings.progressiveMode = true
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType(JavaCompile::class.java) {
options.encoding = "UTF8"
}
kotlin {
sourceSets.all {
target.compilations.all {
kotlinOptions {
freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=enable"
jvmTarget = "1.8"
}
}
languageSettings.apply {
enableLanguageFeature("InlineClasses")
progressiveMode = true
useExperimentalAnnotation("kotlin.Experimental")
useExperimentalAnnotation("kotlin.OptIn")
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
}
}
sourceSets {
getByName("test") {
languageSettings.apply {
languageVersion = "1.4"
}
}
}
}
@ -32,39 +59,17 @@ dependencies {
compileAndRuntime("net.mamoe:mirai-core:${Versions.Mirai.core}")
compileAndRuntime(kotlin("stdlib"))
api("net.mamoe.yamlkt:yamlkt:0.2.0")
api("org.jsoup:jsoup:1.12.1")
api("net.mamoe.yamlkt:yamlkt:0.3.1")
api("org.jetbrains:annotations:19.0.0")
api(kotlinx("coroutines-jdk8", Versions.Kotlin.coroutines))
testApi("net.mamoe:mirai-core-qqandroid:${Versions.Mirai.core}")
testApi(kotlin("stdlib"))
testApi(kotlin("stdlib-jdk8"))
testApi(kotlin("test"))
testApi(kotlin("test-junit5"))
}
version = Versions.Mirai.console
description = "Console backend for mirai"
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
tasks.withType(JavaCompile::class.java) {
options.encoding = "UTF8"
}
// region PUBLISHING
tasks.register("ensureBintrayAvailable") {
doLast {
@ -130,3 +135,5 @@ if (Bintray.isBintrayAvailable(project)) {
}
}
} else println("bintray isn't available. NO PUBLICATIONS WILL BE SET")
// endregion

View File

@ -1,51 +0,0 @@
package net.mamoe.mirai.console.event;
import net.mamoe.mirai.console.plugins.PluginBase;
import net.mamoe.mirai.event.Event;
import net.mamoe.mirai.event.Listener;
import net.mamoe.mirai.event.ListeningStatus;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
import java.util.function.Function;
public class EventListener {
PluginBase base;
public EventListener(
PluginBase base
){
this.base = base;
}
/**
* 监听一个事件, {@code onEvent} 返回 {@link ListeningStatus#STOPPED} 时停止监听.
* 机器人离线后不会停止监听.
*
* @param eventClass 事件类
* @param onEvent 事件处理. 返回 {@link ListeningStatus#LISTENING} 时继续监听.
* @param <E> 事件类型
* @return 事件监听器. 可调用 {@link Listener#complete()} {@link Listener#completeExceptionally(Throwable)} 让监听正常停止或异常停止.
*/
@NotNull
public <E extends Event> Listener<E> subscribe(@NotNull Class<E> eventClass, @NotNull Function<E, ListeningStatus> onEvent) {
return EventsImplKt.subscribeEventForJaptOnly(eventClass, base, onEvent);
}
/**
* 监听一个事件, 直到手动停止.
* 机器人离线后不会停止监听.
*
* @param eventClass 事件类
* @param onEvent 事件处理. 返回 {@link ListeningStatus#LISTENING} 时继续监听.
* @param <E> 事件类型
* @return 事件监听器. 可调用 {@link Listener#complete()} {@link Listener#completeExceptionally(Throwable)} 让监听正常停止或异常停止.
*/
@NotNull
public <E extends Event> Listener<E> subscribeAlways(@NotNull Class<E> eventClass, @NotNull Consumer<E> onEvent) {
return EventsImplKt.subscribeEventForJaptOnly(eventClass, base, onEvent);
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.event;
import net.mamoe.mirai.event.Event;
import org.jetbrains.annotations.NotNull;
/**
* 事件处理
*/
public final class Events {
/**
* 阻塞地广播一个事件.
*
* @param event 事件
* @param <E> 事件类型
* @return {@code event} 本身
*/
@NotNull
public static <E extends Event> E broadcast(@NotNull E event) {
return EventsImplKt.broadcast(event);
}
}

View File

@ -1,36 +0,0 @@
package net.mamoe.mirai.console.scheduler;
/**
* Java开发者的SchedulerTask
* 使用kt实现, java的API
*/
/**
* PluginScheduler.RepeatTaskReceipt repeatTaskReceipt = this.getScheduler().repeat(() -> {
* getLogger().info("I repeat");
* },100);
*
*
* this.getScheduler().delay(() -> {
* repeatTaskReceipt.setCancelled(true);
* },10000);
*
*
* Future<String> future = this.getScheduler().async(() -> {
* //do some task
* return "success";
* });
*
* try {
* getLogger().info(future.get());
* } catch (InterruptedException | ExecutionException e) {
* e.printStackTrace();
* }
*/
public class SchedulerTaskManager {
public static SchedulerTaskManagerInstance getInstance(){
return SchedulerTaskManagerInstance.INSTANCE;
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.utils;
import net.mamoe.mirai.Bot;
@ -5,8 +14,8 @@ import net.mamoe.mirai.Bot;
import java.util.List;
/**
* 获取Bot Manager
* Java友好API
* 获取 Bot Manager
* Java 友好 API
*/
public class BotManager {
@ -15,15 +24,15 @@ public class BotManager {
return getManagers(bot);
}
public static List<Long> getManagers(Bot bot){
public static List<Long> getManagers(Bot bot) {
return BotHelperKt.getBotManagers(bot);
}
public static boolean isManager(Bot bot, long target){
public static boolean isManager(Bot bot, long target) {
return getManagers(bot).contains(target);
}
public static boolean isManager(long botAccount, long target){
public static boolean isManager(long botAccount, long target) {
return getManagers(botAccount).contains(target);
}
}

View File

@ -1,35 +0,0 @@
package net.mamoe.mirai.console.utils;
import org.jetbrains.annotations.Range;
import java.util.concurrent.Callable;
public final class Utils {
/**
* 执行N次 callable
* 成功一次就会结束
* 否则就会throw
*/
public static <T> T tryNTimes(@Range(from = 1, to = Integer.MAX_VALUE) int n,
Callable<T> callable) throws Exception {
Exception last = null;
while (n-- > 0) {
try {
return callable.call();
} catch (Exception e) {
if (last == null) {
last = e;
} else {
try {
last.addSuppressed(e);
} catch (Throwable ignored) {
}
}
}
}
throw last;
}
}

View File

@ -13,35 +13,19 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.io.charsets.Charset
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.utils.MiraiConsoleFrontEnd
import net.mamoe.mirai.console.plugins.PluginLoader
import net.mamoe.mirai.console.plugins.builtin.JarPluginLoader
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiLogger
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintStream
import kotlin.coroutines.CoroutineContext
// 前端使用
interface IMiraiConsole : CoroutineScope {
val build: String
val version: String
/**
* Console运行路径
/**
* mirai 控制台实例.
*/
val path: String
/**
* Console前端接口
*/
val frontEnd: MiraiConsoleFrontEnd
/**
* 与前端交互所使用的Logger
*/
val mainLogger: MiraiLogger
}
object MiraiConsole : CoroutineScope, IMiraiConsole {
private lateinit var instance: IMiraiConsole
@ -52,19 +36,17 @@ object MiraiConsole : CoroutineScope, IMiraiConsole {
override val build: String get() = instance.build
override val version: String get() = instance.version
override val path: String get() = instance.path
override val rootDir: File get() = instance.rootDir
override val frontEnd: MiraiConsoleFrontEnd get() = instance.frontEnd
override val mainLogger: MiraiLogger get() = instance.mainLogger
override val coroutineContext: CoroutineContext get() = instance.coroutineContext
override val builtInPluginLoaders: List<PluginLoader<*, *>> = instance.builtInPluginLoaders
init {
DefaultLogger = {
this.newLogger(it)
}
DefaultLogger = { identity -> this.newLogger(identity) }
this.coroutineContext[Job]!!.invokeOnCompletion {
Bot.botInstances.forEach {
it.close()
}
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
}
}
@ -72,6 +54,36 @@ object MiraiConsole : CoroutineScope, IMiraiConsole {
fun newLogger(identity: String?): MiraiLogger = frontEnd.loggerFor(identity)
}
// 前端使用
internal interface IMiraiConsole : CoroutineScope {
val build: String
val version: String
/**
* Console 运行路径
*/
val rootDir: File
/**
* Console 前端接口
*/
val frontEnd: MiraiConsoleFrontEnd
/**
* 与前端交互所使用的 Logger
*/
val mainLogger: MiraiLogger
/**
* 内建加载器列表, 一般需要包含 [JarPluginLoader]
*/
val builtInPluginLoaders: List<PluginLoader<*, *>>
}
/**
* Included in kotlin stdlib 1.4
*/
internal val Throwable.stacktraceString: String
get() =
ByteArrayOutputStream().apply {

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.utils
package net.mamoe.mirai.console
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.center.CuiPluginCenter

View File

@ -1,89 +1,82 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:OptIn(MiraiExperimentalAPI::class)
package net.mamoe.mirai.console.center
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.request.get
import io.ktor.util.KtorExperimentalAPI
import kotlinx.serialization.json.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import net.mamoe.mirai.console.utils.retryCatching
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import java.io.File
@OptIn(UnstableDefault::class)
internal val json = Json(JsonConfiguration(isLenient = true, ignoreUnknownKeys = true))
@OptIn(KtorExperimentalAPI::class)
internal val Http = HttpClient(CIO)
internal object CuiPluginCenter : PluginCenter {
var plugins: JsonArray? = null
var plugins: List<PluginCenter.PluginInsight>? = null
/**
* 一页10个吧,pageMinNum=1
* 一页 10 pageMinNum=1
*/
override suspend fun fetchPlugin(page: Int): Map<String, PluginCenter.PluginInsight> {
check(page > 0)
val startIndex = (page - 1) * 10
val endIndex = startIndex + 9
val map = mutableMapOf<String, PluginCenter.PluginInsight>()
(startIndex until endIndex).forEach {
if (plugins == null) {
(startIndex until endIndex).forEach { index ->
val plugins = plugins ?: kotlin.run {
refresh()
}
if (it >= plugins!!.size) {
plugins
} ?: return mapOf()
if (index >= plugins.size) {
return@forEach
}
val info = plugins!![it]
with(info.jsonObject) {
map[this["name"]!!.toString()] = PluginCenter.PluginInsight(
this["name"]?.primitive?.content ?: "",
this["version"]?.primitive?.content ?: "",
this["core"]?.primitive?.content ?: "",
this["console"]?.primitive?.content ?: "",
this["author"]?.primitive?.content ?: "",
this["description"]?.primitive?.content ?: "",
this["tags"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(),
this["commands"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf()
)
}
map[name] = plugins[index]
}
return map
}
@OptIn(KtorExperimentalAPI::class)
private val Http = HttpClient(CIO)
override suspend fun findPlugin(name: String): PluginCenter.PluginInfo? {
val result = retryCatching(3) {
Http.get<String>("https://miraiapi.jasonczc.cn/getPluginDetailedInfo?name=$name")
}.recover {
return null
}.getOrNull() ?: return null
if (result == "err:not found") {
return null
}
return result.asJson().run {
PluginCenter.PluginInfo(
this["name"]?.primitive?.content ?: "",
this["version"]?.primitive?.content ?: "",
this["core"]?.primitive?.content ?: "",
this["console"]?.primitive?.content ?: "",
this["tags"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(),
this["author"]?.primitive?.content ?: "",
this["contact"]?.primitive?.content ?: "",
this["description"]?.primitive?.content ?: "",
this["usage"]?.primitive?.content ?: "",
this["vsc"]?.primitive?.content ?: "",
this["commands"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf(),
this["changeLog"]?.jsonArray?.map { it.primitive.content } ?: arrayListOf()
)
}
}.getOrElse { return null }
if (result == "err:not found") return null
return json.parse(PluginCenter.PluginInfo.serializer(), result)
}
override suspend fun refresh() {
val results = Http.get<String>("https://miraiapi.jasonczc.cn/getPluginList").asJson()
if (!(results.containsKey("success") && results["success"]?.boolean == true)) {
error("Failed to fetch plugin list from Cui Cloud")
}
plugins = results["result"]?.jsonArray//先不解析
@Serializable
data class Result(
val success: Boolean,
val result: List<PluginCenter.PluginInsight>
)
val result = json.parse(Result.serializer(), Http.get("https://miraiapi.jasonczc.cn/getPluginList"))
check(result.success) { "Failed to fetch plugin list from Cui Cloud" }
plugins = result.result
}
override suspend fun <T : Any> T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File {
@ -112,15 +105,6 @@ internal object CuiPluginCenter : PluginCenter {
*/
}
override val name: String
get() = "崔云"
private val json = Json(JsonConfiguration.Stable)
private fun String.asJson(): JsonObject {
return json.parseJson(this).jsonObject
}
override val name: String get() = "崔云"
}

View File

@ -1,13 +1,29 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.center
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import java.io.File
@MiraiExperimentalAPI
interface PluginCenter {
@Serializable
data class PluginInsight(
val name: String,
val version: String,
@SerialName("core")
val coreVersion: String,
@SerialName("console")
val consoleVersion: String,
val author: String,
val description: String,
@ -15,10 +31,13 @@ interface PluginCenter {
val commands: List<String>
)
@Serializable
data class PluginInfo(
val name: String,
val version: String,
@SerialName("core")
val coreVersion: String,
@SerialName("console")
val consoleVersion: String,
val tags: List<String>,
val author: String,
@ -32,7 +51,7 @@ interface PluginCenter {
/**
* 获取一些中心的插件基本信息,
* 能获取到多少由实际的PluginCenter决定
* 能获取到多少由实际的 [PluginCenter] 决定
* 返回 插件名->Insight
*/
suspend fun fetchPlugin(page: Int): Map<String, PluginInsight>
@ -41,17 +60,18 @@ interface PluginCenter {
* 尝试获取到某个插件 by 全名, case sensitive
* null 则没有
*/
suspend fun findPlugin(name:String):PluginInfo?
suspend fun findPlugin(name: String): PluginInfo?
suspend fun <T:Any> T.downloadPlugin(name:String, progressListener:T.(Float) -> Unit): File
suspend fun <T : Any> T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File
suspend fun downloadPlugin(name:String, progressListener:PluginCenter.(Float) -> Unit): File = downloadPlugin<PluginCenter>(name,progressListener)
suspend fun downloadPlugin(name: String, progressListener: PluginCenter.(Float) -> Unit): File =
downloadPlugin<PluginCenter>(name, progressListener)
/**
* 刷新
*/
suspend fun refresh()
val name:String
val name: String
}

View File

@ -13,7 +13,9 @@ package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.description.*
import net.mamoe.mirai.console.command.description.CommandParam
import net.mamoe.mirai.console.command.hasAnnotation
import net.mamoe.mirai.console.command.description.CommandParserContext
import net.mamoe.mirai.console.command.description.EmptyCommandParserContext
import net.mamoe.mirai.console.command.description.plus
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
import java.lang.Exception

View File

@ -1,10 +1,19 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
@file:JvmName("CommandManager")
package net.mamoe.mirai.console.command
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.console.plugins.Plugin
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.SingleMessage
@ -13,7 +22,7 @@ sealed class CommandOwner
object TestCommandOwner : CommandOwner()
abstract class PluginCommandOwner(val plugin: PluginBase) : CommandOwner()
abstract class PluginCommandOwner(val plugin: Plugin) : CommandOwner()
// 由前端实现
internal abstract class ConsoleCommandOwner : CommandOwner()

View File

@ -117,7 +117,6 @@ inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean =
inline fun CommandPermission.testPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() }
internal class OrCommandPermission(
private val first: CommandPermission,
private val second: CommandPermission
@ -127,6 +126,7 @@ internal class OrCommandPermission(
}
}
internal class AndCommandPermission(
private val first: CommandPermission,
private val second: CommandPermission

View File

@ -1,3 +1,12 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.console.command.description

View File

@ -10,11 +10,7 @@
package net.mamoe.mirai.console.command.description
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.BotAwareCommandSender
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.MemberCommandSender
import net.mamoe.mirai.console.command.UserCommandSender
import net.mamoe.mirai.console.utils.fuzzySearchMember
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member

View File

@ -1,3 +1,12 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.command.description

View File

@ -9,6 +9,8 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import java.util.concurrent.locks.ReentrantLock
@ -62,3 +64,96 @@ internal infix fun <T> Array<out T>.intersects(other: Array<out T>): Boolean {
}
return false
}
internal fun String.fuzzyCompare(target: String): Double {
var step = 0
if (this == target) {
return 1.0
}
if (target.length > this.length) {
return 0.0
}
for (i in this.indices) {
if (target.length == i) {
step--
} else {
if (this[i] != target[i]) {
break
}
step++
}
}
if (step == this.length - 1) {
return 1.0
}
return step.toDouble() / this.length
}
/**
* 模糊搜索一个List中index最接近target的东西
*/
internal inline fun <T : Any> Collection<T>.fuzzySearch(
target: String,
index: (T) -> String
): T? {
if (this.isEmpty()) {
return null
}
var potential: T? = null
var rate = 0.0
this.forEach {
val thisIndex = index(it)
if (thisIndex == target) {
return it
}
with(thisIndex.fuzzyCompare(target)) {
if (this > rate) {
rate = this
potential = it
}
}
}
return potential
}
/**
* 模糊搜索一个List中index最接近target的东西
* 并且确保target是唯一的
* 如搜索index为XXXXYY list中同时存在XXXXYYY XXXXYYYY 将返回null
*/
internal inline fun <T : Any> Collection<T>.fuzzySearchOnly(
target: String,
index: (T) -> String
): T? {
if (this.isEmpty()) {
return null
}
var potential: T? = null
var rate = 0.0
var collide = 0
this.forEach {
with(index(it).fuzzyCompare(target)) {
if (this > rate) {
rate = this
potential = it
}
if (this == 1.0) {
collide++
}
if (collide > 1) {
return null//collide
}
}
}
return potential
}
internal fun Group.fuzzySearchMember(nameCardTarget: String): Member? {
return this.members.fuzzySearchOnly(nameCardTarget) {
it.nameCard
}
}

View File

@ -1,3 +1,12 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.event
import net.mamoe.mirai.console.command.Command

View File

@ -1,3 +1,12 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.event
import net.mamoe.mirai.event.Event

View File

@ -1,32 +0,0 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.mirai.console.event
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.internal._subscribeEventForJaptOnly
import java.util.function.Consumer
import java.util.function.Function
internal fun <E : Event> broadcast(e: E): E = runBlocking { e.broadcast() }
internal fun <E : Event> Class<E>.subscribeEventForJaptOnly(
scope: CoroutineScope,
onEvent: Function<E, ListeningStatus>
): Listener<E> = _subscribeEventForJaptOnly(scope, onEvent)
internal fun <E : Event> Class<E>.subscribeEventForJaptOnly(scope: CoroutineScope, onEvent: Consumer<E>): Listener<E> =
_subscribeEventForJaptOnly(scope, onEvent)

View File

@ -1,426 +0,0 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.Serializable
sealed class JarPlugin : Plugin(), CoroutineScope {
internal lateinit var _description: JarPluginDescription
final override val description: PluginDescription get() = _description
final override val loader: JarPluginLoader get() = JarPluginLoader
}
@Serializable
internal class JarPluginDescription(
override val name: String,
override val author: String,
override val version: String,
override val info: String,
override val depends: List<String>
) : PluginDescription
abstract class JavaPlugin : JarPlugin()
abstract class KotlinPlugin : JarPlugin()
/**
* 内建的 Jar (JVM) 插件加载器
*/
object JarPluginLoader : PluginLoader<JarPlugin> {
override val list: List<JarPlugin>
get() = TODO("Not yet implemented")
override fun load(plugin: JarPlugin) {
TODO("Not yet implemented")
}
override fun enable(plugin: JarPlugin) {
TODO("Not yet implemented")
}
}
/*
object PluginManagerOld {
/**
* 通过插件获取介绍
* @see description
*/
fun getPluginDescription(base: PluginBase): PluginDescription {
nameToPluginBaseMap.forEach { (s, pluginBase) ->
if (pluginBase == base) {
return pluginDescriptions[s]!!
}
}
error("can not find plugin description")
}
/**
* 获取所有插件摘要
*/
fun getAllPluginDescriptions(): Collection<PluginDescription> {
return pluginDescriptions.values
}
/**
* 关闭所有插件
*/
@JvmOverloads
fun disablePlugins(throwable: CancellationException? = null) {
pluginsSequence.forEach { plugin ->
plugin.unregisterAllCommands()
plugin.disable(throwable)
}
nameToPluginBaseMap.clear()
pluginDescriptions.clear()
pluginsLoader.clear()
pluginsSequence.clear()
}
/**
* 重载所有插件
*/
fun reloadPlugins() {
pluginsSequence.forEach {
it.disable()
}
loadPlugins(false)
}
/**
* 尝试加载全部插件
*/
fun loadPlugins(clear: Boolean = true) = loadPluginsImpl(clear)
//////////////////
//// internal ////
//////////////////
internal val pluginsPath = (MiraiConsole.path + "/plugins/").replace("//", "/").also {
File(it).mkdirs()
}
private val logger = MiraiConsole.newLogger("Plugin Manager")
/**
* 加载成功的插件, 名字->插件
*/
internal val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf()
/**
* 加载成功的插件, 名字->插件摘要
*/
private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf()
/**
* 加载插件的PluginsLoader
*/
private val pluginsLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
/**
* 插件优先级队列
* 任何操作应该按这个Sequence顺序进行
* 他的优先级取决于依赖,
* 在这个队列中, 被依赖的插件会在依赖的插件之前
*/
private val pluginsSequence: LockFreeLinkedList<PluginBase> = LockFreeLinkedList()
/**
* 广播Command方法
*/
internal fun onCommand(command: Command, sender: CommandSender, args: List<String>) {
pluginsSequence.forEach {
try {
it.onCommand(command, sender, args)
} catch (e: Throwable) {
logger.info(e)
}
}
}
@Volatile
internal var lastPluginName: String = ""
/**
* 判断文件名/插件名是否已加载
*/
private fun isPluginLoaded(file: File, name: String): Boolean {
pluginDescriptions.forEach {
if (it.key == name || it.value.file == file) {
return true
}
}
return false
}
/**
* 寻找所有安装的插件在文件夹, 并将它读取, 记录位置
* 这个不等同于加载的插件, 可以理解为还没有加载的插件
*/
internal data class FindPluginsResult(
val pluginsLocation: MutableMap<String, File>,
val pluginsFound: MutableMap<String, PluginDescription>
)
internal fun findPlugins(): FindPluginsResult {
val pluginsLocation: MutableMap<String, File> = mutableMapOf()
val pluginsFound: MutableMap<String, PluginDescription> = mutableMapOf()
File(pluginsPath).listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") {
val jar = JarFile(file)
val pluginYml =
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
if (pluginYml == null) {
logger.info("plugin.yml not found in jar " + jar.name + ", it will not be consider as a Plugin")
} else {
try {
val description = PluginDescription.readFromContent(
URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().let {
val res = it.inputStream.use { input ->
input.readBytes().encodeToString()
}
// 关闭jarFile解决热更新插件问题
(it as JarURLConnection).jarFile.close()
res
}, file
)
if (!isPluginLoaded(file, description.name)) {
pluginsFound[description.name] = description
pluginsLocation[description.name] = file
}
} catch (e: Exception) {
logger.info(e)
}
}
}
}
return FindPluginsResult(pluginsLocation, pluginsFound)
}
internal fun loadPluginsImpl(clear: Boolean = true) {
logger.info("""开始加载${pluginsPath}下的插件""")
val findPluginsResult = findPlugins()
val pluginsFound = findPluginsResult.pluginsFound
val pluginsLocation = findPluginsResult.pluginsLocation
//不仅要解决A->B->C->A, 还要解决A->B->A->A
fun checkNoCircularDepends(
target: PluginDescription,
needDepends: List<String>,
existDepends: MutableList<String>
) {
if (!target.noCircularDepend) {
return
}
existDepends.add(target.name)
if (needDepends.any { existDepends.contains(it) }) {
target.noCircularDepend = false
}
existDepends.addAll(needDepends)
needDepends.forEach {
if (pluginsFound.containsKey(it)) {
checkNoCircularDepends(pluginsFound[it]!!, pluginsFound[it]!!.depends, existDepends)
}
}
}
pluginsFound.values.forEach {
checkNoCircularDepends(it, it.depends, mutableListOf())
}
//load plugin individually
fun loadPlugin(description: PluginDescription): Boolean {
if (!description.noCircularDepend) {
logger.error("Failed to load plugin " + description.name + " because it has circular dependency")
return false
}
if (description.loaded || nameToPluginBaseMap.containsKey(description.name)) {
return true
}
description.depends.forEach { dependent ->
if (!pluginsFound.containsKey(dependent)) {
logger.error("Failed to load plugin " + description.name + " because it need " + dependent + " as dependency")
return false
}
val depend = pluginsFound[dependent]!!
if (!loadPlugin(depend)) {//先加载depend
logger.error("Failed to load plugin " + description.name + " because " + dependent + " as dependency failed to load")
return false
}
}
logger.info("loading plugin " + description.name)
val jarFile = pluginsLocation[description.name]!!
val pluginClass = try {
pluginsLoader.loadPluginMainClassByJarFile(description.name, description.basePath, jarFile)
} catch (e: ClassNotFoundException) {
pluginsLoader.loadPluginMainClassByJarFile(description.name, "${description.basePath}Kt", jarFile)
}
val subClass = pluginClass.asSubclass(PluginBase::class.java)
lastPluginName = description.name
val plugin: PluginBase =
subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().apply {
kotlin.runCatching {
this.isAccessible = true
}
}.newInstance()
plugin.dataFolder // initialize right now
description.loaded = true
logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author)
logger.info(description.info)
nameToPluginBaseMap[description.name] = plugin
pluginDescriptions[description.name] = description
plugin.pluginName = description.name
pluginsSequence.addLast(plugin)//按照实际加载顺序加入队列
return true
}
if (clear) {
//清掉优先级队列, 来重新填充
pluginsSequence.clear()
}
pluginsFound.values.forEach {
try {
// 尝试加载插件
loadPlugin(it)
} catch (e: Throwable) {
pluginsLoader.remove(it.name)
when (e) {
is ClassCastException -> logger.error(
"failed to load plugin " + it.name + " , Main class does not extends PluginBase",
e
)
is ClassNotFoundException -> logger.error(
"failed to load plugin " + it.name + " , Main class not found under " + it.basePath,
e
)
is NoClassDefFoundError -> logger.error(
"failed to load plugin " + it.name + " , dependent class not found.",
e
)
else -> logger.error("failed to load plugin " + it.name, e)
}
}
}
pluginsSequence.forEach {
try {
it.load()
} catch (ignored: Throwable) {
logger.info(ignored)
logger.info(it.pluginName + " failed to load, disabling it")
logger.info(it.pluginName + " 推荐立即删除/替换并重启")
if (ignored is CancellationException) {
disablePlugin(it, ignored)
} else {
disablePlugin(it)
}
}
}
pluginsSequence.forEach {
try {
it.enable()
} catch (ignored: Throwable) {
logger.info(ignored)
logger.info(it.pluginName + " failed to enable, disabling it")
logger.info(it.pluginName + " 推荐立即删除/替换并重启")
if (ignored is CancellationException) {
disablePlugin(it, ignored)
} else {
disablePlugin(it)
}
}
}
logger.info("""加载了${nameToPluginBaseMap.size}个插件""")
}
private fun disablePlugin(
plugin: PluginBase,
exception: CancellationException? = null
) {
plugin.unregisterAllCommands()
plugin.disable(exception)
nameToPluginBaseMap.remove(plugin.pluginName)
pluginDescriptions.remove(plugin.pluginName)
pluginsLoader.remove(plugin.pluginName)
pluginsSequence.remove(plugin)
}
/**
* 根据插件名字找Jar的文件
* null => 没找到
* 这里的url的jarFile没关热更新插件可能出事
*/
internal fun getJarFileByName(pluginName: String): File? {
File(pluginsPath).listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") {
val jar = JarFile(file)
val pluginYml =
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
if (pluginYml != null) {
val description =
PluginDescription.readFromContent(
URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().inputStream.use {
it.readBytes().encodeToString()
}, file
)
if (description.name.toLowerCase() == pluginName.toLowerCase()) {
return file
}
}
}
}
return null
}
/**
* 根据插件名字找Jar中的文件
* null => 没找到
* 这里的url的jarFile没关热更新插件可能出事
*/
internal fun getFileInJarByName(pluginName: String, toFind: String): InputStream? {
val jarFile = getJarFileByName(pluginName) ?: return null
val jar = JarFile(jarFile)
val toFindFile =
jar.entries().asSequence().filter { it.name == toFind }.firstOrNull() ?: return null
return URL("jar:file:" + jarFile.absoluteFile + "!/" + toFindFile.name).openConnection().inputStream
}
}*/

View File

@ -9,45 +9,17 @@
package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiLogger
import kotlin.coroutines.CoroutineContext
import net.mamoe.mirai.console.plugins.builtin.JvmPlugin
/**
* 插件信息
*/
interface PluginDescription {
val name: String
val author: String
val version: String
val info: String
val depends: List<String>
}
/**
* 插件基类.
* 表示一个 mirai-console 插件.
*
* 内建的插件类型:
* - [JarPlugin]
* @see JvmPlugin
* @see PluginDescription 插件描述
*/
abstract class Plugin : CoroutineScope {
abstract val description: PluginDescription
abstract val loader: PluginLoader<*>
@OptIn(MiraiExperimentalAPI::class)
val logger: MiraiLogger by lazy { MiraiConsole.newLogger(description.name) }
override val coroutineContext: CoroutineContext
get() = SupervisorJob(MiraiConsole.coroutineContext[Job]) + CoroutineExceptionHandler { _, throwable ->
logger.error(throwable)
}
open fun onLoaded() {}
open fun onDisabled() {}
open fun onEnabled() {}
interface Plugin {
/**
* 所属插件加载器实例
*/
val loader: PluginLoader<*, *>
}

View File

@ -1,149 +0,0 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.*
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.event.EventListener
import net.mamoe.mirai.console.scheduler.PluginScheduler
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
import java.io.InputStream
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/**
* 所有插件的基类
*/
abstract class PluginBase
@JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope {
final override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob()
/**
* 插件被分配的数据目录数据目录会与插件名称同名
*/
val dataFolder: File by lazy {
TODO()
/*
File(PluginManager.pluginsPath + "/" + PluginManager.lastPluginName)
.also { it.mkdir() }*/
}
/**
* 当一个插件被加载时调用
*/
open fun onLoad() {
}
/**
* 当插件被启用时调用.
* 此时所有其他插件都已经被调用了 [onLoad]
*/
open fun onEnable() {
}
/**
* 当插件关闭前被调用
*/
open fun onDisable() {
}
/**
* 当任意指令被使用时调用.
*
* 指令调用将优先触发 [Command.onCommand], 若该函数返回 `false`, 则不会调用 [PluginBase.onCommand]
*/
open fun onCommand(command: Command, sender: CommandSender, args: List<String>) {
}
/**
* 插件的日志
*/
val logger: MiraiLogger by lazy {
TODO()
/*
SimpleLogger("Plugin $pluginName") { priority, message, e ->
val identityString = "[${pluginName}]"
MiraiConsole.logger(priority, identityString, 0, message)
if (e != null) {
MiraiConsole.logger(priority, identityString, 0, e)
}
}*/
}
/**
* 加载 resources 中的文件
*/
fun getResources(fileName: String): InputStream? {
return try {
this.javaClass.classLoader.getResourceAsStream(fileName)
} catch (e: Exception) {
TODO()
/*
PluginManager.getFileInJarByName(
this.pluginName,
fileName
)*/
}
}
/**
* Java API Scheduler
*/
val scheduler: PluginScheduler? = PluginScheduler(this.coroutineContext)
/**
* Java API EventListener
*/
val eventListener: EventListener = EventListener(@Suppress("LeakingThis") this)
// internal
private var loaded = false
private var enabled = false
internal fun load() {
if (!loaded) {
onLoad()
loaded = true
}
}
internal fun enable() {
if (!enabled) {
onEnable()
enabled = true
}
}
internal fun disable(throwable: CancellationException? = null) {
if (enabled) {
this.coroutineContext[Job]!!.cancelChildren(throwable)
try {
onDisable()
} catch (e: Exception) {
logger.error(e)
}
enabled = false
}
}
internal var pluginName: String = ""
}

View File

@ -1,29 +1,76 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("unused")
package net.mamoe.mirai.console.plugins
import java.io.File
/**
* 插件加载器
* 插件加载器.
*
* 插件加载器只实现寻找插件列表, 加载插件, 启用插件, 关闭插件这四个功能.
*
* 有关插件的依赖和已加载的插件列表由 [PluginManager] 维护.
*/
interface PluginLoader<P : Plugin, D : PluginDescription> {
/**
* 扫描并返回可以被加载的插件的 [描述][PluginDescription] 列表. 此函数只会被调用一次
*/
fun listPlugins(): List<D>
/**
* 获取此插件的描述
*/
@Throws(PluginLoadException::class)
fun getPluginDescription(plugin: P): D
/**
* 加载一个插件 (实例), 但不 [启用][enable] . 返回加载成功的实例
*
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
*/
@Throws(PluginLoadException::class)
fun load(description: D): P
fun enable(plugin: P)
fun disable(plugin: P)
}
open class PluginLoadException : RuntimeException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}
/**
* '/plugins' 目录中的插件的加载器. 每个加载器需绑定一个后缀.
*
* @see AbstractFilePluginLoader
* @see JarPluginLoader 内建的 Jar (JVM) 插件加载器.
*/
interface PluginLoader<P : Plugin> {
val list: List<P>
fun loadAll() = list.forEach(::load)
fun enableAll() = list.forEach(::enable)
fun unloadAll() = list.forEach(::unload)
fun reloadAll() = list.forEach(::reload)
val isUnloadSupported: Boolean
get() = false
fun load(plugin: P)
fun enable(plugin: P)
fun unload(plugin: P) {
error("NotImplemented")
}
fun reload(plugin: P) {
unload(plugin)
load(plugin)
}
interface FilePluginLoader<P : Plugin, D : PluginDescription> : PluginLoader<P, D> {
/**
* 所支持的插件文件后缀, 不含 '.'. [JarPluginLoader] "jar"
*/
val fileSuffix: String
}
abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription>(
override val fileSuffix: String
) : FilePluginLoader<P, D> {
private fun pluginsFilesSequence(): Sequence<File> =
PluginManager.pluginsDir.walk().filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) }
protected abstract fun Sequence<File>.mapToDescription(): List<D>
final override fun listPlugins(): List<D> = pluginsFilesSequence().mapToDescription()
}

View File

@ -1,24 +1,194 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.console.plugins
import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.console.MiraiConsole
import java.io.File
import java.util.concurrent.locks.ReentrantLock
val Plugin.description: PluginDescription
get() = PluginManager.resolvedPlugins.firstOrNull { it == this }?.description ?: error("Plugin is unloaded")
inline fun PluginLoader<*, *>.register() = PluginManager.registerPluginLoader(this)
inline fun PluginLoader<*, *>.unregister() = PluginManager.unregisterPluginLoader(this)
object PluginManager {
private val _loaders: MutableSet<PluginLoader<*>> = mutableSetOf()
val pluginsDir = File(MiraiConsole.rootDir, "plugins").apply { mkdir() }
val loaders: Set<PluginLoader<*>> get() = _loaders
private val _pluginLoaders: MutableList<PluginLoader<*, *>> = mutableListOf()
private val loadersLock: ReentrantLock = ReentrantLock()
fun registerPluginLoader(loader: PluginLoader<*>) {
_loaders.add(loader)
@JvmField
internal val resolvedPlugins: MutableList<Plugin> = mutableListOf()
/**
* 已加载的插件列表
*/
@JvmStatic
val plugins: List<Plugin>
get() = resolvedPlugins.toList()
/**
* 内建的插件加载器列表. [MiraiConsole] 初始化
*/
@JvmStatic
val builtInLoaders: List<PluginLoader<*, *>>
get() = MiraiConsole.builtInPluginLoaders
/**
* 由插件创建的 [PluginLoader]
*/
@JvmStatic
val pluginLoaders: List<PluginLoader<*, *>>
get() = _pluginLoaders.toList()
@JvmStatic
fun registerPluginLoader(loader: PluginLoader<*, *>): Boolean = loadersLock.withLock {
if (_pluginLoaders.any { it::class == loader }) {
return false
}
_pluginLoaders.add(loader)
}
fun unregisterPluginLoader(loader: PluginLoader<*>) {
_loaders.remove(loader)
@JvmStatic
fun unregisterPluginLoader(loader: PluginLoader<*, *>) = loadersLock.withLock {
_pluginLoaders.remove(loader)
}
fun loadPlugins() {
loaders.forEach(PluginLoader<*>::loadAll)
// region LOADING
private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.loadPluginNoEnable(description: D): P {
// TODO: 2020/5/23 HANDLE INITIALIZATION EXCEPTION
return this.load(description).also { resolvedPlugins.add(it) }
}
fun enablePlugins() {
loaders.forEach(PluginLoader<*>::enableAll)
private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.loadPluginAndEnable(description: D) {
@Suppress("UNCHECKED_CAST")
return this.enable(loadPluginNoEnable(description.unwrap()))
}
/**
* STEPS:
* 1. 遍历插件列表, 使用 [builtInLoaders] 加载 [PluginKind.LOADER] 类型的插件
* 2. [启动][PluginLoader.enable] 所有 [PluginKind.LOADER] 的插件
* 3. 使用内建和所有插件提供的 [PluginLoader] 加载全部除 [PluginKind.LOADER] 外的插件列表.
* 4. 解决依赖并排序
* 5. 依次 [PluginLoader.load]
* 但不 [PluginLoader.enable]
*
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
*/
@Suppress("UNCHECKED_CAST")
@Throws(PluginMissingDependencyException::class)
internal fun loadEnablePlugins() {
val all = loadAndEnableLoaderProviders() + _pluginLoaders.listAllPlugins().flatMap { it.second }
for ((loader, desc) in all.sortByDependencies()) {
loader.loadPluginAndEnable(desc)
}
}
/**
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
*/
@Suppress("UNCHECKED_CAST")
@Throws(PluginMissingDependencyException::class)
private fun loadAndEnableLoaderProviders(): List<PluginDescriptionWithLoader> {
val allDescriptions =
this.builtInLoaders.listAllPlugins()
.asSequence()
.onEach { (loader, descriptions) ->
loader as PluginLoader<Plugin, PluginDescription>
for (it in descriptions.filter { it.kind == PluginKind.LOADER }.sortByDependencies()) {
loader.loadPluginAndEnable(it)
}
}
.flatMap { it.second.asSequence() }
return allDescriptions.toList()
}
private fun List<PluginLoader<*, *>>.listAllPlugins(): List<Pair<PluginLoader<*, *>, List<PluginDescriptionWithLoader>>> {
return associateWith { loader -> loader.listPlugins().map { desc -> desc.wrapWith(loader) } }.toList()
}
@Throws(PluginMissingDependencyException::class)
private fun <D : PluginDescription> List<D>.sortByDependencies(): List<D> {
val resolved = ArrayList<D>(this.size)
fun D.canBeLoad(): Boolean = this.dependencies.all { it.isOptional || it in resolved }
fun List<D>.consumeLoadable(): List<D> {
val (canBeLoad, cannotBeLoad) = this.partition { it.canBeLoad() }
resolved.addAll(canBeLoad)
return cannotBeLoad
}
fun List<PluginDependency>.filterIsMissing(): List<PluginDependency> =
this.filterNot { it.isOptional || it in resolved }
tailrec fun List<D>.doSort() {
if (this.isEmpty()) return
val beforeSize = this.size
this.consumeLoadable().also { resultPlugins ->
check(resultPlugins.size < beforeSize) {
throw PluginMissingDependencyException(resultPlugins.joinToString("\n") { badPlugin ->
"Cannot load plugin ${badPlugin.name}, missing dependencies: ${badPlugin.dependencies.filterIsMissing()
.joinToString()}"
})
}
}.doSort()
}
this.doSort()
return resolved
}
// endregion
}
class PluginMissingDependencyException : PluginResolutionException {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}
open class PluginResolutionException : Exception {
constructor() : super()
constructor(message: String?) : super(message)
constructor(message: String?, cause: Throwable?) : super(message, cause)
constructor(cause: Throwable?) : super(cause)
}
internal data class PluginDescriptionWithLoader(
@JvmField val loader: PluginLoader<*, PluginDescription>, // easier type
@JvmField val delegate: PluginDescription
) : PluginDescription by delegate
@Suppress("UNCHECKED_CAST")
internal fun <D : PluginDescription> PluginDescription.unwrap(): D =
if (this is PluginDescriptionWithLoader) this.delegate as D else this as D
@Suppress("UNCHECKED_CAST")
internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>): PluginDescriptionWithLoader =
PluginDescriptionWithLoader(
loader as PluginLoader<*, PluginDescription>, this
)
internal operator fun List<PluginDescription>.contains(dependency: PluginDependency): Boolean =
any { it.name == dependency.name }

View File

@ -1,15 +0,0 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("unused", "unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.console.plugins
val PluginBase.description: PluginDescription get() = TODO()

View File

@ -0,0 +1,105 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.plugins.builtin
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.AbstractFilePluginLoader
import net.mamoe.mirai.console.plugins.PluginLoadException
import net.mamoe.mirai.console.plugins.PluginsLoader
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.error
import net.mamoe.yamlkt.Yaml
import java.io.File
import java.net.URL
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.full.createInstance
/**
* 内建的 Jar (JVM) 插件加载器
*/
object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>("jar"), CoroutineScope {
private val logger: MiraiLogger by lazy {
MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!)
}
override val coroutineContext: CoroutineContext by lazy {
MiraiConsole.coroutineContext + SupervisorJob(
MiraiConsole.coroutineContext[Job]
) + CoroutineExceptionHandler { _, throwable ->
logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable)
}
}
private val supervisor: Job = coroutineContext[Job]!!
private val classLoader: PluginsLoader = PluginsLoader(this.javaClass.classLoader)
init {
supervisor.invokeOnCompletion {
classLoader.clear()
}
}
override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description
override fun Sequence<File>.mapToDescription(): List<JvmPluginDescription> {
return this.associateWith { URL("jar:${it.absolutePath}!/plugin.yml") }.mapNotNull { (file, url) ->
kotlin.runCatching {
url.readText()
}.fold(
onSuccess = { yaml ->
Yaml.nonStrict.parse(JvmPluginDescription.serializer(), yaml)
},
onFailure = {
logger.error("Cannot load plugin file ${file.name}", it)
null
}
)?.also { it._file = file }
}
}
@Suppress("RemoveExplicitTypeArguments") // until Kotlin 1.4 NI
@Throws(PluginLoadException::class)
override fun load(description: JvmPluginDescription): JvmPlugin =
description.runCatching<JvmPluginDescription, JvmPlugin> {
ensureActive()
val main = classLoader.loadPluginMainClassByJarFile(name, mainClassName, file).kotlin.run {
objectInstance
?: kotlin.runCatching { createInstance() }.getOrNull()
?: (java.constructors + java.declaredConstructors)
.firstOrNull { it.parameterCount == 0 }
?.apply { kotlin.runCatching { isAccessible = true } }
?.newInstance()
} ?: error("No Kotlin object or public no-arg constructor found")
check(main is JvmPlugin) { "The main class of Jar plugin must extend JvmPlugin, recommending JavaPlugin or KotlinPlugin" }
if (main is JvmPluginImpl) {
main._description = description
main.internalOnLoad()
} else main.onLoad()
main
}.getOrElse<JvmPlugin, JvmPlugin> {
throw PluginLoadException("Exception while loading ${description.name}", it)
}
override fun enable(plugin: JvmPlugin) {
ensureActive()
if (plugin is JvmPluginImpl) {
plugin.internalOnEnable()
} else plugin.onEnable()
}
override fun disable(plugin: JvmPlugin) {
if (plugin is JvmPluginImpl) {
plugin.internalOnDisable()
} else plugin.onDisable()
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS")
package net.mamoe.mirai.console.plugins.builtin
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.Plugin
import net.mamoe.mirai.console.plugins.PluginLoader
import net.mamoe.mirai.console.utils.JavaPluginScheduler
import net.mamoe.mirai.utils.MiraiLogger
import java.util.concurrent.locks.ReentrantLock
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/**
* Java Kotlin Jar 插件
*
* @see JavaPlugin Java 插件
* @see KotlinPlugin Kotlin 插件
*/
interface JvmPlugin : Plugin, CoroutineScope {
/** 日志 */
val logger: MiraiLogger
/** 插件描述 */
val description: JvmPluginDescription
/** 所属插件加载器实例 */
override val loader: PluginLoader<*, *> get() = JarPluginLoader
@JvmDefault
fun onLoad() {
}
@JvmDefault
fun onEnable() {
}
@JvmDefault
fun onDisable() {
}
}
/**
* Java 插件的父类
*/
abstract class JavaPlugin @JvmOverloads constructor(
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
) : JvmPlugin, JvmPluginImpl(parentCoroutineContext) {
/**
* Java API Scheduler
*/
val scheduler: JavaPluginScheduler =
JavaPluginScheduler(this.coroutineContext)
}
abstract class KotlinPlugin @JvmOverloads constructor(
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
) : JvmPlugin, JvmPluginImpl(parentCoroutineContext) {
// that's it
}
internal sealed class JvmPluginImpl(
parentCoroutineContext: CoroutineContext
) : JvmPlugin, CoroutineScope {
/**
* Initialized immediately after construction of [JvmPluginImpl] instance
*/
@Suppress("PropertyName")
internal lateinit var _description: JvmPluginDescription
// for future use
@Suppress("PropertyName")
@JvmField
internal var _intrinsicCoroutineContext: CoroutineContext = EmptyCoroutineContext
override val description: JvmPluginDescription get() = _description
final override val logger: MiraiLogger by lazy { MiraiConsole.newLogger(this._description.name) }
@JvmField
internal val coroutineContextInitializer = {
CoroutineExceptionHandler { _, throwable -> logger.error(throwable) }
.plus(parentCoroutineContext)
.plus(SupervisorJob(parentCoroutineContext[Job])) + _intrinsicCoroutineContext
}
private var firstRun = true
internal fun internalOnDisable() {
firstRun = false
this.onDisable()
}
internal fun internalOnLoad() {
this.onLoad()
}
internal fun internalOnEnable() {
if (!firstRun) refreshCoroutineContext()
this.onEnable()
}
private fun refreshCoroutineContext(): CoroutineContext {
return coroutineContextInitializer().also { _coroutineContext = it }
}
private val contextUpdateLock: ReentrantLock = ReentrantLock()
private var _coroutineContext: CoroutineContext? = null
final override val coroutineContext: CoroutineContext
get() = _coroutineContext
?: contextUpdateLock.withLock { _coroutineContext ?: refreshCoroutineContext() }
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.plugins.builtin
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.console.plugins.FilePluginDescription
import net.mamoe.mirai.console.plugins.PluginDependency
import net.mamoe.mirai.console.plugins.PluginDescription
import net.mamoe.mirai.console.plugins.PluginKind
import java.io.File
@Serializable
class JvmPluginDescription internal constructor(
override val kind: PluginKind = PluginKind.NORMAL,
override val name: String,
@SerialName("main")
val mainClassName: String,
override val author: String = "",
override val version: String,
override val info: String = "",
@SerialName("depends")
override val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency> = listOf()
) : PluginDescription, FilePluginDescription {
/**
* 在手动实现时使用这个构造器.
*/
@Suppress("unused")
constructor(
kind: PluginKind, name: String, mainClassName: String, author: String,
version: String, info: String, depends: List<PluginDependency>,
file: File
) : this(kind, name, mainClassName, author, version, info, depends) {
this._file = file
}
override val file: File
get() = _file ?: error("Internal error: JvmPluginDescription(name=$name)._file == null")
@Suppress("PropertyName")
@Transient
@JvmField
internal var _file: File? = null
override fun toString(): String {
return "JvmPluginDescription(kind=$kind, name='$name', mainClassName='$mainClassName', author='$author', version='$version', info='$info', dependencies=$dependencies, _file=$_file)"
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.plugins
import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.setting.internal.map
import net.mamoe.yamlkt.Yaml
import net.mamoe.yamlkt.YamlDynamicSerializer
import java.io.File
/** 插件类型 */
@Serializable(with = PluginKind.AsStringSerializer::class)
enum class PluginKind {
/** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */
LOADER,
/** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */
NORMAL;
object AsStringSerializer : KSerializer<PluginKind> by String.serializer().map(
serializer = { it.name },
deserializer = { str ->
values().firstOrNull {
it.name.equals(str, ignoreCase = true)
} ?: NORMAL
}
)
}
/**
* 插件描述
*/
interface PluginDescription {
val kind: PluginKind
val name: String
val author: String
val version: String
val info: String
/** 此插件依赖的其他插件, 将会在这些插件加载之后加载此插件 */
val dependencies: List<@Serializable(with = PluginDependency.SmartSerializer::class) PluginDependency>
}
/** 插件的一个依赖的信息 */
@Serializable
data class PluginDependency(
/** 依赖插件名 */
val name: String,
/**
* 依赖版本号. null 时则为不限制版本.
* @see versionKind 版本号类型
*/
val version: String? = null,
/** 版本号类型 */
val versionKind: VersionKind = VersionKind.AT_LEAST,
/**
* 若为 `false`, 插件在找不到此依赖时也能正常加载.
*/
val isOptional: Boolean = false
) {
/** 版本号类型 */
@Serializable(with = VersionKind.AsStringSerializer::class)
enum class VersionKind(
private vararg val serialNames: String
) {
/** 要求依赖精确的版本 */
EXACT("exact"),
/** 要求依赖最低版本 */
AT_LEAST("at_least", "AtLeast", "least", "lowest", "+"),
/** 要求依赖最高版本 */
AT_MOST("at_most", "AtMost", "most", "highest", "-");
object AsStringSerializer : KSerializer<VersionKind> by String.serializer().map(
serializer = { it.serialNames.first() },
deserializer = { str ->
values().firstOrNull {
it.serialNames.any { name -> name.equals(str, ignoreCase = true) }
} ?: AT_LEAST
}
)
}
override fun toString(): String {
return "$name ${versionKind.toEnglishString()}v$version"
}
/**
* 可支持解析 [String] 作为 [PluginDependency.version] 或单个 [PluginDependency]
*/
object SmartSerializer : KSerializer<PluginDependency> by YamlDynamicSerializer.map(
serializer = { it },
deserializer = { any ->
when (any) {
is Map<*, *> -> Yaml.nonStrict.parse(serializer(), Yaml.nonStrict.stringify(any))
else -> PluginDependency(any.toString())
}
}
)
}
/**
* 基于文件的插件的描述
*/
interface FilePluginDescription : PluginDescription {
val file: File
}
internal fun PluginDependency.VersionKind.toEnglishString(): String = when (this) {
PluginDependency.VersionKind.EXACT -> ""
PluginDependency.VersionKind.AT_LEAST -> "at least "
PluginDependency.VersionKind.AT_MOST -> "at most "
}

View File

@ -1,149 +0,0 @@
package net.mamoe.mirai.console.scheduler
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.console.plugins.PluginBase
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.function.Supplier
import kotlin.coroutines.CoroutineContext
/**
* 作为Java插件开发者, 你应该使用PluginScheduler
* 他们使用kotlin更高效的协程实现并在API上对java有很高的亲和度
* 且可以保证在PluginBase关闭的时候结束所有任务
*
* 你应该使用SchedulerTaskManager获取PluginScheduler, 或直接通过PluginBase获取
*/
class PluginScheduler(_coroutineContext: CoroutineContext) : CoroutineScope {
override val coroutineContext: CoroutineContext = _coroutineContext + SupervisorJob(_coroutineContext[Job])
class RepeatTaskReceipt(@Volatile var cancelled: Boolean = false)
/**
* 新增一个 Repeat Task (定时任务)
*
* 这个 Runnable 会被每 [intervalMs] 调用一次(不包含 [runnable] 执行时间)
*
* 使用返回的 [RepeatTaskReceipt], 可以取消这个定时任务
*/
fun repeat(runnable: Runnable, intervalMs: Long): RepeatTaskReceipt {
val receipt = RepeatTaskReceipt()
this.launch {
while (isActive && (!receipt.cancelled)) {
withContext(Dispatchers.IO) {
runnable.run()
}
delay(intervalMs)
}
}
return receipt
}
/**
* 新增一个 Delay Task (延迟任务)
*
* 在延迟 [delayMs] 后执行 [runnable]
*
* 作为 Java 使用者, 你要注意可见性, 原子性
*/
fun delay(runnable: Runnable, delayMs: Long) {
this.launch {
delay(delayMs)
withContext(Dispatchers.IO) {
runnable.run()
}
}
}
/**
* 异步执行一个任务, 最终返回 [Future], Java 使用方法无异, 但效率更高且可以在插件关闭时停止
*/
fun <T> async(supplier: Supplier<T>): Future<T> {
return AsyncResult(
this.async {
withContext(Dispatchers.IO) {
supplier.get()
}
}
)
}
/**
* 异步执行一个任务, 没有返回
*/
fun async(runnable: Runnable) {
this.launch {
withContext(Dispatchers.IO) {
runnable.run()
}
}
}
}
/**
* 这个类作为 Java Kotlin 的桥接
* Java interface 进行了 Kotlin 的实现
* 使得 Java 开发者可以使用 Kotlin 的协程 [CoroutineScope.async]
* 具体使用方法与 Java [Future] 没有区别
*/
class AsyncResult<T>(
private val deferred: Deferred<T>
) : Future<T> {
override fun isDone(): Boolean {
return deferred.isCompleted
}
override fun get(): T {
return runBlocking {
deferred.await()
}
}
@OptIn(ExperimentalCoroutinesApi::class)
override fun get(p0: Long, p1: TimeUnit): T {
return runBlocking {
withTimeoutOrNull(p1.toMillis(p0)) {
deferred.await()
} ?: throw TimeoutException()
}
}
override fun cancel(p0: Boolean): Boolean {
deferred.cancel()
return true
}
override fun isCancelled(): Boolean {
return deferred.isCancelled
}
}
internal object SchedulerTaskManagerInstance {
private val schedulerTaskManagerInstance = mutableMapOf<PluginBase, PluginScheduler>()
private val mutex = Mutex()
fun getPluginScheduler(pluginBase: PluginBase): PluginScheduler {
runBlocking {
mutex.withLock {
if (!schedulerTaskManagerInstance.containsKey(pluginBase)) {
schedulerTaskManagerInstance[pluginBase] = PluginScheduler(pluginBase.coroutineContext)
}
}
}
return schedulerTaskManagerInstance[pluginBase]!!
}
}

View File

@ -12,23 +12,40 @@
package net.mamoe.mirai.console.setting
import kotlinx.serialization.KSerializer
import net.mamoe.mirai.console.setting.internal.SettingImpl
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import kotlin.reflect.full.findAnnotation
/**
* 在配置文件和图像界面中保存的名称.
*/
typealias SerialName = kotlinx.serialization.SerialName
/**
* 在配置文件和图像界面中显示的说明.
*/
typealias Comment = net.mamoe.yamlkt.Comment
/**
* 配置的基类. 所有配置必须拥有一个无参构造器, 以用于在 [MutableList] [MutableMap] 中动态识别类型
*/
@Suppress("EXPOSED_SUPER_CLASS")
abstract class Setting : AbstractSetting() {
abstract class Setting : SettingImpl() {
/**
* 这个配置的名称, 仅对于顶层配置有效.
*/
@MiraiExperimentalAPI
open val serialName: String
get() = this::class.findAnnotation<SerialName>()?.value
?: this::class.qualifiedName
?: error("Names should be assigned to anonymous classes manually by overriding serialName")
/**
* 提供属性委托, 并添加这个对象的自动保存跟踪.
*/
@JvmSynthetic
operator fun <T : Any> Value<T>.provideDelegate(
thisRef: Setting,
@ -39,9 +56,12 @@ abstract class Setting : AbstractSetting() {
return this
}
override fun toString(): String = yaml.stringify(this.serializer, this)
override fun toString(): String = yamlForToString.stringify(this.serializer, this)
}
/**
* 用于更新或保存这个 [Value] 的序列化器.
*/
@Suppress("UNCHECKED_CAST")
val <T : Setting> T.serializer: KSerializer<T>
get() = kotlinSerializer as KSerializer<T>

View File

@ -1,16 +0,0 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.setting
object ValueSerializerMark
/*
* More generic ones
*/

View File

@ -7,9 +7,14 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused")
package net.mamoe.mirai.console.setting
import net.mamoe.mirai.console.setting.internal.valueImpl
import kotlin.internal.LowPriorityInOverloadResolution
/**
* !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.SettingValueUseSiteCodegen.kt
* !!! DO NOT MODIFY THIS FILE MANUALLY
@ -136,3 +141,23 @@ inline fun <reified T : Setting> Setting.value(default: Set<T>): SettingSetValue
@JvmName("valueMutable")
inline fun <reified T : Setting> Setting.value(default: MutableSet<T>): MutableSettingSetValue<T> = valueImpl(default)
/**
* 创建一个只引用对象而不跟踪其属性的值.
*
* @param T 类型. 必须拥有 [kotlinx.serialization.Serializable] 注解 (因此编译器会自动生成序列化器)
*/
@DangerousReferenceOnlyValue
@JvmName("valueDynamic")
@LowPriorityInOverloadResolution
inline fun <reified T : Any> Setting.value(default: T): Value<T> = valueImpl(default)
@RequiresOptIn(
"""
这种只保存引用的 Value 可能会导致意料之外的结果, 在使用时须保持谨慎.
对值的改变不会触发自动保存, 也不会同步到 UI . UI 中只能编辑序列化之后的值.
""", level = RequiresOptIn.Level.WARNING
)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION)
annotation class DangerousReferenceOnlyValue

View File

@ -14,7 +14,7 @@ import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
/**
* !!! These primitive types are auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt
* !!! This file is auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt
* !!! for better performance
* !!! DO NOT MODIFY THIS FILE MANUALLY
*/
@ -62,28 +62,36 @@ sealed class PrimitiveArrayValue<T : Any> : ArrayValue<T>()
abstract class IntArrayValue internal constructor() : PrimitiveArrayValue<IntArray>(), Iterable<Int> {
override fun iterator(): Iterator<Int> = this.value.iterator()
}
abstract class ShortArrayValue internal constructor() : PrimitiveArrayValue<ShortArray>(), Iterable<Short> {
override fun iterator(): Iterator<Short> = this.value.iterator()
}
abstract class ByteArrayValue internal constructor() : PrimitiveArrayValue<ByteArray>(), Iterable<Byte> {
override fun iterator(): Iterator<Byte> = this.value.iterator()
}
abstract class LongArrayValue internal constructor() : PrimitiveArrayValue<LongArray>(), Iterable<Long> {
override fun iterator(): Iterator<Long> = this.value.iterator()
}
abstract class FloatArrayValue internal constructor() : PrimitiveArrayValue<FloatArray>(), Iterable<Float> {
override fun iterator(): Iterator<Float> = this.value.iterator()
}
abstract class DoubleArrayValue internal constructor() : PrimitiveArrayValue<DoubleArray>(), Iterable<Double> {
override fun iterator(): Iterator<Double> = this.value.iterator()
}
abstract class BooleanArrayValue internal constructor() : PrimitiveArrayValue<BooleanArray>(), Iterable<Boolean> {
override fun iterator(): Iterator<Boolean> = this.value.iterator()
}
abstract class CharArrayValue internal constructor() : PrimitiveArrayValue<CharArray>(), Iterable<Char> {
override fun iterator(): Iterator<Char> = this.value.iterator()
}
sealed class TypedPrimitiveArrayValue<E> : ArrayValue<Array<E>>(), Iterable<E> {
override fun iterator() = this.value.iterator()
}
@ -98,10 +106,7 @@ abstract class TypedBooleanArrayValue internal constructor() : TypedPrimitiveArr
abstract class TypedCharArrayValue internal constructor() : TypedPrimitiveArrayValue<Char>()
abstract class TypedStringArrayValue internal constructor() : TypedPrimitiveArrayValue<String>()
sealed class ListValue<E> : Value<List<E>>(), Iterable<E> {
override fun iterator() = this.value.iterator()
}
sealed class ListValue<E> : Value<List<E>>(), List<E>
abstract class IntListValue internal constructor() : ListValue<Int>()
abstract class ShortListValue internal constructor() : ListValue<Short>()
abstract class ByteListValue internal constructor() : ListValue<Byte>()
@ -114,10 +119,7 @@ abstract class StringListValue internal constructor() : ListValue<String>()
abstract class SettingListValue<T : Setting> internal constructor() : Value<List<T>>(), List<T>
sealed class SetValue<E> : Value<Set<E>>(), Iterable<E> {
override fun iterator() = this.value.iterator()
}
sealed class SetValue<E> : Value<Set<E>>(), Set<E>
abstract class IntSetValue internal constructor() : SetValue<Int>()
abstract class ShortSetValue internal constructor() : SetValue<Short>()
abstract class ByteSetValue internal constructor() : SetValue<Byte>()

View File

@ -7,14 +7,11 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.setting.internal
package net.mamoe.mirai.console.setting
import kotlinx.serialization.Decoder
import kotlinx.serialization.Encoder
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import net.mamoe.mirai.console.setting.*
/**
@ -414,8 +411,9 @@ internal fun Setting.valueImpl(default: Array<String>): TypedStringArrayValue {
}
internal fun Setting.valueImpl(default: List<Int>): IntListValue {
return object : IntListValue() {
private var internalValue: List<Int> = default
var internalValue: List<Int> = default
val delegt = dynamicList { internalValue }
return object : IntListValue(), List<Int> by delegt {
override var value: List<Int>
get() = internalValue
set(new) {
@ -429,8 +427,9 @@ internal fun Setting.valueImpl(default: List<Int>): IntListValue {
}
internal fun Setting.valueImpl(default: List<Short>): ShortListValue {
return object : ShortListValue() {
private var internalValue: List<Short> = default
var internalValue: List<Short> = default
val delegt = dynamicList { internalValue }
return object : ShortListValue(), List<Short> by delegt {
override var value: List<Short>
get() = internalValue
set(new) {
@ -444,8 +443,9 @@ internal fun Setting.valueImpl(default: List<Short>): ShortListValue {
}
internal fun Setting.valueImpl(default: List<Byte>): ByteListValue {
return object : ByteListValue() {
private var internalValue: List<Byte> = default
var internalValue: List<Byte> = default
val delegt = dynamicList { internalValue }
return object : ByteListValue(), List<Byte> by delegt {
override var value: List<Byte>
get() = internalValue
set(new) {
@ -459,8 +459,9 @@ internal fun Setting.valueImpl(default: List<Byte>): ByteListValue {
}
internal fun Setting.valueImpl(default: List<Long>): LongListValue {
return object : LongListValue() {
private var internalValue: List<Long> = default
var internalValue: List<Long> = default
val delegt = dynamicList { internalValue }
return object : LongListValue(), List<Long> by delegt {
override var value: List<Long>
get() = internalValue
set(new) {
@ -474,8 +475,9 @@ internal fun Setting.valueImpl(default: List<Long>): LongListValue {
}
internal fun Setting.valueImpl(default: List<Float>): FloatListValue {
return object : FloatListValue() {
private var internalValue: List<Float> = default
var internalValue: List<Float> = default
val delegt = dynamicList { internalValue }
return object : FloatListValue(), List<Float> by delegt {
override var value: List<Float>
get() = internalValue
set(new) {
@ -489,8 +491,9 @@ internal fun Setting.valueImpl(default: List<Float>): FloatListValue {
}
internal fun Setting.valueImpl(default: List<Double>): DoubleListValue {
return object : DoubleListValue() {
private var internalValue: List<Double> = default
var internalValue: List<Double> = default
val delegt = dynamicList { internalValue }
return object : DoubleListValue(), List<Double> by delegt {
override var value: List<Double>
get() = internalValue
set(new) {
@ -504,8 +507,9 @@ internal fun Setting.valueImpl(default: List<Double>): DoubleListValue {
}
internal fun Setting.valueImpl(default: List<Boolean>): BooleanListValue {
return object : BooleanListValue() {
private var internalValue: List<Boolean> = default
var internalValue: List<Boolean> = default
val delegt = dynamicList { internalValue }
return object : BooleanListValue(), List<Boolean> by delegt {
override var value: List<Boolean>
get() = internalValue
set(new) {
@ -519,8 +523,9 @@ internal fun Setting.valueImpl(default: List<Boolean>): BooleanListValue {
}
internal fun Setting.valueImpl(default: List<Char>): CharListValue {
return object : CharListValue() {
private var internalValue: List<Char> = default
var internalValue: List<Char> = default
val delegt = dynamicList { internalValue }
return object : CharListValue(), List<Char> by delegt {
override var value: List<Char>
get() = internalValue
set(new) {
@ -534,8 +539,9 @@ internal fun Setting.valueImpl(default: List<Char>): CharListValue {
}
internal fun Setting.valueImpl(default: List<String>): StringListValue {
return object : StringListValue() {
private var internalValue: List<String> = default
var internalValue: List<String> = default
val delegt = dynamicList { internalValue }
return object : StringListValue(), List<String> by delegt {
override var value: List<String>
get() = internalValue
set(new) {
@ -549,8 +555,9 @@ internal fun Setting.valueImpl(default: List<String>): StringListValue {
}
internal fun Setting.valueImpl(default: Set<Int>): IntSetValue {
return object : IntSetValue() {
private var internalValue: Set<Int> = default
var internalValue: Set<Int> = default
val delegt = dynamicSet { internalValue }
return object : IntSetValue(), Set<Int> by delegt {
override var value: Set<Int>
get() = internalValue
set(new) {
@ -564,8 +571,9 @@ internal fun Setting.valueImpl(default: Set<Int>): IntSetValue {
}
internal fun Setting.valueImpl(default: Set<Short>): ShortSetValue {
return object : ShortSetValue() {
private var internalValue: Set<Short> = default
var internalValue: Set<Short> = default
val delegt = dynamicSet { internalValue }
return object : ShortSetValue(), Set<Short> by delegt {
override var value: Set<Short>
get() = internalValue
set(new) {
@ -579,8 +587,9 @@ internal fun Setting.valueImpl(default: Set<Short>): ShortSetValue {
}
internal fun Setting.valueImpl(default: Set<Byte>): ByteSetValue {
return object : ByteSetValue() {
private var internalValue: Set<Byte> = default
var internalValue: Set<Byte> = default
val delegt = dynamicSet { internalValue }
return object : ByteSetValue(), Set<Byte> by delegt {
override var value: Set<Byte>
get() = internalValue
set(new) {
@ -594,8 +603,9 @@ internal fun Setting.valueImpl(default: Set<Byte>): ByteSetValue {
}
internal fun Setting.valueImpl(default: Set<Long>): LongSetValue {
return object : LongSetValue() {
private var internalValue: Set<Long> = default
var internalValue: Set<Long> = default
val delegt = dynamicSet { internalValue }
return object : LongSetValue(), Set<Long> by delegt {
override var value: Set<Long>
get() = internalValue
set(new) {
@ -609,8 +619,9 @@ internal fun Setting.valueImpl(default: Set<Long>): LongSetValue {
}
internal fun Setting.valueImpl(default: Set<Float>): FloatSetValue {
return object : FloatSetValue() {
private var internalValue: Set<Float> = default
var internalValue: Set<Float> = default
val delegt = dynamicSet { internalValue }
return object : FloatSetValue(), Set<Float> by delegt {
override var value: Set<Float>
get() = internalValue
set(new) {
@ -624,8 +635,9 @@ internal fun Setting.valueImpl(default: Set<Float>): FloatSetValue {
}
internal fun Setting.valueImpl(default: Set<Double>): DoubleSetValue {
return object : DoubleSetValue() {
private var internalValue: Set<Double> = default
var internalValue: Set<Double> = default
val delegt = dynamicSet { internalValue }
return object : DoubleSetValue(), Set<Double> by delegt {
override var value: Set<Double>
get() = internalValue
set(new) {
@ -639,8 +651,9 @@ internal fun Setting.valueImpl(default: Set<Double>): DoubleSetValue {
}
internal fun Setting.valueImpl(default: Set<Boolean>): BooleanSetValue {
return object : BooleanSetValue() {
private var internalValue: Set<Boolean> = default
var internalValue: Set<Boolean> = default
val delegt = dynamicSet { internalValue }
return object : BooleanSetValue(), Set<Boolean> by delegt {
override var value: Set<Boolean>
get() = internalValue
set(new) {
@ -654,8 +667,9 @@ internal fun Setting.valueImpl(default: Set<Boolean>): BooleanSetValue {
}
internal fun Setting.valueImpl(default: Set<Char>): CharSetValue {
return object : CharSetValue() {
private var internalValue: Set<Char> = default
var internalValue: Set<Char> = default
val delegt = dynamicSet { internalValue }
return object : CharSetValue(), Set<Char> by delegt {
override var value: Set<Char>
get() = internalValue
set(new) {
@ -669,8 +683,9 @@ internal fun Setting.valueImpl(default: Set<Char>): CharSetValue {
}
internal fun Setting.valueImpl(default: Set<String>): StringSetValue {
return object : StringSetValue() {
private var internalValue: Set<String> = default
var internalValue: Set<String> = default
val delegt = dynamicSet { internalValue }
return object : StringSetValue(), Set<String> by delegt {
override var value: Set<String>
get() = internalValue
set(new) {
@ -690,7 +705,8 @@ internal fun Setting.valueImpl(
): MutableIntListValue {
var internalValue: MutableList<Int> = default
return object : MutableIntListValue(), MutableList<Int> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList{ internalValue }
return object : MutableIntListValue(), MutableList<Int> by delegt {
override var value: MutableList<Int>
get() = internalValue
set(new) {
@ -700,7 +716,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableList<Int>> = object : KSerializer<MutableList<Int>> {
private val delegate = ListSerializer(Int.serializer())
@ -708,7 +724,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableList<Int> {
return delegate.deserialize(decoder).toMutableList().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -725,7 +741,8 @@ internal fun Setting.valueImpl(
): MutableShortListValue {
var internalValue: MutableList<Short> = default
return object : MutableShortListValue(), MutableList<Short> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList{ internalValue }
return object : MutableShortListValue(), MutableList<Short> by delegt {
override var value: MutableList<Short>
get() = internalValue
set(new) {
@ -735,7 +752,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableList<Short>> = object : KSerializer<MutableList<Short>> {
private val delegate = ListSerializer(Short.serializer())
@ -743,7 +760,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableList<Short> {
return delegate.deserialize(decoder).toMutableList().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -760,7 +777,8 @@ internal fun Setting.valueImpl(
): MutableByteListValue {
var internalValue: MutableList<Byte> = default
return object : MutableByteListValue(), MutableList<Byte> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList{ internalValue }
return object : MutableByteListValue(), MutableList<Byte> by delegt {
override var value: MutableList<Byte>
get() = internalValue
set(new) {
@ -770,7 +788,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableList<Byte>> = object : KSerializer<MutableList<Byte>> {
private val delegate = ListSerializer(Byte.serializer())
@ -778,7 +796,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableList<Byte> {
return delegate.deserialize(decoder).toMutableList().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -795,7 +813,8 @@ internal fun Setting.valueImpl(
): MutableLongListValue {
var internalValue: MutableList<Long> = default
return object : MutableLongListValue(), MutableList<Long> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList{ internalValue }
return object : MutableLongListValue(), MutableList<Long> by delegt {
override var value: MutableList<Long>
get() = internalValue
set(new) {
@ -805,7 +824,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableList<Long>> = object : KSerializer<MutableList<Long>> {
private val delegate = ListSerializer(Long.serializer())
@ -813,7 +832,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableList<Long> {
return delegate.deserialize(decoder).toMutableList().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -830,7 +849,8 @@ internal fun Setting.valueImpl(
): MutableFloatListValue {
var internalValue: MutableList<Float> = default
return object : MutableFloatListValue(), MutableList<Float> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList{ internalValue }
return object : MutableFloatListValue(), MutableList<Float> by delegt {
override var value: MutableList<Float>
get() = internalValue
set(new) {
@ -840,7 +860,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableList<Float>> = object : KSerializer<MutableList<Float>> {
private val delegate = ListSerializer(Float.serializer())
@ -848,7 +868,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableList<Float> {
return delegate.deserialize(decoder).toMutableList().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -865,7 +885,8 @@ internal fun Setting.valueImpl(
): MutableDoubleListValue {
var internalValue: MutableList<Double> = default
return object : MutableDoubleListValue(), MutableList<Double> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList{ internalValue }
return object : MutableDoubleListValue(), MutableList<Double> by delegt {
override var value: MutableList<Double>
get() = internalValue
set(new) {
@ -875,7 +896,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableList<Double>> = object : KSerializer<MutableList<Double>> {
private val delegate = ListSerializer(Double.serializer())
@ -883,7 +904,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableList<Double> {
return delegate.deserialize(decoder).toMutableList().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -900,7 +921,8 @@ internal fun Setting.valueImpl(
): MutableBooleanListValue {
var internalValue: MutableList<Boolean> = default
return object : MutableBooleanListValue(), MutableList<Boolean> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList{ internalValue }
return object : MutableBooleanListValue(), MutableList<Boolean> by delegt {
override var value: MutableList<Boolean>
get() = internalValue
set(new) {
@ -910,7 +932,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableList<Boolean>> = object : KSerializer<MutableList<Boolean>> {
private val delegate = ListSerializer(Boolean.serializer())
@ -918,7 +940,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableList<Boolean> {
return delegate.deserialize(decoder).toMutableList().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -935,7 +957,8 @@ internal fun Setting.valueImpl(
): MutableCharListValue {
var internalValue: MutableList<Char> = default
return object : MutableCharListValue(), MutableList<Char> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList{ internalValue }
return object : MutableCharListValue(), MutableList<Char> by delegt {
override var value: MutableList<Char>
get() = internalValue
set(new) {
@ -945,7 +968,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableList<Char>> = object : KSerializer<MutableList<Char>> {
private val delegate = ListSerializer(Char.serializer())
@ -953,7 +976,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableList<Char> {
return delegate.deserialize(decoder).toMutableList().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -970,7 +993,8 @@ internal fun Setting.valueImpl(
): MutableStringListValue {
var internalValue: MutableList<String> = default
return object : MutableStringListValue(), MutableList<String> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList{ internalValue }
return object : MutableStringListValue(), MutableList<String> by delegt {
override var value: MutableList<String>
get() = internalValue
set(new) {
@ -980,7 +1004,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableList<String>> = object : KSerializer<MutableList<String>> {
private val delegate = ListSerializer(String.serializer())
@ -988,7 +1012,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableList<String> {
return delegate.deserialize(decoder).toMutableList().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1005,7 +1029,8 @@ internal fun Setting.valueImpl(
): MutableIntSetValue {
var internalValue: MutableSet<Int> = default
return object : MutableIntSetValue(), MutableSet<Int> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet{ internalValue }
return object : MutableIntSetValue(), MutableSet<Int> by delegt {
override var value: MutableSet<Int>
get() = internalValue
set(new) {
@ -1015,7 +1040,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableSet<Int>> = object : KSerializer<MutableSet<Int>> {
private val delegate = SetSerializer(Int.serializer())
@ -1023,7 +1048,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableSet<Int> {
return delegate.deserialize(decoder).toMutableSet().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1040,7 +1065,8 @@ internal fun Setting.valueImpl(
): MutableShortSetValue {
var internalValue: MutableSet<Short> = default
return object : MutableShortSetValue(), MutableSet<Short> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet{ internalValue }
return object : MutableShortSetValue(), MutableSet<Short> by delegt {
override var value: MutableSet<Short>
get() = internalValue
set(new) {
@ -1050,7 +1076,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableSet<Short>> = object : KSerializer<MutableSet<Short>> {
private val delegate = SetSerializer(Short.serializer())
@ -1058,7 +1084,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableSet<Short> {
return delegate.deserialize(decoder).toMutableSet().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1075,7 +1101,8 @@ internal fun Setting.valueImpl(
): MutableByteSetValue {
var internalValue: MutableSet<Byte> = default
return object : MutableByteSetValue(), MutableSet<Byte> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet{ internalValue }
return object : MutableByteSetValue(), MutableSet<Byte> by delegt {
override var value: MutableSet<Byte>
get() = internalValue
set(new) {
@ -1085,7 +1112,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableSet<Byte>> = object : KSerializer<MutableSet<Byte>> {
private val delegate = SetSerializer(Byte.serializer())
@ -1093,7 +1120,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableSet<Byte> {
return delegate.deserialize(decoder).toMutableSet().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1110,7 +1137,8 @@ internal fun Setting.valueImpl(
): MutableLongSetValue {
var internalValue: MutableSet<Long> = default
return object : MutableLongSetValue(), MutableSet<Long> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet{ internalValue }
return object : MutableLongSetValue(), MutableSet<Long> by delegt {
override var value: MutableSet<Long>
get() = internalValue
set(new) {
@ -1120,7 +1148,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableSet<Long>> = object : KSerializer<MutableSet<Long>> {
private val delegate = SetSerializer(Long.serializer())
@ -1128,7 +1156,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableSet<Long> {
return delegate.deserialize(decoder).toMutableSet().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1145,7 +1173,8 @@ internal fun Setting.valueImpl(
): MutableFloatSetValue {
var internalValue: MutableSet<Float> = default
return object : MutableFloatSetValue(), MutableSet<Float> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet{ internalValue }
return object : MutableFloatSetValue(), MutableSet<Float> by delegt {
override var value: MutableSet<Float>
get() = internalValue
set(new) {
@ -1155,7 +1184,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableSet<Float>> = object : KSerializer<MutableSet<Float>> {
private val delegate = SetSerializer(Float.serializer())
@ -1163,7 +1192,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableSet<Float> {
return delegate.deserialize(decoder).toMutableSet().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1180,7 +1209,8 @@ internal fun Setting.valueImpl(
): MutableDoubleSetValue {
var internalValue: MutableSet<Double> = default
return object : MutableDoubleSetValue(), MutableSet<Double> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet{ internalValue }
return object : MutableDoubleSetValue(), MutableSet<Double> by delegt {
override var value: MutableSet<Double>
get() = internalValue
set(new) {
@ -1190,7 +1220,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableSet<Double>> = object : KSerializer<MutableSet<Double>> {
private val delegate = SetSerializer(Double.serializer())
@ -1198,7 +1228,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableSet<Double> {
return delegate.deserialize(decoder).toMutableSet().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1215,7 +1245,8 @@ internal fun Setting.valueImpl(
): MutableBooleanSetValue {
var internalValue: MutableSet<Boolean> = default
return object : MutableBooleanSetValue(), MutableSet<Boolean> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet{ internalValue }
return object : MutableBooleanSetValue(), MutableSet<Boolean> by delegt {
override var value: MutableSet<Boolean>
get() = internalValue
set(new) {
@ -1225,7 +1256,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableSet<Boolean>> = object : KSerializer<MutableSet<Boolean>> {
private val delegate = SetSerializer(Boolean.serializer())
@ -1233,7 +1264,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableSet<Boolean> {
return delegate.deserialize(decoder).toMutableSet().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1250,7 +1281,8 @@ internal fun Setting.valueImpl(
): MutableCharSetValue {
var internalValue: MutableSet<Char> = default
return object : MutableCharSetValue(), MutableSet<Char> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet{ internalValue }
return object : MutableCharSetValue(), MutableSet<Char> by delegt {
override var value: MutableSet<Char>
get() = internalValue
set(new) {
@ -1260,7 +1292,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableSet<Char>> = object : KSerializer<MutableSet<Char>> {
private val delegate = SetSerializer(Char.serializer())
@ -1268,7 +1300,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableSet<Char> {
return delegate.deserialize(decoder).toMutableSet().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1285,7 +1317,8 @@ internal fun Setting.valueImpl(
): MutableStringSetValue {
var internalValue: MutableSet<String> = default
return object : MutableStringSetValue(), MutableSet<String> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet{ internalValue }
return object : MutableStringSetValue(), MutableSet<String> by delegt {
override var value: MutableSet<String>
get() = internalValue
set(new) {
@ -1295,7 +1328,7 @@ internal fun Setting.valueImpl(
}
}
private inline val `this` get() = this
private val outerThis get() = this
override val serializer: KSerializer<MutableSet<String>> = object : KSerializer<MutableSet<String>> {
private val delegate = SetSerializer(String.serializer())
@ -1303,7 +1336,7 @@ internal fun Setting.valueImpl(
override fun deserialize(decoder: Decoder): MutableSet<String> {
return delegate.deserialize(decoder).toMutableSet().observable {
onElementChanged(`this`)
onElementChanged(outerThis)
}
}
@ -1326,7 +1359,7 @@ internal fun <T : Setting> Setting.valueImpl(default: T): Value<T> {
onElementChanged(this)
}
}
override val serializer = object : KSerializer<T> {
override val serializer = object : KSerializer<T>{
override val descriptor: SerialDescriptor
get() = internalValue.updaterSerializer.descriptor

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.setting
package net.mamoe.mirai.console.setting.internal
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.serializer

View File

@ -7,15 +7,21 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.setting
package net.mamoe.mirai.console.setting.internal
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.setting.SerialName
import net.mamoe.mirai.console.setting.Setting
import net.mamoe.mirai.console.setting.Value
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.yamlkt.Yaml
import net.mamoe.yamlkt.YamlConfiguration
import kotlin.reflect.KProperty
import kotlin.reflect.full.findAnnotation
internal abstract class AbstractSetting {
internal abstract class SettingImpl {
@JvmField
internal var valueList: MutableList<Pair<Value<*>, KProperty<*>>> = mutableListOf()
@ -31,15 +37,18 @@ internal abstract class AbstractSetting {
internal val kotlinSerializer: KSerializer<Setting> by lazy {
object : KSerializer<Setting> {
override val descriptor: SerialDescriptor
get() = this@AbstractSetting.updaterSerializer.descriptor
get() = this@SettingImpl.updaterSerializer.descriptor
override fun deserialize(decoder: Decoder): Setting {
this@AbstractSetting.updaterSerializer.deserialize(decoder)
return this@AbstractSetting as Setting
this@SettingImpl.updaterSerializer.deserialize(decoder)
return this@SettingImpl as Setting
}
override fun serialize(encoder: Encoder, value: Setting) {
this@AbstractSetting.updaterSerializer.serialize(encoder, SettingSerializerMark)
this@SettingImpl.updaterSerializer.serialize(
encoder,
SettingSerializerMark
)
}
}
}
@ -50,7 +59,7 @@ internal abstract class AbstractSetting {
companion object {
@JvmStatic
internal val yaml =
internal val yamlForToString =
Yaml(
configuration = YamlConfiguration(
nonStrictNullability = true,
@ -67,6 +76,7 @@ internal class SettingUpdaterSerializer(
private val instance: Setting
) : KSerializer<SettingSerializerMark> {
override val descriptor: SerialDescriptor by lazy {
@OptIn(MiraiExperimentalAPI::class)
SerialDescriptor(instance.serialName) {
for ((value, property) in instance.valueList) {
element(property.serialNameOrPropertyName, value.serializer.descriptor, annotations, true)
@ -89,9 +99,8 @@ internal class SettingUpdaterSerializer(
while (true) {
val index = this.decodeElementIndex(descriptor)
if (index == CompositeDecoder.READ_DONE) return@decodeStructure SettingSerializerMark
val value = instance.valueList[index].first
this.decodeSerializableElement(
val value = instance.valueList[index].first as Value<Any>
value.value = this.decodeSerializableElement(
descriptor,
index,
value.serializer
@ -101,20 +110,43 @@ internal class SettingUpdaterSerializer(
SettingSerializerMark
}
override fun serialize(encoder: Encoder, value: SettingSerializerMark) = encoder.encodeStructure(descriptor) {
private val emptyList = emptyList<String>()
private val emptyListSerializer = ListSerializer(String.serializer())
override fun serialize(encoder: Encoder, value: SettingSerializerMark) {
if (instance.valueList.isEmpty()) {
emptyListSerializer.serialize(encoder, emptyList)
} else encoder.encodeStructure(descriptor) {
instance.valueList.forEachIndexed { index, (value, _) ->
@Suppress("UNCHECKED_CAST") // erased, no problem.
this.encodeSerializableElement(
descriptor,
index,
value.serializer as KSerializer<Any>,
value.value
)
this.encodeElementSmart(descriptor, index, value)
}
}
}
}
// until https://github.com/Him188/yamlkt/issues/2 fixed
internal fun <T : Any> CompositeEncoder.encodeElementSmart(
descriptor: SerialDescriptor,
index: Int,
value: Value<T>
) {
when (value.value::class) {
String::class -> this.encodeStringElement(descriptor, index, value.value as String)
Int::class -> this.encodeIntElement(descriptor, index, value.value as Int)
Byte::class -> this.encodeByteElement(descriptor, index, value.value as Byte)
Char::class -> this.encodeCharElement(descriptor, index, value.value as Char)
Long::class -> this.encodeLongElement(descriptor, index, value.value as Long)
Float::class -> this.encodeFloatElement(descriptor, index, value.value as Float)
Double::class -> this.encodeDoubleElement(descriptor, index, value.value as Double)
Boolean::class -> this.encodeBooleanElement(descriptor, index, value.value as Boolean)
else ->
@Suppress("UNCHECKED_CAST")
this.encodeSerializableElement(descriptor, index, value.serializer as KSerializer<Any>, value.value)
}
}
internal object SettingSerializerMark
internal val KProperty<*>.serialNameOrPropertyName: String get() = this.findAnnotation<SerialName>()?.value ?: this.name
@ -132,3 +164,14 @@ internal inline fun <E> KSerializer<E>.bind(
this@bind.serialize(encoder, getter())
}
}
internal inline fun <E, R> KSerializer<E>.map(
crossinline serializer: (R) -> E,
crossinline deserializer: (E) -> R
): KSerializer<R> {
return object : KSerializer<R> {
override val descriptor: SerialDescriptor get() = this@map.descriptor
override fun deserialize(decoder: Decoder): R = this@map.deserialize(decoder).let(deserializer)
override fun serialize(encoder: Encoder, value: R) = this@map.serialize(encoder, value.let(serializer))
}
}

View File

@ -6,13 +6,16 @@
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.console.setting
package net.mamoe.mirai.console.setting.internal
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.SetSerializer
import net.mamoe.mirai.console.setting.*
import net.mamoe.yamlkt.YamlDynamicSerializer
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
@ -53,7 +56,10 @@ internal fun <T : Any> Setting.valueImpl(
internalValue.shadowMap(transform = { it.value }, transformBack = { valueMapper(it) })
var shadowed: MutableList<T> = updateShadow()
return object : MutableListValue<T>(), MutableList<T> by dynamicMutableList({ shadowed }) {
val delegt = dynamicMutableList { shadowed }
return object : MutableListValue<T>(), MutableList<T> by delegt {
override var value: MutableList<Value<T>>
get() = internalValue
set(new) {
@ -86,7 +92,8 @@ internal fun <T : Setting> Setting.valueImpl(
): MutableSettingListValue<T> {
var internalValue: MutableList<T> = default
return object : MutableSettingListValue<T>(), MutableList<T> by dynamicMutableList({ internalValue }) {
val delegt = dynamicMutableList { internalValue }
return object : MutableSettingListValue<T>(), MutableList<T> by delegt {
override var value: MutableList<T>
get() = internalValue
set(new) {
@ -118,7 +125,8 @@ internal fun <T : Setting> Setting.valueImpl(
): SettingListValue<T> {
var internalValue: List<T> = default
return object : SettingListValue<T>(), List<T> by dynamicList({ internalValue }) {
val delegt = dynamicList { internalValue }
return object : SettingListValue<T>(), List<T> by delegt {
override var value: List<T>
get() = internalValue
set(new) {
@ -150,7 +158,8 @@ internal fun <T : Setting> Setting.valueImpl(
): SettingSetValue<T> {
var internalValue: Set<T> = default
return object : SettingSetValue<T>(), Set<T> by dynamicSet({ internalValue }) {
val delegt = dynamicSet { internalValue }
return object : SettingSetValue<T>(), Set<T> by delegt {
override var value: Set<T>
get() = internalValue
set(new) {
@ -182,7 +191,8 @@ internal fun <T : Setting> Setting.valueImpl(
): MutableSettingSetValue<T> {
var internalValue: MutableSet<T> = default
return object : MutableSettingSetValue<T>(), MutableSet<T> by dynamicMutableSet({ internalValue }) {
val delegt = dynamicMutableSet { internalValue }
return object : MutableSettingSetValue<T>(), MutableSet<T> by delegt {
override var value: MutableSet<T>
get() = internalValue
set(new) {
@ -247,7 +257,9 @@ internal fun <T : Any> Setting.valueImpl(
internalValue.shadowMap(transform = { it.value }, transformBack = { valueMapper(it) })
var shadowed: MutableSet<T> = updateShadow()
return object : MutableSetValue<T>(), MutableSet<T> by dynamicMutableSet({ shadowed }) {
val delegt = dynamicMutableSet { shadowed }
return object : MutableSetValue<T>(), MutableSet<T> by delegt {
override var value: MutableSet<Value<T>>
get() = internalValue
set(new) {
@ -280,6 +292,7 @@ internal fun <T : Any> Setting.valueImpl(
* For primitives and serializable only
*/
@PublishedApi
@LowPriorityInOverloadResolution
internal inline fun <reified T : Any> Setting.valueImpl(default: T): Value<T> =
valueImpl(default, T::class)
@ -293,19 +306,14 @@ internal fun <T : Any> Setting.valueImpl(default: T, clazz: KClass<out T>): Valu
}
return object : DynamicReferenceValue<T>() {
override var value: T = default
override val serializer: KSerializer<T>
get() = object : KSerializer<T> {
override val serializer: KSerializer<T> = object : KSerializer<T> {
override val descriptor: SerialDescriptor
get() = YamlDynamicSerializer.descriptor
override fun deserialize(decoder: Decoder): T {
return YamlDynamicSerializer.deserialize(decoder).smartCastPrimitive(clazz)
}
override fun deserialize(decoder: Decoder): T =
YamlDynamicSerializer.deserialize(decoder).smartCastPrimitive(clazz)
@OptIn(ImplicitReflectionSerializer::class)
override fun serialize(encoder: Encoder, value: T) {
YamlDynamicSerializer.serialize(encoder, value)
}
override fun serialize(encoder: Encoder, value: T) = YamlDynamicSerializer.serialize(encoder, value)
}
}
}

View File

@ -22,13 +22,6 @@ import java.io.File
val User.isManager: Boolean
get() = this.bot.managers.contains(this.id)
@JvmName("addManager")
@JvmSynthetic
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun Bot.addManagerDeprecated(long: Long) {
addManager(long)
}
internal fun Bot.addManager(long: Long): Boolean {
TODO()
return true

View File

@ -11,7 +11,6 @@ package net.mamoe.mirai.console.utils
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.PluginBase
import java.util.concurrent.Executors
@Suppress("unused")
@ -25,30 +24,28 @@ object ConsoleInput {
* 如弹出框或一行字
*/
suspend fun requestInput(
hint:String
):String{
hint: String
): String {
return withContext(inputDispatcher) {
MiraiConsole.frontEnd.requestInput(hint)
}
}
fun requestInputBlocking(hint:String):String = runBlocking { requestInput(hint) }
fun requestInputBlocking(hint: String): String = runBlocking { requestInput(hint) }
/**
* asnyc获取
*/
fun requestInputAsync(
pluginBase: PluginBase,
scope: CoroutineScope,
hint: String
):Deferred<String>{
return pluginBase.async {
): Deferred<String> {
return scope.async {
requestInput(hint)
}
}
suspend fun MiraiConsole.requestInput(hint:String):String = requestInput(hint)
suspend fun PluginBase.requestInputAsync(hint:String):Deferred<String> = requestInputAsync(hint)
suspend fun MiraiConsole.requestInput(hint: String): String = requestInput(hint)
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2020 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/master/LICENSE
*/
package net.mamoe.mirai.console.utils
import kotlinx.coroutines.*
import kotlinx.coroutines.future.future
import net.mamoe.mirai.console.plugins.builtin.JavaPlugin
import java.util.concurrent.Callable
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Future
import kotlin.coroutines.CoroutineContext
/**
* 拥有生命周期管理的 Java 线程池.
*
* 在插件被 [卸载][JavaPlugin.onDisable] 时将会自动停止.
*
* @see JavaPlugin.scheduler 获取实例
*/
class JavaPluginScheduler internal constructor(parentCoroutineContext: CoroutineContext) : CoroutineScope {
override val coroutineContext: CoroutineContext =
parentCoroutineContext + SupervisorJob(parentCoroutineContext[Job])
/**
* 新增一个 Repeating Task (定时任务)
*
* 这个 Runnable 会被每 [intervalMs] 调用一次(不包含 [runnable] 执行时间)
*
* @see Future.cancel 取消这个任务
*/
fun repeating(intervalMs: Long, runnable: Runnable): Future<Void?> {
return this.future {
while (isActive) {
withContext(Dispatchers.IO) { runnable.run() }
delay(intervalMs)
}
null
}
}
/**
* 新增一个 Delayed Task (延迟任务)
*
* 在延迟 [delayMillis] 后执行 [runnable]
*/
fun delayed(delayMillis: Long, runnable: Runnable): CompletableFuture<Void?> {
return future {
delay(delayMillis)
withContext(Dispatchers.IO) {
runnable.run()
}
null
}
}
/**
* 新增一个 Delayed Task (延迟任务)
*
* 在延迟 [delayMillis] 后执行 [runnable]
*/
fun <R> delayed(delayMillis: Long, runnable: Callable<R>): CompletableFuture<Void?> {
return future {
delay(delayMillis)
withContext(Dispatchers.IO) { runnable.call() }
null
}
}
/**
* 异步执行一个任务, 最终返回 [Future], Java 使用方法无异, 但效率更高且可以在插件关闭时停止
*/
fun <R> async(supplier: Callable<R>): Future<R> {
return future {
withContext(Dispatchers.IO) { supplier.call() }
}
}
/**
* 异步执行一个任务, 没有返回
*/
fun async(runnable: Runnable): Future<Void?> {
return future {
withContext(Dispatchers.IO) { runnable.run() }
null
}
}
}

View File

@ -1,161 +0,0 @@
package net.mamoe.mirai.console.utils
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* 执行N次 builder
* 成功一次就会结束
* 否则就会throw
*/
@OptIn(ExperimentalContracts::class)
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
@kotlin.internal.InlineOnly
inline fun <R> retryCatching(n: Int, block: () -> R): Result<R> {
contract {
callsInPlace(block, InvocationKind.AT_LEAST_ONCE)
}
require(n >= 0) { "param n for retryCatching must not be negative" }
var exception: Throwable? = null
repeat(n){
try {
return Result.success(block())
} catch (e: Throwable) {
exception?.addSuppressedMirai(e)
exception = e
}
}
return Result.failure(exception!!)
}
@OptIn(ExperimentalContracts::class)
inline fun <T> tryNTimes(n: Int = 2, block: () -> T):T {
contract {
callsInPlace(block, InvocationKind.AT_LEAST_ONCE)
}
require(n >= 0) { "param n for tryNTimes must not be negative" }
var last:Exception? = null
repeat(n){
try {
return block.invoke()
}catch (e:Exception){
last = e
}
}
//给我编译
throw last!!
}
@PublishedApi
internal fun Throwable.addSuppressedMirai(e: Throwable) {
if (e === this) {
return
}
kotlin.runCatching {
this.addSuppressed(e)
}
}
/**
* 两个字符串的近似值
* 要求前面完全一致
*
* XXXXXYYYYY.fuzzyCompare(XXXXXYYY) = 0.8
* XXXXXYYYYY.fuzzyCompare(XXXXXYYYZZ) = 0.8
*/
internal fun String.fuzzyCompare(target: String): Double {
var step = 0
if (this == target) {
return 1.0
}
if (target.length > this.length) {
return 0.0
}
for (i in this.indices) {
if (target.length == i) {
step--
}else {
if (this[i] != target[i]) {
break
}
step++
}
}
if(step == this.length-1){
return 1.0
}
return step.toDouble()/this.length
}
/**
* 模糊搜索一个List中index最接近target的东西
*/
internal inline fun <T : Any> Collection<T>.fuzzySearch(
target: String,
index: (T) -> String
): T? {
if (this.isEmpty()) {
return null
}
var potential: T? = null
var rate = 0.0
this.forEach {
val thisIndex = index(it)
if(thisIndex == target){
return it
}
with(thisIndex.fuzzyCompare(target)) {
if (this > rate) {
rate = this
potential = it
}
}
}
return potential
}
/**
* 模糊搜索一个List中index最接近target的东西
* 并且确保target是唯一的
* 如搜索index为XXXXYY list中同时存在XXXXYYY XXXXYYYY 将返回null
*/
internal inline fun <T : Any> Collection<T>.fuzzySearchOnly(
target: String,
index: (T) -> String
): T? {
if (this.isEmpty()) {
return null
}
var potential: T? = null
var rate = 0.0
var collide = 0
this.forEach {
with(index(it).fuzzyCompare(target)) {
if (this > rate) {
rate = this
potential = it
}
if(this == 1.0){
collide++
}
if(collide > 1){
return null//collide
}
}
}
return potential
}
internal fun Group.fuzzySearchMember(nameCardTarget: String): Member? {
return this.members.fuzzySearchOnly(nameCardTarget) {
it.nameCard
}
}

View File

@ -1,50 +0,0 @@
package net.mamoe.mirai.console.utils
import net.mamoe.mirai.utils.MiraiExperimentalAPI
/**
* A Value
* the input type of this Value is T while the output is V
*/
@MiraiExperimentalAPI
abstract class Value<T, V> {
operator fun invoke(): V = get()
abstract fun get(): V
abstract fun set(t: T)
}
/**
* This value can be used as a Config Value
*/
@MiraiExperimentalAPI
interface ConfigValue
/**
* A simple value
* the input type is same as output value
*/
@MiraiExperimentalAPI
open class SimpleValue<T>(
var value: T
) : Value<T, T>() {
override fun get() = this.value
override fun set(t: T) {
this.value = t
}
}
@MiraiExperimentalAPI
open class NullableSimpleValue<T>(
value: T? = null
) : SimpleValue<T?>(
value
) {
fun isNull() = value == null
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2020 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/master/LICENSE
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE")
package net.mamoe.mirai.console.utils
import org.jetbrains.annotations.Range
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* 执行 [n] [block], 在第一次成功时返回执行结果, 在捕获到异常时返回异常.
*/
@kotlin.internal.InlineOnly
inline fun <R> retryCatching(n: @Range(from = 1, to = Long.MAX_VALUE) Int, block: () -> R): Result<R> {
contract {
callsInPlace(block, InvocationKind.AT_LEAST_ONCE)
}
require(n >= 0) { "param n for retryCatching must not be negative" }
var exception: Throwable? = null
repeat(n) {
try {
return Result.success(block())
} catch (e: Throwable) {
exception?.addSuppressedMirai(e)
exception = e
}
}
return Result.failure(exception!!)
}
@PublishedApi
internal fun Throwable.addSuppressedMirai(e: Throwable) {
if (e === this) {
return
}
kotlin.runCatching {
this.addSuppressed(e)
}
}

View File

@ -1,5 +1,5 @@
import net.mamoe.mirai.console.utils.fuzzySearch
import net.mamoe.mirai.console.utils.fuzzySearchOnly
import net.mamoe.mirai.console.command.fuzzySearch
import net.mamoe.mirai.console.command.fuzzySearchOnly
class Him188(val name:String){
override fun toString(): String {

View File

@ -12,13 +12,13 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.console.plugins.builtin.KotlinPlugin
import net.mamoe.mirai.message.data.*
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
val plugin: PluginBase = object : PluginBase() {
val plugin: KotlinPlugin = object : KotlinPlugin() {
}

View File

@ -1,6 +1,3 @@
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.kotlin.dsl.DependencyHandlerScope
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
@ -12,7 +9,7 @@ import org.gradle.kotlin.dsl.DependencyHandlerScope
object Versions {
object Mirai {
const val core = "1.0-RC2-1"
const val core = "1.0.0"
const val console = "0.5.1"
const val consoleGraphical = "0.0.7"
const val consoleTerminal = "0.1.0"
@ -21,20 +18,8 @@ object Versions {
object Kotlin {
const val stdlib = "1.3.72"
const val coroutines = "1.3.5"
const val coroutines = "1.3.7"
const val serialization = "0.20.0"
const val ktor = "1.3.2"
}
}
@Suppress("unused")
fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
@Suppress("unused")
fun DependencyHandlerScope.ktor(id: String, version: String = Versions.Kotlin.ktor) = "io.ktor:ktor-$id:$version"
@Suppress("unused")
fun DependencyHandler.compileAndRuntime(any: Any) {
add("compileOnly", any)
add("runtimeOnly", any)
}

View File

@ -0,0 +1,14 @@
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.kotlin.dsl.DependencyHandlerScope
@Suppress("unused")
fun DependencyHandlerScope.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
@Suppress("unused")
fun DependencyHandlerScope.ktor(id: String, version: String = Versions.Kotlin.ktor) = "io.ktor:ktor-$id:$version"
@Suppress("unused")
fun DependencyHandler.compileAndRuntime(any: Any) {
add("compileOnly", any)
add("runtimeOnly", any)
}