mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-09 18:00:33 +08:00
Merge remote-tracking branch 'origin/master'
# Conflicts: # mirai-console/src/main/kotlin/net/mamoe/mirai/console/core/MiraiCoreLoader.kt
This commit is contained in:
commit
34ad7ac194
@ -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
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
@ -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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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() }
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user