Commit 4961b604 authored by Him188's avatar Him188

Merge branch 'master' into dev

parents a379ceb3 614d1649
# Version 1.x # Version 1.x
## `1.0.2` 2020/6/1
- 新增 `Bot.botInstancesSequence`
- 修复日志中的时间未更新的问题
- 修复在某些情况下,Bot登录的时候无限重连 (#361)
- 优化一些文档注释
## `1.0.1` 2020/5/25 ## `1.0.1` 2020/5/25
- 新增临时会话消息发送事件: `TempMessageSendEvent` (#338) - 新增临时会话消息发送事件: `TempMessageSendEvent` (#338)
- 新增 `Bot.isOnline` (#342) - 新增 `Bot.isOnline` (#342)
......
# 贡献 # 贡献
感谢你来到这里和你对 mirai 做的所有贡献。 **感谢你来到这里和你对 mirai 做的所有贡献。**
mirai 欢迎一切形式的代码贡献。你可以通过以下几种途径向 mirai 贡献。 mirai 欢迎一切形式的代码贡献。你可以通过以下几种途径向 mirai 贡献。
...@@ -8,21 +8,18 @@ mirai 欢迎一切形式的代码贡献。你可以通过以下几种途径向 m ...@@ -8,21 +8,18 @@ mirai 欢迎一切形式的代码贡献。你可以通过以下几种途径向 m
**阅读文档**[docs/mirai.md](docs/mirai.md) **阅读文档**[docs/mirai.md](docs/mirai.md)
**请基于 `dev` 分支进行除文档以外的修改** ### 能做什么?
**请将文档修改之类的 PR 提交到 `master`, 将其他修改提交到 `dev`** **请基于 `master` 分支进行文档修改; 基于 `dev` 分支进行除其他修改 (否则你的修改可能被关闭或不会立即合并)** (特殊情况除外)
### 代码优化 - 代码优化: 优化任何功能设计或实现, 或是引入一个新的设计(请先通过 issue 与维护者达成共识)
优化功能设计或实现, 或是引入一个新的设计(建议先通过 issue 与我们达成共识) - 解决问题: 在 [issues](https://github.com/mamoe/mirai/issues) 查看 mirai 正遇到的所有问题, 或在 [里程碑](https://github.com/mamoe/mirai/milestones) 查看版本计划
- 协议支持: 添加新协议支持, 但需要避免添加 [mirai.md#声明](docs/mirai.md#L9) 中的内容
### 协议更新
为 mirai 添加更广泛的协议支持。
### 注意事项 ### 注意事项
- mirai 使用 [`kotlinx.io`](https://github.com/Kotlin/kotlinx-io) IO 库
- 尽量不要引用新的库 - 尽量不要引用新的库
- 遵守 Kotlin 代码规范(提交前使用 IDE 格式化代码 (commit 时勾选 'Reformat code')) - 遵守 Kotlin 官方代码规范(提交前使用 IDE 格式化代码 (commit 时勾选 'Reformat code'))
- 不要手动拆解数据包. 请一定使用 `kotlinx.serialization` 拆解 ProtoBuf, 使用 mirai 的 `Jce` 序列化器拆解 Jce 数据包, 使用 `kotlinx.serialization` 拆解 Json 数据. - 不要手动拆解数据包. 请一定使用 `kotlinx.serialization` 拆解 ProtoBuf, 使用 [`jcekt`](https://github.com/him188/jcekt) 拆解 Tars 数据包, 使用 `kotlinx.serialization` 拆解 Json 数据.
## 社区 ## 社区
......
...@@ -77,28 +77,46 @@ mirai 既可以作为项目中的 QQ 协议支持库, 也可以作为单独的 ...@@ -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) 直接编写插件并与其他插件开发者合作共享 基于 `mirai-http-api` (配合 [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 为第三方依赖库引入项目 - `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) Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
...@@ -108,27 +126,12 @@ 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的插件可以在 mirai 中加载, 详见 [Mirai-Native](https://github.com/iTXTech/mirai-native)
- 使用 `酷Q HTTP API` 的插件将可以在 mirai 中加载,`Mirai-CQ-Adapter` 正在进行中 - 使用 `酷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) ## [贡献](CONTRIBUTING.md)
...@@ -141,6 +144,8 @@ Demos: [mirai-demos](https://github.com/mamoe/mirai-demos) ...@@ -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 的授权 特别感谢 [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) [<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) ...@@ -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 This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
......
@file:Suppress("UnstableApiUsage", "UNUSED_VARIABLE") @file:Suppress("UnstableApiUsage", "UNUSED_VARIABLE")
import org.jetbrains.dokka.gradle.DokkaTask
import java.time.Duration import java.time.Duration
import kotlin.math.pow import kotlin.math.pow
...@@ -121,15 +122,15 @@ subprojects { ...@@ -121,15 +122,15 @@ subprojects {
apply(plugin = "org.jetbrains.dokka") apply(plugin = "org.jetbrains.dokka")
this.tasks { this.tasks {
val dokka by getting(org.jetbrains.dokka.gradle.DokkaTask::class) { val dokka by getting(DokkaTask::class) {
outputFormat = "html" outputFormat = "html"
outputDirectory = "$buildDir/dokka" outputDirectory = "$buildDir/dokka"
} }
val dokkaMarkdown by creating(org.jetbrains.dokka.gradle.DokkaTask::class) { val dokkaMarkdown by creating(DokkaTask::class) {
outputFormat = "markdown" outputFormat = "markdown"
outputDirectory = "$buildDir/dokka-markdown" outputDirectory = "$buildDir/dokka-markdown"
} }
val dokkaGfm by creating(org.jetbrains.dokka.gradle.DokkaTask::class) { val dokkaGfm by creating(DokkaTask::class) {
outputFormat = "gfm" outputFormat = "gfm"
outputDirectory = "$buildDir/dokka-gfm" outputDirectory = "$buildDir/dokka-gfm"
} }
...@@ -138,17 +139,20 @@ subprojects { ...@@ -138,17 +139,20 @@ subprojects {
val dokkaGitHubUpload by tasks.creating { val dokkaGitHubUpload by tasks.creating {
group = "mirai" group = "mirai"
dependsOn(tasks.getByName("dokkaGfm")) val dokkaTaskName = "dokka"
dependsOn(tasks.getByName(dokkaTaskName))
doFirst { doFirst {
val baseDir = file("./build/dokka-gfm/${project.name}") val baseDir = file("./build/$dokkaTaskName/${project.name}")
timeout.set(Duration.ofHours(6)) timeout.set(Duration.ofHours(6))
file("build/dokka-gfm/").walk() file("build/$dokkaTaskName/").walk()
.filter { it.isFile } .filter { it.isFile }
.map { old -> .map { old ->
if (old.name == "index.md") File(old.parentFile, "README.md").also { new -> old.renameTo(new) } if (old.name == "index.md") File(old.parentFile, "README.md").also { new -> old.renameTo(new) }
else old else old
} }
// optimize md
.forEach { file -> .forEach { file ->
if (file.endsWith(".md")) { if (file.endsWith(".md")) {
file.writeText( file.writeText(
...@@ -213,7 +217,7 @@ subprojects { ...@@ -213,7 +217,7 @@ subprojects {
} }
afterEvaluate { afterEvaluate {
tasks.filterIsInstance<org.jetbrains.dokka.gradle.DokkaTask>().forEach { task -> tasks.filterIsInstance<DokkaTask>().forEach { task ->
with(task) { with(task) {
configuration { configuration {
perPackageOption { perPackageOption {
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
object Versions { object Versions {
object Mirai { object Mirai {
const val version = "1.0.1" const val version = "1.0.2"
} }
object Kotlin { 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 -> ...@@ -86,7 +86,7 @@ bot.subscribeAlways<GroupMessageEvent> { event ->
if (event.message.content.contains("你好")) { if (event.message.content.contains("你好")) {
reply("你好!") reply("你好!")
} else if (event.message.content.contains("你好")) { } else if (event.message.content.contains("你好")) {
File("C:\\image.png").uploadAsImage() File("C:\\image.png").sendAsImage()
} }
} }
......
...@@ -94,7 +94,7 @@ kotlin { ...@@ -94,7 +94,7 @@ kotlin {
val jvmMain by getting { val jvmMain by getting {
dependencies { dependencies {
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE 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("moe.him188:jcekt:${Versions.jcekt}")
api(kotlinx("serialization-runtime", Versions.Kotlin.serialization)) api(kotlinx("serialization-runtime", Versions.Kotlin.serialization))
//api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization)) //api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization))
......
...@@ -7,13 +7,18 @@ ...@@ -7,13 +7,18 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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 package net.mamoe.mirai.qqandroid
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.closeAndJoin
import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
...@@ -61,6 +66,10 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -61,6 +66,10 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
if (event.bot != this@BotImpl) { if (event.bot != this@BotImpl) {
return@subscribeAlways return@subscribeAlways
} }
if (!event.bot.isActive) {
// bot closed
return@subscribeAlways
}
if (!::_network.isInitialized) { if (!::_network.isInitialized) {
// bot 还未登录就被 close // bot 还未登录就被 close
return@subscribeAlways return@subscribeAlways
...@@ -79,6 +88,7 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -79,6 +88,7 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
} }
bot.logger.info { "Connection dropped by server or lost, retrying login" } bot.logger.info { "Connection dropped by server or lost, retrying login" }
var failed = false
val time = measureTime { val time = measureTime {
tailrec suspend fun reconnect() { tailrec suspend fun reconnect() {
retryCatching<Unit>( retryCatching<Unit>(
...@@ -95,26 +105,30 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -95,26 +105,30 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class) @OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin((event as? BotOfflineEvent.Dropped)?.cause) relogin((event as? BotOfflineEvent.Dropped)?.cause)
} }
BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast() launch { BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast() }
return return
}.getOrElse { }.getOrElse {
if (it is LoginFailedException && !it.killBot) { if (it is LoginFailedException && !it.killBot) {
logger.info { "Cannot reconnect" } logger.info { "Cannot reconnect." }
logger.warning(it) logger.warning(it)
logger.info { "Retrying in 3s..." } logger.info { "Retrying in 3s..." }
delay(3000) delay(3000)
return@getOrElse return@getOrElse
} }
logger.info { "Cannot reconnect" } logger.info { "Cannot reconnect due to fatal error." }
throw it bot.cancel(CancellationException("Cannot reconnect due to fatal error.", it))
failed = true
return
} }
reconnect() reconnect()
} }
reconnect() reconnect()
} }
if (!failed) {
logger.info { "Reconnected successfully in ${time.asHumanReadable}" } logger.info { "Reconnected successfully in ${time.asHumanReadable}" }
} }
}
is BotOfflineEvent.Active -> { is BotOfflineEvent.Active -> {
val cause = event.cause val cause = event.cause
val msg = if (cause == null) { val msg = if (cause == null) {
...@@ -122,12 +136,12 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -122,12 +136,12 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
} else { } else {
" with exception: " + cause.message " with exception: " + cause.message
} }
bot.logger.info { "Bot is closed manually$msg" } bot.logger.info { "Bot is closed manually: $msg" }
closeAndJoin(CancellationException(event.toString())) bot.cancel(CancellationException(event.toString()))
} }
is BotOfflineEvent.Force -> { is BotOfflineEvent.Force -> {
bot.logger.info { "Connection occupied by another android device: ${event.message}" } 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( ...@@ -217,6 +231,8 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
init { init {
coroutineContext[Job]!!.invokeOnCompletion { throwable -> coroutineContext[Job]!!.invokeOnCompletion { throwable ->
logger.info { "Bot cancelled" + throwable?.message?.let { ": $it" }.orEmpty() }
kotlin.runCatching { kotlin.runCatching {
network.close(throwable) network.close(throwable)
} }
...@@ -237,16 +253,19 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor( ...@@ -237,16 +253,19 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
// already cancelled // already cancelled
return return
} }
this.launch { GlobalScope.launch {
BotOfflineEvent.Active(this@BotImpl, cause).broadcast() runCatching { BotOfflineEvent.Active(this@BotImpl, cause).broadcast() }.exceptionOrNull()
?.let { logger.error(it) }
} }
logger.info { "Bot cancelled" + cause?.message?.let { ": $it" }.orEmpty() }
if (supervisorJob.isActive) {
if (cause == null) { if (cause == null) {
supervisorJob.cancel() supervisorJob.cancel()
} else { } else {
supervisorJob.cancel(CancellationException("Bot closed", cause)) supervisorJob.cancel(CancellationException("Bot closed", cause))
} }
} }
}
} }
@RequiresOptIn(level = RequiresOptIn.Level.ERROR) @RequiresOptIn(level = RequiresOptIn.Level.ERROR)
......
...@@ -123,7 +123,6 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo ...@@ -123,7 +123,6 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
channel.close() channel.close()
} }
channel = PlatformSocket() channel = PlatformSocket()
// TODO: 2020/2/14 连接多个服务器, #52
while (isActive) { while (isActive) {
try { try {
......
...@@ -137,7 +137,10 @@ internal open class QQAndroidClient( ...@@ -137,7 +137,10 @@ internal open class QQAndroidClient(
return@retryCatching return@retryCatching
}.getOrElse { }.getOrElse {
bot.client.serverList.remove(pair) bot.client.serverList.remove(pair)
if (it !is LoginFailedException) {
// 不要重复打印.
bot.logger.warning(it) bot.logger.warning(it)
}
throw it throw it
} }
}.getOrElse { }.getOrElse {
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
package net.mamoe.mirai.qqandroid.utils package net.mamoe.mirai.qqandroid.utils
import kotlinx.serialization.Transient
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.debug
import kotlin.reflect.KClass import kotlin.reflect.KClass
...@@ -210,5 +211,13 @@ private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass<out Any> ...@@ -210,5 +211,13 @@ private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass<out Any>
.map { it.members } .map { it.members }
.flatMap { it.asSequence() } .flatMap { it.asSequence() }
.filterIsInstance<KProperty1<*, *>>() .filterIsInstance<KProperty1<*, *>>()
.filterNot { it.hasAnnotation<Transient>() }
.filterNot { it.isTransient() }
.mapNotNull { it as KProperty1<Any, *> } .mapNotNull { it as KProperty1<Any, *> }
} }
@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 package net.mamoe.mirai.qqandroid.utils
import java.lang.reflect.Modifier
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1 import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaField
internal actual fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? { internal actual fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? {
return this.javaField?.apply { isAccessible = true }?.get(receiver) return this.javaField?.apply { isAccessible = true }?.get(receiver)
} }
// 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() } ...@@ -50,7 +50,7 @@ suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
abstract class Bot internal constructor( abstract class Bot internal constructor(
val configuration: BotConfiguration val configuration: BotConfiguration
) : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI, ContactOrBot { ) : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI, ContactOrBot {
final override val coroutineContext: CoroutineContext = final override val coroutineContext: CoroutineContext = // for id
configuration.parentCoroutineContext configuration.parentCoroutineContext
.plus(SupervisorJob(configuration.parentCoroutineContext[Job])) .plus(SupervisorJob(configuration.parentCoroutineContext[Job]))
.plus(configuration.parentCoroutineContext[CoroutineExceptionHandler] .plus(configuration.parentCoroutineContext[CoroutineExceptionHandler]
...@@ -58,6 +58,8 @@ abstract class Bot internal constructor( ...@@ -58,6 +58,8 @@ abstract class Bot internal constructor(
logger.error("An exception was thrown under a coroutine of Bot", e) logger.error("An exception was thrown under a coroutine of Bot", e)
} }
) )
.plus(CoroutineName("Mirai Bot"))
companion object { companion object {
@JvmField @JvmField
......
...@@ -90,7 +90,7 @@ actual open class PlatformLogger @JvmOverloads constructor( ...@@ -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 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 * @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