1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-03-25 06:50:09 +08:00

[console] EndUserReadme

This commit is contained in:
Karlatemp 2023-07-17 07:44:43 +08:00
parent 3763996590
commit d9e3045d65
No known key found for this signature in database
GPG Key ID: BA173CA2B9956C59
9 changed files with 543 additions and 0 deletions
mirai-console/backend
integration-test/src
mirai-console
build.gradle.kts
compatibility-validation/jvm/api
resources/net/mamoe/mirai/console/internal/enduserreadme
src

View File

@ -54,6 +54,7 @@ internal fun main() {
error("Don't launch IntegrationTestBootstrap directly. See /test/MiraiConsoleIntegrationTestBootstrap.kt")
}
}
System.setProperty("mirai.console.skip-end-user-readme", "")
// @context: env.testunit = true
// @context: env.inJUnitProcess = false
// @context: env.exitProcessSafety = true

View File

@ -103,6 +103,10 @@ tasks {
}
}
tasks.withType<Test> {
this.jvmArgs("-Dmirai.console.skip-end-user-readme")
}
tasks.getByName("compileKotlin").dependsOn(
DependencyDumper.registerDumpTaskKtSrc(
project,

View File

@ -1287,6 +1287,29 @@ public final class net/mamoe/mirai/console/data/java/JavaAutoSavePluginData$Comp
public final fun createKType (Ljava/lang/Class;[Lkotlin/reflect/KType;)Lkotlin/reflect/KType;
}
public final class net/mamoe/mirai/console/enduserreadme/EndUserReadme {
public static final field Companion Lnet/mamoe/mirai/console/enduserreadme/EndUserReadme$Companion;
public static final field DELAY Ljava/lang/String;
public static final field PAUSE Ljava/lang/String;
public fun <init> ()V
public final fun put (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public final fun putAll (Ljava/lang/String;)V
}
public final class net/mamoe/mirai/console/enduserreadme/EndUserReadme$Companion {
}
public final class net/mamoe/mirai/console/enduserreadme/EndUserReadme$Render {
public fun <init> ()V
public final fun delay ()V
public final fun delay (I)V
public final fun msg (Ljava/lang/String;)V
public final fun pause ()V
public final fun plusAssign (Ljava/lang/String;)V
public final fun render ()Ljava/lang/String;
public final fun unaryPlus (Ljava/lang/String;)V
}
public abstract class net/mamoe/mirai/console/events/AutoLoginEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/console/events/ConsoleEvent, net/mamoe/mirai/event/events/BotEvent {
}
@ -1302,6 +1325,10 @@ public final class net/mamoe/mirai/console/events/AutoLoginEvent$Success : net/m
public abstract interface class net/mamoe/mirai/console/events/ConsoleEvent : net/mamoe/mirai/event/Event {
}
public final class net/mamoe/mirai/console/events/EndUserReadmeInitializeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/console/events/ConsoleEvent {
public final fun getReadme ()Lnet/mamoe/mirai/console/enduserreadme/EndUserReadme;
}
public final class net/mamoe/mirai/console/events/StartupEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/console/events/ConsoleEvent {
public final fun getTimestamp ()J
}

View File

@ -0,0 +1,129 @@
::mirai-console.greeting
欢迎使用 mirai-console。
在您正式开始使用 mirai-console 前,您需要完整阅读此用户须知。
此用户须知包含 mirai-console 本体及其所安装的插件的用户须知。
当相关的最终用户须知更新时mirai-console 只会显示已更新部分,而不会重新完整显示整个用户须知。
::mirai-console.usage
在使用 mirai-console 前,您需要完整阅读用户手册。
<delay>2
用户手册地址:
GitHub: https://github.com/mamoe/mirai/blob/dev/docs/UserManual.md
VuePress: https://docs.mirai.mamoe.net/UserManual.html
<delay>3
当您遇到问题前,请先查阅
<delay>2
常见问题参考: https://docs.mirai.mamoe.net/Questions.html
<delay>1
mirai 历史问题提问: https://github.com/mamoe/mirai/issues?q=is%3Aissue
<delay>3
如果您使用的 mirai-console 来自一个单独整合包,您需要参考该整合包内的 `readme` 文件
::mirai-console.issuing
在使用 mirai-console 的过程中,您可能会遇到各种问题。
在您向他人咨询前,您需要做好以下准备。
<delay>2
无论是
<delay>2
`- 在 mirai 主仓库发起 issue
<delay>1
`- 在 mirai 论坛发起帖子
<delay>1
`- 在群聊向他人咨询
<delay>1
`- 在私聊向他人咨询
<delay>1
`- 或者更多
<delay>1
您都需要做好以下准备。
<delay>1
这不仅能让您更快解决问题,也是对被询问者的尊重。
<delay>1
1. 说明您正在使用的版本
<delay>2
版本号是确定问题的关键信息,
<delay>1
mirai-console 的版本号会在 mirai-console 运行时就打印至控制台。
其他组件版本可以通过执行 /status 命令获取
<delay>3
2. 携带报错信息 / 携带日志
<delay>3
报错信息是分析问题的关键,没有日志相当于闭眼开车。
<delay>3
当您咨询时,一定要携带当时的日志
<delay>3
「没有日志我能做的事只有帮你算一卦」
<delay>3
标准的咨询模板参考:
https://github.com/mamoe/mirai/issues/new?template=bug.yml
::mirai-core.EncryptService.alert
Reference: https://github.com/mamoe/mirai/releases/tag/v2.15.0
关于包数据加密 / 签名 Internal(#2716)
<delay>2
mirai 不会内置任何第三方 签名/加密 服务,而是提供 SPI 让用户自行实现。
<delay>2
mirai 已经提供了外部 EncryptService SPI 供用户对接。如果您没有能力自行对接,您可以考虑到论坛寻找社区对接。
<delay>2
在使用社区服务前,您需要了解并理解以下内容
<delay>2
<pause>
1. 确认服务来源
<delay>2
当您安装此服务后,所有的信息都会经过此消息服务。
<delay>2
这其中包括
Bot 的登录请求(包含密码,登录凭证等)
<delay>2
Bot 发出去的所有信息
<delay>2
更多.....
<delay>2
<pause>
2. 保护好网络,建立通讯防火墙
<delay>2
部分服务通讯链路是无加密的
<delay>1
如果您访问的服务位于公开网络,您的数据有被窃取、拦截的风险。
<delay>2
<pause>
3. 保护好日志。
<delay>2
并非所有日志都能直接传递给他人
<pause>
在您公开您的日志前,请先对日志中的关键信息进行抹除。
<pause>
部分相关服务使用 HTTP GET 请求传递数据体,
当远程服务出错时,服务对接可能会直接将此次请求的连接直接输出到日志中,
此日志可能包含了此次尝试 签名/加密 的内容,
而此内容可能包含关键信息。
<pause>
如果您无法分辨哪些请求需要被抹除时,您可以参考以下规则:
<pause>
请求连接包含大量 Hex 文本,抹除 Hex: 由 0-9 和 ABCDEF 组成的序列
<delay>2
<pause>
请求包含大量 Base64 文本,抹除 (如您不知道什么是 Base64 文本,您可以简单当做是超长的英文与符号组合)
<delay>2
<pause>
请求连接过长,抹除(如连接日志换行了三次都还没有显示完全)
<delay>2
<pause>

View File

@ -0,0 +1,152 @@
/*
* Copyright 2019-2023 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/dev/LICENSE
*/
package net.mamoe.mirai.console.enduserreadme
import java.util.*
/**
* 最终用户须知
*
* @since 2.16.0
*/
public class EndUserReadme {
public companion object {
public const val PAUSE: String = "<pause>"
public const val DELAY: String = "<delay>"
}
internal val pages: MutableMap<String, String> = linkedMapOf()
public class Render {
private val msgs = mutableListOf<String>()
@KeepDetermination
public operator fun String.unaryPlus() {
msg(this)
}
@KeepDetermination
public operator fun plusAssign(s: String) {
msg(s)
}
@KeepDetermination
public fun pause() {
msg(PAUSE)
}
@KeepDetermination
public fun delay() {
msg(DELAY)
}
/**
* 延迟一段时间
*
* @param time 单位
*/
@KeepDetermination
public fun delay(time: Int) {
msg(DELAY + time)
}
@KeepDetermination
public fun msg(message: String) {
msgs.add(message)
}
public fun render(): String = msgs.joinToString(separator = "\n")
}
@KeepDetermination
public fun put(category: String, render: Render.() -> Unit) {
pages[category] = Render().also(render).render()
}
/**
* 同时添加多个须知定义
*
* 格式:
* ```text
*
* ::category.c1
*
* Here is c1
*
* delay 2s
* <delay>2
*
* paused
* <pause>
*
* ::category.c1
*
* Here is c2
*
* ```
*/
@KeepDetermination
public fun putAll(fullText: String) {
if (fullText.isBlank()) return
val lines = LinkedList(fullText.lines())
var category: String
val buffer = mutableListOf<String>()
while (true) {
if (lines.isEmpty()) return
val rm = lines.removeFirst()
if (rm.isBlank()) continue
if (rm.startsWith("::")) {
category = rm.substring(2)
break
}
throw IllegalArgumentException("First non-empty line must be category define: $rm")
}
fun flush() {
while (buffer.isNotEmpty()) {
if (buffer.first().isBlank()) {
buffer.removeAt(0)
continue
}
break
}
while (buffer.isNotEmpty()) {
if (buffer.last().isBlank()) {
buffer.removeAt(buffer.lastIndex)
continue
}
break
}
pages[category] = buffer.joinToString(separator = "\n")
buffer.clear()
}
while (lines.isNotEmpty()) {
val rm = lines.removeFirst()
if (rm.startsWith("::")) {
flush()
category = rm.substring(2)
continue
}
buffer.add(rm)
}
flush()
}
@DslMarker
private annotation class KeepDetermination
}

View File

@ -0,0 +1,18 @@
/*
* Copyright 2019-2023 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/dev/LICENSE
*/
package net.mamoe.mirai.console.events
import net.mamoe.mirai.console.enduserreadme.EndUserReadme
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.utils.MiraiInternalApi
public class EndUserReadmeInitializeEvent @MiraiInternalApi constructor(
public val readme: EndUserReadme,
) : ConsoleEvent, AbstractEvent()

View File

@ -46,6 +46,7 @@ import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.Pa
import net.mamoe.mirai.console.internal.data.builtins.DataScope
import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig
import net.mamoe.mirai.console.internal.data.builtins.PluginDependenciesConfig
import net.mamoe.mirai.console.internal.enduserreadme.EndUserReadmeProcessor
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorageImpl
import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl
@ -365,6 +366,10 @@ ___ ____ _ _____ _
mainLogger.info { "${pluginManager.plugins.count { it.isEnabled }} plugin(s) enabled." }
}
phase("end-user-readme") {
EndUserReadmeProcessor.process(this)
}
phase("auto-login bots") {
runBlocking {
val config = DataScope.get<AutoLoginConfig>()

View File

@ -0,0 +1,32 @@
/*
* Copyright 2019-2023 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/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.data.builtins
import net.mamoe.mirai.console.data.PluginDataHolder
import net.mamoe.mirai.console.data.PluginDataStorage
import net.mamoe.mirai.console.data.ReadOnlyPluginConfig
import net.mamoe.mirai.console.data.value
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
@OptIn(ConsoleExperimentalApi::class)
internal class EndUserReadmeData : ReadOnlyPluginConfig("EndUserReadme") {
val data: MutableMap<String, String> by value()
private lateinit var storage_: PluginDataStorage
private lateinit var owner_: PluginDataHolder
override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) {
this.storage_ = storage
this.owner_ = owner
}
internal fun saveNow() {
storage_.store(owner_, this)
}
}

View File

@ -0,0 +1,175 @@
/*
* Copyright 2019-2023 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/dev/LICENSE
*/
package net.mamoe.mirai.console.internal.enduserreadme
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import net.mamoe.mirai.console.ConsoleFrontEndImplementation
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.enduserreadme.EndUserReadme
import net.mamoe.mirai.console.events.EndUserReadmeInitializeEvent
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
import net.mamoe.mirai.console.internal.data.builtins.EndUserReadmeData
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.sendAnsiMessage
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.sha256
import net.mamoe.mirai.utils.toUHexString
import java.io.File
import java.net.InetAddress
internal object EndUserReadmeProcessor {
private val PADDING = "=".repeat(100)
private fun StringBuilder.pad(size: Int) {
var size0 = size
while (size0 > 0) {
val padded = size0.coerceAtMost(PADDING.length)
append(PADDING, 0, padded)
size0 -= padded
}
}
private fun header(title: String): String {
val padding = 100 - title.length
val lpadding = padding / 2
val rpadding = padding - lpadding
return buildString {
pad(lpadding)
append(" [ ").append(title).append(" ] ")
pad(rpadding)
}
}
private val systemDefaultNames = hashSetOf<String>(
"ubuntu", "debian", "arch",
"centos", "fedora", "localhost",
)
private fun getComputerName(): String {
System.getenv("COMPUTERNAME")?.takeUnless(String::isBlank)?.let { return it }
System.getenv("HOSTNAME")?.takeUnless(String::isBlank)?.let { return it }
runCatching {
InetAddress.getLocalHost().hostName
?.takeIf { it.lowercase() !in systemDefaultNames }
?.takeUnless(String::isBlank)
?.let { return it }
}
runCatching {
File("/etc/machine-id").readText().takeUnless(String::isBlank)?.let { return it.trim() }
}
return "Unknown Computer"
}
@OptIn(MiraiInternalApi::class, ConsoleFrontEndImplementation::class)
fun process(console: MiraiConsoleImplementationBridge) {
if (System.getenv("CI") == "true") return
if (System.getProperty("mirai.console.skip-end-user-readme") in listOf<String?>("", "true", "yes")) return
val pcName = getComputerName()
val dataObject = EndUserReadmeData()
console.consoleDataScope.addAndReloadConfig(dataObject)
runBlocking {
val readme = EndUserReadme()
runCatching {
EndUserReadmeProcessor::class.java.getResourceAsStream("readme.txt")?.bufferedReader()?.use {
readme.putAll(it.readText())
}
}.onFailure { console.mainLogger.error(it) }
EndUserReadmeInitializeEvent(readme).broadcast()
// region Remove already read
val pcNameBCode = pcName.toByteArray()
var changed = false
readme.pages.asSequence().map { (key, value) ->
return@map key to value.sha256()
}.onEach { (_, hash) ->
for (i in hash.indices) {
hash[i] = hash[i].toInt().xor(pcNameBCode[i % pcNameBCode.size].toInt()).toByte()
}
}.map { (k, v) ->
return@map k to v.toUHexString()
}.toList().forEach { (key, hash) ->
if (dataObject.data[key] == hash) {
readme.pages.remove(key)
} else {
dataObject.data[key] = hash
changed = true
}
}
// endregion
suspend fun wait(seconds: Int) {
if (seconds < 1) return
var printWaiting = true
repeat(seconds) { counter ->
val suffix = (seconds - counter).toString() + "s"
withTimeoutOrNull(1000L) {
if (printWaiting) {
ConsoleInput.requestInput("Please wait $suffix...")
printWaiting = false
}
while (true) {
ConsoleInput.requestInput("Please read before continuing ($suffix)")
}
}
}
}
suspend fun pause() {
ConsoleInput.requestInput("Enter to continue")
}
if (readme.pages.isNotEmpty()) {
listOf(
header("End User Readme"),
"最终用户须知有更新,在您继续使用前,您必须完整阅读新的用户须知。",
).forEach { ConsoleCommandSender.sendMessage(it) }
}
readme.pages.forEach { (category, message) ->
ConsoleCommandSender.sendMessage(header(category))
message.lines().forEach { command ->
val ctrim = command.trim()
if (ctrim == EndUserReadme.PAUSE) {
pause()
} else if (ctrim == EndUserReadme.DELAY) {
wait(3)
} else if (ctrim.startsWith(EndUserReadme.DELAY)) {
wait(ctrim.removePrefix(EndUserReadme.DELAY).trim().toIntOrNull() ?: 3)
} else {
ConsoleCommandSender.sendAnsiMessage(command)
}
}
wait(3)
pause()
}
if (changed) {
dataObject.saveNow()
}
}
}
}