diff --git a/CHANGELOG.md b/CHANGELOG.md index fe56aac85..b34647d2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,12 @@ 开发版本. 频繁更新, 不保证高稳定性 -## `0.13.0` *On the way* +## `0.13.0` 2020/2/12 ### mirai-core 1. 修改 BotFactory, 添加 `context` 参数. +2. currentTimeMillis 减少不必要对象创建 +3. 优化无锁链表性能 (大幅提升 `addAll` 性能) ### mirai-core-qqanroid 安卓协议发布, 基于最新 QQ, 版本 `8.2.0` @@ -14,7 +16,8 @@ - 消息: 文字消息, 图片消息(含表情消息), 群员 At. - 列表: 群列表, 群员列表, 好友列表均已稳定. - 群操作: 查看和修改群名, 查看和修改群属性(含全体禁言, 坦白说, 自动批准加入, 匿名聊天, 允许成员拉人), 设置和解除成员禁言, 查看和修改成员名片, 踢出成员. -- 事件: 接受群消息和好友消息并解析 +- 消息事件: 接受群消息和好友消息并解析 +- 群事件: 群员加入, 群员离开, 禁言和解除禁言, 群属性(含全体禁言, 坦白说, 匿名聊天, 允许成员拉人)改动. ### mirai-api-http HTTP API 已完成, by [@ryoii](https://github.com/ryoii). diff --git a/gradle.properties b/gradle.properties index cce0a8c34..ddf817985 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # style guide kotlin.code.style=official # config -mirai_version=0.12.0 +mirai_version=0.13.0 kotlin.incremental.multiplatform=true kotlin.parallel.tasks.in.project=true # kotlin diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 78b54628b..9126bc908 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -47,9 +47,10 @@ import kotlin.time.ExperimentalTime internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler() { override val bot: QQAndroidBot by bot.unsafeWeakRef() override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job]) + override val logger: MiraiLogger get() = bot.configuration.networkLoggerSupplier(this) override val coroutineContext: CoroutineContext = bot.coroutineContext + CoroutineExceptionHandler { _, throwable -> - bot.logger.error("Exception in NetworkHandler", throwable) + logger.error("Exception in NetworkHandler", throwable) } private lateinit var channel: PlatformSocket @@ -62,7 +63,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler channel.connect("113.96.13.208", 8080) this.launch(CoroutineName("Incoming Packet Receiver")) { processReceive() } - // bot.logger.info("Trying login") + // logger.info("Trying login") var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect() mainloop@ while (true) { when (response) { @@ -104,7 +105,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler } is WtLogin.Login.LoginPacketResponse.Success -> { - bot.logger.info("Login successful") + logger.info("Login successful") break@mainloop } } @@ -118,7 +119,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler override suspend fun init(): Unit = coroutineScope { this@QQAndroidBotNetworkHandler.subscribeAlways<BotOfflineEvent> { if (this@QQAndroidBotNetworkHandler.bot == this.bot) { - this.bot.logger.error("被挤下线") + logger.error("被挤下线") close() } } @@ -130,7 +131,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler val friendListJob = launch { try { - bot.logger.info("开始加载好友信息") + logger.info("开始加载好友信息") var currentFriendCount = 0 var totalFriendCount: Short while (true) { @@ -149,21 +150,21 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler currentFriendCount++ } } - bot.logger.verbose("正在加载好友列表 ${currentFriendCount}/${totalFriendCount}") + logger.verbose("正在加载好友列表 ${currentFriendCount}/${totalFriendCount}") if (currentFriendCount >= totalFriendCount) { break } // delay(200) } - bot.logger.info("好友列表加载完成, 共 ${currentFriendCount}个") + logger.info("好友列表加载完成, 共 ${currentFriendCount}个") } catch (e: Exception) { - bot.logger.error("加载好友列表失败|一般这是由于加载过于频繁导致/将以热加载方式加载好友列表") + logger.error("加载好友列表失败|一般这是由于加载过于频繁导致/将以热加载方式加载好友列表") } } val groupJob = launch { try { - bot.logger.info("开始加载群组列表与群成员列表") + logger.info("开始加载群组列表与群成员列表") val troopListData = FriendList.GetTroopListSimplify(bot.client) .sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2) @@ -196,15 +197,15 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ) ) } catch (e: Exception) { - bot.logger.error("群${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试") - bot.logger.error(e) + logger.error("群${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试") + logger.error(e) } } } - bot.logger.info("群组列表与群成员加载完成, 共 ${troopListData.groups.size}个") + logger.info("群组列表与群成员加载完成, 共 ${troopListData.groups.size}个") } catch (e: Exception) { - bot.logger.error("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表") - bot.logger.error(e) + logger.error("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表") + logger.error(e) } } @@ -317,7 +318,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler if (packet is CancellableEvent && packet.isCancelled) return } - bot.logger.info("Received: ${packet.toString().replace("\n", """\n""").replace("\r", "")}") + logger.info("Received: ${packet.toString().replace("\n", """\n""").replace("\r", "")}") packetFactory?.run { when (this) { @@ -416,13 +417,13 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler bot.tryReinitializeNetworkHandler(e) return } catch (e: ReadPacketInternalException) { - bot.logger.error("Socket channel read failed: ${e.message}") + logger.error("Socket channel read failed: ${e.message}") bot.tryReinitializeNetworkHandler(e) return } catch (e: CancellationException) { return } catch (e: Throwable) { - bot.logger.error("Caught unexpected exceptions", e) + logger.error("Caught unexpected exceptions", e) bot.tryReinitializeNetworkHandler(e) return } @@ -438,7 +439,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler * 发送一个包, 但不期待任何返回. */ suspend fun OutgoingPacket.sendWithoutExpect() { - bot.logger.info("Send: ${this.commandName}") + logger.info("Send: ${this.commandName}") withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { channel.send(delegate) } @@ -461,7 +462,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { channel.send(delegate) } - bot.logger.info("Send: ${this.commandName}") + logger.info("Send: ${this.commandName}") return withTimeoutOrNull(timeoutMillis) { @Suppress("UNCHECKED_CAST") handler.await() as E @@ -480,7 +481,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { channel.send(data, 0, length) } - bot.logger.info("Send: ${this.commandName}") + logger.info("Send: ${this.commandName}") return withTimeoutOrNull(timeoutMillis) { @Suppress("UNCHECKED_CAST") handler.await() as E diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt index 3754f973e..0e9cee6be 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt @@ -132,9 +132,9 @@ internal class OnlinePush { bot.getGroupByUin(groupUin).let { group -> val member = group[target] as MemberImpl this.discardExact(1) - return MemberLeaveEvent.Kick(member, group.members[readUInt().toLong()].also { - group.members.delegate.remove(it) - }) + return MemberLeaveEvent.Kick(member.also { + group.members.delegate.remove(member) + }, group.members[readUInt().toLong()]) } } } @@ -187,14 +187,18 @@ internal class OnlinePush { ) } } else { - if (target == bot.uin) { - - } - val member = group[target] - if (time == 0) { - MemberUnmuteEvent(operator = operator, member = member) + return if (target == bot.uin) { + if (time == 0) { + BotUnmuteEvent(operator) + } else + BotMuteEvent(durationSeconds = time, operator = operator) } else { - MemberMuteEvent(operator = operator, member = member, durationSeconds = time) + val member = group[target] + if (time == 0) { + MemberUnmuteEvent(operator = operator, member = member) + } else { + MemberMuteEvent(operator = operator, member = member, durationSeconds = time) + } } } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt index 8003cc10e..3793b2ceb 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -40,7 +40,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor( @UseExperimental(RawAccountIdUse::class) override val uin: Long get() = account.id - final override val logger: MiraiLogger by lazy { configuration.logger ?: DefaultLogger("Bot($uin)").also { configuration.logger = it } } + final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) } init { instances.addLast(this.weakRef()) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt index 0a8346cf4..3aff7e809 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt @@ -79,6 +79,12 @@ suspend fun <E : Event> E.broadcast(): E = apply { this@broadcast.broadcastInternal() // inline, no extra cost } +/** + * 设置为 `true` 以关闭事件. + * 所有的 `subscribe` 都能正常添加到监听器列表, 但所有的广播都会直接返回. + */ +var EventDisabled = false + /** * 可控制是否需要广播这个事件包 */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt index b33e6c205..b1b2722bb 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt @@ -129,12 +129,27 @@ data class BotGroupPermissionChangeEvent( */ data class BotMuteEvent( val durationSeconds: Int, - override val group: Group, /** - * 操作人. 为 null 则为机器人操作 + * 操作人. */ - val operator: Member? -) : GroupEvent, Packet, BotPassiveEvent + val operator: Member +) : GroupEvent, Packet, BotPassiveEvent { + override val group: Group + get() = operator.group +} + +/** + * Bot 被取消禁言 + */ +data class BotUnmuteEvent( + /** + * 操作人. + */ + val operator: Member +) : GroupEvent, Packet, BotPassiveEvent { + override val group: Group + get() = operator.group +} /** * Bot 加入了一个新群 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt index 366ce9d02..2a9f8f3b7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt @@ -11,22 +11,17 @@ package net.mamoe.mirai.event.internal import kotlinx.coroutines.* import net.mamoe.mirai.event.Event +import net.mamoe.mirai.event.EventDisabled import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.ListeningStatus -import net.mamoe.mirai.utils.LockFreeLinkedList -import net.mamoe.mirai.utils.MiraiDebugAPI -import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.io.logStacktrace import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.jvm.JvmField import kotlin.reflect.KClass -/** - * 设置为 `true` 以关闭事件. - * 所有的 `subscribe` 都能正常添加到监听器列表, 但所有的广播都会直接返回. - */ -var EventDisabled = false +val EventLogger: MiraiLoggerWithSwitch = DefaultLogger("Event").withSwitch(false) @PublishedApi internal fun <L : Listener<E>, E : Event> KClass<out E>.subscribeInternal(listener: L): L { @@ -108,6 +103,8 @@ internal object EventListenerManager { internal suspend inline fun Event.broadcastInternal() { if (EventDisabled) return + EventLogger.info { "Event broadcast: $this" } + callAndRemoveIfRequired(this::class.listeners()) var supertypes = this::class.supertypes diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt index a37550997..c7d286b97 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.io.PlatformDatagramChannel /** @@ -47,6 +48,11 @@ abstract class BotNetworkHandler : CoroutineScope { */ abstract val supervisor: CompletableJob + /** + * logger + */ + abstract val logger: MiraiLogger + /** * 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回. * 本函数将挂起直到登录成功. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt index 4cb969396..26a532f7c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.utils import kotlinx.io.core.IoBuffer import net.mamoe.mirai.Bot +import net.mamoe.mirai.network.BotNetworkHandler import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmStatic @@ -38,7 +39,11 @@ class BotConfiguration { /** * 日志记录器 */ - var logger: MiraiLogger? = null + var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.uin})") } + /** + * 网络层日志构造器 + */ + var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.uin})") } /** * 设备信息覆盖 */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt index 187b2e266..4e15bb879 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt @@ -12,14 +12,12 @@ package net.mamoe.mirai.utils import io.ktor.client.HttpClient -import io.ktor.util.date.GMTDate -import kotlinx.coroutines.CoroutineDispatcher import kotlinx.io.core.toByteArray /** * 时间戳 */ -inline val currentTimeMillis: Long get() = GMTDate().timestamp +expect val currentTimeMillis: Long inline val currentTimeSeconds: Long get() = currentTimeMillis / 1000 diff --git a/settings.gradle b/settings.gradle index 548c79dc8..ce530da22 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,15 +21,19 @@ pluginManagement { rootProject.name = 'mirai' -def keyProps = new Properties() -def keyFile = file("local.properties") -if (keyFile.exists()) keyFile.withInputStream { keyProps.load(it) } -if (!keyProps.getProperty("sdk.dir", "").isEmpty()) { - include(':mirai-demos:mirai-demo-android') - project(':mirai-demos:mirai-demo-android').projectDir = file('mirai-demos/mirai-demo-android') -} else { - println("Android SDK 可能未安装. \n将不会加载模块 `mirai-demo-android`, 但这并不影响其他 demo 的加载 ") - println("Android SDK might not be installed. \nModule `mirai-demo-android` will not be included, but other demos will not be influenced") +try { + def keyProps = new Properties() + def keyFile = file("local.properties") + if (keyFile.exists()) keyFile.withInputStream { keyProps.load(it) } + if (!keyProps.getProperty("sdk.dir", "").isEmpty()) { + include(':mirai-demos:mirai-demo-android') + project(':mirai-demos:mirai-demo-android').projectDir = file('mirai-demos/mirai-demo-android') + } else { + println("Android SDK 可能未安装. \n将不会加载模块 `mirai-demo-android`, 但这并不影响其他 demo 的加载 ") + println("Android SDK might not be installed. \nModule `mirai-demo-android` will not be included, but other demos will not be influenced") + } +} catch (Exception e) { + e.printStackTrace() } include(':mirai-core') @@ -47,10 +51,18 @@ include(':mirai-plugins') include(':mirai-plugins:image-sender') def javaVersion = System.getProperty("java.version") -if (javaVersion.substring(0, javaVersion.indexOf(".")).toInteger() >= 11) { - include(':mirai-debug') -} else { - println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 最低需要 JDK 11 才能引入模块 `:mirai-debug`") +def versionPos = javaVersion.indexOf(".") +if (versionPos==-1) versionPos = javaVersion.indexOf("-") +if (versionPos==-1){ + println("jdk version unknown") +}else{ + def javaVersionNum = javaVersion.substring(0, versionPos).toInteger() + if (javaVersionNum >= 11) { + println("jdk版本为 "+ javaVersionNum) + include(':mirai-debug') + } else { + println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 最低需要 JDK 11 才能引入模块 `:mirai-debug`") + } } project(':mirai-demos:mirai-demo-1').projectDir = file('mirai-demos/mirai-demo-1')