diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ea0e429..4031a124e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ 开发版本. 频繁更新, 不保证高稳定性 +## `0.18.0` 2020/2/20 + +### mirai-core +- 添加 `MessageSource.time` +- 添加事件监听时额外的 `coroutineContext` +- 为一些带有 `operator` 的事件添加 `.isByBot` 的属性扩展 +- 优化事件广播逻辑, 修复可能无法触发监听的问题 +- 为所有 `Contact` 添加 `toString()` (#80) + +### mirai-core-qqandroid +- 支持成员禁言状态和时间查询 `Member.muteTimeRemaining` +- 修复 `At` 的 `display` (#73), 同时修复 `QuoteReply` 无法显示问题 (#54). +- 广播 `BotReloginEvent` (#78) +- 支持机器人自身禁言时间的更新和查询 (#82) + ## `0.17.0` 2020/2/20 ### mirai-core diff --git a/gradle.properties b/gradle.properties index 4a4049d31..97dc4d58d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # style guide kotlin.code.style=official # config -mirai_version=0.17.0 +mirai_version=0.18.0 mirai_japt_version=1.0.1 kotlin.incremental.multiplatform=true kotlin.parallel.tasks.in.project=true diff --git a/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleTerminalUI.kt b/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleTerminalUI.kt index 70cf38f3b..8998c0b83 100644 --- a/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleTerminalUI.kt +++ b/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleTerminalUI.kt @@ -482,7 +482,9 @@ object MiraiConsoleTerminalUI : MiraiConsoleUI { val width = terminal.terminalSize.columns - 6 var x = string while (true) { - if (x == "") break + if (x == "") { + break + } val toWrite = if (x.actualLength() > width) { val index = x.getSubStringIndexByActualLength(width) x.substring(0, index).also { diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index 6a5e16ad3..f1049b290 100644 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -305,6 +305,7 @@ object MiraiConsole { if (!CommandManager.runCommand(fullCommand)) { logger("未知指令 $fullCommand") } + } } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index 5cad965a2..29a17b2c9 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -162,16 +162,11 @@ internal class QQImpl( TODO("not implemented") } - override fun equals(other: Any?): Boolean { - if (this === other) return true - return other is QQ && other.id == this.id - } - - override fun hashCode(): Int = super.hashCode() + override fun toString(): String = "QQ($id)" } -@Suppress("MemberVisibilityCanBePrivate") +@Suppress("MemberVisibilityCanBePrivate", "DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") internal class MemberImpl( qq: QQImpl, group: GroupImpl, @@ -182,9 +177,21 @@ internal class MemberImpl( val qq: QQImpl by qq.unsafeWeakRef() override var permission: MemberPermission = memberInfo.permission + @Suppress("PropertyName") internal var _nameCard: String = memberInfo.nameCard + @Suppress("PropertyName") internal var _specialTitle: String = memberInfo.specialTitle + @Suppress("PropertyName") + var _muteTimestamp: Int = memberInfo.muteTimestamp + + override val muteTimeRemaining: Int = + if (_muteTimestamp == 0 || _muteTimestamp == 0xFFFFFFFF.toInt()) { + 0 + } else { + _muteTimestamp - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt() + } + override var nameCard: String get() = _nameCard set(newValue) { @@ -220,7 +227,7 @@ internal class MemberImpl( newValue ).sendWithoutExpect() } - MemberSpecialTitleChangeEvent(oldValue, newValue, this@MemberImpl).broadcast() + MemberSpecialTitleChangeEvent(oldValue, newValue, this@MemberImpl, null).broadcast() } } } @@ -279,12 +286,9 @@ internal class MemberImpl( } } - override fun equals(other: Any?): Boolean { - if (this === other) return true - return other is Member && other.id == this.id + override fun toString(): String { + return "Member($id)" } - - override fun hashCode(): Int = super.hashCode() } internal class MemberInfoImpl( @@ -301,6 +305,7 @@ internal class MemberInfoImpl( else -> MemberPermission.MEMBER } override val specialTitle: String get() = jceInfo.sSpecialTitle ?: "" + override val muteTimestamp: Int get() = jceInfo.dwShutupTimestap?.toInt() ?: 0 } /** @@ -323,13 +328,13 @@ internal class GroupImpl( @UseExperimental(MiraiExperimentalAPI::class) override lateinit var botPermission: MemberPermission - var _botMuteRemaining: Int = groupInfo.botMuteRemaining + var _botMuteTimestamp: Int = groupInfo.botMuteRemaining override val botMuteRemaining: Int = - if (_botMuteRemaining == 0 || _botMuteRemaining == 0xFFFFFFFF.toInt()) { + if (_botMuteTimestamp == 0 || _botMuteTimestamp == 0xFFFFFFFF.toInt()) { 0 } else { - _botMuteRemaining - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt() + _botMuteTimestamp - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt() } override val members: ContactList = ContactList(members.mapNotNull { @@ -600,10 +605,7 @@ internal class GroupImpl( image.input.close() } - override fun equals(other: Any?): Boolean { - if (this === other) return true - return other is Group && other.id == this.id + override fun toString(): String { + return "Group($id)" } - - override fun hashCode(): Int = super.hashCode() } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt index 236581cc5..76d8e6e33 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt @@ -194,6 +194,7 @@ internal class MessageSvc { override val nameCard: String get() = "" override val permission: MemberPermission get() = MemberPermission.MEMBER override val specialTitle: String get() = "" + override val muteTimestamp: Int get() = 0 override val uin: Long get() = msg.msgHead.authUin override val nick: String get() = msg.msgHead.authNick.takeIf { it.isNotEmpty() } ?: msg.msgHead.fromNick }).also { group.members.delegate.addLast(it) }) 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 88f8db929..117b1601e 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 @@ -142,9 +142,10 @@ internal class OnlinePush { @UseExperimental(ExperimentalStdlibApi::class) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet { val reqPushMsg = decodeUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req") - val packets = reqPushMsg.vMsgInfos.mapNotNull { msgInfo: MsgInfo -> - msgInfo.vMsg!!.read { + @Suppress("USELESS_CAST") // 不要信任 kotlin 类型推断 + val packets: List = reqPushMsg.vMsgInfos.mapNotNull { msgInfo: MsgInfo -> + msgInfo.vMsg!!.read { when { msgInfo.shMsgType.toInt() == 732 -> { val group = bot.getGroup(this.readUInt().toLong()) @@ -162,41 +163,54 @@ internal class OnlinePush { val target = this.readUInt().toLong() val time = this.readInt() - return@mapNotNull if (target == 0L) { + if (target == 0L) { if (time == 0) { - GroupMuteAllEvent( + return@mapNotNull GroupMuteAllEvent( origin = group.isMuteAll.also { group._muteAll = false }, new = false, operator = operator, group = group - ) + ) as Packet } else { - GroupMuteAllEvent( + return@mapNotNull GroupMuteAllEvent( origin = group.isMuteAll.also { group._muteAll = true }, new = true, operator = operator, group = group - ) + ) as Packet } } else { if (target == bot.uin) { - @Suppress("IMPLICIT_CAST_TO_ANY") // false positive - return@mapNotNull if (time == 0) { - BotUnmuteEvent(operator) - } else - BotMuteEvent(durationSeconds = time, operator = operator) + if (group._botMuteTimestamp != time) { + if (time == 0) { + group._botMuteTimestamp = 0 + return@mapNotNull BotUnmuteEvent(operator) as Packet + } else { + group._botMuteTimestamp = time + return@mapNotNull BotMuteEvent(durationSeconds = time, operator = operator) as Packet + } + } else { + return@mapNotNull null + } } else { val member = group[target] - @Suppress("IMPLICIT_CAST_TO_ANY") // false positive - return@mapNotNull if (time == 0) { - MemberUnmuteEvent(operator = operator, member = member) + member as MemberImpl + if (member._muteTimestamp != time) { + if (time == 0) { + member._muteTimestamp = 0 + return@mapNotNull MemberUnmuteEvent(member, operator) as Packet + } else { + member._muteTimestamp = time + return@mapNotNull MemberMuteEvent(member, time, operator) as Packet + } } else { - MemberMuteEvent(operator = operator, member = member, durationSeconds = time) + return@mapNotNull null } } } } - 3585 -> { // 匿名 + 3585 -> { + // 匿名 val operator = group[this.readUInt().toLong()] val switch = this.readInt() == 0 return@mapNotNull GroupAllowAnonymousChatEvent( @@ -239,7 +253,7 @@ internal class OnlinePush { } else -> { bot.network.logger.debug { "Unknown server messages $message" } - return@mapNotNull NoPacket + return@mapNotNull null } } } @@ -250,6 +264,7 @@ internal class OnlinePush { // } else -> { bot.network.logger.debug { "unknown group internal type $internalType , data: " + this.readBytes().toUHexString() + " " } + return@mapNotNull null } } } @@ -257,19 +272,18 @@ internal class OnlinePush { bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" } // val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer()) // println(content.contentToString()) + return@mapNotNull null } else -> { bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" } + return@mapNotNull null } } } - return@mapNotNull null } - return MultiPacket(packets) } - override suspend fun QQAndroidBot.handle(packet: Packet, sequenceId: Int): OutgoingPacket? { return buildResponseUniPacket(client, sequenceId = sequenceId) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index 80c2900bc..dcb80fe9f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -74,6 +74,16 @@ interface Contact : CoroutineScope { * 而 [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人. */ override fun equals(other: Any?): Boolean + + /** + * @return `bot.hashCode() * 31 + id.hashCode()` + */ + override fun hashCode(): Int + + /** + * @return "QQ($id)" or "Group($id)" or "Member($id)" + */ + override fun toString(): String } suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.toChain()) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt index a3c2a8ff2..3d618c9aa 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -12,6 +12,7 @@ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.events.* import net.mamoe.mirai.utils.MiraiExperimentalAPI @@ -89,19 +90,19 @@ interface Group : Contact, CoroutineScope { /** * 机器人被禁言还剩余多少秒 * - * @see BotMuteEvent - * @see isBotMuted + * @see BotMuteEvent 机器人被禁言事件 + * @see isBotMuted 判断机器人是否正在被禁言 */ val botMuteRemaining: Int /** * 机器人在这个群里的权限 * - * **MiraiExperimentalAPI**: 在未来可能会被修改 + * @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限 + * @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator] * - * @see BotGroupPermissionChangeEvent + * @see BotGroupPermissionChangeEvent 机器人群员修改 */ - @MiraiExperimentalAPI val botPermission: MemberPermission @@ -129,6 +130,7 @@ interface Group : Contact, CoroutineScope { /** * 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败 */ + @MiraiExperimentalAPI("还未支持") suspend fun quit(): Boolean /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt index d21bf9b1d..74f0a1431 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt @@ -1,7 +1,7 @@ /* * Copyright 2020 Mamoe Technologies and contributors. * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * 此源代码的使用受 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 @@ -29,33 +29,50 @@ interface Member : QQ, Contact { /** * 成员的权限, 动态更新. + * + * @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发. */ val permission: MemberPermission /** - * 群名片. 可能为空. 修改时将会触发事件 + * 群名片. 可能为空. + * + * 管理员和群主都可修改任何人(包括群主)的群名片. * * 在修改时将会异步上传至服务器. * * @see [nameCardOrNick] 获取非空群名片或昵称 * - * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件 + * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. * @throws PermissionDeniedException 无权限修改时 */ var nameCard: String /** - * 群头衔 + * 群头衔. + * + * 仅群主可以修改群头衔. * * 在修改时将会异步上传至服务器. * - * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件 + * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. * @throws PermissionDeniedException 无权限修改时 */ var specialTitle: String /** - * 禁言 + * 被禁言剩余时长. 单位为秒. + * + * @see isMuted 判断改成员是否处于禁言状态 + * @see mute 设置禁言 + * @see unmute 取消禁言 + */ + val muteTimeRemaining: Int + + /** + * 禁言. + * + * 管理员可禁言成员, 群主可禁言管理员和群员. * * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. * @return 机器人无权限时返回 `false` @@ -72,6 +89,8 @@ interface Member : QQ, Contact { /** * 解除禁言. * + * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. + * * @see MemberUnmuteEvent 成员被取消禁言事件. * @throws PermissionDeniedException 无权限修改时 */ @@ -80,6 +99,8 @@ interface Member : QQ, Contact { /** * 踢出该成员. * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * * @see MemberLeaveEvent.Kick 成员被踢出事件. * @throws PermissionDeniedException 无权限修改时 */ @@ -98,6 +119,13 @@ interface Member : QQ, Contact { */ val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick +/** + * 判断改成员是否处于禁言状态. + */ +fun Member.isMuted(): Boolean { + return muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt() +} + @ExperimentalTime suspend inline fun Member.mute(duration: Duration) { require(duration.inDays <= 30) { "duration must be at most 1 month" } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt index ac1137744..baeb5951f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.contact +import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.MiraiExperimentalAPI @@ -68,7 +69,6 @@ inline fun Member.isAdministrator(): Boolean = this.permission.isAdministrator() inline fun Member.isOperator(): Boolean = this.permission.isOperator() - /** * 权限不足 */ @@ -77,6 +77,11 @@ expect class PermissionDeniedException : IllegalStateException { constructor(message: String?) } +/** + * 要求 [Bot] 在这个群里的权限为 [required], 否则抛出异常 [PermissionDeniedException] + * + * @throws PermissionDeniedException + */ @UseExperimental(MiraiExperimentalAPI::class) inline fun Group.checkBotPermission( required: MemberPermission, @@ -89,6 +94,11 @@ inline fun Group.checkBotPermission( } } +/** + * 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator], 否则抛出异常 [PermissionDeniedException] + * + * @throws PermissionDeniedException + */ @UseExperimental(MiraiExperimentalAPI::class) inline fun Group.checkBotPermissionOperator( lazyMessage: () -> String = { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt index 3ed39ef10..37e6ba4b0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt @@ -17,4 +17,6 @@ interface MemberInfo : FriendInfo { val permission: MemberPermission val specialTitle: String + + val muteTimestamp: Int } \ No newline at end of file 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 0deab64d0..749697d01 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 @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot @@ -194,7 +196,7 @@ data class GroupNameChangeEvent( override val origin: String, override val new: String, override val group: Group, - val isByBot: Boolean + val isByBot: Boolean // 无法获取 operator ) : GroupSettingChangeEvent, Packet /** @@ -210,6 +212,8 @@ data class GroupEntranceAnnouncementChangeEvent( val operator: Member? ) : GroupSettingChangeEvent, Packet +val GroupEntranceAnnouncementChangeEvent.isByBot: Boolean get() = operator != null + /** * 群 "全员禁言" 功能状态改变. 此事件广播前修改就已经完成. @@ -224,6 +228,8 @@ data class GroupMuteAllEvent( val operator: Member? ) : GroupSettingChangeEvent, Packet +val GroupMuteAllEvent.isByBot: Boolean get() = operator != null + /** * 群 "匿名聊天" 功能状态改变. 此事件广播前修改就已经完成. */ @@ -237,6 +243,8 @@ data class GroupAllowAnonymousChatEvent( val operator: Member? ) : GroupSettingChangeEvent, Packet +val GroupAllowAnonymousChatEvent.isByBot: Boolean get() = operator != null + /** * 群 "坦白说" 功能状态改变. 此事件广播前修改就已经完成. */ @@ -260,6 +268,8 @@ data class GroupAllowMemberInviteEvent( val operator: Member? ) : GroupSettingChangeEvent, Packet +val GroupAllowMemberInviteEvent.isByBot: Boolean get() = operator != null + // endregion @@ -293,6 +303,8 @@ sealed class MemberLeaveEvent : GroupMemberEvent { data class Quit(override val member: Member) : MemberLeaveEvent() } +val MemberLeaveEvent.Kick.isByBot: Boolean get() = operator != null + // endregion // region 名片和头衔 @@ -319,6 +331,8 @@ data class MemberCardChangeEvent( val operator: Member? ) : GroupMemberEvent +val MemberCardChangeEvent.isByBot: Boolean get() = operator != null + /** * 群头衔改动. 一定为群主操作 */ @@ -333,9 +347,18 @@ data class MemberSpecialTitleChangeEvent( */ val new: String, - override val member: Member + override val member: Member, + + /** + * 操作人. + * 不为 null 时一定为群主. 可能与 [member] 引用相同, 此时为群员自己修改. + * 为 null 时则是机器人操作. + */ + val operator: Member? ) : GroupMemberEvent +val MemberSpecialTitleChangeEvent.isByBot: Boolean get() = operator != null + // endregion @@ -367,6 +390,8 @@ data class MemberMuteEvent( val operator: Member? ) : GroupMemberEvent, Packet +val MemberMuteEvent.isByBot: Boolean get() = operator != null + /** * 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人 */ @@ -378,6 +403,8 @@ data class MemberUnmuteEvent( val operator: Member? ) : GroupMemberEvent, Packet +val MemberUnmuteEvent.isByBot: Boolean get() = operator != null + // endregion // endregion