Merge remote-tracking branch 'origin/master'

# Conflicts:
#	mirai-console/src/main/kotlin/net/mamoe/mirai/console/core/MiraiCoreLoader.kt
This commit is contained in:
jiahua.liu 2020-03-05 16:57:22 +08:00
commit 34ad7ac194
17 changed files with 621 additions and 627 deletions

View File

@ -2,7 +2,7 @@
kotlin.code.style=official
# config
miraiVersion=0.24.1
mirai_console_version=0.3.1
miraiConsoleVersion=0.3.1
kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true
# kotlin

View File

@ -1,5 +1,5 @@
#Thu Feb 27 13:09:44 CST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip
#Wed Mar 04 22:27:09 CST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-all.zip
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStorePath=wrapper/dists

View File

@ -13,6 +13,13 @@ javafx {
apply(plugin = "com.github.johnrengelman.shadow")
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
manifest {
attributes["Main-Class"] = "net.mamoe.mirai.console.graphical.MiraiGraphicalLoader"
}
}
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
@ -41,4 +48,4 @@ dependencies {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
}

View File

@ -17,8 +17,11 @@ import tornadofx.App
import tornadofx.find
import tornadofx.launch
fun main(args: Array<String>) {
launch<MiraiGraphicalUI>(args)
object MiraiGraphicalLoader {
@JvmStatic
fun main(args: Array<String>) {
launch<MiraiGraphicalUI>(args)
}
}
class MiraiGraphicalUI : App(Decorator::class, PrimaryStyleSheet::class) {

View File

@ -3,7 +3,6 @@ package net.mamoe.mirai.console.graphical.controller
import javafx.application.Platform
import javafx.collections.ObservableList
import javafx.stage.Modality
import kotlinx.io.core.IoBuffer
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.graphical.model.BotModel
@ -11,6 +10,7 @@ import net.mamoe.mirai.console.graphical.model.ConsoleInfo
import net.mamoe.mirai.console.graphical.model.PluginModel
import net.mamoe.mirai.console.graphical.model.VerificationCodeModel
import net.mamoe.mirai.console.graphical.view.VerificationCodeFragment
import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.utils.MiraiConsoleUI
import net.mamoe.mirai.utils.LoginSolver
import tornadofx.*
@ -75,7 +75,7 @@ class MiraiGraphicalUIController : Controller(), MiraiConsoleUI {
override fun createLoginSolver(): LoginSolver = loginSolver
private fun getPluginsFromConsole(): ObservableList<PluginModel> =
MiraiConsole.pluginManager.getAllPluginDescriptions().map(::PluginModel).toObservable()
PluginManager.getAllPluginDescriptions().map(::PluginModel).toObservable()
}

View File

@ -1,3 +1,5 @@
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console
import com.googlecode.lanterna.SGR
@ -8,7 +10,6 @@ import com.googlecode.lanterna.input.KeyStroke
import com.googlecode.lanterna.input.KeyType
import com.googlecode.lanterna.terminal.DefaultTerminalFactory
import com.googlecode.lanterna.terminal.Terminal
import com.googlecode.lanterna.terminal.TerminalResizeListener
import com.googlecode.lanterna.terminal.swing.SwingTerminal
import com.googlecode.lanterna.terminal.swing.SwingTerminalFrame
import kotlinx.coroutines.*
@ -16,7 +17,6 @@ import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.coroutines.io.close
import kotlinx.coroutines.io.jvm.nio.copyTo
import kotlinx.coroutines.io.reader
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.use
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsoleTerminalUI.LoggerDrawer.cleanPage
@ -51,49 +51,26 @@ import kotlin.system.exitProcess
*
*/
fun String.actualLength(): Int {
var x = 0
this.forEach {
if (it.isChineseChar()) {
x += 2
} else {
x += 1
}
}
return x
}
val String.actualLength: Int get() = this.sumBy { if (it.isChineseChar) 2 else 1 }
fun String.getSubStringIndexByActualLength(widthMax: Int): Int {
var index = 0
var currentLength = 0
this.forEach {
if (it.isChineseChar()) {
currentLength += 2
} else {
currentLength += 1
}
if (currentLength > widthMax) {
return@forEach
}
++index
}
if (index < 2) {
index = 2
}
return index
return this.sumBy { if (it.isChineseChar) 2 else 1 }.coerceAtMost(widthMax).coerceAtLeast(2)
}
fun Char.isChineseChar(): Boolean {
return this.toString().isChineseChar()
}
val Char.isChineseChar: Boolean
get() {
return this.toString().isChineseChar
}
fun String.isChineseChar(): Boolean {
return this.matches(Regex("[\u4e00-\u9fa5]"))
}
val String.isChineseChar: Boolean
get() {
return this.matches(Regex("[\u4e00-\u9fa5]"))
}
object MiraiConsoleTerminalUI : MiraiConsoleUI {
val cacheLogSize = 50
const val cacheLogSize = 50
var mainTitle = "Mirai Console v0.01 Core v0.15"
override fun pushVersion(consoleVersion: String, consoleBuild: String, coreVersion: String) {
@ -122,7 +99,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
@Volatile
var requesting = false
var requestResult: String? = null
private var requestResult: String? = null
override suspend fun requestInput(): String {
requesting = true
while (requesting) {
@ -132,7 +109,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
}
suspend fun provideInput(input: String) {
private suspend fun provideInput(input: String) {
if (requesting) {
requestResult = input
requesting = false
@ -164,17 +141,13 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
}
}
var toLog = ""
lateinit var toLog: String
tempFile.inputStream().use {
val img = ImageIO.read(it)
if (img == null) {
toLog += "无法创建字符图片. 请查看文件\n"
} else {
toLog += img.createCharImg((terminal.terminalSize.columns / 1.5).toInt())
}
toLog += img?.createCharImg((terminal.terminalSize.columns / 1.5).toInt()) ?: "无法创建字符图片. 请查看文件\n"
}
pushLog(0, "$toLog[Login Solver]请输验证码. ${tempFile.absolutePath}")
return requestInput()!!
return requestInput()
.takeUnless { it.isEmpty() || it.length != 4 }
.also {
pushLog(0, "[Login Solver]正在提交[$it]中...")
@ -206,11 +179,11 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
}
}
val log = ConcurrentHashMap<Long, LimitLinkedQueue<String>>().also {
private val log = ConcurrentHashMap<Long, LimitLinkedQueue<String>>().also {
it[0L] = LimitLinkedQueue(cacheLogSize)
}
val botAdminCount = ConcurrentHashMap<Long, Int>()
private val botAdminCount = ConcurrentHashMap<Long, Int>()
private val screens = mutableListOf(0L)
private var currentScreenId = 0
@ -219,7 +192,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
lateinit var terminal: Terminal
lateinit var textGraphics: TextGraphics
var hasStart = false
private var hasStart = false
private lateinit var internalPrinter: PrintStream
fun start() {
if (hasStart) {
@ -277,11 +250,12 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
*/
var lastJob: Job? = null
terminal.addResizeListener(TerminalResizeListener { terminal1: Terminal, newSize: TerminalSize ->
terminal.addResizeListener { _: Terminal, _: TerminalSize ->
lastJob = GlobalScope.launch {
try {
delay(300)
if (lastJob == coroutineContext[Job]) {
@Suppress("BlockingMethodInNonBlockingContext")
terminal.clearScreen()
//inited = false
update()
@ -292,7 +266,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
pushLog(0, "[UI ERROR] ${e.message}")
}
}
})
}
if (terminal !is SwingTerminalFrame) {
System.setOut(PrintStream(object : OutputStream() {
@ -322,7 +296,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
thread {
while (true) {
try {
var keyStroke: KeyStroke = terminal.readInput()
val keyStroke: KeyStroke = terminal.readInput()
when (keyStroke.keyType) {
KeyType.ArrowLeft -> {
@ -411,7 +385,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.GREEN
textGraphics.putString((width - mainTitle.actualLength()) / 2, 1, mainTitle, SGR.BOLD)
textGraphics.putString((width - mainTitle.actualLength) / 2, 1, mainTitle, SGR.BOLD)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
textGraphics.putString(2, 3, "-".repeat(width - 4))
@ -426,15 +400,15 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
val leftName =
getScreenName(getLeftScreenId())
// clearRows(2)
textGraphics.putString((width - title.actualLength()) / 2 - "$leftName << ".length, 2, "$leftName << ")
textGraphics.putString((width - title.actualLength) / 2 - "$leftName << ".length, 2, "$leftName << ")
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.YELLOW
textGraphics.putString((width - title.actualLength()) / 2, 2, title, SGR.BOLD)
textGraphics.putString((width - title.actualLength) / 2, 2, title, SGR.BOLD)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
val rightName =
getScreenName(getRightScreenId())
textGraphics.putString((width + title.actualLength()) / 2 + 1, 2, ">> $rightName")
textGraphics.putString((width + title.actualLength) / 2 + 1, 2, ">> $rightName")
}
fun drawMainFrame(
@ -447,7 +421,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
clearRows(4)
textGraphics.putString(2, 4, "|Online Bots: $onlineBotCount")
textGraphics.putString(
width - 2 - "Powered By Mamoe Technologies|".actualLength(),
width - 2 - "Powered By Mamoe Technologies|".actualLength,
4,
"Powered By Mamoe Technologies|"
)
@ -463,7 +437,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
clearRows(4)
textGraphics.putString(2, 4, "|Admins: $adminCount")
textGraphics.putString(width - 2 - "Add admins via commands|".actualLength(), 4, "Add admins via commands|")
textGraphics.putString(width - 2 - "Add admins via commands|".actualLength, 4, "Add admins via commands|")
}
@ -472,7 +446,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
fun drawLog(string: String, flush: Boolean = true) {
val maxHeight = terminal.terminalSize.rows - 4
val heightNeed = (string.actualLength() / (terminal.terminalSize.columns - 6)) + 1
val heightNeed = (string.actualLength / (terminal.terminalSize.columns - 6)) + 1
if (heightNeed - 1 > maxHeight) {
pushLog(0, "[UI ERROR]: 您的屏幕太小, 有一条超长LOG无法显示")
return//拒绝打印
@ -481,7 +455,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
cleanPage()//翻页
}
if (string.contains("\n")) {
string.split("\n").forEach {
string.split("\n").forEach { _ ->
drawLog(string, false)
}
} else {
@ -491,7 +465,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
if (x == "") {
break
}
val toWrite = if (x.actualLength() > width) {
val toWrite = if (x.actualLength > width) {
val index = x.getSubStringIndexByActualLength(width)
x.substring(0, index).also {
x = if (index < x.length) {
@ -539,7 +513,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
var vara = 0
val toPrint = mutableListOf<String>()
toDraw.forEach {
val heightNeed = (it.actualLength() / (terminal.terminalSize.columns - 6)) + 1
val heightNeed = (it.actualLength / (terminal.terminalSize.columns - 6)) + 1
vara += heightNeed
if (currentHeight + vara < terminal.terminalSize.rows - 4) {
logsToDraw++
@ -558,8 +532,8 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
}
var commandBuilder = StringBuilder()
fun redrawCommand() {
private var commandBuilder = StringBuilder()
private fun redrawCommand() {
val height = terminal.terminalSize.rows
val width = terminal.terminalSize.columns
clearRows(height - 3)
@ -594,7 +568,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
}
private fun deleteCommandChar() {
if (!commandBuilder.isEmpty()) {
if (commandBuilder.isNotEmpty()) {
commandBuilder = StringBuilder(commandBuilder.toString().substring(0, commandBuilder.length - 1))
}
val height = terminal.terminalSize.rows
@ -606,7 +580,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
}
var lastEmpty: Job? = null
private var lastEmpty: Job? = null
private fun emptyCommand() {
commandBuilder = StringBuilder()
if (terminal is SwingTerminal) {
@ -617,7 +591,9 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
try {
delay(100)
if (lastEmpty == coroutineContext[Job]) {
terminal.clearScreen()
withContext(Dispatchers.IO) {
terminal.clearScreen()
}
//inited = false
update()
redrawCommand()
@ -630,7 +606,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
}
}
fun update() {
private fun update() {
when (screens[currentScreenId]) {
0L -> {
drawMainFrame(screens.size - 1)
@ -658,7 +634,7 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI {
class LimitLinkedQueue<T>(
val limit: Int = 50
private val limit: Int = 50
) : ConcurrentLinkedDeque<T>() {
override fun push(e: T) {
if (size >= limit) {

View File

@ -26,7 +26,7 @@ fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$v
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar> {
manifest {
attributes["Main-Class"] = "net.mamoe.mirai.console.pure.MiraiConsolePureLoader"
}
@ -35,11 +35,19 @@ tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
val miraiVersion: String by rootProject.ext
kotlin {
sourceSets {
all {
languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
languageSettings.useExperimentalAnnotation("kotlin.OptIn")
}
}
}
dependencies {
compileOnly("net.mamoe:mirai-core-jvm:$miraiVersion")
compileOnly("net.mamoe:mirai-core-qqandroid-jvm:$miraiVersion")
api(kotlin("serialization"))
// compileOnly("net.mamoe:mirai-core-qqandroid-jvm:$miraiVersion")
api(group = "com.alibaba", name = "fastjson", version = "1.2.62")
@ -50,32 +58,25 @@ dependencies {
api(kotlin("stdlib", kotlinVersion))
api(kotlin("serialization", kotlinVersion))
api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlinx("coroutines-core", coroutinesVersion))
api(ktor("client-core-jvm", ktorVersion))
api(kotlinx("serialization-runtime", serializationVersion))
api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlin("reflect", kotlinVersion))
api(kotlinx("coroutines-io-jvm", coroutinesIoVersion))
api(kotlinx("io-jvm", coroutinesIoVersion))
api(kotlinx("coroutines-core", coroutinesVersion))
api(kotlinx("serialization-runtime", serializationVersion))
api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
api("org.bouncycastle:bcprov-jdk15on:1.64")
api(kotlin("reflect", kotlinVersion))
api(kotlin("serialization", kotlinVersion))
api(kotlinx("coroutines-core-common", coroutinesVersion))
api(kotlinx("serialization-runtime-common", serializationVersion))
api(ktor("http-cio", ktorVersion))
api(ktor("http", ktorVersion))
api(ktor("http-jvm", ktorVersion))
api(ktor("io-jvm", ktorVersion))
api(ktor("client-core-jvm", ktorVersion))
api(ktor("client-cio", ktorVersion))
api(ktor("client-core", ktorVersion))
api(ktor("network", ktorVersion))
}
val mirai_console_version: String by project.ext
version = mirai_console_version
val miraiConsoleVersion: String by project.ext
version = miraiConsoleVersion
description = "Console with plugin support for mirai"
bintray {
@ -118,7 +119,7 @@ publishing {
groupId = rootProject.group.toString()
artifactId = "mirai-console"
version = mirai_console_version
version = miraiConsoleVersion
pom.withXml {
val root = asNode()

View File

@ -19,11 +19,8 @@ import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.command.DefaultCommands
import net.mamoe.mirai.console.core.MiraiCoreLoader
import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.plugins.loadAsConfig
import net.mamoe.mirai.console.plugins.withDefaultWrite
import net.mamoe.mirai.console.utils.MiraiConsoleUI
import net.mamoe.mirai.utils.cryptor.ECDH
import java.io.File
object MiraiConsole {
@ -40,20 +37,10 @@ object MiraiConsole {
* */
val bots get() = Bot.instances
fun getBotByUIN(uin: Long): Bot? {
bots.forEach {
if (it.get()?.uin == uin) {
return it.get()
}
}
return null
fun getBotOrNull(uin: Long): Bot? {
return bots.asSequence().mapNotNull { it.get() }.firstOrNull { it.uin == uin }
}
/**
* PluginManager
*/
val pluginManager: PluginManager get() = PluginManager
/**
* 与前端交互所使用的Logger
*/
@ -91,7 +78,7 @@ object MiraiConsole {
logger("Mirai为开源项目请自觉遵守开源项目协议")
logger("Powered by Mamoe Technologies and contributors")
MiraiCoreLoader()
MiraiCoreLoader.loadCore()
/* 加载ECDH */
try {
@ -103,7 +90,7 @@ object MiraiConsole {
/* 依次启用功能 */
DefaultCommands()
pluginManager.loadPlugins()
PluginManager.loadPlugins()
CommandProcessor.start()
/* 通知启动完成 */
@ -151,6 +138,7 @@ object MiraiConsole {
fun runConsoleCommandBlocking(command: String) = runBlocking { runConsoleCommand(command) }
@Suppress("unused")
fun runCommandBlocking(sender: CommandSender, command: String) = runBlocking { runCommand(sender, command) }
private suspend fun processNextCommandLine() {
@ -180,6 +168,12 @@ object MiraiConsole {
frontEnd.pushLog(identity, "$identityStr: $any")
}
}
operator fun invoke(identityStr: String, identity: Long, e: Exception? = null) {
if (e != null) {
frontEnd.pushLog(identity, "$identityStr: ${e.stackTrace}")
}
}
}
}

View File

@ -7,82 +7,18 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.console.command
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.MiraiExperimentalAPI
object CommandManager {
private val registeredCommand: MutableMap<String, Command> = mutableMapOf()
fun getCommands(): Collection<Command> {
return registeredCommand.values
}
fun register(command: Command) {
val allNames = mutableListOf(command.name).also { it.addAll(command.alias) }
allNames.forEach {
if (registeredCommand.containsKey(it)) {
error("Command Name(or Alias) $it is already registered, consider if same functional plugin was installed")
}
}
allNames.forEach {
registeredCommand[it] = command
}
}
fun unregister(command: Command) {
val allNames = mutableListOf<String>(command.name).also { it.addAll(command.alias) }
allNames.forEach {
registeredCommand.remove(it)
}
}
fun unregister(commandName: String) {
registeredCommand.remove(commandName)
}
/*
* Index: MiraiConsole
* */
internal suspend fun runCommand(sender: CommandSender, fullCommand: String): Boolean {
val blocks = fullCommand.split(" ")
val commandHead = blocks[0].replace("/", "")
if (!registeredCommand.containsKey(commandHead)) {
return false
}
val args = blocks.subList(1, blocks.size)
registeredCommand[commandHead]?.run {
try {
if (onCommand(
sender,
blocks.subList(1, blocks.size)
)
) {
PluginManager.onCommand(this, sender, args)
} else {
sender.sendMessage(this.usage)
}
} catch (e: Exception) {
sender.sendMessage("在运行指令时出现了未知错误")
e.printStackTrace()
} finally {
(sender as CommandSenderImpl).flushMessage()
}
}
return true
}
}
interface CommandSender {
/**
@ -92,7 +28,7 @@ interface CommandSender {
suspend fun sendMessage(message: String)
/**
* 写入要发送的内容 所有内容最后会被以一条发出, 不管成功与否
* 写入要发送的内容 所有内容最后会被以一条发出
*/
fun appendMessage(message: String)
@ -139,21 +75,6 @@ open class ContactCommandSender(val contact: Contact) : CommandSenderImpl() {
}
}
/**
* 弃用中
* */
class GroupCommandSender(val toQuote: GroupMessage, contact: Contact) : ContactCommandSender(contact) {
@MiraiExperimentalAPI
override suspend fun sendMessage(message: String) {
toQuote.quoteReply(message)
}
@MiraiExperimentalAPI
override suspend fun sendMessage(messageChain: MessageChain) {
toQuote.quoteReply(messageChain)
}
}
interface Command {
val name: String
val alias: List<String>
@ -161,9 +82,18 @@ interface Command {
val usage: String
suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean
fun register()
}
inline fun Command.register() = CommandManager.register(this)
fun registerCommand(builder: CommandBuilder.() -> Unit): Command {
return CommandBuilder().apply(builder).register()
}
// for java
@Suppress("unused")
abstract class BlockingCommand(
override val name: String,
override val alias: List<String> = listOf(),
@ -172,7 +102,7 @@ abstract class BlockingCommand(
) : Command {
/**
* 最高优先级监听器
* 如果 return `false` 这次指令不会被 [PluginBase] 的全局 onCommand 监听器监听
* 如果 return `false`, 这次指令不会被 [PluginBase] 的全局 onCommand 监听器监听
* */
final override suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean {
return withContext(Dispatchers.IO) {
@ -181,10 +111,6 @@ abstract class BlockingCommand(
}
abstract fun onCommandBlocking(sender: CommandSender, args: List<String>): Boolean
override fun register() {
CommandManager.register(this)
}
}
class AnonymousCommand internal constructor(
@ -197,10 +123,6 @@ class AnonymousCommand internal constructor(
override suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean {
return onCommand.invoke(sender, args)
}
override fun register() {
CommandManager.register(this)
}
}
class CommandBuilder internal constructor() {
@ -208,30 +130,25 @@ class CommandBuilder internal constructor() {
var alias: List<String>? = null
var description: String = ""
var usage: String = "use /help for help"
var onCommand: (suspend CommandSender.(args: List<String>) -> Boolean)? = null
internal var onCommand: (suspend CommandSender.(args: List<String>) -> Boolean)? = null
fun onCommand(commandProcess: suspend CommandSender.(args: List<String>) -> Boolean) {
onCommand = commandProcess
}
}
fun register(): Command {
if (name == null || onCommand == null) {
error("CommandBuilder not complete")
}
if (alias == null) {
alias = listOf()
}
return AnonymousCommand(
name!!,
alias!!,
description,
usage,
onCommand!!
).also { it.register() }
private fun CommandBuilder.register(): AnonymousCommand {
if (name == null || onCommand == null) {
error("CommandBuilder not complete")
}
}
fun registerCommand(builder: CommandBuilder.() -> Unit): Command {
return CommandBuilder().apply(builder).register()
}
if (alias == null) {
alias = listOf()
}
return AnonymousCommand(
name!!,
alias!!,
description,
usage,
onCommand!!
).also { it.register() }
}

View File

@ -0,0 +1,70 @@
/*
* 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
import net.mamoe.mirai.console.plugins.PluginManager
object CommandManager {
private val registeredCommand: MutableMap<String, Command> = mutableMapOf()
val commands: Collection<Command> get() = registeredCommand.values
fun register(command: Command) {
val allNames = mutableListOf(command.name).also { it.addAll(command.alias) }
allNames.forEach {
if (registeredCommand.containsKey(it)) {
error("Command Name(or Alias) $it is already registered, consider if same functional plugin was installed")
}
}
allNames.forEach {
registeredCommand[it] = command
}
}
fun unregister(command: Command) {
(command.alias.asSequence() + command.name).forEach {
registeredCommand.remove(it)
} // label compilation failed
}
fun unregister(commandName: String): Boolean {
return registeredCommand.remove(commandName) != null
}
/*
* Index: MiraiConsole
*/
internal suspend fun runCommand(sender: CommandSender, fullCommand: String): Boolean {
val blocks = fullCommand.split(" ")
val commandHead = blocks[0].replace("/", "")
if (!registeredCommand.containsKey(commandHead)) {
return false
}
val args = blocks.drop(1)
registeredCommand[commandHead]?.run {
try {
if (onCommand(sender, blocks.drop(1))) {
PluginManager.onCommand(this, sender, args)
} else {
sender.sendMessage(this.usage)
}
} catch (e: Exception) {
sender.sendMessage("在运行指令时出现了未知错误")
e.printStackTrace()
} finally {
(sender as CommandSenderImpl).flushMessage()
}
}
return true
}
}

View File

@ -14,7 +14,7 @@ import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.plugins.PluginManager
import net.mamoe.mirai.console.utils.addManager
import net.mamoe.mirai.console.utils.checkManager
import net.mamoe.mirai.console.utils.getManagers
import net.mamoe.mirai.console.utils.managers
import net.mamoe.mirai.console.utils.removeManager
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.subscribeMessages
@ -49,7 +49,7 @@ object DefaultCommands {
MiraiConsole.logger("[Bot Manager]", 0, it[1] + " 不是一个Bot的ID")
return@onCommand false
}
val bot = MiraiConsole.getBotByUIN(botId)
val bot = MiraiConsole.getBotOrNull(botId)
if (bot == null) {
MiraiConsole.logger("[Bot Manager]", 0, "$botId 没有在Console中登陆")
return@onCommand false
@ -88,7 +88,7 @@ object DefaultCommands {
MiraiConsole.logger("[Bot Manager]", 0, it[2] + "移除成功")
}
"list" -> {
bot.getManagers().forEach {
bot.managers.forEach {
MiraiConsole.logger("[Bot Manager]", 0, " -> $it")
}
}
@ -106,7 +106,7 @@ object DefaultCommands {
return@onCommand false
}
if (it.size < 2) {
MiraiConsole.logger("\"/login qqnumber qqpassword \" to login a bot")
MiraiConsole.logger("\"/login qq password \" to login a bot")
MiraiConsole.logger("\"/login qq号 qq密码 \" 来登录一个BOT")
return@onCommand false
}
@ -138,11 +138,11 @@ object DefaultCommands {
}
bot.login()
bot.subscribeMessages {
this.startsWith("/") {
startsWith("/") { message ->
if (bot.checkManager(this.sender.id)) {
val sender = ContactCommandSender(this.subject)
MiraiConsole.CommandProcessor.runCommand(
sender, it
sender, message
)
}
}
@ -159,13 +159,13 @@ object DefaultCommands {
registerCommand {
name = "status"
description = "获取状态"
onCommand {
when (it.size) {
onCommand { args ->
when (args.size) {
0 -> {
sendMessage("当前有" + MiraiConsole.bots.size + "个BOT在线")
}
1 -> {
val bot = it[0]
val bot = args[0]
var find = false
MiraiConsole.bots.forEach {
if (it.get()?.uin.toString().contains(bot)) {
@ -196,13 +196,13 @@ object DefaultCommands {
return@onCommand false
}
val bot: Bot? = if (it.size == 2) {
if (MiraiConsole.bots.size == 0) {
if (MiraiConsole.bots.isEmpty()) {
MiraiConsole.logger("还没有BOT登录")
return@onCommand false
}
MiraiConsole.bots[0].get()
} else {
MiraiConsole.getBotByUIN(it[0].toLong())
MiraiConsole.getBotOrNull(it[0].toLong())
}
if (bot == null) {
MiraiConsole.logger("没有找到BOT")
@ -228,11 +228,11 @@ object DefaultCommands {
alias = listOf("plugin")
description = "获取插件列表"
onCommand {
PluginManager.getAllPluginDescriptions().let {
it.forEach {
PluginManager.getAllPluginDescriptions().let { descriptions ->
descriptions.forEach {
appendMessage("\t" + it.name + " v" + it.version + " by" + it.author + " " + it.info)
}
appendMessage("加载了" + it.size + "个插件")
appendMessage("加载了" + descriptions.size + "个插件")
true
}
}
@ -243,10 +243,10 @@ object DefaultCommands {
alias = listOf("commands", "help", "helps")
description = "获取指令列表"
onCommand {
CommandManager.getCommands().let {
CommandManager.commands.let { commands ->
var size = 0
appendMessage("")//\n
it.toSet().forEach {
commands.forEach {
++size
appendMessage("-> " + it.name + " :" + it.description)
}

View File

@ -1,9 +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("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.console.core
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.request.get
import io.ktor.client.request.url
import io.ktor.client.statement.HttpResponse
import io.ktor.http.URLProtocol
import io.ktor.utils.io.ByteReadChannel
@ -12,17 +22,16 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import net.mamoe.mirai.console.MiraiConsole
import java.io.File
import java.net.URLClassLoader
import kotlin.math.pow
import kotlin.system.exitProcess
@Suppress("EXPERIMENTAL_API_USAGE")
val Http: HttpClient get() = HttpClient(CIO)
val Http: HttpClient
get() = HttpClient(CIO)
object MiraiCoreLoader {
val coresPath by lazy {
private val coresPath by lazy {
File(System.getProperty("user.dir") + "/core/").also {
if (!it.exists()) {
it.mkdirs()
@ -30,7 +39,7 @@ object MiraiCoreLoader {
}
}
private fun getProtocolLib():File?{
private fun getProtocolLib(): File? {
this.coresPath.listFiles()?.forEach { file ->
if (file != null && file.extension == "jar" && file.name.contains("qqandroid")) {
return file
@ -39,7 +48,7 @@ object MiraiCoreLoader {
return null
}
private fun getCore():File?{
private fun getCore(): File? {
this.coresPath.listFiles()?.forEach { file ->
if (file != null && file.extension == "jar" && file.name.contains("core") && (!file.name.contains("qqandroid"))) {
return file
@ -49,14 +58,14 @@ object MiraiCoreLoader {
}
operator fun invoke():String{
fun loadCore(): String {
MiraiConsole.logger("Fetching Newest Core Version .. ")
val newest = runBlocking {
getNewestVersion()
}
val current = getCurrentVersion()
MiraiConsole.logger("Local Version: $current | Newest Version: $newest")
if(current != newest){
if (current != newest) {
MiraiConsole.logger("Updating from V$current -> V$newest, this is a force update")
cleanCoreAndLib()
runBlocking {
@ -72,26 +81,26 @@ object MiraiCoreLoader {
/**
* 使用Protocol Lib判断最新版本
* 判断最新版本
* */
private suspend fun getNewestVersion(): String {
try {
return """>([0-9])*\.([0-9])*\.([0-9])*/""".toRegex().findAll(
Http.get<String> {
url {
protocol = URLProtocol.HTTPS
host = "jcenter.bintray.com"
path("net/mamoe/mirai-core-qqandroid-jvm/")
Http.get<String> {
url {
protocol = URLProtocol.HTTPS
host = "jcenter.bintray.com"
path("net/mamoe/mirai-core-qqandroid-jvm/")
}
}
}
).asSequence()
).asSequence()
.map { it.value.drop(1).dropLast(1) }
.maxBy {
it.split('.').foldRightIndexed(0) { index: Int, s: String, acc: Int ->
acc + 100.0.pow(index).toInt() + (s.toIntOrNull() ?: 0)
}
}!!
}catch (e:Exception){
} catch (e: Exception) {
MiraiConsole.logger("Failed to fetch newest Core version, please seek for help")
e.printStackTrace()
MiraiConsole.logger("Failed to fetch newest Core version, please seek for help")
@ -100,21 +109,21 @@ object MiraiCoreLoader {
}
/**
* 使用Protocol Lib判断当前版本
* 如果没有 会返回0.0.0
* */
private fun getCurrentVersion():String{
val file = getProtocolLib()
if(file == null || getCore() == null)return "0.0.0"
* 判断当前版本
* 默认返回 "0.0.0"
*/
private fun getCurrentVersion(): String {
val file = getProtocolLib()
if (file == null || getCore() == null) return "0.0.0"
val numberVersion = """([0-9])*\.([0-9])*\.([0-9])*""".toRegex().find(file.name)?.value
if(numberVersion != null) {
if (numberVersion != null) {
return numberVersion + file.name.substringAfter(numberVersion).substringBefore(".jar")
}
return "0.0.0"
}
private fun cleanCoreAndLib(){
private fun cleanCoreAndLib() {
this.coresPath.listFiles()?.forEach {
if (it != null && it.extension == "jar") {
it.delete()
@ -123,34 +132,38 @@ object MiraiCoreLoader {
}
@Suppress("SpellCheckingInspection")
private object Links {
internal const val libJcenter =
"https://jcenter.bintray.com/net/mamoe/mirai-core-qqandroid-jvm/{version}/:mirai-core-qqandroid-jvm-{version}.jar"
internal const val libAliyun =
"https://maven.aliyun.com/nexus/content/repositories/jcenter/net/mamoe/mirai-core-qqandroid-jvm/{version}/mirai-core-qqandroid-jvm-{version}.jar"
val lib_jcenter = "https://jcenter.bintray.com/net/mamoe/mirai-core-qqandroid-jvm/{version}/:mirai-core-qqandroid-jvm-{version}.jar"
val lib_aliyun = "https://maven.aliyun.com/nexus/content/repositories/jcenter/net/mamoe/mirai-core-qqandroid-jvm/{version}/mirai-core-qqandroid-jvm-{version}.jar"
internal const val coreJcenter =
"https://jcenter.bintray.com/net/mamoe/mirai-core-jvm/{version}/:mirai-core-jvm-{version}.jar"
internal const val coreAliyun =
"https://maven.aliyun.com/nexus/content/repositories/jcenter/net/mamoe/mirai-core-jvm/{version}/mirai-core-jvm-{version}.jar"
}
val core_jcenter = "https://jcenter.bintray.com/net/mamoe/mirai-core-jvm/{version}/:mirai-core-jvm-{version}.jar"
val core_aliyun = "https://maven.aliyun.com/nexus/content/repositories/jcenter/net/mamoe/mirai-core-jvm/{version}/mirai-core-jvm-{version}.jar"
private suspend fun downloadCoreAndLib(version:String){
var fileStream = File(coresPath.absolutePath + "/" + "mirai-core-qqandroid-jvm-$version.jar").also{
private suspend fun downloadCoreAndLib(version: String) {
var fileStream = File(coresPath.absolutePath + "/" + "mirai-core-qqandroid-jvm-$version.jar").also {
withContext(Dispatchers.IO) {
it.createNewFile()
}
}.outputStream()
suspend fun downloadRequest(url:String, version:String):ByteReadChannel{
return Http.get<HttpResponse>(){
this.url(url.replace("{version}",version))
}.content
suspend fun downloadRequest(url: String, version: String): ByteReadChannel {
return Http.get<HttpResponse>(url.replace("{version}", version)).content
}
var stream = try{
var stream = kotlin.runCatching {
MiraiConsole.logger("Downloading newest Protocol lib from Aliyun")
downloadRequest(lib_aliyun,version)
}catch (ignored:Exception){
try{
downloadRequest(Links.libAliyun, version)
}.getOrElse {
kotlin.runCatching {
MiraiConsole.logger("Downloading newest Protocol lib from JCenter")
downloadRequest(lib_jcenter,version)
}catch (e:Exception){
downloadRequest(Links.libJcenter, version)
}.getOrElse { e ->
MiraiConsole.logger("Failed to download Protocol lib, please seeking for help")
e.printStackTrace()
MiraiConsole.logger("Failed to download Protocol lib, please seeking for help")
@ -163,21 +176,21 @@ object MiraiCoreLoader {
fileStream.flush()
}
fileStream = File(coresPath.absolutePath + "/" + "mirai-core-jvm-$version.jar").also{
fileStream = File(coresPath.absolutePath + "/" + "mirai-core-jvm-$version.jar").also {
withContext(Dispatchers.IO) {
it.createNewFile()
}
}.outputStream()
stream = try{
stream = try {
MiraiConsole.logger("Downloading newest Mirai Core from Aliyun")
downloadRequest(core_aliyun,version)
}catch (ignored:Exception){
try{
downloadRequest(Links.coreAliyun, version)
} catch (ignored: Exception) {
try {
MiraiConsole.logger("Downloading newest Mirai Core from JCenter")
downloadRequest(core_jcenter,version)
}catch (e:Exception){
downloadRequest(Links.coreJcenter, version)
} catch (e: Exception) {
MiraiConsole.logger("Failed to download Mirai Core, please seeking for help")
e.printStackTrace()
MiraiConsole.logger("Failed to download Mirai Core, please seeking for help")
@ -192,25 +205,30 @@ object MiraiCoreLoader {
}
private fun loadCoreAndLib(){
private fun loadCoreAndLib() {
try {
MiraiConsole.logger("Core:" + getCore())
MiraiConsole.logger("Protocol:" + getProtocolLib())
val coreFile = getCore()!!
val protocolFile = getProtocolLib()!!
MiraiConsole.logger("Core: $coreFile")
MiraiConsole.logger("Protocol: $protocolFile")
MiraiCoreClassLoader(
(getCore()!!),
val classloader = URLClassLoader(
arrayOf(coreFile.toURI().toURL(), protocolFile.toURI().toURL()),
this.javaClass.classLoader
)
.loadClass("net.mamoe.mirai.BotImpl")
ClassLoader.getSystemClassLoader()
// this.javaClass.classLoader.
println(classloader.loadClass("net.mamoe.mirai.BotFactory"))
println(classloader.loadClass("net.mamoe.mirai.qqandroid.QQAndroid"))
println(classloader.loadClass("net.mamoe.mirai.utils.cryptor.ECDHJvmKt"))
val a = classloader.loadClass("net.mamoe.mirai.qqandroid.QQAndroid").kotlin.objectInstance!!
println(a::class.java)
MiraiCoreClassLoader(
(getProtocolLib()!!),
this.javaClass.classLoader
)
.loadClass("net.mamoe.mirai.qqandroid.QQAndroid")
println(Class.forName("net.mamoe.mirai.qqandroid.QQAndroid"))
} catch (e: ClassNotFoundException) {
MiraiConsole.logger("Failed to load core, please seek for help")
@ -223,8 +241,5 @@ object MiraiCoreLoader {
}
internal class MiraiCoreClassLoader(file: File, parent: ClassLoader) :
URLClassLoader(arrayOf(file.toURI().toURL()), parent)

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.plugins
import com.alibaba.fastjson.JSON
@ -17,12 +19,14 @@ import com.moandjiezana.toml.Toml
import com.moandjiezana.toml.TomlWriter
import kotlinx.serialization.Serializable
import kotlinx.serialization.UnstableDefault
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.encodeToString
import org.yaml.snakeyaml.Yaml
import java.io.File
import java.io.InputStream
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.NoSuchElementException
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
@ -134,16 +138,16 @@ inline operator fun <reified T : Any> Config.setValue(thisRef: Any?, property: K
}
/* 带有默认值的代理 */
@Suppress("unused")
inline fun <reified T : Any> Config.withDefault(
noinline defaultValue: () -> T
crossinline defaultValue: () -> T
): ReadWriteProperty<Any, T> {
val default by lazy { defaultValue.invoke() }
return object : ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (this@withDefault.exist(property.name)) {//unsafe
return this@withDefault.smartCast(property)
}
return default
return defaultValue()
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
@ -153,6 +157,7 @@ inline fun <reified T : Any> Config.withDefault(
}
/* 带有默认值且如果为空会写入的代理 */
@Suppress("unused")
inline fun <reified T : Any> Config.withDefaultWrite(
noinline defaultValue: () -> T
): WithDefaultWriteLoader<T> {
@ -191,7 +196,7 @@ class WithDefaultWriteLoader<T : Any>(
return object : ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (config.exist(property.name)) {//unsafe
return config._smartCast(property.name, _class)
return config.smartCastInternal(property.name, _class)
}
return defaultValue
}
@ -203,12 +208,14 @@ class WithDefaultWriteLoader<T : Any>(
}
}
inline fun <reified T : Any> Config.smartCast(property: KProperty<*>): T {
return _smartCast(property.name, T::class)
@PublishedApi
internal inline fun <reified T : Any> Config.smartCast(property: KProperty<*>): T {
return smartCastInternal(property.name, T::class)
}
@PublishedApi
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
fun <T : Any> Config._smartCast(propertyName: String, _class: KClass<T>): T {
internal fun <T : Any> Config.smartCastInternal(propertyName: String, _class: KClass<T>): T {
return when (_class) {
String::class -> this.getString(propertyName)
Int::class -> this.getInt(propertyName)
@ -251,70 +258,72 @@ fun <T : Any> Config._smartCast(propertyName: String, _class: KClass<T>): T {
interface ConfigSection : Config, MutableMap<String, Any> {
override fun getConfigSection(key: String): ConfigSection {
val content = get(key) ?: error("ConfigSection does not contain $key ")
val content = get(key) ?: throw NoSuchElementException(key)
if (content is ConfigSection) {
return content
}
@Suppress("UNCHECKED_CAST")
return ConfigSectionDelegation(
Collections.synchronizedMap(
(get(key) ?: error("ConfigSection does not contain $key ")) as LinkedHashMap<String, Any>
(get(key) ?: throw NoSuchElementException(key)) as LinkedHashMap<String, Any>
)
)
}
override fun getString(key: String): String {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString()
return (get(key) ?: throw NoSuchElementException(key)).toString()
}
override fun getInt(key: String): Int {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toInt()
return (get(key) ?: throw NoSuchElementException(key)).toString().toInt()
}
override fun getFloat(key: String): Float {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toFloat()
return (get(key) ?: throw NoSuchElementException(key)).toString().toFloat()
}
override fun getBoolean(key: String): Boolean {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toBoolean()
return (get(key) ?: throw NoSuchElementException(key)).toString().toBoolean()
}
override fun getDouble(key: String): Double {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toDouble()
return (get(key) ?: throw NoSuchElementException(key)).toString().toDouble()
}
override fun getLong(key: String): Long {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toLong()
return (get(key) ?: throw NoSuchElementException(key)).toString().toLong()
}
override fun getList(key: String): List<*> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>)
return ((get(key) ?: throw NoSuchElementException(key)) as List<*>)
}
override fun getStringList(key: String): List<String> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString() }
return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString() }
}
override fun getIntList(key: String): List<Int> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toInt() }
return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toInt() }
}
override fun getFloatList(key: String): List<Float> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toFloat() }
return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toFloat() }
}
override fun getDoubleList(key: String): List<Double> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toDouble() }
return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toDouble() }
}
override fun getLongList(key: String): List<Long> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toLong() }
return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map { it.toString().toLong() }
}
override fun getConfigSectionList(key: String): List<ConfigSection> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map {
return ((get(key) ?: throw NoSuchElementException(key)) as List<*>).map {
if (it is ConfigSection) {
it
} else {
@Suppress("UNCHECKED_CAST")
ConfigSectionDelegation(
Collections.synchronizedMap(
it as MutableMap<String, Any>
@ -334,7 +343,7 @@ interface ConfigSection : Config, MutableMap<String, Any> {
}
@Serializable
open class ConfigSectionImpl() : ConcurrentHashMap<String, Any>(),
open class ConfigSectionImpl : ConcurrentHashMap<String, Any>(),
ConfigSection {
override fun set(key: String, value: Any) {
super.put(key, value)
@ -370,7 +379,7 @@ open class ConfigSectionDelegation(
private val delegate: MutableMap<String, Any>
) : ConfigSection, MutableMap<String, Any> by delegate {
override fun set(key: String, value: Any) {
delegate.put(key, value)
delegate[key] = value
}
override fun contains(key: String): Boolean {
@ -394,6 +403,7 @@ interface FileConfig : Config {
}
@MiraiInternalAPI
abstract class FileConfigImpl internal constructor(
private val rawContent: String
) : FileConfig,
@ -402,6 +412,7 @@ abstract class FileConfigImpl internal constructor(
internal var file: File? = null
@Suppress("unused")
constructor(file: File) : this(file.readText()) {
this.file = file
}
@ -425,7 +436,7 @@ abstract class FileConfigImpl internal constructor(
override fun remove(key: String): Any? = content.remove(key)
override fun save() {
if (isReadOnly()) {
if (isReadOnly) {
error("Config is readonly")
}
if (!((file?.exists())!!)) {
@ -434,7 +445,7 @@ abstract class FileConfigImpl internal constructor(
file?.writeText(serialize(content))
}
fun isReadOnly() = file == null
val isReadOnly: Boolean get() = file == null
override fun contains(key: String): Boolean {
return content.contains(key)
@ -454,6 +465,7 @@ abstract class FileConfigImpl internal constructor(
}
@UseExperimental(MiraiInternalAPI::class)
class JsonConfig internal constructor(
content: String
) : FileConfigImpl(content) {
@ -466,7 +478,7 @@ class JsonConfig internal constructor(
if (content.isEmpty() || content.isBlank() || content == "{}") {
return ConfigSectionImpl()
}
return JSON.parseObject<ConfigSectionImpl>(
return JSON.parseObject(
content,
object : TypeReference<ConfigSectionImpl>() {},
Feature.OrderedField
@ -479,6 +491,7 @@ class JsonConfig internal constructor(
}
}
@UseExperimental(MiraiInternalAPI::class)
class YamlConfig internal constructor(content: String) : FileConfigImpl(content) {
constructor(file: File) : this(file.readText()) {
this.file = file
@ -490,7 +503,7 @@ class YamlConfig internal constructor(content: String) : FileConfigImpl(content)
}
return ConfigSectionDelegation(
Collections.synchronizedMap(
Yaml().load<LinkedHashMap<String, Any>>(content) as LinkedHashMap<String, Any>
Yaml().load(content) as LinkedHashMap<String, Any>
)
)
}
@ -501,6 +514,7 @@ class YamlConfig internal constructor(content: String) : FileConfigImpl(content)
}
@UseExperimental(MiraiInternalAPI::class)
class TomlConfig internal constructor(content: String) : FileConfigImpl(content) {
constructor(file: File) : this(file.readText()) {
this.file = file

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.*
@ -15,39 +17,28 @@ import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.SimpleLogger
import net.mamoe.mirai.utils.io.encodeToString
import java.io.File
import java.io.InputStream
import java.net.URL
import java.net.URLClassLoader
import java.util.jar.JarFile
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
abstract class PluginBase(coroutineContext: CoroutineContext) : CoroutineScope {
constructor() : this(EmptyCoroutineContext)
abstract class PluginBase
@JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : CoroutineScope {
private val supervisorJob = SupervisorJob()
final override val coroutineContext: CoroutineContext = coroutineContext + supervisorJob
/**
* 插件被分配的data folder 如果插件改名了 data folder 也会变 请注意
* 插件被分配的数据目录数据目录会与插件名称同名
*/
val dataFolder: File by lazy {
File(_getDataFolder()).also {
File(PluginManager.pluginsPath + "/" + PluginManager.lastPluginName).also {
it.mkdir()
}
}
private fun _getDataFolder():String{
return if(inited){
PluginManager.pluginsPath + "/" + pluginName
}else{
PluginManager.pluginsPath + "/" + PluginManager.lastPluginName//for init
}
}
/**
* 当一个插件被加载时调用
*/
@ -95,28 +86,19 @@ abstract class PluginBase(coroutineContext: CoroutineContext) : CoroutineScope {
this.onDisable()
}
internal var pluginName:String = ""
private var inited = false
internal fun init() {
this.onLoad()
inited = true
}
val pluginManager = PluginManager
internal var pluginName: String = ""
val logger: MiraiLogger by lazy {
SimpleLogger("Plugin ${pluginName}") { _, message, e ->
SimpleLogger("Plugin $pluginName") { _, message, e ->
MiraiConsole.logger("[${pluginName}]", 0, message)
if (e != null) {
MiraiConsole.logger("[${pluginName}]", 0, e.toString())
e.printStackTrace()
MiraiConsole.logger("[${pluginName}]", 0, e)
}
}
}
/**
* 加载一个插件jar, resources中的东西
* 加载 resources 中的文件
*/
fun getResources(fileName: String): InputStream? {
return try {
@ -130,16 +112,15 @@ abstract class PluginBase(coroutineContext: CoroutineContext) : CoroutineScope {
}
/**
* 加载一个插件jar, resources中的Config
* 这个Config是read-only
* 加载 resource 中的 [Config]
* 这个 [Config] read-only
*/
fun getResourcesConfig(fileName: String): Config {
if (fileName.contains(".")) {
error("Unknown Config Type")
}
return Config.load(getResources(fileName) ?: error("Config Not Found"), fileName.split(".")[1])
return Config.load(getResources(fileName) ?: error("No such file: $fileName"), fileName.substringAfter('.'))
}
}
class PluginDescription(
@ -159,37 +140,37 @@ class PluginDescription(
companion object {
fun readFromContent(content_: String): PluginDescription {
with(Config.load(content_,"yml")){
with(Config.load(content_, "yml")) {
try {
return PluginDescription(
name = this.getString("name"),
author = try{
author = kotlin.runCatching {
this.getString("author")
}catch (e:Exception){
}.getOrElse {
"unknown"
},
basePath = try{
basePath = kotlin.runCatching {
this.getString("path")
}catch (e:Exception){
}.getOrElse {
this.getString("main")
},
version = try{
version = kotlin.runCatching {
this.getString("version")
}catch (e:Exception){
}.getOrElse {
"unknown"
},
info = try{
info = kotlin.runCatching {
this.getString("info")
}catch (e:Exception){
}.getOrElse {
"unknown"
},
depends = try{
depends = kotlin.runCatching {
this.getStringList("depends")
}catch (e:Exception){
listOf<String>()
}.getOrElse {
listOf()
}
)
}catch (e:Exception){
} catch (e: Exception) {
error("Failed to read Plugin.YML")
}
}
@ -199,237 +180,3 @@ class PluginDescription(
internal class PluginClassLoader(file: File, parent: ClassLoader) :
URLClassLoader(arrayOf(file.toURI().toURL()), parent)
object PluginManager {
internal var lastPluginName: String = ""
internal val pluginsPath = (System.getProperty("user.dir") + "/plugins/").replace("//", "/").also {
File(it).mkdirs()
}
val logger = SimpleLogger("Plugin Manager") { _, message, e ->
MiraiConsole.logger("[Plugin Manager]", 0, message)
}
//已完成加载的
private val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf()
private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf()
fun onCommand(command: Command, sender: CommandSender,args: List<String>) {
nameToPluginBaseMap.values.forEach {
it.onCommand(command,sender, args)
}
}
fun getPluginDescriptions(base:PluginBase):PluginDescription{
nameToPluginBaseMap.forEach{ (s, pluginBase) ->
if(pluginBase == base){
return pluginDescriptions[s]!!
}
}
error("can not find plugin description")
}
fun getPluginDataFolder(){
}
fun getAllPluginDescriptions(): Collection<PluginDescription> {
return pluginDescriptions.values
}
/**
* 尝试加载全部插件
*/
fun loadPlugins() {
val pluginsFound: MutableMap<String, PluginDescription> = mutableMapOf()
val pluginsLocation: MutableMap<String, File> = mutableMapOf()
logger.info("""开始加载${pluginsPath}下的插件""")
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().inputStream.use {
it.readBytes().encodeToString()
})
pluginsFound[description.name] = description
pluginsLocation[description.name] = file
}catch (e:Exception){
logger.info(e.message)
}
}
}
}
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
fun loadPlugin(description: PluginDescription): Boolean {
if (!description.noCircularDepend) {
logger.error("Failed to load plugin " + description.name + " because it has circular dependency")
return false
}
//load depends first
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 (!depend.loaded && !loadPlugin(pluginsFound[dependent]!!)) {
logger.error("Failed to load plugin " + description.name + " because " + dependent + " as dependency failed to load")
return false
}
}
//在这里所有的depends都已经加载了
//real load
logger.info("loading plugin " + description.name)
try {
val pluginClass = try {
PluginClassLoader(
(pluginsLocation[description.name]!!),
this.javaClass.classLoader
)
.loadClass(description.basePath)
} catch (e: ClassNotFoundException) {
logger.info("failed to find Main: " + description.basePath + " checking if it's kotlin's path")
PluginClassLoader(
(pluginsLocation[description.name]!!),
this.javaClass.classLoader
)
.loadClass("${description.basePath}Kt")
}
return try {
lastPluginName = description.name
val subClass = pluginClass.asSubclass(PluginBase::class.java)
val plugin: PluginBase =
subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().newInstance()
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
plugin.init()
true
} catch (e: ClassCastException) {
logger.error("failed to load plugin " + description.name + " , Main class does not extends PluginBase ")
false
}
} catch (e: ClassNotFoundException) {
e.printStackTrace()
logger.error("failed to load plugin " + description.name + " , Main class not found under " + description.basePath)
return false
}
}
pluginsFound.values.forEach {
loadPlugin(it)
}
nameToPluginBaseMap.values.forEach {
it.enable()
}
logger.info("""加载了${nameToPluginBaseMap.size}个插件""")
}
@JvmOverloads
fun disableAllPlugins(throwable: CancellationException? = null) {
nameToPluginBaseMap.values.forEach {
it.disable(throwable)
}
}
/**
* 根据插件名字找Jar的文件
* null => 没找到
*/
fun getJarPath(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()
})
if (description.name.toLowerCase() == pluginName.toLowerCase()) {
return file
}
}
}
}
return null
}
/**
* 根据插件名字找Jar中的文件
* null => 没找到
*/
fun getFileInJarByName(pluginName: String, toFind: String): InputStream? {
val jarFile = getJarPath(pluginName)
if (jarFile == null) {
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

@ -0,0 +1,249 @@
/*
* 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")
package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.CancellationException
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.utils.SimpleLogger
import net.mamoe.mirai.utils.io.encodeToString
import java.io.File
import java.io.InputStream
import java.net.URL
import java.util.jar.JarFile
object PluginManager {
@Volatile
internal var lastPluginName: String = ""
internal val pluginsPath = (System.getProperty("user.dir") + "/plugins/").replace("//", "/").also {
File(it).mkdirs()
}
private val logger = SimpleLogger("Plugin Manager") { _, message, e ->
MiraiConsole.logger("[Plugin Manager]", 0, message)
MiraiConsole.logger("[Plugin Manager]", 0, e)
}
//已完成加载的
private val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf()
private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf()
fun onCommand(command: Command, sender: CommandSender, args: List<String>) {
nameToPluginBaseMap.values.forEach {
it.onCommand(command, sender, args)
}
}
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
}
/**
* 尝试加载全部插件
*/
fun loadPlugins() {
val pluginsFound: MutableMap<String, PluginDescription> = mutableMapOf()
val pluginsLocation: MutableMap<String, File> = mutableMapOf()
logger.info("""开始加载${pluginsPath}下的插件""")
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().inputStream.use {
it.readBytes().encodeToString()
})
pluginsFound[description.name] = description
pluginsLocation[description.name] = file
} catch (e: Exception) {
logger.info(e.message)
}
}
}
}
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
fun loadPlugin(description: PluginDescription): Boolean {
if (!description.noCircularDepend) {
logger.error("Failed to load plugin " + description.name + " because it has circular dependency")
return false
}
//load depends first
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 (!depend.loaded && !loadPlugin(pluginsFound[dependent]!!)) {
logger.error("Failed to load plugin " + description.name + " because " + dependent + " as dependency failed to load")
return false
}
}
//在这里所有的depends都已经加载了
//real load
logger.info("loading plugin " + description.name)
try {
val pluginClass = try {
PluginClassLoader(
(pluginsLocation[description.name]!!),
this.javaClass.classLoader
)
.loadClass(description.basePath)
} catch (e: ClassNotFoundException) {
logger.info("failed to find Main: " + description.basePath + " checking if it's kotlin's path")
PluginClassLoader(
(pluginsLocation[description.name]!!),
this.javaClass.classLoader
)
.loadClass("${description.basePath}Kt")
}
return try {
lastPluginName = description.name
val subClass = pluginClass.asSubclass(PluginBase::class.java)
val plugin: PluginBase =
subClass.kotlin.objectInstance ?: subClass.getDeclaredConstructor().newInstance()
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
true
} catch (e: ClassCastException) {
logger.error("failed to load plugin " + description.name + " , Main class does not extends PluginBase ")
false
}
} catch (e: ClassNotFoundException) {
logger.error("failed to load plugin " + description.name + " , Main class not found under " + description.basePath)
logger.error(e)
return false
}
}
pluginsFound.values.forEach {
loadPlugin(it)
}
nameToPluginBaseMap.values.forEach {
it.enable()
}
logger.info("""加载了${nameToPluginBaseMap.size}个插件""")
}
@JvmOverloads
fun disableAllPlugins(throwable: CancellationException? = null) {
nameToPluginBaseMap.values.forEach {
it.disable(throwable)
}
}
/**
* 根据插件名字找Jar的文件
* null => 没找到
*/
fun getJarPath(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()
})
if (description.name.toLowerCase() == pluginName.toLowerCase()) {
return file
}
}
}
}
return null
}
/**
* 根据插件名字找Jar中的文件
* null => 没找到
*/
fun getFileInJarByName(pluginName: String, toFind: String): InputStream? {
val jarFile = getJarPath(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

@ -18,8 +18,8 @@ import net.mamoe.mirai.utils.LoginSolver
import kotlin.concurrent.thread
class MiraiConsoleUIPure : MiraiConsoleUI {
var requesting = false
var requestStr = ""
private var requesting = false
private var requestStr = ""
init {
thread {

View File

@ -18,32 +18,33 @@ import net.mamoe.mirai.console.plugins.withDefaultWriteSave
import net.mamoe.mirai.console.utils.BotManagers.BOT_MANAGERS
import java.io.File
object BotManagers {
internal object BotManagers {
val config = File("${MiraiConsole.path}/bot.yml").loadAsConfig()
val BOT_MANAGERS: ConfigSection by config.withDefaultWriteSave { ConfigSectionImpl() }
}
fun Bot.addManager(long: Long) {
internal fun Bot.addManager(long: Long) {
BOT_MANAGERS.putIfAbsent(this.uin.toString(), mutableListOf<Long>())
BOT_MANAGERS[this.uin.toString()] =
(BOT_MANAGERS.getLongList(this.uin.toString()) as MutableList<Long>).apply { add(long) }
BotManagers.config.save()
}
fun Bot.removeManager(long: Long) {
internal fun Bot.removeManager(long: Long) {
BOT_MANAGERS.putIfAbsent(this.uin.toString(), mutableListOf<Long>())
BOT_MANAGERS[this.uin.toString()] =
(BOT_MANAGERS.getLongList(this.uin.toString()) as MutableList<Long>).apply { add(long) }
BotManagers.config.save()
}
fun Bot.getManagers(): List<Long> {
BOT_MANAGERS.putIfAbsent(this.uin.toString(), mutableListOf<Long>())
return BOT_MANAGERS.getLongList(this.uin.toString())
}
internal val Bot.managers: List<Long>
get() {
BOT_MANAGERS.putIfAbsent(this.uin.toString(), mutableListOf<Long>())
return BOT_MANAGERS.getLongList(this.uin.toString())
}
fun Bot.checkManager(long: Long): Boolean {
return this.getManagers().contains(long)
internal fun Bot.checkManager(long: Long): Boolean {
return this.managers.contains(long)
}