Commit c6ae8e32 authored by Him188's avatar Him188

Separate `mirai-console` series from main repository

parent 6110cf8f
### Mirai Console Graphical
支持windows/mac
有正式UI界面实现的CONSOLE
优点: 适合新手/完全不懂编程的/界面美丽
缺点: 不能在linux服务器运行
所使用插件系统与terminal版本一致 可以来回切换
\ No newline at end of file
plugins {
id("kotlinx-serialization")
id("org.openjfx.javafxplugin") version "0.0.8"
id("kotlin")
id("java")
}
javafx {
version = "13.0.2"
modules = listOf("javafx.controls")
//mainClassName = "Application"
}
apply(plugin = "com.github.johnrengelman.shadow")
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
dependencies {
api(project(":mirai-core"))
api(project(":mirai-core-qqandroid"))
api(project(":mirai-api-http"))
api(project(":mirai-console"))
runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main"))
api(group = "no.tornado", name = "tornadofx", version = "1.7.19")
api(group = "com.jfoenix", name = "jfoenix", version = "9.0.8")
api("org.bouncycastle:bcprov-jdk15on:1.64")
// classpath is not set correctly by IDE
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.graphical
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController
import net.mamoe.mirai.console.graphical.styleSheet.PrimaryStyleSheet
import net.mamoe.mirai.console.graphical.view.Decorator
import tornadofx.App
import tornadofx.find
import tornadofx.launch
fun main(args: Array<String>) {
launch<MiraiGraphicalUI>(args)
}
class MiraiGraphicalUI : App(Decorator::class, PrimaryStyleSheet::class) {
override fun init() {
super.init()
MiraiConsole.start(find<MiraiGraphicalUIController>())
}
override fun stop() {
super.stop()
MiraiConsole.stop()
}
}
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
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.utils.MiraiConsoleUI
import net.mamoe.mirai.utils.LoginSolver
import tornadofx.*
class MiraiGraphicalUIController : Controller(), MiraiConsoleUI {
private val loginSolver = GraphicalLoginSolver()
private val cache = mutableMapOf<Long, BotModel>()
val mainLog = observableListOf<String>()
val botList = observableListOf<BotModel>()
val pluginList: ObservableList<PluginModel> by lazy(::getPluginsFromConsole)
val consoleInfo = ConsoleInfo()
fun login(qq: String, psd: String) {
MiraiConsole.CommandProcessor.runConsoleCommandBlocking("/login $qq $psd")
}
fun sendCommand(command: String) = MiraiConsole.CommandProcessor.runConsoleCommandBlocking(command)
override fun pushLog(identity: Long, message: String) = Platform.runLater {
when (identity) {
0L -> mainLog.add(message)
else -> cache[identity]?.logHistory?.add(message)
}
}
override fun prePushBot(identity: Long) = Platform.runLater {
BotModel(identity).also {
cache[identity] = it
botList.add(it)
}
}
override fun pushBot(bot: Bot) = Platform.runLater {
cache[bot.uin]?.bot = bot
}
override fun pushVersion(consoleVersion: String, consoleBuild: String, coreVersion: String) {
Platform.runLater {
consoleInfo.consoleVersion = consoleVersion
consoleInfo.consoleBuild = consoleBuild
consoleInfo.coreVersion = coreVersion
}
}
override suspend fun requestInput(question: String): String {
val model = VerificationCodeModel()
find<VerificationCodeFragment>(Scope(model)).openModal(
modality = Modality.APPLICATION_MODAL,
resizable = false
)
return model.code.value
}
override fun pushBotAdminStatus(identity: Long, admins: List<Long>) = Platform.runLater {
cache[identity]?.admins?.setAll(admins)
}
override fun createLoginSolver(): LoginSolver = loginSolver
private fun getPluginsFromConsole(): ObservableList<PluginModel> =
MiraiConsole.pluginManager.getAllPluginDescriptions().map(::PluginModel).toObservable()
}
class GraphicalLoginSolver : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.model
import javafx.beans.property.SimpleObjectProperty
import net.mamoe.mirai.Bot
import tornadofx.*
class BotModel(val uin: Long) {
val botProperty = SimpleObjectProperty<Bot>(null)
var bot: Bot by botProperty
val logHistory = observableListOf<String>()
val admins = observableListOf<Long>()
}
class BotViewModel(botModel: BotModel? = null) : ItemViewModel<BotModel>(botModel) {
val bot = bind(BotModel::botProperty)
val logHistory = bind(BotModel::logHistory)
val admins = bind(BotModel::admins)
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.model
import javafx.beans.property.SimpleStringProperty
import tornadofx.setValue
import tornadofx.getValue
class ConsoleInfo {
val consoleVersionProperty = SimpleStringProperty()
var consoleVersion by consoleVersionProperty
val consoleBuildProperty = SimpleStringProperty()
var consoleBuild by consoleBuildProperty
val coreVersionProperty = SimpleStringProperty()
var coreVersion by coreVersionProperty
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.model
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject
import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleStringProperty
import net.mamoe.mirai.console.plugins.PluginDescription
import tornadofx.getValue
import tornadofx.setValue
class PluginModel(
val name: String,
val version: String,
val author: String,
val description: String
) : RecursiveTreeObject<PluginModel>() {
constructor(plugin: PluginDescription):this(plugin.name, plugin.version, plugin.author, plugin.info)
val enabledProperty = SimpleBooleanProperty(this, "enabledProperty")
var enabled by enabledProperty
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.model
import javafx.beans.property.SimpleStringProperty
import tornadofx.ItemViewModel
import tornadofx.getValue
import tornadofx.setValue
class VerificationCode {
val codeProperty = SimpleStringProperty("")
var code: String by codeProperty
}
class VerificationCodeModel(code: VerificationCode) : ItemViewModel<VerificationCode>(code) {
constructor(): this(VerificationCode())
val code = bind(VerificationCode::codeProperty)
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.styleSheet
import javafx.scene.Cursor
import javafx.scene.effect.BlurType
import javafx.scene.effect.DropShadow
import javafx.scene.paint.Color
import javafx.scene.text.FontWeight
import tornadofx.*
class LoginViewStyleSheet : Stylesheet() {
companion object {
val vBox by csselement("VBox")
}
init {
vBox {
maxWidth = 500.px
maxHeight = 500.px
backgroundColor += c("39c5BB", 0.3)
backgroundRadius += box(15.px)
padding = box(50.px, 100.px)
spacing = 25.px
borderRadius += box(15.px)
effect = DropShadow(BlurType.THREE_PASS_BOX, Color.GRAY, 10.0, 0.0, 15.0, 15.0)
}
textField {
prefHeight = 30.px
textFill = Color.BLACK
fontWeight = FontWeight.BOLD
}
button {
backgroundColor += c("00BCD4", 0.8)
padding = box(10.px, 0.px)
prefWidth = 500.px
textFill = Color.WHITE
fontWeight = FontWeight.BOLD
cursor = Cursor.HAND
}
}
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.styleSheet
import tornadofx.*
class PrimaryStyleSheet : Stylesheet() {
companion object {
val jfxTitle by cssclass("jfx-decorator-buttons-container")
val container by cssclass("jfx-decorator-content-container")
}
init {
jfxTitle {
backgroundColor += c("00BCD4")
}
container {
borderColor += box(c("00BCD4"))
borderWidth += box(0.px, 4.px, 4.px, 4.px)
}
}
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.util
import com.jfoenix.controls.*
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject
import javafx.beans.value.ObservableValue
import javafx.collections.ObservableList
import javafx.event.EventTarget
import javafx.scene.Node
import javafx.scene.control.*
import tornadofx.SortedFilteredList
import tornadofx.attachTo
import tornadofx.bind
internal fun EventTarget.jfxTabPane(op: TabPane.() -> Unit = {}) = JFXTabPane().attachTo(this, op)
internal fun EventTarget.jfxButton(text: String = "", graphic: Node? = null, op: Button.() -> Unit = {}) =
JFXButton(text).attachTo(this, op) {
if (graphic != null) it.graphic = graphic
}
fun EventTarget.jfxTextfield(value: String? = null, op: JFXTextField.() -> Unit = {}) = JFXTextField().attachTo(this, op) {
if (value != null) it.text = value
}
fun EventTarget.jfxTextfield(property: ObservableValue<String>, op: JFXTextField.() -> Unit = {}) = jfxTextfield().apply {
bind(property)
op(this)
}
fun EventTarget.jfxPasswordfield(value: String? = null, op: JFXPasswordField.() -> Unit = {}) = JFXPasswordField().attachTo(this, op) {
if (value != null) it.text = value
}
fun EventTarget.jfxPasswordfield(property: ObservableValue<String>, op: JFXPasswordField.() -> Unit = {}) = jfxPasswordfield().apply {
bind(property)
op(this)
}
internal fun <T> EventTarget.jfxListView(values: ObservableList<T>? = null, op: ListView<T>.() -> Unit = {}) =
JFXListView<T>().attachTo(this, op) {
if (values != null) {
if (values is SortedFilteredList<T>) values.bindTo(it)
else it.items = values
}
}
fun <T : RecursiveTreeObject<T>?> EventTarget.jfxTreeTableView(items: ObservableList<T>? = null, op: JFXTreeTableView<T>.() -> Unit = {})
= JFXTreeTableView<T>(RecursiveTreeItem(items, RecursiveTreeObject<T>::getChildren)).attachTo(this, op)
\ No newline at end of file
package net.mamoe.mirai.console.graphical.view
import com.jfoenix.controls.JFXDecorator
import tornadofx.View
class Decorator: View() {
override val root = JFXDecorator(primaryStage, find<PrimaryView>().root)
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.view
import javafx.beans.property.SimpleStringProperty
import javafx.geometry.Pos
import javafx.scene.image.Image
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController
import net.mamoe.mirai.console.graphical.styleSheet.LoginViewStyleSheet
import net.mamoe.mirai.console.graphical.util.jfxButton
import net.mamoe.mirai.console.graphical.util.jfxPasswordfield
import net.mamoe.mirai.console.graphical.util.jfxTextfield
import tornadofx.*
class LoginView : View("CNM") {
private val controller = find<MiraiGraphicalUIController>()
private val qq = SimpleStringProperty("")
private val psd = SimpleStringProperty("")
override val root = borderpane {
addStylesheet(LoginViewStyleSheet::class)
center = vbox {
imageview(Image(LoginView::class.java.classLoader.getResourceAsStream("character.png"))) {
alignment = Pos.CENTER
}
jfxTextfield(qq) {
promptText = "QQ"
isLabelFloat = true
}
jfxPasswordfield(psd) {
promptText = "Password"
isLabelFloat = true
}
jfxButton("Login").action {
runAsync {
runBlocking { controller.login(qq.value, psd.value) }
}.ui {
qq.value = ""
psd.value = ""
}
}
}
}
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.view
import com.jfoenix.controls.JFXTreeTableColumn
import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController
import net.mamoe.mirai.console.graphical.model.PluginModel
import net.mamoe.mirai.console.graphical.util.jfxTreeTableView
import tornadofx.View
class PluginsView : View() {
private val controller = find<MiraiGraphicalUIController>()
val plugins = controller.pluginList
override val root = jfxTreeTableView(plugins) {
columns.addAll(
JFXTreeTableColumn<PluginModel, String>("插件名").apply {
prefWidthProperty().bind(this@jfxTreeTableView.widthProperty().multiply(0.1))
},
JFXTreeTableColumn<PluginModel, String>("版本").apply {
prefWidthProperty().bind(this@jfxTreeTableView.widthProperty().multiply(0.1))
},
JFXTreeTableColumn<PluginModel, String>("作者").apply {
prefWidthProperty().bind(this@jfxTreeTableView.widthProperty().multiply(0.1))
},
JFXTreeTableColumn<PluginModel, String>("介绍").apply {
prefWidthProperty().bind(this@jfxTreeTableView.widthProperty().multiply(0.6))
},
JFXTreeTableColumn<PluginModel, String>("操作").apply {
prefWidthProperty().bind(this@jfxTreeTableView.widthProperty().multiply(0.08))
}
)
}
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.view
import com.jfoenix.controls.*
import javafx.collections.ObservableList
import javafx.scene.control.Tab
import javafx.scene.control.TabPane
import javafx.scene.image.Image
import javafx.scene.input.KeyCode
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController
import net.mamoe.mirai.console.graphical.model.BotModel
import net.mamoe.mirai.console.graphical.util.jfxListView
import net.mamoe.mirai.console.graphical.util.jfxTabPane
import tornadofx.*
class PrimaryView : View() {
private val controller = find<MiraiGraphicalUIController>()
override val root = borderpane {
prefWidth = 1000.0
prefHeight = 650.0
left = vbox {
imageview(Image(PrimaryView::class.java.classLoader.getResourceAsStream("logo.png")))
// bot list
jfxListView(controller.botList) {
fitToParentSize()
setCellFactory {
object : JFXListCell<BotModel>() {
init {
onDoubleClick {
(center as TabPane).logTab(
text = item.uin.toString(),
logs = item.logHistory
).select()
}
}
override fun updateItem(item: BotModel?, empty: Boolean) {
super.updateItem(item, empty)
if (item != null && !empty) {
graphic = null
text = item.uin.toString()
} else {
graphic = null
text = ""
}
}
}
}
}
// command input
textfield {
setOnKeyPressed {
if (it.code == KeyCode.ENTER) {
runAsync {
runBlocking { controller.sendCommand(text) }
}.ui { text = "" }
}
}
}
}
center = jfxTabPane {
tab("Login").content = find<LoginView>().root
tab("Plugins").content = find<PluginsView>().root
tab("Settings").content = find<SettingsView>().root
logTab("Main", controller.mainLog)
}
}
}
private fun TabPane.logTab(
text: String? = null,
logs: ObservableList<String>,
op: Tab.() -> Unit = {}
)= tab(text) {
listview(logs) {
fitToParentSize()
cellFormat {
graphic = label(it) {
maxWidthProperty().bind(this@listview.widthProperty())
isWrapText = true
}
}
}
also(op)
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.view
import net.mamoe.mirai.console.graphical.controller.MiraiGraphicalUIController
import net.mamoe.mirai.console.graphical.util.jfxButton
import net.mamoe.mirai.console.graphical.util.jfxTextfield
import tornadofx.*
class SettingsView : View() {
private val controller = find<MiraiGraphicalUIController>()
override val root = form {
fieldset {
field {
jfxButton("撤掉") { }
jfxButton("保存") { }
}
}
fieldset("插件目录") {
field {
jfxTextfield("...") { isEditable = false }
jfxButton("打开目录")
}
}
fieldset("最大日志容量") {
field {
jfxTextfield("...") {
}
}
}
}
}
\ No newline at end of file
package net.mamoe.mirai.console.graphical.view
import javafx.scene.Parent
import tornadofx.*
class VerificationCodeFragment : Fragment() {
override val root = vbox {
//TODO: 显示验证码
form {
fieldset {
field("验证码") {
textfield()
}
}
}
}
}
\ No newline at end of file
### Mirai Console Terminal
支持windows/mac/linux
在terminal环境下的Console, 由控制台富文本实现简易UI
优点: 可以在linux环境下运行/简洁使用效率高
缺点: 需要有略微的terminal知识
所使用插件系统与graphical版本一致 可以来回切换
\ No newline at end of file
plugins {
id("kotlinx-serialization")
id("kotlin")
id("java")
}
apply(plugin = "com.github.johnrengelman.shadow")
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
manifest {
attributes["Main-Class"] = "net.mamoe.mirai.console.MiraiConsoleTerminalLoader"
}
}
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
dependencies {
api(project(":mirai-core"))
api(project(":mirai-core-qqandroid"))
api(project(":mirai-api-http"))
api(project(":mirai-console"))
runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main"))
runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main"))
api(group = "com.googlecode.lanterna", name = "lanterna", version = "3.0.2")
api("org.bouncycastle:bcprov-jdk15on:1.64")
// classpath is not set correctly by IDE
}
\ No newline at end of file
package net.mamoe.mirai.console
import net.mamoe.mirai.console.pure.MiraiConsoleUIPure
import kotlin.concurrent.thread
class MiraiConsoleTerminalLoader {
companion object {
@JvmStatic
fun main(args: Array<String>) {
if (args.contains("pure") || args.contains("-pure") || System.getProperty(
"os.name",
""
).toLowerCase().contains("windows")
) {
println("[MiraiConsoleTerminalLoader]: 将以Pure[兼容模式]启动Console")
MiraiConsole.start(MiraiConsoleUIPure())
} else {
MiraiConsoleTerminalUI.start()
thread {
MiraiConsole.start(
MiraiConsoleTerminalUI
)
}
}
Runtime.getRuntime().addShutdownHook(thread(start = false) {
MiraiConsole.stop()
MiraiConsoleTerminalUI.exit()
})
}
}
}
\ No newline at end of file
# Mirai Console
你可以在全平台运行Mirai高效率机器人框架
### Mirai Console提供了6个版本以满足各种需要
#### 所有版本的Mirai Console API相同 插件系统相同
| 名字 | 介绍 |
| --- | --- |
| Mirai-Console-Pure | 最纯净版, CLI环境, 通过标准输入与标准输出 交互 |
| Mirai-Console-Terminal | (UNIX)Terminal环境 提供简洁的富文本控制台 |
| Mirai-Console-Android | 安卓APP (TODO) |
| Mirai-Console-Graphical | JavaFX的图形化界面 (.jar/.exe/.dmg) |
| Mirai-Console-WebPanel | Web Panel操作(TODO) |
| Mirai-Console-Ios | IOS APP (TODO) |
### 如何选择版本
1: Mirai-Console-Pure 兼容性最高, 在其他都表现不佳的时候请使用</br>
2: 以系统区分
```kotlin
return when(operatingSystem){
WINDOWS -> listOf("Graphical","WebPanel","Pure")
MAC_OS -> listOf("Graphical","Terminal","WebPanel","Pure")
LINUX -> listOf("Terminal","Pure")
ANDROID -> listOf("Android","Pure","WebPanel")
IOS -> listOf("Ios")
else -> listOf("Pure")
}
```
3: 以策略区分
```kotlin
return when(task){
体验 -> listOf("Graphical","Terminal","WebPanel","Android","Pure")
测试插件 -> listOf("Pure")
调试插件 -> byOperatingSystem()
稳定挂机 -> listOf("Terminal","Pure")
else -> listOf("Pure")
}
```
#### More Importantly, Mirai Console support <b>Plugins</b>, tells the bot what to do
#### Mirai Console 支持插件系统, 你可以自己开发或使用公开的插件来逻辑化机器人, 如群管
<br>
#### download 下载
#### how to get/write plugins 如何获取/写插件
<br>
<br>
### how to use(如何使用)
#### how to run Mirai Console
<ul>
<li>download mirai-console.jar</li>
<li>open command line/terminal</li>
<li>create a folder and put mirai-console.jar in</li>
<li>cd that folder</li>
<li>"java -jar mirai-console.jar"</li>
</ul>
<ul>
<li>下载mirai-console.jar</li>
<li>打开终端</li>
<li>在任何地方创建一个文件夹, 并放入mirai-console.jar</li>
<li>在终端中打开该文件夹"cd"</li>
<li>输入"java -jar mirai-console.jar"</li>
</ul>
#### how to add plugins
<ul>
<li>After first time of running mirai console</li>
<li>/plugins/folder will be created next to mirai-console.jar</li>
<li>put plugin(.jar) into /plugins/</li>
<li>restart mirai console</li>
<li>checking logger and check if the plugin is loaded successfully</li>
<li>if the plugin has it own Config file, it normally appears in /plugins/{pluginName}/</li>
</ul>
<ul>
<li>在首次运行mirai console后</li>
<li>mirai-console.jar 的同级会出现/plugins/文件夹</li>
<li>将插件(.jar)放入/plugins/文件夹</li>
<li>重启mirai console</li>
<li>在开启后检查日志, 是否成功加载</li>
<li>如该插件有配置文件, 配置文件一般会创建在/plugins/插件名字/ 文件夹下</li>
</ul>
plugins {
id("kotlinx-serialization")
id("kotlin")
id("java")
}
apply(plugin = "com.github.johnrengelman.shadow")
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
manifest {
attributes["Main-Class"] = "net.mamoe.mirai.console.pure.MiraiConsolePureLoader"
}
}
dependencies {
api(project(":mirai-core"))
api(project(":mirai-core-qqandroid"))
// api(project(":mirai-api-http"))
runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main"))
runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main"))
api(kotlin("serialization"))
api(group = "com.alibaba", name = "fastjson", version = "1.2.62")
api(group = "org.yaml", name = "snakeyaml", version = "1.25")
api(group = "com.moandjiezana.toml", name = "toml4j", version = "0.7.2")
api("org.bouncycastle:bcprov-jdk15on:1.64")
implementation("no.tornado:tornadofx:1.7.19")
// classpath is not set correctly by IDE
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole.CommandProcessor.processNextCommandLine
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.command.DefaultCommands
import net.mamoe.mirai.console.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 {
/**
* 发布的版本号 统一修改位置
*/
const val version = "0.1.0"
const val coreVersion = "v0.18.0"
const val build = "Alpha"
/**
* 获取从Console登陆上的Bot, Bots
* */
val bots get() = Bot.instances
fun getBotByUIN(uin: Long): Bot? {
bots.forEach {
if (it.get()?.uin == uin) {
return it.get()
}
}
return null
}
/**
* PluginManager
*/
val pluginManager: PluginManager get() = PluginManager
/**
* 与前端交互所使用的Logger
*/
var logger = UIPushLogger
/**
* Console运行路径
*/
var path: String = System.getProperty("user.dir")
/**
* Console前端接口
*/
lateinit var frontEnd: MiraiConsoleUI
/**
* 启动Console
*/
var start = false
fun start(
frontEnd: MiraiConsoleUI
) {
if (start) {
return
}
start = true
/* 加载ECDH */
try {
ECDH()
} catch (ignored: Exception) {
}
//Security.removeProvider("BC")
/* 初始化前端 */
this.frontEnd = frontEnd
frontEnd.pushVersion(version, build, coreVersion)
logger("Mirai-console [$version $build | core version $coreVersion] is still in testing stage, major features are available")
logger("Mirai-console now running under $path")
logger("Get news in github: https://github.com/mamoe/mirai")
logger("Mirai为开源项目,请自觉遵守开源项目协议")
logger("Powered by Mamoe Technologies and contributors")
/* 依次启用功能 */
DefaultCommands()
HTTPAPIAdaptar()
pluginManager.loadPlugins()
CommandProcessor.start()
/* 通知启动完成 */
logger("Mirai-console 启动完成")
logger("\"/login qqnumber qqpassword \" to login a bot")
logger("\"/login qq号 qq密码 \" 来登录一个BOT")
}
fun stop() {
PluginManager.disableAllPlugins()
try {
bots.forEach {
it.get()?.close()
}
} catch (ignored: Exception) {
}
}
object CommandProcessor : Job by {
GlobalScope.launch(start = CoroutineStart.LAZY) {
processNextCommandLine()
}
}() {
internal class FullCommand(
val sender: CommandSender,
val commandStr: String
)
private val commandChannel: Channel<FullCommand> = Channel()
suspend fun runConsoleCommand(command: String) {
commandChannel.send(
FullCommand(ConsoleCommandSender, command)
)
}
suspend fun runCommand(sender: CommandSender, command: String) {
commandChannel.send(
FullCommand(sender, command)
)
}
fun runConsoleCommandBlocking(command: String) = runBlocking { runConsoleCommand(command) }
fun runCommandBlocking(sender: CommandSender, command: String) = runBlocking { runCommand(sender, command) }
private suspend fun processNextCommandLine() {
for (command in commandChannel) {
var commandStr = command.commandStr
if (!commandStr.startsWith("/")) {
commandStr = "/$commandStr"
}
if (!CommandManager.runCommand(command.sender, commandStr)) {
command.sender.sendMessage("未知指令 $commandStr")
}
}
}
}
object UIPushLogger {
operator fun invoke(any: Any? = null) {
invoke(
"[Mirai$version $build]",
0L,
any
)
}
operator fun invoke(identityStr: String, identity: Long, any: Any? = null) {
if (any != null) {
frontEnd.pushLog(identity, "$identityStr: $any")
}
}
}
}
object MiraiProperties {
var config = File("${MiraiConsole.path}/mirai.properties").loadAsConfig()
var HTTP_API_ENABLE: Boolean by config.withDefaultWrite { true }
var HTTP_API_PORT: Int by config.withDefaultWrite { 8080 }
/*
var HTTP_API_AUTH_KEY: String by config.withDefaultWriteSave {
"InitKey" + generateSessionKey()
}*/
}
object HTTPAPIAdaptar {
operator fun invoke() {
/*
if (MiraiProperties.HTTP_API_ENABLE) {
if (MiraiProperties.HTTP_API_AUTH_KEY.startsWith("InitKey")) {
MiraiConsole.logger("请尽快更改初始生成的HTTP API AUTHKEY")
}
MiraiConsole.logger("正在启动HTTPAPI; 端口=" + MiraiProperties.HTTP_API_PORT)
MiraiHttpAPIServer.logger = SimpleLogger("HTTP API") { _, message, e ->
MiraiConsole.logger("[Mirai HTTP API]", 0, message)
}
MiraiHttpAPIServer.start(
MiraiProperties.HTTP_API_PORT,
MiraiProperties.HTTP_API_AUTH_KEY
)
MiraiConsole.logger("HTTPAPI启动完成; 端口= " + MiraiProperties.HTTP_API_PORT)
}*/
}
}
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command
import kotlinx.coroutines.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.contact.Contact
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.quote
import net.mamoe.mirai.message.data.toMessage
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import java.lang.StringBuilder
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, args)
} else {
sender.sendMessage(this.usage)
}
} catch (e: Exception) {
sender.sendMessage("在运行指令时出现了未知错误")
e.printStackTrace()
} finally {
(sender as CommandSenderImpl).flushMessage()
}
}
return true
}
}
interface CommandSender {
/**
* 立刻发送一条Message
*/
suspend fun sendMessage(messageChain: MessageChain)
suspend fun sendMessage(message: String)
/**
* 写入要发送的内容 所有内容最后会被以一条发出, 不管成功与否
*/
fun appendMessage(message: String)
fun sendMessageBlocking(messageChain: MessageChain) = runBlocking { sendMessage(messageChain) }
fun sendMessageBlocking(message: String) = runBlocking { sendMessage(message) }
}
abstract class CommandSenderImpl : CommandSender {
internal val builder = StringBuilder()
override fun appendMessage(message: String) {
builder.append(message).append("\n")
}
internal open suspend fun flushMessage() {
if (!builder.isEmpty()) {
sendMessage(builder.toString().removeSuffix("\n"))
}
}
}
object ConsoleCommandSender : CommandSenderImpl() {
override suspend fun sendMessage(messageChain: MessageChain) {
MiraiConsole.logger("[Command]", 0, messageChain.toString())
}
override suspend fun sendMessage(message: String) {
MiraiConsole.logger("[Command]", 0, message)
}
override suspend fun flushMessage() {
super.flushMessage()
builder.clear()
}
}
open class ContactCommandSender(val contact: Contact) : CommandSenderImpl() {
override suspend fun sendMessage(messageChain: MessageChain) {
contact.sendMessage(messageChain)
}
override suspend fun sendMessage(message: String) {
contact.sendMessage(message)
}
}
/**
* 弃用中
* */
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>
val description: String
val usage: String
suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean
fun register()
}
abstract class BlockingCommand(
override val name: String,
override val alias: List<String> = listOf(),
override val description: String = "",
override val usage: String = ""
) : Command {
/**
* 最高优先级监听器
* 如果 return `false` 这次指令不会被 [PluginBase] 的全局 onCommand 监听器监听
* */
final override suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean {
return withContext(Dispatchers.IO) {
onCommandBlocking(sender, args)
}
}
abstract fun onCommandBlocking(sender: CommandSender, args: List<String>): Boolean
override fun register() {
CommandManager.register(this)
}
}
class AnonymousCommand internal constructor(
override val name: String,
override val alias: List<String>,
override val description: String,
override val usage: String = "",
val onCommand: suspend CommandSender.(args: List<String>) -> Boolean
) : Command {
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() {
var name: String? = null
var alias: List<String>? = null
var description: String = ""
var usage: String = "use /help for help"
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() }
}
}
fun registerCommand(builder: CommandBuilder.() -> Unit): Command {
return CommandBuilder().apply(builder).register()
}
package net.mamoe.mirai.console.pure
import net.mamoe.mirai.console.MiraiConsole
import kotlin.concurrent.thread
class MiraiConsolePureLoader {
companion object {
@JvmStatic
fun main(args: Array<String>) {
MiraiConsole.start(MiraiConsoleUIPure())
Runtime.getRuntime().addShutdownHook(thread(start = false) {
MiraiConsole.stop()
})
}
}
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.pure
import kotlinx.coroutines.delay
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.utils.MiraiConsoleUI
import net.mamoe.mirai.utils.DefaultLoginSolver
import net.mamoe.mirai.utils.LoginSolver
import net.mamoe.mirai.utils.LoginSolverInputReader
import kotlin.concurrent.thread
class MiraiConsoleUIPure : MiraiConsoleUI {
var requesting = false
var requestStr = ""
init {
thread {
while (true) {
val input = readLine() ?: return@thread
if (requesting) {
requestStr = input
requesting = false
} else {
MiraiConsole.CommandProcessor.runConsoleCommandBlocking(input)
}
}
}
}
override fun pushLog(identity: Long, message: String) {
println(message)
}
override fun prePushBot(identity: Long) {
}
override fun pushBot(bot: Bot) {
}
override fun pushVersion(consoleVersion: String, consoleBuild: String, coreVersion: String) {
}
override suspend fun requestInput(question: String): String {
requesting = true
while (true) {
delay(50)
if (!requesting) {
return requestStr
}
}
}
override fun pushBotAdminStatus(identity: Long, admins: List<Long>) {
}
override fun createLoginSolver(): LoginSolver {
return DefaultLoginSolver(
reader = object : LoginSolverInputReader {
override suspend fun read(question: String): String? {
return requestInput(question)
}
}
)
}
}
package net.mamoe.mirai.console.utils
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiProperties
import net.mamoe.mirai.console.utils.BotManagers.BOT_MANAGERS
import net.mamoe.mirai.console.plugins.ConfigSection
import net.mamoe.mirai.console.plugins.ConfigSectionImpl
import net.mamoe.mirai.console.plugins.loadAsConfig
import net.mamoe.mirai.console.plugins.withDefaultWriteSave
import java.io.File
object BotManagers {
val config = File("${MiraiConsole.path}/bot.yml").loadAsConfig()
val BOT_MANAGERS: ConfigSection by config.withDefaultWriteSave { ConfigSectionImpl() }
}
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) {
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())
}
fun Bot.checkManager(long: Long): Boolean {
return this.getManagers().contains(long)
}
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.utils
import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.LoginSolver
/**
* 只需要实现一个这个 传入MiraiConsole 就可以绑定UI层与Console层
* 注意线程
*/
interface MiraiConsoleUI {
/**
* 让UI层展示一条log
*
* identity:log所属的screen, Main=0; Bot=Bot.uin
*/
fun pushLog(
identity: Long,
message: String
)
/**
* 让UI层准备接受新增的一个BOT
*/
fun prePushBot(
identity: Long
)
/**
* 让UI层接受一个新的bot
* */
fun pushBot(
bot: Bot
)
fun pushVersion(
consoleVersion: String,
consoleBuild: String,
coreVersion: String
)
/**
* 让UI层提供一个Input
* 这个Input 不 等于 Command
*
*/
suspend fun requestInput(
question: String
): String
/**
* 让UI层更新BOT管理员的数据
*/
fun pushBotAdminStatus(
identity: Long,
admins: List<Long>
)
/**
* 由UI层创建一个LoginSolver
*/
fun createLoginSolver(): LoginSolver
}
\ No newline at end of file
......@@ -42,9 +42,6 @@ include(':mirai-core')
//include(':mirai-core-timpc')
include(':mirai-core-qqandroid')
include(':mirai-japt')
include(':mirai-console')
include(':mirai-console-terminal')
//include(':mirai-api')
include(':mirai-api-http')
include(':mirai-demos:mirai-demo-1')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment