Commit 4961b604 authored by Him188's avatar Him188

Merge branch 'master' into dev

parents a379ceb3 614d1649
# Version 1.x
## `1.0.2` 2020/6/1
- 新增 `Bot.botInstancesSequence`
- 修复日志中的时间未更新的问题
- 修复在某些情况下,Bot登录的时候无限重连 (#361)
- 优化一些文档注释
## `1.0.1` 2020/5/25
- 新增临时会话消息发送事件: `TempMessageSendEvent` (#338)
- 新增 `Bot.isOnline` (#342)
......
# 贡献
感谢你来到这里和你对 mirai 做的所有贡献。
**感谢你来到这里和你对 mirai 做的所有贡献。**
mirai 欢迎一切形式的代码贡献。你可以通过以下几种途径向 mirai 贡献。
......@@ -8,23 +8,20 @@ mirai 欢迎一切形式的代码贡献。你可以通过以下几种途径向 m
**阅读文档**[docs/mirai.md](docs/mirai.md)
**请基于 `dev` 分支进行除文档以外的修改**
### 能做什么?
**请将文档修改之类的 PR 提交到 `master`, 将其他修改提交到 `dev`**
**请基于 `master` 分支进行文档修改; 基于 `dev` 分支进行除其他修改 (否则你的修改可能被关闭或不会立即合并)** (特殊情况除外)
### 代码优化
优化功能设计或实现, 或是引入一个新的设计(建议先通过 issue 与我们达成共识)
### 协议更新
为 mirai 添加更广泛的协议支持。
- 代码优化: 优化任何功能设计或实现, 或是引入一个新的设计(请先通过 issue 与维护者达成共识)
- 解决问题: 在 [issues](https://github.com/mamoe/mirai/issues) 查看 mirai 正遇到的所有问题, 或在 [里程碑](https://github.com/mamoe/mirai/milestones) 查看版本计划
- 协议支持: 添加新协议支持, 但需要避免添加 [mirai.md#声明](docs/mirai.md#L9) 中的内容
### 注意事项
- mirai 使用 [`kotlinx.io`](https://github.com/Kotlin/kotlinx-io) IO 库
- 尽量不要引用新的库
- 遵守 Kotlin 代码规范(提交前使用 IDE 格式化代码 (commit 时勾选 'Reformat code'))
- 不要手动拆解数据包. 请一定使用 `kotlinx.serialization` 拆解 ProtoBuf, 使用 mirai 的 `Jce` 序列化器拆解 Jce 数据包, 使用 `kotlinx.serialization` 拆解 Json 数据.
- 遵守 Kotlin 官方代码规范(提交前使用 IDE 格式化代码 (commit 时勾选 'Reformat code'))
- 不要手动拆解数据包. 请一定使用 `kotlinx.serialization` 拆解 ProtoBuf, 使用 [`jcekt`](https://github.com/him188/jcekt) 拆解 Tars 数据包, 使用 `kotlinx.serialization` 拆解 Json 数据.
## 社区
插件社区不要求太高的代码质量,任何人都可以帮助 mirai。
插件社区不要求太高的代码质量,任何人都可以帮助 mirai。
可以为 [mirai-console](https://github.com/mamoe/mirai-console) 编写插件, 并发布到社区网站: (建设中)
......@@ -77,28 +77,46 @@ mirai 既可以作为项目中的 QQ 协议支持库, 也可以作为单独的
## 开始
### 文档
- **开发文档**[docs/mirai.md](docs/mirai.md)
- **常见问题**: [docs/FAQ.md](docs/FAQ.md)
- **更新日志**: [CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md)[release](https://github.com/mamoe/mirai/releases)
- **开发计划**: [milestones](https://github.com/mamoe/mirai/milestones)
- **贡献**: [CONTRIBUTING](CONTRIBUTING.md)
### 开发者
#### **开发文档**: [docs/mirai.md](docs/mirai.md)
#### 使用 mirai-console 服务端,为 mirai-console 开发插件
官方支持 SDK 列表:
- `Java`, `Kotlin` 等 JVM 语言: 为 [mirai-console](https://github.com/mamoe/mirai-console) 直接编写插件并与其他插件开发者合作共享
- `Kotlin Script`[mirai-kts](https://github.com/iTXTech/mirai-kts) 支持使用 `kts` 编写插件,享受 `Kotlin` 带来的一切便利(**仅 OpenJDK 8 以上环境,不支持 Android**
- `C`, `C++` 等原生语言: [mirai-native](https://github.com/iTXTech/mirai-native) 支持酷 Q 插件在 mirai 上运行 **(仅限 `Windows 32 位 JRE`/支持 `Wine`)**
- `JavaScript`[mirai-js](https://github.com/iTXTech/mirai-js) 支持使用 `JavaScript` 编写插件并**直接**与 mirai 交互
- *Http*:使用由 [mirai-api-http](https://github.com/mamoe/mirai-api-http) 提供的 http 接口进行接入
<details>
<summary>社区支持的 SDK 列表</summary>
基于 `mirai-core` (独立使用):
- `Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) 基于 mirai-core 的 Lua SDK,并提供了 Java 扩展支持,可在 Lua 中调用 Java 代码开发机器人
#### 使用 mirai 作为服务器,为 mirai 开发插件
- (官方)`Java``Kotlin`: 为 [mirai-console](https://github.com/mamoe/mirai-console) 直接编写插件并与其他插件开发者合作共享
- (官方)`Kotlin Script`[mirai-kts](https://github.com/iTXTech/mirai-kts) 支持使用`kts`编写插件,享受`Kotlin`带来的一切便利(**仅支持OpenJDK 8以上环境,不支持Android**
- (官方)`C`, `C++` 等原生语言: [mirai-native](https://github.com/iTXTech/mirai-native) 支持酷Q插件在mirai上运行 **(仅限`Windows 32位JRE`/支持`Wine`)**
- (官方)`JavaScript`[mirai-js](https://github.com/iTXTech/mirai-js) 支持使用`JavaScript`编写插件并**直接**`Mirai`交互
- (社区)`Python`: [python-mirai](https://github.com/NatriumLab/python-mirai) 基于 `mirai-api-http` 的机器人开发框架
- (社区)`JavaScript`(`Node.js`): [node-mirai](https://github.com/RedBeanN/node-mirai) mirai 的 Node.js SDK
- (社区)`Go`: [gomirai](https://github.com/Logiase/gomirai) 基于 mirai-api-http 的 GoLang SDK
- (社区)`Mozilla Rhino`: [mirai-rhinojs-sdk](https://github.com/StageGuard/mirai-rhinojs-sdk) 为基于 Rhino(如 Auto.js 等安卓 app 或运行环境)的 JavaScript 提供简单易用的 SDK
- (社区)`Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) 基于mirai-core 的Lua SDK,并提供了java扩展支持,可在lua中调用java代码开发机器人
- (社区)`C++`: [mirai-cpp](https://github.com/cyanray/mirai-cpp) mirai-http-api 的 C++ 封装,方便使用 C++ 开发 mirai-http-api 插件
- (社区)`C++`: [miraipp](https://github.com/Chlorie/miraipp-template) mirai-http-api 的另一个 C++ 封装,使用现代 C++ 特性,并提供了较完善的说明文档
- (社区)`C#`: [Mirai-CSharp](https://github.com/Executor-Cheng/Mirai-CSharp) 基于 mirai-api-http 的 C# SDK
- (社区)`Rust`: [mirai-rs](https://github.com/HoshinoTented/mirai-rs) mirai-http-api 的 Rust 封装
- (官方)其他任意语言:使用由 [mirai-api-http](https://github.com/mamoe/mirai-api-http) 提供的 http 接口进行接入
基于 `mirai-http-api` (配合 [mirai-console](https://github.com/mamoe/mirai-console)):
#### 使用 mirai 为第三方依赖库引入项目
- `Python`: [python-mirai](https://github.com/NatriumLab/python-mirai) 基于 `mirai-api-http` 的机器人开发框架
- `JavaScript`(`Node.js`): [node-mirai](https://github.com/RedBeanN/node-mirai) mirai 的 Node.js SDK
- `Go`: [gomirai](https://github.com/Logiase/gomirai) 基于 mirai-api-http 的 GoLang SDK
- `Mozilla Rhino`: [mirai-rhinojs-sdk](https://github.com/StageGuard/mirai-rhinojs-sdk) 为基于 Rhino(如 Auto.js 等安卓 app 或运行环境)的 JavaScript 提供简单易用的 SDK
- `C++`: [mirai-cpp](https://github.com/cyanray/mirai-cpp) mirai-http-api 的 C++ 封装,方便使用 C++ 开发 mirai-http-api 插件
- `C++`: [miraipp](https://github.com/Chlorie/miraipp-template) mirai-http-api 的另一个 C++ 封装,使用现代 C++ 特性,并提供了较完善的说明文档
- `C#`: [Mirai-CSharp](https://github.com/Executor-Cheng/Mirai-CSharp) 基于 mirai-api-http 的 C# SDK
- `Rust`: [mirai-rs](https://github.com/HoshinoTented/mirai-rs) mirai-http-api 的 Rust 封装
</details>
#### 使用 mirai-core 为第三方依赖库引入项目
Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
......@@ -108,27 +126,12 @@ Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
### 使用者
- [mirai-console](https://github.com/mamoe/mirai-console) 支持插件 **本模块正在完善**
- [mirai-console](https://github.com/mamoe/mirai-console) 支持插件的控制台服务端,支持PC和Android平台 **本模块正在开发中**
### 其他平台的使用者
#### 从其他平台迁移
- 酷Q的插件可以在 mirai 中加载, 详见 [Mirai-Native](https://github.com/iTXTech/mirai-native)
- 使用 `酷Q HTTP API` 的插件将可以在 mirai 中加载,`Mirai-CQ-Adapter` 正在进行中
- [MiraiAndroid](https://github.com/mzdluo123/MiraiAndroid) 让mirai-console的插件在Android运行 **实验性**
## 我想马上开始使用它
请下载这里的一键安装包[下载地址](https://suihou-my.sharepoint.com/:f:/g/personal/user18_5tb_site/ErWGr97FpPVDjkboIDmDAJkBID-23ZMNbTPggGajf1zvGw?e=51NZWM),它可以让你快速在你的服务器/个人电脑上运行mirai-console
**请注意**
* 使用时请留意安装包里的说明文字
* 目前本安装包只支持Windows系统,且mirai-console仍在开发中,可能会存在一些bug
* 关于安装包本身的一切问题请到QQ群内反馈
* 如果上面的链接下载过慢,你可以到QQ群内高速下载
## 更新日志
*[CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md) 查看版本更新记录 (仅发布的版本)
## [贡献](CONTRIBUTING.md)
......@@ -141,6 +144,8 @@ Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
## 鸣谢
> IntelliJ IDEA 是一个在各个方面都最大程度地提高开发人员的生产力的 IDE, 适用于 JVM 平台语言。
特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai) 为开源项目提供免费的 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai) 等 IDE 的授权
[<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai)
......@@ -157,7 +162,7 @@ Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
------
Copyright (C) 2019-2020 mamoe and Mirai contributors
Copyright (C) 2019-2020 Mamoe Technologies and mirai contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
......
@file:Suppress("UnstableApiUsage", "UNUSED_VARIABLE")
import org.jetbrains.dokka.gradle.DokkaTask
import java.time.Duration
import kotlin.math.pow
......@@ -121,15 +122,15 @@ subprojects {
apply(plugin = "org.jetbrains.dokka")
this.tasks {
val dokka by getting(org.jetbrains.dokka.gradle.DokkaTask::class) {
val dokka by getting(DokkaTask::class) {
outputFormat = "html"
outputDirectory = "$buildDir/dokka"
}
val dokkaMarkdown by creating(org.jetbrains.dokka.gradle.DokkaTask::class) {
val dokkaMarkdown by creating(DokkaTask::class) {
outputFormat = "markdown"
outputDirectory = "$buildDir/dokka-markdown"
}
val dokkaGfm by creating(org.jetbrains.dokka.gradle.DokkaTask::class) {
val dokkaGfm by creating(DokkaTask::class) {
outputFormat = "gfm"
outputDirectory = "$buildDir/dokka-gfm"
}
......@@ -138,17 +139,20 @@ subprojects {
val dokkaGitHubUpload by tasks.creating {
group = "mirai"
dependsOn(tasks.getByName("dokkaGfm"))
val dokkaTaskName = "dokka"
dependsOn(tasks.getByName(dokkaTaskName))
doFirst {
val baseDir = file("./build/dokka-gfm/${project.name}")
val baseDir = file("./build/$dokkaTaskName/${project.name}")
timeout.set(Duration.ofHours(6))
file("build/dokka-gfm/").walk()
file("build/$dokkaTaskName/").walk()
.filter { it.isFile }
.map { old ->
if (old.name == "index.md") File(old.parentFile, "README.md").also { new -> old.renameTo(new) }
else old
}
// optimize md
.forEach { file ->
if (file.endsWith(".md")) {
file.writeText(
......@@ -213,7 +217,7 @@ subprojects {
}
afterEvaluate {
tasks.filterIsInstance<org.jetbrains.dokka.gradle.DokkaTask>().forEach { task ->
tasks.filterIsInstance<DokkaTask>().forEach { task ->
with(task) {
configuration {
perPackageOption {
......
......@@ -9,7 +9,7 @@
object Versions {
object Mirai {
const val version = "1.0.1"
const val version = "1.0.2"
}
object Kotlin {
......
# FAQ
## 使用相关
### 如何使用 mirai?
GitHub 上 mamoe/mirai(你现在所在项目)是 mirai 的核心,而若要独立启动 mirai 需要 [mamoe/mirai-console](https://github.com/mamoe/mirai-console) 的帮助。
为了使启动更方便,版本更新更顺滑,mirai 提供 [mirai-console 启动器: mamoe/mirai-console-wrapper](https://github.com/mamoe/mirai-console-wrapper)
**注意:** mirai-console 与 mirai-console-wrapper 都处于实验性阶段,任何功能都不具有稳定性。
### mirai 稳定性如何?适用于哪些工作?
mirai 已经可以用于各项工作,但我们并不保证完全的稳定。在使用时遇到问题请通过 issue 告诉我们。
在使用 mirai 时,使用者应已经清楚,并且自愿承担所有可能产生的后果,如批量恶意发布广告、滥用富文本消息等功能而导致的账号封禁;进行违法活动而产生的法律责任。
### mirai 与酷 Q 等热门机器人程序有什么区别?
- mirai 完全开源且免费;
- mirai 使用 Kotlin,运行在 JVM,可以全平台使用;
- mirai 没有宣称稳定性,不保证长久的维护;
#### 如何从其他机器人框架迁移到 mirai ?
如果你在使用其他机器人框架时没有遇到问题,我们**非常不建议**迁移到 mirai。
### mirai 有哪些系列项目?
阅读 [项目整体架构](mirai.md#%E9%A1%B9%E7%9B%AE%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84).
**警告:** mirai 开发者自愿花费其休息时间,无偿维护 mirai 系列项目,**但没有义务提供任何方面的帮助**
使用者应秉持对开发者无私贡献的尊重,而不应该期望得到帮助。
## 开发相关
> 欢迎帮助维护这个问题列表: 提交 PR 或者 issue.
... 待补充
\ No newline at end of file
......@@ -86,7 +86,7 @@ bot.subscribeAlways<GroupMessageEvent> { event ->
if (event.message.content.contains("你好")) {
reply("你好!")
} else if (event.message.content.contains("你好")) {
File("C:\\image.png").uploadAsImage()
File("C:\\image.png").sendAsImage()
}
}
......
......@@ -94,7 +94,7 @@ kotlin {
val jvmMain by getting {
dependencies {
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
// api(kotlinx("coroutines-debug", "1.3.5"))
// api(kotlinx("coroutines-debug", Versions.Kotlin.coroutines))
api("moe.him188:jcekt:${Versions.jcekt}")
api(kotlinx("serialization-runtime", Versions.Kotlin.serialization))
//api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization))
......
......@@ -7,13 +7,18 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR", "OverridingDeprecatedMember", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
@file:Suppress(
"EXPERIMENTAL_API_USAGE",
"DEPRECATION_ERROR",
"OverridingDeprecatedMember",
"INVISIBLE_REFERENCE",
"INVISIBLE_MEMBER"
)
package net.mamoe.mirai.qqandroid
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.closeAndJoin
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotOfflineEvent
......@@ -61,6 +66,10 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
if (event.bot != this@BotImpl) {
return@subscribeAlways
}
if (!event.bot.isActive) {
// bot closed
return@subscribeAlways
}
if (!::_network.isInitialized) {
// bot 还未登录就被 close
return@subscribeAlways
......@@ -79,6 +88,7 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
}
bot.logger.info { "Connection dropped by server or lost, retrying login" }
var failed = false
val time = measureTime {
tailrec suspend fun reconnect() {
retryCatching<Unit>(
......@@ -95,25 +105,29 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin((event as? BotOfflineEvent.Dropped)?.cause)
}
BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast()
launch { BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast() }
return
}.getOrElse {
if (it is LoginFailedException && !it.killBot) {
logger.info { "Cannot reconnect" }
logger.info { "Cannot reconnect." }
logger.warning(it)
logger.info { "Retrying in 3s..." }
delay(3000)
return@getOrElse
}
logger.info { "Cannot reconnect" }
throw it
logger.info { "Cannot reconnect due to fatal error." }
bot.cancel(CancellationException("Cannot reconnect due to fatal error.", it))
failed = true
return
}
reconnect()
}
reconnect()
}
logger.info { "Reconnected successfully in ${time.asHumanReadable}" }
if (!failed) {
logger.info { "Reconnected successfully in ${time.asHumanReadable}" }
}
}
is BotOfflineEvent.Active -> {
val cause = event.cause
......@@ -122,12 +136,12 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
} else {
" with exception: " + cause.message
}
bot.logger.info { "Bot is closed manually$msg" }
closeAndJoin(CancellationException(event.toString()))
bot.logger.info { "Bot is closed manually: $msg" }
bot.cancel(CancellationException(event.toString()))
}
is BotOfflineEvent.Force -> {
bot.logger.info { "Connection occupied by another android device: ${event.message}" }
closeAndJoin(ForceOfflineException(event.toString()))
bot.cancel(ForceOfflineException(event.toString()))
}
}
}
......@@ -217,6 +231,8 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
init {
coroutineContext[Job]!!.invokeOnCompletion { throwable ->
logger.info { "Bot cancelled" + throwable?.message?.let { ": $it" }.orEmpty() }
kotlin.runCatching {
network.close(throwable)
}
......@@ -237,14 +253,17 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
// already cancelled
return
}
this.launch {
BotOfflineEvent.Active(this@BotImpl, cause).broadcast()
GlobalScope.launch {
runCatching { BotOfflineEvent.Active(this@BotImpl, cause).broadcast() }.exceptionOrNull()
?.let { logger.error(it) }
}
logger.info { "Bot cancelled" + cause?.message?.let { ": $it" }.orEmpty() }
if (cause == null) {
supervisorJob.cancel()
} else {
supervisorJob.cancel(CancellationException("Bot closed", cause))
if (supervisorJob.isActive) {
if (cause == null) {
supervisorJob.cancel()
} else {
supervisorJob.cancel(CancellationException("Bot closed", cause))
}
}
}
}
......
......@@ -123,7 +123,6 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
channel.close()
}
channel = PlatformSocket()
// TODO: 2020/2/14 连接多个服务器, #52
while (isActive) {
try {
......
......@@ -137,7 +137,10 @@ internal open class QQAndroidClient(
return@retryCatching
}.getOrElse {
bot.client.serverList.remove(pair)
bot.logger.warning(it)
if (it !is LoginFailedException) {
// 不要重复打印.
bot.logger.warning(it)
}
throw it
}
}.getOrElse {
......
......@@ -11,6 +11,7 @@
package net.mamoe.mirai.qqandroid.utils
import kotlinx.serialization.Transient
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.debug
import kotlin.reflect.KClass
......@@ -210,5 +211,13 @@ private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass<out Any>
.map { it.members }
.flatMap { it.asSequence() }
.filterIsInstance<KProperty1<*, *>>()
.filterNot { it.hasAnnotation<Transient>() }
.filterNot { it.isTransient() }
.mapNotNull { it as KProperty1<Any, *> }
}
\ No newline at end of file
}
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal expect inline fun <reified T : Annotation> KProperty<*>.hasAnnotation(): Boolean
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
internal expect fun KProperty<*>.isTransient(): Boolean
\ No newline at end of file
package net.mamoe.mirai.qqandroid.utils
import java.lang.reflect.Modifier
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.jvm.javaField
internal actual fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? {
return this.javaField?.apply { isAccessible = true }?.get(receiver)
}
\ No newline at end of file
}
// on JVM, it will be resolved to member function
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
actual internal inline fun <reified T : Annotation> KProperty<*>.hasAnnotation(): Boolean =
findAnnotation<T>() != null
// on JVM, it will be resolved to member function
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
actual internal fun KProperty<*>.isTransient(): Boolean =
javaField?.modifiers?.and(Modifier.TRANSIENT) != 0
......@@ -50,7 +50,7 @@ suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
abstract class Bot internal constructor(
val configuration: BotConfiguration
) : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI, ContactOrBot {
final override val coroutineContext: CoroutineContext =
final override val coroutineContext: CoroutineContext = // for id
configuration.parentCoroutineContext
.plus(SupervisorJob(configuration.parentCoroutineContext[Job]))
.plus(configuration.parentCoroutineContext[CoroutineExceptionHandler]
......@@ -58,6 +58,8 @@ abstract class Bot internal constructor(
logger.error("An exception was thrown under a coroutine of Bot", e)
}
)
.plus(CoroutineName("Mirai Bot"))
companion object {
@JvmField
......
......@@ -90,7 +90,7 @@ actual open class PlatformLogger @JvmOverloads constructor(
}
private val timeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.SIMPLIFIED_CHINESE)
private val currentTimeFormatted = timeFormat.format(Date())
private val currentTimeFormatted get() = timeFormat.format(Date())
/**
* @author NaturalHG
......
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