diff --git a/mirai-core-api/compatibility-validation/android/api/android.api b/mirai-core-api/compatibility-validation/android/api/android.api index 0b72025f50..0fa4a16d7d 100644 --- a/mirai-core-api/compatibility-validation/android/api/android.api +++ b/mirai-core-api/compatibility-validation/android/api/android.api @@ -26,6 +26,8 @@ public abstract interface class net/mamoe/mirai/Bot : kotlinx/coroutines/Corouti public static fun getInstances ()Ljava/util/List; public static fun getInstancesSequence ()Lkotlin/sequences/Sequence; public abstract fun getLogger ()Lnet/mamoe/mirai/utils/MiraiLogger; + public fun getNewFriendRequestList ()Ljava/util/List; + public abstract fun getNewFriendRequestList (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getOtherClients ()Lnet/mamoe/mirai/contact/ContactList; public fun getStranger (J)Lnet/mamoe/mirai/contact/Stranger; public fun getStrangerOrFail (J)Lnet/mamoe/mirai/contact/Stranger; @@ -137,6 +139,8 @@ public abstract interface annotation class net/mamoe/mirai/LowLevelApi : java/la public abstract interface class net/mamoe/mirai/LowLevelApiAccessor { public fun getGroupVoiceDownloadUrl (Lnet/mamoe/mirai/Bot;[BJJ)Ljava/lang/String; public abstract fun getGroupVoiceDownloadUrl (Lnet/mamoe/mirai/Bot;[BJJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getNewFriendRequestList (Lnet/mamoe/mirai/Bot;)Ljava/util/List; + public abstract fun getNewFriendRequestList (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getRawGroupList (Lnet/mamoe/mirai/Bot;)Lkotlin/sequences/Sequence; public abstract fun getRawGroupList (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getRawGroupMemberList (Lnet/mamoe/mirai/Bot;JJJ)Lkotlin/sequences/Sequence; diff --git a/mirai-core-api/compatibility-validation/jvm/api/jvm.api b/mirai-core-api/compatibility-validation/jvm/api/jvm.api index c5311a71a2..b5ec757d48 100644 --- a/mirai-core-api/compatibility-validation/jvm/api/jvm.api +++ b/mirai-core-api/compatibility-validation/jvm/api/jvm.api @@ -26,6 +26,8 @@ public abstract interface class net/mamoe/mirai/Bot : kotlinx/coroutines/Corouti public static fun getInstances ()Ljava/util/List; public static fun getInstancesSequence ()Lkotlin/sequences/Sequence; public abstract fun getLogger ()Lnet/mamoe/mirai/utils/MiraiLogger; + public fun getNewFriendRequestList ()Ljava/util/List; + public abstract fun getNewFriendRequestList (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getOtherClients ()Lnet/mamoe/mirai/contact/ContactList; public fun getStranger (J)Lnet/mamoe/mirai/contact/Stranger; public fun getStrangerOrFail (J)Lnet/mamoe/mirai/contact/Stranger; @@ -137,6 +139,8 @@ public abstract interface annotation class net/mamoe/mirai/LowLevelApi : java/la public abstract interface class net/mamoe/mirai/LowLevelApiAccessor { public fun getGroupVoiceDownloadUrl (Lnet/mamoe/mirai/Bot;[BJJ)Ljava/lang/String; public abstract fun getGroupVoiceDownloadUrl (Lnet/mamoe/mirai/Bot;[BJJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getNewFriendRequestList (Lnet/mamoe/mirai/Bot;)Ljava/util/List; + public abstract fun getNewFriendRequestList (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getRawGroupList (Lnet/mamoe/mirai/Bot;)Lkotlin/sequences/Sequence; public abstract fun getRawGroupList (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getRawGroupMemberList (Lnet/mamoe/mirai/Bot;JJJ)Lkotlin/sequences/Sequence; diff --git a/mirai-core-api/src/commonMain/kotlin/Bot.kt b/mirai-core-api/src/commonMain/kotlin/Bot.kt index 3afdd5a957..6ae905c3e0 100644 --- a/mirai-core-api/src/commonMain/kotlin/Bot.kt +++ b/mirai-core-api/src/commonMain/kotlin/Bot.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.* import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.friendgroup.FriendGroups +import net.mamoe.mirai.data.RequestEventData import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.message.action.BotNudge @@ -170,6 +171,13 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot { */ public override fun nudge(): BotNudge = BotNudge(this) + /** + * 获取未处理的好友请求 + * + * @see RequestEventData.NewFriendRequest + * @since 2.17 + */ + public suspend fun getNewFriendRequestList(): List /** * 关闭这个 [Bot], 立即取消 [Bot] 的 [SupervisorJob], 取消与这个 [Bot] 相关的所有有协程联系的任务. diff --git a/mirai-core-api/src/commonMain/kotlin/LowLevelApiAccessor.kt b/mirai-core-api/src/commonMain/kotlin/LowLevelApiAccessor.kt index a51f08d278..e1f111bc54 100644 --- a/mirai-core-api/src/commonMain/kotlin/LowLevelApiAccessor.kt +++ b/mirai-core-api/src/commonMain/kotlin/LowLevelApiAccessor.kt @@ -16,6 +16,7 @@ import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.MemberInfo +import net.mamoe.mirai.data.RequestEventData import net.mamoe.mirai.data.StrangerInfo import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.NotStableForInheritance @@ -196,4 +197,10 @@ public interface LowLevelApiAccessor { groupId: Long, seconds: Int, ) + + /** + * 获取新好友请求列表 + */ + @LowLevelApi + public suspend fun getNewFriendRequestList(bot: Bot): List } diff --git a/mirai-core-mock/src/internal/MockBotImpl.kt b/mirai-core-mock/src/internal/MockBotImpl.kt index 0e7e67fe15..c2a8f7c847 100644 --- a/mirai-core-mock/src/internal/MockBotImpl.kt +++ b/mirai-core-mock/src/internal/MockBotImpl.kt @@ -20,12 +20,14 @@ import net.mamoe.mirai.contact.ContactList import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.friendgroup.FriendGroups +import net.mamoe.mirai.data.RequestEventData import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotReloginEvent +import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.components.EventDispatcher @@ -119,6 +121,10 @@ internal class MockBotImpl( } } + override suspend fun getNewFriendRequestList(): List { + return listOf() + } + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") override fun close(cause: Throwable?) { diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 926cfad0fc..0a7ba3c138 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -801,4 +801,11 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { } } } + + override suspend fun getNewFriendRequestList(bot: Bot): List{ + bot.asQQAndroidBot() + val resp = bot.network.sendAndExpect(NewContact.SystemMsgNewFriend(bot.client)) + + return resp.list + } } diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index 56d0d08441..4a7bf2c840 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -12,11 +12,14 @@ package net.mamoe.mirai.internal import kotlinx.atomicfu.atomic import kotlinx.coroutines.* import net.mamoe.mirai.Bot +import net.mamoe.mirai.Mirai import net.mamoe.mirai.auth.AuthReason +import net.mamoe.mirai.data.RequestEventData import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotReloginEvent +import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.internal.contact.friendgroup.FriendGroupsImpl import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ComponentStorageDelegate @@ -318,6 +321,10 @@ internal open class QQAndroidBot constructor( }, ) // We can move the factory to configuration but this is not necessary for now. } + + override suspend fun getNewFriendRequestList(): List { + return Mirai.getNewFriendRequestList(this) + } } internal fun QQAndroidBot.getGroupByUinOrFail(uin: Long) = diff --git a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt index 0c13bf1be5..3f64ad5f0f 100644 --- a/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt +++ b/mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt @@ -171,6 +171,7 @@ internal abstract class MixedNoticeProcessor : AnyNoticeProcessor() { is Structmsg.StructMsg -> processImpl(data) is RequestPushStatus -> processImpl(data) is DecodedNotifyMsgBody -> processImpl(data) + is Structmsg.RspSystemMsgNew -> processImpl(data) } } @@ -183,4 +184,5 @@ internal abstract class MixedNoticeProcessor : AnyNoticeProcessor() { protected open suspend fun NoticePipelineContext.processImpl(data: RequestPushStatus) {} protected open suspend fun NoticePipelineContext.processImpl(data: DecodedNotifyMsgBody) {} + protected open suspend fun NoticePipelineContext.processImpl(data: Structmsg.RspSystemMsgNew) {} } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt index 15f1c5e042..df8c309aa9 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt @@ -25,12 +25,14 @@ import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.components.ContactUpdater import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext +import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.notice.decoders.DecodedNotifyMsgBody import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x44 +import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.parseToMessageDataList import net.mamoe.mirai.internal.utils.toMemberInfo @@ -38,6 +40,7 @@ import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.context import net.mamoe.mirai.utils.read import net.mamoe.mirai.utils.structureToString +import kotlin.math.max /** @@ -197,19 +200,26 @@ internal class GroupOrMemberListNoticeProcessor( // Structmsg.StructMsg /////////////////////////////////////////////////////////////////////////// - override suspend fun NoticePipelineContext.processImpl(data: Structmsg.StructMsg) = data.msg.context { - if (this == null) return + override suspend fun NoticePipelineContext.processImpl(data: Structmsg.StructMsg) { + val systemMsg = data.msg ?: return + if (attributes[NewContact.SYSTEM_MSG_TYPE] != 1) return + + if (data.msgTime <= bot.syncController.latestMsgNewGroupTime) return + if (!bot.syncController.syncNewGroup(data.msgSeq, data.msgTime)) return // duplicate + markAsConsumed() - when (subType) { + var consumed = true + + when (systemMsg.subType) { 0 -> { - if (groupMsgType == 8) { + if (systemMsg.groupMsgType == 8) { // #1388: 使用手机TIM邀请入群,我为管理员,成功邀请 bot 入群 // 能正常解析 BotInvitedJoinGroupRequestEvent 和 BotJoinGroupEvent.Active, 因此忽略该通知 return } else { throw contextualBugReportException( - "解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=$groupMsgType", + "解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=${systemMsg.groupMsgType}", data.structureToString(), null, "并描述此时机器人是否被邀请加入群等其他", @@ -218,26 +228,27 @@ internal class GroupOrMemberListNoticeProcessor( } // 处理被邀请入群 或 处理成员入群申请 - 1 -> when (groupMsgType) { + 1 -> when (systemMsg.groupMsgType) { 1 -> { // 成员申请入群 collected += MemberJoinRequestEvent( - bot, data.msgSeq, msgAdditional, - data.reqUin, groupCode, groupName, reqUinNick + bot, data.msgSeq, systemMsg.msgAdditional, + data.reqUin, systemMsg.groupCode, systemMsg.groupName, systemMsg.reqUinNick ) } 2 -> { // Bot 被邀请入群 collected += BotInvitedJoinGroupRequestEvent( - bot, data.msgSeq, actionUin, - groupCode, groupName, actionUinNick + bot, data.msgSeq, systemMsg.actionUin, + systemMsg.groupCode, systemMsg.groupName, systemMsg.actionUinNick ) } 22 -> { // 成员邀请入群 collected += MemberJoinRequestEvent( - bot, data.msgSeq, msgAdditional, - data.reqUin, groupCode, groupName, reqUinNick, actionUin + bot, data.msgSeq, systemMsg.msgAdditional, + data.reqUin, systemMsg.groupCode, systemMsg.groupName, + systemMsg.reqUinNick, systemMsg.actionUin ) } else -> throw contextualBugReportException( @@ -256,8 +267,8 @@ internal class GroupOrMemberListNoticeProcessor( 3 -> { // 已被请他管理员处理 } 5 -> { - val group = bot.getGroup(groupCode) ?: return - when (groupMsgType) { + val group = bot.getGroup(systemMsg.groupCode) ?: return + when (systemMsg.groupMsgType) { 3 -> { // https://github.com/mamoe/mirai/issues/651 // msgDescribe=将你设置为管理员 @@ -268,7 +279,7 @@ internal class GroupOrMemberListNoticeProcessor( // 但无法获取是哪个成员. } 7 -> { // 机器人被踢 - val operator = group[actionUin] ?: return + val operator = group[systemMsg.actionUin] ?: return collected += BotLeaveEvent.Kick(operator) } 6 -> { @@ -284,7 +295,7 @@ internal class GroupOrMemberListNoticeProcessor( } else -> { throw contextualBugReportException( - "解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=$groupMsgType", + "解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=${systemMsg.groupMsgType}", this.structureToString(), null, "并描述此时机器人是否被踢出群等", @@ -292,7 +303,15 @@ internal class GroupOrMemberListNoticeProcessor( } } } - else -> markNotConsumed() + else -> { + consumed = false + markNotConsumed() + } + } + + if (consumed) { + val latestTime = bot.syncController.latestMsgNewGroupTime + bot.syncController.latestMsgNewGroupTime = max(latestTime, data.msgTime) } } diff --git a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt index 99e3c00f92..73454adc6c 100644 --- a/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt +++ b/mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt @@ -21,11 +21,13 @@ import net.mamoe.mirai.internal.contact.toMiraiFriendInfo import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.msgInfo +import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.notice.NewContactSupport import net.mamoe.mirai.internal.network.notice.group.get import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.proto.FrdSysMsg import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x115.SubMsgType0x115 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x122 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.* @@ -36,10 +38,12 @@ import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList.GetFrien import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.utils.* +import kotlin.math.max /** - * All [FriendEvent] except [FriendMessageEvent] + * All [FriendEvent] except [FriendMessageEvent], plus [NewFriendRequestEvent] * + * @see NewFriendRequestEvent * @see FriendInputStatusChangedEvent * @see FriendAddEvent * @see StrangerRelationChangeEvent.Friended @@ -136,6 +140,27 @@ internal class FriendNoticeProcessor( } } + override suspend fun NoticePipelineContext.processImpl(data: Structmsg.StructMsg) { + val systemMsg = data.msg ?: return + if (attributes[NewContact.SYSTEM_MSG_TYPE] != 0) return + markAsConsumed() + + if (data.msgTime <= bot.syncController.latestMsgNewFriendTime) return + if (!bot.syncController.syncNewFriend(data.msgSeq, data.msgTime)) return // duplicate + + collected += NewFriendRequestEvent( + bot, + data.msgSeq, + systemMsg.msgAdditional, + data.reqUin, + systemMsg.groupCode, + systemMsg.reqUinNick, + ) + + val latestTime = bot.syncController.latestMsgNewFriendTime + bot.syncController.latestMsgNewFriendTime = max(latestTime, data.msgTime) + } + @Serializable private class Wording( diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/StructMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/StructMsg.kt index 6f4ea362fb..9286ba4220 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/StructMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/StructMsg.kt @@ -137,6 +137,7 @@ internal class Structmsg : ProtoBuf { @ProtoNumber(8) @JvmField val isGetFrdRibbon: Boolean = true, @ProtoNumber(9) @JvmField val isGetGrpRibbon: Boolean = true, @ProtoNumber(10) @JvmField val friendMsgTypeFlag: Long = 0L, + @ProtoNumber(11) @JvmField val reqMsgType: Int = 0 ) : ProtoBuf @Serializable diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NewContact.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NewContact.kt index 97169f577a..ca18455b17 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NewContact.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NewContact.kt @@ -12,12 +12,11 @@ package net.mamoe.mirai.internal.network.protocol.packet.chat import io.ktor.utils.io.core.* -import net.mamoe.mirai.event.events.NewFriendRequestEvent +import net.mamoe.mirai.data.RequestEventData import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline -import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket @@ -25,12 +24,13 @@ import net.mamoe.mirai.internal.network.toPacket import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf -import kotlin.math.max +import net.mamoe.mirai.utils.TypeKey +import net.mamoe.mirai.utils.buildTypeSafeMap internal class NewContact { internal object SystemMsgNewFriend : - OutgoingPacketFactory("ProfileService.Pb.ReqSystemMsgNew.Friend") { + OutgoingPacketFactory("ProfileService.Pb.ReqSystemMsgNew.Friend") { operator fun invoke(client: QQAndroidClient) = buildOutgoingUniPacket(client) { writeProtoBuf( @@ -49,35 +49,30 @@ internal class NewContact { isGetGrpRibbon = false, msgNum = 20, version = 1000, + reqMsgType = 2, ), ) } + internal class Response(val list: List) : Packet - override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet { - readProtoBuf(Structmsg.RspSystemMsgNew.serializer()).run { - return friendmsgs.filter { - it.msgTime >= bot.syncController.latestMsgNewFriendTime - }.mapNotNull { struct -> - if (!bot.syncController.syncNewFriend(struct.msgSeq, struct.msgTime)) { // duplicate - return@mapNotNull null - } - struct.msg?.run { - NewFriendRequestEvent( - bot, - struct.msgSeq, - msgAdditional, - struct.reqUin, - groupCode, - reqUinNick, - ) - } - }.toPacket().also { - bot.syncController.run { - latestMsgNewFriendTime = max(latestMsgNewFriendTime, friendmsgs.maxOfOrNull { it.msgTime } ?: 0) - } - } - } + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val resp = readProtoBuf(Structmsg.RspSystemMsgNew.serializer()) + + return resp.friendmsgs.map { struct -> + bot.processPacketThroughPipeline(struct, buildTypeSafeMap { set(SYSTEM_MSG_TYPE, 0) }) + + val systemMsg = struct.msg ?: return@map null + if (systemMsg.actions.size < 2) return@map null // 只返回可以操作的(同意或拒绝好友请求) + + RequestEventData.NewFriendRequest( + struct.msgSeq, + struct.reqUin, + systemMsg.reqUinNick, + systemMsg.groupCode, + systemMsg.msgAdditional, + ) + }.filterNotNull().let { Response(it) } } internal object Action : OutgoingPacketFactory("ProfileService.Pb.ReqSystemMsgAction.Friend") { @@ -152,20 +147,11 @@ internal class NewContact { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet { - return readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run { - groupmsgs.filter { - it.msgTime >= bot.syncController.latestMsgNewGroupTime - }.mapNotNull { struct -> - if (!bot.syncController.syncNewGroup(struct.msgSeq, struct.msgTime)) { // duplicate - return@mapNotNull null - } - bot.processPacketThroughPipeline(struct) - }.toPacket().also { - bot.syncController.run { - latestMsgNewGroupTime = max(latestMsgNewGroupTime, groupmsgs.maxOfOrNull { it.msgTime } ?: 0) - } - } - } + val resp = readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()) + + return resp.groupmsgs.map { struct -> + bot.processPacketThroughPipeline(struct, buildTypeSafeMap { set(SYSTEM_MSG_TYPE, 1) }) + }.toPacket() } internal object Action : OutgoingPacketFactory("ProfileService.Pb.ReqSystemMsgAction.Group") { @@ -179,8 +165,7 @@ internal class NewContact { accept: Boolean?, blackList: Boolean = false, message: String = "", - ) = - buildOutgoingUniPacket(client) { + ) = buildOutgoingUniPacket(client) { writeProtoBuf( Structmsg.ReqSystemMsgAction.serializer(), Structmsg.ReqSystemMsgAction( @@ -209,4 +194,11 @@ internal class NewContact { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot) = null } } + + internal companion object { + /** + * friend = 0, group = 1 + */ + internal val SYSTEM_MSG_TYPE = TypeKey("SystemMsgType") + } } diff --git a/mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt b/mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt index 0636588c93..206a59b583 100644 --- a/mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt +++ b/mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt @@ -39,6 +39,8 @@ import net.mamoe.mirai.utils.* * To add breakpoint, see [NoticeProcessorPipelineImpl.process] */ internal abstract class AbstractNoticeProcessorTest : AbstractCommonNHTest(), GroupExtensions { + var mockTime = currentTimeSeconds() + init { setSystemProp("mirai.network.notice.pipeline.log.full", "true") } diff --git a/mirai-core/src/commonTest/kotlin/notice/processors/BotInvitedJoinTest.kt b/mirai-core/src/commonTest/kotlin/notice/processors/BotInvitedJoinTest.kt index 230702e2be..738b1de8c8 100644 --- a/mirai-core/src/commonTest/kotlin/notice/processors/BotInvitedJoinTest.kt +++ b/mirai-core/src/commonTest/kotlin/notice/processors/BotInvitedJoinTest.kt @@ -16,7 +16,10 @@ import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.network.components.NoticeProcessorPipelineImpl import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg +import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact import net.mamoe.mirai.internal.test.runBlockingUnit +import net.mamoe.mirai.utils.buildTypeSafeMap +import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -24,13 +27,15 @@ import kotlin.test.assertIs internal class BotInvitedJoinTest : AbstractNoticeProcessorTest() { @Test fun `invited join`() = runBlockingUnit { - suspend fun runTest() = use { - + suspend fun runTest() = use( + attributes = buildTypeSafeMap { set(NewContact.SYSTEM_MSG_TYPE, 1) } + ) { + mockTime += 1000 Structmsg.StructMsg( version = 1, msgType = 2, - msgSeq = 1630, - msgTime = 1630, + msgSeq = mockTime * 1000, + msgTime = mockTime, reqUin = 1230, msg = Structmsg.SystemMsg( subType = 1, @@ -81,6 +86,7 @@ internal class BotInvitedJoinTest : AbstractNoticeProcessorTest() { c2cInviteJoinGroupFlag = 1, ), ) + } setBot(1230003) @@ -93,7 +99,7 @@ internal class BotInvitedJoinTest : AbstractNoticeProcessorTest() { assertEquals("user1", invitorNick) assertEquals(2230203, groupId) assertEquals("testtest", groupName) - assertEquals(1630, eventId) + assertEquals(mockTime * 1000, eventId) } } diff --git a/mirai-core/src/commonTest/kotlin/notice/processors/MemberJoinTest.kt b/mirai-core/src/commonTest/kotlin/notice/processors/MemberJoinTest.kt index f78fa2ed0c..c0f427d0e6 100644 --- a/mirai-core/src/commonTest/kotlin/notice/processors/MemberJoinTest.kt +++ b/mirai-core/src/commonTest/kotlin/notice/processors/MemberJoinTest.kt @@ -13,19 +13,25 @@ import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.MemberJoinEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg +import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact import net.mamoe.mirai.internal.test.runBlockingUnit +import net.mamoe.mirai.utils.buildTypeSafeMap +import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.test.* internal class MemberJoinTest : AbstractNoticeProcessorTest() { @Test fun `member actively request join`() = runBlockingUnit { - suspend fun runTest() = use { + suspend fun runTest() = use( + attributes = buildTypeSafeMap { set(NewContact.SYSTEM_MSG_TYPE, 1) } + ) { + mockTime += 1000 Structmsg.StructMsg( version = 1, msgType = 2, - msgSeq = 16300, - msgTime = 1630, + msgSeq = mockTime * 1000, + msgTime = mockTime, reqUin = 1230001, msg = Structmsg.SystemMsg( subType = 1, diff --git a/mirai-core/src/commonTest/kotlin/notice/processors/NewFriendRequestTest.kt b/mirai-core/src/commonTest/kotlin/notice/processors/NewFriendRequestTest.kt new file mode 100644 index 0000000000..dac0e0a754 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/notice/processors/NewFriendRequestTest.kt @@ -0,0 +1,260 @@ +/* + * Copyright 2019-2023 Mamoe Technologies and contributors. + * + * 此源代码的使用受 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/dev/LICENSE + */ + +package net.mamoe.mirai.internal.notice.processors + +import net.mamoe.mirai.event.events.NewFriendRequestEvent +import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg +import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact +import net.mamoe.mirai.internal.test.runBlockingUnit +import net.mamoe.mirai.utils.buildTypeSafeMap +import net.mamoe.mirai.utils.currentTimeSeconds +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs + +internal class NewFriendRequestTest : AbstractNoticeProcessorTest() { + @Test + fun `new friend request from search test`() = runBlockingUnit { + + setBot(114514) + + use(attributes = buildTypeSafeMap { set(NewContact.SYSTEM_MSG_TYPE, 0) }) { + mockTime += 1000 + Structmsg.StructMsg( + version = 1, + msgType = 2, + msgSeq = mockTime * 1000, + msgTime = mockTime, + reqUin = 123456, + unreadFlag = 0, + msg = Structmsg.SystemMsg( + subType = 1, + msgTitle = "好友申请", + msgDescribe = "请求加为好友", + msgAdditional = "我是颠佬", + msgSource = "QQ群", + msgDecided = "", + srcId = 3004, + subSrcId = 2, + groupCode = 1, + actionUin = 0, + groupMsgType = 0, + groupInviterRole = 0, + friendInfo = Structmsg.FriendInfo( + msgJointFriend = "1 个共同好友", + msgBlacklist = "设为黑名单后你将拒绝对方,并不再接收此人请求。" + ), + groupInfo = null, + actorUin = 0, + msgActorDescribe = "", + msgAdditionalList = "", + relation = 0, + reqsubtype = 0, + cloneUin = 0, + discussUin = 0, + eimGroupId = 0, + msgInviteExtinfo = null, + msgPayGroupExtinfo = null, + sourceFlag = 1, + gameNick = byteArrayOf(), + gameMsg = byteArrayOf(), + groupFlagext3 = 0, + groupOwnerUin = 0, + doubtFlag = 0, + warningTips = byteArrayOf(), + nameMore = byteArrayOf(), + reqUinFaceid = 21762, + reqUinNick = "颠佬", + groupName = "", + actionUinNick = "", + msgQna = "", + msgDetail = "", + groupExtFlag = 0, + actorUinNick = "", + picUrl = "", + cloneUinNick = "", + reqUinBusinessCard = "", + eimGroupIdName = "", + reqUinPreRemark = "", + actionUinQqNick = "", + actionUinRemark = "", + reqUinGender = 255, + reqUinAge = 0, + c2cInviteJoinGroupFlag = 0, + cardSwitch = 0, + actions = listOf( + Structmsg.SystemMsgAction( + name = "同意", + result = "已同意", + action = 1, + actionInfo = Structmsg.SystemMsgActionInfo( + type = 2, + groupCode = 0, + sig = byteArrayOf(), + msg = "", + groupId = 0, + remark = "", + blacklist = false, + addFrdSNInfo = null + ), + detailName = "同意" + ), + Structmsg.SystemMsgAction( + name = "拒绝", + result = "已拒绝", + action = 1, + actionInfo = Structmsg.SystemMsgActionInfo( + type = 3, + groupCode = 0, + sig = byteArrayOf(), + msg = "", + groupId = 0, + remark = "", + blacklist = false, + addFrdSNInfo = null + ), + detailName = "拒绝" + ), + ) + ) + ) + + }.run { + assertEquals(1, size, toString()) + val event = single() + assertIs(event) + + assertEquals(123456, event.fromId) + assertEquals("颠佬", event.fromNick) + } + }@Test + fun `new friend request test`() = runBlockingUnit { + + setBot(114514) + + use(attributes = buildTypeSafeMap { set(NewContact.SYSTEM_MSG_TYPE, 0) }) { + mockTime += 1000 + Structmsg.StructMsg( + version = 1, + msgType = 1, + msgSeq = mockTime * 1000, + msgTime = mockTime, + reqUin = 654321, + unreadFlag = 0, + msg = Structmsg.SystemMsg( + subType = 1, + msgTitle = "好友申请", + msgDescribe = "请求加为好友1", + msgAdditional = "", + msgSource = "QQ群-%group_name%", + msgDecided = "", + srcId = 6, + subSrcId = 2, + groupCode = 111111, + actionUin = 0, + groupMsgType = 0, + groupInviterRole = 0, + friendInfo = Structmsg.FriendInfo( + msgJointFriend = "2 个共同好友", + msgBlacklist = "设为黑名单后你将拒绝对方,并不再接收此人请求。" + ), + groupInfo = Structmsg.GroupInfo( + groupAuthType = 0, + displayAction = 0, + msgAlert = "", + msgDetailAlert = "", + msgOtherAdminDone = "", + appPrivilegeFlag = 67633344 + ), + actorUin = 0, + msgActorDescribe = "", + msgAdditionalList = "", + relation = 0, + reqsubtype = 0, + cloneUin = 0, + discussUin = 0, + eimGroupId = 0, + msgInviteExtinfo = null, + msgPayGroupExtinfo = null, + sourceFlag = 1, + gameNick = byteArrayOf(), + gameMsg = byteArrayOf(), + groupFlagext3 = 0, + groupOwnerUin = 0, + doubtFlag = 0, + warningTips = byteArrayOf(), + nameMore = byteArrayOf(), + reqUinFaceid = 0, + reqUinNick = "颠佬2", + groupName = "%group_name%", + actionUinNick = "", + msgQna = "", + msgDetail = "", + groupExtFlag = 1076036672, + actorUinNick = "", + picUrl = "", + cloneUinNick = "", + reqUinBusinessCard = "", + eimGroupIdName = "", + reqUinPreRemark = "", + actionUinQqNick = "", + actionUinRemark = "", + reqUinGender = 0, + reqUinAge = 0, + c2cInviteJoinGroupFlag = 0, + cardSwitch = 0, + actions = listOf( + Structmsg.SystemMsgAction( + name = "同意", + result = "已同意", + action = 1, + actionInfo = Structmsg.SystemMsgActionInfo( + type = 2, + groupCode = 0, + sig = byteArrayOf(), + msg = "", + groupId = 0, + remark = "", + blacklist = false, + addFrdSNInfo = null + ), + detailName = "同意" + ), + Structmsg.SystemMsgAction( + name = "拒绝", + result = "已拒绝", + action = 1, + actionInfo = Structmsg.SystemMsgActionInfo( + type = 3, + groupCode = 0, + sig = byteArrayOf(), + msg = "", + groupId = 0, + remark = "", + blacklist = false, + addFrdSNInfo = null + ), + detailName = "拒绝" + ), + ) + ) + ) + + }.run { + assertEquals(1, size, toString()) + val event = single() + assertIs(event) + + assertEquals(654321, event.fromId) + assertEquals("颠佬2", event.fromNick) + assertEquals(111111, event.fromGroupId) + } + } +} \ No newline at end of file