Conflicts:
	mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt
This commit is contained in:
jiahua.liu 2020-03-22 18:14:48 +08:00
commit de742bf904
26 changed files with 538 additions and 370 deletions

View File

@ -1,7 +1,7 @@
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
miraiVersion=0.26.1 miraiVersion=0.28.0
miraiConsoleVersion=0.3.4 miraiConsoleVersion=0.3.4
miraiConsoleWrapperVersion=0.1.3 miraiConsoleWrapperVersion=0.1.3
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true

View File

@ -6,9 +6,11 @@ import javafx.stage.Modality
import javafx.stage.StageStyle import javafx.stage.StageStyle
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.graphical.model.* import net.mamoe.mirai.console.graphical.model.*
import net.mamoe.mirai.console.graphical.view.VerificationCodeFragment import net.mamoe.mirai.console.graphical.view.dialog.InputDialog
import net.mamoe.mirai.console.graphical.view.dialog.VerificationCodeFragment
import net.mamoe.mirai.console.plugins.PluginManager import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.utils.MiraiConsoleUI import net.mamoe.mirai.console.utils.MiraiConsoleUI
import net.mamoe.mirai.network.WrongPasswordException import net.mamoe.mirai.network.WrongPasswordException
@ -30,10 +32,10 @@ class MiraiGraphicalUIController : Controller(), MiraiConsoleUI {
val consoleInfo = ConsoleInfo() val consoleInfo = ConsoleInfo()
fun login(qq: String, psd: String) { fun login(qq: String, psd: String) {
MiraiConsole.CommandProcessor.runConsoleCommandBlocking("/login $qq $psd") CommandManager.runCommand(ConsoleCommandSender, "/login $qq $psd")
} }
fun sendCommand(command: String) = MiraiConsole.CommandProcessor.runConsoleCommandBlocking(command) fun sendCommand(command: String) = CommandManager.runCommand(ConsoleCommandSender, command)
override fun pushLog(identity: Long, message: String) = Platform.runLater { override fun pushLog(identity: Long, message: String) = Platform.runLater {
fun ObservableList<*>.trim() { fun ObservableList<*>.trim() {
@ -78,13 +80,15 @@ class MiraiGraphicalUIController : Controller(), MiraiConsoleUI {
} }
} }
override suspend fun requestInput(): String { override suspend fun requestInput(hint: String): String {
val model = VerificationCodeModel() var ret: String? = null
find<VerificationCodeFragment>(Scope(model)).openModal(
modality = Modality.APPLICATION_MODAL, // UI必须在UI线程执行requestInput在协程种被调用
resizable = false Platform.runLater {
) ret = InputDialog(hint).open()
return model.code.value }
while (ret == null) { delay(1000) }
return ret!!
} }
override fun pushBotAdminStatus(identity: Long, admins: List<Long>) = Platform.runLater { override fun pushBotAdminStatus(identity: Long, admins: List<Long>) = Platform.runLater {
@ -102,7 +106,7 @@ class GraphicalLoginSolver : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? { override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? {
val code = VerificationCodeModel(VerificationCode(data)) val code = VerificationCodeModel(VerificationCode(data))
// 界面需要运行在主线程 // UI必须在UI线程执行requestInput在协程种被调用
Platform.runLater { Platform.runLater {
find<VerificationCodeFragment>(Scope(code)).openModal( find<VerificationCodeFragment>(Scope(code)).openModal(
stageStyle = StageStyle.UNDECORATED, stageStyle = StageStyle.UNDECORATED,

View File

@ -0,0 +1,15 @@
package net.mamoe.mirai.console.graphical.styleSheet
import tornadofx.Stylesheet
import tornadofx.c
open class BaseStyleSheet : Stylesheet() {
companion object {
const val primaryColor = "0EA987"
const val stressColor = "35867C"
const val secondaryColor = "32CABA"
const val lightColor ="9FD1CC"
const val FontColor = "FFFFFF"
}
}

View File

@ -7,7 +7,7 @@ import javafx.scene.paint.Color
import javafx.scene.text.FontWeight import javafx.scene.text.FontWeight
import tornadofx.* import tornadofx.*
class LoginViewStyleSheet : Stylesheet() { class LoginViewStyleSheet : BaseStyleSheet() {
companion object { companion object {
val vBox by csselement("VBox") val vBox by csselement("VBox")
@ -15,11 +15,14 @@ class LoginViewStyleSheet : Stylesheet() {
init { init {
/*
* center box
*/
vBox { vBox {
maxWidth = 500.px maxWidth = 500.px
maxHeight = 500.px maxHeight = 500.px
backgroundColor += c("39c5BB", 0.3) backgroundColor += c(primaryColor, 0.3)
backgroundRadius += box(15.px) backgroundRadius += box(15.px)
padding = box(50.px, 100.px) padding = box(50.px, 100.px)
@ -35,8 +38,11 @@ class LoginViewStyleSheet : Stylesheet() {
fontWeight = FontWeight.BOLD fontWeight = FontWeight.BOLD
} }
/*
* login button
*/
button { button {
backgroundColor += c("00BCD4", 0.8) backgroundColor += c(stressColor, 0.8)
padding = box(10.px, 0.px) padding = box(10.px, 0.px)
prefWidth = 500.px prefWidth = 500.px
textFill = Color.WHITE textFill = Color.WHITE

View File

@ -1,21 +1,51 @@
package net.mamoe.mirai.console.graphical.styleSheet package net.mamoe.mirai.console.graphical.styleSheet
import javafx.scene.Cursor
import javafx.scene.paint.Color
import tornadofx.* import tornadofx.*
class PrimaryStyleSheet : Stylesheet() { class PrimaryStyleSheet : BaseStyleSheet() {
companion object { companion object {
// window
val jfxTitle by cssclass("jfx-decorator-buttons-container") val jfxTitle by cssclass("jfx-decorator-buttons-container")
val container by cssclass("jfx-decorator-content-container") val container by cssclass("jfx-decorator-content-container")
// tab
val jfxTabPane by cssclass("tab-header-background")
val closeButton by cssclass("tab-close-button")
} }
init { init {
/*
* window
*/
jfxTitle { jfxTitle {
backgroundColor += c("00BCD4") backgroundColor += c(primaryColor)
} }
container { container {
borderColor += box(c("00BCD4")) borderColor += box(c(primaryColor))
borderWidth += box(0.px, 4.px, 4.px, 4.px) borderWidth += box(0.px, 4.px, 4.px, 4.px)
} }
/*
* tab pane
*/
jfxTabPane {
backgroundColor += c(primaryColor)
}
// 去除JFoenix默认样式
tab {
and(":closable") {
borderWidth += box(0.px)
borderInsets += box(6.px, 0.px)
}
closeButton {
and(hover) { cursor = Cursor.HAND }
}
}
} }
} }

View File

@ -8,12 +8,11 @@ import javafx.event.EventTarget
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.control.Button import javafx.scene.control.Button
import javafx.scene.control.ListView import javafx.scene.control.ListView
import javafx.scene.control.TabPane
import tornadofx.SortedFilteredList import tornadofx.SortedFilteredList
import tornadofx.attachTo import tornadofx.attachTo
import tornadofx.bind import tornadofx.bind
internal fun EventTarget.jfxTabPane(op: TabPane.() -> Unit = {}) = JFXTabPane().attachTo(this, op) internal fun EventTarget.jfxTabPane(op: JFXTabPane.() -> Unit = {}) = JFXTabPane().attachTo(this, op)
internal fun EventTarget.jfxButton(text: String = "", graphic: Node? = null, op: Button.() -> Unit = {}) = internal fun EventTarget.jfxButton(text: String = "", graphic: Node? = null, op: Button.() -> Unit = {}) =
JFXButton(text).attachTo(this, op) { JFXButton(text).attachTo(this, op) {

View File

@ -23,7 +23,9 @@ class PrimaryView : View() {
left = vbox { left = vbox {
imageview(Image(PrimaryView::class.java.classLoader.getResourceAsStream("logo.png"))) imageview(Image(PrimaryView::class.java.classLoader.getResourceAsStream("logo.png"))) {
isPreserveRatio = true
}
// bot list // bot list
jfxListView(controller.botList) { jfxListView(controller.botList) {
@ -70,13 +72,15 @@ class PrimaryView : View() {
center = jfxTabPane { center = jfxTabPane {
tabClosingPolicy = TabPane.TabClosingPolicy.ALL_TABS
logTab("Main", controller.mainLog, closeable = false) logTab("Main", controller.mainLog, closeable = false)
tab("Plugins").content = find<PluginsView>().root tab("Plugins").apply { isClosable = false }.content = find<PluginsView>().root
tab("Settings").content = find<SettingsView>().root tab("Settings").apply { isClosable = false }.content = find<SettingsView>().root
tab("Login").content = find<LoginView>().root tab("Login").apply { isClosable = false }.content = find<LoginView>().root
mainTabPane = this mainTabPane = this
} }
@ -95,6 +99,8 @@ private fun TabPane.logTab(
op: Tab.() -> Unit = {} op: Tab.() -> Unit = {}
) = tab(text) { ) = tab(text) {
this.isClosable = closeable
vbox { vbox {
buttonbar { buttonbar {
@ -117,8 +123,6 @@ private fun TabPane.logTab(
}.ui {// isSucceed: Boolean -> }.ui {// isSucceed: Boolean ->
// notify something // notify something
} }
if (closeable) button("关闭").action { close() }
} }
} }

View File

@ -0,0 +1,39 @@
package net.mamoe.mirai.console.graphical.view.dialog
import javafx.scene.control.TextField
import javafx.stage.Modality
import javafx.stage.StageStyle
import tornadofx.*
class InputDialog(title: String) : Fragment() {
private lateinit var input: TextField
init {
titleProperty.value = title
}
override val root = form {
fieldset {
field(title) {
input = textfield("")
}
buttonbar {
button("提交").action { close() }
}
}
}
fun open(): String {
// 阻塞窗口直到关闭
openModal(
stageStyle = StageStyle.DECORATED,
modality = Modality.APPLICATION_MODAL,
block = true
)
return input.text
}
}

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.console.graphical.view package net.mamoe.mirai.console.graphical.view.dialog
import javafx.scene.image.Image import javafx.scene.image.Image
import net.mamoe.mirai.console.graphical.model.VerificationCodeModel import net.mamoe.mirai.console.graphical.model.VerificationCodeModel
@ -30,7 +30,8 @@ class VerificationCodeFragment : Fragment() {
this@VerificationCodeFragment.close() this@VerificationCodeFragment.close()
} }
button("取消").action { button("取消").action {
code.code.value = MAGIC_KEY code.code.value =
MAGIC_KEY
code.commit() code.commit()
this@VerificationCodeFragment.close() this@VerificationCodeFragment.close()
} }

View File

@ -23,6 +23,7 @@ import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.cleanPage
import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.drawLog import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.drawLog
import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.redrawLogs import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.redrawLogs
import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.utils.MiraiConsoleUI import net.mamoe.mirai.console.utils.MiraiConsoleUI
import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.SimpleLogger.LogPriority import net.mamoe.mirai.utils.SimpleLogger.LogPriority
@ -124,7 +125,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
requestResult = input requestResult = input
requesting = false requesting = false
} else { } else {
CommandManager.runConsoleCommand(commandBuilder.toString()) CommandManager.runCommand(ConsoleCommandSender, commandBuilder.toString())
} }
} }

View File

@ -11,7 +11,7 @@ const val CONSOLE_TERMINAL = "Terminal"
const val CONSOLE_GRAPHICAL = "Graphical" const val CONSOLE_GRAPHICAL = "Graphical"
object ConsoleUpdater { internal object ConsoleUpdater {
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
private object Links : HashMap<String, Map<String, String>>() { private object Links : HashMap<String, Map<String, String>>() {

View File

@ -20,7 +20,7 @@ import java.net.URLClassLoader
import kotlin.math.pow import kotlin.math.pow
import kotlin.system.exitProcess import kotlin.system.exitProcess
object CoreUpdator { internal object CoreUpdater {
fun getProtocolLib(): File? { fun getProtocolLib(): File? {
contentPath.listFiles()?.forEach { file -> contentPath.listFiles()?.forEach { file ->

View File

@ -1,9 +1,9 @@
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.console.wrapper package net.mamoe.mirai.console.wrapper
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
import io.ktor.client.features.ClientRequestException
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.ByteReadChannel
@ -11,14 +11,11 @@ import io.ktor.utils.io.jvm.javaio.copyTo
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import kotlin.system.exitProcess import kotlin.system.exitProcess
val Http: HttpClient internal val Http: HttpClient = HttpClient(CIO)
get() = HttpClient(CIO)
internal inline fun <R> tryNTimesOrQuit(repeat: Int, errorHint: String, block: (Int) -> R) {
inline fun <R> tryNTimesOrQuit(repeat: Int, errorHint: String, block: (Int) -> R){
var lastException: Throwable? = null var lastException: Throwable? = null
repeat(repeat) { repeat(repeat) {
@ -38,82 +35,84 @@ inline fun <R> tryNTimesOrQuit(repeat: Int, errorHint: String, block: (Int) -> R
} }
suspend inline fun HttpClient.downloadRequest(url: String): ByteReadChannel { internal suspend inline fun HttpClient.downloadRequest(url: String): ByteReadChannel {
return with(this.get<HttpResponse>(url)){ return with(this.get<HttpResponse>(url)) {
if(this.status.value == 404 || this.status.value == 403){ if (this.status.value == 404 || this.status.value == 403) {
error("File not found") error("File not found")
} }
if(this.headers["status"] !=null && this.headers["status"] == "404"){ if (this.headers["status"] != null && this.headers["status"] == "404") {
error("File not found") error("File not found")
} }
this.content this.content
} }
} }
private val jcenterPath = "https://jcenter.bintray.com/{group}/{project}/{version}/:{project}-{version}.{extension}" private val jcenterPath = "https://jcenter.bintray.com/{group}/{project}/{version}/:{project}-{version}.{extension}"
private val aliyunPath = "https://maven.aliyun.com/nexus/content/repositories/jcenter/{group}/{project}/{version}/{project}-{version}.{extension}" private val aliyunPath =
"https://maven.aliyun.com/nexus/content/repositories/jcenter/{group}/{project}/{version}/{project}-{version}.{extension}"
private fun String.buildPath( private fun String.buildPath(
groupName: String, groupName: String,
projectName: String, projectName: String,
version: String, version: String,
extension: String extension: String
):String{ ): String {
return this return this
.replace( .replace(
"{group}",groupName "{group}", groupName
) )
.replace( .replace(
"{project}",projectName "{project}", projectName
) )
.replace( .replace(
"{extension}",extension "{extension}", extension
) )
.replace( .replace(
"{version}",version "{version}", version
) )
} }
suspend fun HttpClient.downloadMaven( internal suspend fun HttpClient.downloadMaven(
groupName: String, groupName: String,
projectName: String, projectName: String,
version: String, version: String,
extension: String extension: String
):ByteReadChannel{ ): ByteReadChannel {
return kotlin.runCatching { return kotlin.runCatching {
downloadRequest( downloadRequest(
aliyunPath.buildPath(groupName,projectName,version,extension) aliyunPath.buildPath(groupName, projectName, version, extension)
) )
}.getOrElse { }.getOrElse {
downloadRequest( downloadRequest(
jcenterPath.buildPath(groupName,projectName,version,extension) jcenterPath.buildPath(groupName, projectName, version, extension)
) )
} }
} }
suspend inline fun HttpClient.downloadMavenArchive( internal suspend inline fun HttpClient.downloadMavenArchive(
groupName: String, groupName: String,
projectName: String, projectName: String,
version: String version: String
):ByteReadChannel{ ): ByteReadChannel {
return downloadMaven(groupName,projectName,version,"jar") return downloadMaven(groupName, projectName, version, "jar")
} }
suspend inline fun HttpClient.downloadMavenPom( internal suspend inline fun HttpClient.downloadMavenPom(
groupName: String, groupName: String,
projectName: String, projectName: String,
version: String version: String
):ByteReadChannel{ ): ByteReadChannel {
return downloadMaven(groupName,projectName,version,"pom") return downloadMaven(groupName, projectName, version, "pom")
} }
suspend fun HttpClient.downloadMavenPomAsString( internal suspend fun HttpClient.downloadMavenPomAsString(
groupName: String, groupName: String,
projectName: String, projectName: String,
version: String version: String
):String{ ): String {
return kotlin.runCatching { return kotlin.runCatching {
this.get<String>( this.get<String>(
aliyunPath.buildPath(groupName,projectName,version,"pom") aliyunPath.buildPath(groupName, projectName, version, "pom")
) )
}.getOrElse { }.getOrElse {
this.get( this.get(
@ -123,12 +122,11 @@ suspend fun HttpClient.downloadMavenPomAsString(
} }
/** /**
* 只要填content path后面的就可以 * 只要填 content path 后面的就可以
*/ */
suspend fun ByteReadChannel.saveToContent(filepath:String){ internal suspend fun ByteReadChannel.saveToContent(filepath: String) {
val fileStream = File(contentPath.absolutePath + "/" + filepath).also { val fileStream = File(contentPath.absolutePath + "/" + filepath).also {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
it.createNewFile() it.createNewFile()
} }

View File

@ -9,7 +9,10 @@
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.console.wrapper package net.mamoe.mirai.console.wrapper
import kotlinx.coroutines.* import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File import java.io.File
import java.net.URLClassLoader import java.net.URLClassLoader
import java.util.* import java.util.*
@ -56,31 +59,31 @@ object WrapperMain {
println("Starting version check...") println("Starting version check...")
runBlocking { runBlocking {
launch { launch {
CoreUpdator.versionCheck() CoreUpdater.versionCheck()
} }
launch { launch {
ConsoleUpdater.versionCheck(type) ConsoleUpdater.versionCheck(type)
} }
} }
println("Version check complete, starting Mirai") println("Version check complete, starting Mirai")
println("Core :" + CoreUpdator.getCore()!!) println("Core :" + CoreUpdater.getCore()!!)
println("Protocol:" + CoreUpdator.getProtocolLib()!!) println("Protocol:" + CoreUpdater.getProtocolLib()!!)
println("Console :" + ConsoleUpdater.getFile()!! ) println("Console :" + ConsoleUpdater.getFile()!!)
println("Root :" + System.getProperty("user.dir") + "/") println("Root :" + System.getProperty("user.dir") + "/")
val loader = MiraiClassLoader( val loader = MiraiClassLoader(
CoreUpdator.getCore()!!, CoreUpdater.getCore()!!,
CoreUpdator.getProtocolLib()!!, CoreUpdater.getProtocolLib()!!,
ConsoleUpdater.getFile()!!, ConsoleUpdater.getFile()!!,
this.javaClass.classLoader this.javaClass.classLoader
) )
when(type) { when (type) {
CONSOLE_PURE -> { CONSOLE_PURE -> {
loader.loadClass("net.mamoe.mirai.BotFactoryJvm") loader.loadClass("net.mamoe.mirai.BotFactoryJvm")
loader.loadClass( loader.loadClass(
"net.mamoe.mirai.console.pure.MiraiConsolePureLoader" "net.mamoe.mirai.console.pure.MiraiConsolePureLoader"
).getMethod("load", String::class.java,String::class.java) ).getMethod("load", String::class.java, String::class.java)
.invoke(null,CoreUpdator.getCurrentVersion(),ConsoleUpdater.getCurrentVersion()) .invoke(null, CoreUpdater.getCurrentVersion(), ConsoleUpdater.getCurrentVersion())
} }
} }
} }

View File

@ -9,10 +9,11 @@
package net.mamoe.mirai.console package net.mamoe.mirai.console
import kotlinx.coroutines.* import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.command.DefaultCommands import net.mamoe.mirai.console.command.DefaultCommands
import net.mamoe.mirai.console.plugins.PluginManager import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.utils.MiraiConsoleUI import net.mamoe.mirai.console.utils.MiraiConsoleUI
@ -45,14 +46,16 @@ object MiraiConsole {
* Console运行路径 * Console运行路径
*/ */
var path: String = System.getProperty("user.dir") var path: String = System.getProperty("user.dir")
internal set
/** /**
* Console前端接口 * Console前端接口
*/ */
lateinit var frontEnd: MiraiConsoleUI lateinit var frontEnd: MiraiConsoleUI
internal set
var start = false private var started = false
/** /**
@ -63,10 +66,10 @@ object MiraiConsole {
coreVersion: String = "0.0.0", coreVersion: String = "0.0.0",
consoleVersion: String = "0.0.0" consoleVersion: String = "0.0.0"
) { ) {
if (start) { if (started) {
return return
} }
start = true started = true
/* 初始化前端 */ /* 初始化前端 */
this.version = consoleVersion this.version = consoleVersion
@ -110,49 +113,55 @@ object MiraiConsole {
} }
} }
@Suppress("RedundantSuspendModifier") // binary compatibility
@Deprecated("Please use CommandManager directly, this will be removed in later release") @Deprecated("Please use CommandManager directly, this will be removed in later release")
object CommandProcessor{ object CommandProcessor {
@Deprecated("Please use CommandManager directly, this will be removed in later release", ReplaceWith( @Deprecated(
"CommandManager.runConsoleCommand(command)", "Please use CommandManager directly, this will be removed in later release", ReplaceWith(
"net.mamoe.mirai.console.command.CommandManager" "CommandManager.runConsoleCommand(command)",
) "net.mamoe.mirai.console.command.CommandManager"
), level = DeprecationLevel.ERROR
) )
suspend fun runConsoleCommand(command: String) { suspend fun runConsoleCommand(command: String) {
CommandManager.runConsoleCommand(command) CommandManager.runCommand(ConsoleCommandSender, command)
}
@Deprecated("Please use CommandManager directly, this will be removed in later release", ReplaceWith(
"CommandManager.runCommand(sender, command)",
"net.mamoe.mirai.console.command.CommandManager"
)
)
suspend fun runCommand(sender: CommandSender, command: String) {
CommandManager.runCommand(sender,command)
} }
@Deprecated("Please use CommandManager directly, this will be removed in later release", ReplaceWith( @Deprecated(
"CommandManager.runConsoleCommand(command)", "Please use CommandManager directly, this will be removed in later release", ReplaceWith(
"net.mamoe.mirai.console.command.CommandManager" "CommandManager.runCommand(sender, command)",
"net.mamoe.mirai.console.command.CommandManager"
), level = DeprecationLevel.ERROR
) )
suspend fun runCommand(sender: CommandSender, command: String) {
CommandManager.runCommand(sender, command)
}
@Deprecated(
"Please use CommandManager directly, this will be removed in later release", ReplaceWith(
"CommandManager.runCommand(command)",
"net.mamoe.mirai.console.command.CommandManager",
"net.mamoe.mirai.console.command.ConsoleCommandSender"
), level = DeprecationLevel.ERROR
) )
fun runConsoleCommandBlocking(command: String) = runBlocking { runConsoleCommand(command)} fun runConsoleCommandBlocking(command: String) =
runBlocking { CommandManager.runCommand(ConsoleCommandSender, command) }
@Suppress("unused") @Suppress("unused")
@Deprecated("Please use CommandManager directly, this will be removed in later release", ReplaceWith( @Deprecated(
"CommandManager.runCommand(sender, command)", "Please use CommandManager directly, this will be removed in later release", ReplaceWith(
"net.mamoe.mirai.console.command.CommandManager" "CommandManager.runCommand(sender, command)",
"net.mamoe.mirai.console.command.CommandManager"
)
) )
) fun runCommandBlocking(sender: CommandSender, command: String) = runBlocking {
fun runCommandBlocking(sender: CommandSender, command: String) = runBlocking { runCommand(sender, command) } CommandManager.runCommand(sender, command)
}
} }
} }
/**
* Mirai Console的logger
* 它用于适配不同的Console前段
*/
internal object MiraiConsoleLogger { internal object MiraiConsoleLogger {
operator fun invoke(any: Any? = null) { operator fun invoke(any: Any? = null) {
invoke( invoke(

View File

@ -7,90 +7,53 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("NOTHING_TO_INLINE") @file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.PluginBase import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.SimpleLogger.LogPriority
interface CommandSender {
/**
* 立刻发送一条Message
*/
suspend fun sendMessage(messageChain: MessageChain)
suspend fun sendMessage(message: String)
/**
* 写入要发送的内容 所有内容最后会被以一条发出
*/
fun appendMessage(message: String)
fun sendMessageBlocking(messageChain: MessageChain) = runBlocking { sendMessage(messageChain) }
fun sendMessageBlocking(message: String) = runBlocking { sendMessage(message) }
}
abstract class CommandSenderImpl : CommandSender {
internal val builder = StringBuilder()
override fun appendMessage(message: String) {
builder.append(message).append("\n")
}
internal open suspend fun flushMessage() {
if (!builder.isEmpty()) {
sendMessage(builder.toString().removeSuffix("\n"))
}
}
}
object ConsoleCommandSender : CommandSenderImpl() {
override suspend fun sendMessage(messageChain: MessageChain) {
MiraiConsole.logger("[Command]", 0, messageChain.toString())
}
override suspend fun sendMessage(message: String) {
MiraiConsole.logger("[Command]", 0, message)
}
override suspend fun flushMessage() {
super.flushMessage()
builder.clear()
}
}
open class ContactCommandSender(val contact: Contact) : CommandSenderImpl() {
override suspend fun sendMessage(messageChain: MessageChain) {
contact.sendMessage(messageChain)
}
override suspend fun sendMessage(message: String) {
contact.sendMessage(message)
}
}
/**
* 指令
*
* @see register 注册这个指令
* @see registerCommand 注册指令 DSL
*/
interface Command { interface Command {
/**
* 指令主名称
*/
val name: String val name: String
/**
* 别名
*/
val alias: List<String> val alias: List<String>
/**
* 描述, 将会显示在 "/help" 指令中
*/
val description: String val description: String
/**
* 用法说明
*/
val usage: String val usage: String
suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean
} }
/**
* 注册这个指令
*/
inline fun Command.register() = CommandManager.register(this) inline fun Command.register() = CommandManager.register(this)
/**
fun registerCommand(builder: CommandBuilder.() -> Unit): Command { * 构造并注册一个指令
*/
inline fun registerCommand(builder: CommandBuilder.() -> Unit): Command {
return CommandBuilder().apply(builder).register() return CommandBuilder().apply(builder).register()
} }
@ -104,8 +67,9 @@ abstract class BlockingCommand(
override val usage: String = "" override val usage: String = ""
) : Command { ) : Command {
/** /**
* 最高优先级监听器 * 最高优先级监听器.
* 如果 return `false`, 这次指令不会被 [PluginBase] 的全局 onCommand 监听器监听 *
* 指令调用将优先触发 [Command.onCommand], 若该函数返回 `false`, 则不会调用 [PluginBase.onCommand]
* */ * */
final override suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean { final override suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
@ -116,7 +80,27 @@ abstract class BlockingCommand(
abstract fun onCommandBlocking(sender: CommandSender, args: List<String>): Boolean abstract fun onCommandBlocking(sender: CommandSender, args: List<String>): Boolean
} }
class AnonymousCommand internal constructor( /**
* @see registerCommand
*/
class CommandBuilder @PublishedApi internal constructor() {
var name: String? = null
var alias: List<String>? = null
var description: String = ""
var usage: String = "use /help for help"
internal var onCommand: (suspend CommandSender.(args: List<String>) -> Boolean)? = null
fun onCommand(commandProcess: suspend CommandSender.(args: List<String>) -> Boolean) {
onCommand = commandProcess
}
}
// internal
internal class AnonymousCommand internal constructor(
override val name: String, override val name: String,
override val alias: List<String>, override val alias: List<String>,
override val description: String, override val description: String,
@ -128,19 +112,8 @@ class AnonymousCommand internal constructor(
} }
} }
class CommandBuilder internal constructor() { @PublishedApi
var name: String? = null internal fun CommandBuilder.register(): AnonymousCommand {
var alias: List<String>? = null
var description: String = ""
var usage: String = "use /help for help"
internal var onCommand: (suspend CommandSender.(args: List<String>) -> Boolean)? = null
fun onCommand(commandProcess: suspend CommandSender.(args: List<String>) -> Boolean) {
onCommand = commandProcess
}
}
private fun CommandBuilder.register(): AnonymousCommand {
if (name == null || onCommand == null) { if (name == null || onCommand == null) {
error("CommandBuilder not complete") error("CommandBuilder not complete")
} }

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("unused") @file:Suppress("unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
@ -18,7 +18,6 @@ import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.console.plugins.PluginManager import net.mamoe.mirai.console.plugins.PluginManager
import java.util.concurrent.Executors import java.util.concurrent.Executors
object CommandManager : Job by { object CommandManager : Job by {
GlobalScope.launch(start = CoroutineStart.LAZY) { GlobalScope.launch(start = CoroutineStart.LAZY) {
processCommandQueue() processCommandQueue()
@ -28,6 +27,11 @@ object CommandManager : Job by {
val commands: Collection<Command> get() = registeredCommand.values val commands: Collection<Command> get() = registeredCommand.values
/**
* 注册这个指令.
*
* @throws IllegalStateException 当已注册的指令与 [command] 重名时
*/
fun register(command: Command) { fun register(command: Command) {
val allNames = mutableListOf(command.name).also { it.addAll(command.alias) } val allNames = mutableListOf(command.name).also { it.addAll(command.alias) }
allNames.forEach { allNames.forEach {
@ -41,9 +45,10 @@ object CommandManager : Job by {
} }
fun unregister(command: Command) { fun unregister(command: Command) {
(command.alias.asSequence() + command.name).forEach { command.alias.forEach {
registeredCommand.remove(it) registeredCommand.remove(it)
} }
registeredCommand.remove(command.name)
} }
fun unregister(commandName: String): Boolean { fun unregister(commandName: String): Boolean {
@ -52,23 +57,65 @@ object CommandManager : Job by {
/** /**
* 指令是单线程运行的 * 最基础的执行指令的方式
* 指令将会被加入队列依次执行
*
* @param sender 指令执行者, 可为 [ConsoleCommandSender] [ContactCommandSender]
*/ */
private val commandDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher() fun runCommand(sender: CommandSender, command: String) {
commandChannel.offer(
FullCommand(sender, command)
)
}
/** /**
* 执行一个指令, 但是如果你想模拟一个指令的执行 * 插队异步执行一个指令并返回 [Deferred]
* 请向下看
* *
* 返回一个指令是否执行成功 * @param sender 指令执行者, 可为 [ConsoleCommandSender] [ContactCommandSender]
* @see PluginBase.runCommandAsync 扩展
*/ */
private suspend fun processCommand(sender: CommandSender, fullCommand: String):Boolean { fun runCommandAsync(pluginBase: PluginBase, sender: CommandSender, command: String): Deferred<Boolean> {
return withContext(commandDispatcher) { return pluginBase.async {
_processCommand(sender, fullCommand) processCommand(sender, command)
} }
} }
private suspend fun _processCommand(sender: CommandSender, fullCommand: String): Boolean { /**
* 插队执行一个指令并返回 [Deferred]
*
* @param sender 指令执行者, 可为 [ConsoleCommandSender] [ContactCommandSender]
* @see PluginBase.runCommandAsync 扩展
*/
@Suppress("KDocUnresolvedReference")
suspend fun dispatchCommand(sender: CommandSender, command: String): Boolean {
return processCommand(sender, command)
}
/**
* 阻塞当前线程, 插队执行一个指令
*
* @param sender 指令执行者, 可为 [ConsoleCommandSender] [ContactCommandSender]
*/
// for java
fun dispatchCommandBlocking(sender: CommandSender, command: String): Boolean =
runBlocking { dispatchCommand(sender, command) }
// internal
/**
* 单线程执行指令
*/
private val commandDispatcher = Executors.newFixedThreadPool(1).asCoroutineDispatcher()
private suspend fun processCommand(sender: CommandSender, fullCommand: String): Boolean {
return withContext(commandDispatcher) {
processCommandImpl(sender, fullCommand)
}
}
private suspend fun processCommandImpl(sender: CommandSender, fullCommand: String): Boolean {
val blocks = fullCommand.split(" ") val blocks = fullCommand.split(" ")
val commandHead = blocks[0].replace("/", "") val commandHead = blocks[0].replace("/", "")
val args = blocks.drop(1) val args = blocks.drop(1)
@ -81,17 +128,16 @@ object CommandManager : Job by {
sender.sendMessage(this.usage) sender.sendMessage(this.usage)
} }
} }
}catch (e: Exception){ } catch (e: Exception) {
sender.sendMessage("在运行指令时出现了未知错误") sender.sendMessage("在运行指令时出现了未知错误")
e.printStackTrace() e.printStackTrace()
false false
}finally { } finally {
(sender as CommandSenderImpl).flushMessage() (sender as AbstractCommandSender).flushMessage()
} }
}?: throw UnknownCommandException(commandHead) } ?: throw UnknownCommandException(commandHead)
} }
internal class FullCommand( internal class FullCommand(
val sender: CommandSender, val sender: CommandSender,
val commandStr: String val commandStr: String
@ -103,61 +149,23 @@ object CommandManager : Job by {
val command = commandChannel.receive() val command = commandChannel.receive()
try { try {
processCommand(command.sender, command.commandStr) processCommand(command.sender, command.commandStr)
}catch (e:UnknownCommandException){ } catch (e: UnknownCommandException) {
command.sender.sendMessage("未知指令 " + command.commandStr) command.sender.sendMessage("未知指令 " + command.commandStr)
}catch (e:Throwable){//should never happen } catch (e: Throwable) {//should never happen
e.printStackTrace() e.printStackTrace()
} }
processCommandQueue() processCommandQueue()
} }
/**
* runCommand()是最基础的执行指令的方式
* 指令将会被加入队列依次执行
* 方法由0.27.0的阻塞模式改为不阻塞(鉴于commandChannel大小无限)
*/
fun runCommand(sender: CommandSender, command: String) {
runBlocking {//it wont be blocking
commandChannel.send(
FullCommand(sender, command)
)
}
}
@Suppress("unused")
fun runConsoleCommand(command: String) = runCommand(ConsoleCommandSender,command)
/**
* runCommandAnsyc()执行一个指令并返回deferred
* 为插队执行
*/
fun runCommandAsync(pluginBase: PluginBase, sender: CommandSender, command: String):Deferred<Boolean>{
return pluginBase.async{
processCommand(sender,command)
}
}
fun runConsoleCommandAsync(pluginBase: PluginBase, command: String):Deferred<Boolean> = runCommandAsync(pluginBase,ConsoleCommandSender,command)
/**
* dispatchCommand()执行一个指令并等到完成
* 为插队执行
*/
suspend fun dispatchCommand(sender: CommandSender,command: String):Boolean{
return processCommand(sender,command)
}
suspend fun dispatchConsoleCommand(command: String):Boolean = dispatchCommand(ConsoleCommandSender,command)
fun dispatchCommandBlocking(sender: CommandSender,command: String):Boolean = runBlocking { dispatchCommand(sender, command) }
fun dispatchConsoleCommandBlocking(command: String):Boolean = runBlocking { dispatchConsoleCommandBlocking(command) }
} }
fun PluginBase.runCommandAsnyc(sender: CommandSender, command: String):Deferred<Boolean> = CommandManager.runCommandAsync(this,sender,command) /**
* 插队异步执行一个指令并返回 [Deferred]
fun PluginBase.runConsoleCommandAsync(command: String):Deferred<Boolean> = CommandManager.runConsoleCommandAsync(this,command) *
* @param sender 指令执行者, 可为 [ConsoleCommandSender] [ContactCommandSender]
* @see PluginBase.runCommandAsync 扩展
*/
fun PluginBase.runCommandAsync(sender: CommandSender, command: String): Deferred<Boolean> =
CommandManager.runCommandAsync(this, sender, command)
class UnknownCommandException(command: String) : Exception("unknown command \"$command\"")
class UnknownCommandException(command: String):Exception("unknown command \"$command\"")

View File

@ -0,0 +1,84 @@
/*
* 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.command
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.message.data.Message
/**
* 指令发送者
*
* @see AbstractCommandSender 请继承于该抽象类
*/
interface CommandSender {
/**
* 立刻发送一条消息
*/
suspend fun sendMessage(messageChain: Message)
suspend fun sendMessage(message: String)
/**
* 写入要发送的内容 所有内容最后会被以一条发出
*/
fun appendMessage(message: String)
fun sendMessageBlocking(messageChain: Message) = runBlocking { sendMessage(messageChain) }
fun sendMessageBlocking(message: String) = runBlocking { sendMessage(message) }
}
abstract class AbstractCommandSender : CommandSender {
internal val builder = StringBuilder()
override fun appendMessage(message: String) {
builder.append(message).append("\n")
}
internal open suspend fun flushMessage() {
if (builder.isNotEmpty()) {
sendMessage(builder.toString().removeSuffix("\n"))
}
}
}
/**
* 控制台指令执行者. 代表由控制台执行指令
*/
object ConsoleCommandSender : AbstractCommandSender() {
override suspend fun sendMessage(messageChain: Message) {
MiraiConsole.logger("[Command]", 0, messageChain.toString())
}
override suspend fun sendMessage(message: String) {
MiraiConsole.logger("[Command]", 0, message)
}
override suspend fun flushMessage() {
super.flushMessage()
builder.clear()
}
}
/**
* 联系人指令执行者. 代表由一个 QQ 用户执行指令
*/
@Suppress("MemberVisibilityCanBePrivate")
open class ContactCommandSender(val contact: Contact) : AbstractCommandSender() {
override suspend fun sendMessage(messageChain: Message) {
contact.sendMessage(messageChain)
}
override suspend fun sendMessage(message: String) {
contact.sendMessage(message)
}
}

View File

@ -143,7 +143,7 @@ object DefaultCommands {
startsWith("/") { message -> startsWith("/") { message ->
if (bot.checkManager(this.sender.id)) { if (bot.checkManager(this.sender.id)) {
val sender = ContactCommandSender(this.subject) val sender = ContactCommandSender(this.subject)
MiraiConsole.CommandProcessor.runCommand( CommandManager.runCommand(
sender, message sender, message
) )
} }

View File

@ -14,14 +14,3 @@ import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.broadcast
internal fun <E : Event> broadcast(e: E): E = runBlocking { e.broadcast() } internal fun <E : Event> broadcast(e: E): E = runBlocking { e.broadcast() }
class A{
var x = 0
var width:Int
get() = x
set(value){
x = value
}
}

View File

@ -15,17 +15,17 @@ import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.pure.MiraiConsoleUIPure
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.SimpleLogger import net.mamoe.mirai.utils.SimpleLogger
import net.mamoe.mirai.utils.SimpleLogger.LogPriority
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.net.URLClassLoader import java.net.URLClassLoader
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
/**
* 所有插件的基类
*/
abstract class PluginBase abstract class PluginBase
@JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope { @JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope {
@ -36,9 +36,8 @@ abstract class PluginBase
* 插件被分配的数据目录数据目录会与插件名称同名 * 插件被分配的数据目录数据目录会与插件名称同名
*/ */
val dataFolder: File by lazy { val dataFolder: File by lazy {
File(PluginManager.pluginsPath + "/" + PluginManager.lastPluginName).also { File(PluginManager.pluginsPath + "/" + PluginManager.lastPluginName)
it.mkdir() .also { it.mkdir() }
}
} }
/** /**
@ -63,37 +62,21 @@ abstract class PluginBase
} }
/** /**
* 当任意指令被使用 * 当任意指令被使用时调用.
*
* 指令调用将优先触发 [Command.onCommand], 若该函数返回 `false`, 则不会调用 [PluginBase.onCommand]
*/ */
open fun onCommand(command: Command, sender: CommandSender, args: List<String>) { open fun onCommand(command: Command, sender: CommandSender, args: List<String>) {
} }
internal fun enable() {
this.onEnable()
}
/** /**
* 加载一个data folder中的Config * 加载一个 [dataFolder] 中的 [Config]
* 这个config是read-write的
*/ */
fun loadConfig(fileName: String): Config { fun loadConfig(fileName: String): Config {
return Config.load(dataFolder.absolutePath + "/" + fileName) return Config.load(dataFolder.absolutePath + "/" + fileName)
} }
@JvmOverloads
internal fun disable(throwable: CancellationException? = null) {
this.coroutineContext[Job]!!.cancelChildren(throwable)
try {
this.onDisable()
} catch (e: Exception) {
logger.info(e)
}
}
internal var pluginName: String = ""
val logger: MiraiLogger by lazy { val logger: MiraiLogger by lazy {
SimpleLogger("Plugin $pluginName") { priority, message, e -> SimpleLogger("Plugin $pluginName") { priority, message, e ->
val identityString = "[${pluginName}]" val identityString = "[${pluginName}]"
@ -120,16 +103,34 @@ abstract class PluginBase
/** /**
* 加载 resource 中的 [Config] * 加载 resource 中的 [Config]
* 这个 [Config] read-only * 这个 [Config] 只读
*/ */
fun getResourcesConfig(fileName: String): Config { fun getResourcesConfig(fileName: String): Config {
if (!fileName.contains(".")) { require(fileName.contains(".")) { "Unknown Config Type" }
error("Unknown Config Type")
}
return Config.load(getResources(fileName) ?: error("No such file: $fileName"), fileName.substringAfter('.')) return Config.load(getResources(fileName) ?: error("No such file: $fileName"), fileName.substringAfter('.'))
} }
// internal
internal fun enable() {
this.onEnable()
}
internal fun disable(throwable: CancellationException? = null) {
this.coroutineContext[Job]!!.cancelChildren(throwable)
try {
this.onDisable()
} catch (e: Exception) {
logger.info(e)
}
}
internal var pluginName: String = ""
} }
/**
* 插件描述
*/
class PluginDescription( class PluginDescription(
val name: String, val name: String,
val author: String, val author: String,

View File

@ -19,32 +19,31 @@ import net.mamoe.mirai.utils.SimpleLogger
import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.utils.io.encodeToString
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import java.net.URL import java.net.URL
import java.util.jar.JarFile import java.util.jar.JarFile
object PluginManager { object PluginManager {
@Volatile
internal var lastPluginName: String = ""
internal val pluginsPath = (System.getProperty("user.dir") + "/plugins/").replace("//", "/").also { internal val pluginsPath = (System.getProperty("user.dir") + "/plugins/").replace("//", "/").also {
File(it).mkdirs() File(it).mkdirs()
} }
private val logger = SimpleLogger("Plugin Manager") { _, message, e -> private val logger = SimpleLogger("Plugin Manager") { p, message, e ->
MiraiConsole.logger("[Plugin Manager]", 0, message) MiraiConsole.logger(p, "[Plugin Manager]", 0, message)
MiraiConsole.logger("[Plugin Manager]", 0, e) MiraiConsole.logger(p, "[Plugin Manager]", 0, e)
} }
//已完成加载的 //已完成加载的
private val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf() private val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf()
private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf() private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf()
fun onCommand(command: Command, sender: CommandSender, args: List<String>) { internal fun onCommand(command: Command, sender: CommandSender, args: List<String>) {
nameToPluginBaseMap.values.forEach { nameToPluginBaseMap.values.forEach {
try { try {
it.onCommand(command, sender, args) it.onCommand(command, sender, args)
}catch (e:Throwable){ } catch (e: Throwable) {
logger.info(e) logger.info(e)
} }
} }
@ -64,6 +63,10 @@ object PluginManager {
return pluginDescriptions.values return pluginDescriptions.values
} }
@Volatile
internal var lastPluginName: String = ""
/** /**
* 尝试加载全部插件 * 尝试加载全部插件
*/ */
@ -153,27 +156,31 @@ object PluginManager {
//real load //real load
logger.info("loading plugin " + description.name) logger.info("loading plugin " + description.name)
lastPluginName = description.name
try { try {
val pluginClass = try { val pluginClass = try {
PluginClassLoader( PluginClassLoader(
(pluginsLocation[description.name]!!), (pluginsLocation[description.name]!!),
this.javaClass.classLoader this.javaClass.classLoader
) ).loadClass(description.basePath)
.loadClass(description.basePath)
} catch (e: ClassNotFoundException) { } catch (e: ClassNotFoundException) {
logger.info("failed to find Main: " + description.basePath + " checking if it's kotlin's path") logger.info("failed to find Main: " + description.basePath + " checking if it's kotlin's path")
PluginClassLoader( PluginClassLoader(
(pluginsLocation[description.name]!!), (pluginsLocation[description.name]!!),
this.javaClass.classLoader this.javaClass.classLoader
) ).loadClass("${description.basePath}Kt")
.loadClass("${description.basePath}Kt")
} }
return try { return try {
val subClass = pluginClass.asSubclass(PluginBase::class.java) val subClass = pluginClass.asSubclass(PluginBase::class.java)
lastPluginName = description.name
val plugin: PluginBase = val plugin: PluginBase =
subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().newInstance() subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().apply {
againstPermission()
}.newInstance()
plugin.dataFolder // initialize right now
description.loaded = true description.loaded = true
logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author) logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author)
logger.info(description.info) logger.info(description.info)
@ -200,8 +207,8 @@ object PluginManager {
nameToPluginBaseMap.values.forEach { nameToPluginBaseMap.values.forEach {
try { try {
it.onLoad() it.onLoad()
}catch (ignored : Throwable){ } catch (ignored: Throwable) {
if(ignored is CancellationException) { if (ignored is CancellationException) {
logger.info(ignored) logger.info(ignored)
logger.info(it.pluginName + " failed to load, disabling it") logger.info(it.pluginName + " failed to load, disabling it")
it.disable(ignored) it.disable(ignored)
@ -212,10 +219,10 @@ object PluginManager {
nameToPluginBaseMap.values.forEach { nameToPluginBaseMap.values.forEach {
try { try {
it.enable() it.enable()
}catch (ignored : Throwable){ } catch (ignored: Throwable) {
logger.info(ignored) logger.info(ignored)
logger.info(it.pluginName + " failed to enable, disabling it") logger.info(it.pluginName + " failed to enable, disabling it")
if(ignored is CancellationException) { if (ignored is CancellationException) {
it.disable(ignored) it.disable(ignored)
} }
} }
@ -238,7 +245,7 @@ object PluginManager {
*/ */
fun getJarFileByName(pluginName: String): File? { fun getJarFileByName(pluginName: String): File? {
File(pluginsPath).listFiles()?.forEach { file -> File(pluginsPath).listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") { if (file != null && file.extension == "jar") {
val jar = JarFile(file) val jar = JarFile(file)
val pluginYml = val pluginYml =
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull() jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
@ -270,4 +277,18 @@ object PluginManager {
return URL("jar:file:" + jarFile.absoluteFile + "!/" + toFindFile.name).openConnection().inputStream return URL("jar:file:" + jarFile.absoluteFile + "!/" + toFindFile.name).openConnection().inputStream
} }
} }
private val trySetAccessibleMethod: Method? = runCatching {
Class.forName("java.lang.reflect.AccessibleObject").getMethod("trySetAccessible")
}.getOrNull()
private fun Constructor<out PluginBase>.againstPermission() {
trySetAccessibleMethod?.let { it.invoke(this, true) }
?: kotlin.runCatching {
@Suppress("DEPRECATED")
this.isAccessible = true
}
}

View File

@ -11,7 +11,8 @@ package net.mamoe.mirai.console.pure
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.utils.MiraiConsoleUI import net.mamoe.mirai.console.utils.MiraiConsoleUI
import net.mamoe.mirai.utils.DefaultLoginSolver import net.mamoe.mirai.utils.DefaultLoginSolver
import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.LoginSolver
@ -49,7 +50,7 @@ class MiraiConsoleUIPure : MiraiConsoleUI {
requestStr = input requestStr = input
requesting = false requesting = false
} else { } else {
MiraiConsole.CommandProcessor.runConsoleCommandBlocking(input) CommandManager.runCommand(ConsoleCommandSender, input)
} }
} }
} }
@ -65,8 +66,7 @@ class MiraiConsoleUIPure : MiraiConsoleUI {
override fun pushLog(priority: LogPriority, identityStr: String, identity: Long, message: String) { override fun pushLog(priority: LogPriority, identityStr: String, identity: Long, message: String) {
var priorityStr = "[${priority.name}]" var priorityStr = "[${priority.name}]"
val _message = message + COLOR_RESET /*
/**
* 通过ANSI控制码添加颜色 * 通过ANSI控制码添加颜色
* 更多的颜色定义在 [MiraiConsoleUIPure] companion * 更多的颜色定义在 [MiraiConsoleUIPure] companion
*/ */
@ -85,7 +85,7 @@ class MiraiConsoleUIPure : MiraiConsoleUI {
else -> priorityStr else -> priorityStr
} }
println("\u001b[0m " + sdf.format(Date()) + " $priorityStr $identityStr $_message") println("\u001b[0m " + sdf.format(Date()) + " $priorityStr $identityStr ${message + COLOR_RESET}")
} }
override fun prePushBot(identity: Long) { override fun prePushBot(identity: Long) {

View File

@ -2,25 +2,25 @@ package net.mamoe.mirai.console.scheduler
import net.mamoe.mirai.console.plugins.PluginBase import net.mamoe.mirai.console.plugins.PluginBase
interface SchedulerTask<T:PluginBase>{ interface SchedulerTask<T : PluginBase> {
abstract fun onTick(i:Long) abstract fun onTick(i: Long)
abstract fun onRun() abstract fun onRun()
} }
abstract class RepeatTask<T:PluginBase>( abstract class RepeatTask<T : PluginBase>(
val intervalInMs: Int val intervalInMs: Int
):SchedulerTask<T>{ ) : SchedulerTask<T> {
override fun onTick(i: Long) { override fun onTick(i: Long) {
if(i%intervalInMs == 0L){ if (i % intervalInMs == 0L) {
onRun() onRun()
} }
} }
companion object{ companion object {
fun <T:PluginBase> of( fun <T : PluginBase> of(
intervalInMs: Int, runnable: () -> Unit intervalInMs: Int, runnable: () -> Unit
):RepeatTask<T>{ ): RepeatTask<T> {
return AnonymousRepeatTask<T>( return AnonymousRepeatTask<T>(
intervalInMs, runnable intervalInMs, runnable
) )
@ -28,42 +28,18 @@ abstract class RepeatTask<T:PluginBase>(
} }
} }
internal class AnonymousRepeatTask<T: PluginBase>( internal class AnonymousRepeatTask<T : PluginBase>(
intervalInMs: Int, private val runnable: () -> Unit intervalInMs: Int, private val runnable: () -> Unit
): RepeatTask<T>(intervalInMs){ ) : RepeatTask<T>(intervalInMs) {
override fun onRun() { override fun onRun() {
runnable.invoke() runnable.invoke()
} }
} }
fun <T:PluginBase> T.repeatTask( fun <T : PluginBase> T.repeatTask(
intervalInMs: Int, runnable: () -> Unit intervalInMs: Int, runnable: () -> Unit
):RepeatTask<T>{ ): RepeatTask<T> {
return AnonymousRepeatTask<T>( return AnonymousRepeatTask<T>(
intervalInMs, runnable intervalInMs, runnable
) )
} }
fun <T> repeatTask(){
}
class X: PluginBase() {
override fun onLoad() {
//kotlin
this.repeatTask(5){
}
//java1
RepeatTask.of<X>(5){
}
//java2
class Xtask:RepeatTask<X>(5){
override fun onRun() {
}
}
}
}

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 package net.mamoe.mirai.console.utils
import kotlinx.coroutines.* import kotlinx.coroutines.*

View File

@ -14,15 +14,14 @@ import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.SimpleLogger.LogPriority import net.mamoe.mirai.utils.SimpleLogger.LogPriority
/** /**
* 只需要实现一个这个 传入MiraiConsole 就可以绑定UI层与Console层 * 只需要实现一个这个传入 MiraiConsole 就可以绑定 UI 层与 Console
* 注意线程 * 需要保证线程安全
*/ */
interface MiraiConsoleUI { interface MiraiConsoleUI {
/** /**
* UI层展示一条log * UI 层展示一条 log
* *
* identitylog所属的screen, Main=0; Bot=Bot.uin * identitylog 所属的 screen, Main=0; Bot=Bot.uin
*/ */
fun pushLog( fun pushLog(
identity: Long, identity: Long,
@ -37,14 +36,14 @@ interface MiraiConsoleUI {
) )
/** /**
* UI层准备接受新增的一个BOT * UI 层准备接受新增的一个BOT
*/ */
fun prePushBot( fun prePushBot(
identity: Long identity: Long
) )
/** /**
* UI层接受一个新的bot * UI 层接受一个新的bot
* */ * */
fun pushBot( fun pushBot(
bot: Bot bot: Bot
@ -58,14 +57,13 @@ interface MiraiConsoleUI {
) )
/** /**
* 让UI层提供一个Input * UI 层提供一个输入, 相当于 [readLine]
* 这个Input 等于 Command
*/ */
suspend fun requestInput(hint:String): String suspend fun requestInput(hint: String): String
/** /**
* UI层更新BOT管理员的数据 * UI 层更新 bot 管理员的数据
*/ */
fun pushBotAdminStatus( fun pushBotAdminStatus(
identity: Long, identity: Long,
@ -73,7 +71,7 @@ interface MiraiConsoleUI {
) )
/** /**
* UI层创建一个LoginSolver * UI 层创建一个 [LoginSolver]
*/ */
fun createLoginSolver(): LoginSolver fun createLoginSolver(): LoginSolver