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 b136af038..259fe6591 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 @@ -325,6 +325,15 @@ internal class GroupImpl( @UseExperimental(MiraiExperimentalAPI::class) override lateinit var botPermission: MemberPermission + var _botMuteRemaining: Int = groupInfo.botMuteRemaining + + override val botMuteRemaining: Int = + if (_botMuteRemaining == 0 || _botMuteRemaining == 0xFFFFFFFF.toInt()) { + 0 + } else { + _botMuteRemaining - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt() + } + override val members: ContactList = ContactList(members.mapNotNull { if (it.uin == bot.uin) { botPermission = it.permission @@ -487,6 +496,7 @@ internal class GroupImpl( } override suspend fun sendMessage(message: MessageChain) { + check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" } val event = GroupMessageSendEvent(this, message).broadcast() if (event.isCancelled) { throw EventCancelledException("cancelled by FriendMessageSendEvent") diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt index e073d068d..3d12d9022 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt @@ -44,6 +44,7 @@ internal inline class GroupInfoImpl( override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0 override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0 override val muteAll: Boolean get() = delegate.shutupTimestamp != 0 + override val botMuteRemaining: Int get() = delegate.shutupTimestampMe ?: 0 } internal class TroopManagement { 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 90bc21f74..7aad3fefa 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 @@ -11,12 +11,18 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.Packet import net.mamoe.mirai.event.BroadcastControllable +import net.mamoe.mirai.event.events.BotJoinGroupEvent import net.mamoe.mirai.event.events.BotOfflineEvent +import net.mamoe.mirai.event.events.MemberJoinEvent import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.qqandroid.GroupImpl import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf @@ -32,6 +38,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie import net.mamoe.mirai.qqandroid.network.protocol.packet.* +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl +import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.math.absoluteValue @@ -94,13 +103,13 @@ internal class MessageSvc { } @UseExperimental(MiraiInternalAPI::class) - internal class GetMsgSuccess(delegate: List) : Response(MsgSvc.SyncFlag.STOP, delegate) + internal class GetMsgSuccess(delegate: List) : Response(MsgSvc.SyncFlag.STOP, delegate) /** * 不要直接 expect 这个 class. 它可能 */ @MiraiInternalAPI - open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List) : MultiPacket(delegate), + open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List) : MultiPacket(delegate), BroadcastControllable { override val shouldBroadcast: Boolean get() = syncFlagFromServer == MsgSvc.SyncFlag.STOP @@ -112,7 +121,7 @@ internal class MessageSvc { object EmptyResponse : Response(MsgSvc.SyncFlag.STOP, emptyList()) - @UseExperimental(MiraiInternalAPI::class) + @UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00 val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer()) @@ -130,40 +139,86 @@ internal class MessageSvc { return EmptyResponse } - val messages = resp.uinPairMsgs.asSequence().filterNot { it.msg == null }.flatMap { it.msg!!.asSequence() }.mapNotNull { - when (it.msgHead.msgType) { - 33 -> { - if (it.msgHead.authUin == bot.uin) { - val group = bot.getGroupByUinOrNull(it.msgHead.fromUin) - if (group == null) { - TODO("查询群信息, 添加群") + val messages = resp.uinPairMsgs.asSequence() + .filterNot { it.msg == null } + .flatMap { it.msg!!.asSequence() } + .toList() // so as to inline + .mapNotNull { msg -> + when (msg.msgHead.msgType) { + 33 -> { + val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin) + if (msg.msgHead.authUin == bot.uin) { + if (group != null) { + error("group is not null while bot is invited to the group") + } + // 新群 + + val troopNum = bot.network.run { + FriendList.GetTroopListSimplify(bot.client) + .sendAndExpect(retry = 2) + }.groups.first { it.groupUin == msg.msgHead.fromUin } + + + val newGroup = GroupImpl( + bot = bot, + coroutineContext = bot.coroutineContext, + id = Group.calculateGroupCodeByGroupUin(msg.msgHead.fromUin), + groupInfo = bot.queryGroupInfo(troopNum.groupCode).apply { + + this as GroupInfoImpl + + if (this.delegate.groupName == null) { + this.delegate.groupName = troopNum.groupName + } + + if (this.delegate.groupMemo == null) { + this.delegate.groupMemo = troopNum.groupMemo + } + + if (this.delegate.groupUin == null) { + this.delegate.groupUin = troopNum.groupUin + } + + this.delegate.groupCode = troopNum.groupCode + }, + members = bot.queryGroupMemberList(troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin) + ) + bot.groups.delegate.addLast(newGroup) + return@mapNotNull BotJoinGroupEvent(newGroup) + } else { + checkNotNull(group) { "group is null while a member is joining to" } + if (group.members.contains(msg.msgHead.authUin)) { + return@mapNotNull null + } else { + return@mapNotNull MemberJoinEvent(group.Member(object : MemberInfo { + override val nameCard: String get() = "" + override val permission: MemberPermission get() = MemberPermission.MEMBER + override val specialTitle: String get() = "" + 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) }) + } } } - - TODO("为 group 添加一个 fun Member() 来构造 member") - // bot.getGroupByUin(it.msgHead.fromUin).members.delegate.addLast() - println("GroupUin" + it.msgHead.fromUin + "新群员" + it.msgHead.authUin + " 出现了[" + it.msgHead.authNick + "] 添加刷新") - null - } - 166 -> { - when { - it.msgHead.fromUin == bot.uin -> null - !bot.firstLoginSucceed -> null - else -> FriendMessage( - bot, - bot.getFriend(it.msgHead.fromUin), - it.toMessageChain() - ) + 166 -> { + return@mapNotNull when { + msg.msgHead.fromUin == bot.uin -> null + !bot.firstLoginSucceed -> null + else -> FriendMessage( + bot, + bot.getFriend(msg.msgHead.fromUin), + msg.toMessageChain() + ) + } } + else -> return@mapNotNull null } - else -> null } - }.toMutableList() if (resp.syncFlag == MsgSvc.SyncFlag.STOP) { messages.ifEmpty { return EmptyResponse } - return GetMsgSuccess(mutableListOf(messages.last())) + return GetMsgSuccess(listOf(messages.last())) } return Response(resp.syncFlag, messages) } @@ -201,7 +256,8 @@ internal class MessageSvc { } data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() { - override fun toString(): String = "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)" + override fun toString(): String = + "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)" } } 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 9ab84545c..3754f973e 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 @@ -187,6 +187,9 @@ internal class OnlinePush { ) } } else { + if (target == bot.uin) { + + } val member = group[target] if (time == 0) { MemberUnmuteEvent(operator = operator, member = member) diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt index 73cfcd07c..2ba68a6ba 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt @@ -94,3 +94,7 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { } } +/** + * 时间戳 + */ +actual val currentTimeMillis: Long get() = System.currentTimeMillis() \ No newline at end of file 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 96ba7609c..42a96bec4 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 @@ -14,6 +14,7 @@ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.BeforeImageUploadEvent +import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent @@ -47,16 +48,20 @@ interface Contact : CoroutineScope { * * @see FriendMessageSendEvent 发送好友信息事件, cancellable * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 */ suspend fun sendMessage(message: MessageChain) /** * 上传一个图片以备发送. - * TODO: 群图片与好友图片之间是否通用还不确定. - * TODO: 好友之间图片是否通用还不确定. + * TODO 群图片与好友图片在服务器上是通用的, 在 mirai 目前不通用. * * @see BeforeImageUploadEvent 图片发送前事件, cancellable * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 */ suspend fun uploadImage(image: ExternalImage): Image 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 ff0a60346..7414d14c3 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 @@ -24,18 +24,20 @@ interface Group : Contact, CoroutineScope { /** * 群名称. * - * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] + * 在修改时将会异步上传至服务器. * 频繁修改可能会被服务器拒绝. * * @see MemberPermissionChangeEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ var name: String /** * 入群公告, 没有时为空字符串. * - * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] + * 在修改时将会异步上传至服务器. * * @see GroupEntranceAnnouncementChangeEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ var entranceAnnouncement: String /** @@ -49,17 +51,19 @@ interface Group : Contact, CoroutineScope { /** * 坦白说状态. `true` 为允许. * - * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] - + * 在修改时将会异步上传至服务器. + * * @see GroupAllowConfessTalkEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ var confessTalk: Boolean /** * 允许群员邀请好友入群的状态. `true` 为允许 * - * 在修改时将会异步上传至服务器. 无权限修改时将会抛出异常 [PermissionDeniedException] + * 在修改时将会异步上传至服务器. * * @see GroupAllowMemberInviteEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ var allowMemberInvite: Boolean /** @@ -77,10 +81,17 @@ interface Group : Contact, CoroutineScope { override val id: Long /** - * 群主 (同步事件更新) + * 群主 */ val owner: Member + /** + * 机器人被禁言还剩余多少秒 + * + * @see BotMuteEvent + * @see isBotMuted + */ + val botMuteRemaining: Int /** * 机器人在这个群里的权限 @@ -124,7 +135,7 @@ interface Group : Contact, CoroutineScope { * 非特殊情况请不要使用这个函数. 优先使用 [get]. */ @MiraiExperimentalAPI("dangerous") - @Suppress("INAPPLICABLE_JVM_NAME") + @Suppress("INAPPLICABLE_JVM_NAME", "FunctionName") @JvmName("newMember") fun Member(memberInfo: MemberInfo): Member @@ -168,4 +179,9 @@ interface Group : Contact, CoroutineScope { @MiraiExperimentalAPI fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})" -} \ No newline at end of file +} + +/** + * 返回机器人是否正在被禁言 + */ +val Group.isBotMuted: Boolean get() = this.botMuteRemaining == 0 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt index 98b722d6d..0a594a4b0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt @@ -57,4 +57,9 @@ interface GroupInfo { * 全员禁言 */ val muteAll: Boolean + + /** + * 机器人被禁言还剩时间, 秒. + */ + val botMuteRemaining: 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 79f888a41..b33e6c205 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 @@ -18,6 +18,7 @@ import net.mamoe.mirai.event.CancellableEvent import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalAPI @Suppress("unused") @@ -123,6 +124,26 @@ data class BotGroupPermissionChangeEvent( val new: MemberPermission ) : BotPassiveEvent, GroupEvent, Packet +/** + * Bot 被禁言 + */ +data class BotMuteEvent( + val durationSeconds: Int, + override val group: Group, + /** + * 操作人. 为 null 则为机器人操作 + */ + val operator: Member? +) : GroupEvent, Packet, BotPassiveEvent + +/** + * Bot 加入了一个新群 + */ +@MiraiExperimentalAPI +data class BotJoinGroupEvent( + override val group: Group +) : BotPassiveEvent, GroupEvent, Packet + // region 群设置 /** @@ -219,7 +240,7 @@ data class GroupAllowMemberInviteEvent( /** * 成员加入群的事件 */ -data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent +data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent, Packet /** * 成员离开群的事件 diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt index d952acdba..840072726 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt @@ -74,3 +74,8 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { return output.toByteArray() } } + +/** + * 时间戳 + */ +actual val currentTimeMillis: Long get() = System.currentTimeMillis() \ No newline at end of file